发帖数

53

原创数

53

关注者

11

阅读数

10867

点赞数

1

黄忠

  • 程序的优化技巧

    大家好,我是张飞实战电子黄忠老师,今天我们来讨论下程序的优化技巧!

    在嵌入式系统中由于资源比较有限,特别是内存资源,因此对程序运行的性能要求比较高。对执行效率高的程序段所占用的空间和运行效率进行全方位的优化,可以对程序运行的整体效率将产生可观的提升。

    1、循环缓冲区

    在一些嵌入式的系统中,常常需要开辟一块缓冲区保存数据。例如:对于数据采集系统,需要将一定时间段内的数据放入一个内存区域中。这个内存区域的放置方法是从低地址开始放置,如果放满了(到达了最高的地址),则需要从头部的低地址开始重新放置。这样的内存结构就组成了一个循环缓冲区。

    在一般的嵌入式处理器中没有硬件自动完成循环放置的功能,通常的做法是在程序的每次循环中都判断缓冲区是否放满了,显然这样的开销很大。

    如果要在程序中执行缓冲区类型的操作,这些操作一般需要占用一块连续的内存。在栈上分配的内存,一般只能在函数内部使用,函数退出的时候就会被释放,因此不适合作为缓冲区使用。而在堆上的内存和静态内存都可以作为缓冲区内存使用。

    我们举例来看下:

    #define  BUFFERSIZE 256

    int x[BUFFERSIZE];

    unsigned int k;

    unsigned int i;

    while(1)

    {

    k = i & (BUFFERSIZE-1);

    x[k] = ImputData();

    /*……*/

    i++;

    }

    从程序中可见,数组x[]是作为程序的缓冲区使用的,而由于开始并没有进行数组的初始化,x[]是一个建立在BSS段上的数组,其大小由BUFFERSIZE确定。

    我们看循环内的操作,可以完成自动循环的过程,这个例程中,当i增加到256的时候,k作为数组下标,又会返回为0i本身增加到最大值的时候也会变为0

    那么大家很容易看出来,由于不需要使用if做判断,可以节省几条程序指令的时间。对于这几条指令看似节省的时间不多,但是由于上述语句执行的频率非常高,所以这些时间的节省占程序总运行时间的权重还是比较大的。尤其对于实时采样处理问题,程序必须在指定时间内完成一系列的操作。所以对于执行效率比较高的指令,哪怕只节省一条指令,对运行效率的提高都是很有意义的。

    从以上的例子中可以看出,当进行程序优化的时候,不仅需要考虑程序段运行的绝对时间,还应该考虑程序段运行的频率。对于运行频率非常高的程序,对其进行优化会在很大的程度上提高系统的性能。

    2、查表法

    由于资源有限,程序的运行效率在嵌入式系统上比在PC上的程序开发更为重要。程序的运行速度和所占用的存储器空间这两个效率问题都是必须考虑的。嵌入式系统程序的运行速度与处理器频率有关系;而程序所能占用的存储器空间与ROMRAM的大小有关系。

    在当前的嵌入式系统中,程序的运行速度比程序所占用的存储器空间显得更重要,一是存储器方便扩展,二是存储器的容量是比较容易控制,程序运行占用的处理器时间比较难控制。

    在设计过程中,程序的容量和速度在很多时候是有些矛盾的,在程序中牺牲一定的存储容量换取程序的运行速度,这对于嵌入式系统来说是有一定好处的。典型的例子就是查表法。

    例如:在一个4位的二进制数中,确定有几位为1,也就是要统计0x0~0xf中的任何一个数,中间有几个1

    典型思路:         查表法:

    int getnumber(unsigned int a)     const int table[16] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4};

    {           int getnumber(unsigned int a)

    unsigned int i=0;       {

    int num = 0;         return table[a&0x0f];

    unsigned int temp = a & (0xf);    }

    for(i =0;i < 4;i++)

    {

    if ((temp>>i)&0x01)

    num++;

    }

    return num;

    }

    典型的思路就是使用循环的方法让程序在这个4位的数中依次查找各个位是否为1,最后累加得出1的数目。那么在实现这个简单的功能的过程中,需要进行4次循环、4次判断,这是有一定开销的,占用了不少处理器的时间。从程序需要实现的功能考虑,输入是一个4位的数,范围是0x0~0xf,输出数的范围是0~4,这实际上是完成了一种映射功能,可以换成第二种查表法的思路,就是构造一个16个元素的数组,可以通过数组得到结果。实际上数组的下标就是输入的数值,而数组的元素就是输出的数值。

    那么很容易看出来,这种做法的优点是每个数值的获取非常快,代价则是增加了一个有16个元素的数组。数组是预先固化好的常量,而不是程序动态生成的,这种利用静态空间换取程序执行时间的方式转换后的程序执行效率非常高。如果把它应用在使用频率很高的程序中,就可以节省很多的系统开销。

    同样,大家可以考虑一下如果是查找8位数中的1的个数怎么做?16位呢?如果变通。

    3、针对循环执行效率的优化

    循环是C语言程序中的常用语法功能,由于循环执行的次数较多,占程序执行时间的权重大,所以对循环的优化是提高程序效率的关键点。

    例如,

    void change_list_value()

    {

    int i,count;

    POSITION pos;

    CPtrList* plist;

    plist = get_start(pos);

    for(i = 0; i < get_count(); i++)     count = get_count();      

    {          for(i = 0; i < count ; i++)

    plist = get_next(pos);     {}

    set_val (plist);

    }

    return 0;

    }

    上面这个循环代码左边是原始写法,右边是改进的。可以发现循环中执行的函数减少了,原来的get_count()函数从原来的内部转移到了循环外部,也就是说这个循环函数改进后只执行一次,如果这个链表中的元素有几千个至几万个,那么第一段代码比第二段代码多执行了几千条几万条的语句,这样会导致时间上巨大的开销。

    总结:在循环系统中,针对于循环条件,应该尽可能地使用临时变量来替代函数调用,这样可以在循环次数较多的情况下,减少大量不必要的函数调用。

    你有没有更好的优化技巧也分享出来啊~


    收藏 0 回复 0 浏览 110
  • 你知道“链接”吗

    在最开始人们编写程序时,都将所有的代码都写在同一个源文件中,经过长期的积累,程序可能包含了N多行的代码,程序员维护起来非常困难。迫切地希望将程序源代码分散到多个文件中,一个文件一个模块,能够更好地阅读和维护程序,这个时候,链接器就闪亮登场了。

    我们知道,数据是保存在存储器中的,对于单片机来说,必须知道这些数据的地址才能使用。变量名、函数名等仅仅是地址的一种代名词儿,旨在编程时更加方便地使用数据,当源文件被编译成可执行文件后,这些标识符都不存在了,它们都被替换成了数据的地址。

    任何程序的执行,最终都要依靠计算机硬件来完成,单片机是大规模集成电路,它只认识高低两个电平(电压),假设高电平为 3.3V,用1表示,低电平为 0V,用0表示。也就是说,在单片机底层,只有 0 和 1 两个二进制数字,这就是机器语言。

    使用机器语言编程,十分繁琐又耗时,并且很容易出错。如果程序包含了多个源文件,就很可能会有跨文件的跳转、在程序拥有多个模块时会导致更加严重的问题。于是大神们发明了汇编语言,这相比机器语言来说是个很大的进步。汇编语言使用接近人类的各种标号来帮助记忆,比如用jmp表示跳转指令,用func表示一个子程序(C语言中的函数就是一个子程序)的起始地址,标号的方法使得人们从具体的机器指令和二进制地址中解放出来。标号这个概念随着汇编语言的普及被广泛接受,它用来表示一个地址,这个地址可能是一段子程序的起始地址,也可以是一个变量的地址。

    随着软件规模的日渐庞大,代码量开始疯长,汇编语言的缺点逐渐暴露出来。汇编虽然提供了多种标号,但它依然非常接近计算机硬件,程序员要考虑很多细节问题和边界问题,而且不利于模块化开发,所以后来人们发明了C语言。C语言是比汇编更加高级的编程语言,极大地提高了开发效率,以加法为例,C语言只需要一条语句,汇编却需要四五条。

    单片机编程中,程序员通过会把很多功能分散到成许多个模块中。这些模块之间相互依赖又相互独立,原则上每个模块都可以单独开发、编译、测试,改变一个模块中的代码不需要编译整个程序。在程序被分隔成多个模块后,需要解决的一个重要问题是如何将这些模块组合成一个单一的可执行程序。在C语言中,模块之间的依赖关系主要有两种:一种是模块间的函数调用,另外一种是模块间的变量访问。函数调用需要知道函数的首地址,变量访问需要知道变量的地址,所以这两种方式可以归结为一种,那就是模块间的符号引用。这种通过符号将多个模块拼接为一个独立的可执行程序的过程就叫做链接(Linking)。
        在一个STM32项目中,代码被分为多个文件时,链接器可以链接ARM代码、Thumb代码、Thumb-2 代码,并自动生成交互操作中间代码,以便在需要时切换处理器状态。链接器还可以在需要时自动生成内联中间代码或长跳转中间代码,以扩展跳转指令的范围。

    链接器还可以生成关于链接文件的调试和引用信息、生成静态调用图并列出堆栈的使用情况、控制输出映像中符号表的内容、显示输出中代码和数据的大小。链接器针对下一次文件编译提供反馈信息,提示编译器有关未使用函数的情况。 可以根据提示在后续编译中将未使用的函数放置在各自的节中,以便链接器将来删除这些函数。

    图片38.png 

    使用链接器构建可执行映像时,链接器将解析输入对象文件之间的符号引用,从库中提取对象模块来满足还未满足的符号引用的需要,根据属性和名称排序输入节,并将属性和名称相似的节合并为相邻块,删除未使用节,删除重复的公共组和公共代码、数据及调试节,根据提供的分组和布局信息将对象片段组织为内存区,给可重定位值分配地址,最终生成可执行映像。


    收藏 0 回复 0 浏览 109
  • TTL通往RS232神奇之黑盒(一)

        大家好!我是张飞实战电子黄忠老师。

        做单片机通信的,这些不同通信接口转换的模块是必不可少的,都说这些模块那么多,又不贵,那我索性也来做一个,全隔离的TTL转RS232模块,在这里就跟大家分享下我的实现过程,还有需要注意的地方。如果大家有不同的看法随时欢迎交流哈~

    首先呢先来说下需要实现什么,就是TTL转RS232,那么就可以来选择芯片,有信号隔离芯片、电源隔离芯片、转换芯片,主要就是这些。

        那么接下来我就来详细分享下我的实现过程。

        1、输入端电压我设计的是5V供电,因为后端我选取的DCDC隔离电源需要5V供电,所以这里需要提示下的。输入进来加个保险丝(9V/100mA),起到保护作用。还有输入端我一般会选择加个LED灯,提示用的。

    图片1.png 

        2、信号输入后开始进行全隔离,信号通过隔离芯片实现,电源信号通过隔离电源,实现全隔离;信号线一般需要加个小电阻(10R够了)增强鲁棒性,对于隔离部分处理相对简单,看图吧

    图片2.png图片3.png 

        3、接下来就是转换芯片(SP3232EIM/TR)部分了,对于RS232的信号,我做了功课,这里简单说明下,RS232信号0和1与TTL不一样,TTL工作电压是0~3.3V,RS232逻辑1电平是低于-3V的(保证电平在-3~-15V);逻辑0电平是高于3V的(保证电平在3~15V)接通状态呢有效电平高于3V,断开状态电平低于-3V,也就是当传输电平绝对值大于3V时,电路可以有效的检查出来。

    通过阅读RS232转换芯片的数据手册,看下对于电荷泵电容的说法,

     

    图片4.png 

    图片5.png图片6.png 

    那么我的设计就参照手册的推荐设计,另外我选用的芯片可以实现两路转换,我只用了一路。对于有芯片的设计还是要多看手册。

    4、最后就是输出部分的端口了,DB9接口,这个也要查询下相关资料,这里我跟大家分享下我查询的结果还有我的端口设计。

    图片7.png 

    图片8.png图片9.png 

    图片10.png 

        最后需要注意的是:主控目标和隔离模块通讯才用 杜邦线连接,隔离模块和从控目标通讯采用带有DB9接口的线材连接,这里需要注意带有DB9接口的线分TX和RX交叉、不交叉两个版本,采购时切记分清楚。

        那么这个小模块的设计就完成了,是不是觉得其他我们平时买的黑盒内容也没想象中那么复杂,技术需要沉淀,不管做软件设计也好,硬件设计也好,真的都要去实际完成才会有不一样的收获。或者在我的设计基础上也可以有不同的见解,还可以有更好的升华。思路需要开阔,当有了丰富的经验,设计什么都会变得容易的多。更多的积累才会换来更大的财富。

        后面就开始设计PCB过程了,最后产品出来就可以通信使用了,如果大家感兴趣的话就持续关注我吧。后面会陆续分享出我的设计视频过程,随时欢迎大家跟我来探讨,也让我可以开阔下思路,或许还有其他实现方式。


    收藏 0 回复 0 浏览 108
  • TTL通往RS232神奇之黑盒(二)

         延续前篇,跟大家分享了全隔离模块的通信接口转换原理设计过程,那么接下来就分享下我的PCB设计路程,有些过程看似简单,自己真正动手才发现自己可能会出错的点在哪里,这就是积累经验的过程。

    PCB实现也是一样的,需要细致,需要根据实际情况做出调整。

    首先是左进右出原则,根据实际需求,这个不是固定的。

    在布局前还是要先把规则设置好,有了规则自然事情就会变得有了约束,做事就不会没条理了。那么规则设置是需要注意什么呢。

        1、电气特性是必要配置的,间距设置,常规设置为0.2mm,敷铜间距可以稍微大点,我会设置为0.5mm。(这个间距要根据实际项目需求)

        2、线宽需要设置,首先我常规走线都是用推荐值,但是电源和地的线宽我用的大点,所以这里需要把范围配置一下,主要还是规避错误嘛。

    图片11.png 

        3、过孔的孔径常规我用0.3mm/0.6mm,那么对于限制就要设置好,否则也会报错的吧。

    图片12.png 

        4、还有一些间距为了自己去把控,我一般会设置为0,比如孔到孔的间距,最小阻焊的间距,丝印到阻焊的间距,丝印到丝印的间距等。

        规则设置好,就可以开始布局了。

        有信号隔离,那么隔离电源和隔离芯片也是要考虑敷铜的问题,所以布局需要考虑摆放问题。

     

    图片13.png 

        由于RS232接口尺寸问题,需要考虑最大,那么右端整个部分就是要放RS232,所以考虑合理化,那么隔离芯片部分就放在上端了,先模块化说完,后面贴整个效果图,那么大家就可以理解了。

    接下来看转换芯片和后端部分模块。

    图片14.png 

        这个布局除了要考虑放置问题,敷铜问题,还有就是走线。

        那么布局就实现了,接下来走线就不多说了,主要走线要先走信号线,最后走电源和地线,地常规都是通过敷铜方式实现,那么最终效果看下:

    图片15.png 

        最后需要提醒一点是泪滴效果。丝印调整以及版本号等信息的添加。

        到这里基本功能实现了,就是打板了,最后产品出来就可以通信使用了,如果大家感兴趣的话就持续关注我吧。后面会陆续分享出我的设计视频过程,随时欢迎大家跟我来探讨,或许还有其他实现方式。


    收藏 0 回复 0 浏览 103
  • ADC参考电压有多重要?

    大家好,我是张飞实战电子黄忠老师,今天我们学习ADC参考电压。

    工程中大家经常会用到ADC来采集模拟电压,把模拟量变为数字量进行系统处理,有时候看到采集结果,什么?这个结果跟实际采集的信号怎么还有点小差距?那么就有可能是参考电压的问题。

    参考电压有多重要,我们得要弄清楚它在ADC转换中扮演一个什么样的角色,弄清楚这个问题,我们需要从ADC的转换原理入手,一般单片机里面ADC模块使用的是逐次逼近型转换,也就是通过这种方法原理把模拟量转换为数字量,那什么是逐次逼近呢?

    我们先来说一个生活中的案例,我们用天平称一个物体的重量,过程是这样的:从最重的砝码开始试放,与被称物体行进比较,若物体重于砝码,该砝码保留,否则移去。再加上第二个次重砝码,看物体的重量是否大于砝码的重量决定第二个砝码是留下还是移去。照此一直加砝码,到最小一个砝码为止。将所有留下的砝码重量相加,就得到物体的重量。

    逐次逼近原理和上面的原理相同,下面我们看逐次逼近型ADC的原理,请看图:

                      图片30.png     

    上图是一个8位逐次逼近型ADC的框图,“输入的模拟量”是输入电压信号,“START”用来控制ADC启动转换,“CLOCK”是ADC模块的输入时钟,“EOC”是ADC转换结束信号,“OE”是ADC转换结果输出允许信号,“VREF”是参考电压。

    随着时钟信号的输入,启动信号的开始,控制模块会逐次控制逐次比较寄存器产生不同的数据,数据产生后会送给D/A转换器,D/A转换器会依据参考电压,把这个数字量转化为模拟量送给比较器,比较器比较D/A转换器送出来的模拟量和输入模拟量的大小,产生的结果给控制单元电路,控制单元电路根据上一次的结果再次控制产生不同的数据,让D/A变成模拟量,再去比较,以此这样循环,每次比较,比较器会得出一个结果高或者低,根据这个结果决定当前产生的数字量是大了还是小了,一次一次的比较,找到那个和输入模拟量最接近的数字量,最后把这个数字量控制送到输出缓冲器,并且控制送出EOC输出转换完成信号,这就是一个大致的逐次逼近工作原理。

    关于具体是怎么控制比较的,这个过程我们就不再展开,我有一个免费的视频是专门解析这个过程的,链接是(https://www.bilibili.com/video/BV1xV411s7J4/;从上面的描述中,我们抓住一个重点是:D/A转换器会依据参考电,把生成的数字量变为模拟量,在转换的时候必须需要有一个参考电压,这个电压就是我们AD模块的参考电压,那么大家试想,如果参考电压都不稳定的话,转出来的模拟量是不是也不会稳定,那么和输入模拟量比较的时候,比较的结果也就可能会发生偏差,造成错误的比较结果。

    那怎么来保证这个参考电压比较稳定呢?1.我们可以在参考电压引脚附近就近放置电容(一大一小,大的储能,小的滤波);2.可以在参考电源前端串一个小电感再加电容。如图所示,这两种方法比较常见,也比较便宜,大家可以参考.

    图片31.png 

    总结,ADC的参考电压是非常重要的,所以参考电压精确度不容忽略,要尽可能地使参考电压稳定,不受干扰。

    这个知识我们就分享到这里,你理解了吗?

     


    收藏 0 回复 0 浏览 101
×
黄忠