单片机

SPI是一个环形总线结构,由ss(cs)、sck、sdi、sdo构成,其时序其实很简单,主要是在sck的控制下,两个双向移位寄存器进行数据交换。

SPI总线是Motorola公司推出的三线同步接口,同步串行3线方式进行通信:一条时钟线SCK,一条数据输入线MOSI,一条数据输出线MISO;用于 CPU与各种外围器件进行全双工、同步串行通讯。SPI主要特点有:可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可编程时钟;发送结束 中断标志;写冲突保护;总线竞争保护等。

SPI总线有四种工作方式(SP0, SP1, SP2, SP3),其中使用的最为广泛的是SPI0和SPI3方式。

SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果 CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传 输协议之一进行数据传输。如果 CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。 SPI主模块和与之通信的外设音时钟相位和极性应该一致。

SPI接口在模式0下输出第一位数据的时刻,SPI接口有四种不同的数据传输时序,取决于CPOL和CPHL这两位的组合。图1中表现了这四种时序,时序与CPOL、CPHL的关系也可以从图中看出。

图1

CPOL是用来决定SCK时钟信号空闲时的电平,CPOL=0,空闲电平为低电平,CPOL=1时,空闲电平为高电平。CPHA是用来决定采样时刻的,CPHA=0,在每个周期的第一个时钟沿采样,CPHA=1,在每个周期的第二个时钟沿采样。

由于我使用的器件工作在模式0这种时序(CPOL=0,CPHA=0),所以将图1简化为图2,只关注模式0的时序。

图2

我们来关注SCK的第一个时钟周期,在时钟的前沿采样数据(上升沿,第一个时钟沿),在时钟的后沿输出数据(下降沿,第二个时钟沿)。首先来看主器件,主器件的输出口(MOSI)输出的数据bit1,在时钟的前沿被从器件采样,那主器件是在何时刻输出bit1的呢?bit1的输出时刻实际上在SCK信号有效以前,比 SCK的上升沿还要早半个时钟周期。bit1的输出时刻与SSEL信号没有关系。再来看从器件,主器件的输入口MISO同样是在时钟的前沿采样从器件输出的bit1的,那从器件又是在何时刻输出bit1的呢。从器件是在SSEL信号有效后,立即输出bit1,尽管此时SCK信号还没有起效。关于上面的主器件和从器件输出bit1位的时刻,可以从图3、4中得到验证。

图3

注意图3中,CS信号有效后(低电平有效,注意CS下降沿后发生的情况),故意用延时程序延时了一段时间,之后再向数据寄存器写入了要发送的数据,来观察主器件输出bit1的情况(MOSI)。可以看出,bit1(值为1)是在SCK信号有效之前的半个时钟周期的时刻开始输出的(与CS信号无关),到了SCK的第一个时钟周期的上升沿正好被从器件采样。

图4

图4中,注意看CS和MISO信号。我们可以看出,CS信号有效后,从器件立刻输出了bit1(值为1)。通常我们进行的spi操作都是16位的。图5记录了第一个字节和第二个字节间的相互衔接的过程。第一个字节的最后一位在SCK的上升沿被采样,随后的SCK下降沿,从器件就输出了第二个字节的第一位。

围观 351

对电磁干扰的设计我们主要从硬件和软件方面进行设计处理,下面就是从单片机的PCB设计到软件处理方面来介绍对电磁兼容性的处理。

一、影响EMC的因数

1、电压

电源电压越高,意味着电压振幅越大,发射就更多,而低电源电压影响敏感度。

2、频率

高频产生更多的发射,周期性信号产生更多的发射。在高频单片机系统中,当器件开关时产生电流尖峰信号;在模拟系统中,当负载电流变化时产生电流尖峰信号。

3、接地

在所有EMC题目中,主要题目是不适当的接地引起的。有三种信号接地方法:单点、多点和混合。在频率低于1MHz时,可采用单点接地方法,但不适宜高频;在高频应用中,最好采用多点接地。混合接地是低频用单点接地,而高频用多点接地的方法。地线布局是关键,高频数字电路和低电平模拟电路的接地电路尽不能混合。

4、PCB设计

适当的印刷电路板(PCB)布线对防止EMI是至关重要的。

5、电源往耦

当器件开关时,在电源线上会产生瞬态电流,必须衰减和滤掉这些瞬态电流。来自高di/dt源的瞬态电流导致地和线迹“发射”电压,高di/dt产生大范围的高频电流,激励部件和线缆辐射。流经导线的电流变化和电感会导致压降,减小电感或电流随时间的变化可使该压降最小。

二、对干扰措施的硬件处理方法

1、印刷线路板(PCB)的电磁兼容性设计

PCB是单片机系统中电路元件和器件的支撑件,它提供电路元件和器件之间的电气连接。随着电子技术的飞速发展,PCB的密度越来越高。PCB设计的好坏对单片机系统的电磁兼容性影响很大,实践证实,即使电路原理图设计正确,印刷电路板设计不当,也会对单片机系统的可靠性产生不利影响。例如,假如印刷电路板的两条细平行线靠的很近,会形成信号波形的延迟,在传输线的终端形成反射噪声。因此,在设计印刷电路板的时候,应留意采用正确的方法,遵守PCB设计的一般原则,并应符合抗干扰的设计要求。要使电子电路获得最佳性能,元器件的布局及导线的布设是很重要的。

2、输入/输出的电磁兼容性设计

在单片机系统中输进/输出也是干扰源的传导线,和接收射频干扰信号的拾检源,我们设计时一般要采取有效的措施:

①采用必要的共模/差模抑制电路,同时也要采取一定的滤波和防电磁屏蔽措施以减小干扰的进进。

②在条件许可的情况下尽可能采取各种隔离措施(如光电隔离或者磁电隔离),从而阻断干扰的传播。

3、单片机复位电路的设计

在单片机系统中,看门狗系统对整个单片机的运行起着特别重要的作用,由于所有的干扰源不可能全部被隔离或往除,一旦进进CPU干扰程序的正常运行,那么复位系统结合软件处理措施就成了一道有效的纠错防御的屏障了。常用的复位系统有以下两种:

①外部复位系统。外部“看门狗”电路可以自己设计也可以用专门的“看门狗”芯片来搭建。然而,他们各有优缺点,大部分专用“看门狗”芯片对低频“喂狗”信号不能响应,而高频“喂狗”信号都能响应,使其在低频“喂狗”信号下产生复位动作而在高频的“喂狗”信号下不产生复位动作,这样,假如程序系统陷进一个死循环,而该循环中恰巧有着“喂狗”信号的话,那么该复位电路就无法实现它的应有的功能了。然而,我们自己可以设计一个具有带通的“喂狗”电路和其他复位电路构成的系统就是一个很有效外部监控系统了。

②现在越来越多的单片机都带有自己的片上复位系统,这样用户就可以很方便的使用其内部的复位定时器了,但是,有一些型号的单片机它的复位指令太过于简单,这样也会存在象上述死循环那样的“喂狗”指令,使其失往监控作用。有一些单片机的片上复位指令就做的比较好,一般他们把“喂狗”信号做成固定格式的多条指令依顺序来执行,假如有一定错误则该“喂狗”操纵无效,这样就大大进步了复位电路的可靠性。

4、振荡器

大部分的单片机都有一个耦合于外部晶体或陶瓷谐振器的振荡器电路。在PCB上,要求外接是电容、晶体或陶瓷谐振器的引线越短越好。RC振荡器对干扰信号有潜伏的敏感性,它能产生很短的时钟周期,因而最好选晶体或陶瓷谐振器。另外,石英晶体的外壳要接地。

5、防雷击措施

室外使用的单片机系统或从室外排挤引进室内的电源线、信号线,要考虑系统的防雷击题目。常用的防雷击器件有:气体放电管、TVS(Transient Voltage Suppression)等。气体放电管是当电源的电压大于某一数值时,通常为数十V或数百V,气体击穿放电,将电源线上强冲击脉冲导进大地。TVS可以看成两个并联且方向相反的齐纳二极管,当两端电压高于某一值时导通。其特点是可以瞬态通过数百乃上千A的电流。

三、对干扰措施的软件处理方法

