我的小车调试进度之:实现参数存储

it2022-05-06  6

小车调试进度五更~~


emmm,小车主控板上没有画24C02,不能用EEPROM来存参数,但STM32的内部Flash是可访问可操作的,所以就可以把参数存到stm32内部的Flash里啦~~


先推荐两篇博客,原理说的很清楚啦~ STM32学习笔记:读写内部Flash。 STM32操作访问flash,包括写入数据到flash和从flash读取数据


明确你的MCU内部Flash的大小和地址空间 这个可以看对应芯片的手册,上面都有详细的说明: 128KB的Flash的地址分布: 我用的MCU是C8T6,内部Flash是64KB,内部Flash的起始地址是0x08000000,那么对应的尾地址就是0x0800FFFF,具体计算方法: 1KB = 2的10次方; 64KB = 2的16次方,16个1,每4个1是一个F,所以就是FFFF 64KB大小占4个扇区空间:

明确Flash内存储的程序所占的地址空间 我们知道单片机内部Flash是用来存储程序和一些const常量的,在向Flash内部写数据时必须先擦除这部分的地址空间,而Flash内部规定必须以扇为单位进行擦除,存储程序的那一部分的地址空间显然是不能动的,我们在向Flash内部写入数据时需要避开存储程序的扇区,存储程序的地址空间可以在.map文件中找到(查找以“Memory Map of the image”开头的记录): 以我的程序为例,程序存储空间是以程序ROM加载空间来计算的:基地址是0x08000000,大小(size)是:0x00003C34,所以程序存储空间就是:0x08000000到0x08000000+0x00003C34也就是:0x08000000~0x08003c34,这个范围是位于扇区0内的,所以扇区1以后的存储空间我们都可以作为数据的存储空间。(emmm,为了保险起见也可以将数据存储的地址向后推一个扇区,我的程序中定义的存储基地址是:0X0800E000)

关于底层的部分我就不多说明了,可以参考STM32F1开发指南(库函数版)中实验三十九:Flash模拟EEPROM的部分,有详细的说明,这里我贴出代码:

