STM32双舵机控制系统与OLED计数功能的设计与实现(记录所遇到的问题与修正)
一、项目背景与目标
1.1 项目概述
本项目基于STM32F103C8T6微控制器,设计并实现一个双舵机控制系统。系统通过按键触发,控制两个舵机依次执行"0°→180°→0°"的往返动作,并在OLED显示屏上实时记录和显示动作执行的累计次数。
1.2 技术指标
| 项目 | 规格 |
|---|---|
| 主控芯片 | STM32F103C8T6 |
| 控制对象 | 2个SG90标准舵机(0°~180°) |
| 控制方式 | PWM脉宽调制(50Hz) |
| 显示模块 | 0.96寸OLED(I2C接口) |
| 输入方式 | 独立按键(PB0) |
| 计数范围 | 0~255 |
| 供电方式 | STM32(USB)/ 舵机(独立5V/2A) |
1.3 功能要求
-
按下按键PB0一次
-
舵机1执行:0°→180°→0°(往返一次)
-
舵机2执行:0°→180°→0°(往返一次)
-
OLED屏幕显示累计动作次数+1
-
等待下一次按键触发
1.4 系统框图
text
┌──────────────────────────────────────┐
│ STM32F103C8T6 │
│ │
┌──────────┐ │ ┌─────────────┐ │
│ 按键 │────│────│ GPIO输入 │ │
│ (PB0) │ │ │ (上拉输入) │ │
└──────────┘ │ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ ┌──────────┐ │ ┌─────────┐
│ │ 主控逻辑 │──── │ TIM2_CH1 │────│──│ 舵机1 │
│ │ (main.c) │ │ (PA0) │ │ │ (PA0) │
│ └─────────────┘ └──────────┘ │ └─────────┘
│ │ │
│ ▼ │
│ ┌─────────────┐ ┌──────────┐ │ ┌─────────┐
│ │ OLED显示 │ │ TIM3_CH1 │────│── │ 舵机2 │
│ │ (I2C) │ │ (PA6) │ │ │ (PA6) │
│ └─────────────┘ └──────────┘ │ └─────────┘
│ │
└─────────────────────────────────────┘
二、硬件设计
2.1 器件选型
| 器件 | 型号 | 数量 | 说明 |
|---|---|---|---|
| 主控芯片 | STM32F103C8T6 | 1 | ARM Cortex-M3,72MHz |
| 舵机 | SG90(标准版) | 2 | 0°~180°角度控制 |
| OLED | 0.96寸 SSD1306 | 1 | I2C接口,128×64 |
| 按键 | 轻触开关 | 1 | 6×6×5mm |
| 电源 | 5V/2A适配器 | 1 | 舵机独立供电 |
| 杜邦线 | 公对母/母对母 | 若干 | 连接线 |
2.2 引脚分配表
| 外设 | 功能引脚 | STM32引脚 | 片上资源 | 说明 |
|---|---|---|---|---|
| 舵机1 | 信号线 | PA0 | TIM2_CH1 | 独立定时器通道1 |
| 舵机2 | 信号线 | PA6 | TIM3_CH1 | 独立定时器通道1 |
| OLED | SCL | PB8 | I2C1_SCL | I2C时钟线 |
| OLED | SDA | PB9 | I2C1_SDA | I2C数据线 |
| 按键 | 信号 | PB0 | GPIO输入 | 内部上拉 |
2.3 舵机工作原理
2.3.1 控制信号规范
舵机通过PWM(脉冲宽度调制)信号控制,周期固定为20ms(50Hz):
text
┌─────────────────────────────────────────────────────────┐ │ 20ms (50Hz) │ │ ┌──────┐ │ │ │ 脉宽 │ │ │ └──────┘ │ │ ←0.5~2.5ms→ │ └─────────────────────────────────────────────────────────┘
2.3.2 角度与脉宽对应关系
| 角度 | 脉宽 | 计数值(1MHz时钟) |
|---|---|---|
| 0° | 0.5ms | 500 |
| 45° | 1.0ms | 1000 |
| 90° | 1.5ms | 1500 |
| 135° | 2.0ms | 2000 |
| 180° | 2.5ms | 2500 |
2.3.3 计算公式
脉宽(μs) = (角度 / 180) × 2000 + 500
2.4 硬件连接图
┌─────────────────────────────────────┐
│ STM32F103C8T6 │
│ │
舵机1 ── 黄色 ──│ PA0 (TIM2_CH1) │
舵机1 ── 红色 ──│ 5V (独立电源) │
舵机1 ── 棕色 ──│ GND (共地) │
│ │
舵机2 ── 黄色 ──│ PA6 (TIM3_CH1) │
舵机2 ── 红色 ──│ 5V (独立电源) │
舵机2 ── 棕色 ──│ GND (共地) │
│ │
OLED ── VCC ──│ 3.3V │
OLED ── GND ──│ GND │
OLED ── SCL ──│ PB8 │
OLED ── SDA ──│ PB9 │
│ │
按键 ── 一端 ──│ PB0 │
按键 ── 另一端 ──│ GND │
└─────────────────────────────────────┘
2.5 舵机选型注意事项
标准舵机 vs 360°舵机对比
| 对比项 | 标准舵机(SG90) | 360°舵机(SG90-360) |
|---|---|---|
| 控制方式 | 角度控制 | 速度控制 |
| 旋转范围 | 0°~180°(有限位) | 无限制(连续旋转) |
| 停止条件 | 到达目标角度自动停止 | 只有PWM=1.5ms时停止 |
| 位置反馈 | 有(内部电位器) | 无(电位器固定) |
| 适用场景 | 机械臂、云台、转向 | 车轮、传送带 |
本项目必须使用标准舵机!(其实是主播连接完之后发现自己手边的sg90都是360°的,随着烧录开始疯狂的旋转也是非常之崩溃,所以没有特殊需要大家就不要买升级改装过的sg90了。。。
三、软件设计
这里主播是使用了b站江科协老师免费分享的标准库,相对基础薄弱的同学来说hal库的配置可能会相对麻烦一点,所以主播在进行一系列的CubeMX的使用之后选择了在老师的源代码的基础上进行改装!!Or2
3.1 开发环境
| 项目 | 版本/工具 |
|---|---|
| IDE | Keil MDK V5 |
| 固件库 | STM32F10x_StdPeriph_Lib V3.5.0 |
| 调试器 | ST-Link V2 |
| 编程语言 | C语言 |
3.2 工程结构

3.3 核心代码详解
3.3.1 PWM驱动(PWM.c)
设计思路:
-
使用两个独立定时器避免通道冲突
-
TIM2_CH1(PA0)控制舵机1
-
TIM3_CH1(PA6)控制舵机2
-
每个定时器独立配置,互不干扰
3.3.2 舵机驱动(Servo.c)
3.3.3 按键驱动(Key.c)

3.3.4 主程序(main.c)

3.4 程序流程图
┌─────────────────────────────────────┐
│ 系统启动 │
└───────────────┬─────────────────────┘
▼
┌─────────────────────────────────────┐
│ 初始化外设 │
│ OLED_Init() / Key_Init() │
│ Servo_Init() │
└───────────────┬─────────────────────┘
▼
┌─────────────────────────────────────┐
│ 舵机复位到0° │
│ Servo1_SetAngle(0) │
│ Servo2_SetAngle(0) │
└───────────────┬─────────────────────┘
▼
┌─────────────────────────────────────┐
│ 显示初始界面 │
│ "Dual Servo" │
│ "Count: 000" │
│ "Press PB0 to Start" │
└───────────────┬─────────────────────┘
▼
┌───────────────┐
│ 按键按下? │
│ Key_GetNum() │
└───────┬───────┘
│ 是
▼
┌─────────────────────────────────────┐
│ 显示"Running..." │
└───────────────┬─────────────────────┘
▼
┌─────────────────────────────────────┐
│ 舵机1: 0° → 180° → 0° │
│ 每个动作间隔1000ms │
└───────────────┬─────────────────────┘
▼
┌─────────────────────────────────────┐
│ 舵机2: 0° → 180° → 0° │
│ 每个动作间隔1000ms │
└───────────────┬─────────────────────┘
▼
┌─────────────────────────────────────┐
│ Count++ │
│ OLED显示更新后的计数值 │
└───────────────┬─────────────────────┘
▼
┌─────────────────────────────────────┐
│ 显示"Press PB0 to Start" │
│ 等待下次按键 │
└─────────────────────────────────────┘
四、调试过程与问题解决
4.1 问题一:舵机疯狂旋转不停止
现象描述
舵机接入后持续高速旋转,完全不听指令,无法停在指定角度。
初步排查
-
检查PWM频率 → 50Hz正常 ✅
-
检查GPIO配置 → 复用推挽正常 ✅
-
检查定时器配置 → 周期20ms正常 ✅
根本原因
检查舵机型号发现——购买的是 "SG90 360度连续旋转舵机"。(我真的要哭了,等我有💴了一定要统一硬件界的度量衡!!!
问题分析
两种舵机的本质区别:
| 舵机类型 | 控制方式 | 停止条件 |
|---|---|---|
| 标准舵机(0°~180°) | 角度控制 | 到达目标角度自动停止 |
| 360°舵机 | 速度控制 | 只有PWM=1.5ms时才停止 |
解决方案
更换为标准SG90舵机(0°~180°版本),购买时确认没有"360°"或"连续旋转"字样。
经验教训
舵机外观相似,但内部结构完全不同。购买前必须确认型号,我的改装版和普通版都没有信息被标出来,建议
4.2 问题二:单定时器双通道异常
现象描述
使用TIM2的CH1(PA0)和CH2(PA1)时:
-
PA0:舵机不转动 ❌
-
PA1:舵机疯狂旋转 ❌
问题分析
-
两个通道共用同一个定时器,可能存在通道冲突
-
初始化顺序可能导致通道配置相互覆盖
-
映射关系容易搞混
解决方案
改用两个完全独立的定时器:
| 舵机 | 定时器 | 通道 | 引脚 |
|---|---|---|---|
| 舵机1 | TIM2 | CH1 | PA0 |
| 舵机2 | TIM3 | CH1 | PA6 |
方案优势
-
✅ 完全独立,互不干扰
-
✅ 避免通道映射混乱
-
✅ 代码清晰,易于调试
-
✅ 可靠性更高
4.3 问题三:舵机供电不足
现象描述
舵机动作时出现抖动、卡顿,偶尔复位重启。
根本原因
STM32的USB供电(5V/500mA)无法驱动两个舵机。舵机瞬间启动电流可达1A以上。
解决方案
采用独立5V/2A电源供电:
text
┌──────────┐ ┌─────────────┐
│ 5V/2A │─────▶│ 舵机VCC │
│ 电源 │ │ │
│ GND │─────▶│ 舵机GND │
└──────────┘ │ │
│ │ STM32 GND │
└───────▶│ (共地) │
└─────────────┘
4.4 问题四:OLED显示异常
现象描述
OLED无显示或显示乱码。
原因分析
-
I2C时序不匹配
-
上电初始化延时不足
-
供电不稳定
解决方案
-
确认I2C地址为0x78
-
增加上电延时(100ms以上)
-
按照SSD1306标准初始化序列配置
4.5 问题五:按键误触发
现象描述
未按下按键时,系统偶尔自动触发。
根本原因
GPIO浮空输入,电平不稳定。
解决方案
c
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 内部上拉
同时增加软件消抖(20ms延时)。
五、实验结果
5.1 硬件连接展示


5.2 系统性能
| 参数 | 指标 | |
|---|---|---|
| PWM频率 | 50Hz(精度±0.1Hz) | |
| 角度精度 | ±1° | |
| 舵机响应时间 | < 200ms | |
| 单次动作周期 | 约2.4秒 | |
| 计数范围 | 0~255 | |
| 系统功耗 | < 10W(峰值) | |
| 动作间隔可调 | 100~1000ms |
六、技术要点总结
6.1 PWM控制要点
| 要点 | 说明 |
|---|---|
| 周期 | 20ms(50Hz),固定不变 |
| 脉宽范围 | 0.5ms~2.5ms |
| 角度计算公式 | pulse = angle/180 × 2000 + 500 |
| 定时器时钟 | 1MHz(72MHz/72) |
| 计数值范围 | 500~2500 |
6.2 双定时器方案优势
| 对比项 | 单定时器双通道 | 双定时器方案 |
|---|---|---|
| 通道冲突 | 可能存在 | 无 |
| 代码清晰度 | 较复杂 | 清晰 |
| 调试难度 | 较高 | 低 |
| 扩展性 | 受限于通道数 | 易于扩展 |
| 可靠性 | 中 | 高 |
6.3 舵机选型要点
购买舵机时注意事项:
├── 确认类型
│ ├── 标准舵机(0°~180°)→ 角度控制,本项目使用
│ └── 360°舵机 → 速度控制,本项目不能用
├── 确认参数
│ ├── 工作电压:4.8V~6V
│ ├── 扭矩:根据负载选择
│ └── 尺寸:9g/12g/20g等
└── 确认配件
├── 舵机臂(十字/一字/圆盘)
├── 螺丝
└── 杜邦线
七、踩坑经验总结
7.1 硬件坑
| 序号 | 坑点 | 正确做法 |
|---|---|---|
| 1 | 买了360°舵机当标准舵机用 | 购买时确认型号,看是否有"360°"(很多不会标的!) |
| 2 | 用USB供电驱动舵机 | 使用独立5V/2A以上电源 |
| 3 | 忘记共地连接 | 确保STM32与舵机GND相连 |
| 4 | 信号线接触不良 | 使用杜邦线并确保连接牢固 |
7.2 软件坑
| 序号 | 坑点 | 正确做法 |
|---|---|---|
| 1 | 单定时器通道冲突 | 使用独立定时器 |
| 2 | 引脚映射错误 | 核对数据手册确认复用功能 |
| 3 | 延迟时间不足 | 舵机动作至少500ms |
| 4 | 按键无上拉 | 配置内部上拉或外接上拉电阻 |
7.3 调试方法论
调试流程: 1. 先确认硬件(型号、供电、连接) 2. 再验证基本功能(固定PWM输出测试) 3. 分模块调试(PWM→舵机→按键→OLED) 4. 最后整合完整逻辑 5. 逐步添加功能,不要一次性集成
八、项目总结
8.1 项目成果
本次项目成功实现了:
-
✅ 基于STM32F103的双舵机精确角度控制
-
✅ 按键触发舵机往返动作(0°→180°→0°)
-
✅ OLED实时显示动作累计次数
-
✅ 系统稳定可靠,无异常旋转
8.2 关键技术决策
| 决策点 | 选择 | 原因 |
|---|---|---|
| 舵机类型 | 标准SG90 | 需要角度控制 |
| 控制方式 | 双定时器 | 避免通道冲突 |
| 供电方式 | 独立电源 | 保证驱动能力 |
| 显示方案 | I2C OLED | 接口简单,占用引脚少 |
8.3 心得体会
嵌入式开发中,硬件确认是第一关。本次项目中,一个"360°舵机"的误判导致了大量时间浪费在软件调试上,而实际上问题根源在硬件选型。
核心教训:
在嵌入式开发中,遇到异常现象时,应该先确认硬件是否选型正确,再检查软件逻辑。很多时候,问题不在代码,而在硬件本身。
8.4 改进方向
-
限位保护:软件限制角度范围(0°~180°),防止舵机过载
-
状态指示:增加LED指示系统运行状态
-
串口调试:通过USART输出调试信息
-
数据存储:增加EEPROM保存计数
-
多模式切换:支持单舵机/双舵机模式切换
-
速度控制:实现舵机缓动效果
十、附录
附录A:物料清单
| 序号 | 名称 | 型号 | 数量 | |
|---|---|---|---|---|
| 1 | STM32开发板 | STM32F103C8T6 | 1 | |
| 2 | 舵机 | SG90标准版 | 2 | |
| 3 | OLED | 0.96寸 I2C | 1 | |
| 4 | 按键 | 轻触开关 | 1 | |
| 5 | 电源 | 5V/2A | 1 | |
| 6 | 杜邦线 | 公对母/母对母 | 若干 |
附录B:代码获取
完整项目源码已上传github,
代码链接🔗
GitHub - guanli-1020/Dual-Servo-with-OLED-Counter: Dual Servo with OLED Counter · GitHub
更多推荐


所有评论(0)