YModem协议-嵌入式系统远程升级OTA,BootLoad文件传输轻量化协议

Ymodem协议数据帧格式详解
博客介绍了Xmodem、Ymodem和Zmodem三种常用通信协议,重点阐述Ymodem协议。Ymodem是Xmodem改进版,传输快速稳定。详细说明了Ymodem起始帧、数据帧、结束帧的数据格式,还提及文件传输过程略,最后给出相关代码文件名。

前言:

Xmodem、Ymodem和Zmodem协议是最常用的三种通信协议。

Xmodem协议是最早的,传输128字节信息块。

Ymodem是Xmodem的改进版协议,具有传输快速稳定的优点。它可以一次传输1024字节的信息块,同时还支持传输多个文件。

平常所说的Ymodem协议是指的Ymodem-1K,除此还有Ymodem-g(没有CRC校验,不常用)。

YModem-1K用1024字节信息块传输取代标准的128字节传输,数据的发送回使用CRC校验,保证数据传输的正确性。它每传输一个信息块数据时,就会等待接收端回应ACK信号,接收到回应后,才会继续传输下一个信息块,保证数据已经全部接收。

1.起始帧的数据格式

YModem的起始帧并不直接传输文件的数据,而是将文件名与文件的大小放在数据帧中传输,它的帧长=3字节数据首部+128字节数据+2字节CRC16校验码=133字节。它的数据结构如下:

SOH 00 FF  filename  filezise NUL  CRCH CRCL

其中SOH=0x01,表示这个数据帧中包含着128个字节的数据(STX表示1024字节,初始帧只有128个),00表示数据帧序号,初始是0,依次向下排,FF是帧序号的取反,filename是要传输的文件名,如USTB_V3_1.0.1.26_NMEA.Bin,它在数据帧中的格式为:55 53 54 42 5F 56 33 5F 31 2E 30 2E 31 2E 32 36 5F 4E 4D 45 41 2E 42 69 6E 00,也就是把ASCII码转成十六进制,但是最后一定要在文件名后加上00,表示文件名的结束;filesize表示文件的大小,如上面的USTB_V3_1.0.1.26_NMEA.Bin大小是132KB,也就是135168Byte,转换成十六进制就是0x21000,它在数据帧中的格式就是32 31 30 30 30 00,也就是ASCII的“21000”,同样最后要加上00表示结束,NUL就是数据部分的128字节中除去文件名和文件大小占据的剩下的字节都用00填充,CRCH和CRCL分别表示16位CRC校验码的高8位与低8位。

2.数据帧的数据格式

       YModem的数据帧中会预留1024字节空间用来传输文件数据,它跟起始帧接收差不多,如下:

       STX 01 FEdata[1024] CRCH CRCL

        其中STX=0x02,表示这帧数据帧后面包含着1024字节的数据部分;01是表示帧序号,FE是它的取反,再下一帧数据就是02 FD,以此类推;data[1024]表示存放着1024字节的文件数据;CRCH与CRCL是CRC16检验码的高8位与低8位。

        如果文件数据的最后剩余的数据在128~1024之前,则还是使用STX的1024字节传输,但是剩余空间全部用0x1A填充,如下结构:

        STX 01 FE data[1024] 1A 1A……… CRCH CRCL

        有一种特殊的情况:如果文件大小小于等于128字节或者文件数据最后剩余的数据小于128字节,则YModem会选择SOH数据帧用128字节来传输数据,如果数据不满128字节,剩余的数据用0x1A填充这是数据帧的结构就变成了:

文件大小小于128字节:               SOH 01 FE data[ ] 1A ...1A CRCH CRCL  

文件最后剩余数据小于128字节:  SOH 01 FE data[ ] 1A...1A CRCH CRCL

3.结束帧数据结构

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

        SOH 00 FF NUL[128] CRCH CRCL

        结束帧同样以SOH开头,表示后面跟着128字节大小的数据;结束帧的帧序也认为是00 FF;结束帧的128字节的数据部分不存放任何信息,即全部用00填充。

4.文件传输过程

     略;

注:完整工程请联系作者获取,可以定制上位机配合使用,网上也能找到相关上位机可以使用。

代码:

ymodem.c

#include "ymodem.h"
#include "common.h"
#include "string.h"
#include "stdio.h"
#include "ymodem.h"
#include "usart.h"
#include "flash_if.h"


uint8_t ymodem_file_name[FILE_NAME_LENGTH];
uint8_t tab_1024[1024] = {0};