//底层部分 #include "stmflash.h" #include "delay.h" #define FLASH_SAVE_ADDR 0X0800E000 //设置FLASH保存地址(必须为偶数,且其值要大于本代码所占用FLASH的大小+0X08000000) //解锁STM32的FLASH void STMFLASH_Unlock(void) { FLASH->KEYR=FLASH_KEY1;//写入解锁序列. FLASH->KEYR=FLASH_KEY2; } //flash上锁 void STMFLASH_Lock(void) { FLASH->CR|=1<<7;//上锁 } //得到FLASH状态 u8 STMFLASH_GetStatus(void) { u32 res; res=FLASH->SR; if(res&(1<<0))return 1; //忙 else if(res&(1<<2))return 2; //编程错误 else if(res&(1<<4))return 3; //写保护错误 return 0; //操作完成 } //等待操作完成 //time:要延时的长短 //返回值:状态. u8 STMFLASH_WaitDone(u16 time) { u8 res; do { res=STMFLASH_GetStatus(); if(res!=1)break;//非忙,无需等待了,直接退出. delay_us(1); time--; }while(time); if(time==0)res=0xff;//TIMEOUT return res; } //擦除页 //paddr:页地址 //返回值:执行情况 u8 STMFLASH_ErasePage(u32 paddr) { u8 res=0; res=STMFLASH_WaitDone(0X5FFF);//等待上次操作结束,>20ms if(res==0) { FLASH->CR|=1<<1;//页擦除 FLASH->AR=paddr;//设置页地址 FLASH->CR|=1<<6;//开始擦除 res=STMFLASH_WaitDone(0X5FFF);//等待操作结束,>20ms if(res!=1)//非忙 { FLASH->CR&=~(1<<1);//清除页擦除标志. } } return res; } //在FLASH指定地址写入半字 //faddr:指定地址(此地址必须为2的倍数!!) //dat:要写入的数据 //返回值:写入的情况 u8 STMFLASH_WriteHalfWord(u32 faddr, u16 dat) { u8 res; res=STMFLASH_WaitDone(0XFF); if(res==0)//OK { FLASH->CR|=1<<0;//编程使能 *(vu16*)faddr=dat;//写入数据 res=STMFLASH_WaitDone(0XFF);//等待操作完成 if(res!=1)//操作成功 { FLASH->CR&=~(1<<0);//清除PG位. } } return res; } //读取指定地址的半字(16位数据) //faddr:读地址 //返回值:对应数据. u16 STMFLASH_ReadHalfWord(u32 faddr) { return *(vu16*)faddr; } #if STM32_FLASH_WREN //如果使能了写 //不检查的写入 //WriteAddr:起始地址 //pBuffer:数据指针 //NumToWrite:半字(16位)数 void STMFLASH_Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite) { u16 i; for(i=0;i<NumToWrite;i++) { STMFLASH_WriteHalfWord(WriteAddr,pBuffer[i]); WriteAddr+=2;//地址增加2. } } //从指定地址开始写入指定长度的数据 //WriteAddr:起始地址(此地址必须为2的倍数!!) //pBuffer:数据指针 //NumToWrite:半字(16位)数(就是要写入的16位数据的个数.) #if STM32_FLASH_SIZE<256 #define STM_SECTOR_SIZE 1024 //字节 #else #define STM_SECTOR_SIZE 2048 #endif u16 STMFLASH_BUF[STM_SECTOR_SIZE/2];//最多是2K字节 void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite) { u32 secpos; //扇区地址 u16 secoff; //扇区内偏移地址(16位字计算) u16 secremain; //扇区内剩余地址(16位字计算) u16 i; u32 offaddr; //去掉0X08000000后的地址 if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址 STMFLASH_Unlock(); //解锁 offaddr=WriteAddr-STM32_FLASH_BASE; //实际偏移地址. secpos=offaddr/STM_SECTOR_SIZE; //扇区地址 0~127 for STM32F103RBT6 secoff=(offaddr%STM_SECTOR_SIZE)/2; //在扇区内的偏移(2个字节为基本单位.) secremain=STM_SECTOR_SIZE/2-secoff; //扇区剩余空间大小 if(NumToWrite<=secremain)secremain=NumToWrite;//不大于该扇区范围 while(1) { STMFLASH_Read(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容 for(i=0;i<secremain;i++)//校验数据 { if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;//需要擦除 } if(i<secremain)//需要擦除 { STMFLASH_ErasePage(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE);//擦除这个扇区 for(i=0;i<secremain;i++)//复制 { STMFLASH_BUF[i+secoff]=pBuffer[i]; } STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区 }else STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);//写已经擦除了的,直接写入扇区剩余区间. if(NumToWrite==secremain)break;//写入结束了 else//写入未结束 { secpos++; //扇区地址增1 secoff=0; //偏移位置为0 pBuffer+=secremain; //指针偏移 WriteAddr+=secremain*2; //写地址偏移(16位数据地址,需要*2) NumToWrite-=secremain; //字节(16位)数递减 if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完 else secremain=NumToWrite;//下一个扇区可以写完了 } }; STMFLASH_Lock();//上锁 } #endif //从指定地址开始读出指定长度的数据 //ReadAddr:起始地址 //pBuffer:数据指针 //NumToWrite:半字(16位)数 void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead) { u16 i; for(i=0;i<NumToRead;i++) { pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);//读取2个字节. ReadAddr+=2;//偏移2个字节. } } //读写PWM值 /// //读写PWM值 extern u8 Flash_PWM[2]; extern u8 PWM[2]; extern u8 PWML; extern u8 PWMR; void Test_Write(u32 WriteAddr,u16 WriteData) { STMFLASH_Write(WriteAddr,&WriteData,1);//写入一个字 } void Flash_Read(void) { STMFLASH_Read(FLASH_SAVE_ADDR,(u16*)PWM,2); PWML = PWM[0]; PWMR = PWM[1]; } void Flash_Write(void) { Flash_PWM[0]=PWML; Flash_PWM[1]=PWMR; STMFLASH_Write(FLASH_SAVE_ADDR,(u16*)Flash_PWM,2); }

一点说明: Flash_Read();读取函数只在主函数开头调用,Flash_Write();写入函数在PWM值改变时写入,也就是按键按下时写入。


最新回复(0)