LittleFS Flash File System: Difference between revisions

From Embedded Workshop
Jump to navigation Jump to search
 
(17 intermediate revisions by 2 users not shown)
Line 29: Line 29:
To connect up the signals for power and the SPI BUS, here's the wiring diagram that was used:<br>
To connect up the signals for power and the SPI BUS, here's the wiring diagram that was used:<br>
'''Winbond W25Q128FVSG SPI Flash Module wiring:'''
'''Winbond W25Q128FVSG SPI Flash Module wiring:'''
  '''Signal    Windbond Pin  W25QXX Pin  NUCLEO-F103RB Signal & Pin'''
        '''W25QXX  Windbond'''
   DI           5              1      PB15 SPI2_MOSI  CN10-26
  '''Signal   Pin     Pin  Color    NUCLEO-F103RB Signal & Pin'''
   CLK         6              2      PB13 SPI2_SCK  CN10-30
   DI       1      5    Brown    PB15 SPI2_MOSI  CN10-26
   GND         4              3      GND            CN10-20
   CLK     2      6    Red      PB13 SPI2_SCK  CN10-30
   DO           2              4      PB14 SPI2_MISO  CN10-28
   GND     3      4    Orange  GND            CN10-20
   CS           1              5      PB12 SPI2_NSS   CN10-16
   DO       4      2    Yellow  PB14 SPI2_MISO  CN10-28
   VCC         8              6      +3V3            CN7-16
   CS       5      1    Green    PB12 SPI2_NCS   CN10-16
   VCC     6      8    Blue    +3V3            CN7-16


==The Software==
==The Software==
===The SPI and Serial Interface===
===The SPI and Serial Interface===
  Let's start by getting enough in place to read the Manufacturer and Device ID from the Winbond part.
  Let's start by getting enough in place to read the Manufacturer and Device ID from the Winbond part.
  Within the STM32CubeMX package for your project:
  Within the STM32CubeIDE configuration for your project:
  * Enable SPI2 as "Full-Duplex Master".,   
  * Enable SPI2 as "Full-Duplex Master".,   
  * Hardware NSS Signal: "Disable"  - We will use GPIO control for this signal.
  * Hardware NSS Signal: "Disable"  - We will use GPIO control for this signal.
   - Don't bother trying to use some form of SPI hardware control for this signal pin.
   - Don't bother trying to use some form of SPI hardware control for this signal pin.
  * Frame Format - Motorola, 8-Bits, MSB First
  * Frame Format - Motorola, 8-Bits, MSB First
  * Clock Prescaler 256,  (140,625KBits/s - this can be changed later)
  * Clock Prescaler 256,  (140,625KBits/s - Using 72MHz SYSCLK - this can be changed later)
  * Clock Polarity - CPOL: Low (signal is normally low)
  * Clock Polarity - CPOL: Low (signal is normally low)
  * Clock Phase - CPHA: 1 Edge (first rising edge)
  * Clock Phase - CPHA: 1 Edge (first rising edge)
  * CRC Calculation: Disabled
  * CRC Calculation: Disabled
  * NSS Signal Type: Software (GPIO contol)
  * NSS Signal Type: Software (GPIO control)
  * Select PB12 Pin, change signal to GPIO_Output, name it "SPI2_NCS" (so we will be using the same names)
  * Select PB12 Pin, change signal to GPIO_Output, name it "SPI2_NCS"
   Click on Pinout & Configuration tab -> System Core -> GPIO -> PB12 - Set GPIO output to "High"
   Click on Pinout & Configuration tab -> System Core -> GPIO -> PB12 - Set GPIO output to "High"
  We want the SPI2_NCS signal high - inactive initially.
  * Configure the HCLK for 72MHz (this is rather optional).
  * Configure the HCLK for 72MHz (this is rather optional).
     (The Winbond part will function well beyond what this STM32 processor can clock over the SPI bus)
     (The Winbond part will function well beyond what this STM32 processor can clock over the SPI bus)
Line 61: Line 63:


