发帖数

53

原创数

53

关注者

11

阅读数

9260

点赞数

1

黄忠

  • 讲解ADC模数转白话

    首先我们来简单介绍一下ADCADCAnalogToDigitalConverter简单来讲就是它可以把模拟量转化为数字量方便程序去处理

    下面我们来看一下ADC的框图,我们从框图上来介绍ADC采样的原理

    图片19.png 

    1. 外部被采样信号从单片机特定的引脚输入ADC模块,具体信号从哪个采样引脚输入,取决于‘通道选择’配置。

    2. 当正常开始采样后,被采样信号在规定的时间(即采样时间)内对图中的电容(即采样保持电容,简称采保电容)充电,当充完电之后会控制图中的开关断开,这个过程就像科学家提取了一份标本,拿回实验室研究一样。

    3. 当开关断开之后,图中的转换模块会花费一定的时间(即转换时间)对电容中的存的电压进行转换,并把转换的结果存入缓冲器,供程序员读取使用,同理,就像科学家对提取的标本花时间研究,得出一个结果。

    以上就是整个的ADC工作过程,虽然草草几段话了解,但是其中不乏很多关键的细节问题,什么细节问题呢,很多老铁会想,这不都是自动的嘛,有嘛要注意的,那么我们先来看一下这样几个问题。

    1. 采样时间设置多久?

    2. 采样时间是越短越好还是越长越好?

    3. 采样时间设置的大小和被采样信号的源内阻有什么关系?

    4. 如何从有干扰的信号中提取有用的采样信号,实现正确采样?

    5. 采样的结果和参考电压有什么关系?

    现在我们就上面几个问题,展开说明:

    上面的描述可以得到:从采样到转换完成中间2个时间采样时间+转换时间转换时间是固定的,采样时间可以设置),这两个时间决定了ADC采样的速度问题当需要高速AD采样的时候,这两个时间尤为重要因为它决定了采样速度

    我们再回头来想这里是被采样信号先对采保电容充电因为转换时间是固定的我们能配置的就只有采样时间了是不是说采样时间我们配置的越小越好呢答案是:不是的,最起码得保证采保电容电压和被采样信号的电压非常接近吧如果一味的小,追求速度快,采保电容充不满,转换出来的数据也不对

    那就带来了一个问题这个被采样信号源内阻的问题说白了就是对这个采保电容充电的电流够不够大只有充电的电流大了,才能在最短的时间内充满如果充电的电流非常小,恰恰配置的采样时间很小,结果只有一个,采样不准确我们经常看到,有的设计方案,做电压采样,分压电阻设计的阻值非常大当这样的大阻抗遇到高速采样,那就有可能会不准确了

    图片20.png 

    大家看上面的R1R2设置大了,那么充电电流就小了那么R1R2的阻值设计小了,充电电流大了,那么带来的问题功耗就大了当需要高速采样的时候,就需要注意这个阻值分配的问题对功耗要求比较高的产品,如可穿戴产品,蓝牙耳机,智能手表等,这个就需要大的阻值了一般情况下功耗要求不高的场合,我们基本上设计在1-2mA所以从此可以得出一个结论,采样时间不是越快越好,也不是越慢越好,恰到好处即可,需要你去实际调试。

    如何从带有干扰杂波的信号中提取到有用的信号呢,一般情况下,我们会根据系统的特性进行定点采样,即避开干扰区来采样提取有用的信号,再配软件或者硬件滤波手段来提取有用信号。

    采保电容得到了准确的采样信号,就一定意味着转换结果准确吗?答案肯定

    NO,这个和参考电压也有关系,参考电压就像一标准样品一样,举例来讲:老板说照着这个样品给我做100个,那首先前提是你的样品得准确,如果样品都不准确,那做出来的东西也必然会有偏差。

    想必通过上面的描述我们已经对ADC采样有了一定的理解认知,上面的描述只是ADC的一部分要点,介于篇幅原因,这里就不再赘述了,文末留个大家几个问题:大家看图中绿色地方电容这个电容加了好还是不加好?加多大好?在布板的时候这个电容应该摆放在哪里比较好?

    图片21.png 



    收藏 0 回复 0 浏览 119
  • 单片机的异常处理

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

    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 浏览 115
  • 什么是程序映像

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

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

    向量表;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 浏览 112
  • 指针和数组的恩恩怨怨

    指针和数组有没有关系呢?到底有什么关系,今天我们就来好好的看一看。

    指针就是指针,指针变量在32位系统下,永远占4字节,其值为某一个内存的地址。指针可以指向任何地方,但是不是任何地方你都能通过这个指针变量访问到呢?

    数组就是数组,其大小与元素的类型和个数有关;定义数组时必须指定其元素的类型和个数;数组可以存任何类型的数据,但不能存函数。

    既然它们之间没有任何关系,那为何很多人经常把数组和指针混淆,甚至很多人认为指针和数组是一样的呢?我们先来看下吧。

    1、以指针的形式访问和以下标的形式访问
    下面我们就详细讨论讨论它们之间似是而非的一些特点。例如,函数内部有如下定义:

    (A)char *p = abcdef;

    (B)char a[ ] = abcdef;

    ①以指针的形式访问指针和以下标的形式访问指针

    以指针的形式:*p+4

    以下标的形式:p[4]

    这里的4 是偏移量,都是先取出p里存储的地址值,加上偏移量,计算出新的地址,然后从新的地址中取出值。那么上面形式不同,访问的本质是一样的。

    ②以指针的形式访问数组和以下标的形式访问数组

    以指针形式:*a+4

    以下标形式:a[4]

    我们都知道数组名代表数组首元素的首地址,加上4个元素的偏移量,得到新的地址,然后取出新地址上的值。

    由此得出指针和数组都是可以“以指针的形式”或“以下标的形式”进行访问,但是是完全不一样的东西。

    还有需要注意的是这个偏移量代表的是元素,而不是字节,偏移元素的个数再计算新的地址取值。

    2、a &a 的区别

    先来举个例子:

    int a[5] = {1,2,3,4,5},b,c;

    int *prt = (int *)(&a + 1);

    b = *(a+1);

    c = *(ptr-1);

    对指针进行加1操作,得到的是下一个元素的地址,而不是原有地址值直接加1,所以一个类型为T的指针的移动,以sizeof(T)为移动单位。因此,对上面例子来说,a是一个一维数组,数组中有5个元素,所以&a+1是取数组a的首地址,该地址的值加上sizeof(a)的值,也就是&a+5*sizeof(int),也就是下一个数组的首地址,显然当前指针已经越过了数组的界限。

    那么*a+1):a&a的值是一样的,但是意思不一样,a是数组首元素的地址,也就是a[0]的首地址,&a是数组的首地址,a+1是数组下一个元素的首地址,也就是a[1]的首地址,&a+1是下一个数组的首地址,所以b的值应该是输出2*ptr-1),因为前面我们分析ptr是指向a[5]的,并且ptrint*类型,所以*ptr-1)是指向a[4],输出为5

    由此我们可以得知数组名a代表的是数组首元素的首地址,而不是数组的首地址,&a才是整个数组的首地址。

    指针和数组你能完全理清楚了吗?

     


    收藏 0 回复 0 浏览 111
  • 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 浏览 107
×
黄忠