当前位置: 首页 > news >正文

STM32F103用USART3连陶晶串口屏实时显示PA1采集的电压值(附TFT同步对比)

本文还有配套的精品资源,点击获取

简介:基于STM32F103主控,通过ADC通道PA1持续采集外部模拟电压信号,采集结果以数字形式实时发送至陶晶科技串口屏(HMI),同时在本地TFT液晶屏同步显示,便于直观比对两种显示方案的效果。串口屏通信使用硬件USART3,TX/RX引脚固定为PB10/PB11,已适配主流320×240分辨率陶晶HMI屏,无需修改底层协议即可直接导入配套的320240例.HMI工程文件。整个Keil MDK项目结构清晰,包含标准外设库(STM32F10x_FWLib)、HARDWARE驱动层、SYSTEM底层支持、USER主程序入口及OBJ编译输出目录;提供simulation.c/simulation.h用于仿真调试,main_sim.c为主函数入口,另有keilkilll.bat一键清理编译残留、README.TXT和例程说明.txt详细说明软硬件配置与运行步骤。所有代码基于标准库开发,不依赖HAL或LL,适合嵌入式初学者理解ADC采样、串口通信与人机界面联动的基本流程。

1. 项目概述:为什么双屏同步显示是嵌入式调试的“黄金组合”

我第一次在客户现场调试一个压力传感器采集系统时,就栽在了显示环节上。客户要求实时看到0~5V电压变化,我用TFT屏做了个简单波形图,结果对方工程师盯着屏幕看了三分钟,突然问:“这个数值是ADC原始值还是换算后的实际电压?小数点后几位?有没有滤波?你确定没丢数据?”——那一刻我才意识到,单靠一块本地TFT屏,根本无法向非嵌入式背景的用户清晰传递“数据从物理世界到数字世界的完整可信链路”。

后来我把陶晶串口屏加了进来,用它只干一件事:纯数字、高对比度、无干扰地显示当前电压值(比如“3.284 V”),而TFT屏继续画波形、做趋势、加单位图标。客户一眼就懂:左边那个大字是“真实读数”,右边那个图是“变化过程”。这种分工,就是本项目最核心的价值——不是炫技,而是构建可验证、可追溯、可沟通的数据可视化闭环

这个方案专为STM32F103设计,不碰HAL库,全用标准外设库(STM32F10x_FWLib),所有代码都在Keil MDK里跑得稳稳当当。核心信号链非常干净:外部电压接在PA1引脚 → ADC1通道1采样 → 软件滤波与电压换算 → 一路通过USART3(PB10/TX, PB11/RX)发给陶晶串口屏 → 另一路驱动本地TFT屏(通常是SPI接口的ILI9341或ST7789)。两个屏幕显示内容完全同步,刷新率稳定在20Hz以上,实测从ADC启动采样到两个屏幕同时更新,端到端延迟低于65ms。

关键词里的“USART3”不是随便选的——F103的USART3复用在PB10/PB11,这两脚恰好和SWD调试接口(SWDIO/SWCLK)不冲突,意味着你烧录程序、在线调试、串口通信可以三件事同时进行,不用反复拔插线。而“陶晶串口屏”之所以被选中,是因为它把复杂的UART协议封装成极简指令集:你不需要手写Modbus CRC校验,不用纠结帧头帧尾长度,只要按文档发一串ASCII命令,比如"t0.txt=\"2.731\"",屏幕上的文本控件t0就立刻更新。这对初学者来说,相当于把“通信协议”这堵墙直接拆了,让你专注在“怎么把ADC数据变成人能看懂的数字”这件事上。

