你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【福利三:逢7发帖赢大礼】8、基于STM32G070板子的Ymodem串口协议IAP Bootloader开发

[复制链接]
STMWoodData 发布时间:2026-7-26 16:58

之前已经在板子上跑通了Ymodem协议通信,本次就做一下基于Ymodem协议实现IAP Bootloader代码更新。之前也在其他MCU上实现过Ymodem更新,这次基于之前的经验,很快也实现了这个基于STM32G070的Bootloader。

Ymodem协议介绍:

首先简单介绍一下Ymodem协议。Ymodem协议是一种基于Xmodem改进的文件传输协议,广泛用于嵌入式系统串口传输。它支持文件名、文件大小、块大小可选(128或1024字节),并采用CRC16校验,可靠性更高。下面详细介绍其帧格式和传输流程。

Ymodem的数据包分为三种:开始包(文件名/大小)、数据包(文件内容)、结束包(空包)。每种包都包含包头、序号、序号补码、数据区、CRC校验

  • 包头SOH(128字节)或 STX(1024字节)。
  • 序号 :从0开始递增,每个包序号加1(模256)。开始包序号为0,数据包从1开始。
  • 补码0xFF - 序号,用于简单校验。
  • 数据区 :包体内容。对于开始包,为文件名字符串 + 空格 + 文件大小(十进制字符串),不足长度用0x00填充;对于数据包,为文件数据;对于结束包,全为0x00
  • CRC :对数据区 计算得到的CRC16-CCITT值(多项式0x1021),先高字节后低字节。

YModem传输流程:

接收方通常首先发送字符C,通知发送方已准备好使用CRC校验。

  1. 接收方发送C (可重复发送直到收到第一个包或超时)。
  2. 发送方发送开始包 (包序号0,数据区包含文件名 文件大小,末尾填充0x00)。
    • 接收方收到后,若CRC正确且序号匹配,回复<span leaf="">ACK</span>,然后发送C请求下一个包。
    • 若CRC错误或序号错,回复NAK,发送方重发。
  3. 发送方发送数据包 (序号从1开始,每包数据长度128或1024,最后一个包可能不足)。
  • 接收方每正确接收一包,回复ACK,序号递增。
  • 若出错,回复NAK要求重发当前包。
  1. 发送方发送EOT (表示文件数据发送完毕)。
  • 接收方回复ACK
  1. 发送方发送结束包 (包序号0,数据区全0x00)。
  • 接收方回复ACK,传输完成。

接下来实现Ymodem协议接口的读写flash回调。这里注意STM32G070的flash每次是8字节写入。

volatile uint32_t ymodem_tick;
static uint32_t get_tick(void) {
    return ymodem_tick;
}

uint8_t  yModem_PutChar(uint8_t ch)
{
    uart_send_char(1, ch);
    return 0;
}
uint8_t  yModem_GetChar(uint8_t *ch)
{
    return uart_recv_char(1, ch);
}

uint32_t CurAddrWrite;
uint32_t CurFileSize;
uint32_t CurFileWriteSize;

uint32_t CurFileInfoSize;
uint64_t CurFileInfoBuff[64];   //64*8=512字节

/* 进度回调 */
static void progress_callback(uint32_t total, uint32_t current)
{
    uint8_t percent = (current * 100) / total;
    ymodem_printf("Progress: %d%%\r\n", percent);
}


static uint32_t file_open_write(const char* name, uint32_t size)
{
    uint32_t addr,i,page_err,status;
    FLASH_EraseInitTypeDef flash_erase_temp;
    /* 打开文件准备写入 */
    ymodem_printf("ymodem recv file: %s,lenght:%u.\r\n",name,size);
    CurFileSize = size;
    CurFileWriteSize = 0;
    CurFileInfoSize = 0;
    if(CurFileSize > APP_FW_FLASH_SIZE)
    {
        return 1;
    }
    CurAddrWrite = APP_FW_START_ADDR;
    flash_erase_temp.TypeErase = FLASH_TYPEERASE_PAGES;
    flash_erase_temp.Page      = 10;    // 从 N page * 2K = 20K开始擦除
    flash_erase_temp.NbPages   = 50;    // 擦除50个page页
//    flash_erase_temp.Banks     = 
    //擦除
    HAL_FLASH_Unlock();
    do{
        status = HAL_FLASHEx_Erase(&flash_erase_temp,&page_err);
    }while(status != HAL_OK);
    HAL_FLASH_Lock();

    ymodem_printf("flash erase over.\r\n");
    progress_callback(CurFileSize,CurFileWriteSize);
    return 0;
}

