本帖最后由 shanyuxiang 于 2024-4-27 18:27 编辑
#申请原创# @21小跑堂
用Xmodem协议实现APM32的固件升级
一、前言
1.1、关于Xmodem协议
Xmodem是一个经典的串口文件输协议,发送端发送大小为128字节或1024字节的包,每一包都带有CRC校验,接收端根据接收到的包返回一个字节的控制字。
由发送端发出的控制字:
由接收端发出的控制字:
简单地说说Xmodem的通信流程:
接收端(MCU)运行后间每隔大约1s发送一个字节的0x43('C'),发送端(PC上位机)收到0x43后发送第一包,
接收端收到这一帧并且CRC校验正确后返回ACK,然后发送端才会会发下一包;如果CRC不对返回的是NAK,则发送端重传该包。
当所有数据包传输完成后,发送端发送0x04(EOT)表示文件已经全部传完。
网上关于Xmodem资料很多,有兴趣的话可以找来看看,这里就不展开了。
Xmodem这种具有校验并重传的协议非常适合用来做固件的传输,有固定的包大小也便于flash的扇区写入。
这里就用一个Xmodem的开源协议栈代码来实现APM32的固件下载和升级。
1.2、准备工作
xmodem的协议栈代码:
Xmodem function source code
https://www.menie.org/georges/embedded/#xmodem
上位机软件:
支持xmodem协议的文件发送软件或串口助手皆可,例如SecureCRT
SDK工程:
APM32E10x_SDK_V1.1
二、Xmodem协议栈的移植
2.1 源码文件的添加
首先添加下载下来的开源代码 xmodem.c 、crc16.c,同时新建一个 xmodem_port.c 存放待补充的代码。
2.2 补充待实现的代码
xmodem.c中还需要实现这几个函数,usleep、_inbyte、_outbyte。
usleep是一个微秒级延时,时间不用太精准,能实现等待就可以,这里滴答定时做一个:
void usleep(unsigned int us)
{
unsigned int temp;
SysTick->LOAD = us * (SystemCoreClock / 1000000 / 8); //时间加载
SysTick->VAL = 0x00;
SysTick->CTRL = 0x01 ;
do
{
temp = SysTick->CTRL;
}
while (temp & 0x01 && !(temp & (1 << 16)));
SysTick->CTRL = 0x00;
SysTick->VAL = 0X00;
}
_inbyte是字节的读取,有点类似文件的读取,用串口的轮询接收来实现:
unsigned short uart_read()
{
unsigned short data = 0;
if (USART_ReadStatusFlag(XMODEM_UART, USART_FLAG_RXBNE) != RESET)
{
data = USART_RxData(XMODEM_UART);
data |= 0x2000;
}
return data;
}
int _inbyte(unsigned short timeout) // msec timeout
{
unsigned short c;
int delay = timeout << 4;
while (((c = uart_read()) & 0x2000) == 0)
{
usleep(5);//usleep(60); /* 60 us * 16 = 960 us (~ 1 ms) */
if (timeout)
{
if (--delay == 0) return -2;
}
}
return c & 0x0FF;
}
_outbyte 是字节发送,这个好办,用串口的字节发送实现:void _outbyte(int byte)
{
while (USART_ReadStatusFlag(XMODEM_UART, USART_FLAG_TXBE) == RESET);
USART_TxData(XMODEM_UART, byte);
}
2.3 Xmodem的调用
注释掉xmodem.c中宏定义 "TEST_XMODEM_RECEIVE",并给其中的main()函数改个名,改成xmodem_main()。
在真正的main函数循环中执行xmodem_main(),让协议栈一直运行,最后不要忘了初始化一下xmodem要用到的串口。
xmodem_init(); //初始化Xmodem所需要的串口
while (1)
{
xmodem_main();
}
至此,Xmodem的移植就完成了,接下来就来实现固件的烧写。
三、固件的烧录
3.1 内部flash的擦写
具体操作可以参考FMC例程,简单的说就是解锁、擦除、编程...擦除、编程、上锁。
因为这个boot要占用4KB多的空间,所以APP就从8KB的位置开始,这也就为什么烧写的起始地址是0x08002000;
该芯片的最小擦除单位是2KB,所以每次就按2KB来写入。
#define FLASH_PAGE_SIZE 2048
#define FLASH_BOOT_ADDR 0x08002000
void xmodem_write_flash(unsigned char buf[], unsigned int len)
{
unsigned int sectors, i, j;
unsigned int word;
sectors = (len + 2047) / 2048;
FMC_Unlock();
for (i = 0; i < sectors; i++)
{
FMC_ErasePage(FLASH_BOOT_ADDR + (FLASH_PAGE_SIZE * i));
for (j = 0; j < FLASH_PAGE_SIZE / 4; j++)
{
word = buf[FLASH_PAGE_SIZE * i + j * 4 + 0];
word |= buf[FLASH_PAGE_SIZE * i + j * 4 + 1] << 8;
word |= buf[FLASH_PAGE_SIZE * i + j * 4 + 2] << 16;
word |= buf[FLASH_PAGE_SIZE * i + j * 4 + 3] << 24;
FMC_ProgramWord(FLASH_BOOT_ADDR + FLASH_PAGE_SIZE * i + j * 4, word);
}
}
FMC_Lock();
}
3.2 APP的接收与写入
先建一个缓存用来存放接收固件文件,数组地址传给 xmodemReceive 函数,当接收完成后,调用刚才写好 xmodem_write_flash 函数对固件进行烧写。
int xmodem_main(void)
{
int st;
printf("Send data using the xmodem protocol from your terminal emulator now...\r\n");
/* the following should be changed for your environment:
0x30000 is the download address,
65536 is the maximum size to be written at this address
*/
//st = xmodemReceive((char *)0x30000, 65536);
st = xmodemReceive((char *)FirmwareBuffer, 65536);
if (st < 0)
{
printf("Xmodem receive error: status: %d\r\n", st);
}
else
{
printf("Xmodem successfully received %d bytes\r\n", st);
xmodem_write_flash(FirmwareBuffer, st);
}
return 0;
}
3.3 跳转到APP执行
固件烧写完后就可以跳转了,常规流程,先判断一下APP的头,如果正确就跳转到APP。
typedef void (*pFunction)(void);
pFunction JumpToApplication;
unsigned int JumpAddresss;
void xmodem_jump(void)
{
if (((*(__IO uint32_t *)FLASH_BOOT_ADDR) & 0x2FFE0000) == 0x20000000)
{
printf("[boot]APM32 xmodem jump\r\n\r\n");
JumpAddresss = *(__IO uint32_t *)(FLASH_BOOT_ADDR + 4);
JumpToApplication = (pFunction)JumpAddresss;
__set_MSP(*(__IO uint32_t *)FLASH_BOOT_ADDR);
JumpToApplication();
}
else
{
printf("[boot]APM32 invalid app!\r\n\r\n");
}
printf("[boot]APM32 xmodem jump fail!\r\n");
}
四、固件升级的测试验证
4.1 制作测试用的APP
bootloader代码已经完成,为了测试固件升级功能,还需要做一个app固件。相对于bootloader来说,app部分就比较简单了。
因为APP是打算放在 0x08002000 的位置,所以需要让编译出的APP代码的在 0x08002000 基础上进行偏移,在工程设置中改一下编译的地址:
另外 system_apm32e10x.c 中的向量偏移也改一下:
#define VECT_TAB_OFFSET 0x2000//0x00
APP中加点串口打印代码便于观察执行情况:
printf("\r\n\r\n[app]APM32 Xmodem appliaction star...\r\n");
while (1)
{
Delay();
printf("[app]%03d\r\n", LoopCount++);
}
为了得到APP的bin文件,在工程设置选项中添加这条命令并编译:
fromelf.exe --bin -o "$L@L.bin" "#L
4.2 用 SecureCRT 发送固件文件
下面是见证奇迹的时刻,先运行bootloader程序,打开 SecureCRT 软件,协议选择"Serial",设置正确的串口号和波特率,点下面的"Connect"按钮:
串口打开后会看到MCU一直在发送字符'C':
这时拖动刚才编译好的bin文件,选择"Send Xmodem...":
等待进度完成,程序跳转,看到App程序执行并打印出串口信息,至此固件成功升级。
不足与改进:
这里的xmodem是接收完整个固件才进行烧录操作,当app固件非常大时需要占用很大的sram空间;
理论上来说xmodem是支持边收边烧写的,比如收够2KB字节就写入到flash中,这样适用面会更广。
|
在APM32上使用Xmodem协议通过串口升级固件,实现步骤详细,代码简介清晰,实现效果较好,不足之处也有举出,期待后续完善实验。