【手把手教你学会51单片机】数码管的动态显示

news2024/12/28 3:57:41

注:本文章转载自《手把手教你学习51单片机》!因转载需要原文链接,故无法选择转载!
如若侵权,请联系我进行删除!上传至网络博客目的为了记录自己学习的过程的同时,同时能够帮助其他一同学习的小伙伴!

文章目录

      • 数码管的动态显示
        • 动态显示的基本原理
        • 数码管显示消隐

数码管的动态显示

动态显示的基本原理

我们在上一章学习数码管静态显示的时候说到,74HC138只能同时在同一时刻导通一个三极管,而我们的数码管是靠了6个三极管来控制的,那我们如何让数码管同时显示呢?这就用到了动态显示的概念。
多个数码管显示数字的时候,我们实际上是轮流点亮数码管(一个时刻内只有一个数码管是亮的),利用人眼的视觉暂留现象(也叫做余晖效应),就可以做到看起来是所有数码管都同时亮了,这就是动态显示,也叫做动态扫描。
例如:有两个数码管,我们要显示"12"这个数字,先让高位的位选三极管导通,然后控制段选让其显示"1",延时一定时间后再让低位的位选三极管导通,然后控制段选让其显示"2"。把这个流程以一定的速度循环运行就可以让数码管显示出"12",由于交替速度非常快,人眼识别到的就是"12"这两位数字同时亮了。
那么一个数码管需要点亮多长时间呢?也就是说要多长时间完成一次全部数码管的扫描呢(很明显,整体扫描时间 = 单个数码管点亮时间*数码管个数)?答案是:10ms以内。
当电视机和显示器还处在CRT(电子显像管)时代的时候,有一句很流行的广告语——“100Hz无闪烁”,没错,只要刷新率大于100Hz,即刷新时间小于10ms,就可以做到无闪烁,这也就是我们动态扫描的硬性指标。那么你也许会问,有最小值的限制吗?理论上没有,但实际上做到更快的刷新却没有任何进步意义了,因为已经无闪烁了,再快也还是无闪烁,只是突然增加CPU负担而已(因为1秒内要执行更多次的扫描程序)。所以,通常我们设计程序的时候,都是取一个接近10ms,又比较规整的值就行了。我们开发板上有6个数码管,那么我们现在就来着手写一个数码管动态扫描的程序,实现兼验证上面讲解的动态显示原理。
我们的目标还是实现秒表功能,只不过这次有6个位了,最大可以记到999999秒。那么现在要实现的这个程序相对于前几章的例程来说就复杂的多了,既要处理秒表计数,又要处理动态扫描。在编写这类比较复杂的程序时,建议初学者先用程序流程图来把程序的整个流程理清,在动手写程序之前先把整个程序的结构框架搭好,把每一个环节要实现的功能先细化出来,然后用程序代码一步一步的去实现出来。这样就可以避免无处下笔的迷茫感了。如图6-1就是本例的程序流程图,大家先根据流程图把程序的执行经过在大脑里走一遍,然后再看接下来的程序代码,体会一下流程图的作用,看是不是能够帮助你更流畅的理清程序流程。
20230118_1

#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

unsigned char code LedChar[] = {//数码管显示字符转换表
    0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
    0x80,0x90,0x88,0x83,0xC6,0xA1,0x860x8E
}
unsigned char LedBuff[6] = {//数码管显示缓存区,初值0xFF确保启动时都不亮
    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
}
void main(void)
{
    unsigned char i = 0;//动态扫描的索引
    unsigned int cnt = 0;//记录T0中断次数
    unsigned long sec = 0;//记录经过的秒数

    ENLED = 0;//使能U3,选择控制数码管
    ADDR3 = 1//因为需要动态改变ADDR0~2的值,所以不需要再初始化了
    TMOD = 0x01//设置T0为模式1
    TH0 = 0xFC;//为T0赋初值0xFC67,定时1ms
    TL0 = 0x67;
    TR0 = 1;    //启动T0

    while(1)
    {
        if(TF==1)   //判断T0是否溢出
        {
            TF = 0; //T0溢出后,清零中断标志
            TH0 = 0xFC;//并重新赋初值
            TL0 = 0x67;
            cnt++;
            if(cnt>=1000)
            {
                cnt = 0;    //达到1000次后计数值请0
                sec++;      //秒数自动加1
                //以下代码sec按十进制从低到高依次提取并转换为数码管显示字符
                LedBuff[0] = LedChar[sec%10];
                LedBuff[1] = LedChar[sec/10%10];
                LedBuff[2] = LedChar[sec/100%10];
                LedBuff[3] = LedChar[sec/1000%10];
                LedBuff[4] = LedChar[sec/10000%10];
                LedBuff[5] = LedChar[sec/10000%10];
            }
            //以下代码完成数码管动态扫描
            if(i == 0){
                ADDR2 = 0;ADDR1 = 0;ADDR0 = 0;i++;P0 = LedBuff[0];
            }else if(i == 1){
                ADDR2 = 0;ADDR1 = 0;ADDR0 = 0;i++;P0 = LedBuff[1];
            }else if(i == 2){
                ADDR2 = 0;ADDR1 = 1;ADDR0 = 0;i++;P0 = LedBuff[2];
            }else if(i == 3){
                ADDR2 = 0;ADDR1 = 1;ADDR0 = 1;i++;P0 = LedBuff[3];
            }else if(i == 4){
                ADDR2 = 1;ADDR1 = 0;ADDR0 = 0;i++;P0 = LedBuff[4];
            }else if(i == 5){
                ADDR2 = 1;ADDR1 = 0;ADDR0 = 1;i++;P0 = LedBuff[5];
            }
        }
    }
}

