发新帖本帖赏金 80.00元(功能说明)我要提问
返回列表
打印
[APM32E1]

用Xmodem协议实现APM32的固件升级

[复制链接]
2710|4
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 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中,这样适用面会更广。








      

APM32E10x_SDK_V1.1_xmode_boot.rar

275.18 KB, 阅读权限: 10

源码工程

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 80.00 元 2024-04-28
理由:恭喜通过原创审核!期待您更多的原创作品~

评论
21小跑堂 2024-4-28 16:15 回复TA
在APM32上使用Xmodem协议通过串口升级固件,实现步骤详细,代码简介清晰,实现效果较好,不足之处也有举出,期待后续完善实验。 
沙发
xiaoqi976633690| | 2024-4-28 17:57 | 只看该作者
学习了

使用特权

评论回复
板凳
WoodData| | 2024-4-28 23:17 | 只看该作者
学习了

使用特权

评论回复
地板
WoodData| | 2024-4-28 23:17 | 只看该作者
学习了

使用特权

评论回复
发新帖 本帖赏金 80.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

5

主题

28

帖子

1

粉丝