×
单片机 > 单片机程序设计 > 详情

AVR单片机教程——数码管

发布时间:2020-06-08 发布时间:
|

先解答之前一个思考题:如果不把引脚配置为输出而写高电平,连接LED会怎样?


实验结果是,LED会亮,但相比于输出高电平的情况,亮度很低。这是为什么呢?


通过上一篇教程我们知道,引脚输入输出模式是由寄存器DDRx中DDxn位控制的,可以推断出 pin_mode 函数会改变一个引脚对应的DDxn值,输入为0,输出为1,而其复位后的值为0,即输入,因此如果不把引脚配置为输出,它的模式就是输入。类似地 pin_write 函数会改变PORTxn,其值为函数的第二个参数。


所以不配置输出而写高电平的结果就是,这一引脚的DDxn为0,PORTxn为1,是带上拉电阻的输入模式。上拉电阻相当于VCC接电阻后再接在引脚上,外部电路是引脚接一个电阻再接一个LED到地,总体可以等效为LED被一个电阻限流后接在VCC和地之间,因此LED会亮。


这个电阻的阻值是上拉电阻和本来的限流电阻的阻值之和,上拉电阻是比较大的(根据datasheet P432 Figure30-164可以估算出上拉电阻约40kΩ),相比于限流电阻就是外电阻的输出高电平的情况,LED上的电流小很多,因此亮度也相应低了。


这是一个模拟电路的问题,只用数字电路的分析方法是解决不了的。这个简答的问题也反映了单片机相关知识的综合性。


今天来讲数码管,就是开发板左边那两个日字。


早期数码管也成为辉光灯,依靠气体放电发光,现在一般指7段数码管(因为日字有7段),其本质是许多LED。我们的开发板上是2位数码管,也有1位、3位、4位的;除了7段数码管还有米字数码管,不过比较少见。注意7段数码管其实有8段,每个数字右下角有一个小数点。还有一种4位时钟数码管还有中间两点,但有两个小数点不能点亮。

数码管的8段从上面一段开始顺时针依次为A、B、C、D、E、F,中间为G,小数点称为DP。


各种LED数码管的原理都相同,内部电路连接略有不同。1位数码管有10个引脚,而内部有8个LED,是怎样连接的呢?在数字输出那一篇中提到过共阳和共阴,数码管也是这样的:8个LED的正极(或负极)一起连接到两个引脚上,负极(或正极)分别连接到一个引脚,称为共阳(或共阴)数码管。开发板上的2位数码管中,每一位都是共阴连接的。


8个正极与单片机之间的连接有些复杂,现在无法讲解,不过可以理解为8个由单片机控制的、相互独立的输出引脚,每个分别串联了电阻后连接数码管的正极。左右两位的负极连接到数码管右侧的排针,使用时要把它们连接到单片机引脚上。


如果要让数码管的特定几段亮,应该如何配置9个引脚的电平呢?答案是要亮的段的正极为高电平,其他为低电平,负极也是低电平。另外,如果负极是高电平,则LED正负极之间电压为0或小于0,是不会亮的,这一事实在驱动多位数码管时很重要。注意到负极低电平亮而高电平不亮,与此前使用过的LED不同,写程序的时候不要想当然。


根据手册,我们可以用 segment_dec 等函数设置数码管显示的数据,用 segment_display 来让数码管显示。把显示数据分成设置数据与控制显示,分别由两个函数来完成,往高深讲是将数据与视图分离,而更贴近本篇教程主题的原因是,我们后面将会看到,多位数码管无法仅靠一个函数显示多位数字(至少在顺序控制流下)。


作为数码管的第一个程序,我们先点亮右边的数码管,让它循环显示0~9。左右两位的负极分别连接到单片机4和5号引脚上。


 1 #include

 2 #include

 3 

 4 int main()

 5 {

 6     segment_init(PIN_4, PIN_5);

 7     while (1)

 8         for (uint8_t i = 0; i != 10; ++i)

 9         {

10             segment_dec(i);

11             segment_display(SEGMENT_DIGIT_R);

12             delay(500);

13         }

14 }


根据上一篇教程的内容,你应该不难推断出 segment_display 对数码管负极做的工作吧。它把要显示的一位的负极所连接的引脚的电平拉低,另一位拉高,这是比较显然的。segment_display 对数码管正极做的工作是根据数据配置数码管8个正极的电平,这里的数据是由 segment_dec 设置的。数据中每一个bit和数码管中一段的亮暗相对应,最低位对应A段而最高位对应DP段(这只是我设计开发板时的连接方式)。那么,显示数字1需要点亮B和C段,其显示数据就是0b00000110,即数字1的段码。segment_dec 等函数根据输入参数,给一个大小为2的数组写入了两个值,分别对应两位数码管要显示的数据。


