单片机

三极管在数字电路里的开关特性,最常见的应用有 2 个:一个是控制应用,一个是驱动应用。所谓的控制就是如图 3-7 里边介绍的,我们可以通过单片机控制三极管的基极来间接控制后边的小灯的亮灭,用法大家基本熟悉了。还有一个控制就是进行不同电压之间的转换控制,比如我们的单片机是 5V 系统,它现在要跟一个 12V 的系统对接,如果 IO 直接接 12V电压就会烧坏单片机,所以我们加一个三极管,三极管的工作电压高于单片机的 IO 口电压,用 5V 的 IO 口来控制 12V 的电路,如图 3-8 所示。


图 3-8 三极管实现电压转换

图 3-8 中,当 IO 口输出高电平 5V 时,三极管导通,OUT 输出低电平 0V,当 IO 口输出低电平时,三极管截止,OUT 则由于上拉电阻 R2 的作用而输出 12V 的高电平,这样就实现了低电压控制高电压的工作原理。

所谓的驱动,主要是指电流输出能力。我们再来看如图 3-9 中两个电路之间的对比。


图 3-9 LED 小灯控制方式对比

图 3-9 中上边的 LED 灯,和我们第二课讲过的 LED 灯是一样的,当 IO 口是高电平时,小灯熄灭,当 IO 口是低电平时,小灯点亮。那么下边的电路呢,按照这种推理,IO 口是高电平的时候,应该有电流流过并且点亮小灯,但实际上却并非这么简单。

单片机主要是个控制器件,具备四两拨千斤的特点。就如同杠杆必须有一个支点一样,想要撑起整个地球必须有力量承受的支点。单片机的 IO 口可以输出一个高电平,但是他的输出电流却很有限,普通 IO 口输出高电平的时候,大概只有几十到几百 uA 的电流,达不到1mA,也就点不亮这个 LED 小灯或者是亮度很低,这个时候如果我们想用高电平点亮 LED,就可以用上三极管来处理了,我们板上的这种三极管型号,可以通过 500mA 的电流,有的三极管通过的电流还更大一些,如图 3-10 所示。


图 3-10 三极管驱动 LED 小灯

图 3-10 中,当 IO 口是高电平,三极管导通,因为三极管的电流放大作用,c 极电流就可以达到 mA 以上了,就可以成功点亮 LED 小灯。

虽然我们用了 IO 口的低电平可以直接点亮 LED,但是单片机的 IO 口作为低电平,输入电流就可以很大吗?这个我想大家都能猜出来,当然不可以。单片机的 IO 口电流承受能力,不同型号不完全一样,就 STC89C52 来说,官方手册的 81 页有对电气特性的介绍,整个单片机的工作电流,不要超过 50mA,单个 IO 口总电流不要超过 6mA。即使一些增强型 51 的IO 口承受电流大一点,可以到 25mA,但是还要受到总电流 50mA 的限制。那我们来看电路图的 8 个 LED 小灯这部分电路,如图 3-11 所示。


图 3-11 LED 电路图(一)

这里我们要学会看电路图的一个知识点,电路图右侧所有的 LED 下侧的线最终都连到一根黑色的粗线上去了,大家注意,这个地方不是实际的完全连到一起,而是一种总线的画法,画了这种线以后,表示这是个总线结构。而所有的名字一样的节点是一一对应的连接到一起,其他名字不一样的,是不连在一起的。比如左侧的 DB0 和右侧的最右边的 LED2 小灯下边的DB0 是连在一起的,而和 DB1 等其他线不是连在一起的。

那么我们把图 3-11 中现在需要讲解的这部分单独摘出来看,如图 3-12 所示。


图 3-12 LED 电路图(二)

现在我们通过 3-12 的电路图来计算一下,5V 的电压减去 LED 本身的压降,减掉三极管e 和 c 之间的压降,限流电阻用的是 330 欧,那么每条支路的电流大概是 8mA,那么 8 路 LED如果全部同时点亮的话电流总和就是 64mA。这样如果直接接到单片机的 IO 口,那单片机肯定是承受不了的,即使短时间可以承受,长时间工作就会不稳定,甚至导致单片机烧毁。

有的同学会提出来可以加大限流电阻的方式来降低这个电流。比如改到 1K,那么电流不到 3mA,8 路总的电流就是 20mA 左右。首先,降低电流会导致 LED 小灯亮度变暗,小灯的亮度可能关系还不大,但因为我们同样的电路接了数码管,后边我们要讲数码管还要动态显示,如果数码管亮度不够的话,那视觉效果就会很差,所以降低电流的方法并不可取。其次,对于单片机来说,他主要是起到控制作用,电流输入和输出的能力相对较弱,P0 的 8 个口总电流也有一定限制,所以如果接一两个 LED 小灯观察,可以勉强直接用单片机的 IO 口来接,但是接多个小灯,从实际工程的角度去考虑,就不推荐直接接 IO 口了。那么我们如果要用单片机控制多个 LED 小灯该怎么办呢?

除了三极管之外,其实还有一些驱动 IC,这些驱动 IC 可以作为单片机的缓冲器,仅仅是电流驱动缓冲,不起到任何逻辑控制的效果,比如我们板子上用的 74HC245 这个芯片,这个芯片在逻辑上起不到什么别的作用,就是当做电流缓冲器的,我们通过查看其数据手册,74HC245 稳定工作在 70mA 电流是没有问题的,比单片机的 8 个 IO 口大多了,所以我们可以把他接在小灯和 IO 口之间做缓冲,如图 3-13 所示。


图 3-13 74HC245 功能图

从图 3-13 我们来分析,其中 VCC 和 GND 就不用多说了,细心的同学会发现这里有个0.1uF 的去耦电容哦。

74HC245 是个双向缓冲器,1 引脚 DIR 是方向引脚,当这个引脚接高电平的时候,右侧所有的 B 编号的电压都等于左侧 A 编号对应的电压。比如 A1 是高电平,那么 B1 就是高电平,A2 是低电平,B2 就是低电平等等。如果 DIR 引脚接低电平,得到的效果是左侧 A 编号的电压都会等于右侧 B 编号对应的电压。因为我们这个地方控制端是左侧接的是 P0 口,我们要求 B 等于 A 的状态,所以 1 脚我们直接接的 5V 电源,即高电平。图 3-13 中还有一排电阻 R10 到 R17 是上拉电阻,这个电阻的用法我们在后边介绍。

还有最后一个使能引脚 19 脚 OE,叫做输出使能,这个引脚上边有一横,表明是低电平有效,当接了低电平后,74HC245 就会按照刚才上边说的起到双向缓冲器的作用,如果 OE接了高电平,那么无论 DIR 怎么接,A 和 B 的引脚是没有关系的,也就是 74HC245 功能不能实现出来。

