51单片机编程学习笔记——动态数码管显示多个数字
大纲
- 视觉残留
- 原理
- 生理基础
- 神经传导与处理
- 应用
- 与视觉暂留相关的现象
- 频闪融合
- 不好的实现
- 好的效果
- 延伸
在《51单片机编程学习笔记——动态数码管》一文中,我们看到如何使用动态数码管显示数字。但是基于动态数码管设计的特点,每次只能显示1个数字。这就不能让我们一次性显示多个数字,比如666就无法显示。
如果我们要让动态数码管“显示”多个数,就要结合我们人眼的视觉残留。
人眼的视觉残留又称视觉暂留,是指物体在快速运动时,当人眼所看到的影像消失后,人眼仍能继续保留其影像 0.1 - 0.4 秒左右的图像。
视觉残留
原理
生理基础
这一现象主要与视网膜上的感光细胞有关。感光细胞在受到光刺激后,会产生神经冲动并传递给大脑。当光刺激消失后,感光细胞并不会立即停止工作,而是会有一个短暂的 “余晖” 效应,使得神经冲动的发放和大脑对图像的感知会延续一小段时间。
神经传导与处理
从视网膜到大脑视觉中枢的神经传导过程也会对视觉残留产生影响。神经信号的传递和处理需要一定时间,当新的视觉信息快速更替时,大脑还来不及完全清除上一个图像的信息,就会出现视觉残留。
应用
- 电影:电影胶片以每秒 24 帧的速度播放,每帧画面在人眼中停留的时间非常短,但由于视觉残留,前一帧画面的影像还未在人眼中消失,下一帧画面就已经出现,这样就使得一系列静态的画面在人眼中形成了连续的动态影像。
- 动画:传统手绘动画通过绘制一系列略有差异的静态画面,然后以一定的速度播放这些画面,利用视觉残留让观众看到连贯的动画效果。在现代数字动画制作中,同样是基于视觉残留原理,通过计算机生成大量的帧,快速播放来呈现生动的动画。
- CRT 显示器:阴极射线管(CRT)显示器通过电子枪发射电子束来激发荧光粉发光,形成图像。由于荧光粉发光后会有短暂的余辉,利用视觉残留,使得快速扫描的电子束形成的一幅幅图像能够在人眼中融合成连续的画面。
与视觉暂留相关的现象
- 余晖效应:在黑暗环境中,如果快速移动一个发光的物体,人眼会看到物体移动的轨迹好像有一条 “尾巴”,这就是余晖效应,是视觉残留的一种直观表现。
- 频闪融合:当闪烁的光源频率达到一定程度时,人眼会感觉不到闪烁,而是看到一个稳定的光源,这也是基于视觉残留,使得闪烁的光在人眼中融合成了连续的光。
频闪融合
在《51单片机编程学习笔记——动态数码管》中,我们实现了display方法。通过它,我们可以指定在左起index(0~7)位置显示单个数字的能力。
#include <REG52.H>
#include <intrins.h>void Delay1ms() //@11.0592MHz
{unsigned char i, j;_nop_();i = 2;j = 199;do{while (--j);} while (--i);
}void Delay(unsigned int millisecond) {unsigned int i = 0;for (i = 0; i < millisecond; i++) {Delay1ms(); }
}sbit P22 = P2^2;
sbit P23 = P2^3;
sbit P24 = P2^4;void select_led_index(unsigned char index) {// 如果index=0,则P22=1,P23=1,P24=1// 如果index=1,则P22=0,P23=1,P24=1// 如果index=2,则P22=1,P23=0,P24=1// 如果index=3,则P22=0,P23=0,P24=1// 如果index=4,则P22=1,P23=1,P24=0// 如果index=5,则P22=0,P23=1,P24=0// 如果index=6,则P22=1,P23=0,P24=0// 如果index=7,则P22=0,P23=0,P24=0P22 = (index & 0x01) ? 0 : 1;P23 = (index & 0x02) ? 0 : 1;P24 = (index & 0x04) ? 0 : 1;
}void display(unsigned char index, unsigned char digit) { select_led_index(index);P0 = digit;
}
那么我们就可以基于display方法实现一个依次显示多个数字的方法show_number。
不好的实现
在第一版的show_number中,我们从右向左,依次算出每位数字,将其保存到show_digit数组中。比如我们要显示98762345,则show_digit保存的是[9,8,7,6,2,3,4,5]。
void show_number(unsigned long number) {unsigned char i;unsigned char show_digit[8] = {0};unsigned char digit[10] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f};for (i = 0; i < 8; i++) {show_digit[7-i] = number % 10;number /= 10;}for (i = 0; i < 8; i++) {display(i, digit[show_digit[i]]);}
}
在main函数的while循环中,我们
void main() {unsigned int i = 0;while(1) {i = 98762345;show_number(i);}
}

