Working with STM32 and External Flash W25QXX Part3: Writing data

In the previous guide (here), we saw how to read from a sector and get the required data.

In this guide, we shall see how to erase a sector, write the new values.

In this guide, we shall cover the following:

  • Steps required to write to W25QXX.
  • Enable/Disable write.
  • Sector erase.
  • Write to a page.
  • Header file update.
  • Main code.
  • Results.

1. Steps Required to Write to Flash:

  • First, we need to check the length of the data to be written to the flash and calculate how many sectors will be used according to the size and the offset.
  • Enable the write
  • After calculating the number of sectors to be written, we shall erase those sectors since W25QXX doesn’t allow to write a sector with value other than 0xFF.
  • After erasing the sector, we shall write the new data starting.
  • Finally disable write.

2. Enable / Disable Write:

In order to enable the write, need to send enable write command as following:

  • Set CS pin to Low.
  • Write Enable Command.
  • Set CS pin to high

As shown in the figure:

Hence, enable and disable write functions as following:

static void write_enable (void)
{
	uint8_t tData = 0x06;  // enable write
	W25QXX_CS_LOW();
	SPI1_Transmit(&tData, 1);
	W25QXX_CS_HIGH();
	delay(5);  // 5ms delay
}

static void write_disable(void)
{
	uint8_t tData = 0x04;  // disable write
	W25QXX_CS_LOW();
	SPI1_Transmit(&tData, 1);
	W25QXX_CS_HIGH();
	delay(5);  // 5ms delay
}

The functions are declared as static since only this source file needs those functions.

3. Sector Erase:

The figure shows the architecture of W25QXX:

In case of W25Q32, there is 64 blocks, each block contains 15 sectors of 4KB, within each sector, there is 16 pages each page is 256 byte in length.

Hence, in order to update single element of the page, we need to erase the entire 4KB of sector.

Hence, declare the following function:

void W25QXX_Erase_Sector (uint16_t sectore)

The function will take sector number as argument and returns nothing.

Declare local buffer of byte to be transmitted over SPI which will hold the erase sector command and the address:

uint8_t tData[6];

Calculate the address:

uint32_t memAddr = sectore*16*256;   // Each sector contains 16 pages * 256 bytes

Enable write:

write_enable();

Write the sector erase command and the address:

	if (NumberOfSector<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

		W25QXX_CS_LOW();
		SPI1_Transmit(tData, 4);
		W25QXX_CS_HIGH();
	}
	else  // we use 32bit memory address for chips >= 256Mb
	{
		tData[0] = 0x21;  // ERASE Sector with 32bit address
		tData[1] = (memAddr>>24)&0xFF;
		tData[2] = (memAddr>>16)&0xFF;
		tData[3] = (memAddr>>8)&0xFF;
		tData[4] = memAddr&0xFF;

		W25QXX_CS_LOW();  // pull the CS LOW
		SPI1_Transmit(tData, 5);
		W25QXX_CS_HIGH();  // pull the HIGH
	}

Give a delay according to the AC characteristics:

Since it will take 200ms to erase the sector, to be sure, we shall use 300ms delay:

delay(300);  // 200ms delay for sector erase (300ms to be sure)

Finally, disable write:

write_disable();

The entire function:

void W25QXX_Erase_Sector (uint16_t sectore)
{
	uint8_t tData[6];
	uint32_t memAddr = sectore*16*256;   // Each sector contains 16 pages * 256 bytes

	write_enable();

	if (NumberOfSector<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

		W25QXX_CS_LOW();
		SPI1_Transmit(tData, 4);
		W25QXX_CS_HIGH();
	}
	else  // we use 32bit memory address for chips >= 256Mb
	{
		tData[0] = 0x21;  // ERASE Sector with 32bit address
		tData[1] = (memAddr>>24)&0xFF;
		tData[2] = (memAddr>>16)&0xFF;
		tData[3] = (memAddr>>8)&0xFF;
		tData[4] = memAddr&0xFF;

		W25QXX_CS_LOW();  // pull the CS LOW
		SPI1_Transmit(tData, 5);
		W25QXX_CS_HIGH();  // pull the HIGH
	}

	delay(300);  // 200ms delay for sector erase (300ms to be sure)

	write_disable();

}

