51单片机如何静态点亮数码管?

一、硬件分析

1.1 数码管

        数码管就是一种特殊的发光二极管,它的外观和原理图是这样的:

  

每个“8”字的亮灭都由A~DP八个阳极引脚和一个阴极引脚控制。

一个发光二极管本身就是一个半导体,这就相当于电路中的“单向阀”:电流流入二极管的方向是阳极,流出的方向被称为阴极;反之则不能导通。如果我们将阳极和阴极做为解释,将二极管发光与否做为命题逻辑的真值,那么我们就有下面这张真值表:

1.2 最一般的点亮逻辑

        1.2.1 阳极的连接

       如上所示是两个数码管放在一起,按照最一般也是最自然的链接逻辑,我们只要将16个阳极引脚逐个连接到单片机上就差不多了。事实上这也确实行得通,但是单片机的引脚资源是极为珍贵的,我们能不能想想什么办法省略一些引脚同时还能实现我们的需求呢?

        既然我们的需求只是点亮一位数字就好了,那么我们不妨将所有阳极都接在一起(如上所示),这样就一举省去了8个引脚。不过这样一来,所有的八个数码管都将同时显示同一个数字(因为此时我们假设阴极接地,即为0)。

        1.2.2 阴极的连接

        接下来,根据上面的真值表,我们想让哪位亮,就将阴极置为0就可以了。

假如我们现在的需求是让第二个数码管的第二位显示5,阳极的输入首先肯定应当是1111 1011;如果此时将阴极单纯接地,那么所有数码管上的数字都将显示5。为了达成某1位显示数字,其他不显示的效果,我们只要让阴极为1111 1011就可以了。

通过上面的梳理,因此我们可以得到下面这张表:

        那么阴极该怎么和单片机连接呢?按照最一般也是最自然的想法:当然是把八个阴极引脚逐个和单片机的引脚相连啦!这种方法当然是正确的,也可以实现需求,但是这还是对引脚的极大浪费。

        因为我们的需求十分简单:只需要让某一位亮就可以了,因此借用38译码器就可以实现这个功能:

上面是38译码器的真值表,也就是说,现在我们只需要使用3个单片机的引脚就可以操纵阴极了!

总结:阳极控制段选,阴极控制位选。这个逻辑就相当于先让所有数码管显示5(段选),然后再让某个数码管单独亮就可以了(位选)。

二、代码编写

2.1 了解引脚

        在编写代码前我们需要了解单片机的哪8位引脚控制阳极,哪3位控制阴极。

        通过观察原理图(这里就不发了)发现:        P00~P07控制阳极,P13~P15控制阴极。

        这样就好办了。

2.2 编写代码

我们试试编写下面的例子:

        根据上面的分析,单个数码管只有bc引脚为1,其余都为0,因此段选为:0110 0000。位选则是:1111 1110。根据38译码器的真值表和段选对应的引脚,按照最一般的思路,我们可以给逐个引脚赋值,因此可以得出下面的C代码:

#include <STC89C5xRC.H> // 包含 STC89C52 的头文件

#define SMG_EN P36

// 增加自定义类型,简化代码编写
typedef unsigned char u8;
typedef unsigned int u16;


void main() {
    // 打开数码管总开关
    SMG_EN = 0;

    //段选代码
    P00=0;
    P01=1;
    P02=1;
    P03=0;
    P04=0;
    P05=0;
    P06=0;
    P07=0;

    //位选代码
    P13=1;
    P14=1;
    P15=1;

    // 卡住程序
    while (1);
}

烧录运行,哇哦,真的和上面一模一样诶~ 但是这还不够,如果我们将需求稍微修改一下成第三个数码管给我显示4,这个代码就不能用了,有没有什么通用的办法呢?

.2.3 代码的优化

        这个过程初看起来好像没有什么逻辑,但是这有点像给你一个数列让你找通项公式一样,我们需要发现其中的某些规律。

2.3.1 段选的规律

        所谓段选的规律,就是在问:十进制的0~9,对应到单个数码管的段选的二进制是多少?很容易就可以得到下面这张表:

这就是所有可能的情况了,我们不妨将这些情况写到数组里面:

unsigned char digit_codes[10] = {
    0x3F, // 数字 0
    0x06, // 数字 1
    0x5B, // 数字 2
    0x4F, // 数字 3
    0x66, // 数字 4
    0x6D, // 数字 5
    0x7D, // 数字 6
    0x07, // 数字 7
    0x7F, // 数字 8
    0x6F  // 数字 9
};

        非常巧的是,段选的数字和下标是一致的,比如a[0]所要显示的数字就是0!当然,这里元素选用16进制数的原因是因为一个完整的16进制数是由8个2进制数构成,而我们这里由8个引脚做为阳极,那么这里用16进制数恰如其分。而unsigned char 类型在 C 语言中通常是 8 位,因此这里使用的是unsigned char 类型的数组。

