
The W25Q32 is a 4 MByte SPI Flash ROM device which costs about 30 cents. It comes in an 8 pin surface mount package and so must be mounted on a breakout board if you want to use it with a breadboard. I designed a breakout board with KiCad and you can see it in the above image.
A test program was written along with a C++ class to encapsulate the device’s functions. The test program begins by powering up the device, it then does a bulk erase followed by a write of the string “Hello World”. The main loop then continuously reads this string back from the chip. Be careful how often you erase the device – you only get so many write/erase cycles.
This sort of device could be useful for event logging in an embedded system or it could be used to store an large program that is pulled in to RAM by a bootloader.
file: main.cpp
// Developed on the NUCLEO-L031K6 board from ST Micro and
// the W25Q32BV SPI Flash rom
#include "mbed.h"
#include "spiflash.h"
DigitalOut myled(LED1);
SPI spi(PB_5, PB_4, PB_3);
Serial pc(USBTX, USBRX);
DigitalOut nSS(PA_11);
spiflash Myflash(spi,nSS);
int main() {
spi.format(8,3);
pc.printf("Powering up\r\n");
Myflash.powerUp();
wait(0.1);
pc.printf("Erasing\r\n");
Myflash.eraseAll();
pc.printf("Writing\r\n");
Myflash.writeDataBytes(0,(uint8_t *)"Hello World",11);
while(1) {
uint8_t Contents[16];
// Write guard bytes to the end of the buffer to test operation
// of read function.
Contents[13]=13;
Contents[14]=14;
Contents[15]=15;
Myflash.readDataBytes(0,Contents,14);
pc.printf("\r\n++++++++++++++++++++++++++++++++++\r\n");
for (int i=0;i < 16; i++)
{
pc.printf("%02x ", Contents[i]);
}
myled = 1; // LED is ON
wait(0.2); // 200 ms
myled = 0; // LED is OFF
wait(1.0); // 1 sec
pc.printf("\r\nID = %X\r\n",Myflash.readDeviceIdentifier());
}
}
The listings for the spiflash class are listed further down this post. I used PulseView and a cheap 8 channel USB logic analyzer to capture the following waveforms for the function readDeviceIdentifier()
file: spiflash.h
#ifndef __spiflash_h
#define __spiflash_h
#include "mbed.h"
class spiflash {
public:
spiflash(SPI &Spi, DigitalOut & NSS);
void powerUp();
void eraseAll();
void eraseSector(uint32_t Address);
void enableWrite();
void disableWrite();
void readDataBytes(uint32_t Address, uint8_t *ByteArray, uint32_t Length);
void writeDataBytes(uint32_t Address, uint8_t *ByteArray, uint32_t Length);
uint32_t readDeviceIdentifier();
private:
static const uint32_t FlashMemorySize=4194304; // Capacity of W25Q32BV
static const uint32_t SectorSize=4096; // Erase sector size
static const uint32_t PageSize=256; // Number of bytes that can be programmed at one time
SPI & spi;
DigitalOut &nSS;
uint32_t readStatusRegister1();
};
#endif
file: spiflash.cpp
#include "spiflash.h"
spiflash::spiflash(SPI &Spi, DigitalOut & NSS) : spi(Spi), nSS(NSS)
{
}
void spiflash::powerUp()
{
char TxBuffer[4]; // Transmit buffer
char RxBuffer[4]; // Receive buffer
TxBuffer[0]=0xAB; // Command 0xAB = "Release from power down"
nSS = 0; // Drive nCS low to connect slave to the bus
spi.write(TxBuffer,1,RxBuffer,1); // Write 1 byte and read 1 byte
nSS = 1; // Drive nCS high to disconnect slave from the bus
}
void spiflash::eraseAll()
{
enableWrite();
// Use sparingly!!! Limited number of cycles available
char TxBuffer[4]; // Transmit buffer
char RxBuffer[4]; // Receive buffer
TxBuffer[0]=0xc7; // Command 0xc7 = "Bulk Erase"
nSS = 0; // Drive nCS low to connect slave to the bus
spi.write(TxBuffer,1,RxBuffer,1); // Write 1 byte and read 1 byte
nSS = 1; // Drive nCS high to disconnect slave from the bus
while (readStatusRegister1() & 1)
{ // Wait for device erase to complete
}
}
void spiflash::eraseSector(uint32_t Address)
{
enableWrite();
// Mask off the lower bits of the Sector Address (redundant?)
//Address = Address & 0xfffc00; //0b1111 1111 1111 1100 0000 0000
// Use sparingly!!! Limited number of cycles available
char TxBuffer[4]; // Transmit buffer
char RxBuffer[4]; // Receive buffer
TxBuffer[0]=0x20; // Command 0x20 = "Sector Erase"
TxBuffer[1] = (Address >> 16) & 0xff;
TxBuffer[2] = (Address >> 8) & 0xff;
TxBuffer[3] = (Address & 0xff);
nSS = 0; // Drive nCS low to connect slave to the bus
spi.write(TxBuffer,4,RxBuffer,1); // Write 1 byte and read 1 byte
nSS = 1; // Drive nCS high to disconnect slave from the bus
while (readStatusRegister1() & 1)
{
// Wait for sector erase to complete
}
}
void spiflash::enableWrite()
{
char TxBuffer[4]; // Transmit buffer
char RxBuffer[4]; // Receive buffer
TxBuffer[0]=0x06; // Command 0x06 = "Enable writes"
nSS = 0; // Drive nCS low to connect slave to the bus
spi.write(TxBuffer,1,RxBuffer,1); // Write 1 byte and read 1 byte
nSS = 1; // Drive nSS high to disconnect slave from the bus
}
void spiflash::disableWrite()
{
char TxBuffer[4]; // Transmit buffer
char RxBuffer[4]; // Receive buffer
TxBuffer[0]=0x06; // Command 0x06 = "Disable writes"
nSS = 0; // Drive nCS low to connect slave to the bus
spi.write(TxBuffer,1,RxBuffer,1); // Write 1 byte and read 1 byte
nSS = 1; // Drive nSS high to disconnect slave from the bus
}
void spiflash::readDataBytes(uint32_t Address, uint8_t *ByteArray, uint32_t Length)
{
// Read Length bytes starting at Address and return in ByteArray
char TxBuffer[4]; // Transmit buffer
TxBuffer[0]=0x03; // Command 0x03 = "Read bytes"
nSS = 0;
TxBuffer[1] = (Address >> 16) & 0xff;
TxBuffer[2] = (Address >> 8) & 0xff;
TxBuffer[3] = (Address) & 0xff;
spi.write(TxBuffer,4,0,0); // Send command and Address of interest
spi.write(0,0,(char *)ByteArray,Length); // Send command and Address of interest
nSS = 1;
}
void spiflash::writeDataBytes(uint32_t Address, uint8_t *ByteArray, uint32_t Length)
{
enableWrite();
char TxBuffer[4]; // Transmit buffer
TxBuffer[0]=0x02; // Command 0x02 = "Write bytes"
TxBuffer[1] = (Address >> 16) & 0xff;
TxBuffer[2] = (Address >> 8) & 0xff;
TxBuffer[3] = (Address) & 0xff;
nSS = 0;
spi.write(TxBuffer,4,0,0); // Send command and Address of interest
spi.write((char *)ByteArray, Length, 0,0); // Send command and Address of interest
nSS = 1;
while (readStatusRegister1() & 1)
{
// Wait for write data to complete
}
}
uint32_t spiflash::readDeviceIdentifier()
{
// If the chip conforms with JEDEC norms, the last byte of the ID should
// indicate the capacity. The last byte is the size of the chip express
// in powers of 2. For example if you read 0x16 (22 decimal) then the
// chip should have a capacity of 2^22 = 4MiB.
char TxBuffer[4]; // Transmit buffer
char RxBuffer[4]; // Receive buffer
TxBuffer[0]=0x9f; // Command 0x9f = "Read status Device ID"
nSS = 0; // Drive nCS low to connect slave to the bus
spi.write(TxBuffer,1,RxBuffer,4); // Write 1 byte and read 4 bytes
nSS = 1; // Drive nCS high to disconnect slave from the bus
uint32_t ID=RxBuffer[1];
ID = ID << 8;
ID += RxBuffer[2];
ID = ID << 8;
ID += RxBuffer[3];
return ID; // return the status register contents
}
uint32_t spiflash::readStatusRegister1()
{
char TxBuffer[4]; // Transmit buffer
char RxBuffer[4]; // Receive buffer
TxBuffer[0]=0x05; // Command 0x9f = "Read Status register 1"
nSS = 0; // Drive nCS low to connect slave to the bus
spi.write(TxBuffer,1,RxBuffer,2); // Write 1 byte and read 1 byte
nSS = 1; // Drive nCS high to disconnect slave from the bus
return RxBuffer[1]; // return the status register contents
}