1 简介-STM32 IAP
- IAP(In Application Programming,应用内编程),是指程序在运行过程中对User Flash的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。
- 下面针对STM32 IAP功能。在设计固件程序时,需要建立两个MDK工程。
MDK工程 | IAP工程 | APP工程 |
---|---|---|
代码功能 | 通过某种通信管道(USART等)接收数据,写入APP程序对应的Flash,修改参数或程序 | 正常的目标功能 |
存放位置 | Flash的开始区域(一般会设置写保护,正常响应中断) | Flash中IAP代码空间之后(需要重映射中断向量表) |
执行顺序 | 上电执行 | 等待IAP程序跳转 |
下载方式 | ISP、SWD等 | ISP、SWD等、IAP |
- 两个工程都需要下载到芯片中,上电后执行顺序为:
2 实例-STM32F103C8T6
2.1 IAP工程主要部分
// IAP程序代码
#include "stm32f10x.h"
// 函数指针类型定义
typedef void(*pFunction)(void);
#define IAP_ADDRESS 0x8000000 // 16 KB
// 定义APP程序地址(需要与工程配置一致)
#define APP_ADDRESS 0x8004000 // 48 KB
uint32_t jumpAddress;
pFunction Jump_To_App;
int main(void)
{
FLASH_Unlock();
// if(满足更新条件)
// {
// 从通信管道接收数据
// 写入APP对应的Flash
// }
// 通过堆栈地址是否合法来检测该Flash区域是否已有APP程序
// APP程序首地址存放的就是堆栈地址,可查看中断向量表:
// DCD __initial_sp ; Top of Stack
// 堆栈地址指向RAM,RAM的起始地址一般设置为0x20000000(内部SRAM)
// 一般情况下堆栈地址(内部SRAM)合法范围是0x20000000~0x2001FFFF,大小为128K
if(((*(__IO uint32_t*)APP_ADDRESS) & 0x2FFE0000) == 0x20000000)
{
// 堆栈地址往后移一个字是复位中断的中断函数的地址,可查看中断向量表:
// DCD Reset_Handler ; Reset Handler
jumpAddress = *(__IO uint32_t*)(APP_ADDRESS + 4);
// 将中断函数地址转为函数指针
Jump_To_App = (pFunction)jumpAddress;
// 设置主堆栈地址为APP程序堆栈地址
__set_MSP(*(__IO uint32_t*)APP_ADDRESS);
// APP中断向量表复位异常处理
Jump_To_App();
}
/* Infinite loop */
while (1)
{
}
}
##2.2 APP工程主要部分
// APP程序代码
#include "stm32f10x.h"
#define IAP_ADDRESS 0x8000000 // 16 KB
#define APP_ADDRESS 0x8004000 // 48 KB
int main(void)
{
// 设置APP程序中断向量表偏移值(不设置中断无法正常响应)
NVIC_SetVectorTable(NVIC_VectTab_FLASH, APP_ADDRESS - IAP_ADDRESS);
/* Infinite loop */
while (1)
{
// 正常的目标功能
}
}
##2.3 程序通过IAP方式下载
- 需要先将APP MDK工程生成的HEX文件(十六进制)转为BIN文件(二进制)。可以使用
hex2bin.exe
,例如在CMD窗口中输入hex2bin.exe .\Output\app.hex
,即可生成app.bin
。 - 生成二进制文件后,即可通过通信管道(USART等),将数据发送给STM32,STM32运行在IAP程序中,接收并烧写到对应的Flash中。
- 为此,自定义了上位机(PC)将APP程序下载到下位机(STM32)的分包下载协议。
上位机 | |||
---|---|---|---|
名称 | 字节数(Byte) | 值 | 备注 |
指令码 | 1 | 0xEE | 任意,暂定为0xEE |
数据包总数 | 1 | 0x00~0xFF | 总包数=值+1 |
当前数据包序号 | 1 | 0x00~0xFF | 包序号=值+1 |
数据段长度 | 1 | 0x01~0xFC | 4字节对齐,长度为4的倍数,最后一包长度不大于单包发送长度则不受限制,但至少为1Byte |
数据段 | 1~252 | 0x00~0xFF | |
校验码(偶校验) | 1 | 0x00、0x01 | 仅数据内容偶校验 |
合计 | 260 | - | 最大可下载文件大小为 255*256 = 65280 Byte < 64KB |
下位机 | |||
---|---|---|---|
名称 | 字节数(Byte) | 值 | 备注 |
ACK/NACK | 1 | 0xEE/其他值 | 返回0xEE表示ACK,其他值为NACK |
- | - | - | 上位机收到NACK则重发当前数据包 |
- 上位机(Delphi 7)
- IAP工程擦除Flash部分
// 按键按下时进入更新APP程序模式
if(Key1_GetStatus())
{
// 计算擦除的Flash总共有几页
pageNbr = Flash_PagesMask(FULL_ADDRESS - APP_ADDRESS);
// 擦除APP程序存放地址以后的Flash
for(cnt = 0; (cnt < pageNbr) && (flashStatus == FLASH_COMPLETE); cnt++)
{
// 注意擦除Flash时要按页擦除
flashStatus = FLASH_ErasePage(APP_ADDRESS + (PAGE_SIZE * cnt));
}
}
- IAP工程接收数据和写入Flash部分
while (1)
{
// 接收一包数据写一包数据
if(bt_rc_flag)
{
// 检查串口不定长接收的帧是否出错
isNotBinFrame = Iap_CheckFrame(flashDataPackCnt);
if(isNotBinFrame)
{
// 出错处理
if(isNotBinFrame == 3) // 未接收完全
{
bt_rc_flag = 0; // 清除串口接收标志
}
else
{
bt_rx_len = 0; // 清除串口接收长度
bt_rc_flag = 0; // 清除串口接收标志
Bt_SendByte(0xFF); // NACK
}
}
else
{
// 处理单包数据
// 将数据复制到结构体变量中
Iap_CopyFrame(&binFrame);
allFlashDataSize += binFrame.dataSize;
for(cnt = 0; (cnt < binFrame.dataSize) && (flashAddress < APP_ADDRESS + allFlashDataSize); cnt += 4)
{
// 按字写入Flash
FLASH_ProgramWord(flashAddress, *(uint32_t*)(binFrame.data + cnt));
// Flash地址偏移4个字节
flashAddress += 4;
}
flashDataPackCnt += 1;
bt_rx_len = 0; // 清除串口接收长度
bt_rc_flag = 0; // 清除串口接收标志
Bt_SendByte(0xEE); // ACK
if(flashDataPackCnt == binFrame.packAll)
{
printf(">>>Reboot!\r\n");
Stm32_SoftReset(); // 软复位
}
}
}
}
-
整体工程代码见 码云Gitee 。
- 注意:没有上传工程配置文件,工程下载后需要修改一下ROM地址设置(如前文两张图片)。
- 按住Key0,上电或复位则进入IAP模式,此时等待Key1按下;当Key1按下时,擦除对应Flash并从USART2接收程序,校验并写入到Flash中;最后软复位,即下载完自动重启。
- 同理,更换成其他通信管道(如WiFi),触发条件不使用按键,使用其他可远程触发的条件(如自定义的通信协议),即可实现远程在线升级。
- 实际上,在升级过程中应禁止其他中断,同时可以先将整个升级程序保存在内存中,待整个程序校验正常后再烧录到FLASH中,这样在程序分包烧录中断的情况下还可以使用原来的程序,否则按上文方法升级失败则原来程序也被破坏,必须要重新升级成功才能使用。