这里插一句,0b前缀表示二进制数,这是GNU扩展,不属于C标准,C++也从C++14才开始支持这种表示法。按理来说应该尽可能少用编译器扩展,但这个特性实在是太好用了,所以就拿来用了。


库中已经声明了一些段码。segment_digit 为0~9、A~F的段码,segment_dot 表示小数点,它们的定义是:


 1 const uint8_t segment_digit[] =

 2 {

 3     0b00111111, // 0

 4     0b00000110, // 1

 5     0b01011011, // 2

 6     0b01001111, // 3

 7     0b01100110, // 4

 8     0b01101101, // 5

 9     0b01111101, // 6

10     0b00000111, // 7

11     0b01111111, // 8

12     0b01101111, // 9

13     0b01110111, // A

14     0b01111100, // B

15     0b00111001, // C

16     0b01011110, // D

17     0b01111001, // E

18     0b01110001  // F

19 };

20 const uint8_t segment_dot = 0b10000000;


这些段码是根据字符形状手动输入的。你可以用相同的方法创造更多字符,也可以在网上找一些工具来完成。


如果(我是说如果)共阴数码管的A~G、DP段分别连接单片机PA0~PA7,负极接低电平,DDRA全为1,那么用一个段码给PORTA赋值,数码管上就能显示对应的数字或字母,或其他图案。这里留个思考题,对于共阳数码管,应该在什么地方作些改动?


以上就是驱动一位数码管的方法。


现在我们来看如何驱动多位数码管。开发板上是2位数码管,实际上多位数码管的驱动方法都是一样的,在此以共阴2位数码管为例。


前面提到共阴多位数码管的每一位都是共阴连接的,那么不同位之间是如何连接的呢?有一些2位数码管的两位之间没有任何联系,整个器件至少有18个引脚,这个数字已经有些大了。而有一种电路连接可以做到在1位数码管的基础上,每增加一位,只需多一个引脚,见下图:

增加的这个引脚就是新的一位的负极,而其每一段的正极与第一位是共用的。对于不同位上的同一段,不难发现它们是共阳连接的。位是共阴的,段是共阳的,处于习惯上的考虑,这样的数码管称为共阴数码管。


然而。这样节省引脚的接法是有代价的,就是你在一个时刻只能独立控制8个LED,即一位的8段(根据对称性,你也可以控制2位的同一段,但这样更受限制)。你无法做到的是点亮第1位的A段和第2位的B段,同时其他段都不亮。为什么呢?点亮第1位的A段,需要使共阳A段为高电平而共阴第1位为低电平,同理B段为高电平,第2位为低电平,结果就是第1位的B段和第2为的A段也被同时点亮了。在这样的方案下,两位数码管只能显示同一个数字。


我们需要一些别的办法。根据前一部分的内容,分别显示两位中的任意一位都是可以做到的。如果用户想看到两个数字,程序可以在第1秒显示左边的数字,第2秒显示右边的数字,用户至少是可以理解的,尽管这种方法相当糟糕。为什么是1秒而不是10秒呢?1秒的情况下用户平均等待0.5秒可以看到两个数字的信息,10秒的话就是5秒等待,用户不愿意。


可是现在用户连0.5秒都不愿意等了,怎么办?一定要把等待时间压缩到0吗?用户是人,人眼的刷新率不太高,只要每秒有24幅图像,它(其实是大脑)就认为这些图像是连续的。所以只要这一等待时间比人眼获取图像的间隔要小,用户就不会再有怨言了。


现在你来做用户。你能看到两个数字,两个数字呈现的时间间隔是一段很短的时间,短到你分辨不出它到底有没有一段间隔。这意味着什么?两个数字同时显示了出来。在此背后,数码管的控制器每隔一小段时间切换一个数字来显示,这种驱动方式成为动态扫描。


我们来写个程序验证一下这种方案。在前一个程序的基础上,我们让左边一位显示0。


 1 #include

 2 #include

 3 

 4 int main()

 5 {

 6     segment_init(PIN_4, PIN_5);

 7     while (1)

 8     for (uint8_t i = 0; i != 10; ++i)

 9     {

10         segment_dec(i);

11         for (uint8_t j = 0; j != 250; ++j)

12         {

13             segment_display(SEGMENT_DIGIT_L);

14             delay(1);

15             segment_display(SEGMENT_DIGIT_R);

16             delay(1);

17         }

18


关键字:AVR  单片机  数码管

『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
基于单片机的医院护理呼叫对讲系统设计