1. Overview
When writing code for STM32 microcontrollers, we often encounter situations where a specific function or variable needs to be debugged repeatedly. The common method is to modify the source code and download it to the microcontroller for debugging. This repetitive process is not only cumbersome but also affects the FLASH memory of the microcontroller due to frequent reprogramming. Therefore, we consider developing a small tool that can achieve the following:1) Control the microcontroller to execute the desired function via serial port, supporting up to 5 parameters of types char, short, int, float, and their unsigned types, along with corresponding pointers. Long and double types are not supported.2) For functions that involve string and array operations, the variable address must be written when calling the function after passing the array value to manipulate these variables. The function return value will also be displayed.3) Support arbitrary modifications to global variables.4) Support switching between decimal and hexadecimal.5) Automatic retransmission or closure of the serial port in case of communication timeout.It is recommended to use this tool in conjunction with KEIL for better results.This software is written in C# and runs in the .NET 4.5 environment.Let’s first take a look at the results; if you are interested, you can continue reading:1. Host Debugging Settings2. Function Call
3. Writing Global Variables
4. Communication Timeout Handling
2. Host Processing2.1 PrincipleAfter compiling STM32 with KEIL, we will find a .map file in the same folder as the .hex file. This .map file contains information about the addresses, sizes, and optimizations of functions and global variables in the source code. Here is a simplified .map file for reference:
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 Size Object(Section)
../Core/Src/gpio.c 0x00000000 Number 0 gpio.o ABSOLUTE
../Core/Src/main.c 0x00000000 Number 0 main.o ABSOLUTE
../Core/Src/stm32f1xx_hal_msp.c 0x00000000 Number 0 stm32f1xx_hal_msp.o ABSOLUTE
../Core/Src/stm32f1xx_it.c 0x00000000 Number 0 stm32f1xx_it.o ABSOLUTE
../Core/Src/system_stm32f1xx.c 0x00000000 Number 0 system_stm32f1xx.o ABSOLUTE
../Core/Src/tim.c 0x00000000 Number 0 tim.o ABSOLUTE
../Core/Src/usart.c 0x00000000 Number 0 usart.o ABSOLUTE
../Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal.c 0x00000000 Number 0 stm32f1xx_hal.o ABSOLUTE
../Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_cortex.c 0x00000000 Number 0 stm32f1xx_hal_cortex.o ABSOLUTE
../Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_dma.c 0x00000000 Number 0 stm32f1xx_hal_dma.o ABSOLUTE
../Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_exti.c 0x00000000 Number 0 stm32f1xx_hal_exti.o ABSOLUTE
Global Symbols
Symbol Name Value Ov Type Size Object(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 0 anon$obj.o ABSOLUTE
__ARM_use_no_argv 0x00000000 Number 0 main.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 0 startup_stm32f103xe.o ABSOLUTE
__Vectors 0x08000000 Data 4 startup_stm32f103xe.o(RESET)
__Vectors_End 0x08000130 Data 0 startup_stm32f103xe.o(RESET)
__main 0x08000131 Thumb Code 8 __main.o(!!!main)
in 0x2000001c Data 4 main.o(.data)
uin 0x20000020 Data 4 main.o(.data)
uwTick 0x20000024 Data 4 stm32f1xx_hal.o(.data)
uwTickPrio 0x20000028 Data 4 stm32f1xx_hal.o(.data)
uwTickFreq 0x2000002c Data 1 stm32f1xx_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 RO Size (Code + RO Data) 11156 ( 10.89kB)
Total RW Size (RW Data + ZI Data) 35760 ( 34.92kB)
Total ROM Size (Code + RO Data + RW Data) 11688 ( 11.41kB)
By closely observing, we can find that the .map file mainly consists of the following parts:
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 Size Object(Section)
Global Symbols
Symbol Name Value Ov Type Size Object(Section)
==============================================================================
Memory Map of the image
==============================================================================
Image component sizes
==============================================================================
Code (inc. data) RO Data RW Data ZI Data Debug
==============================================================================
Total RO Size (Code + RO Data)
Total RW Size (RW Data + ZI Data)
Total ROM Size (Code + RO Data + RW Data)
Our main concern is the information such as the addresses and sizes of functions and global variables, which can be found in the .map file under Image Symbol Table -> Global Symbols. Knowing these addresses, we only need to send the addresses of the functions and variables of interest to the microcontroller, which can then execute the corresponding functions via pointers. The entire host is developed based on this principle. The specific process is illustrated in the following diagram:
2.2 Implementation of class Get_Map_Address_And_Size_Table — Extracting Addresses and Sizes of Functions and Global Variables from .map
The addresses and sizes of functions and global variables are located in the .map file under Image Symbol Table -> Global Symbols, consisting of Symbol Name, Value, Ov Type, Size, and Object(Section). Therefore, we first define a public struct Symbol to contain the above information:
public struct Symbol
{
public String Symbol_Name;
public uint Symbol_Address;
public SymbolType Symbol_Type;
public ushort Symbol_Size;
public String Symbol_Section;
};
Next, we will use FileStream to obtain information from the .map file, locate it to Image Symbol Table -> Global Symbols, and read Symbol Name, Value, Ov Type, Size, and Object(Section) to assign values to symbol_table:
public void Create_Address_And_Size_Table(String filename){
try
{
uint i;
FileStream file_read = new FileStream(filename, FileMode.Open, FileAccess.Read);//Create a new file stream
filelist = File.ReadAllLines(filename, Encoding.Default);//Read all lines of the file content into a string array.
for (i = 0; i <= filelist.Length - 1; i++) //Locate the interested position
{
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) //Get information
{
//Table_DeInit();
Get_Symbol_Data(i);
}
file_read.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Get_Symbol_Data(i); is responsible for assigning Symbol Name, Value, Ov Type, Size, and Object(Section) from Image Symbol Table -> Global Symbols to symbol_table. Two points need to be noted:
1) In Global Symbols,
xxxxxx - Undefined Weak Reference
does not contain useful information and needs to be excluded, which can be done using the Contains(“- Undefined Weak Reference”) method.
2) The Symbol Name, Value, Ov Type, Size, and Object(Section) in Image Symbol Table -> Global Symbols are separated by spaces, so we can use
Split(new Char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
to obtain the data set.
The function void Get_Symbol_Data(uint index) is as follows:
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")) //Exclude - Undefined Weak Reference
{
index++;
continue;
}
else
{
int str_index = 0;
string[] split_str = filelist[index].Split(new Char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);//Get data set
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;
}
}
The above is the main implementation method of class Get_Map_Address_And_Size_Table. With these two methods, we can obtain information about functions and global variables in the .map file.
2.3 Implementation of class Get_Function_Address_And_Size_Table — Obtaining the List of Required Functions
After obtaining the symbol_table containing information about functions and global variables, we need to get the list of functions we are interested in. In this host, the user needs to create a .function file. This file contains the list of functions that the user needs to debug. Generally, it is sufficient to directly copy the function declarations from the .h file. The host will then retrieve the function names, parameters, return types, and other parameters from this list, and finally query the function in symbol_table to obtain its address. This is the goal of class Get_Function_Address_And_Size_Table. In class Get_Function_Address_And_Size_Table, we first define:
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;
};
to conveniently store the information of the functions to be debugged. It is important to note that since structs in C# cannot directly define a fixed-length array like in C, we use the cumbersome method of defining five function parameter information as Function_ParameterX.
The most important function in class Get_Function_Address_And_Size_Table is void Get_Need_Function_Table(). This function retrieves the function list from the .function file and parses the function names, parameters, return types, and other parameters, assigning them to 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 = "";
}
}
After obtaining the function_table list, we only need to use
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);
}
to store the relevant information of global variables.
2.5 Control Instructions
2.5.1 Command Words and Their Data Formats
Function sends command word:
Function return value command word:
Lower machine receives timeout command word:
Some may wonder why the address of STM32 is only 4 bytes, but in the command word, the address occupies 8 bytes. This relates to converting different types of data into bytes.
The trick to converting function parameters of different types into bytes is to use a union. By defining different types of variables and a char array of maximum word length in the union, we can easily obtain their distribution in memory. Initially, to accommodate double type function parameters, a double was defined in the “union”, resulting in a length of 8 bytes. The same method was used for function address conversion, so the address length in the command word also became 8 bytes. It is important to note that C# does not have the concept of unions, so we can only use structs and specify the starting address of variables to achieve the effect of C’s union:
public struct TypeUnion
{
[FieldOffset(0)]
public byte uc;
[FieldOffset(0)]
public sbyte sc;
[FieldOffset(0)]
public ushort us;
[FieldOffset(0)]
public short ss;
[FieldOffset(0)]
public uint ui;
[FieldOffset(0)]
public uint pointer; //Pointer
[FieldOffset(0)]
public int si;
[FieldOffset(0)]
public float f;
[FieldOffset(0)]
public double d;
}
Since we cannot define char[8], we will also use static byte[] StructToBytes(object structObj) to obtain the corresponding variable’s memory distribution byte[8]
2.5.2 Sending Process of Debugging Functions and Global Variables
After pressing the function debugging send button, the function void SendFunctionButton_Click(object sender, EventArgs e) is triggered. The main process in this function is to check whether the serial port is open -> function parameter type conversion -> CRC check -> timeout judgment and retransmission. The function parameter type conversion is mainly completed by TypeUnion TypeTransfer(String type_s,String text_s). This function converts the incoming parameters into the corresponding data based on the parameter type using the Convert.ToXXX(text_s, f_base) method and directly assigns it to TypeUnion, which is a union variable, and then obtains the memory distribution byte[8] through static byte[] StructToBytes(object structObj).
The CRC check uses the CRC16 CITT algorithm. After filling the first 49 bytes, the last two bytes are initially set to 0, and a CRC check is performed to obtain the data, which is then assigned to the last two bytes.
2.5.3 Function Return Value Reception Process
After sending the function debugging command, the host will automatically wait until it receives a reply from the lower machine or reaches the set timeout. Using static object BytesToStuct(byte[] bytes, Type type), the first 8 bytes are converted into a TypeUnion variable. The CRC check also uses the CRC16 CITT algorithm. After filling the first 8 bytes, a CRC check is performed. If the check fails, a timeout handling is performed, and the function debugging command is resent after a certain time.
2.5.4 Timeout and Retransmission Handling
In actual serial data transmission and reception, it is inevitable to encounter data loss or interruption. For example, during this development, using a virtual serial port for data transmission and reception resulted in data loss:
Clearly, the monitored data was correctly transmitted and received, but data was still lost, and the reason is unknown. Therefore, we can only implement timeout retransmission handling to cope with this situation. In the host, this is mainly handled through the function bool Is_Timeout().
private bool Is_Timeout()
{
bool timeout = false;
ushort count_ = 0;
while (SerialPort.BytesToRead < RETURN_MAX_LENTH)
{
System.Threading.Thread.Sleep(1); //Check every 1ms if all data has been received
count_++;
if (count_ > timeout_set)
{
break;
}
}
if (count_ < timeout_set) //Data processing if not timed out
{
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++) //If all 8 bytes received are 0xFF, it indicates that the lower machine did not correctly receive the data
{
if (byteArray[i] == 0xFF)
{
count++;
}
}
if (count >= PARAMENT_MAX_LENTH)
{
timeout = true;
}
else
{
if (function_send) //Get function return value
{
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 //If the set timeout is exceeded, directly close the serial port and report an error
{
timeout = true;
SerialPort.Close();
ControlSerialButton.Text = "Open Serial Port";
COMComboBox.Enabled = true;
BaudRateComboBox.Enabled = true;
ParityBitsComboBox.Enabled = true;
StopBitComboBox.Enabled = true;
DataBitsComboBox.Enabled = true;
MessageBox.Show("Communication timeout! Serial port has been closed!");
}
return timeout;
}
3. Lower Machine Processing
3.1 Reception Handling
Initially, I planned to use DMA + idle interrupt to receive command words, but considering that some low-end microcontrollers do not have idle interrupts, and data loss during actual use can cause continuous waiting, I opted for a single-byte interrupt reception scheme. Upon receiving a fixed byte, the flag data_recived is set, and the data is copied out.
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //Receive interrupt{
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 Timeout Handling
Due to data loss during actual data transmission and reception, the host may finish sending while the lower machine has not fully received, causing the lower machine to remain in a waiting state. To resolve this, a timer with a frequency of 200Hz is introduced. Upon entering the receive interrupt, the timer is first stopped and cleared, and after reading the received data, it is restarted. If data loss occurs, causing the lower machine to wait, a timer interrupt will be triggered. In the timer interrupt, the receive counter is cleared, and a timeout command is sent to the host.
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //Timeout interrupt, timeout period is 5ms, once timed out, send 8 bytes of 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 Function Pointers
The function debugging command sent by the host is implemented through the periodic call of 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 check
if(crc == crc_16)
{
num = r_data[0]; //Get parameter count
for(i = 0;i < PARAMENT_MAX_LENTH;i++) //Get address
{
addr.u_char[i] = r_data[1 + i];
}
for(i = 0;i < 5;i++) //Get parameters
{
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) //Get return value
{
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);
}
}
}
The function implementation is completed by 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;
}
#define PARAMENT_TRANSFER(p) (*(volatile unsigned int*)((unsigned int)&p))
The operational meaning is as follows:
1) &p means to take the address of variable p;
2) (unsigned int)&p means to forcibly convert the obtained address to unsigned int;
3) (volatile unsigned int*)((unsigned int)&p) means to convert the number to a pointer of unsigned int type;
4) *(volatile unsigned int*)((unsigned int)&p) means to obtain the data within that address;
However, this may cause a problem, which is that for double and long type variables, obtaining values may lead to errors:
As can be seen, for the double type, the function parameter value only obtained the first 4 bytes of data, while the last 4 bytes of data were lost. Attempting to define
#define PARAMENT_TRANSFER(p) (*(volatile unsigned long long*)((unsigned int)&p))
can correctly obtain the double parameter, but char and other types cannot be correctly retrieved:
Therefore, we temporarily use the first definition of PARAMENT_TRANSFER.
(*(unsigned int(*)())p)() is used to execute the function, similar to a callback function. It allows us to execute the specified function.
3.4 Modifying Global Variables
Data writing is implemented through void Set_Global_Data(unsigned int addr,unsigned char len,parameter_kind_union data). For arrays, this function is called in a loop, and a test retransmission feature is added.
Declaration:This article is authorized by the authorand reprinted from“21ic Electronics Network“, copyright belongs to the author. If there is any infringement, please contact us for deletion!
❤「If useful, please share」❤
👇Click Follow, Technologyto receive timely technical content!👇
-
What is the relationship between Fourier Transform, Laplace Transform, and Z Transform? Why perform these transformations?
-
Those microcontrollers that were once very popular~
-
Can you imagine a Bluetooth chip that costs less than 2 yuan?
-
Why do domestic chips also use English to write “datasheet”?
-
Wonderful operational amplifier circuits
-
Why is it 50 ohms???
-
The most detailed basics of diodes
-
What is a BSP engineer?
Like, share, good articles need your support and encouragement<img src=”https://mmbiz.qpic.cn/mmbiz_gif/iapXlcghssnXZFQ8rtknqDZ2w6yJ1DP3smnzf8GWGRoZNI4XEqfqU011YKSMkfDKmOFNf9kHBTrjBSY3EH1aWMQ/640?wx_fmt=gif&wxfrom=5&wx_lazy=1″