Using an SPI Flash chip with the STM32L031 Nucleo and mbed

breadboard_spi_flash_stm32l031

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()
pulseview_spi_flash_stm32l031

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 
    
}