#ifdef __EEPDEBUG
#define ymodem_ymodem_printf(format, ...) \
      	printf("[%s:%d->%s]:"format, __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#else
#define ymodem_ymodem_printf(format, ...)
#endif


/**
  * @brief  整形转换为字符串
  * @param  str: The string
  * @param  intnum: The intger to be converted
  * @retval None
  */
void ymode_int_str(uint8_t* str, int32_t intnum)
{
    uint32_t i, Div = 1000000000, j = 0, Status = 0;

    for (i = 0; i < 10; i++)
    {
        str[j++] = (intnum / Div) + 48;
        intnum = intnum % Div;
        Div /= 10;
        if ((str[j-1] == '0') & (Status == 0))
        {
            j = 0;
        }
        else
        {
            Status++;
        }
    }
}

/**
  * @brief  Convert a string to an integer
  * @param  inputstr: The string to be converted
  * @param  intnum: The intger value
  * @retval 1: Correct
  *         0: Error
  */
uint32_t ymode_str_int(uint8_t *inputstr, int32_t *intnum)
{
    uint32_t i = 0, res = 0;
    uint32_t val = 0;

    if (inputstr[0] == '0' && (inputstr[1] == 'x' || inputstr[1] == 'X'))
    {
        if (inputstr[2] == '\0')
        {
            return 0;
        }
        for (i = 2; i < 11; i++)
        {
            if (inputstr[i] == '\0')
            {
                *intnum = val;
                res = 1;
                break;
            }
            if (ISVALIDHEX(inputstr[i]))
            {
                val = (val << 4) + CONVERTHEX(inputstr[i]);
            }
            else
            {
                res = 0;
                break;
            }
        }

        if (i >= 11)
        {
            res = 0;
        }
    }
    else
    {
        for (i = 0; i < 11; i++)
        {
            if (inputstr[i] == '\0')
            {
                *intnum = val;
                res = 1;
                break;
            }
            else if ((inputstr[i] == 'k' || inputstr[i] == 'K') && (i > 0))
            {
                val = val << 10;
                *intnum = val;
                res = 1;
                break;
            }
            else if ((inputstr[i] == 'm' || inputstr[i] == 'M') && (i > 0))
            {
                val = val << 20;
                *intnum = val;
                res = 1;
                break;
            }
            else if (ISVALIDDEC(inputstr[i]))
            {
                val = val * 10 + CONVERTDEC(inputstr[i]);
            }
            else
            {
                res = 0;
                break;
            }
        }

        if (i >= 11)
        {
            res = 0;
        }
    }
    return res;
}



/**
  * @brief  Update CRC16 for input byte
  * @param  CRC input value
  * @param  input byte
  * @retval Updated CRC value
  */
uint16_t ymodem_invert_uint32(uint16_t crcIn, uint8_t byte)
{
    uint32_t crc = crcIn;
    uint32_t in = byte|0x100;

    do
    {
        crc <<= 1;
        in <<= 1;

        if(in&0x100)
        {
            ++crc;
        }

        if(crc&0x10000)
        {
            crc ^= 0x1021;
        }
    }
    while(!(in&0x10000));

    return (crc&0xffffu);
}


/**
  * @brief  Cal CRC16 for YModem Packet
  * @param  data
  * @param  length
  * @retval CRC value
  */
uint16_t ymodem_crc(const uint8_t* data, uint32_t size)
{
    uint32_t crc = 0;
    const uint8_t* dataEnd = data+size;

    while(data<dataEnd)
    {
        crc = ymodem_invert_uint32(crc,*data++);
    }
    crc = ymodem_invert_uint32(crc,0);
    crc = ymodem_invert_uint32(crc,0);

    return (crc&0xffffu);
}


/**
  * @brief  从发送方接收一个数据包
  * @param  data: 存储接收到的数据包的缓冲区
  * @param  length: 数据包的长度(返回参数)
  * @param  timeout: 接收超时时间
  *          0: 传输结束
  *          -1: 发送方中止传输
  *          >0: 数据包的长度
  * @retval 0: 正常返回
  *         -1: 超时或数据包错误
  *         1: 用户中止
  */
int32_t ymodem_receive_packet (uint8_t *data, int32_t *length, uint32_t timeout)
{
    uint16_t i, packet_size, computedcrc;
    uint8_t c;
    *length = 0;

    // 接收一个字节
    if (ymode_revice_char(&c, timeout) != 0)
    {
        return -1; // 没读到数据,超时或错误
    }

    switch (c)
    {
        case SOH:
            packet_size = PACKET_SIZE; // 128字节数据包
            break;
        case STX:
            packet_size = PACKET_1K_SIZE; // 1KB数据包
            break;
        case EOT:
            return 0; // 传输结束
        case CA:
            // 接收到CA字符,判断是否为中止传输信号
            if ((ymode_revice_char(&c, timeout) == 0) && (c == CA))
            {
                *length = -1; // 中止传输
                return 0;
            }
            else
            {
                return -1; // 错误
            }
        case ABORT1: // 用户中止信号
        case ABORT2:
            return 1;
        default:
            return -1; // 错误
    }

    *data = c; // 存储接收到的字符
    for (i = 1; i < (packet_size + PACKET_OVERHEAD); i++)
    {
        // 接收剩余的数据字节
        if (ymode_revice_char(data + i, timeout) != 0)
        {
            return -1; // 接收超时或错误
        }
    }

    if (data[PACKET_SEQNO_INDEX] != ((data[PACKET_SEQNO_COMP_INDEX] ^ 0xff) & 0xff))
    {
        return -1; // 数据包序列号错误
    }

    /* 计算CRC校验值 */
    computedcrc = ymodem_crc(&data[PACKET_HEADER], (uint32_t)packet_size);

    /* 检查接收到的CRC校验值与已计算的CRC校验值是否匹配
       data[packet_size+3]<<8) | data[packet_size+4] 包含了接收到的CRC校验值
       computedcrc 包含了已计算的CRC校验值 */
    if (computedcrc != (uint16_t)((data[packet_size+3]<<8) | data[packet_size+4]))
    {
        /* CRC错误 */
        return -1;
    }

    *length = packet_size; // 设置数据包的长度
    return 0; // 正常返回
}


/**
  * @brief  使用Ymodem协议接收文件
  * @param  buf: 存储文件数据的缓冲区的首地址
  * @retval 文件的大小,若返回负数表示出现错误
  */
int32_t ymodem_receive_handle (uint8_t *buf)
{
    uint8_t packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD], file_size[FILE_SIZE_LENGTH], *file_ptr, *buf_ptr;
    int32_t i, packet_length, session_done, file_done, packets_received, errors, session_begin, size = 0;
    uint32_t flashdestination, ramsource;

    // 初始化Flash目标地址
    flashdestination = APPLICATION_ADDRESS;
    ymodem_flash_init();//FLASH解锁,,每次进来都解锁下防止又自锁

    // 外层循环,用于控制整个Ymodem传输会话
    for (session_done = 0, errors = 0, session_begin = 0; ;)
    {
        // 内层循环,用于接收并处理Ymodem数据包
        for (packets_received = 0, file_done = 0, buf_ptr = buf; ;)
        {
            switch (ymodem_receive_packet(packet_data, &packet_length, NAK_TIMEOUT))
            {
                case 0:
                    errors = 0;
                    switch (packet_length)
                    {
                        // 发送方中止传输
                        case -1:
                            ymodem_put_char(ACK);
                            return 0;
                        // 传输结束
                        case 0:
                            ymodem_put_char(ACK);
                            file_done = 1;
                            break;

                        // 普通数据包
                        default:
                            if ((packet_data[PACKET_SEQNO_INDEX] & 0xff) != (packets_received & 0xff))
                            {
                                // 接收到的数据包序列号不匹配,请求重发
                                ymodem_put_char(NAK);
                            }
                            else
                            {
                                if (packets_received == 0)
                                {
                                    // 文件名数据包
                                    if (packet_data[PACKET_HEADER] != 0)
                                    {
                                        // 解析文件名和文件大小
                                        for (i = 0, file_ptr = packet_data + PACKET_HEADER; (*file_ptr != 0) && (i < FILE_NAME_LENGTH);)
                                        {
                                            ymodem_file_name[i++] = *file_ptr++;
                                        }
                                        ymodem_file_name[i++] = '\0';
                                        for (i = 0, file_ptr++; (*file_ptr != ' ') && (i < (FILE_SIZE_LENGTH - 1));)
                                        {
                                            file_size[i++] = *file_ptr++;
                                        }
                                        file_size[i++] = '\0';
                                        ymode_str_int(file_size, &size);

                                        // 检查文件大小是否超过Flash容量
                                        if (size > (USER_FLASH_SIZE + 1))
                                        {
                                            // 文件大小超过Flash容量,结束会话
                                            ymodem_put_char(CA);
                                            ymodem_put_char(CA);
                                            return -1;
                                        }

                                        // 擦除用户应用区域
                                        ymodem_flash_erase(APPLICATION_ADDRESS);

                                        // 发送ACK确认数据包接收,并请求发送CRC校验
                                        ymodem_put_char(ACK);
                                        ymodem_put_char(CRC16);
                                    }
                                    else
                                    {
                                        // 文件名数据包为空,结束会话
                                        ymodem_put_char(ACK);
                                        file_done = 1;
                                        session_done = 1;
                                        break;
                                    }
                                }
                                else
                                {
                                    // 数据包
                                    // 将数据包中的数据拷贝到缓冲区
                                    memcpy(buf_ptr, packet_data + PACKET_HEADER, packet_length);
                                    ramsource = (uint32_t)buf;

                                    // 将接收到的数据写入Flash内存
                                    if (ymodem_flash_write(flashdestination, (uint32_t*) ramsource, (uint16_t) packet_length/4)  == 0)
                                    {
                                        // 数据写入成功,发送ACK确认数据包接收
                                        flashdestination += packet_length;
                                        ymodem_put_char(ACK);
                                    }
                                    else
                                    {
                                        // 写入Flash内存时发生错误,结束会话
                                        ymodem_put_char(CA);
                                        ymodem_put_char(CA);

                                        return -2;
                                    }
                                }
                                packets_received++;
                                session_begin = 1;
                            }
                    }
                    break;
                case 1:
                    // 超时错误,发送CA(中止)字符
                    ymodem_put_char(CA);
                    ymodem_put_char(CA);
                    return -3;
                default:
                    if (session_begin > 0)
                    {
                        errors++;
                    }
                    if (errors > MAX_ERRORS)
                    {
                        // 传输错误次数超过最大限制,发送CA(中止)字符
                        ymodem_put_char(CA);
                        ymodem_put_char(CA);
                        //ymodem_ymodem_printf("222");
                        return 0;
                    }
                    // 发送CRC校验请求,等待继续传输数据(每隔大约1秒发送一个CRC校验请求)
                    ymodem_put_char(CRC16);
                    break;
            }
            if (file_done != 0)
            {
                break;
            }
        }
        if (session_done != 0)
        {
            break;
        }
    }

    // 返回文件的大小
    return (int32_t)size;
}


