介绍了STM32中Ymodem协议的使用,包括协议的传输效果、协议介绍、最低要求、帧详解以及文件传输过程。通过SecureCRT发送端和接收端的实现,解析了Ymodem协议的帧结构和命令

STM32 Ymodem 协议及代码解析

文章导图:(出处STM32 Ymodem 协议及代码解析-CSDN博客

Ymodem 协议及代码解析思维导图

1. Ymodem 协议传输效果
1.1 发送端软件效果图
  • SecureCRT 软件:SecureCRT 支持 Ymodem 协议传输,可以作为接收端也可以作为发送端,这里我们作为发送端发送升级固件给 STM32 接收端。
  • SecureCRT 软件配置如下:

SecureCRT 软件 Ymodem 协议传输

1.2 STM32接收端效果图

STM32 作为 Ymodem 接收端接收升级固件进行升级。
STM32 IAP 在线升级

2. Ymodem 协议介绍

  YModem 协议是由 XModem 协议演变而来的,每包数据可以达到 1024 字节,是一个非常高效的文件传输协议。我们平常所说的 Ymodem 协议是指的 Ymodem-1K,除此还有 Ymodem-g(没有 CRC 校验,不常用)。YModem-1K 协议用 1024 字节数据帧传输取代了标准的 128 字节数据帧传输,发送的数据会使用 CRC 校验,保证数据传输的正确性。它每传输一个信息块时,就会等待接收端返回 ACK 信号,接收到响应信号后,才会继续传输下一个信息块,从而保证能够接收到全部数据。

3. Ymodem 协议的最低要求

  所有声称支持 Ymodem 协议的项目必须满足以下最低要求:

  • 发送端应在第一个数据包中发送路径名(文件名)。
  • 路径名应为以 “NULL” 结尾的 ASCII 字符串,如下所述:
  •  1)除非有特别要求,否则只发送文件名部分。
  •  2)不发送驱动器符号。
  •  3)不区分文件名中大小写字母的系统应仅以小写形式发送路径名。
  • 接收端应使用第一个数据包中发送的路径名作为接收文件名。
  • 当接收端接收到第一个数据包并成功打开输出文件后,应该返回一个 “ACK” 字符给发送端用来确认这个数据包,然后接收端再发送 “C” 或 “NAK” 来开始进行正常的 Ymodem 传输。
  • 接收端在接收每个文件时要能接受 128 字节和 1024 字节两种数据包的任意混合发送。发送端可以在 1024 字节和 128 字节两种数据包之间任意切换发送。
  • 发送端不能更改未确认的数据包的长度。
  • 在每次文件传输的末尾,发送端只能发送最多 10 次 “EOT” 字符,直到它收到一个 “ACK” 字符。(这是 Xmodem 规范的一部分。)
  • 发送路径名为空的路径名数据包来表示传输会话的结束,该路径名数据包应与其它路径名数据包一样被确认。

  没有满足以上要求的程序不兼容 Ymodem 协议,不应被描述为支持 Ymodem 协议。

4. Ymodem 协议帧详解
4.1 帧格式

  Ymodem 有两种帧格式,主要区别是数据长度的不同。

名称结构长度(byte)标识符备注
帧头SOH/STX10x01/0x02表示帧格式类型;
SOH 为 128 字节帧,STX 为 1024 字节帧;
帧序号FN1帧序号为 1 个字节,取值范围为 0~255;
当帧序号大于 255 后,序号重新从 0 开始累加;
帧序号反码XFN1帧序号的反码,数值为 0xFF-FN;
数据DATA128/1024帧头是 SOH 的数据帧,数据为 128 字节,该类型帧的总长度为 133 字节;
帧头是 STX 的数据帧,数据是 1024 字节,该类型帧总长度为 1029 字节;
校验CRC2Ymodem 采用的是 CRC16 校验算法,校验值为 2 字节,传输时 CRC 高八位在前,低八位在后;
CRC 计算的是数据部分,不包含帧头、帧序号和帧序号反码;
4.2 帧命令
命令命令码备注
SOH0x01start of 128-byte data packet
STX0x02start of 1024-byte data packet
EOT0x04end of transmission
ACK0x06acknowledge
NAK0x15negative acknowledge
CA0x18two of these in succession aborts transfer
C0x43‘C’ == 0x43, request command
ABORT10x41‘A’ == 0x41, abort by user
ABORT20x61‘a’ == 0x61, abort by user
4.3 起始帧

  Ymodem 的起始帧并不直接传输文件的数据,而是将文件名和文件大小放在数据段中进行传输。
