【笔记】直流减速电机(GA25-370)调速

一句话 :直流有刷电机的转速只由电枢端电压 决定,所以"调速"就是"调平均电压 "。 单片机没法直接调电压,于是用 PWM 斩波 + H 桥把电池的固定电压切成可调的平均电压。

0. 速查表

# 环节 干什么 关键量 代码位置
1 电机本体 电压 → 转速 e = Ke·ω,空载 ω = Ua/Ke ---
2 H 桥 换向(电压极性可正可负) IN1/IN2 两根 GPIO App_PWM_Set_L/R()
3 PWM 把固定 Vbat 切成可调平均电压 Ua = D·Vbat,72kHz、1000 级 TIM1/TIM4 初始化
4 占空比 期望电压 → 占空比 D = Va/Vbat×100,单位 % App_PWM_Set_L/R() 入参

信号链(自上而下):

复制代码
期望的平均电压 Ua (V)
   └─► [÷ 电池电压 Vbat] ──► Duty (%, ±100)
          └─► [两根方向 GPIO + PWM 占空比] ──► TB6612 内部 H 桥 ──► 电机 ──► 转速 ω

1. 调速到底在调什么

1.1 电机的电学方程

电机拆开看是"一半电路、一半机械",电学方程把电和转连起来(3.1 章)。

电学方程:

复制代码
Ua = Ra·Ia + La·(dIa/dt) + e        e = Ke·ω
符号 含义 符号 含义
Ua 加在电枢两端的电压 Ia 电枢电流
Ra 电枢电阻 La 电枢电感
e 反电动势(自己发出来顶住电源的电压) Ke 反电动势常数 V/(rad/s)
ω 转子角速度 Kt 扭矩常数 N·m/A

(F = B·L·I)就是这个链路的物理源头: 电压 → 电流 → 电磁力 → 转子转起来 → 转起来以后又产生反电动势 e = Ke·ω, 转速越高反电动势越大,把电流顶回去,最后停在某个平衡点上。这个"自平衡"就是转速由电压唯一确定的原因。

1.2 稳态:转速由端电压决定

电机稳速旋转时 dIa/dt → 0,电学方程退化成:

复制代码
Ua = Ra·Ia + Ke·ω

空载时 Ia 很小、Ra·Ia 这一项可以忽略,于是:

复制代码
ω ≈ Ua / Ke

也就是说,转速最终是 Ua 的函数 :电压越高、空载转速越大;电流越大、输出扭矩越大(这两条就是 3.1 章第一页的结论)。想改转速,就去改 Ua。

带载时电流增大、Ra·Ia 压降变大,转速会比空载低一截;而且负载一变电流就变、转速跟着变。这正是 1.3 节说的"机械特性变软"的由来。

1.3 为什么不能"串个电阻"调速

既然转速由电压决定,串电阻分压不就行了?不行,三个原因:

  1. 电阻上白烧功率 ,P = I²R 全变成热,电池续航和驱动板都扛不住;

  2. 机械特性变软:串了 R 以后,负载一变电流就变,电阻上的压降跟着变,转速跟着飘;

  3. 没法反向:串电阻只能降速,不能换向,而平衡车必须要能反转。

1.4 结论

调速 = 调加在电机上的平均电压。 实现手段:用开关管快速通断(PWM)切出可调的平均电压;用电桥(H 桥)切换极性实现正反转。

2. 怎么产生可调的平均电压:PWM + H 桥

2.1 PWM 调压:用"时间占比"代替"阻值"

管子只有"通"和"断"两个状态,通的时候管子内阻极小、几乎不发热。于是让它在一个周期 T 内导通 D·T 的时间:

复制代码
 Vbat ────────┐              ┌────────────┐
              │              │            │
    0         └──────────────┘            └────
              ←──── D·T ────→←─ (1-D)·T ──→
              ←───────────── T ─────────────→
 平均电压  Ua = D · Vbat        (D = 占空比,0 ~ 1)

Vbat+ / Vbat- 表示 H 桥给电机端加的是正接还是反接,所以 D 取正负号就能表达"正转多快 / 反转多快"。

2.2 H 桥:让电压能正能负

4 个开关排成"H"形,电机横在中间(3.1 章 slide 4/5):

复制代码
        Vbat+
          │
     ┌────┴────┐
     Q1        Q2
     │          │
     ├───[ M ]──┤        M = 电机
     │          │
     Q3        Q4
     └────┬────┘
          │
        Vbat-
想要 导通的管子 电机端电压
正转 Q1 + Q4 +Vbat
反转 Q2 + Q3 −Vbat
停(刹车/短路) Q1+Q2 或 Q3+Q4 0(绕组被短路)
停(滑行/高阻) 全断 悬空

注意 Q1 和 Q3 不能同时导通 (会把 Vbat 直接短路,叫"直通"), 所以真正的 H 桥驱动都需要死区时间。本工程用的是集成芯片,这部分由芯片内部处理。

2.3 本工程用的是 TB6612FNG