===Serial Output, Serial Input, and printf() Support ===
===Serial Output, Serial Input, and printf() Support ===
To use the printf() library, pushing the output to the USB serial port, we need define our own fputc().<br>
See: https://merkles.com/wiki/index.php/Getting_Started_with_STM32#Create_a_New_STM32CubeIDE_1.10.1_Project_-_Hello-World
I put the following in main.c, in the "Private user code" section, #0 as follows:
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
// Defining an fputc() function allows the printf() to work as expected
int fputc(int ch, FILE *f)
{
    uint8_t c = (uint8_t)ch;
    HAL_StatusTypeDef status = HAL_UART_Transmit(&huart2,&c,1,50); // send character to UART2 with 50ms timeout
    if(HAL_OK == status)
        return ch;
    else
        return EOF;
}
// For serial input - read a character from the serial port:
int fgetc(FILE *f)
{
    uint8_t c;
    HAL_StatusTypeDef status = HAL_UART_Receive(&huart2,&c,1,50); // read character from UART2 with 50ms timeout
    if(HAL_OK == status)
        return c;
    else
        return EOF;
}
/* USER CODE END 0 */
Add the following in main.c, in the "Private includes" section as follows:
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h> // printf()
#include "w25q128.h" // Winbond block driver
/* USER CODE END Includes */
Add the following in main.c, at the end of MX_SPI2_Init() as follows:
  /* USER CODE BEGIN SPI2_Init 2 */
  // Drive SPI2_NCS high to inactive state
  HAL_GPIO_WritePin(SPI2_NCS_GPIO_Port, SPI2_NCS_Pin, GPIO_PIN_SET); // Drive /CS high
  /* USER CODE END SPI2_Init 2 */


Create a new C file for our Winbond Block Driver code, "w25q128.c"<br>
===The Winbond 25Q128 (128M-Bit) Library===
  // w25q128.c
Here's my example of a STM32 HAL interface to the Winbond W25Q128 part:
  // Author: Jim Merkle, 10/10/2020
  [http://merkles.com/example_source_code/w25q128/w25q128.c w25q128.c]
#include "w25q128.h"
  [http://merkles.com/example_source_code/w25q128/w25q128.h w25q128.h]
  #include "main.h" // STM32 HAL defines
 
  #include <stdio.h> // printf()
==Debugging LittleFS==
   
Capture and check the return code from all function calls. The original example didn't do this.
// Return 3 byte Manufacturer and device ID (requires 3 byte buffer)
  * Check the return code for lfs_mount(). Make sure it retuns LFS_ERR_OK.
  int W25_ReadID(uint8_t *buf, int bufSize) {
  - Although, the first time, it will return an error, and require an lfs_format() call.
  int retval;
  * Check the return code for lfs_file_open().  Make sure it retuns LFS_ERR_OK.
  uint8_t idcmd = W25_CMD_READID;
* Although I didn't check the return code for lfs_file_rewind(), lfs_file_write(), and lfs_file_close(),
  if(bufSize < W25_ID_BUF_SIZE)
  it wouldn't hurt.
    return HAL_ERROR; // buffer too small
* There are plenty of debug printf()s in the code already.  Just need to enable them.
  W25_CS_ENABLE(); // Drive Winbond chip select, /CS low
   Open up lfs_util.h, around line 50, you will want to define LFS_YES_TRACE, to enable printf() tracing:
  HAL_SPI_Transmit(&hspi, &idcmd , sizeof(idcmd ), TIMEOUT); // Send the ID command
'''#define LFS_YES_TRACE'''
   retval = HAL_SPI_Receive(&hspi, buf, W25_ID_BUF_SIZE, TIMEOUT);
// Logging functions
  W25_CS_DISABLE();
#ifdef LFS_YES_TRACE}}
  printf("%s: retval %d, 0x%02X, 0x%02X, 0x%02X\r\n",__func__, retval, buf[0],buf[1],buf[2]);
* If your terminal wants CR/LF pairs, edit the printf() strings, adding "\r" to go with the "\n" line feed character.
  return retval;
} // W25_ReadID()


Create a new header file, "w25q128.h"
==To Do==
// w25q128.h
  Using the command line interface, test the SPI interface with the W25Q128.
  // Author: Jim Merkle, 10/10/2020
  1) Begin with the JEDIC ID commandDisplay the part's ID:
#ifndef _w25q128_h_
   See: cl_w25_id() in w25q128.c
#define _w25q128_h_
    >w25id
#include <stdint.h> // uint8_t
    Jedec ID: 0xEF 0x40 0x18
  #include "main.h"  // HAL header files, SPI and GPIO defines
#define W25_CMD_READID        0x9F
extern SPI_HandleTypeDef hspi2; // STM32 SPI instance
#define TIMEOUT          100  // MS
#define W25_ID_BUF_SIZE  3    // bytes
   
/* W25Q128 chip select is active LOW */
#define W25_CS_ENABLE()   HAL_GPIO_WritePin(SPI2_NCS_GPIO_Port, SPI2_NCS_Pin, GPIO_PIN_RESET)
#define W25_CS_DISABLE()  HAL_GPIO_WritePin(SPI2_NCS_GPIO_Port, SPI2_NCS_Pin, GPIO_PIN_SET)
   
   
  // Return 3 byte Manufacturer and device ID (requires 3 byte buffer)
  2) This part has a unique ID assigned to it.  Display it:
  int W25_ReadID(uint8_t *buf, size_t bufSize);
  See: cl_w25_unique_id() in w25q128.c
    >w25uid
    Unique ID: D2 65 CC 22 67 24 37 28  <This ID is from one of my parts. Yours will be different.>
   
   
  #endif // _w25q128_h_
  At this point, you have both written to and read from the SPI FLASH device.
 
  If your ID is correct and you're not getting errors, chances are your SPI interface is functioning well.
