STM32F103C8T6实战:ADC采样与OLED实时波形可视化系统
2026/5/14 17:39:19 网站建设 项目流程

1. 项目背景与硬件选型

第一次用STM32做信号采集时,我盯着示波器上跳动的波形突发奇想:能不能把这些数据实时显示在OLED上?于是就有了这个项目。STM32F103C8T6作为性价比之王,内置12位ADC和丰富的外设接口,配合0.96寸OLED就能搭建微型示波器系统。实测下来,这套方案成本不到30元,却可以稳定采集0-3.3V的模拟信号。

选型时要注意几个关键点:

  • OLED尺寸:0.96寸(128x64)最常用,SSD1306驱动芯片兼容性好
  • ADC通道:STM32F103C8T6有10个通道,PA0-PA7对应ADC1_IN0-IN7
  • 通信方式:4线SPI比I2C刷新更快,但I2C接线更简单(推荐新手使用)

我最初用I2C连接OLED时遇到过屏幕闪烁问题,后来发现是上拉电阻没接好。建议直接购买带4.7kΩ上拉电阻的模块,省去很多麻烦。

2. 硬件连接与配置

2.1 引脚连接图

实际接线时最容易出错的是I2C引脚。STM32的I2C1默认对应PB6(SCL)和PB7(SDA),但不同开发板可能有差异。我的接线方案如下:

设备STM32引脚说明
OLED SCLPB6需接4.7kΩ上拉电阻
OLED SDAPB7需接4.7kΩ上拉电阻
信号输入PA1ADC1通道1
OLED VCC3.3V严禁接5V!

注意:OLED屏幕供电一定要接3.3V,接5V会烧毁屏幕。我就因此损失过两块OLED。

2.2 ADC配置技巧

在CubeMX中配置ADC时,建议选择"Continuous Conversion Mode"和"DMA Continuous Requests"。这样配置后,ADC会自动连续采样,通过DMA将数据存入内存,完全不占用CPU资源。关键参数设置:

hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE; hadc1.Init.ContinuousConvMode = ENABLE; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 1;

采样时间建议设置为239.5周期(对应ADC_SAMPLETIME_239CYCLES_5),这是精度和速度的最佳平衡点。实测在72MHz主频下,单次采样仅需4us。

3. 软件实现详解

3.1 OLED驱动移植

网上开源的SSD1306驱动很多,但大部分都臃肿难用。我优化过的版本仅需以下关键函数:

void OLED_Init(void); // 初始化 void OLED_Clear(void); // 清屏 void OLED_ShowWaveform(uint8_t *data, uint8_t len); // 绘制波形

波形显示的核心是建立显示缓冲区。我定义了一个128字节的数组对应屏幕X轴,每个元素值代表Y轴坐标。通过DMA获取ADC值后,先进行归一化处理:

// 将0-4095的ADC值转换为0-63的屏幕坐标 uint8_t normalize_adc(uint16_t raw) { return (raw * 63) / 4095; }

3.2 波形绘制算法

直接绘制原始数据会产生锯齿状波形。我采用了三点滑动平均滤波算法:

for(int i=1; i<127; i++) { display_buf[i] = (adc_buf[i-1] + adc_buf[i] + adc_buf[i+1]) / 3; }

更高级的做法可以加入峰值检测。下面是我常用的简易峰值标记代码:

if((adc_buf[i] > adc_buf[i-1]) && (adc_buf[i] > adc_buf[i+1])) { OLED_DrawPixel(i, 0); // 在屏幕顶部标记峰值 }

4. 性能优化实战

4.1 实时性提升技巧

最初我的波形刷新率只有15fps,经过三项优化后提升到45fps:

  1. 将I2C时钟从100kHz提升到400kHz
  2. 使用DMA传输ADC数据
  3. 只刷新波形区域而非全屏(OLED_UpdateArea函数)

关键配置代码:

hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 400000; // 400kHz I2C hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;

4.2 抗干扰处理

在工业现场测试时,发现信号有严重毛刺。通过软件滤波+硬件改造解决了问题:

  • 软件:增加中值滤波算法
  • 硬件:在ADC输入引脚加0.1uF电容

中值滤波实现示例:

uint16_t median_filter(uint16_t new_val) { static uint16_t buf[5] = {0}; static uint8_t idx = 0; buf[idx++] = new_val; if(idx >=5) idx=0; // 排序取中值(省略排序代码) return buf[2]; }

5. 扩展应用案例

这套系统我已经成功用在三个实际项目中:

  1. 环境监测站:采集温湿度传感器信号
  2. 简易心电图:通过AD8232模块采集心电信号(需前置放大)
  3. 音频分析仪:接驻极体麦克风显示声波波形

以音频应用为例,关键改造点是增加预加重电路:

MIC → 10kΩ电阻 → 100nF电容 → PA1 ↑ 3.3V

这个电路可以将麦克风输出信号提升到ADC可检测的范围。实际测试时发现,直接采集音频信号会有直流偏置,后来在代码中增加了去除DC分量的处理:

int16_t ac_couple(uint16_t raw) { static uint16_t dc_offset = 2048; dc_offset = (dc_offset * 31 + raw) / 32; // 低通滤波 return (int16_t)(raw - dc_offset) + 2048; }

6. 常见问题解决

调试过程中最常遇到的三个坑:

  1. 屏幕花屏:检查I2C地址是否正确(通常0x78或0x7A)
  2. 波形跳动:降低采样率或增加软件滤波
  3. ADC值不准:确保VDDA和VREF+接3.3V

有个特别隐蔽的bug我花了三天才解决:当采样率超过50kHz时,ADC值会出现周期性波动。最后发现是开发板上的滤波电容缺失,飞线接了个10uF电容后立即稳定。建议大家在PCB设计时就在VDDA和VSSA之间预留电容位置。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询