发帖数

53

原创数

53

关注者

11

阅读数

9159

点赞数

1

黄忠

  • 单片机支持操作系统的特性


    大家好!我是张飞实战电子黄忠老师!今天给大家分享单片机支持操作系统的特性。

    1、支持操作系统的特性概述:

    就拿M0核的单片机来说,就有一部分特性是针对嵌入式操作系统的(OS),包括:

    SysTick定时器,24位向下计数,且周期产生SysTick异常。

    栈指针,即进程栈指针,两个栈指针的结构可以使得应用栈和OS内核栈相互独立。

    SVC异常和SVC指令,通过异常机制,应用程序可以使用SVC访问OS服务。

    PendSV异常,其可以被OS、设备驱动或者应用程序使用来产生可延迟的服务请求。

    2、为什么要使用嵌入式操作系统?

    当提到操作系统的时候,大多数人首先会想到WindowsLinux之类的桌面操作系统。这些操作系统要想运行起来,需要强大的处理器、大量的存储器以及其他硬件,而对于嵌入式设备,各种OS的差别很大。嵌入式操作系统可以运行在低功耗的微控制器上,它们需要很少的存储器(相对于桌面系统),并且运行的时钟频率要低很多,比如Keil RTX只需要4KB的程序空间以及大约0.5KBSRAM,一般情况下,这些操作系统设置不需要显示或者键盘。当然也可以增加一些显示接口和输入设备,并且通过运行在OS上的应用任务来访问这些输入和输出接口。

    在嵌入式应用程序中,OS一般用来管理多任务。在这种情况下,OS将处理器时间划分多个时间片,并且在每个时间片上执行不同的任务。当一个时间片结束时,OS任务调度器开始执行,这样在下一个时间片开始的时候,处理器已经切换到其他任务执行了。这种任务切换一般被称作上下文切换。

    每个时间片的长度依赖于硬件以及操作系统的设计,有些嵌入式操作系统每秒会进行几百次的任务切换。

    有些嵌入式OS也为每个任务定义了优先级,这样高优先级的任务就能在低优先级任务之前执行。如果一个任务的优先级比其他的都要高,在其到达空闲状态前,OS可能会连续多个时间片都在执行这个任务。应该注意的是,OS的优先级的定义与异常优先级是完全独立的(例如中断的优先级)。任务的优先级基于特定的OS,并且随着OS的不同而有所区别。

    除了支持多任务以外,嵌入式OS也提供了其他各种功能,包括资源管理、内存管理、电源管理,以及应用程序编程接口(API)用以访问外设、硬件和信道。


    hu.jpg

    使用嵌入式OS并不总是有好处的,因为它需要额外的程序空间来存放OS内核,而且会增加执行周期的开销。多数简单应用并不需要嵌入式OS,不过,有些复杂的嵌入式应用需要并行执行任务,这时使用OS会使软件开发更加容易,并且降低出现错误的概率。

    目前,可以应用在M0上的嵌入式OS有很多,例如,Keil 微控制器开发套件提供的免费且易于使用的RTX kernel,另外还有Micriumuc/OS-IIuc/OS-III等都支持M0处理器。并且这个支持的操作系统在不断的增加中。

    由于很多微控制器是不具备存储器管理单元(MMU),比如我们上面时候的M0核的处理器,所以它不能运行需要虚拟地址的嵌入式OS,比如Windows CESymbian OS。平常使用的Linux OS也需要MMU,它也不能再M0上工作。而uCLinuxLinux的特殊版,并且面向的是没有MMU的嵌入式设备,所以要在微控制器上加入OS也要先看能不能支持,并且支持哪些,再结合自己的项目实际选取。

     


    收藏 0 回复 0 浏览 129
  • 抛开CRC校验的“神秘面纱”

    大家好!我是张飞实战电子黄忠老师!今天给大家抛开CRC校验的“神秘面纱”!

    模2运算是一种二进制算法,CRC校验技术中的核心部分。与四则运算相同,模2运算也包括模2加法、模2减法、模2乘法、模2除法四种运算。


    与四则运算不同的是模2运算不考虑进位和借位,模2运算是编码理论中多项式运算的基础。下面我们就来看一看什么是模2运算。


    模2运算使用与四则运算相同的运算符,即“+”表示模2加,“-”表示模2减,“×”或“·”表示模2乘,“÷”或“/”表示模2除。与四则运算不同的是模2运算不考虑进位和借位,即模2加法是不带进位的二进制加法运算,模2减法是不带借位的二进制减法运算。这样,两个二进制位相运算时,这两个位的值就能确定运算结果,不受前一次运算的影响,也不对下一次造成影响。


    “模2加法”就是0和1之间的加法,其中0+0 =0,1+0 =0+1 =1,1+1=0。这种运算是比较常用的,并不神秘。对于任意多个数a1,a2,…,an(每个都是0或1),可以把它们做模2加法a1+a2+…+an。当这n个数中有奇数个1时,结果为1,否则结果为0。例如0101+0011=0110,列竖式计算:

    image.png

    模2减法是一种不考虑借位的减法,其定义如下:0-0=0,1-1=0,1-0=1,0-1=1。同样,第四式代表了模2减法的特征。在多位模减法中,每位都按上述定义进行运算,不考虑借位问题。根据上面减法可以得出一个结论:奇数个1相减得1,偶数个1相减得0。例如0110-0011=0101,列竖式计算:

    image.png

    一位数的模2乘法定义如下:0*0=0,0*1=0,1*0=0,1*1=1,多位数的模2乘法与普通乘法一样演算,区别是,部分积相加时按模2加,即奇数个1相加得1,偶数个1相加得0。例如1011×101=100111,列竖式计算:

    image.png

    模2除法是模2乘法的逆运算,定义如下:0÷1=0,1÷1=1,类似于普通的多位二进制除法,但是在如何确定商的问题上两者采用不同的规则。普通多位二进制除法按带借位的二进制减法,根据余数减除数够减与否确定商1还是商0,若够减则商1,否则商0。多位模2除法采用模2减法,不带借位的二进制减法,因此考虑余数够不够减除数是没有意义的。实际上,在CRC运算中,总能保证除数的首位为1,则模2除法运算的商是由余数首位与除数首位的模2除法运算结果确定。因为除数首位总是1,按照模2 除法运算法则,那么余数首位是1就商1,是0就商0。例如1011÷101=110101...001,列竖式计算:

    image.png

    模2算术是编码理论中多项式运算的基础。大家一定要掌握哦,下篇文章我们就一起看看它到底在CRC中是如何发挥作用的。

    收藏 0 回复 0 浏览 95
  • 烧写算法FLM文件如何实现呢?

    大家好!我是张飞实战电子黄忠老师!今天给大家分享烧写算法FLM文件如何实现的

    当我们在开发过程中用到MDK下载程序的时候可能都知道,在下载程序之前需要都在Debug设置的Flash Download子选项卡选择编程算法。大多数时候,我们只要安装了芯片包之后,就可以直接得到对应的编程算法,并不需要我们去修改它。但是,当我们是一个芯片包的开发者,或者我们有独特的下载需求(比如在程序里加入一些校验信息),这个时候我们就需要去了解它了!

    image.png

    编程算法其实就是一段程序,主要功能就是擦除相应的内存块,并将我们的程序写入到相应的内存区域上去。在点击下载按钮的时候,这段程序会被先下载到RAM上(RAM for Algorithm上的设置),然后才会通过它,将用户写的程序写入到指定的内存区域内。


    怎么去实现一个自己的编程算法?首先我们找到自己的MDK的安装路径,进入到ARMFlash文件夹下。这里有个编程算法的工程模板,复制这个工程到你的工程文件夹下,重命名你自己的想要的名字。

    image.png

    打开工程,里面主要有两个文件 FlashPrg.c 和 FlashDev.c:

    image.png

    FlashDev.c主要实现了一个设备相关的结构体(根据自己的Flash情况去实现)

    image.png

    比如STM32F103实现如下:

    image.png

    FlashPrg.c实现了几个Flash编程相关的函数:

    image.png

    根据自己的需要去实现,从上面我们就可以看出,下载程序的时候就是调用了上面的几个函数,跟我们自己写Flash没有太大的区别。那么程序都编程完成之后,怎么生成FLM文件呢?我们先编译工程,完成之后你去看你的工程输出目录,这个时候你就已经可以找到FLM后缀的文件了,这个就是我们自己的编程算法,把它复制到 ' MDK安装路径 'ARMFlash下面就可以了,在选项卡里选择我们自己的编程算法就可以使用了。但是为什么我们自己的工程就生成不了FLM文件呢?工程中的.axf文件跟.FLM文件是一样的,把.axf后缀改为.FLM即可。


    怎么样?心动嘛?赶快去写一个文件试试吧!



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

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

    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语言中的构造类型,其变量在初始化的时候相对复杂,也有一些比较特殊的技巧和方法。

    数组的初始化

    以下的代码是一个关于数组的初始化的示例:

    image.png

    从程序上来看,方式1直接使用数组初始化的方式,方式2使用了函数完成数组的赋值。方式3是方式2的等价形式。

    从表面上来看方式1要简单很多,实际上,无论从代码的规模上,还是效率上,二者都没有太大区别。

    方式1看似直接使用初始化的过程完成赋值,实际上对于类似char a[10]=abcde形式的语句,编译器还是需要做很多事情才能完成。a是函数中使用局部数组变量,开辟在栈内存空间上。当程序运行至fun处,不会凭空得到一段字符串,也就是说abcde必须有地方存放,这就是只读区(RO Data)。因此,程序运行赋值语句时,要在栈上开辟10个字节的空间,然后将调用内存复制函数将只读区的abcde复制到这个栈空间上。

    由此可见,方式1和方式2的运行没有本质区别,只是方式1利用编译器完成的操作,方式2要在运行程序时完成,二者依赖的库不同,但是都是内存复制一类的功能,同时二者的abcde都需要占用只读数据区的空间。

    从占用空间和运行效率上,方式1,方式2,方式3基本都是等价的。无论程序中有没有声明,abcde所占用的只读数据区(RO Data)都是必需的,复制的过程也是必需的。

    方式4是直接把a定义为已初始化可读写的全局变量,在使用的时候直接操作。作为已初始化的全局变量(RW Data),将在程序总体初始化的阶段复制到内存中,而不是在函数调用的时候复制。其优点是不用在函数调用的时候完成内存复制操作,缺点是全局的数据会一直占用内存,而栈上数据将在函数退出的时候释放。

    实质上,在数组的定义中,变量可以是全局变量或者局部变量,如果是全局变量,将会增加10字节已初始化的数据区(RW Data),初始化的内容将被放入,这段数据区是可读写的,对全局变量的访问就是对这段已初始化的数据区的访问。如果是局部变量,内容被放入只读数据区,函数运行到的时候要在栈上分配相应的数据区,把只读区的内容复制到栈上,对数组的访问是访问这段在栈上的内存。

    结构体的初始化

    在数组初始化的时候可以使用直接赋值的方式,而在结构体初始化的时候可以使用参数列表。这两种形式比较类似,因此结构体在初始化阶段和数组的情况是相似的。

    例如:

    image.png

    结构体的两种初始化方式和上面数组的两种初始化方式有一定的对应关系。第一种方式使用成员列表的方式初始化,第二种使用对结构体成员变量赋值的方式。实质上,第1种方式编译器将自动生成一些指令完成变量a的初始化,而第2种方式编译器在处理Score a语句的时候只需要开辟栈空间,而在后面在对其每个成员进行赋值,开辟栈空间和赋值都是简单的处理语句,编译器没有做过多的工作。

    在嵌入式系统中,对程序性能是非常敏感的,有以下几个方面的开销:首先是程序各段执行的效率,这是程序开销的主要方面,其次是函数的参数和返回值传递中入栈和出栈的时间。由于各个处理器一般都具有直接栈操作的指令(入栈和出栈),因此函数中使用的局部变量的可以使用处理器的基本的入栈和出栈指令来完成,这种指令的执行性能是很高的。但如果是为变量赋初值,虽然是C语言中基本的语法,却并不能以简单的方式处理,编译器实际上需要做一些附加的工作,来完成对局部变量的初始化。也就是说在程序中没有写出的语句,编译器也需要处理。根据以上的程序和分析,可见如果栈上变量需要初始化,有可能也会带来一定的开销。


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