电磁干扰源所产生的干扰信号在一些特定的情况下(比如在一些电磁环境比较恶劣的情况下)是无法完全消除的,终极将会进进CPU处理的的核心单元,这样在一些大规模集成电路经常会受到干扰,导致不能正常工作或在错误状态下工作。特别是像RAM这种利用双稳态进行存储的器件,往往会在强干扰下发生翻转,使原来存储的“0”变为“1”,或者“1”变为“0”;一些串行传输的时序及数据会因干扰而发生改变;更严重的会破坏一些重要的数据参数等;造成的后果往往是很严重的。在这种情况下软件设计的好坏直接影响到整个系统的抗干扰能力的高低。

1、程序会由于电磁干扰大致会一下几种情况:

①程序跑飞。

这种情况是最常见的干扰结果,一般来说有一个好的复位系统或软件帧测系统即可,对整个运行系统的不会产生太大的影响。

②死循环或不正常程序代码运行。

当然这种死循环和不正常程序代码并非设计职员有意写进的,我们知道程序的指令是由字节组成的,有的是单字节指令而有的是多字节指令,当干扰产生后使得PC指针发生变化,从而使原来的程序代码发生了重组产生了不可猜测的可执行的程序代码,那么,这种错误是致命的,它会有可能会往修改重要的数据参数,有可能产生不可猜测的控制输出等一系列错误状态。

2、对重要参数储存的措施

一般情况下,我们可以采用错误检测与纠正来有效地减少或避免这种情况的出现。根据检错、纠错的原理,主要思想是在数据写进时,根据写进的数据天生一定位数的校验码,与相应的数据一起保存起来;当读出时,同时也将校验码读出,进行判决。假如出现一位错误则自动纠正,将正确的数据送出,并同时将改正以后的数据回写覆盖原来错误的数据;假如出现两位错误则产生中断报告,通知CPU进行异常处理。所有这一切动作都是靠软件设计自动完成的,具有实时性和自动完成的特点。通过这样的设计,能大大进步系统的抗干扰能力,从而进步系统的可靠性。

检错与纠错原理:

首先来看看检错和纠错的基本原理。进行差错控制的基本思想是在信息码组中以一定规则加进不同方式的冗余码,以便在信息读出的时候依靠多余的监视码或校码码来发现或自动纠正错误。

针对误码发生的特点,即错误发生的随机性和小概任性,它几乎总是随机地影响某个字节中的某一位(bit),因此,假如能够设计自动纠正一位错误,而检查两位错误的编码方式。就可以大大进步系统的可靠性。

3、对RAM和FLASH(ROM)的检测

在编制程序时我们最好是写进一些检测程序来测试RAM和FLASH(ROM)的数据代码,看有无发生错误,一旦发生要立即纠正,纠正不了的要及时给出错误指示,以便用户往处理。

另外,在编制程序时加进程序冗余是不可缺少的。在一定的地方加进三条或三条以上NOP指令对程序的重组有着很有效防止作用。同时,在程序的运行状态中要引进标志数据和检测状态,从而及时发现和纠正错误产生。

来源:网络(版权归原著作者所有)

围观 468

学习与应用单片机的高潮正在工厂、学校及企事业单位大规模地兴起。过去习惯于传统电子领域的工程师、技术员正面临着全新的挑战,如不能在较短时间内学会单片机,势必会被时代所遗弃,只有勇敢地面对现实,挑战自我,加强学习,争取在较短的时间内将单片机技术融会贯通,才能跟上时代的步伐。

但是,许多的学习者(包括在校学生),他们总不得要领,从一开始学习时的热情高涨,到最后的沮丧放弃,使得大家对单片机产生了既爱又怕的感觉。

学习单片机并不象学习传统数字电路或模拟电路那样比较直观,原因是除了“硬件”之外还存在一个“软件”的因素。正是这个“软件”因素的存在,使得许多初学者怎么也弄不懂单片机的工作过程,他们怎么也不明白为什么将几个数送来送去,就能控制一盏灯亮/灭?能控制一个电机变速?由此对单片机产生一种“神奇”、“敬畏”甚至“恐惧”感,阻碍了学习单片机的热情与兴趣,这就有社会上“单片机难学”一说。

笔者多年来与众多的电子爱好者、在校学生打过交道,深知他们学习单片机中碰到的难处,况且作者本人也是从一位电子爱好者成长为工程师的,此过程自然少不了学习、探索、实践、进步这样一条规律,因此深切地知道,学单片机难,主要是不得要领,难以入门。一旦找到学习的捷径,入了门,能初步掌握编程技术并产生实际效果,那么必然信心大增。接下来,再向新的深度、广度进军时,心里也不那么焦虑,比较坦然了,能够一步一个脚印下去扩展自己的知识面。这里根据笔者的经验谈谈学习方法、技巧及如何在较短时间内学会单片机。

学习单片机的最有效方法是理论与实践并重

对一个初学单片机的人来说,如果按教科书式的学法,上来就是一大堆指令、名词,学了半天还搞不清这些指令起什么作用,能够产生什么实际效果,那么也许用不了几天就会觉得枯燥乏味而半途而废。所以学习与实践结合是一个好方法,边学习、边演练,循序渐进,这样用不了几次就能将用到的指令理解、吃透、扎根于脑海,甚至“根深蒂固”。也就是说,当你此次学习完某几条指令后(一次数量不求多,只求懂),接下去就该做实验了,通过实验,使你感受刚才的指令产生了控制效果,眼睛看得见(灯光)、耳朵听得到(声音),更能深刻理解指令是怎样转化成信号去控制电子产品的。说句过分的话,单片机与其说是学出来的,还不如说是做实验练出来的,何况做实验本身也是一种学习过程。因此边学边练的学习方法,效果特别好,许多读者经3~6个月的学习已能开发简单的产品了(如霓红灯广告牌控制、累加计数器等)。

学习单片机要合理安排学习时间持之以恒

学习单片机可不能“三天打鱼、二天晒网”,要有持之以恒的毅力与决心,学习完几条指令后,就应及时做实验,融会贯通,而不要等几天或几个星期有时间后再做实验,这样效果不好甚至前学后忘。另外要有打“持久战”的心理准备,不要兴趣来时学上几天,无兴趣时凉上几星期。学习单片机很重要的一点就是持之以恒。

学习单片机要使用循环学习法使之根深蒂固

笔者曾在其它刊物举办过《手把手教你学单片机》讲座,该讲座入门起点低,很多朋友觉得好学、易学,很快就能将讲座从头至尾学完、学懂,但过了几个月,在开发产品时对指令的具体作用就有些淡忘了。根据现代科学的研究,对只短暂学过一遍的知识,充其量只比浮光掠影稍好。因此,较好的方法是,过一段时间后(1~2个月)再重新做一遍,这样反复循环几次就能彻底弄懂消化,永不忘却。有道是:若人生能细看《水浒传》10遍,那么里面的故事内容、人物场情将永生不忘。

学习单片机要进行适当投资购买实验器材及书籍资料

单片机技术是一门含金量高的技术,一旦学会后,它给你带来的效益回报当然也高,无论是应聘求职还是自起炉灶开厂办公司,其前景是光明无限。因此在学习时要舍得适当投资购买必要的学习、实验器材,另外还要经常去科技图书店看看,购买一些适合自己学习、提高的书籍。总之,春天不播种哪来秋天的收获?考虑到学习成本,对初学者可采用“程序完成后软件仿真→单片机烧录程序→试验板通电实验”的方法(现在的快闪型单片机其程序可烧写1000次以上),这样整套实验器材(不包括PC机)只有几百元,对大部分已工作的爱好者来说都有这个能力承受。而经济条件较好的读者可考虑使用在线仿真器(ICE)进行实验,这样学习时直观性更好。

总之这里所谈的就是作者的亲身体验。我们希望以最实用的方法,最易入门的手法,将初学者领进单片机世界的大门里,使这些仅稍懂硬件原理的人通过实践能理解软件的作用,让他们知道在单片机组成的系统中硬件与软件的区分并不绝对,硬件能做的工作一般情况下软件也能完成,软件的功能也可用硬件替代。等初步学会了单片机软件设计后,可将通常由硬件完成的工作交由软件实现,这样,系统的体积、功耗、成本将大大降低,而功能得到提升与增强,使习惯于传统电路设计的人对单片机产生一种妙不可言的相见恨晚之感,感觉到真正找到了一种理想化的器件,真正感受、体会到现代单片微型计算机的强大作用,从而投身于单片机的领域中。只要你肯努力、下功夫、多实践,一定会成功的。

文章来源: 玩转单片机

围观 321

