Modern microcontroller come with plenty of internal FLASH memory. On the other side, many high performance MCUs as the NXP i.MX RT are ‘flashless’, because the silicon process for high performance cores is not matching the FLASH memory technology, so they are using external serial SPI or Quad-SPI (QSPI) memory instead.
Why not using an external SPI FLASH for a ‘normal’ microcontroller too?
The typical usage of external SPI flash memory is using it to load or store data. With the addition of a small external device on the SPI bus I can easily add several MBytes of memory to the microcontroller. Such SPI memory devices are very inexpensive: I ordered a few breakout modules with Winbond W25Q128 (16 MByte) from AliExpress for $1.50 each. The device uses the following pins:
- VCC: 3.3V
- GND
- DO: SPI MISO
- DI: SPI MOSI
- CS: SPI Chip Select
A red LED on the breakout module indicates if the board is powered.
Usually I use micro-SD cards for logging data. They are easily available, provide nearly ‘unlimited’ storage and with the FatFS file system I can easily exchange data with the host. But it needs space on the PCB, the SD card socket needs to be accessible, the socket is a mechanical component and has its costs plus is not very reliable in an environment with vibrations and subject of corrosion. Using a FLASH chip might be the better solution.
I have created a driver with a command line interface: that way I can read/write data of the SPI FLASH memory device. An Eclipse example project is available on GitHub (check the links at the end of this article).
The example is running on an ARM Cortex-M4 from NXP (Kinetis K20DX128), but easily can be ported for any other microcontroller.
The project includes a command line shell:
With the shell I can read/write the serial flash:
The ‘status’ command gives information about the device found:
Summary
I have now a working driver for using the Winbond W25Q128 16 MByte serial/SPI flash chip. The driver is still in an early stage, and I might update it to support other Flash devices too. With the command line interface I can read/write the memory. In a next step I plan to use the memory with a file system, more about this in a future article. The project is available on GitHub (see links below).
Happy Winbonding 🙂
Links
- Example project on GitHub: https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/KDS/tinyK20/tinyK20_LittleFS_W25Q128
- Winbond W25Q128 Datasheet: http://www.winbond.com/resource-files/w25q128jv%20spi%20revc%2011162016.pdf
- Serial Flash driver by Paul Stoffregen: https://github.com/PaulStoffregen/SerialFlash/blob/master/SerialFlashChip.cpp
- Winbond driver for STM32: https://github.com/RRPRIME/STM32/blob/master/libraries/winbondflash/winbondflash.cpp
- W25Q128 breakout module: https://www.aliexpress.com/item/1pc-W25Q128-NorFlash-Storage-Module-128Mbit-Flash-SPI-Interface-Control-Module/32852244173.html
No reason you couldn’t put a FAT file system on the flash chip, no different than an SD card in serial mode. I’ve also used SD cards in memory mode (emulates a simple flash memory). For a project that needed to save several counters on a regular basis I wrote the counters as sequential blocks in the flash. On boot it would scan from the top down to find the last block. When the flash was full, only then would I erase it and start at the beginning. This way even with the cheap flash that’s only good for 10,000 erase cycles, you still have a very long time before burnout.
LikeLike
Yes, using a file system like FatFS has many benefits. For me the biggest one is having the ability to exchange easily data with a host machine (swap out the card, insert it into a host PC, …).
I have used I2C EEPROMs as ‘raw’ memory block devices in a similar way you describe, with a very simple header and data structure.
This works very well for small data. But having a file system makes things easier for the application, and for that W25Q128 I’m working on getting littleFS on it. Making goog progress, so I could hopefully post an update on this the next days or so.
LikeLike
Hi Erich,
I think I sent you one of my projects that has my SPI flash driver. You’re welcome to use and adapt that one if it’s any use to you – there’s nothing sensitive there. spiflash.c is the main part, and there’s a FatFs interface in diskio.c. The way it’s set up now it also requires cache.c, which implements a write-back cache that can drastically speed up file system access.
I think I took out support for Micron multi-die parts, but I’ve got that around somewhere. Never liked working with them. I’ve gone through several iterations of this driver, starting with a thin interface for the W25P16 around 10 or 12 years ago. It’s not super fancy, but flash_write() will take data of any length and do as many page writes as necessary, and flash_erase() will erase a given range using the most appropriate sector sizes.
You might also have cache_ftl.c. That one implements a flash translation layer to make flash writes faster and safer. Writes go to already-erased sectors and the relocation table uses a journal to guarantee integrity. It’s not thoroughly tested yet and I’m not sure its behavior will be correct when the drive fills up, and it really needs to support the trim ioctl for efficiency.
I use the W25Q128 in a product where flash I/O bandwidth is a major bottleneck, so the spi_block_read() function has had a fair amount of optimization. It can either use DMA if it’s running FreeRTOS and other tasks can run while it’s reading, or it can block and use polling mode and will switch to 16-bit transfers if you’re reading more than a few bytes, which gives a performance gain of about 6% by eliminating an idle bit between words.
For now, the cache.c module doesn’t cache on reads and will only read from cache if the block is already cached because of a write – it normally reads around the cache. I’m planning to add an option for cached reads that will use DMA to continue the read to fill up the cache block in the background, so it’ll return the requested LBA and then if the following LBA is requested it’ll already have a head start, and might have finished it already.
If you use NXP’s SPI drivers, especially the DMA ones, watch out – they have a ton of overhead and give horrible performance for short transactions, like if you’re sending a write enable command and then a page write and address separately. To get decent performance you have to either queue up everything with the hardware CS control enabled so that the whole transaction is done through DMA (which is a pain) or use a lightweight method to do the setup parts.
mass_storage.c implements an MSD interface for the SPI flash, but it’s written for the abandoned Freescale bare metal 5.0 beta stack so I don’t know how applicable it is. If you poke around in the USB stack’s MSD class driver you’ll see I’ve made some tweaks and it’ll detect when the local file system driver has modified the disk contents and will send a removable media change notification to the host. On Windows you still have to hit ‘refresh’ to see changed contents, but it works. It doesn’t go the other way, though – it gets the lock and unlock signals from the host, but so far I haven’t found a way to force FatFs to invalidate its cached data. Each file object (if it’s not in tiny mode) has its own LBA-sized cache and there’s no registry of open files so I’d probably have to be a modification to FatFs itself to attach a counter to the object and force a reload from flash when it’s accessed.
LikeLike
Hi Scott,
many thanks for all the details! My driver is not that sophisticated yet, and for the current project I don’t need to write lots of data, so bandwidth is not a concern (yet?). I’ll have to dig out that project you reference to see the details. Initially I wanted to go with FatFS as file system too, but my project will be battery operated and unfortunately it might turn off any time. So what I’m looking into is using littleFS: it is does not provide interoperability as FatFS, but includes power-loss features plus some kind of wear leveling (still have to investigate the details).
LikeLike
If you do need FAT compatibility and you’re just writing log data or something, you can do it more safely by expanding the new file first. That way FatFs isn’t constantly updating the FAT with each write, and you can also tell it to try to allocate a contiguous block. If it succeeds, you can get the address of the allocation and bypass FatFs entirely and guarantee that it’s not changing the FAT or directory entry when you make your log writes.
I wonder if anyone has a FAT emulation layer available for littleFS or something similar – something that would generate an LBA at a time worth of data, synthesizing the FAT and directory entries on the fly. Seems feasible for read-only access, at least. Translating FAT writes would get more complicated.
If you end up doing USB MSD access with FAT, beware the dirty bit. Desktop OSes like to set a bit in the MBR to track whether the volume was removed safely or not. If you’re using a simple driver and it has to erase and rewrite the whole sector for that single bit change, it makes the system vulnerable to corruption from power loss, particularly if it’s USB powered and you get an intermittent connection while plugging it in. My driver (at least in some versions) deals with that by intercepting MBR writes and tracking that bit in RAM so that reads are consistent (and the host OS won’t freak out if it’s trying to format and verify or something) but it doesn’t actually have to erase and rewrite the sector.
LikeLike
Good suggestion about expanding the file. This is what I do by default with appending data, followed by periodic sync/flush to get the data written on the card.
In case of data loss, I have the ability to read the card with raw block accesses.
I have added a command line interface to littleFS, that way I can list directories and files, and might end up in a command line way to send data to the host.
I have encountered that dirty bit already with my bootloader project, thanks for the reminder.
LikeLike
Hello Scott,
I am working on a project in which I need to store 4MB data. My project consists of the STM32 microcontroller which has FreeRTOS in it and interfaced with W25Q128JV serial flash memory to store the data. Now my concern is how do I put FatFS on W25Q128. Can you please help me out with it?Thanks for consideration.
LikeLike
Hi,
FatFs is the file system driver so you’re not putting *that* on the serial flash. What you have to do is to tell FatFs how to talk to your external flash. You do that by editing diskio.c. The interface is fairly simple. Mine implements the following:
get_fattime() – returns the RTC time, if the system has an RTC, otherwise just returns a fixed date/time
disk_initialize() – Returns RES_OK since it’s already initialized by this point
disk_read() – Reads one or more LBAs from serial flash (but I’ve never seen it request more than one LBA). In my case this goes to a cache layer but it could be a direct SPI read.
disk_write() – Writes an LBA of data to flash
disk_ioctl() – Needs to return block size and sector count but nothing else is required. I also implement CTRL_SYNC to flush my cache.
Once you’ve got FatFs able to talk to your flash chip, you’ll use the f_mkfs() command to format it and f_mount() to mount it.
LikeLike
Hi Scott,
Can u please let me know what are the configurations u made in ffconf.h to use FATFS for w25q128jv. Can u please share example code that uses the FATFS with w25q128jv.
LikeLike
There is the LittleFS implementation for it (see the links section of this article).
LikeLike
Mushtakh – nothing in ffconf.h is specific to the W25Q128. That’s where you configure options about the file system driver’s general operation, like whether it’s going to be read-only, whether you want to enable fast seek, and what code page you’re using. Most of those options balance features against memory usage or speed. I use different settings across different projects, depending on what they need.
I’m afraid I can’t share my full driver code – it ties in with a cache module, an optional flash translation layer, and some SPI DMA driver code. You’d be better off finding an example that uses the vendor HAL directly.
LikeLike
I used an SPI NOR Flash in one project in combination with SPIFFS (https://github.com/pellepl/spiffs) with good results.
Note: the issue with this kind of flash is erase perfromance who is really slow.The new SST26 family from Microchip is way better than others on this point.
kpa
LikeLiked by 1 person
Yes, I noticed too that erasing the 16 MBytes takes a very long time. For now it is fine for me, but in the future I might have to check out that SST26, so thanks for that one.
LikeLike
As soon as you start using a FS on your NOR it will perform erase command while you write file so write will start to be randomly slow
LikeLike
Hi Eric,
Can you please re-post the link to your repository.
Many thanks!
LikeLike
Link is fixed (thanks for reporting!), please try again.
LikeLike