STM32速成笔记(15)—串口IAP

一、串口iap简介1.1 什么是iapiap,英文全称in application programming,在应用中编程。很好理解,就是在程序运行过程中我们进行程序的烧写,或者叫升级。
1.2 stm32下载程序我们都知道,stm32可以利用串口下载程序,这是因为st公司在产线上就在产品中内嵌了自举程序。所谓的自举程序,实际就是支持我们通过串口下载程序的代码。自举程序被存放在系统存储区,因此如果我们需要通过串口下载程序,需要将boot0接高电平,boot1接低电平,让程序从系统存储器开始运行,运行自举程序。下载完成后我们再将boot0接地,让程序从主闪存存储器开始运行。自举程序是我们用户无法修改的。
二、串口iap有什么作用上面我们介绍了什么是iap,那么这个iap到底有什么作用呢?
首先介绍两个词——bootloader和 application 。bootloader实际就是一段引导程序,单片机上电后先执行bootloader程序,然后再执行用户编写的应用程序application。介绍完这两个词,我们来介绍一下iap有什么作用。比如我们生产了a,b两款产品。a产品是某个精密器件的一部分,b产品是一款物联网产品。我们的产品销售范围很广,远销海外。
某天a产品的某个客户反映了一个bug,我们编写好了程序,需要进行程序更新,或者叫升级。利用iap,我们可以在程序运行时,通过预留的通信接口直接烧写程序。而不需要再把整个设备拆开,像我们调试时那样下载程序。甚至我们可以直接给客户邮寄一个小设备,客户直接进行傻瓜式升级。
又过了一段时间,b产品的程序出现了一个bug,风险等级比较低,但是依旧需要全体升级程序。我们总不能挨个产品派人去升级,成本极大。这时候又轮到我们的iap出场了。它可以在所有设备在线运行的情况下,直接通过网络下发升级程序,实现在线升级,节约了大量的人力成本。
通过上面这两个例子,大家应该能够基本了解iap的用处,使用iap让我们不需要再使用调试器进行下载,甚至实现设备的在线升级。
三、启动流程在介绍如何实现iap之前,我们先来简单了解以下stm32的启动流程。
3.1 正常启动流程这里的正常启动流程指的是,没有添加iap的流程。
正常启动流程
程序启动时首先开辟栈空间,配置栈顶指针。然后配置堆空间。配置完成后,建立中断向量表,在中断向量表中找到复位中断,开始执行复位中断服务函数,然后跳转到main函数中,执行用户代码。当用户代码中有中断请求时,会回到中断向量表,根据中断源执行相应的中断服务函数。
3.2 加入iap后的启动流程下面是加入iap之后的启动流程。
加入iap启动流程
可以看到,与上面不同的是,加入iap后,执行完复位中断服务函数后直接进入iap的main函数。在执行完iap之后,跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的main 函数。
由上面的两个启动过程我们可以看出
• 新程序必须在 iap 程序之后的某个偏移量为 x 的地址开始。
• 必须将新程序的中断向量表相应的移动,移动的偏移量为 x。
四、必备知识44.1 修改程序运行起始地址点击魔术棒,选择“target”,修改运行起始地址和代码大小。
修改app运行起始地址
4.2 设置中断向量表偏移vtor 寄存器存放的是中断向量表的起始地址。如果要设置中断向量表偏移,只需要在main函数最开始添加如下语句即可
scb- >vtor = flash base | 偏移量:4.3 生成.bin文件点击魔术棒,选择“user”,按照如下配置,输入下面的内容
fromelf --bin -o $l@l.bin #l
生成.bin文件配置
点击编译,不报错就可以,去设置的输出文件夹中就可以找到对应的.bin文件。
编译提示
五、串口iap实现本次的目标是实现一个串口iap,也就是编写bootloader,在程序运行过程中实现程序的下载。bootloader程序应该可以通过串口接收上位机发来的.bin文件(app程序),检查后将.bin文件写入到flash特定位置,然后跳转到app程序运行。
5.1 串口中断服务函数本次的串口中断服务函数与之前不同,这里单独贴出来。需要定义一个接收数组,接收数组的起始地址限制为0x20001000。接收数组最多可以接收55k字节,可以根据需要调整。但是需要注意的是,数组的大小需要比app程序要大,而且不能超过芯片的sram空间大小。
/* *============================================================================== *函数名称:usart1_irqhandler *函数功能:usart1中断服务函数 *输入参数:无 *返回值:无 *备 注:无 *============================================================================== */u32 grececount = 0; // 接收计数变量// 接收数组// 限制起始地址为0x20001000// 保证偏移量为 0x200的倍数// 是为了给app留sram空间u8 grecefifo[usart_rece_max_len]__attribute__ ((at(0x20001000)));void usart1_irqhandler(void) { if(usart_getitstatus(usart1, usart_it_rxne) != reset) //接收到一个字节 { if (grececount dr; } else { printf (app code out of memory!rn); } }}5.2 flash写入程序关于flash程序,这里就不在赘述,只是贴一下带检查的写入程序。其他具体内容可以到博主stm32速成笔记专栏查看。
/* *============================================================================== *函数名称:med_flash_write *函数功能:从指定地址开始写入指定长度的数据 *输入参数:writeaddr:写入起始地址;pbuffer:数据指针; numtoread:写入(半字)数 *返回值:无 *备 注:对内部flash的操作是以半字为单位,所以读写地址必须是2的倍数 *============================================================================== */// 根据中文参考手册,大容量产品的每一页是2k字节#if stm32_flash_size < 256 #define stm32_sector_size 1024 // 字节#else #define stm32_sector_size 2048#endif// 一个扇区的内存u16 stm32_flash_buf[stm32_sector_size / 2];void med_flash_write (u32 writeaddr,u16 *pbuffer,u16 numtowrite){ u32 secpos; // 扇区地址 u16 secoff; // 扇区内偏移地址(16位字计算) u16 secremain; // 扇区内剩余地址(16位计算) u16 i; u32 offaddr; // 去掉0x08000000后的地址 // 判断写入地址是否在合法范围内 if (writeaddr = (stm32_flash_base + 1024 * stm32_flash_size))) { return; // 非法地址 } flash_unlock(); // 解锁 offaddr = writeaddr - stm32_flash_base; // 实际偏移地址 secpos = offaddr / stm32_sector_size; // 扇区地址 secoff = (offaddr % stm32_sector_size) / 2; // 在扇区内的偏移(2个字节为基本单位) secremain = stm32_sector_size / 2 - secoff; // 扇区剩余空间大小 if (numtowrite <= secremain) { secremain = numtowrite; // 不大于该扇区范围 } while (1) { // 读出整个扇区的内容 med_flash_read(secpos * stm32_sector_size + stm32_flash_base,stm32_flash_buf,stm32_sector_size / 2); // 校验数据 for (i = 0;i < secremain;i ++) { // 需要擦除 if (stm32_flash_buf[secoff + i] != 0xffff) { break; } } // 需要擦除 if (i < secremain) { flash_erasepage(secpos * stm32_sector_size + stm32_flash_base); // 擦除这个扇区 // 复制 for (i = 0;i (stm32_sector_size / 2)) { secremain = stm32_sector_size / 2; // 下一个扇区还是写不完 } else { secremain = numtowrite; // 下一个扇区可以写完了 } } } flash_lock(); // 上锁}5.3 iap程序iap程序包含两部分,一部分是将接收到的.bin文件写入flash,另一部分是跳转到app执行。
/* *============================================================================== *函数名称:iap_write_appbin *函数功能:写入.bin文件 *输入参数:appxaddr:app程序起始地址;appbuf:缓存app程序的数组; appsize:app程序大小 *返回值:无 *备 注:无 *============================================================================== */ // 对flash操作的最小单位是16位 u16 iapbuf[1024]; // 要写入flash的内容 void iap_write_appbin (u32 appxaddr,u8 *appbuf,u32 appsize){ u16 t; // 临时循环变量 u16 i = 0; // 自增索引 u16 temp; // 临时计算变量 u32 fwaddr = appxaddr; // 当前写入的地址 u8 *dfu = appbuf; // 指向app代码数组首地址的指针 // for循环,2k为单位进行写入 for(t = 0;t < appsize;t += 2) { temp = (u16)dfu[1] < < 8; temp += (u16)dfu[0]; dfu += 2; // 偏移2个字节 iapbuf[i++] = temp; if(i == 1024) { i = 0; med_flash_write (fwaddr,iapbuf,1024); fwaddr += 2048; // 偏移2048 16=2*8所以要乘以2 } } if(i) { med_flash_write (fwaddr,iapbuf,i); // 将最后的一些内容字节写进去 }}/* *============================================================================== *函数名称:iap_load_app *函数功能:跳转到app *输入参数:appxaddr:app程序起始地址 *返回值:无 *备 注:无 *============================================================================== */iapfun jump2app;void iap_load_app (u32 appxaddr){ if(((*(vu32*)appxaddr)&0x2ffe0000)==0x20000000) // 检查栈顶地址是否合法 { jump2app=(iapfun)*(vu32*)(appxaddr+4); // 用户代码区第二个字为程序开始地址(复位地址) msr_msp(*(vu32*)appxaddr); // 初始化app堆栈指针(用户代码区的第一个字用于存放栈顶地址) jump2app(); // 跳转到app }}5.4 main函数main函数设计如下
int main(void){ u32 curreccunt = 0; // 当前接收数量 // 设置nvic中断分组2:2位抢占优先级,2位响应优先级 nvic_prioritygroupconfig(nvic_prioritygroup_2); delay_init(); // 延时初始化 uart_init(115200); // 串口初始化 printf (enter the bootloader code!rn); while(1) { // 如果接收到内容且传输完成 if (curreccunt == grececount && grececount != 0) { printf (app code reception complete!rn); printf (the length of the received app code is %d!rn,grececount); // 开始准备写入flash if(((*(vu32*)(0x20001000+4)) & 0xff000000) == 0x08000000) // 判断是否为0x08xxxxxx. { iap_write_appbin(flash_app1_addr,grecefifo,grececount); // 更新flash代码 grececount = 0; // 清零接收计数变量 printf(update complete!rn); } else { printf(update error!rn); } // 准备跳转app执行 if(((*(vu32*)(flash_app1_addr+4))&0xff000000)==0x08000000) //判断是否为0x08xxxxxx. { printf(enter app!rn); iap_load_app(flash_app1_addr); //执行flash app代码 }else { printf(enter error!rn); } } else // 未传输完成 { curreccunt = grececount; // 更新当前接收数量 // 延时一定要有,给串口中断留出时间 // 如果没有会导致接收不完全 delay_ms(10); } }}六、注意事项需要注意的是,上面的程序中app程序是从0x8010000开始,需要配置好程序起始地址和中断向量表偏移。

IAR单片机编程软件的使用方法介绍
虹科方案 | 虹科Panorama SCADA平台的HMI功能
中国半导体市场空间大,国外半导体企业纷纷在中国建立合资公司
跟关晓彤学做“自拍小仙女” 她的自拍必杀技你get了吗
KD-211型数码分段开关工作原理图
STM32速成笔记(15)—串口IAP
MA24340A功率传感器的独特设计以及它的优势
Eero不只是路由器,进军智能家居,Eero将成为智能家居的核心?
电力电缆跟普通电线在构造方面有什么不同
亚马逊自研芯片处理Alexa语音助手部分计算任务 成本较英伟达T4可降低30%
基于CAN总线技术实现汽车检测线测控系统的设计
英伟达放大招发布专业级显卡Quadro系列:人工智能,VR,深度学习都能干
nfc手环怎么用_荣耀手环3nfc怎么使用
车辆智能无钥匙系统Keyless Entry System
美国 HP/惠普 33120A 函数信号发生器
海康威视称萤石网络将分拆至科创板上市
IR推出全新µIPM-DIP功率模块
基于软件仿真验证的运放电路设计方法
机器人关节精准控制的内部结构原理
研究表明可使用超声波技术来测量肺部的液体