/**
  * @brief  准备第一块
  * @param  timeout
  * @retval None
  */
void ymodem_prepare_intial_packet(uint8_t *data, const uint8_t* ymodem_file_name, uint32_t *length)
{
    uint16_t i, j;
    uint8_t file_ptr[10];

    /* Make first three packet */
    data[0] = SOH;
    data[1] = 0x00;
    data[2] = 0xff;

    /* ymodem_file_name packet has valid data */
    for (i = 0; (ymodem_file_name[i] != '\0') && (i < FILE_NAME_LENGTH); i++)
    {
        data[i + PACKET_HEADER] = ymodem_file_name[i];
    }

    data[i + PACKET_HEADER] = 0x00;

    ymode_int_str (file_ptr, *length);
    for (j =0, i = i + PACKET_HEADER + 1; file_ptr[j] != '\0' ; )
    {
        data[i++] = file_ptr[j++];
    }

    for (j = i; j < PACKET_SIZE + PACKET_HEADER; j++)
    {
        data[j] = 0;
    }
}


/**
  * @brief  准备数据包
  * @param  timeout
  * @retval None
  */
void ymodem_prepare_packet(uint8_t *SourceBuf, uint8_t *data, uint8_t pktNo, uint32_t sizeBlk)
{
    uint16_t i, size, packetSize;
    uint8_t* file_ptr;

    /* Make first three packet */
    packetSize = sizeBlk >= PACKET_1K_SIZE ? PACKET_1K_SIZE : PACKET_SIZE;
    size = sizeBlk < packetSize ? sizeBlk :packetSize;
    if (packetSize == PACKET_1K_SIZE)
    {
        data[0] = STX;
    }
    else
    {
        data[0] = SOH;
    }
    data[1] = pktNo;
    data[2] = (~pktNo);
    file_ptr = SourceBuf;

    /* ymodem_file_name packet has valid data */
    for (i = PACKET_HEADER; i < size + PACKET_HEADER; i++)
    {
        data[i] = *file_ptr++;
    }
    if ( size  <= packetSize)
    {
        for (i = size + PACKET_HEADER; i < packetSize + PACKET_HEADER; i++)
        {
            data[i] = 0x1A; /* EOF (0x1A) or 0x00 */
        }
    }
}


