基于stm32的485_IAP程序升级

文章目录

  • 一.Flash的映射

  • 二、stm32内存结构和上电运行过程

  • 三.Flash的写入(从sram到Flash)

  • 四.用户代码的修改

  • 五.代码文件格式的转换和发送升级命令

  • 六.程序的升级命令和跳转

  • 七.跳转标志位



前言

        我们平常常用的ISP下载,实际上是通过单片机专用的串行编程接口对单片机内部的Flash存储器进行编程(通过厂商固化好的程序),需要外部电路辅助实现,而IAP则是通过将Flash映射为两个区域,一部分为用户引导程序区BOOT,一部分为用户程序,利用引导程序实现在程序内编程(IAP)。  而485也好,串口也好,只是硬件传输上的协议,本质方法都是一样的。

        整体思路就是Flash映射为两部分,首先运行引导程序,发送指令和程序包给stm32,再让stm32将sram储存的内容写入Flash的第二部分,再让主进程跳转至Flash的第二部分即可。



一、Flash的映射

        第一步首先我们需要将Flash划分为两个区域,一部分为BOOT,一部分为用户程序。这就需要我们知道BOOT程序的大概大小。

通过keil5编译程序我们可以得到

linking...
Program Size: Code=7252 RO-data=336 RW-data=60 ZI-data=48068  
FromELF: creating hex file...
"..\OBJ\USART.axf" - 0 Error(s), 0 Warning(s).
Build Time Elapsed:  00:00:01

keil5代码分为两部分:

       1.  code,即程序代码部分      

       2.  inline data. 即 literal pools(文字常量池), and short strings(短字符串)等

另外 RO Data: read-only data,只读的数据

        RW Data: read write data,可读写的数据

        ZI Data: zero initialized data,零初始化的可读写变量,存放未初始化的全局变量及初始化为0的变量

RO size: Code + RO_data,表示程序占用Flash空间的大小。

RW size: RW_data + ZI_data,表示运行时占用RAM的大小。

可以计算出我们的上面程序所占Flash大小为(7252+336)/1024=7K。

我采用的是stm32f103mini板,可以从keil5魔术棒Target栏看到

基于stm32的485_IAP程序升级主要参数如下

CPU:STM32F103RCT6,LQFP64,FLASH:256KB,SRAM:48KB;
flash起始地址为0x08000000,大小为0x4000(16进制)—>262144字节(10进制)—>256KB
RAM起始地址为0x20000000,大小为0xC000(16进制)—>49125字节(10进制)—>48KB

从Flash起始到我们的用户代码区要预留充足的空间给引导程序。这里我预留了0x5000=20K,不够可以在加,但已经绰绰有余了。即我们的用户程序起始地址为0x08005000。


二、stm32内存结构和上电运行过程

1.stm32的内存结构

1.栈区stack:由编译器自动分配释放,存放函数的参数值,局部变量的值。

2.堆区heap:由程序员分配和释放,若程序员不释放,程序结束时由OS回收。