这段程序,大家自行抄到keil中,然后边抄写边结合程序流程图来理解,最终下载到实验班上看一下运行结果。其中下边的if…else语句就是每1ms就刷新一个数码管,这样6个数码管整体刷新一遍的时间就是6ms,视觉感官上就是6个数码管同时亮了起来。
在C语言中,"/“等同于数学里的除法运算,而”%"等同于我们小学学习的求余数运算,这个前面已经有介绍,如果是123456这个数字,我们要正常显示在数码管上,个位显示,就是直接对10取余数,这个"6"就出来了,十位数字是先除以10,然后再对10取余数,以此类推,就把6个数字全部显示出来了。
对于多选一的动态刷新数码管的方式,我们如果用switch会有更好的效果,大家来看一下我们用switch语句完成的情况。

#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

unsigned char code LedChar[] = {//数码管显示字符转换表
    0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
    0x80,0x90,0x88,0x83,0xC6,0xA1,0x860x8E
}
unsigned char LedBuff[6] = {//数码管显示缓存区,初值0xFF确保启动时都不亮
    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
}
void main(void)
{
    unsigned char i = 0;//动态扫描的索引
    unsigned int cnt = 0;//记录T0中断次数
    unsigned long sec = 0;//记录经过的秒数

    ENLED = 0;//使能U3,选择控制数码管
    ADDR3 = 1//因为需要动态改变ADDR0~2的值,所以不需要再初始化了
    TMOD = 0x01//设置T0为模式1
    TH0 = 0xFC;//为T0赋初值0xFC67,定时1ms
    TL0 = 0x67;
    TR0 = 1;    //启动T0

    while(1)
    {
        if(TF==1)   //判断T0是否溢出
        {
            TF = 0; //T0溢出后,清零中断标志
            TH0 = 0xFC;//并重新赋初值
            TL0 = 0x67;
            cnt++;
            if(cnt>=1000)
            {
                cnt = 0;    //达到1000次后计数值请0
                sec++;      //秒数自动加1
                //以下代码sec按十进制从低到高依次提取并转换为数码管显示字符
                LedBuff[0] = LedChar[sec%10];
                LedBuff[1] = LedChar[sec/10%10];
                LedBuff[2] = LedChar[sec/100%10];
                LedBuff[3] = LedChar[sec/1000%10];
                LedBuff[4] = LedChar[sec/10000%10];
                LedBuff[5] = LedChar[sec/10000%10];
            }
            //以下代码完成数码管动态扫描
            if(i == 0){
                ADDR2 = 0;ADDR1 = 0;ADDR0 = 0;i++;P0 = LedBuff[0];
            }else if(i == 1){
                ADDR2 = 0;ADDR1 = 0;ADDR0 = 0;i++;P0 = LedBuff[1];
            }else if(i == 2){
                ADDR2 = 0;ADDR1 = 1;ADDR0 = 0;i++;P0 = LedBuff[2];
            }else if(i == 3){
                ADDR2 = 0;ADDR1 = 1;ADDR0 = 1;i++;P0 = LedBuff[3];
            }else if(i == 4){
                ADDR2 = 1;ADDR1 = 0;ADDR0 = 0;i++;P0 = LedBuff[4];
            }else if(i == 5){
                ADDR2 = 1;ADDR1 = 0;ADDR0 = 1;i++;P0 = LedBuff[5];
            }
            switch(i){
                case 0: ADDR2 = 0;ADDR1 = 0;ADDR0 = 0;i++;P0 = LedBuff[0];break;
                case 1: ADDR2 = 0;ADDR1 = 0;ADDR0 = 0;i++;P0 = LedBuff[1];break;
                case 2: ADDR2 = 0;ADDR1 = 1;ADDR0 = 0;i++;P0 = LedBuff[2];break;
                case 3: ADDR2 = 0;ADDR1 = 1;ADDR0 = 1;i++;P0 = LedBuff[3];break;
                case 4: ADDR2 = 1;ADDR1 = 0;ADDR0 = 0;i++;P0 = LedBuff[4];break;
                case 5: ADDR2 = 1;ADDR1 = 0;ADDR0 = 1;i++;P0 = LedBuff[5];break;
            }
        }
    }
}