/**
  * @brief  Transmit a file using the ymodem protocol
  * @param  buf: Address of the first byte
  * @retval The size of the file
  */
uint8_t ymodem_transmit_handle (uint8_t *buf, const uint8_t* sendymodem_file_name, uint32_t sizeFile)
{
    uint8_t packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD];
    uint8_t ymodem_file_name[FILE_NAME_LENGTH];
    uint8_t *buf_ptr;
    uint16_t tempCRC, blkNumber;
    uint8_t receivedC[2], i;
    uint32_t errors = 0, ackReceived = 0, size = 0, pktSize;

    for (i = 0; i < (FILE_NAME_LENGTH - 1); i++)
    {
        ymodem_file_name[i] = sendymodem_file_name[i];
    }

    /* Prepare first block */
    ymodem_prepare_intial_packet(&packet_data[0], ymodem_file_name, &sizeFile);

    do
    {
        /* Send Packet */
        ymode_transmit_buffer(packet_data, PACKET_SIZE + PACKET_HEADER);

        /* Send CRC or Check Sum based on CRC16_F */
        tempCRC = ymodem_crc(&packet_data[3], PACKET_SIZE);
        ymodem_put_char(tempCRC >> 8);
        ymodem_put_char(tempCRC & 0xFF);

        /* Wait for Ack and 'C' */
        if (ymode_revice_char(&receivedC[0], 1000000) == 0)
        {
            if (receivedC[0] == ACK)
            {
                /* Packet transfered correctly */
                ackReceived = 1;
            }
        }
        else
        {
            errors++;
        }
    }
    while (!ackReceived && (errors < 0x0A));

    if (errors >=  0x0A)
    {
        return errors;
    }

    buf_ptr = buf;
    size = sizeFile;
    blkNumber = 0x01;

    /* Here 1024 bytes package is used to send the packets */
    while (size)
    {
        /* Prepare next packet */
        ymodem_prepare_packet(buf_ptr, &packet_data[0], blkNumber, size);
        ackReceived = 0;
        receivedC[0]= 0;
        errors = 0;
        do
        {
            /* Send next packet */
            if (size >= PACKET_1K_SIZE)
            {
                pktSize = PACKET_1K_SIZE;

            }
            else
            {
                pktSize = PACKET_SIZE;
            }

            ymode_transmit_buffer(packet_data, pktSize + PACKET_HEADER);
            /* Send CRC or Check Sum based on CRC16_F */
            tempCRC = ymodem_crc(&packet_data[3], pktSize);
            ymodem_put_char(tempCRC >> 8);
            ymodem_put_char(tempCRC & 0xFF);


            /* Wait for Ack */
            if (ymode_revice_char(&receivedC[0], 1000000) == 0)
            {
                if (receivedC[0] == ACK)
                {
                    ackReceived = 1;
                    if (size > pktSize)
                    {
                        buf_ptr += pktSize;
                        size -= pktSize;
                        if (blkNumber == (USER_FLASH_SIZE/1024))
                        {
                            return 0xFF; /*  error */
                        }
                        else
                        {
                            blkNumber++;
                        }
                    }
                    else
                    {
                        buf_ptr += pktSize;
                        size = 0;
                    }
                }
            }
            else
            {
                errors++;
            }
        }
        while(!ackReceived && (errors < 0x0A));

        /* Resend packet if NAK  for a count of 10 else end of commuincation */
        if (errors >=  0x0A)
        {
            return errors;
        }

    }
    ackReceived = 0;
    receivedC[0] = 0x00;
    receivedC[1] = 0x00;
    errors = 0;
    do
    {
        ymodem_put_char(EOT);
        /* Send (EOT); */
        /* Wait for Ack */

        ymodem_get_char(&receivedC[0]);
        if (receivedC[0] == ACK)
        {
            ackReceived = 1;
        }
        else
        {
            errors++;
        }
    }
    while (!ackReceived && (errors < 0x0A));

    if (errors >=  0x0A)
    {
        return errors;
    }

    /* Last packet preparation */
    ackReceived = 0;
    receivedC[0] = 0x00;
    receivedC[1] = 0x00;
    errors = 0;

    packet_data[0] = SOH;
    packet_data[1] = 0;
    packet_data [2] = 0xFF;

    for (i = PACKET_HEADER; i < (PACKET_SIZE + PACKET_HEADER); i++)
    {
        packet_data [i] = 0x00;
    }

    do
    {
        /* Send Packet */
        ymode_transmit_buffer(packet_data, PACKET_SIZE + PACKET_HEADER);

        /* Send CRC or Check Sum based on CRC16_F */
        tempCRC = ymodem_crc(&packet_data[3], PACKET_SIZE);
        ymodem_put_char(tempCRC >> 8);
        ymodem_put_char(tempCRC & 0xFF);

        /* Wait for Ack and 'C' */
        if (ymode_revice_char(&receivedC[1], 1000000) == 0)
        {
            if (receivedC[1] == ACK)
            {
                /* Packet transfered correctly */
                ackReceived = 1;
            }
        }
        else
        {
            errors++;
        }
    }
    while (!ackReceived && (errors < 0x0A));

    /* Resend packet if NAK  for a count of 10  else end of commuincation */
    if (errors >=  0x0A)
    {
        return errors;
    }
    receivedC[0] = 0x00;
    do
    {
        ymodem_put_char(EOT);
        /* Send (EOT); */
        /* Wait for Ack */
        if ((ymode_revice_char(&receivedC[0], 1000000) == 0)  && receivedC[0] == ACK)
        {
            ackReceived = 1;
        }
        else
        {
            errors++;
        }
        /* Clear Overrun flag of the USART2 */
    }
    while (!ackReceived && (errors < 0x0A));

    if (errors >=  0x0A)
    {
        return errors;
    }
    return 0; /* file trasmitted successfully */
}





