稍微复杂一点的电子产品,程序中都设计有bootloader,有bootloader的好处是:烧写程序以后,如果发现了新的BUG或者用户增加、修改需求,就可以在不拆壳子(产品出厂一般都是用环氧树脂密封固化)的情况下,通过预留的通信口给产品升级。再有产品到用户手里后,程序进行了升级,那我们可以将新程序的.bin文件发送给用户,让用户自己升级,从而避免了返厂的费用。
IAP即在应用编程,需要为STM32程序设计一个bootloader,在此bootloader中,要设计进入bootloader下载的触发条件,并且设计通信端口协议,如串口、网口、SD卡等等,将接收到的bin文件烧写到内部的flash中。以前设计过的激光车检器的bootloader,为modbus协议,用在了山海路限高杆上;本程序是串口TTL通信协议,设计用于电喷ECU中的bootloader,已测试并量产。
硬件平台是STM32F103RCT6,为STM32F10X_HD类型,FLash:256K,RAM:48K,Flash每2K一个扇区。本程序是在ST官方IAP程序AN2557的基础上修改优化而来的。ST官方程序是:刚上电检测外部按键是否按下,如果按下,则进入程序升级代码,使用Ymodem协议传输.bin文件。我们程序进入IAP升级的方法:上电后长按键盘c,程序在200ms内检测字符c,如果200ms内按下c,则进入下一个500ms检测,如果没有按下,则判断是否有应用程序,如果有,则跳转到应用程序执行。这里为何前一个是200ms?因为在APP程序中设计有看门狗复位,如果看门狗复位后bootloader等待时间过长,则发动机在转速比较低的情况下容易因为看门狗复位而熄火,所以实测200ms看门狗不会熄火,而且在按键盘c触发IAP升级,人的手速也来得及,不受影响。
若改为MD或者LD类型,则在option里面选择相应型号的芯片,启动文件更换相应的启动文件(有ld、md、hd),宏定义里使用STM32F10X_LD或者STM32F10X_MD。
RAM使用总48K, 即:0x20000000 ———— 0x2000C000
Flash使用前64K,即:0x8000000 ———— 0x8010000
配置如图1、图2,则bootloader使用flash的前64K,注意图2是要必须选择的,尤其在APP程序中,如果不选的话,APP生成的.bin文件是从flash起始地址开始的。
图1
图2
主函数如下,上电后首先执行startup.s文件,初始化堆栈、PC指针然后进入上电复位程序,最终跳转到main()函数中。
#include "include.h" volatile uint32_t cnt_ms; /** * @brief Main program. * @param None * @retval None */ int main(void) { char recv_data; volatile uint8_t state; RCC_Configuration(); //init system clock,72MHz FLASH_Unlock(); //Flash unlock IAP_Init(); //USART1 初始化 delay_init(); SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; //使能滴答定时器 //开机0.2S内是否接收到字符c,如果没接收到,则执行应用程序 //如果接收到字符c,则判断下个0.5S是否能接收到c,如果接收到 //字符c,则进入bootloader升级程序,否则跳转到应用程序 cnt_ms = 200; while(cnt_ms) { if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != RESET) { recv_data = USART_ReceiveData(USART1); USART_ClearFlag(USART1, USART_FLAG_RXNE); if('c' == recv_data) { break; } } } if(cnt_ms) //前0.2内接收到字符c,则继续判断下一个0.5S内是否接收到字符c { cnt_ms = 500; USART_ClearFlag(USART1, USART_FLAG_RXNE); while(cnt_ms) { if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != RESET) { recv_data = USART_ReceiveData(USART1); USART_ClearFlag(USART1, USART_FLAG_RXNE); if('c' == recv_data) { break; } } } state = (cnt_ms > 0) ? 1:0; } else //前0.5内没有接收到字符c,则跳转到应用程序 { state = 0; } if(state) //Execute the IAP driver in order to re-program the Flash { SerialPutString(" ======================================================================"); SerialPutString(" = (C) COPYRIGHT 2020 ShanDong ***** Aviation Engine Co., Ltd. ="); SerialPutString(" = ="); SerialPutString(" = In-Application Programming Application (Version 1.0.0) ="); SerialPutString(" = ="); SerialPutString(" = By Yuan Anbin R & D Team ="); SerialPutString(" ======================================================================"); SerialPutString(" "); Main_Menu(); } else //Keep the user application running { //Test if user code is programmed starting from address "ApplicationAddress" if(((*(__IO uint32_t*)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000)//检查栈顶地址是否合法,即检查此段Flash中是否已有APP程序 { //Jump to user application __disable_irq(); //关闭总中断 JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4);//取得APP代码区第二个字 Jump_To_Application = (pFunction) JumpAddress; //将APP代码区第二个字强制转换,即取得复位中断函数地址 __set_MSP(*(__IO uint32_t*) ApplicationAddress); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址) Jump_To_Application(); //跳转到APP复位中断函数,从而进入APP的main()执行 } } while(1) { } }
定时中断服务函数在stm32f10x_it.c文件中,如下:
/** * @brief This function handles SysTick Handler. * @param None * @retval None */ extern volatile uint32_t cnt_ms; void SysTick_Handler(void) //1ms定时中断 { if(cnt_ms != 0x00) { cnt_ms--; } }