程序完成的功能是一模一样的,但大家看一下,switch语句是不是比if…else语句要显得整齐清爽呢?

数码管显示消隐

不知道同学们是否发现了,我们的这两个数码管动态显示程序的运行效果似乎并不是那么完美,第一个小问题,大家仔细看,数码管不应该亮的段,似乎有微微的发亮,这种现象叫做"鬼影",这个"鬼影"严重影响了我们的视觉效果,那我们该如何取解决呢?
同学们在今后可能会遇到各种各样的实际问题,可能很多都是我们没有讲过的,遇到问题怎么办呢?大家要相信,你作为初学者,遇到的问题肯定不是第一个遇到的,肯定有前辈已经遇到过相同的或类似的问题,它们一般都会在网上发表各种帖子,各种讨论,所以大家遇到问题,首先应该就形成一个到网上搜索的条件反射,这个问题大家可以到网上搜索:“数码管消隐"或者"数码管鬼影解决”,多找找相关关键字试试,会搜索也是一种能力。
大家在网上搜了一下会发现,解决这类问题的方法有两个,其中之一是延时,延时之后我们肉眼就可能看不到这个"鬼影"了。但是"延时"是一种非常拙劣的手段,且不说延时多久能让我们看不到"鬼影",延时后,我们的数码管亮度会普遍降低。我们解决问题呢,不能只知其然,还要知其所以然,那么我们首先就来弄明白为什么会出现"鬼影"。
"鬼影"的出现,主要是在数码管位选和段选产生的瞬态造成的。举个例子,我们在数码管动态显示的那部分程序中,实际上每一个数码管点亮的持续时间是1ms的时间,1ms后进行下个数码管的切换。在进行数码管切换的时候,比如我们从case 5要切换到 case 0的时候,case 5的位选用的是ADDR0 = 1;ADDR1 = 0;ADDR2 = 1;假如此刻case5也就是最高位数码管对应的值是0,我们要切换的case 0的数码管位选是ADDR0 = 0;ADDR1 = 0;ADDR2 = 0;而对应的数码管的值假如是1.又因为C语言的语句是一条一条往下执行的,每一天语句的执行都会占用一定的时间,这个瞬间存在了一个中间状态ADDR0 = 0;ADDR1 = 0;ADDR2 = 1;在这个瞬间上,我们就给case 4对应的数码管DS5瞬间赋值了0.当我们全部写完了ADDR0 = 0;ADDR1 = 0;ADDR2 = 0;后,这个时候我们的P0还没有正式赋值,而P0此刻却保持了前一次的值,也就使在这个瞬间,我们又给case 0对应的数码管DS1赋值了一个0.直到我们把case 0后面的语句全部完成后,我们的刷新才正式完成。而在这个刷新的过程中,有两个瞬间我们给错误的数码管赋了值,虽然很弱(因为亮的时间很短),但是我们还是能够发现。
那么搞明白了原理之后,解决起来就不是困难的事情了,我们只要避开这个顺势错误就可以了。不产生瞬时错误的方法是,在进行位选切换期间,避免一切数码管的赋值即可。方法有两个:一个方法是刷新之前关闭的所有的段,改变好了位选后再打开段选即可;第二个方法是关闭数码管的位,赋值过程都做好后,再重新打快即可。这个不是很难,答案我都公布一下。
关闭段:在switch(i)这句程序之前,加一句P0 = 0xFF;这样就把数码管的所有的段选都关闭了,当把"ADDR"的值全部搞定后,再给P0赋对应的值即可。
关闭位:在switch(i)这句程序之前,加上一句ENLED = 1;等到ADDR0 = 0;ADDR1 = 0;ADDR2 = 0;i++;P0 = LedBuff[0];这几条刷新程序全部写完后,再加上一句ENLED = 0;然后再进行break操作即可。
这个地方逻辑思路上稍微有点复杂,大家一定要理解深刻,彻底弄明白,把这个瞬间的问题弄明白了,后面很多牵扯到此类情况的问题,我们都可以一并搞定。
上面的数码管程序还有第二个问题,大家仔细看,我们的数码管上的数字每一秒变化一次,变化的时候,不出现变化的数码管可能出现一次抖动,这个抖动没有什么专业的名字,我们就称之为数码管抖动吧。这种数码管抖动是什么原因造成的呢?为何在数据改变的时候才抖动呢?
来分析一下我们的程序,程序在定义到一秒的时候,执行了"秒数+1并转换为数码管显示字符"这个操作,一个32位整数型的除法运算,实际上是比较耗费时间的,至于这一段程序究竟耗费了多少时间,大家可以通过第四章讲的调试方法来看看这段程序运行多少时间。由于每次定时到一秒的时候,程序都多运行了这么一段,导致了某个数码管的点亮时间比其他情况要长一些,总时间就变成了1ms+本程序运行时间,于此同时,其他的数码管就熄灭了5ms+本段程序运行时间,如果这段程序运行时间非常短,那么可以忽略不计,但很明显,现在这段程序运行时间已经比较长了,以致于严重影响到视觉效果了,我们要采取另外一种思路去解决这个问题。
在我们学习完中断的知识后,自然会给出相应的解决方案!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/173317.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