void ymodem_download(void)
{
    int32_t size = 0;

    size = ymodem_receive_handle(&tab_1024[0]);

    if (size > 0)
    {
        ymodem_ymodem_printf("download success!\n");
    }
    else
    {
        ymodem_ymodem_printf("download fail:%d!\n", size);
    }
}


/**
  * @brief  Upload a file via serial port.
  * @param  None
  * @retval None
  */
void ymodem_upload(void)
{
    uint8_t status = 0 ;
    uint8_t ch;
    ymode_revice_char(&ch, -1);
    if (ch == CRC16)
    {
        /* Transmit the flash image through ymodem protocol */
        status = ymodem_transmit_handle((uint8_t*)APPLICATION_ADDRESS, (const uint8_t*)"UploadedFlashImage.bin", USER_FLASH_SIZE);
        if (status != 0)
        {

        }
        else
        {

        }
    }
}


void jump_application(void)
{
    typedef  void (*pFunction)(void);
    pFunction Jump_To_Application;
    uint32_t JumpAddress;

    JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
    Jump_To_Application = (pFunction) JumpAddress;

    __set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
    Jump_To_Application();
}

ymodem.h

#ifndef _YMODEM_H_
#define _YMODEM_H_

#define CMD_STRING_SIZE       128

#include "main.h"