一、GPIO简介

I/O(Input/Output)接口是一颗微控制器必须具备的最基本外设功能。通常在ARM里,所有I/O都是通用的,称为GPIO(General Purpose Input/Output)。每个GPIO端口包含8个管脚,如PA端口是PA0~PA7。GPIO模块支持多个可编程输入/输出管脚(具体取决于与GPIO复用的外设的使用情况)。GPIO模块包含以下特性:

1)可编程控制GPIO中断

a. 屏蔽中断发生

b. 边沿触发(上升沿、下降沿、双边沿)

c. 电平触发(高电平、低电平)

2)输入/输出可承受5V

3)在读和写操作中通过地址线进行位屏蔽

4)可编程控制GPIO管脚配置:

a. 弱上拉或弱下拉电阻

b. 2mA、4mA、8mA驱动,以及带驱动转换速率(Slew Rate)控制的8mA驱动

c. 开漏使能

d. 数字输入使能

二、GPIO的各种模式

GPIO管脚可以被配置为多种工作模式,其中有3种比较常用:高阻输入、推挽输出、开漏输出。

1、高阻输入(Input)

图1.1 GPIO高阻输入模式结构示意图

为减少信息传输线的数目,大多数计算机中的信息传输线采用总线形式,即凡要传输的同类信息都在同一组传输线,且信息是分时传送的。在计算机中一般有三组总线,即数据总线、地址总线和控制总线。为防止信息相互干扰,要求凡挂到总线上的寄存器或存储器等,它的输入输出端不仅能呈现0、1两个信息状态,而且还应能呈现第三个状态----高阻抗状态,即此时好像它们的输出被开关断开,对总线状态不起作用,此时总线可由其他器件占用。三态缓冲器即可实现上述功能,它除具有输入输出端之外,还有一控制端。

如图1.1所示,为GPIO管脚在高阻输入模式下的等效结构示意图。这是一个管脚的情况,其它管脚的结构也是同样的。输入模式的结构比较简单,就是一个带有施密特触发输入(Schmitt-triggered input)的三态缓冲器(U1),并具有很高的输入等效阻抗。施密特触发输入的作用是能将缓慢变化的或者是畸变的输入脉冲信号整形成比较理想的矩形脉冲信号。执行GPIO管脚读操作时,在读脉冲(Read Pulse)的作用下会把管脚(Pin)的当前电平状态读到内部总线上(Internal Bus)。在不执行读操作时,外部管脚与内部总线之间是隔离的。

2、推挽输出(Output)

图1.2 GPIO推挽输出模式结构示意图

推挽输出原理:在功率放大器电路中大量采用推挽放大器电路,这种电路中用两只三极管构成一级放大器电路,两只三极管分别放大输入信号的正半周和负半周,即用一只三极管放大信号的正半周,用另一只三极管放大信号的负半周,两只三极管输出的半周信号在放大器负载上合并后得到一个完整周期的输出信号。

推挽放大器电路中,一只三极管工作在导通、放大状态时,另一只三极管处于截止状态,当输入信号变化到另一个半周后,原先导通、放大的三极管进入截止,而原先截止的三极管进入导通、放大状态,两只三极管在不断地交替导通放大和截止变化,所以称为推挽放大器(armjishu.com)。

如图1.2所示,为GPIO管脚在推挽输出模式下的等效结构示意图。U1是输出锁存器,执行GPIO管脚写操作时,在写脉冲(Write Pulse)的作用下,数据被锁存到Q和/Q。T1和T2构成CMOS反相器,T1导通或T2导通时都表现出较低的阻抗,但T1和T2不会同时导通或同时关闭,最后形成的是推挽输出。在推挽输出模式下,GPIO还具有回读功能,实现回读功能的是一个简单的三态门U2。注意:执行回读功能时,读到的是管脚的输出锁存状态,而不是外部管脚Pin的状态。

3、开漏输出(OutputOD)

图1.3 GPIO开漏输出结构示意图

如图1.3所示,为GPIO管脚在开漏输出模式下的等效结构示意图。开漏输出和推挽输出相比结构基本相同,但只有下拉晶体管T1而没有上拉晶体管。同样,T1实际上也是多组可编程选择的晶体管。开漏输出的实际作用就是一个开关,输出“1”时断开、输出“0”时连接到GND(有一定内阻)。回读功能:读到的仍是输出锁存器的状态,而不是外部管脚Pin的状态。因此开漏输出模式是不能用来输入的。

开漏输出结构没有内部上拉,因此在实际应用时通常都要外接合适的上拉电阻(通常采用4.7~10kΩ)。开漏输出能够方便地实现“线与”逻辑功能,即多个开漏的管脚可以直接并在一起(不需要缓冲隔离)使用,并统一外接一个合适的上拉电阻,就自然形成“逻辑与”关系。开漏输出的另一种用途是能够方便地实现不同逻辑电平之间的转换(如3.3V到5V之间),只需外接一个上拉电阻,而不需要额外的转换电路。典型的应用例子就是基于开漏电气连接的I2C总线。

4、钳位二极管

GPIO内部具有钳位保护二极管,如图1.4所示。其作用是防止从外部管脚Pin输入的电压过高或者过低。VDD正常供电是3.3V,如果从Pin输入的信号(假设任何输入信号都有一定的内阻)电压超过VDD加上二极管D1的导通压降(假定在0.6V左右),则二极管D1导通,会把多于的电流引到VDD,而真正输入到内部的信号电压不会超过3.9V。同理,如果从Pin输入的信号电压比GND还低,则由于二极管D2的作用,会把实际输入内部的信号电压钳制在-0.6V左右。

图1.4 GPIO钳位二极管示意图

假设VDD=3.3V,GPIO设置在开漏模式下,外接10kΩ上拉电阻连接到5V电源,在输出“1”时,我们通过测量发现:GPIO管脚上的电压并不会达到5V,而是在4V上下,这正是内部钳位二极管在起作用。虽然输出电压达不到满幅的5V,但对于实际的数字逻辑通常3.5V以上就算是高电平了(armjishu.com)。

图1.5 解决开漏模式上拉电压不足的方法

如果确实想进一步提高输出电压,一种简单的做法是先在GPIO管脚上串联一只二极管(如1N4148),然后再接上拉电阻。参见图1.5,框内是芯片内部电路。向管脚写“1”时,T1关闭,在Pin处得到的电压是3.3+VD1+VD3=4.5V,电压提升效果明显;向管脚写“0”时,T1导通,在Pin处得到的电压是VD3=0.6V,仍属低电平

围观 1206

由于单片机的性能同电脑的性能是天渊之别的,无论从空间资源上、内存资源、工作频率,都是无法 与之比较的。PC 机编程基本上不用考虑空间的占用、内存的占用的问题,最终目的就是实现功能就可以了。

对于单片机来说就截然不同了,一般的单片机的Flash 和Ram 的资源是以KB 来衡量的,可想而知,单片 机的资源是少得可怜,为此我们必须想法设法榨尽其所有资源,将它的性能发挥到最佳,程序设计时必须 遵循以下几点进行优化:

1、使用尽量小的数据类型

能够使用字符型(char)定义的变量,就不要使用整型(int)变量来定义;能够使用整型变量定义的变 量就不要用长整型(long int),能不使用浮点型(float)变量就不要使用浮点型变量。当然,在定义变 量后不要超过变量的作用范围,如果超过变量的范围赋值,C 编译器并不报错,但程序运行结果却错了, 而且这样的错误很难发现。

2、使用自加、自减指令

通常使用自加、自减指令和复合赋值表达式(如a-=1 及a+=1 等)都能够生成高质量的 程序代码,编译器通常都能够生成inc 和dec 之类的指令,而使用a=a+1 或a=a-1 之类 的指令,有很多C 编译器都会生成二到三个字节的指令。

3、减少运算的强度

可以使用运算量小但功能相同的表达式替换原来复杂的的表达式。

(1) 求余运算

N= N %8 可以改为N = N &7

说明:位操作只需一个指令周期即可完成,而大部分的C 编译器的“%”运算均是调用子程序来完成,代码长、执行速度慢。通常,只要求是求2n 方的余数,均可使用位操作的方法来代替。

(2) 平方运算

N=Pow(3,2) 可以改为N=3*3

说明:在有内置硬件乘法器的单片机中(如51 系列),乘法运算比求平方运算快得多, 因为浮点数 的求平方是通过调用子程序来实现的,乘法运算的子程序比平方运算的子程序代码短,执行速度快。

