玩转单片机PC·MCU066无线电2009.08终极原理为你而来mini1608电子时钟◎杜洋今天阳光明媚,今夜多云转晴,《无线电》的老地方,杜洋静静地等候。一个月前,我心潮澎湃地把我最新设计的mini1608电子时钟与你分享。精简的制作、漂亮的外观、强大的功能都深深地吸引了你。你热情高涨,兴冲冲炮制了一个,结果大获成功。也许欣喜之后你却变得迷惑起来,这是认真研究、深入思考的结果,当你从制作层面往技术原理层面思考的时候,就会遇见问题,有所不解。好在8月杜洋与你有约,促膝长谈mini1608的技术原理,带你从制作到创作,全面了解并彻底使用它们,为你的DIY事业创造新鲜乐趣。自力更生——什么是I/O接口的推挽工作方式上文提到STC11F32XE的每一个I/O接口都有4种工作方式,其中包括推挽输出。推挽输出是什么呢?对于没有使用过增强型8051单片机的朋友,这个名词是有些陌生的。传统8051单片机的I/O接口只可以作为标准双向输入/输出接口,如果用它来驱动LED则只能用灌电流的方式或是用三极管外扩驱动电路。灌电流方式是将LED正极接在VCC上,负极接在I/O接口上。当I/O接口为高电平时,LED两极的电平相同,没有电流,LED为熄灭状态。当I/O接口为低电平时,电流从VCC流入I/O接口,LED点亮。当把LED正极接在I/O接口,负极接在GND,再将I/O接口置于高电平时,LED被点亮,但因为I/O接口上拉能力不足而使亮度不够理想。推挽工作方式就是一种具有强上拉能力的工作方式,借助它可以实现高电平驱动LED。惊喜出现了,把LED正负极分别接在两个I/O接口上,然后设置正极的I/O接口为推挽输出,负极的I/O接口为标准双向灌电流输入,(原理篇)P1接口设定(P1.7,P1.6,P1.5,P1.4,P1.3,P1.2,P1.1,P1.0)P1M1【7:0】P1M0【7:0】I/O接口模式(P1.x如作A/D使用,需先将其设置成开漏或高阻输入)00准双向口(传统8051I/O接口模式),灌电流可达20mA,拉电流为230µA,由于制造误差,实际为250µA~150µA01推挽输出(强上拉输出,可达20mA,要加限流电阻)10仅为输入(高阻),如果该I/O接口需作为A/D使用,可选此模式11开漏(OpenDrain),如果该I/O接口需作为A/D使用,可选此模式结果会怎么样呢?非常好,我们可以直接用I/O接口驱动LED而不需要VCC和GND。LED点阵屏就是多个LED的阵列连接,只要把LED点阵屏的所有引脚接在I/O接口上,然后根据LED点阵屏的引脚定义,将对应正极的I/O接口设置成推挽,将对应负极的I/O接口设置成标准双向输入,余下的就是把将要点亮的LED点阵屏上的点所对应的行列线分别给予高低电平,那么一切就尽在你的掌握之中。有朋友可能会问,推挽工作方式这么好,我们在编程时要如何设置呢?这个问题问得好,其实设置I/O接口的工作方式和设置I/O接口的电平状态一样简单。我们以C语言编程为例,首先要加载STC11Fxx.h的头文件,因mini1608电子时钟——终极原理为你而来2009.08无线电067为STC单片机官方给出的头文件中有I/O接口工作状态设置的定义。可以在宏晶公司STC单片机的官方网站上找到头文件,然后把下载到的头文件放到C:\Keil\C51\INC文件夹里面。下一步就是在程序开始处声明这个头文件。注意声明中的文件名要和INC文件夹中存放的头文件的名字相同。#includeSTC11Fxx.h//声明STC单片机头文件P1M0=0x01;//设置P1接口工作方式P1M1=0x04;//设置P1接口工作方式P4M0=0x02;//设置P3接口工作方式P4M1=0xf4;//设置P3接口工作方式P4SW=0xff;//设置单片机功能引脚作为P4接口使用例如我们对I/O接口的工作方式做出如下设置:P1M0=0x09;//00001001P1M1=0x05;//00000101即表示P1接口中,P1.7到P1.4为标准双向输入/输出接口,P1.3为推挽工作输出接口,P1.2为高阻态输入接口,P1.1为标准双向输入/输出接口,P1.0为开漏状态接口。具体设置可以对照STC单片机的SFR数据表。注意P4SW寄存器对应位设置为1时,则此引脚才可作为P4接口使用。权宜之计——为什么要用逐点扫描通常我们驱动LED点阵屏都会使用一种习惯的方法,那就是遂列扫描法。把一组I/O接口定义为行,然后依次选通每一列,在选通某列时对应地送入这一列所需要的行数据,也就是说在同一时间里会有至多一列LED被点亮。对于mini1608的电路设计是不可能实现的,因为数据手册告诉我们,I/O接口的推挽工作方式也并非万能。一般情况下推挽工作方式所能输出的最大电流是20mA,而标准双向输入/输出工作方式也只能灌入20mA的电流。同时,整个单片机在同一时间内所能承受的最大电流为60mA,超过这个电流值,单片机就会有生命危险。遂列扫描时,每一个LED都会消耗10~20mA的电流,让I/O接口同时驱动8个LED,STC11/10xx系列8051单片机I/O接口特殊功能寄存器PortSFRs寄存器地址名称76543210复位值P080h8-bitPort0P0.7P0.6P0.5P0.4P0.3P0.2P0.1P0.01111,1111P0M193h0000,0000P0M094h0000,0000P190h8-bitPort1P1.7P1.6P1.5P1.4P1.3P1.2P1.1P1.01111,1111P1M191h0000,0000P1M092h0000,0000P2A0h8-bitPort2P2.7P2.6P2.5P2.4P2.3P2.2P2.1P2.01111,1111P2M195h0000,0000P2M096h0000,0000P3B0h8-bitPort3P3.7P3.6P3.5P3.4P3.3P3.2P3.1P3.01111,1111P3M1B1h0000,0000P3M0B2h0000,0000P4C0h8-bitPort3P4.7P4.6P4.5P4.4P4.3P4.2P4.1P4.01111,1111P4M1B3h0000,0000P4M0B4h0000,0000P4SWBBhPort4Switch-NA_P4.6ALE_P4.5NA_P4.4----x000,xxxx结果只有死路一条。为了解决这个问题,我们只好采用遂点扫描方式,即在同一时间内只有一个LED被点亮。这样做既保证了单片机和LED的身体健康,让单片机I/O接口的推挽能力恰好达到要求,又不会出现遂列扫描时LED显示亮度不均的问题。从这一点上看,这并非被迫之选,而是绝佳之计。技术严谨又注意细节的爱好者朋友可能会发现整个电路设计中没有用到任何电阻,自然也就没有LED必配的限流电阻。限流电阻是在电路电压大于LED驱动电压时,为保持LED在正常工作电流范围内而使用的。现在整个电路的电压为5V,mini1608公然省略了限流电阻是否会对电路有一定影响呢?我在设计时也认真考虑并做了一些实验,结果令人愉快。使用I/O接口的推挽方式,并在软件上使用遂点扫描,不加限流电阻依然可以保证LED工作在正常电流范围。有惊无险,畅通无阻。层出不穷——怎样实现LED点阵屏多级显示亮度当我第一次把自己设计的渐明渐暗LED灯程序演示给一个同学看的时候,他的感觉是好神奇。他奇怪为什玩转单片机PC·MCU068无线电2009.08么单片机只能输出高、低电平,却会使LED产生渐变的亮度呢?他对单片机性能的了解确实值得肯定,但他忘记了眼睛的双重间谍身份。当我们以1分钟为一个周期,在1分钟内让LED亮1分钟,就也是一直亮着,LED的亮度就是最大的亮度。当我们在1分钟内只让LED在前30秒钟点亮,后30秒钟熄灭,那么LED的亮度就只有原来亮度的一半。当我们在1分钟内只让LED在前6秒钟点亮,那么LED的亮度就只有原来亮度的十分之一,这就是亮度控制的基本原理,见图1。现在该有人暴跳如雷地说我骗人了,因为地球人都知道我上面说的现象实际上只会让LED慢慢地亮熄闪烁,怎么会是亮度的变化呢?是的,如果以1分钟为一个周期,LED确实只会闪烁,这仅是为了方便大家理解。当我们以1毫秒为一个周期时候,就是我们见证奇迹的时刻,由于眼睛的视觉暂留特性,已经看不出LED的闪烁,我们的眼睛亲眼见证了LED亮度真的不同了。眼睛骗了你,善意的谎言。那么让单片机控制亮度变化的程序应该怎么写呢?我反问单片机在1分钟之内前6秒点亮,后54秒熄灭的程序应该怎么写呢?问题自有答案。一箭双雕——如何用LED点阵屏实现测光《无线电》2009年第4期的《LED进化史》有一段介绍LED的反向应用,即把LED当作光电二极管使用。当时我只是引用了一个外国网站的资料,从那之后我就一直关注着LED的反向应用,不久前的实验拉近了我们的距离,将LED反向应用的始末完全呈现在我的眼前。在光线不同的情况下,LED的反向电阻会有所变化,光线强时电阻值变小,光线弱时电阻值变大。如果利用AD转换功能可以读出电阻值变化的每一个细节,但我在mini1608上没有使用AD转换,而是利用了单片机I/O接口的高阻态输入功能。mini1608的LED点阵显示屏在正常情况下,正极为推挽输出,负极为标准双向接口,上文已经讲过。当我们要检测环境光线的时候,就将LED负极的I/O接口变为推挽高电平输出,给LED一个反向电压,LED正极的I/O接口变为高阻态输入。当环境光线强的时候,LED的反向电阻值变小,LED正极的电压会变高,当高过I/O接口高电平的最小值时(一般为2V),则单片机识别为高电平输入。当环境光线弱时,LED反向电阻值变大,LED正极的电压会变低,当低于I/O接口高电平的最小值时,单片机识别为低电平输入,见图2。虽然只有两种状态的判断,但对于mini1608观察白天还是晚上已经足够了。如果想判断光线强弱的更多种状态,可以通过读取反向LED电平变化的时间,如果电平变化时间短,则说明光线强,电平变化时间长,则说明光线弱。还可以同时并入反向LED来增加测光的精确度,完全不需要ADC就可以完成测光。白天环境光线强,LED点阵屏本身要亮一些才能看得清楚。晚上环境光线弱,LED点阵屏要变得暗一些才不会让起夜上厕所的你感觉刺眼。mini1608变聪明了,也更体贴了。我还会继续研究LED反向应用的技术,并会在《无线电》杂志上一一与大家分享。各大电子技术类杂志都不把源程序代码算做稿费的一部分,这样做可以使作者不愿意公开源程序代码,是一种变相激励读者独立创作热情的方法,对此我也表示支持。在此仅提供HEX文件供感兴趣的爱好者朋友仿制。如果你有能力、也有热情编写程序,按照上文说明的原理去思考,很快你便有了思路甚至方案。无论是白天还是夜晚,当你阅读本文时,我正端坐在电脑前,面对上千条的程序代码,对mini1608进行升级。我有热情和勇气与mini1608并肩作战,实现每种奇妙的功能,完成每个看似不可完成的任务。mini1608会不会是LED点阵屏电子时钟的终结者?我们拭目以待。图1亮度调节原理图2LED测光原理