它的帧结构如下所示:

名称结构长度(byte)标识符备注
帧头SOH/STX10x01起始帧为128字节帧;
帧序号FN10x00起始帧的帧序号固定为 0x00;
帧序号反码XFN10xFF起始帧的帧序号反码固定为 0xFF;
数据FILENAME要传输的文件名;
数据FILESIZE要传输的文件大小;
数据NULL若 FILENAME 和 FILESIZE 加起来不满 128 字节则以 0x00 填充剩余字节;
校验CRC2数据部分的 CRC 校验;
  • 帧头为 SOH,表示起始帧中包含着128个字节的数据。
  • 起始帧帧序号固定为 0x00,帧序号反码为 0xFF。
  • FILENAME 是要传输的文件名,如 Template.bin,它在数据帧中的格式为:0x54,0x65,0x6D,0x70,0x6C,0x61,0x74,0x65,0x2E,0x62,0x69,0x6E,0x00,也就是把 ASCII 码转成十六进制,但是最后一定要在文件名后加上 0x00,表示文件名的结束。
  • FILESIZE 是要传输的文件大小,如上面的 Template.bin 大小 107544 byte,转换成十六进制就是 0x1A418。它在数据帧中的格式可以是十进制数的 0x31,0x30,0x37,0x35,0x34,0x34,也可以是十六进制数的 0x30,0x78,0x31,0x41,0x34,0x31,0x38,同样最后要加上 0x00 表示结束。
  • NULL 是数据部分若 FILENAME 和 FILESIZE 加起来不满 128 字节则以 0x00 填充剩余字节。
  • CRC 表示的是数据部分的 CRC 校验,不包含帧头、帧序号和帧序号反码。校验值为 2 字节,传输时 CRC 高八位在前,低八位在后。
4.4 数据帧

  YModem 数据帧会将数据段用来传输文件。
它的帧结构如下所示:

名称结构长度(byte)标识符备注
帧头SOH/STX10x01/0x02数据帧可以是 128 字节帧或 1024 字节帧;
帧序号FN1数据帧帧序号为 0~255;
帧序号反码XFN1帧序号的反码,数值为0xFF-FN;
数据DATA要传输的数据;
数据FILL若 DATA 不满 128/1024 字节则以 0x1A 填充剩余字节;
校验CRC2数据部分的 CRC 校验;
  • 帧头为 SOH 时,表示数据帧的数据段为 128 个字节;帧头为 STX 时,表示数据帧的数据段为 1024 个字节。
  • 数据帧帧序号的取值范围为 0~255,帧序号反码是它的取反;
  • DATA 表示要传输的文件数据;
  • FILL 是数据部分若 DATA 不满 128/1024 字节则以 0x1A 填充剩余字节。
  • CRC 表示的是数据部分的 CRC 校验,不包含帧头、帧序号和帧序号反码。校验值为 2 字节,传输时 CRC 高八位在前,低八位在后。
4.5 结束帧

  YModem 的结束帧也采用帧头为 SOH 的 128 字节帧,它的结构如下:

名称结构长度(byte)标识符备注
帧头SOH10x01结束帧为 128 字节帧;
帧序号FN10x00结束帧的帧序号固定为 0x00;
帧序号反码XFN10xFF结束帧的帧序号反码固定为 0xFF;
数据DATA128结束帧的数据部分不存放任何信息,全部用 0x00 填充;
校验CRC2数据部分的 CRC 校验;
  • 帧头为 SOH,表示结束帧中包含着 128 个字节的数据。
  • 结束帧帧序号固定为 0x00,帧序号反码为 0xFF。
  • 结束帧的 128 字节的数据部分不存放任何信息,即全部用 0x00 填充。
  • CRC 表示的是数据部分的 CRC 校验,不包含帧头、帧序号和帧序号反码。校验值为 2 字节,传输时 CRC 高八位在前,低八位在后。
