news 2026/10/28 19:09:16

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【笔记】直流减速电机(GA25-370)调速

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

0. 速查表

#环节干什么关键量代码位置
1电机本体电压 → 转速e = Ke·ω,空载ω = Ua/Ke—
2H 桥换向(电压极性可正可负)IN1/IN2两根 GPIOApp_PWM_Set_L/R()
3PWM把固定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+Q40(绕组被短路)
停(滑行/高阻)全断悬空

注意 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左电机PWMAAF_PP(TIM1_CH1)PWM_L_Init()
PA9/PA10左电机AIN1 / AIN2Out_PPApp_PWM_Set_L()
PA1STBYOut_PPStbyPin_Init()
PB6右电机PWMBAF_PP(TIM4_CH1)PWM_R_Init()
PB5/PB7右电机BIN1 / BIN2Out_PPApp_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 设置占空比

voidApp_PWM_Set_L(floatDuty){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里没有"转速"这个量的任何反馈。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 19:07:19

问数平台为什么越用越准,FewShot 样本库那只看不见的手

把每一次人工纠正&#xff0c;变成系统下次能复用的经验。机制一样本沉淀每一次人工纠正&#xff0c;都会被记成一张样本卡存进库。比如模型把「带看转化佣金」按错口径算了&#xff0c;业务老师改回正确算法&#xff0c;这条问法到正确结果就落成一张卡。卡里不只存答案&#…

作者头像 李华
网站建设 2026/10/28 19:05:16

怎么在客户拜访前快速了解一家企业

一、商务精准化趋势下&#xff0c;拜访前调研的效率瓶颈当前企业商务合作、供应链对接、客户拓展都在向精细化转型&#xff0c;面对面拜访仍是建立信任、推进合作的核心环节&#xff0c;但拜访前的信息准备工作&#xff0c;普遍成为制约效率的瓶颈。无论是金融机构对公客户经理…

作者头像 李华
网站建设 2026/10/28 19:02:49

【C/C++ std::string 的 SSO:你以为省了分配,其实可能复制了更多字节】

🔥 个人主页: flos chen ❄️ 个人专栏: 《系统分析师》 《C/C++》 《Qt》 《Linux》 《SQL》 《深度学习》 🌟 边学习,边记录,一起学习进步! 文章目录 前言 1.1 问题:短字符串的堆分配浪费 1.2 解决方案:在对象内部预留缓冲区 2. 内存布局 2.1 l…

作者头像 李华
网站建设 2026/10/28 18:54:03

《AI Agent原理与实战》2026版 第25课 可信赖的 Agent:边界与审核

本文收录于专栏 《AI Agent原理与实战》2026版 —— 专栏系统覆盖 AI Agent 的记忆、工具、插件与实战&#xff0c;点击订阅可跟踪后续更新。 本课定位&#xff1a;系统能跑只是及格线&#xff0c;能被信任才能上线。本课讲三道防线&#xff1a;输出质量&#xff08;系统消息框…

作者头像 李华
网站建设 2026/10/28 18:51:06

MORUS算法的各种密码分析方法全面盘点

MORUS算法的各种密码分析方法全面盘点针对MORUS算法的密码分析&#xff0c;已有研究从多个角度进行了深入探索。这些分析主要针对全版本或简化轮数的MORUS&#xff0c;其结论对评估算法的安全边界有重要参考价值。以下是各项攻击方法的盘点。核心发现&#xff1a;全版本MORUS的…

作者头像 李华
网站建设 2026/10/28 18:50:16

生活 RAG 的评估表怎么写:先说清分母,再谈“准确率提升”

“准确率从 64.2% 提升到 94.8%”很醒目&#xff0c;但若只有 50 道题&#xff0c;每题只算对错&#xff0c;单次整体比例应以 2 个百分点为步长变化。小数不一定错&#xff0c;也可能来自多次平均或不同指标&#xff1b;问题是这些口径有没有写出来。 个人知识库的评估至少拆…

作者头像 李华