4. Write to a Page:

Declare the following function:

void W25QXX_Write(uint32_t page, uint16_t offset, uint8_t *data, uint32_t size)

The function will take the following arguments:

  • Page, which is the page number.
  • Offset, which is which the distance from the start of the page.
  • Pointer to buffer that hold the data to be written.
  • Size of the data to be written.

Declare a local buffer with size of 266 which will be enough to hold the 256 byte of data and the commands.

uint8_t tData[266];

Calculate the start page, end of the page and how many pages are needed:

	uint32_t startPage = page;
	uint32_t endPage  = startPage + ((size+offset-1)/256);
	uint32_t numPages = endPage-startPage+1;

Calculate the needed sectors:

	uint16_t startSector  = startPage/16;
	uint16_t endSector  = endPage/16;
	uint16_t numSectors = endSector-startSector+1;

Erase the sectors:

	for (uint16_t i=0; i<numSectors; i++)
	{
		W25QXX_Erase_Sector(startSector++);
	}

Declare a local variable to track the data:

uint32_t dataPosition = 0;

Write the data:

for (uint32_t i=0; i<numPages; i++)
	{
		uint32_t memAddr = (startPage*256)+offset;
		uint16_t bytesremaining  = bytestowrite(size, offset);
		uint32_t indx = 0;

		write_enable();

		if (NumberOfSector<512)   // Chip Size<256Mb
		{
			tData[0] = 0x02;  // page program
			tData[1] = (memAddr>>16)&0xFF;  // MSB of the memory Address
			tData[2] = (memAddr>>8)&0xFF;
			tData[3] = (memAddr)&0xFF; // LSB of the memory Address

			indx = 4;
		}

		else // we use 32bit memory address for chips >= 256Mb
		{
			tData[0] = 0x12;  // page program with 4-Byte Address
			tData[1] = (memAddr>>24)&0xFF;  // MSB of the memory Address
			tData[2] = (memAddr>>16)&0xFF;
			tData[3] = (memAddr>>8)&0xFF;
			tData[4] = (memAddr)&0xFF; // LSB of the memory Address

			indx = 5;
		}

		uint16_t bytestosend  = bytesremaining + indx;

		for (uint16_t i=0; i<bytesremaining; i++)
		{
			tData[indx++] = data[i+dataPosition];
		}

		if (bytestosend > 200)
		{
			W25QXX_CS_LOW();
			SPI1_Transmit(tData, 100);
			SPI1_Transmit(tData+100, bytestosend-100);
			W25QXX_CS_HIGH();
		}

		else
		{
			W25QXX_CS_LOW();
			SPI1_Transmit(tData, bytestosend);
			W25QXX_CS_HIGH();
		}

		startPage++;
		offset = 0;
		size = size-bytesremaining;
		dataPosition = dataPosition+bytesremaining;

		delay(5);
		write_disable();

	}

The steps as following:

  • Step through all the pages using for loop.
  • Within the for loop:
  • Get the starting address.
  • Get the remaining bytes using the following function:
static uint32_t bytestowrite (uint32_t size, uint16_t offset)
{
	if ((size+offset)<256) return size;
	else return 256-offset;
}
  • Set Indx to zero.
  • Check the memory size if it is less than 256Mb or bigger.
  • Send the page program command 0x02 for less than 25Mb or page program with 32-bit address of 0x12.
  • Set the indx to 4 or 5 according to the memory size.
  • Get the bytes to be send.
  • Fill the buffer.
  • Check the size of the data, if it it more than 200, send it in certain way, if it less, in other way.
  • Update the tracking variables.
  • Delay by 5ms to ensure data is written.
  • Disable the write.

Hence, the write function as following:

void W25QXX_Write(uint32_t page, uint16_t offset, uint8_t *data, uint32_t size)
{
	uint8_t tData[266];
	uint32_t startPage = page;
	uint32_t endPage  = startPage + ((size+offset-1)/256);
	uint32_t numPages = endPage-startPage+1;

	uint16_t startSector  = startPage/16;
	uint16_t endSector  = endPage/16;
	uint16_t numSectors = endSector-startSector+1;
	for (uint16_t i=0; i<numSectors; i++)
	{
		W25QXX_Erase_Sector(startSector++);
	}

	

	// write the data
	for (uint32_t i=0; i<numPages; i++)
	{
		uint32_t memAddr = (startPage*256)+offset;
		uint16_t bytesremaining  = bytestowrite(size, offset);
		uint32_t indx = 0;

		write_enable();

		if (NumberOfSector<512)   // Chip Size<256Mb
		{
			tData[0] = 0x02;  // page program
			tData[1] = (memAddr>>16)&0xFF;  // MSB of the memory Address
			tData[2] = (memAddr>>8)&0xFF;
			tData[3] = (memAddr)&0xFF; // LSB of the memory Address

			indx = 4;
		}

		else // we use 32bit memory address for chips >= 256Mb
		{
			tData[0] = 0x12;  // page program with 4-Byte Address
			tData[1] = (memAddr>>24)&0xFF;  // MSB of the memory Address
			tData[2] = (memAddr>>16)&0xFF;
			tData[3] = (memAddr>>8)&0xFF;
			tData[4] = (memAddr)&0xFF; // LSB of the memory Address

			indx = 5;
		}

		uint16_t bytestosend  = bytesremaining + indx;

		for (uint16_t i=0; i<bytesremaining; i++)
		{
			tData[indx++] = data[i+dataPosition];
		}

		if (bytestosend > 200)
		{
			W25QXX_CS_LOW();
			SPI1_Transmit(tData, 100);
			SPI1_Transmit(tData+100, bytestosend-100);
			W25QXX_CS_HIGH();
		}

		else
		{
			W25QXX_CS_LOW();
			SPI1_Transmit(tData, bytestosend);
			W25QXX_CS_HIGH();
		}

		startPage++;
		offset = 0;
		size = size-bytesremaining;
		dataPosition = dataPosition+bytesremaining;

		delay(5);
		write_disable();

	}
}

5. Update the Header File:

Open W25QXX.h header file and include the following two functions:

/*
 * @brief This function will write to W25QXX.
 * @param sector is the sector number of the flash
 * @param offset is the offset of the memory location to be read.
 * @param *rData is a pointer to buffer that will hold the data to be written.
 * @param size if the data to be written.
 * @return nothing
 *
 * */

void W25QXX_Write(uint32_t page, uint16_t offset, uint8_t *data, uint32_t size);

/*
 * @brief This function will erase a sectore from W25QXX.
 * @param sector is the sector number of the flash
 * @return nothing
 * */
void W25QXX_Erase_Sector (uint16_t sector);

6. Main code.

In main.c file:

#include "delay.h"
#include "SPI1.h"
#include "W25QXX.h"
#include "stdlib.h"

uint32_t W25QXX_ID;
#define W25Q32_ID	(uint32_t)0xEF4016

uint8_t rxData[10];
uint8_t txData[10];


int main(void)
{	delay_init(16000000);
	srand(millis());
	SPI1_Pins_Init();
	W25QXX_CS_Pins_Init();
	SPI1_Config();

	W25QXX_NumberOfSector(64);
	W25QXX_Reset();
	delay(100);



	while(1)
	{
		W25QXX_ID=W25QXX_ReadID();

		/*If the ID is matched read the data*/
		if(W25QXX_ID==W25Q32_ID)
		{

			for (int i=0;i<10;i++)
			{
				txData[i]=rand() % 255;
			}


			W25QXX_Write(0,0,txData,10);

			/*Write the data*/

			W25QXX_Read(0,0,rxData,10);


		}

		delay(1000);


	}

}

We shall randomize the data to be written to the W25QXX page zero every second.

7. Results:

As you can see, we have successfully erased and wrote the new data to page0.

Happy coding 🙂

Add Comment

Your email address will not be published. Required fields are marked *