板上用的是 TB6612FNG : 芯片内部就是两路完整的 H 桥,还带欠压保护(UVLO)、过热保护(TSD)和待机引脚(STBY)。

芯片的输入引脚分成两组:

引脚 作用
PWMA / PWMB 该路的速度(PWM 占空比)
AIN1/AIN2、BIN1/BIN2 该路的方向(哪两个管子导通)
STBY 总开关。拉低 → 整片待机,输出全关
AO1/AO2、BO1/BO2 接电机的两根线

所以单片机只需要给:一路 PWM + 两根方向 GPIO + 一根 STBY,H 桥的四管时序和死区都不用管。

2.4 频率怎么选:电感 La 决定了电流纹波

PWM 加在电机上,电机不是纯电阻------La 会把电流"稳住",所以电流并不会像电压那样方波跳动。 用三张图讲这件事:

图 情况 电流长什么样
图 1 忽略 La 电流和电压同形状,方波
图 2 考虑 La,PWM 频率低 电流上冲下掉,纹波大,甚至断续(CCM → DCM)
图 3 考虑 La,PWM 频率高 电流被电感滤得很平,近似直流

时间常数 τe = La/Ra 是分界线:频率远高于 1/(2π·τe) 时电流才够平。 本工程电机 La ≈ 1.5mH, Ra ≈ 3.9Ω → τe ≈ 384µs → 转折频率约 400Hz。

本工程选的 PWM 频率:

复制代码
f = 72MHz / ((PSC+1) × (ARR+1)) = 72MHz / (1 × 1000) = 72kHz
  • 72kHz 比 400Hz 高两个数量级 → 电流纹波很小,近似"平滑直流"(图 3 那种);

  • 72kHz 远高于人耳听觉上限 20kHz → 没有啸叫(低于 20kHz 的 PWM 电机是会有"吱吱"声的);

  • 代价是开关损耗,但 TB6612 允许到 100kHz,72kHz 留有余量。

⚠️ 坑 :TIM4 挂在 APB1 上,APB1 只有 36MHz。但 STM32 的规则是"APB 分频系数 ≠ 1 时定时器时钟翻倍", 所以 TIM4 实际也是 72MHz。TIM1 挂 APB2(72MHz)也是 72MHz。两路 PWM 频率相同、不用分开算。

2.5 分辨率、死区与量纲

项 值 说明
计数范围 CNT: 0 ~ ARR = 999 共 1000 级
分辨率 1/1000 = 0.1% 电压分辨率 = 0.1% × Vbat(满电 8.2V 时约 8.2mV)
周期 1/72kHz = 13.89µs
最小非零占空比 CCR=1 → 0.1%

低占空比死区 :占空比小于某个值(几 %)时,平均电压太低,产生的电磁转矩 不足以克服静摩擦和 H 桥压降,电机根本不转。调参和标定"占空比 → 转速"曲线时, 这一段是要单独留意的(曲线不是从原点起的直线)。

3. 代码实现:app_pwm.c

3.1 引脚映射

引脚 接哪里 配置 代码
PA8 左电机 PWMA AF_PP(TIM1_CH1) PWM_L_Init()
PA9 / PA10 左电机 AIN1 / AIN2 Out_PP App_PWM_Set_L()
PA1 STBY Out_PP StbyPin_Init()
PB6 右电机 PWMB AF_PP(TIM4_CH1) PWM_R_Init()
PB5 / PB7 右电机 BIN1 / BIN2 Out_PP App_PWM_Set_R()

3.2 初始化:6 步

以左电机(PWM_L_Init())为例,右电机完全对称,只是 TIM1 → TIM4、APB2 → APB1:

复制代码
// #1. 初始化IO引脚:PA9、PA10 普通推挽输出(方向)
// #2. 使能定时器1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
// #3. 配置定时器的基本参数
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;  // 向上计数
TIM_TimeBaseInitStruct.TIM_Period = PERIOD;      // ARR = 999  ← 决定频率和分辨率
TIM_TimeBaseInitStruct.TIM_Prescaler = 0;        // PSC = 0    ← 不分频
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStruct);
// #4. 配置输出比较:PWM1 模式,高电平有效
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStruct.TIM_OutputState = ENABLE;
TIM_OCInitStruct.TIM_Pulse = 0;                  // 初始 CCR = 0 → 0%
TIM_OC1Init(TIM1, &TIM_OCInitStruct);
// 然后 PA8 配成 AF_PP(复用推挽),作为定时器输出脚
// #5. 开启CCR的预加载   ← ⚠️ 见 3.5 节的坑
TIM_CCPreloadControl(TIM1, ENABLE);
// #6. 闭合总开关
TIM_Cmd(TIM1, ENABLE);
TIM_CtrlPWMOutputs(TIM1, ENABLE);   // 高级定时器必须开 MOE,否则没有输出

PWM1 模式(3.2 章 slide 11 那张模式表)的含义:

复制代码
CNT < CCR  →  OCxRef = 高
CNT ≥ CCR  →  OCxRef = 低