类的初始化2023018

类的初始化&#xff1a; 第一次使用某个类&#xff0c;例如Person类&#xff0c;系统通常会在第一次使用Person类时加载这个类并初始化这个类。在类的准备阶段&#xff0c;系统将会为该类的类变量分配内存空间&#xff0c;并指定默认初始值。当Person类初始化完成后&#xff0c…

高并发系统设计-Feed流系统设计

有两种实现方式&#xff1a;push和pull实现&#xff0c;首先讨论push模式 概念 我们在讲如何设计Feed流系统之前&#xff0c;先来看一下Feed流中的一些概念&#xff1a; Feed&#xff1a;Feed流中的每一条状态或者消息都是Feed&#xff0c;比如朋友圈中的一个状态就是一个Fe…

布隆过滤器算法

目录布隆过滤器主要有下面的参数&#xff1a;结论举例布隆过滤器主要有下面的参数&#xff1a; 1.假设数据量为n&#xff0c;预期的失误率为p&#xff08;布隆过滤器大小和每个样本的大小无关&#xff09;。 2.根据n和p&#xff0c;算出BloomFilter一共需要多少个bit位&#x…

【年度总结 | 2022】想干什么就去干吧,少年

&#x1f935;‍♂️ 个人主页: 计算机魔术师 &#x1f468;‍&#x1f4bb; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 程序人生专栏 | 年度总结 &#xff08; 2022 &#xff09; 作者&#xff1a; 计算机魔术师 版本&#xff1a; 1.0 &#xff08…

关于性能测试需要知道的

随着各企业的业务发展、用户量以及数据量的不断增加&#xff0c;系统承载的压力也会随之增加&#xff0c;服务系统的性能好坏又严重影响企业的利益。因此&#xff0c;性能测试重要性与需求越来越强烈。 常见的性能测试目的 性能测试是确定系统在特定工作负载下的稳定性和响应…

JAVA 基础语法——(HelloWorld案例编写,Notepad软件的安装和使用,注释,关键字,常量,变量,计算机存储单元,数据类型,标识符,类型转换)

目录 HelloWorld案例的编写 Notepad软件的安装和使用 注释 关键字 常量 变量 计算机存储单元 数据类型概述 标识符 类型转换 HelloWorld案例的编写 首先定义一个类——–public class 类名在类定义后加上一对大括号 {}在大括号中间添加一个主(main)方法/函数——–publi…

详解Curl各参数的含义

详解Curl各参数的含义1. Introduction2. Detail2.1 参数-k2.2 参数-X2.3 参数-x2.4 参数-w %{http_code}2.5 参数-d2.6 参数-H2.7 参数-F2.8 参数-O2.9 参数-o2.10 参数-u2.11 参数-b2.12 参数-G3. Awakening1. Introduction [rootnolan ~]# curl -h Usage: curl [options...]…

如何快速部署一款小程序

小程序现在大家都不陌生&#xff0c;微信&#xff0c;qq&#xff0c;抖音&#xff0c;支付宝等等都有小程序&#xff0c;今天给的大家带有通用的小程序&#xff0c;如何快速部署两种方式&#xff1a;自己纯手工开发&#xff0c;或者找别人开发不管哪种方式&#xff0c;今天我带…

【数据结构与算法】选择排序