整个工程结构像一本摊开的教科书:HARDWARE目录里放着adc.c、usart3.c、tft.c这些驱动;SYSTEM里是delay、sys、usart(注意,这里的usart是用于printf重定向的调试串口,和USART3完全独立);USER下main_sim.c是主入口,simulation.c里甚至预留了模拟电压生成函数,方便你在没接真实传感器时也能跑通全流程。就连keilkilll.bat这种细节都考虑到了——双击它,OBJ、Listings、Debug这些编译垃圾全清空,下次编译就是干干净净的全新状态。这不是一个“能跑就行”的Demo,而是一个经得起产线拷问、教学演示、客户验收的工业级最小可行系统(MVP)。

2. 硬件设计与信号链解析:从PA1引脚到两个屏幕的物理路径

要让电压值在两个屏幕上同步跳动,第一步不是写代码,而是把硬件信号链理清楚。很多人卡在“屏不亮”“数值乱跳”“串口没反应”,八成问题出在硬件连接没吃透。我带过十几届学生做这个实验,最常见的三个硬件坑,全在这条链路上。

2.1 ADC采集端:PA1不是万能接口,必须懂它的电气约束

PA1是ADC1的通道1(CH1),但F103的ADC输入有明确的电压范围和阻抗要求。手册里白纸黑字写着:ADC输入电压必须在VREF- ≤ VIN ≤ VREF+之间,且推荐源阻抗 ≤ 10kΩ。VREF+默认接的是VDDA(模拟电源),也就是3.3V,所以PA1只能安全采集0~3.3V的电压。如果你硬接5V信号,轻则ADC读数饱和(永远是4095),重则损伤内部采样保持电路。

更隐蔽的问题是信号源阻抗。比如你用一个100kΩ电位器分压,输出接到PA1,会发现读数严重偏低且波动大。这是因为ADC采样时,内部采样电容(几pF)需要通过信号源内阻充电,100kΩ × 几pF ≈ 几百纳秒,而F103默认采样周期只有1.5个ADC时钟周期(假设ADCCLK=14MHz,周期≈71ns),电容根本充不满,导致采样值失真。我的做法是:在PA1前加一级运放电压跟随器(比如LM358),或者至少串一个1kΩ电阻再并一个10nF电容到地,构成低通滤波+阻抗匹配。实测下来,这样处理后,同样一个3.2V稳压源,ADC读数从3982稳定到4085(理论最大值4095),误差从2.7%降到0.25%。

提示:PA1的复用功能必须显式开启。很多新手只开了RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_GPIOA, ENABLE),却忘了RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_ADC1, ENABLE)。GPIOA时钟不开,PA1连推挽输出都做不到;ADC1时钟不开,你调用ADC_RegularChannelConfig()函数会直接卡死在while循环里——因为ADC外设根本没上电。

2.2 USART3通信端:PB10/PB11的“双重身份”与电平匹配

USART3的TX(PB10)和RX(PB11)是F103上少有的“调试友好型”串口。为什么?因为它们和SWD调试接口(PA13/SWDIO, PA14/SWCLK)物理引脚完全不重叠。你可以一边用ST-Link烧录程序、设置断点,一边用USART3和串口屏通信,互不干扰。但这里有个致命细节:PB10/PB11的IO模式必须配置为“复用推挽输出”(GPIO_Mode_AF_PP)和“浮空输入”(GPIO_Mode_IN_FLOATING)。如果错配成“上拉输入”,RX脚会把串口屏发来的低电平拉高,通信直接中断;如果TX配成“开漏输出”,没有上拉电阻时,高电平可能达不到3.3V,串口屏识别为噪声。

电平匹配是另一个雷区。陶晶串口屏(如TGUS系列)标称支持3.3V TTL电平,但实测发现,其RX脚对高电平阈值很敏感——低于2.8V就可能误判为低。而F103的PB10在“复用推挽”模式下,空载输出高电平是3.3V,但一旦接上串口屏(输入阻抗约100kΩ),线路分布电容+接收端输入电容会导致上升沿变缓,高电平有效值可能跌到3.0V以下。我的解决方案是:在PB10和串口屏TXD之间串一个22Ω电阻,再在串口屏TXD脚对地接一个10kΩ上拉电阻。这个RC网络能把上升沿“拽”得更陡峭,实测高电平稳定在3.25V以上。至于RX端(PB11),因为是输入,只需确保串口屏的TXD(即F103的RXD)输出符合TTL规范,一般无需额外电路。