从下面的图 3-14 可以看出来,单片机的 P0 口和 74HC245 的 A 端是直接接起来的。这个地方,有个别同学有个疑问,就是我们明明在电源 VCC 那地方加了一个三极管驱动了,为何还要再加 245 驱动芯片呢。这里大家要理解一个道理,电路上从正极经过器件到地,首先必须有电流才能正常工作,电路中任何一个位置断开,都不会有电流,器件也就不会参与工作了。其次,和水流一个道理,从电源正极到负极的电流水管的粗细都要满足要求,任何一个位置的管子过细,都会出现瓶颈效应,电流在整个通路中细管处会受到限制而降低,所以在电路通路的每个位置上,都要保证通道足够畅通,这个 74HC245 的作用就是消除单片机IO 这一环节的瓶颈。


图 3-14 单片机与 74HC245 的连接

来源:电子工程世界

围观 311

单片机程序死机,跑飞了可以从以下几个方面查找原因:

1
意外中断。是否打开了某个中断,但是没有响应和清除中端标志,导致程序一直进入中断,造成死机假象;

2
中断变量处理不妥。若定义某些会在中断中修改的全局变量,这时要注意两个问题:首先为了防止编译器优化中断变量,要在这些变量定义时前加volatile,其次在主循环中读取中断变量前应该首先关闭全局中断,防止读到一半被中断给修改了,读完之后再打开全局中断;否则出现造成数据乱套。

3
地址溢出,常见错误为指针操作错误。我要着重说的是数组下标使用循环函数中循环变量,如果循环变量没控制好则会出现数组下标越界,意外修改系统的寄存器造成死机,这种情况下如果死机说明运气好,否则后面不知道发生什么头疼的事。

4
无条件的死循环;比如使用while(x);等待电平变化,正常情况下x都会变成0,就怕万一,因此最好加上时间限制;

5
看门狗没有关闭。有的单片机即使没使用看门狗开机时也有可能意外自动开启了最小周期的看门狗,导致软件不断复位,造成死机,这个要看芯片手册,最好在程序复位后首先应该显式清除看门狗再关闭看门狗;

6
堆栈溢出。最难查找的问题,对于容量小的单片机,尽量减少函数调用层级,减少局部变量,从而减少压栈的时候所需的空间。当你把以上几条都试过不能解决问题,试一试把你的被调用少函数直接内置到调用的地方并且把占用RAM大的局部变量改成全局变量,试一试说不定就可以了。

来源:21ic电子网

围观 499

状态机的概念

状态机是软件编程中的一个重要概念。比这个概念更重要的是对它的灵活应用。在一个思路清晰而且高效的程序中,必然有状态机的身影浮现。

比如说一个按键命令解析程序,就可以被看做状态机:本来在A状态下,触发一个按键后切换到了B状态;再触发另一个键后切换到C状态,或者返回到A状态。这就是最简单的按键状态机例子。实际的按键解析程序会比这更复杂些,但这不影响我们对状态机的认识。

进一步看,击键动作本身也可以看做一个状态机。一个细小的击键动作包含了:释放、抖动、闭合、抖动和重新释放等状态。

同样,一个串行通信的时序(不管它是遵循何种协议,标准串口也好、I2C也好;也不管它是有线的、还是红外的、无线的)也都可以看做由一系列有限的状态构成。

显示扫描程序也是状态机;通信命令解析程序也是状态机;甚至连继电器的吸合/释放控制、发光管(LED)的亮/灭控制又何尝不是个状态机。

当我们打开思路,把状态机作为一种思想导入到程序中去时,就会找到解决问题的一条有效的捷径。有时候用状态机的思维去思考程序该干什么,比用控制流程的思维去思考,可能会更有效。这样一来状态机便有了更实际的功用。
程序其实就是状态机。

也许你还不理解上面这句话。请想想看,计算机的大厦不就是建立在“0”和“1”两个基本状态的地基之上么?

状态机的要素

状态机可归纳为4个要素,即现态、条件、动作、次态。这样的归纳,主要是出于对状态机的内在因果关系的考虑。“现态”和“条件”是因,“动作”和“次态”是果。详解如下:
① 现态:是指当前所处的状态。
② 条件:又称为“事件”。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
③ 动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
④ 次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。

如果我们进一步归纳,把“现态”和“次态”统一起来,而把“动作”忽略(降格处理),则只剩下两个最关键的要素,即:状态、迁移条件。

状态机的表示方法有许多种,我们可以用文字、图形或表格的形式来表示一个状态机。

纯粹用文字描述是很低效的,所以就不介绍了。接下来先介绍图形的方式。

状态迁移图(STD)

状态迁移图(STD),是一种描述系统的状态、以及相互转化关系的图形方式。状态迁移图的画法有许多种,不过一般都大同小异。我们结合一个例子来说明一下它的画法,如图1所示。

图1状态迁移图

图1状态迁移图

①状态框:用方框表示状态,包括所谓的“现态”和“次态”。
②条件及迁移箭头:用箭头表示状态迁移的方向,并在该箭头上标注触发条件。
③节点圆圈:当多个箭头指向一个状态时,可以用节点符号(小圆圈)连接汇总。
④动作框:用椭圆框表示。
⑤附加条件判断框:用六角菱形框表示。

状态迁移图和我们常见的流程图相比有着本质的区别,具体体现为:在流程图中,箭头代表了程序PC指针的跳转;而在状态迁移图中,箭头代表的是状态的改变。

我们会发现,这种状态迁移图比普通程序流程图更简练、直观、易懂。这正是我们需要达到的目的。

状态迁移表

除了状态迁移图,我们还可以用表格的形式来表示状态之间的关系。这种表一般称为状态迁移表。

表1就是前面介绍的那张状态迁移图的另一种描述形式。

表1状态迁移表

表1状态迁移表

①采用表格方式来描述状态机,优点是可容纳更多的文字信息。例如,我们不但可以在状态迁移表中描述状态的迁移关系,还可以把每个状态的特征描述也包含在内。
②如果表格内容较多,过于臃肿不利于阅读,我们也可以将状态迁移表进行拆分。经过拆分后的表格根据其具体内容,表格名称也有所变化。
③比如,我们可以把状态特征和迁移关系分开列表。被单独拆分出来的描述状态特征的表格,也可以称为“状态真值表”。这其中比较常见的就是把每个状态的显示内容单独列表。这种描述每个状态显示内容的表称之为“显示真值表”。同样,我们把单独表述基于按键的状态迁移表称为“按键功能真值表”。另外,如果每一个状态包含的信息量过多,我们也可以把每个状态单独列表。
④由此可见,状态迁移表作为状态迁移图的有益补充,它的表现形式是灵活的。
⑤状态迁移表优点是信息涵盖面大,缺点是视觉上不够直观,因此它并不能取代状态迁移图。比较理想的是将图形和表格结合应用。用图形展现宏观,用表格说明细节。二者互为参照,相得益彰。

状态机思路实现一个时钟程序

接下来,我将就状态机的应用,结合流程图、状态迁移图和状态迁移,举一个实际例子。下面这张图是一个时钟程序的状态迁移图,如图2所示。

图2时钟程序状态迁移图

图2时钟程序状态迁移图