#define IS_AF(c)             ((c >= 'A') && (c <= 'F'))
#define IS_af(c)             ((c >= 'a') && (c <= 'f'))
#define IS_09(c)             ((c >= '0') && (c <= '9'))
#define ISVALIDHEX(c)        IS_AF(c) || IS_af(c) || IS_09(c)
#define ISVALIDDEC(c)        IS_09(c)
#define CONVERTDEC(c)        (c - '0')

#define CONVERTHEX_alpha(c)  (IS_AF(c) ? (c - 'A'+10) : (c - 'a'+10))
#define CONVERTHEX(c)        (IS_09(c) ? (c - '0') : CONVERTHEX_alpha(c))


#define PACKET_SEQNO_INDEX      (1)
#define PACKET_SEQNO_COMP_INDEX (2)

#define PACKET_HEADER           (3)
#define PACKET_TRAILER          (2)
#define PACKET_OVERHEAD         (PACKET_HEADER + PACKET_TRAILER)
#define PACKET_SIZE             (128)
#define PACKET_1K_SIZE          (1024)

#define FILE_NAME_LENGTH        (256)
#define FILE_SIZE_LENGTH        (16)

#define SOH                     (0x01)  /* start of 128-byte data packet */
#define STX                     (0x02)  /* start of 1024-byte data packet */
#define EOT                     (0x04)  /* end of transmission */
#define ACK                     (0x06)  /* acknowledge */
#define NAK                     (0x15)  /* negative acknowledge */
#define CA                      (0x18)  /* two of these in succession aborts transfer */
#define CRC16                   (0x43)  /* 'C' == 0x43, request 16-bit CRC */

#define ABORT1                  (0x41)  /* 'A' == 0x41, abort by user */
#define ABORT2                  (0x61)  /* 'a' == 0x61, abort by user */

#define NAK_TIMEOUT             (0x100000)
#define MAX_ERRORS              (10000)



#endif











common.c

/* Includes ------------------------------------------------------------------*/
#include "common.h"
#include "main.h"



/**
  * @brief  Print a character on the HyperTerminal
  * @param  c: The character to be printed
  * @retval None
  */
void ymodem_put_char(uint8_t c)
{ 
  HAL_UART_Transmit(&huart1, &c, 1, 0xff);
}


/**
  * @brief  Test to see if a key has been pressed on the HyperTerminal
  * @param  key: The key pressed
  * @retval 1: Correct
  *         0: Error
  */