(3) 用位移代替乘法除法

N=M*8 可以改为N=M<<3
N=M/8 可以改为N=M>>3

说明:通常如果需要乘以或除以2n,都可以用移位的方法代替。如果乘以2n,都可以生成左移 的代码,而乘以其它的整数或除以任何数,均调用乘除法子程序。用移位的方法得到代码比调用乘除法子 程序生成的代码效率高。实际上,只要是乘以或除以一个整数,均可以用移位的方法得到结果。如N=M*9 可以改为N=(M<<3)+M;

(4) 自加自减的区别

例如我们平时使用的延时函数都是通过采用自加的方式来实现。

void DelayNms(UINT16 t)
{
UINT16 i,j;
for(i=0;i for(j=0;i<1000;j++)
}

可以改为

void DelayNms(UINT16 t)
{
UINT16 i,j;
for(i=t;i>=0;i--)
for(j=1000;i>=0;j--)
}

说明:两个函数的延时效果相似,但几乎所有的C 编译对后一种函数生成的代码均比前一种代码少1~3 个字节,因为几乎所有的 MCU 均有为0 转移的指令,采用后一种方式能够生成这类指令。

4、while 与do...while 的区别

void DelayNus(UINT16 t)
{
while(t--)
{
NOP();
}
}
可以改为
void DelayNus(UINT16 t)
{
do
{
NOP();
}while(--t)
}

说明:使用do…while 循环编译后生成的代码的长度短于while 循环。

5、register 关键字

void UARTPrintfString(INT8 *str)
{
while(*str && str)
{
UARTSendByte(*str++)
}
}

可以改为

void UARTPrintfString(INT8 *str)
{
register INT8 *pstr=str;
while(*pstr && pstr)
{
UARTSendByte(*pstr++)
}
}

说明:在声明局部变量的时候可以使用register 关键字。这就使得编译器把变量放入一个多用途的寄存器中,而不是在堆栈中,合理使用这种方法可以提高执行速度。函数调用越是频繁,越是可能提高代码的速度,注意register 关键字只是建议编译器而已。

6、volatile 关键字

volatile 总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以死代码消除。一般来说,volatile 关键字只用在以下三种情况:

a) 中断服务函数中修改的供其它程序检测的变量需要加volatile(参考本书高级实验程序)

b) 多任务环境下各任务间共享的标志应该加volatile

c)存储器映射的硬件寄存器通常也要加volatile 说明,因为每次对它的读写都可能由不同意义 。

总之,volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

7、以空间换时间

在数据校验实战当中,CRC16 循环冗余校验其实还有一种方法是查表法,通过查表可以更加快获得校验值,效率更高,当校验数据量大的时候,使用查表法优势更加明显,不过唯一的缺点是占用大量的空间。

//查表法:

code UINT16 szCRC16Tbl[256] = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};
UINT16 CRC16CheckFromTbl(UINT8 *buf,UINT8 len)
{
UINT16 i;
UINT16 uncrcReg = 0, uncrcConst = 0xffff;
for(i = 0;i < len;i ++)
{
uncrcReg = (uncrcReg << 8) ^ szCRC16Tbl[(((uncrcConst ^ uncrcReg) >> 8)
^ *buf++) & 0xFF];
uncrcConst <<= 8;
}
return uncrcReg;
}

如果系统要求实时性比较强,在CRC16 循环冗余校验当中,推荐使用查表法,以空间换时间。

8、宏函数取代函数

首先不推荐所有函数改为宏函数,以免出现不必要的错误。但是一些基本功能的函数很有必要使用宏
函数来代替。

UINT8 Max(UINT8 A,UINT8 B)
{
return (A>B?A:B)
}

可以改为

#define MAX(A,B) {(A)>(B)?(A):(B)}

说明:函数和宏函数的区别就在于,宏函数占用了大量的空间,而函数占用了时间。大家要知道的是,函数调用是要使用系统的栈来保存数据的,如果编译器里有栈检查选项,一般在函数的头会嵌入一些汇编语句对当前栈进行检查;同时,cpu 也要在函数调用时保存和恢复当前的现场,进行压栈和弹栈操作,所以,函数调用需要一些cpu 时间。而宏函数不存在这个问题。宏函数仅仅作为预先写好的代码嵌入到当前程序,不会产生函数调用,所以仅仅是占用了空间,在频繁调用同一个宏函数的时候,该现象尤其突出。

9、适当地使用算法

假如有一道算术题,求1~100 的和。

作为程序员的我们会毫不犹豫地点击键盘写出以下的计算方法:

UINT16 Sum(void)
{
UINT8 i,s;
for(i=1;i<=100;i++)
{
s+=i;
}
return s;
}

很明显大家都会想到这种方法,但是效率方面并不如意,我们需要动脑筋,就是采用数学算法解决问题,
使计算效率提升一个级别。

UINT16 Sum(void)
{
UINT16 s;
s=(100 *(100+1))>>1;
return s;
}

结果很明显,同样的结果不同的计算方法,运行效率会有大大不同,所以我们需要最大限度地通过数
学的方法提高程序的执行效率。

10、用指针代替数组

在许多种情况下,可以用指针运算代替数组索引,这样做常常能产生又快又短的代码。与数组索引相 比,指针一般能使代码速度更快,占用空间更少。使用多维数组时差异更明显。下面的代码作用是相同的, 但是效率不一样。

UINT8 szArrayA[64];
UINT8 szArrayB[64];
UINT8 i;
UINT8 *p=szArray;
for(i=0;i<64;i++)szArrayB[i]=szArrayA[i];
for(i=0;i<64;i++)szArrayB[i]=*p++;

指针方法的优点是,szArrayA 的地址装入指针p 后,在每次循环中只需对p 增量操作。在数组索引 方法中,每次循环中都必须进行基于i 值求数组下标的复杂运算。

11、强制转换

C 语言精髓第一精髓就是指针的使用,第二精髓就是强制转换的使用,恰当地利用指针和强制转换不但可以提供程序效率,而且使程序更加之简洁,由于强制转换在C 语言编程中占有重要的地位,下面将已五个比较典型的例子作为讲解。

例子1:将带符号字节整型转换为无符号字节整型

UINT8 a=0;
INT8 b=-3;
a=(UINT8)b;

例子2:在大端模式下(8051 系列单片机是大端模式),将数组a[2]转化为无符号16 位整型值。

方法1:采用位移方法。

UINT8 a[2]={0x12,0x34};
UINT16 b=0;
b=(a[0]<<8)|a[1];

结果:b=0x1234

方法2:强制类型转换。

UINT8 a[2]={0x12,0x34};
UINT16 b=0;
b= *(UINT16 *)a; //强制转换

结果:b=0x1234

例子3:保存结构体数据内容。

方法1:逐个保存。

typedef struct _ST
{
UINT8 a;
UINT8 b;
UINT8 c;
UINT8 d;
UINT8 e;
}ST;
ST s;
UINT8 a[5]={0};
s.a=1;
s.b=2;
s.c=3;
s.d=4;
s.e=5;
a[0]=s.a;
a[1]=s.b;
a[2]=s.c;
a[3]=s.d;
a[4]=s.e;

结果:数组a 存储的内容是1、2、3、4、5。

方法2:强制类型转换。

