快闪存储器(英语:flash memory),是一种电子式可清除程序化只读存储器的形式,允许在操作中被多次擦或写的存储器。这种科技主要用于一般性资料存储,以及在电脑与其他数字产品间交换传输资料,如储存卡与U盘。闪存是一种特殊的、以宏块抹写的 EEPROM 。单片机的代码都存储在内部 flash ,mm32f3277 属于中容量产品, 其flash被划分为128个扇区,每个扇区四页,每页 1K 字节,共 512K Flash, 完全可以用于存储 GPS 坐标点信息。
GPS 信息中的经纬度坐标进行度分秒格式转换后为浮点型数据,而官方提供的 FLASH 写入函数传参是三十二位无符号整型数据,所以坐标经纬度无法直接写入 FLASH 中,因此参照网络上大多数算法后决定使用 c 语言中的共用体进行数据类型变换,具体实现过程如下。
unsigned long ex_float2int(float value) //浮点型转换为长整型
{
// 定义共用体
union{
float float_value;
unsigned long int_value;
}c;
// 存储数据转换
c.float_value = value;//以浮点型存入共用体
return c.int_value;//以整型返回
}
共用体是一种特殊的数据类型,有时也被称为联合或者联合体,共用体允许在相同的内存位置存储不同的数据类型。用户可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。
简单点讲也就是共用体的所有成员占用一个内存,修改一个成员会影响其余所有成员,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉,其内存大小取决于最长的成员占用的内存,如上述程序的联合体占用大小为四个字节( 32 bits )
当我们将函数的传入参数 value 赋值给联合体中的成员变量 float_value 时,相当于是向联合体所占的内存空间中写入了 value 的二进制表达形式。
运行如下图:
虽然以整型读出和以浮点型读出的数值不同,但二者在内存空间中的二进制组合形式是相同的。
所以,不管是以无符号长整型变量读出,还是以浮点型变量读出,联合体的内存空间中保存的二进制组合都是不变的。所以我们只需把浮点型数据存入共用体中,之后再以无符号长整型将数据读出并交给转存数组保存,就成功绕过强制类型转换,可以直接写入 FLASH 了。
FLASH 的存储特点在于,必须先将需要写入的扇区全部擦除才能写入数据,即使只想改变其中一个值,也得把整片扇区全部擦除,才能把新内容写入,所以每次更改数据记得先校验有无数据,并将需要的数据保存后再擦除并写入。这里可直接调用逐飞提供的 FLASH 操作函数实现擦除,写入及读取。注意写入的扇区尽量靠后,靠前的扇区可能保存着程序代码。
//-------------------------------------------------------------------------------------------------------------------
// @brief 校验FLASH是否有数据
// @param sector_num 需要写入的扇区编号 参数范围 FLASH_SECTION_0-FLASH_SECTION_127
// @param page_num 当前扇区页的编号 参数范围 FLASH_PAGE_0-FLASH_PAGE_3
// @return 返回1有数据 返回0没有数据 如果需要对有数据的区域写入新的数据则应该对所在扇区进行擦除操作
// @since v1.0
// Sample usage: flash_check(FLASH_SECTION_127,FLASH_PAGE_3);
//-------------------------------------------------------------------------------------------------------------------
uint8 flash_check (FLASH_SECTION_enum sector_num, FLASH_PAGE_enum page_num)
{
uint16 temp_loop;
uint32 flash_addr = ((FLASH_BASE_ADDR+FLASH_SECTION_SIZE*sector_num+FLASH_PAGE_SIZE*page_num)); // 提取当前 Flash 地址
for(temp_loop = 0; temp_loop < FLASH_PAGE_SIZE; temp_loop+=4) // 循环读取 Flash 的值
{
if( (*(__IO u32*) (flash_addr+temp_loop)) != 0xffffffff ) // 如果不是 0xffffffff 那就是有值
return 1;
}
return 0;
}
//-------------------------------------------------------------------------------------------------------------------
// @brief 擦除扇区
// @param sector_num 需要写入的扇区编号 参数范围 FLASH_SECTION_0-FLASH_SECTION_127
// @param page_num 当前扇区页的编号 参数范围 FLASH_PAGE_0-FLASH_PAGE_3
// @return 返回1有表示失败 返回0表示成功
// @since v1.0
// Sample usage: flash_erase_page(FLASH_SECTION_127, FLASH_PAGE_3);
//-------------------------------------------------------------------------------------------------------------------
uint8 flash_erase_page (FLASH_SECTION_enum sector_num, FLASH_PAGE_enum page_num)
{
static volatile FLASH_Status gFlashStatus = FLASH_COMPLETE;
uint32 flash_addr = ((FLASH_BASE_ADDR+FLASH_SECTION_SIZE*sector_num+FLASH_PAGE_SIZE*page_num)); // 提取当前 Flash 地址
FLASH_Unlock(); // 解锁 Flash
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); // 清除操作标志
gFlashStatus = FLASH_ErasePage(flash_addr); // 擦除
FLASH_ClearFlag(FLASH_FLAG_EOP ); // 清楚操作标志
FLASH_Lock(); // 锁定 Flash
if(gFlashStatus != FLASH_COMPLETE) // 判断操作是否成功
return 1;
return 0;
}
//-------------------------------------------------------------------------------------------------------------------
// @brief 读取一页
// @param sector_num 需要读取的扇区编号 参数范围 FLASH_SECTION_0-FLASH_SECTION_127
// @param page_num 当前扇区页的编号 参数范围 FLASH_PAGE_0-FLASH_PAGE_3
// @param buf 需要读取的数据地址 传入的数组类型必须为uint32
// @param len 需要写入的数据长度 参数范围 1-256
// @return 返回1有表示失败 返回0表示成功
// @since v1.0
// Sample usage: flash_page_read(FLASH_SECTION_127, FLASH_PAGE_3, data_buffer, 256);
//-------------------------------------------------------------------------------------------------------------------
void flash_page_read (FLASH_SECTION_enum sector_num, FLASH_PAGE_enum page_num, uint32 *buf, uint16 len)
{
uint16 temp_loop;
uint32 flash_addr = ((FLASH_BASE_ADDR+FLASH_SECTION_SIZE*sector_num+FLASH_PAGE_SIZE*page_num)); // 提取当前 Flash 地址
for(temp_loop = 0; temp_loop < len; temp_loop++) // 根据指定长度读取
{
*buf++ = *(__IO u32*)(flash_addr+temp_loop*4); // 循环读取 Flash 的值
}
}
//-------------------------------------------------------------------------------------------------------------------
// @brief 编程一页
// @param sector_num 需要写入的扇区编号 参数范围 FLASH_SECTION_0-FLASH_SECTION_127
// @param page_num 当前扇区页的编号 参数范围 FLASH_PAGE_0-FLASH_PAGE_3
// @param buf 需要写入的数据地址 传入的数组类型必须为uint32
// @param len 需要写入的数据长度 参数范围 1-256
// @return 返回1有表示失败 返回0表示成功
// @since v1.0
// Sample usage: flash_page_program(FLASH_SECTION_127, FLASH_PAGE_3, data_buffer, 256);
//-------------------------------------------------------------------------------------------------------------------
uint8 flash_page_program (FLASH_SECTION_enum sector_num, FLASH_PAGE_enum page_num, const uint32 *buf, uint16 len)
{
static volatile FLASH_Status gFlashStatus = FLASH_COMPLETE;
uint32 flash_addr = ((FLASH_BASE_ADDR+FLASH_SECTION_SIZE*sector_num+FLASH_PAGE_SIZE*page_num)); // 提取当前 Flash 地址
if(flash_check(sector_num, page_num)) // 判断是否有数据 这里是冗余的保护 防止有人没擦除就写入
flash_erase_page(sector_num, page_num); // 擦除这一页
FLASH_Unlock(); // 解锁 Flash
while(len--) // 根据长度
{
gFlashStatus = FLASH_ProgramWord(flash_addr, *buf++); // 按字 32bit 写入数据
if(gFlashStatus != FLASH_COMPLETE) // 反复确认操作是否成功
return 1;
flash_addr += 4; // 地址自增
}
FLASH_Lock(); // 锁定 Flash
return 0;
}
将采集到的数据写入并读出的效果如下:
其中每行前四位为经度, 后四位为维度信息, 一行就是一个坐标点,由此可得一个扇区每页最多可写入128个坐标点信息,容量足够。
这一步比较简单,只需要仿照浮点型转换整型的步骤即可。
float ex_int2float(unsigned long value) //长整型转换为浮点型
{
// 定义共用体
union{
float float_value;
unsigned long int_value;
}c;
// 存储数据转换
c.int_value = value;//以长整型存入共用体
return c.float_value;//以浮点型返回
}