一、概述
在平時編寫STM32單片機代碼時,我們經常會遇到某一個函數或某一個變量需要反復調試的情況,而常用的方法只能是在源碼修改并下載至單片機調試。反復這樣不僅麻煩,而且反復燒寫單片機對其FLASH也有影響,因此就考慮編寫一款小工具,可以實現: 1)通過串口控制單片機執行我們期望的函數,同時函數參數最大支持5個,其參數類型支持char、short、int、float及其無符號類型和相應的指針,不支持long及double。2)對于含有對字符串及數組操作的函數,需要通過數組傳值后,在調用函數時寫入該變量地址才能實現對這些變量的操作。支持函數返回值得顯示。3)支持對全局變量進行任意的修改。4)支持十進制與十六進制切換.5)通訊超時自動重傳或關閉串口。建議配合KEIL一起使用,效果更好。本軟件使用C#編寫,運行環境為NET 4.5。先讓大家看看效果,感興趣的話可以繼續往下看: 1.上位機調試設置 ?2.函數調用 ?3.全局變量的寫入 ?4.通訊超時處理 ?二、上位機的處理 2.1 原理 在使用keil編譯STM32后,我們會在.hex文件的同一個文件夾中發現一個.map文件。這個.map文件包含了源碼中函數與全局變量的地址、大小、優化等信息。這里貼一個簡化的.map文件給大家看一下:
Component: ARM Compiler 5.06 update 6 (build 750) Tool: armlink [4d35ed]
==============================================================================
Section Cross References
startup_stm32f103xe.o(STACK) refers (Special) to heapauxi.o(.text) for __use_two_region_memory
startup_stm32f103xe.o(HEAP) refers (Special) to heapauxi.o(.text) for __use_two_region_memory
startup_stm32f103xe.o(RESET) refers (Special) to heapauxi.o(.text) for __use_two_region_memory
startup_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 Table
Local Symbols
Symbol 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 ABSOLUTE
Global Symbols
Symbol 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 image
Image Entry point : 0x08000131
Load 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 Object
0x08000000 0x08000000 0x00000130 Data RO 3 RESET startup_stm32f103xe.o
0x08000130 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 PAD
0x080001a8 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 Object
0x20000000 COMPRESSED 0x00000010 Data RW 18 .data test.o
0x20000010 COMPRESSED 0x00000014 Data RW 78 .data main.o
0x20000024 COMPRESSED 0x00000009 Data RW 1481 .data stm32f1xx_hal.o
0x2000002d COMPRESSED 0x00000003 PAD
0x20000030 COMPRESSED 0x00000004 Data RW 2832 .data system_stm32f1xx.o
0x20000034 COMPRESSED 0x00000004 PAD
0x20000038 COMPRESSED 0x0000021c Data RW 2910 .data debug_revice.o
==============================================================================
Image component sizes
Code (inc. data) RO Data RW Data ZI Data Debug Object Name
172 6 0 0 0 3002 debug_function.o
580 98 0 540 2104 3763 debug_revice.o
36 4 0 0 0 767 gpio.o
288 24 0 20 50 486558 main.o
64 26 304 0 32768 820 startup_stm32f103xe.o
152 32 0 9 0 5977 stm32f1xx_hal.o
304 22 0 0 0 29503 stm32f1xx_hal_cortex.o
510 10 0 0 0 1927 stm32f1xx_hal_dma.o
832 40 0 0 0 2092 stm32f1xx_hal_gpio.o
84 8 0 0 0 918 stm32f1xx_hal_msp.o
1784 110 0 0 0 6112 stm32f1xx_hal_rcc.o
1260 44 0 0 0 9974 stm32f1xx_hal_tim.o
160 22 0 0 0 2453 stm32f1xx_hal_tim_ex.o
1844 10 0 0 0 11460 stm32f1xx_hal_uart.o
66 12 0 0 0 4980 stm32f1xx_it.o
2 0 24 4 0 1155 system_stm32f1xx.o
134 10 0 16 0 6385 test.o
192 18 0 0 72 1702 tim.o
220 26 0 0 68 1778 usart.o
----------------------------------------------------------------------
8702 522 362 596 35068 581326 Object Totals
0 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 Name
58 0 0 0 0 0 __dczerorl.o
8 0 0 0 0 68 __main.o
0 0 0 0 0 0 __rtentry.o
12 0 0 0 0 0 __rtentry2.o
6 0 0 0 0 0 __rtentry4.o
52 8 0 0 0 0 __scatter.o
28 0 0 0 0 0 __scatter_zi.o
18 0 0 0 0 80 exit.o
6 0 0 0 0 152 heapauxi.o
0 0 0 0 0 0 indicate_semi.o
2 0 0 0 0 0 libinit.o
2 0 0 0 0 0 libinit2.o
2 0 0 0 0 0 libshutdown.o
2 0 0 0 0 0 libshutdown2.o
8 4 0 0 96 68 libspace.o
78 0 0 0 0 80 rt_memclr_w.o
2 0 0 0 0 0 rtexit.o
10 0 0 0 0 0 rtexit2.o
12 4 0 0 0 68 sys_exit.o
74 0 0 0 0 80 sys_stackheap_outer.o
2 0 0 0 0 68 use_no_semi.o
804 16 0 0 0 272 daddsub_clz.o
90 4 0 0 0 92 dfixu.o
156 4 0 0 0 92 dnaninf.o
12 0 0 0 0 68 dretinf.o
430 8 0 0 0 168 faddsub_clz.o
62 4 0 0 0 84 ffixu.o
140 4 0 0 0 84 fnaninf.o
10 0 0 0 0 68 fretinf.o
0 0 0 0 0 0 usenofp.o
----------------------------------------------------------------------
2092 56 0 0 96 1592 Library Totals
6 0 0 0 0 0 (incl. Padding)
----------------------------------------------------------------------
Code (inc. data) RO Data RW Data ZI Data Debug Library Name
382 16 0 0 96 664 c_w.l
1704 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 Debug
10794 578 362 596 35164 577922 Grand Totals
10794 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)
仔細觀察可以發現.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 Table
Local Symbols
Symbol Name Value Ov Type SizeObject(Section)
Global Symbols
Symbol 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)
而我們最關注的信息如函數和全局變量的地址與大小都在.map文件中的Image Symbol Table->Global Symbols。知道了這些地址,我們只需將其感興趣的函數與變量地址發送給單片機,單片機通過指針就可以執行相應的函數了。整個上位機正是基于這個原理而編寫的。具體流程如下圖所示:
2.2 class Get_Map_Address_And_Size_Table的實現——————.map中函數和全局變量的地址與大小等信息提取
函數和全局變量的地址與大小都在.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);//讀取文件內容所有行保存到字符串數組中。
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)是通過空格將數據進行分割,所以可以通過
Split(newChar[]{''},StringSplitOptions.RemoveEmptyEntries);
就可以得到數據集。
void Get_Symbol_Data(uint index)函數如下:
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);//獲取數據集
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最主要的實現方法。通過這兩個方法,就可以得到.map文件中函數與全局變量的信息了。
2.3 class Get_Function_Address_And_Size_Table的實現——————獲取我們所需的函數列表
在得到含有函數與全局變量的信息的symbol_table后,我們需要得到我們感興趣的函數列表。在本上位機中,需要用戶新建一個.function文件。在該文中包含有用戶需要調試的函數列表。一般只需直接復制.h文件中的函數申明即可。然后上位機通過該列表獲取函數名稱、參數、返回類型等參量,最后在symbol_table中查詢該函數,并獲取其地址。以上就是class Get_Function_Address_And_Size_Table所要實現的目標。在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;
};
以方便存儲所要調試函數信息。這里需要需要注意的是,由于C#中struct不能像C中struct一樣直接定義一個固定長度的數組,所以直接用Function_ParameterX這樣的笨辦法來定義5個函數參數信息。
在class Get_Function_Address_And_Size_Table中最重要的就是void Get_Need_Function_Table()函數。其獲取.function文件中的函數列表并解析處該列表函數名稱、參數、返回類型等參量,并賦值給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_Number
String 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);
}
用以實現存儲全局變量的相關信息。
2.5 控制說明
2.5.1 命令字及其數據格式
函數發送命令字:
函數返回值命令字:
下位機接收超時命令字:
有人會疑惑STM32的地址只有4字節,為何在命令字中地址卻占用8字節?這要從不同類型數據轉換為byte說起。
將不同類型數據的函數參數轉換為byte的技巧就是使用聯合體。只要在聯合體中定義不同類型的變量與最大字長的char數組,就可以很容易的得到其在內存中的分布。在一開始函數參數轉換時,為了兼容double類型函數參數,在“聯合體”中定義了double,導致其長度為8字節。而函數地址轉換也使用了這一方法,所以發送命令字中地址長度也變為8字節。需要注意的是,在C#中沒有聯合體這一概念,所以只能使用struct并指定變量起始地址以實現C的聯合體:
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)得到相應變量的內存分布byte[8]
2.5.2 調試函數與全局變量的發送流程
按下函數調試發送按鈕之后,會觸發void SendFunctionButton_Click(object sender, EventArgs e)函數。在該函數中主要流程是判斷串口是否開啟->函數參數類型轉換->CRC校驗->超時判斷與重發。函數參數類型轉換主要由TypeUnion TypeTransfer(String type_s,String text_s)完成。該函數主要依據參數類型,將傳入的參數用 Convert.ToXXX(text_s, f_base)方法轉換為對應的數據,并直接賦值給TypeUnion,即一個聯合體變量,然后通過static byte[] StructToBytes(object structObj)得到內存分布byte[8]。
而CRC校驗則使用CRC16 CITT算法。在前49個字節填充完畢后,最后兩個字節先賦值為0,做一次CRC校驗,得到的數據再賦值給最后兩個字節。
2.5.3 函數返回值接收流程函數
在發送完函數調試命令后,上位機會自動等待直至接收到下位機發送的回復或到達設置的超時時間。利用static object BytesToStuct(byte[] bytes, Type type)將前8個字節轉換為TypeUnion變量。而CRC校驗則使用CRC16 CITT算法。在前8個字節填充完畢后做一次CRC校驗。如果校驗失敗則直接做一次超時處理,并在一定時間后重新發送函數調試命令。
2.5.4 超時與重傳處理
在實際的串口數據收發中,難免會遇到數據收發丟失或中斷。比如這次開發中使用虛擬串口收發數據就遇到數據丟失的情況:
明明監控數據都正確收發,但就是會漏數據,也不知怎么回事。沒辦法,只能做超時重發處理以應對這種情況。在上位機中,主要通過函數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讀取數據是否都收到
count_++;
if (count_ > timeout_set)
{
break;
}
}
if (count_ < timeout_set)? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? //未超時數據處理
{
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個字節都是0xFF,說明下位機未正確收到數據
{
if (byteArray[i] == 0xFF)
{
count++;
}
}
if (count >= PARAMENT_MAX_LENTH)
{
timeout = true;
}
else
{
if (function_send) //獲取函數返回值
{
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 //超過設置的超時時間,直接關閉串口并報錯
{
timeout = true;
SerialPort.Close();
ControlSerialButton.Text = "打開串口";
COMComboBox.Enabled = true;
BaudRateComboBox.Enabled = true;
ParityBitsComboBox.Enabled = true;
StopBitComboBox.Enabled = true;
DataBitsComboBox.Enabled = true;
MessageBox.Show("通訊超時!已關閉串口!");
}
return timeout;}
三、下位機的處理
3.1 接收處理
本來打算使用DMA+空閑中斷接收命令字,但考慮到有些低端的單片機沒有空閑中斷,同時實際使用中出現數據丟失會造成持續的等待,所以直接使用單字節中斷接收的方案。在接收到固定的字節后,標志位data_recived置一,并將數據拷貝出來。
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 超時處理
由于在實際的數據收發中,會出現數據丟失而造成上位機發送完畢但下位機并未全部接受,從而下位機一直處于等待的情況。為了解決這一情況,引入一個定時為200Hz的定時器。在進入接收中斷后,先關閉清空定時器,讀取接收的數據后再開啟定時。如果出現數據丟失而造成下位機等待的情況,則會引發定時中斷。在定時中斷內直接清空接收計數器,并給上位機發送超時指令。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //超時中斷,超時時間為5ms,一旦超時就發送8字節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 函數指針
通過周期調用void Recived_Command_Handle(void)來實現上位機發送的函數調試命令字。
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]; //獲取參數數量
for(i = 0;i < PARAMENT_MAX_LENTH;i++)? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?//獲取地址
{
addr.u_char[i] = r_data[1 + i];
}
for(i = 0;i < 5;i++) //獲取參數
{
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);
}
}
}
其中函數實現由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)含義為將取得的地址強制轉換為unsigned int;
3)(volatile unsigned int*)((unsigned int)&p)含義為將數字轉換為unsigned int類型的指針;
4)*(volatile unsigned int*)((unsigned int)&p)含義為取得該地址內的數據;
可這樣會造成一個問題,這就是對于double和long類型的變量,其在取值時會造成錯誤:
可以看到對于double類型,函數參數值只獲得了前4個字節的數據,后4個字節數據丟失了。嘗試定義
#definePARAMENT_TRANSFER(p)(*(volatileunsignedlonglong*)((unsignedint)&p))
可以正確獲得double參數,但char等類型則不能正確獲取:
所以暫時使用第一種PARAMENT_TRANSFER定義。
(*(unsigned int(*)())p)()則為執行函數,類似于回調函數。通過它可以執行指定的函數。
3.4 修改全局變量
通過void Set_Global_Data(unsigned int addr,unsigned char len,parameter_kind_union data)實現數據的寫入。而數組的寫入則是循環調用該函數,并加入測試重傳功能。
審核編輯 :李倩-
單片機
+關注
關注
6040文章
44602瀏覽量
637037 -
STM32
+關注
關注
2270文章
10918瀏覽量
356821 -
函數
+關注
關注
3文章
4344瀏覽量
62820
原文標題:自編一個單片機調試小工具,并談談其編程思路
文章出處:【微信號:mcu168,微信公眾號:硬件攻城獅】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論