继续固件升级的一些内容-上位机部分

发布于 2020-04-21  449 次阅读


继续固件升级的一些内容-上位机部分

Ymodem升级上位机部分

如果没有特殊情况,用secure CRT或者XShell等终端软件的Ymodem发送功能就可以了,稳定,可靠。根据自己下位机的情况选择128字节还是1K的数据包发送就行。

但在我的实际使用中,有一个很难受的问题,下位机只有一个串口可供IAP升级,再APP中还得配置波特率为2400,用2400的波特率传输30K左右的固件包,速度慢的很尴尬。如果想要高速,那么我得先以2400的波特率触发升级,然后在BootLaoder中配置一个比较高的波特率接收数据。一顿操作自己都嫌麻烦,客户估计得疯掉。

为了解决这个问题,无奈直接动手用C#搞一个升级上位机出来。

上位机CRC16计算

定义一个CRC16计算的类,这个没啥可说的,直接看代码就是了

public class CRC16
{
    /// <summary>
    /// CRC16校验
    /// </summary>
    /// <param name="buff">要检验的数据</param>
    /// <returns>校验结果</returns>
    public byte[] ComputeChecksum(byte[] buff)
    {
        uint crc = 0;

        foreach (var t in buff)
        {
            crc ^= (uint)(t) << 8;
            for (var j = 0; j < 8; j++)
            {
                if ((crc & 0x8000) != 0)
                {
                    crc = (crc << 1) ^ 0x1021;
                }
                else
                {
                    crc <<= 1;
                }
            }
        }

        return new[] { (byte)(crc >> 8), (byte)(crc & 0x00ff) };
    }
}

上位机Ymodem发送

这里面要特别注意的第一帧数据和最后一帧数据

第一帧数据应该以Soh开头,帧序为00,帧序取反FF内容长度应该是128字节,包含文件名,文件大小,理论上应该再包含一个文件时间戳信息和一些文件读写可执行等属性,再加两个字节长度的CRC16计算结果。

考虑到实际下位机情况和电脑系统情况,数据帧省略了时间戳信息和文件属性信息。下位机主要靠文件大小信息来决定擦除Flash空间的大小。

最后一帧数据一般都是固定的

SOH 00 FF NUL[128] CRCH CRCL

再具体的信息,请参考前面写的STM32固件升级

之所以这么强调一下,是因为Ymode协议这部分是从GitHub上参考别人的做法,结果找了好几份,内容都是大同小异,这部分都有一个同样的错误,第一帧数据和最后一帧数据的数据长度都是1024字节,且都是以Stx开头,更加难受的是,程序可以烧写并且竟然可以执行。调试的时候差点忽略了这个错误,下面为修改了这部分错误的代码。

为了不引起不必要的误会,他们的源码直接在GitHub上面搜索YmodemC#等关键词,很容易找到,期待大家的分析验证。

/* packet define */
private const byte C = 67;   // capital letter C
private const byte Soh = 1;  // Start Of 128-byte data packet
private const byte Stx = 2;  // Start Of Text
private const byte Eot = 4;  // End Of Transmission
private const byte Ack = 6;  // Positive ACknowledgement
private const int CrcSize = 2;

public bool YmodemUpdateFile(string filePath)
{
    var progressVal = 0;
    const int dataSize = 1024;
    var packetNumber = 0;
    var invertedPacketNumber = 255;
    /* data: 1024 bytes */
    var dateBytes1K = new byte[dataSize];
    var dateBytes128 = new byte[128];
    /* footer: 2 bytes */
    var crc = new byte[CrcSize];
    /* get the file */
    var fileStream = new FileStream(@filePath, FileMode.Open, FileAccess.Read);
    var packetCnt = (fileStream.Length % dataSize) == 0 ? (int)(fileStream.Length / dataSize) : (int)(fileStream.Length / dataSize) + 1;
    Form1.form1.progressBar.Maximum = packetCnt;
    try
    {
        if (!SendYmodemInitialPacket(Soh, packetNumber, invertedPacketNumber, dateBytes128, 128, filePath, fileStream, crc, CrcSize)) 
            return false;
        if (Form1.form1.SerialPort.ReadByte() != Ack)
        {
            Debug.WriteLine("Can't send the initial packet.");
            return false;
        }

        if (Form1.form1.SerialPort.ReadByte() != C)
        {
            return false;
        }

        /* send packets with a cycle until we send the last byte */
        int fileReadCount;
        do
        {
            /* if this is the last packet fill the remaining bytes with 0 */
            fileReadCount = fileStream.Read(dateBytes1K, 0, dataSize);
            if (fileReadCount == 0) break;
            if (fileReadCount != dataSize)
                for (var i = fileReadCount; i < dataSize; i++)
                    dateBytes1K[i] = 0;

            /* calculate packetNumber */
            packetNumber++;
            if (packetNumber > 255)
                packetNumber -= 256;
            Debug.WriteLine(packetNumber);

            /* calculate invertedPacketNumber */
            invertedPacketNumber = 255 - packetNumber;

            /* calculate CRC */
            var crc16 = new CRC16();
            crc = crc16.ComputeChecksum(dateBytes1K);

            /* send the packet */
            if (!SendYmodemPacket(Stx, packetNumber, invertedPacketNumber, dateBytes1K, dataSize, crc, CrcSize)) return false;
            Form1.form1.progressBar.Value = ++progressVal;
            /* wait for ACK */
            if (Form1.form1.SerialPort.ReadByte() == Ack) continue;
            Debug.WriteLine("Couldn't send a packet.");
            return false;
        } while (dataSize == fileReadCount);
        /* send EOT (tell the downloader we are finished) */
        Form1.form1.SerialPort.Write(new byte[] { Eot }, 0, 1);
        /* send closing packet */
        packetNumber = 0;
        invertedPacketNumber = 255;
        dateBytes128 = new byte[dataSize];
        crc = new byte[CrcSize];
        if (!SendYmodemClosingPacket(Soh, packetNumber, invertedPacketNumber, dateBytes128, 128, crc, CrcSize)) return false;
        /* get ACK (downloader acknowledge the EOT) */
        if (Form1.form1.SerialPort.ReadByte() != Ack)
        {
            Debug.WriteLine("Can't complete the transfer.");
            return false;
        }
    }
    catch (TimeoutException)
    {
        Form1.form1.progressBar.Value = 0;
        MessageBox.Show(@"数据传输超时!");
    }
    catch (InvalidOperationException)
    {
        Form1.form1.progressBar.Value = 0;
        MessageBox.Show(@"串口打开失败!");
    }
    catch (IOException)
    {
        Form1.form1.progressBar.Value = 0;
        MessageBox.Show(@"串口被中断,请检查连接!");
    }
    finally
    {
        fileStream.Close();
    }

    Debug.WriteLine("File transfer is succesful");
    //MessageBox.Show(@"固件更新结束!");
    Form1.form1.tb_Info.AppendText("\r\n\r\n" + @"固件升级结束!" + "\r\n\r\n");
    Form1.form1.progressBar.Value = 0;
    return true;
}

