|
事无巨细,数码管闪亮
仍然是深夜,仍然精力充沛,遂来写写数码管的显示。篇幅也许会很长,各位看客最好端杯茶慢慢消遣。
言归正传,我们先看看什么是数码管(图片来自网络):
上图就是各种长相各种样子的数码管了,肯定很眼熟了吧。
简单讲讲它的构造:一般数码管都称为“X位X段数码管”:比如一位八段数码管、两位八段数码管、四位八段数码管等。“位”即是指这排数码管共有几位(即有几个“8.”),“段”则是指每一位数码管由几段组成,很容易数出来,是八段(小数点也算一段)如上图左下则是一位八段数码管,而最上边的就是四位八段数码管了。应该很容易理解。而这些“段”,其实是一个个长条形状的发光二极管(LED),所以数码管点亮的时候,本质上就是这八个发光二极管的某几个在被点亮,就组成了我们需要的字符。
我们来看看它的工作原理,如下图(来自google,鄙视百度充满广告的搜索结果)
上图展出了常用的两种数码管的引脚排列和内部结构。总所周知,点亮发光二极管就是要给予它足够大的正向压降。所以点亮数码管其实也就是给它内部相应的发光二极管正向压降。如上图左(一共a、b、c、d、e、f、g、DP八段),如果要显示“1”则要点亮b、c两段LED;显示“A”则点亮a、b、c、e、f、g这六段LED;我们还知道,既然LED加载的是正向压降,它的两端电压必然会有高低之分:如果八段LED电压高的一端为公共端,我们称之为共阳极数码管(如上图中);如果八段LED电压低的一段为公共端,则称之为共阴极数码管(上图右)。所以,要点亮共阳极数码管,则要在公共端给予高于非公共端的电平;反之点亮共阴极数码管,则要在非公共端给予较高电平。
一般共阳极数码管更为常用,为什么呢?这是因为数码管的非公共端往往接在IC芯片的IO上,而IC芯片的驱动能力往往是比较小的,如果采用共阴极数码管,它的驱动端在非公共端,就有可能受限于IC芯片输出电流不够而显示昏暗(比如51单片机),要外加上拉电阻或者是三极管加大驱动能力。所以使用共阳数码管的好处是:将驱动数码管的工作交到公共端(一般接驱动电源),加大驱动电源的功率自然要比加大IC芯片IO口的驱动电流简单许多。另一方面,这样也能减轻MCU的负担。
现在来看我们CEPARK板子上用来驱动数码管的一种芯片:74HC595。
先了解两个简单的概念,我尽量用易懂的白话阐述:
串行口:既传输数据只用两个IO,输出IO和输入IO,通过时钟的配合一次只做一位数据的传输,比如传输一个字符类型的八位数据,就要八次时钟配合八次传输。
并行口:相比于串行口,并行口一次可以传输多位数据(8位、16位甚至32位),但是要占用大量IO口(也是8个、16个、或者32个IO),原理与串行口类似:数据在数个IO上有效后,一次时钟配合传输这数位数据。
明显,串行口节省IO,但是传输速度慢。并行口速度快,但是占用大量IO。那到底哪种更被广泛使用呢?对,串行口。传输速度取决于CPU的主频,传输位数取决于IO口的个数。相比于主频来说,MCU的IO要宝贵的多,当今时代,CPU的主频符合摩尔定理增长定理:即每十八个月就可以大幅增长。但是因为制造技术的限制,IO数就远远没有这么快的增长了。
现在来看74HC595,双列直插封装(DIP),是一种移位寄存器内部带有移位存储器和数据存储器,数据存储器的数据直接展现在它的输出IO上。它最主要的功能,就是将串行数据输出转变为并行数据输出,达到一种扩展IO的功能。见下图(来自官方PDF):
上图展示了74HC595的管脚,其中
14脚,串行数据输入脚
1~7,15脚刚好是八位的并行输出口
13脚,输出使能端,高电平有效,即该引脚给予高电平是,74HC595才能输出数据
12脚,存储时钟,在每个上升沿,将移位寄存器的数据存储至存储寄存器,即将74HC595 的数据刷新输出。
11脚,移位时钟,也是在每个上升沿,将14脚即串行数据输入脚的数据移至移位寄存 器。注意首先写入数据的最高位。
9脚,串行输出脚,注意该脚的数据是从移位寄存器的最高位移过来的。一般用于两级74HC595的级联,CEPARK AVR的板子便是如此。
现在阐述一下74HC595的工作过程:通过移位时钟的配合,将串行输入口的数据逐位移进移位寄存器。移位完成之后,通过存储时钟将移位寄存器的数据移至存储寄存器,此时输出IO对外显示相应电平。特别说明9脚(SQH),该脚的数据是从7脚(QH)移上来的,即进行八次移位后,数据在第九次移位第一次来到9脚。理解这点,才能理解CEPARK AVR的工作原理。
好,现在我们可以看看CEPARK AVR的数码管显示模块的原理图:
很明显,论坛的板子用的是四位八段共阳极数码管。其中9~12脚分别为四位数码管各自的公共端,要点亮某一位,就要给9~12这四个脚的某一位高电平(共阳),1~8脚给相应的低电平。图中的电阻为470欧限流电阻,计算方法请参看《事无巨细,流水灯简入》一文。
下面是两片74HC595级联,其中比较关键的几点:
1.注意看他们的11脚和12脚共用了AVR单片机的PB7和PB4,即共用了移位时钟和存储时钟,表示他们对数据的操作是同时进行的。
2.两片74HC595的9脚相连,表示要将上级74HC595的串行数据再以串行的方式传输到下级。但是不是直接传输,而是通过上级74HC595的移位寄存器。即如果要将数据写至下级74HC595,则必须先把数据依次移到上级的9脚,再配合时钟一位一位写入。(上级和下级以数据流先后为准,此原理图数据先到达左边的74HC595,遂其为上级)
这个显示模块的工作原理并不复杂,上级74HC595提供段选信号,下级74HC595提供位选信号。首先配合移位时钟先将八位位选信号(注意,一定是位选信号,因为先写入的数据最终会存储在下级74HC595中!!!)写入上级74HC595,然后通过16次移位将其移植下级74HC595;再将八位段选信号写入上级74HC595。完成之后给存储时钟上升沿,一次刷新两级74HC595的IO电平状态。数码管即显示相应信息了。
这样的设计方法,会给程序的编写带来难度,电路连线更复杂,稍带电路成本增加。
但其好处足以掩盖上述缺点:
1.只用3个MUC的IO就可以驱动8位数码管,极大节省了IO。(本应占用16个IO)
2.两级74HC595级联,时钟线共用使得段选和位选信号同时得到刷新,避免了因为分 步刷新而引起的数码管短暂乱码的现象。(这个读者要细细体会,我接触过的大部分的板子是做不到这点的)
PS:在此多说一句,评判一个电子产品,质量,工作稳定性和成本,永远都是三个最重要的要素,特别的这是个追求高效率,对价格敏感的时代。所以孰重孰轻,笔者相信读者经过权衡,心中自然明了。
现在来看这次实验的源程序,功能是在八位数码管上从左到右依次显示“12345678”
八个数字。
#include
#include
#define uint unsigned int
#define uchar unsigned char
unsigned char const LedData[]=
{0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xF8,
0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e}; //数码管端选数组,静态
unsigned char const LedPos[]=
{0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; //数码管位选数组,静态
uchar n,m=0xfe;
void HC595send(uchar x);
void init(void);
void HC595shift(void);
void HC595store(void);
void display(uchar pos,uchar dat); //以上为各个函数声明,详细功见下
int main(void)
{
init();
while(1)
{
for(n=1;n<9;n++) //循环显示,八次
{
display(n-1,n); //显示函数调用
HC595store();
}
}
}
void init(void) //IO初始化函数
{
DDRB=0xff;//B口全部输出
PORTB=0x00;//B口全部低电平
}
void HC595send(uchar x) //HC595字节写入函数
{
uchar n,temp;
for(n=0;n<8;n++)
{
temp=x&0x80;
if(temp!=0)
{
PORTB|=(1<< PB5);
HC595shift();
}
else
{
PORTB&=~(1<< PB5);
HC595shift();
}
x<<=1;
}
}
void HC595store(void) //字节写入存储寄存器
{
PORTB|=(1<< PB4);
PORTB&=~(1<< PB4);
}
void HC595shift(void) //寄存器移位函数
{
PORTB|=(1<< PB7);
PORTB&=~(1<< PB7);
}
void display(uchar pos,uchar dat) //显示函数,在某位显示某数据
{
HC595send(LedPos[pos]);
HC595send( LedData[dat]);
}
程序依然不长,我这里挑一些比较关键的地方说说
1、数码管位选和段选的两个数组定义为const 类型,意为该数组的元素在全局程序里全部不可被修改,在某些数据始终都不希望被修改的时候可以用此关键字。
2、注意到void HC595send(uchar x)中,因为第一位被写入的数据是被写入字节的最高位,所以做temp=x&0x80语句处理以分离最高位(如果是分离最低位应该是temp=x&0x01)。
3、注意到第一次写入的数据是不需要经过待写字节的移位的,移位寄存器一共移位八次,但是被写入数据只有七次移位,移位语句x<<=1放在了该函数的最后实现该目的。
4、程序中大量出现的类似PORTB|=(1<
某位置1:PORTX|=(1<< PBX)[/code]某位清零:PORTX&=~(1<< PBX)[/code]某位取反:PORTX~=&(1<< PBX)大家要牢记再牢记,非常高效,非常常用,非常有用。
5.void HC595shift(void) 中,因为时钟线在上升沿将数据线数据写入,所以每次上升沿完成,要重新将时钟线置低,准备下一次上升。有说法称这种行为为为:释放时钟线。
6.void display(uchar pos,uchar dat)函数中,要先写入下级寄存器的数据后写上级寄存器的数据,所以先写pos(position,位置),后写dat(data,数据)注意保持良好的变量名称定义习惯有重大意义。
希望能给有需要的人一点帮助。
PS:晒晒我的板子 :;P
[ 本帖最后由 losingamong 于 2010-3-12 12:30 编辑 ]
|
|