And here we are to talk again about storage systems, we already explained SD management (you can take a look at SD management on “How to use SD card with esp8266 and Arduino “), now we’re going to take a look to an alternative storage like external SPI Flash, similar to the EEPROM but with biggest size. SD certainly remains the best choice for size and compatibility, but we pay for these features with a good quantity of energy; the SPI Flash has a smaller capacity but is small, fast, and has very low power consumption.
Arduino UNO external SPI Flash storage
For devices like Arduino UNO, we can use use the SPI Flash with a basic and very light library, but you can manage a good quantity of memory (from 256Kb to 64Mb), and it’s enough for a lot of projects, It’s possible to use a complete filesystem, but I don’t recommend it with low-resource devices, we are going to see how to use a filesystem with devices like Arduino SAMD or esp devices.
Today we are going to see the SPI Flash memory (NOR Flash). They are a single chip that can be managed via SPI and have high-speed access and low power consumption.
Flash memory is an electronic non-volatile computer memory storage medium that can be electrically erased and reprogrammed. The two main types of flash memory, NOR flash and NAND flash, are named for the NOR and NAND logic gates. NAND flash and NOR flash use the same cell design, consisting of floating gate MOSFETs. They differ at the circuit level: in NAND flash, the relationship between the bit line and the word lines resembles a NAND gate; in NOR flash, it resembles a NOR gate; this depends on whether the state of the bit line or word lines is pulled high or low. Flash memory, a type of floating-gate memory, was invented at Toshiba in 1980 and is based on EEPROM technology. Toshiba began marketing flash memory in 1987. EPROMs had to be erased completely before they could be rewritten. NAND flash memory, however, may be erased, written, and read in blocks (or pages), which generally are much smaller than the entire device. NOR flash memory allows a single machine word to be written – to an erased location – or read independently. A flash memory device typically consists of one or more flash memory chips (each holding many flash memory cells), along with a separate flash memory controller chip.
Wikipedia
SPI Flash memories pinout
There are SMD, and Discrete IC managed by SPI protocol.
The pinout is equal for the majority of SPI Flash (Winbond, Fujitsu etc. etc.), and It’s also the same for SMD and discrete components.
SPI Flash Discrete PDIP pinout
SPI Flash SMD SOIC DIP8 pinout
Here a set of SPI flash with different size w25q16 SMD 2Mb - w25q16 Discrete 2Mb -
w25q32 SMD 4Mb - w25q32 Discrete 4Mb -
w25q64 SMD 8Mb - w25q64 Discrete 8Mb -
w25q128 SMD 16Mb - w25q128 Discrete 16Mb
W25Q32 W25Q64 w25q128 module 4Mb 8Mb 16Mb
Arduino wiring diagram
The first problem with the connection is that Arduino UNO has 5v logic, but the SPI Flash has 3.3v logic, so the fastest way to connect (but not the best) is to use a voltage divider, refer to this article “Voltage divider: calculator and application “, another solution is to use a logic level converter, a simple device that converts logic from a voltage to another and vice versa.
Here the logic level converter Aliexpress
The connection schema becomes like so:
Arduino SPI Flash 10 /CS Pulled UP if not standard CS; Voltage divider. 11 DI (IO1 ) Voltage divider 12 DI (IO0 ) 13 CLK Voltage divider 3.3v /WP 3.3v /Hold GND GND 3.3v VCC
Arduino UNO connection DIP8 SPI Flash breadboard w25q80
I add a 0.1μF capacitor and It’s work good, but the standard value in this situation is 0.01μF
Arduino UNO connection DIP8 SPIFlash schema 2 w25q32
It works well, but if you want to use Dual SPI mode, you need to use MISO also to read data, and this connection isn’t bidirectional, so you need a bidirectional logic level converter.
Library
The library selected for these tests is the SPIMemory. It works well and with a low resources.
You can download It also directly in the Arduino IDE.
SPIMemory library from Arduino IDE library manager
Troubleshooting
It is unlikely that your memory will not be recognized, but if this happens, it is probably an SPI channel configuration problem. The set of memory types that are supported It’s very wide:
1
2
3
4
5
6
7
8
9
10
const
uint8_t _capID[
18
]
=
{
0x10
,
0x11
,
0x12
,
0x13
,
0x14
,
0x15
,
0x16
,
0x17
,
0x18
,
0x19
,
0x41
,
0x42
,
0x43
,
0x4B
,
0x00
,
0x01
,
0x13
,
0x37
};
const
uint32_t _memSize[
18
]
=
{KB(
64
), KB(
128
), KB(
256
), KB(
512
), MB(
1
), MB(
2
), MB(
4
), MB(
8
), MB(
16
), MB(
32
), MB(
2
), MB(
4
), MB(
8
), MB(
8
), KB(
256
), KB(
512
), MB(
4
), KB(
512
)};
const
uint8_t _supportedManID[
9
]
=
{WINBOND_MANID, MICROCHIP_MANID, CYPRESS_MANID, ADESTO_MANID, MICRON_MANID, ON_MANID, GIGA_MANID, AMIC_MANID, MACRONIX_MANID};
const
uint8_t _altChipEraseReq[
3
]
=
{A25L512, M25P40, SST26};
And if you have some problem with your flash, probably It’s an SPI configuration problem.
For example, though you use a standard SPI channel and you are going to use the standard constructor of SPIMemory too like so:
it’s better if you specify the configuration like so, where SS is the CS selector and SPI It’s the SPI channel:
1
SPIFlash flash(SS,
&
SPI);
if you have already problems, try to configure the speed of SPI channel because all the libraries try to find the best performance for SPI using the CPU frequencies, but in some cases it may not work, so reduce the SPI clock with this command.
1
flash.setClock(
12000000
);
Commands
The commands are very simple and can be used to manage a lot of situations. Naturally, the ram usage is very low, and if you pay attention can be simple to manage It on devices like Arduino UNO with very low memory.
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
bool sfdpPresent(
void
);
uint8_t error(bool verbosity
=
false
);
uint16_t getManID(
void
);
uint32_t getJEDECID(
void
);
uint64_t getUniqueID(
void
);
uint32_t getAddress(uint16_t size);
uint16_t sizeofStr(String
&
inputStr);
uint32_t getCapacity(
void
);
uint32_t getMaxPage(
void
);
float
functionRunTime(
void
);
bool writeByte(uint32_t _addr, uint8_t data, bool errorCheck
=
true
);
uint8_t readByte(uint32_t _addr, bool fastRead
=
false
);
bool writeByteArray(uint32_t _addr, uint8_t
*
data_buffer, size_t bufferSize, bool errorCheck
=
true
);
bool readByteArray(uint32_t _addr, uint8_t
*
data_buffer, size_t bufferSize, bool fastRead
=
false
);
bool writeChar(uint32_t _addr, int8_t data, bool errorCheck
=
true
);
int8_t readChar(uint32_t _addr, bool fastRead
=
false
);
bool writeCharArray(uint32_t _addr,
char
*
data_buffer, size_t bufferSize, bool errorCheck
=
true
);
bool readCharArray(uint32_t _addr,
char
*
data_buffer, size_t buffer_size, bool fastRead
=
false
);
bool writeShort(uint32_t _addr, int16_t data, bool errorCheck
=
true
);
int16_t readShort(uint32_t _addr, bool fastRead
=
false
);
bool writeWord(uint32_t _addr, uint16_t data, bool errorCheck
=
true
);
uint16_t readWord(uint32_t _addr, bool fastRead
=
false
);
bool writeLong(uint32_t _addr, int32_t data, bool errorCheck
=
true
);
int32_t readLong(uint32_t _addr, bool fastRead
=
false
);
bool writeULong(uint32_t _addr, uint32_t data, bool errorCheck
=
true
);
uint32_t readULong(uint32_t _addr, bool fastRead
=
false
);
bool writeFloat(uint32_t _addr,
float
data, bool errorCheck
=
true
);
float
readFloat(uint32_t _addr, bool fastRead
=
false
);
bool writeStr(uint32_t _addr, String
&
data, bool errorCheck
=
true
);
bool readStr(uint32_t _addr, String
&
data, bool fastRead
=
false
);
template <class T> bool writeAnything(uint32_t _addr,
const
T
&
data, bool errorCheck
=
true
);
template <class T> bool readAnything(uint32_t _addr, T
&
data, bool fastRead
=
false
);
bool eraseSection(uint32_t _addr, uint32_t _sz);
bool eraseSector(uint32_t _addr);
bool eraseBlock32K(uint32_t _addr);
bool eraseBlock64K(uint32_t _addr);
bool eraseChip(
void
);
bool suspendProg(
void
);
bool resumeProg(
void
);
bool powerDown(
void
);
bool powerUp(
void
);
You must pay attention to these commands:
uint32_t getCapacity(void)
: get the capacity of the Chip, this library offers support for a lot of IC, and probably when you try It, find for you all specs, If not, you can pass the size of the device to the begin(capacity)
command.
uint32_t getAddress(uint16_t size)
: with this command, you can get a free address that can store a uint16_t size
of data.
write commands:
there is a lot of command for various type of data, It’s quite intuitive, and in all, you must specify a start address that will be used as an initial point to write the data.
read command
: as write commands, there are read commands that can be used to re-read the contiguous data.
template bool writeAnything(uint32_t _addr, const T& data, bool errorCheck = true)
: more interesting is the writeAnithing
command that is used to store a complex structure, remember, the structure must be created with static size value, no data like String or similar .
template bool readAnything(uint32_t _addr, T& data, bool fastRead = false)
: when you write a structure, you must re-read It, and this command does It, for all read commands, you can set fastRead
to true
, but not all SPI Flash support It, check the datasheet.
erase commands:
you can erase various sizes and portions of the chip, you can see the list of commands.
bool eraseChip(void)
: there is also a special erase command that format all the chip to the initial state.
bool suspendProg(void), bool resumeProg(void)
: you can suspend the erase commands, and you start to read immediately.
bool powerDown(void), bool powerUp(void)
: Puts device in low power state. Good when using battery power. In powerDown()
the chip will only respond to powerUp()
.
Examples
Arduino UNO external SPI Flash memory on a breadboard
The library offers a huge set of examples here, I’m going to show you basic usage.
Here is a simple example store the first string in the initial address 0, and I re-read It than I ask the method getAddress
for the first available contiguous position where I can store another string, and I save It and re-read.
For Arduino MKR, you must set the constructor like so:
SPIFlash flash(SS,
&
SPI);
Get SPI Flash information
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
#include
<SPIMemory.h>
SPIFlash flash;
void
setup
() {
Serial.begin
(
115200
);
while
(
!
Serial) ;
delay
(
100
);
flash.begin();
Serial.print
(F(
"Flash size: "
));
Serial.print
((
long
)(flash.getCapacity()
/
1000
));
Serial.println
(F(
"Kb"
));
unsigned
long
strAddr
=
0
;
unsigned
long
strAddrSecondString
=
0
;
Serial.println
();
String inputString
=
"I'm going to write this string on IC"
;
flash.writeStr(strAddr, inputString);
Serial.print
(F(
"Written string: "
));
Serial.println
(inputString);
Serial.print
(F(
"To address: "
));
Serial.println
(strAddr);
String outputString
=
""
;
if
(flash.readStr(strAddr, outputString)) {
Serial.print
(F(
"Read string: "
));
Serial.println
(outputString);
Serial.print
(F(
"From address: "
));
Serial.println
(strAddr);
}
Serial.println
();
String secondInputString
=
"I'm going to write this second string on IC"
;
Serial.print
(F(
"Check first free sector: "
));
strAddrSecondString
=
flash.getAddress(secondInputString.length());
Serial.println
(strAddrSecondString);
Serial.println
();
flash.writeStr(strAddrSecondString, secondInputString);
Serial.print
(F(
"Written string: "
));
Serial.println
(secondInputString);
Serial.print
(F(
"To address: "
));
Serial.println
(strAddrSecondString);
outputString
=
""
;
if
(flash.readStr(strAddrSecondString, outputString)) {
Serial.print
(F(
"Read string: "
));
Serial.println
(outputString);
Serial.print
(F(
"From address: "
));
Serial.println
(strAddrSecondString);
}
while
(
!
flash.eraseSector(strAddr));
while
(
!
flash.eraseSector(strAddrSecondString));
}
void
loop
() {
}
Here the serial result:
1
2
3
4
5
6
7
8
9
10
11
12
13
Flash size: 8388Kb
Written string: I'm going to write this string on IC
To address: 0
Read string: I'm going to write this string on IC
From address: 0
Check first free sector: 43
Written string: I'm going to write this second string on IC
To address: 43
Read string: I'm going to write this second string on IC
From address: 43
Save and read a JSON structure
Here is a more realistic example, instead of saving a text like a String, we are going to save a JSON structure like a String.
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
#include
<SPIMemory.h>
#include
<ArduinoJson.h>
SPIFlash flash;
void
setup
() {
Serial.begin
(
115200
);
while
(
!
Serial) ;
delay
(
100
);
flash.begin();
Serial.print
(F(
"Flash size: "
));
Serial.print
((
long
)(flash.getCapacity()
/
1000
));
Serial.println
(F(
"Kb"
));
unsigned
long
strAddr
=
0
;
unsigned
long
strAddrSecondString
=
0
;
Serial.println
();
Serial.println
(F(
"Generate JSON file!"
));
DynamicJsonDocument doc(
512
);
doc[
"energyLifetime"
]
=
21698620
;
doc[
"energyYearly"
]
=
1363005
;
Serial.print
(F(
"Put data in a buffer.. "
));
String buf;
if
(serializeJson(doc, buf)
=
=
0
) {
Serial.println
(F(
"failed to write buffer"
));
}
if
(flash.writeStr(strAddr, buf)){
Serial.print
(F(
"OK, writed on address "
));
Serial.println
(strAddr);
}
else
{
Serial.println
(F(
"KO"
));
}
String outputString
=
""
;
if
(flash.readStr(strAddr, outputString)) {
Serial.print
(F(
"Read json: "
));
Serial.println
(outputString);
Serial.print
(F(
"From address: "
));
Serial.println
(strAddr);
}
Serial.println
(F(
"Generate JSON file!"
));
DynamicJsonDocument doc2(
512
);
doc2[
"energyLifetime"
]
=
222
;
doc2[
"energyYearly"
]
=
333
;
Serial.println
();
Serial.print
(F(
"Check first free sector: "
));
strAddrSecondString
=
flash.getAddress(doc2.size());
Serial.println
(strAddrSecondString);
Serial.println
();
Serial.print
(F(
"Stream data in flash memory!"
));
Serial.print
(F(
"Put data in a buffer.."
));
String buf2;
if
(serializeJson(doc2, buf2)
=
=
0
) {
Serial.println
(F(
"failed to write buffer"
));
}
if
(flash.writeStr(strAddrSecondString, buf2)){
Serial.print
(F(
"OK, writed on address "
));
Serial.println
(strAddrSecondString);
}
else
{
Serial.println
(F(
"KO"
));
}
String outputString2
=
""
;
if
(flash.readStr(strAddrSecondString, outputString2)) {
Serial.print
(F(
"Read data: "
));
Serial.println
(outputString2);
Serial.print
(F(
"From address: "
));
Serial.println
(strAddrSecondString);
}
while
(
!
flash.eraseSector(strAddr));
while
(
!
flash.eraseSector(strAddrSecondString));
}
void
loop
() {
}
Here is the console result.
1
2
3
4
5
6
7
8
9
10
11
12
13
Flash size: 8388Kb
Generate JSON file!
Put data in a buffer.. OK, writed on address 0
Read json: {"energyLifetime":21698620,"energyYearly":1363005}
From address: 0
Generate JSON file!
Check first free sector: 56
Stream data in flash memory!Put data in a buffer..OK, writed on address 56
Read data: {"energyLifetime":222,"energyYearly":333}
From address: 56
Save and read structure
Naturally, you can use a JSON structure to save complex data, but if you want to get the best performance and better space usage you must use a structure like so.
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
#include
<SPIMemory.h>
struct ConfigurationIn {
float
lux
=
3.24
;
float
vOut
=
4.45
;
float
RLDR
=
1.234
;
bool light
=
true
;
uint8_t adc
=
45
;
uint8_t arr[
8
]
=
{
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
};
struct MISC {
byte
tempHigh
=
30
;
byte
tempLow
=
20
;
bool parkingMode
=
false
;
bool allowDataToBeSent
=
false
;
} misc;
struct NETWORK {
char
ssid[
5
]
=
"ssid"
;
char
pwd[
4
]
=
"pwd"
;
char
userid[
7
]
=
"userid"
;
} network;
struct CHARGING_INFO {
byte
interval
=
5
;
byte
highChargingDefault
=
80
;
} charging;
};
ConfigurationIn configurationIn;
struct ConfigurationOut {
float
lux;
float
vOut;
float
RLDR;
bool light;
uint8_t adc;
uint8_t arr[
8
];
struct MISC {
byte
tempHigh;
byte
tempLow;
bool parkingMode;
bool allowDataToBeSent;
} misc;
struct NETWORK {
char
ssid[
5
];
char
pwd[
4
];
char
userid[
7
];
} network;
struct CHARGING_INFO {
byte
interval;
byte
highChargingDefault;
} charging;
};
ConfigurationOut configurationOut;
SPIFlash flash;
void
setup
() {
Serial.begin
(
115200
);
while
(
!
Serial) ;
delay
(
100
);
flash.begin();
Serial.print
(F(
"Flash size: "
));
Serial.print
((
long
)(flash.getCapacity()
/
1000
));
Serial.println
(F(
"Kb"
));
unsigned
long
strAddr
=
0
;
Serial.println
();
Serial.print
(F(
"Start writing structure "
));
if
(flash.writeAnything(strAddr, configurationIn,
true
)){
Serial.println
(
"OK"
);
}
else
{
Serial.println
(
"KO"
);
}
Serial.println
();
if
(flash.readAnything(strAddr, configurationOut)) {
Serial.print
(F(
"Read lux on configuration loaded from Flash: "
));
Serial.println
(configurationOut.lux);
}
else
{
Serial.println
(F(
"Read not work!"
));
}
Serial.println
();
while
(
!
flash.eraseSector(strAddr));
}
void
loop
() {
}
Here is the serial result (with diagnostic active).
1
2
3
4
5
6
7
8
9
Chip Diagnostics initiated.
No Chip size defined by user. Checking library support.
Chip identified. This chip is fully supported by the library.
Flash size: 8388Kb
Start writing structure OK
Read lux on configuration loaded from Flash: 3.24
The fat filesystem on SPI Flash
It’s possible to use a Fat filesystem also, but I discourage this approach because the resource of Arduino UNO and Mega are too low for use those resources, but If you want try, you can read the article about SPI Flash for Arduino SAMD or esp8266 and esp32.
Thanks
Arduino: fast external SPI Flash memory
Arduino MKR SAMD: FAT filesystem on external SPI flash memory
esp32 and esp8266: FAT filesystem on external SPI flash memory
STM32: SPI flash memory FAT FS