forked from DNANUDGELTD/SPIFlash
-
Notifications
You must be signed in to change notification settings - Fork 0
/
SPIFlash.cpp
357 lines (325 loc) · 11.1 KB
/
SPIFlash.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
// Copyright (c) 2013-2015 by Felix Rusu, LowPowerLab.com
// SPI Flash memory library for arduino/moteino.
// This works with 256byte/page SPI flash memory
// For instance a 4MBit (512Kbyte) flash chip will have 2048 pages: 256*2048 = 524288 bytes (512Kbytes)
// Minimal modifications should allow chips that have different page size but modifications
// DEPENDS ON: Arduino SPI library
// > Updated Jan. 5, 2015, TomWS1, modified writeBytes to allow blocks > 256 bytes and handle page misalignment.
// > Updated Feb. 26, 2015 TomWS1, added support for SPI Transactions (Arduino 1.5.8 and above)
// > Selective merge by Felix after testing in IDE 1.0.6, 1.6.4
// **********************************************************************************
// License
// **********************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
#include <SPIFlash.h>
uint8_t SPIFlash::UNIQUEID[8];
/// IMPORTANT: NAND FLASH memory requires erase before write, because
/// it can only transition from 1s to 0s and only the erase command can reset all 0s to 1s
/// See http://en.wikipedia.org/wiki/Flash_memory
/// The smallest range that can be erased is a sector (4K, 32K, 64K); there is also a chip erase command
/// Constructor. JedecID is optional but recommended, since this will ensure that the device is present and has a valid response
/// get this from the datasheet of your flash chip
/// Example for Atmel-Adesto 4Mbit AT25DF041A: 0x1F44 (page 27: http://www.adestotech.com/sites/default/files/datasheets/doc3668.pdf)
/// Example for Winbond 4Mbit W25X40CL: 0xEF30 (page 14: http://www.winbond.com/NR/rdonlyres/6E25084C-0BFE-4B25-903D-AE10221A0929/0/W25X40CL.pdf)
SPIFlash::SPIFlash(uint8_t slaveSelectPin, uint16_t jedecID) {
_slaveSelectPin = slaveSelectPin;
_jedecID = jedecID;
}
/// Select the flash chip
boolean SPIFlash::select() {
//save current SPI settings
#ifndef SPI_HAS_TRANSACTION
noInterrupts();
#endif
// _SPCR = SPCR;
// _SPSR = SPSR;
#ifdef SPI_HAS_TRANSACTION
SPI.beginTransaction(_settings);
#else
// set FLASH SPI settings
SPI.setDataMode(SPI_MODE0);
SPI.setBitOrder(MSBFIRST);
SPI.setClockDivider(SPI_CLOCK_DIV4); //decided to slow down from DIV2 after SPI stalling in some instances, especially visible on mega1284p when RFM69 and FLASH chip both present
SPI.begin();
#endif
digitalWrite(_slaveSelectPin, LOW);
return true;
}
/// UNselect the flash chip
boolean SPIFlash::unselect() {
digitalWrite(_slaveSelectPin, HIGH);
//restore SPI settings to what they were before talking to the FLASH chip
#ifdef SPI_HAS_TRANSACTION
SPI.endTransaction();
#else
interrupts();
#endif
// SPCR = _SPCR;
// SPSR = _SPSR;
return true;
}
/// setup SPI, read device ID etc...
boolean SPIFlash::initialize()
{
// _SPCR = SPCR;
// _SPSR = SPSR;
pinMode(_slaveSelectPin, OUTPUT);
#ifdef SPI_HAS_TRANSACTION
_settings = SPISettings(4000000, MSBFIRST, SPI_MODE0);
#endif
unselect();
wakeup();
if (_jedecID == 0 || readDeviceId() == _jedecID) {
// if (command(SPIFLASH_STATUSWRITE, true)) // Write Status Register
// {
// SPI.transfer(0); // Global Unprotect
// unselect();
return true;
// }
}
return false;
}
/// Get the manufacturer and device ID bytes (as a short word)
uint16_t SPIFlash::readDeviceId()
{
#if defined(__AVR_ATmega32U4__) // Arduino Leonardo, MoteinoLeo
if (!command(SPIFLASH_IDREAD)) return 0xFFFF; // Read JEDEC ID
#else
select();
SPI.transfer(SPIFLASH_IDREAD);
#endif
uint16_t jedecid = SPI.transfer(0) << 8;
jedecid |= SPI.transfer(0);
unselect();
return jedecid;
}
/// Get the 64 bit unique identifier, stores it in UNIQUEID[8]. Only needs to be called once, ie after initialize
/// Returns the byte pointer to the UNIQUEID byte array
/// Read UNIQUEID like this:
/// flash.readUniqueId(); for (uint8_t i=0;i<8;i++) { Serial.print(flash.UNIQUEID[i], HEX); Serial.print(' '); }
/// or like this:
/// flash.readUniqueId(); uint8_t* MAC = flash.readUniqueId(); for (uint8_t i=0;i<8;i++) { Serial.print(MAC[i], HEX); Serial.print(' '); }
uint8_t* SPIFlash::readUniqueId()
{
if (command(SPIFLASH_MACREAD))
{
SPI.transfer(0);
SPI.transfer(0);
SPI.transfer(0);
SPI.transfer(0);
for (uint8_t i=0;i<8;i++)
UNIQUEID[i] = SPI.transfer(0);
unselect();
}
else
{
for (uint8_t i=0;i<8;i++)
UNIQUEID[i] = 0xFF;
}
return UNIQUEID;
}
/// read 1 byte from flash memory
boolean SPIFlash::readByte(uint32_t addr, uint8_t* result) {
if (command(SPIFLASH_ARRAYREADLOWFREQ))
{
SPI.transfer(addr >> 24);
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
*result = SPI.transfer(0);
return unselect();
}
return false;
}
/// read unlimited # of bytes
boolean SPIFlash::readBytes(uint32_t addr, void* buf, uint16_t len) {
if (command(SPIFLASH_ARRAYREAD))
{
SPI.transfer(addr >> 24);
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
SPI.transfer(0); //"dont care"
for (uint16_t i = 0; i < len; ++i)
((uint8_t*) buf)[i] = SPI.transfer(0);
return unselect();
}
return false;
}
/// Send a command to the flash chip, pass TRUE for isWrite when its a write command
boolean SPIFlash::command(uint8_t cmd, boolean isWrite){
#if defined(__AVR_ATmega32U4__) // Arduino Leonardo, MoteinoLeo
DDRB |= B00000001; // Make sure the SS pin (PB0 - used by RFM12B on MoteinoLeo R1) is set as output HIGH!
PORTB |= B00000001;
#endif
if (isWrite)
{
if(command(SPIFLASH_WRITEENABLE)) // Write Enable
unselect();
else
return false;
// select();
// SPI.transfer(SPIFLASH_WRITEENABLE);
// unselect();
}
//wait for any write/erase to complete
// a time limit cannot really be added here without it being a very large safe limit
// that is because some chips can take several seconds to carry out a chip erase or other similar multi block or entire-chip operations
// a recommended alternative to such situations where chip can be or not be present is to add a 10k or similar weak pulldown on the
// open drain MISO input which can read noise/static and hence return a non 0 status byte, causing the while() to hang when a flash chip is not present
if (cmd != SPIFLASH_WAKE)
if (busy()) return false;
select();
SPI.transfer(cmd);
return true;
}
/// check if the chip is busy erasing/writing
boolean SPIFlash::busy()
{
/*
select();
SPI.transfer(SPIFLASH_STATUSREAD);
uint8_t status = SPI.transfer(0);
unselect();
return status & 1;
*/
return readStatus() & 1;
}
/// return the STATUS register
uint8_t SPIFlash::readStatus()
{
select();
SPI.transfer(SPIFLASH_STATUSREAD);
uint8_t status = SPI.transfer(0);
unselect();
return status;
}
/// Write 1 byte to flash memory
/// WARNING: you can only write to previously erased memory locations (see datasheet)
/// use the block erase commands to first clear memory (write 0xFFs)
boolean SPIFlash::writeByte(uint32_t addr, uint8_t byt) {
if (command(SPIFLASH_BYTEPAGEPROGRAM, true)) // Byte/Page Program
{
SPI.transfer(addr >> 24);
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
SPI.transfer(byt);
return unselect();
}
return false;
}
/// write multiple bytes to flash memory (up to 64K)
/// WARNING: you can only write to previously erased memory locations (see datasheet)
/// use the block erase commands to first clear memory (write 0xFFs)
/// This version handles both page alignment and data blocks larger than 256 bytes.
///
uint16_t SPIFlash::writeBytes(uint32_t addr, const void* buf, uint16_t len) {
uint16_t n;
uint16_t maxBytes = 256-(addr%256); // force the first set of bytes to stay within the first page
uint16_t offset = 0;
while (len>0)
{
#ifdef INC_FREERTOS_H
while (busy()) {delay(1);}
#endif
n = (len<=maxBytes) ? len : maxBytes;
if (command(SPIFLASH_BYTEPAGEPROGRAM, true)) // Byte/Page Program
{
SPI.transfer(addr >> 24);
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
for (uint16_t i = 0; i < n; i++)
SPI.transfer(((uint8_t*) buf)[offset + i]);
}
else
{
return len; // Return unwritten number of bytes;
}
unselect();
addr+=n; // adjust the addresses and remaining bytes by what we've just transferred.
offset +=n;
len -= n;
maxBytes = 256; // now we can do up to 256 bytes per loop
}
return 0;
}
/// erase entire flash memory array
/// may take several seconds depending on size, but is non blocking
/// so you may wait for this to complete using busy() or continue doing
/// other things and later check if the chip is done with busy()
/// note that any command will first wait for chip to become available using busy()
/// so no need to do that twice
boolean SPIFlash::chipErase() {
if (command(SPIFLASH_CHIPERASE, true)) return unselect();
return false;
}
/// erase a 4Kbyte block
boolean SPIFlash::blockErase4K(uint32_t addr) {
if (command(SPIFLASH_BLOCKERASE_4K, true)) // Block Erase
{
SPI.transfer(addr >> 24);
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
return unselect();
}
return false;
}
/// erase a 32Kbyte block
boolean SPIFlash::blockErase32K(uint32_t addr) {
if (command(SPIFLASH_BLOCKERASE_32K, true)) // Block Erase
{
SPI.transfer(addr >> 24);
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
return unselect();
}
return false;
}
/// erase a 64Kbyte block
boolean SPIFlash::blockErase64K(uint32_t addr) {
if (command(SPIFLASH_BLOCKERASE_64K, true)) // Block Erase
{
SPI.transfer(addr >> 24);
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
return unselect();
}
return false;
}
boolean SPIFlash::sleep() {
if (command(SPIFLASH_SLEEP)) return unselect();
return false;
}
boolean SPIFlash::wakeup() {
if (command(SPIFLASH_WAKE)) return unselect();
return false;
}
/// cleanup
void SPIFlash::end() {
SPI.end();
}