51单片机数字电压表代码详解(ADC0832+LCD1602)
代码实现双通道电压测量(0-5V),通过LCD1602实时显示电压值。:通过向LCD发送初始化指令序列(如4位/8位模式选择、光标设置等),建立单片机与显示屏的通信基础。:将浮点电压值转换为整数mV处理(例如2.5V→2500mV),避免直接处理浮点数的复杂性。:通过特定的CLK和DI引脚电平序列选择ADC0832的输入通道(CH0/CH1):通过LCD_ShowStr的坐标参数(0,0和0,1)
文件下载
https://pan.baidu.com/s/18ZQMLuENQnzUzO0NaHta7w?pwd=8jdk 提取码: 8jdk
本文针对基于51单片机的数字电压表代码进行深度解析,帮助初学者快速理解ADC模数转换与LCD显示的核心实现逻辑。代码实现双通道电压测量(0-5V),通过LCD1602实时显示电压值。
一、代码整体流程图
二、代码模块分解与核心思想
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时钟沿同步数据位传输
• 关键时序:
- 片选信号CS拉低启动转换
- 发送通道选择命令(2个时钟周期)
- 在时钟下降沿读取DO引脚数据位
- 完成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:为什么实际电压与显示值有偏差?
• 校准方法:
- 输入精确的5V参考电压
- 调节公式中的系数(例如将5.0改为实际测量值)
五、扩展学习建议
- 进阶功能:添加按键校准、数据存储、串口通信等功能
- 硬件优化:改用12位ADC芯片(如ADS1015)提升精度
- 仿真验证:在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;
}
仿真图
更多推荐



所有评论(0)