typedef struct _ST
{
UINT8 a;
UINT8 b;
UINT8 c;
UINT8 d;
UINT8 e;
}ST;
ST s;
UINT8 a[5]={0};
UINT8 *p=(UINT8 *)&s;//强制转换
UINT8 i=0;
s.a=1;
s.b=2;
s.c=3;
s.d=4;
s.e=5;
for(i=0;i {
a[i]=*p++;
}

结果:数组a 存储的内容是1、2、3、4、5。

例子4:在大端模式下(8051 系列单片机是大端模式)将含有位域的结构体赋给无符号字节整型值

方法1:逐位赋值。

typedef struct __BYTE2BITS
{
UINT8 _bit7:1;
UINT8 _bit6:1;
UINT8 _bit5:1;
UINT8 _bit4:1;
UINT8 _bit3:1;
UINT8 _bit2:1;
UINT8 _bit1:1;
UINT8 _bit0:1;
}BYTE2BITS;
BYTE2BITS Byte2Bits;
Byte2Bits._bit7=0;
Byte2Bits._bit6=0;
Byte2Bits._bit5=1;
Byte2Bits._bit4=1;
Byte2Bits._bit3=1;
Byte2Bits._bit2=1;
Byte2Bits._bit1=0;
Byte2Bits._bit0=0;
UINT8 a=0;
a|= Byte2Bits._bit7<<7;
a|= Byte2Bits._bit6<<6;
a|= Byte2Bits._bit5<<5;
a|= Byte2Bits._bit4<<4;
a|= Byte2Bits._bit3<<3;
a|= Byte2Bits._bit2<<2;
a|= Byte2Bits._bit1<<1;
a|= Byte2Bits._bit0<<0;

结果:a=0x3C

方法2:强制转换。

typedef struct __BYTE2BITS
{
UINT8 _bit7:1;
UINT8 _bit6:1;
UINT8 _bit5:1;
UINT8 _bit4:1;
UINT8 _bit3:1;
UINT8 _bit2:1;
UINT8 _bit1:1;
UINT8 _bit0:1;
}BYTE2BITS;
BYTE2BITS Byte2Bits;
Byte2Bits._bit7=0;
Byte2Bits._bit6=0;
Byte2Bits._bit5=1;
Byte2Bits._bit4=1;
Byte2Bits._bit3=1;
Byte2Bits._bit2=1;
Byte2Bits._bit1=0;
Byte2Bits._bit0=0;
UINT8 a=0;
a = *(UINT8 *)&Byte2Bits

结果:a=0x3C

例子5:在大端模式下(8051 系列单片机是大端模式)将无符号字节整型值赋给含有位域的结构体。

方法1:逐位赋值。

typedef struct __BYTE2BITS
{
UINT8 _bit7:1;
UINT8 _bit6:1;
UINT8 _bit5:1;
UINT8 _bit4:1;
UINT8 _bit3:1;
UINT8 _bit2:1;
UINT8 _bit1:1;
UINT8 _bit0:1;
}BYTE2BITS;
BYTE2BITS Byte2Bits;
UINT8 a=0x3C;
Byte2Bits._bit7=a&0x80;
Byte2Bits._bit6=a&0x40;
Byte2Bits._bit5=a&0x20;
Byte2Bits._bit4=a&0x10;
Byte2Bits._bit3=a&0x08;
Byte2Bits._bit2=a&0x04;
Byte2Bits._bit1=a&0x02;
Byte2Bits._bit0=a&0x01;

方法2:强制转换。

typedef struct __BYTE2BITS
{
UINT8 _bit7:1;
UINT8 _bit6:1;
UINT8 _bit5:1;
UINT8 _bit4:1;
UINT8 _bit3:1;
UINT8 _bit2:1;
UINT8 _bit1:1;
UINT8 _bit0:1;
}BYTE2BITS;
BYTE2BITS Byte2Bits;
UINT8 a=0x3C;
Byte2Bits= *(BYTE2BITS *)&a;

12、减少函数调用参数

使用全局变量比函数传递参数更加有效率。这样做去除了函数调用参数入栈和函数完成后参数出栈所需要的时间。然而决定使用全局变量会影响程序的模块化和重入,故要慎重使用。

13、 switch 语句中根据发生频率来进行case 排序

switch 语句是一个普通的编程技术,编译器会产生if-else-if 的嵌套代码,并按照顺序进行比较,发现匹配时,就跳转到满足条件的语句执行。使用时需要注意。每一个由机器语言实现的测试和跳转仅仅是为了决定下一步要做什么,就把宝贵的处理器时间耗尽。为了提高速度,没法把具体的情况按照它们发生的相对频率排序。换句话说,把最可能发生的情况放在第一位,最不可能的情况放在最后。

14、将大的switch 语句转为嵌套switch 语句

当switch 语句中的case 标号很多时,为了减少比较的次数,明智的做法是把大switch 语句转为嵌套switch 语句。把发生频率高的case 标号放在一个switch 语句中,并且是嵌套switch 语句的最外层,发生相对频率相对低的case 标号放在另一个switch 语句中。比如,下面的程序段把相对发生频率低的情况放在缺省的case 标号内。

UINT8 ucCurTask=1;
void Task1(void);
void Task2(void);
void Task3(void);
void Task4(void);
……………
void Task16(void);
switch(ucCurTask)
{
case 1: Task1();break;
case 2: Task2();break;
case 3: Task3();break;
case 4: Task4();break;
………………………
case 16: Task16();break;
default:break;
}
可以改为
UINT8 ucCurTask=1;
void Task1(void);
void Task2(void);
void Task3(void);
void Task4(void);
……………
void Task16(void);
switch(ucCurTask)
{
case 1: Task1();break;
case 2: Task2();break;
default:
switch(ucCurTask)
{
case 3: Task3();break;
case 4: Task4();break;
………………………
case 16: Task16();break;
default:break;
}
Break;
}

由于switch 语句等同于if-else-if 的嵌套代码,如果大的if 语句同样要转换为嵌套的if 语句。

UINT8 ucCurTask=1;
void Task1(void);
void Task2(void);
void Task3(void);
void Task4(void);
……………
void Task16(void);
if (ucCurTask==1) Task1();
else if(ucCurTask==2) Task2();
else
{
if (ucCurTask==3) Task3();
else if(ucCurTask==4) Task4();
………………
else Task16();
}

15、函数指针妙用

当switch 语句中的case 标号很多时,或者if 语句的比较次数过多时,为了提高程序执行速度,可以运用函数指针来取代switch 或if 语句的用法,这些用法可以参考电子菜单实验代码、USB 实验代码和网络实验代码。

UINT8 ucCurTask=1;
void Task1(void);
void Task2(void);
void Task3(void);
void Task4(void);
……………
void Task16(void);
switch(ucCurTask)
{
case 1: Task1();break;
case 2: Task2();break;
case 3: Task3();break;
case 4: Task4();break;
………………………
case 16: Task16();break;
default:break;
}

可以改为

UINT8 ucCurTask=1;
void Task1(void);
void Task2(void);
void Task3(void);
void Task4(void);
……………
void Task16(void);
void (*szTaskTbl)[16])(void)={Task1,Task2,Task3,Task4,…,Task16};

调用方法1:(*szTaskTbl[ucCurTask])();

调用方法2: szTaskTbl[ucCurTask]();

16、循环嵌套

循环在编程中经常用到的,往往会出现循环嵌套。现在就已for 循环为例。

UINT8 i,j;
for(i=0;i<255;i++)
{
for(j=0;j<25;j++)
{
………………
}
}

较大的循环嵌套较小的循环编译器会浪费更加多的时间,推荐的做法就是较小的循环嵌套较大的循环。

UINT8 i,j;
for(j=0;j<25;j++)
{
for(i=0;i<255;i++)
{
………………
}
}

17、内联函数

在C++中,关键字inline 可以被加入到任何函数的声明中。这个关键字请求编译器用函数内部的代码替换所有对于指出的函数的调用。这样做在两个方面快于函数调用。这样做在两个方面快于函数调用:

第一,省去了调用指令需要的执行时间;第二,省去了传递变元和传递过程需要的时间。但是使用这种方法在优化程序速度的同时,程序长度变大了,因此需要更多的ROM。使用这种优化在inline 函数频繁调用并且只包含几行代码的时候是最有效的。

如果编译器允许在C 语言编程中能够支持inline 关键字,注意不是C++语言编程,而且单片机的ROM 足够大,就可以考虑加上inline 关键字。支持inline 关键字的编译器如ADS1.2,RealView MDK 等。

18、从编译器着手

很多编译器都具有偏向于代码执行速度上的优化、代码占用空闲太小的优化。例如Keil 开发环境编译时可以选择偏向于代码执行速度上的优化(Favor Speed)还是代码占用空间太小的优化(Favor Size)。还有其他基于GCC 的开发环境一般都会提供-O0、-O1、-O2、—O3、-Os 的优化选项,而使用-O2 的优化代码执行速度上最理想,使用-Os 优化代码占用空间大小最小。

19、嵌入汇编---杀手锏

汇编语言是效率最高的计算机语言,在一般项目开发当中一般都采用C 语言来开发的,因为嵌入汇编之后会影响平台的移植性和可读性,不同平台的汇编指令是不兼容的。但是对于一些执着的程序员要求程序获得极致的运行的效率,他们都在C 语言中嵌入汇编,即“混合编程”。

