一、概述
在平時編寫STM32單片機代碼時,我們經(jīng)常會遇到某一個函數(shù)或某一個變量需要反復(fù)調(diào)試的情況,而常用的方法只能是在源碼修改并下載至單片機調(diào)試。反復(fù)這樣不僅麻煩,而且反復(fù)燒寫單片機對其FLASH也有影響,因此就考慮編寫一款小工具,可以實現(xiàn): 1)通過串口控制單片機執(zhí)行我們期望的函數(shù),同時函數(shù)參數(shù)最大支持5個,其參數(shù)類型支持char、short、int、float及其無符號類型和相應(yīng)的指針,不支持long及double。2)對于含有對字符串及數(shù)組操作的函數(shù),需要通過數(shù)組傳值后,在調(diào)用函數(shù)時寫入該變量地址才能實現(xiàn)對這些變量的操作。支持函數(shù)返回值得顯示。3)支持對全局變量進行任意的修改。4)支持十進制與十六進制切換.5)通訊超時自動重傳或關(guān)閉串口。建議配合KEIL一起使用,效果更好。本軟件使用C#編寫,運行環(huán)境為NET 4.5。先讓大家看看效果,感興趣的話可以繼續(xù)往下看: 1.上位機調(diào)試設(shè)置
?2.函數(shù)調(diào)用
?3.全局變量的寫入
?4.通訊超時處理
?二、上位機的處理
2.1 原理
在使用keil編譯STM32后,我們會在.hex文件的同一個文件夾中發(fā)現(xiàn)一個.map文件。這個.map文件包含了源碼中函數(shù)與全局變量的地址、大小、優(yōu)化等信息。這里貼一個簡化的.map文件給大家看一下:
Component: ARM Compiler 5.06 update 6 (build 750) Tool: armlink [4d35ed]==============================================================================Section Cross Referencesstartup_stm32f103xe.o(STACK) refers (Special) to heapauxi.o(.text) for __use_two_region_memorystartup_stm32f103xe.o(HEAP) refers (Special) to heapauxi.o(.text) for __use_two_region_memorystartup_stm32f103xe.o(RESET) refers (Special) to heapauxi.o(.text) for __use_two_region_memorystartup_stm32f103xe.o(RESET) refers to startup_stm32f103xe.o(STACK) for __initial_sp==============================================================================Removing Unused input sections from the image.Removing main.o(.rev16_text), (4 bytes).Removing main.o(.revsh_text), (4 bytes).Removing main.o(.rrx_text), (6 bytes).Removing gpio.o(.rev16_text), (4 bytes).Removing gpio.o(.revsh_text), (4 bytes).384 unused section(s) (total 34104 bytes) removed from the image.==============================================================================Image Symbol TableLocal SymbolsSymbol Name Value Ov Type SizeObject(Section)../Core/Src/gpio.c 0x00000000 Number 0gpio.o ABSOLUTE../Core/Src/main.c 0x00000000 Number 0main.o ABSOLUTE../Core/Src/stm32f1xx_hal_msp.c 0x00000000 Number 0stm32f1xx_hal_msp.o ABSOLUTE../Core/Src/stm32f1xx_it.c 0x00000000 Number 0stm32f1xx_it.o ABSOLUTE../Core/Src/system_stm32f1xx.c 0x00000000 Number 0system_stm32f1xx.o ABSOLUTE../Core/Src/tim.c 0x00000000 Number 0tim.o ABSOLUTE../Core/Src/usart.c 0x00000000 Number 0usart.o ABSOLUTE../Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal.c 0x00000000 Number 0stm32f1xx_hal.o ABSOLUTE../Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_cortex.c 0x00000000 Number 0stm32f1xx_hal_cortex.o ABSOLUTE../Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_dma.c 0x00000000 Number 0stm32f1xx_hal_dma.o ABSOLUTE../Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_exti.c 0x00000000 Number 0stm32f1xx_hal_exti.o ABSOLUTEGlobal SymbolsSymbol Name Value Ov Type SizeObject(Section)BuildAttributes$THM_ISAv4$P$D$K$B$S$PE$A:L22UL41UL21$X:L11$S22US41US21$IEEE1$IW$USESV6$~STKCKD$USESV7$~SHL$OSPACE$ROPI$EBA8$UX$STANDARDLIB$REQ8$PRES8$EABIv2 0x00000000 Number 0anon$obj.o ABSOLUTE__ARM_use_no_argv 0x00000000 Number 0main.o ABSOLUTE__ARM_exceptions_init - Undefined Weak Reference__alloca_initialize - Undefined Weak Reference__arm_preinit_ - Undefined Weak Reference__cpp_initialize__aeabi_ - Undefined Weak Reference_terminate_alloc - Undefined Weak Reference_terminate_user_alloc - Undefined Weak Reference_terminateio - Undefined Weak Reference__Vectors_Size 0x00000130 Number 0startup_stm32f103xe.o ABSOLUTE__Vectors 0x08000000 Data 4startup_stm32f103xe.o(RESET)__Vectors_End 0x08000130 Data 0startup_stm32f103xe.o(RESET)__main 0x08000131 Thumb Code 8__main.o(!!!main)in 0x2000001c Data 4main.o(.data)uin 0x20000020 Data 4main.o(.data)uwTick 0x20000024 Data 4stm32f1xx_hal.o(.data)uwTickPrio 0x20000028 Data 4stm32f1xx_hal.o(.data)uwTickFreq 0x2000002c Data 1stm32f1xx_hal.o(.data)==============================================================================Memory Map of the imageImage Entry point : 0x08000131Load Region LR_IROM1 (Base: 0x08000000, Size: 0x00002de8, Max: 0x00080000, ABSOLUTE, COMPRESSED[0x00002da8])Execution Region ER_IROM1 (Exec base: 0x08000000, Load base: 0x08000000, Size: 0x00002b94, Max: 0x00080000, ABSOLUTE)Exec Addr Load Addr Size Type Attr Idx E Section Name Object0x08000000 0x08000000 0x00000130 Data RO 3 RESET startup_stm32f103xe.o0x08000130 0x08000130 0x00000008 Code RO 2955* !!!main c_w.l(__main.o)0x08000138 0x08000138 0x00000034 Code RO 3143 !!!scatter c_w.l(__scatter.o)0x0800016c 0x0800016c 0x0000003a Code RO 3141 !!dczerorl c_w.l(__dczerorl.o)0x080001a6 0x080001a6 0x00000002 PAD0x080001a8 0x080001a8 0x0000001c Code RO 3145 !!handler_zi c_w.l(__scatter_zi.o)Execution Region RW_IRAM1 (Exec base: 0x20000000, Load base: 0x08002b94, Size: 0x00008bb0, Max: 0x00010000, ABSOLUTE, COMPRESSED[0x00000214])Exec Addr Load Addr Size Type Attr Idx E Section Name Object0x20000000 COMPRESSED 0x00000010 Data RW 18 .data test.o0x20000010 COMPRESSED 0x00000014 Data RW 78 .data main.o0x20000024 COMPRESSED 0x00000009 Data RW 1481 .data stm32f1xx_hal.o0x2000002d COMPRESSED 0x00000003 PAD0x20000030 COMPRESSED 0x00000004 Data RW 2832 .data system_stm32f1xx.o0x20000034 COMPRESSED 0x00000004 PAD0x20000038 COMPRESSED 0x0000021c Data RW 2910 .data debug_revice.o==============================================================================Image component sizesCode (inc. data) RO Data RW Data ZI Data Debug Object Name172 6 0 0 0 3002 debug_function.o580 98 0 540 2104 3763 debug_revice.o36 4 0 0 0 767 gpio.o288 24 0 20 50 486558 main.o64 26 304 0 32768 820 startup_stm32f103xe.o152 32 0 9 0 5977 stm32f1xx_hal.o304 22 0 0 0 29503 stm32f1xx_hal_cortex.o510 10 0 0 0 1927 stm32f1xx_hal_dma.o832 40 0 0 0 2092 stm32f1xx_hal_gpio.o84 8 0 0 0 918 stm32f1xx_hal_msp.o1784 110 0 0 0 6112 stm32f1xx_hal_rcc.o1260 44 0 0 0 9974 stm32f1xx_hal_tim.o160 22 0 0 0 2453 stm32f1xx_hal_tim_ex.o1844 10 0 0 0 11460 stm32f1xx_hal_uart.o66 12 0 0 0 4980 stm32f1xx_it.o2 0 24 4 0 1155 system_stm32f1xx.o134 10 0 16 0 6385 test.o192 18 0 0 72 1702 tim.o220 26 0 0 68 1778 usart.o----------------------------------------------------------------------8702 522 362 596 35068 581326 Object Totals0 0 32 0 0 0 (incl. Generated)18 0 2 7 6 0 (incl. Padding)----------------------------------------------------------------------Code (inc. data) RO Data RW Data ZI Data Debug Library Member Name58 0 0 0 0 0 __dczerorl.o8 0 0 0 0 68 __main.o0 0 0 0 0 0 __rtentry.o12 0 0 0 0 0 __rtentry2.o6 0 0 0 0 0 __rtentry4.o52 8 0 0 0 0 __scatter.o28 0 0 0 0 0 __scatter_zi.o18 0 0 0 0 80 exit.o6 0 0 0 0 152 heapauxi.o0 0 0 0 0 0 indicate_semi.o2 0 0 0 0 0 libinit.o2 0 0 0 0 0 libinit2.o2 0 0 0 0 0 libshutdown.o2 0 0 0 0 0 libshutdown2.o8 4 0 0 96 68 libspace.o78 0 0 0 0 80 rt_memclr_w.o2 0 0 0 0 0 rtexit.o10 0 0 0 0 0 rtexit2.o12 4 0 0 0 68 sys_exit.o74 0 0 0 0 80 sys_stackheap_outer.o2 0 0 0 0 68 use_no_semi.o804 16 0 0 0 272 daddsub_clz.o90 4 0 0 0 92 dfixu.o156 4 0 0 0 92 dnaninf.o12 0 0 0 0 68 dretinf.o430 8 0 0 0 168 faddsub_clz.o62 4 0 0 0 84 ffixu.o140 4 0 0 0 84 fnaninf.o10 0 0 0 0 68 fretinf.o0 0 0 0 0 0 usenofp.o----------------------------------------------------------------------2092 56 0 0 96 1592 Library Totals6 0 0 0 0 0 (incl. Padding)----------------------------------------------------------------------Code (inc. data) RO Data RW Data ZI Data Debug Library Name382 16 0 0 96 664 c_w.l1704 40 0 0 0 928 fz_ws.l----------------------------------------------------------------------2092 56 0 0 96 1592 Library Totals----------------------------------------------------------------------==============================================================================Code (inc. data) RO Data RW Data ZI Data Debug10794 578 362 596 35164 577922 Grand Totals10794 578 362 532 35164 577922 ELF Image Totals (compressed)10794 578 362 532 0 0 ROM Totals==============================================================================Total ROSize (Code + RO Data) 11156 (10.89kB)Total RWSize (RW Data + ZI Data) 35760 (34.92kB)Total ROM Size (Code + RO Data + RW Data) 11688 (11.41kB)
仔細觀察可以發(fā)現(xiàn).map文件主要由以下幾個部分組成:
Component: ARM Compiler 5.06 update 6 (build 750) Tool: armlink [4d35ed]==============================================================================Section Cross References==============================================================================Removing Unused input sections from the image.==============================================================================Image Symbol TableLocal SymbolsSymbol Name Value Ov Type SizeObject(Section)Global SymbolsSymbol Name Value Ov Type SizeObject(Section)==============================================================================Memory Map of the image==============================================================================Image component sizes==============================================================================Code (inc. data) RO Data RW Data ZI Data Debug==============================================================================Total ROSize (Code + RO Data)Total RWSize (RW Data + ZI Data)TotalROMSize(Code+ROData+RWData)
而我們最關(guān)注的信息如函數(shù)和全局變量的地址與大小都在.map文件中的Image Symbol Table->Global Symbols。知道了這些地址,我們只需將其感興趣的函數(shù)與變量地址發(fā)送給單片機,單片機通過指針就可以執(zhí)行相應(yīng)的函數(shù)了。整個上位機正是基于這個原理而編寫的。具體流程如下圖所示:

