过渡模拟一:电压采集器

电压采集器

目录


系统概述

这是一个基于 STC89C52 单片机的电压采集与监测系统,主要功能包括:

:white_check_mark: 电压数据采集(4位精度)
:white_check_mark: 实时数据显示(四舍五入处理)
:white_check_mark: 阈值参数设置(1.0V ~ 6.0V)
:white_check_mark: 下降沿计数统计
:white_check_mark: LED 状态指示
:white_check_mark: 按键错误检测


硬件配置

外设清单

外设 数量 功能
数码管 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 :white_check_mark:


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. 电压数据采集

  1. 开机默认进入模式0(数码管显示 ------
  2. 按数字键输入4位电压值(例如:3→4→5→6 显示 --3.456
  3. 当前输入位每250ms闪烁一次
  4. 按确认键(11)
    • 输入不满4位 → LED2点亮(报错)
    • 输入满4位 → 进入模式1显示结果

2. 查看数据(模式1)

  • 数码管显示 U--X.XX(四舍五入后的3位数据)
  • 示例:输入 3.456 → 显示 U--3.46
  • 按确认键(11)返回模式0

3. 设置阈值(模式2)

  1. 在模式1按 模式切换键(12) 进入模式2
  2. 数码管显示 P--X.X0(当前阈值)
  3. 增加键(15)减少键(16) 调节阈值
    • 步进:0.5V
    • 范围:1.0V ~ 6.0V
    • 超出范围自动循环
  4. 按确认键(11)返回模式0

4. 查看计数(模式3)

  1. 在模式1/2按 模式切换键(12) 循环到模式3
  2. 数码管显示 NXXXXX(下降沿计数值)
  3. 按确认键(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次后,需执行正确操作才能清零


技术特点

优点

:white_check_mark: 状态机设计清晰:4种模式逻辑分明
:white_check_mark: 防错机制完善:错误计数、越界保护
:white_check_mark: 用户体验友好:闪烁提示、LED指示
:white_check_mark: 算法严谨:四舍五入、下降沿检测无误

可优化点

:wrench: 代码复用性:四舍五入逻辑在两处重复
:wrench: 参数配置:调节步进可改为变量
:wrench: 计数范围:当前最大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)

文档结束