目录
[📄 delay.c --- 延时函数](#📄 delay.c — 延时函数)
[📄 delay.h --- 延时函数头文件](#📄 delay.h — 延时函数头文件)
[📄 digiter.h --- 数码管头文件](#📄 digiter.h — 数码管头文件)
[📄 digiter.c --- 数码管驱动](#📄 digiter.c — 数码管驱动)
[① 段码表](#① 段码表)
[② 显示缓冲区](#② 显示缓冲区)
[③ Bit_Select() --- 位选函数](#③ Bit_Select() — 位选函数)
[④ Seg_Select() --- 段选函数](#④ Seg_Select() — 段选函数)
[⑤ num_to_buff() --- 数字拆位存入缓冲区](#⑤ num_to_buff() — 数字拆位存入缓冲区)
[⑥ digiter_show() --- 动态显示核心](#⑥ digiter_show() — 动态显示核心)
[📄 main.c --- 主程序](#📄 main.c — 主程序)
[🗺️ 整体数据流总结](#🗺️ 整体数据流总结)
项目
├── main.c → 程序入口,控制整体逻辑
├── delay.c → 延时函数的实现
├── delay.h → 延时函数的声明(头文件)
├── digiter.c → 数码管驱动的实现
└── digiter.h → 数码管函数的声明(头文件)
为什么要拆成这么多文件? 这是"模块化编程"思想:把不同功能的代码分开写,方便维护和复用。
.h是"菜单"(告诉别人有哪些函数),.c是"厨房"(函数的具体实现)。
📄 delay.c --- 延时函数
cs
void delay(unsigned int n)
{
while (n--)
{
// 什么都不做,空循环消耗时间
}
}
讲解:
- 单片机执行每条指令都需要时间。这个函数就是让CPU"原地踏步",什么都不干,来产生一段时间延迟。
n--是"先用n的值判断,再让n减1",所以循环执行 n 次后退出。- 传入的
n越大,延时越长。 - 在51单片机上这是最简单的软件延时方法,不精确但够用。
📄 delay.h --- 延时函数头文件
cs
#ifndef _DELAY_H_
#define _DELAY_H_
extern void delay(unsigned int n);
#endif
讲解:
| 代码 | 含义 |
|---|---|
#ifndef / #define / #endif |
防重复包含保护。防止同一个头文件被 #include 两次导致报错 |
extern void delay(...) |
声明"delay这个函数存在于某个.c文件里",让其他文件可以使用它 |
📄 digiter.h --- 数码管头文件
cs
extern void Bit_Select(unsigned char n); // 选择第几位数码管
extern void Seg_Select(unsigned int n); // 选择显示什么数字
extern void num_to_buff(unsigned long n); // 把数字拆开存入缓冲区
extern void digiter_show(void); // 执行一次动态显示刷新
这是"菜单",告诉 main.c 可以调用哪些函数。
📄 digiter.c --- 数码管驱动
① 段码表
bash
unsigned char seg_table[] =
{
0x3F, // 0
0x06, // 1
0x5B, // 2
0x4F, // 3
0x66, // 4
0x6D, // 5
0x7D, // 6
0x07, // 7
0x7F, // 8
0x6F // 9
};
数码管本质是7段LED灯(a~g段),要显示"0"就要点亮对应的段。0x3F 转成二进制是 0011 1111,对应点亮 a、b、c、d、e、f 六段,恰好组成"0"的形状。
_
|_| ← 这就是 "0",需要6个LED段亮起来
|_|
② 显示缓冲区
unsigned char display_buf[8] = {0,0,0,0,0,0,0,0};
8个元素对应8位数码管,每个元素存储该位要显示的数字(0-9)。
③ Bit_Select() --- 位选函数
cs
void Bit_Select(unsigned char n)
{
switch (n)
{
case 1: // 选第1位:A=0, B=0, C=0
P2 &= ~(1 << 2); // P2.2 清0
P2 &= ~(1 << 3); // P2.3 清0
P2 &= ~(1 << 4); // P2.4 清0
break;
case 2: // A=1, B=0, C=0
P2 |= (1 << 2);
P2 &= ~(1 << 3);
P2 &= ~(1 << 4);
break;
// ... 以此类推到 case 8
}
}
讲解:
硬件上用了一个3-8译码器(74HC138),只需要3根引脚(P2.2、P2.3、P2.4)就能控制8位数码管选哪一位亮。
A(P2.2) B(P2.3) C(P2.4) → 选中第几位
0 0 0 → 第1位(Y0)
1 0 0 → 第2位(Y1)
0 1 0 → 第3位(Y2)
...
1 1 1 → 第8位(Y7)
P2 |= (1 << 2) 的意思是:把P2寄存器的第2位置1,其余位不变
④ Seg_Select() --- 段选函数
void Seg_Select(unsigned int n)
{
P0 = seg_table[n];
}
讲解:
把要显示的数字 n(0~9)通过段码表转换成8位控制信号,直接输出到 P0 口,控制数码管的a~g各段亮灭。
⑤ num_to_buff() --- 数字拆位存入缓冲区
bash
void num_to_buff(unsigned long n)
{
int i = 0;
// 先清空缓冲区
for (i = 0; i < 8; i++) display_buf[i] = 0;
// 逐位拆解:个位、十位、百位...
i = 0;
while (n != 0 && i < 8)
{
display_buf[i++] = n % 10; // 取个位
n /= 10; // 去掉个位
}
}
举例: 输入 1234
n % 10 = 4 → display_buf[0] = 4(个位)
n = 123
n % 10 = 3 → display_buf[1] = 3(十位)
n = 12
n % 10 = 2 → display_buf[2] = 2(百位)
n = 1
n % 10 = 1 → display_buf[3] = 1(千位)
n = 0,结束
缓冲区结果:[4, 3, 2, 1, 0, 0, 0, 0](低位在前)
⑥ digiter_show() --- 动态显示核心
bash
void digiter_show(void)
{
static int pos = 1; // 记住上次显示到第几位(static很关键!)
P0 = 0; // 先关闭所有段(消隐,防止鬼影)
Bit_Select(pos); // 选中当前位
if (display_buf[pos-1] != 0)
{
Seg_Select(display_buf[pos-1]); // 显示对应数字
}
delay(100); // 停留一小段时间让眼睛看到
pos++; // 移到下一位
if (pos > 8) pos = 1; // 循环回第1位
}
动态显示原理:
8位数码管不是同时亮的!而是轮流快速点亮 ,利用人眼的视觉暂留效应(余晖效应),让你以为它们是同时亮的:
bash
时刻1: 只亮第1位 → 显示个位数字 → delay
时刻2: 只亮第2位 → 显示十位数字 → delay
时刻3: 只亮第3位 → 显示百位数字 → delay
...
时刻8: 只亮第8位 → delay
时刻9: 回到第1位...(速度极快,眼睛感觉是同时亮的)
static int pos :static 让这个变量在函数调用之间保持上次的值,不会每次调用都重置为1。
📄 main.c --- 主程序
bash
#include <reg51.h> // 51单片机寄存器定义(P0、P1、P2...从这里来)
#include "digiter.h"
#include "delay.h"
int main(void)
{
unsigned long i = 0;
while (1) // 死循环,单片机程序永远不能退出
{
for (i = 0; i < 99999999; i++)
{
num_to_buff(i); // 把 i 拆位存入缓冲区
digiter_show(); // 刷新显示一位数码管
}
}
return 0;
}
整体流程:
i=0 → num_to_buff(0) → digiter_show()(显示第1位)
num_to_buff(0) → digiter_show()(显示第2位)
...(注意:每次for循环只刷新一位!)
i=1 → num_to_buff(1) → digiter_show()(继续下一位)
...
digiter_show()每次只刷新一位,但num_to_buff(i)在每次循环都被调用。这意味着数字显示会更新得很频繁。
🗺️ 整体数据流总结
cs
main.c
│
├─ num_to_buff(1234)
│ └─ 把1234拆成 [4,3,2,1,0,0,0,0] 存入 display_buf[]
│
└─ digiter_show() ← 每调用一次,只显示一位
├─ P0 = 0 (消隐)
├─ Bit_Select(pos) → 操作 P2.2/P2.3/P2.4 → 译码器选位
├─ Seg_Select(数字) → P0 = seg_table[数字] → 点亮对应段
├─ delay(100) → 停留一段时间
└─ pos++ → 准备下一位