注意:如果想嵌入汇编,一定要对汇编有深刻的了解。不到万不得已的情况,不要使用嵌入汇编

围观 1053

A/D器件和芯片是实现单片机数据采集的常用外围器件。A/D转换器的品种繁多、性能各异,在设计数据采集系统时,首先碰到的就是如何选择合适的 A/D转换器以满足系统设计要求的问题。选择A/D转换器件需要考虑器件本身的品质和应用的场合要求,基本上,可以根据以下几个方面的指标选择一个A/D 器件。

(1)A/D转换器位数

A/D转换器位数的确定,应该从数据采集系统的静态精度和动态平滑性这两个方面进行考虑。从静态精度方面来说,要考虑输入信号的原始误差传递到输出所产生的误差,它是模拟信号数字化时产生误差的主要部分。量化误差与A/D转换器位数有关。一般把8位以下的A/D转换器归为低分辨率A/D转换器,9~12位的称为中分辨率转换器,13位以上的称为高分辨率转换器。10位A/D芯片以下误差较大,11位以上对减小误差并无太大贡献,但对A/D转换器的要求却提得过高。因此,取10位或11位是合适的。由于模拟信号先经过测量装置,再经A/D转换器转换后才进行处理,因此,总的误差是由测量误差和量化误差共同构成的。A/D转换器的精度应与测量装置的精度相匹配。也就是说,一方面要求量化误差在总误差中所占的比重要小,使它不显著地扩大测量误差;另一方面必须根据目前测量装置的精度水平,对A/D转换器的位数提出恰当的要求。

目前,大多数测量装置的精度值不小于0.1%~0.5%,故A/D转换器的精度取0.05%~0.1%即可,相应的二进制码为10~11位,加上符号位,即为11~12位。当有特殊的应用时,A/D转换器要求更多的位数,这时往往可采用双精度的转换方案。

(2)A/D转换器的转换速率

A/D转换器从启动转换到转换结束,输出稳定的数字量,需要一定的转换时间。转换时间的倒数就是每秒钟能完成的转换次数,称为转换速率。

确定A/D转换器的转换速率时,应考虑系统的采样速率。例如,如果用转换时间为100us($1.6606)的A/D转换器,则其转换速率为10KHz。根据采样定理和实际需要,一个周期的波形需采10个样点,那么这样的A/D转换器最高也只有处理频率为1KHz的模拟信号。把转换时间减小,信号频率可提高。对一般的单片机而言,要在采样时间内完成A/D转换以外的工作,如读数据、再启动、存数据、循环计数等已经比较困难了。

(3)采样/保持器

采集直流和变化非常缓慢的模拟信号时可不用采样保持器。对于其他模拟信号一般都要加采样保持器。如果信号频率不高,A/D转换器的转换时间短,即采样高速A/D时,也可不用采样/保持器。

(4)A/D转换器量程

A/D转换时需要的是双极性的,有时是单极性的。输入信号最小值有的从零开始,也有从非零开始的。有的转换器提供了不同量程的引脚,只有正确使用,才能保证转换精度。在使用中,影响A/D转换器量程的因素有:量程变换和双极性偏置;双基准电压;A/D转换器内部比较器输入端的正确使用。

(5)满刻度误差

满度输出时对应的输入信号与理想输入信号值之差。

(6)线性度

实际转换器的转移函数与理想直线的最大偏移。

围观 405

单片机控制系统必须具有较高的灵敏度,但是灵敏度越高越容易把干扰引入系统中, 因此抗干扰技术己成为单片机控制系统设计时必须考虑的环节。本文分析了单片机控制系统干扰的主要来源,介绍了印制电路板中地线和电源线的布线方法,从硬件和软件两个方面阐述了抗干扰设计。这些抗干扰方法实际应用中取得了良好的效果,使一些单片机控制系统在现场成功运行。

引言

单片机组成的控制系统必须具有较高的灵敏度,灵敏度越高,更容易把干扰引入系统中。在强噪声背景下,被测信号往往被淹没,使测量无法进行。在工业现场的应用中,存在多种干扰源,它们以一种或者多种方式作用于计算机测控系统,对系统产生强烈的干扰,往往使系统的性能指标偏离设计要求,导致错误结果,因此抗干扰技术己成为单片机控制系统设计开始时就必须考虑的环节。

本文分析了单片机控制系统干扰的主要来源,从硬件和软件两个方面阐述了抗干扰设计。

1 、系统干扰源分析

1. 1 现场干扰源

电磁干扰可划分为传导与辐射两类。传导类型的干扰是可以通过金属、(分布)电感、(分布)电容和变压器进行传播的;而辐射类型的干扰则以多种途径向外传播,如设备的外壳及外壳上的缝隙,设备间的连接电缆,甚至一根导线本身也可以成为辐射类型干扰的传播途径。另外传导干扰和辐射干扰常常是伴生的,并且在干扰吸收上可以相互转化。

电磁干扰进入测控系统的途径是“ 场” ,即电磁干扰源的能量通过电场或磁场的形式传递给测控系统。电场途径干扰的实质是电容性耦合干扰,干扰信号通过导线或电路的分布电容进入测控系统;磁场途径干扰的实质是互感性耦合干扰,干扰信号通过导线或电路之间的互感耦合进入测控系统。

1. 2 系统自身干扰源

通常为与外部干扰相区别, 把系统内部由器件、材料、部件的物理因素产生的自然扰动称为噪声。如果在系统设计时对某些问题考虑不全面, 如元器件布局不合理、元器件间连线不合理以及元器件质量差等原因,都会造成测控系统自身干扰源。在单片机控制系统中,主要噪声类型有如下几种。

(1)按产生的原因

①热噪声:热噪声是指任何电阻即使不与电源相连,在它的两端也存在着微弱的电压。这种由于电子的热运动而出现在电阻两端的噪声电压称为热噪声。热噪声电压与热力学温度、带宽和电阻值的平均根成正比例。

②散粒噪声:散粒噪声存在于半导体元件中。在半导体内, 散粒噪声是通过晶体管基区载流子的随机扩散以及电子—空穴随机发生复合而形成的。

③接触噪声:接触噪声是由两种材料之间不完全接触,从而形成电导率的起伏而产生的,它发生在两个导体连接的地方。接触噪声正比于直流电流, 其功率密度正比于频率f 的倒数。在低频电路中,接触噪声是重要的噪声源。

(2)按传导模式

①常模噪声(normal mode noise),又称线间感应噪声或对称噪声。如图1(a)所示, 噪声往返于两条线路间,N为噪声源, R 为受扰设备,UN 为噪声电压,噪声电流IN 和信号电流I S 的路径往返两条线上是一致的。这种噪声难以除掉。

②共模噪声(common mode noise),又叫地感应噪声、纵向噪声或不对称噪声。如图1(b)所示,噪声侵入线路和地线间。噪声电流在两条线上各流过一部分,以地为公共回路,而信号电流只在往返两条线路中流过。从本质上讲,这种噪声是可以除掉的。但是由于线路的不平衡状态,共模噪声会转换成常模噪声。可用图1(c)来说明共模噪声转换成常模噪声的原理。

图1 常模噪声和共模噪声

在图1(c)中,N 为噪声源, L 为负载,Z1 和Z2 是导线1和导线2 的对地阻抗。如果Z1 =Z2 , 则噪声电压VN1 和噪声电压VN2 相等,从而噪声电流IN1 和IN2 不流过负载。然而当Z1 ≠Z2 时,则VN1 ≠VN2 ,从而IN1 ≠IN2 ,于是VN1 -VN2 =VN,VN /Z L =IN (ZL 为负载阻抗),这是常模噪声。因此,当发现常模噪声时,首先考虑它是否由于电路不平衡状态而从共模噪声转换来的。通常,输入输出线与大地或机壳之间发生的噪声都是共模噪声,信号线受到静电感应时产生的噪声也多为共模噪声。抑制共模噪声的方法很多,如屏蔽、接地、隔离等。抗干扰技术在很多方面都是围绕共模噪声来研究其有效的抑制措施。

2、印制电路板中地线和电源线的布线方法

印制电路板是微机系统中器件、信号线、电源线的高密度集合体,印制电路板布线的好坏对抗干扰能力影响很大。对于双面板,地线和电源线布置特别讲究,通过采用单点接地法,电源和地是从电源的两端接到印制电路板上来的, 电源一个接点,地一个接点。印制电路板上,要有多个返回地线,这些都会聚到回电源的那个接点上,就是所谓单点接地。下面主要介绍地线和电源线布置。

