W25QXX in SPI with External Loader Part 2: Flash Source Code

In part 1, we setup the environment and created the header file for the required functions and symbolic to be hold.

In the second part of the external loader, we shall develop the source file for W25QXX.

9. W25QXX Source file:

We start off by creating new source file with name of W25QXX.c.

Within the source file, include the following:

#include "W25QXX.h"

#include "SPI1.h"

#include "delay.h"

Then declare the number of blocks (In W25Q32 is 64 blocks):

#define NumberOfBlocks 64  // number of total blocks for 32Mb flash

Now, we declare a function that will read the status register and check if the write/eras is still in progress:

static void W25Q_Waitforwrite()
{
	uint8_t tData = W25Q_READ_SR1;
	W25QXX_CS_LOW();
	SPI1_Transmit(&tData, 1);
	do
	{
		SPI1_Receive(&tData, 1);  //keep reading status register
	} while (tData & 0x01);  // until the bit to reset
	W25QXX_CS_HIGH();
}

For write enable/disable functions:

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

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

For more details about this, please refer to this guide.

Bytes to write and modify functions:

static uint32_t bytestowrite (uint32_t size, uint16_t offset)
{
	if ((size+offset)<256) return size;
	else return 256-offset;
}

static uint32_t bytestomodify (uint32_t size, uint16_t offset)
{
	if ((size+offset)<4096) return size;
	else return 4096-offset;
}

Flash reset and get the ID:

void W25Q_Reset (void)
{
	uint8_t tData[2];
	tData[0] = W25Q_ENABLE_RST;  // enable Reset
	tData[1] = W25Q_RESET;  // Reset
	W25QXX_CS_LOW();
	SPI1_Transmit(tData, 2);
	W25QXX_CS_HIGH();
	delay(100);
}

uint32_t W25Q_ReadID (void)
{
	uint8_t tData = 0x9F;  // Read JEDEC ID
	uint8_t rData[3];
	W25QXX_CS_LOW();
	SPI1_Transmit(&tData, 1);
	SPI1_Receive(rData, 3);
	W25QXX_CS_HIGH();
	return ((rData[0]<<16)|(rData[1]<<8)|rData[2]);
}

For more details about these two functions, please refer to this guide.

Chip erase functions:

void W25Q_Chip_Erase (void)
{
	uint8_t tData = W25Q_CHIP_ERASE;

	write_enable();

	W25QXX_CS_LOW();
	SPI1_Transmit(&tData, 1);
	W25QXX_CS_HIGH();

	W25Q_Waitforwrite();

	write_disable();
}

The following steps are required:

  • Enable write.
  • Send chip erase command.
  • Wait until the operation is completed.
  • Disable write.

For read and fast read functions:

void W25Q_Read (uint32_t startPage, uint8_t offset, uint32_t size, uint8_t *rData)
{
	uint8_t tData[5];
	uint32_t memAddr = (startPage*256) + offset;

	if (NumberOfBlocks<512)   // Chip Size<256Mb
	{
		tData[0] = W25Q_READ_DATA;  // enable Read
		tData[1] = (memAddr>>16)&0xFF;  // MSB of the memory Address
		tData[2] = (memAddr>>8)&0xFF;
		tData[3] = (memAddr)&0xFF; // LSB of the memory Address
	}
	else  // we use 32bit memory address for chips >= 256Mb
	{
		tData[0] = W25Q_READ_DATA_4B;  // Read Data 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
	}

	W25QXX_CS_LOW();  // pull the CS Low
	if (NumberOfBlocks<512)
	{
		SPI1_Transmit(tData, 4);  // send read instruction along with the 24 bit memory address
	}
	else
	{
		SPI1_Transmit(tData, 5);  // send read instruction along with the 32 bit memory address
	}

	SPI1_Receive(rData, size);  // Read the data
	W25QXX_CS_HIGH();  // pull the CS High
}

void W25Q_FastRead (uint32_t startPage, uint8_t offset, uint32_t size, uint8_t *rData)
{
	uint8_t tData[6];
	uint32_t memAddr = (startPage*256) + offset;

	if (NumberOfBlocks<512)   // Chip Size<256Mb
	{
		tData[0] = W25Q_FAST_READ;  // enable Fast Read
		tData[1] = (memAddr>>16)&0xFF;  // MSB of the memory Address
		tData[2] = (memAddr>>8)&0xFF;
		tData[3] = (memAddr)&0xFF; // LSB of the memory Address
		tData[4] = 0;  // Dummy clock
	}
	else  // we use 32bit memory address for chips >= 256Mb
	{
		tData[0] = W25Q_FAST_READ_4B;  // Fast Read 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
		tData[5] = 0;  // Dummy clock
	}

	W25QXX_CS_LOW();  // pull the CS Low
	if (NumberOfBlocks<512)
	{
		SPI1_Transmit(tData, 5);  // send read instruction along with the 24 bit memory address
	}
	else
	{
		SPI1_Transmit(tData, 6);  // send read instruction along with the 32 bit memory address
	}

	SPI1_Receive(rData, size);  // Read the data
	W25QXX_CS_HIGH();  // pull the CS High
}


