STM32的IAP功能在一些需要升级维护的场景下显得十分的重要,当然在实际项目中,我们需要远程发送升级指令,使得主控进入升级模式,进而将固件下发升级。很多网上的资源中,只有IAP跳转至APP,并且不带有任何协议,直接将bin文件一次性下发。但是这样的话,在项目实际使用过程中,会非常不稳定。还有一些是有代码,但是上位机代码或者stm32的代码不给你,只是提供思路。也是挺麻烦的一件事情。
我先讲讲自己的思路,C#上位机方面,首先打开串口,串口的波特率使用115200(上位机中没有给出设置界面)。打开需要升级的bin文件,左侧textbox显示文件绝对路径,下侧textbox显示文件大小。此程序将bin文件分成2k字节一个包,并且在每个包的头部加入0x5a的包头,再接着包的序号。以及包尾部加入0xaa。总共2051字节一包。下位机接收并保存之后,返回0x5a,包序号,0xaa。上位机接收到返回之后,继续下发后序bin包。其中最后一包序号为0xa5。下位机接收到0xa5序号的包时,下位机开始跳转至APP。
下位机跳转至APP之后,上位机点击进入升级模式按钮后,上位机下发AA,BB,CC,DD,EE指令,下位机接收到指令,跳转回IAP程序,等待固件的下发。若一分钟后没有收到固件,重新跳转回APP。
下面是bin文件组包。
public byte[] SplitArray(byte[] Source, int StartIndex, int EndIndex,byte Block)
{
try
{
byte[] result = new byte[EndIndex - StartIndex + 1+2+1];//加入帧头(2 bytes)尾(1 byte)
result[0] = 0x5a;
result[1] = Block;
for (int i = 0; i <= EndIndex - StartIndex; i++) result[i+2] = Source[i + StartIndex];
result[EndIndex - StartIndex + 1 + 2] = 0xaa;
return result;
}
catch (IndexOutOfRangeException ex)
{
throw new Exception(ex.Message);
}
}
接收到返回之后继续下发后序bin文件包
int recl;
if (serialPort1.BytesToRead > 0)
{
timer1.Stop();
recl = serialPort1.BytesToRead;//读取串口接收的长度
byte[] recFile = new byte[recl];
for (int i = 0; i < recl; i++)
{
recFile[i] = (byte)(serialPort1.ReadByte());
}
if (recFile[0] == 0x5a && recFile[2] == 0xaa)
{
if (recFile[1] == 0xa5)//接收完成
{
progressBar1.Value = 100;
sendflag = false;
textBox2.AppendText("单片机升级完成\r\n");
}
else if(recFile[1] == 0xff)
{
textBox2.AppendText("单片机已经进入升级模式\r\n");
}
else if ((file_len - read_len * recFile[1]) / read_len < 1)
{
progressBar1.Value = read_len * recFile[1] * 100 / file_len;
textBox2.AppendText("单片机升级........." + progressBar1.Value + "%\r\n");
try
{
sendchar = SplitArray(binchar, read_len * recFile[1], file_len - 1, 0xa5);
serialPort1.Write(sendchar, 0, file_len - read_len * recFile[1] + 3);
timer1.Start();
}
catch (Exception)
{
}
}
else
{
progressBar1.Value = read_len * recFile[1] * 100 / file_len;
textBox2.AppendText("单片机升级......." + progressBar1.Value + "%\r\n");
try
{
sendchar = SplitArray(binchar, read_len * recFile[1], read_len * (recFile[1] + 1) - 1, (byte)((recFile[1] + 1) & 0xff));
serialPort1.Write(sendchar, 0, read_len + 3);
timer1.Start();
}
catch (Exception)
{
}
}
}
else
{
if (sendflag == true)
{
textBox2.AppendText("单片机接收错误,请重新发送\r\n");
sendflag = false;
}
}
}
}
下位机方面:IAP跳转程序
//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(u32 appxaddr)
{
u8 i;
if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000) //检查栈顶地址是否合法.
{
jump2app=(iapfun)*(vu32*)(appxaddr+4); //用户代码区第二个字为程序开始地址(复位地址)
MSR_MSP(*(vu32*)appxaddr); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
for(i = 0; i < 8; i++)
{
NVIC->ICER[i] = 0xFFFFFFFF; /* 关闭中断*/
NVIC->ICPR[i] = 0xFFFFFFFF; /* 清除中断标志位 */
}
jump2app(); //跳转到APP.
}
}
跳转之前,需要将中断关闭,APP中使用了串口IDLE的中断,不清楚为什么,一定需要在程序中将其DISABLE,不然跳转回IAP之后,不能跳回APP。
APP中采用了FreeRTOS实时系统,APP程序起始偏移如下。
NVIC_SetVectorTable(FLASH_APP1_ADDR,0);
说明:STM32程序大部分参考正点原子例程。稍作修改。
源码位置:https://download.youkuaiyun.com/download/qq_23229787/10807490
上传的c#程序有mysql的登录和注册,可以注册再登录,也可以在登录界面点击版本号进入系统。对了,本该将bin文件分包之后,采用较为标准的协议,但是自己为了方便就自定义了协议。另外代码中有什么不好之处还请大家提出,一起学习。