发帖数

53

原创数

53

关注者

11

阅读数

10236

点赞数

1

黄忠

  • 单片机的异常处理

    大家好!我是张飞实战电子黄忠老师!今天给大家分享单片机的异常处理

    ARM处理器中,如果一个程序产生了错误并且被处理器检测到,这是就会产生错误异常。

    错误是怎么发生的呢?

    许多可能的原因都会引起错误发生,比如对于存储器相关错误,总线系统的异常响应可以有以下原因:

    访问的地址非法;

    由于传输的类型非法,总线的从设备不接受此次传输(从设备决定)

    由于传输未使能或初始化,总线的从设备无法进行此次传输(例如,如果外设的时钟被关闭,那么访问这个外设时,微控制器就可能会产生错误响应)。

    当确定了硬件错误异常的直接原因以后,我们可能还得花费一些时间来确定问题的根源。例如,总线错误可以由很多种情况引发,例如错误的指针操作、栈空间损坏、内存溢出、非法存储器映射以及其他原因。

    分析错误

    根据错误类型的不同,通常能够直接确定引起硬件错误异常的指令的位置。要实现这个目的,就需要知道进入硬件错误异常时的寄存器的内容,以及异常处理前压入栈中的寄存器的内容。这些值中包含了程序返回地址,通过它也能知道引起错误的指令地址。

    如果使用了调试器,那么可在工程中创建硬件错误异常处理,并且在其中添加一个用以暂停处理器的断点指令;或者也可以在硬件错误异常处理的开始部分设置一个断点,这样当硬件错误发生时,处理器就会自动暂停。在处理器由于硬件错误暂停后,我们就可以尝试着按照下面图的流程对错误进行定位。

    image.png

    为了给分析提供更多的信息,也可以生成程序映像的汇编代码,并且利用在栈帧中找到的PC值确定错误的位置。如果错误的地址为存储器访问指令,就应该检查寄存器的值确定存储器访问的地址是否合法。除了检查地址范围,也应该确认存储器的地址是否正确地对齐。


    除了压入栈中的PC值(返回地址),栈帧中也包含了其他有助于调试的寄存器值。例如,压入栈的IPSR能够反映处理器是否在进行异常处理,EPSR则代表了处理器状态(EPSR的T位为0,则表示错误由意外切换至ARM状态引起)。


    栈中的LR也可能会提供一些信息,例如发生错误的函数的返回地址,错误是否发生在异常处理中,以及EXC_RETURN的值是否被异常破坏等。


    另外,当前的寄存器值也可以提供有助于定位错误原因的各种信息,除了当前栈指针的值,当前的链接寄存器的值也可能有帮助。如果LR中为非法的EXC_RETURN的值,这就意味着它在前面异常处理中被错误地修改了。


    CONTROL寄存器也可以提供帮助。在没有OS的简单应用程序中,进程栈指针(PSP)不会被用到,并且CONTROL寄存器会一直保持为0。如果CONTROL寄存器被设置为0x2(PSP用于线程状态),这就意味着LR在之前的异常处理中被错误地修改了,或者栈内容被破坏导致了EXC_RETURN的值错误。


    收藏 0 回复 0 浏览 131
  • USB之特殊包

    前面文章我们说了令牌包、数据包、握手包,今天我们来看最后一个特殊包,本篇文章主要说明特殊包以及如何处理数据包,下面们开说.

    特殊包是一些在特殊场合使用的包。总共有4:pErSPLITPNG其中PRESPLITPING是令牌包,ERR是握手包。ERRSPLITPING三个是在USB2.0协议中新增的。

    PRE是通知集线器打开其低速端口的一种前导包。PRE只使用在全速模式中。平时,为了防止全速信号使低速设备误动作,集线器是没有将全速信号传送给低速设备的。只有当收到PRE令牌包时,才打开其低速端口。PRE令牌包与握手包的结构一样,只有同步域、PIDEOP。当需要传送低速事务时,主机首先发送一个PRE令牌包(以全速模式发送)。对于全速设备,将会忽略这个令牌包。集线器在收到这个令牌包后,打开其连接了低速设备的端口。接着,主机就会以低速模式给低速设备发送令牌包、数据包等。

    PING令牌包与OUT令牌包具有一样的结构,但是PING令牌包后并不发送数据,而是等待设备返回ACK或者NAK,以判断设备是否能够传送数据。在USB1.1,是没有PING令牌包的。只有在USB.0高速环境中才会使用PING令牌包,它只被使用在批量传输和控制传输的输出事务中直接使用OUT令牌包发送数据时,不管设备是否有空间接收数据,都会在OUT令牌包之后跟着发送一个数据包,如果设备没有空间接收数据,就返回一个NAK这样的结果就是浪费了总线带宽,白白传送了数据。在高速设备中增加了这个PING机制,主机先用PING令牌包试试设备是否有空间接收数据,而不用事先把数据发送出去。在全速模式下,有时会遇到一个很有趣的现象,就是下位机程序慢了一点点处理完数据,结果传输速度却下降了很多。这就是前面所说的OUT过程直接发送数据导致的,也就是说,虽然程序只慢了一点,但是却丢弃了整个数据包。

    接下来我们来说说如何处理数据包. SPLIT令牌包是高速事务分裂令牌包,通知集线器将高速数据包转化为全速或者低速数据包发送给其下面的端口。ERR握手包是在分裂事务中表示错误使用。由于高速分裂事务过程比较复杂,主要属于集线器的功能,在此就不详述了,感兴趣的读者可以阅读USB2.0协议相关部分。

    这么多类型的包以及传输过程,那我们该怎么去处理呢?其实,如果使用现成的USB接口芯片,很多过程,USB接口芯片都已经处理好了,可以不用太关心这些细节,只要知道有这么一个过程就行了。

    一般的USB接口芯片会完成如CRC校验、位填充、PD识别、数据包切换、握手等协议的处理。

    USB接口芯片正确接收到数据时,如果有空间保存,则它将数据保存并返回ACK,同时,设置一个标志表示已经正确接收到数据;如果没有空间保存数据,则自动会返回NAK

    收到输入请求时,如果有数据需要发送,发送数据,并等待接收ACK。只有当数据成功发送出去(即接收到应答信号AC)之后,它才设置标志,表示数据已成功发送;如果无数据需要发送,则它自动返回NAK

    通常只需要根据芯片提供的一些标志,准备要发送的数据到端点,或者从端点读取接收到的数据即可。所要发送和接收的数据是指数据包中的数据,至于同步域、包标识、地址、端点、CRC等是看不到的在 BUS Hound中抓到数据也是如此,仅是数据包;并且, BUS Hound中只能看到成功传输的数据,即只有ACK确认过的数据包。在USB接口芯片中,通过一些标志可以知道是哪个端点接收或者成功发送了数据。另外,由于控制传输比较特殊, SETUP包也会有相应的标志供我们使用。

    至此为止我们的包结构和包分类就全部说完了,通过对这些包的了解,我们会更加清楚的认识USB的通信知识.对我们做USB通信有很大的帮助.希望通过文章的内容分享,能给大家带来收获.


    收藏 0 回复 0 浏览 130
  • STM32中断如此简单

    大家好,我是张飞实战电子黄忠老师,下面我们先来了解一些基本概念:

    中断:中断是什么?举个例子来说,当我们正在工作时,突然电话响了,这时你会把手里的工作先停下来,然后去接电话,当接完电话后,电话里的人安排你马上做一件事,这时你需要立刻去做这件事,当把这件事做完后你会继续之前被打断的工作,这个过程为一次中断。

    异常:一个系统本应该正常的运行,但由于某些条件使系统产生了错误,就会使系统运行不正常,我们称之为异常。就好比一个健康的人,如果身体某个器官出现了问题,那他将会生病,不能像以前那样健康生活,称他的身体出现了异常。系统出现异常,我们必须对异常做出处理,才能让系统正常运行。

    事件:比如一个老师在教室里给学生上课,下面的学生会做出各种不同的动作,如有认真记笔记的,有讲小话的,有翻自己书包的等等,我们把学生的这些行为称为事件。但老师对这些事件有些是不会有动作的,有些事件是需要老师干预的,比如两个学生讲话,影响了老师上课,老师需要警告讲话的学生,然后再继续上课。

    优先级:当我们接到了两个电话,两个电话都安排你去做别的事,这时你需要先完成比较急的事,然后再完成不是太急的事,这就是优先级的问题。当有多个中断时,我们需要根据中断优先级判断先响应优先级高的中断,然后再响应优先级低的中断。

    中断与事件的联系与区别:有些事件需要响应,称这个事件为可中断事件,但有些事件不需要做出响应称这些事件为不可中断事件。当硬件正常连接时,对应事件会自动产生,但中断则需软件配置相应的中断使能位。

    抢占式优先级和响应优先级:所谓抢占式优先级和响应优先级,具有高抢占式优先级的中断可以在低抢占式优先级中断处理过程中被响应,即中断嵌套。当两个中断源的抢占式优先级相同时,这两个中断将没有嵌套关系,当一个中断到来后,如果正在处理另一个中断,这个后到来的中断就要等到前一个中断处理完之后 才能被处理。如果这两个中断同时到达,则中断控制器根据他们的响应优先级高低来决定先处理哪一个;如果他们的抢占式优先级和响应优先级都相等,则根据他们在中断表中的排位顺序决定先处理哪一个。每一个中断源都必须定义2个优先级。

     

    STM32的中断管理利用了NVIC(Nested Vectored Interrupt Controller)嵌套向量中断控制器,它把所有的外设中断和系统异常用一张向量表来管理,每个系统异常和外设中断都被分配相应的地址,除了一些系统异常的优先级不能改变外,其余的系统异常和中断的优先级都可变化。具体向量表部分截图如下图所示:

     图片27.png图片28.png        

    29.png                  

    其中包含10个系统异常,有82个外部中断地址,其中有11个被保留,没有使用,从上表可查出对应的默认优先级和地址分配情况。

    NVIC是嵌套向量中断控制器,它控制芯片所有中断功能,是Cortex-Mx内核里的一个外设下图为Cortex-M4内核NVIC寄存器的分布图:

    30.png 

    从上图可以看出,Cortex-M4内核NVIC嵌套向量中断控制器总的有7个类型的寄存器,其中有1个控制器类型的寄存器,8个中断使能寄存器,8个中断失能寄存器,8个中断挂起设定/清除寄存器,8个中断有效位寄存器,60个中断优先级寄存器。而STCM4内核NVIC控制器寄存器做了一定缩减,其寄存器分布图如下:

    31.png 

        在中断编程时,我们一般使用ST提供的固件库,对中断配置在程序编写时我们一般就使用中断使能、中断失能、中断优先级设定三个寄存器。

    NVIC嵌套向量中断控制器里有一个用于管理中断优先级的寄存器NVIC_IPRx(x=0,1,20),其数据位宽度为8bit,如果8位全使用,则可配置的优先级为0-255,数值小的优先级越高,但在STM32F373中,只是使用了高4位,可配置的优先级为0-15

    4位又被分为抢占优先级和响应优先级。对这4位又有5种搭配方式,定义抢占优先级和响应优先级的位数,其分组由内核外设SCB模块的应用程序中断及复位控制寄存器AIRCRbit8-bit10(PRIGROUP[0:2])三位决定,分组可用下图所示:

    32.png 

    ST官方已经把设定优先级分组封装成了一个库函数,我们设置优先级分组时,可直接调用相关库函数即可,中断库函数可在官方库文件misc.cmisc.h中找到,其优先级分组设定函数如下图所示:

    33.png 

    如我们想设置0位响应优先级、4位抢占优先级即优先级分组0,其函数可写为:

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);

    那我们仅仅是配置了优先级就够了吗?我们还需要配置通道,即配置中断源,按照前面中断向量表中的Position中断编号来查询即可配置即可。配置了中断源后,我们还需对中断源使能中断,ST官方把通道配置、优先级设定、通道使能定义为了一个结构体,其定义如下:

    34.png 

    下面我们以USART1为例,配置中断优先级分组为3,即3位响应优先级,1位抢占优先级,抢占优先级级数为0,响应优先级级数为1,对其编写程序如下:

    Void NVIC_Init(void)

    {

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);         //配置优先级分组

    NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;      //USART1中断通道

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级为最高 0

    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  //响应优先级为 1

    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   //使能中断通道

    NVIC_Init(&NVIC_InitStructure);    //将结构体成员的值写入对应的寄存器

    }

     

    至此,中断篇讲解完毕,更多详细的NVIC的介绍,可查阅官方技术手册NVIC部分。


    收藏 0 回复 0 浏览 124
  • 什么是程序映像

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

    我们通常说的单片机的程序映像一般包含以下几个部分:

    向量表;C启动例程;程序代码(应用程序代码和数据);C库代码(C库函数的程序代码,链接时插入)

    分别来看下组成部分都是什么,代表什么……

    向量表

    向量表可以用C语言或汇编语言实现。由于向量表的入口需要编译器和链接器生成的内容,所以向量表代码的实现细节是同开发工具链接相关的。例如,栈指针的初始值被链接到链接器生成的栈空间地址,而复位向量则指向了C启动代码的地址,这些都是同编译器相关的。有些开发工具,包括Keil MDK,则将向量表作为汇编启动代码的一部分,并且使用定义常量数(DCD)指令创建。

    汇编实现的向量表的例子:

    image.png

    这个例子中,向量表被赋予了一个段名(RESET),为了将向量表置于系统存储器映射的开头(地址:0x00000000),链接文件或命令行选项需要知道段的名字,以便链接器能够正确识别向量并将其进行地址映射。复位向量一般指向C启动代码的开头,不过,也可以自己定义复位处理,在跳转到C启动代码前执行附加的初始化操作。

    C启动代码

    C启动代码用于设置像全局变量之类的数据,也会清零加载时未被初始化的内存区域。对于使用malloc()C函数的应用程序,C启动代码还需要初始化堆空间的控制变量。初始化完成后,启动代码跳转到main()程序执行。

    C启动代码由编译器/链接器自动嵌入到程序中,并且是和开发工具链相关的,而只使用汇编代码编程则可能不存在C启动代码。对于ARM编译器,C启动代码被标识为“_main”,而使用GNU C编译器生成的代码则通常被标记为“_start”。

    程序代码

    用户指定的任务是由应用程序生成的指令完成的,除了指令以外,还有以下各类数据:

    ①变量的初始值,函数或子程序中的局部变量需要初始化,这些初始值会在程序执行期间被赋给相应的变量。

    ②程序代码中的常量。

    ③有些应用程序可能也会包括其他的常量,比如查找表和图像数据,他们也被合并在程序映像中。

    C库代码

    当使用特定的C/C++库函数时,它们的库代码就会由链接器嵌入到程序映像中,另外,由于有些数据处理任务需要浮点数或除法运算,在进行这些运算时,C库代码也会被包含进来。具体应用场合不同,内核不同,对C库代码多少以及使用情况也不同。

    RAM中的数据

    像程序ROM一样,微控制器的RAM也有很多种用法。典型地,RAM的使用一般可以分为数据、栈和堆区域。

    对于嵌入式操作系统(如uClinux)或RTOS(如Keil RTX)的微控制器系统,每个任务的栈空间都是独立的。有些操作系统允许用户自定义任务的栈,这样也就需要更大的栈空间。有些操作系统则将内存分为若干个段,每个任务分配一个段,用于各自的数据、栈和堆区域。

    那么,这些数据、栈和堆区域都存储了什么内容?

    数据,数据存储在内存的底部,包含全局变量和静态变量。

    栈,栈空间用于临时数据存储、局部变量的存储空间、函数调用参数传递和异常处理的寄存器备份等。

    堆,堆存储用于C函数自动分配存储器区域,例如alloc()malloc(),以及其他使用这些函数的函数调用,为了确保这些函数能够正确地分配存储器空间,C启动代码需要初始化堆存储及其控制变量。

    image.png

    一般说来,栈位于存储器空间的顶部,而堆区域则位于底部,这样做使得内存使用具有最大的灵活性。在操作系统环境中,可能会有多个内存区域用作数据、栈和堆。


    收藏 0 回复 0 浏览 122
  • 异常和中断

    异常是能够引起程序流偏离正常流程的事件,当异常发生时,正在执行的程序就会被挂起,处理器转而执行一块与该事件相关的代码(异常处理)。事件可以是外部输入,也可以是内部产生的,外部产生的事件通常被称作中断或中断请求(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 浏览 119
×
黄忠