在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

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)實現數據的寫入。而數組的寫入則是循環調用該函數,并加入測試重傳功能。

審核編輯 :李倩

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 單片機
    +關注

    關注

    6040

    文章

    44602

    瀏覽量

    637037
  • STM32
    +關注

    關注

    2270

    文章

    10918

    瀏覽量

    356821
  • 函數
    +關注

    關注

    3

    文章

    4344

    瀏覽量

    62820

原文標題:自編一個單片機調試小工具,并談談其編程思路

文章出處:【微信號:mcu168,微信公眾號:硬件攻城獅】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    單片機Debug工具性能對比 單片機調試常用命令

    單片機(Microcontroller Unit, MCU)調試是嵌入式開發中的重要環節,它幫助開發者發現和修復代碼中的錯誤,優化程序性能。不同的
    的頭像 發表于 12-19 09:56 ?386次閱讀

    單片機Debug與仿真區別

    單片機的開發是復雜的過程,涉及到硬件設計、軟件開發和測試等多個環節。為了確保單片機能夠按照預期工作,開發者需要使用Debug和仿真技術來檢測和修正代碼中的錯誤。 Debug(
    的頭像 發表于 12-19 09:47 ?238次閱讀

    單片機編程語言有哪些選擇

    單片機(Microcontroller Unit,MCU)編程是指為單片機編寫程序的過程,這些程序控制單片機的行為和功能。單片機廣泛應用于嵌
    的頭像 發表于 11-01 14:13 ?803次閱讀

    單片機調試常見問題與解決方法

    單片機調試是嵌入式系統開發中的重要環節,它涉及到對單片機程序的測試和優化,以確保系統能夠正常工作。在
    的頭像 發表于 11-01 14:11 ?1097次閱讀

    在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 ?720次閱讀

    keil可以讀出單片機的程序嗎

    表述存在定的誤解,因為Keil主要是用于編寫、編譯和調試單片機程序的工具,而不是直接從單片機中讀取已
    的頭像 發表于 09-02 10:32 ?1152次閱讀

    stm32單片機用什么軟件編程

    STM32單片機種廣泛應用于嵌入式系統領域的微控制器,具有高性能、低功耗、豐富的外設接口等特點。要對STM32單片機進行編程,需要選擇合適的軟件
    的頭像 發表于 09-02 10:16 ?1775次閱讀

    單片機圖形化編程軟件有哪些

    單片機圖形化編程軟件為開發者提供了種更加直觀、易于上手的編程方式,尤其適合初學者和教育領域。以下是些常見的
    的頭像 發表于 09-02 10:14 ?1523次閱讀

    暑假如何學習單片機

    暑假是學習和掌握單片機基礎知識的良好時機。以下是關于如何在暑假期間學習單片機的建議計劃
    的頭像 發表于 07-03 09:19 ?552次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>個</b>暑假如何學習<b class='flag-5'>單片機</b>

    cadence實用腳本工具分享,實現orcad原理圖快捷設計,減少重復性工作

    本文會教大家如何配置這樣的工具,并且分享我正在用的小工具
    的頭像 發表于 06-15 17:31 ?5886次閱讀
    cadence實用腳本<b class='flag-5'>工具</b>分享,實現orcad原理圖快捷設計,減少重復性工作

    原理圖設計OrCAD Capture 小工具:Parts操作小助手

    1小工具用途作為名硬件設計人員,在用Capture畫原理圖時,可曾想過,在操作Parts,不僅可以快速排列對齊,而且還能夠使用格式刷將Parts批量刷成目標格式,甚至還能夠快速對
    發表于 04-17 16:49

    fpga編程單片機編程的區別

    FPGA編程單片機編程的主要區別體現在以下幾個方面。
    的頭像 發表于 03-14 17:16 ?1064次閱讀

    應用單片機開發的ST LINK調試器設計制作

    調試ST單片機的過程中,ST-LINK是很好使用的調試工具。今天,我們就根據網絡上的設計方案進行簡化,設計制作
    發表于 03-06 10:26 ?1193次閱讀
    應用<b class='flag-5'>單片機</b>開發的ST LINK<b class='flag-5'>調試</b>器設計制作

    單片機編程和plc編程有什么區別

    編程的基本概念 單片機種在芯片上集成了處理器核心、內存、輸入輸出接口等功能的微控制器。單片機
    的頭像 發表于 02-22 10:23 ?2861次閱讀
    主站蜘蛛池模板: 国产精品女仆装在线播放| 又粗又大的机巴好爽欧美| 欧美精品专区55页| 欧美video free xxxxx| 性欧美大战久久久久久久| 亚洲成人毛片| 狠狠色噜噜综合社区| 丁香激情六月| 8050网| 黄网观看| 视频在线观看高清免费大全| 插吧插吧综合网| 国产视频三区| 黑人破乌克兰美女处| www在线小视频免费| 午夜精品久视频在线观看| 2017亚洲男人天堂| 成人免费看毛片| 成人欧美精品大91在线| 午夜看片在线观看| 国产高清一级视频在线观看| 69日本xxⅹxxxxx19| 国产精品欧美激情第一页| 国产性老妇女做爰在线| 色天使色护士 在线视频观看| 特级一级片| 毛片在线播| 久草视频一区| 美女毛片免费看| 最近免费| www射com| 中文天堂在线最新版在线www| aaaaa国产毛片| 日本在线一级| bt种子天堂| 插插插天天| 一级@片| 色骚综合| 国产成人永久免费视频| 色花堂国产精品首页第一页| h视频在线看|