private static bool SendYmodemPacket(byte head, int packetNumber, int invertedPacketNumber, byte[] data, int size, byte[] crc, int crcSize)
{
    try
    {
        Form1.form1.SerialPort.Write(new byte[] { head }, 0, 1);
        Form1.form1.SerialPort.Write(new byte[] { (byte)packetNumber }, 0, 1);
        Form1.form1.SerialPort.Write(new byte[] { (byte)invertedPacketNumber }, 0, 1);
        Form1.form1.SerialPort.Write(data, 0, size);
        Form1.form1.SerialPort.Write(crc, 0, crcSize);
        return true;
    }
    catch (InvalidOperationException)
    {
        MessageBox.Show(@"串口打开失败!");
        return false;
    }
    catch (TimeoutException)
    {
        MessageBox.Show(@"数据传输超时!");
        return false;
    }
    catch (IOException)
    {
        MessageBox.Show(@"串口被中断,请检查连接!");
        return false;
    }
}

private bool SendYmodemInitialPacket(byte head, int packetNumber, int invertedPacketNumber, byte[] data, int size, string path, FileStream fileStream, byte[] crc, int crcSize)
{
    var fileName = System.IO.Path.GetFileName(path);
    var fileSize = fileStream.Length.ToString();

    /* add filename to data */
    int i;
    Debug.Assert(fileName != null, nameof(fileName) + " != null");
    for (i = 0; i < fileName.Length && (fileName.ToCharArray()[i] != 0); i++)
    {
        data[i] = (byte)fileName.ToCharArray()[i];
    }
    data[i] = 0;

    /* add filesize to data */
    int j;
    for (j = 0; j < fileSize.Length && (fileSize.ToCharArray()[j] != 0); j++)
    {
        data[(i + 1) + j] = (byte)fileSize.ToCharArray()[j];
    }
    data[(i + 1) + j] = 0;

    /* fill the remaining data bytes with 0 */
    for (var k = ((i + 1) + j) + 1; k < size; k++)
    {
        data[k] = 0;
    }

    /* calculate CRC */
    var crc16 = new CRC16();
    crc = crc16.ComputeChecksum(data);

    /* send the packet */
    return SendYmodemPacket(head, packetNumber, invertedPacketNumber, data, size, crc, crcSize);
}

private static bool SendYmodemClosingPacket(byte head, int packetNumber, int invertedPacketNumber, byte[] data, int size, byte[] crc, int crcSize)
{
    /* calculate CRC */
    var crc16 = new CRC16();
    crc = crc16.ComputeChecksum(data);

    /* send the packet */
    return SendYmodemPacket(head, packetNumber, invertedPacketNumber, data, size, crc, crcSize);
}

自己写上位机部分,主要是解决下位机APP和BootLoader波特率的问题,现在可以在上位机中以2400的波特率触发升级,以115200甚至更高的波特率传输固件。

上位机部分的难点应该在于Ymodem协议部分,GitHub上面有很多Ymodem协议相关的代码,各种语言的都有,大家按照自己的需求评估即可。

最后

这些代码并没有什么很高的技术含量或者有什么保密的必要,稍微花点心思的人都可以找到。下位机基本上是从官方库改的,上位机也是GitHub上Clone下来的。贴出来的主要目的是当作一个笔记,方便整理查找。

至此,所有!


这是一个记录个人学习笔记的博客,如果内容涉嫌侵权,请及时联系我删改。