可以看到,这个显示并不好,只有最后一位显示的比较明显,但是还是错误的——将第一位9显示到最后一位。
造成这个现象的原因是多种的,其中包括动态数码管的残影,以及视觉残留没有很好的形成。
回顾动态数码管的原理图,可以看到显示哪一位数字是由上侧的LED1~LED8引脚决定的;而显示什么数字,则是由P0决定。

通过对比不通电状态下的动态数码管,可以发现上述代码还是让各个位置都点亮了。只是除了最后一位很亮之外,其他都不太亮。

那我们先要解决一个问题,就是让各个位置都要同等程度亮起来。
根据之前人眼“视觉残留”的原理,前几位不太亮的根本原因是它们亮的时间太短了,没有给我们视觉神经足够的刺激,导致没有形成足够的“亮度”信号残留。
基于这样的分析,我们只要让每一位显示时间长一点就行了。(如果出现快速闪烁,那就是Delay的时间太长了,“视觉残留”没有很好的形成,神经系统已经察觉到了我们的“作弊行为”。)
void show_number(unsigned long number) {unsigned char i;unsigned char show_digit[8] = {0};unsigned char digit[10] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f};for (i = 0; i < 8; i++) {show_digit[7-i] = number % 10;number /= 10;}for (i = 0; i < 8; i++) {display(i, digit[show_digit[i]]);Delay(1); // 每位多显示1ms}
}

可以看到,展示效果得到了很好的改善。但是还有部分重影,或者说:不该亮的地方还是有点亮。
下图是987这三个数的显示。可以看到组成数组7的,不该亮的几个二极管,还是有点亮。

要解决这个问题,就要消除动态数码管的残影。具体方法就是在现实下个数字前,让P0处于低电位,这样就可以消除让之前所有处于高电位的二极管熄灭。
好的效果
void show_number(unsigned long number) {unsigned char i;unsigned char show_digit[8] = {0};unsigned char digit[10] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f};for (i = 0; i < 8; i++) {show_digit[7-i] = number % 10;number /= 10;}for (i = 0; i < 8; i++) {display(i, digit[show_digit[i]]);Delay(1);P0 = 0x00;}
}
可以看到显示的987的对比度要好一些了。

延伸
如果我们希望动态数码管显示的数字一直在递增,一种比较容易想到的方法是
void main() {unsigned long i = 0;unsigned int j = 0;while(1) {for(i=0;i<99999999;i++) {show_number(i);}}
}
这种实现会让最后一位变化的特别快。如果我们希望最后一位的变化可以被我们肉眼识别,一种比较容易想到的办法是
void main() {unsigned long i = 0;unsigned int j = 0;while(1) {for(i=0;i<99999999;i++) {show_number(i);Delay(10);}}
}
这个方案存在的问题是:如果Delay时间太长,视觉残留被打破,我们看到明显的闪烁;如果Delay时间太短,最后一位递增太快。
针对这个问题,我们可以使用下面这个方法。即多增加一层循环,让显示更加清晰(保持了高频刷新,视觉残留被加强了),同时最后一位递增速度变慢了。
void main() {unsigned long i = 0;unsigned int j = 0;while(1) {for(i=0;i<99999999;i++) {for (j = 0; j < 10; j++) {show_number(i);}}}
}