把这张图稍做归纳,就可以得到它的另一种表现形式——状态迁移表,如表2所示。

表2时钟程序状态迁移表

表2时钟程序状态迁移表

状态机应用的注意事项

基于状态机的程序调度机制,其应用的难点并不在于对状态机概念的理解,而在于对系统工作状态的合理划分。

初学者往往会把某个“程序动作”当作是一种“状态”来处理。我称之为“伪态”。那么如何区分“动作”和“状态”。本匠人的心得是看二者的本质:“动作”是不稳定的,即使没有条件的触发,“动作”一旦执行完毕就结束了;而“状态”是相对稳定的,如果没有外部条件的触发,一个状态会一直持续下去。

初学者的另一种比较致命的错误,就是在状态划分时漏掉一些状态。我称之为“漏态”。

“伪态”和“漏态”这两种错误的存在,将会导致程序结构的涣散。因此要特别小心避免。

更复杂的状态机

前面介绍的是一种简单的状态结构。它只有一级,并且只有一维,如图3所示。

图3 线性状态机结构

图3 线性状态机结构

如果有必要,我们可以建立更复杂的状态机模型。

(1)多级状态结构

状态机可以是多级的。在分层的多级状态机系统里面,一个“父状态”下可以划分多个“子状态”,这些子状态共同拥有上级父状态的某些共性,同时又各自拥有自己的一些个性。

在某些状态下,还可以进一步划分子状态。比如,我们可以把前面的时钟例子修改如下:

把所有和时钟功能有关的状态,合并成1个一级状态。在这个状态下,又可以划分出3个二级子状态,分别为显示时间、设置小时、设置分钟;

同样,我们也可以把所有和闹钟功能有关的状态,合并成1个一级状态。在这个状态下,再划分出4个二级子状态,分别为显示闹钟、设置“时”、设置“分”、设置鸣叫时间。

我们需要用另一个状态变量(寄存器)来表示这些子状态。

子状态下面当然还可以有更低一级的孙状态(子子孙孙无穷尽也),从而将整个状态体系变成了树状多级状态结构,如图4所示。

图4树状多级状态结构

图4树状多级状态结构

(2)多维状态结构

状态结构也可以是多维的。从不同的角度对系统进行状态的划分,这些状态的某些特性是交叉的。比如,在按照按键和显示划分状态的同时,又按照系统的工作进程做出另一种状态划分。这两种状态划分同时存在,相互交叉,从而构成了二维的状态结构空间。

举一个这方面的例子,如:空调遥控器,如图5所示。

图5多维状态机结构

图5多维状态机结构

同样,我们也可以构建三维、四维甚至更多维的状态结构。每一维的状态都需要用一个状态变量(寄存器)来表示。

无论多级状态结构和多维状态结构看上去多么迷人,匠人的忠告是:我们依然要尽可能地简化状态结构,能用单级、单维的结构,就不要给自己找事,去玩那噩梦般的复杂结构。

简单的才是最有效的。

围观 504

设计电路板最基本的过程可以分为三大步骤:电路原理图的设计,产生网络表,印制电路板的设计。不管是板上的器件布局还是走线等等都有着具体的要求。

例如,输入输出走线应尽量避免平行,以免产生干扰。两信号线平行走线必要是应加地线隔离,两相邻层布线要尽量互相垂直,平行容易产生寄生耦合。电源与地线应尽量分在两层互相垂直。线宽方面,对数字电路PCB可用宽的地线做一回路,即构成一地网(模拟电路不能这样使用),用大面积铺铜。

下面这篇文章就单片机控制板设计需要注意的原则和一些细节问题进行了说明。

1.元器件布局

在元器件的布局方面,应该把相互有关的元件尽量放得靠近一些,例如,时钟发生器、晶振、CPU的时钟输入端都易产生噪声,在放置的时候应把它们靠近些。对于那些易产生噪声的器件、小电流电路、大电流电路开关电路等,应尽量使其远离单片机的逻辑控制电路和存储电路(ROM、RAM),如果可能的话,可以将这些电路另外制成电路板,这样有利于抗干扰,提高电路工作的可靠性。

2.去耦电容

尽量在关键元件,如ROM、RAM等芯片旁边安装去耦电容。实际上,印制电路板走线、引脚连线和接线等都可能含有较大的电感效应。大的电感可能会在Vcc 走线上引起严重的开关噪声尖峰。防止Vcc走线上开关噪声尖峰的唯一方法,是在VCC与电源地之间安放一个0.1uF的电子去耦电容。如果电路板上使用的是表面贴装元件,可以用片状电容直接紧靠着元件,在Vcc引脚上固定。最好是使用瓷片电容,这是因为这种电容具有较低的静电损耗(ESL)和高频阻抗,另外这种电容温度和时间上的介质稳定性也很不错。尽量不要使用钽电容,因为在高频下它的阻抗较高。

在安放去耦电容时需要注意以下几点:

● 在印制电路板的电源输入端跨接100uF左右的电解电容,如果体积允许的话,电容量大一些则更好。

● 原则上每个集成电路芯片的旁边都需要放置一个0.01uF的瓷片电容,如果电路板的空隙太小而放置不下时,可以每10个芯片左右放置一个1~10的钽电容。

● 对于抗干扰能力弱、关断时电流变化大的元件和RAM、ROM等存储元件,应该在电源线(Vcc)和地线之间接入去耦电容。

● 电容的引线不要太长,特别是高频旁路电容不能带引线。

3.地线设计

在单片机控制系统中,地线的种类有很多,有系统地、屏蔽地、逻辑地、模拟地等,地线是否布局合理,将决定电路板的抗干扰能力。在设计地线和接地点的时候,应该考虑以下问题:

● 逻辑地和模拟地要分开布线,不能合用,将它们各自的地线分别与相应的电源地线相连。在设计时,模拟地线应尽量加粗,而且尽量加大引出端的接地面积。一般来讲,对于输入输出的模拟信号,与单片机电路之间最好通过光耦进行隔离。

● 在设计逻辑电路的印制电路版时,其地线应构成闭环形式,提高电路的抗干扰能力。

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

● 要注意接地点的选择。当电路板上信号频率低于1MHz时,由于布线和元件之间的电磁感应影响很小,而接地电路形成的环流对干扰的影响较大,所以要采用一点接地,使其不形成回路。当电路板上信号频率高于10MHz时,由于布线的电感效应明显,地线阻抗变得很大,此时接地电路形成的环流就不再是主要的问题了。所以应采用多点接地,尽量降低地线阻抗。

4.其他

● 电源线的布置除了要根据电流的大小尽量加粗走线宽度外,在布线时还应使电源线、地线的走线方向与数据线的走线方身一致在布线工作的最后,用地线将电路板的底层没有走线的地方铺满,这些方法都有助于增强电路的抗干扰能力。

● 数据线的宽度应尽可能地宽,以减小阻抗。数据线的宽度至少不小于0.3mm(12mil),如果采用0.46~0.5mm(18mil~20mil)则更为理想。