Now, we can call the W25_ReadID() function in main() to read the chip ID<br>
You can test erasing, reading, and writing to the FLASH device, and if you have an logic analyzer or
Add the following line to main.c, just before the while(1) loop as follows:
digital scope, you can validate the resulting timing with the FLASH chip's data sheet.
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  uint8_t idbuffer[W25_ID_BUF_SIZE]; // buffer for W25_ReadID()
  W25_ReadID(idbuffer,sizeof(idbuffer)); // Read the Manufacturer ID and device ID from the Winbond part
  while (1)
 
Running the program should produce the following output: '''"W25_ReadID: retval 0, 0xEF, 0x40, 0x18"'''<br>
If you have a Saleae Logic analyzer, you would see something like the following:
[[File:SaleaeSpiCapture.jpg]]<br>
Note: It appears that using the HAL_SPI_Receive() function to clock in receive data may transmit
      garbage on the MOSI line.  This doesn't appear to bother the Winbond part.
   
   
===The LittlFS Code===
I Cloned '''LittleFS''' to my local machine from GitHub.
One thin you will notice about LittleFS, is the lack of code to talk to SPI FLASH devices.
- That's your job, providing any interface code that your FLASH devices need.
  This isn't too difficult in that LittleFS is responsible for managing the blocks.  You just need to
  to provide the base level access to your FLASH device and LittleFS can do the rest.
 
I created the following functions to interact with the W25Q128 (taken from w25q128.h)<br>
Not all these functions are required, but allowed for early testing of the FLASH device.
// Return 3 byte Manufacturer and device ID (requires 3 byte buffer)
int W25_ReadJedecID(uint8_t *buf, int bufSize);
// Return 8 byte Unique ID (requires 8 byte buffer)
int W25_ReadUniqueID(uint8_t *buf, int bufSize);
// Return Status1 register
uint8_t W25_ReadStatusReg1(void);
// Returns 0:Not busy, or 1:Busy
int W25_Busy(void);
int W25_DelayWhileBusy(uint32_t msTimeout); // Delay up to TIMEOUT value
// Send Write Enable command, allowing writing to the device
int W25_WriteEnable(void);
// Send Write Disable command, preventing writing to the device
int W25_WriteDisable(void);
// Read Data from the FLASH device - no limit (the whole device can be read with this)
int W25_ReadData(uint32_t address, uint8_t *buf, int bufSize);
// Write a page (or partial page) of data (up to 256 bytes - does not cross page boundaries)
int W25_PageProgram(uint32_t address, uint8_t *buf, int bufSize);
// Erase a 4096 byte sector
int W25_SectorErase(uint32_t address);
// Erase the entire chip (May take 40 seconds or more depending on the device)
int W25_ChipErase(void);


Reference Information:<br>
==Reference / Additional Information==
  https://uimeter.com/2018-04-12-Try-LittleFS-on-STM32-and-SPI-Flash/
  https://uimeter.com/2018-04-12-Try-LittleFS-on-STM32-and-SPI-Flash/
  https://github.com/ARMmbed/littlefs
  https://github.com/ARMmbed/littlefs
  https://github.com/ARMmbed/littlefs/blob/master/README.md
  https://github.com/ARMmbed/littlefs/blob/master/README.md

Latest revision as of 15:15, 20 June 2024

LittleFS Flash File System

I recently learned about a compact embedded Flash File System known as SpiFFS.
While researching SpiFFS, I soon learned of another compact embedded Flash File System, LittleFS.
LittleFS appears to be even more compact and has additional security in that it creates and maintains a CRC for each file, and verifies the CRC each time the file is read.

  • Compact
  • Power-loss resilience
  • Dynamic wear leveling
  • Bounded RAM/ROM
  • Maintains revision count and CRC (security features)

The Hardware

Let's put this file system onto an STM32 board and check it out.

I'll be using a NUCLEO-F103RB ($10.34), with a Winbond W25Q128FVSG SPI Flash module from Ebay.
https://www.ebay.com/itm/264290947181  If the link doesn't work, just search Ebay for "W25Q128 Module".
I bought two for $8.26 (free shipping).  Download the PDF for the W25Q128FVSG:
https://www.winbond.com/resource-files/w25q128fv%20rev.m%2005132016%20kms.pdf

The W25Q128FVSG uses 2.7V to 3.6V for operation. Here's a diagram for the the chip's pins and signals,
along with the Ebay "W25QXX" module" photo:

Since the NUCLEO-F103RB powers its STM32F103RB processor with 3.3V, this "W25QXX" module"
is compatible with the NUCLEO-F103RB.  (The STM32-F103RB is capable of running from 2.0V to 3.6V.)