3.全局区(静态区 static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量、未初始化的静态变量在相邻的另一块区域。

4.文字常量区:常量字符串就是放在这里的。

5.程序代码区 : 存放函数体的二进制代码。

两个比较关键的指针

PC:Program Counter,是通用寄存器,但是有特殊用途,用来指向当前运行指令的下一条指令。

SP:Stack Pointer,堆栈指针,也是通用寄存器,用于入栈和出栈操作。

2.上电运行过程

stm32矢量表  上图

基于stm32的485_IAP程序升级转载-STM32片上FLASH内存映射、页面大小、寄存器映射 - kanite - 博客园

        MSP主栈指针,reset复位向量入口地址,外设中断向量表IRQ0-More IRQs

        stm32上电启动有3种,从flash种启动,从sram种启动,从系统存储器种启动。STM32上电复位以后,代码区都是从0x00000000开始的,三种启动模式只是将各自存储空间的地址映射到0x00000000中。stm32以为自己执行的是0x00000000,但实际却不是,只是被骗了。

第一步--- 芯片自动从0地址读取32位整数,设置MSP主堆栈指针  即设置SP=initial main sp。

第二部---芯片自动从4地址读取32位整数,并跳转到该地址执行   即PC->复位程序。

三.Flash的写入(从sram到Flash)

  IAPFlash的写入采用的是正点原子的代码。

//appxaddr:应用程序的起始地址
//appbuf:应用程序CODE.
//appsize:应用程序大小(字节).
void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize)
{
	u16 t;
	u16 i=0;
	u16 temp;
	u32 fwaddr=appxaddr;//当前写入的地址
	u8 *dfu=appbuf;
	for(t=0;t<appsize;t+=2)
	{						    
		temp=(u16)dfu[1]<<8;
		temp+=(u16)dfu[0];	  
		dfu+=2;//偏移2个字节
		IAP_buff[i++]=temp;	    
		if(i==1024)
		{
			i=0;
			STMFLASH_Write(fwaddr,IAP_buff,1024);	
			fwaddr+=2048;//偏移2048  16=2*8.所以要乘以2.
		}
	}
	if(i)STMFLASH_Write(fwaddr,IAP_buff,i);//将最后的一些内容字节写进去.  
}

代码具体是将我们要写入的u8类型数组每两个拼成一个u16数据,再把这些u16装到个BUFF里一共1024个(2K字节),满了便整体写入flash里,最后再把剩余的也写入。

定义我们串口接收数据的BUFF,sram中我们接收数据的地址,可以使用__attribute__

u8 Appreceive_buff[Max_lenth]  __attribute__  ((at(0x20001000)));   

四.用户代码的修改

        需要修改代码储存地址和大小,以及中断向量表的映射。

基于stm32的485_IAP程序升级

 中断向量表的映射即0x08000000开始的代码的表换成0x08005000代码的表

SCB->VTOR = FLASH_BASE | 0x05000; /* Vector Table Relocation in Internal FLASH. */

五.代码文件格式的转换和发送升级命令

         程序要运行起来,必须要经过四个步骤:预处理、编译、汇编和链接。

基于stm32的485_IAP程序升级

         我们需要将我们的用户代码转化为bin文件。bin文件是最纯粹的二进制机器代码,也就是可执行文件。

        keil5中魔术棒--user--After Build/Rebuild勾选--选择路径即keil中ARM/ARMCC/bin/fromelf.exe--添加" --bin -o  ..\OBJ\XXX.bin ..\OBJ\XXX.axf" 即可生成bin文件,其中XXX为生成代码名称。

        之后我们便可以通过串口助手发送bin文件,波特率设大些,否则很慢。

另外加入通讯协议,在运行引导程序时,必须发送“ok”返回“ok”才能发送程序,发送完成后发送“++”实现升级和跳转。

//usart1_state  2开始接收,5开始升级,6升级完成,7开始跳转
void USART1_IRQHandler(void)                	//串口1中断服务程序
{
	u8 Res;
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断
		{
			Res =USART_ReceiveData(USART1);//(USART1->DR);	//读取接收到的数据
			if(usart1_state==2)
			{
				if(usart1_cnt<Max_lenth)Appreceive_buff[usart1_cnt++]=Res;
				else printf("Save_Err\r\n");
			}		
			//接收到"ok"即可开始发送程序包  不能加发送新行!
			if((usart1_state==1)&&(Res==0x6B))//字母'k'
			{
				usart1_state=2;   
				printf("ok\r\n");
			}
			else if((usart1_state==1)&&(Res!=0x6B))
			{
				printf("Err\r\n");
				usart1_state=0;
			}
			if((usart1_state==0)&&(Res==0x6F))usart1_state=1;   //字母'0'
			else if((usart1_state==0)&&(Res!=0x6F))printf("Err\r\n");
			//接收完成后 发送"++"即可开始升级
			if((usart1_state==4)&&(Res==0x2B))usart1_state=5;  //'+'
			else if((usart1_state==4)&&(Res!=0x2B))
				{
					usart1_state=3;
					printf("Err\r\n");
				}
			if((usart1_state==3)&&(Res==0x2B))usart1_state=4;  //'+'
			else if((usart1_state==3)&&(Res!=0x2B))printf("Error\r\n");
		}
} 