注意:陶晶串口屏的默认波特率是115200bps,8N1,无流控。这个参数必须和USART3初始化完全一致。我在工程里看到有人把USART3_InitTypeDef结构体里的USART_InitStruct->USART_BaudRate = 9600; 粘贴错了位置,导致串口屏收不到任何指令,屏幕一直黑着——其实它一直在等正确的波特率握手。

2.3 TFT显示端:SPI速率与DMA搬运的取舍

本地TFT屏(假设是常见的2.4寸ILI9341)走SPI总线,通常接在SPI1(PA5/CLK, PA6/MISO, PA7/MOSI, PA4/NSS)。这里的关键矛盾是:ADC采样要快,TFT刷屏也要快,但SPI1和ADC1共用APB2总线,带宽争抢严重。如果ADC每10ms采一次,而刷一次全屏(320×240×16bit)需要20ms,那ADC就会被SPI操作阻塞,造成采样间隔抖动。

我的经验是:放弃“每次ADC更新都重刷全屏”,改用“局部刷新+双缓冲”。具体来说,在TFT驱动里开辟两块显存(front buffer和back buffer),ADC新数据来时,只更新屏幕上电压数值所在的矩形区域(比如80×30像素),然后用DMA把这块区域的数据从back buffer搬运到TFT的GRAM。实测下来,刷一个80×30的16位色块,DMA耗时仅1.2ms,而全屏刷新要18ms。这样,ADC可以稳定在50Hz采样(20ms间隔),TFT刷新也跟得上,两个任务在时间轴上完全解耦。

3. 软件架构与核心流程:三层驱动如何协同工作

这个项目的软件结构看似是“ADC→USART3→TFT”一条直线,实则暗藏三层异步协同:ADC是硬件自动触发的“生产者”,USART3和TFT是“消费者”,而中间的“消息队列”就是内存中的一个共享变量。标准外设库没提供RTOS,所以我们用最朴实的“标志位+轮询”方式,既保证实时性,又避免复杂调度。

3.1 ADC驱动层:不只是启动转换,关键是时序控制与滤波

ADC初始化绝不是调用ADC_Init()就完事。F103的ADC有“规则组”和“注入组”之分,我们只用规则组(Regular Channel)。关键参数有三个:

  1. ADC_ScanConvMode:设为DISABLE。因为我们只采PA1一个通道,不需要扫描多个通道,关掉扫描能省下切换通道的时间。
  2. ADC_ContinuousConvMode:设为ENABLE。这是实现“连续采集”的核心,ADC转换完一次自动启动下一次,无需CPU干预。
  3. ADC_ExternalTrigConv:设为ADC_ExternalTrigConv_None。不用外部触发,用软件触发(ADC_SoftwareStartConvCmd())或连续模式自动触发。

但连续模式有个陷阱:如果ADC转换速度太快(比如采样时间设为1.5个周期),而你的软件来不及读取DR寄存器,新的转换结果会覆盖旧结果,造成数据丢失。我的做法是:把ADCCLK设为14MHz(RCC_PCLK2/4),采样时间设为239.5个周期(对应17.1μs),这样单次转换耗时约25μs,而主循环每5ms执行一次ADC_GetConversionValue(),完全来得及。

滤波是让电压值“看起来稳”的关键。我用的是“滑动平均+限幅”复合滤波:
- 滑动平均:维护一个长度为8的数组,每次新ADC值进来,去掉最老的,加入最新的,求平均。这能消除高频噪声。
- 限幅:计算相邻两次滤波后电压值的差,如果绝对值超过0.02V(对应ADC值约25),就认为是干扰脉冲,丢弃本次数据,沿用上次值。这个阈值是我用示波器抓PA1引脚实测定的——正常传感器信号变化率远低于此。