For more details, please refer to this guide.

Sector erase:

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 (NumberOfBlocks<512)   // Chip Size<256Mb
	{
		tData[0] = W25Q_SECTOR_ERASE;  // 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] = W25Q_SECTOR_ERASE_4B;  // 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
	}

	W25Q_Waitforwrite();

	write_disable();

}

Write clean:

void W25Q_Write_Clean (uint32_t page, uint16_t offset, uint32_t size, uint8_t *data)
{
	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++)
	{
		W25Q_Erase_Sector(startSector++);
	}

	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 (NumberOfBlocks<512)   // Chip Size<256Mb
		{
			tData[0] = W25Q_PAGE_PROGRAM;  // 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] = W25Q_PAGE_PROGRAM_4B;  // 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 > 250)
		{
			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;

		W25Q_Waitforwrite();
		write_disable();

	}
}

This function will erase the sector and write the new data.

For W25QXX write function:

void W25Q_Write (uint32_t page, uint16_t offset, uint32_t size, uint8_t *data)
{
	uint16_t startSector  = page/16;
	uint16_t endSector  = (page + ((size+offset-1)/256))/16;
	uint16_t numSectors = endSector-startSector+1;

	uint8_t previousData[4096];
	uint32_t sectorOffset = ((page%16)*256)+offset;
	uint32_t dataindx = 0;

	for (uint16_t i=0; i<numSectors; i++)
	{
		uint32_t startPage = startSector*16;
		W25Q_FastRead(startPage, 0, 4096, previousData);

		uint16_t bytesRemaining = bytestomodify(size, sectorOffset);
		for (uint16_t i=0; i<bytesRemaining; i++)
		{
			previousData[i+sectorOffset] = data[i+dataindx];
		}

		W25Q_Write_Clean(startPage, 0, 4096, previousData);

		startSector++;
		sectorOffset = 0;
		dataindx = dataindx+bytesRemaining;
		size = size-bytesRemaining;
	}
}

This function will store the current data of the sector, erase the sector, write back the modified sector data back to the sector since write to any location of the sector requires entire sector to be erased and written again.

For read/write byte:

uint8_t W25Q_Read_Byte (uint32_t Addr)
{
	uint8_t tData[5];
	uint8_t rData;

	if (NumberOfBlocks<512)   // Chip Size<256Mb
	{
		tData[0] = W25Q_READ_DATA;  // enable Read
		tData[1] = (Addr>>16)&0xFF;  // MSB of the memory Address
		tData[2] = (Addr>>8)&0xFF;
		tData[3] = (Addr)&0xFF; // LSB of the memory Address
	}
	else  // we use 32bit memory address for chips >= 256Mb
	{
		tData[0] = W25Q_READ_DATA_4B;  // Read Data with 4-Byte Address
		tData[1] = (Addr>>24)&0xFF;  // MSB of the memory Address
		tData[2] = (Addr>>16)&0xFF;
		tData[3] = (Addr>>8)&0xFF;
		tData[4] = (Addr)&0xFF; // LSB of the memory Address
	}

	W25QXX_CS_LOW();  // pull the CS Low
	if (NumberOfBlocks<512)
	{
		SPI1_Transmit(tData, 4);  // send read instruction along with the 24 bit memory address
	}
	else
	{
		SPI1_Transmit(tData, 5);  // send read instruction along with the 32 bit memory address
	}

	SPI1_Receive(&rData, 1);  // Read the data
	W25QXX_CS_HIGH();  // pull the CS High

	return rData;
}

