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

手把手教你用STM32F103ZET6的ADC+TIM+DMA三件套,做个能测频率的简易示波器

手把手教你用STM32F103ZET6的ADC+TIM+DMA三件套实现高精度频率测量

在嵌入式开发中,信号采集与分析是调试和验证的重要环节。本文将带你深入探索如何利用STM32F103ZET6的三大核心外设——ADC、TIM和DMA,构建一个既能采集波形又能精确测量频率的实用工具。不同于市面上昂贵的专业设备,这个方案成本低廉但功能强大,特别适合项目调试和学习研究。

1. 硬件架构设计思路

1.1 系统组成框图

整个系统由三个关键模块构成:

  • 信号输入调理电路:负责将外部信号调整到MCU可处理的电压范围(0-3.3V)
  • STM32F103ZET6核心板:搭载ARM Cortex-M3内核,主频72MHz
  • TFTLCD显示模块:320×240分辨率,用于实时波形展示

关键参数对比表

模块主要功能性能指标
ADC模拟信号数字化12位精度,1μs转换时间
TIM精确采样控制16位计数器,最高72MHz
DMA数据高效传输7通道,支持循环模式

1.2 时钟树配置要点

正确的时钟配置是系统稳定运行的基础:

// 系统时钟初始化示例 void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; // 配置HSE振荡器 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; HAL_RCC_OscConfig(&RCC_OscInitStruct); // 配置时钟总线 RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2); }

注意:APB1总线最大频率为36MHz,配置时需确保不超过此限制

2. ADC采样与DMA传输实现

2.1 多通道ADC配置

采用规则组连续采样模式,配合DMA实现无CPU干预的数据搬运:

// ADC初始化代码片段 void ADC1_Init(void) { ADC_ChannelConfTypeDef sConfig = {0}; hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE; hadc1.Init.ContinuousConvMode = ENABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 1; HAL_ADC_Init(&hadc1); // 配置采样通道 sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_71CYCLES_5; HAL_ADC_ConfigChannel(&hadc1, &sConfig); // 启动DMA传输 HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&adc_buffer, BUFFER_SIZE); }

2.2 双缓冲技术应用

为避免数据竞争,采用乒乓缓冲策略:

  1. 设置两个等大小的内存区域BufferA和BufferB
  2. DMA完成BufferA传输后触发中断,自动切换到BufferB
  3. 主程序处理BufferA数据时,DMA继续填充BufferB

内存管理关键点

  • 缓冲区大小应为2的整数次幂(如1024点)
  • 对齐到4字节边界提升DMA效率
  • 使用__attribute__((aligned(4)))确保内存对齐

3. 定时器精确触发机制

3.1 TIM主从模式配置

利用TIM2作为主定时器触发ADC采样:

void TIM2_Config(uint32_t freq) { TIM_HandleTypeDef htim2; uint32_t prescaler = (SystemCoreClock / 1000000) - 1; uint32_t period = (1000000 / freq) - 1; htim2.Instance = TIM2; htim2.Init.Prescaler = prescaler; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = period; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; HAL_TIM_Base_Init(&htim2); // 配置触发输出 TIM_MasterConfigTypeDef sMasterConfig = {0}; sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE; HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig); HAL_TIM_Base_Start(&htim2); }

3.2 动态频率调整算法

通过按键实时改变采样率:

void Adjust_Sample_Rate(uint8_t key) { static const uint32_t freq_table[] = { 100000, // 100kHz 50000, // 50kHz 20000, // 20kHz 10000, // 10kHz 5000 // 5kHz }; if(key < sizeof(freq_table)/sizeof(freq_table[0])) { uint32_t new_freq = freq_table[key]; TIM2->ARR = (SystemCoreClock / (TIM2->PSC + 1)) / new_freq - 1; } }

提示:采样率选择应遵循奈奎斯特准则,至少为信号最高频率的2倍

4. 频率测量核心算法

4.1 过零检测法实现

适用于正弦波等周期性信号:

# 伪代码描述算法流程 def zero_cross_detect(samples): zero_crossings = [] for i in range(1, len(samples)): if (samples[i-1] < mid_value and samples[i] >= mid_value): zero_crossings.append(i) if len(zero_crossings) < 2: return 0 avg_period = (zero_crossings[-1] - zero_crossings[0]) / (len(zero_crossings)-1) return sample_rate / avg_period

4.2 FFT频谱分析优化

针对STM32优化的实数FFT实现:

void FFT_Analysis(float* input, float* output, uint16_t size) { arm_rfft_fast_instance_f32 fft_inst; arm_rfft_fast_init_f32(&fft_inst, size); // 执行FFT变换 arm_rfft_fast_f32(&fft_inst, input, output, 0); // 计算幅值 for(uint16_t i=0; i<size/2; i++) { float real = output[2*i]; float imag = output[2*i+1]; output[i] = sqrtf(real*real + imag*imag); } // 寻找峰值频率 uint16_t max_idx = 0; float max_val = 0; arm_max_f32(output, size/2, &max_val, &max_idx); float freq = (float)max_idx * sample_rate / size; }

窗函数选择建议

窗类型主瓣宽度旁瓣衰减适用场景
矩形窗13dB瞬态信号
汉宁窗中等31dB一般频谱分析
平顶窗44dB幅值精度要求高

5. 显示优化与用户体验

5.1 动态波形绘制技巧

采用差异刷新策略提升显示流畅度:

  1. 记录前一帧波形所有点坐标
  2. 新帧绘制前,先用背景色擦除旧轨迹
  3. 仅更新变化超过阈值的像素区域
void Draw_Waveform(uint16_t* samples, uint16_t count) { static uint16_t prev_samples[MAX_SAMPLES]; uint16_t x, y_prev, y_now; for(uint16_t i=0; i<count; i++) { x = i * SCREEN_WIDTH / count; y_prev = SCREEN_HEIGHT - (prev_samples[i] * SCREEN_HEIGHT >> 12); y_now = SCREEN_HEIGHT - (samples[i] * SCREEN_HEIGHT >> 12); // 只重绘变化明显的点 if(abs(y_now - y_prev) > REDRAW_THRESHOLD) { LCD_DrawPixel(x, y_prev, BACKGROUND_COLOR); // 擦除旧点 LCD_DrawPixel(x, y_now, WAVEFORM_COLOR); // 绘制新点 } } memcpy(prev_samples, samples, count*sizeof(uint16_t)); }

5.2 界面元素布局方案

推荐界面分区

  • 顶部20%:显示测量参数(频率、Vpp等)
  • 中间60%:主波形显示区
  • 底部20%:控制按钮和状态指示

在STM32F103上实现时,发现直接使用LTDC控制器驱动LCD比FSMC接口快3倍,但需要额外的外部RAM作为帧缓冲区。经过实测,在320x240 16位色模式下,全屏刷新率可达45fps,完全满足波形显示需求。

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

相关文章:

  • SAP PP模块新手避坑指南:从CRC1到C223,手把手教你搞定流程制造主数据
  • 别再对着芯片型号发愁了!手把手教你用Realtek RTL8382L系列搞定千兆交换机主板选型
  • 为什么92%的AI工程师还在用2023版Docker AI Toolkit?2026新版动态资源编排器已淘汰手动cgroups绑定
  • 3.【Verilog】Verilog 门延迟
  • 2026年终极指南:3步快速上手BiliTools哔哩哔哩下载神器
  • ARM Cortex-A73 PMU架构与性能监控实战指南
  • ARM Cortex-M1 TCM架构解析与初始化实践
  • 别再折腾了!2024年最新TeXLive+TeXstudio保姆级安装配置指南(含中文路径避坑)
  • 北京环球度假区游记
  • 救砖实录:小米路由器R4A刷OpenWRT失败后,我是如何用官方工具救回来的
  • 别再手动K帧了!用GhostTrails插件5分钟搞定3DMAX粒子拖尾特效(附PFlow联动技巧)
  • Xinference-v1.17.1应用案例:快速部署,为你的项目添加AI能力
  • 不只是调参:在Carsim里给车道保持PID算法‘加戏’——聊聊传感器布局与预瞄点选择的门道
  • 别再到处找破解了!手把手教你合法获取Halcon试用License(附官方申请指南)
  • Spring Boot项目实战:手把手教你集成Google Authenticator实现两步验证(附完整代码)
  • Windows Cleaner:开源高效的Windows系统清理终极解决方案
  • 生成引擎优化(GEO)如何重塑内容创作与用户体验:从理论到实践的最佳指南
  • 终极内存故障排查指南:Memtest86+ 高效诊断方案
  • RWKV7-1.5B-G1A效果展示:多语言文本生成实测,效果惊艳
  • Open Live Writer 界面灰色、无法编辑
  • 从养猫到星际旅行:盘点那些藏在安卓系统设置里的隐藏小游戏(附触发教程)
  • MAXQ2000软堆栈实现原理与优化实践
  • web基础知识
  • 别再乱写application.yml了!Spring Boot多环境配置(dev/test/prod)保姆级实战指南
  • 别再买现成模块了!手把手教你用FT232RL-REEL芯片,从零设计一个USB转串口调试器(附完整原理图)
  • 从零构建大语言模型训练框架:BumbleCore的设计、实现与实战
  • 2026年3月管夹品牌推荐,支吊架/固定管托/保冷管托/弹簧支吊架/管道支吊架/聚氨酯管托,管夹批发厂家口碑推荐 - 品牌推荐师
  • Transformer模型量化实战:用Neural Compressor提升推理效率
  • 保姆级Wireshark抓包实战:从访问百度到看懂HTTP请求的完整流程
  • 我做了个毒舌版 MBTI 测试 iOS App,聊聊计分模型设计和多场景文案架构