在定义数码管的段码表的时候,我多加了一个字节的代码0xbf: code uint8 g_u8LedDisplayCode[]= {
0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8, 0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E, 0xbf, //'-'号代码 } ;
通过SendLedSegData(g_u8LedDisplayCode[pBuffer[s_LedDisPos]]) ;送出相应的段码数据后,然后通过SendLedBitData(s_LedDisPos);打开相应的位选。这样对应的数码管就被点亮了。 if(++s_LedDisPos > 7) {
s_LedDisPos = 0 ; }
然后s_LedDisPos自加1,以便下次执行本函数时,扫描下一个数码管。因为我们的系统共有8个数码管,所以当s_LedDisPos > 7后,要对其进行清0 。否则,没有任何一个数码管被选中。这也是为什么我们可以用
SendLedBitData(8) ; //消隐,只需要设置位选不为0~7即可 对数码管进行消隐操作的原因。
下面我们来编写相应的主函数,并实现数码管上面类似时钟的效果,如显示10-20-30 即10点20分30秒。 Main.c
#include
sbit io_led = P1^6 ;
void main(void) {
io_led = 0 ; //发光二极管与数码管共用P0口,这里禁止掉发光二极管的锁存输出 Timer0Init() ;
g_u8LedDisplayBuffer[0] = 1 ; g_u8LedDisplayBuffer[1] = 0 ; g_u8LedDisplayBuffer[2] = '-' ; g_u8LedDisplayBuffer[3] = 2 ; g_u8LedDisplayBuffer[4] = 0 ; g_u8LedDisplayBuffer[5] = '-' ; g_u8LedDisplayBuffer[6] = 3 ; g_u8LedDisplayBuffer[7] = 0 ; EA = 1 ; while(1)
{
LedDisplay(g_u8LedDisplayBuffer) ; } }
将整个工程进行编译,看看效果如何? 动起来
既然我们想要模拟一个时钟,那么时钟肯定是要走动的,不然还称为什么时钟撒。下面我们在前面的基础之上,添加一点相应的代码,让我们这个时钟走动起来。
我们知道,之前我们以及设置了一个扫描数码管用到的2 ms时标。 如果我们再对这个时标进行计数,当计数值达到500,即500 * 2 = 1000 ms 时候,即表示已经逝去了1 S的时间。我们再根据这个1 S的时间更新显示缓冲区即可。听起来很简单,让我们实现它吧。 首先在Timer.c中声明如下两个变量:
bit g_bTime1S = 0 ; //时钟1S时标消息 static uint16 s_u16ClockTickCount = 0 ; //对2 ms 时标进行计数
再在定时器中断函数中添加如下代码: if(++s_u16ClockTickCount == 500) {
s_u16ClockTickCount = 0 ; g_bTime1S = 1 ; }
从上面可以看出,s_u16ClockTickCount计数值达到500的时候,g_bTime1S时标消息产生。然后我们根据这个时标消息刷新数码管显示缓冲区: void RunClock(void) {
if(g_bTime1S ) {
g_bTime1S = 0 ;
if(++g_u8LedDisplayBuffer[7] == 10) {
g_u8LedDisplayBuffer[7] = 0 ; if(++g_u8LedDisplayBuffer[6] == 6) {
g_u8LedDisplayBuffer[6] = 0 ; if(++g_u8LedDisplayBuffer[4] == 10) {
g_u8LedDisplayBuffer[4] = 0 ; if(++g_u8LedDisplayBuffer[3] == 6)
{
g_u8LedDisplayBuffer[3] = 0 ; if( g_u8LedDisplayBuffer[0]<2) {
if(++g_u8LedDisplayBuffer[1]==10) {
g_u8LedDisplayBuffer[1] = 0 ; g_u8LedDisplayBuffer[0]++; } } else {
if(++g_u8LedDisplayBuffer[1]==4) {
g_u8LedDisplayBuffer[1] = 0 ; g_u8LedDisplayBuffer[0] = 0 ; } } } } }
} } }
这个函数的作用就是对每个数码管缓冲位的值进行判断,判断的标准就是我们熟知的24小时制。如秒的个位到了10 就清0,同时秒的十位加1….诸如此类,我就不一一详述了。
同时,我们再编写一个时钟初始值设置函数,这样,可以很方便的在主程序开始的时候修改时钟初始值。 void SetClock(uint8 nHour, uint8 nMinute, uint8 nSecond) {
g_u8LedDisplayBuffer[0] = nHour / 10 ; g_u8LedDisplayBuffer[1] = nHour % 10 ; g_u8LedDisplayBuffer[2] = '-' ;
g_u8LedDisplayBuffer[3] = nMinute / 10 ; g_u8LedDisplayBuffer[4] = nMinute % 10 ; g_u8LedDisplayBuffer[5] = '-' ;
g_u8LedDisplayBuffer[6] = nSecond / 10 ;
g_u8LedDisplayBuffer[7] = nSecond % 10 ; }
然后修改下我们的主函数如下: void main(void) {
io_led = 0 ; //发光二极管与数码管共用P0口,这里禁止掉发光二极管的锁存输出 Timer0Init() ;
SetClock(10,20,30) ; //设置初始时间为10点20分30秒 EA = 1 ; while(1) {
LedDisplay(g_u8LedDisplayBuffer) ; RunClock(); } }
编译好之后,下载到我们的实验板上,怎么样,一个简单的时钟就这样诞生了。
至此,本章所诉就告一段落了。至于如何完成数码管的闪烁显示,就像本章开头所说的那个数码管时钟的功能,就作为一个思考的问题留给大家思考吧。
同时整个LED篇就到此结束了,在以后的文章中,我们将开始学习如何编写实用的按键扫描程序.
从单片机初学者迈向单片机工程师”之
LED主题讨论周第四章----渐明渐暗的灯
看着学习板上的LED按照我们的意愿开始闪烁起来,你心里是否高兴了,我相信你会的。但是很快你就会感觉到太单调,总是同一个频率在闪烁,总是同一个亮度在闪烁。如果要是能够由暗逐渐变亮,然后再由亮变暗该多漂亮啊。嗯,想法不错,可以该从什么地方入手呢。 在开始我们的工程之前,首先来了解一个概念:PWM。
PWM(Pulse Width Modulation)是脉冲宽度调制的英文单词的缩写。下面这段话是通信百科中对其的定义: 脉冲宽度调制(PWM)是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。脉宽调制是开关型稳压电源中的术语。这是按稳压的控制方式分类的,除了PWM型,还有PFM型和PWM、PFM混合型。脉宽调制式开关型稳压电路是在控制电路输出频率不变的情况下,通过电压反馈调整其占空比,从而达到稳定输出电压的目的。
读起来有点晦涩难懂。其实简单的说来,PWM技术就是通过调整一个周期固定的方波的占空比,来调节输出电压的平均当电压,电流或者功率等被控量。我们可以用一个水龙头来类比,把1S时间分成50等份,即每一个等份20MS。在这20MS时间里如果我们把水龙头水阀一直打开,那么在这20MS里流过的水肯定是最多的,如果我们把水阀打开15MS,剩下的5MS关闭水阀,那么流出的水相比刚才20MS全开肯定要小的多。同样的道理,我们可以通过控制20MS时间里水阀开启的时间的长短来控制流过的水的多少。那么在1S内平均流出的水流量也就可以被控制了。
当我们调整PWM的占空比时,就会引起电压或者电流的改变,LED的明暗状态就会随之发生相应的变化,听起来好像可以通过这种方法来实现我们想要的渐明渐暗的效果。让我们来试一下吧。
大家都知道人眼有一个临界频率,当LED的闪烁频率达到一定的时候,人眼就分辨不出LED是否在闪烁了。就像我们平常看电视一样,看起来画面是连续的,实质不是这个样子,所有连续动作都是一帧帧静止的画面在1S的时间里快速播放出来,譬如每秒24帧的速度播放,由于人眼的视觉暂留效应,看起来画面就是连续的了。同样的道理,为了让我们的LED在变化的过程中,我们感觉不到其在闪烁,可以将其闪烁的频