介绍一个单片机小工具的编程思路

嵌入式大杂烩 2022-04-26 21:26


出品 21ic论坛 纪国圣

网站:bbs.21ic.com


一、概述


在平时编写STM32单片机代码时,我们经常会遇到某一个函数或某一个变量需要反复调试的情况,而常用的方法只能是在源码修改并下载至单片机调试。反复这样不仅麻烦,而且反复烧写单片机对其FLASH也有影响,因此就考虑编写一款小工具,可以实现:

1)通过串口控制单片机执行我们期望的函数,同时函数参数最大支持5个,其参数类型支持char、short、int、float及其无符号类型和相应的指针,不支持long及double。
2)对于含有对字符串及数组操作的函数,需要通过数组传值后,在调用函数时写入该变量地址才能实现对这些变量的操作。支持函数返回值得显示。
3)支持对全局变量进行任意的修改。
4)支持十进制与十六进制切换.
5)通讯超时自动重传或关闭串口。
建议配合KEIL一起使用,效果更好。
本软件使用C#编写,运行环境为NET 4.5。
先让大家看看效果,感兴趣的话可以继续往下看:

1.上位机调试设置


2.函数调用


3.全局变量的写入


4.通讯超时处理


二、上位机的处理

2.1 原理

在使用keil编译STM32后,我们会在.hex文件的同一个文件夹中发现一个.map文件。这个.map文件包含了源码中函数与全局变量的地址、大小、优化等信息。这里贴一个简化的.map文件给大家看一下:

Component: ARM Compiler 5.06 update 6 (build 750) Tool: armlink [4d35ed]


==============================================================================


Section Cross References


    startup_stm32f103xe.o(STACK) refers (Special) to heapauxi.o(.text) for __use_two_region_memory
    startup_stm32f103xe.o(HEAP) refers (Special) to heapauxi.o(.text) for __use_two_region_memory
    startup_stm32f103xe.o(RESET) refers (Special) to heapauxi.o(.text) for __use_two_region_memory
    startup_stm32f103xe.o(RESET) refers to startup_stm32f103xe.o(STACK) for __initial_sp




==============================================================================


Removing Unused input sections from the image.


    Removing main.o(.rev16_text), (4 bytes).
    Removing main.o(.revsh_text), (4 bytes).
    Removing main.o(.rrx_text), (6 bytes).
    Removing gpio.o(.rev16_text), (4 bytes).
    Removing gpio.o(.revsh_text), (4 bytes).


384 unused section(s) (total 34104 bytes) removed from the image.


==============================================================================


Image Symbol Table


    Local Symbols


    Symbol Name                              Value     Ov Type        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)

仔细观察可以发现.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        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)      


而我们最关注的信息如函数和全局变量的地址与大小都在.map文件中的Image Symbol Table->Global Symbols。知道了这些地址,我们只需将其感兴趣的函数与变量地址发送给单片机,单片机通过指针就可以执行相应的函数了。整个上位机正是基于这个原理而编写的。具体流程如下图所示:



2.2 class Get_Map_Address_And_Size_Table的实现——————.map中函数和全局变量的地址与大小等信息提取


函数和全局变量的地址与大小都在.map文件中的Image Symbol Table->Global Symbols,由Symbol Name、Value、Ov Type、Size、Object(Section)组成,所以先定义一个public struct Symbol来包含上述信息:


public struct Symbol{   public String Symbol_Name;   public uint Symbol_Address;   public SymbolType Symbol_Type;   public ushort Symbol_Size;   public String Symbol_Section; };



接下来就是通过FileStream获取.map文件中的信息,并定位至Image Symbol Table->Global Symbols,读取Symbol Name、Value、Ov Type、Size、Object(Section)并赋值给symbol_table:


public void Create_Address_And_Size_Table(String filename){    try    {
        uint i;


        FileStream file_read = new FileStream(filename, FileMode.Open, FileAccess.Read);//新建文件流


        filelist = File.ReadAllLines(filename, Encoding.Default);//读取文件内容所有行保存到字符串数组中。


        for (i = 0; i <= filelist.Length - 1; i++)              //定位到感兴趣的位置
        {
             if (filelist[i].Contains("Global Symbols"))
            {
               break;
            }
         }


        for (uint j = i; j <= filelist.Length - 1; j++)
       {
          if (filelist[j].Contains("Object(Section)"))
         {
            i = j + 1;
            break;
         }
       }


        if (i < filelist.Length - 1)                                //获取信息
        {
            //Table_DeInit();
            Get_Symbol_Data(i);
         }


         file_read.Close();


    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
     }
}


Get_Symbol_Data(i);就是负责将Image Symbol Table->Global Symbols中的Symbol Name、Value、Ov Type、Size、Object(Section)赋值给symbol_table。有两点需要说明一下:

1)由于在Global Symbols中,


xxxxxx - Undefined Weak Reference


不包含有用信息,是需要被排除的,可以通过Contains("- Undefined Weak Reference")方法将其排除。

2)Image Symbol Table->Global Symbols中的Symbol Name、Value、Ov Type、Size、Object(Section)是通过空格将数据进行分割,所以可以通过