// adc.c 中的核心滤波函数 #define ADC_FILTER_LEN 8 uint16_t adc_filter_buf[ADC_FILTER_LEN]; uint8_t adc_filter_idx = 0; float last_voltage = 0.0f; float ADC_GetVoltageFiltered(void) { uint16_t raw = ADC_GetConversionValue(ADC1); // 读取原始ADC值 float voltage = (float)raw * 3.3f / 4095.0f; // 换算为电压值 // 限幅滤波:变化超过0.02V视为干扰 if (fabsf(voltage - last_voltage) > 0.02f) { return last_voltage; // 返回上一次有效值 } // 滑动平均 adc_filter_buf[adc_filter_idx] = raw; adc_filter_idx = (adc_filter_idx + 1) % ADC_FILTER_LEN; uint32_t sum = 0; for (int i = 0; i < ADC_FILTER_LEN; i++) { sum += adc_filter_buf[i]; } last_voltage = (float)sum * 3.3f / (4095.0f * ADC_FILTER_LEN); return last_voltage; }

3.2 USART3驱动层:陶晶协议的“零学习成本”封装

陶晶串口屏的指令本质是ASCII字符串,格式固定为"控件名.属性=值",以\0结尾。比如更新文本控件t0显示电压,指令是t0.txt="3.284"。难点不在发送,而在确保指令被完整、无误地送达。UART通信容易受干扰,一个字节出错,整条指令就失效。

我的USART3驱动做了三件事:
1.发送缓冲区:定义一个256字节的数组,把要发的指令先拼好,再整包发送,避免边拼边发导致中断打断。
2.发送完成等待:调用USART_SendData()后,必须等USART_GetFlagStatus(USART3, USART_FLAG_TC) == SET,即发送完成标志置位,才能发下一条。否则指令会粘连。
3.指令标准化:所有发给屏幕的指令,都用宏封装,比如#define SEND_VOLTAGE(v) usart3_printf("t0.txt=\"%.3f\"", v),避免手写字符串出错。

// usart3.c 中的简化发送函数 void USART3_SendString(char *str) { while (*str != '\0') { while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); // 等待发送完成 USART_SendData(USART3, *str++); } } // 在main循环里调用 float voltage = ADC_GetVoltageFiltered(); char cmd[32]; sprintf(cmd, "t0.txt=\"%.3f\"", voltage); USART3_SendString(cmd);

3.3 TFT驱动层:局部刷新如何节省90%的刷屏时间

TFT驱动的核心是TFT_FillRectangle()函数,它接受坐标、宽高、颜色,只刷指定区域。本项目中,电压值显示在一个80×30像素的白色背景框里,字体用16×32的ASCII点阵。每次更新,只需:
1. 用TFT_FillRectangle(x,y,w,h,BLACK)把旧数值区域涂黑;
2. 用TFT_ShowNum(x+8,y+4,(int)(voltage*1000),7,16)显示新数值(乘1000转为整数,显示7位,含小数点);
3. 最后TFT_ShowString(x+8,y+4,"V")加单位。

这个过程耗时约1.8ms,而刷全屏要20ms。更重要的是,它让TFT和USART3彻底解耦:TFT刷屏时,USART3可以同时收发数据;USART3发指令时,TFT正在DMA搬运像素,互不影响。整个系统就像两条平行铁轨上的火车,各自按自己的时刻表运行。

4. 实操步骤与关键配置:从新建工程到双屏同显的完整路径

现在,我们把前面所有的原理,落地成Keil MDK里可点击、可编译、可下载的具体操作。这不是“复制粘贴就能跑”的教程,而是告诉你每一步背后的意图和常见翻车点。

4.1 Keil工程搭建:标准外设库的“正确打开方式”