2. 1 地线布置

为了抑制地线阻抗噪声,地线的布置通常遵循以下3个原则。

(1)地线尽量粗,如果地线很细,则地线电阻将会较大,造成接地电位随电流的变化而变化,致使信号电平不稳,导致电路的抗干扰能力下降。在布线空间允许的情况下,要保证主要地线宽度至少在2 ~ 3 mm 以上。

(2)接地线构成闭环形式,能明显提高抗噪声能力。其原因在于:印制电路板上有很多集成电路元件,尤其遇
到耗电多的元件时,因受接地线粗细的限制,会在地结上产生较大的电位差,引起抗噪声能力下降;若将接地构成环路,则会缩小电位差值,提高电子设备的抗噪声能力。

(3)地线设计采用分区集中并联一点接地,当同一印制电路板上有多个功能不同的电路时,可将同一功能单元
的元器件集中于一点接地,自成独立回路。这就可使地线电流不会流到其他单元的电路中去,避免了对其他单元的干扰。D /A 、A /D 转换电路中要特别注意地线的正确连接,否则干扰将很严重。D /A 、A /D 芯片及采样保持芯片均提供了独立的数字地和模拟地, 分别有相应的引脚。在线路设计中,必须将所有器件的数字地和模拟地分别相连,但数字地和模拟地仅在一点上相连。应特别注意,在全部电路中的数字地和模拟地仅仅连在一点上,在芯片和其他电路中不可再有公共点。图2 是地线的正确连接方法。

图2 正确的地线接线

2. 2 电源线布置

电源线的布线要根据电流的大小,应在电路板的器件面和底面布线成90°,还要尽量加大导线宽度,采取电源
线、地线的走线方向与数据线的走线方向一致,减少存在噪声的单元和其他单元之间公共电源阻抗,有助于增强抗噪声能力。

3 、硬件抗干扰设计

系统硬件电路性能的好坏直接影响整个系统工作质量,应用硬件抗干扰措施是经常采用的一种有效方法。通过合理的硬件电路设计可以削弱或抑制绝大部分干扰。在单片机控制系统硬件抗干扰设计中,可以采用以下几种抗干扰措施。

3. 1 去耦电容配置

数字电路除了地线阻抗问题外,还存在电源线的阻抗问题。当数字电路受到高速跳变电流的作用时,也将产生阻抗噪声。可以在每一块集成电路芯片的要去耦的电源和地之间跨接去耦电容,以便随时充放电,一般选用0. 1 μF的独石电容。

3. 2 数字输入端的噪声抑制

数字电路输入端最危险的是脉冲噪声。因此抑制脉冲噪声是数字设备电磁兼容性设计着重考虑的因素。可以采用的方法有:在输入端接RC 滤波器和施密特集成电路,其中RC 滤波器的时间常数大于现场可能出现噪声的最大脉宽和小于信号宽度,这样既可抑制噪声,也不会丢失信号。在输入端通过加上拉电阻以及提高供电电源电压等措施提高输入端的电平来提高输入端的噪声容限。

而提高输出低电平的噪声容限则采用降低信号源内阻的方法,如使用放大倍数为1 的电压跟随器。三态数据缓冲器的低电平输出阻抗很低,还可以使用三态数据缓冲器,经过三态数据缓冲器驱动之后的信号具有较好的抑制低电平噪声能力。

为了防止工作现场强电磁干扰或工频电压通过输出通道反串到测控系统,主要考虑采用光电隔离技术,它以光为介质进行间接耦合,使夹杂在输入开关量中的各种干扰电磁脉冲挡在输入回路的一侧,因此具有较高的电气隔离和抗干扰能力。

3. 3 数字电路不用端的处理

当数字电路的输入端有多余而被闲置时,与高电平“1”的输入逻辑状态一致。但开路的输入端具有很高的输入阻抗,容易受到外部的电磁干扰,使悬浮端的电平有时处于“1”和“0”的过渡状态,引起逻辑电路的误导通。为保证系统运行安全,采用的方法有:(1)将不用的输入端固定在高电平上;(2)将不使用端与有用信号输入端并联接在一起。

3. 4 外围扩展存储器系统抗干扰处理方法

控制系统中配置的程序存储器及数据存储器芯片的信息电流大、工作频率高,设计时要着重考虑外界电磁干扰。主要是印制板电路中的抗干扰设计,可以采用的方法如下所述。

(1)数据线、地址线、控制线要尽量短,以减少对地产生的电容。特别考虑各条地址线的长短,布线方式应尽量一致,以免造成各线的阻抗差异过大,使地址信号在传输过程中到达终端时波形差异过大,形成控制信息的非同步干扰。

(2)由于开关噪声严重,因此考虑在电源的入口处,以及存储器芯片的VCC 和GND 之间接入去耦电容。

(3)由于负载的电流较大,因此电源线和地线要尽量加粗,走线尽量短。同时,印制板两面的三总线相互垂直,以防止总线之间的电磁干扰。

(4)在总线的始端和终端加上适合的上拉电阻,可以提高高电平的噪声容限,增加存储器端口在高阻状态下的
抗干扰能力和削弱反射波的干扰。

4 、软件抗干扰技术

窜入微机测控系统的干扰,其频谱往往很宽,且具有随机性,采用硬件抗干扰措施,只能抑制某个频率段的干
扰,仍有一些干扰会侵入系统 。因此,仅采取硬件抗干扰方法是不够的。单片机控制系统依赖于程序的执行来
完成数据采集和其他各种功能。一个细微的故障, 都有可能使程序跑飞或进入死循环,给系统带来不可预料的后果。因此采取软件抗干扰是十分必要的。软件抗干扰以其设计灵活、节省硬件资源、可靠性好越来越受到重视,为使程序混乱时重新步入正轨,程序设计中主要采取了以下几种方法。

4. 1 软件滤波算法

采用此种方法可以滤掉大部分由输入信号干扰而引起的采集错误。最常用的方法有算术平均值法、比较舍取
法、中值法、一阶递推数字滤波法。可以根据被测信号的特点,在不影响系统效率的情况下将多次采集的数据去掉一个最大值,去掉一个最小值,其余数据取平均值。这种方法大大增加了数据可靠性。

4. 2 指令冗余技术

如果单片机受到干扰的影响,程序寄存器PC 不是按正常情况下先取操作码,再取操作数,而是指向错误的字
节,将操作数当作操作码,程序将出错。只要在双字节指令和3 字节指令后加入几条单字节指令或将有效单字节指令重写就可以将PC 值纳入正轨。因为空操作指令(NOP)是单字节的,当程序跑飞到某条单字节指令上时,就不会发生将操作数当成指令来执行的错误,可确保这些指令正确执行。

4. 3 设置软件陷阱

当乱飞程序进入非程序区, 冗余指令便无法起作用。利用一条无条件跳转指令强行将捕获的程序空间引向复
位地址进行处理:

NOP
NOP
LJMP 0000H

软件陷阱安置在:

(1)中断向量区,干扰因素的存在可以激活不希望出现的中断,所以在放大板单片机中断向量区加入软件陷
阱,以防止因中断而造成混乱;

(2)程序区及大量未使用的ROM 空间,程序区由于有大量的程序组成,不能随意设置陷阱,否则, 正常执行的程序也将陷入进去。程序区的陷阱应设置在程序执行的断裂点处。例如。可以在LJMP 、SJMP 、RET 等指令处设置陷阱。考虑到程序存贮器的容量,软件陷阱一般1 KB 空间有2 ~ 3 个就可以进行有效拦截。

在采用以上这些措施时,应综合考虑系统程序的执行效率,以取得最好的运行效果。

5、结束语

综上所述,抗干扰设计是单片机控制系统设计的重要环节, 其设计的好坏往往决定整个系统的成败。本文介绍
了印制电路板中地线和电源线的布线方法,从硬件和软件两个方面探讨了一些提高抗干扰能力的方法。这些方法有效可行, 在剑杆织机电子送经/电子卷取控制系统、生物医学信号数据采集系统等的实际应用中取得了良好的效果,使系统在现场成功运行。

围观 359

在单片机应用开发中,代码的使用效率问题、单片机抗干扰性和可靠性等问题仍困扰着。现归纳出单片机开发中应掌握的几个基本技巧。

1、如何减少程序中的bug:

对于如何减少程序的bug,应该先考虑系统运行中应考虑的超范围管理参数如下。物理参数:这些参数主要是系统的输入参数,它包括激励参数、采集处理中的运行参数和处理结束的结果参数。资源参数:这些参数主要是系统中的电路、器件、功能单元的资源,如记忆体容量、存储单元长度、堆叠深度。应用参数:这些应用参数常表现为一些单片机、功能单元的应用条件。过程参数:指系统运行中的有序变化的参数。

2、如何提高C语言编程代码的效率:

用C语言进行单片机程序设计是单片机开发与应用的必然趋势。如果使用C编程时,要达到最高的效率,最好熟悉所使用的C编译器。先试验一下每条C语言编译以后对应的汇编语言的语句行数,这样就可以很明确的知道效率。在今后编程的时候,使用编译效率最高的语句。各家的C编译器都会有一定的差异,故编译效率也会有所不同,优秀的嵌入式系统C编译器代码长度和执行时间仅比以汇编语言编写的同样功能程度长5-20%。对于复杂而开发时间紧的项目时,可以采用C语言,但前提是要求你对该MCU系统的C语言和C编译器非常熟悉,特别要注意该C编译系统所能支持的数据类型和算法。虽然C语言是最普遍的一种高级语言,但由于不同的MCU厂家其C语言编译系统是有所差别的,特别是在一些特殊功能模块的操作上。所以如果对这些特性不了解,那么调试起来问题就会很多,反而导致执行效率低于汇编语言。

3、如何解决单片机的抗干扰性问题:

防止干扰最有效的方法是去除干扰源、隔断干扰路径,但往往很难做到,所以只能看单片机抗干扰能力够不够强了。在提高硬件系统抗干扰能力的同时,软件抗干扰以其设计灵活、节省硬件资源、可靠性好越来越受到重视。单片机干扰最常见的现象就是复位;至于程序跑飞,其实也可以用软件陷阱和看门狗将程序拉回到复位状态;所以单片机软件抗干扰最重要的是处理好复位状态。一般单片机都会有一些标志寄存器,可以用来判断复位原因;另外你也可以自己在RAM中埋一些标志。在每次程序复位时,通过判断这些标志,可以判断出不同的复位原因;还可以根据不同的标志直接跳到相应的程序。这样可以使程序运行有连续性,用户在使用时也不会察觉到程序被重新复位过。

4、如何测试单片机系统的可靠性:

当一个单片机系统设计完成,对于不同的单片机系统产品会有不同的测试项目和方法,但是有一些是必须测试的:测试单片机软件功能的完善性;上电、掉电测试;老化测试;ESD和EFT等测试。有时候,我们还可以模拟人为使用中,可能发生的破坏情况。例如用人体或者衣服织物故意摩擦单片机系统的接触端口,由此测试抗静电的能力。用大功率电钻靠近单片机系统工作,由此测试抗电磁干扰能力等。

综上所述,单片机已成为计算机发展和应用的一个重要方面,单片机应用的重要意义还在于,它从根本上改变了传统的控制系统设计思想和设计方法。从前必须由模拟电路或数字电路实现的大部分功能,现在已能用单片机通过软件方法来实现了。这种软件代替硬件的控制技术也称为微控制技术,是传统控制技术的一次革命。此外在开发和应用过程中我们更要掌握技巧,提高效率,以便于发挥它更加广阔的用途。

来源:网络

围观 448

外部复位(External Reset) 

它是影响时钟模块和所有内部电路,属于同步复位,但外部Reset引脚为逻辑低电平。在引脚变为低电平后,CPU的复位控制逻辑单元确认复位状态直到Reset释放。复位控制逻辑保持复位低电平状态,在额外512个时钟周期内。因为当复位引脚为低电平时与MCU执行复位命令是相互冲突的,因此复位引脚必须保证520时间周期内低电平才能保证外部复位被外部总线辨识出来。

上电复位(Power-on reset) 

它是由外部总线产生的一种异步复位。单片机在电源电压VDD小于大约2.5V的时候复位,只要VDD电压不超过这个阈值,单片机就仍然保持复位状态。 

电压跌落的时间大概在纳米级(如果一旦出现了,马上会复位)。因此监测上电复位不能单片机内部,因为小于这个电压单片机逻辑功能。  

低电压复位(Low-Voltage Reset) 

它是部分单片级内部监控器形成的异步复位,单片机电压小于一定触发值时,单片机开始复位。 低电压的复位电平是和供电电压相关的,会有一个波动: 

软件复位(Software Reset) 

它是由软件看门狗定时器超时引起的一个异步复位。如果要开启软件复位,必须要注意设置软件内部寄存器,使之有效。这个功能主要是用来防止程序跑飞。  

双总线故障复位(Double Bus Fault Reset) 

它是由双总线错误监视器产生的异步复位,它是总线错误的特殊状态会导致中止异常处理。     
 
时钟丢失复位(Loss of Clock Reset) 

它在参考时钟子模块消失的时候产生的同步复位。如果要使该复位有效,需要设置寄存器SYNCR。  

关于更详细的外部Reset的资料可以这样描述(51单片机): 

为了确保良好的外部复位和上电复位,复位脉冲宽度必须足够宽,我们要考虑以下两个参数来确定复位脉冲宽度:  

tosc:振荡器才可达到Vih1或Vil1电压的时间。  

tvddrise:电压VDD由10上升至90%的时间。
  
当这两个参数的条件得到满足时,还必须维持至少一定的机器周期来保证单片机内部的启动。

如果是不正常的复位的话:

如果要具体的计算,关于LDO的Reset可参考前面关于拉普拉斯变换的计算过程。 

偷懒的话可以查表:

围观 561

单片机的片选方法有线选法和译码器。线选法就是用其中剩余一条地址线做为单片机选择其它芯片的片选信号线,连接简单,但当单片机外围芯片较多时,由于单片机剩余地址线数量有限,有可能不够用。这时可以使用译码方式,对单片机剩余的高位地址线进行译码,用译码器的输出线做为单片机芯片的片选。

全译码方式是将片内寻址的地址线以外的高位地址线,全部输人到译码器进行译码,利用译码器的输出端作为各存储器芯片的片选信号。常用的译码器有74LS138、 74LS139、74LS154等。这里介绍74LS138、74LS139译码器。

74LS138是一种 3-8 译码器,有3个数据输人端,经译码产生 8 种状态。其引脚如图1所示,译码功能如表1所示。由表1可见,当译码器的输人为某一个编码时其输出就有一固定的引脚输出为低电平,其余的为高电平。
74LS139 是一种双2-4 译码器。这两个译码器完全独立,分别有各自的数据输人端、译码状态输出端以及数据输入允许端。其引脚如图2所示,真值表如表1所示(只给出其中一组)。

输入

输出

允许

选择

G1   G2

C   B   A

Y0    Y1    Y2    Y3    Y4    Y5    Y6    Y7

X    1

X   X   X

1     1     1     1     1     1     1     1

0    X

X   X   X

1     1     1     1     1     1     1     1

1    0

0   0   0

0     1     1     1     1     1     1     1

1    0

0   0   1

1     0     1     1     1     1     1     1

1    0

0   1   0

1     1     0     1     1     1     1     1

1    0

0   1   1

1     1     1     0     1     1     1     1

1    0

1   0   0

1     1     1     1     0     1     1     1

1    0

1   0   1

1     1     1     1     1     0     1     1

1    0

1   1   0

1     1     1     1     1     1     0     1

1    0

1   1   1

1     1     1     1     1     1     1     0

表1 74LS138真值表

图1 74LS138 的引脚 图2 74LS139 的引脚

表1 74LS139 真值表

下面我们以74LS138 为例。来介绍如何进行地址分配。例如要扩8 片8KB 的RAM 6264,如何通过74LS138 把64K 空间分配给各个芯片?由74LS138 真值表可知,把Gl 接到+5V,G2A 、G2B 接地,P2.7、P2.6、P2.5 分别接到74LS138 的C、B、A 端,剩余13 根地址线接到8 片6264 的A12 一A0 脚。

由于对高3 位地址译码,这样译码器有8 个输出Y0—Y7,分别接到8 片6264的片选端,而低13 位地址(P2.4 一P2.0 ,P0.7 一P0.0)完成对6264 存储单元的选择。这样就把64K 存储空间分成8 个8K 空间了。

围观 623

页面

订阅 RSS - 单片机