【WCH蓝牙系列芯片】-基于CH584开发板—串口IAP升级说明和移植过程
------------------------------------------------------------------------------------------------------------------------------------
IAP 程序(Bootloader):通常存放在 Flash 的最起始位置(地址 0x00000000),体积非常小(例程是4KB)。它是用来接收新固件、擦写 Flash,并引导启动。
APP 程序(用户程序):存放在 IAP 之后的空间(如地址 0x00001000 开始)。放在IAP的4K后面,它负责执行用户的程序逻辑功能。
一、UART_IAP程序
在UART_IAP程序中,先看一下LD文件中FLASH起始地址,IAP的起始地址是从0x00000000开始的,设置大小为16K给IAP使用。

整个 IAP 程序的运行过程可以分为三大阶段:启动与分流判定、串口数据轮询与协议解析、Flash擦写与程序跳转。
当芯片上电复位后,首先运行的是位于地址 0x00000000 的 IAP 程序。在main() 函数中决定:是留在 IAP 等待串口发来新固件,还是直接跳去执行旧的 APP 业务代码;在主循环中有两个方式进行判断是否升级固件操作
第一阶段:启动与分流判定
方式一:读取 DataFlash 中的标志位,如果读出的标志不是 0x55 (代表要求升级),则说明是正常开机,执行jumpApp()函数,直接跳转到 APP 地址执行。

方式二:检测外部硬件引脚电平状态,这个程序中,将 PB4 配置为升级按键, 如果PB4状态是高电平则不进入升级过程,正常跳转到APP里面,执行用户程序,如果PB4是低电平状态,那就会进入升级过程。

如果程序运行到这个位置,说明触发UART_IAP升级操作,决定留在 IAP 模式,对串口1(PA8\PA9)进行初始化操作,打开FIFO。串口初始化完毕调用Main_Circulation(); 进入 IAP 核心死循环,开始接收上位机发来的数据包并执行升级动作。

第二阶段:数据轮询与协议解析
进入 Main_Circulation() 后,程序进入死循环。 函数带有 __attribute__((section(".highcode"))) 修饰符,将这段代码会被加载到 RAM 中运行。通过无中断的纯轮询方式,接收串口下发的固件数据包;利用一个状态机解析协议;最后将解析出的固件数据安全、高效地烧写到 Flash 中。
1、通过串口轮询的方式接收数据,在串口通信极易受干扰丢包,利用超时防死锁判断;如果使用串口中断,进出中断需要压栈出栈,且需要中断向量表,这会占用极大的 Flash 空间,为了不占用FLASH空间,采用串口轮询接收数据。

在超时机制中包含有短超时和长超时,短超时(防烂包死锁)如果收到一半的数据突然断了,程序里的 g_tcnt 会在没有数据时不断累加。当超过 43 次循环(约 1毫秒,等同于 10 个字节的时间)没收到新数据,就会判定这包数据废了,直接丢弃并复位状态机,向主机报错。
长超时(防开机卡死)如果用户误入了 IAP 模式,但长达 120 秒 (g_tcnt > 6000000) 都没有上位机发数据过来,程序会认为“不需要升级了”,自动调用 jumpApp() 跳回正常APP程序中。

2、串口接收数据,通过协议包格式为:[包头AA] [包头55] [命令CMD] [长度LEN] [地址4字节] [数据载荷] [校验和2字节] [包尾55] [包尾AA]取出一包完整的数据。

第三阶段:Flash擦写与程序跳转
3、当成功接收到一包数据(IAP_DATA_REC_STATE_OK),就会根据命令字 CMD 执行擦除烧录动作



上位机必须先发擦除命令 (CMD_IAP_ERASE),擦除成功后,代码将 g_update_permition 置为 1。如果没有这一步,后面的写入命令统统拒接,这是防误写保护。上位机每次下发是 Flash 按页写入(256 字节)。 所以,程序设立了 g_write_buf[256 + 64] 缓存。每次收到数据先塞进缓存(my_memcpy),等 g_buf_write_ptr >= 256 满了一页,才调用底层的 FLASH_ROM_WRITE 一次性刷入 Flash 中。剩下的结尾的数据则被挪到下一次拼装。
4、写完数据之后,执行校验命令CMD_IAP_VERIFY,如果检验失败就直接报错。