所以 CCR 就是高电平持续的计数个数,占空比 D = CCR/1000。

3.3 设置占空比

c 复制代码
void App_PWM_Set_L(float Duty)
{
    if(Duty >  100) Duty = 100;          // ① 限幅
    if(Duty < -100) Duty = -100;
    if(Duty >= 0)                        // ② 方向:两根 GPIO
    {
        GPIO_WriteBit(GPIOA, GPIO_Pin_10, Bit_SET);
        GPIO_WriteBit(GPIOA, GPIO_Pin_9,  Bit_RESET);
    }
    else
    {
        GPIO_WriteBit(GPIOA, GPIO_Pin_10, Bit_RESET);
        GPIO_WriteBit(GPIOA, GPIO_Pin_9,  Bit_SET);
    }
    // ③ 大小:百分比 → 计数值
    TIM_SetCompare1(TIM1, (uint16_t)(fabsf(Duty) / 100.0f * (PERIOD + 1)));
}

三个要点:

  1. 符号拆成两件事:符号 → 方向 IO,绝对值 → 占空比。芯片替你完成"哪两个管子导通"。

  2. PERIOD + 1 而不是 PERIOD:Duty = 100 时 CCR = 1000 > ARR = 999,

保证 CNT < CCR 恒成立 → 输出恒高 、真正 100%。若写成 ×999,CCR = 999 时 CNT = 999 那一拍会掉低,实际只有 99.9%。

  1. 限幅放在函数内部:入参单位是百分比,调用方把"想给的电压"换算成占空比时要除以电池电压。

电池电压掉下来时(比如想给 8.2V 而电池只剩 6.6V),算出来的占空比会到 124%, 必须在这里兜住------限幅的责任在 PWM 这一层,不在调用方。

3.4 开环实验

**① 最原始的验证,直接给固定占空比:

复制代码
App_PWM_Init();
App_PWM_Cmd(1);          // 拉高 STBY,唤醒
App_PWM_Set_L(30);  App_PWM_Set_R(30);   Delay(2000);  // 30% 转 2 秒
App_PWM_Set_L(60);  App_PWM_Set_R(60);   Delay(2000);  // 60% 转 2 秒
App_PWM_Set_L(90);  App_PWM_Set_R(90);   Delay(2000);  // 90% 转 2 秒
App_PWM_Cmd(0);          // 拉低 STBY,休眠

现象:占空比越大,轮子转得越快 ------ 这就是"调平均电压"的直观验证。

②改成了"给固定电压**":

复制代码
// 正转3s:给 3V
App_PWM_Set_L( 3 / bat * 100.0f);   // ← 占空比 = 3V / 电池电压
App_PWM_Set_R( 3 / bat * 100.0f);
// 反转3s:给 -3V(只是把符号取反)
App_PWM_Set_L(-3 / bat * 100.0f);

这一步演进很关键 :从"给固定占空比"变成"给固定电压"。 电池从 8.2V 掉到 6.6V,同样 80% 的占空比电压就从 6.56V 掉到 5.28V; 改成 3/bat*100 之后,无论电池多少伏,电机端都是 3V。 这就是 Ua = D·Vbat 这个式子的直接应用 :把想给的 Ua 反解成 D。

两个开环实验的共同结论:转速能调,但调不准 。 换一块电池、或者用手捏一下轮子,同样的占空比转速就变了------因为开环的 D 里没有"转速"这个量的任何反馈。

相关推荐
国科安芯1 小时前
从点火到入轨的毫秒尺度:商业运载火箭飞行时序控制中的抗辐射MCU可靠性边界—— 面向短时高可靠任务的单粒子效应证据与架构分析
网络·人工智能·单片机·嵌入式硬件·架构·火箭·抗辐射
吠品1 小时前
78B 只激活 3.46B,Kolibri 给模型选型上了一课
人工智能·单片机·嵌入式硬件
路上有只喵1 小时前
认识元器件 · 单片机(MCU/MPU/SOC) · 规格书解读1
单片机·嵌入式硬件
2501_922639581 小时前
率能半导体 SS6810H双通道H桥驱动芯片:32V/1A,覆盖POS打印机、安防相机、办公自动化、舞台灯光四大应用场景
单片机·嵌入式硬件·机器人·硬件工程
1wpc1 小时前
BCIKit M1 脑电开发板使用教程(一):认识采集板与首次上电
单片机·嵌入式硬件·硬件工程
海绵宝宝的月光宝盒1 小时前
9-AutoCAD-移动、旋转、复制命令应用与案例实战
经验分享·笔记·职场和发展·课程设计·制造·学习方法·cad
LG.YDX1 小时前
基于中心的3D目标检测和探索追踪-笔记
笔记·算法·目标检测
dtq04242 小时前
嵌入式5 - GPIO工作模式2
单片机·嵌入式硬件·学习
叼烟扛炮2 小时前
Linux 学习笔记(三十四):线程同步——条件变量与生产消费者模型
linux·运维·服务器·笔记·学习·1024程序员节