从江科大笔记到独立项目:手把手教你用OLED+EXTI做个简易计数器(附避坑指南)
2026/5/6 10:09:32 网站建设 项目流程

STM32实战:从零搭建OLED计数器(附EXTI避坑指南)

1. 项目背景与核心思路

在嵌入式开发中,将基础外设模块组合实现完整功能是进阶的关键一步。本项目基于STM32F103C8T6核心板,通过EXTI外部中断捕获旋转编码器信号,配合OLED实时显示计数值,打造一个可应用于工业控制、智能家居等场景的实用计数器。

技术栈组合亮点

  • EXTI中断响应:μs级响应旋转编码器脉冲
  • GPIO输入检测:双通道正交信号方向判断
  • OLED驱动移植:0.96寸SSD1306屏幕的I2C驱动
  • 模块化编程:中断服务与主程序解耦设计
// 典型接线配置(基于STM32F103C8T6) #define ENCODER_A_PIN GPIO_Pin_0 // PB0 #define ENCODER_B_PIN GPIO_Pin_1 // PB1 #define OLED_SCL_PIN GPIO_Pin_6 // PB6 #define OLED_SDA_PIN GPIO_Pin_7 // PB7

2. 硬件设计与关键电路

2.1 旋转编码器接口电路

机械编码器需配置硬件消抖电路,典型设计如下:

元件参数作用
上拉电阻10KΩ保证默认高电平
滤波电容100nF消除触点抖动
限流电阻1KΩ保护GPIO输入引脚

注意:编码器A/B相应接GPIO需支持外部中断功能,且Pin编号不同(如PB0/PB1)

2.2 OLED显示模块驱动

SSD1306屏幕的I2C硬件连接方案:

# I2C引脚配置(软件模拟实现) PB6 -> OLED_SCL PB7 -> OLED_SDA VCC -> 3.3V GND -> GND

初始化时序要点

  1. 上电后延迟100ms等待屏幕稳定
  2. 发送初始化命令序列(含对比度、扫描方向等)
  3. 清屏并设置初始光标位置

3. 软件架构设计

3.1 中断服务程序实现

// 编码器A相中断服务函数 void EXTI0_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line0) == SET) { // B相为低电平时计数递减 if(GPIO_ReadInputDataBit(GPIOB, ENCODER_B_PIN) == 0) { encoder_count--; } EXTI_ClearITPendingBit(EXTI_Line0); } } // 编码器B相中断服务函数 void EXTI1_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line1) == SET) { // A相为低电平时计数递增 if(GPIO_ReadInputDataBit(GPIOB, ENCODER_A_PIN) == 0) { encoder_count++; } EXTI_ClearITPendingBit(EXTI_Line1); } }

3.2 主程序流程

graph TD A[系统初始化] --> B[外设初始化] B --> C[OLED显示欢迎界面] C --> D{主循环} D --> E[读取编码器计数值] E --> F[OLED刷新显示] F --> D

4. 关键问题与解决方案

4.1 中断抖动问题

现象:机械编码器触发多次误中断
解决方案

  1. 硬件层面增加RC滤波(τ=1ms)
  2. 软件二次验证:
if(EXTI_GetITStatus(EXTI_Line0)) { Delay_us(500); // 延时后再次检测 if(GPIO_ReadInputDataBit(GPIOB, ENCODER_A_PIN)==0) { // 确认有效触发 } }

4.2 计数方向误判

正交信号解码真值表

A相边沿B相电平方向
下降沿正转
下降沿反转
上升沿反转
上升沿正转

提示:建议在中断内打印原始信号电平辅助调试

5. 性能优化技巧

  1. 中断响应优化

    • 将OLED刷新放在主循环,避免中断内执行耗时操作
    • 使用__attribute__((section(".fastcode")))将中断函数放在RAM执行
  2. 显示刷新策略

// 仅当计数值变化时刷新OLED static int16_t last_count = 0; if(encoder_count != last_count) { OLED_ShowNum(2, 5, encoder_count, 6); last_count = encoder_count; }
  1. 电源管理
// 无操作时进入低功耗模式 if(no_operation_timeout++ > 10000) { PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); }

6. 扩展功能实现

多功能计数器模式

typedef enum { MODE_COUNT_UP, MODE_COUNT_DOWN, MODE_PULSE_WIDTH } CounterMode; void handle_encoder_press() { // 长按切换模式 if(KEY_Pressed(300)) { current_mode = (current_mode + 1) % 3; OLED_Clear(); show_mode_indicator(); } }

典型应用场景接线示例

  1. 流水线产品计数:编码器安装在传送带滚轴
  2. 智能旋钮控制:编码器+OLED组成人机界面
  3. 电机转速测量:配合光电传感器实现非接触测速

7. 调试与验证方法

  1. 逻辑分析仪抓取信号

    • 同时捕获A/B相波形和中断触发时刻
    • 验证正交信号相位差是否为90°
  2. STM32CubeMonitor实时监测

# 通过SWD接口输出变量值 printf("Count: %d\tA: %d\tB: %d\r\n", count, GPIO_ReadInputDataBit(GPIOB, ENCODER_A_PIN), GPIO_ReadInputDataBit(GPIOB, ENCODER_B_PIN));
  1. OLED诊断界面
[DEBUG MODE] CNT: 0256 A: H B: L INT: EXTI0

通过模块化设计和中断优化,这个计数器项目在测试中实现了±1的计数精度,响应延迟小于50μs。实际部署时建议增加EEPROM存储功能,断电后可保存当前计数值。

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

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

立即咨询