5. 文件传输过程

  文件的传输过程我们用具体的例子来说明。将大小为 107544 byte 的文件 “Template.bin” 作为传输对象,则它的传输过程如下:
Ymodem 协议传输过程

  • 当发送端选择完发送文件后则进入准备发送文件状态。
  • 当接收端想要开始接收文件时发送命令 ‘C’ 请求发送端开始传输文件,然后开始等待发送端的起始帧。如果发送端没有发送起始帧,则接收端可选择超时退出或者继续发送命令 ‘C’ 请求发送端开始传输文件。
  • 发送端在开始时处于准备发送文件状态,等待接收端发送命令 ‘C’。在发送端收到命令 ‘C’ 后发送起始帧给接收端,内容如下(文件名为 Template.bin,文件大小为 107544 byte):
    SOH 00 FF FILENAME[54 65…6E 00] FILESIZE[31 30…34 00] NULL[108] CRC
    然后发送端开始等待接收端发送 ‘ACK’。
  • 当接收端收到起始帧,CRC 校验通过且确定要接收此文件后,则发送 ‘ACK’ 给发送端。
  • 发送端收到 ‘ACK’ 后则开始等待接收端的“请求开始传输数据”信号,即重新进入等待接收端发送命令 “C” 的状态。
  • 接收端发送命令 ‘C’,请求发送端开始传输数据。
  • 发送端在收到命令 ‘C’ 后,开始发送数据帧。内容如下:
    STX 01 FE DATA[1024] CRC
    然后发送端开始等待接收端发送 ‘ACK’。
  • 接收端收到数据帧后,返回一个 ‘ACK’,然后等待接收下一个数据帧,再继续返回 ‘ACK’。直到所有数据传输完毕。
  • 数据传输完毕后,发送端发送 ‘EOT’ 命令给接收端,接收端第一次收到 ‘EOT’ 命令后返回 ‘NAK’,要求进行二次确认。发送端收到 ‘NAK’ 后,重发 ‘EOT’ 命令。接收端第二次收到 ‘EOT’ 命令,返回 ‘ACK’ 应答,确认文件传输完毕。
  • 最后接收端发送命令 ‘C’ 请求发送端开启下一个文件传输,发送端在没有第二个文件要传输的情况下,发送结束帧给接收端。内容如下:
    SOH 00 FF NULL[128] CRC
    在接收端返回 ‘ACK’ 后,正式结束数据传输。
6. STM32 代码解析

BOOT程序

/********************************************************************
* 文件:ymodem.c
* 描述:Ymodem协议实现文件,包含协议处理、数据接收、进度显示等功能
* 版本:v1.0
* 作者:weiwenjie
********************************************************************/

#include "ymodem.h"

// 全局变量定义
file_info_t current_file = {0};  // 当前传输文件信息结构体

// Ymodem协议处理结构体初始化
ymodem_t ymodem = {
    .process = WAIT_START_PROGRAM,    // 初始状态:等待开始编程
    .status = 0,                      // 协议状态机初始状态
    .addr = 0x08004000,               // Flash编程起始地址
    .app_sector_addr = APP_SECTOR_ADDR,    // 应用程序扇区地址
    .file_size = 0,                   // 文件大小初始为0
    .transferred_size = 0,            // 已传输数据大小初始为0
    .file_info = {0},                 // 文件信息结构体清零
    .cb = NULL                        // 回调函数指针初始化为空
};

download_buf_t recvBuf;    // 数据接收缓冲区
seq_queue_t rx_queue;      // 串口接收队列