static uint32_t file_write(const uint8_t* buff, uint32_t len)
{
    /* 写入文件 */
    uint32_t i;
    uint64_t *dat;
    uint32_t g_status;
    ymodem_printf("ymodem write file:%u.\r\n",len);
    if(CurFileSize > APP_FW_FLASH_SIZE)
    {
        return 0;
    }

    HAL_FLASH_Unlock();
    for(i=0;i<len;i+=8) //page=2048byte
    {
        dat = (uint64_t *)(buff+i);
        do{
            g_status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,CurAddrWrite,*dat);
        }while(g_status != HAL_OK);
        CurAddrWrite += 8;
    }
    HAL_FLASH_Lock();
    CurFileWriteSize += len;
    ymodem_printf("flash write OK.\r\n");

    progress_callback(CurFileSize,CurFileWriteSize);
    return len;
}

static uint32_t file_close_write(void) 
{
    /* 关闭文件 */
    uint32_t g_status;
    HAL_FLASH_Unlock();
    do{
        g_status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, APP_OK_MAGIC_WORD_ADDR, 0xFFFFFFFF00000000 | APP_OK_MAGIC_WORD);
    }while(g_status != HAL_OK);
    HAL_FLASH_Lock();
    progress_callback(CurFileSize,CurFileWriteSize);

//    HAL_FLASH_Unlock();
//    do{
//        g_status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,APP_SOFTRST_MAGIC_WORD_ADDR , 0xFFFFFFFF00000000 | APP_SOFTRST_MAGIC_WORD);
//    }while(g_status != HAL_OK);
//    HAL_FLASH_Lock();

    ymodem_printf("bootloader ymodem write file over.\r\n");

    return 0;
}

再就是Ymodem协议处理主函数:

ymodem_handle_t g_flash_ymodem;

void YModem_Flash_Proc(void)
{
    CurAddrWrite = 0;
    CurFileSize  = 0;
    ymodem_tick = 0;

    ymodem_init(&g_flash_ymodem, yModem_GetChar, yModem_PutChar, get_tick);

    ymodem_start_receive(&g_flash_ymodem, 25000, file_open_write, file_write, file_close_write);

    while (1)
    {
        ymodem_result_t res = ymodem_receive_process(&g_flash_ymodem);
        if (res == YMODEM_RESULT_SUCCESS) {
            ymodem_printf("\n\nsuccess!\n");
            break;
        } else if (res != YMODEM_RESULT_IN_PROGRESS) {
            ymodem_printf("\n\nerror: %d\n", res);
            break;
        }

        //将串口FIFO内数据继续发送
        for(int i=0;i<UART_DEV_NUM;i++)
        {
            uart_flush_data(i);
        }
    }
    while(uart_flush_data(0));
    while(uart_flush_data(1));
    // 直接调用库函数
    NVIC_SystemReset();

    return ;
}

接着是检查标志从Bootloader跳转APP运行。这里通过按复位键强制进入Bootloader,或者向指定地址写标志数据,软复位后检查标志强制进入Bootloader。

uint32_t    UpdateFlag,RstTimeTick;   //

//typedef     void (*pFun)(void);
//pFun        Jump_to_app;
//uint32_t    Jump_addr;


#if defined (__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050)
/* Avoids the semihosting issue */
__asm("  .global __ARM_use_no_argv\n");
#elif defined(__GNUC__)
/* Disables part of C/C++ runtime startup/teardown */
void __libc_init_array (void) {}
#endif

#if defined(__CC_ARM)
__asm void modify_stack_pointer_and_start_app(uint32_t r0_sp, uint32_t r1_pc)
{
    MOV SP, R0
    BX R1
}
#elif defined(__GNUC__)
void modify_stack_pointer_and_start_app(uint32_t r0_sp, uint32_t r1_pc)
{
    uint32_t z = 0;
    __asm volatile (  "msr    control, %[z]   \n\t"
                      "isb                    \n\t"
                      "mov    sp, %[r0_sp]    \n\t"
                      "bx     %[r1_pc]"
                      :
                      :   [z] "l" (z),
                          [r0_sp] "l" (r0_sp),
                          [r1_pc] "l" (r1_pc)
                   );
}
#else
#error "Unknown compiler!"
#endif


