发帖数

53

原创数

53

关注者

11

阅读数

9260

点赞数

1

黄忠

  • 异常和中断

    异常是能够引起程序流偏离正常流程的事件,当异常发生时,正在执行的程序就会被挂起,处理器转而执行一块与该事件相关的代码(异常处理)。事件可以是外部输入,也可以是内部产生的,外部产生的事件通常被称作中断或中断请求(IRQ)。几乎所有的现代处理器都支持异常和中断,微控制器的中断可以由片上外设或软件产生。由此可见,通常我们处理的中断是异常的一种。

    每种异常类型都有对应的优先级,有些异常的优先级是固定的,有些是可编程的。

    先说几个概念:

    1、不可屏蔽中断(NMI

    NMIIRQ类似,只是它不能被禁止,并且优先级仅次于复位,它对于工业控制和汽车之类的高可靠性系统非常有用。根据微控制器设计的不同,NMI可以用于掉电处理,也可以连接到看门狗单元,以便在系统停止响应时将系统复位。由于NMI不能被控制寄存器禁止,其响应的及时性就得到了保证。

    2、硬件错误

    硬件错误异常用于处理程序执行时产生的错误,这些错误可以是试图执行未知的操作码、总线接口或存储器系统的错误,也可以是试图切换至ARM状态之类的非法操作。

    3、SVC(请求管理调用)

    SVC指令执行时就会产生SVC异常,其通常用在具有操作系统的系统中,为应用程序提供了访问系统服务的入口。

    4、PendSV(可挂起的系统调用)

    PendSV是用于带OS(操作系统)的应用程序的另外一个异常,SVC异常在SVC指令执行后会马上开始,PendSV在这点上有所不同,它可以延迟执行,在OS上使用PendSV就要确保高优先级任务完成后才执行系统调度。

    5、系统节拍

    NVIC中的SysTick定时器为OS应用可以利用的另外一个特性。几乎所有操作系统的运行都需要上下文切换,而这一过程通常需要依靠定时器产生定时中断来完成。

    6、中断

    中断信号可以连接到片上外设,也可以通过IO端口连接到外部中断源上。外部中断只有在使能后才能使用,如果中断被禁止了,或者处理器正在运行另外一个相同或更高优先级的异常处理,则该中断请求会被存储在挂起状态寄存器中。当高优先级的中断处理完成或返回后,挂起的中断请求才可以执行。NVIC能够接受的中断请求信号可以是高逻辑电平,也可以是中断脉冲。应该注意的是,在微控制器的外部接口中,外部中断信号可以是高电平也可以是低电平,或者可以通过编程配置。

    异常的处理流程

    1、接受异常请求

    处理器要接受一个异常,需要满足的条件:

    ①对于中断和SysTick中断请求,中断必须使能

    ②处理器正在执行的异常处理的优先级不能相同或更大

    ③中断屏蔽寄存器没有屏蔽掉异常

    特别注意一点:对于SVC异常,如果用到SVC指令的异常处理的优先级与SVC异常本身相同或更大,这种情况就会引起硬件错误异常处理的执行。

    2、压栈和出栈

    为了使被中断的程序能正确继续执行,在程序切换至异常处理前,处理器当前状态的一部分应该被保存。不同架构处理器的处理方法不同,有的采用硬件自动处理的方法来备份和恢复处理器状态,看需求,有的是需要程序中增加软件处理过程。

    异常处理过程执行到最后时,将会利用执行特殊值来触发异常返回机制。处理器还会查看当前是否还有其他异常需要处理,如果没有,处理器就会恢复之前存储在栈空间的寄存器值,并继续执行中断前的程序。

    自动保存和恢复寄存器内容的操作被称为“压栈”和“出栈”,这种机制使得异常处理可以跟普通的C函数一样处理,同时也减小了软件开销以及回路大小,因此也降低了系统的功耗。

    3、异常返回指令

    根据处理器的不同中断处理返回有的需要特殊指令,一般都是普通的返回指令,加载到PC中的数值则会触发异常返回,这样就使得异常处理可以和普通的C函数一样使用。

    两个不同的指令可以用于异常返回:

    BX  <Reg>q     ;将寄存器中的值加载到PC

    POP {<Reg1>,<Reg1>,...,PC}  ;POP指令,PC也是更新的寄存器之一

    当其中一个指令执行,异常返回机制就会启动。

    4、末尾连锁

    如果当其他的异常处理完成后,还有异常处于挂起状态,这时处理器不会返回到中断前的程序,而是重新进入异常处理流程,这也被称作末尾连锁。当末尾连锁发生时,处理器不必马上恢复栈的值,因为如果这么做的话还得重新压栈。异常的末尾连锁降低了异常处理的开销,因此也提高了能耗效率。

    图片1.png

     


    收藏 0 回复 0 浏览 106
  • 51单片机DIY抽奖-技术分享(四)

    软件设计

    延续前篇,继续分享我的DIY抽奖设计之软件部分,上一篇我介绍了我做这个小产品的PCB设计过程,然后现在就到了实物板调试的阶段了,只有把相应的程序实现了,这个产品才能真正实现自己的价值,实现的过程可能会比较曲折,但没有过程哪来的结果,没有实践哪来的经验,话说真的一点没错吧。

    这里我只能描述实现过程和碰到的一些小问题,具体实现过程以及程序源码细节我也会陆续更新出视频,感兴趣的可以关注下~自己实践下更好了。

    我们要完成一个程序设计的话需要先设计算法,然后再实现算法,实现就要用到计算机语言来表述了,不然单片机也不认不是。这里我说一个概念就是结构化程序的设计强调程序设计风格和程序结构的规范化,那么就提倡用清晰的结构,我们不管是新手还是老手,规范化还是很有必要的,对自己程序的逻辑关系的考验,或者是对阅读者都是很好的,以至于到后期的调试或者扩展和修改算法都会变得容易了。

    我要实现抽奖功能,首先不能只有大的想法就够了,既然要实现就要把每个细节的逻辑关系都要落实才行,否则就不会出现你想要的结果。

    那么接下来我从大的方向到细化的过程以及设计要点都分享下哈~

    1、程序主要实现的功能:

    image01.pngimage02.png


    简单清晰,主要的调试点在内部实现的逻辑关系上。后续精彩着呢,待更完~


    2、顺序往下吧,对于单片机设计,首先就是要初始化,对IO口和定时器等进行配置才能使得单片机正常工作,每款单片机对外设的配置都是不一样的,那么就要研读数据手册了,这是避不开的。先来看下我的初始化部分。

    image03.png


    既然前面提到了分模块,从初始化到实际处理都要分模块进行,这样思路清晰。比如第一个函数是对单片机的初始化,主要就是I2C通信的IO口部分,蜂鸣器的IO口部分,定时器的部分,那么初始化就是直接对外设寄存器的配置;后面的模块主要就是对当前模块的全局变量的初值配置了。全局变量我一般都是用结构体来实现,每个模块定义一个结构体,名称和用法一目了然,想扩展也方便。


    3、初始化了,只能说单片机可以用了,具体还要看需要的应用模块,这样单片机才能顺利完成你需要的使命。继续看下抽奖大的模块,就是显示和按键了,再有一个就是蜂鸣器模块。

    有了大的模块就可以细分,把具体功能先确定好,再去实现就方便多了,我觉得做程序一定要把模块做好,然后模块间的全局变量尽量通过结构体来实现,这样结构清晰,逻辑性好。

    这是我个人经验,当能把习惯养好的时候,一定不要随意,否则浪费时间不说,程序结果也会不稳定。



    4、那这里我就分享下我的小DIY的模块的逻辑实现,先说显示部分吧,首先我做的这个有两个按键,一个开始,一个停止。按键开始后我会把对应显示模式的变量配置好,对应不同的模式再去进行处理。

    image04.png


    接下来细化了,就是在不同模式下做些什么,先来看下我的流程图,然后我再针对设计细节把想法说一下。

    image05.png


    对于获取当前获奖者,我的处理方法是通过指针获取当前序号对应数组的值,同时把当前值从数组中删除,直到数组中数据全部被抽完为止。

    流水灯以及显示数值由慢变快是怎么做的呢,主要就是定义一个全局变量,作为时间参数配置一个大一点的初始值,定时时间到时间参数减值,直到减到一个很小的值,这就是一个渐变的过程。

    image06.png


    具体的实现过程可以关注我的视频,所有代码都会分享到。

    5、那么按键模块的话就是扫描和处理了,按键部分我当时选取数码管驱动芯片就很明确了,具体可以看下我第一篇给大家推荐的那款芯片,很实用啊。那么对于扫描不需要做过多的处理,只需要直接读取就可以了,这个就没什么好说的了。只是I2C相关的读写函数要仔细研读手册了。

    接下来看下我的处理,也是状态机模式,整个网络之间的关系通过结构体全局变量实现。逻辑关系清晰。

    image07.pngimage08.png

        框架出来了,就是具体逻辑关系了,有短按和长按,这个长按只针对停止键,短按就是常规抽奖模式的开始和停止了,那么停止键在短按情况下,要判断当前模式是转盘转动模式还是设置模式,如果是转动模式就是进入停止模式(显示当前号码,蜂鸣器唱歌),如果是设置模式(配置抽奖号码总数),号码加1;启动键短按的处理,也是要判断当前模式是停止模式还是设置模式,如果是停止模式则进入转动模式,如果是设置模式,号码减1;还有一个长按停止键了,当前模式是停止模式,长按则进入设置模式,如果当前是设置模式那么就进入停止模式。长按的处理就是进入和退出设置模式。按键处理就这些需要实现的功能。

    最后就是一个蜂鸣器的模块处理,由于蜂鸣器我做了唱歌的功能,涉及到频率问题,所以我放在中断中完成(也是经过多次调试)。蜂鸣器唱歌要分析谱子,调调,真的有点音乐人的感觉了,通过计算低音,中音,高音的频率先计算配置好,然后要有曲子的分析,查表配置周期(可以用定时器的时钟分频模式)实现唱歌。这里学问太深,有兴趣的可以深挖一下,还是通过蜂鸣器模式来进入相应的状态,比如一声响,两声响,唱歌。这个都是可以自由分配的。

       到这里抽奖程序设计主体思路就这样实现了,整个DIY的产品就完成了。下面就是成品啦。

    image09.png

           纵观看下,功能不难,其实实现起来真的没你想的那么简单,逻辑关系要确定好,手册要研读好。有兴趣的话可以自己动手实践下,每个实践过程都会有很大收获的,后续会分享我的程序实现的视频过程,会更细致些。每个项目不论大小都会经历不同的坎坷啊。当你遇到不同的问题的时候慢慢解决了才是经验的积累过程。这个抽奖DIY小产品就完成了,大家可以看下我的实现有没有需要改进的地方呢?随时欢迎交流啊,或者有更好玩的产品一起探讨实现啊,每个都有每个的特点,都能学到不一样的知识点。后面我会陆续做出更多的小产品,每一个都会有侧重点,或者可以给我启发或建议一起完成你的想法啊,感谢大家对我的持续关注,还希望能给出更多的意见和建议















    收藏 0 回复 0 浏览 103
  • USB的四种传输类型之控制传输

    在我们前面的文章里面,我们描述了什么是批量传输,中断传输,等时传输,下面的文章我们来介绍一下控制传输。

    控制传输与前面三种传输相比,要稍微复杂一些。前面在介绍设备的枚举过程时,就提到过控制传输。控制传输分为三个过程:第一个过程是建立过程;第二个过程是可选的数据过程;第三个过程是状态过程。

    建立过程使用一个建立事务。建立事务是一个输出数据的过程,与批量传输的输出事务相比,有几处不一样:首先是令牌包不一样,建立过程使用 SETUP令牌包;其次是数据包类型, SETUP只能使用DATA0;最后是握手包,设备只能使用ACK来应答(除非出错了,不应答),而不能使用NAK或者 STALL来应答,即设备必须要接收建立事务的数据。图1是建立事务的流程图.

    image.png

    1

    数据过程是可选的,即一个控制传输可能没有数据过程。如果有,一个数据过程可以包含一笔或者多笔数据事务。控制传输所使用的数据事务与批量传输中的批量事务是一样的。要注意的是,在数据过程中,所有的数据事务必须是同一个传输方向的。也就是说,在控制读传输中,数据过程中的所有数据事务都必须是输入的;在控制写传输中,数据过程中的所有数据事务都必须是输出的。一旦数据传输方向发生改变,就会认为进入到了状态过程。数据过程的第一个数据包必须是DATA1,然后每次正确传输一个数据包后就在DATA0DATA1之间交替。

    状态过程也是一笔批量事务,它的传输方向刚好跟前面的数据阶段相反,即控制写传输在状态过程使用一个批量输入事务;控制读传输在状态过程使用一个批量输出事务。状态过程只使用DATA1包。

    控制传输之所以要弄得这么复杂,是因为它要保证数据传输过程的数据完整性。设备枚举过程中各种描述符的获取以及设置地址、设置配置等,都是通过控制传输来实现的。关于USB协议中定义的控制传输所使用的各种标准请求的数据结构和请求命令,将会在后面的实例中具体、详细地分析。

    接下来我们来说一下端点类型和传输类型的关系。一个具体的端点,只能工作在一种传输模式下。通常,我们把工作在什么模式下的端点,就叫做什么端点。例如,控制端点、批量端点等。端点0是每个USB设备都必须具备的默认控制端点,它一上电就存在并且可用。设备的各种描述符以及主机发送的一些命令,都是通过端点0传输的。其他端点是可选的,需要根据具体的设备来决定。非0端点只有在 Set Config之后才能使用。

    今天就跟大家分享到这里,你学会了吗?

     


    收藏 0 回复 0 浏览 103
  • 程序的优化技巧

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

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

    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 浏览 99
  • 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 浏览 98
×
黄忠