5、升级结束,跳转到APP程序中,执行新固件的APP程序

在IAP程序中,还包含APP的起始地址和结束地址。还有标志位定义的数据,如果后续要修改空间大小就得修改这部分地址参数

二、APP程序
在APP程序中,可以先看一下LD文件里面FLASH的起始位置是16K位置(0x00004000),FLASH的大小给432K,直接把APP固件放在IAP固件的后面放置。

int main() { uint16_t i = 0; uint8_t s = 0; HSECFG_Capacitance(HSECap_18p); SetSysClock(SYSCLK_FREQ); /* 配置串口调试 */ DebugInit(); PRINT("Start @ChipID=%02x\n", R8_CHIP_ID); printf(" Build Date: %s\r\n", __DATE__); //打印编译时的日期 printf(" Build Time: %s\r\n", __TIME__); //打印编译时的时间 /* app程序必须执行该语句,保证app更新失败时,下次依然运行IAP */ // 这里的 FLAG_USER_CALL_APP 通常是 0xAA。 // 这样做的目的是防止 IAP 升级失败后死机,只要成功跑进 APP, // 就 APP是好的,下次断电重启,IAP 读取到 0xAA 就会直接跳回 APP。 SwitchImageFlag(FLAG_USER_CALL_APP); // 把 DataFlash 里的升级标志位清除。 GPIOB_ModeCfg(GPIO_Pin_4, GPIO_ModeIN_PU); //配置按键引脚(PB4 为升级触发引脚) while (1) { PRINT("i:%d\n",i); i++; DelayMs(10); if (GPIOB_ReadPortPin(GPIO_Pin_4) == 0) // 检测到低电平(按键按下) 触发升级 { s++; //连续两次检测到按键按下(软件消抖逻辑),跳转到IAP if(s >= 2) { jumpToIap(); // 触发跳转 } } else { s = 0; } DelayMs(100); } }
在APP的主函数程序里面,先执行 SwitchImageFlag(FLAG_USER_CALL_APP); 这段程序。APP 和 IAP 是两个独立的程序,它们约定了一个掉电不丢失的区域(DataFlash 的 0 地址)作为标志位判断。

这里写入的是 FLAG_USER_CALL_APP设置为0xAA,只要代码能成功跑到 main 函数这里,说明刚才 IAP 刷入的这个新 APP 是好用的、没跑飞的。就把DataFlash 里的升级标志(0x55)擦掉,换成正常启动标志(0xAA)。这样下次断电重启时,IAP 读取到 0xAA,就会直接放行跳回 APP,不再死等串口了。这里使用 0x55 (01010101) 和 0xAA (10101010) 的二进制位是交替的, 这样在 Flash 中极难因为单比特翻转发生误判,是最安全的标志位选择。

在APP主函数中判断完跳转的标志位之后,配置升级触发条件,这里使用PB4作为按键触发功能,然后进入主循环中,一直打印 i++数值;如果 检测到 PB4 被拉低之后,就会触发跳转jumpToIap(); 改写标志位为 0x55 并软复位单片机,芯片内部硬件会自动将 PC 指针指回 0x00000000,从IAP程序的起始位置开始运行。如果没有检测到PB4拉低,就会一直i++数值打印。

IAP+APP程序运行大体流程:

三、BLE程序移植UART_IAP的功能
1、将APP例程里面的app_flah.c和app_flah.h复制到蓝牙从机Peripheral工程中。

2、修改LD文件,将FLASH的起始地址改为0x00004000(16k),FLASH大小设置为432K.

3、修改启动文件,将原本这里的 0x88修改为0x1888,改为机器模式

4、在蓝牙程序中,添加dataflash的标志位判断程序,和触发条件的的按键初始化。
SwitchImageFlag(FLAG_USER_CALL_APP); // 把 DataFlash 里的升级标志位清除。 GPIOB_ModeCfg(GPIO_Pin_4, GPIO_ModeIN_PU); //配置按键引脚(PB4 为升级触发引脚)
然后再主循环中加入是否跳转的判断——jumpToIap();


5、配合PC端的上位机工具,进行串口IAP的升级,进行验证测试




浙公网安备 33010602011771号