完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
IAP应用编程
1、IAP简介 IAP是In Application Programming的首字母缩写,IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。2、STM32内置Flash 1、STM32内部FLASH的起始地址为0X08000000,Bootloader程序文件就从此地址开始写入,存放APP程序的首地址设置在紧跟Bootloader之后。当程序开始执行时,首先运行的是Bootloader程序,然后Bootloader收到BIN文件并将其复制到APP区域使固件得以更新,固件更新结束后还需要跳转到APP程序开始执行新的程序,完成这最后这一步要了解Cortex-M3的中断向量表。2.1、内置Flash的分配情况大致如下: 2.2、具体内置Flash大小参考选型手册 3、BootLoader程序 1、上电初始程序依然从0x08000004处取出复位中断向量地址,执行复位中断函数后跳转到IAP的main(标号①所示),3.1、标注如下图所示: 4、流程图 4.1、IAP升级过程中,我采用的大致方案是:5、程序代码解析 5.1、bootloader程序设计 在 /Drivers/src/IAP.c中 Step 1) 确定存放APP程序的首地址 #define FLASH_APP1_ADDR 0x08006000 //应用程序起始地址 Step 2) 跳转到新程序运行 void Run_application(void) { if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX. { printf("准备执行自带的APP代码!!rn"); iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码 printf("rn"); } else { printf("Err:自带的APP代码 地址是非法的 无法执行!!!!rn"); printf("准备软件复位 !!rn"); Reset_MCU(); printf("rn"); } } if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000) 串口接收过来的数据,是从:0X20001000开始存储的。 第一个4个字节是MSP地址,第二个4个字节,才是复位中断向量的入口地址。 &0xFF000000就是取最高8位。因为FLASH的地址范围是0X0800 0000开始的。这可以一定程度上确保地址范围正常。 //跳转到应用程序段 //appxaddr:用户代码起始地址. void iap_load_app(u32 appxaddr) { if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000) //检查栈顶地址是否合法. { SysTick->CTRL = 0; __disable_irq(); jump2app=(iapfun)*(vu32*)(appxaddr+4); //用户代码区第二个字为程序开始地址(复位地址) MSR_MSP(*(vu32*)appxaddr); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址) // SCB->VTOR = FLASH_BASE | 0x06000; jump2app(); //跳转到APP. } else { printf("Err: 跳转 程序段栈顶地址非法 !!rn"); } } if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000); 检查栈顶地址是否合法(用户代码的第一个字存放的是栈顶地址,即检查此地址) jump2app=(iapfun)*(vu32*)(appxaddr+4); 用户代码区第二个字为程序开始地址(复位中断向量地址),强制把该地址转化为iapfun类型的函数指针,再赋给函数指针jump2app Step 3) 串口接收缓存 IAP 数据 void USART1_IRQHandler(void) //串口1中断服务程序 { u8 Res; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾) { Res =USART_ReceiveData(USART1); //读取接收到的数据 mscp_rcv_data_api(&usartMsg_t[TRANS_DIRECTION_RELAY], &Res, 1); } } 这里列举USART1为例。mscp_rcv_data_api(&usartMsg_t[TRANS_DIRECTION_RELAY], &Res, 1); 是以数组实现的循环链表的具体实现。 Step 4) 轮询 IAP 数据 void Updata_application(void) { __IO u32 UpdaCnt=0x5c50;//程序的大小 printf("有更新程序,已经开启了看门狗 !!"); printf("rn"); if(EM_USER_BIN_2 == GetFlashAddrLuenchCsFlag()) { printf("擦除了FLASH_APP1_ADDR....rn"); STMFLASH_Erase_Sector(FLASH_APP1_ADDR,40);//擦除addr1地址以及以上40页 (1024/页) s_copy_address = FLASH_APP1_ADDR;//存储用户程序地址 newDataAddr = FLASH_APP1_ADDR; printf("rn"); } else { printf("擦除了FLASH_APP2_ADDR....rn"); STMFLASH_Erase_Sector(FLASH_APP2_ADDR,40);//擦除addr2地址以及以上40页 (1024/页) s_copy_address = FLASH_APP2_ADDR;//存储用户程序地址 newDataAddr = FLASH_APP2_ADDR; printf("rn"); } printf("开始进入主函数rn"); Task_delay[EM_USER_UPDATE_TIMEOUT_DELAY] = 0; printf("rn"); while(1) { if(Task_delay[EM_PRINTF_TIMEOUT_DELAY] >= D_DELAY_S(5)) { IWDG_FeedDog();//喂狗 Task_delay[EM_PRINTF_TIMEOUT_DELAY] = 0; printf("等待接收用户程序, 喂狗!!!!rn"); } if(Task_delay[EM_USER_UPDATE_TIMEOUT_DELAY] >= D_DELAY_S(2*60)) { printf("Err:升级超时2分钟!!rn"); printf("rn"); Reset_MCU(); } //串口数据分发(轮询) uart_receive_data_distribute(); //接收升级数据完成 if(uart_receive_data_success(true)) break;//数据接收完成 } uart_receive_data_success(false); } /** * @brief 串口数据分发 * @param argument: Not used * @retval None */ static void uart_receive_data_distribute(void) { mscp_poll_packet_data(&usartMsg_t[TRANS_DIRECTION_RELAY]);//获取串口升级部分的数据 mscp_poll_packet_data(&usartMsg_t[TRANS_DIRECTION_BLE]);//获取串口升级部分的数据 mscp_poll_packet_data(&usartMsg_t[TRANS_DIRECTION_RS485_NOUSE]);//获取串口升级部分的数据 mscp_poll_packet_data(&usartMsg_t[TRANS_DIRECTION_RS485_INUSE]);//获取串口升级部分的数据 mscp_poll_packet_data(&usartMsg_t[TRANS_DIRECTION_4G]);//获取串口升级部分的数据 mscp_poll_packet_data(&usartMsg_t[TRANS_DIRECTION_WIFI]);//获取串口升级部分的数据 RFID_reader_poll_packet_data(&RFID_reader_msg); //获取RS485升级部分的数据 } STMFLASH_Erase_Sector(FLASH_APP1_ADDR,40);//擦除addr1地址以及以上40页 (1024/页) STMFLASH_Erase_Sector 是一个擦除 FLASH 的自定义函数 Step 5) 擦除 内部 FLASH void STMFLASH_Erase_Sector(u32 adderss, u8 len) { u8 i; for( i=0;i STMFLASH_Erase(adderss,512);//擦除addr2地址以及以上40页 adderss +=1024; } } void STMFLASH_Erase(u32 WriteAddr,u16 NumToWrite) { u32 secpos; //扇区地址 u16 secoff; //扇区内偏移地址(16位字计算) u16 secremain; //扇区内剩余地址(16位字计算) u16 i; u32 offaddr; //去掉0X08000000后的地址 if(WriteAddr FLASH_Unlock(); //解锁 offaddr=WriteAddr-STM32_FLASH_BASE; //实际偏移地址. secpos=offaddr/STM_SECTOR_SIZE; //扇区地址 0~127 for STM32F103RBT6 secoff=(offaddr%STM_SECTOR_SIZE)/2; //在扇区内的偏移(2个字节为基本单位.) secremain=STM_SECTOR_SIZE/2-secoff; //扇区剩余空间大小 if(NumToWrite<=secremain)secremain=NumToWrite;//不大于该扇区范围 while(1) { STMFLASH_Read(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容 for(i=0;i if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;//需要擦除 } if(i FLASH_ErasePage(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE);//擦除这个扇区 } if(NumToWrite==secremain)break;//写入结束了 else//写入未结束 { secpos++; //扇区地址增1 secoff=0; //偏移位置为0 WriteAddr+=secremain; //写地址偏移 NumToWrite-=secremain; //字节(16位)数递减 if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完 else secremain=NumToWrite;//下一个扇区可以写完了 } }; FLASH_Lock();//上锁 } //从指定地址开始读出指定长度的数据 //ReadAddr:起始地址 //pBuffer:数据指针 //NumToWrite:半字(16位)数 void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead) { u16 i; for(i=0;i pBuffer=STMFLASH_ReadHalfWord(ReadAddr);//读取2个字节. ReadAddr+=2;//偏移2个字节. } } //读取指定地址的半字(16位数据) //faddr:读地址(此地址必须为2的倍数!!) //返回值:对应数据. u16 STMFLASH_ReadHalfWord(u32 faddr) { return *(vu16*)faddr; } Step 6) 写入 IAP 数据到 FLASH /** * @brief cmd dfu * @param argument: Not used * @retval None */ static void mcu_cmd_dfu_handle(Msg_t *p_msg_t) { Copy_application(p_msg_t); } void Copy_application(Msg_t *p_msg_t) { uint8_t index = 4; uint16_t _readData = 0; uint16_t _checkData = 0; while(index < p_msg_t->msgHead_t.data_size) { IWDG_FeedDog();//喂狗 _readData = 0; if((index+1) < p_msg_t->msgHead_t.data_size) { _readData = (uint16_t)p_msg_t->rcv_buf[index+1]<<8; } _readData = _readData|p_msg_t->rcv_buf[index]; STMFLASH_Write(s_copy_address,&_readData,1); s_copy_address +=2; index +=2; printf("%x-%04x x ", s_copy_address, _readData); } printf("n 升级数据接收完成,长度:%d d n", p_msg_t->msgHead_t.data_size); //检测 总包长 发完 _readData = ((p_msg_t->rcv_buf[0]) | (p_msg_t->rcv_buf[1]<<8)); _checkData = ((p_msg_t->rcv_buf[2]) | (p_msg_t->rcv_buf[3]<<8)); if(_checkData >= _readData) { dataReceivedSuccess(); } } Step 7) 检测 写入的 IAP 数据合法性 void Check_application(void) { if(((*(vu32*)(newDataAddr+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX. { printf("准备执行新的APP代码!!rn"); printf("rn"); printf("rn"); printf("rn"); Reset_MCU(); } else { printf("Err:接收的应用程序为 非FLASH应用程序,无法执行!rn"); printf("rn"); printf("rn"); printf("rn"); Reset_MCU(); } } 5.2、用户App设计 Step 1) 新建一个工程,编写一个 App LED 闪烁代码 这部分代码不用抄,自己任意写一个 LED 灯闪烁的程序即可。 Step 2) 设置 App 地址偏移起点(理解成中断向量表偏移地址) 这里的地址与 Bootloader 的FLASH_APP1_ADDR必须一致。 Step 3) Flash地址设置(这个地址需要和BootLoader中对应) 这里的地址与 Bootloader 的FLASH_APP1_ADDR必须一致。 0x7A000 是由 0x80000 - FLASH_APP1_ADDR 得来。 Step 4) App.bin生成 基本的命令格式是 这里需要注意空格,大小写。 D:Keil_v5ARMARMCCbinfromelf.exe --bin --output=output@L.bin !L Step 5) 编译链接生成bin文件: 窗口中有如下的提示信息,表示bin文件生成成功。 在输出文件夹中找到BIN文件,这个文件就是用户App的BIN文件。 5.3、使用流程 Step 1) 烧入 Bootloader 程序: Step 2) 打开 XCOM V2.1.exe 串口助手: Step 3) 开发版接线图: 本例程以串口1测试为例。 Step 4) 使用 XCOM V2.1.exe 发送 BIN 文件: 过程中,显示等待接收用户程序!!!表示等待接收 BIN 文件 。 过程中,显示准备执行新的APP代码!!,准备执行自带的APP代码!!表示程序跳转完成。 Step 5) 效果展示: 跳转前灯灭,跳转成功后,LED 快闪。(开发版 STM32F103ZET6)。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1780 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1621 浏览 1 评论
1081 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
728 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1679 浏览 2 评论
1938浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
731浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
570浏览 3评论
596浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
556浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-23 20:08 , Processed in 0.662566 second(s), Total 42, Slave 37 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号