void W25Q_Write_Byte (uint32_t Addr, uint8_t data)
{
	uint8_t tData[6];
	uint8_t indx;

	if (NumberOfBlocks<512)   // Chip Size<256Mb
	{
		tData[0] = W25Q_PAGE_PROGRAM;  // page program
		tData[1] = (Addr>>16)&0xFF;  // MSB of the memory Address
		tData[2] = (Addr>>8)&0xFF;
		tData[3] = (Addr)&0xFF; // LSB of the memory Address
		tData[4] = data;
		indx = 5;
	}
	else  // we use 32bit memory address for chips >= 256Mb
	{
		tData[0] = W25Q_PAGE_PROGRAM_4B;  // Write Data with 4-Byte Address
		tData[1] = (Addr>>24)&0xFF;  // MSB of the memory Address
		tData[2] = (Addr>>16)&0xFF;
		tData[3] = (Addr>>8)&0xFF;
		tData[4] = (Addr)&0xFF; // LSB of the memory Address
		tData[5] = data;
		indx = 6;
	}


	if (W25Q_Read_Byte(Addr) == 0xFF)
	{
		write_enable();
		W25QXX_CS_LOW();
		SPI1_Transmit(tData, indx);
		W25QXX_CS_HIGH();

		W25Q_Waitforwrite();
		write_disable();
	}
}

The following functions will allow you store variable data of 32-bit and float to the flash (not need though):

uint8_t tempBytes[4];

void float2Bytes(uint8_t * ftoa_bytes_temp,float float_variable)
{
    union {
      float a;
      uint8_t bytes[4];
    } thing;

    thing.a = float_variable;

    for (uint8_t i = 0; i < 4; i++) {
      ftoa_bytes_temp[i] = thing.bytes[i];
    }

}

float Bytes2float(uint8_t * ftoa_bytes_temp)
{
    union {
      float a;
      uint8_t bytes[4];
    } thing;

    for (uint8_t i = 0; i < 4; i++) {
    	thing.bytes[i] = ftoa_bytes_temp[i];
    }

   float float_variable =  thing.a;
   return float_variable;
}

void W25Q_Write_NUM (uint32_t page, uint16_t offset, float data)
{
	float2Bytes(tempBytes, data);

	/* Write using sector update function */
	W25Q_Write(page, offset, 4, tempBytes);
}

float W25Q_Read_NUM (uint32_t page, uint16_t offset)
{
	uint8_t rData[4];
	W25Q_Read(page, offset, 4, rData);
	return (Bytes2float(rData));
}

void W25Q_Write_32B (uint32_t page, uint16_t offset, uint32_t size, uint32_t *data)
{
	uint8_t data8[size*4];
	uint32_t indx = 0;

	for (uint32_t i=0; i<size; i++)
	{
		data8[indx++] = data[i]&0xFF;   // extract LSB
		data8[indx++] = (data[i]>>8)&0xFF;
		data8[indx++] = (data[i]>>16)&0xFF;
		data8[indx++] = (data[i]>>24)&0xFF;
	}

	W25Q_Write(page, offset, indx, data8);
}

void W25Q_Read_32B (uint32_t page, uint16_t offset, uint32_t size, uint32_t *data)
{
	uint8_t data8[size*4];
	uint32_t indx = 0;

	W25Q_FastRead(page, offset, size*4, data8);

	for (uint32_t i=0; i<size; i++)
	{
		data[i] = (data8[indx++]) | (data8[indx++]<<8) | (data8[indx++]<<16) | (data8[indx++]<<24);
	}
}

The following functions are needed by the external loader:

void flash_WriteMemory(uint8_t* buffer, uint32_t address, uint32_t buffer_size)
{
	uint32_t page = address/256;
	uint16_t offset = address%256;
	uint32_t size = buffer_size;
	uint8_t tData[266];
	uint32_t startPage = page;
	uint32_t endPage  = startPage + ((size+offset-1)/256);
	uint32_t numPages = endPage-startPage+1;

	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 (NumberOfBlocks<512)   // Chip Size<256Mb
		{
			tData[0] = W25Q_PAGE_PROGRAM;  // 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] = W25Q_PAGE_PROGRAM_4B;  // 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++] = buffer[i+dataPosition];
		}

		if (bytestosend > 250)
		{
			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;

		W25Q_Waitforwrite();
		write_disable();

	}

}

void flash_ReadMemory (uint32_t Addr, uint32_t Size, uint8_t* buffer)
{
	uint32_t page = Addr/256;
	uint16_t offset = Addr%256;

	W25Q_FastRead(page, offset, Size, buffer);
}

void flash_SectorErase(uint32_t EraseStartAddress, uint32_t EraseEndAddress)
{
	uint16_t startSector  = EraseStartAddress/4096;
	uint16_t endSector  = EraseEndAddress/4096;
	uint16_t numSectors = endSector-startSector+1;
	for (uint16_t i=0; i<numSectors; i++)
	{
		W25Q_Erase_Sector(startSector++);
	}
}

void flash_ChipErase (void)
{
	W25Q_Chip_Erase();
}

void flash_Reset (void)
{
	W25Q_Reset();
}

Next, we shall start developing the source code for the external loader and get the data into our chip.

Stay tuned and happy coding 🙂

Add Comment

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