恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

STM32三片LCD1602电压表:ADC多通道采样与驱动实现

  • 首页
  • 资讯中心
  • /
  • STM32三片LCD1602电压表:ADC多通道采样与驱动实现

相关资讯

报错注入实战指南:从updatexml到extractvalue与floor的MySQL注入原理与绕过技巧 2026/9/16 20:58:25
AI 辅助重构实战:Class 组件迁移到 FC+Hooks 2026/9/16 20:58:25
Docker Hub 注册全攻略:从账号创建到安全配置的完整指南 2026/9/16 20:58:25

最新资讯

Oracle 19c RAC集群节点重新添加全流程详解:从环境准备到实例验证
InfiniBand如何成为AI集群的通信基石
2026年有线入耳式耳机技术指南:结构、接口与可靠性实战解析
God‘s Eye View 诚实的卫星:SGP4轨道传播与GMST对齐如何做到零漂移
CentOS 7安装Docker CE完整教程:环境检查、配置优化与排错实战
Dify本地知识库搭建与公网访问实操:从Docker部署到模型对接全指南

今日推荐

IoT-For-Beginners 智能语音计时器:Wio Terminal 基于 DMAC 与 Flash 的音频采集实战
基于MATLAB的CRI显色指数计算:从SPD光谱到Ra的完整流程
JSP+Servlet+MySQL博客系统源码部署与优化全攻略

本周热门

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验
Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化
Flutter应用改名全指南:从Android到iOS的配置与工具实践

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

STM32三片LCD1602电压表:ADC多通道采样与驱动实现