● 由于电路板的一个过孔会带来大约10pF的电容效应,这对于高频电路,将会引入太多的干扰,所以在布线的时候,应尽可能地减少过孔的数量。再有,过多的过孔也会造成电路板的机械强度降低。

围观 527

单片机主要作用是控制外围的器件,并实现一定的通信和数据处理。但在某些特定场合,不可避免地要用到数学运算,尽管单片机并不擅长实现算法和进行复杂的运算。下面主要是介绍如何用单片机实现数字滤波。

在单片机进行数据采集时,会遇到数据的随机误差,随机误差是由随机干扰引起的,其特点是在相同条件下测量同一量时,其大小和符号会现无规则的变化而无法预测,但多次测量的结果符合统计规律。为克服随机干扰引起的误差,硬件上可采用滤波技术,软件上可采用软件算法实现数字滤波。滤波算法往往是系统测控算法的一个重要组成部分,实时性很强。

采用数字滤波算法克服随机干扰的误差具有以下优点:

1、数字滤波无需其他的硬件成本,只用一个计算过程,可靠性高,不存在阻抗匹配问题。尤其是数字滤波可以对频率很低的信号进行滤波,这是模拟滤波器做不到的。

2、数字滤波使用软件算法实现,多输入通道可共用一个滤波程序,降低系统开支。

3、只要适当改变滤波器的滤波程序或运算,就能方便地改变其滤波特性,这对于滤除低频干扰和随机信号会有较大的效果。

4、在单片机系统中常用的滤波算法有限幅滤波法、中值滤波法、算术平均滤波法、加权平均滤波法、滑动平均滤波等。

(1)限幅滤波算法

该运算的过程中将两次相邻的采样相减,求出其增量,然后将增量的绝对值,与两次采样允许的最大差值A进行比较。A的大小由被测对象的具体情况而定,如果小于或等于允许的最大差值,则本次采样有效;否则取上次采样值作为本次数据的样本。

算法的程序代码如下:

#defineA //允许的最大差值
chardata; //上一次的数据
char filter()
{
chardatanew; //新数据变量
datanew=get_data(); //获得新数据变量
if((datanew-data)>A||(data-datanew>A))
return data;
else
returndatanew;
}

说明:限幅滤波法主要用于处理变化较为缓慢的数据,如温度、物体的位置等。使用时,关键要选取合适的门限制A。通常这可由经验数据获得,必要时可通过实验得到。

(2)中值滤波算法

该运算的过程是对某一参数连续采样N次(N一般为奇数),然后把N次采样的值按从小到大排列,再取中间值作为本次采样值,整个过程实际上是一个序列排序的过程。

