STM32F103用CubeMX实现ADC欠采样:用800Hz采样率捕获1kHz正弦波(附工程源码)
STM32F103实战:用CubeMX配置ADC欠采样捕获1kHz正弦波
在嵌入式系统开发中,ADC采样是获取模拟信号的关键技术。传统采样理论告诉我们,采样频率必须至少是信号最高频率的两倍(奈奎斯特采样定理),但欠采样技术却打破了这一常规认知。本文将手把手教你如何在STM32F103平台上,通过CubeMX配置TIM触发ADC,实现用800Hz采样率捕获1kHz正弦波信号的完整过程。
1. 硬件准备与环境搭建
1.1 所需硬件清单
- STM32F103C8T6最小系统板(Blue Pill开发板)
- USB转TTL串口模块(用于VOFA+数据可视化)
- 信号发生器(或能输出1kHz正弦波的任何设备)
- 示波器(可选,用于验证信号质量)
- 杜邦线若干
1.2 软件工具准备
- STM32CubeMX(版本6.5.0或更高)
- Keil MDK-ARM(或STM32CubeIDE)
- VOFA+(串口数据可视化工具)
- 串口调试助手(如Putty)
提示:VOFA+是一款功能强大的串口数据可视化工具,支持多种协议和显示方式,特别适合嵌入式开发中的实时数据展示。
2. CubeMX工程配置详解
2.1 时钟树配置
STM32F103的ADC时钟最大为14MHz,我们需要合理配置系统时钟:
- 选择外部高速时钟(HSE)
- 设置系统时钟为72MHz
- APB2总线时钟设为72MHz(ADC挂载在此总线上)
- ADC预分频设为6,得到12MHz的ADC时钟
// 时钟树关键配置(CubeMX自动生成) RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;2.2 ADC配置步骤
- 启用ADC1,选择通道(如通道0)
- 配置为"Regular Conversion"模式
- 设置数据对齐为右对齐
- 扫描模式禁用(单通道)
- 连续转换模式禁用(由TIM触发)
- DMA设置:启用DMA连续请求,循环模式
// ADC初始化结构体关键参数 hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = DISABLE; hadc1.Init.ContinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T3_TRGO; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 1;2.3 定时器TIM3配置
定时器用于精确控制ADC采样间隔,关键参数计算:
- 目标采样率:800Hz
- 定时器时钟:72MHz
- 分频计算:72MHz / (1000 * 90) = 800Hz
具体配置:
- 选择TIM3
- 时钟源选择内部时钟
- Prescaler(PSC)设为1000-1
- Counter Period(ARR)设为90-1
- 触发输出(TRGO)选择更新事件
// TIM3初始化结构体 htim3.Instance = TIM3; htim3.Init.Prescaler = 999; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 89; htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;2.4 DMA配置
DMA用于高效传输ADC数据,避免CPU干预:
- 添加DMA通道(ADC1)
- 模式选择循环模式
- 数据宽度:半字(ADC为12位)
- 内存地址递增
- 外设到内存传输
// DMA配置结构体 hdma_adc1.Instance = DMA1_Channel1; hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE; hdma_adc1.Init.MemInc = DMA_MINC_ENABLE; hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; hdma_adc1.Init.Mode = DMA_CIRCULAR;3. 欠采样原理深入解析
3.1 传统采样与欠采样对比
| 采样类型 | 采样率要求 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| 传统采样 | ≥2倍信号频率 | 低频信号 | 实现简单 | 高频信号需要极高采样率 |
| 欠采样 | 可低于信号频率 | 高频信号 | 降低硬件要求 | 需要精确同步 |
3.2 欠采样数学原理
欠采样本质上是利用了信号的周期性。对于1kHz正弦波:
- 信号周期:1ms
- 采样间隔:1.25ms(800Hz采样率)
- 相位增量:360° × (1.25ms/1ms) = 450° = 90°(模360°)
这样,每次采样点相当于在信号周期上移动90°,四个采样点就能完整重构一个周期。
3.3 实际应用中的限制
- 信号稳定性:被测信号必须严格周期稳定
- 时钟精度:定时器触发必须精确
- 相位同步:采样起始点影响重构效果
- 噪声影响:高频噪声可能导致混叠
注意:欠采样不适用于非周期信号或宽带信号,仅适用于单一频率或窄带信号。
4. 代码实现与调试技巧
4.1 主程序关键代码
#define ADC_BUF_LEN 256 uint16_t adcBuffer[ADC_BUF_LEN]; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); MX_ADC1_Init(); MX_TIM3_Init(); HAL_TIM_Base_Start(&htim3); HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adcBuffer, ADC_BUF_LEN); while (1) { // 数据通过DMA自动传输,主循环可处理其他任务 } }4.2 VOFA+数据可视化配置
- 串口参数:115200bps, 8N1
- 协议选择"Float"模式
- 添加波形显示控件
- 设置X轴时间范围为4ms(显示4个周期)
# VOFA+数据格式示例(Python模拟) import serial import math import time ser = serial.Serial('COM3', 115200) for i in range(1000): t = i * 0.00125 # 800Hz采样率 phase = (t % 0.001) / 0.001 * 2 * math.pi # 1kHz信号相位 value = math.sin(phase) * 1000 + 2048 # 模拟ADC值 ser.write(struct.pack('<f', float(value))) time.sleep(0.00125)4.3 常见问题排查
无数据输出:
- 检查DMA是否启用
- 验证TIM是否触发ADC
- 确认串口连接正确
波形失真:
- 检查信号发生器输出是否纯净
- 验证采样时钟精度
- 调整VOFA+显示参数
数据跳变:
- 确保ADC参考电压稳定
- 检查电路接地
- 适当添加硬件滤波
5. 工程优化与扩展应用
5.1 性能优化建议
内存优化:
- 合理设置DMA缓冲区大小
- 使用双缓冲技术减少处理延迟
精度提升:
- 添加硬件抗混叠滤波器
- 使用ADC过采样技术提高分辨率
- 校准ADC参考电压
实时性改进:
- 利用ADC中断处理关键数据
- 优化DMA传输策略
5.2 扩展应用场景
- 电力监测:工频信号分析
- 音频处理:特定频率成分提取
- 无线通信:窄带信号解调
- 传感器接口:谐振式传感器读取
5.3 进阶实验建议
- 尝试捕获更高频率信号(如10kHz)
- 实现多通道交替采样
- 添加数字滤波算法改善信号质量
- 探索等效采样率与信号频率的关系
在实际项目中,我发现TIM触发ADC的同步精度对欠采样效果影响极大。当需要捕获更高频率信号时,可以考虑使用硬件触发信号替代定时器,或者使用STM32的更高级型号(如F4系列)提供更高精度的时钟源。
