Iap,全名为in applacation programming,即在应用编程,与之相对应的叫做isp,in system programming,在系统编程,两者的不同是isp需要依靠烧写器在单片机复位离线的情况下编程,需要人工的干预,而iap则是用户自己的程序在运行过程中对User Flash 的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。在工程应用中经常会出现我们的产品被安装在某个特定的机械结构中,更新程序的时候拆机很不方便,使用iap技术能很好地降低工作量.
实现iap有两个很重要的前提,首先,单片机程序能对自身的内部flash进行擦写,第二,单片机要有能够和外部进行通讯的方式,无论是网络还是别的方式,只要能传输数据就行
要做iap首先我们要知道stm32的启动流程,流程如下
两种实现方式:
一定要划分清楚IAP程序和APP程序的flash地址,不要重合了
#1.IAP程序设置,程序地址
#2.APP程序设置,程序地址
生成bin文件
fromelf.exe --bin -o "[email protected]" "#L
移植正点原子的两部分代码,IAP和Fashl,也可以移植官方的代码,有能实现的Dome都可以
修改IAP.C
#define FLASH_APP1_ADDR 0x8005000 //第一个应用程序起始地址(存放在FLASH)
iapfun jump2app;
u16 iapbuf[512];//**缓存区大小,因为stm32f103c8t6的flash一页是1K的,所以要改小为512**
添加函数
//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(u32 addr)
{
MSR MSP, r0 //set Main Stack value
BX r14
}
修改"stmflash.h"
//用户根据自己的需要设置
#define STM32_FLASH_SIZE 64 //所选STM32的FLASH容量大小(单位为K)
#define STM32_FLASH_WREN 1 //使能FLASH写入(0,不是能;1,使能)
#define FLASH_WAITETIME 50000 //FLASH等待超时时间
//FLASH起始地址
#define STM32_FLASH_BASE 0x08000000 //STM32 FLASH的起始地址
修改UART
#include "usart.h"
#include "stdio.h"
int fputc(int ch, FILE *f) {
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
/* USER CODE BEGIN 0 */
#define USART_REC_LEN 15*1024 //定义最大接收字节数 55K
#define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收
#define RXBUFFERSIZE 1 //缓存大小
uint8_t USART_RX_BUF[USART_REC_LEN] __attribute__ ( ( at ( 0X20001000 ) ) ); //接收缓冲,最大USART_REC_LEN个字节,起始地址为0X20001000.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
uint16_t USART_RX_STA = 0; //接收状态标记
uint16_t USART_RX_CNT = 0; //接收的字节数
/* USER CODE END 0 */
u8 aRxBuffer[RXBUFFERSIZE];//HAL库使用的串口接收缓冲
UART_HandleTypeDef huart1;
/* USART1 init function */
//初始化IO 串口1
//bound:波特率
void uart_init(u32 bound)
{
//UART 初始化设置
huart1.Instance=USART1; //USART1
huart1.Init.BaudRate=bound; //波特率
huart1.Init.WordLength=UART_WORDLENGTH_8B; //字长为8位数据格式
huart1.Init.StopBits=UART_STOPBITS_1; //一个停止位
huart1.Init.Parity=UART_PARITY_NONE; //无奇偶校验位
huart1.Init.HwFlowCtl=UART_HWCONTROL_NONE; //无硬件流控
huart1.Init.Mode=UART_MODE_TX_RX; //收发模式
HAL_UART_Init(&huart1); //HAL_UART_Init()会使能UART1
HAL_UART_Receive_IT(&huart1, (u8 *)aRxBuffer, RXBUFFERSIZE);//该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量
}
//UART底层初始化,时钟使能,引脚配置,中断配置
//此函数会被HAL_UART_Init()调用
//huart:串口句柄
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_Initure;
if(huart->Instance==USART1)//如果是串口1,进行串口1 MSP初始化
{
__HAL_RCC_GPIOA_CLK_ENABLE(); //使能GPIOA时钟
__HAL_RCC_USART1_CLK_ENABLE(); //使能USART1时钟
__HAL_RCC_AFIO_CLK_ENABLE();
GPIO_Initure.Pin=GPIO_PIN_9; //PA9
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化PA9
GPIO_Initure.Pin=GPIO_PIN_10; //PA10
GPIO_Initure.Mode=GPIO_MODE_AF_INPUT; //模式要设置为复用输入模式!
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化PA10
#if EN_USART1_RX
HAL_NVIC_EnableIRQ(USART1_IRQn); //使能USART1中断通道
HAL_NVIC_SetPriority(USART1_IRQn,3,3); //抢占优先级3,子优先级3
#endif
}
}
extern u8 flag_1;
//串口1中断服务程序
void USART1_IRQHandler(void)
{
u8 Res;
if((__HAL_UART_GET_FLAG(& huart1,UART_FLAG_RXNE)!=RESET)) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res=USART1->DR;
if(USART_RX_CNT<USART_REC_LEN)
{
USART_RX_BUF[USART_RX_CNT]=Res;
USART_RX_CNT++;
}
}
flag_1=1;
HAL_UART_IRQHandler(& huart1);
}
main.c
int main(void)
{
/* USER CODE BEGIN 1 */
u16 oldcount = 0; //老的串口接收数据值
u16 applenth = 0; //接收到的app代码长度
u16 app_bin = 0;
u16 app_enter = 0;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
uart_init(115200);
while ( 1 )
{
// 首先判断app代码的首地址是否为0x0800 000,是则进入app,否的话进行引导区。
if ( ( ( * ( vu32* ) ( FLASH_APP1_ADDR + 4 ) ) & 0xFF000000 ) == 0x08000000 ) //判断是否为0X08XXXXXX.
{
iap_load_app ( FLASH_APP1_ADDR ); //执行FLASH APP代码
}
// 判断app代码栈顶是否为0x2000 000,不是则进入升级模式,代码如下,其中 FLASH_APP1_ADDR=0x8005000;
if ( ( ( ( * ( vu32* ) FLASH_APP1_ADDR ) & 0x2FFE0000 ) != 0x20000000 ) )
{
printf ( "/***** No APP! *****/ \r\n" );
printf ( "stm32f103c8t6在线升级 \r\n" );
printf ( "选择对应的app bin文件 \r\n" );
printf ( "输入 A 发送bin文件 \r\n" );
printf ( "输入 E 进入app \r\n" );
while ( 1 )
{
printf ( "滴答!\r\n" );
if( flag_1==1)
{
flag_1=0;
printf ( "holle wored!\r\n" );
}
if ( USART_RX_CNT )
{
if ( oldcount == USART_RX_CNT ) //新周期内,没有收到任何数据,认为本次数据接收完成.
{
applenth = USART_RX_CNT;
oldcount = 0;
USART_RX_CNT = 0;
if ( applenth > 100 )
{
printf ( "用户程序接收完成!\r\n" );
printf ( "代码长度:%dBytes\r\n", applenth );
}
}
else
oldcount = USART_RX_CNT;
}
HAL_Delay(1000);
if ( USART_RX_BUF[0] == 'A' )
{
if ( applenth )
printf ( "\r\n 请发送bin文件 \r\n" );
app_bin = 1;
applenth = 0;
}
else if ( app_bin )
{
if ( applenth )
{
printf ( "开始更新固件...\r\n" );
printf ( "Copying APP2FLASH..." );
//此处 0X20001000 地址为串口缓冲区开始接收数据地址
if ( ( ( * ( vu32* ) ( 0X20001000 + 4 ) ) & 0xFF000000 ) == 0x08000000 ) //判断是否为0X08XXXXXX. 串口是否接收到数据
{
iap_write_appbin ( FLASH_APP1_ADDR, USART_RX_BUF, applenth ); //更新FLASH代码
printf ( "Copy APP Successed!!" );
printf ( "固件更新完成!\r\n" );
applenth = 0;
app_bin = 0;
}
}
}
if ( USART_RX_BUF[0] == 'E' )
{
if ( applenth )
printf ( "\r\n 将要执行APP \r\n" );
app_enter = 1;
applenth = 0;
}
if ( app_enter )
{
printf ( "开始执行FLASH用户代码!!\r\n" );
if ( ( ( * ( vu32* ) ( FLASH_APP1_ADDR + 4 ) ) & 0xFF000000 ) == 0x08000000 ) //判断是否为0X08XXXXXX.
{
iap_load_app ( FLASH_APP1_ADDR ); //执行FLASH APP代码
}
else
{
printf ( "非FLASH应用程序,无法执行!\r\n" );
printf ( "Illegal FLASH APP!" );
}
}
}
}
}
/* USER CODE END 3 */
}
执行下面代码可以重回函数
SCB->VTOR = FLASH_BASE | 0x0000;
HAL_NVIC_SystemReset();