mao

Reading/Writing external Flash(SPI, NOR) with STM32F4 MCU

Discussion created by mao on Jul 1, 2017
Latest reply on Jul 3, 2017 by Imen D

I am not asking question, instead posting a tutorial for accessing extern flash with stm32f4 chip. Maybe someone needs it. I was stucked a day for that.

 

MCU: stm32f411ret6

Flash: S25FL256SAGNFI000. it is a flash with 256Mb space.

Software: cubemx4.12 generated, pack F4 1.10. 

Keil development tool

Access of the external flash is via the SPI1 interface, set to 24Mb/s clock rate.

 

The code was generated by cubemx with dma enabled. Actually poling mode was used.

 

Normally WP  and hold were disabled by tiring them to VCC. 

CS pin has to be used to access the chip.

#define FLASH_CHIP_ENABLE HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_RESET) //PC9, LOW=cs SELECTED
#define FLASH_CHIP_DISABLE HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_SET) //PC9, HIGH=DISABLE

initialization code for spi interface

 

/* SPI1 init function */
void MX_SPI1_Init(void)
{
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLED;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLED;
hspi1.Init.CRCPolynomial = 10;
// HAL_SPI_Init(&hspi1);
if(HAL_SPI_Init(&hspi1) != HAL_OK)
{
/* Initialization Error */
Error_Handler("ERROR ON init of spi");
}
}

 

When access the flash chip via spi interface, the most important thing you have to remember is that you have to read the dummy data whenever you write to the interface. Whenever you need to read the data, you have to write data(dummy data) to receive data.  Ref: Chapter 9.4: SPI Flash Part 4 – Transfer SPI Data ... - Silicon Labs Community 

 

If you understand this, it is not difficult to implement the interface.

The first step to verify the access of the flash is reading the device ID and compare with datasheet.

 

//check if flash exists. return 0 if OK.
unsigned char VerifyFlashMemory(void)
{
//read flash device id to verify
uint8_t commands[10];
commands[0]=RDID;//read device id
FLASH_CHIP_ENABLE; //enable the chip, CS=0
if(HAL_SPI_TransmitReceive(&hspi1, commands,ReceiveTemp, 8, SPI_TIMEOUT_MAX) != HAL_OK)
{
Error_Handler("Read Device ID Error");
}
FLASH_CHIP_DISABLE; //pull CS pin to high voltage
if(ReceiveTemp[1]!=1)return 1; //byte0, device id should be 01h
if(ReceiveTemp[2]!=2)return 1; //byte1, 02h=256Mb
if(ReceiveTemp[3]!=0x19)return 1; //byte2,19h=256Mb
if(ReceiveTemp[4]!=0x4d)return 1; //byte3,4D
if(ReceiveTemp[5]!=0x01)return 1; //byte4,01h, 4-kb parameter sector with uniform 64kB
if(ReceiveTemp[6]!=0x80)return 1; //byte5,80h

return 0;

}

ReceiveTemp is a uint8_t array defined as global var.

RDID is a uint8_t value defined by manufacture.

//define commands
#define WREN 0x06 //writing enable
#define WRDI 0x04 //writing disable
#define READ4 0x13 //reading data
#define PP4 0x12 //page writing
#define ERESE4 0xdc //erase 64kB block, 4 byte addr
#define RDSR1 0x05 //read SR1 status register . last bit is WIP status bit
#define RDID 0x9f //device id

 

If you get the right value for device ID, it means hardware and spi protocol is clear to use.

 

Then implement erasing function. You have to erase the flash before you write data to it.  The flash is kind of a white paper. After you write something on it, you can't write anymore until you erase it. It is different from EEPROM and RAM.

 

//erase sectors. each sector has size of 64KB
void Master_EraseFlash(uint32_t flash_addr, uint16_t sectors)
{
//enable writing first
uint8_t commands[10]; // command to chip
commands[0]=WREN; //enable writing
FLASH_CHIP_ENABLE; //enable the chip, CS=0
if(HAL_SPI_TransmitReceive(&hspi1, commands,ReceiveTemp, 1, SPI_TIMEOUT_MAX) != HAL_OK)
{
Error_Handler("Enable writing error");
}
FLASH_CHIP_DISABLE; //must disable first to enable chip writing.


//now erase sectors.
for(uint16_t i=0;i<sectors;i++)
{
//erase sector
commands[0]=ERESE4;//erase sector
commands[1]=(flash_addr>>24)&0xff; //highest byte first
commands[2]=(flash_addr>>16)&0xff; //2nd byte
commands[3]=(flash_addr>>8)&0xff; //3rd byte
commands[4]=(flash_addr)&0xff; //last byte
FLASH_CHIP_ENABLE; //enable the chip, CS=0
if(HAL_SPI_TransmitReceive(&hspi1, commands, ReceiveTemp,5, SPI_TIMEOUT_MAX) != HAL_OK)
{
Error_Handler("Erase sector error");
}
FLASH_CHIP_DISABLE; //must disable first for command to take effect.

//check status
if(CheckIsWritingReadingComplete(30000)!=0)//in testing it needs 21370=2.1s for 1 sector. Erase of one sector may take 130ms
{
Error_Handler("Erase sector timeout");
}

//next flash addr
flash_addr+=0x10000; //64*1024; 64kB
}

}