uint32_t ymodem_get_char(uint8_t *key)
{
  if ( __HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET)
  {
    HAL_UART_Receive(&huart1, key, 1, 0xff);
    return 1;
  }
  else
  {
	if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_ORE) != RESET) 
	{
		__HAL_UART_CLEAR_OREFLAG(&huart1);
	}
    return 0;
  }
}


/**
  * @brief  接收一个字节
  * @param  目标字符地址
  * @param  超时时间
  * @retval 0: 接收成功
  *         -1: 接收超时
  */
int ymode_revice_char(uint8_t *c, int timeout)
{
    if(timeout == -1) {
        while(ymodem_get_char(c) == 1);
    } else {
        while (timeout-- > 0) {
            if (ymodem_get_char(c) == 1) {
                return 0;
            }
        }
        return -1;
    }
    return 0;
}

/**
  * @brief  接收一个字节
  * @param  目标字符地址
  * @param  超时时间
  * @retval 0: 接收成功
  *         -1: 接收超时
  */
int ymode_transmit_buffer(uint8_t *c, int len)
{
    uint16_t i;
    i = 0;
    while (i < len) {
        ymodem_put_char(c[i]);
        i++;
    }
}

flash_if.c

#include "flash_if.h"


/**
  * @brief  解锁FLASH
  * @param  None
  * @retval None
  */
void ymodem_flash_init(void)
{
	/* Unlock the Program memory */
	HAL_FLASH_Unlock();
	/* Clear all FLASH flags */
	__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPERR);
	/* Unlock the Program memory */
	HAL_FLASH_Lock();
}


/**
 *@brief此功能擦除所有用户闪光区域
 *@param start:用户闪光区的开始
 *@retval FLASHIF_OK:用户闪光区成功擦除
 *FLASHIF_ERASEKO:发生错误
 */
uint32_t ymodem_flash_erase(uint32_t start)
{
	uint32_t NbrOfPages = 0;
	uint32_t PageError = 0;
	FLASH_EraseInitTypeDef pEraseInit;
	HAL_StatusTypeDef status = HAL_OK;

	/* Unlock the Flash to enable the flash control register access *************/ 
	HAL_FLASH_Unlock();

	/* Get the sector where start the user flash area */
	NbrOfPages = (USER_FLASH_END_ADDRESS - start)/FLASH_PAGE_SIZE;

	pEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
	pEraseInit.PageAddress = start;
	pEraseInit.NbPages = NbrOfPages;
	status = HAL_FLASHEx_Erase(&pEraseInit, &PageError);

	/* Lock the Flash to disable the flash control register access (recommended
	 to protect the FLASH memory against possible unwanted operation) *********/
	HAL_FLASH_Lock();

	if (status != HAL_OK)
	{
	/* Error occurred while page erase */
	return FLASHIF_ERASEKO;
	}

	return FLASHIF_OK;
}


/**
 *@brief此函数在flash中写入数据缓冲区(数据以32位对齐)。
 *@note写入数据缓冲区后,将检查闪存内容。
 *@param destination:目标位置的起始地址
 *@param p_source:要写入数据的缓冲区上的指针
 *@param length:数据缓冲区的长度(单位为32位字)
 *@retval uint32_t0:数据成功写入闪存
 *1:在闪存中写入数据时出错
 *2:闪存中的写入数据与预期数据不同
 */
uint32_t ymodem_flash_write(uint32_t destination, uint32_t *p_source, uint32_t length)
{
	uint32_t i = 0;

	/* Unlock the Flash to enable the flash control register access *************/
	HAL_FLASH_Unlock();

	for (i = 0; (i < length) && (destination <= (USER_FLASH_END_ADDRESS-4)); i++)
	{
	/* Device voltage range supposed to be [2.7V to 3.6V], the operation will
	   be done by word */ 
	if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, destination, *(uint32_t*)(p_source+i)) == HAL_OK)      
	{
	 /* Check the written value */
	  if (*(uint32_t*)destination != *(uint32_t*)(p_source+i))
	  {
		/* Flash content doesn't match SRAM content */
		return(FLASHIF_WRITINGCTRL_ERROR);
	  }
	  /* Increment FLASH destination address */
	  destination += 4;
	}
	else
	{
	  /* Error occurred while writing data in Flash memory */
	  return (FLASHIF_WRITING_ERROR);
	}
	}

	/* Lock the Flash to disable the flash control register access (recommended
	 to protect the FLASH memory against possible unwanted operation) *********/
	HAL_FLASH_Lock();

	return (FLASHIF_OK);
}

flash_if.h

#ifndef __FLASH_IF_H
#define __FLASH_IF_H

#include "stm32f3xx_hal.h"