/********************************************************************
* name         : queue_initiate
* description  : 初始化循环队列
* Input        : Q - 队列结构体指针
* Output       : 无
* Return       : 无
********************************************************************/
void queue_initiate(seq_queue_t *Q)
{
    Q->rear = 0;     // 队尾指针初始化为0
    Q->front = 0;    // 队首指针初始化为0
    Q->count = 0;    // 队列元素计数初始化为0
}

/********************************************************************
* name         : queue_not_empty
* description  : 判断循环队列是否非空
* Input        : Q - 队列结构体指针
* Output       : 无
* Return       : 1-队列非空,0-队列为空
********************************************************************/
int queue_not_empty(seq_queue_t *Q)
{
    if(Q->count != 0)
        return 1;    // 队列中有元素,返回1
    else 
        return 0;    // 队列为空,返回0
}

/********************************************************************
* name         : queue_append
* description  : 数据元素入队列操作
* Input        : Q - 队列结构体指针
*                x - 要插入的数据元素
* Output       : 无
* Return       : 1-插入成功,0-插入失败(队列已满)
********************************************************************/
int queue_append(seq_queue_t *Q, uint8_t x)
{
    if(Q->count > 0 && Q->rear == Q->front)
    {    
        uart_log("queue is full! \n");  // 队列已满,输出提示信息
        return 0;                       // 插入失败
    }
    else
    {    
        Q->queue[Q->rear] = x;                    // 数据存入队尾
        Q->rear = (Q->rear + 1) % MAX_QUEUE_SIZE; // 队尾指针循环递增
        Q->count ++;                              // 队列元素计数加1
        return 1;                                 // 插入成功
    }
}

/********************************************************************
* name         : queue_delete
* description  : 数据元素出队列操作
* Input        : Q - 队列结构体指针
*                d - 用于存储出队数据的指针
* Output       : d - 存储出队的数据
* Return       : 1-删除成功,0-删除失败(队列为空)
********************************************************************/
int queue_delete(seq_queue_t *Q, uint8_t *d)
{
    if(Q->count == 0)
    {    
        // uart_log("queue is empty! \n");  // 队列为空,可取消注释用于调试
        return 0;                           // 删除失败
    }
    else
    {    
        *d = Q->queue[Q->front];                   // 获取队首数据
        Q->front = (Q->front + 1) % MAX_QUEUE_SIZE; // 队首指针循环递增
        Q->count--;                                // 队列元素计数减1
        return 1;                                  // 删除成功
    }
}

/********************************************************************
* name         : queue_get
* description  : 获取队头数据元素(不删除)
* Input        : Q - 队列结构体
*                d - 用于存储队头数据的指针
* Output       : d - 存储队头数据
* Return       : 1-获取成功,0-获取失败(队列为空)
********************************************************************/
int queue_get(seq_queue_t Q, uint8_t *d)
{
    if(Q.count == 0)
    {
        // uart_log("queue is empty! \n");  // 队列为空,可取消注释用于调试
        return 0;                           // 获取失败
    }
    else
    {
        *d = Q.queue[Q.front];  // 获取队首数据但不移动指针
        return 1;               // 获取成功
    }
}

/********************************************************************
* name         : ymodem_ack
* description  : 发送Ymodem ACK确认信号
* Input        : 无
* Output       : 无
* Return       : 无
********************************************************************/
void ymodem_ack(void) 
{
    uint8_t buf;
    buf = YMODEM_ACK;           // 设置ACK字节
    RS485_Send_Data(&buf, 1);   // 通过RS485发送ACK
}

/********************************************************************
* name         : ymodem_nack
* description  : 发送Ymodem NACK否定确认信号
* Input        : 无
* Output       : 无
* Return       : 无
********************************************************************/
void ymodem_nack(void) 
{
    uint8_t buf;
    buf = YMODEM_NAK;           // 设置NAK字节
    RS485_Send_Data(&buf, 1);   // 通过RS485发送NAK
}

