文件下载
https://pan.baidu.com/s/18ZQMLuENQnzUzO0NaHta7w?pwd=8jdk 提取码: 8jdk

本文针对基于51单片机的数字电压表代码进行深度解析,帮助初学者快速理解ADC模数转换与LCD显示的核心实现逻辑。代码实现双通道电压测量(0-5V),通过LCD1602实时显示电压值。


一、代码整体流程图
程序启动
硬件初始化
循环测量
读取ADC值
通道0电压转换
通道1电压转换
LCD显示处理

二、代码模块分解与核心思想
1. 硬件初始化模块
void main() {
    LCD_Init();  // 初始化LCD
    ...
}

核心作用:配置LCD1602的工作模式,清空屏幕准备显示
底层细节:通过向LCD发送初始化指令序列(如4位/8位模式选择、光标设置等),建立单片机与显示屏的通信基础


2. ADC数据采集模块
unsigned char adc0832(unsigned char CH) {
    // 通道选择时序
    if (CH == 0x00) { /* 通道0配置 */ } 
    else { /* 通道1配置 */ }
    
    // 数据读取时序
    for(i=0;i<8;i++) { /* 读取前8位 */ }
    for(i=0;i<8;i++) { /* 读取后8位 */ }
    
    // 数据校验
    if(adval == test) dat = test; // 校验有效数据
}

核心思想
通道选择:通过特定的CLK和DI引脚电平序列选择ADC0832的输入通道(CH0/CH1)
双采样校验:先后读取两组8位数据,比对一致后才视为有效数据,提升抗干扰能力
SPI通信协议:严格遵循ADC0832的时序要求,通过CLK时钟沿同步数据位传输

关键时序

  1. 片选信号CS拉低启动转换
  2. 发送通道选择命令(2个时钟周期)
  3. 在时钟下降沿读取DO引脚数据位
  4. 完成16位数据读取后校验有效性

3. 电压计算模块
voltage0 = (adc_value0 * 5.0) / 255;  // 电压换算公式

数学原理
• ADC0832为8位ADC,量程0-255对应0-5V输入
公式推导:电压值 = (ADC原始值 × 参考电压) / 最大分辨率
精度分析:5V/256≈19.5mV,即最小可分辨19.5mV电压变化


4. LCD显示模块
void Display_Voltage(float v1, float v2) {
    sprintf(str, "CH0:%d.%03d V", ...); // 格式化字符串
    LCD_ShowStr(0, 0, str);            // 第一行显示
}

实现技巧
数值放大法:将浮点电压值转换为整数mV处理(例如2.5V→2500mV),避免直接处理浮点数的复杂性
sprintf格式化:将电压值拆分为整数和小数部分,生成"X.XXX V"标准格式字符串
双行显示:通过LCD_ShowStr的坐标参数(0,0和0,1)实现双通道分屏显示


5. 主循环控制
while(1) {
    // 双通道交替采样
    adc_value0 = adc0832(0);
    adc_value1 = adc0832(1);
    DelayMs(500); // 控制刷新率
}

设计策略
500ms刷新周期:平衡显示稳定性与响应速度,避免屏幕闪烁
顺序采样:先读通道0再读通道1,保证两路数据时间一致性


三、关键代码段注释说明
ADC读取函数核心段
// 通道选择时序(以通道0为例)
ADC_CLK = 0;         // 时钟线置低
ADC_DI = 1;          // 发送通道选择位1
_nop_();             // 微小延时保证电平稳定
ADC_CLK = 1;         // 产生上升沿锁存数据
ADC_CLK = 0;         // 恢复低电平
ADC_DI = 0;          // 发送通道选择位0
数据读取校验段
// 前8位数据读取(高位在前)
for(i=0;i<8;i++){
    adval <<= 1;            // 左移腾出低位
    if(ADC_DO) adval |= 1;  // 读取DO引脚电平
    ADC_CLK = 1;            // 时钟上升沿触发
    ADC_CLK = 0;            // 恢复低电平
}

// 后8位数据读取(低位在前)
for(i=0;i<8;i++){
    test >>= 1;             // 右移腾出高位
    if(ADC_DO) test |= 0x80;// 设置最高位
    ADC_CLK = 1;
    ADC_CLK = 0;
}

四、常见问题答疑(Q&A)

Q1:为什么显示值偶尔跳动?
• 可能原因:电源纹波、未做软件滤波
• 解决方案:在Display_Voltage函数中加入滑动平均滤波算法

Q2:如何扩展测量范围?
• 改造方案:在ADC前端增加分压电路(例如10:1分压可测0-50V),公式改为:

voltage = (adc_value * 5.0 / 255) * 10; 

Q3:为什么实际电压与显示值有偏差?
• 校准方法:

  1. 输入精确的5V参考电压
  2. 调节公式中的系数(例如将5.0改为实际测量值)

五、扩展学习建议
  1. 进阶功能:添加按键校准、数据存储、串口通信等功能
  2. 硬件优化:改用12位ADC芯片(如ADS1015)提升精度
  3. 仿真验证:在Proteus中搭建完整电路测试代码

通过本代码解读,初学者可掌握单片机数据采集系统的典型开发模式:传感器→信号调理→ADC转换→数据处理→人机交互。建议结合硬件实操加深理解。
完整代码

#include <reg52.h>
#include <intrins.h>
#include "LCD.h"
#include <stdio.h>

// ADC0832引脚定义
sbit ADC_CS = P3^3;  // 片选
sbit ADC_CLK = P3^0; // 时钟
sbit ADC_DI = P3^2;  // 数据输入
sbit ADC_DO = P3^1;  // 数据输出

unsigned char adc0832(unsigned char CH) ;
// 延时函数
void DelayMs(unsigned int ms) {
    unsigned int i, j;
    for (i = 0; i < ms; i++)
        for (j = 0; j < 1000; j++);
}


// 显示电压值
void Display_Voltage(float voltage1, float voltage2) {
    unsigned int volt1_mv = voltage1 * 1000;  // 转换为mV
    unsigned int volt2_mv = voltage2 * 1000;  // 转换为mV
    unsigned char str[16];

    LCD_Clear();

    // 显示通道1电压
    sprintf(str, "CH0:%d.%03d V", volt1_mv / 1000, volt1_mv % 1000);
    LCD_ShowStr(0, 0, str);

    // 显示通道2电压
    sprintf(str, "CH1:%d.%03d V", volt2_mv / 1000, volt2_mv % 1000);
    LCD_ShowStr(0, 1, str);
}

// 主函数
void main() {
    unsigned int adc_value0, adc_value1;
    float voltage0, voltage1;
	
		
    LCD_Init();  // 初始化LCD
    LCD_Clear();
		
	
    while (1) {
        // 读取通道0
        adc_value0 = adc0832(0);
        voltage0 = (adc_value0 * 5.0) / 255;

        // 读取通道1
        adc_value1 = adc0832(1);
        voltage1 = (adc_value1 * 5.0) / 255;

        // 显示两路电压值
        Display_Voltage(voltage0, voltage1);

        // 延时
        DelayMs(500);
		
    }
}

unsigned char adc0832(unsigned char CH)
{
      unsigned char i,test,adval,dat;
     adval = 0x00;
     test = 0x00;
     ADC_CLK = 0;       //初始化
     ADC_DI = 1;
    _nop_();
    ADC_CS = 0;
    _nop_();
    ADC_CLK = 1;
   _nop_();
	ADC_DI = 1; 
	_nop_() ;
 
   if ( CH == 0x00 )      //通道选择
   {
       ADC_CLK = 0;
       ADC_DI = 1;      //通道0的第一位
       _nop_();
      ADC_CLK = 1;
        _nop_();
        ADC_CLK = 0;
      ADC_DI = 0;      //通道0的第二位
      _nop_();
      ADC_CLK = 1;
      _nop_();
    } 
    else
    {
       ADC_CLK = 0;
    ADC_DI = 1;      //通道1的第一位
      _nop_();
      ADC_CLK = 1;
      _nop_();
      ADC_CLK = 0;
      ADC_DI = 1;      //通道1的第二位
    _nop_();
     ADC_CLK = 1;
     _nop_();
   }
 
      ADC_CLK = 0;
      ADC_DI = 1;
    for( i = 0;i < 8;i++ )      //读取前8位的值
    {
       _nop_();
       adval <<= 1;
       ADC_CLK = 1;
       _nop_();
       ADC_CLK = 0;
       if (ADC_DO)
          adval |= 0x01;
      else
          adval |= 0x00;
    }
      for (i = 0; i < 8; i++)      //读取后8位的值
      {
           test >>= 1;
           if (ADC_DO)
              test |= 0x80;
           else 
              test |= 0x00;
          _nop_();
          ADC_CLK = 1;
          _nop_();
          ADC_CLK = 0;
      }
      if (adval == test)      //比较前8位与后8位的值,如果不相同舍去。若一直出现显示为零,请将该行去掉
       dat = test;
       nop_();
       ADC_CS = 1;        //释放ADC0832
       ADC_DO = 1;
       ADC_CLK = 1;
       return dat;
}

仿真图
在这里插入图片描述

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