STM32F1 -内部FLASH编程
STM32的FLASH不但可以存储程序,而且还是可以当EEPROM用。32的FLASH一般都比较大,FLASH的前面部分可以放代码,而最后几页可以存储数据,用于掉电记忆还是挺不错的。
STM32的FLASH是按页类操作的,也就是说每次擦除都必须整页擦除,而不能只擦除一页的一部分,读数据的话不存在这种限制。大容量的芯片每页是2k,而小容量的芯片每页是1k。
STM32的FLASH地址是从0x08000000开始的。比如要操作大容量512k芯片的最后一页FLASH,那么地址是0x08000000+2048*255.其中0x08000000是FLASH的起始地址,512k的芯片共有256页,每页2k,所以地址就是起始地址+前面255页的大小。比如操作64k的小容量的芯片的最后一页,那么地址就是0x08000000+1024*63.因为小容量芯片每页只有1k,所以地址=起始地址+前面63页 大小。
在利用FLASH存储数据的过程中发现一个小问题,存储浮点数精度会丢失,所以只好把浮点数放大再存储,读出来的时候再还原回去。在利用库函数操作的时候,发现库函数的底层会把数据强制转换为无符号整型才存储进FLASH里面,但是如果把负数存储进去,读出来的时候还是负数,就好像强制转换语句失效了一样,其实库函数是没问题的,这个和正数负数在内存中的存储结构有关,在这里不过多的解释。
在STM32的技术手册里是找不到FLASH的相关寄存器和操作的,在《STM32的FLASH编程》里面才有。操作STM32的FLASH如果用寄存器的话是非常麻烦的,而且还非常容易出错,但是如果用库操作的话会非常简单,比如关键字、延时什么的库都帮我们做好了,我们只要调用接口就行了。
关于FLASH的时钟,有些资料说要打开内部高速时钟才能操作FLASH,但是我没打开打开内部高速时钟好像也可以读写FLASH。
对于FLASH的读是非常简单的,直接读地址就得了,而且还没有页操作的限制
A=*(vu32*)addr;//读取addr地址的数据
对于FLASH的写会比较复杂点,但是库函数已经帮我们简化了
第一步,FLASH是有锁的,所以得先开锁
FLASH_Unlock(); //解锁FLASH编程擦除控制器
第二步;擦除FLASH,在写之前得先擦除,至少擦除一页,也可以全部擦除,当然在擦除前先清一下状态位还是比较好的
FLASH_ClearFlag(FLASH_FLAG_BSY|FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR);//把所有状态都清零
FLASH_ErasePage(addr);//擦除页,一次性擦除2k
第三步;把数据写入FLASH,可以一次性写入16位(半字),也可以一次性写入32位(全字),其实全字写入函数在底层也是进行两次半字写入。
FLASH_ProgramWord(addr,writeData);
// FLASH_ProgramWord(addr+4,writeData);//因为写的是一个32位的数据,每个数据占用内存为4b,所以下一个数据的地址要+4
// FLASH_ProgramWord(addr+8,writeData);//如果数据多的话按照这个格式去写就好了
第四步;数据写完后得上锁,在上锁前最好清零下状态位
FLASH_ClearFlag(FLASH_FLAG_BSY|FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR);//把所有状态都清零
FLASH_Lock(); //上锁
到此,写操作就完成了
本实验用的是正点原子的精英板,大容量512k FLASH
功能;从FLASH读取数据并发送到电脑,并把从电脑发送来的数据存储进FLASH里
每次上电后电脑收到的数据就是断电前电脑最后一次向单片机发送的数据,这样就实现了掉电记忆的功能
因为编码的问题,返回的好像是ASCII码。这个问题不纠结。
代码如下;
#include "sys.h"
#include "delay.h"
#include "stdio.h"
/************************************************************
功能;从FLASH读取数据并发送到电脑,把从电脑发送来的数据存储进FLASH里
这样每次上电后电脑收到的数据就是断电前电脑最后一次向单片机发送的数据
****************************************************************/
#define FLASH_ADDR 0x08000000+2048*255
//0x08000000为FLASH的起始地址,2048为每一页的大小,为2k,很明显,现在是操作FLASH的最后一页
void init__uart1();//串口函数声明
/**************************************************************
功能;向FLASH指定地址写一个32位的数据
入口1;要写入的数据
入口2;数据写入的地址
说明;这里并没有把地址所在的页的其他数据保存下来,所以写数据时会把地址页的其他数据全部清空
当然项目中不能这样搞,但是作为一篇总结这样写对于FLASH的操作步骤还是说明的挺好的
****************************************************************/
void my_writeFlash(uint32_t writeData,uint32_t addr)
{
FLASH_Unlock(); //解锁FLASH编程擦除控制器
FLASH_ClearFlag(FLASH_FLAG_BSY|FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR);//把所有状态都清零
FLASH_ErasePage(addr);//擦除页,一次性擦除2k
FLASH_ProgramWord(addr,writeData);
// FLASH_ProgramWord(addr+4,writeData);//因为写的是一个32位的数据,每个数据占用内存为4b,所以下一个数据的地址要+4
// FLASH_ProgramWord(addr+8,writeData);//如果数据多的话按照这个格式去写就好了
FLASH_ClearFlag(FLASH_FLAG_BSY|FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR);//把所有状态都清零
FLASH_Lock(); //上锁
}
/*******************************
功能;从指定地址读取一个32位的数据
入口;无
返回值;读取到的32位的数据
***********************************/
uint32_t my_readFlash(uint32_t addr)
{
return *(vu32*)addr;
}
//*主函数*******************************************************************
int main(void)
{
uint32_t flash_data=0;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
init__uart1();//串口1初始化
printf("hello world\r\n");//为了测试printf函数是否能用
while(1)
{
//从FLASH读取数据并发送到电脑
flash_data=my_readFlash(FLASH_ADDR);
printf("flash_data=%d\r\n",flash_data);//因为编码格式的问题,发送的数据和接收到的数据可能不一样
delay_ms(1000);//间隔一秒钟读取一次吧
}
}
//串口中断处理函数
void USART1_IRQHandler(void)
{
char re_data=0;//为了接收字符,还是定义为字符类型吧
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==1)//确认下是不是串口1接收中断
{
re_data=USART_ReceiveData(USART1); //接收数据
my_writeFlash(re_data,FLASH_ADDR);//把数据存储进FLASH
}
//这个中断是不需要手动清除标志位的,因为读取数据后接收标志位会自动清零
}
void init__uart1()
{
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
// 串口IO配置,PA9,PA10
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//IO时钟打开
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;//IO方式具体看《中文手册》8.1.11章节
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;//IO方式具体看《中文手册》8.1.11章节
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
//配置串口1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//打开串口时钟
USART_InitStruct.USART_BaudRate=115200; //波特率115200
USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //无硬件流
USART_InitStruct.USART_Mode=USART_Mode_Rx|USART_Mode_Tx; //接收和发送都使能
USART_InitStruct.USART_Parity=USART_Parity_No; //无奇偶校验
USART_InitStruct.USART_StopBits=USART_StopBits_1; //停止位1位
USART_InitStruct.USART_WordLength=USART_WordLength_8b; //数据长度8位
USART_Init(USART1,&USART_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel=USART1_IRQn;//虽然知道这个参数的意思,但是还真不知道这个参数放在哪里
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;//使能通道中断
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority=2;//子优先级
NVIC_Init(&NVIC_InitStruct);//这个函数在misc.c文件里
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//打开串口中断,第二个参数是选择中断类型,这里打开的是接收中断
USART_Cmd(USART1,ENABLE); //配置完成后一定要记得使能串口
}
//重定义fputc函数 ,想要使用printf函数得添加这个函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}