【dsPIC33E】Bootloader(四)Bootloader上位機
阿新 • • 發佈:2018-12-17
前面已經將下位機部署完畢,本節將講述上位機。
上位機的工作相對比較簡單,主要就是解析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 } }