/********************************************************************
* name         : ymodem_c
* description  : 发送Ymodem 'C'字符,请求使用CRC校验
* Input        : 无
* Output       : 无
* Return       : 无
********************************************************************/
void ymodem_c(void) 
{
    uint8_t buf;
    buf = YMODEM_C;             // 设置'C'字节
    RS485_Send_Data(&buf, 1);   // 通过RS485发送'C'
}

/********************************************************************
* name         : set_ymodem_status
* description  : 设置Ymodem处理状态
* Input        : process - 要设置的处理状态
* Output       : 无
* Return       : 无
********************************************************************/
void set_ymodem_status(process_status process) 
{
    ymodem.process = process;   // 更新Ymodem处理状态
}

/********************************************************************
* name         : get_ymodem_status
* description  : 获取当前Ymodem处理状态
* Input        : 无
* Output       : 无
* Return       : 当前处理状态
********************************************************************/
process_status get_ymodem_status(void) 
{
    process_status process = ymodem.process;
    return process;             // 返回当前处理状态
}

/********************************************************************
* name         : ymodem_start
* description  : 启动Ymodem传输
* Input        : cb - 回调函数指针
* Output       : 无
* Return       : 无
********************************************************************/
void ymodem_start(ymodem_callback cb) 
{
    if (ymodem.status == 0)     // 确保在初始状态下启动
    {
        ymodem.cb = cb;         // 设置回调函数
    }
}

/********************************************************************
* name         : parse_file_header
* description  : 解析Ymodem文件头信息(文件名和文件大小)
* Input        : data - 包含文件头数据的缓冲区
*                file_info - 文件信息结构体指针
* Output       : file_info - 解析后的文件信息
* Return       : 1-解析成功,0-解析失败
********************************************************************/
int parse_file_header(uint8_t *data, file_info_t *file_info)
{
    int i = 3; // 跳过SOH(0x01), 包序列号, 包序列号取反

    // 解析文件名
    int filename_idx = 0;
    while (data[i] != 0 && i < 128 && filename_idx < sizeof(file_info->filename) - 1) {
        file_info->filename[filename_idx++] = data[i++];  // 逐个字符复制文件名
    }
    file_info->filename[filename_idx] = '\0';             // 添加字符串结束符

    i++; // 跳过文件名结束的0字节

    // 解析文件大小
    char size_str[16] = {0};     // 文件大小字符串缓冲区
    int size_idx = 0;
    while (data[i] != 0 && i < 128 && size_idx < sizeof(size_str) - 1) {
        size_str[size_idx++] = data[i++];  // 逐个字符复制文件大小字符串
    }

    if (size_idx > 0) {
        file_info->filesize = atoi(size_str);           // 字符串转整数
        file_info->file_found = 1;                      // 标记文件头已找到
        file_info->transferred_size = 0;                // 初始化已传输大小为0

        uart_log("File: %s, Size: %lu bytes\r\n", file_info->filename, file_info->filesize);
        return 1;  // 解析成功
    }

    return 0;  // 解析失败
}

/********************************************************************
* name         : show_update_progress
* description  : 显示带进度条的升级进度信息
* Input        : file_info - 文件信息结构体指针
*                current_transferred - 当前已传输字节数
* Output       : 无
* Return       : 无
********************************************************************/
void show_update_progress(file_info_t *file_info, uint32_t current_transferred)
{
    if (file_info->file_found && file_info->filesize > 0) {
        file_info->transferred_size = current_transferred;  // 更新已传输大小

        // 计算百分比(限制在0-100%)
        uint32_t percent = (file_info->transferred_size * 100) / file_info->filesize;
        if (percent > 100) percent = 100;

        // 显示进度条
        uart_log("[");
        for (int i = 0; i < 50; i++) {
            if (i < percent / 2) {
                uart_log("=");      // 已完成部分
            } else if (i == percent / 2) {
                uart_log(">");      // 当前进度位置
            } else {
                uart_log(" ");      // 未完成部分
            }
        }
        uart_log("] %3lu%% (%lu/%lu bytes)\r", 
                 percent, file_info->transferred_size, file_info->filesize);

        // 如果完成,换行
        if (percent == 100) {
            uart_log("\n");
        }
    }
}

