STM32F103C8T6 ADC实战:手把手教你做个LCD1602显示的电压表(Proteus 8.9仿真+Keil 5源码)
STM32F103C8T6 ADC实战:从零构建LCD1602数字电压表
1. 项目概述与硬件准备
在嵌入式开发领域,ADC(模数转换器)的应用堪称基本功。这次我们选择STM32F103C8T6这款性价比极高的Cortex-M3内核芯片,配合LCD1602显示屏,打造一个实用型数字电压表。不同于简单的理论讲解,我们将从芯片选型、电路设计到代码调试,完整呈现开发全流程。
核心硬件清单 :
- STM32F103C8T6最小系统板(或Proteus仿真模型)
- LCD1602液晶显示屏(兼容HD44780控制器)
- 10kΩ多圈精密电位器(用于电压调节)
- 0.1μF去耦电容若干
- 3.3V稳压电路(若使用实际硬件)
提示:实际硬件搭建时,建议在ADC输入引脚前加入RC低通滤波(如1kΩ电阻串联100nF电容到地),可有效抑制高频干扰。
2. 硬件电路设计要点
2.1 ADC输入电路设计
STM32F103的ADC输入电压范围为0-3.3V,对应12位分辨率(4096个量化等级)。在Proteus仿真中,我们需要特别注意参考电压的设定:
// 电压计算公式
float voltage = (float)adc_value * (3.4 / 4096); // 注意仿真中使用3.4V而非3.3V
这个看似奇怪的3.4V数值源于Proteus中STM32模型的内部参考电压校准特性。实际测试表明,使用3.4V作为计算基准时,仿真结果更接近预期值。
2.2 LCD1602接口连接
推荐使用4位数据线模式节省IO资源,典型连接方式:
| LCD引脚 | STM32连接 | 备注 |
|---|---|---|
| RS | PB0 | 寄存器选择 |
| RW | GND | 始终设置为写模式 |
| EN | PB1 | 使能信号 |
| D4-D7 | PB12-PB15 | 数据线高四位 |
| VSS | GND | 电源地 |
| VDD | 5V | 注意逻辑电平匹配 |
注意:虽然STM32 GPIO为3.3V电平,但大多数LCD1602模块在5V供电时仍能可靠识别3.3V信号。若出现显示异常,可考虑添加电平转换电路。
3. 软件架构与关键代码解析
3.1 ADC初始化配置
ADC的配置需要关注几个关键参数:
void ADC1_Init(void) {
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 设置ADC时钟为12MHz(72MHz/6)
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5);
ADC_Cmd(ADC1, ENABLE);
// ADC校准流程(实际硬件必需)
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
3.2 电压计算与显示处理
在main循环中,我们需要处理原始ADC值并转换为可显示的电压:
while(1) {
uint16_t adc_value = ADC_GetConversionValue(ADC1);
float voltage = (float)adc_value * (3.4 / 4096); // 关键计算公式
// 电压值分解为整数和小数部分
uint8_t integer_part = (uint8_t)voltage;
uint8_t decimal_part = (uint8_t)((voltage - integer_part) * 10);
// LCD显示更新
LCD_SetCursor(0, 0);
LCD_WriteString("Voltage:");
LCD_SetCursor(8, 0);
LCD_WriteChar(integer_part + '0');
LCD_WriteChar('.');
LCD_WriteChar(decimal_part + '0');
LCD_WriteChar('V');
Delay_ms(200); // 适当延时防止刷新过快
}
4. Proteus仿真技巧与调试
4.1 仿真环境搭建要点
-
元件选择 :
- STM32F103C8T6(需加载正确固件)
- LCD1602(带HD44780控制器)
- POT-HG(高精度电位器)
-
关键连接 :
- 电位器中间引脚接PA1(ADC输入)
- 两端分别接3.3V和GND
- LCD按前述接线图连接
-
调试工具配置 :
- 使用Proteus自带的虚拟示波器监控ADC输入
- 开启STM32寄存器查看窗口观察ADC数据
4.2 常见问题解决方案
问题1:ADC读数不稳定
- 检查电位器连接是否牢靠
- 在仿真设置中增加"ANALOGUE SAMPLE TIME"值
- 代码中增加软件滤波(如移动平均)
#define SAMPLE_COUNT 10
uint32_t filtered_adc = 0;
for(uint8_t i=0; i<SAMPLE_COUNT; i++) {
filtered_adc += ADC_GetConversionValue(ADC1);
Delay_ms(5);
}
filtered_adc /= SAMPLE_COUNT;
问题2:LCD显示乱码
- 检查初始化时序是否满足HD44780规范
- 确认总线模式设置(4位/8位)与硬件匹配
- 调整操作间隔时间,特别是EN使能脉冲宽度
5. 性能优化与功能扩展
5.1 精度提升方案
虽然基本设计满足±0.1V精度要求,但通过以下改进可达更高精度:
-
参考电压优化 :
- 实际硬件中采用外部精密基准源(如TL431)
- 在代码中动态校准参考电压
-
数字滤波算法 :
- 中值滤波+移动平均组合
- 卡尔曼滤波(适合动态测量)
// 简易中值滤波实现
uint16_t median_filter(uint16_t new_value) {
static uint16_t buffer[5] = {0};
static uint8_t index = 0;
buffer[index++] = new_value;
if(index >= 5) index = 0;
// 排序取中值(简化实现)
uint16_t temp[5];
memcpy(temp, buffer, sizeof(temp));
bubble_sort(temp, 5); // 需实现排序函数
return temp[2];
}
5.2 功能扩展方向
-
多通道测量 :
- 配置ADC扫描模式
- 循环检测多个输入通道
-
数据记录功能 :
- 添加EEPROM存储历史数据
- 通过串口输出测量结果
-
报警阈值设置 :
- 添加按键输入接口
- 实现电压超限声光报警
// 电压超限检查示例
if(voltage > MAX_VOLTAGE) {
GPIO_SetBits(GPIOC, GPIO_Pin_13); // 点亮LED
Buzzer_On(); // 触发蜂鸣器
} else {
GPIO_ResetBits(GPIOC, GPIO_Pin_13);
Buzzer_Off();
}
6. 实际项目经验分享
在实验室测试时发现一个有趣现象:当使用USB供电时,ADC测量结果会出现周期性波动。经过示波器检查,发现是电源纹波导致。解决方法包括:
- 在VDDA引脚增加10μF钽电容+0.1μF陶瓷电容组合
- 软件上增加50Hz工频滤波算法
- 避免将模拟地与数字地单点连接的位置靠近MCU的开关电源引脚
另一个常见问题是LCD在低温环境下响应变慢。针对这种情况可以:
- 在初始化前增加500ms延时
- 降低总线通信速率
- 采用负温度系数热敏电阻进行温度补偿
更多推荐
所有评论(0)