单片机的数码管原理和模块分析(以STC的IAP15F2K61S2芯片为例)
数码管有8个独立的发光的二极管。如下图。
理论部分的要点:
数码管的内部结构原理:

数码管有8个独立的发光的二极管。如下图


动态扫描的原理:
既然A-DP的8个端口都是相同的,那我们好像没有办法让8个数码管输出不一样的数字啊。
这里就需要一个:动态内存扫描
假设我们想要让前4个数码管显示1234(如图)

基于我们刚刚的,虽然A-DP是公用的,但是每一位数码管我们都有独立的com引脚来控制哪一个数码管亮,例如我们让4个端口都是显示1

然后熄灭后三位(com2-4给低电平)

这个是可以完全实现的
类似的让他全部显示为2

然后熄灭1,3,4(com1,com3,com4低电平),com2高电平

以此类推,我们让最后只显示4

我们就让他对应的位置显示相应的数字。
那么我们就可以让它更快切换1234,让他循环的速度足够快,这样我们人眼就看出来他是连贯的,那么这样以此无论是8个数码管也是一样的原理。这就是动态扫描的原理。
那么他的a1-dp1又是连接到哪里的呢?看下图

这里就是一个74HC573锁存器的原理了,再之前的LED点亮的博客里面有写:
单片机的LED原理和模块分析(STC的IAP15F2K61S2芯片为例)-CSDN博客
共阳数码管段码表:

解释:
举第一个例子:0xC0
0xc0换成2进制就是1100 0000,分别对应着8个端口dp g f e d c b a(从高到低的),根据上面分析的,数码管的LED的正极已经是高电平了,我们直接给负极(8个端口的那一段)输入低电平就行,就是现在的dp g这两个给高电平(不会亮),其他低电平(点亮),那么这样就可以知道,他在数码管上显示的就是0了。
其他的显示数字原理一样不在重复赘述。
关于上面段码的注意事项:
为什么字母A-F有些显示的是大写,有些是小写呢,b显示大写就跟8一样了,d写成大写就是0,他们就很数字共用了一个段码,就没有必要了。
实践部分和代码原理分析:——主要看两个函数
Seg_Tran函数——显示的数字转换成数码管能识别的段码
代码:
void Seg_Tran(unsigned char* pucseg_buf,unsigned char* pucseg_code)
{
//这里pucseg_buf是我们想要让数码管显示的数字,pucseg_code是段码,char*是要传入数组,要用指针变量的形式接收
unsigned char i,j;
for(i=0,j=0;i<=7;i++,j++)
{
switch(pucSeg_Buf[j])
{
case '0':
pucSeg_Code[i] = 0xC0;
break;
case '1':
pucSeg_Code[i] = 0xF9;
break;
case '2':
pucSeg_Code[i] = 0xA4;
break;
case '3':
pucSeg_Code[i] = 0xB0;
break;
case '4':
pucSeg_Code[i] = 0x99;
break;
case '5':
pucSeg_Code[i] = 0x92;
break;
case '6':
pucSeg_Code[i] = 0x82;
break;
case '7':
pucSeg_Code[i] = 0xF8;
break;
case '8':
pucSeg_Code[i] = 0x80;
break;
case '9':
pucSeg_Code[i] = 0x90;
break;
case 'A':
pucSeg_Code[i] = 0x88;
break;
case 'b':
pucSeg_Code[i] = 0x83;
break;
case 'C':
pucSeg_Code[i] = 0xC6;
break;
case 'd':
pucSeg_Code[i] = 0xA1;
break;
case 'E':
pucSeg_Code[i] = 0x86;
break;
case 'F':
pucSeg_Code[i] = 0x8E;
break;
case ' ':
pucSeg_Code[i] = 0xFF;
break;
}
if(pucSeg_Buf[j+1] == '.')//1234.5678
{
pucSeg_Code[i] &= 0x7F;//123(4.)5678
j++;
}
}
}
两个变量:
-
j:用来遍历pucseg_buf,取要显示的字符 -
i:用来给pucseg_code赋值
注意:这里 i 和 j 是同步增加的,所以效果上 pucseg_code[i] 和 pucseg_buf[j] 是对应位置的。
switch(pucseg_buf[j]):
代码作用:
根据 pucseg_buf[j] 的值(一个字符),决定执行哪个 case。
举例:
当 j=0,pucseg_buf[0] 是 '1',就会匹配 case '1'。
case '1': pucseg_code[i] = 0xf9;:
代码作用:
将段码 0xF9 存入 pucseg_code 的第 i 个位置。
段码 0xF9 的含义:
-
0xF9=1111 1001(二进制) -
对应数码管的段:dp g f e d c b a
-
1表示灭,0表示亮 -
结果是:b、c 段亮,其他灭 → 显示数字 1
if(pucSeg_Buf[j+1] == '.')
{
pucSeg_Code[i] &= 0x7F;//123(4.)5678
j++;
}
如果要显示的数字的下一位是有.的话,那就再他的前一位加上0(点亮要取反),把4.看成是一个整体
Seg_Disp函数——将指定的数码管位置上显示对应的段码。
void Seg_Disp(unsigned char *pucSeg_Code, unsigned char ucSeg_Pos)
{
P0 = pucSeg_Code[ucSeg_Pos];
P2 = P2 & 0x1F | 0xE0;
P2 &= 0x1F;
P0 = 1<<ucSeg_Pos;
P2 = P2 & 0x1F | 0xC0;
P2 &= 0x1F;
}
1、送段码
1、P0 = pucseg_code[ucSeg_Pos];//送段码
把当前要显示的段码送到 P0 口。
举例:
如果 ucSeg_Pos = 2,就取 pucseg_code[2] 的值(比如 0xB0,显示 3)送到 P0。
2、P2 = P2 & 0x1F | 0xE0;
拆解:
-
0xE0=1110 0000 -
P2 & 0x1F:保留低 5 位 -
| 0xE0:强制 P27=1, P26=1, P25=1
结果:
P27=1, P26=1, P25=1 → 二进制 111 → 选中 Y7
硬件意义:
选中 Y7 对应的锁存器(段码锁存器),LE 端为高电平,数据进入锁存器
3、 P2 &= 0x1F;
清零高 3 位,P27~P25 全变 0,选中 Y0。
硬件意义:
LE 端变低,数据被锁存。段码被固定住。
2、送位码
1、P0 = 1<<ucSeg_Pos;
将 1 左移 ucSeg_Pos 位,结果送到 P0。
移位效果:
-
ucSeg_Pos = 0→1 << 0=0000 0001 -
ucSeg_Pos = 1→1 << 1=0000 0010 -
ucSeg_Pos = 2→1 << 2=0000 0100 -
...
-
ucSeg_Pos = 7→1 << 7=1000 0000
硬件意义:
每个位对应一个数码管的公共端(COM)。哪个位是 1,哪个数码管就被选中点亮。
2、P2 = P2 & 0x1F | 0xC0;
拆解:
-
0xC0=1100 0000 -
强制 P27=1, P26=1, P25=0 → 二进制
110→ 选中 Y6
硬件意义:
选中 Y6 对应的锁存器(位码锁存器),LE 端为高电平,位码数据进入。
3、 P2 &= 0x1F;
代码作用:
再次锁存,位码被固定。此时指定位置的数码管亮起,显示刚才送进去的段码。
主文件的代码:
#include "init.h"
#include "led.h"
#include "seg.h"
#include "time.h"
//Seg
unsigned char pucSeg_Buf[12], pucSeg_Code[8], pucSeg_Pos = 0;
//Timer
unsigned long ulms = 0;
unsigned int uiSeg_Dly = 0;
void Seg_Proc(void);
void main(void)
{
Cls_Peripheral();//清除外设函数(led 蜂鸣器)
Timer0Init();//定时器的声明
EA = 1;
while(1)
{
Seg_Proc();//单片机工作停不下来
}
}
void Seg_Proc(void)
{
if(uiSeg_Dly < 200)
return;
uiSeg_Dly = 0;
sprintf(pucSeg_Buf, "12.3 124 ");
Seg_Tran(pucSeg_Buf, pucSeg_Code);
}
void Time_0(void) interrupt 1
{
ulms++;
uiSeg_Dly++;
if(ulms % 2 == 0)
{
pucSeg_Pos = (pucSeg_Pos + 1)%8;
Seg_Disp(pucSeg_Code, pucSeg_Pos);
}
}
pucSeg_Buf,长度为 12确保足够的容量
1、Cls_Peripheral();
代码作用:
调用外设清除函数,让所有外设处于初始安全状态。
注释解读:
函数会关掉LED和蜂鸣器。
2、Timer0Init();
代码作用:
调用定时器 0 初始化函数,设置每 1ms 中断一次。
注释解读:
调用
EA=1;
代码作用:
打开总中断允许位。
硬件意义:
CPU 开始响应所有已打开的中断源。此时定时器 0 每 1ms 就会触发一次中断。
Seg_Proc() 函数分析
if(uiSeg_Dly < 200) return;
代码作用:
判断 uiSeg_Dly 是否小于 200,如果是,立即退出函数。
变量追踪:
-
uiSeg_Dly在定时器中断里每 1ms 加 1 -
初始为 0
执行流程:
-
第 1~199 次进入:
uiSeg_Dly从 0 到 199,每次都<200→ 成立 → 返回 -
第 200 次进入:
uiSeg_Dly = 200→<200不成立 → 继续往下执行
作用:
实现每 200ms 才执行一次 sprintf 和 Seg_Tran,即每 200ms 更新一次要显示的内容。
2 uiSeg_Dly = 0
代码作用:
将计数器清零,让下一次计时重新开始。
3 sprintf(pucSeg_Buf, "12.3 124 ");
代码作用:
将字符串 "12.3 124 " 格式化后存入 pucSeg_Buf 数组。
字符串分析:
位置: 0 1 2 3 4 5 6 7 8 9 字符: '1' '2' '.' '3' ' ' '1' '2' '4' ' ' '\0'
显示效果:
如果连接到 8 位数码管,显示效果是:
-
第1位:1
-
第2位:2
-
第3位:.(小数点)
-
第4位:3
-
第5位:空格(灭)
-
第6位:1
-
第7位:2
-
第8位:4
实际显示:12.3 124(中间有个空格)
4 Seg_Tran(pucSeg_Buf, pucSeg_Code);
代码作用:
调用段码转换函数,将 pucSeg_Buf 里的字符转换成段码,存入 pucSeg_Code。
执行频率:
每 200ms 执行一次。
定时器中断函数
1、ulms++
代码作用:
毫秒计数器加 1,每 1ms 一次。
2、 uiSeg_Dly++
代码作用:
软件定时器计数器加 1,供主循环的 Seg_Proc() 判断使用。
3 、if(ulms % 2 == 0)
代码作用:
判断 ulms 是否是偶数。
执行频率:
-
ulms每 1ms 加 1 -
奇数和偶数交替出现
-
所以这个条件每 2ms 成立一次
硬件意义:
实现每 2ms 切换一次数码管显示位置。
4 、pucSeg_Pos = (pucSeg_Pos + 1) % 8
代码作用:
将 pucSeg_Pos 加 1,然后对 8 取余,保证在 0~7 循环。
执行频率:
每 2ms 执行一次。
作用:
实现数码管的动态扫描,每次切换一个数码管。
5、 Seg_Disp(pucSeg_Code, pucSeg_Pos)
代码作用:
调用显示函数,把 pucSeg_Code 里的段码显示在 pucSeg_Pos 指定的数码管上。
执行频率:
每 2ms 执行一次。
更多推荐



所有评论(0)