/********************************************************************
* name         : show_update_percent
* description  : 显示简化的升级百分比信息
* Input        : file_info - 文件信息结构体指针
*                current_transferred - 当前已传输字节数
* Output       : 无
* Return       : 无
********************************************************************/
void show_update_percent(file_info_t *file_info, uint32_t current_transferred)
{
    if (file_info->file_found && file_info->filesize > 0) {
        file_info->transferred_size = current_transferred;  // 更新已传输大小
        uint32_t percent = (file_info->transferred_size * 100) / file_info->filesize;
        if (percent > 100) percent = 100;                   // 限制百分比不超过100%

        uart_log("Progress: %3lu%% (%lu/%lu bytes)\r",
                 percent, file_info->transferred_size, file_info->filesize);

        if (percent == 100) {
            uart_log("\nUpdate Complete!\r\n");  // 传输完成提示
        }
    }
}

/********************************************************************
* name         : ymodem_recv
* description  : Ymodem数据接收处理函数
* Input        : p - 接收数据缓冲区指针
* Output       : 无
* Return       : 无
********************************************************************/
void ymodem_recv(download_buf_t *p)
{
    uint8_t type = p->data[0];        // 获取数据包类型
    static uint32_t total_transferred = 0;  // 累计传输字节数
    
    switch (ymodem.status)
    {
    case 0:  // 状态0:等待文件头开始包
        if (type == YMODEM_SOH)       // 接收到文件头开始包
        {
            ymodem.process = BUSY;    // 设置为忙状态
            ymodem.addr = APP_SECTOR_ADDR;  // 设置Flash编程地址
            
            uart_log("erase flash: 0x%08X\r\n", APP_SECTOR_ADDR);
            mcu_flash_erase(ymodem.addr, APP_ERASE_SECTORS_NUM);  // 擦除Flash
            uart_log("erase flash success\r\n");
            
            // 解析文件头信息
            if (parse_file_header(p->data, &current_file))
            {
                // 显示开始升级信息
                uart_log("Starting update: %s (%lu bytes)\r\n",
                         current_file.filename, current_file.filesize);
            }
            ymodem_ack();    // 发送ACK确认
            ymodem_c();      // 发送'C'请求CRC校验
            ymodem.status++; // 状态转移到下一阶段
        }
        else if (type == '1')  // 接收到开始编程指令
        {
            uart_log("start program now\r\n");
            ymodem.process = START_PROGRAM;  // 设置为开始编程状态
        }
        else if (type == '2')  // 接收到开始更新指令
        {
            uart_log("enter update mode\r\n");
            ymodem.process = UPDATE_PROGRAM;  // 设置为更新编程状态
        }
        break;
        
    case 1:  // 状态1:接收数据包阶段
        if (type == YMODEM_SOH || type == YMODEM_STX)  // 数据包开始
        {
            uint32_t block_size = 0;
            if (type == YMODEM_SOH)   // 128字节数据包
            {
                block_size = 128;
                mcu_flash_write(ymodem.addr, &p->data[3], 128);  // 写入Flash
                ymodem.addr += block_size;  // 地址递增
            }
            else  // 1024字节数据包
            {
                block_size = 1024;
                mcu_flash_write(ymodem.addr, &p->data[3], 1024);  // 写入Flash
                ymodem.addr += block_size;  // 地址递增
            }

            total_transferred += block_size;  // 累计传输字节数

            // 显示升级进度(每10个包显示一次,避免过于频繁)
            static uint16_t packet_count = 0;
            packet_count++;
            if (packet_count % 10 == 0) {
                show_update_progress(&current_file, total_transferred);  // 显示进度
                // 或者使用简化版本:show_update_percent(&current_file, total_transferred);
            }

            ymodem_ack();  // 发送ACK确认
        }
        else if (type == YMODEM_EOT)  // 传输结束标志
        {
            // 显示最终进度
            ymodem_nack();     // 发送NACK要求重传EOT
            ymodem.status++;   // 状态转移到下一阶段
        }
        else
        {
            ymodem.status = 0;  // 错误情况,重置状态
        }
        break;
        
    case 2:  // 状态2:确认传输结束
        if (type == YMODEM_EOT)  // 再次收到传输结束标志
        {
            uart_log("END \r\n");
            ymodem_ack();      // 发送ACK确认
            ymodem_c();        // 发送'C'准备接收下一个文件
            ymodem.status++;   // 状态转移到下一阶段
        }
        break;
        
    case 3:  // 状态3:接收空包,完全结束传输
        // 空包,表示传输完全结束
        if (type == YMODEM_SOH)
        {
            ymodem_ack();      // 发送最终ACK
            ymodem.status = 0; // 重置状态机
            ymodem.process = UPDATE_SUCCESS;  // 设置为更新成功状态
            uart_log("Update successful! File: %s, Total: %lu bytes\r\n",
                     current_file.filename, total_transferred);
            // 重置文件信息
            memset(&current_file, 0, sizeof(current_file));
        }
    }
    p->len = 0;  // 重置接收缓冲区长度
}