算法的程序代码如下:
#define N11 //定义获得的数据个数
char filter()
{
charvalue_buff[N]; //定义存储数据的数组
char count,i,j,temp;
for(count=0;count
{
value_buf[count]=get_data();
delay(); //如果采集数据比较慢,那么就需要延时或中断
}
for(j=0;j
{
for(value_buff[i]>value_buff[i+1]
{
temp=value_buff[i];
value_buff[i]=value_buff[i+1];
value_buff[i+1]=temp;
}
}
returnvalue_buff[(N-1)/2];
}

说明:中值滤波比较适用于去掉由偶然因素引起的波动和采样器不稳定而引起的脉动干扰。若被测量值变化比较慢,采用中值滤波法效果会比较好,但如果数据变化比较快,则不宜采用此方法。

(3)算术平均滤波算法

该算法的基本原理很简单,就是连续取N次采样值后进行算术平均。
算法的程序代码如下:
char filter()
{
int sum=0;
for(count=0;count
{
sum+=get_data();
delay():
}
return (char)(sum/N);
}

说明:算术平均滤波算法适用于对具有随机干扰的信号进行滤波。这种信号的特点是有一个平均值,信号在某一数值附近上下波动。信号的平均平滑程度完全到决于N值。当N较大时,平滑度高,灵敏度低;当N较小时,平滑度低,但灵敏度高。为了方便求平均值,N一般取4、8、16、32之类的2的整数幂,以便在程序中用移位操作来代替除法。

(4)加权平均滤波算法

由于前面所说的“算术平均滤波算法”存在平滑度和灵敏度之间的矛盾。为了协调平滑度和灵敏度之间的关系,可采用加权平均滤波。它的原理是对连续N次采样值分别乘上不同的加权系数之后再求累加,加权系数一般先小后大,以突出后面若干采样的效果,加强系统对参数变化趋势的认识。各个加权系数均小于1的小数,且满足总和等于1的结束条件。这样加权运算之后的累加和即为有效采样值。其中加权平均数字滤波的数学模型是:

式中:D为N个采样值的加权平均值:XN-i为第N-i次采样值;N为采样次数;Ci为加权系数。加权系数Ci体现了各种采样值在平均值中所占的比例。一般来说采样次数越靠后,取的比例越大,这样可增加新采样在平均值中所占的比重。加权平均值滤波法可突出一部分信号抵制另一部分信号,以提高采样值变化的灵敏度。

样例程序代码如下:

char codejq[N]={1,2,3,4,5,6,7,8,9,10,11,12}; //code数组为加权系数表,存在程序存储区
char codesum_jq=1+2+3+4+5+6+7+8+9+10+11+12;
char filter()
{
char count;
char value_buff[N];
int sum=0;
for(count=0;count
{
value_buff[count]=get_data();
delay();
}
for(count=0;count
sum+=value_buff[count]*jq[count];
return(char)(sum/sum_jq);
}

(5)滑动平均滤波算法

以上介绍和各种平均滤波算法有一个共同点,即每获取一个有效采样值必须连续进行若干次采样,当采速度慢时,系统的实时得不到保证。这里介绍的滑动平均滤波算法只采样一次,将一次采样值和过去的若干次采样值一起求平均,得到的有效采样值即可投入使用。如果取N个采样值求平均,存储区中必须开辟N个数据的暂存区。每新采集一个数据便存入暂存区中,同时去掉一个最老数据,保存这N个数据始终是最新更新的数据。采用环型队列结构可以方便地实现这种数据存放方式。

程序代码如下:
char value_buff[N];
char i=0;
char filter()
{
char count;
int sum=0;
value_buff[i++]=get_data();
if(i==N)
i=0;
for(count=0;count
sum=value_buff[count];
return (char)(sum/N);
}

(6)低通滤波

将普通硬件RC低通滤波器的微分方程用差分方程来表求,变可以采用软件算法来模拟硬件滤波的功能,经推导,低通滤波算法如下:

Yn=a* Xn+(1-a) *Yn-1
式中 Xn——本次采样值
Yn-1——上次的滤波输出值;
,a——滤波系数,其值通常远小于1;
Yn——本次滤波的输出值。

由上式可以看出,本次滤波的输出值主要取决于上次滤波的输出值(注意不是上次的采样值,这和加权平均滤波是有本质区别的),本次采样值对滤波输出的贡献是比较小的,但多少有些修正作用,这种算法便模拟了具体有教大惯性的低通滤波器功能。滤波算法的截止频率可用以下式计算:

fL=a/2Pit pi为圆周率3.14…
式中 a——滤波系数;
, t——采样间隔时间;
例如:当t=0.5s(即每秒2次),a=1/32时;
fL=(1/32)/(2*3.14*0.5)=0.01Hz

当目标参数为变化很慢的物理量时,这是很有效的。另外一方面,它不能滤除高于1/2采样频率的干搅信号,本例中采样频率为2Hz,故对1Hz以上的干搅信号应采用其他方式滤除,

低通滤波算法程序于加权平均滤波相似,但加权系数只有两个:a和1-a。为计算方便,a取一整数,1-a用256-a,来代替,计算结果舍去最低字节即可,因为只有两项,a和1-a,均以立即数的形式编入程序中,不另外设表格。虽然采样值为单元字节(8位A/D)。为保证运算精度,滤波输出值用双字节表示,其中一个字节整数,一字节小数,否则有可能因为每次舍去尾数而使输出不会变化。
设Yn-1存放在30H(整数)和31H(小数)两单元中,Yn存放在32H(整数)和33H(小数)中。滤波程序如下:副表6.

今天就写到这,因为数字滤波的算法还有很多种方法,比如一阶滞后低通滤波器(惯性滤波法),限时滤波,容错冗余三中取二滤波法等等。不过由于个人能力和时间的原因,还没能把它们一一地列出。以后我会不断地找资料把它们完善。

来源:玩转单片机

围观 295

看过标题,可能很多朋友都会冒出很多疑问:单片机?单片机不是电子设计的心脏吗?它的心脏又是什么?

你知道单片机的“心脏”是怎么工作的吗?

无论是单片机还是微处理器,它们的核心都是大规模的时序逻辑电路,而驱动时序逻辑电路的动力则是准确而稳定的时钟源——不要小看定语“准确而稳定”哦,实际上人类的科技之所以能如此稳定、高速的发展,就是离不开准确而稳定的时钟源。比如单片机所使用的晶体振荡器,就是一种比较准确的时钟源。

在晶体振荡器之前,振荡源一般是LC振荡电路等电路,但它们会受到外界的影响(振动、温度等),其误差可能达到kHz级别。长时间的工作情况下其准确度也难以保证。无线电和数字电路的发展迫使一种准确且稳定的时钟源诞生,于是就有了晶体振荡器。

将石英晶体按照一定的方式切割,并向其施加电压,它就会在外加电场的作用下开始振动,这种性质被称作“压电性”。这就使得石英晶体拥有了能够与外界的振荡谐振的能力。按照同样的方式切割出来的晶体拥有着相同的振荡频率,这也就使得批量生产石英晶体成为了可能。

你知道单片机的“心脏”是怎么工作的吗?

实用的石英晶体振荡器的内部一般是这样的:一片精密切割好的晶体放置在中间,被两片化学处理过的金属氧化物夹在中间,并用结实的金属外壳包装好。这也是为什么晶振的电路图符号是一两个电极夹着一个晶体。典型的石英晶体振荡器的应用电路如下图所示:

你知道单片机的“心脏”是怎么工作的吗?

这种电路被称作皮尔斯振荡器,利用一个反相器来完成电路。下方的两个电容一般取相同的电容值,它们的取值可以微弱的影响整个电路的振荡频率。它们的作用是与石英晶体构成带通滤波器,在石英晶体的共振频率上形成一个180度的相移。这个电路最常见于单片机的时钟部分。

对于石英晶体来说,被切割的越细,其共振频率越高——但也不是无极限的,切割的过细的话,石英晶体将很容易破碎。而这个极限频率大概为20MHz。那那些20MHz以上的晶体是怎么制作的呢?

你知道单片机的“心脏”是怎么工作的吗?

实际上,对于超过20MHz的晶体,它们所利用的是晶体振荡频率中的谐波。我们可以加强晶体的奇数倍谐波并利用它。比如我想要使用50MHz的晶体的话,我可以制造一块10MHz的晶体,并使它的结构增强5倍谐波(50MHz)。对于这样的晶体来说,如果需要纯净的50MHz的话,在使用时就需要使用外接的谐振电路选择出这个50MHz的谐波。

你知道单片机的“心脏”是怎么工作的吗?

对于时钟源这种东西来说,我们永远不会介意它的精度高,越高越好。但是即使是晶振这种有很长历史的振荡器,也足够我们好好研究上一段时间了呢!

来源:至秦单片机

围观 452

通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开,而是在闭合和断开的瞬间伴随了一连串的抖动,如图 8-10 所示。

单片机按键消抖程序

图 8-10 按键抖动状态图

按键稳定闭合时间长短是由操作人员决定的,通常都会在 100ms 以上,刻意快速按的话能达到 40-50ms 左右,很难再低了。抖动时间是由按键的机械特性决定的,一般都会在 10ms以内,为了确保程序对按键的一次闭合或者一次断开只响应一次,必须进行按键的消抖处理。当检测到按键状态变化时,不是立即去响应动作,而是先等待闭合或断开稳定后再进行处理。按键消抖可分为硬件消抖和软件消抖。

硬件消抖就是在按键上并联一个电容,如图 8-11 所示,利用电容的充放电特性来对抖动过程中产生的电压毛刺进行平滑处理,从而实现消抖。但实际应用中,这种方式的效果往往不是很好,而且还增加了成本和电路复杂度,所以实际中使用的并不多。

单片机按键消抖程序

图 8-11 硬件电容消抖

在绝大多数情况下,我们是用软件即程序来实现消抖的。最简单的消抖原理,就是当检测到按键状态变化后,先等待一个 10ms 左右的延时时间,让抖动消失后再进行一次按键状态检测,如果与刚才检测到的状态相同,就可以确认按键已经稳定的动作了。将上一个的程序稍加改动,得到新的带消抖功能的程序如下。

#include

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY1 = P2^4;
sbit KEY2 = P2^5;
sbit KEY3 = P2^6;
sbit KEY4 = P2^7;

unsigned char code LedChar[] = { //数码管显示字符转换表
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};

void delay();
void main(){
bit keybuf = 1; //按键值暂存,临时保存按键的扫描值
bit backup = 1; //按键值备份,保存前一次的扫描值
unsigned char cnt = 0; //按键计数,记录按键按下的次数

ENLED = 0; //选择数码管 DS1 进行显示
ADDR3 = 1;
ADDR2 = 0;
ADDR1 = 0;
ADDR0 = 0;
P2 = 0xF7; //P2.3 置 0,即 KeyOut1 输出低电平
P0 = LedChar[cnt]; //显示按键次数初值

while (1){
keybuf = KEY4; //把当前扫描值暂存
if (keybuf != backup){ //当前值与前次值不相等说明此时按键有动作
delay(); //延时大约 10ms
if (keybuf == KEY4){ //判断扫描值有没有发生改变,即按键抖动
if (backup == 0){ //如果前次值为 0,则说明当前是弹起动作
cnt++; //按键次数+1
//只用 1 个数码管显示,所以加到 10 就清零重新开始
if (cnt >= 10){
cnt = 0;
}
P0 = LedChar[cnt]; //计数值显示到数码管上
}
backup = keybuf; //更新备份为当前值,以备进行下次比较
}
}
}
}
/* 软件延时函数,延时约 10ms */
void delay(){
unsigned int i = 1000;
while (i--);
}
大家把这个程序下载到板子上再进行试验试试,按一下按键而数字加了多次的问题是不是就这样解决了?把问题解决掉的感觉是不是很爽呢?

这个程序用了一个简单的算法实现了按键的消抖。作为这种很简单的演示程序,我们可以这样来写,但是实际做项目开发的时候,程序量往往很大,各种状态值也很多, while(1)这个主循环要不停的扫描各种状态值是否有发生变化,及时的进行任务调度,如果程序中间加了这种 delay 延时操作后,很可能某一事件发生了,但是我们程序还在进行 delay 延时操作中,当这个事件发生完了,程序还在 delay 操作中,当我们 delay 完事再去检查的时候,已经晚了,已经检测不到那个事件了。为了避免这种情况的发生,我们要尽量缩短 while(1)循环一次所用的时间,而需要进行长时间延时的操作,必须想其它的办法来处理。

那么消抖操作所需要的延时该怎么处理呢?其实除了这种简单的延时,我们还有更优异的方法来处理按键抖动问题。举个例子:我们启用一个定时中断,每 2ms 进一次中断,扫描一次按键状态并且存储起来,连续扫描 8 次后,看看这连续 8 次的按键状态是否是一致的。8 次按键的时间大概是 16ms,这 16ms 内如果按键状态一直保持一致,那就可以确定现在按键处于稳定的阶段,而非处于抖动的阶段,如图 8-12。

单片机按键消抖程序

图 8-12 按键连续扫描判断

假如左边时间是起始 0 时刻,每经过 2ms 左移一次,每移动一次,判断当前连续的 8 次按键状态是不是全 1 或者全 0,如果是全 1 则判定为弹起,如果是全 0 则判定为按下,如果0 和 1 交错,就认为是抖动,不做任何判定。想一下,这样是不是比简单的延时更加可靠?

利用这种方法,就可以避免通过延时消抖占用单片机执行时间,而是转化成了一种按键状态判定而非按键过程判定,我们只对当前按键的连续 16ms 的 8 次状态进行判断,而不再关心它在这 16ms 内都做了什么事情,那么下面就按照这种思路用程序实现出来,同样只以K4 为例。

#include

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY1 = P2^4;
sbit KEY2 = P2^5;
sbit KEY3 = P2^6;
sbit KEY4 = P2^7;

unsigned char code LedChar[] = { //数码管显示字符转换表
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
bit KeySta = 1; //当前按键状态

void main(){
bit backup = 1; //按键值备份,保存前一次的扫描值
unsigned char cnt = 0; //按键计数,记录按键按下的次数
EA = 1; //使能总中断
ENLED = 0; //选择数码管 DS1 进行显示
ADDR3 = 1;
ADDR2 = 0;
ADDR1 = 0;
ADDR0 = 0;
TMOD = 0x01; //设置 T0 为模式 1
TH0 = 0xF8; //为 T0 赋初值 0xF8CD,定时 2ms
TL0 = 0xCD;
ET0 = 1; //使能 T0 中断
TR0 = 1; //启动 T0
P2 = 0xF7; //P2.3 置 0,即 KeyOut1 输出低电平
P0 = LedChar[cnt]; //显示按键次数初值

while (1){
//****************KeySta = KEY4; //把当前扫描值暂存
if (KeySta != backup){ //当前值与前次值不相等说明此时按键有动作
if (backup == 0){ //如果前次值为 0,则说明当前是弹起动作
cnt++; //按键次数+1
if (cnt >= 10){ //只用 1 个数码管显示,所以加到 10 就清零重新开始
cnt = 0;
}
P0 = LedChar[cnt]; //计数值显示到数码管上
}
//更新备份为当前值,以备进行下次比较
backup = KeySta;
}
}
}
/* T0 中断服务函数,用于按键状态的扫描并消抖 */
void InterruptTimer0() interrupt 1{
//扫描缓冲区,保存一段时间内的扫描值
static unsigned char keybuf = 0xFF;

TH0 = 0xF8; //重新加载初值
TL0 = 0xCD;
//缓冲区左移一位,并将当前扫描值移入最低位
keybuf = (keybuf<<1) | KEY4;
//连续 8 次扫描值都为 0,即 16ms 内都只检测到按下状态时,可认为按键已按下
if (keybuf == 0x00){
KeySta = 0;
//连续 8 次扫描值都为 1,即 16ms 内都只检测到弹起状态时,可认为按键已弹起
}else if (keybuf == 0xFF){
KeySta = 1;
}
else{
//其它情况则说明按键状态尚未稳定,则不对 KeySta 变量值进行更新
}
}
这个算法是我们在实际工程中经常使用按键所总结的一个比较好的方法,介绍给大家,今后都可以用这种方法消抖了。当然,按键消抖也还有其它的方法,程序实现更是多种多样,大家也可以再多考虑下其它的算法,拓展下思路。

围观 497

我要问的不是如何制作烧写接口。 而是,电脑在通过接口向单片机烧写时(此时单片机 内没有程序),电脑是如何通过接口访问内部rom的??对应p口怎么就会指向单片机内部rom(就像单片机是一个rom?

有三种方式:
1、把单片机当做一个ROM芯片,早期的单片机都是如此。将单片机放在通用编程上编程时,就像给28C256这样的ROM中写程序的过程一样。只是不同的单片机使用的端口,编程用的时序不一样。

2、像AT89S52或AVR单片机一样,在单片机上有SPI接口,这时用专用的下载线将程序烧写到单片机中。这时不同的是,单片机的CPU除了执行单片机本身的指令之外,还能执行对ROM进行操作的特殊指令,如ROM擦除、烧写和校验指令。在编程ROM时,下载线先通过传输这些指令给CPU执行(擦除ROM、读入数据、烧写ROM、和校验ROM),这样完成对单片机的ROM的烧写。此外,现在普遍使用的JTAG仿真器也是这样,单片机的CPU能执行JTAG的特殊指令,完成对ROM的烧写操作。

3、引导程序,即单片机中已经存在了一个烧写程序。启动单片机时首先运行这程序,程序判断端口状态,如果符合“要烧写ROM”的状态存在,就从某个端口(串口、SPI等等)读取数据,然后写入到单片机的ROM中。如果没有“要烧写ROM”的状态,就转到用户的程序开始执行。像AVR单片机的bootloader方式、STC的串口下载方式,还有其他单片机的串口编程等等都是这样。

来源:电子工程专辑

围观 353

PWM 在单片机中的应用是非常广泛的,它的基本原理很简单,但往往应用于不同场合上意义也不完全一样,这里我先把基本概念和基本原理给大家介绍一下,后边遇到用的时候起码知道是个什么东西。

PWM 是 Pulse Width Modulation 的缩写,它的中文名字是脉冲宽度调制,一种说法是它利用微处理器的数字输出来对模拟电路进行控制的一种有效的技术,其实就是使用数字信号达到一个模拟信号的效果。这是个什么概念呢?我们一步步来介绍。

首先从它的名字来看,脉冲宽度调制,就是改变脉冲宽度来实现不同的效果。我们先来看三组不同的脉冲信号,如图 10-1 所示。

单片机中PWM的原理与控制程序

图 10-1 PWM 波形

这是一个周期是 10ms,即频率是 100Hz 的波形,但是每个周期内,高低电平脉冲宽度各不相同,这就是 PWM 的本质。在这里大家要记住一个概念,叫做“占空比”。占空比是指高电平的时间占整个周期的比例。比如第一部分波形的占空比是 40%,第二部分波形占空比是 60%,第三部分波形占空比是 80%,这就是 PWM 的解释。

那为何它能对模拟电路进行控制呢?大家想一想,我们数字电路里,只有 0 和 1 两种状态,比如我们第 2 章学会的点亮 LED 小灯那个程序,当我们写一个 LED = 0;小灯就会长亮,当我们写一个 LED = 1;小灯就会灭掉。当我们让小灯亮和灭间隔运行的时候,小灯是闪烁。

如果我们把这个间隔不断的减小,减小到我们的肉眼分辨不出来,也就是 100Hz 以上的频率,这个时候小灯表现出来的现象就是既保持亮的状态,但亮度又没有 LED = 0;时的亮度高。那我们不断改变时间参数,让 LED = 0;的时间大于或者小于 LED = 1;的时间,会发现亮度都不一样,这就是模拟电路的感觉了,不再是纯粹的 0 和 1,还有亮度不断变化。大家会发现,如果我们用 100Hz 的信号,如图 10-1 所示,假如高电平熄灭小灯,低电平点亮小灯的话,第一部分波形熄灭 4ms,点亮 6ms,亮度最高,第二部分熄灭 6ms,点亮 4ms,亮度次之,第三部分熄灭 8ms,点亮 2ms,亮度最低。那么用程序验证一下我们的理论,我们用定时器T0 定时改变 P0.0 的输出来实现 PWM,与纯定时不同的是,这里我们每周期内都要重载两次定时器初值,即用两个不同的初值来控制高低电平的不同持续时间。为了使亮度的变化更加明显,程序中使用的占空比差距更大。
#include
sbit PWMOUT = P0^0;
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

unsigned char HighRH = 0; //高电平重载值的高字节
unsigned char HighRL = 0; //高电平重载值的低字节
unsigned char LowRH = 0; //低电平重载值的高字节
unsigned char LowRL = 0; //低电平重载值的低字节

void ConfigPWM(unsigned int fr, unsigned char dc);
void ClosePWM();

void main(){
unsigned int i;
EA = 1; //开总中断
ENLED = 0; //使能独立 LED
ADDR3 = 1;
ADDR2 = 1;
ADDR1 = 1;
ADDR0 = 0;

while (1){
ConfigPWM(100, 10); //频率 100Hz,占空比 10%
for (i=0; i<40000; i++);
ClosePWM();
ConfigPWM(100, 40); //频率 100Hz,占空比 40%
for (i=0; i<40000; i++);
ClosePWM();
ConfigPWM(100, 90); //频率 100Hz,占空比 90%
for (i=0; i<40000; i++);
ClosePWM(); //关闭 PWM,相当于占空比 100%
for (i=0; i<40000; i++);
}
}
/* 配置并启动 PWM,fr-频率,dc-占空比 */
void ConfigPWM(unsigned int fr, unsigned char dc){
unsigned int high, low;
unsigned long tmp;

tmp = (11059200/12) / fr; //计算一个周期所需的计数值
high = (tmp*dc) / 100; //计算高电平所需的计数值
low = tmp - high; //计算低电平所需的计数值
high = 65536 - high + 12; //计算高电平的重载值并补偿中断延时
low = 65536 - low + 12;//计算低电平的重载值并补偿中断延时

HighRH = (unsigned char)(high>>8); //高电平重载值拆分为高低字节
HighRL = (unsigned char)high;
LowRH = (unsigned char)(low>>8); //低电平重载值拆分为高低字节
LowRL = (unsigned char)low;

TMOD &= 0xF0; //清零 T0 的控制位
TMOD |= 0x01; //配置 T0 为模式 1
TH0 = HighRH; //加载 T0 重载值
TL0 = HighRL;
ET0 = 1; //使能 T0 中断
TR0 = 1; //启动 T0
PWMOUT = 1; //输出高电平
}
/* 关闭 PWM */
void ClosePWM(){
TR0 = 0; //停止定时器
ET0 = 0; //禁止中断
PWMOUT = 1; //输出高电平
}
/* T0 中断服务函数,产生 PWM 输出 */
void InterruptTimer0() interrupt 1{
if (PWMOUT == 1){ //当前输出为高电平时,装载低电平值并输出低电平
TH0 = LowRH;
TL0 = LowRL;
PWMOUT = 0;
}else{ //当前输出为低电平时,装载高电平值并输出高电平
TH0 = HighRH;
TL0 = HighRL;
PWMOUT = 1;
}
}
需要提醒大家的是,由于标准 51 单片机中没有专门的 PWM 模块,所以我们用定时器加中断的方式来产生 PWM,而现在有很多的单片机都会集成硬件的 PWM 模块,这种情况下需要我们做的就仅仅是计算一下周期计数值和占空比计数值然后配置到相关的 SFR 中即可,既使程序得到了简化又确保了 PWM 的输出品质(因为消除了中断延时的影响)。

大家编译下载程序后,会发现小灯从最亮到灭一共 4 个亮度等级。如果我们让亮度等级更多,并且让亮度等级连续起来,会产生一个小灯渐变的效果,与呼吸有点类似,所以我们习惯上称之为呼吸灯,程序代码如下,这个程序用了 2 个定时器 2 个中断,这是我们第一次这样用,大家可以学习一下。我们来试试这个程序,试完了大家一定要能自己把程序写出来,切记。
纯文本复制
#include
sbit PWMOUT = P0^0;
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

unsigned long PeriodCnt = 0; //PWM 周期计数值
unsigned char HighRH = 0; //高电平重载值的高字节
unsigned char HighRL = 0; //高电平重载值的低字节
unsigned char LowRH = 0; //低电平重载值的高字节
unsigned char LowRL = 0; //低电平重载值的低字节
unsigned char T1RH = 0; //T1 重载值的高字节
unsigned char T1RL = 0; //T1 重载值的低字节

void ConfigTimer1(unsigned int ms);
void ConfigPWM(unsigned int fr, unsigned char dc);

void main(){
EA = 1; //开总中断
ENLED = 0; //使能独立 LED
ADDR3 = 1;
ADDR2 = 1;
ADDR1 = 1;
ADDR0 = 0;

ConfigPWM(100, 10); //配置并启动 PWM
ConfigTimer1(50); //用 T1 定时调整占空比
while (1);
}
/* 配置并启动 T1,ms-定时时间 */
void ConfigTimer1(unsigned int ms){
unsigned long tmp; //临时变量
tmp = 11059200 / 12; //定时器计数频率
tmp = (tmp * ms) / 1000; //计算所需的计数值
tmp = 65536 - tmp; //计算定时器重载值
tmp = tmp + 12; //补偿中断响应延时造成的误差
T1RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
T1RL = (unsigned char)tmp;
TMOD &= 0x0F; //清零 T1 的控制位
TMOD |= 0x10; //配置 T1 为模式 1
TH1 = T1RH; //加载 T1 重载值
TL1 = T1RL;
ET1 = 1; //使能 T1 中断
TR1 = 1; //启动 T1
}
/* 配置并启动 PWM,fr-频率,dc-占空比 */
void ConfigPWM(unsigned int fr, unsigned char dc){
unsigned int high, low;
PeriodCnt = (11059200/12) / fr; //计算一个周期所需的计数值
high = (PeriodCnt*dc) / 100; //计算高电平所需的计数值
low = PeriodCnt - high; //计算低电平所需的计数值
high = 65536 - high + 12; //计算高电平的定时器重载值并补偿中断延时
low = 65536 - low + 12; //计算低电平的定时器重载值并补偿中断延时
HighRH = (unsigned char)(high>>8); //高电平重载值拆分为高低字节
HighRL = (unsigned char)high;
LowRH = (unsigned char)(low>>8); //低电平重载值拆分为高低字节
LowRL = (unsigned char)low;

TMOD &= 0xF0; //清零 T0 的控制位
TMOD |= 0x01; //配置 T0 为模式 1
TH0 = HighRH; //加载 T0 重载值
TL0 = HighRL;
ET0 = 1; //使能 T0 中断
TR0 = 1; //启动 T0
PWMOUT = 1; //输出高电平
}
/* 占空比调整函数,频率不变只调整占空比 */
void AdjustDutyCycle(unsigned char dc){
unsigned int high, low;
high = (PeriodCnt*dc) / 100; //计算高电平所需的计数值
low = PeriodCnt - high; //计算低电平所需的计数值
high = 65536 - high + 12; //计算高电平的定时器重载值并补偿中断延时
low = 65536 - low + 12; //计算低电平的定时器重载值并补偿中断延时

HighRH = (unsigned char)(high>>8); //高电平重载值拆分为高低字节
HighRL = (unsigned char)high;
LowRH = (unsigned char)(low>>8); //低电平重载值拆分为高低字节
LowRL = (unsigned char)low;
}
/* T0 中断服务函数,产生 PWM 输出 */
void InterruptTimer0() interrupt 1{
if (PWMOUT == 1){ //当前输出为高电平时,装载低电平值并输出低电平
TH0 = LowRH;
TL0 = LowRL;
PWMOUT = 0;
}else{ //当前输出为低电平时,装载高电平值并输出高电平
TH0 = HighRH;
TL0 = HighRL;
PWMOUT = 1;
}
}
/* T1 中断服务函数,定时动态调整占空比 */
void InterruptTimer1() interrupt 3{
static bit dir = 0;
static unsigned char index = 0;
unsigned char code table[13] = { //占空比调整表
5, 18, 30, 41, 51, 60, 68, 75, 81, 86, 90, 93, 95
};

TH1 = T1RH; //重新加载 T1 重载值
TL1 = T1RL;
AdjustDutyCycle(table[index]); //调整 PWM 的占空比
if (dir == 0){ //逐步增大占空比
index++;
if (index >= 12){
dir = 1;
}
}else{ //逐步减小占空比
index--;
if (index == 0){
dir = 0;
}
}
}

呼吸灯效果做出来后,利用这个基本原理,其它各种效果的灯光闪烁都应该可以做出来,大家看到的 KTV 里边那绚丽的灯光闪烁,其实就是采用的 PWM 技术控制的。

来源:玩转单片机

围观 1051

什么是单片机,相信很多人都还不知道。也不知道单片机的作用是什么。单片机简称为单片微控制器(Microcontroler),它不是完成某一个逻辑功能的芯片,而是把一个计算机系统集成到一个芯片上,相当于一个微型的计算机,因为它最早被用在工业控制领域。单片机由芯片内仅有CPU的专用处理器发展而来。最早的设计理念是通过将大量外围设备和CPU集成在一个芯片中,使计算机系统更小,更容易集成进复杂的而对提及要求严格的控制设备当中。

大家都知道我们的电脑主要是由中央处理单元CPU(进行运算、控制)、随机存储器RAM(数据存储)、存储器ROM(程序存储)、输入/输出设备I/O(串行口、并行输出口等)。安装在一个被称之为主板的印刷线路板上,就是我们个人的计算机了。

把单片机看成一个整体分成四块就很容易认识了。 把这些东西(CPU,硬盘,内存,主板等等)用集成块做好后,如下图所视:

一文看懂单片机与CPU的相似与不同

单机片

就成了我们要学习的“单片机”了。而在单机片的内部,CPU,硬盘,内存,主板等等却又是另外的名字。

1)CPU(Central Processing Unit)。它是单片机的核心部件,包括运算器和控制器。运算器既是算术逻辑单元ALU(Arithmetic logic Unit),其功能是进行算术运算和逻辑运算。控制器一般由指令寄存器、指令译码器、时序电路和控制电路组成。起作用是完成取指令、将指令译码形成各种微操作并执行指令,同时控制计算机的各个部件有条不紊地工作。

一文看懂单片机与CPU的相似与不同

cpu内部图

2)计算机中的内存,在单片机里叫数据存储器,也叫随机存储器。用RAM(Random Access Memery)表示。其作用是用于存放运算的中间结果,数据暂存和缓冲,标志位等。特点是:掉电后会丢失数据。

一文看懂单片机与CPU的相似与不同

程序存储器

3)计算机中的硬盘,在单片机中,叫程序存储器,也叫只读存储器。用ROM(Read only memery)表示。其作用和硬盘差不多,用来存放用户程序。特点是:掉电后不会丢失数据。

4)输入/输出设备I/O“主板”,在单片机里,叫做I/O(输入输出设备)当然也包含了串行口,并行口,定时器,记时器等等。

一文看懂单片机与CPU的相似与不同

单片机的使用领域已十分广泛,如智能仪表、实时工控、通讯设备、导航系统、家用电器等。各种产品一旦用上了单片机,就能起到使产品升级换代的功效,常在产品名称前冠以形容词——“智能型”,如智能型洗衣机等。因此,单片机的学习、开发与应用将造就一批计算机应用与智能化控制的科学家、工程师。

本文引用地址: http://www.eepw.com.cn/article/201702/343732.htm

围观 498

页面

订阅 RSS - 单片机