2.2 class Get_Map_Address_And_Size_Table的實現(xiàn)——————.map中函數(shù)和全局變量的地址與大小等信息提取
函數(shù)和全局變量的地址與大小都在.map文件中的Image Symbol Table->Global Symbols,由Symbol Name、Value、Ov Type、Size、Object(Section)組成,所以先定義一個public struct Symbol來包含上述信息:
public struct Symbol{public String Symbol_Name;public uint Symbol_Address;public SymbolType Symbol_Type;public ushort Symbol_Size;public String Symbol_Section;};
接下來就是通過FileStream獲取.map文件中的信息,并定位至Image Symbol Table->Global Symbols,讀取Symbol Name、Value、Ov Type、Size、Object(Section)并賦值給symbol_table:
public void Create_Address_And_Size_Table(String filename){try{uint i;FileStream file_read = new FileStream(filename, FileMode.Open, FileAccess.Read);//新建文件流filelist = File.ReadAllLines(filename, Encoding.Default);//讀取文件內(nèi)容所有行保存到字符串數(shù)組中。for (i = 0; i <= filelist.Length - 1; i++) //定位到感興趣的位置{if (filelist[i].Contains("Global Symbols")){break;}}for (uint j = i; j <= filelist.Length - 1; j++){if (filelist[j].Contains("Object(Section)")){i = j + 1;break;}}if (i < filelist.Length - 1) //獲取信息{//Table_DeInit();Get_Symbol_Data(i);}file_read.Close();}catch (Exception ex){MessageBox.Show(ex.Message);}}
Get_Symbol_Data(i);就是負責將Image Symbol Table->Global Symbols中的Symbol Name、Value、Ov Type、Size、Object(Section)賦值給symbol_table。有兩點需要說明一下:
1)由于在Global Symbols中,
xxxxxx-UndefinedWeakReference
不包含有用信息,是需要被排除的,可以通過Contains("- Undefined Weak Reference")方法將其排除。
2)Image Symbol Table->Global Symbols中的Symbol Name、Value、Ov Type、Size、Object(Section)是通過空格將數(shù)據(jù)進行分割,所以可以通過
Split(newChar[]{''},StringSplitOptions.RemoveEmptyEntries);
就可以得到數(shù)據(jù)集。
void Get_Symbol_Data(uint index)函數(shù)如下:
private void Get_Symbol_Data(uint index){table_length = 0;while (index <= filelist.Length - 1){if (filelist[index].Equals("")){index++;continue;}if(filelist[index].Contains("=") == false){if (filelist[index].Contains("- Undefined Weak Reference"))//排除- Undefined Weak Reference{index++;continue;}else{int str_index = 0;string[] split_str = filelist[index].Split(new Char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);//獲取數(shù)據(jù)集symbol_table[table_length].Symbol_Name = split_str[str_index];str_index++;symbol_table[table_length].Symbol_Address = Convert.ToUInt32(split_str[str_index], 16);str_index++;if (split_str[str_index].Equals("Thumb")){symbol_table[table_length].Symbol_Type = SymbolType.Thumb_Code;}else if (split_str[str_index].Equals("Section")){symbol_table[table_length].Symbol_Type = SymbolType.Section;}else if (split_str[str_index].Equals("Number")){symbol_table[table_length].Symbol_Type = SymbolType.Number;}else if (split_str[str_index].Equals("Data")){symbol_table[table_length].Symbol_Type = SymbolType.Data;}str_index++;if (split_str[str_index].Equals("Code")){str_index++;symbol_table[table_length].Symbol_Size = Convert.ToUInt16(split_str[str_index], 10);str_index++;symbol_table[table_length].Symbol_Section = split_str[str_index];}else{symbol_table[table_length].Symbol_Size = Convert.ToUInt16(split_str[str_index], 10);str_index++;symbol_table[table_length].Symbol_Section = split_str[str_index];}table_length = table_length + 1;index++;if (table_length >= table_len){break;}}}else{break;}
以上是class Get_Map_Address_And_Size_Table最主要的實現(xiàn)方法。通過這兩個方法,就可以得到.map文件中函數(shù)與全局變量的信息了。
2.3 class Get_Function_Address_And_Size_Table的實現(xiàn)——————獲取我們所需的函數(shù)列表
在得到含有函數(shù)與全局變量的信息的symbol_table后,我們需要得到我們感興趣的函數(shù)列表。在本上位機中,需要用戶新建一個.function文件。在該文中包含有用戶需要調(diào)試的函數(shù)列表。一般只需直接復(fù)制.h文件中的函數(shù)申明即可。然后上位機通過該列表獲取函數(shù)名稱、參數(shù)、返回類型等參量,最后在symbol_table中查詢該函數(shù),并獲取其地址。以上就是class Get_Function_Address_And_Size_Table所要實現(xiàn)的目標。在class Get_Function_Address_And_Size_Table中先定義
public struct Function{public String Function_List_Name;public String Function_Name;public uint Function_Address;public String Function_Parameter1;public String Function_Parameter2;public String Function_Parameter3;public String Function_Parameter4;public String Function_Parameter5;public String Function_Return;public uint Function_Parameter_Number;};
以方便存儲所要調(diào)試函數(shù)信息。這里需要需要注意的是,由于C#中struct不能像C中struct一樣直接定義一個固定長度的數(shù)組,所以直接用Function_ParameterX這樣的笨辦法來定義5個函數(shù)參數(shù)信息。
在class Get_Function_Address_And_Size_Table中最重要的就是void Get_Need_Function_Table()函數(shù)。其獲取.function文件中的函數(shù)列表并解析處該列表函數(shù)名稱、參數(shù)、返回類型等參量,并賦值給function_table中。
private void Get_Need_Function_Table(){uint index = 0;for (index = 0; index < table_length; index++){string[] split_str = filelist[index].Split(new Char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);uint str_index = 0;function_table[index].Function_List_Name = filelist[index];if (split_str[str_index].Equals("unsigned") || split_str[str_index].Equals("signed")) //Function_Return{function_table[index].Function_Return = split_str[str_index] + " " + split_str[str_index + 1];str_index = str_index + 2;}else{function_table[index].Function_Return = split_str[str_index];str_index++;}if(split_str[str_index].Equals("*")){function_table[index].Function_Return = function_table[index].Function_Return + split_str[str_index];str_index++;}if (split_str[str_index].Contains("*")) //Function_Name{function_table[index].Function_Return = function_table[index].Function_Return + "*";function_table[index].Function_Name = split_str[str_index].TrimStart(new char[1] { '*' });str_index++;}else{function_table[index].Function_Name = split_str[str_index];}string[] split_paramenter_str = new String[3];split_paramenter_str = function_table[index].Function_Name.Split(new Char[] { '(' }, StringSplitOptions.RemoveEmptyEntries);function_table[index].Function_Name = split_paramenter_str[0];string[] paramenter = filelist[index].Split(new Char[] { '(' }, StringSplitOptions.RemoveEmptyEntries);//Function_Parameter_NumberString paramenter_string = paramenter[1];paramenter_string = paramenter_string.TrimEnd(new char[2] { ')', ';' });str_index = 0;if(paramenter_string.Equals("") || paramenter_string.Equals(" ") || paramenter_string.Equals("void")){function_table[index].Function_Parameter_Number = 0;function_table[index].Function_Parameter1 = "";function_table[index].Function_Parameter2 = "";function_table[index].Function_Parameter3 = "";function_table[index].Function_Parameter4 = "";function_table[index].Function_Parameter5 = "";}else if(paramenter_string.Contains(",")){string[] s = paramenter_string.Split(new Char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);switch(s.Length){case 2: function_table[index].Function_Parameter1 = Get_Data_Kind(s[0]);function_table[index].Function_Parameter2 = Get_Data_Kind(s[1]);function_table[index].Function_Parameter3 = "";function_table[index].Function_Parameter4 = "";function_table[index].Function_Parameter5 = "";function_table[index].Function_Parameter_Number = 2;break;case 3: function_table[index].Function_Parameter1 = Get_Data_Kind(s[0]);function_table[index].Function_Parameter2 = Get_Data_Kind(s[1]);function_table[index].Function_Parameter3 = Get_Data_Kind(s[2]);function_table[index].Function_Parameter4 = "";function_table[index].Function_Parameter5 = "";function_table[index].Function_Parameter_Number = 3;break;case 4: function_table[index].Function_Parameter1 = Get_Data_Kind(s[0]);function_table[index].Function_Parameter2 = Get_Data_Kind(s[1]);function_table[index].Function_Parameter3 = Get_Data_Kind(s[2]);function_table[index].Function_Parameter4 = Get_Data_Kind(s[3]);function_table[index].Function_Parameter5 = "";function_table[index].Function_Parameter_Number = 4;break;case 5: function_table[index].Function_Parameter1 = Get_Data_Kind(s[0]);function_table[index].Function_Parameter2 = Get_Data_Kind(s[1]);function_table[index].Function_Parameter3 = Get_Data_Kind(s[2]);function_table[index].Function_Parameter4 = Get_Data_Kind(s[3]);function_table[index].Function_Parameter5 = Get_Data_Kind(s[4]);function_table[index].Function_Parameter_Number = 8;break;}}else{function_table[index].Function_Parameter_Number = 1;function_table[index].Function_Parameter1 = Get_Data_Kind(paramenter_string);function_table[index].Function_Parameter2 = "";function_table[index].Function_Parameter3 = "";function_table[index].Function_Parameter4 = "";function_table[index].Function_Parameter5 = "";}}
在得到function_table列表后,只需通過
for (uint i = 0; i < function_table.table_length; i++){index = map_table.Get_Index(function_table.function_table[i].Function_Name);addr = map_table.Get_Address(index);function_table.Set_Address(i, addr);}
用以實現(xiàn)存儲全局變量的相關(guān)信息。
2.5 控制說明
2.5.1 命令字及其數(shù)據(jù)格式
函數(shù)發(fā)送命令字:
函數(shù)返回值命令字:
下位機接收超時命令字:
有人會疑惑STM32的地址只有4字節(jié),為何在命令字中地址卻占用8字節(jié)?這要從不同類型數(shù)據(jù)轉(zhuǎn)換為byte說起。
將不同類型數(shù)據(jù)的函數(shù)參數(shù)轉(zhuǎn)換為byte的技巧就是使用聯(lián)合體。只要在聯(lián)合體中定義不同類型的變量與最大字長的char數(shù)組,就可以很容易的得到其在內(nèi)存中的分布。在一開始函數(shù)參數(shù)轉(zhuǎn)換時,為了兼容double類型函數(shù)參數(shù),在“聯(lián)合體”中定義了double,導(dǎo)致其長度為8字節(jié)。而函數(shù)地址轉(zhuǎn)換也使用了這一方法,所以發(fā)送命令字中地址長度也變?yōu)?字節(jié)。需要注意的是,在C#中沒有聯(lián)合體這一概念,所以只能使用struct并指定變量起始地址以實現(xiàn)C的聯(lián)合體:
public struct TypeUnion{[]public byte uc;[]public sbyte sc;[]public ushort us;[]public short ss;[]public uint ui;[]public uint pointer; //指針[]public int si;[]public float f;[]public double d;}
由于不能定義char[8],所以之后還要使用static byte[] StructToBytes(object structObj)得到相應(yīng)變量的內(nèi)存分布byte[8]
2.5.2 調(diào)試函數(shù)與全局變量的發(fā)送流程
按下函數(shù)調(diào)試發(fā)送按鈕之后,會觸發(fā)void SendFunctionButton_Click(object sender, EventArgs e)函數(shù)。在該函數(shù)中主要流程是判斷串口是否開啟->函數(shù)參數(shù)類型轉(zhuǎn)換->CRC校驗->超時判斷與重發(fā)。函數(shù)參數(shù)類型轉(zhuǎn)換主要由TypeUnion TypeTransfer(String type_s,String text_s)完成。該函數(shù)主要依據(jù)參數(shù)類型,將傳入的參數(shù)用 Convert.ToXXX(text_s, f_base)方法轉(zhuǎn)換為對應(yīng)的數(shù)據(jù),并直接賦值給TypeUnion,即一個聯(lián)合體變量,然后通過static byte[] StructToBytes(object structObj)得到內(nèi)存分布byte[8]。
而CRC校驗則使用CRC16 CITT算法。在前49個字節(jié)填充完畢后,最后兩個字節(jié)先賦值為0,做一次CRC校驗,得到的數(shù)據(jù)再賦值給最后兩個字節(jié)。
2.5.3 函數(shù)返回值接收流程函數(shù)
在發(fā)送完函數(shù)調(diào)試命令后,上位機會自動等待直至接收到下位機發(fā)送的回復(fù)或到達設(shè)置的超時時間。利用static object BytesToStuct(byte[] bytes, Type type)將前8個字節(jié)轉(zhuǎn)換為TypeUnion變量。而CRC校驗則使用CRC16 CITT算法。在前8個字節(jié)填充完畢后做一次CRC校驗。如果校驗失敗則直接做一次超時處理,并在一定時間后重新發(fā)送函數(shù)調(diào)試命令。
2.5.4 超時與重傳處理
在實際的串口數(shù)據(jù)收發(fā)中,難免會遇到數(shù)據(jù)收發(fā)丟失或中斷。比如這次開發(fā)中使用虛擬串口收發(fā)數(shù)據(jù)就遇到數(shù)據(jù)丟失的情況:



明明監(jiān)控數(shù)據(jù)都正確收發(fā),但就是會漏數(shù)據(jù),也不知怎么回事。沒辦法,只能做超時重發(fā)處理以應(yīng)對這種情況。在上位機中,主要通過函數(shù)bool Is_Timeout()來處理這一情況。
private bool Is_Timeout(){bool timeout = false;ushort count_ = 0;while (SerialPort.BytesToRead < RETURN_MAX_LENTH){System.Threading.Thread.Sleep(1); //每隔1ms讀取數(shù)據(jù)是否都收到count_++;if (count_ > timeout_set){break;}}if (count_ < timeout_set)? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? //未超時數(shù)據(jù)處理{byte[] byteArray = new byte[RETURN_MAX_LENTH];SerialPort.Read(byteArray, 0, byteArray.Length);uint count = 0;for (uint i = 0; i < PARAMENT_MAX_LENTH; i++)? ?? ?? ???//收到8個字節(jié)都是0xFF,說明下位機未正確收到數(shù)據(jù){if (byteArray[i] == 0xFF){count++;}}if (count >= PARAMENT_MAX_LENTH){timeout = true;}else{if (function_send) //獲取函數(shù)返回值{function_send = false;ushort crc1 = 0;crc1 = (ushort)byteArray[RETURN_MAX_LENTH - 2];crc1 = (ushort)(crc1 << 8);crc1 = (ushort)(crc1 | (ushort)byteArray[RETURN_MAX_LENTH - 1]);byte[] byte_Array = new byte[RETURN_MAX_LENTH - 2];for (uint i = 0; i < RETURN_MAX_LENTH - 2; i++){byte_Array[i] = byteArray[i];}CRC16 c = new CRC16();ushort crc = c.GetCRC16(byte_Array);if(crc == crc1){TypeUnion return_data = (TypeUnion)BytesToStuct(byte_Array, typeof(TypeUnion));String s = TypeTransferToString(function_table.function_table[select_function_index].Function_Return, return_data);RecivedTextBox.Text = s;}else{timeout = true;}}}}else //超過設(shè)置的超時時間,直接關(guān)閉串口并報錯{timeout = true;SerialPort.Close();ControlSerialButton.Text = "打開串口";COMComboBox.Enabled = true;BaudRateComboBox.Enabled = true;ParityBitsComboBox.Enabled = true;StopBitComboBox.Enabled = true;DataBitsComboBox.Enabled = true;MessageBox.Show("通訊超時!已關(guān)閉串口!");}return timeout;}
三、下位機的處理
3.1 接收處理
本來打算使用DMA+空閑中斷接收命令字,但考慮到有些低端的單片機沒有空閑中斷,同時實際使用中出現(xiàn)數(shù)據(jù)丟失會造成持續(xù)的等待,所以直接使用單字節(jié)中斷接收的方案。在接收到固定的字節(jié)后,標志位data_recived置一,并將數(shù)據(jù)拷貝出來。
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //接收中斷{unsigned char i = 0;HAL_TIM_Base_Stop_IT(&htim3);__HAL_TIM_SET_COUNTER(&htim3, 0);data[data_length] = recv_data;data_length++;if(data_length < MAX_RECIVE_LENGTH){HAL_TIM_Base_Start_IT(&htim3);}else{data_length = 0;data_recived = 1;for(i = 0;i < MAX_RECIVE_LENGTH;i++){r_data[i] = data[i];}}HAL_UART_Receive_IT(&huart1, &recv_data, 1);}
3.2 超時處理
由于在實際的數(shù)據(jù)收發(fā)中,會出現(xiàn)數(shù)據(jù)丟失而造成上位機發(fā)送完畢但下位機并未全部接受,從而下位機一直處于等待的情況。為了解決這一情況,引入一個定時為200Hz的定時器。在進入接收中斷后,先關(guān)閉清空定時器,讀取接收的數(shù)據(jù)后再開啟定時。如果出現(xiàn)數(shù)據(jù)丟失而造成下位機等待的情況,則會引發(fā)定時中斷。在定時中斷內(nèi)直接清空接收計數(shù)器,并給上位機發(fā)送超時指令。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //超時中斷,超時時間為5ms,一旦超時就發(fā)送8字節(jié)0xFF{unsigned char i = 0;HAL_TIM_Base_Stop_IT(&htim3);__HAL_TIM_SET_COUNTER(&htim3, 0);data_length = 0;for(i = 0;i < PARAMENT_MAX_LENTH;i++){returndata[i] = 0xFF;}crc = crc16(returndata, PARAMENT_MAX_LENTH);returndata[PARAMENT_MAX_LENTH] = crc >> 8;returndata[PARAMENT_MAX_LENTH + 1] = crc;HAL_UART_Transmit(&huart1, returndata, 10, 0xFF);}
3.3 函數(shù)指針
通過周期調(diào)用void Recived_Command_Handle(void)來實現(xiàn)上位機發(fā)送的函數(shù)調(diào)試命令字。
void Recived_Command_Handle(void){unsigned char i = 0;unsigned char j = 0;if(data_recived){data_recived = 0;crc_16 = (r_data[49] << 8) | r_data[50];r_data[MAX_RECIVE_LENGTH - 2] = 0;r_data[MAX_RECIVE_LENGTH - 1] = 0;crc = crc16(r_data, MAX_RECIVE_LENGTH); //CRC_CITT校驗if(crc == crc_16){num = r_data[0]; //獲取參數(shù)數(shù)量for(i = 0;i < PARAMENT_MAX_LENTH;i++)? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?//獲取地址{addr.u_char[i] = r_data[1 + i];}for(i = 0;i < 5;i++) //獲取參數(shù){for(j = 0;j < PARAMENT_MAX_LENTH;j++){paramen[i].u_char[j] = r_data[(1 + PARAMENT_MAX_LENTH) + PARAMENT_MAX_LENTH*i + j];}}if(addr.ul != 0) //獲取返回值{for(i = 0;i < PARAMENT_MAX_LENTH;i++){return_data.u_char[i] = 0;}return_data = function(addr.ul,num,paramen);}for(i = 0;i < PARAMENT_MAX_LENTH;i++){returndata[i] = return_data.u_char[i];}crc = crc16(return_data.u_char, PARAMENT_MAX_LENTH);returndata[PARAMENT_MAX_LENTH] = crc >> 8;returndata[PARAMENT_MAX_LENTH + 1] = crc;HAL_UART_Transmit(&huart1, returndata, 10, 0xFF);}}}
其中函數(shù)實現(xiàn)由parameter_kind_union function(unsigned int function_addr,unsigned char paramenter_num,parameter_kind_union *paramenter)完成。
parameter_kind_union function(unsigned int function_addr,unsigned char paramenter_num,parameter_kind_union *paramenter){void *p = (void *)function_addr;parameter_kind_union return_data;switch(paramenter_num){case 0: return_data.ull = (*(unsigned int(*)())p)();break;case 1: return_data.ull = (*(unsigned int(*)())p)(PARAMENT_TRANSFER(paramenter[0]));break;case 2: return_data.ull = (*(unsigned int(*)())p)(PARAMENT_TRANSFER(paramenter[0]),PARAMENT_TRANSFER(paramenter[1]));break;case 3: return_data.ull = (*(unsigned int(*)())p) (PARAMENT_TRANSFER(paramenter[0]),PARAMENT_TRANSFER(paramenter[1]),PARAMENT_TRANSFER(paramenter[2]));break;case 4: return_data.ull = (*(unsigned int(*)())p)(PARAMENT_TRANSFER(paramenter[0]),PARAMENT_TRANSFER(paramenter[1]),PARAMENT_TRANSFER(paramenter[2]),PARAMENT_TRANSFER(paramenter[3]));break;case 5: return_data.ull = (*(unsigned int(*)())p)(PARAMENT_TRANSFER(paramenter[0]),PARAMENT_TRANSFER(paramenter[1]),PARAMENT_TRANSFER(paramenter[2]),PARAMENT_TRANSFER(paramenter[3]),PARAMENT_TRANSFER(paramenter[4]));break;}return return_data;}
#definePARAMENT_TRANSFER(p)(*(volatileunsignedint*)((unsignedint)&p))
其操作含義如下:
1)&p含義為取變量p地址;
2)(unsigned int)&p)含義為將取得的地址強制轉(zhuǎn)換為unsigned int;
3)(volatile unsigned int*)((unsigned int)&p)含義為將數(shù)字轉(zhuǎn)換為unsigned int類型的指針;
4)*(volatile unsigned int*)((unsigned int)&p)含義為取得該地址內(nèi)的數(shù)據(jù);
可這樣會造成一個問題,這就是對于double和long類型的變量,其在取值時會造成錯誤:


可以看到對于double類型,函數(shù)參數(shù)值只獲得了前4個字節(jié)的數(shù)據(jù),后4個字節(jié)數(shù)據(jù)丟失了。嘗試定義
#definePARAMENT_TRANSFER(p)(*(volatileunsignedlonglong*)((unsignedint)&p))
可以正確獲得double參數(shù),但char等類型則不能正確獲?。?/p>


所以暫時使用第一種PARAMENT_TRANSFER定義。
(*(unsigned int(*)())p)()則為執(zhí)行函數(shù),類似于回調(diào)函數(shù)。通過它可以執(zhí)行指定的函數(shù)。
3.4 修改全局變量
通過void Set_Global_Data(unsigned int addr,unsigned char len,parameter_kind_union data)實現(xiàn)數(shù)據(jù)的寫入。而數(shù)組的寫入則是循環(huán)調(diào)用該函數(shù),并加入測試重傳功能。
審核編輯 :李倩-
單片機
+關(guān)注
關(guān)注
6076文章
45501瀏覽量
670819 -
STM32
+關(guān)注
關(guān)注
2310文章
11166瀏覽量
373591 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4417瀏覽量
67570
原文標題:自編一個單片機調(diào)試小工具,并談?wù)勂渚幊趟悸?/p>
文章出處:【微信號:mcu168,微信公眾號:硬件攻城獅】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
是否可以將 Vision Five 2 配置為 SuperSpeed 上的 USB 3.0 mass_storage小工具?
單片機系統(tǒng)硬件的調(diào)試方法
淘寶API應(yīng)用:小工具撬動大流量,訂單接到手軟!
為什么單片機還在用C語言編程?
第1章 如何學習單片機
單片機:一個承載科技與工程思維的核心平臺
開發(fā)單片機需要學習什么?軟件編程的 4 個關(guān)鍵層次
怎么測單片機系統(tǒng)頻率
單片機怎么燒程序
單片機定制開發(fā)的設(shè)計思路
一個單片機調(diào)試小工具的編程思路
評論