/********************************************************************
* name         : ymodem_init
* description  : Ymodem协议初始化函数
* Input        : 无
* Output       : 无
* Return       : 无
********************************************************************/
void ymodem_init(void)
{
    RS485_Init(115200);        // 初始化RS485通信,波特率115200
    timer_init();              // 初始化定时器
    queue_initiate(&rx_queue); // 初始化接收队列
}

/********************************************************************
* name         : USART2_IRQHandler
* description  : USART2串口中断服务函数
* Input        : 无
* Output       : 无
* Return       : 无
********************************************************************/
void USART2_IRQHandler(void)
{
    uint8_t res;        
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)  // 检查接收中断
    {         
        res = USART_ReceiveData(USART2);          // 读取接收到的数据
        queue_append(&rx_queue, res);             // 数据入队列
        // uart_log("%c",res);                   // 可取消注释用于调试
    }        
    TIM3->CNT = 0;                                // 重置定时器计数值
    TIM_Cmd(TIM3, ENABLE);                        // 使能定时器
}  

/********************************************************************
* name         : timer_init
* description  : 定时器3初始化函数
* Input        : 无
* Output       : 无
* Return       : 无
********************************************************************/
void timer_init(void) 
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // 使能TIM3时钟
    
    // 定时器TIM3初始化
    TIM_TimeBaseStructure.TIM_Period = 999;        // 自动重装载值 (999+1)*1us=1ms
    TIM_TimeBaseStructure.TIM_Prescaler = 71;      // 预分频值 72M/(71+1)=1MHz 1us
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;     // 时钟分割
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);            // 初始化TIM3
 
    TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);     // 使能TIM3更新中断

    // 中断优先级NVIC设置
    NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;           // TIM3中断通道
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级0级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;        // 响应优先级1级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;           // 使能IRQ通道
    NVIC_Init(&NVIC_InitStructure);                           // 初始化NVIC寄存器

    TIM_Cmd(TIM3, ENABLE);  // 使能TIM3定时器
}

/********************************************************************
* name         : TIM3_IRQHandler
* description  : 定时器3中断服务函数,处理接收队列数据
* Input        : 无
* Output       : 无
* Return       : 无
********************************************************************/
void TIM3_IRQHandler(void) 
{
    if(TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)  // 检查更新中断
    {
        int result = 1;
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);  // 清除中断标志
        TIM_Cmd(TIM3, DISABLE);                      // 禁用定时器
        
        result = queue_not_empty(&rx_queue);         // 检查队列是否非空
        if(result == 1)
        {
            recvBuf.len = 0;  // 重置接收缓冲区长度
            // 循环读取队列中的所有数据
            do
            {
                result = queue_delete(&rx_queue, &recvBuf.data[recvBuf.len]);
                if(result == 1)
                {
                    recvBuf.len ++;  // 递增缓冲区长度
                }
            }
            while(result);  // 直到队列为空
            
            ymodem_recv(&recvBuf);  // 处理接收到的数据
        }
    }
}