相关文章:
51单片机编程学习笔记——动态数码管显示多个数字
大纲 视觉残留原理生理基础神经传导与处理 应用与视觉暂留相关的现象 频闪融合不好的实现好的效果 延伸 在《51单片机编程学习笔记——动态数码管》一文中,我们看到如何使用动态数码管显示数字。但是基于动态数码管设计的特点,每次只能显示1个数字。这就…...
金蝶ERP星空对接流程
1.金蝶ERP星空OPENAPI地址: 金蝶云星空开放平台 2.下载金蝶云星空的对应SDK包 金蝶云星空开放平台 3.引入SDK流程步骤 引入Kingdee.CDP.WebApi.SDK 右键项目添加引用,在打开的引用管理器中选择浏览页签,点击浏览按钮,找到从官…...
【随手笔记】利尔达NB模组
1.名称 移芯EC6263GPP 参数 指令备注 利尔达上电输出 [2025-03-04 10:24:21.379] I_AT_WAIT:i_len2 [2025-03-04 10:24:21.724] LI_AT_WAIT:i_len16 [2025-03-04 10:24:21.724] [2025-03-04 10:24:21.733] Lierda [2025-03-04 10:24:21.733] [2025-03-04 10:24:21.745] OK移…...
Vue3的核心语法【未完】
Vue3的核心语法 OptionsAPI与CompositionAPI Options API(选项式) 和 Composition API (组合式)是 Vue.js 中用于构建组件的两种不同方式。Options API Options API Options API 是 Vue 2 中的传统模式,并在 Vue 3…...
解决redis lettuce连接池经常出现连接拒绝(Connection refused)问题
一.软件环境 windows10、11系统、springboot2.x、redis 6 7 linux(centos)系统没有出现这问题,如果你是linux系统碰到的,本文也有一定大参考价值。 根本思路就是:tcp/ip连接的保活(keepalive)。 二.问题描述 在spr…...
C#进阶指南
C# 是一种功能强大的编程语言,其高级语法特性为开发者提供了更灵活、高效和简洁的编程方式。以下是一些常见的 C# 高级语法特性: 1. 委托(Delegate) 委托是一种类型安全的函数指针,用于封装方法的引用。它可以将方法作为参数传递,实现回调机制。 定义委托: csharp复制 …...
从DNS到TCP:DNS解析流程和浏览器输入域名访问流程
1 DNS 解析流程 1.1 什么是DNS域名解析 在生活中我们会经常遇到域名,比如说CSDN的域名www.csdn.net,百度的域名www.baidu.com,我们也会碰到IP,现在目前有的是IPV4,IPV6。那这两个有什么区别呢?IP地址是互联网上计算机…...
【MySQL、Oracle、SQLserver、postgresql】查询多条数据合并成一行
四大数据库多行合并为单行:函数详解与对比 一、MySQL**GROUP_CONCAT()** 函数说明:语法结构:参数解释:示例:注意事项: 二、Oracle**LISTAGG()** 函数说明:语法结构:参数解释…...
解锁Egg.js:从Node.js小白到Web开发高手的进阶之路
一、Egg.js 是什么 在当今的 Web 开发领域,Node.js 凭借其事件驱动、非阻塞 I/O 的模型,在构建高性能、可扩展的网络应用方面展现出独特的优势 ,受到了广大开发者的青睐。它让 JavaScript 不仅局限于前端,还能在服务器端大展身手&…...
JavaWeb后端基础(4)
这一篇就开始是做一个项目了,在项目里学习,我主要记录在学习过程中遇到的问题,以及一些知识点 Restful风格 一种软件架构风格 在REST风格的URL中,通过四种请求方式,来操作数据的增删改查。 GET : 查询 …...
软件试用 防破解 防软件调试(C# )
防破解&防软件调试 实现思路 这里采用C#语言为例: 获取网络北京时间:向百度发送 HTTP 请求,从响应头中提取日期时间信息,将其转换为本地时间。记录试用开始时间:首次运行软件时,将获取的百度北京时间作为试用开始时间,并加密存储在本地文件中。检查试用是否过期:每…...
【文献阅读】The Efficiency Spectrum of Large Language Models: An Algorithmic Survey
这篇文章发表于2024年4月 摘要 大语言模型(LLMs)的快速发展推动了多个领域的变革,重塑了通用人工智能的格局。然而,这些模型不断增长的计算和内存需求带来了巨大挑战,阻碍了学术研究和实际应用。为解决这些问题&…...
OpenGL ES -> GLSurfaceView纹理贴图
贴图 XML文件 <?xml version"1.0" encoding"utf-8"?> <com.example.myapplication.MyGLSurfaceViewxmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height…...
FastGPT 源码:基于 LLM 实现 Rerank (含Prompt)
文章目录 基于 LLM 实现 Rerank函数定义预期输出实现说明使用建议完整 Prompt 基于 LLM 实现 Rerank 下边通过设计 Prompt 让 LLM 实现重排序的功能。 函数定义 class LLMReranker:def __init__(self, llm_client):self.llm llm_clientdef rerank(self, query: str, docume…...
DE2115实现4位全加器和3-8译码器(FPGA)
一、配置环境 1、Quartus 18.1安装教程 软件:Quartus版本:Quartus 18.1语言:英文大小:5.78G安装环境:Win11/Win10/Win8/Win7硬件要求:CPU2.0GHz 内存4G(或更高) 下载通道①百度网盘丨64位下载…...
大语言模型(LLM)如何赋能时间序列分析?
引言 近年来,大语言模型(LLM)在文本生成、推理和跨模态任务中展现了惊人能力。与此同时,时间序列分析作为工业、金融、物联网等领域的核心技术,长期依赖传统统计模型(如ARIMA)或深度学习模型&a…...
【AI大模型】DeepSeek + Kimi 高效制作PPT实战详解
目录 一、前言 二、传统 PPT 制作问题 2.1 传统方式制作 PPT 2.2 AI 大模型辅助制作 PPT 2.3 适用场景对比分析 2.4 最佳实践与推荐 三、DeepSeek Kimi 高效制作PPT操作实践 3.1 Kimi 简介 3.2 DeepSeek Kimi 制作PPT优势 3.2.1 DeepSeek 优势 3.2.2 Kimi 制作PPT优…...
如何快速上手RabbitMQ 笔记250304
如何快速上手RabbitMQ 要快速上手 RabbitMQ,可以按照以下步骤进行,从安装到基本使用逐步掌握核心概念和操作: 1. 理解核心概念 Producer(生产者):发送消息的程序。Consumer(消费者)…...
run方法执行过程分析
文章目录 run方法核心流程SpringApplicationRunListener监听器监听器的配置与加载SpringApplicationRunListener源码解析实现类EventPublishingRunListener 初始化ApplicationArguments初始化ConfigurableEnvironment获取或创建环境配置环境 打印BannerSpring应用上下文的创建S…...
面试-----每日一题
一、字节一面(操作系统) 什么是死锁?如何处理死锁问题? 死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通讯而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。…...
学习与事务平衡技巧
当学习过程中需要处理其他事务时,关键在于平衡专注与灵活性,避免中断打乱学习节奏。以下是具体建议: 一、快速判断事务优先级 紧急且重要(如突发工作、紧急回复): 立刻处理,但完成后用5分钟快速…...
CentOS 7中安装Dify
Dify 是一个开源的 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、Agent、模型管理、可观测性功能等,让您可以快速从原型到生产。尤其是我们本地部署DeepSeek等大模型时,会需要用到Dify来帮我们快捷的开发和应用。 大家可以参考学习它的中…...
qt-C++笔记之Linux下Qt环境变量设置及与QtCreator的关系
qt-C++笔记之Linux下Qt环境变量设置及与QtCreator的关系 code review! 文章目录 qt-C++笔记之Linux下Qt环境变量设置及与QtCreator的关系一.Qt关键的环境变量1.1.PATH1.2.LD_LIBRARY_PATH1.3.QML2_IMPORT_PATH二.若不手动设置这三个环境变量2.1.PATH 的默认路径2.2.LD_LIBRARY_…...
问deepseek:有哪些支持OpenMP多线程并行的AMG代数多重网格软件库
AMG(Algebraic Multigrid)是一种用于求解大规模稀疏线性方程组的有效方法,广泛应用于科学计算和工程领域。OpenMP 是一种用于共享内存并行编程的 API,支持多线程并行计算。 以下是一些支持 OpenMP 多线程的开源 AMG 软件包&#…...
【Flink银行反欺诈系统设计方案】1.短时间内多次大额交易场景的flink与cep的实现
【flink应用系列】1.Flink银行反欺诈系统设计方案 1. 经典案例:短时间内多次大额交易1.1 场景描述1.2 风险判定逻辑 2. 使用Flink实现2.1 实现思路2.2 代码实现2.3 使用Flink流处理 3. 使用Flink CEP实现3.1 实现思路3.2 代码实现 4. 总结 1. 经典案例:短…...
Android 系统开发的指导文档
Android 系统开发的指导文档 文章目录 Android 系统开发的指导文档一、基础准备1、学习编程语言2、熟悉开发环境 二、核心知识学习1、Android 系统架构:2、四大组件(1)Activity:是 Android 应用中最基本的组件,用于实现…...
网络编程——http
在Linux系统中使用C语言实现HTTP客户端或服务器通常涉及使用套接字编程和一些HTTP协议的基本知识。下面是一个简单的示例,展示了如何用C语言实现一个HTTP客户端,向一个HTTP服务器发送请求并接收响应。 1. HTTP客户端示例 (C语言) 这个例子展示了如何用C…...
Flutter 学习之旅 之 flutter 使用 carousel_slider 简单实现轮播图效果
Flutter 学习之旅 之 flutter 使用 carousel_slider 简单实现轮播图效果 目录 Flutter 学习之旅 之 flutter 使用 carousel_slider 简单实现轮播图效果 一、简单介绍 二、简单介绍 carousel_slider 三、安装 carousel_slider 四、简单案例实现 五、关键代码 一、简单介…...
【JavaScript—前端快速入门】JavaScript 对象与函数
JavaScript 对象 1. JavaScripe 数组 创建数组的方式 使用 new 关键字创建 使用字面量方式创建 [常用] 注意,JavaScipt 不要求数组元素类型都相同; 数组操作 读:使用下标的方式访问数组元素(从0开始) 保存代码,打开…...
java中的局部变量
文章目录 一、定义二、作用域和作用位置三、声明周期和初始化四、内存管理五、Java内存区域划分六、例子 一、定义 在java中,局部变量指在方法、构造方法、代码块(如{}包裹的语句块)内部声明的变量 class work {{int a 10;}public work() {i…...
