??Unlocking the Power of W25Q Flash Memory: A Deep Dive into Firmware Development??
Subhrajit Majumder
Embedded Developer (Research and Development) | Embedded Software | Embedded Hardware | Embedded Product Development | IoT
In the world of embedded systems , firmware development plays a crucial role in enabling devices to operate effectively and stably. One such device is the W25Q flash memory, widely used in various industries. In this article, I will delve into the firmware development for the W25Q series of flash memory, a popular choice due to its high performance and reliability.
Understanding W25Q Flash Memory
The W25Q series, manufactured by Winbond, is a family of Serial Peripheral Interface (SPI) flash memory devices. These memory chips are known for their high-speed performance, small form factor, and cost-effectiveness, making them ideal for applications ranging from consumer electronics to industrial automation. It can provide more than 20-year data retention.
Key features of W25Q flash memory include:
Hardware Connection
In this demonstration, we will explore the integration of the STM32 microcontroller with the W25Q Flash memory, highlighting the benefits of using this combination for efficient data storage and retrieval. The STM32 microcontroller, known for its high-performance capabilities and low power consumption, is paired with the W25Q Flash memory, a high-capacity and reliable storage solution.
Hardware Components:
The W25Q NOR Flash memories can use the SPI in Single/Dual/Quad mode. For the simplicity of the operation, we will stick to the Single SPI mode, where we would need 4 pins to connect the Flash with the controller.
GPIO Connection:
STM32CubeIDE Configuration
We will enable the SPI in Full Duplex master mode. The configuration is shown below.
The Data width is set to 8 bits and the data should be transferred as the MSB first. The prescaler is set such that the Baud Rate is around 1.5 Mbits/sec. We can set Baud Rate upto 3Mbits/sec.
According to the datasheet of the W25Q Flash, the SPI Mode 0 and Mode 3 are supported. Below is the image from the datasheet.
In the SPI configuration, we keep the Clock Polarity (CPOL) low and the Clock Phase (CPHA) to 1 edge. Here 1 edge means that the data will be sampled on the first edge of the clock. And when the CPOL is Low, the first edge is the rising edge. Basically we are using the SPI Mode 0.
In Full duplex Mode, SPI uses 3 pins, MOSI, MISO and CLK. We need to set one more pin as output so to be used as the Chip Select (CS) Pin.
The Pin PB6 is set as the CS pin. The initial output level is set to HIGH. This is because the pin needs to be pulled low in order to select the slave device, so we keep it high initially to make sure the slave device isn’t selected. The Output speed is set to Very High because we might need to select and unselect the slave at higher rate.
Firmware for W25Q Flash Memory
First we need to define some fucntions on the main.c file.
I am defining a delay function in case we need to use the delay in the code. The implementation of the delay function can be changed based on what MCU you are using.
void W25Q_Delay(uint32_t time){
HAL_Delay(time);
}
The chip select pin needs to be pulled High and Low whenever we want to read or write the data to the device. So we define the chip select functions separately.
void csLOW(void){
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);
}
void csHIGH(void){
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);
}
The pin PB6 is connected to the CS pin of the device. The csLOW() function will simply pull the pin PB6 Low and csHIGH() will pull it High.
I will also define the SPI Read and Write functions. This will make it easier to modify the code for different MCUs without having to modify a lot in the code.
void SPI_Write(uint8_t *data, uint8_t len){
HAL_SPI_Transmit(&hspi1, data, len, 2000);
}
void SPI_Read(uint8_t *data, uint8_t len){
HAL_SPI_Receive(&hspi1, data, len, 2000);
}
The parameter for the SPI_Write function is pointer to the data to be transmitted and the length of the data. It then passes the same parameters to the HAL_SPI_Transmit function.
Similarly, the SPI_Read function reads the “len” number of bytes, and store them in the array whose address is passed to the function.
Resetting the Chip
Before starting a ne session with the chip we should reset it. This would terminate any on going operation and the device will return to its default power-on state. It will lose all the current volatile settings, such as Volatile Status Register bits, Write Enable Latch (WEL) status, Program/Erase Suspend status, Read parameter setting (P7-P0) and Wrap Bit setting (W6-W4).
To avoid the accidental Reset, a Reset command is made of 2 instructions. these Instructions, “Enable Reset (66h)” and “Reset (99h)” must be issues in a sequence. Once the Reset command is accepted by the device, the device will take approximately 30us to reset. During this period, no command will be accepted.
I will write a separate function to reset the chip. The code is shown below
void W25Q_Reset(void){
uint8_t tData[2];
tData[0] = 0x66; // Reset Enable; Binary: 0110 0110
tData[1] = 0x99; // Reset Trigger; Binary: 1001 1001
csLOW();
SPI_Write(tData, 2);
csHIGH();
W25Q_Delay(100);
}
Reading ID
I will also Read the ID of the device. The W25Q series devices are equipped with different IDs like manufacturer ID, Device ID, Unique ID and JEDEC ID. We will read the JEDEC ID as it give a lot of useful information about the device.
The instruction 9Fh can be used to read the JEDEC ID.
The JEDEC assigned Manufacturer ID byte for Winbond (EFh) and two Device ID bytes, Memory Type (ID15-ID8) and Capacity (ID7-ID0) are then shifted out on the falling edge of CLK with most significant bit (MSB) first.
The manufacturer ID (EFh) remains same across all the winbond flash memories. The Memory Type ID and the Capacity ID changes based on what device you are using.
I have the W25Q128JV chip, which is 16 Megabits in size. The ID for this chip is shown below.
The Manufacturer ID is 0xEF and the 2 device ID bytes are going to be 0x4018. Below is the function to read the ID.
uint32_t ID = 0;
uint32_t W25Q_ReadID(void){
uint8_t tData = 0x9F; // Binary: 1001 1111
uint8_t rData[3];
csLOW();
SPI_Write(&tData, 1);
SPI_Read(rData, 3);
csHIGH();
return ((rData[0] << 16) | (rData[1] << 8) | rData[2]);
}
int main ()
{
W25Q_Reset();
ID = W25Q_ReadID();
}
We define a variable and store the instruction (0x9F) to Read the ID. The array rData is used to store the 3 received bytes.
Then send the instruction using SPI_Write() function and read 3 bytes using the SPI_Read() function.
The rData[0] stores the Manufacturer ID, rData[1] stores the Memory ID and the rData[2] stores the Capacity ID. We combine the 3 bytes and make a final 24 bit ID in the format MFN ID : MEM ID : CAPACITY ID. Finally return the 24 bit ID.
FastRead Command
The Fast Read instruction can operate at the highest possible frequency.The Fast Read is accomplished by adding eight “dummy” clocks after the 24/32-bit address. The dummy clocks allow the devices internal circuits additional time for setting up the initial address.
I will write the function for FastRead, with the addition of dummy clocks in the command. Below the code shows the Fast Read function.
领英推荐
uint8_t RxData[4096];
uint8_t TxData[30];
void W25Q_FastRead(uint32_t startpage, uint8_t offset, uint32_t length, uint8_t *rData){
uint8_t tData[6];
uint32_t memAddr = (startpage * 256) + offset;
tData[0] = 0x0B;
tData[1] = (memAddr >> 16) & 0xFF;
tData[2] = (memAddr >> 8) & 0xFF;
tData[3] = (memAddr) & 0xFF;
tData[4] = 0;
csLOW();
SPI_Write(tData, 5);
SPI_Read(rData, length);
csHIGH();
}
int main(){
W25Q_FastRead(0, 0, 512, RxData);
// Read 512 bytes (2 Pages) from the start of Page 0
W25Q_FastRead(16, 0, 512, RxData);
// Read 512 bytes (2 Pages) from the start of Page 16
W25Q_FastRead(0, 0, 4608, RxData);
// Read 4608 bytes (18 Pages) from the start of Page 0
}
Result
I have stored the data “Hello world” at 2 different locations in the memory. Both locations are in different sectors, sector 0 (page0 to page15) and sector 1 (page16 to page31).
I intentionally chose the different sectors, to demonstrate that we can perform continuous read even between the sectors. The image below shows how the data is placed and what is the memory address for the corresponding byte.
Let’s see the output of the first statement, i.e. W25Q_FastRead(0, 0, 512, RxData); // Read 512 bytes (2 Pages) from the start of Page 0
The image above shows that we received the data, “Hello world”, at the beginning of the RxData buffer. This is as expected because we read from the offset of 85. This is where the data is actually stored in the memory.
The next statement reads the data from the start of the page 16. This time we should get data in the RxData buffer at an offset of 266 (1×256 + 10). This is the actual position of the data from the start of the page 16 as shown in the figure main memory map.
Write Enable & Disable
The write enable instruction must be executed prior to every Page Program, Sector Erase, Block Erase, Chip Erase, Write Status Register and Erase/Program Security Registers instruction. The instruction is shown below.
void enable_write (void)
{
uint8_t tData = 0x06; // enable Write
csLOW(); // pull the CS LOW
SPI_Write(&tData, 1);
csHIGH(); // pull the HIGH
W25Q_Delay (5); // Write cycle delay (5ms)
}
We simply send the instruction (0x06) to the device. This enables the write and we can perform the page write or erasing operations after this.
Just like write enable, there is an instruction to disable the write also. Below is the code for the same.
void disable_write (void)
{
uint8_t tData = 0x04; // disable Write
csLOW(); // pull the CS LOW
SPI_Write(&tData, 1);
csHIGH(); // pull the HIGH
W25Q_Delay (5); // Write cycle delay (5ms)
}
The instruction is performed in the similar way as the write enable instruction, except the instruction (0x04) is different. Note that the write is automatically disabled after Power-up and upon completion of the Write Status Register, Erase/Program Security Registers, Page Program, Sector Erase, Block Erase, Chip Erase and Reset instructions. I am just including it so as to perform the safe operations.
Erase Sectors
Erasing a sector is important before we learn about writing data into the flash memory. According to the datasheet of the module, we can only write the data at some location, if it has been erased (current data is 0xFF). Also a sector is the smallest memory that we can erase.
That means we can not erase a particular memory byte, or even a page. Although we can erase a complete sector (16 pages), or a 32 KB memory block (8 sectors), or a 64 KB memory block (16 sectors), or even a complete chip. But the higher the memory block we erase, the more time consuming the process is.
For getting started, erasing a sector is good enough and it only takes 400ms to do so. This information is provided in the datasheet of the device.
The erase sector instruction (0x20) is sent along with the memory address, whose sector should be erased. We send the instruction (0x21) for the same, but for the 32bit memory addresses (size>=256Mb). The Sector Erase instruction sets all memory within a specified sector (4K-bytes) to the erased state of all 1s (0xFF). A Write Enable instruction must be executed before the device will accept the Sector Erase Instruction.
void W25Q_Erase_Sector (uint16_t numsector)
{
uint8_t tData[6];
uint32_t memAddr = numsector*16*256; // Each sector contains 16 pages * 256 bytes
write_enable();
if (numBLOCK<512) // Chip Size<256Mb
{
tData[0] = 0x20; // Erase sector
tData[1] = (memAddr>>16)&0xFF; // MSB of the memory Address
tData[2] = (memAddr>>8)&0xFF;
tData[3] = (memAddr)&0xFF; // LSB of the memory Address
csLOW();
SPI_Write(tData, 4);
csHIGH();
}
else // we use 32bit memory address for chips >= 256Mb
{
tData[0] = 0x21; // ERASE Sector with 32bit address
tData[1] = (memAddr>>24)&0xFF;
Data[2] = (memAddr>>16)&0xFF;
tData[3] = (memAddr>>8)&0xFF;
tData[4] = memAddr&0xFF;
csLOW(); // pull the CS LOW
SPI_Write(tData, 5);
csHIGH(); // pull the HIGH
}
W25Q_Delay(450); // 450ms delay for sector erase
write_disable();
}
int main(){
W25Q_Erase_Sector(0);
}
The parameter of the function is the number of sector that we want to erase.
We use the same code from the previous tutorial, where we read the data from 2 different sectors. We erase the sector 0 and sector 1, and again read the data.
If the sectors gets erased, we should receive the data 0xFF instead of what was stored in those positions.
Page Write Function
I have already covered the functions to enable the write and to erase the sectors, so I will only focus on the page program part.
uint8_t RxData[4096];
uint8_t TxData[30];
void W25Q_Write(uint32_t page, uint16_t offset, uint32_t length, uint8_t *data){
uint8_t tData[256];
uint32_t indx = 4;
uint16_t startsector = page / 16;
uint16_t endsector = (page + ((offset + length -1) / 256))/16;
uint32_t numSectors = endsector - startsector + 1;
uint32_t startpage = page;
uint32_t endpage = startpage + ((length+offset-1)/256);
uint32_t numPage = endpage - startpage + 1;
for(int i = 0; i<numSectors; i++){
W25Q_Sector_Erase(startsector++);
}
uint32_t dataPosition = 0;
for(int i = 0; i<numPage; i++){
uint32_t memAddr = (startpage*256) + offset;
uint16_t byteremaining = bytetowrite(length, offset);
write_enable();
tData[0] = 0x02;
tData[1] = (memAddr >> 16) & 0xFF;
tData[2] = (memAddr >> 8) & 0xFF;
tData[3] = (memAddr) & 0xFF;
uint16_t bytetosend = byteremaining + indx;
for(int i =0; i < byteremaining; i++){
tData[indx++] = data[i+dataPosition];
}
csLOW();
SPI_Write(tData, bytetosend);
csHIGH();
startpage++;
offset = 0;
length = length - byteremaining;
dataPosition = dataPosition+byteremaining;
W25Q_Delay(10);
write_disable();
}
}
int main(){
sprintf (TxData, "Hello from W25Q");
W25Q_Write(0, 0, strlen(TxData), TxData);
W25Q_FastRead(0, 0, 600, RxData);
// Use W25Q_FastRead() function, to check W25Q_Write() function works properly or not.
}
The W25Q_Write_Page function takes the following parameters
Hardware Support
This firmware is designed to work seamlessly with the
I have rigorously tested the firmware with all these modules and observed flawless performance across the board. This confirms the firmware's robustness and compatibility, ensuring reliable operation for various embedded applications.
Optimizing and Troubleshooting
When working with flash memory, it's crucial to optimize your firmware for speed and reliability. Consider the following tips:
Conclusion
The W25Q series flash memory offers a robust solution for non-volatile data storage in embedded systems. By mastering the firmware development process, you can unlock the full potential of these memory devices, ensuring your products are reliable and efficient.
I hope this article provides you with valuable insights into W25Q flash memory and guides you in your firmware development journey.Follow for more insights on embedded system design and firmware development. Feel free to reach out if you have any questions or need guidance on similar projects!
Best Regards,
Subhrajit Majumder.