发布时间:2026/9/16 20:58:25
STM32三片LCD1602电压表:ADC多通道采样与驱动实现 简介这份嵌入式数字电压表工程基于STM32F10x与LCD1602显示屏实现电压信号的ADC采集、换算与实时显示面向电子爱好者、嵌入式初学者及课程设计人群。压缩包共183个文件、约2.95MB核心包含完整C/H源码、Keil工程配置uvprojx/uvopt、编译生成物o/d/crf以及HEX/AXF烧录镜像从源码到工程配置一应俱全便于直接打开学习与二次开发。已有386人浏览学习具备较高参考价值。借助源码可深入理解ADC通道设置与采样时序、LCD1602字符显示控制、定时器中断及STM32外设驱动框架配合MAP/LST调试文档还能分析内存布局与链接细节是一份适合快速落地的嵌入式项目实践资料。1. 三片LCD1602拼出来的电压表能让你看到什么问题手头有个很“老派”的嵌入式练习3 LCD数字电压表。名字里的 3 不是三位半而是板子上挂了 3 块 LCD1602每块屏独立显示一路 ADC 通道的电压值。主控是 STM32F103标准外设库工程源文件里能看到 stm32f10x_adc.c、stm32f10x_rcc.c、stm32f10x_tim.c这套东西一看就是用来练 ADC、GPIO 和 LCD 驱动基本功的。整个系统的核心链路很短模拟电压进 ADC算成毫伏数再转成字符串按 LCD1602 时序写出去。但链路短不代表容易采样时间没配好、参考电压没想清楚、四线模式时序差几十微秒屏幕就会乱码或显示错位。对嵌入式从业者来说把三块屏同时调稳定比单纯跑通一个协议更能看出对底层资源的把握能力。2. 从模拟量到数字量STM32 ADC采样链路与硬件连接2.1 ADC 测的是“相对值”参考电压必须先立住STM32F103 的 ADC 是 12 位逐次逼近型输出范围是 0 到 4095。它能得到的始终是输入电压相对 VREF 的比例而不是绝对电压。换算公式是ADC值 Vin / VREF * 4095很多新手在这里直接用 3.3 作为 VREF 去算但 VREF 通常接的是主板上的 3.3V这个电压往往不是精准的 3.300V可能是 3.28V也可能是 3.31V。用错误的参考值算出来的电压在 1V 左右只差几个毫伏但到 2.5V 时能差出十几毫伏做数字电压表完全不能接受。所以第一步是实测板子上的 VREF 电压或者用 STM32 内部的参照电压通道做校准。下表是 STM32F103 最常用 ADC 参数这套项目里用的就是这些基础能力参数数值说明分辨率12 位0~4095低位右对齐通道数最多 16 个外部通道这里用 ADC1 的通道 0、1、2VREF通常接 3.3V决定满量程电压要实测采样时间1.5~239.5 个 ADC 周期周期越长输入阻抗要求越低转换模式单次、连续、扫描多通道推荐扫描 DMA如果你的设计把 VREF 直接接到了 VDDA 上而 VDDA 又由普通 LDO 供电那么建议在 PCB 上预留一个 100nF 电容靠近 VREF 引脚。测量动作本身不会改参考值但电源纹波会直接折进采样结果。2.2 引脚分配与三路通道接线方式三个测量通道放在 PA0、PA1、PA2对应 ADC1 的通道 0、1、2。LCD1602 的数据线 D4~D7 用 PB8~PB11控制线 RS 用 PB12三块屏的使能端 EN 分别用 PB13、PB14、PB15。这样安排可以避开 JTAG 调试接口占用的 PA13、PA14、PA15也方便后续把串口留给调试。信号引脚备注被测电压 V1PA0ADC1_IN00~3.3V被测电压 V2PA1ADC1_IN10~3.3V被测电压 V3PA2ADC1_IN20~3.3VLCD1602 D4~D7PB8~PB11四线数据总线三块屏共连LCD1602 RSPB120 写指令1 写数据LCD1602 EN1PB13第一块屏使能LCD1602 EN2PB14第二块屏使能LCD1602 EN3PB15第三块屏使能LCD1602 RWGND只写模式省一个 IOADC 输入引脚必须配置成GPIO_Mode_AIN不能配置成浮空输入或上拉输入。内部上下拉电阻会有微小的漏电流遇到高阻抗信号源时会让读数偏低或偏高。模拟输入模式下内部施密特触发器也会被关闭能减少数字电路对采样结果的干扰。2.3 ADC 初始化代码三个通道、扫描、软件触发下面这段是基于标准外设库的 ADC1 初始化使用扫描模式连续转换三路通道。为了让转换结果稳定先把采样时间调到最大档 239.5 周期。void ADC_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); } void ADC_Config(void) { ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); ADC_DeInit(ADC1); ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode ENABLE; ADC_InitStructure.ADC_ContinuousConvMode ENABLE; ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 3; ADC_Init(ADC1, ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_239Cycles5); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)) ; ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)) ; }这段代码的要点在ADC_RegularChannelConfig的第三个参数它决定了规则组里每个通道的排列顺序。这里把通道 0 排在第一位通道 1 第二位通道 2 第三位扫描完成后 ADC_DR 寄存器里最后一次转换是通道 2 的值。ADC_SampleTime_239Cycles5是 12MHz ADC 时钟下约 20us 一次采样对 10k 以上内阻的信号源更友好代价是采样速率下降。电压表本身不追求高速慢一点无妨。2.4 为什么多通道不能直接从 ADC_DR 里读三个结果ADC_RegularChannelConfig配置好之后如果直接启动连续扫描模式ADC 会自动把三个通道按顺序转换完然后从 0 通道再开始下一轮。但这个过程中ADC_DR只有一个寄存器每完成一个通道结果就会被下一个通道覆盖。等主循环去读的时候只能拿到最后一次转换的通道 2 数据通道 0 和 1 的结果早就丢了。常见做法有两个一是用 DMA 把规则组结果搬进数组这是最推荐的方式二是关掉扫描模式每次手动切换通道、触发、等待再读一次。后一种方式代码直观但每次切换通道都要重新设置ADC_RegularChannelConfig否则读到的还是上一个通道的残留数据。对三路电压表这种低速场景两种方案都行我更倾向于 DMA因为它不占 CPU刷屏时不会漏采样点。3. LCD1602驱动与三屏显示策略3.1 四线模式引脚少但时序要求更高LCD1602 本身是 HD44780 兼容控制器有两种接法。八线模式效率高但占用 11 个 IO对三屏并联来说不划算。四线模式把 D0~D3 悬空只接 D4~D7每次传输 8 位数据要拆成高 4 位和低 4 位两次写。速度慢一半但省了 4 根线。RS 信号区分当前写的是指令还是数据。RW 接 GND让模块只处于写状态省掉读忙标志的操作。这样做的代价是不能读 BF 标志来确认模块是否忙所以每次写操作之后必须延时。常见延时是执行指令后至少等 40us清屏和归位需要等 1.7ms 以上。这个延时不够LCD1602 就会出现显示错位或数据丢失。下表是四线模式下 LCD1602 常用指令做初始化时直接抄指令代码作用功能设置0x284 位总线、2 行、5x7 点阵显示开关0x0C开显示、关光标、不闪烁清屏0x01清 DDRAM地址归零输入模式0x06写数据后地址加 1屏幕不移动初始化信号0x33 / 0x32首次上电切换到 4 位模式的固定步骤四线模式初始化有个特殊要求上电后先按 8 位模式写 0x33再写 0x32 切到 4 位模式然后才能发 0x28。如果上来直接写 0x28模块会误判成 8 位数据屏幕只会黑块。3.2 写指令和写数据的底层函数下面这段是 LCD1602 最底层的写函数。三块屏共用了数据线和控制线唯一不同的是 EN 引脚。#define LCD_D4_PIN GPIO_Pin_8 #define LCD_D5_PIN GPIO_Pin_9 #define LCD_D6_PIN GPIO_Pin_10 #define LCD_D7_PIN GPIO_Pin_11 #define LCD_RS_PIN GPIO_Pin_12 static void LCD_WriteNibble(uint8_t nibble, uint8_t isData) { /* 先放好 D4~D7 上的电平 */ GPIO_WriteBit(GPIOB, LCD_D4_PIN, (BitAction)((nibble 0) 0x01)); GPIO_WriteBit(GPIOB, LCD_D5_PIN, (BitAction)((nibble 1) 0x01)); GPIO_WriteBit(GPIOB, LCD_D6_PIN, (BitAction)((nibble 2) 0x01)); GPIO_WriteBit(GPIOB, LCD_D7_PIN, (BitAction)((nibble 3) 0x01)); GPIO_WriteBit(GPIOB, LCD_RS_PIN, (BitAction)isData); /* EN 产生下降沿LCD 在下降沿锁存数据 */ GPIO_SetBits(GPIOB, active_en_pin); delay_us(1); GPIO_ResetBits(GPIOB, active_en_pin); delay_us(1); } void LCD_WriteCmd(uint8_t cmd, uint8_t screen) { LCD_Select(screen); LCD_WriteNibble(cmd 4, 0); LCD_WriteNibble(cmd 0x0F, 0); delay_us(50); } void LCD_WriteData(uint8_t dat, uint8_t screen) { LCD_Select(screen); LCD_WriteNibble(dat 4, 1); LCD_WriteNibble(dat 0x0F, 1); delay_us(50); }这里有三个必须注意的点。第一active_en_pin是全局变量由LCD_Select(screen)设置作用是选通当前需要操作的那块屏幕同时避免多块屏同时使能。第二LCD_WriteNibble里必须先置好数据线和 RS再拉高 EN最后拉低 EN顺序反了会把上一个总线状态锁存进去。第三四线模式写一个字节必须分两次下降沿中间间隔至少 1us否则两个 nibble 会粘在一起。3.3 三块屏共用总线关键是“一次只选一张”三块屏的数据线、RS、RW 全部连在一起只有 EN 是独立控制的。这种设计能大幅度减少 IO 占用但也带来一个约束同一时刻只能操作一块屏。如果有一个函数忘记切换 EN 就把数据写到总线上所有屏都会收到同样的数据。表现就是本来只想更新第一块屏结果三块屏一起变了。解决办法是维护一个屏幕选中函数每次操作前显式选屏void LCD_Select(uint8_t screen) { /* 默认全部拉高EN 高电平无效下降沿写入 */ GPIO_SetBits(GPIOB, GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15); if (screen 0) { GPIO_ResetBits(GPIOB, GPIO_Pin_13); } else if (screen 1) { GPIO_ResetBits(GPIOB, GPIO_Pin_14); } else if (screen 2) { GPIO_ResetBits(GPIOB, GPIO_Pin_15); } }在多屏系统里初始化时也必须对每块屏单独走一遍完整的 LCD1602 初始化流程。先选屏 0执行 0x33、0x32、0x28、0x0C、0x01、0x06再选屏 1 重复一遍。清屏指令里的延时要拉长到 2ms因为 0x01 清屏需要约 1.7ms。如果三块屏之间操作得太快后一块还没处理完前一条指令数据就会丢失。3.4 用整数拼接代替 printf 显示电压默认工程里不少教程会直接调用sprintf把浮点数转成字符串这在 STM32F103 上不是不能跑但会占用大量栈空间和 Flash。三路电压表要同时维护三块屏显示频率一高浮点格式化带来的开销会被放大。更可控的做法是直接用整数运算生成字符串。void Voltage_Display(uint8_t screen, uint16_t mv) { char buf[8]; uint16_t temp mv; buf[0] 0 (temp / 1000) % 10; /* 个位 */ buf[1] .; buf[2] 0 (temp / 100) % 10; /* 十分位 */ buf[3] 0 (temp / 10) % 10; /* 百分位 */ buf[4] 0 (temp) % 10; /* 千分位 */ buf[5] ; buf[6] V; buf[7] \0; LCD_SetCursor(0, 0, screen); LCD_WriteString(buf, screen); }这个函数接收的mv是毫伏数。比如输入 1234表示 1.234V显示结果为“1.234V”。不使用浮点除法因为整数除法的结果更可预测也不会出现四舍五入造成的显示跳动。LCD_SetCursor需要额外实现 DDRAM 和行列的换算规则第一行地址从 0x00 开始第二行从 0x40 开始写地址时要加上 0x80。4. 多通道采样、数字滤波与电压换算4.1 从 ADC 原始值到毫伏的整数运算拿到 DMA 搬运或单次读取的 ADC 值后第一步不能直接显示。需要一个精确的换算函数。如果用浮点公式很简洁但在嵌入式里我习惯用 32 位整数处理原因有两个一是标准外设库环境下浮点运算会增大中断延迟二是在两块屏刷新间隙做高频率换算时整数运算的耗时更稳定。换算公式#define VREF_MV 3296UL /* 实测 VREF 电压单位 mV */ uint16_t ADC_To_mV(uint16_t adc_val) { return (uint16_t)(((uint32_t)adc_val * VREF_MV) / 4095UL); }代码中的VREF_MV必须是自己用万用表实测的值。每个板子都不一样不要统一用 3300。adc_val * VREF_MV最大约 4095 * 3296接近 1350 万用uint32_t完全足够。除法放在乘法之后做可以避免小数运算。如果最终显示分辨率要求到 1mV换算结果直接就是毫伏数。4.2 数字滤波中值滤波更适合电压表这种抗脉冲场景电压表最常见的干扰来自继电器吸合、电机启停以及测量线靠近开关电源。这种干扰是脉冲型尖峰不是白噪声。滑动平均对白噪声有效但对尖峰响应很慢一个尖峰要经过 N 次采样才能被拉回。中值滤波则是取一组采样值排序后选中间值能直接丢弃异常大和异常小的值。代价是每次要排序但在三路电压表这种低速场景下CPU 完全扛得住。下面是一个简单的中值滤波函数#define FILTER_SIZE 5 static uint16_t adc_samples[3][FILTER_SIZE]; uint16_t ADC_MedianFilter(uint16_t *buffer) { uint8_t i, j; uint16_t tmp, sorted[FILTER_SIZE]; for (i 0; i FILTER_SIZE; i) { sorted[i] buffer[i]; } /* 冒泡排序 5 个数代码量小可读性好 */ for (i 0; i FILTER_SIZE - 1; i) { for (j i 1; j FILTER_SIZE; j) { if (sorted[j] sorted[i]) { tmp sorted[i]; sorted[i] sorted[j]; sorted[j] tmp; } } } return sorted[FILTER_SIZE / 2]; }这里重用了一个sorted局部数组避免直接打乱原始采样缓冲。FILTER_SIZE取 5 是平衡响应速度和滤波效果的常见选择。取 3 的话滤波能力太弱取 9 以上显示结果会显得“发钝”电压变化时数字要等很久才跟上。实际调试时可以在 ADC 中断里把每次采样结果塞进adc_samples[ch][pos]主循环每次取中值后刷新一次屏幕。4.3 采样与刷屏调度在裸机上做三路 AD 采集三路电压表不需要操作系统但代码结构不能把所有事都堆在主循环里。比较稳的做法是用定时器触发 ADC 采样DMA 把三路结果搬入数组采完一轮后设置一个标志位主循环只负责取出最新值、滤波、换算、刷新 LCD。如果不想用 DMA也可以使用轮询模式每轮切换一次通道uint16_t Read_ADC_Channel(uint8_t ch) { ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5); ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)) ; return ADC_GetConversionValue(ADC1); }这段代码必须放在主循环里不能被外部中断打断太长时间。ADC_SoftwareStartConvCmd启动一次转换然后等待 EOC 标志置位。读完后不用手动清 EOC下一次启动转换前硬件会自动清零。轮询方式的缺点是三块屏在刷新时如果耗时太长ADC 采样节奏会被拖慢但因为这里用的不是连续采样影响不大。更好的方式是 DMA 模式。配置 DMA1 通道 1 绑定 ADC1循环搬运 3 个半字搬运完成一次后自动从通道 0 开始下一轮。DMA 模式下 ADC 可以一直扫描转换结果始终是最近一轮的数据就算 LCD 刷新过程中卡了几毫秒采样也在后台持续进行。4.4 量程扩展电阻分压直接量 0~3.3V 的 ADC 能测的电压范围太小。实际电压表至少要能测到 20V需要在输入前端加分压电阻。常见比例是 1/6也就是输入 20V 时ADC 引脚上只有 3.33V。量程分压比分压电阻示例输入阻抗0~3.3V1:1无由信号源决定0~20V1:610k 50k60k0~50V1:1510k 140k150k分压电阻必须选精度在 1% 以内的金属膜电阻因为分压比直接决定测量精度。用两个 1% 电阻分压理论上误差能控制在 2% 以内但实际温漂也会影响读数。想要更准可以用 0.1% 精密电阻成本稍高。分压接入点需要靠近 ADC 引脚加一个 100nF 电容这样既能滤高频噪声也能降低 ADC 采样瞬间的电荷注入影响。5. 校准、验证和几个容易翻车的细节5.1 两点校准不用改硬件也能压掉固定误差ADC 链路里总会有误差比如参考电压不准、分压电阻有偏差、线路压降。软件校准能把这些固定误差一起修正。做法是用一台精度可靠的台式万用表作基准给电压表输入两个标准电压比如 1.000V 和 10.000V记录对应的 ADC 采样值然后算斜率 k 和偏置 b。/* 从两个校准点计算出 k 和 b */ /* 输入x1, y1 是校准点 1 的 ADC 值和实际电压 */ /* x2, y2 是校准点 2 的 ADC 值和实际电压 */ float k (float)(y2 - y1) / (float)(x2 - x1); float b (float)y1 - k * (float)x1; uint16_t Calibrate(uint16_t adc_val) { return (uint16_t)(k * adc_val b); }这里用浮点计算会比较直观校准参数可以在开机时从 Flash 读出来也可以直接在代码里写死。如果不想用浮点可以把 k 和 b 都放大 10000 倍后存成整数最终结果再除以 10000。校准完成后必须回测中间值比如输入 5.000V 看显示是不是 5.000V 附近不能只认两个端点。5.2 显示乱码、黑块和亮度异常先查这几处LCD1602 上电后如果第二行出现一排黑块最常见的是对比度电位器没调好。V0 脚的电压决定了显示浓度一般要调到 0.5V 到 1V 之间具体需要一边看屏幕一边调。如果 V0 直接接地屏幕可能完全看不见字不是硬件坏。乱码还有一个高频诱因是初始化时序不对。四线模式下0x33 和 0x32 必须保证足够延时尤其是清屏 0x01 之后要等 1.7ms很多移植代码在这里延时不达标导致后续指令全部错位。三块屏并联时还要检查LCD_Select后是否对非选中的屏全部拉高了 EN。如果某块屏的 EN 莫名保持低电平就会在总线变化时多锁存一次数据表现就是只有一块屏乱码。5.3 采样值跳动的排查顺序先看 VREF 引脚上的纹波用示波器测量是否有 50mV 以上的高频噪声。如果有在 VREF 和 VSSA 之间加 100nF 与 10uF 电容并联。再看电源部分ADC 引脚旁边最好不要走开关电源的功率线地线要单点汇接避免形成地环路。最后把滤波窗口和刷新率匹配好。数字电压表显示的最后一位如果一直跳不一定是噪声大也有可能是刷新率太高。把滤波窗口从 5 加大到 9显示会稳定很多。校准完成后用 5.000V 标准源做 10 分钟连续测试观察显示值是否在 4.998V 到 5.002V 之间波动这个结果直接决定这套三屏电压表能不能当作简易工装投入使用。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号