-
Notifications
You must be signed in to change notification settings - Fork 90
/
SPIFlash.cpp
351 lines (319 loc) · 12.2 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
// 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
/// IMPORTANT: When flash chip is powered down, aka sleeping, the only command it will respond to is
/// Release Power-down / Device ID (ABh), per section 8.2.19 of the W25X40CL datasheet.
/// This means after using the sleep() function of this library, wake() must be the first
/// function called. If other commands are used, the flash chip will ignore the commands.
/// 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
void SPIFlash::select() {
//save current SPI settings
#ifndef SPI_HAS_TRANSACTION
noInterrupts();
#endif
#if defined (SPCR) && defined (SPSR)
_SPCR = SPCR;
_SPSR = SPSR;
#endif
#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
#endif
digitalWrite(_slaveSelectPin, LOW);
}
/// UNselect the flash chip
void 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
#if defined (SPCR) && defined (SPSR)
SPCR = _SPCR;
SPSR = _SPSR;
#endif
}
/// setup SPI, read device ID etc...
boolean SPIFlash::initialize()
{
#if defined (SPCR) && defined (SPSR)
_SPCR = SPCR;
_SPSR = SPSR;
#endif
pinMode(_slaveSelectPin, OUTPUT);
SPI.begin();
#ifdef SPI_HAS_TRANSACTION
_settings = SPISettings(4000000, MSBFIRST, SPI_MODE0);
#endif
unselect();
wakeup();
if (_jedecID == 0 || readDeviceId() == _jedecID) {
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
command(SPIFLASH_IDREAD); // 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()
{
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();
return UNIQUEID;
}
/// read 1 byte from flash memory
uint8_t SPIFlash::readByte(uint32_t addr) {
command(SPIFLASH_ARRAYREADLOWFREQ);
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
uint8_t result = SPI.transfer(0);
unselect();
return result;
}
/// read unlimited # of bytes
void SPIFlash::readBytes(uint32_t addr, void* buf, uint16_t len) {
command(SPIFLASH_ARRAYREAD);
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);
unselect();
}
/// Send a command to the flash chip, pass TRUE for isWrite when its a write command
void 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)
{
command(SPIFLASH_WRITEENABLE); // Write Enable
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
//
// Note: If the MISO line is high, busy() will return true.
// This can be a problem and cause the code to hang when there is noise/static on MISO data line when:
// 1) There is no flash chip connected
// 2) The flash chip connected is powered down, aka sleeping.
if (cmd != SPIFLASH_WAKE) while(busy());
select();
SPI.transfer(cmd);
}
/// 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)
void SPIFlash::writeByte(uint32_t addr, uint8_t byt) {
command(SPIFLASH_BYTEPAGEPROGRAM, true); // Byte/Page Program
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
SPI.transfer(byt);
unselect();
}
/// 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.
///
void 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)
{
n = (len<=maxBytes) ? len : maxBytes;
command(SPIFLASH_BYTEPAGEPROGRAM, true); // Byte/Page Program
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]);
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
}
}
/// 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
void SPIFlash::chipErase() {
command(SPIFLASH_CHIPERASE, true);
unselect();
}
/// erase a 4Kbyte block
void SPIFlash::blockErase4K(uint32_t addr) {
command(SPIFLASH_BLOCKERASE_4K, true); // Block Erase
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
unselect();
}
/// erase a 32Kbyte block
void SPIFlash::blockErase32K(uint32_t addr) {
command(SPIFLASH_BLOCKERASE_32K, true); // Block Erase
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
unselect();
}
/// erase a 64Kbyte block
void SPIFlash::blockErase64K(uint32_t addr) {
command(SPIFLASH_BLOCKERASE_64K, true); // Block Erase
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
unselect();
}
/// found() - checks there is a FLASH chip by checking the deviceID repeatedly - should be a consistent value
uint8_t SPIFlash::found() {
uint16_t deviceID=0;
wakeup(); //if sleep() was previously called, wakeup() is required or it's non responsive
for (uint8_t i=0;i<10;i++) {
uint16_t idNow = readDeviceId();
if (idNow==0 || idNow==0xffff || (i>0 && idNow != deviceID)) {
deviceID=0;
break;
}
deviceID=idNow;
}
if (deviceID==0) { //NO FLASH CHIP FOUND, ABORTING
return false;
}
return true;
}
///regionIsEmpty() - check a random flashmem byte array is all clear and can be written to (ie. it's all 0xff)
uint8_t SPIFlash::regionIsEmpty(uint32_t startAddress, uint8_t length) {
uint8_t flashBuf[length];
readBytes(startAddress, flashBuf, length);
for (uint8_t i=0;i<length;i++) if (flashBuf[i]!=0xff) return false;
return true;
}
/// Put flash memory chip into power down mode
/// WARNING: after this command, only the WAKEUP and DEVICE_ID commands are recognized
/// hence a wakeup() command should be invoked first before further operations
/// If a MCU soft restart is possible with flash chip left in sleep(), then a wakeup() command
/// should always be invoked before any other commands to ensure the flash chip was not left in sleep
void SPIFlash::sleep() {
command(SPIFLASH_SLEEP);
unselect();
}
/// Wake flash memory from power down mode
/// NOTE: this command is required after a sleep() command is used, or no other commands will be recognized
/// If a MCU soft restart is possible with flash chip left in sleep(), then a wakeup() command
/// should always be invoked before any other commands to ensure the flash chip was not left in sleep
void SPIFlash::wakeup() {
command(SPIFLASH_WAKE);
unselect();
}
/// cleanup
void SPIFlash::end() {
SPI.end();
}