AVR单片机教程——数码管

先解答之前一个思考题:如果不把引脚配置为输出而写高电平,连接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 <ee1/segment.h>
 2 #include <ee1/delay.h>
 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 <ee1/segment.h>
 2 #include <ee1/delay.h>
 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     }
19 }

在这个程序中,数码管刷新间隔设置为2毫秒(1毫秒*2位)。实际上这一间隔在10毫秒以下即可,人眼感受不到差别。在这个程序中你甚至可以不写延时(相应地循环次数要增加很多倍),刷新间隔就很小,但其他应用中单片机除了驱动数码管还有别的工作,如果这些工作的运行时间是不固定的,就不能用它们替换掉延时,因为会导致数码管亮度不均衡。应该加入一个比运行时间长得多的延时来保证间隔大致相同,然而这一方案在其他工作运行时间达到10毫秒这一量级时就不能使用了。

在使用动态扫描来控制数码管时,你会发现一种从前所未有的麻烦,尤其是当与按键等输入相结合时,如作业第2题。以前使用的LED等设备,只需调用一次函数,其状态就能一直保持,直到再调用相应函数;而动态扫描是动态的,即你需要不断去控制它,这就是麻烦的根源所在。单片机当然有别的方案来解决这些问题,我们以后再讲。

今天的作业:

1. 保持1毫秒的延时,将显示切换的时间从0.5秒改为1秒(这有什么难的,不是吗?);

2. 实现以下功能:按下一个按键时,数码管显示的十六进制数字加1;保持按下1000毫秒后,每200毫秒数字加1;再加入另一个按键,但它的效果是数字减1。

原文地址:https://www.cnblogs.com/jerry-fuyi/p/11632793.html

时间: 2024-11-13 08:02:19

AVR单片机教程——数码管的相关文章

AVR单片机教程——定时器中断

本文隶属于AVR单片机教程系列. ? 中断,是单片机的精华. 中断基础 当一个事件发生时,CPU会停止当前执行的代码,转而处理这个事件,这就是一个中断.触发中断的事件成为中断源,处理事件的函数称为中断服务程序(ISR). 中断在单片机开发中有着举足轻重的地位--没有中断,很多功能就无法实现.比如,在程序干别的事时接受UART总线上的输入,而uart_scan_char等函数只会接收调用该函数后的输入,先前的则会被忽略.利用中断,我们可以在每次接受到一个字节输入时把数据存放到缓冲区中,程序可以从缓

AVR单片机教程——EasyElectronics Library v2.0手册

本文隶属于AVR单片机教程系列. ? adc.h bit.h button.h buzzer.h dac.h delay.h ee.h exin.h exout.h lcd.h ldr.h led.h pin.h pot.h print.h pwm.h rgbw.h rotary.h segment.h switch.h timer.h tone.h uart.h wave.h ? 主要更新: 由于修改了一些接口,与之前版本不完全兼容,主版本号更新为2: 正式支持中断,初步使用回调: UART支

AVR单片机教程——矩阵键盘

本文隶属于AVR单片机教程系列. ? 开发板上有4个按键,我们可以把每一个按键连接到一个单片机引脚上,来实现按键状态的检测.但是常见的键盘有104键,是每一个键分别连接到一个引脚上的吗?我没有考证过,但我们确实有节省引脚的方法. 矩阵键盘 这是一个4*4的矩阵键盘,共有16个按键只需要8个引脚就可以驱动.我们先来看看它的原理. 每个按键有两个引脚,当按键按下时接通.每一行的一个引脚接在一起,分别连接到左边4个端口,称为"行引脚":每一列的另一个引脚接在一起,分别连接到右边的4个端口,称

AVR单片机教程——UART进阶

本文隶属于AVR单片机教程系列. ? 在第一期中,我们已经开始使用UART来实现单片机开发板与计算机之间的通信,但只是简单地讲了讲一些概念和库函数的使用.在这一篇教程中,我们将从硬件与软件等各方面更深入地了解UART. USART组件 一直在讲的UART其实是USART组件的一部分,USART比UART多了同步的一部分,但这一部分用得太少(我从来没用过),而且缺乏实例,所以就略过了.然而,单片机的设计者很机智地把这个鸡肋功能升华了一下,USART组件可以支持SPI模式.SPI是一种同步串行总线,

AVR单片机教程——数字输入

我们已经学习了如何使用按键和拨动开关,不知你有没有好奇 button_down 和 switch_status 等函数是如何实现的.本篇教程带你一探究竟,让我们从按键的原理开始. 在原理图中,按键的符号如下图所示: 符号很简单,就是两个触点上方有一个动片,当按下时与两个触点接触.实际上按键内部的机械结构大体上就是这样,实现的功能是,没有按下时两端断路,按下时两端短路. 还有一种画法是这样的,即电键: 就按键内部的机械结构来说,第一种更加真实,但从电路角度来看,两者没什么区别. 但是我们的开发板上

AVR单片机教程——PWM调光

PWM 两位数码管的驱动方式是动态扫描,每一位都只有50%的时间是亮的,我们称这个数值为其占空比.让引脚输出高电平点亮LED,占空比就是100%. 在驱动数码管时,我们迫不得已使占空比为50%,因为不能让两位真正同时地显示不同的数字.但是,我们也可以有意地让LED的占空比不到100%,以降低其亮度. 占空比是可以用程序来调节的.下面的程序允许用户用按键调整蓝色LED的占空比,并通过数码管来显示. #include <ee1/ee.h> #define DUTY_MAX 9 int main()

AVR单片机教程——串口发送

到目前为止,我们的开发板只能处理很小量的数据:读取几个引脚电平,输出几个LED,顶多用数码管显示一个两位数字.至于输入一个指令.输出一条调试信息,甚至用scanf和printf来输入输出,在已经接触过的这些器件上是难以想象的.而本讲"串口发送"与下一讲"串口接收",将打开这一扇大门. 硬件 本讲的主题是UART(Universal Asynchronous Receiver-Transmitter,通用异步收发器),俗称串口.实际上串口是串行接口的统称,在单片机领域

AVR单片机教程——闪烁LED

上次我们把LED点亮了.你可能已经试过把 LED_RED 换成其他灯,也可能已经用 led_on() 把所有LED一起点亮了.但是LED点亮以后,程序就退出了,之后LED一直没有暗,直到没有供电.这一次,我们用程序来控制LED的亮和暗. 新建一个C executable项目,选择ATmega324PA单片机,在项目属性中添加库libee1,将配置改为Release.这是本教程现阶段中每一次新建项目都要做的.我刚才尝试用project template简化,然而设置无法导入. 默认生成的main.

AVR单片机教程——串口接收

上一讲中,我们实现了单片机开发板向电脑传输数据.在这一讲中,我们将通过电脑向单片机发送指令,让单片机根据指令控制LED.这一次,两端的TX与RX需要交叉连接,单片机TX连接串口工具RX也是需要的,因为程序会根据指令反馈信息. 为了简单起见,我们的程序只需要控制4个板载LED.指令包含两个字节:第一个字节为r.y.g.b中的一个,分别表示红.黄.绿.蓝灯:第二个字节为0或1,表示灯不亮或亮. 然而,a2这样的指令是没有意义的,却是可能出现的.即使用户已经熟悉了这8条指令,也可能会不小心打错.我们应