六.程序的升级命令和跳转

iap_write_appbin(FLASH_APP_LOAD_ADDR,Appreceive_buff,Iap_lenth);//更新FLASH代码


IAP_function Jump_fuction;
typedef void (*IAP_function)(void);

void iap_load_app(u32 appxaddr)
{
	if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)	//检查栈顶地址是否合法.
	{ 
		Jump_fuction=(IAP_function)*(vu32*)(appxaddr+4);		//用户代码区第二个字为程序开始地址(复位地址)		
		MSR_MSP(*(vu32*)appxaddr);					//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
		Jump_fuction();									//跳转到APP.
	}
}	

升级和跳转前都要检查内容是否合法。一是判断起始地址第一个int作为MSP是不是在0x20范围,二是判断第二个int作为PC是不是在0x08范围,即

if(((*(vu32*)(FLASH_APP_LOAD_ADDR+4))&0xFF000000)==0x08000000)

!!!跳转需要注意:需要先将复位入口地址取出来,再设置MSP,再执行复位。不能先设置MSP,再取地址跳转,这样修改MSP是无效的。

这样就不对
MSR_MSP(*(vu32*)0x08005000);
((IAP_function)*(vu32*)(0x08005000+4))();

七.跳转标志位

        我们会发现用户程序升级后复位首先还是会运行引导程序,所以我们需要跳转之前向flash再写一个标志位,这个标志位代表程序是否直接运行升级程序。加入控制引脚,根据引脚电平判断是否更改标志位。这样上电后便会由boot转到用户程序。

if((STMFLASH_ReadHalfWord(FLASH_JUMP_FLAG_ADDR)==Flag_Buff[0]))usart1_state=7;//读取状态标志  两种进入方式 1修改flash 2拉低PA8引脚电平
if(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_8))
{
	usart1_state=0;
	STMFLASH_Write((u32)(FLASH_JUMP_FLAG_ADDR),Flag_Buff+1,1);//拉低复位后强制修改flash标志位  
}

#define  FLASH_JUMP_FLAG_ADDR  0x08004FE0   //0x5000-32 跳转标志地址
u16 Flag_Buff[2]={4,8};    //跳转标志位
在跳转之前
STMFLASH_Write((u32)(FLASH_JUMP_FLAG_ADDR),Flag_Buff,1);   //向Flash里写一个标志位

        向用户程序中添加flash的标志位的更改程序和软件复位程序,以达到利用通讯便可以跳转到引导程序且不跳转回来。

void USART1_IRQHandler(void)                	//串口1中断服务程序
	{
	u8 Res;
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
		{
			Res =USART_ReceiveData(USART1);	//读取接收到的数据
			USART_RX_BUF[usart_cnt++]=Res;
			
			返回引导程序 "~~"
			if((state==1)&&(Res==0x7E))
			{
				printf("ok\r\n");
				state=2;
			}
			else if((state==1)&&(Res!=0x7E))
			{
				state=0;printf("ERROR\r\n");
			}
			if((state==0)&&(Res==0x7E))state=1;
		}
} 

void check_state(u8 *sta)
{
	if(*sta==2)
	{
		STMFLASH_Write((u32)(FLASH_JUMP_FLAG_ADDR),Flag_Buff+1,1);   //向Flash里写一个标志位
		iap_load_app(0x08000000);
		*sta=0;
	}
}

通讯协议接收到“~~”开始跳转state=2;在主程序内添加check_state(&state);即可。

八.实验效果

https://www.bilibili.com/video/BV1Yh411p7nM?share_source=copy_webhttps://www.bilibili.com/video/BV1Yh411p7nM?share_source=copy_web基于stm32的485_IAP程序升级https://www.bilibili.com/video/BV1Yh411p7nM?share_source=copy_web


上一篇:通讯接口应用笔记2:MAX3160实现多协议通讯


下一篇:GUI---班级信息收集