更巧的是,51单片机的通用接口是由P1~P4四个端口组成,每个端口有8个引脚,因此,我们直接对P0端口操作就可以了!

2.4 位选的规律

        位选的规律入下所示:

从真值表可以看出,当我们想要第一个灯亮,即输入0,其3个引脚是000,巧的是0对应的二进制也是000;同理想要第二个灯亮我们要输入1.其二进制和引脚电平都是010、以此类推,最后一个灯亮需要输入7,其二进制和引脚电平也正好是111。

如此一来,我们只要输入十进制下的0~7,经过38译码器的转换得到8位阴极位选码,最后将这个位选码和段选码做运算就可以了。

关键是,P1端口也有8个引脚,我们只用到了其中的P13、P14、P15,如果我们单纯输入10进制的7,它的二进制是0000 0111,而不是我们想要的0011 1000,这该怎么办呢?换言之,我们想要下面这样的数据形式才能正确操纵P13、P14、P15引脚:

观察原数据00000111和需要的数据00111000,我们发现,只要将原来的数据向左移位3位即可。

但是仅仅左移是不够的,我们希望左移之后的P15、14、13能够覆盖原数据,而其他的位不变,如下所示:

也就是说,我们希望只取上面红框中的数据覆盖到原来的P1端口的数据上,这样其他引脚的数据(蓝色框中所表示的)就不会发生变化,应该怎么做到呢?

我们可以先设法将P1端口的P15、P14、P13都先设置为0,即令P1=AA???BBB与11000111做与运算得到AA000BBB,因为任何数和1做与都是任何数本身,然后再和移位后的数字做或运算就可以了。

这个过程如下所示:

这样就做到了只修改指定位,而不影响P1其他位的效果。

事实上,我们有这样的结论:任意一个 n 位有限长的二进制数,可以通过有限次位运算(与、或、异或、取反、移位等)变换为任意另一个 n 位二进制数。

三、完整代码

进过上面的逐步梳理,我们终于得到了可以完全满足要求的代码:

#include <STC89C5xRC.H> // 包含 STC89C52 的头文件

#define SMG_EN P36

// 增加自定义类型,简化代码编写
typedef unsigned char u8;

u8 positive_codes[10] = { //阳极段选
    0x3F, // 数字 0
    0x06, // 数字 1
    0x5B, // 数字 2
    0x4F, // 数字 3
    0x66, // 数字 4
    0x6D, // 数字 5
    0x7D, // 数字 6
    0x07, // 数字 7
    0x7F, // 数字 8
    0x6F  // 数字 9
};
void DigitalTube_DisplaySingle(u8 negetive_code,u8 positive_codes){ //negetive_code为阴极段选
    negetive_code<<=3; //位选:从0开始,第几位显示数字,记得将数字左移3位
    P1=P1&0xc7; //将P1原数据和11 000 111做与运算
    P1=P1|negetive_code;//将与后的结果再和移位后的数据做或运算
    P0=positive_codes;//段选:具体显示什么数字,指定阳极8个引脚的电平
}

void main() {
    // 打开数码管总开关
    SMG_EN = 0;
    DigitalTube_DisplaySingle(6,positive_codes[2]);//(在第几位显示[0~7],显示什么数字positive_codes[0~9])
    // 卡住程序
    while (1);
}

清翔51单片机点亮数码管通常涉及到7段LED数码管显示控制,这是一种常见的微控制器应用。在C语言编程中,你需要了解以下几个步骤: 1. **硬件连接**:将数码管的数据线接到单片机的IO口,例如P0、P1等,通过并行通信的方式控制每个数码管的位。 2. **初始化**:设置数据口的模式,通常需要将最低位设为输出,其余位设为输入,以便于读取公共端的状态。 3. **编写驱动函数**:创建函数如`displayDigit(int digit)`,它接收0-9的数字作为输入,并通过IO口逐位改变数码管的状态来显示该数字。 4. **字符编码**:7段数码管有其特定的字符编码表,每个阿拉伯数字对应一个特定的二进制序列。你需要根据这个编码表将数字转换成对应的二进制表示。 5. **主程序循环**:在主循环中,不断轮询用户输入或者其他源,然后调用`displayDigit()`函数显示相应的数字。 示例代码可能如下: ```c #include <reg51.h> #define P0 0x80 char digitMap[10] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F}; // 数码管字符映射表 void displayDigit(int digit) { unsigned char data = digitMap[digit]; for (int i = 0; i < 8; i++) { if (data & (1 << i)) { P0 |= 1 << (7 - i); // 数据线低电平点亮 } else { P0 &= ~(1 << (7 - i)); // 数据线高电平熄灭 } delay_ms(1); // 短暂延时,让数码管稳定显示 } } void main() { while (1) { int input = get_user_input(); // 获取用户输入 displayDigit(input); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值