第一步,创建新工程。Project → New uVision Project → 选择STM32F103C8(或你板子的具体型号)→ Copy Startup file。这时别急着写代码,先做三件事:

  1. 添加标准外设库路径:Options for Target → C/C++ → Include Paths,添加:
    ..\STM32F10x_FWLib\inc ..\SYSTEM ..\HARDWARE ..\USER
    注意:路径必须是相对路径,且以..\开头,否则编译报“找不到stm32f10x.h”。

  2. 定义宏:在C/C++ → Define里填:
    USE_STDPERIPH_DRIVER, STM32F10X_MD
    USE_STDPERIPH_DRIVER告诉编译器用标准库而非CMSIS;STM32F10X_MD指明是中密度芯片(Flash 64-128KB),影响中断向量表大小。

  3. 添加源文件:把STM32F10x_FWLib\src下的.c文件(除了misc.c,它已被system_stm32f10x.c替代)全部Add。特别注意:stm32f10x_it.csystem_stm32f10x.c必须加到工程里,前者放中断服务函数,后者初始化系统时钟。

提示:很多人卡在“程序下载后不运行”,其实是system_stm32f10x.c里的SystemInit()没执行。检查startup_stm32f10x_md.s里Reset_Handler是否正确跳转到SystemInit,而不是直接跳main

4.2 ADC初始化:三步走,缺一不可

HARDWARE\adc.c里,ADC初始化函数ADCx_Init()必须包含:

  1. 使能时钟
    c RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_GPIOA | RCC_APB2PERIPH_ADC1, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_DMA1, ENABLE); // 如果用DMA

  2. 配置PA1为模拟输入
    c GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // 关键!必须是AIN,不是IPU/IPD GPIO_Init(GPIOA, &GPIO_InitStructure);

  3. 配置ADC1
    ```c
    ADC_DeInit(ADC1);
    ADC_StructInit(&ADC_InitStructure);
    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);

// 配置通道1,采样时间239.5周期
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5);

// 开启ADC和软件开始转换
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1));
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 连续模式下,这条只需调一次
```

4.3 USART3初始化:PB10/PB11的精准配置

HARDWARE\usart3.c里的USART3_Init()函数,重点在GPIO配置:

// 1. 使能GPIOB和USART3时钟 RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_USART3, ENABLE); // 2. 配置PB10为复用推挽输出(TX) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 不是GPIO_Mode_Out_PP! GPIO_Init(GPIOB, &GPIO_InitStructure); // 3. 配置PB11为浮空输入(RX) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 不是GPIO_Mode_IPU! GPIO_Init(GPIOB, &GPIO_InitStructure); // 4. 配置USART3 USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; USART_Init(USART3, &USART_InitStructure); USART_Cmd(USART3, ENABLE);

4.4 主循环逻辑:如何让双屏真正“同步”

USER\main_sim.c里的main()函数,核心就是一个无限循环:

int main(void) { delay_init(); // 初始化SysTick NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置中断优先级分组 uart_init(9600); // 初始化调试串口(PA9/PA10),用于printf LED_Init(); // 初始化LED,用于指示状态 KEY_Init(); // 初始化按键(可选) // 初始化各外设 ADCx_Init(); // ADC初始化 USART3_Init(); // USART3初始化 TFT_Init(); // TFT初始化 TP_Init(); // 触摸屏初始化(可选) // 显示初始界面 TFT_Clear(WHITE); TFT_ShowString(10,10,"Voltage: "); while(1) { float voltage = ADC_GetVoltageFiltered(); // 获取滤波后电压 // 同步更新两个屏幕 update_usart3_screen(voltage); // 发送指令到串口屏 update_tft_screen(voltage); // 局部刷新TFT delay_ms(50); // 控制刷新率为20Hz,太快来不及看,太慢显得卡顿 } }

update_usart3_screen()update_tft_screen()是两个独立函数,互不阻塞。实测下来,delay_ms(50)是最优解:既能保证人眼看清数值跳动,又给CPU留出足够时间处理其他任务(比如按键扫描、LED闪烁)。

5. 常见问题与排查技巧实录:那些官方文档不会写的坑

这个项目我带着不同基础的学员做过不下50遍,总结出一张“问题-现象-根源-解法”的速查表。很多问题看似玄学,其实都有迹可循。

问题现象根本原因快速排查与解决
串口屏完全无反应,黑屏或停留在启动画面1. 波特率不匹配(最常见)
2. TX/RX线接反
3. 串口屏未进入“HMI模式”(需短接BOOT0)
① 用USB转TTL模块接PB10/PB11,用串口助手发t0.txt="TEST",看能否收到回显;
② 检查硬件连线:F103的PB10(TX) → 串口屏的RXD,PB11(RX) → 串口屏的TXD;
③ 断电,短接串口屏BOOT0到GND,再上电,听“滴”一声确认进入HMI模式。
TFT屏显示乱码、花屏、颜色错乱1. SPI时钟极性和相位(CPOL/CPHA)配置错误
2. TFT_RST引脚未正确复位
3. ILI9341初始化序列未执行完就刷屏
① 查阅TFT规格书,确认SPI模式(通常是Mode 0:CPOL=0, CPHA=0);
② 在TFT_Init()开头强制拉低RST引脚50ms,再拉高;
③ 在TFT_Init()末尾加delay_ms(120),确保ILI9341内部稳压电容充满。
ADC读数始终为0或满量程(4095)1. PA1引脚被其他外设复用(如JTAG)
2. ADC参考电压VREF+未接稳
3. 采样时间过短,电容未充满
① 检查RCC_APB2PeriphClockCmd()是否开启了GPIOA和ADC1;
② 用万用表测PA1对地电压,确认在0~3.3V之间;
③ 把ADC_SampleTime_239Cycles5临时改为ADC_SampleTime_71Cycles5,看读数是否变化。
双屏数值不同步,TFT比串口屏慢半拍1. 主循环里delay_ms(50)位置不对,放在了更新屏幕之后
2. TFT刷屏函数里用了while等待SPI忙标志,阻塞了主线程
① 确保delay_ms(50)是循环的最后一条语句;
② 检查TFT_FillRectangle()是否用了while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET),应改为查询+超时退出,或直接用DMA。
程序下载后LED不闪,疑似死机1.SystemInit()里HSE启动失败(外部晶振未焊或损坏)
2.main()里某处进入死循环(如等待某个永不置位的标志)
① 将system_stm32f10x.c里的RCC_WaitForHSEStartUp()注释掉,强制用HSI;
② 在main()开头加LED0 = 0;,如果LED灭了,说明卡在LED0 = 0;之前,重点查时钟配置。

实操心得:我习惯在main()开头加一段“硬件自检”代码:
c LED0 = 0; delay_ms(100); LED0 = 1; // 检查LED printf("ADC:%d\r\n", ADC_GetConversionValue(ADC1)); // 检查ADC USART3_SendString("test"); // 检查USART3 TFT_ShowString(10,50,"HW OK!"); // 检查TFT
这段代码能在5秒内帮你定位90%的硬件连接问题。不要一上来就调双屏同步,先把每个模块单独点亮。

另一个血泪教训:陶晶串口屏的HMI工程文件(.HMI)导入后,一定要在编辑软件里点“生成下载文件”,得到.DGUS文件,再用SD卡或USB线下载到屏幕。很多人直接把.HMI文件拷进去,屏幕根本不认——.HMI是工程源码,.DGUS才是可执行固件。

最后分享一个小技巧:如果想快速验证ADC数据流是否通畅,不用接屏幕,直接在main()循环里加:

printf("V=%.3f\r\n", voltage);

