STM32F103 ADC实战从硬件搭建到波形可视化的全流程解析在嵌入式开发中ADC模数转换器是将模拟世界与数字系统连接的关键桥梁。许多开发者虽然掌握了ADC的基本原理却苦于无法将其转化为实际项目中的有效工具。本文将带你从零开始使用STM32F103C8T6BluePill开发板常见型号构建一个完整的电压采集与可视化系统涵盖CubeMX配置、DMA传输优化、串口协议设计以及Python实时波形显示的全套解决方案。1. 硬件准备与CubeMX工程创建在开始编码前正确的硬件连接和开发环境配置是项目成功的基础。我们需要准备以下硬件组件STM32F103C8T6最小系统板BluePill10kΩ电位器用于模拟电压输入USB转TTL串口模块如CH340G杜邦线若干硬件连接示意图STM32引脚连接目标备注PA0电位器中间引脚ADC1通道0输入3.3V电位器一端供电电压GND电位器另一端接地PA9(TX)USB-TTL模块RXUSART1发送端PA10(RX)USB-TTL模块TXUSART1接收端3.3VUSB-TTL模块VCC可选部分模块需供电GNDUSB-TTL模块GND共地打开STM32CubeMX按照以下步骤创建工程选择MCU型号STM32F103C8T6系统核心配置SYS: Serial Wire (用于ST-Link调试)RCC: High Speed Clock (HSE) 选择Crystal/Ceramic ResonatorADC1配置启用IN0通道对应PA0参数设置Resolution 12Bits Data Alignment Right Scan Conversion Mode Disabled Continuous Conversion Mode Enabled Discontinuous Conversion Mode Disabled DMA Continuous Requests Enabled End Of Conversion Selection EOC flag at the end of single conversionUSART1配置Mode: AsynchronousBaud Rate: 115200Word Length: 8BitsParity: NoneStop Bits: 1DMA配置添加DMA通道ADC1Mode: CircularPriority: HighMemory Increment: Enable时钟树配置确保PCLK2不超过72MHzADC时钟不超过14MHz推荐配置HCLK 72MHz PCLK1 36MHz PCLK2 72MHz ADC Prescaler PCLK2/6 12MHz生成代码Toolchain/IDE选择适合你的开发环境MDK-ARM/IAR/STM32CubeIDE提示CubeMX生成的HAL库代码已经包含了ADC和USART的初始化但我们需要在用户代码区域添加应用逻辑。2. ADC采集与DMA传输优化传统轮询方式采集ADC会占用大量CPU资源而中断方式在高采样率时也会导致频繁中断。使用DMA可以直接将ADC转换结果搬运到内存缓冲区完全解放CPU资源。DMA缓冲区定义与初始化在main.c文件中添加以下全局变量#define ADC_BUF_SIZE 256 uint16_t adcBuffer[ADC_BUF_SIZE]; float voltageBuffer[ADC_BUF_SIZE];在main()函数中的/* USER CODE BEGIN 2 */部分添加DMA启动代码HAL_ADC_Start_DMA(hadc1, (uint32_t*)adcBuffer, ADC_BUF_SIZE);ADC值到实际电压的转换STM32F103的ADC为12位分辨率参考电压通常为3.3V。创建转换函数void ADC_ConvertToVoltage(uint16_t* adcBuf, float* voltBuf, uint32_t size) { for(uint32_t i 0; i size; i) { voltBuf[i] (float)adcBuf[i] * 3.3f / 4095.0f; } }定时采样控制为了实现固定采样率我们可以使用定时器触发ADC转换。在CubeMX中配置TIM2时钟源Internal Clock预分频器72-1 (1MHz)计数器周期1000-1 (1kHz采样率)触发输出(TRGO)Update Event然后在ADC配置中选择Timer 2 Trigger Out event作为外部触发源外部触发边沿上升沿触发注意DMA循环模式会持续覆盖缓冲区数据因此需要合理设计数据处理节奏避免数据丢失。3. 串口数据传输协议设计直接将原始ADC数据通过串口发送效率低下且难以解析。我们需要设计一个简单高效的通信协议。协议帧结构设计字节位置内容说明00xAA帧头用于同步10x55帧头第二字节2数据长度N后续数据字节数3~N2数据内容实际传输的数据N3校验和前面所有字节的和的低8位数据打包函数实现void USART_SendDataPacket(uint8_t* data, uint16_t size) { uint8_t packet[256 4]; // 最大256字节数据4字节协议头尾 uint8_t checksum 0; packet[0] 0xAA; packet[1] 0x55; packet[2] size; for(int i 0; i size; i) { packet[3 i] data[i]; } // 计算校验和 for(int i 0; i size 3; i) { checksum packet[i]; } packet[size 3] checksum; HAL_UART_Transmit(huart1, packet, size 4, HAL_MAX_DELAY); }电压数据发送实现在主循环中定期发送电压数据/* USER CODE BEGIN WHILE */ while (1) { static uint32_t lastTick 0; if(HAL_GetTick() - lastTick 100) { // 每100ms发送一次数据 lastTick HAL_GetTick(); ADC_ConvertToVoltage(adcBuffer, voltageBuffer, ADC_BUF_SIZE); USART_SendDataPacket((uint8_t*)voltageBuffer, ADC_BUF_SIZE * sizeof(float)); } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */4. Python上位机波形显示使用Python的pySerial和Matplotlib库可以快速构建一个实时波形显示工具。Python端代码实现import serial import struct import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation from collections import deque # 串口配置 ser serial.Serial(COM3, 115200, timeout1) # 替换为你的串口号 plt.style.use(ggplot) # 显示配置 MAX_POINTS 500 x_data deque(maxlenMAX_POINTS) y_data deque(maxlenMAX_POINTS) fig, ax plt.subplots() line, ax.plot([], [], b-) ax.set_xlim(0, MAX_POINTS/10) ax.set_ylim(0, 3.3) ax.set_xlabel(Sample Point) ax.set_ylabel(Voltage (V)) ax.set_title(STM32 ADC Real-time Waveform) def parse_packet(data): if len(data) 4 or data[0] ! 0xAA or data[1] ! 0x55: return None length data[2] if len(data) length 4: return None checksum sum(data[:-1]) 0xFF if checksum ! data[-1]: print(fChecksum error: {checksum} ! {data[-1]}) return None return data[3:3length] def update(frame): # 读取串口数据 while ser.in_waiting 0: header ser.read(1) if header b\xAA: rest ser.read(3) # 读取协议头剩余部分 if len(rest) 3 and rest[0] 0x55: length rest[1] data ser.read(length 1) # 数据校验和 packet b\xAA rest data payload parse_packet(packet) if payload: # 解析浮点数据 num_floats len(payload) // 4 voltages struct.unpack(f{num_floats}f, payload) # 更新显示数据 for i, v in enumerate(voltages): x_data.append(len(x_data)) y_data.append(v) # 更新曲线 line.set_data(x_data, y_data) # 调整X轴范围 if len(x_data) MAX_POINTS/10: ax.set_xlim(x_data[-1] - MAX_POINTS/10, x_data[-1]) return line, ani FuncAnimation(fig, update, blitTrue, interval50) plt.show()功能增强建议多通道支持修改协议格式包含通道信息数据保存添加按钮将当前数据保存为CSV采样率显示计算实际采样率并显示触发功能添加边沿触发功能稳定波形显示5. 系统优化与调试技巧一个健壮的数据采集系统需要考虑多方面因素。以下是几个关键优化点电源噪声抑制在ADC参考电压引脚添加10μF和0.1μF去耦电容使用独立的LDO为模拟部分供电避免数字信号线与模拟信号线平行走线软件滤波算法移动平均滤波简单有效#define FILTER_WINDOW 5 float movingAverageFilter(float newValue) { static float buffer[FILTER_WINDOW] {0}; static uint8_t index 0; static float sum 0; sum - buffer[index]; buffer[index] newValue; sum buffer[index]; index (index 1) % FILTER_WINDOW; return sum / FILTER_WINDOW; }中值滤波适合消除脉冲噪声卡尔曼滤波动态系统最优估计DMA双缓冲技术当处理速度跟不上采集速度时可以使用双缓冲技术#define BUF_SIZE 256 uint16_t adcBuffer1[BUF_SIZE]; uint16_t adcBuffer2[BUF_SIZE]; volatile uint8_t activeBuffer 0; // 0 for buffer1, 1 for buffer2 // 在DMA完成中断中切换缓冲区 void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc) { activeBuffer 1; // 处理adcBuffer1 } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { activeBuffer 0; // 处理adcBuffer2 }常见问题排查无数据或数据全零检查CubeMX中ADC通道配置是否正确测量实际输入电压是否在0-3.3V范围内确认DMA配置为循环模式数据不稳定检查电源稳定性适当增加采样时间ADC_SMPR寄存器添加软件滤波串口数据错乱确认双方波特率一致检查地线连接降低传输速率或缩短数据包长度