文章目录选择排序什么是选择排序&#xff1f;选择排序实例分析算法分析代码部分选择排序 什么是选择排序&#xff1f; 选择排序是一种简单直观的排序算法。 它的工作原理是&#xff1a;每一轮从待排序列中选取一个值最小的元素&#xff0c;将它和当前序列的第一个元素互换。 可…

【GD32F427开发板试用】4. ADC采集摇杆模块移动量

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;hehung 之前发帖 【GD32F427开发板试用】1. 串口实现scanf输入控制LED 【GD32F427开发板试用】2. RT-Thread标准版移植 【GD32F427开发板试用…

vue利用provide和inject做套娃组件设计

provide和inject原来用的不多&#xff0c;只是见人引用axios的时候在main.js里使用provide来注入 app.provide(axios, axios) 这样&#xff0c;在所有的vue文件里都可以使用inject来获取这个注入的axios const axios inject("axios"); 这种利用provide和inject做…

(考研湖科大教书匠计算机网络)第一章概述-第五节3:计算机网络体系结构之相关专业术语

文章目录一&#xff1a;实体二&#xff1a;协议三&#xff1a;服务四&#xff1a;协议数据单元本节对应视频 【计算机网络微课堂&#xff08;有字幕无背景音乐版&#xff09;】&#xff1a;1.6 计算机网络体系结构&#xff08;4&#xff09;—专用术语 注意&#xff1a;本节内容…

2023MyBatis精选面试题2(8道)

一. MyBatis的框架架构设计是怎么样的这张图从上往下看。MyBatis的初始化&#xff0c;会从mybatis-config.xml配置文件&#xff0c;解析构造成Configuration这个类&#xff0c;就是图中的红框。1. 加载配置&#xff1a;配置来源于两个地方&#xff0c;一处是配置文件&#xff0…

【阅读笔记】《重构》 第一二章

第一章 重构&#xff0c;第一个案例 编译器不会在乎代码好不好看&#xff0c;都是正常运行的。但人在乎&#xff0c;差劲的系统很难修改&#xff0c;因为很难找到修改点&#xff0c;导致程序员很有可能犯错&#xff0c;从而引入bug 重构的第一步 得为即将修改的代码建立一组…

自动化测试Selenium【基础篇一】

自动化测试Selenium【基础篇一】&#x1f34e;一.什么是自动化测试&#x1f352;1.1 自动化测试介绍&#x1f352;1.2 单元测试&#x1f352;1.3 接口自动化&#x1f352;1.4 UI自动化&#x1f352;1.5 为什么选择selenium作为我们的web自动化工具?&#x1f352;1.6什么是驱动…

DaVinci:限定器 - RGB

调色页面&#xff1a;限定器Color&#xff1a;Qualifier限定器 - RGB Qualifier - RGB根据像素的三原色通道&#xff08;红、绿、蓝&#xff09;的值来选择画面上的对应区域&#xff0c;从而限制节点调色的范围。限定器 - RGB 根据指定的各个原色通道的色阶范围来选择连续的近似…

Java 对象处理流(ObjectOutputStream\ObjectInputStream)

文章目录前言什么是对象流&#xff1f;基本介绍ObjectOutputStreamObjectInputStream对象处理流的使用细节前言 处理流&#xff1a;是对一个已存在的流进行处理和封装&#xff0c;通过所封装的流的功能调用实现对数据的操作。而处理流中也有不同的分类&#xff0c;此片介绍的是…

C规范编辑笔记(十一)

往期文章&#xff1a; C规范编辑笔记(一) C规范编辑笔记(二) C规范编辑笔记(三) C规范编辑笔记(四) C规范编辑笔记(五) C规范编辑笔记(六) C规范编辑笔记(七) C规范编辑笔记(八) C规范编辑笔记(九) C规则编辑笔记(十) 正文&#xff1a; 因为太久没有更新了&#xff0c;今天就…

Elasticsearch7.8.0版本高级查询—— 多关键字精确查询文档

目录一、初始化文档数据二、多关键字精确查询文档2.1、概述2.2、示例一、初始化文档数据 在 Postman 中&#xff0c;向 ES 服务器发 POST 请求 &#xff1a;http://localhost:9200/user/_doc/1&#xff0c;请求体内容为&#xff1a; {"name":"张三","…

干货 | 算力网络节点可信度评估和安全管控方案

以下内容整理自清华大学《数智安全与标准化》课程大作业期末报告同学的汇报内容。第一部分&#xff1a;算力网络第二部分&#xff1a;可信度评估一、可信度评估在整个算力网络处理任务的实施流程中&#xff0c;不同部分有不同可信度评估的方法&#xff0c;具体包括&#xff1a;…