Let's look at the signal pins on the NUCLEO-F103RB:

On the NUCLEO board, the SPI1_SCK signal is being used to drive the board's Green LED.
So, we will use SPI2 bus and its signal pins.
To connect up the signals for power and the SPI BUS, here's the wiring diagram that was used:
Winbond W25Q128FVSG SPI Flash Module wiring:

        W25QXX  Windbond
Signal    Pin     Pin   Color    NUCLEO-F103RB Signal & Pin
  DI       1       5    Brown    PB15 SPI2_MOSI  CN10-26
  CLK      2       6    Red      PB13 SPI2_SCK   CN10-30
  GND      3       4    Orange   GND             CN10-20
  DO       4       2    Yellow   PB14 SPI2_MISO  CN10-28
  CS       5       1    Green    PB12 SPI2_NCS   CN10-16
  VCC      6       8    Blue     +3V3             CN7-16

The Software

The SPI and Serial Interface

Let's start by getting enough in place to read the Manufacturer and Device ID from the Winbond part.
Within the STM32CubeIDE configuration for your project:
* Enable SPI2 as "Full-Duplex Master".,  
* Hardware NSS Signal: "Disable"  - We will use GPIO control for this signal.
  - Don't bother trying to use some form of SPI hardware control for this signal pin.
* Frame Format - Motorola, 8-Bits, MSB First
* Clock Prescaler 256,  (140,625KBits/s - Using 72MHz SYSCLK - this can be changed later)
* Clock Polarity - CPOL: Low (signal is normally low)
* Clock Phase - CPHA: 1 Edge (first rising edge)
* CRC Calculation: Disabled
* NSS Signal Type: Software (GPIO control)
* Select PB12 Pin, change signal to GPIO_Output, name it "SPI2_NCS"
  Click on Pinout & Configuration tab -> System Core -> GPIO -> PB12 - Set GPIO output to "High"
  We want the SPI2_NCS signal high - inactive initially.
* Configure the HCLK for 72MHz (this is rather optional).
    (The Winbond part will function well beyond what this STM32 processor can clock over the SPI bus)
Optional configuration and code that will ease debugging
Select and configure USART2 (The USART2 signals are routed to the NEUCLEO's ST-LINK chip,
    creating a serial connection to your host computer over USB.)
* Mode: Asychronous, Hardware Flow Control (RS232): Disable
* Baud Rate: 115200, 8 bits, No Parity, 1 Stop Bit, Data Direction: Receive and Transmit

Serial Output, Serial Input, and printf() Support

See: https://merkles.com/wiki/index.php/Getting_Started_with_STM32#Create_a_New_STM32CubeIDE_1.10.1_Project_-_Hello-World

The Winbond 25Q128 (128M-Bit) Library

Here's my example of a STM32 HAL interface to the Winbond W25Q128 part:
w25q128.c
w25q128.h

Debugging LittleFS

Capture and check the return code from all function calls. The original example didn't do this.

* Check the return code for lfs_mount().  Make sure it retuns LFS_ERR_OK.
  - Although, the first time, it will return an error, and require an lfs_format() call.
* Check the return code for lfs_file_open().  Make sure it retuns LFS_ERR_OK.
* Although I didn't check the return code for lfs_file_rewind(), lfs_file_write(), and lfs_file_close(),
  it wouldn't hurt.
* There are plenty of debug printf()s in the code already.  Just need to enable them.
  Open up lfs_util.h, around line 50, you will want to define LFS_YES_TRACE, to enable printf() tracing:
#define LFS_YES_TRACE
// Logging functions
#ifdef LFS_YES_TRACE}}
* If your terminal wants CR/LF pairs, edit the printf() strings, adding "\r" to go with the "\n" line feed character.

To Do

Using the command line interface, test the SPI interface with the W25Q128.
1) Begin with the JEDIC ID command.  Display the part's ID: 
 See: cl_w25_id() in w25q128.c
    >w25id
    Jedec ID: 0xEF 0x40 0x18

2) This part has a unique ID assigned to it.  Display it:
 See: cl_w25_unique_id() in w25q128.c
    >w25uid
    Unique ID: D2 65 CC 22 67 24 37 28   <This ID is from one of my parts.  Yours will be different.>

At this point, you have both written to and read from the SPI FLASH device.
If your ID is correct and you're not getting errors, chances are your SPI interface is functioning well.
You can test erasing, reading, and writing to the FLASH device, and if you have an logic analyzer or
digital scope, you can validate the resulting timing with the FLASH chip's data sheet.

Reference / Additional Information

https://uimeter.com/2018-04-12-Try-LittleFS-on-STM32-and-SPI-Flash/
https://github.com/ARMmbed/littlefs
https://github.com/ARMmbed/littlefs/blob/master/README.md