enum 
{
  FLASHIF_OK = 0,
  FLASHIF_ERASEKO,
  FLASHIF_WRITINGCTRL_ERROR,
  FLASHIF_WRITING_ERROR,
  FLASHIF_PROTECTION_ERRROR
};


#define APPLICATION_ADDRESS     (uint32_t)0x08006000		//APP起始地址
#define FLASH_PAGE_STEP         FLASH_PAGE_SIZE       		//页大小
#define USER_FLASH_END_ADDRESS  0x08016000					//APP结束地址
#define USER_FLASH_SIZE         (USER_FLASH_END_ADDRESS - USER_FLASH_END_ADDRESS) //APP大小



#define ABS_RETURN(x,y)               ((x) < (y)) ? ((y)-(x)) : ((x)-(y))
#define FLASH_SECTOR_NUMBER           ((uint32_t)(ABS_RETURN(APPLICATION_ADDRESS,FLASH_START_BANK1))>>12)
#define FLASH_PROTECTED_SECTORS       (~(uint32_t)((1 << FLASH_SECTOR_NUMBER) - 1))

void ymodem_flash_init(void);
uint32_t ymodem_flash_erase(uint32_t StartSector);
uint32_t ymodem_flash_write(uint32_t destination, uint32_t *p_source, uint32_t length);




#endif

<think>我们之前讨论了单片机升级协议的核心框架、安全流程和工业级实现。现在用户明确要求查找"单片机远程升级协议"的相关信息,并参考了之前关于远程升级设计的讨论[^1]。我们需要重点介绍远程升级协议的关键要素和实现方式。 </think>### 单片机远程升级协议详解 基于工业物联网应用需求,远程升级协议需解决**可靠传输**、**断电恢复**和**安全验证**三大核心问题。以下是关键设计要素: --- #### 一、协议分层架构 ```mermaid graph TB A[应用层] --> B[协议层] B -->|数据封装| C[传输层] C -->|物理传输| D[通信接口] ``` 1. **应用层**:升级指令、版本号、设备ID 2. **协议层**:升级帧结构设计(核心) 3. **传输层**:TCP/UDP/MQTT协议选择 4. **通信接口**:WiFi/4G/Ethernet/CAN --- #### 二、核心协议帧设计(以太网示例) **帧结构标准化**: ```c #pragma pack(1) typedef struct { uint8_t magic[2]; // 0x55 0xAA uint32_t seq_num; // 分块序号 uint16_t cmd_type; // 指令类型(0x01:启动 0x02:数据 0x03:校验) uint32_t total_size; // 固件总大小 uint8_t payload[1024]; // 数据负载 uint32_t crc32; // 校验码 } Remote_Update_Frame; #pragma pack() ``` > 注:单帧最大负载通常为1KB,CRC32校验覆盖除crc32外的所有字段[^1] --- #### 三、安全升级流程 **六步握手协议**: ```mermaid sequenceDiagram 服务器->>设备: 升级请求(含固件MD5) 设备->>服务器: 确认请求+剩余存储空间 loop 断点续传 服务器->>设备: 数据帧(seq_num+payload) 设备->>服务器: ACK(seq_num) end 设备->>服务器: 提交校验结果 服务器->>设备: 激活新固件指令 ``` **关键安全机制**: 1. **双向认证**: $$ \text{Auth} = \text{HMAC-SHA256}( \text{DeviceID} \parallel \text{Timestamp}, \text{Pre-shared Key} ) $$ 2. **固件签名**: ECDSA数字签名验证(P-256曲线) 3. **防回滚**: 版本号严格递增检查:$ \text{Version}_{\text{new}} > \text{Version}_{\text{current}} $ --- #### 四、工业场景关键技术 | 挑战 | 解决方案 | 实现示例 | |---------------------|-----------------------------|------------------------| | **网络中断** | 断点续传(记录最后seq_num) | FLASH存储断点位置 | | **电力中断** | 双备份分区+原子写入 | STM32F7的Bank Swap机制 | | **带宽限制** | 差分升级(DiffPatch算法) | bsdiff开源库 | | **实时性要求** | 热升级(双程序并行运行) | RTOS动态加载模块 | > **性能指标**: > - 升级成功率:≥99.99%(工业SIL2认证要求) > - 恢复时间:断电后升级续传延迟 ≤ 100ms > - 传输效率:差分升级可减少80%数据量 --- #### 五、典型开源实现参考 1. **MCUboot**:跨平台安全引导程序(Apache 2.0协议- 支持A/B分区交换 - 集成TLS 1.3加密传输 2. **OpenOCD**:通过JTAG/SWD远程升级 - 适用生产环境批量刷写 - 速度:1MB固件约8s(@10MHz SWD时钟) ---
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值