0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

一个单片机调试小工具的编程思路

GReq_mcu168 来源:21ic论坛 作者:纪国圣 2022-05-16 14:35 次阅读

一、概述

在平时编写STM32单片机代码时,我们经常会遇到某一个函数或某一个变量需要反复调试的情况,而常用的方法只能是在源码修改并下载至单片机调试。反复这样不仅麻烦,而且反复烧写单片机对其FLASH也有影响,因此就考虑编写一款小工具,可以实现: 1)通过串口控制单片机执行我们期望的函数,同时函数参数最大支持5个,其参数类型支持char、short、int、float及其无符号类型和相应的指针,不支持long及double。2)对于含有对字符串及数组操作的函数,需要通过数组传值后,在调用函数时写入该变量地址才能实现对这些变量的操作。支持函数返回值得显示。3)支持对全局变量进行任意的修改。4)支持十进制与十六进制切换.5)通讯超时自动重传或关闭串口。建议配合KEIL一起使用,效果更好。本软件使用C#编写,运行环境为NET 4.5。先让大家看看效果,感兴趣的话可以继续往下看: 1.上位机调试设置 e3db53a4-d4df-11ec-bce3-dac502259ad0.png  2.函数调用 e3fd9432-d4df-11ec-bce3-dac502259ad0.png  3.全局变量的写入 e4106620-d4df-11ec-bce3-dac502259ad0.png  4.通讯超时处理 e431e50c-d4df-11ec-bce3-dac502259ad0.png  二、上位机的处理 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。知道了这些地址,我们只需将其感兴趣的函数与变量地址发送给单片机,单片机通过指针就可以执行相应的函数了。整个上位机正是基于这个原理而编写的。具体流程如下图所示:

e44a2ea0-d4df-11ec-bce3-dac502259ad0.png

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
{
 [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;              //指针
 [FieldOffset(0)]
 public int si;
 [FieldOffset(0)]
 public float f;
 [FieldOffset(0)]
 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 超时与重传处理

在实际的串口数据收发中,难免会遇到数据收发丢失或中断。比如这次开发中使用虚拟串口收发数据就遇到数据丢失的情况:

e494a552-d4df-11ec-bce3-dac502259ad0.png

e4b16192-d4df-11ec-bce3-dac502259ad0.png

e4e1e2ea-d4df-11ec-bce3-dac502259ad0.png

明明监控数据都正确收发,但就是会漏数据,也不知怎么回事。没办法,只能做超时重发处理以应对这种情况。在上位机中,主要通过函数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类型的变量,其在取值时会造成错误:

e5200ef8-d4df-11ec-bce3-dac502259ad0.png

e55e9204-d4df-11ec-bce3-dac502259ad0.png

可以看到对于double类型,函数参数值只获得了前4个字节的数据,后4个字节数据丢失了。尝试定义

#definePARAMENT_TRANSFER(p)(*(volatileunsignedlonglong*)((unsignedint)&p))

可以正确获得double参数,但char等类型则不能正确获取:

e579def6-d4df-11ec-bce3-dac502259ad0.png

e5e2f9f4-d4df-11ec-bce3-dac502259ad0.png

所以暂时使用第一种PARAMENT_TRANSFER定义。

(*(unsigned int(*)())p)()则为执行函数,类似于回调函数。通过它可以执行指定的函数。

3.4 修改全局变量

通过void Set_Global_Data(unsigned int addr,unsigned char len,parameter_kind_union data)实现数据的写入。而数组的写入则是循环调用该函数,并加入测试重传功能。

审核编辑 :李倩

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 单片机
    +关注

    关注

    6032

    文章

    44516

    浏览量

    633031
  • STM32
    +关注

    关注

    2266

    文章

    10871

    浏览量

    354807
  • 函数
    +关注

    关注

    3

    文章

    4306

    浏览量

    62430

原文标题:自编一个单片机调试小工具,并谈谈其编程思路

文章出处:【微信号:mcu168,微信公众号:硬件攻城狮】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    单片机编程语言有哪些选择

    单片机(Microcontroller Unit,MCU)编程是指为单片机编写程序的过程,这些程序控制单片机的行为和功能。单片机广泛应用于嵌
    的头像 发表于 11-01 14:13 336次阅读

    在DRA7xx器件上使用CONFIG-FS的USB复合小工具

    电子发烧友网站提供《在DRA7xx器件上使用CONFIG-FS的USB复合小工具.pdf》资料免费下载
    发表于 10-10 09:26 0次下载
    在DRA7xx器件上使用CONFIG-FS的USB复合<b class='flag-5'>小工具</b>

    单片机基本io功能调试过程

    单片机基本IO功能的调试过程涉及多个步骤,旨在确保IO口能够正确地执行输入和输出操作。以下是调试过程,涵盖了从准备阶段到实际测试的关键步
    的头像 发表于 09-14 14:38 437次阅读

    keil可以读出单片机的程序吗

    表述存在定的误解,因为Keil主要是用于编写、编译和调试单片机程序的工具,而不是直接从单片机中读取已
    的头像 发表于 09-02 10:32 706次阅读

    stm32单片机用什么软件编程

    STM32单片机种广泛应用于嵌入式系统领域的微控制器,具有高性能、低功耗、丰富的外设接口等特点。要对STM32单片机进行编程,需要选择合适的软件
    的头像 发表于 09-02 10:16 997次阅读

    原理图设计OrCAD Capture 小工具:Parts操作小助手

    1小工具用途作为名硬件设计人员,在用Capture画原理图时,可曾想过,在操作Parts,不仅可以快速排列对齐,而且还能够使用格式刷将Parts批量刷成目标格式,甚至还能够快速对
    发表于 04-17 16:49

    fpga编程单片机编程的区别

    FPGA编程单片机编程的主要区别体现在以下几个方面。
    的头像 发表于 03-14 17:16 928次阅读

    应用单片机开发的ST LINK调试器设计制作

    调试ST单片机的过程中,ST-LINK是很好使用的调试工具。今天,我们就根据网络上的设计方案进行简化,设计制作
    发表于 03-06 10:26 1028次阅读
    应用<b class='flag-5'>单片机</b>开发的ST LINK<b class='flag-5'>调试</b>器设计制作

    单片机编程和plc编程有什么区别

    编程的基本概念 单片机种在芯片上集成了处理器核心、内存、输入输出接口等功能的微控制器。单片机
    的头像 发表于 02-22 10:23 2520次阅读

    单片机编程实例介绍

    是指使用特定的编程语言编写控制单片机工作的程序。 在本文中,将为您提供些常见的单片机编程实例,包括基础应用、传感器应用、通信应用等方面的案
    的头像 发表于 01-16 09:37 2555次阅读

    单片机编程实例总结

    单片机编程实例总结
    的头像 发表于 01-16 09:17 984次阅读

    单片机程序读取需要什么工具

    单片机程序读取的工具可以分为硬件工具和软件工具两类。下面将详细介绍这些工具的种类、功能和使用方法。
    的头像 发表于 01-02 18:10 3653次阅读

    pic单片机编程序实例

    以下是简单的PIC单片机编程序示例,这个程序将实现LED闪烁的功能。 首先,我们需要了
    的头像 发表于 12-14 11:01 1783次阅读

    STM32单片机般有几种调试方式

    JTAG调试*:这是种常用的调试工具,可以用于单步执行、断点调试等操作。在STM32单片机中,可以使用ST-Link、STMicroele
    的头像 发表于 12-13 10:31 3049次阅读

    单片机程序调试常见问题 STM32单片机般有几种调试方式

    SWD是STM32单片机常用的调试模式之。它通过单调试引脚(SWDIO)和时钟引脚(SWCLK)来进行
    的头像 发表于 12-02 17:14 3338次阅读