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

发布者: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


f8f713e79ade37277ef7e5b56b0d54ed_wKgZomTm_kuAUowIAAMz2HnHGac391.png


f8f713e79ade37277ef7e5b56b0d54ed_wKgZomTm_kuAUowIAAMz2HnHGac391.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,说明下位机未正确收到数据

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

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

推荐阅读最新更新时间:2024-11-14 09:32

MSP430单片机理论复习知识点
纵观微处理器的发展,一是朝着具有复杂数据运算、高速通信、信息处理等功能的高性能计算机系统方向发展;二是产生了一种将中央处理器,存储器,I/O接口电路以及连接他们的总线都集成一块芯片上的计算机。单片机在设计上主要突出了控制功能,调整了接口配置,在单一芯片上制成了结构完整的计算机。 目前最常用的3中可编程处理器:微控制器(MCU)、微处理器(MPU)、数字信号处理器(DSP); 单片机可应用的领域:工业控制(工业机器人)、智能化仪器仪表(温度湿度的测量)、日常生活钟的电器产品(MP3)、计算机网络与通信(以太网)、计算机外部设备(微型打印机); 单片机的结构特点:时钟频率比通用MPU和DSP低;功耗低;字长一般为8-32位;内
[单片机]
单片机的定时器中断0
定时器工作的流程可以按照这个顺序(以51为例用定时器0方式一产生50毫秒的定时) 1、确定使用哪个定时器,使用哪种方式,这一步通过TMOD设置,TMOD的低四位是设置定时器0的,高四位是用来设置定时器1的,其中的M0,M1是用来设置定时器工作在哪种方式,GATE一般用不要设置,C/T是选择计数模式还是定时模式的,如:TMOD = 0X01,就说明定时器0工作在方式1。 2、接下来就要设置定时的时间,用定时器定时50毫秒,可以用这种方式TH0 = (65535 - 50000) / 256,TL0 = (65535 - 50000) % 256;可以这样理解:因为这是定时器的初值,也就是说计数脉冲就是在这个数的基础上向上递增,到达65
[单片机]
<font color='red'>单片机</font>的定时器中断0
51单片机汇编语言数字时钟
数字时钟proteus+ 51单片机+LCD1602+汇编语言+1602器件资料,适合做单片机课程结课作品 单片机汇编源程序如下: ;/******************************************************************************** ; LCD1602 时钟显示Cekong time测控何小双 ;********************************************************************************/ RS EQU P2.0; //控制端接口 R_W
[单片机]
51<font color='red'>单片机</font>汇编语言数字时钟
51单片机IO口模拟串口通讯C源程
51 IO口模拟串口通讯C源程 #include reg51.h sbit BT_SND =P1^0; sbit BT_REC =P1^1; #define MODE_QUICK #define F_TM F0 #define TIMER0_ENABLE TL0=TH0; TR0=1; #define TIMER0_DISABLE TR0=0; sbit ACC0= ACC^0; sbit ACC1= ACC^1; sbit ACC2= ACC^2; sbit ACC3= ACC^3; sbit ACC4= ACC^4; sbit ACC5= ACC^5; sbit ACC6= ACC^6; sbit ACC7= ACC^
[单片机]
单片机MCU内存分配
谈到内存,我们都会想到PC,对于单片机或者arm来说也是存在内存的,简单的理解是:内存嘛……就是存放东西的地方,只不过这个东西是数据而已,好了,还是把重点放在mcu上面,对于一款mcu来说,在性能描述的时候都会告诉sram,flash的容量大小,对于初学者来说,也不会去考虑和理会这些东西,拿到东西就只用。其实不然,这些量都是十分重要的,仔细想想,代码为什么可以运行,代码量是多少,定义的int、short等等类型的变量究竟是怎么分配和存储的,这些问题都和内寸有关系。 首先单片机的内存可以大小分为ram和rom,这里就不再解释ram和rom的区别了,我们可以将其等效为flash和sram,其中根据flash和sram的定义可得,
[单片机]
飞思卡尔推出面向软件工程师的汽车微控制器产品线S32K MCU
采用ARM Cortex 技术的新汽车架构实现了软件和硬件可扩展性,通过面向未来的特性和最佳的软件复用加快开发 2015年6月23日,德克萨斯州奥斯汀(2015年飞思卡尔技术论坛)讯-随着新汽车中芯片内容和复杂性的不断增加,未来的汽车发展对软件愈发依赖。新汽车通常集成了超过1亿行内置代码,比大多数民用客机的代码数量还要多。虽然软件带来了令人兴奋的创新机会,但同时也增加了复杂性,催生了大量的代码维护和上市速度挑战。因此,汽车电子供应商现在投入到软件领域的开发资源超过了在硬件方面的开发资源。 为了应对这些挑战,飞思卡尔半导体推出了首个旨在大大加快和简化软件开发的汽车微控制器产品线S32K。S32K基于广泛采用的
[单片机]
PIC单片机对清洁护理机的设计
引言 随着我国老龄化进程的加剧,当今社会中存在着一种因失去生活自理能力而“长期卧床的弱势群体”,特别是那些几乎无意识的弱势群体,他们需要被人长期照顾,特别是他们的大小便的清洁处理。然而由于该弱势群体数量大、护理人员紧缺和护理费用高等问题,导致这些弱势群体的家庭护理矛盾日益凸显。目前市场上的长期卧床病人大小便清洁护理机在“智能护理”方面己比较完善,已经具有大小便自动识别与回收、温水清洗与自动烘干等功能,达到了“人性化”护理的要求,但在使用过程中仍然需要专门的“陪护人员”,对于绝大多数现代家庭而言,无论从人力还是财力,这都将是一个沉重的负担,同时也限制了大小便清洁护理机在家庭中的推广使用,因此,社会迫切需要一种在现有护理功能的基础上能
[单片机]
PIC<font color='red'>单片机</font>对清洁护理机的设计
PIC单片机AD通道转换函数问题解析
  AD转换   D转换就是模数转换。顾名思义,就是把模拟信号转换成数字信号。主要包括积分型、逐次逼近型、并行比较型/串并行型、Σ-Δ调制型、电容阵列逐次比较型及压频变换型。   A/D转换器是用来通过一定的电路将模拟量转变为数字量。模拟量可以是电压、电流等电信号,也可以是压力、温度、湿度、位移、声音等非电信号。但在A/D转换前,输入到A/D转换器的输入信号必须经各种传感器把各种物理量转换成电压信号。   AD转换分类   1)积分型(如TLC7135)   积分型AD工作原理是将输入电压转换成时间(脉冲宽度信号)或频率(脉冲频率),然后由定时器/计数器获得数字值。其优点是用简单电路就能获得高分辨率, 但缺点是由于转换精度
[单片机]
PIC<font color='red'>单片机</font>AD通道转换函数问题解析
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

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

换一换 更多 相关热搜器件
更多往期活动
随便看看

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

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