Erasing of flash is a very slow process. You have to check if it is done. I don't know why it takes 2.1seconds to erase one sector, which is mush slower than the recommended(0.65s). If you knows it, please leave a comment behind.\

 

 

//write to flash. must be pages. Each page is 256 byte
void Master_WriteToFlash_Page(uint32_t flash_addr, uint8_t *pData, uint16_t pages)
{
//enable writing first
//send command to chip
uint8_t commands[10+PAGE_SIZE];
uint8_t RxDummy[2048];//max 2kB at a time

commands[0]=WREN;
FLASH_CHIP_ENABLE; //enable the chip, CS=0
if(HAL_SPI_TransmitReceive(&hspi1, commands,ReceiveTemp, 1, SPI_TIMEOUT_MAX) != HAL_OK)
{
Error_Handler("Enable writing error");
}
FLASH_CHIP_DISABLE; //must disable first to enable chip writing.
//start to write data to flash
for(uint16_t i=0;i<pages;i++)
{
//write addr , then send data
commands[0]=PP4;//page write
commands[1]=(flash_addr>>24)&0xff; //highest byte first
commands[2]=(flash_addr>>16)&0xff; //2nd byte
commands[3]=(flash_addr>>8)&0xff; //3rd byte
commands[4]=(flash_addr)&0xff; //last byte
//copy the data to array to send
for(uint16_t p0=0;p0<PAGE_SIZE;p0++)
{
commands[5+p0]=pData[p0];//copy to command array
}
FLASH_CHIP_ENABLE; //enable the chip, CS=0
if(HAL_SPI_TransmitReceive(&hspi1, commands,RxDummy,PAGE_SIZE+5,SPI_TIMEOUT_MAX) != HAL_OK)
{
Error_Handler("Writing Data To Flash Error");
}
FLASH_CHIP_DISABLE; //must disable first to enable chip writing.

//check status
if(CheckIsWritingReadingComplete(100)!=0)//10ms. we use poling mode to write data, should be done after calling the code
{
Error_Handler("wriing pate timeout");
}

flash_addr+=PAGE_SIZE;
pData+=PAGE_SIZE;//move to next page
}

}
//read data from flash. max 1024B at a time unless you increase buffer size
void Master_ReadFromFlash(uint32_t flash_addr, uint8_t *pData, uint16_t size)
{
//enable writing first
//send command to chip
uint8_t commands[10+PAGE_SIZE*4];
uint8_t RxDummy[10+PAGE_SIZE*4];//max 1kB at a time

commands[0]=READ4;//page read
commands[1]=(flash_addr>>24)&0xff; //highest byte first
commands[2]=(flash_addr>>16)&0xff; //2nd byte
commands[3]=(flash_addr>>8)&0xff; //3rd byte
commands[4]=(flash_addr)&0xff; //last byte
FLASH_CHIP_ENABLE; //enable the chip, CS=0
if(HAL_SPI_TransmitReceive(&hspi1, commands,RxDummy, size+5,SPI_TIMEOUT_MAX) != HAL_OK)
{
Error_Handler("Reading Flash Data Error");
}
FLASH_CHIP_DISABLE; //must disable first to enable chip writing.

//check status
if(CheckIsWritingReadingComplete(100)!=0)//10ms. we use poling mode to write data, should be done after calling the code
{
Error_Handler("Reading Data Timeout");
}

//transfer to destination buffer
for(uint16_t p0=0;p0<size;p0++)
{
pData[p0]=RxDummy[5+p0];//there are 5 dummy data in the Rx buffer
}
//done

}

 

I implement flash reading/writing. For flash writing, I impemented page writing. actually you don't have to write a complete page (256B for this chip) each time. You can write one byte each time. 

 

Finally I define a function to test the reading and writing.

 

//test erasing, writing and reading data for pages(should be <256 pages). return 0 if everything is right
unsigned char TestFlash(uint32_t flash_addr, uint16_t pages)
{
//first erase the sectors.
//decide which sector to test
uint16_t sector_starts=flash_addr/64/1024; //64kB
flash_addr=sector_starts*64*1024;
Master_EraseFlash(flash_addr,1);//only erase 1sector, 64kB for testing

//now start to test
uint16_t dataTest=0x0000;
for(uint16_t i=0;i<pages;i++)
{
//fill the buffer first with testing data
for(uint16_t p0=0;p0<PAGE_SIZE/2;p0++)
{
aTxMasterBuffer[2*p0]=dataTest&0xff;
aTxMasterBuffer[2*p0+1]=(dataTest>>8)&0xff;//higher byte
dataTest+=1;
}
//now write to flash
Master_WriteToFlash_Page(flash_addr,aTxMasterBuffer,1);//one page only
//read data to buffer
Master_ReadFromFlash( flash_addr, aRxBuffer,PAGE_SIZE);
if(Buffercmp(aTxMasterBuffer,aRxBuffer,PAGE_SIZE)==1)
{
Error_Handler("Writing Page/Reading Error");
}
//move to next page
flash_addr+=PAGE_SIZE;
}

}

 

For convenience, source codes are attached. You may find there is error when you use it, because error_handling(char text[]) was defined somewhere else. you have to define it or remove it.

 

Last important thing:

Increase the heap size and stack size, otherwise you will run into very ridiculous trouble. The txbuffer was modified for no reason. and it triggers problem when comparing data.

Stacksize 0x4000, Heapsize 0x4000 is large enough for most application.

In test of my code, set both to 0x400 wouldn't work.

Outcomes