文章目录
为什么要写这篇
有些断电保存的功能需要操作FLASH,固件更新的功能也需要操作FLASH,当意识到它的重要性后,投入一些关注是自然而然的事情。
熟悉一个项目所用到的芯片,了解到FLASH相关能用的只有5个底层函数:
void FMC_Open(void); /* Enable FMC function */
int32_t FMC_Erase(uint32_t u32PageAddr); /* Erase a page, page size if 512 bytes */
uint32_t FMC_Read(uint32_t u32Addr); /* Read a word from specified flash address */
void FMC_Write(uint32_t u32Addr, uint32_t u32Data); /* Writes a word data to specified flash address */
void FMC_Close(void); /* Disable FMC function */
公司有人写了一个FLASH的封装,我敲了一下代码,基本理解其要干什么,然后考虑我能给它添加什么改动。
单字节读取
源代码
uint8_t fmc_read_byte(uint32_t addr)
{
uint32_t offset_addr = addr - (addr & 3); //addr-(addr%4)
union_t f_data;
uint8_t ret;
if (addr < boot_flag_addr || addr > APROM_END_ADDR ) return 0xFF;
FMC_Open();
f_data.four[0] = FMC_Read(offset_addr);
FMC_Close();
ret = f_data.one[addr & 3];
return ret;
}
源代码分析
了解到32位单片机的FLASH地址是4字节跳跃式递增的,每个地址能存储4字节的数值。要读取FLASH中某一个地址的字节,需要先4字节对齐得到地址,读取该地址的4字节数据中对应字节,返回。
uint8_t fmc_read_byte(uint32_t addr)
{
/* 将地址4字节对齐后获得新的地址 */
/* 地址合法性检测 */
/* 使能FLASH */
/* 读取4字节 */
/* 失能FLASH */
/* 返回对应地址的数据(一个字节) */
}
他用到的这种方式感觉很任性,随时占用512字节的连续空间作为变量。
#define FLASH_PAGE_SIZE 512
typedef union
{
uint32_t four[FLASH_PAGE_SIZE >> 2];
uint16_t two[FLASH_PAGE_SIZE >> 1];
uint8_t one[FLASH_PAGE_SIZE];
} union_t;
理解要实现目的后我的写法
typedef union
{
uint32_t four_bytes;
uint8_t byte[4];
} BYTE;
uint8_t fmc_read_byte(uint32_t addr)
{
if (addr < boot_flag_addr || addr > APROM_END_ADDR) return ;
uint32_t start_addr = (addr >> 2) << 2; /* Four byte alignment */
BYTE temp_val;
FMC_Open();
temp_val.four_bytes = FMC_Read(start_addr);
FMC_Close();
return temp_val.byte[addr - start_addr];
}
一些说明
关于联合体的使用方式可能需要说明一下,涉及到大小端模式。
对于小端模式(我用的电脑和单片机都是小端模式)
BYTE temp_val;
temp_val.four_bytes = 0x08070605;
小端模式为 :低地址存储低字节
temp_val.byte[0] 和 temp_val.byte[1] 地址对比,下标小的0 为低地址
temp_val.four_bytes 的0x05 和 0x06 字节对比, 表示数值小的0x05(0x05 * (160) ) 为低字节
我的改进措施
- 通过右移2位(除4)去掉余数得商,再左移2位(乘4)得到 4字节对齐后地址
- 读取的起始地址的4字节,返回的是地址偏移量的字节,可读性较好
- 使用占用4字节的联合体,极大减少空间占用
我的担心
使用右移左移的方式我担心会被编译器优化掉,验证方式有三种:
- 看编译后汇编代码。假设说addr