串口1打印输出

[14:19:14.298]收←◆erase flash: 0x08008000

[14:19:18.037]收←◆erase flash success
File: APP1.bin, Size: 32676 bytes
Starting update: APP1.bin (32676 bytes)

[14:19:18.661]收←◆[=>                                                ]   3% (1280/32676 bytes)
[14:19:19.295]收←◆[===>                                              ]   7% (2560/32676 bytes)
[14:19:19.916]收←◆[=====>                                            ]  11% (3840/32676 bytes)
[14:19:20.539]收←◆[=======>                                          ]  15% (5120/32676 bytes)
[14:19:21.161]收←◆[=========>                                        ]  19% (6400/32676 bytes)
[14:19:21.788]收←◆[===========>                                      ]  23% (7680/32676 bytes)
[14:19:22.411]收←◆[=============>                                    ]  27% (8960/32676 bytes)
[14:19:23.035]收←◆[===============>                                  ]  31% (10240/32676 bytes)
[14:19:23.656]收←◆[=================>                                ]  35% (11520/32676 bytes)
[14:19:24.287]收←◆[===================>                              ]  39% (12800/32676 bytes)
[14:19:24.916]收←◆[=====================>                            ]  43% (14080/32676 bytes)
[14:19:25.529]收←◆[=======================>                          ]  47% (15360/32676 bytes)
[14:19:26.160]收←◆[=========================>                        ]  50% (16640/32676 bytes)
[14:19:26.787]收←◆[===========================>                      ]  54% (17920/32676 bytes)
[14:19:27.415]收←◆[=============================>                    ]  58% (19200/32676 bytes)
[14:19:28.033]收←◆[===============================>                  ]  62% (20480/32676 bytes)
[14:19:28.661]收←◆[=================================>                ]  66% (21760/32676 bytes)
[14:19:29.287]收←◆[===================================>              ]  70% (23040/32676 bytes)
[14:19:29.913]收←◆[=====================================>            ]  74% (24320/32676 bytes)
[14:19:30.532]收←◆[=======================================>          ]  78% (25600/32676 bytes)
[14:19:31.164]收←◆[=========================================>        ]  82% (26880/32676 bytes)
[14:19:31.785]收←◆[===========================================>      ]  86% (28160/32676 bytes)
[14:19:32.415]收←◆[=============================================>    ]  90% (29440/32676 bytes)
[14:19:33.037]收←◆[===============================================>  ]  94% (30720/32676 bytes)
[14:19:33.657]收←◆[================================================> ]  97% (32000/32676 bytes)
[14:19:34.157]收←◆END 

[14:19:34.225]收←◆Update successful! File: APP1.bin, Total: 32768 bytes
update success
system reboot...

[14:19:35.234]收←◆---------- Enter BootLoader ----------

======== flash pration table =========
| name     | offset     | size       |
--------------------------------------
| boot     | 0x08000000 | 0x00004000 |
| setting  | 0x08004000 | 0x00004000 |
| app      | 0x08008000 | 0x00078000 |
======================================
------- Please enter parameter -------
USArt2 Send:'1'-->Start program
USArt2 Send:'2'-->Update program
--------------------------------------
wait start app...(5s)

[14:19:36.281]收←◆wait start app...(4s)

[14:19:37.283]收←◆wait start app...(3s)

[14:19:38.287]收←◆wait start app...(2s)

[13:51:26.920]收←◆---------- Enter BootLoader ----------

======== flash pration table =========
| name     | offset     | size       |
--------------------------------------
| boot     | 0x08000000 | 0x00004000 |
| setting  | 0x08004000 | 0x00004000 |
| app      | 0x08008000 | 0x00078000 |
======================================
------- Please enter parameter -------
USArt2 Send:'1'-->Start program 
串口1 显示打印信息
串口2 接受升级指令 和APP.bin数据

APP程序 其重点设置便偏移地址

代码链接下载:https://download.csdn.net/download/qq_36958104/92287760



 

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