GD32的flash特征
1、在flash的前512K字节空间内,CPU执行指令零等待;在此范围外,CPU读取指令存在较长延时;
2、对于flash大于1024KB(不包括等于1024KB)的,使用了两片闪存;前1024KB容量在第一片闪存(bank0)中,后续的容量在第二片闪存(bank1)中;
3、对于flash容量小于等于1024KB,只使用了bank0;
4、支持32位整字或16位半字编程,页擦除和整片擦除操作;
GD32 flash内部结构
bank0 内存分布入下
bank1内存分布如下
GD32 flash读函数
flash可以想普通空间一样直接地址访问内存
value = *(volatile u32 *)flash_addr;
u32 IapFlashRead(u32 addr)
{
return (*(volatile u32 *)addr);
}
GD32 flash 擦除函数
由于GD32 flash只支持四种擦除模式:整体擦除,bank0擦除,bank1擦除,页擦除,我们在实际读写flash过程中使用最多的还是页擦除
这里我们可以直接调用GD32的库函数进行擦除
/*!
\brief erase sector
\param[in] fmc_sector: select the sector to erase
\arg CTL_SECTOR_NUMBER_0: sector 0
\arg CTL_SECTOR_NUMBER_1: sector 1
\arg CTL_SECTOR_NUMBER_2: sector 2
\arg CTL_SECTOR_NUMBER_3: sector 3
\arg CTL_SECTOR_NUMBER_4: sector 4
\arg CTL_SECTOR_NUMBER_5: sector 5
\arg CTL_SECTOR_NUMBER_6: sector 6
\arg CTL_SECTOR_NUMBER_7: sector 7
\arg CTL_SECTOR_NUMBER_8: sector 8
\arg CTL_SECTOR_NUMBER_9: sector 9
\arg CTL_SECTOR_NUMBER_10: sector 10
\arg CTL_SECTOR_NUMBER_11: sector 11
\arg CTL_SECTOR_NUMBER_12: sector 12
\arg CTL_SECTOR_NUMBER_13: sector 13
\arg CTL_SECTOR_NUMBER_14: sector 14
\arg CTL_SECTOR_NUMBER_15: sector 15
\arg CTL_SECTOR_NUMBER_16: sector 16
\arg CTL_SECTOR_NUMBER_17: sector 17
\arg CTL_SECTOR_NUMBER_18: sector 18
\arg CTL_SECTOR_NUMBER_19: sector 19
\arg CTL_SECTOR_NUMBER_20: sector 20
\arg CTL_SECTOR_NUMBER_21: sector 21
\arg CTL_SECTOR_NUMBER_22: sector 22
\arg CTL_SECTOR_NUMBER_23: sector 23
\arg CTL_SECTOR_NUMBER_24: sector 24
\arg CTL_SECTOR_NUMBER_25: sector 25
\arg CTL_SECTOR_NUMBER_26: sector 26
\arg CTL_SECTOR_NUMBER_27: sector 27
\arg CTL_SECTOR_NUMBER_28: sector 28
\arg CTL_SECTOR_NUMBER_29: sector 29
\arg CTL_SECTOR_NUMBER_30: sector 30
\param[out] none
\retval fmc_state_enum
*/
fmc_state_enum fmc_sector_erase(uint32_t fmc_sector)
{
fmc_state_enum fmc_state = FMC_READY;
/* wait for the FMC ready */
fmc_state = fmc_ready_wait();
if(FMC_READY == fmc_state){
/* start sector erase */
FMC_CTL &= ~FMC_CTL_SN;
FMC_CTL |= (FMC_CTL_SER | fmc_sector);
FMC_CTL |= FMC_CTL_START;
/* wait for the FMC ready */
fmc_state = fmc_ready_wait();
/* reset the SER bit */
FMC_CTL &= (~FMC_CTL_SER);
FMC_CTL &= ~FMC_CTL_SN;
}
/* return the FMC state */
return fmc_state;
}
这里我们发现传参是内部定义好的宏,我们只需要将需要写入的地址先进行判断所处页上再将该页进行整体擦除即可,擦除时记得解除flash写保护
/*==============================================================================
函数名 : IapGetSecNum
功能 : 根据地址得到扇区号
输入参数说明: addr: 地址
返回值说明 : 扇区号,这里这个函数写的稍微有点复杂了
------------------------------------------------------------------------------*/
static uint32_t IapGetSecNum (uint32_t addr)
{
if((addr >= ADDR_FLASH_SECTOR_0) && (addr < ADDR_FLASH_SECTOR_1))
return 0;
else if((addr >= ADDR_FLASH_SECTOR_1) && (addr < ADDR_FLASH_SECTOR_2))
return 1;
else if((addr >= ADDR_FLASH_SECTOR_2) && (addr < ADDR_FLASH_SECTOR_3))
return 2;
else if((addr >= ADDR_FLASH_SECTOR_3) && (addr < ADDR_FLASH_SECTOR_4))
return 3;
else if((addr >= ADDR_FLASH_SECTOR_4) && (addr < ADDR_FLASH_SECTOR_5))
return 4;
else if((addr >= ADDR_FLASH_SECTOR_5) && (addr < ADDR_FLASH_SECTOR_6))
return 5;
else if((addr >= ADDR_FLASH_SECTOR_6) && (addr < ADDR_FLASH_SECTOR_7))
return 6;
else if((addr >= ADDR_FLASH_SECTOR_8) && (addr < ADDR_FLASH_SECTOR_9))
return 8;
else if((addr >= ADDR_FLASH_SECTOR_9) && (addr < ADDR_FLASH_SECTOR_10))
return 9;
else if((addr >= ADDR_FLASH_SECTOR_11) && (addr < ADDR_FLASH_SECTOR_12))
return 11;
else if((addr >= ADDR_FLASH_SECTOR_12) && (addr < ADDR_FLASH_SECTOR_13))
return 12;
else if((addr >= ADDR_FLASH_SECTOR_13) && (addr < ADDR_FLASH_SECTOR_14))
return 13;
else if((addr >= ADDR_FLASH_SECTOR_14) && (addr < ADDR_FLASH_SECTOR_15))
return 14;
return 0;
}
/*==============================================================================
函数名 : IapEraseSector
功能 : 擦除相关扇区
输入参数说明: addr_start: 起始地址
addr_end: 结束地址
------------------------------------------------------------------------------*/
fmc_state_enum IapEraseSector(uint32_t addr_start, uint32_t addr_end)
{
printf("erase\r\n");
uint32_t start_sec, end_sec;
uint32_t i = 0;
start_sec = IapGetSecNum(addr_start);
end_sec = IapGetSecNum(addr_end);
fmc_unlock(); //解锁flash保护
for(i = start_sec; i <= end_sec; i++)
{
if((i >= 12) && (i <= 23))
{
if((fmc_sector_erase(CTL_SN(i + 4))) != FMC_READY)
{
return FMC_WPERR;
}
}
else if((i >= 24) && (i <= 27))
{
if((fmc_sector_erase(CTL_SN(i - 12))) != FMC_READY)
{
return FMC_WPERR;
}
}
else
{
if((fmc_sector_erase(CTL_SN(i))) != FMC_READY)
{
return FMC_WPERR;
}
}
}
fmc_luck(); //flash上锁
return FMC_READY;
}
GD32 flash写函数
在写入数据时,需要先调用擦除函数进行页的擦除,关于写flash的函数,GD32提供了3个格式的写入分别时32位整字写入,16位半字写入,8位字节写入,这里我们采用32位整字写入
#define IAP_FLASH_PAGE_SIZE 4
/*==============================================================================
函数名 : IapFlashWrite
功能 : 写入FLASH
输入参数说明:
dwAddr: 写入Flash的起始地址, 从0开始的偏移地址
dwLen : 写入数据的字节数
pbyBuf: 写入数据的buf, 地址必须4字节对齐
返回值说明 : 成功返回0; 失败返回-1或错误码
------------------------------------------------------------------------------*/
s32 IapFlashWrite(u32 dwAddr, u32 dwLen, u8 *pbyBuf)
{
fmc_state_enum tRtn;
/* 写入数据的字节数,必须是4字节,即一个page的大小,不足则补齐 */
if (dwLen % IAP_FLASH_PAGE_SIZE)
{
dwLen = (dwLen / IAP_FLASH_PAGE_SIZE + 1) * IAP_FLASH_PAGE_SIZE;
}
if (((u32)pbyBuf) % 4)
return -4;
fmc_unlock();
while (dwLen)
{
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_OPERR | FMC_FLAG_WPERR | \
FMC_FLAG_PGMERR | FMC_FLAG_PGSERR);
tRtn = fmc_word_program(dwAddr, *(u32 *)pbyBuf);
if (tRtn != FMC_READY)
{
IAP_ERR("failed, rtn=%d\n", tRtn);
return -5;
}
dwAddr += IAP_FLASH_PAGE_SIZE;
pbyBuf += IAP_FLASH_PAGE_SIZE;
dwLen -= IAP_FLASH_PAGE_SIZE;
}
fmc_lock();
return 0;
}