1. 程式人生 > >【dsPIC33E】Bootloader(四)Bootloader上位機

【dsPIC33E】Bootloader(四)Bootloader上位機

前面已經將下位機部署完畢,本節將講述上位機。

上位機的工作相對比較簡單,主要就是解析Hex檔案,然後將資料傳送到下位機。注意傳送的Hex檔案只能是User App,不能帶有Bootloader,否則可能會覆蓋之前的Bootloader,導致出錯。

上位機原始碼下載地址:https://download.csdn.net/download/u010875635/10819828

 

上位機主要工作流程如下:

1、選擇Hex檔案。

2、進入Bootloader。

3、燒錄檔案,燒錄實際上有三個動作,一是檢查是否處於Bootloader中;二是擦除使用者程式區;三是燒錄檔案。

4、燒錄完畢後,下位機會自動復位。

 

注意資料傳送時,一定要採用一問一答模式,即傳送一幀資料,收到回饋後再發送下一幀,避免時間不夠MCU將資料寫入Flash。

 

部分解析Hex檔案的程式碼如下,詳細參考上位機原始碼程式:https://download.csdn.net/download/u010875635/10819828

 

Hex檔案內容與實際地址資料之間的轉換:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SpecialFunctions.HexParse
{
    /// <summary>
    /// Microchip的MPLAB X生成的HEX解析
    /// MPLAB可以生成3中不同格式的執行檔案
    /// 其中兩種是HEX檔案,它們分別稱為
    /// INHX8M(Intel Hex Format) -- 一般用於8位核心裝置程式設計器
    /// INHX32(Intel Hex 32 Format) --  一般用於16位核心裝置程式設計器
    /// 另外一種INHX8S(Intel Split Hex Format)生成的是HXL和HXH檔案,分別儲存指令資料的低位元組和高位元組,這裡不做說明
    /// 詳情參考MPLAX幫助檔案
    /// </summary>
    public class MicrochipHexParse
    {



        #region INHX32格式解析,MPLAB內嵌聯結器MPLINK在預設情況下生成INHX32,適用於dsPIC33E/PIC24E
        /*************************************************
        * 1、hex檔案以ascii形式,按照行來記錄資料
        * 2、每一行從:開始,每至少2個字元表示一組16進位制資料,格式為 :BBAAAATTHHHH....HHHCC
        *                   BB -- 16進位制,表示此行資料長度位元組數,表示HH的數目
        *                 AAAA -- 16進位制,表示資料記錄的起始地址,若此行是資料記錄,則表示偏移地址,其它無意義
        *                   TT -- 16進位制,表示記錄型別,
        *                                   00-資料記錄(Data Record); 
        *                                   01-檔案記錄結束(End of File record); 
        *                                   02-擴充套件段地址記錄(Extend Segment address record);後面所有資料地址+段地址左移4位
        *                                   04-擴充套件線性地址記錄(Extend Linear address record);後面所有資料地址+線性地址左移16位
        *                   HH...HH -- 16進位制,低位元組/高位元組 結合資料,高位元組在後;注意,若是偏移地址,則都是2位元組,高位元組在前,低位元組在後
        *                   CC -- 16進位制,校驗碼,除冒號和自身以外的其他位元組資料加起來模除256的餘數的補碼,例如:10A6B0000000EB00D4FD0700000F78001E007800BA,CC=01+~(00+00+EB+00+D4+FD+07+00+00+0F+78+00+1E+00+78+00)=BA
        *
        *
        * 需要特別注意的是,:
        * 1、對於dsPIC33E/PIC24E,Hex檔案地址是乘了一個2,例如一個部分存於0x100,而在Hex中為0x200,詳見dsPIC33E/PIC24E程式設計規範(DS70619B)擴充套件A中的介紹。
        * 2、dsPIC33E/PIC24E沒有擴充套件段地址,只有擴充套件線性地址
        * 3、MPLAB X專案屬性中的建設裡,勾選規範會HEX檔案和不勾選生成的HEX檔案是不一樣的
        * 4、資料記錄為little-endian,低端在前
        *
        *
        * 一旦出現段地址或者線性地址,之後所有資料都要加偏移地址,直到出現一個新的段地址或者線性地址,再重新變更偏移地址
        * 對於真實地址,是 線性地址左移16位+段地址左移4位+偏移地址
        *
        * 示例:
        * :020000040108EA           線性偏移地址:0108
        * :0200000212FFBD           段偏移地址:12FF
        * :0401000090FFAA5502       資料地址:0100
        * :00000001FF               檔案結束
        * 真實地址為:0108左移16位,為01080000;12FF左移4位,為00012FF0;資料地址為00000100;加起來為010930F0
        * 最終解析出來(8位微控制器):
        * 地址     資料
        * 010930F0 90
        * 010930F1 FF
        * 010930F2 AA
        * 010930F3 55
        * 對於dsPIC33E/PIC24E,16為微控制器,2個16位組成32(有效的是低24位)地址要除以2,所以真實地址解析如下:
        * 00849878 55AAFF90
        * 0084987A ......
        * 而2個地址組成一個24位的指令字(32位的高8位為0),低端在前,所以表示地址應該如下:
        * 00849878 00AAFF90
        * 0084987a xxxxxxxx
        *************************************************/

        /// <summary>
        /// 一行資料解析
        /// </summary>
        public struct HexTextOneLine
        {
            /// <summary>
            /// 資料長度
            /// </summary>
            public byte dataLength;

            /// <summary>
            /// 資料地址
            /// </summary>
            public uint addr;

            /// <summary>
            /// 型別
            /// </summary>
            public ENUM_HexDATA_TYPE dataType;

            /// <summary>
            /// 資料
            /// </summary>
            public byte[] data;

            /// <summary>
            /// 校驗和
            /// </summary>
            public byte checkSum;

            /// <summary>
            /// 計算所得校驗和
            /// </summary>
            public byte realCheckSum;

            /// <summary>
            /// 所有資料轉換成位元組
            /// </summary>
            public byte[] allBytes;
        }

        /// <summary>
        /// Hex每一個地址的資料,可能由多行組成
        /// 例如包含線性偏移地址,或者段偏移地址
        /// </summary>
        public struct HexParseDataOneAddrStruct
        {
            /// <summary>
            /// 非程式儲存
            /// 超過最大程式地址的都是非程式儲存
            /// 例如配置字
            /// </summary>
            public bool notProgramFlash;

            /// <summary>
            /// 線性地址偏移
            /// </summary>
            public uint linearOffset;

            /// <summary>
            /// 段地址偏移
            /// 實際Microchip沒有段地址,此處備用
            /// </summary>
            public uint segmentOffset;

            /// <summary>
            /// 前面是線性地址偏移
            /// </summary>
            public bool beforeIsLinearOffset;

            /// <summary>
            /// 前面是段地址偏移
            /// </summary>
            public bool beforeIsSegmentOffset;

            /// <summary>
            /// 資料地址
            /// </summary>
            public uint addr;

            /// <summary>
            /// 實際地址
            /// 線性地址偏移,左移16位與addr相加為實際地址
            /// </summary>
            public uint realAddr;

            /// <summary>
            /// 最大程式儲存地址
            /// </summary>
            public const uint maxProgramAddr = 0x2AFEA;

            /// <summary>
            /// 資料
            /// </summary>
            public UInt32 data;
            

            /// <summary>
            /// 屬於Hex第幾行
            /// </summary>
            public int lineNum;

            
            /// <summary>
            /// 檢查校驗是否正確
            /// </summary>
            public bool checkSumError;

            /// <summary>
            /// 檢查長度是否合法
            /// </summary>
            public bool lengthError;
        }


        /// <summary>
        /// 上一個為線性地址
        /// </summary>
        bool m_LastIsLinearOffset = false;

        /// <summary>
        /// 線性地址,已經左移16位
        /// </summary>
        UInt32 m_INHX32_ExtendLinearAddressRecord = 0;

        /// <summary>
        /// 上一個為段地址
        /// </summary>
        bool m_LastIsSegmentOffset = false;

        /// <summary>
        /// 段地址,已經左移4位
        /// </summary>
        UInt32 m_INHX32_ExtendSegmentAddressRecord = 0;


        /// <summary>
        /// 資料型別
        /// </summary>
        public enum ENUM_HexDATA_TYPE : byte
        {
            DATA=0,END=1, ExtendSegmentAddressRecord=2, ExtendLinearAddressRecord=4
        }

        #region 單行字串與資料之間轉換
        /// <summary>
        /// 行字串轉換為結構體
        /// </summary>
        public HexTextOneLine StringToDataStruct(string str)
        {
            HexTextOneLine oneLine = new HexTextOneLine();

            //所有資料的位元組集合
            oneLine.allBytes = Converter.MyStringConverter.HexStringToBytes("0x" + str.Substring(1, str.Length - 1));
            
            //標明資料長度
            oneLine.dataLength = Convert.ToByte("0x" + str.Substring(1, 2),16);

            //偏移地址,為顯示地址的一半
            oneLine.addr = Converter.MyStringConverter.HexStringToUint("0x" + str.Substring(3, 4))/2;
            
            //實際資料轉換成位元組,每四個組成一個數據地址,高位元組在後
            oneLine.data = Converter.MyStringConverter.HexStringToBytes("0x" + str.Substring(9, (int)(oneLine.dataLength * 2)));

            //資料型別
            oneLine.dataType = (ENUM_HexDATA_TYPE)Convert.ToInt32(str.Substring(7, 2));

            //校驗和
            oneLine.checkSum = oneLine.allBytes[oneLine.allBytes.Length - 1]; 

            //實際計算所得校驗和
            oneLine.realCheckSum = 0x00;
            for (int i = 0; i < oneLine.allBytes.Length - 1; i++)
                oneLine.realCheckSum += oneLine.allBytes[i];
            oneLine.realCheckSum = (byte)(0x01 + ~oneLine.realCheckSum);

            return oneLine;
        }

        /// <summary>
        /// 將一行資料轉換成string
        /// </summary>
        public string DataStructToString(HexTextOneLine oneLine)
        {
            string str = string.Empty;
            //顯示地址是實際地址的2倍
            str = ":" ;
            //資料低位在前
            for (int i = 0; i<oneLine.allBytes.Length; i++)
                str += oneLine.allBytes[i].ToString("X2");
            return str;
        }

        /// <summary>
        /// 將偏移地址轉換為位元組
        /// 適用於線性地址和段地址
        /// offsetOffset為自身已經偏移的位數
        /// </summary>
        private byte[] GetOffset(uint offset,byte offsetOffset)
        {
            byte[] dataTmp = new byte[7];

            dataTmp[0] = 2; //資料長度
            dataTmp[1] = 0;
            dataTmp[2] = 0; //地址
            dataTmp[3] = 4; //資料型別,線性偏移地址
            //偏移地址,高位元組在前
            dataTmp[6] = Convert.ToByte((offset >> offsetOffset) & 0xff); //本身已經偏移了16位,低位
            dataTmp[5] = Convert.ToByte((offset >> (offsetOffset+8)) & 0xff); //本身已經偏移了16位,再偏移8位,總共偏移24位,高位
                                                                                 //實際計算所得校驗和
            byte checkSum = 0x00;
            for (int i = 0; i < dataTmp.Length - 1; i++)
                checkSum += dataTmp[i];
            checkSum = (byte)(0x01 + ~checkSum);

            dataTmp[7] = checkSum;

            return dataTmp;
        }

        /// <summary>
        /// 根據線性地址獲取對應的資料
        /// linearOffset為左偏移16位之後的資料
        /// </summary>
        public byte[] GetLinearOffset(uint linearOffset)
        {
            return GetOffset(linearOffset, 16); //本身偏移了16位
        }

        /// <summary>
        /// 根據段地址獲取對應的資料
        /// segmentOffset為左偏移4位之後的資料
        /// </summary>
        public byte[] GetSegmentOffset(uint segmentOffset)
        {
            return GetOffset(segmentOffset, 4); //本身偏移了4位
        }
        #endregion

        #region 位元組陣列與Hex字串之間轉換
        /// <summary>
        /// 字串陣列轉換成結構體陣列
        /// </summary>
        public List<HexTextOneLine> HexStringToBytes(ref string[] allLines)
        {
            List<HexTextOneLine> allLineBytes = new List<HexTextOneLine>();
            for (int i = 0; i < allLines.Length; i++)
            {
                allLineBytes.Add(StringToDataStruct(allLines[i]));
            }
            return allLineBytes;
        }
        #endregion

        #region 地址與字串之間轉換

        /// <summary>
        /// 解析某一行字串
        /// </summary>
        public List<HexParseDataOneAddrStruct> ParseOnRow(string str, int lineNum)
        {
            List<HexParseDataOneAddrStruct> oneRowData = new List<HexParseDataOneAddrStruct>();

            //:10 0030 00 60030000 24030000 60030000 60030000 70
            //:02 0000 04 0005 F5
            //:00 0000 01 FF

            HexTextOneLine oneLine = StringToDataStruct(str);

            //實際資料長度的2倍,之所以不用除法,避免出現奇數導致問題
            int doubleRealDataLength = (str.Length - 3 - 4 - 2 - 2);

            
            #region 有異常
            //資料長度不一致
            if (oneLine.dataLength * 2 != doubleRealDataLength)
            {
                HexParseDataOneAddrStruct dataOne = new HexParseDataOneAddrStruct();
                dataOne.lineNum = lineNum;
                dataOne.lengthError = true;
                oneRowData.Add(dataOne);
                return oneRowData;
            }

            //校驗不通過
            if (oneLine.checkSum != oneLine.realCheckSum)
            {
                HexParseDataOneAddrStruct dataOne = new HexParseDataOneAddrStruct();
                dataOne.lineNum = lineNum;
                dataOne.checkSumError = true;
                oneRowData.Add(dataOne);
                return oneRowData;
            }
            #endregion


            switch (oneLine.dataType)
            {
                case ENUM_HexDATA_TYPE.DATA:
                    {
                        //實際計算所得地址,每一行最多記錄16bytes,4組資料;每組佔2個地址,4bytes,實際地址要除以2
                        UInt32 realAddr = (m_INHX32_ExtendLinearAddressRecord + m_INHX32_ExtendSegmentAddressRecord + oneLine.addr);

                        //資料低位在前,與其他不同
                        for (int i = 0; i <= oneLine.data.Length - 4; i += 4)
                        {
                            HexParseDataOneAddrStruct oneAddr = new HexParseDataOneAddrStruct();
                            uint tmp = (uint)oneLine.data[i] + (uint)(oneLine.data[i + 1] << 8) + (uint)(oneLine.data[i + 2] << 16) + (uint)(oneLine.data[i + 3] << 24);
                            oneAddr.data = tmp;
                            
                            //線性地址和段地址
                            oneAddr.linearOffset = m_INHX32_ExtendLinearAddressRecord;
                            oneAddr.beforeIsLinearOffset = m_LastIsLinearOffset;
                            oneAddr.segmentOffset = m_INHX32_ExtendSegmentAddressRecord;
                            oneAddr.beforeIsSegmentOffset = m_LastIsSegmentOffset;
                            m_LastIsLinearOffset = m_LastIsSegmentOffset = false; //清空,下次就知道這次不是線性或者段地址

                            //地址
                            oneAddr.addr = Convert.ToUInt32(oneLine.addr + i/2);

                            //實際地址由線性地址和段地址以及地址組成
                            oneAddr.realAddr = Convert.ToUInt32(oneAddr.linearOffset + oneAddr.segmentOffset + oneAddr.addr); //16位微控制器,每4個位元組地址增長一半

                            //dsPIC33E最大程式地址為0x2AFEA,超過地址可能為配置字或者其它
                            if (oneAddr.realAddr > HexParseDataOneAddrStruct.maxProgramAddr)
                                oneAddr.notProgramFlash = true; //非程式儲存

                            oneAddr.lineNum = lineNum;
                            oneRowData.Add(oneAddr);
                        }
                    }
                    break;
                case ENUM_HexDATA_TYPE.END:
                    m_LastIsLinearOffset = m_LastIsSegmentOffset = false; //清空,下次就知道這次不是線性或者段地址
                    break;
                case ENUM_HexDATA_TYPE.ExtendSegmentAddressRecord: //段地址,左移4位
                    {
                        UInt32 segAddr = 0;
                        segAddr = Convert.ToUInt32((oneLine.data[0]<<8)+ oneLine.data[1]); //地址高位在前
                        m_INHX32_ExtendSegmentAddressRecord = (segAddr << 4) / 2;//顯示地址為實際地址的2倍

                        m_LastIsSegmentOffset = true; //下次即知此次為段地址
                    }
                    break;
                case ENUM_HexDATA_TYPE.ExtendLinearAddressRecord: //線性地址,左移16位
                    {
                        UInt32 lineAddr = 0;
                        lineAddr = Convert.ToUInt32((oneLine.data[0] << 8) + oneLine.data[1]); //地址高位在前
                        m_INHX32_ExtendLinearAddressRecord =  (lineAddr << 16) / 2; //顯示地址為實際地址的2倍
                        m_LastIsLinearOffset = true; //下次即知此次為線性地址
                    }
                    break;
            }

            return oneRowData;
        }

        
        #endregion

        #region 將Hex解析成地址資料


        /// <summary>
        /// Microchip的Hex解析
        /// 輸入為Hex的所有行資料
        /// </summary>
        public List<HexParseDataOneAddrStruct> GetDataFromMicrochipINHX32(ref string[] hexDatas)
        {
            List<HexParseDataOneAddrStruct> lsHexParseData = new List<HexParseDataOneAddrStruct>();

            //地址歸0
            m_INHX32_ExtendLinearAddressRecord = 0;
            m_INHX32_ExtendSegmentAddressRecord = 0;
            m_LastIsLinearOffset = m_LastIsSegmentOffset = false; //清空,下次就知道這次不是線性或者段地址

            //填充flash資料
            for (int i = 0; i < hexDatas.Length; i++)
            {
                lsHexParseData.AddRange(ParseOnRow(hexDatas[i], i));
            }

            return lsHexParseData;
        }

        #endregion

        #region 將地址資料變回Hex
        
        /// <summary>
        /// 解析屬於同一行的地址,將其變回hex字串
        /// 可能包含偏移地址的多行
        /// </summary>
        private List<string> EncryptOnAddr(List<HexParseDataOneAddrStruct> oneRowDatas)
        {
            List<string> strS = new List<string>();

            //前面是否需要插入線性地址偏移
            if (oneRowDatas[0].beforeIsLinearOffset)
            {
                string linearOffset = ":"+BitConverter.ToString(GetLinearOffset(oneRowDatas[0].linearOffset)).Replace("-", "");
                strS.Add(linearOffset);
            }

            //前面是否需要插入線性地址偏移
            if (oneRowDatas[0].beforeIsSegmentOffset)
            {
                string linearSegment = ":" + BitConverter.ToString(GetSegmentOffset(oneRowDatas[0].segmentOffset)).Replace("-", "");
                strS.Add(linearSegment);
            }

            List<byte> bsTmp = new List<byte>();
            bsTmp.Add(Convert.ToByte(4 * oneRowDatas.Count)); //datalength
            //addr
            bsTmp.Add(Convert.ToByte((oneRowDatas[0].addr >> 24) & 0xff));
            bsTmp.Add(Convert.ToByte((oneRowDatas[0].addr >> 16) & 0xff));
            bsTmp.Add(Convert.ToByte((oneRowDatas[0].addr >> 8) & 0xff));
            bsTmp.Add(Convert.ToByte(oneRowDatas[0].addr & 0xff));
            bsTmp.Add(0); //datatype
            for (int i = 0; i < oneRowDatas.Count; i++)
            {
                //每組地址4個bytes資料,低位在前
                bsTmp.Add(Convert.ToByte(oneRowDatas[i].data & 0xff));
                bsTmp.Add(Convert.ToByte((oneRowDatas[i].data >> 8) & 0xff));
                bsTmp.Add(Convert.ToByte((oneRowDatas[i].data >> 16) & 0xff));
                bsTmp.Add(Convert.ToByte((oneRowDatas[i].data >> 24) & 0xff));
            }
            //實際計算所得校驗和
            byte checkSum = 0x00;
            for (int i = 0; i < bsTmp.Count - 1; i++)
                checkSum += bsTmp[i];
            checkSum = (byte)(0x01 + ~checkSum);

            bsTmp.Add(checkSum);

            string strData = BitConverter.ToString(bsTmp.ToArray()).Replace("-", "");
            strS.Add(strData);

            return strS;
        }

        /// <summary>
        /// Microchip的Hex解析
        /// 輸入為Hex的所有行資料
        /// </summary>
        public List<string> SetDataToMicrochipINHX32(ref List<HexParseDataOneAddrStruct> lsHexParseData)
        {
            List<string> hexString = new List<string>();

            List<List<HexParseDataOneAddrStruct>> listList = new List<List<HexParseDataOneAddrStruct>>();

            int lineNum = 0;
            int startIndex = 0; //本段起始索引
            //填充flash資料
            for (int i = 0; i < lsHexParseData.Count; i++)
            {
                
                if (lineNum < lsHexParseData[i].lineNum) //邊界
                {
                    List<HexParseDataOneAddrStruct> list = new List<HexParseDataOneAddrStruct>();
                    //從邊界之間開始複製
                    for (int t = startIndex; t < i; t++)
                    {
                        list.Add(lsHexParseData[t]);
                    }

                    listList.Add(list);

                    startIndex = i;
                    lineNum = lsHexParseData[i].lineNum;
                }
            }

            //解析每一組資料
            for (int i = 0; i < listList.Count; i++)
                hexString.AddRange(EncryptOnAddr(listList[i]));

            //結束
            hexString.Add(":00000001FF");
            return hexString;
        }
        
        #endregion

        #endregion

    }
}