1、烧写程序三种方式
嵌入式系统的主要升级方式有ICP、IAP、ISP。
- ISP(In-SystemProgramming)为在系统中编程,出厂时ST公司已经在ROM中写入了BootLoader,通过通信接口就可以擦除与编程用户程序;
- IAP(In-ApplicationProgram)为在应用程序中编程,用户在运行程序的时候,通过BootLoader程序对指定的Flash区域进行烧写,除了ST公司出厂时写入的BootLoader外,用户可以自己写入一个BootLoader程序来运行存储在Flash中指定区域的程序,在程序的运行过程中我们就可以通过任何通讯方式,把接收到的bin文件写入到指定区域的Flash中,然后进行应用程序的升级;
- ICP(In-CircuitProgramming)为在电路编程,这是一种串行编程方式,可以使用JTAG或者SWD接口来对STM32进行程序烧写。
2、远程升级实现
首先,我的思路是将STM32的FLASH区域化分为四大区域,例如我使用的是STM32F103VET6,该芯片的FLASH区域有512K。第一块区域大小为50K,用来存放BOOTLOADER程序。第二块与第三块区域可以每个分200K大小,用来存放用户应用程序一(APP1)与用户应用程序二(APP2)。第四部分就用来存放各种标志位,比如当前运程的程序标志、应用程序版本标志等等。如果当前是运行在APP1下,那么接收到的升级程序就要写在APP2的区域内。我采用的是MQTT协议来实现远程升级,设备端使用Wi-Fi连接至互联网,通过MQTT协议连接至云端服务器。这里用的MQTT服务器为EMQX,并使用python作为另一个客户端(paho-mqtt)连接至服务器,这样就实现了python写的客户端与设备终端之间的通讯。
终端设备程序流程图(示意):
(1) BOOTLOADER
BOOTLOADER程序程序负责根据升级FLAG去跳转至对应的APP程序,没有其他工作。在编译时要制定FLASH空间,分配50K的大小。跳转的程序就不列出来了,有很多可以参考的例程。
(2)用户应用程序一(APP1)
使用ESP8266模块连接Wi-Fi,并通过MQTT协议连接至云端服务器。由于我的应用程序中使用了ucOSIII与emwin所以,bin文件会比较大。此时使用MQTT协议一次发送数据包过大可能会引起数据丢失等错误。所以为了避免这些错误。经测试,一次发送4K左右的数据包时可以保证数据的完整度。所以当bin文件大小为200K左右时,需要将其分成50个数据包来发送数据。如果要实现多台设备终端升级时,可以考虑先订阅一个升级包,收到该主题的消息后就取消订阅。这样两个设备之间不会相互影响。
APP1的地址要避开其他。
一定要在main函数中加入这条。放在最前面,设置中断向量表的偏移。
SCB->VTOR = FLASH_BASE | 0x0800C800;
(2)用户应用程序二(APP2)
同APP1
同样要在main函数中首先设置中断向量偏移。
SCB->VTOR = FLASH_BASE | 0x803E800;
3、Bootloader程序
1.首先先给主函数的例子
// 设置FLASH保存地址(必须为偶数,且其值要大于本代码所占用FLASH的大小)
// Bootloader 程序空间50k
/* 第一个APP的起始位置 IAP程序空间留有(100k)每一个APP大小为200K */
#define FLASH_APP1_ADDR 0X0800C800
/* 第二个APP的起始位置为250K */
#define FLASH_APP2_ADDR 0X0803E800
/* APP_FLAG存储起始位置为450K */
#define FLASH_FLAG_ADDR 0X08070800
#define Flag_Size 5
int main(void)
{
USARTx_Init();
uint8_t Read_Flag[5];
STMFLASH_Read(FLASH_FLAG_ADDR,(uint16_t *)Read_Flag,Flag_Size);
while(1)
{
if(strcmp((char *)Read_Flag,"0") == 0)
{
printf("开始执行用户代码(app1)!\n");
if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)
{
iap_load_app(FLASH_APP1_ADDR);
}
else
printf("FLASH地址有误!\n");
}
if(strcmp((char *)Read_Flag,"1") == 0)
{
printf("开始执行用户代码(app2)!\n");
if(((*(vu32*)(FLASH_APP2_ADDR+4))&0xFF000000)==0x08000000)
{
iap_load_app(FLASH_APP2_ADDR);
}
else
printf("FLASH地址有误!\n");
}
}
}
2.iap_load_app()函数
typedef void (*iapfun) (void);
iapfun jump2app;
void iap_load_app(uint32_t app_addr)
{
// 检查栈顶地址是否合法.
if(((*(vu32*)app_addr)&0x2FFE0000)==0x20000000)
{
jump2app = (iapfun)*(vu32*)(app_addr + 4);
MSR_MSP(*(vu32*)app_addr);
jump2app();
}
}
3.这样就完成每次读取(FLASH_FLAG_ADDR 0X08070800)这个地址的存放的数据,然后根据读出的标志是0还是1来跳转至相应的APP区域执行程序。
4.关于升级文件的接收
首先我们的单片机要连接到EMQ X服务器,然后订阅相关的升级Topic。当单片机发送升级请求时,使用python编写的MQTT客户端接收到请求,根据所要升级的文件大小,将其分成若干个小包(我这里测试过每包分为4K的大小单片机可以正常接收数据)。每个小包为一个Topic,然后将数据发送至单片机。单片机每接收一个topic,经过检查后将其写入FLASH。接收完所有的升级包后,将FLASH_FLAG_ADDR中的标志位置0或1,通过软复位来进入bootloader程序。然后bootloader程序根据标志位跳转相应的APP区。
4.后期还会继续加更完整程序(狗头)
关于MQTT程序https://blog.csdn.net/qq_40893726/article/details/109449151
在这篇文章有介绍