Split(new Char[] { ' ' }, 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 超时与重传处理


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





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


#define PARAMENT_TRANSFER(p)        (*(volatile unsigned int*)((unsigned int)&p))  


其操作含义如下:


1)&p含义为取变量p地址;


2)(unsigned int)&p)含义为将取得的地址强制转换为unsigned int;


3)(volatile unsigned int*)((unsigned int)&p)含义为将数字转换为unsigned int类型的指针;


4)*(volatile unsigned int*)((unsigned int)&p)含义为取得该地址内的数据;

可这样会造成一个问题,这就是对于double和long类型的变量,其在取值时会造成错误:





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


#define PARAMENT_TRANSFER(p)        (*(volatile unsigned long long*)((unsigned int)&p))


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




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


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


3.4 修改全局变量


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


本文系21ic论坛网友纪国圣原创,资料下载请点击“阅读原文”。

版权归原作者所有,如有侵权,请联系删除。

猜你喜欢:

分享嵌入式软件调试方法及几个有用的工具!

分享一份嵌入式软件工具清单!

一门易用性极强的动态语言!

一位嵌入式前辈 8 年的工作总结,强!

分享嵌入式中几个实用的shell脚本!

嵌入式大杂烩周记 | 第 8 期 AMetal

嵌入式大杂烩周记 | 第 7 期 zlog

嵌入式大杂烩周记 | 第 6 期 FlexibleButton

嵌入式大杂烩周记 | 第 5 期 smartlink

在公众号聊天界面回复1024,可获取嵌入式资源;回复 ,可查看文章汇总。

