51核和ARM核单片机OTA实战解析(二)
文章目录
- 摘要
- 三、OTA代码分析
- 3.1 DATA FLASH空间使用
- 3.2 OTA整个流程
- 3.2 OTA参数初始化
- 3.2 DATA FLASH写入数据
- 3.3 DATA FLASH读数据
摘要
本篇文章主要是从代码实现角度去进行分析。
三、OTA代码分析
3.1 DATA FLASH空间使用
前面一篇文章已经说了DATA FLASH和EEPROM作用是一样的。
而在OTA过程中主要是存放一些OTA升级标志位。
明确data flash大小
#define DATA_BUFF_ADDR 0X000 //Date Flash的起始位置#define DATA_BUFF_SIZE (0x1ff - DATA_BUFF_ADDR + 1) //Date Flash缓存区前512个字节
-
显式表达意图:
使用
(0x1ff - DATA_BUFF_ADDR + 1)
明确表示缓冲区大小是从起始地址0x000
到结束地址0x1ff
的连续空间。这种写法更直观地反映了内存布局的设计意图,便于其他开发者理解。 -
适应地址变更:
若未来
DATA_BUFF_ADDR
的起始地址需要调整(例如改为0x100
),DATA_BUFF_SIZE
会自动重新计算大小,而无需手动修改0x200
。这种动态计算减少了硬编码带来的维护风险。
1、初始化的时候读取Data Flash里面存储的数据,
需要传入参数:起始地址、接收数组的其实地址、数组的长度,
使用库函数,读取Data Flash存储的数据,逐个地址去读取。
根据数据手册可以得知
FLASH 存储器包含程序存储器(APROM)与非易失数据存储器(Data FLASH)。程序存储器空间最大为 64KB,分为 128个扇区,每个扇区包含 512B。数据存储器(Data FLASH)空间最大为 1KB,分为 2 个扇区,每个扇区包含 512B。
通过存储器模块接口,可对存储器进行读取/写入/擦除操作。存储器允许字节读写,写入时间由片上定时器控制,在写入新数据之前需确保该地址中的数据已被擦除。写入和擦除电压是由片上电荷泵产生,此电荷泵额定工作电压在器件的电压范围内,用于进行字节操作。
Flash 存储器擦除操作仅支持扇区擦除,不支持字节擦除。在修改某个地址的数据之前,建议先将其他数据保存后,再擦除当前扇区,最后进行数据写入操作。
在写之前先要擦除,擦除操作模式(擦除操作的范围为:当前地址所在的整个扇区); 这是该芯片的要求。
3.2 OTA整个流程
关于OTA思路有很多种,本文就先以这一种思路进行讲解,该方法可能比较繁琐,后续会逐渐优化该思路和方法,使用不同的程序实现思路,尽量做到解耦。本文这个仅做参考。
都是基于对FLASH存储器的控制
FLASH 存储器包含程序存储器(APROM)与非易失数据存储器(Data FLASH)。程序存储器空间最大为 64KB,分为 128个扇区,每个扇区包含 512B。数据存储器空间最大为 1KB,分为 2 个扇区,每个扇区包含 512B。
可通过相关特殊功能寄存器(SFR)对 FLASH 存储器进行存取操作以实现 IAP 功能,另外通过特殊功能寄存器(SFR)也可对程序空间进行 CRC 校验。用于访问 FLASH 空间的 SFR 寄存器如下:
⚫ MLOCK
⚫ MDATA
⚫ MADRL
⚫ MADRH
⚫ PCRCDL
⚫ PCRCDH
⚫ MCTRL
MLOCK 寄存器用于使能存储器操作,MDATA 寄存器形成一个字节用于保存要读/写的 8 位数据,MADRL/MADRH 寄存器存放被访问的 MDATA 单元的地址或 CRC 校验的地址,PCRCDL/PCRCDH 寄存器用于保持程序 CRC 的运行结果,MCTRL 寄存器用于存储器操作控制。
通过存储器模块接口,可对存储器进行读取/写入/擦除操作。存储器允许字节读写,写入时间由片上定时器控制,在写入新数据之前需确保该地址中的数据已被擦除。写入和擦除电压是由片上电荷泵产生,此电荷泵额定工作电压在器件的电压范围内,用于进行字节操作。
Flash 存储器擦除操作仅支持扇区擦除,不支持字节擦除。在修改某个地址的数据之前,建议先将其他数据保存后,再擦除当前扇区,最后进行数据写入操作。
芯片支持对程序空间代码进行 CRC 校验。
3.2 OTA参数初始化
这一部分的主要目的是验证DATA FLASH空间是否有效。
读取DATA_FLASH函数:IAP_ReadByte();
参数:读取的基地址、数组存储读取数据、长度、区域(DATA_ADDRESS_BEGIN, u8IapData_Read, SUM_FLASH_LEN, FLASH_DATA)
这是因为DATA_FLASH和CODE_FLASH使用共同的FLASH读取库函数因此需要区分是那个FLASH。
其中CODE_FLASH又强制的区分为BOOT和APP区,其中BOOT用于实现OTA。
3.2 DATA FLASH写入数据
关于擦除、写入、读取实现说明:
1、首先擦除是按照扇区擦除,并且不管是擦除、写入还是读取操作之前一定是解锁,操作之后一定是加锁。并且还需要根据Demo中提示擦除后填充0xFF。原因如下:注释提到需增加“循环写入256字节延时”,因Flash写入需时间完成内部电荷注入,连续操作可能导致总线阻塞或看门狗触发。
2、写入函数:IAP_WriteByte();
参数:写入的基地址、数组存储读取数据、长度、区域
(DATA_ADDRESS_BEGIN, u8IapData_Write, SUM_FLASH_LEN, FLASH_DATA)
写的过程也是按照字节写入,IAP_WriteOneByte(StartAddr + j, Area, *(WriteBuffer + j));通过该函数实现,具体可以理解为:通过For循环j实现自增,又因为写入的是单个字节,因此数据之间的地址距离就是1,所以直接使用StartAddr + j就可以依次找到下一个地址,最后在将要存入数据传递进去。此外还需要注意的一点就是,我们在存的时候一定要存一个以后立马读取,逐个验证存入的有效性。最后我们是单个字节存入,因此每个字节的存都需要解锁和加锁。
擦除代码实现:需要注意的是擦除后需要立即写入“1”。
void IAP_Erase_All(u8 area)
{u16 i;u16 k = 0;u32 Begin_Addr = 0; if(area == APPROM_AREA) // APP应用区{k = (APP_SIZE / ONE_PAGE_SIZE);Begin_Addr = APP_ADDRESS_BEGIN;area = FLASH_CODE;}else if(area == DATA_AREA) // DATA区,也即是存入标志位的区域{k = (DATA_SIZE / ONE_PAGE_SIZE);Begin_Addr = DATA_ADDRESS_BEGIN;area = FLASH_DATA;}FLASH_UnLock(); //先解锁for(i = 0 ; i < k ; i++) { FLASH_Erase(area , i*ONE_PAGE_SIZE + Begin_Addr); //确定擦除类型以及对应扇区的首地址 NWS20240103 需在此操作后,增加循环写入256字节延时if(area == FLASH_CODE) // APP应用区{for(ii = 0;ii < 256; ii++){FLASH_Write(area,0xEFFF, 0xff); //ROM最后一个地址}}else if(area == FLASH_DATA) // DATA_FLASH区,也即是存入标志位的区域{for(ii = 0;ii < 256; ii++){FLASH_Write(FLASH_DATA,0x3FF, 0xFF); //DATA区,最后一个地址,demo规定。}} WDT_ClearWDT(); }FLASH_Lock(); //在上锁
}
擦除后立即写入的必要性
-
确保数据一致性:Flash擦除会将目标区域恢复为全1(0xFF),而写入只能将1改为0。若擦除后不立即写入,残留的旧数据可能影响后续读取(如ECC校验错误或数据混淆)。
-
避免意外干扰:擦除后的Flash区域处于不稳定状态(尤其是NOR Flash),立即写入可减少因电压波动或干扰导致的数据异常风险。
代码中的具体实现与潜在问题
-
擦除后填充0xFF的意图:
示例代码在擦除后循环写入
0xFF
(如FLASH_Write(area, 0x3FF, 0xff)
),看似冗余(因擦除后已为全1),但实际可能出于以下目的:-
验证擦除操作:通过写入0xFF确认擦除成功(若擦除不完全,写入会失败)。
-
预置稳定状态:某些Flash芯片(如NAND)擦除后可能存在位翻转,写入全1可强制稳定单元状态。
-
-
延时需求:注释提到需增加“循环写入256字节延时”,因Flash写入需时间完成内部电荷注入,连续操作可能导致总线阻塞或看门狗触发。
for(i = 0 ;i< 256 ;i++) //连续256 bytes的写等待Flash执行完成{ FLASH_Write(FLASH_DATA,0x3FF, 0xFF); //写地址使用最后的地址(任意地址都可以,建议用使用较少的地址)}
这是官方例程给的Demo。目的是:连续256 bytes的写等待Flash执行完成。
接下来是写操作
uint8_t IAP_WriteByte(uint32_t StartAddr, uint8_t * WriteBuffer, uint16_t WriteSize, uint8_t Area)//写单字节IAP操作 20221026
{uint16_t i;uint8_t j;uint8_t State = 0;FLASH_UnLock(); FLASH_Erase(Area, StartAddr); //NWS20240103 需在此操作后,增加循环写入256字节延时 for(i = 0; i < 256; i++){FLASH_Write(Area, 0x1FF , 0xFF);//写FLASH_DATA地址}for(j = 0; j < WriteSize; j++){State = f_DealOTA_IAP_WriteOneByte(StartAddr + j, Area, *(WriteBuffer + j));if(State == 0){break; }}FLASH_Lock(); return State;
}
在写之前还需要进行擦除操作,但是次次擦除操作就是就是使用512个字节 也就是扇区0,
然后写的时候是按照单个字节写的,使用循环的方法,依次遍历需要写入的地址,通过StartAddr + j
实现地址增加,然后将数据写入该地址。
uint8_t IAP_WriteOneByte(uint32_t Addr, uint8_t Area, uint8_t Data)
{ uint8_t WriteState = 0;FLASH_UnLock();FLASH_Write(Area, Addr, Data);if(FLASH_Read(Area,Addr) == Data){WriteState = 1; //写入准确}else{WriteState = 0; //写入有误}FLASH_Lock(); return WriteState; //返回写入状态
}
每次在操作前和操作后一定要注意解锁和加锁。
3.3 DATA FLASH读数据
专栏介绍
《嵌入式通信协议解析专栏》
《PID算法专栏》
《C语言指针专栏》
《单片机嵌入式软件相关知识》
《FreeRTOS源码理解专栏》
文章源码获取方式:
如果您对本文的源码感兴趣,欢迎在评论区留下您的邮箱地址。我会在空闲时间整理相关代码,并通过邮件发送给您。由于个人时间有限,发送可能会有一定延迟,请您耐心等待。同时,建议您在评论时注明具体的需求或问题,以便我更好地为您提供针对性的帮助。
【版权声明】
本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议。这意味着您可以自由地共享(复制、分发)和改编(修改、转换)本文内容,但必须遵守以下条件:
署名:您必须注明原作者(即本文博主)的姓名,并提供指向原文的链接。
相同方式共享:如果您基于本文创作了新的内容,必须使用相同的 CC 4.0 BY-SA 协议进行发布。
感谢您的理解与支持!如果您有任何疑问或需要进一步协助,请随时在评论区留言。