//这里检查复位标识。
void IAP_CheckFlag(void)
{
    uint32_t    Rstflag;
    uint32_t    *pdat;

    UpdateFlag = 0;
    //软件复位或者RST引脚复位进入Bootloader。
    Rstflag = RCC->CSR & 0xFF000000;
    RCC->CSR |= (0x00000001 << 23);  //清复位标志 
    //0x10000000 = 软复位,0x04000000=复位脚按键复位
    if(Rstflag == 0x04000000)
    {
        UpdateFlag |= 0x00000001;   //需要更新
    }else if(Rstflag == 0x10000000)
    {
        //软复位后,检查特定地址值确定是否进入bootloader。
        pdat = (uint32_t *)(APP_SOFTRST_MAGIC_WORD_ADDR);
        if(*pdat == APP_SOFTRST_MAGIC_WORD) //
        {
            UpdateFlag |= 0x00000001;   //需要更新
        }
    }
    //=====================================================
    pdat = (uint32_t *)(APP_OK_MAGIC_WORD_ADDR);
    if(*pdat == APP_OK_MAGIC_WORD) //有app代码
    {
        UpdateFlag |= 0x00000002;
    }else
    {
        UpdateFlag |= 0x00000001;   //需要更新
    }

    if(UpdateFlag == 2)
    {
        if(((*(__IO uint32_t *)APP_FW_START_ADDR)&0x2FFE0000)==0x20000000)
        {
//            Jump_addr = *(__IO uint32_t *)(APP_FW_START_ADDR + 4);
//            Jump_to_app = (pFun)Jump_addr;
//            __set_MSP(*(__IO uint32_t *)(APP_FW_START_ADDR));
//            Jump_to_app();

            modify_stack_pointer_and_start_app(*(volatile uint32_t *)APP_FW_START_ADDR,
                                   (*(volatile uint32_t *)(APP_FW_START_ADDR + 4)));
        }
        UpdateFlag = 1;
    }
    RstTimeTick = 10000;
}

APP的代码空间分配:前20K用于Bootloader代码。后面从0x08005000开始的512字节为信息标志区。从0x08005200开始为APP代码区。

#define  APP_FW_INFO_ADDR       (uint32_t)(0x08005000)
#define  APP_FW_START_ADDR      (APP_FW_INFO_ADDR + 0x200)

#define  APP_FW_FLASH_SIZE      (uint32_t)(1024*100)      //100K
#define  APP_FW_PAGE_SIZE       (uint32_t)(0x00000800)    //2048
#define  APP_FW_END_ADDR        (APP_FW_START_ADDR + APP_FW_FLASH_SIZE)

//存储APP标志数值地址和标志值
#define  APP_OK_MAGIC_WORD_ADDR         (uint32_t)(APP_FW_START_ADDR - 0X08)
#define  APP_OK_MAGIC_WORD              (uint32_t)(0x555AA5AA)

#define  APP_SOFTRST_MAGIC_WORD_ADDR    (uint32_t)(APP_FW_START_ADDR - 0X108)
#define  APP_SOFTRST_MAGIC_WORD         (uint32_t)(0x12345678)

//=========================================================================
extern void IAP_CheckFlag(void);
extern void YModem_Flash_Proc(void);

主函数:

image.png

上面就完成了Ymodem的Bootloader的代码,下面做个简单的app代码测试更新效果。app实现一个简单的点灯和串口打印功能。

image.png

APP代码还要修改中断向量表。设置偏移地址为0x08005200。

image.png

再就是flash分配链接地址也要设置在0x08005200之后。

image.png

接着就是将hex转为bin文件。

D:\Keil_v5\ARM\ARMCLang\bin\fromelf.exe --bin -o PocketDev_demo.bin .\PocketDev\PocketDev.axf

image.png

编译之后通过Ymodem协议软件发送固件更新。

image.png

image.png

image.png
收藏 评论0 发布时间:2026-7-26 16:58

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版