在很多场景应用中需要我们的控制器能够实现远程升级,比如我之前做的一个项目中,项目的主要需求就是老师的电脑能够获取学生的实验设备的实验数据。第一批测试完成之后,所有的学生设备的主控板子都上线了。运行了大概一个月,买家那边发现有些功能需要更新。没办法只能把这批100多块主控板从学生设备的机箱里面拆出来,然后一块一块烧写好程序之后,再装回去。这个时候就很后悔当初没有把IAP远程升级的功能加到这里面去。加了这个功能一方面好和买家谈价钱,另一方面方便自己。
IAP的大概意思就是在自己目前运行的程序中,对用户的另外部分内存进行烧写,烧写完成之后,再跳转到烧写好的那一部分程序(也就是升级之后的程序)当中。所以这里面大概需要了解的是
1、STM32的ROM起始地址以及ROM大小;
2、STM32对flash的读写;
3、程序中断的指针偏移等。
要实现IAP远程升级,程序包含两部分
1、IAP跳转程序;
2、APP运行程序。
大致思路为:程序开始从IAP进入,IAP中判断是否有收到升级包,如果没有升级包跳转到APP程序。有则烧写新的升级包。跳转至APP程序后,APP程序完成我们项目需求的功能,并加上一个跳转回IAP的指令。
上位机程序中,将固件分包为2K一包,包头加上0X5A的固定头,以及长度字符(以0xA5为结尾标志),0xaa为包尾。IAP程序从0x08000000-0x08010000,APP从0x0801000开始。
下面为IAP的主函数,IAP程序从0x08000000开始,主要设置了串口1、按键、LED灯,以及延迟函数。程序采用串口1接收升级的固件包。当接收到数据时,先判断包头和包尾是否正确。再将分包放入待烧写的数组中,烧写后,烧写的位置 Flash_App_Pos 也相应的增加刚才接收的升级包的长度(去除了包头包尾)。待接收到0xA5的包时,表示接收到最后的一包升级分包。接收完成后,烧写成功后,即可开启跳转。因篇幅问题,将程序中的1分钟未接收到升级包便跳转至APP程序略去。程序如下:
函数主要有设置偏移的语句 SCB->VTOR = FLASH_BASE; //@@* Vector Table Relocation in Internal FLASH. */FLASH_BASE=0X08000000;
int main(void)
{
u8 t,half_s;
u8 times=0;
u16 oldcount=0; //老的串口接收数据值
u16 applenth=0; //接收到的app代码长度
u16 buf=0;
u8 CodeSize;
u16 i;
SCB->VTOR = FLASH_BASE; /@@* Vector Table Relocation in Internal FLASH. */
delay_init();
uart_init(115200);
LED_Init();
KEY_Init();
STMFLASH_Read(CONFIG_PARAM_ADDR, &buf, 1);
if(buf!=0xAA)
{
if(((*(vu32*)0x8000000)&0x2FFE0000)==0x20000000) //检查栈顶地址是否合法.
{
delay_init();
uart_init(115200);
LED_Init();
KEY_Init();
}
}
__enable_irq();
while(1)
{
if(USART_RX_CNT)
{
if(oldcount==USART_RX_CNT)//新周期内,没有收到任何数据,认为本次数据接收完成.
{
applenth=USART_RX_CNT;
if(USART_RX_BUF[0]==0x5a&&USART_RX_BUF[applenth-1]==0xaa)
{
LED0=0;
for(i=0;i<applenth-3;i++)
{
Upstring[i]=USART_RX_BUF[i+2];
}
LED0=1;
iap_write_appbin(Flash_App_Pos,Upstring,applenth-3);//更新FLASH代码
Flash_App_Pos+=(applenth-3);
backdata[1]=USART_RX_BUF[1];
Usart1_SendStr_length(backdata,3);
if(USART_RX_BUF[1]==0xa5)
{
iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码FLASH_APP1_ADDR=0X08010000
}
}
oldcount=0;
USART_RX_CNT=0;
}else oldcount=USART_RX_CNT;
}
t++;
delay_ms(100);
}
}
IAP程序点开魔法棒设置size为0x10000.
APP程序主要采用了FreeRTOS操作系统,创建了LED翻转的任务,串口1接收到数据之后之后处理的任务,以及看门狗喂狗任务。同样在程序开始时,需要设置偏移。
NVIC_SetVectorTable(FLASH_APP1_ADDR,0);
串口接收到0XAA,0XBB,0XCC,0XDD,0XEE。的跳转回IAP的指令之后,便写入config之后,开始跳转。跳转前,关闭所有中断,并清除标志位。
程序如下:
int main(void)
{
NVIC_SetVectorTable(FLASH_APP1_ADDR,0);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置中断优先级分组2
delay_init(); //延时函数初始化
Uart1_Init(115200); //串口1初始化为115200
printf("rtos run");
LED_Init(); //LED 初始化
KEY_Init();
IWDG_Init(4,625); //与分频数为64,重载值为625,溢出时间为1s //ADC初始化
AppTaskCreate(); //创建任务
vTaskStartScheduler(); //启动调度,开始执行任务
while(1){}
}
/@@*
*********************************************************************************************************
* 函 数 名: AppTaskCreate
* 功能说明: 创建应用任务
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
static void AppTaskCreate (void)
{
xTaskCreate( vDOG_Feed_Task, /@@* 任务函数 */
"vDOG_Feed_Task", /@@* 任务名 */
100, /@@* 任务栈大小,单位word,也就是4字节 */
NULL, /@@* 任务参数 */
3, /@@* 任务优先级*/
NULL ); /@@* 任务句柄 */
xTaskCreate( vLED_Turn_Task, /@@* 任务函数 */
"vLED_Turn_Task", /@@* 任务名 */
100, /@@* 任务栈大小,单位word,也就是4字节 */
NULL, /@@* 任务参数 */
3, /@@* 任务优先级*/
NULL ); /@@* 任务句柄 */
xTaskCreate( vIAP_UpdataCheck_Task, /@@* 任务函数 */
"vIAP_UpdataCheck_Task", /@@* 任务名 */
100, /@@* 任务栈大小,单位word,也就是4字节 */
NULL, /@@* 任务参数 */
3, /@@* 任务优先级*/
NULL ); /@@* 任务句柄 */
}
各任务函数:
/@@*
*********************************************************************************************************
* 函 数 名: vDOG_Feed_Task
* 功能说明: 看门狗喂狗任务,每200ms喂狗一次。若1s未
喂狗,程序复位。
* 优 先 级: 1 (数值越小优先级越低,这个跟uCOS相反)
*********************************************************************************************************
*/
void vDOG_Feed_Task(void *pvParameters)
{
while(1)
{
IWDG_Feed();
vTaskDelay(200);
}
}
/@@*
*********************************************************************************************************
* 函 数 名: vIAP_UpdataCheck_Task
* 功能说明: 检测远程IAP升级指令,若收到升级指令,则
跳转boot。
* 优 先 级: 1 (数值越小优先级越低,这个跟uCOS相反)
*********************************************************************************************************
*/
void vIAP_UpdataCheck_Task(void *pvParameters)
{
while(1)
{
if(Rx1Flag==1)
{
LED1=0;
STMFLASH_Write(CONFIG_PARAM_ADDR, FLASH_Write , 1);
Usart1_SendStr_length(upflag,3);
USART_ITConfig(USART1, USART_IT_IDLE, DISABLE);
iap_load_app(FLASH_IAP_ADDR);//执行FLASH APP代码
}
vTaskDelay(200);
}
}
/@@*
*********************************************************************************************************
* 函 数 名: vLED_Turn_Task
* 功能说明: 检测远程IAP升级指令,若收到升级指令,则
跳转boot。
* 优 先 级: 1 (数值越小优先级越低,这个跟uCOS相反)
*********************************************************************************************************
*/
void vLED_Turn_Task(void *pvParameters)
{
while(1)
{
LED1=!LED1;
vTaskDelay(500);
}
}
C#上位机程序
private void button3_Click(object sender, EventArgs e)//点击开始按钮
{
try
{
timer1.Start();
textBox2.Text = "文件大小:" + file_len + " Bytes\r\n";
progressBar1.Value = 0;
sendchar = SplitArray(binchar, 0, read_len - 1, 1);
serialPort1.Write(sendchar, 0, read_len + 3);
sendflag = true;
}
catch (Exception)
{
}
}
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
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;
}
}
}
}
说明:STM32程序很多采用了原子的例程,在例程上面改的。
另外keil生成bin文件可以像下图一样设置,在项目文件夹的BIN文件下生成pushrod.bin文件。这就是升级包,在C#程序中打开这个文件即可。
程序下载链接:
https://download.csdn.net/download/qq_23229787/10807490
原创作品,未经权利人授权禁止转载。详情见转载须知。 举报文章
我要举报该内容理由
×