iap,全名为in applacation programming,即在应用编程,与之相对应的叫做isp,in system programming,在系统编程,两者的不同是isp需要依靠烧写器在单片机复位离线的情况下编程,需要人工的干预,而iap则是用户自己的程序在运行过程中对user flash 的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。在工程应用中经常会出现我们的产品被安装在某个特定的机械结构中,更新程序的时候拆机很不方便,使用iap技术能很好地降低工作量.
实现iap有两个很重要的前提,首先,单片机程序能对自身的内部flash进行擦写,第二,单片机要有能够和外部进行通讯的方式,无论是网络还是别的方式,只要能传输数据就行
通常实现 iap 功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两个项目代码,第一个项目程序不执行正常的功能操作,而只是通过某种通信方式(如 usb、 usart)接收程序或数据,执行对第二部分代码的更新;第二个项目代码才是真正的功能代码。这两部分项目代码都同时烧录在 user flash 中,当芯片上电后,首先是第一个项目代码开始运行,它作如下操作:
1)检查是否需要对第二部分代码进行更新2)如果不需要更新则转到 4)3)执行更新操作4)跳转到第二部分代码执行
第一部分代码必须通过其它手段,如 jtag 或 isp 烧入;第二部分代码可以调用第一部分的功能
也就是说,将iap和app做成两个程序,这是其中的一种策略,还有一种策略,可以把iap程序和app程序做在一个代码中,但是那样耦合性有点高,我们先进行第一种尝试.
要做iap首先我们要知道stm32的启动流程,流程如下
1、单片机从0x80000000位置启动,并将该地址当成系统栈顶地址
2、运行到中断向量表中,默认的中断向量表为0x80000004,该位置存放复位中断
3、跳转到复位中断处理函数当中,进行系统初始化,然后运行main函数
当我们准备用iap的时候,单片机内部是有着两套程序的,这个时候我们就需要在iap中
和app中分别放置两套中断向量表,当iap代码中将app烧写到flash中之后,跳转到app的中断向量表中,程序就可以正常执行了,当然需要修改某些系统设置,使得在app和iap阶段单片机可见的中断向量表只能有一套(具体请查看stm32芯片的启动代码)
而当需要从app跳转到iap的时候,只需要将app的中断向量表修改成iap的中断向量表,同时主动跳转到iap的reset中断处理程序,这样就能再次开始iap流程.
这样,在系统中就需要我们确定几个东西,第一个是iap程序的中断向量表,为0x80000004位置(80000000存放的是msp的初始值),第二个是app程序的中断向量表,该位置需要根据iap程序的长度计算,比如iap占用了64k,那么512k的芯片而言,就还有448k的空间存放app程序,448k的最开始放置中断向量表,位置就应该是0x08000000+0x10004的位置.
cortex-m3的中断向量并不是在程序中固定的,我们可以通过修改某些寄存器来修改对于当前应用的中断向量表位置.
决定中断向量表的寄存器是如下这个
通过修改这个寄存器的值,我们可以控制对于当前单片机应用来说可见的向量表的位置(也就说说逻辑上我们有两个向量表,但是同一时间只有一个运行)
以上是内核阶段的操作,在此之外我们还需要对stm32的flash进行编程,那么就涉及到删除的编程和擦除操作,这需要参考stm32的闪存编程手册
首先,当单片机复位之后,闪存式被锁住的,需要主动去解锁,向flash_keyr写入两个指定的连续键值用于解锁
然后将需要写入的闪存擦除,擦除之后在进行写入,写入完成,再次上锁
对应的代码如下
u16 stmflash_buf[stm_sector_size/2];//最多是2k字节
void stmflash_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/stm_sector_size; //扇区地址 0~127 for stm32f103rbt6
secoff=(offaddr%stm_sector_size)/2; //在扇区内的偏移(2个字节为基本单位.)
secremain=stm_sector_size/2-secoff; //扇区剩余空间大小
if(numtowrite(stm_sector_size/2))secremain=stm_sector_size/2;//下一个扇区还是写不完
else secremain=numtowrite;//下一个扇区可以写完了
}
};
flash_lock();//上锁
该函数可以实现flash的写入操作,接下来我们需要定义一套通讯协议用于串口数据传输
//串口接收缓冲区
u8 serial_buffer[serial_max_length] = {0};
//串口接收数据长度
u16 serial_buffer_length = 0;
u8 receivemode = 0;//接收参数的中断处理模型,为0的时候是命令模式,为1的时候为下载模式
u8 receiveexpectcount = 0;//串口期望接收长度
//串口中断处理
static void serialrecv(u8 ch)
{
if(receivemode == 0)
{
if((serial_buffer_length&0x8000) == 0x8000)//已经接收完成,系统还没处理
{
serial_buffer_length |= 0x8000;//退出
}
else if((serial_buffer_length&0x4000) == 0x4000)//接收到回车还没接收到换行
{
if(ch == '\n')serial_buffer_length |= 0x8000;
else
{
//一帧接受失败
serial_buffer_length = 0;
}
}
else
{
if((serial_buffer_length&0xff) < serial_max_length)
{
if(ch == '\r')serial_buffer_length |= 0x4000;
else
{
serial_buffer[(serial_buffer_length&0xff)] = ch;
serial_buffer_length++;
}
}
else
{
//一帧接受失败
serial_buffer_length = 0;
}
}
}
else
{
//下载模式,只控制字符串的量,数据的第一位是该数据包的长度,接收到这么多长度,接收完成位置一
//注意,在这种模式下,清除serial_buffer_length之前应当清除receiveexpectcount的值
if(receiveexpectcount == 0)//期望下载为0,第一个数就是期望下载数
{
receiveexpectcount = ch;
}
else
{
if((serial_buffer_length&0x8000) == 0x8000)//已经接收完成,系统还没处理,此时不接收数据
{
serial_buffer_length |= 0x8000;//退出
}
else
{
serial_buffer[(serial_buffer_length&0xff)] = ch;//接收数据并保存
serial_buffer_length++;
if((serial_buffer_length&0xff) == receiveexpectcount)//接收到了期望长度的数据
{
serial_buffer_length |= 0x8000;//一包接收完成标志
}
}
}
}
}
这样系统就能接收数据了,接下来定义五个命令
iap_down
iap_jump_app
iap_over
iap_set_flag
iap_clear_flag
第一个命令为系统开始下载,在这个命令之后上位机就能够将程序数据发下来了,
第二个命令为iap跳转到app的跳转指令
第三个命令是指示iap完成,将系统缓冲区清空的指令
第四个指令为设置app标志,当iap检测到该标志的时候直接跳转到app程序中
第五个指令为清除app标志,让iap程序不自动跳转到app程序中,我们分别来看
首先是iap_set_flag,其响应函数如下
#define app_config_addr 0x08001ffc //配置地址
#define app_config_set_value 0x5555 //设置值
#define app_config_clear_value 0xffff //清零值
//设置app固化配置
void iap_set_flag_s(void)
{
test_write(app_config_addr,app_config_set_value);
printf(ok\r\n);
}
我们使用0x08000000-0x08003000来存放iap代码,并将0x08001ffc作为存放app固化标志的地方
//清除app固化配置
void iap_clear_flag(void)
{
test_write(app_config_addr,app_config_clear_value);
printf(ok\r\n);
}
对iap_jump2app命令的响应如下
//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(u32 appxaddr)
{
if(((*(vu32*)appxaddr)&0x2ffe0000)==0x20000000) //检查栈顶地址是否合法.0x20000000是sram的起始地址,也是程序的栈顶地址
{
printf(ok\r\n);
delay_ms(10);
jump2app=(iapfun)*(vu32*)(appxaddr+4); //用户代码区第二个字为程序开始地址(复位地址)
msr_msp(*(vu32*)appxaddr); //初始化app堆栈指针(用户代码区的第一个字用于存放栈顶地址)
jump2app(); //跳转到app.
}
else
{
printf(program in flash is error\r\n);
}
}
//跳转到app区域运行
void iap_jump_app_s(void)
{
iap_load_app(flash_app1_addr);//跳转到app的复位向量地址
}
接下来就是iap_down,用于下载的核心算法
#define flash_app1_addr 0x08002000 //第一个应用程序起始地址(存放在flash)
//保留的空间为iap使用
u16 iapbuf[1024] = {0}; //用于缓存数据的数组
u16 receivedatacur = 0; //当前iapbuffer中已经填充的数据长度,一次填充满了之后写入flash并清零
u32 addrcur = flash_app1_addr; //当前系统写入地址,每次写入之后地址增加2048
//开始下载
void iap_down_s(void)
{
u16 i = 0;
u16 temp = 0;
u16 receivecount;
printf(begin,wait data download\r\n);
receivemode = 1;//串口进入下载接收数据模式
while(1)
{
//循环接收数据,每次必须要发128个数据下来,如果没有128,说明这是最后一包数据
//接收到一包数据之后,返回一个小数点,发送完成,系统编程完成之后返回一个iap_over
if(serial_buffer_length & 0x8000)
{
receivecount = (u8)(serial_buffer_length&0x00ff);
if(receivecount == 128)//满足一包,填充并查看是否有了1024字节,有了写入闪存
{
for(i = 0; i < receivecount; i+=2)
{
//数据八位融合为16位
temp = (((u16)serial_buffer[i+1])<<8) + ((u16)serial_buffer[i]);
iapbuf[receivedatacur] = temp;
receivedatacur++;//完成之后receivedatacur++;
}
receiveexpectcount = 0;//清除期望接收模式
serial_buffer_length = 0;//清除串口满标志
printf(.);//每次接受一次数据打一个点
//此时需要检测receivedatacur的值,要是放满了,就需要写入
if(receivedatacur == 1024)
{
//写入flash中
stmflash_write(addrcur,iapbuf,1024);
//printf(\r\nwrite addr %x,length 1024\r\n,addrcur);
addrcur += 2048;//地址+2048
//写完之后receivedatacur要清零等待下一次传输
receivedatacur = 0;
}
else //有可能最后一包有128个数据但是最终没有2048个数据,此时扩展一个指令用于完成最后一个的写入
{
}
//还没放满,等待下一次数据过来
}
else //不满足一包,说明数据传送这是最后一包,写入闪存
{
//没有一包也要传送到缓存中
for(i = 0; i < receivecount; i+=2)
{
//数据八位融合为16位
temp = (((u16)serial_buffer[i+1])
#else
scb->vtor = flash_base | vect_tab_offset; /* vector table relocation in internal flash. */
#endif
其中vect_tab_offset就是我们要定义的偏移量,也就是app程序的起始地址偏移,我们知道是2000,那么该值的宏就需要修改,在大约128行的位置
//此处为flash偏移地址,app应当修改这个地址
#define vect_tab_offset 0x2000 /*!< vector table base offset field.
this value must be a multiple of 0x200. */
嗯,完整流程就是这样了,另外,该工程分为三个部分,一个iap,一个app,还有一个当然是下载程序啦,下载程序是这样的
三个代码的工程我会打包上传到csdn,想更深入了解的可以下载来看看,软件用mfc编写的
一文了解pcb价格构成因素
魅族科技宣布将于本月底在Indiegogo上线魅族zero的众筹
三星10nm工艺技术已经在Galaxy S8上提供支持
人工智能在出行领域有什么新成就
AUTOSAR AP技术形态与技术发展趋势
STM32_IAP详解(有代码,有上位机)
引入虚拟阻抗对下垂法的并联技术进行改进
电源适配器主电路图
芯片供应危机,大众或向供应商索赔
王者之争, 我是该买小米6好还是等魅族pro7好?
扩声系统有什么功能_扩声系统标准是什么
6G能提供什么?如何实现6G的可行技术
诚迈科技成功通过Automotive SPICE Capability Level 2认证
【新品速递】ABAT系列蓄电池在线监测系统
什么情况!苹果iPhone7发布会刚结束 iPhone6S也跟风一起涨价
去耦电容器是否真的有必要,它的作用是什么
昆仑通态HMI触摸屏和西门子V20变频器MODBUS通讯
照明装置的安装要求
玩转三星s8:三星s8跑马灯怎么设置?
当汽车遇上英伟达 会发生什么?