然后用串口助手(如XCOM)看输出。只要这里数值稳定跳动,说明ADC和滤波完全OK,问题一定出在屏幕驱动或通信链路上。这个“分段隔离法”,是我解决嵌入式疑难杂症的终极武器。

6. 扩展思路与进阶方向:从双屏显示到智能终端

这个项目看似简单,但它是一块绝佳的“能力跳板”。当你把ADC、USART、TFT这三座桥都搭稳了,后续的扩展就水到渠成。我根据实际项目经验,梳理了三条清晰的进阶路径,每一条都能直接用到工作中。

6.1 数据记录与分析:从“显示”升级到“诊断”

现在只是实时显示电压,但工业现场更需要“历史数据”。你可以轻松加上SD卡模块(SPI接口),用FatFs文件系统,每秒把电压值写入CSV文件:

FIL fil; f_open(&fil, "VOLTAGE.CSV", FA_OPEN_ALWAYS | FA_WRITE); f_printf(&fil, "%.3f,%lu\r\n", voltage, GetTickCount()); // 电压值+时间戳 f_close(&fil);

再配合PC端Python脚本(用pandas读CSV,matplotlib画图),就能生成专业的趋势分析报告。我帮一家电机厂做的类似系统,就靠这个功能发现了轴承早期磨损导致的电压微小波动,提前两周预警,避免了产线停机。

6.2 多通道协同:从单点测量到系统监控

PA1只是冰山一角。F103的ADC1有16个通道,你可以同时接温度(NTC)、电流(霍尔传感器)、湿度(DHT22)等信号。关键是要重构ADC驱动:
- 用ADC_ScanConvMode = ENABLE,配置多通道扫描;
- 用ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1,用定时器1的捕获比较事件触发ADC,保证多通道采样严格同步;
- 在中断服务函数ADC1_2_IRQHandler()里,一次性读取所有通道的DR寄存器,打包发送。

这样,一个屏幕就能显示“电压:3.28V,温度:25.4℃,电流:1.82A”,构成完整的设备健康画像。

6.3 交互升级:从被动显示到主动控制

陶晶串口屏不止能显示,还能接收触摸指令。在HMI工程里,加一个“校准”按钮,按下后,单片机进入校准模式:采集10次空载电压(0V)和10次满载电压(3.3V),自动计算新的ADC偏移和增益系数,存入EEPROM。下次上电,直接用新系数换算,精度提升一个数量级。这个功能,让设备从“需要工程师手动校准”变成“用户一键自校准”,产品体验直接跃升。

我自己用这个框架,三个月内交付了三个客户项目:一个是实验室温湿度监测仪,一个是光伏板电压巡检终端,一个是智能灌溉控制器。它们的底层代码80%是复用的,差异只在HMI界面和传感器驱动。这就是标准化模块的魅力——当你把ADC采集、串口通信、TFT显示这三件事真正吃透,剩下的,只是在上面搭积木而已

最后再强调一句:不要追求“一步到位”。先让PA1的电压在串口屏上稳定显示,再让它在TFT上同步出现,最后再加滤波、加存储、加交互。每一步都亲手调试、亲眼验证,你积累的就不是代码,而是嵌入式开发的肌肉记忆。等哪天客户说“我们要做个XX终端”,你脑子里浮现的不再是恐惧,而是一张清晰的模块图——哪些能复用,哪些要重写,哪些该外包。这才是这个项目给你最硬核的回报。

本文还有配套的精品资源,点击获取

简介:基于STM32F103主控,通过ADC通道PA1持续采集外部模拟电压信号,采集结果以数字形式实时发送至陶晶科技串口屏(HMI),同时在本地TFT液晶屏同步显示,便于直观比对两种显示方案的效果。串口屏通信使用硬件USART3,TX/RX引脚固定为PB10/PB11,已适配主流320×240分辨率陶晶HMI屏,无需修改底层协议即可直接导入配套的320240例.HMI工程文件。整个Keil MDK项目结构清晰,包含标准外设库(STM32F10x_FWLib)、HARDWARE驱动层、SYSTEM底层支持、USER主程序入口及OBJ编译输出目录;提供simulation.c/simulation.h用于仿真调试,main_sim.c为主函数入口,另有keilkilll.bat一键清理编译残留、README.TXT和例程说明.txt详细说明软硬件配置与运行步骤。所有代码基于标准库开发,不依赖HAL或LL,适合嵌入式初学者理解ADC采样、串口通信与人机界面联动的基本流程。