嵌入式大杂烩 专注于嵌入式技术,包括但不限于C/C++、嵌入式、物联网、Linux等编程学习笔记,同时,内包含大量的学习资源。欢迎关注,一同交流学习,共同进步!
评论 (0)
  • 深圳触觉智能SOM3506核心板现已上市,搭载瑞芯微RK3506B/J超低功耗处理器(1.5GHz三核A7+M0),低功耗满载仅0.7W,支持40℃~85℃工作环境,即日起宽温级59元/工业级68元,特价开售!芯片介绍RK3506是瑞芯微Rockchip在2024年第四季度全新推出的Arm嵌入式芯片平台,三核Cortex-A7+单核Cortex-M0多核异构设计,CPU频率达1.5Ghz, M0 MCU为200Mhz。RK3506适用场景简介工业控制‌:RK3506适用于工业控制、工业通信、人机
    Industio_触觉智能 2025-03-07 10:03 165浏览
  • 一、系统概述MYD-LD25X搭载的Debian系统包含以太网、WIFI/BT、USB、RS485、RS232、CAN、AUDIO、HDMI显示和摄像头等功能,同时也集成了XFCE轻量化桌面、VNC远程操控、SWITCH网络交换和TSN时间敏感网络功能,为工业设备赋予“超强算力+实时响应+极简运维”的体验!类别名称描述源码TF-AArm Trusted Firmware 2.8OP-TEEOP-TEE 3.19BootloaderU-boot 2022.10KernelLinux Ke
    米尔电子嵌入式 2025-03-07 14:08 250浏览
  • 在企业管理和职场环境中,权力是一个常被提及却又让人感到微妙的话题。有人觉得它充满吸引力,有人却对它避之不及。然而,不管你对权力的态度如何,理解它、掌握它,甚至善用它,都是职场成功的重要一环。今天,我们就来深入探讨权力的本质,特别是个人权力和社会权力的区别,以及如何在职场中逐步建立属于自己的影响力。权力的两种面貌:你掌控自己,还是掌控他人?说到权力,首先要区分它的两种类型。个人权力是你对自己生活的掌控感。比如,你能自由决定自己的职业方向,不用总是请示他人。这种权力让人感到踏实和满足,是我们在生活中
    优思学院 2025-03-07 15:56 216浏览
  • 多人同时共享相同无线网络,以下场景是否是您熟悉的日常?姐姐:「妈~我在房间在线上课,影音一直断断续续的怎么上课啊!」奶奶:「媳妇啊~我在在线追剧,影片一直卡卡的,实在让人生气!」除此之外,同时间有老公在跟客户开在线会议,还有弟弟在玩在线游戏,而妈妈自己其实也在客厅追剧,同时间加总起来,共有五个人同时使用这个网络!我们不论是在家里、咖啡厅、餐厅、商场或是公司,都会面临到周遭充斥着非常多的无线路由器(AP),若同时间每位使用者透过手机、平板或是笔电连接到相同的一个网络,可想而知网络上的壅塞及相互干扰
    百佳泰测试实验室 2025-03-06 16:50 162浏览
  • Sub-GHz,即工作频段低于1GHz的无线通信技术,常见频段有315MHz、433MHz、868MHz与915MHz等。其可借助无线电波在自由空间传播的特性,把数据调制到射频载波上进行传输,达成物联网设备间的无线通信,是物联网设备实现高效、稳定、无缝交互的“通信基石”。典型射频信号(无线电波)收发电路简示在工业自动化、智慧城市、智慧农业与智能家居等物联网领域中,LoRa、Wi-SUN、Z-Wave、Sigfox等工业级通信协议大多运行在Sub-GHz频段。而正是通过Sub-GHz射频技术,传感
    华普微HOPERF 2025-03-07 11:39 167浏览
  •        深夜的公园里,当路灯熄灭后,传统监控摄像头只能拍出模糊的黑白画面,仿佛老式胶片电影里的场景。而搭载为旌瑶光ISP的摄像头,却能像猫科动物一样,在几乎全黑的环境中捕捉到行人衣服的颜色、树叶的纹理,甚至快速跑动的宠物狗毛发细节。这种从“黑白默片”到“全彩4K电影”的跨越,背后是为旌瑶光ISP对传统红外补光技术的颠覆性创新。一、传统方案之困:被红外光“绑架”的夜视世界        传统安防摄像头依赖红外
    中科领创 2025-03-07 16:50 335浏览
  • ASL6328芯片支持高达 6.0 Gbps 运行速率的交流和直流耦合输入T-MDS 信号,具备可编程均衡和抖动清理功能。ASL6328 是一款单端口 HDMI/DVI 电平转换 / 中继器,具有重新定时功能。它包含 TypeC双模式 DP 线缆适配器寄存器,可用于识别线缆适配器的性能。抖动清理 PLL(锁相环)能够消除输入抖动,并完全重置系统抖动容限,因此能更好地满足更高数据速率下 HDMI 抖动合规性要求。设备的运行和配置可通过引脚设置或 I2C 总线实现。自动断电和静噪功能提供了灵活的电
    QQ1540182856 2025-03-06 14:26 137浏览
  • 文/Leon编辑/cc孙聪颖2025年全国两会进行时,作为“十四五”规划收官之年,本届两会释放出坚定目标、稳中求进、以进促稳等信号。其中,企业家们的建议备受关注,关系到民营经济在2025年的走向。作为国内科技制造业的“老兵”,全国人大代表、TCL集团创始人及董事长李东生在本届两会中提出三份代表建议,包括《关于优化中国科技制造业融资环境的建议》、《关于加强AI深度伪造欺诈管理的建议》和《关于降低灵活就业人员社会保险参保门槛的建议》,表现出对科技制造、AI发展和劳动者保障方面的关注。会后,李东生接受
    华尔街科技眼 2025-03-06 19:41 149浏览
  • 深圳触觉智能RK3506开发板现已上市,开启预售!搭载瑞芯微RK3506B/J超低功耗工业处理器(1.5GHz三核A7+M0,主频1.5GHz);支持1280×1280显示、双百兆网口、星闪无线三模,板载高达2路CAN FD与5路串口。RK3506适用场景简介工业控制‌:RK3506适用于工业控制、工业通信、人机交互等应用场景。其多核异构架构(3xCortex-A7+Cortex-M0)和外设接口丰富,支持Buildroot、Yocto系统,适合轻量级HMI应用‌。‌工业通信‌:RK3506均支
    Industio_触觉智能 2025-03-07 10:04 136浏览
  • ​CS6212是一款可分别用于USB Type-C主机/显示端口源应用的带重定时的有源开关。这设备符合USB 3.2标准版本1.0和USB Type-C标准上的VESA DisplayPort Alt模式 1.0版,支持通过GPIO或12C进行灵活的模式切换。此设备支持USB 3.2第2x1代 运行速度高达10Gbps,DisplayPort 1.4运行速度高达HBR3 8.1Gbps。CS6212管脚分布及功能定义:CS6212支持重定时器训练,并支持USB 3.2标准中定义的状态状态机(RT
    QQ1540182856 2025-03-07 10:09 166浏览
  • 服务器应用环境与客户需求PCIe 5.0高速接口技术的成熟驱动着生成式AI与高效能运算等相关应用蓬勃发展。在随着企业对服务器性能的要求日益严苛,服务器更新换代的周期也持续加快。在此背景下,白牌与DIY(Do It Yourself)服务器市场迎来了新的发展契机,但同时也面临着更趋复杂的技术挑战。传统上,白牌与DIY服务器以其高度客制化与成本效益优势受到市场青睐。然而,随着PCIe 5.0等高速技术的导入,服务器系统的复杂度大幅提升,对组装技术与组件兼容性也就提出更高的要求。举个简单的例子来说,P
    百佳泰测试实验室 2025-03-06 17:00 169浏览
  • 近年来,越来越多的企业在5S管理的基础上,开始追求6S、7S甚至8S管理,仿佛S越多,管理就越先进,企业就越优秀。于是,6S增加了“安全”,7S又加上了“节约”,8S甚至引入了“学习”……看似更加全面,实则很多企业只是机械地增加S,却忽略了管理的核心目标:提升效率、降低浪费、优化工作环境。优思学院认为,5S本身已经是一套成熟的精益管理工具,它的核心理念不仅简单高效,而且易于实施和推广。如果企业只是为了赶时髦,盲目增加S,而没有真正理解5S的本质,那么这些额外的“S”很可能会变成管理上的负担,而不
    优思学院 2025-03-07 12:43 207浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