IAP简介(简介部分copy自正点原子)
IAP(In Application Programming)即在应用编程,IAP 是用户自己的程序在运行过程中对User Flash 的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。 通常实现 IAP 功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两个项目代码,第一个项目程序不执行正常的功能操作,而只是通过某种通信方式(如 USB、USART)接收程序或数据,执行对第二部分代码的更新;第二个项目代码才是真正的功能代码。这两部分项目代码都同时烧录在 User Flash 中,当芯片上电后,首先是第一个项目代码开始运行,它作如下操作: 1)检查是否需要对第二部分代码进行更新 2)如果不需要更新则转到 4) 3)执行更新操作 4)跳转到第二部分代码执行 第一部分代码必须通过其它手段,如 JTAG 或 ISP 烧入;第二部分代码可以使用第一部分代码 IAP 功能烧入,也可以和第一部分代码一起烧入,以后需要程序更新时再通过第一部分 IAP代码更新。 我们将第一个项目代码称之为 Bootloader 程序,第二个项目代码称之为 APP 程序,他们存放在 STM32L433FLASH 的不同地址范围,一般从最低地址区开始存放 Bootloader,紧跟其后的就是 APP 程序(注意,如果 FLASH 容量足够,是可以设计很多 APP 程序的,本章我们只讨论一个 APP 程序的情况)。这样我们就是要实现 2 个程序:Bootloader 和 APP。 STM32L433 的 APP 程序不仅可以放到 FLASH 里面运行,也可以放到 SRAM 里面运行,
程序思路介绍
当产品定型投产后每一次烧录程序都必须去修改BOOT硬件引脚的电平很不方便所以我们需要自己编写一套BootLoader程序,也就是在单片机FLASH中编写两套程序,利用第一套程序通过串口接收的方式将第二套程序写在指定地址的内部FLASH上,然后强制跳转单片机的PC指针实现运行第二套程序的方法,从理论上来说,单片机可以保存无数套程序并通过强制跳转指针去运行,当然前提是你的FLASH足够大。 我们称第一套BootLoader程序为引导程序,第二套真正要运行的为APP程序,引导程序只是在单片机上电时执行一次,真正工作的是APP程序,下面我们来细说具体原理。
STM32L433内部FLASH
我们从最底层的开始看,因为单片机内部的FLASH被分配的地址为0x8000000到0x8040000,结束地址减去起始地址是40000,对应的就是262444字节,除以1024刚好是256K字节,这也对应着STM32L433的flash为256K。 所以,我们引导程序分配56K字节,APP程序分配200字节。
单片机上电之后从0x8000000这个地址开始运行程序,STM32L4 的内部闪存(FLASH)地址起始于 0x08000000,一般情况下,程序文件就从此地址开始写入。此外 STM32L4 是基于 Cortex-M4 内核的微控制器,其内部通过一张“中断向量表”来响应中断,程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,而这张“中断向量表”的起始地址是 0x08000004,当中断来临,STM32L4 的内部硬件机制亦会自动将 PC 指针定位到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序。在图 中,STM32L4 在复位后,先从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,如图标号①所示;在复位中断服务程序执行完之后,会跳转到我们的 main 函数,如图标号②所示;而我们的 main 函数一般都是一个死循环,在 main 函数执行过程中,如果收到中断请求(发生重中断),此时 STM32L4 强制将 PC 指针指回中断向量表处,如图标号③所示;然后,根据中断源进入相应的中断服务程序,如图标号④所示;在执行完中断服务程序以后,程序再次返回 main 函数执行,如图标号⑤所示。 如果假如了IAP程序,那么流程就会变成: 因为每次有中断发生的时候都会跳转到中断向量表中,中断向量表的起始地址为0x8000004,这个是程序强制执行的,所以我们在APP程序的第一句,要对中断向量表的地址进行移位。 SCB->VTOR = FLASH_BASE | 0x10000; 在APP程序的第一句加上或操作即对APP程序的中断向量表进行的移位,当中断发生时,程序会自动跳转至移位后的中断向量表保证程序不会跑飞。 在引导程序中,接收的程序也是直接被存在单片机的SRAM中,STM32L433的SRAM有64K,所以限制了接收的最大字节数,所以可以采用边接收边写FLASH的办法,最好也要对接收到的数据进行校验。
程序部分
主函数
在主函数中我利用了AT指令的方法去进入BootLoader引导程序,程序上电后5秒没有收到固定的AT指令就会强制跳转指针到APP应用程序,如果收到了AT+BOOTLOADER指令程序即关闭定时器并准备接收APP程序,校验办法是我会在发程序之前先发送APP程序的字节数,当接收到相同字节的程序即认为接收成功,当然这种办法很蠢,最好的是CRC去校验。
extern uint8_t u8RxBuf[];
int main(void)
{
/*上一次串口接收数值*/
uint32_t u32oldcount = 0;
/*接收到的app代码长度*/
uint32_t applenth = 0;
/*上传代码长度*/
int32_t u32ProgramSize = 0;
gtSysFlag.u8GetDataFlag = false;
gtSysFlag.u8GetProgramFlag = false;
gtSysFlag.u8JumpAppFlag = false;
gtSysFlag.u8ProgramOkFlag = false;
HAL_Init();
SystemClock_Config(); //初始化系统时钟为80M
delay_init(80); //初始化延时函数 80M系统时钟
uart_init(115200); //初始化串口,波特率为115200
TIM2_Init(5000 - 1, 8000 - 1);
while(1)
{
/*仅支持大写输入*/
if(gtSysFlag.u8GetDataFlag == true)
{
if((u8RxBuf[0] == 'A') && (u8RxBuf[1] == 'T') && (u8RxBuf[2] == '+'))
{
if((u8RxBuf[3] == 'B') && (u8RxBuf[4] == 'O') && (u8RxBuf[5] == 'O') && (u8RxBuf[6] == 'T'))
{
/*关闭超时定时器*/
__HAL_RCC_TIM2_CLK_DISABLE();
gtSysFlag.u8JumpAppFlag = false;
printf("已进入引导程序\r\n");
}
else if((u8RxBuf[3] == 'B') && (u8RxBuf[4] == 'Y') && (u8RxBuf[5] == 'T') && (u8RxBuf[6] == 'E'))
{
u32ProgramSize = ((u8RxBuf[7] - '0')*10000) + ((u8RxBuf[8] - '0')*1000) +
((u8RxBuf[9] - '0')*100) + ((u8RxBuf[10] - '0')*10) + ((u8RxBuf[11] - '0'));
printf("代码字节数为:%d\r\n",u32ProgramSize);
}
else if((u8RxBuf[3] == 'P') && (u8RxBuf[4] == 'R') && (u8RxBuf[5] == 'O') && (u8RxBuf[6] == 'G'))
{
if((u8RxBuf[7] == 'R') && (u8RxBuf[8] == 'A') && (u8RxBuf[9] == 'M'))
{
if(u32ProgramSize != 0)
{
/*开始接收APP程序*/
gtSysFlag.u8GetProgramFlag = true;
printf("准备接收APP程序\r\n");
}
else
{
printf("请输入要发送的字节数\r\n");
}
}
}
else if((u8RxBuf[3] == 'W') && (u8RxBuf[4] == 'R') && (u8RxBuf[5] == 'I') && (u8RxBuf[6] == 'T'))
{
if((u8RxBuf[7] == 'E') && (u8RxBuf[8] == 'F') && (u8RxBuf[9] == 'L'))
{
if(applenth != 0)
{
if(gtSysFlag.u8ProgramOkFlag == true)
{
/*更新Flash的程序*/
iap_write_appbin(FLASH_APP1_ADDR, USART_RX_BUF, applenth); //更新FLASH代码
printf("固件更新完成!\r\n");
}
else
{
printf("请上传代码后进行固件更新!\r\n");
}
gtSysFlag.u8ProgramOkFlag = false;
}
else
{
printf("没有可以更新的固件!\r\n");
}
}
}
else if((u8RxBuf[3] == 'R') && (u8RxBuf[4] == 'U') && (u8RxBuf[5] == 'N'))
{
/*开始执行flash程序*/
gtSysFlag.u8JumpAppFlag = true;
}
}
gtSysFlag.u8GetDataFlag = false;
}
/*程序接收部分*/
if(gtSysFlag.u8GetProgramFlag == true)
{
if(USART_RX_CNT != 0)
{
if(u32oldcount == USART_RX_CNT) //新周期内,没有收到任何数据,认为本次数据接收完成.
{
applenth = USART_RX_CNT;
u32oldcount = 0;
USART_RX_CNT = 0;
printf("用户程序接收完成!\r\n");
printf("上传代码字节数为:%dBytes\r\n",u32ProgramSize);
printf("实际代码长度:%dBytes\r\n", applenth);
if(applenth == u32ProgramSize)
{
printf("用户程序校验正确!\r\n");
gtSysFlag.u8ProgramOkFlag = true;
}
else
{
printf("用户程序校验错误,请重新上传!\r\n");
}
gtSysFlag.u8GetProgramFlag = false;
}
else
{
/*将最新计数值赋值给上一次变量*/
u32oldcount = USART_RX_CNT;
}
}
delay_ms(50);
}
/*跳转至APP应用程序*/
if(gtSysFlag.u8JumpAppFlag == true)
{
/*判断APP程序起始地址上是否有正确数据*/
if(((*(vu32*)(FLASH_APP1_ADDR + 4)) & 0xFF000000) == 0x08000000)
{
printf("开始执行APP程序");
__HAL_RCC_TIM7_CLK_DISABLE();
gtSysFlag.u8GetProgramFlag = false;
iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码
}
else
{
printf("FLASH无应用程序!");
gtSysFlag.u8JumpAppFlag = false;
}
}
}
}
串口接收程序
#include "usart.h"
#include "delay.h"
#include "sys.h"
#include "timer.h"
tSysFlag gtSysFlag;
extern TIM_HandleTypeDef TIM7_Handler; //定时器句柄
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
/**
* @brief 定义_sys_exit()以避免使用半主机模式
*
* @param void
*
* @return void
*/
void _sys_exit(int x)
{
x = x;
}
/**
* @brief 重定义fputc函数
*
* @param ch 输出字符量
* @param f 文件指针
*
* @return void
*/
int fputc(int ch, FILE *f)
{
while((USART1->ISR & 0X40) == 0); //循环发送,直到发送完毕
USART1->TDR = (u8) ch;
return ch;
}
#endif
#if EN_USART1_RX //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART_RX_BUF[USART_REC_LEN] __attribute__ ((at(0X20001000)));//接收缓冲,最大USART_REC_LEN个字节,起始地址为0X20001000.
uint8_t u8RxBuf[20];
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART_RX_STA = 0; //接收状态标记
u32 USART_RX_CNT=0; //接收的字节数
uint16_t u16LoopData = 0;
UART_HandleTypeDef UART1_Handler; //UART句柄
void uart_init(u32 bound)
{
//UART 初始化设置
UART1_Handler.Instance = USART1; //USART1
UART1_Handler.Init.BaudRate = bound; //波特率
UART1_Handler.Init.WordLength = UART_WORDLENGTH_8B; //字长为8位数据格式
UART1_Handler.Init.StopBits = UART_STOPBITS_1; //一个停止位
UART1_Handler.Init.Parity = UART_PARITY_NONE; //无奇偶校验位
UART1_Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE; //无硬件流控
UART1_Handler.Init.Mode = UART_MODE_TX_RX; //收发模式
HAL_UART_Init(&UART1_Handler); //HAL_UART_Init()会使能UART1
__HAL_UART_ENABLE_IT(&UART1_Handler, UART_IT_RXNE); //开启接收中断
HAL_NVIC_EnableIRQ(USART1_IRQn); //使能USART1中断通道
HAL_NVIC_SetPriority(USART1_IRQn, 3, 3); //抢占优先级3,子优先级3
}
/**
* @brief HAL库串口底层初始化,时钟使能,引脚配置,中断配置
*
* @param huart 串口句柄
*
* @return void
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/**USART1 GPIO Configuration
PB6 ------> USART1_TX
PB7 ------> USART1_RX
*/
GPIO_InitTypeDef GPIO_InitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
/**
* @brief 串口1中断服务程序
*
* @remark 下面代码我们直接把中断控制逻辑写在中断服务函数内部
* 说明:采用HAL库处理逻辑,效率不高。
*
* @param void
*
* @return void
*/
void USART1_IRQHandler(void)
{
uint8_t Res;
static uint8_t u8Res = 0;
if(gtSysFlag.u8GetProgramFlag == true)
{
if((__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_RXNE)!=RESET))
{
HAL_UART_Receive(&UART1_Handler,&Res,1,1000);
if(USART_RX_CNT<USART_REC_LEN)
{
USART_RX_BUF[USART_RX_CNT]=Res;
USART_RX_CNT++;
}
}
}
else if(gtSysFlag.u8GetProgramFlag == false)
{
if((__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_RXNE)!=RESET))
{
HAL_UART_Receive(&UART1_Handler,&Res,1,1000);
u8RxBuf[u16LoopData]=Res;
u16LoopData++;
if(u16LoopData >= 20)
{
u16LoopData = 0;
}
if(Res == 0x0D)
{
u8Res = Res;
}
if((Res == 0x0A) && (u8Res == 0x0D))
{
u8Res = 0;
u16LoopData = 0;
gtSysFlag.u8GetDataFlag = true;
}
}
}
HAL_UART_IRQHandler(&UART1_Handler);
__HAL_UART_ENABLE_IT(&UART1_Handler, UART_IT_RXNE);
}
#endif
|