本文还有配套的精品资源,点击获取

http://www.jsqmd.com/news/919866/

相关文章:

  • 告别数据焦虑:用Python和PyTorch实战Matching Networks,5个样本也能搞定图像分类
  • 保姆级教程:Windows 10/11下JDK 8与Kettle 7.1.0.0的完整安装与环境变量配置
  • 从一次炼丹(训练模型)失败说起:我是如何为Linux服务器配置OOM策略来保住我的Python进程的
  • 别再傻傻在线装了!手把手教你用DNF把Linux软件包和依赖都下载到本地(Fedora/CentOS/RHEL通用)
  • 别急着扔!U盘/内存卡提示无法格式化FAT32?试试这个免费工具(DiskGenius保姆级教程)
  • 2026年5月性价比高的慢速静音粉碎机实力厂家哪家好 - 2026年企业资讯
  • AI安全专项:AI人脸识别的安全风险与防护
  • 凸限制算法在计算流体力学中的IDP性质实现
  • 实盘导向的Python股票交易工具包:整合AKShare数据、QMT直连下单与因子模板
  • 网络连接实时可视化利器TapMap
  • 华硕发布创梦Pro 27 OLED SDI专业显示器:集成nbsp;12G-SDInbsp;与内置色度计
  • 如何快速掌握生物年龄计算:BioAge工具的终极实用指南
  • 书匠策AI写毕业论文有多野?一个教育博主带你拆解这条“论文流水线“的科普实验
  • 如何快速掌握YOLO-Face人脸检测:面向初学者的完整实战指南
  • 2026古玩古董字画服务机构评测:收藏品交易/收藏品元青花/收藏品古币/收藏品字画/收藏品文玩/收藏品瓷器/收藏品鉴定/选择指南 - 优质品牌商家
  • YOLOv5结合双目相机实现实时目标三维定位与距离输出(含训练部署全流程代码)
  • 终极解决方案:在Linux系统上离线构建drawio-desktop流程图工具
  • Claude Code 100个真实案例 - 用AI绘制CAD机械图纸(工程师看了直呼内行)
  • 3D高斯泼溅渲染技术优化与实时化实践
  • 手把手教你将DOTA遥感数据集转成COCO格式(附完整Python代码与可视化对比)
  • 2026年Q2杭州防水维修服务评测:杭州厂房防水防腐修缮/杭州地下空间翻新改造/杭州外立面翻新改造/杭州屋面改造/选择指南 - 优质品牌商家
  • 别再手动分区了!用targetcli在CentOS 7上快速配置iSCSI共享存储(附防火墙和开机自启设置)
  • AI工具如何接管ETL流水线?揭秘2024企业数据中台升级的3个生死转折点
  • Aurora超级计算机架构与Exascale计算技术解析
  • 【图像融合】多重逻辑混沌映射加密和解密异或和傅里叶变换图像融合【含Matlab源码 15578期】
  • 2026年厦门精益生产与数字化转型管理咨询服务推荐指南 - 精选优质企业推荐官
  • 2026年好用的AI编程软件有哪些:权威推荐榜单
  • Go2 ROS2 SDK终极指南:让四足机器人实现智能导航与避障
  • 从图形界面到纯命令行:CentOS 7/RHEL 8 新手必学的运行模式切换与基础命令实战
  • 月省几百订阅费比DeepSeek还便宜的Token,OpenClaw和Hermes随便跑不肉痛