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

发布者:innovator7最新更新时间:2024-03-20 来源: elecfans关键字:单片机  编程思路 手机看文章 扫描二维码
随时随地手机看文章


     }


    }


  }


 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 超时与重传处理


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


afb6a3a75404e826397a8b0d0a0f5a37_wKgZomTm_kuAX5ShAAM_YDAs76s809.png?imageView2/2/w/550


f8f713e79ade37277ef7e5b56b0d54ed_wKgZomTm_kuAUowIAAMz2HnHGac391.png?imageView2/2/w/550


f8f713e79ade37277ef7e5b56b0d54ed_wKgZomTm_kuAUowIAAMz2HnHGac391.png?imageView2/2/w/550


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

[1] [2] [3] [4] [5]
关键字:单片机  编程思路 引用地址:一个单片机调试小工具的编程思路

上一篇:MMC中断的特点及解决方案
下一篇:SAR ADC内部结构

推荐阅读最新更新时间:2026-02-18 10:08

看完就懂!单片机编程入门之基本思路和写法
学习单片机最主要的是学习写程序的方法,程序的功能千变万化,是学不完的,只有掌握了一定方法,才能用这种方法去写新的程序。 以c语言写的单片机程序为例,程序总是从main程序开始,然后顺序执行到main结束。由此可知,程序必须包含而且只能包含一个main程序,也就是常说的主程序。 main() { 主程序的内容。。。。。。 } 实际使用中还需要在main程序中建立一个主循环体while 或者do while,主循环体可以是死循环,也可以是条件循环,如下: main()2 I- l5 q. e, B t4 i) ~9 H: U { r h7 F) J5 G$ C5 C while(1)
[单片机]
高手谈谈单片机编程思路
我曾经做过两年的单片机产品,在对单片机编程的过程中逐渐形成了一个大体固定的整体框架,也可以说是编程思路。现提出来供大家参考。 首先,对外围芯片进行分类,属同一功能或同一芯片的程序采用模块化的形式,用固定的几个函数实现,一般不同芯片之间的函数功能尽量不重叠,也不要使用一个函数覆盖几个芯片,以便于后期的调试,如果涉及到芯片之间通讯的,可以单独列出,使用专门的函数进行处理; 其次,将单片机中需要完成的工作按轻重缓急进行分类,一般我会设置两个定时中断,其中一个定时时间大致在20mS左右,用来处理一些按钮防抖处理、时间日期计算、对输入处理后产生对应标志位、器件刷新等一些实时性要求不是很高,但必须有时间要求的事物处理,另外设立一个定时
[单片机]
STM32CubeIDE所支持的几个调试小工具及功能
意法半导体ST公司为广大STM32用户免费提供了基于GCC的功能强大集成调试工具STM32CubeIDE。这里简单演示下STM32CubeIDE所支持的几个调试小工具及功能。 1、利用Live Expression 实时显示变量数据; 2、利用SWV的SWO功能实现printf打印输出; 3、利用SWV实现数据实时跟踪动态图形显示; 4、利用CubeIDE集成的串口终端软件实现printf输出; 要实现liveexpression,首先要在debugger配置中使能该功能,如下图中1处所示。若要使用SWV功能,也需在debugger配置中使能相关选项,如下图中2处所示。 要实现SWO输出,基于CubeMx进行配置时,要做如下操
[单片机]
STM32CubeIDE所支持的几个<font color='red'>调试</font><font color='red'>小工具</font>及功能
如何学好PLC编程思路和办法
今天,小编为大家收集了一些关于如何学好PLC编程的思路和办法,希望大家收下这波安利后,能对PLC编程有个大概的学习思路,自己独立应用PLC完成编程。 1、基本的硬件知识 编程之前,需要了解一些基本的硬件知识,最好从硬件的选型和画图入手,等把输入输出的类型,模拟量的选型等搞清楚之后,再开始编程会简单点。熟悉基本的硬件电路,你就会发现原来梯形图和这些硬件电路是可以很好对应起来的。 2、了解PLC编程的方式 线性编程、模块化编程、结构化编程。对于西门子plc,以结构化编程为主,但可以使用线性编程和模块化编程,对于结构化编程,需要有一定的结构化编程思想。 如果你想比较快掌握西门子PLC,建议首先学习线性编程或模块化编程。在学习过程中慢
[嵌入式]
STM32库函数编程思路总结及其与寄存器编程的对比剖析
一、STM32库函数编程思路总结 1、基于STM32库函数的开发过程 进行具体的项目开发前,做好项目创建工作,通常包括如下步骤: (1)新建工程项目的文件夹和子文件夹(如user、output、listing等) (2)使用MDK新建(或打开项目),选择目标CPU、添加CMSIS核心、STM32启动代码和外设驱动程序,构成运行环境。 (3)添加包含main()函数的主程序文件。 (4)配置目标选项。 这些项目创建的步骤是通用的,项目构建确认无误后,可以复制整个项目文件夹的内容并保存,再次创建项目时可以直接应用(仅需适当改变项目名称等)。 通过以下步骤对项目本身流程进行分析,以明确并掌握相关外设的ST
[单片机]
STM32库函数<font color='red'>编程</font><font color='red'>思路</font>总结及其与寄存器<font color='red'>编程</font>的对比剖析
STM32寄存器编程思路 - 从51到stm32开发入门,真干货
本文转自 https://www.amobbs.com/thread-5462507-1-3.html 第23楼 尊重原作不做任何修改 =============以下正文=============== 本来只是路过,写详细一点。 我看楼主浮躁得不得了。现在什么都不要做了,先去看几遍《不要做浮躁的嵌入式工程师》这篇文章,想清楚了, 再动手吧。 我做了个实例,不用ST的库来点LED,解答你的问题 我的 KeilMDK 3.5 我的STM32板子奋斗版是 ,IC 是 STM32F103VET6 调试工具 JLINK V8 LED 接在 PB5 ,高电平点亮 既然楼主说一定懂C语言了,那么对于下面我的问题,不查百度,完全靠自己,懂多少?
[单片机]
纳芯微通过港交所聆讯,“MCU+集成方案”抢占车规芯片国产替代赛道
在这一波通往港股的汽车和汽车零部件相关的公司中,纳芯微作为汽车芯片的代表,通过港交所聆讯的消息并不令人意外。这家模拟芯片细分赛道的芯片企业,不断强化在汽车电子和高压信号链市场上的长期布局。 三大品类开始拓展到解决方案,可以说圈的范围是很大的。 ◎ 传感器产品:包括磁传感器、热电堆红外传感器等,汽车电机驱动、体温监测等场景。 ◎ 信号链芯片:如数字隔离芯片,采用电容隔离技术,新能源汽车电池管理系统等高压场景。 ◎ 电源管理芯片: LDO、电压监控、LED 驱动等,汽车尾灯、前灯、车身控制器等汽车电子场景 。 Part 1 营收的波动 纳芯微从2022年、2023年、2024年到2025年
[汽车电子]
纳芯微通过港交所聆讯,“<font color='red'>MCU</font>+集成方案”抢占车规芯片国产替代赛道
意法半导体推出业界首款18nm高性能微控制器
STM32V8 是首款采用新一代 18nm FD-SOI 工艺设计的微控制器,集成先进的嵌入式相变存储器 (PCM) 被SpaceX星链卫星网络高速连接系统采用 适合工厂自动化、电机控制和机器人等要求严苛的工业应用 2025年11月19日,中国– 服务多重电子应用领域、全球排名前列的半导体公司意法半导体 (STMicroelectronics,简称ST) 发布了新一代高性能微控制器(MCU)STM32V8。为要求严苛的工业应用专门设计,STM32V8在意法半导体位于法国克罗勒300毫米晶圆厂生产,采用意法半导体18纳米先进工艺制造,并集成优异的嵌入式相变存储器 (PCM),,同时该系列产品还在三星晶圆代工厂生产。STM
[单片机]
意法半导体推出业界首款18nm高性能<font color='red'>微控制器</font>
小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2026 EEWORLD.com.cn, Inc. All rights reserved