电压采集器
目录
系统概述
这是一个基于 STC89C52 单片机的电压采集与监测系统,主要功能包括:
电压数据采集(4位精度)
实时数据显示(四舍五入处理)
阈值参数设置(1.0V ~ 6.0V)
下降沿计数统计
LED 状态指示
按键错误检测
硬件配置
外设清单
| 外设 | 数量 | 功能 |
|---|---|---|
| 数码管 | 6位 | 显示电压/参数/计数 |
| 按键 | 16个 | 数据输入与模式切换 |
| LED | 3个 | 状态指示 |
按键定义
| 按键 | 功能 |
|---|---|
| 1-10 | 数字输入(对应0-9) |
| 11 | 确认/返回 |
| 12 | 模式切换 |
| 15 | 参数增加 |
| 16 | 参数减少 |
全局变量说明
头文件引用
#include <REGX52.H> // 单片机寄存器专用头文件
#include <Key.h> // 按键底层驱动专用头文件
#include <Seg.h> // 数码管底层驱动专用头文件
#include <intrins.h> // 内部函数库
#include <Led.h> // LED底层驱动专用头文件
按键相关变量
unsigned char Key_Val; // 当前按键值
unsigned char Key_Down; // 按键下降沿标志
unsigned char Key_Old; // 上次按键值
unsigned char Key_Up; // 按键上升沿标志
unsigned char Key_Slow_Down; // 按键扫描减速计数器
unsigned char Key_Error_Count; // 按键错误计数器(累计3次触发LED)
数码管相关变量
unsigned char Seg_Buf[6] = {10,10,13,13,13,13}; // 数码管显示缓冲区
unsigned char Seg_Point[6] = {0,0,0,0,0,0}; // 小数点控制数组
unsigned char Seg_Pos; // 数码管扫描位置
unsigned int Seg_Slow_Down; // 数码管扫描减速计数器
unsigned char Disp_Mode = 0; // 显示模式:0-电压采集 1-数据显示 2-参数设置 3-计数统计
电压处理变量
unsigned char Voltage_Input[4] = {13,13,13,13}; // 电压输入缓冲区
unsigned char Voltage_Data[4] = {0,0,0,0}; // 电压采集数据
unsigned char Voltage_Input_Index; // 输入索引(0-3)
unsigned char Voltage_Data_Index; // 数据索引
unsigned char Voltage_Val; // 临时输入值
unsigned char Voltage_Real_Data[3] = {0,0,0}; // 四舍五入后的3位数据
unsigned int Voltage_Real = 0; // 当前电压值(0-999)
unsigned int Voltage_Old = 0; // 上次电压值(用于下降沿检测)
unsigned int Voltage_Count = 0; // 下降沿计数器
参数设置变量
unsigned char Voltage_Setting_Data[3] = {3,0,0}; // 阈值参数数组(默认3.00V)
unsigned int Voltage_Setting; // 阈值整数值(100-600)
unsigned char Voltage_Setting_Data_Index; // 参数索引
定时与状态变量
unsigned int Timer250; // 250ms计时器(用于闪烁)
bit Input_Flag; // 闪烁标志位
unsigned int Sys_Tick; // 系统计时器(1ms精度)
unsigned char ucLed[8] = {0}; // LED状态数组
unsigned char Led_Pos; // LED扫描位置
功能模块详解
1. 按键处理函数 Key_Proc()
功能概述
处理所有按键输入,实现数据输入、模式切换、参数调节等功能。
代码详解
1.1 按键扫描与边沿检测
void Key_Proc()
{
unsigned char i;
if (Key_Slow_Down) return; // 减速控制(10ms执行一次)
Key_Slow_Down = 1;
Key_Val = Key_Read(); // 读取当前按键值
Key_Down = Key_Val & (Key_Val ^ Key_Old); // 检测下降沿
Key_Up = Key_Old & (Key_Val ^ Key_Old); // 检测上升沿
Key_Old = Key_Val; // 保存当前值
边沿检测原理:
- 下降沿:当前按下 && 之前未按 →
Key_Val & (Key_Val ^ Key_Old) - 上升沿:之前按下 && 当前未按 →
Key_Old & (Key_Val ^ Key_Old)
1.2 数字键输入(按键1-10)
if((Key_Down >= 1) && (Key_Down <= 10))
{
if(Voltage_Input_Index < 4) // 防止越界
{
Key_Error_Count = 0;
if(Disp_Mode == 0) // 仅在电压采集模式有效
{
Voltage_Val = Key_Down - 1; // 按键值转数字(1→0, 2→1...)
Voltage_Input[Voltage_Input_Index] = Voltage_Val;
Voltage_Input_Index++;
}
else
{
Key_Error_Count++; // 错误模式按键,累加错误计数
}
}
}
1.3 确认键(按键11)
if(Key_Down == 11)
{
if(Disp_Mode == 0) // 电压采集模式:确认输入
{
if(Voltage_Input_Index == 4) // 必须输入满4位
{
Key_Error_Count = 0;
// 步骤1:保存旧电压值
Voltage_Old = Voltage_Real;
// 步骤2:复制输入数据
for(i = 0; i < 4; i++)
{
Voltage_Data[i] = Voltage_Input[i];
}
// 步骤3:四舍五入计算
Voltage_Real_Data[0] = Voltage_Data[0];
Voltage_Real_Data[1] = Voltage_Data[1];
Voltage_Real_Data[2] = Voltage_Data[2];
if(Voltage_Data[3] >= 5) // 第4位≥5时进位
{
Voltage_Real_Data[2]++;
if(Voltage_Real_Data[2] >= 10)
{
Voltage_Real_Data[2] = 0;
Voltage_Real_Data[1]++;
}
if(Voltage_Real_Data[1] >= 10)
{
Voltage_Real_Data[1] = 0;
Voltage_Real_Data[0]++;
}
}
// 步骤4:计算整数值
Voltage_Real = Voltage_Real_Data[2] +
10 * Voltage_Real_Data[1] +
100 * Voltage_Real_Data[0];
// 步骤5:下降沿检测
Voltage_Setting = Voltage_Setting_Data[2] +
10 * Voltage_Setting_Data[1] +
100 * Voltage_Setting_Data[0];
if((Voltage_Real < Voltage_Setting) &&
(Voltage_Old >= Voltage_Setting))
{
Voltage_Count++; // 下降沿触发计数
}
Disp_Mode = 1; // 切换到数据显示模式
}
else
{
Key_Error_Count++;
}
}
else // 其他模式:返回电压采集模式
{
Voltage_Input_Index = 0;
for(i = 0; i < 4; i++)
{
Voltage_Input[i] = 13; // 重置为熄灭状态
}
Disp_Mode = 0;
}
}
1.4 模式切换键(按键12)
if(Key_Down == 12)
{
if(Disp_Mode != 0) // 仅在非采集模式有效
{
Key_Error_Count = 0;
if(++Disp_Mode == 4) Disp_Mode = 1; // 循环:1→2→3→1
}
else
{
Key_Error_Count++;
}
}
1.5 参数调节键(按键15/16)
if(Key_Down == 15) // 增加按键
{
if(Disp_Mode == 2) // 仅在参数设置模式有效
{
Key_Error_Count = 0;
Voltage_Setting_Data[1] += 5; // 十位加5(步进0.5V)
if(Voltage_Setting_Data[1] == 10)
{
Voltage_Setting_Data[1] = 0;
Voltage_Setting_Data[0] += 1;
}
// 上限检测:6.5V → 1.0V
if((Voltage_Setting_Data[0] == 6) &&
(Voltage_Setting_Data[1] == 5))
{
Voltage_Setting_Data[0] = 1;
Voltage_Setting_Data[1] = 0;
}
}
else
{
Key_Error_Count++;
}
}
if(Key_Down == 16) // 减少按键
{
if(Disp_Mode == 2)
{
Key_Error_Count = 0;
if(Voltage_Setting_Data[1] == 0)
{
Voltage_Setting_Data[1] = 5;
Voltage_Setting_Data[0] -= 1;
}
else
{
Voltage_Setting_Data[1] -= 5;
}
// 下限检测:0.5V → 6.0V
if((Voltage_Setting_Data[0] == 0) &&
(Voltage_Setting_Data[1] == 5))
{
Voltage_Setting_Data[1] = 0;
Voltage_Setting_Data[0] = 6;
}
}
else
{
Key_Error_Count++;
}
}
}
2. 数码管显示函数 Seg_Proc()
功能概述
根据当前显示模式更新数码管显示缓冲区。
代码详解
void Seg_Proc()
{
unsigned char i;
if (Seg_Slow_Down) return; // 减速控制(50ms执行一次)
Seg_Slow_Down = 1;
switch(Disp_Mode)
{
case 0: // 电压采集模式
Seg_Buf[0] = 10; // 熄灭
Seg_Buf[1] = 10;
// 显示输入的4位数据
Seg_Buf[2] = Voltage_Input[0];
Seg_Buf[3] = Voltage_Input[1];
Seg_Point[3] = 0; // 无小数点
Seg_Buf[4] = Voltage_Input[2];
Seg_Buf[5] = Voltage_Input[3];
// 当前输入位闪烁(仅在未输入满时)
if(Voltage_Input_Index < 4 && Input_Flag == 1)
{
Seg_Buf[Voltage_Input_Index + 2] = 10; // 闪烁位熄灭
}
break;
case 1: // 数据显示模式
Seg_Buf[0] = 14; // 显示 'U'
Seg_Buf[1] = 10;
Seg_Buf[2] = 10;
Seg_Buf[3] = Voltage_Data[0];
Seg_Point[3] = 1; // 显示小数点(X.XX格式)
Seg_Buf[4] = Voltage_Data[1];
Seg_Buf[5] = Voltage_Data[2];
// 四舍五入显示
if(Voltage_Data[3] >= 5)
{
Seg_Buf[5] = Voltage_Data[2] + 1;
}
if(Seg_Buf[5] >= 10)
{
Seg_Buf[5] = 0;
Seg_Buf[4] = Voltage_Data[1] + 1;
}
if(Seg_Buf[4] >= 10)
{
Seg_Buf[4] = 0;
Seg_Buf[3] = Voltage_Data[0] + 1;
}
if(Seg_Buf[3] == 10)
{
Seg_Buf[3] = 0;
Seg_Buf[2] = 1; // 进位到十位
}
break;
case 2: // 参数设置模式
Seg_Buf[0] = 15; // 显示 'P'
Seg_Buf[1] = 10;
Seg_Buf[2] = 10;
Seg_Buf[3] = Voltage_Setting_Data[0];
Seg_Point[3] = 1; // 显示小数点(X.X0格式)
Seg_Buf[4] = Voltage_Setting_Data[1];
Seg_Buf[5] = Voltage_Setting_Data[2];
break;
case 3: // 计数统计模式
Seg_Buf[0] = 16; // 显示 'N'
Seg_Buf[1] = Voltage_Count / 10000 % 10;
Seg_Buf[2] = Voltage_Count / 1000 % 10;
Seg_Buf[3] = Voltage_Count / 100 % 10;
Seg_Point[3] = 0; // 无小数点
Seg_Buf[4] = Voltage_Count / 10 % 10;
Seg_Buf[5] = Voltage_Count % 10;
// 前导零消隐
for(i = 0; i < 5; i++)
{
if(Seg_Buf[i] == 0) Seg_Buf[i] = 10;
}
break;
}
}
显示格式表
| 模式 | 显示格式 | 示例 |
|---|---|---|
| 0 | --X.XXX |
--3.456(输入中)--3.45_(闪烁位) |
| 1 | U--X.XX |
U--3.46(四舍五入) |
| 2 | P--X.X0 |
P--3.00(阈值) |
| 3 | NXXXXX |
N----5(计数5次) |
3. LED 控制函数 Led_Proc()
功能概述
根据系统状态控制3个LED的亮灭。
代码详解
void Led_Proc()
{
// LED0:系统计时器指示
if(Sys_Tick >= 5000)
{
ucLed[0] = 1; // 计时器≥5s时点亮
}
else
{
ucLed[0] = 0;
}
// LED1:计数奇偶指示
if((Voltage_Count % 2) != 0)
{
ucLed[1] = 1; // 计数为奇数时点亮
}
else
{
ucLed[1] = 0;
}
// LED2:错误指示
if(Key_Error_Count == 3)
{
ucLed[2] = 1; // 错误累计3次时点亮
}
else if(Key_Error_Count == 0)
{
ucLed[2] = 0; // 清零时熄灭
}
}
LED 功能表
| LED | 触发条件 | 功能说明 |
|---|---|---|
| LED0 | Sys_Tick ≥ 5000 |
电压低于阈值时间超过5秒 |
| LED1 | Voltage_Count % 2 ≠ 0 |
下降沿计数为奇数 |
| LED2 | Key_Error_Count == 3 |
按键操作错误3次(报警) |
4. 定时器初始化 Timer0Init()
代码详解
void Timer0Init(void) // 1ms@12MHz
{
TMOD &= 0xF0; // 清除定时器0控制位
TMOD |= 0x01; // 设置为模式1(16位定时器)
TL0 = 0x18; // 初值低8位
TH0 = 0xFC; // 初值高8位
TF0 = 0; // 清除溢出标志
TR0 = 1; // 启动定时器0
ET0 = 1; // 使能定时器0中断
EA = 1; // 使能总中断
}
定时器计算
- 晶振频率:12MHz
- 定时周期:1ms
- 初值计算:65536 - 12000 = 53536 = 0xD118 → 错误!
注意:代码中
TL0=0x18, TH0=0xFC→ 初值=0xFC18=64536
实际定时:(65536-64536) × 1µs = 1ms
5. 定时器中断服务函数 Timer0Server()
功能概述
1ms定时中断,处理所有定时任务。
代码详解
void Timer0Server() interrupt 1
{
// 重装初值
TL0 = 0x18;
TH0 = 0xFC;
// 任务1:按键扫描减速(10ms)
if(++Key_Slow_Down == 10) Key_Slow_Down = 0;
// 任务2:数码管刷新减速(50ms)
if(++Seg_Slow_Down == 50) Seg_Slow_Down = 0;
// 任务3:数码管动态扫描(1ms)
if(++Seg_Pos == 6) Seg_Pos = 0;
Seg_Disp(Seg_Pos, Seg_Buf[Seg_Pos], Seg_Point[Seg_Pos]);
// 任务4:LED动态扫描(1ms)
if(++Led_Pos == 8) Led_Pos = 0;
Led_Disp(Led_Pos, ucLed[Led_Pos]);
// 任务5:闪烁计时(250ms)
if(++Timer250 == 250)
{
Timer250 = 0;
Input_Flag ^= 1; // 翻转闪烁标志
}
// 任务6:系统计时(电压低于阈值时计时)
if(Voltage_Real < Voltage_Setting)
{
if(++Sys_Tick == 501) Sys_Tick = 500; // 上限锁定500ms
}
}
中断任务时序表
| 任务 | 执行周期 | 功能 |
|---|---|---|
| 按键扫描减速 | 10ms | 控制按键处理函数执行频率 |
| 数码管刷新减速 | 50ms | 控制显示数据更新频率 |
| 数码管扫描 | 1ms | 6位数码管动态扫描 |
| LED扫描 | 1ms | 8个LED动态扫描 |
| 闪烁控制 | 250ms | 控制输入位闪烁 |
| 系统计时 | 1ms | 电压低于阈值时累加 |
6. 主函数 main()
代码详解
void main()
{
Timer0Init(); // 初始化定时器
while(1) // 主循环
{
Key_Proc(); // 按键处理
Seg_Proc(); // 数码管数据处理
Led_Proc(); // LED状态处理
}
}
代码流程图
系统整体流程
系统上电
↓
定时器初始化
↓
进入主循环 ──┬─→ Key_Proc() (按键处理)
├─→ Seg_Proc() (数码管数据更新)
└─→ Led_Proc() (LED状态更新)
定时器中断(1ms) ──┬─→ 数码管动态扫描
├─→ LED动态扫描
├─→ 闪烁计时
└─→ 系统计时
模式切换状态机
模式0(电压采集) ──[按键11(输入满4位)]──→ 模式1(数据显示)
↓
[按键11] ←─────────┘
↓
模式0
模式1(数据显示) ──[按键12]──→ 模式2(参数设置)
↓
[按键12]
↓
模式2(参数设置) ──[按键12]──→ 模式3(计数统计)
↓
[按键12]
↓
模式3(计数统计) ──[按键12]──→ 模式1(循环)
下降沿检测流程
输入电压数据(4位)
↓
按下确认键(11)
↓
保存旧电压值 Voltage_Old
↓
四舍五入计算 Voltage_Real
↓
判断:Voltage_Real < Voltage_Setting
且 Voltage_Old ≥ Voltage_Setting
↓
是 → Voltage_Count++
否 → 不计数
关键算法
1. 四舍五入算法
算法原理
输入4位小数(X.XXX),保留3位(X.XX),第4位≥5时向第3位进位。
代码实现
// 示例:输入 3.456 → 输出 3.46
Voltage_Real_Data[0] = Voltage_Data[0]; // 3
Voltage_Real_Data[1] = Voltage_Data[1]; // 4
Voltage_Real_Data[2] = Voltage_Data[2]; // 5
if(Voltage_Data[3] >= 5) // 第4位=6 ≥ 5
{
Voltage_Real_Data[2]++; // 5+1=6
if(Voltage_Real_Data[2] >= 10) // 进位检测
{
Voltage_Real_Data[2] = 0;
Voltage_Real_Data[1]++;
}
if(Voltage_Real_Data[1] >= 10)
{
Voltage_Real_Data[1] = 0;
Voltage_Real_Data[0]++;
}
}
// 结果:Voltage_Real_Data = {3, 4, 6}
测试用例
| 输入 | 第4位 | 输出 | 说明 |
|---|---|---|---|
| 3.456 | 6 | 3.46 | 四舍五入向上 |
| 3.454 | 4 | 3.45 | 四舍五入向下 |
| 3.999 | 9 | 4.00 | 连续进位 |
| 0.005 | 5 | 0.01 | 边界情况 |
2. 下降沿检测算法
算法原理
检测电压值从"≥阈值"变为"<阈值"的瞬间。
逻辑表达式
下降沿条件:
(Voltage_Real < Voltage_Setting) && (Voltage_Old >= Voltage_Setting)
时序图
时刻 t0 t1 t2 t3 t4 t5
--------------------------------------------
Voltage 3.5 3.2 2.8 2.5 3.0 3.4
Setting 3.0 3.0 3.0 3.0 3.0 3.0
--------------------------------------------
检测 × × √ × × ×
计数 0 0 1 1 1 1
说明:
t0: 3.5 ≥ 3.0, 未下降
t1: 3.2 ≥ 3.0, 未下降
t2: 2.8 < 3.0 且 上次(3.2) ≥ 3.0 → 下降沿!计数+1
t3: 2.5 < 3.0 但 上次(2.8) < 3.0 → 不触发
代码实现
// 步骤1:保存旧值
Voltage_Old = Voltage_Real;
// 步骤2:计算新值
Voltage_Real = 新输入的四舍五入值;
// 步骤3:下降沿检测
Voltage_Setting = 阈值;
if((Voltage_Real < Voltage_Setting) && (Voltage_Old >= Voltage_Setting))
{
Voltage_Count++; // 触发计数
}
3. 按键边沿检测算法
算法原理
通过异或运算检测按键状态变化。
逻辑推导
// 下降沿(按键按下瞬间)
Key_Down = Key_Val & (Key_Val ^ Key_Old)
真值表:
Key_Old | Key_Val | Key_Val^Key_Old | Key_Down
--------|---------|-----------------|----------
0 | 0 | 0 | 0 (持续未按)
0 | 1 | 1 | 1 (按下瞬间) ✓
1 | 0 | 1 | 0 (释放瞬间)
1 | 1 | 0 | 0 (持续按下)
应用场景
if(Key_Down == 11) // 检测按键11按下瞬间
{
// 执行一次性操作(不会重复触发)
}
4. 闪烁显示算法
算法原理
通过定时翻转标志位,控制显示位的亮/灭。
代码实现
// 定时器中断(1ms)
if(++Timer250 == 250)
{
Timer250 = 0;
Input_Flag ^= 1; // 每250ms翻转一次
}
// 显示处理
if(Voltage_Input_Index < 4 && Input_Flag == 1)
{
Seg_Buf[Voltage_Input_Index + 2] = 10; // 闪烁位熄灭
}
时序示例
时间(ms) 0 250 500 750 1000
----------------------------------------
Input_Flag 0 1 0 1 0
显示状态 亮 灭 亮 灭 亮
使用说明
操作流程
1. 电压数据采集
- 开机默认进入模式0(数码管显示
------) - 按数字键输入4位电压值(例如:3→4→5→6 显示
--3.456) - 当前输入位每250ms闪烁一次
- 按确认键(11):
- 输入不满4位 → LED2点亮(报错)
- 输入满4位 → 进入模式1显示结果
2. 查看数据(模式1)
- 数码管显示
U--X.XX(四舍五入后的3位数据) - 示例:输入
3.456→ 显示U--3.46 - 按确认键(11)返回模式0
3. 设置阈值(模式2)
- 在模式1按 模式切换键(12) 进入模式2
- 数码管显示
P--X.X0(当前阈值) - 按 增加键(15) 或 减少键(16) 调节阈值
- 步进:0.5V
- 范围:1.0V ~ 6.0V
- 超出范围自动循环
- 按确认键(11)返回模式0
4. 查看计数(模式3)
- 在模式1/2按 模式切换键(12) 循环到模式3
- 数码管显示
NXXXXX(下降沿计数值) - 按确认键(11)返回模式0
LED 指示说明
LED0 - 系统计时指示
- 点亮条件:电压低于阈值时间 ≥ 5秒
- 用途:指示低压持续时间
LED1 - 计数奇偶指示
- 点亮条件:下降沿计数为奇数
- 用途:快速判断计数奇偶性
LED2 - 错误报警
- 点亮条件:按键操作错误累计3次
- 错误示例:
- 在模式1/2/3按数字键
- 在未输入满4位时按确认键
- 在模式0按模式切换键
- 清除方法:执行正确操作时自动清零
常见问题
Q1: 输入数据后按确认键没反应?
A: 检查是否输入满4位数据,LED2是否点亮(错误报警)
Q2: 下降沿为什么没有计数?
A: 检查以下条件:
- 新电压值是否 < 阈值
- 上次电压值是否 ≥ 阈值
- 是否在模式2正确设置了阈值
Q3: 数码管闪烁频率异常?
A: 检查定时器初值是否正确(TH0=0xFC, TL0=0x18)
Q4: LED2一直亮着?
A: 按键操作错误累计3次后,需执行正确操作才能清零
技术特点
优点
状态机设计清晰:4种模式逻辑分明
防错机制完善:错误计数、越界保护
用户体验友好:闪烁提示、LED指示
算法严谨:四舍五入、下降沿检测无误
可优化点
代码复用性:四舍五入逻辑在两处重复
参数配置:调节步进可改为变量
计数范围:当前最大99999,可扩展
版本信息
- 创建日期:2026-01-28
- 芯片型号:STC89C52
- 晶振频率:12MHz
- 编译环境:Keil C51
附录
数码管编码表
| 数值 | Seg_Buf | 显示 |
|---|---|---|
| 0-9 | 0-9 | 0-9 |
| 10 | 10 | 熄灭 |
| 13 | 13 | - (横线) |
| 14 | 14 | U |
| 15 | 15 | P |
| 16 | 16 | N |
引脚定义(需参考底层驱动)
- 数码管:段码 + 位选(动态扫描)
- 按键:矩阵键盘(4×4)
- LED:P1口或P2口(需查看Led.h)
文档结束