Uploaded by tang zhe

硬石电机控制专题指导手册(完整版) 20200602

advertisement
STM32 技术开发手册
www.ing10bbs.com
硬石电机控制专题指导手册
STM32 高级应用系列教程
技术论坛 :www.ing10bbs.com
电
话:020-29814159
QQ:2536843366
QQ 交流群:515110016(硬石电子交流群)
旺
旺:硬石电子
STM32 技术开发手册
www.ing10bbs.com
版本历史
版本
发布时间
修改内容
作者
V1.0
2016-06-20
新建文件
硬石
V1.1
2016-07-02
添加章节内容,更新至第 9 章
硬石
V1.2
2016-12-22
添加章节内容,更新至第 12 章
硬石
V1.3
2017-05-10
步进电机改为共阳接线模式
硬石
V1.4
2017-06-23
添加 FOC 电机库代码分析内容
硬石
V1.5
2017-09-05
完成 FOC 电机库代码分析
硬石
V2.0
2017-11-10
无刷驱动板使用新版本电路,文档资料也同步更新 硬石
V2.1
2017-11-28
添加 YS-F4Pro 开发板使用 FOC4.3 电机库
硬石
V2.2
2017-12-23
添加 YS-F1Pro 开发板使用 FOC4.3 电机库
硬石
V3.0
2018-04-29
添加 YS-F4Pro 开发板使用 FOC5.0 电机库
硬石
V3.1
2019-02-26
添加 FOC5.2 电机库部分源码分析,开始持续更新。 硬石
V3.2
2019-07-18
添加 FOC5.2 电机库部分源码分析,持续更新。
硬石
V3.3
2019-11-05
添加 FOC5.2 电机库部分源码分析,持续更新。
硬石
V3.4
2020-01-02
完成 FOC5.2 源码分析,添加算法逻辑分析。
硬石
V3.5
2020-03-07
添加 FOC5.2 编码器代码分析,添加 5.4.3 介绍。
硬石
V3.6
2020-06-02
添加 SPWM 原理分析和代码实现(第 13 章)
硬石
STM32 技术开发手册
www.ing10bbs.com
前言
电动机(Motor)是把电能转换成机械能的一种设备。它是利用通电线圈产
生旋转磁场并作用于转子形成磁电动力旋转扭矩。
众所周知,电机是传动以及控制系统中的重要组成部分,随着现代科学技术
的发展,电机在实际应用中的重点已经开始从过去简单的传动向复杂的控制转移;
尤其是对电机的速度、位置、转矩的精确控制。
STM32 技术开发手册
www.ing10bbs.com
关于本文档几点说明
1) 本文档参考大量文档而编写完成,部分内容可能直接引用网络文档,所
以在结构和语句组织上不是很严谨,我们力求平白化,把内容、原理讲
通就好。
所以本文档有部分资料说明来着网络,在此我们对原作者表示衷心的感谢,
当然如果有原作者认为我们不能引用您的著作内容,请务必联系我们,我们
会把相关内容修改。
2) 因本人能力有限,可能文档部分内容表述不完善,请各路高手务必指出,
我们会虚心求教,我们会根据情况赠送本店模块以表示我们的诚意。
最后,希望大家继续支持硬石科技!!!
3) 本文档所有例程是基于 HAL 库编写的,部分例程下载网址:
http://www.ing10bbs.com/forum.php?mod=viewthread&tid=1458&fromuid=4
STM32 技术开发手册
www.ing10bbs.com
目录
(一)
电机控制基础 ....................................................................... 11
第 1 章 电机分类和原理 ...................................................................................11
1.1 电机分类 ...................................................................................................... 11
1.1.1 伺服电机.......................................................................................... 12
1.1.2 步进电机.......................................................................................... 13
1.1.3 无刷直流电机.................................................................................. 15
1.1.4 舵机.................................................................................................. 16
1.2 三个基本定则 .............................................................................................. 17
1.3 直流电机工作原理 ...................................................................................... 20
1.3.1 构成:.............................................................................................. 21
1.3.2 工作过程.......................................................................................... 21
1.3.3 电动势与能量转换分析.................................................................. 22
第 2 章 TIM-基本定时器 ...................................................................................23
2.1 基本定时器简介 .......................................................................................... 23
2.2 基本定时器功能框图 .................................................................................. 24
2.3 STM32CubeMX 生成工程 ............................................................................ 26
2.4 TIM-基本定时器外设结构体 ...................................................................... 29
2.5 TIM6&TIM7 编程流程分析 ......................................................................... 31
2.6 TIM6&TIM7 基本定时代码实现 ................................................................. 31
第 3 章 TIM-高级控制定时器 ............................................................................35
3.1 高级控制定时器 .......................................................................................... 35
3.2 输入捕获模式 .............................................................................................. 43
3.3 PWM 输入模式............................................................................................ 44
3.4 强置输出模式 .............................................................................................. 46
3.5 输出比较模式 .............................................................................................. 46
3.6 PWM 模式.................................................................................................... 47
3.7 互补输出和死区插入 .................................................................................. 49
3.8 在外部事件时清除 OCxREF 信号 ............................................................... 49
3.9 产生六步 PWM 输出................................................................................... 50
3.10 单脉冲模式 .................................................................................................. 51
3.11 编码器接口模式 .......................................................................................... 51
3.12 与霍尔传感器的接口 .................................................................................. 53
3.13 TIMx 定时器和外部触发的同步................................................................. 54
3.14 STM32CubeMX 生成工程 ............................................................................ 56
3.15 高级控制定时器外设结构体 ...................................................................... 58
3.16 高级控制定时器生成 PWM 编程流程分析............................................... 60
3.17 高级控制定时器生成 PWM 代码实现....................................................... 60
(二)
电机驱动 .............................................................................. 64
第 4 章 有刷直流电机 .......................................................................................64
4.1 直流减速电机 .............................................................................................. 64
STM32 技术开发手册
www.ing10bbs.com
4.2 减速电机参数分析 ...................................................................................... 65
4.3 直流减速电机驱动设计 .............................................................................. 68
4.3.1 驱动电路设计基本原理.................................................................. 68
4.3.2 H 桥电路分析 .................................................................................. 70
4.3.3 电机驱动芯片选型.......................................................................... 72
4.4 L298N 电机驱动芯片 .................................................................................. 73
4.5 PWM—脉冲宽度调制 ................................................................................. 76
第 5 章 减速电机旋转控制实现 ........................................................................79
5.1 25GA370 直流减速电机 .............................................................................. 79
5.2 硬件连接 ...................................................................................................... 80
5.3 STM32CubeMX 生成工程 ............................................................................ 82
5.4 减速电机旋转驱动编程流程 ...................................................................... 86
5.5 减速电机旋转驱动代码分析 ...................................................................... 86
5.6 三轴直流减速电机旋转控制实验 .............................................................. 92
5.6.1 STM32CubeMX 生成工程 ............................................................... 92
5.6.2 三轴减速电机旋转控制代码分析.................................................. 94
第 6 章 编码器测速 ........................................................................................ 101
6.1 编码器 ........................................................................................................ 101
6.2 编码器分类及原理 .................................................................................... 102
6.3 增量式编码器脉冲输入模式 .................................................................... 105
6.4 25GA370 减速电机编码器 ........................................................................ 105
6.5 STM32CubeMX 生成工程 .......................................................................... 109
6.6 减速电机编码测速编程流程 .................................................................... 114
6.7 减速电机编码器测速实现代码分析 ........................................................ 114
第 7 章 舵机控制 ............................................................................................ 120
7.1 概述 ............................................................................................................ 120
7.2 舵机的组成 ................................................................................................ 121
7.3 舵机工作原理 ............................................................................................ 123
7.4 舵机选购 .................................................................................................... 124
7.5 舵机使用中应注意的事项 ........................................................................ 126
7.6 辉盛 SG90 和 MG996R 舵机简介 ............................................................. 126
7.7 硬件设计 .................................................................................................... 128
7.8 STM32CubeMX 生成工程 .......................................................................... 130
7.9 MG996R 舵机旋转控制实现 .................................................................... 132
7.9.1 MG996R 舵机旋转控制编程流程 ................................................ 132
7.9.2 MG996R 舵机旋转控制实现代码分析 ........................................ 132
7.10 四轴舵机旋转控制实现 ............................................................................ 135
7.10.1 STM32CubeMX 生成工程 ............................................................. 136
7.10.2 四轴舵机旋转控制实现代码分析................................................ 138
第 8 章 步进电机 ............................................................................................ 142
8.1 步进电机 .................................................................................................... 142
8.1.1 步进电机介绍................................................................................ 142
8.1.2 步进电机分类................................................................................ 143
8.1.3 步进电机技术指标........................................................................ 144
STM32 技术开发手册
www.ing10bbs.com
8.1.4 步进电机的极性............................................................................ 147
8.2 步进电机工作原理 .................................................................................... 147
8.2.1 单极性驱动的原理........................................................................ 148
8.2.2 双极性驱动的原理........................................................................ 152
8.2.3 细分驱动的简介............................................................................ 155
8.2.4 电机损耗........................................................................................ 156
8.3 28BYJ-48 步进电机旋转驱动实现 ............................................................ 156
8.3.1 28BYJ-48 步进电机 ........................................................................ 156
8.3.2 ULN2003 小型步进电机驱动 IC ................................................... 159
8.3.3 STM32CubeMX 生成工程 ............................................................. 161
8.3.4 28BYJ-48 步进电机驱动实现代码分析 ........................................ 163
8.4 28BYJ-48 步进电机运动控制实现 ............................................................ 165
8.4.1 STM32CubeMX 生成工程 ............................................................. 166
8.4.2 28BYJ-48 步进电机运动控制代码分析 ........................................ 167
8.5 步进电机驱动器 ........................................................................................ 170
8.5.1 57 步进电机 .................................................................................. 171
8.5.2 步进电机驱动芯片........................................................................ 172
8.5.3 步进电机驱动器............................................................................ 174
第 9 章 步进电机控制 ..................................................................................... 179
9.1 步进电机旋转驱动实现 ............................................................................ 179
9.1.1 STM32CubeMX 生成工程 ............................................................. 179
9.1.2 步进电机旋转驱动代码分析........................................................ 184
9.2 步进电机运动控制实现 ............................................................................ 189
9.3 四轴步进电机运动控制实现 .................................................................... 193
9.3.1 STM32CubeMX 生成工程 ............................................................. 193
9.3.2 步进电机旋转驱动代码分析........................................................ 199
9.4 步进电机使用常见问题 ............................................................................ 205
第 10 章 直流无刷电机 ..................................................................................... 210
10.1 直流无刷电机介绍 .................................................................................... 210
10.2 方波和正弦波驱动 .................................................................................... 211
10.3 BLDC 工作原理及硬件设计 ...................................................................... 213
10.3.1 BLDC 电机工作的基本原理 .......................................................... 213
10.3.2 BLDC 驱动硬件设计 ...................................................................... 217
10.3.3 霍尔传感器模式............................................................................ 219
10.3.4 无传感器模式................................................................................ 223
10.3.5 BLDC 速度控制 .............................................................................. 227
10.3.6 无刷驱动电路................................................................................ 229
10.4 BLDC 旋转驱动程序 .................................................................................. 234
10.4.1 6 步 PWM 程序实现 ..................................................................... 234
10.4.2 BLDC 驱动实现 .............................................................................. 241
(三)
电机高级应用 ......................................................................250
第 11 章 基于 PID 算法的直流减速电机控制.................................................... 250
11.1 PID 算法介绍 ............................................................................................. 250
STM32 技术开发手册
www.ing10bbs.com
11.2 模拟 PID 控制原理 .................................................................................... 253
11.3 数字 PID 控制 ............................................................................................ 256
11.4 基于增量式 PID 的电机速度调节实现 .................................................... 262
11.5 基于位置式 PID 的电机速度调节实现 .................................................... 266
11.6 BLDC 闭环控制 .......................................................................................... 268
第 12 章 基于梯形加减速的步进电机控制 ....................................................... 273
12.1 步进电机曲线加减速 ................................................................................ 273
12.2 梯形加减速算法原理分析 ........................................................................ 275
12.3 梯形加减速算法实现 ................................................................................ 289
第 13 章 SPWM 调制原理 ................................................................................. 299
13.1 PWM、SPWM 基本概念 ........................................................................... 299
13.2 SPWM—正弦脉宽调制理论基础 ............................................................. 300
13.3 SPWM 逆变及其控制方法 ........................................................................ 302
13.3.1 SPWM 调制波形的极性................................................................ 302
13.3.2 异步调制和同步调制.................................................................... 303
13.4 SPWM 实现原理 ........................................................................................ 305
13.4.1 计算法............................................................................................ 305
13.4.2 调制法............................................................................................ 305
13.4.3 跟踪控制........................................................................................ 309
13.5 使用 stm32 实现规则采样算法................................................................ 310
13.5.1 对称规则采样算法实现................................................................ 312
13.5.2 不对称规则采样算法实现............................................................ 314
13.5.3 同步调制算法实现........................................................................ 315
13.6 对称规则采样代码实现 ............................................................................ 317
13.7 不对称规则采样代码实现 ........................................................................ 325
13.8 查表法代码实现 ........................................................................................ 330
第 14 章 无刷电机 FOC 控制 ............................................................................. 337
14.1 梯形波和正弦波驱动 ................................................................................ 337
14.2 PWM、SPWM 和 SVPWM......................................................................... 340
14.2.1 SPWM—正弦脉宽调制 ................................................................. 340
14.2.2 SVPWM—空间矢量脉宽调制 ....................................................... 342
14.2.3 SVPWM 基本原理 ......................................................................... 343
14.2.4 SVPWM 法则推导 ......................................................................... 348
14.2.5 SVPWM 控制算法 ......................................................................... 354
14.3 FOC ............................................................................................................. 363
14.3.1 FOC 控制背景 ................................................................................ 363
14.3.2 FOC 概述 ........................................................................................ 365
14.3.3 矢量控制(FOC)原理 ................................................................. 366
14.3.4 矢量控制综述................................................................................ 367
14.4 坐标变换 .................................................................................................... 368
14.4.1 Clarke 变换及其逆变换 ................................................................ 369
14.4.2 Park 变换及其逆(反)变换 ....................................................... 371
14.4.3 PID 控制 ......................................................................................... 375
14.4.4 电机转子位置检测方式................................................................ 375
STM32 技术开发手册
www.ing10bbs.com
14.5 PMSM 电机的无传感器 FOC 控制 ........................................................... 377
14.5.1 电机模型........................................................................................ 377
14.5.2 电流观测器.................................................................................... 379
14.5.3 反电动势估算................................................................................ 380
14.5.4 反电动势滤波................................................................................ 380
14.5.5 反电动势和转子位置的关系........................................................ 381
14.5.6 速度计算........................................................................................ 382
14.5.7 相位补偿........................................................................................ 383
14.5.8 流程图............................................................................................ 384
14.5.9 不同电流模式下的控制策略........................................................ 385
14.6 FOC 控制硬件设计 .................................................................................... 401
14.7 FOCGUI 软件使用 ...................................................................................... 405
14.7.1 PMSM 电机参数 ............................................................................ 406
14.7.2 FOCGUI 使用 .................................................................................. 407
14.8 FOC 工程移植 ............................................................................................ 420
14.8.1 FOC 电机库文件 ............................................................................ 420
14.8.2 FOC 例程工程文件结构 ................................................................ 423
14.8.3 FOC 代码分析 ................................................................................ 426
第 15 章 YS-F4Pro 的 FOC4.3 移植 ..................................................................... 575
15.1 FOC4.3 MC library 文件目录 ..................................................................... 575
15.2 Keil MDK 软件工程目录 ............................................................................ 579
15.2.1 试编译官方例程............................................................................ 579
15.2.2 重新建立工程文件夹.................................................................... 584
15.2.3 移植液晶和按键............................................................................ 594
第 16 章 YS-F4Pro 的 FOC4.3 例程调试和修改参数 ........................................... 599
16.1 STMCWB 的简介 ........................................................................................ 599
16.2 电机参数 .................................................................................................... 602
16.3 上位机参数设置 ........................................................................................ 603
16.4 FOC4.3 上位机调试电机参数 ................................................................... 622
16.4.1 STMCWB 的 Monitor 简介 ............................................................ 622
16.4.2 连接上位机.................................................................................... 624
16.5 使用液晶屏脱机调试参数 ........................................................................ 626
第 17 章 YS-F1Pro 的 FOC4.3 移植 ..................................................................... 629
17.1 FOC4.3 MC library 文件目录 ..................................................................... 629
17.2 Keil MDK 软件工程目录 ............................................................................ 634
17.2.1 试编译官方例程............................................................................ 634
17.2.2 重新建立工程文件夹.................................................................... 638
17.2.3 移植液晶和触摸屏按键................................................................ 649
第 18 章 YS-F1Pro 的 FOC4.3 例程调试和修改参数 ........................................... 654
18.1 STMCWB 的简介 ........................................................................................ 654
18.2 电机参数 .................................................................................................... 657
18.3 上位机参数设置 ........................................................................................ 658
18.4 FOC4.3 上位机调试电机参数 ................................................................... 675
18.5 使用液晶屏脱机调试参数 ........................................................................ 677
STM32 技术开发手册
www.ing10bbs.com
第 19 章 YS-F4Pro 的 FOC v5.0 移植指导 ........................................................... 681
19.1 X-CUBE-MCSDK 开发套件安装.................................................................. 681
19.2 电机参数 .................................................................................................... 683
19.3 Workbench 使用说明 ................................................................................ 684
19.3.1 ST Motor Workbench ..................................................................... 684
19.3.2 参数设置........................................................................................ 687
19.3.3 生成工程文件................................................................................ 708
19.4 修改工程源码 ............................................................................................ 709
19.4.1 直接修改源码................................................................................ 709
19.4.2 使用 CubeMX 修改设置 ................................................................ 717
19.5 使用上位机调试电机参数 ........................................................................ 726
19.6 其他速度反馈模式 .................................................................................... 729
19.6.1 无感模式........................................................................................ 729
19.6.2 编码器模式.................................................................................... 731
19.7 其他事项 .................................................................................................... 733
第 20 章 YS-F4Pro FOC5.2 源码解释 .................................................................. 736
20.1 工程目录结构 ............................................................................................ 736
20.2 FOC5.2 HALLSensor 源码分析 ................................................................... 738
20.2.1 Application/User ............................................................................ 739
20.2.2 Middlewares/MotorControl........................................................... 833
20.3 FOC5.2 Encoder 核心源码解析 ................................................................ 934
20.3.1 Application/User ............................................................................ 935
20.3.2 Middlewares/MotorControl........................................................... 939
第 21 章 YS-F4Pro FOC5.4.3 位置控制 .............................................................. 951
21.1 从 FOC5.2.0 到 5.4.3 的变化 ..................................................................... 951
21.2 编码器模式的轨迹控制 ............................................................................ 955
21.3 使用 MC-Test 软件 .................................................................................... 967
第 22 章 FOC5.x 控制算法分析 ......................................................................... 975
22.1 控制流程与相关算法 ................................................................................ 975
22.1.1 简单介绍面向对象编程................................................................ 975
22.1.2 FOC 电机启动流程 ........................................................................ 979
22.1.3 HALL 测量电角度和速度 ............................................................ 1001
STM32 技术开发手册
www.ing10bbs.com
(一) 电机控制基础
第一部分内容属于电机知识介绍性内容,通过阅读本部分内容可以对电机有
大致的了解。
第1章 电机分类和原理
在旋转电机中,由于发电机是电能的生产机器,所以和电动机相比,它的种
类要少的多;而电动机是工业中的应用机器,所以和发电机相比,人们对电动机
的研究要多的多,对其分类也要详细的多。实际上,我们通常所说的旋转电机都
是狭义的,也就是电动机——俗称“马达”。众所周知,电动机是传动以及控制
系统中的重要组成部分,随着现代科学技术的发展,电动机在实际应用中的重点
已经开始从过去简单的传动向复杂的控制转移;尤其是对电动机的速度、位置、
转矩的精确控制。
1.1
电机分类
对于一个电气工程技术人员来说,熟悉各种电机的类型及其性能是很重要的
一件事情。通常人们根据旋转电机的用途进行基本分类。下面我们就从控制电动
机开始,逐步介绍电机中最有代表性、最常用、最基本的电动机——控制电动机
和功率电动机以及信号电机,见图 1-1。
STM32 技术开发手册
www.ing10bbs.com
图 1-1 电机分类
1.1.1 伺服电机
伺服电机广泛应用于各种控制系统中,能将输入的电压信号转换为电机轴上
的机械输出量,拖动被控制元件,从而达到控制目的。伺服电机系统见图 1-2。
一般地,伺服电机要求电机的转速要受所加电压信号的控制;转速能够随着所加
电压信号的变化而连续变化;转矩能通过控制器输出的电流进行控制;电机的反
映要快、体积要小、控制功率要小。伺服电机主要应用在各种运动控制系统中,
尤其是随动系统。
STM32 技术开发手册
www.ing10bbs.com
图 1-2 伺服电机系统
伺服电机有直流和交流之分,最早的伺服电机是一般的直流电机,在控制精
度不高的情况下,才采用一般的直流电机做伺服电机。当前随着永磁同步电机技
术的飞速发展,绝大部分的伺服电机是指交流永磁同步伺服电机或者直流无刷电
机。
1.1.2 步进电机
所谓步进电机就是一种将电脉冲转化为角位移的执行机构;更通俗一点讲:
当步进驱动器接收到一个脉冲信号,它就驱动步进电机按设定的方向转动一个固
定的角度。我们可以通过控制脉冲的个数来控制电机的角位移量,从而达到精确
定位的目的;同时还可以通过控制脉冲频率来控制电机转动的速度和加速度,从
而达到调速的目的。目前,比较常用的步进电机包括反应式步进电机(VR)、永磁
式步进电机(PM)、混合式步进电机(HB)和单相式步进电机等,见图 1-3。
STM32 技术开发手册
www.ing10bbs.com
图 1-3 步进电机
步进电机和普通电机的区别主要就在于其脉冲驱动的形式,正是这个特点,
步进电机可以和现代的数字控制技术相结合。但步进电机在控制精度、速度变化
范围、低速性能方面都不如传统闭环控制的直流伺服电机;所以主要应用在精度
要求不是特别高的场合。由于步进电机具有结构简单、可靠性高和成本低的特点,
所以步进电机广泛应用在生产实践的各个领域;尤其是在数控机床制造领域,由
于步进电机不需要 A/D 转换,能够直接将数字脉冲信号转化成为角位移,所以一
直被认为是最理想的数控机床执行元件。
除了在数控机床上的应用,步进电机也可以用在其他的机械上,比如作为自
动送料机中的马达,作为通用的软盘驱动器的马达,也可以应用在打印机和绘图
仪中。
此外,步进电机也存在许多缺陷;由于步进电机存在空载启动频率,所以步
进电机可以低速正常运转,但若高于一定速度时就无法启动,并伴有尖锐的啸叫
声;不同厂家的细分驱动器精度可能差别很大,细分数越大精度越难控制;并且,
步进电机低速转动时有较大的振动和噪声。
STM32 技术开发手册
www.ing10bbs.com
1.1.3 无刷直流电机
无刷直流电机(BLDCM),见图 1-4,是在有刷直流电机的基础上发展来的,
但它的驱动电流是不折不扣的交流;无刷直流电机又可以分为无刷速率电机和无
刷力矩电机。一般地,无刷电机的驱动电流有两种,一种是梯形波(一般是“方
波”),另一种是正弦波。有时候把前一种叫直流无刷电机,后一种叫交流伺服电
机,确切地讲也是交流伺服电机的一种。
图 1-4 无刷直流电机
无刷直流电机在重量和体积上要比有刷直流电机小的多,相应的转动惯量可
以减少 40%—50%左右。由于永磁材料的加工问题,致使无刷直流电机一般的容
量都在 100kW 以下。
这种电机的机械特性和调节特性的线性度好,调速范围广,寿命长,维护方
便噪声小,不存在因电刷而引起的一系列问题,所以这种电机在控制系统中有很
大的应用潜力。
这里就简单介绍这几种目前工业常用并合适 STM32 去控制的电机,其他类
型的电机属于大功率、大设备电机,有兴趣的同学可以自己去查找相关资料了解。
STM32 技术开发手册
www.ing10bbs.com
一般地,在一个完整的自动控制系统中,信号电机、功率电动机和控制电动
机都会有自己的用武之地。通常控制电动机是很“精确”的电动机,在控制系统
中充当“核心执行装置”;而功率电动机是比较“强壮”的大功率电动机,常用
来拖动现场的机器设备;信号电机则在控制系统中担任“通讯员”的角色,本质
上就是“电机传感器”。
当然,并不是所有的自动控制系统中都具备这三种电机,在一般的自动化领
域,例如运动控制和过程控制,尤其是在运动控制中,控制电动机是必不可少的
“核心器件”,所以控制电动机在自动化领域中的地位是举足轻重的,这也是人
们对控制电动机研究最多的原因之一。
1.1.4 舵机
除了上面介绍的几种电机之外,还有另外一种电机(实际上是一个电机系统)
在我们生活中非常常用,我们有必要了解一下:舵机。
舵机(英文叫 Servo,伺服):它由直流电机、减速齿轮组、传感器和控制电
路组成的一套自动控制系统,见图 1-5。通过发送信号,指定输出轴旋转角度。
舵机一般而言都有最大旋转角度(比如 180 度。)与普通直流电机的区别主要在,
直流电机是一圈圈转动的,舵机只能在一定角度内转动,不能一圈圈转。普通直
流电机无法反馈转动的角度信息,而舵机可以。用途也不同,普通直流电机一般
是整圈转动做动力用,舵机是控制某物体转动一定角度用(比如机器人的关节)。
STM32 技术开发手册
www.ing10bbs.com
图 1-5 舵机
后面我们会详细介绍:步进电机、有刷直流减速电机、舵机和无刷直流电机
这 4 中电机的工作原理并使用 STM32 开发板实现驱动。
1.2
三个基本定则
电机原理分析需要明白三个物理基本定则。
1. 左手定则
位于磁场中的载流导体,会受到力的作用,力的方向可按左手定则确定,如
图 1-6 所示:伸开左手,使大拇指和其余四指垂直,把手心面向 N 极,四指顺
着电流的方向,那么大拇指所指方向就是载流导体在磁场中的受力方向。
STM32 技术开发手册
www.ing10bbs.com
图 1-6 左手定则
力的大小为:F = BIL sin 𝜃
其中:B 为磁感应强度(单位 T),I 为电流大小(单位 A),L 为导体有效长
度(单位 m),F 为力的大小(单位 N),θ为:B 和 I 的夹角。
2. 右手定则
在磁场中运动的导体因切割磁力线会感生出电动势 E,其示意见图 1-7:
STM32 技术开发手册
www.ing10bbs.com
图 1-7 右手定则
其大小为: E = vBL sin 𝜃
其中:v 为导体的运动速度(单位 m/s),B 为磁感应强度(单位 T),L 为导
体长度(单位 m),θ为:B 和 L 的夹角。
3. 安培定则
用右手握住通电螺线管,使四指弯曲与电流方向一致,那么大拇指所指的那
一端就是通电螺旋管的 N 极,见图 1-8。
STM32 技术开发手册
www.ing10bbs.com
图 1-8 安培定则
1.3
直流电机工作原理
一台最简单的两极直流电机模型如图 1-9,其中固定部分有磁铁,这里称作
主磁极;固定部分还有电刷。转动部分有环形铁心和绕在环形铁心上的绕组。
图 1-9 直流电机的物理模型图
STM32 技术开发手册
www.ing10bbs.com
1.3.1 构成:
1) 磁场:图 1-9 中 N 和 S 是一对静止的磁极,用以产生磁场,其磁感应强
度沿圆周为正弦分布。励磁绕组——容量较小的发电机是用永久磁铁做
磁极的。容量较大的发电机的磁场是由直流电流通过绕在磁极铁心上的
绕组产生的。用来形成 N 极和 S 极的绕组称为励磁绕组,励磁绕组中的
电流称为励磁电流 If。
2) 电枢绕组:在 N 极和 S 极之间,有一个能绕轴旋转的圆柱形铁心,其上
紧绕着一个线圈称为电枢绕组(图中只画出一匝线圈),电枢绕组中的电流
称为电枢电流 Ia。
3) 换向器:电枢绕组两端分别接在两个相互绝缘而和绕组同轴旋转的半圆
形铜片——换向片上,组成一个换向器。换向器上压着固定不动的炭质
电刷。
4) 电枢:铁心、电枢绕组和换向器所组成的旋转部分称为电枢。
1.3.2 工作过程
简易的电机转动过程见图 1-10。
图 1-10 直流电动机工作原理示意图
1) 电磁转矩产生
STM32 技术开发手册
www.ing10bbs.com
电枢绕组通过电刷接到直流电源上,绕组的旋转轴与机械负载相联。电流从
电刷 A 流入电枢绕组,从电刷 B 流出。电枢电流 Ia 与磁场相互作用产生电磁力
F,其方向可用左手定则判定。这一对电磁力所形成的电磁转矩 T,使电动机电枢
逆时针方向旋转。
电磁转矩与电枢旋转方向关系:同向
2)
换向
当电枢转到上图 b 所示位置时,ab 边转到了 S 极下,cd 边转到了 N 极下。
这时线圈电磁转矩的方向发生了改变,但由于换向器随同一起旋转,使得电刷 A
总是接触 N 极下的导线,而电刷 B 总是接触 S 极下的导线,故电流流动方向发
生改变,电磁转矩方向不变。
1.3.3 电动势与能量转换分析
反电动势:电枢转动时,割切磁力线而产生感应电动势,这个电动势(用右手
定则判定)的方向与电枢电流 Ia 和外加电压 U 的方向总是相反的,称为反电动势
Ea。电源只有克服这个反电动势才能向电动机输入电流。
可见,电动机向负载输出机械功率的同时,电源却向电动机输入电功率,电
动机起着将电能转换为机械能的作用。
电动势方向与电流方向关系:反向
能量转换:
电源(电能)->电磁转矩->负载(机械能)
由此可见,加于直流电动机的直流电源,借助于换向器和电刷的作用,使直
流电动机电枢线圈中流过的电流,方向是交变的,从而使电枢产生的电磁转矩的
方向恒定不变,确保直流电动机朝确定的方向连续旋转。这就是直流电动机的基
本工作原理。简单来说,直流电动机就是利用通电导体在磁场中受力运动而“切
割”其磁力线的原理工作的。
STM32 技术开发手册
www.ing10bbs.com
第2章 TIM-基本定时器
STM32 定时器最基本的功能就是定时,常见的有定时发送 USART 数据、定
时采集 AD 数据等。我们硬石开发板主打的就是电机,使用定时器产生的 PWM
来控制电机状态是工业普遍的方法,所以对定时器的深入学习,是很有必要的。
基本定时器简介
2.1
STM32F1xx 系列有总共有 11 个定时器,其中 2 个高级控制定时器、4 个通
用定时器、2 个基本定时器、2 个看门狗定时器和上一章节已经讲的系统滴答定
时器。看门狗定时器以后章节会讲到,这一章节,主要研究的是剩下的 8 个定时
器。其中 TIM1 和 TIM8 能够产生 3 对 PWM 互补输出,常用于三相电机的驱动,
时钟由 APB2 的输出产生。TIM2~TIM5 是通用定时器,TIM6 和 TIM7 是基本定时
器器,时钟由 APB1 输出产生。基本定时器的功能较为简单,包含两个定时器 TIM6
和 TIM7,这两个定时器的功能主要有两个,第一就是基本定时功能,当累加的
时钟脉冲数超过预定值时,能触发中断或者触发 DMA 请求。第二是专门用于驱
动数模转换器(DAC)。TIM6 和 TIM7 两者间是完全独立的,当然,可以同时使
用。
定时器通道功能引脚分布见表格 2-1:
STM32 技术开发手册
www.ing10bbs.com
表格 2-1 定时器通道引脚分布
各个定时器的特性见表格 2-2:
表格 2-2 各定时器的特性
定时器
计数器
分辨率
TIM1
TIM8
16 位
TIM2
TIM3
TIM4
TIM5
TIM6
TIM7
2.2
计数器
类型
向上、向
下、向上/
向下
16 位
向上、向
下、向上/
向下
16 位
向上
预分频
系数
产生 DMA
请求
捕获/比
较通道
互补输出
1-65536
任意数
可以
4
有
可以
4
没有
可以
0
没有
1-65536
任意数
1-65536
任意数
基本定时器功能框图
基本定时器的功能框图是基本定时器的最核心内容,掌握了定时器的功能框
图,脑海中可以试着去思考基本定时器的功能,此时对基本定时器,就会有更加
清晰的理解。
在图 2-1 中可以看到自动重装载寄存器、预分频器下面有一个阴影,在图下
方暗红色框内,可以看到,阴影的解释是:根据控制位的设定,在定时器更新
STM32 技术开发手册
www.ing10bbs.com
(Update)事件(U 事件)时传送预装载寄存器至实际寄存器。这表示在物理上
这个寄存器对应 2 个寄存器:一个是我们可以写入或读出的寄存器,称为预装载
寄存器,另一个是我们看不见的、无法真正对其读写操作的,但是在使用中真正
起作用的寄存器,称为影子寄存器(图中的实际寄存器)。
然后是指向两个不同方向的图标。指向右下角的图标表示一个事件。指向右
上角的图标表示中断和 DMA 输出。在图中的自动重装载寄存器有影子寄存器,
它的左边有一个带有“U”字母的事件图标,表示在更新事件生成时就把自动重装
载寄存器内容拷贝到影子寄存器内。
图 2-1 基本定时器框图
 ①时钟源
定时器要实现定时,那么首先需要时钟源,基本定时器的时钟源只能来自内
部时钟,是由 CK_INK 提供。定时器的时钟不是直接来自 APB1 或 APB2,而是来
自输入为 APB1 或 APB2 的一个倍频器。比如在基本定时器和通用定时器的时钟,
当 APB1 的预分频系数为 1 时,这个倍频不起作用,定时器的时钟频率等于 APB1
的频率;当 APB1 的预分频系数为其它数值(如 2)时,这个倍频器起作用,定
时器的时钟频率等于 APB1 频率的两倍。
STM32 技术开发手册
www.ing10bbs.com
当 TIM6 和 TIM7 的控制寄存器 1(TIMx_CR1)的 CEN 位置 1 时,内部时钟
即向预分频器提供时钟,也就是启动基本定时器。高级控制定时器和通用定时器
则较为复杂,后面的章节会有详细介绍。
 ②控制器
定时器控制器,对基本定时器的复位、使能以及计数的控制。甚至还专门用
于 DAC 转换触发。
 ③计数器
定时器实现定时的功能,我们已经知道基本定时器的时钟源是 36MHz,如何
实现准确定时呢?这就是一个计数的过程,分别涉及到三个寄存器:计数器寄存
器(TIMx_CNT)、预分频寄存器(TIMx_PSC)、自动重装载寄存器(TIMx_ARR)。
这三个寄存器都是 16 位有效数字,可设置的值为 0~65535。
图 2-1 中,我们可以看到预分频器 PSC 有一个输入时钟 CK_PSC 和一个输出
时钟 CK_CNT。输入时钟来源于控制器部分,通过设置预分频的数值,可以得到
不同的 CK_CNT,它实际计算的式子为:CK_CNT=FCK_PSC/(PSC[15:0]+1)。因为
TIMx_PSC 控制寄存器具有缓冲,可以在运行过程中改变它的数值,新的预分频
数值将在下一个更新事件时起作用。
前面说过,基本定时器只能向上计数,在定时器使能后(CEN 置 1),计数器
COUNTER 根据 CK_CNT 频率向上计数,即每一个 CK_CNT 脉冲,TIMx_CNT 值就
加 1,当 TIMx_CNT 值与 TIMx_ARR 的设定值相等时就自动生成事件(产生 DMA
请求、产生中断信号或者触发 DAC 同步电路),并且 TIMx_CNT 自动清零,然后
重新开始计数,不断重复上述过程。因此我们只要设定 CK_PSC 和 TIMx_ARR 这
两个寄存器的值就可以控制事件生成时间。对应的就是程序中定时器预分频设置
和定时器周期。具体的定时周期计算,将会在下面程序中讲解,目的是更加的直
接去理解。
2.3
STM32CubeMX 生成工程
1) 首先我们选择需要用到的基本定时器,如图 2-2。
STM32 技术开发手册
www.ing10bbs.com
图 2-2 引脚选择及定时器的选择
1) 定时器时钟的设置,可以看到图 2-3,我们在前面讲过,基本定时器时钟
是经过 APB1 倍频的。经过倍频后,定时器的时钟是 72MHz。
STM32 技术开发手册
www.ing10bbs.com
图 2-3 定时器时钟
2) 在图 2-4 ,我们将预分频值设置为 71,周期为 1000,触发事件选择为
清零。
图 2-4 定时器设置
STM32 技术开发手册
www.ing10bbs.com
3) 选择开启中断,如图 2-5。
图 2-5 开启中断
上面是对 CubeMX 软件生成定时器工程的主要设置,其它设置,例如 GPIO
之类的,大家应该都会自己选择了。
2.4
TIM-基本定时器外设结构体
对于代码 2-1 句柄,也是作为我们操作定时器的“接口”,下面我们对各个
成员进行介绍。
代码 2-1 基本定时器句柄
01 typedef struct {
02
TIM_TypeDef
*Instance;
03
TIM_Base_InitTypeDef
Init;
04
HAL_TIM_ActiveChannel
Channel;
05
DMA_HandleTypeDef
*hdma[7];
06
HAL_LockTypeDef
Lock;
07
__IO HAL_TIM_StateTypeDef State;
08 } TIM_HandleTypeDef;
/*
寄存器基地址
/*
基本定时器相关参数设置
/* 使用通道
/*
DMA 相关 */
/*
锁定处理
/* 定时器运行相关状态
 Instance:TIM 寄存器基地址。
 Init:基本定时器相关参数设定,下面会有详细的讲解。
 Channel:定时器通道的选择,有四个通道和一个“清理”通道。
*/
*/
*/
*/
*/
STM32 技术开发手册
www.ing10bbs.com
 hdma[7]:定时器 DMA 相关,后面的“7”表示着它只能由 TIM DMA Handle Index
这个定时器的 DMA 处理标志访问。
 Lock/State:锁定机制和定时器操作的状态。
基本定时器参数设定,此结构体成员用来设置定时器的基本工资参数,该结
构体成员设置参数时将会对应定时器相应的寄存器,达到配置定时器工作环境的
目的。
代码 2-2 基本定时器参数设定
01 typedef struct {
02
uint32_t Prescaler;
03
04
uint32_t CounterMode;
05
06
uint32_t Period;
07
08
uint32_t ClockDivision;
09
10
uint32_t RepetitionCounter;
11 } TIM_Base_InitTypeDef;
 Prescaler:定时器预分频设置,时钟源经过该分频器才是定时器时钟,它设
定 TIMx_PSC 寄存器的值,结合上一节的讲解,可以回去看看。可设置值范
围为 0~65535,实现 1 至 65536 分频,我们的工程设置是 71,这样分频后的
时钟是 1MHz。
 CouterMode:定时器计数方式,基本定时器只能向上计数,即 TIMx_CNT 只
能从 0 开始递增,无须初始化。
 Period:定时器周期,可设置值为 0~65535。在定时器预分频我们已经得到分
频后的时钟为 1MHz。Period 的值我们设置为 1000,这样,定时器产生中断
的频率为:1MHz/1000=1KHz,即为 1ms 的定时周期。
 ClockDivision:时钟分频,设置定时器时钟 CK_INT 频率与数字滤波器采样时
钟频率分频比,基本定时器没这个功能,略过。
 RepetitionCounter:重复计数器,属于高级控制寄存器专用寄存器位,利用它
可以非常轻松控制输出 PWM 的个数。这里不用设置。
STM32 技术开发手册
www.ing10bbs.com
2.5
TIM6&TIM7 编程流程分析
1) 初始化三个 LED 灯;
2) 设置定时器周期和预分频器;
3) 开启定时器中断;
4) 开启定时器,进入一次中断加一次数值
5) 无限循环中达到固定值循环点亮 LED
2.6
TIM6&TIM7 基本定时代码实现
bsp_BasicTIM.h 文件内容
bap_BasicTIM.h 是关于两个定时器的宏定义,使用宏定义是为了方便我们程
序的移植和升级。例如,在此文件中,我们可以在两个基本的定时器直接进行选
择,如果使用 TIM7,将#define USE_TIM6 注释即可。然后是中断的定义和时钟
预分频和周期的定义。
代码 2-3 相关宏定义
01 #define USE_TIM6
02
03 #ifdef USE_TIM6 // 使用基本定时器 TIM6
04 #define BASIC_TIMx
TIM6
05 #define BASIC_TIM_RCC_CLK_ENABLE()
__HAL_RCC_TIM6_CLK_ENABLE()
06 #define BASIC_TIM_RCC_CLK_DISABLE()
__HAL_RCC_TIM6_CLK_DISABLE()
07 #define BASIC_TIM_IRQ
TIM6_IRQn
08 #define BASIC_TIM_INT_FUN
TIM6_IRQHandler
09
10 #else // 使用基本定时器 TIM7
11
12 #define BASIC_TIMx
TIM7
13 #define BASIC_TIM_RCC_CLK_ENABLE()
__HAL_RCC_TIM7_CLK_ENABLE()
14 #define BASIC_TIM_RCC_CLK_DISABLE()
__HAL_RCC_TIM7_CLK_DISABLE()
15 #define BASIC_TIM_IRQ
TIM7_IRQn
16 #define BASIC_TIM_INT_FUN
TIM7_IRQHandler
17
18 #endif
19
20 // 定义定时器预分频,定时器实际时钟频率为:72MHz/(BASIC_TIMx_PRESCALER+1)
21 #define BASIC_TIMx_PRESCALER
71
22 #define BASIC_TIMx_PERIOD
1000
STM32 技术开发手册
www.ing10bbs.com
bsp_BasicTIM.c 文件内容
bap_BasicTIM.c 文件中是通过调用 HAL 库中的的结构体成员进行 TIM 基本定
时器相关参数的设定。
TIM 基本定时器初始化配置函数,如代码 2-4。结合我们的宏定义,对引用
的结构体成员进行赋值。然后是初始化定时器。STM32 的每个定时器都可以由另
个一定时器触发而启动定时器,一般是通过软件设置而启动。每个定时器也可以
通过外部信号触发而启动,还可以通过另一个定时器的某一个条件被触发而启动,
这里所谓某一个条件可以是定时到时、定时超时、比较成功等许多条件。这种通
过一个定时器触发另一个定时器的工作方式称为定时器的同步,发出触发信号的
定时器工作于主模式,接受触发信号而启动的定时器工作于从模式,于是便有了
我们代码的 12~14 行,这里设置定时器触发输出复位,定时器从模式不使能,只
配置为主模式。
代码 2-4 基本定时器初始化
01
02 void BASIC_TIMx_Init(void)
03 {
04
TIM_MasterConfigTypeDef sMasterConfig;
05
06
htimx.Instance = BASIC_TIMx;
07
htimx.Init.Prescaler = BASIC_TIMx_PRESCALER;
08
htimx.Init.CounterMode = TIM_COUNTERMODE_UP;
09
htimx.Init.Period = BASIC_TIMx_PERIOD;
10
HAL_TIM_Base_Init(&htimx);
11
12
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
13
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
14
HAL_TIMEx_MasterConfigSynchronization(&htimx, &sMasterConfig);
15 }
定时器硬件初始化和反初始化配置,这是芯片系统级的初始化。
代码 2-5 硬件初始化
01
02 void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base)
03 {
04
05
if (htim_base->Instance==BASIC_TIMx) {
06
/* 基本定时器外设时钟使能 */
07
BASIC_TIM_RCC_CLK_ENABLE();
08
09
/* 外设中断配置 */
10
HAL_NVIC_SetPriority(BASIC_TIM_IRQ, 1, 0);
11
HAL_NVIC_EnableIRQ(BASIC_TIM_IRQ);
12
}
STM32 技术开发手册
www.ing10bbs.com
13 }
14
15
16 void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* htim_base)
17 {
18
19
if (htim_base->Instance==BASIC_TIMx) {
20
/* 基本定时器外设时钟禁用 */
21
BASIC_TIM_RCC_CLK_DISABLE();
22
23
/* 关闭外设中断 */
24
HAL_NVIC_DisableIRQ(BASIC_TIM_IRQ);
25
}
26 }
main.c 文件内容
main.c 文件中可以看到是系统时间的一些配置,然后我们看到主函数。调用
我们需要用的相对应函数进行初始化,启动定时器,进入中断回调函数,点亮
LED2,在中断回调函数中递增数值,因为我们设置的中断周期是 1ms,无限循环
中检测计数值,当计数值达到 1000 时(也就是 1s),计数值归 0,三个 LED 翻
转。
01 int main(void)
02 {
03
/* 复位所有外设,初始化 Flash 接口和系统滴答定时器 */
04
HAL_Init();
05
/* 配置系统时钟 */
06
SystemClock_Config();
07
08
/* 初始化 LED */
09
LED_GPIO_Init();
10
11
/* 基本定时器初始化:1ms 中断一次 */
12
BASIC_TIMx_Init();
13
14
/* 在中断模式下启动定时器 */
15
HAL_TIM_Base_Start_IT(&htimx);
16
17
LED2_ON;
18
19
/* 无限循环 */
20
while (1) {
21
if (timer_count==1000) {
22
timer_count=0;
23
LED1_TOGGLE;
24
LED2_TOGGLE;
25
LED3_TOGGLE;
26
}
27
}
28 }
29
30
31 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
32 {
33
timer_count++;
STM32 技术开发手册
www.ing10bbs.com
34 }
实验现象
检查三个 LED 灯的跳线帽是否已连接,用 USB 连接“调试串口”接口和电脑。将编
译无错误的程序下载至开发板。开发板上 LED2 先点亮,然后按照 1s 的周期,三
个 LED 翻转。
STM32 技术开发手册
www.ing10bbs.com
第3章 TIM-高级控制定时器
高级控制定时器(TIM1 和 TIM8)和通用定时器在基本定时器的基础上引入
了外部引脚,可以输入捕获和输出比较功能。高级控制定时器比通用定时器增加
了可编程死区互补输出、重复计数器、带刹车(断路)功能,这些功能都是对工
业电机控制方面。高级控制定时器已经包含了基本定时器和通用定时器的所有功
能。所以需要大家认真的学习本章节。
3.1
高级控制定时器
高级控制定时包含一个 16 位向上、向下、向上/向下自动装载计数器,一个
16 位计数器,一个 16 位可编程(可以实时修改)预分频器,一个 16 位计数器,
预分频器的时钟源在高级控制定时器中是可选的,可选择内部的时钟和外部的时
钟。还有一个 16 位重复次数寄存器,一共 48 位的计数定时器。
下面图 3-1 是高级控制定时器的系统框图,如果能掌握系统框图,那么在编
程时的思路就会变的很清晰,因为已经清楚了定时器的运作,掌握了原理,那么
一切都变的简单了。
STM32 技术开发手册
www.ing10bbs.com
图 3-1 高级控制定时器功能框图
1. ①时钟源
 高级控制定时器有四个时钟源:
 内部时钟源 CK_INT
 外部时钟模式 1:外部输入引脚 TINx(x=1,2,3,4)
 外部时钟模式 2:外部触发输入 ETR
 内部触发输入:使用一个定时器作为另一个定时器的预分频器。
内部时钟源(CK_INT)
内部时钟 CK_INT 即来自于芯片内部,等于 72MHz,我们在一般情况下都是
使用内部时钟。
STM32 技术开发手册
www.ing10bbs.com
外部时钟模式 1
图 3-2 外部时钟模式 1 框图
① :时钟信号输入引脚
使用外部时钟模式 1 时,有 4 个来自于定时器输入通道的时钟信号,分别是
TI1/2/3/4(TIMx_CH1/2/3/4),配置 TIM_CCMx 的位 CCxS[1:0]来选择哪一路信号,
其中 CCM1 控制 TI1/2,CCM2 控制 TI3/4。
② :滤波器
作用很简单,是为了滤除输入信号上的高频干扰。
③ :边沿检测
来自于滤波后的信号,此时是为了检测上升沿有效还是下降沿有效,具体的
由 TIMx_CCER 的位 CCxP 和 CCxNP 配置。
④ :触发选择
若选择外部时钟模式 1,此时由两个触发源,一个是滤波后的定时器输入 1
(TI1FP1)和滤波后的定时器输入 2(TI2FP2)
,具体的由 TIMx_SMCR 的位 TS[2:0]
配置。
⑤ :从模式选择
选定了触发源信号后,信号是接到 TRGO 引脚的,让触发信号成为外部时钟
模式 1 的输入,即为 CK_PSC。
STM32 技术开发手册
www.ing10bbs.com
完成上述过程后,设置 TIMx_CR1 寄存器的 CEN=1,启动计数器
外部时钟模式 2
图 3-3 外部时钟模式 2
① :时钟信号输入引脚
使用外部时钟模式 2 时,时钟信号来自于定时器特定输入通道 TIM_ETR。
② :外部触发极性
选择 ETR 上升沿检测,置 TIMx_SMCR 寄存器中的 ETP=0。
③ :分频器
当触发信号的频率很高时,就必须使用分频器进行降频,有 1/2/4/8,可选择,
是由 TIMx_SMCR 寄存器中的 ETPS[1:0]配置。
④ :滤波器
如果 ETRP 的信号的频率过高或者混杂有高频干扰信号的话,我们就需要使
用滤波器对 ETRP 信号重新采样,达到降频或者去除干扰的目的。由 TIMx_SMCR
的位 ETF[3:0]配置,其中 fDTS 是由内部时钟 CK_INT 分频得到,由 TIMx_CR1 的位
CKD[1:0]配置。
⑤ :从模式选择
经滤波后的信号连接至 ETRF 引脚,CK_PSC 输出,驱动计数器。由 TIMx_SMCR
的位 ECE 置 1 即可配置为外部时钟模式 2。
STM32 技术开发手册
www.ing10bbs.com
完成上述过程后,设置 TIMx_CR1 寄存器的 CEN=1,启动计数器。
内部触发输入
在前面的基本定时器讲过,STM32 的定时器都可以由另一个定时器而触发。
主模式的定时器可以对从模式定时器执行复位、启动、停止或提供时钟。
如图 3-4 使用定时器 1 作为定时去 2 的预分频器。
图 3-4 内部触发输入
2. ②控制器
高级控制定时器控制器部分包括触发控制器、从模式控制器以及编码器接口。
触发控制器用来针对片内外设输出触发信号,比如为其它定时器提供时钟和触发
DAC/ADC 转换。编码器接口专门针对编码器计数而设计。从模式控制器可以控制
计数器复位、启动、递增/递减、计数。
3. ③时基单元
高级控制定时器主要部分是一个 16 位计数器和与其相关的自动装载寄存器,
我们已经知道这个由预分频器分频得到的计数器可以向上计数、向下计数或者向
上向下双向计数。
时基单元包含:

计数器寄存器(TIMx_CNT)

预分频器寄存器(TIMx_PSC)

自动装载寄存器(TIMx_ARR)
STM32 技术开发手册
www.ing10bbs.com

重复次数寄存器(TIMx_RCR)
预分频器 PSC
它可以将计数器的时钟频率按 1 到 65535 之间的任意值分频。新的预分频
参数在下一次更新事件到来时被采用。输出 CK_CNT 用来驱动计数器 CNT 计数。
计数器 CNT
高级控制定时器的计数器有三种计数模式,前面已经述说。
 向上计数模式,计数器开始从 0 计数到自动重装载值(TIMx_ARR 计数器内
容),然后重新从 0 开始并且产生一个计数器溢出事件。如果使用了重复计
数器功能,在向上计数达到设置的重复计数次数(TIMx_RCR)时,就产生更
新事件;否则每次计数器溢出时才产生更新事件。
 向下计数模式,计数器从自动重装载值(TIMx_ARR 计数器内容)开始递减计
数直到 0,然后重新从自动重装载值开始递减并生成计数器下溢事件,如此
循环。如果使用了重复计数器功能,在向下计数重复了重复计数寄存器
(TIMx_RCR)中设定的次数后,将产生更新事件,否则每次计数器下溢时才
产生更新事件。
 在向上/向下计数模式下,计数器从 0 开始计数到自动加载的值(TIMx_ARR
寄存器)-1,产生一个计数器溢出事件,然后向下计数到 1 并且产生一个计
数器下溢事件;又从 0 开始重新计数,如此循环。每次计数器上溢和下溢都
会生成更新事件。当发生更新事件时,所以的寄存器都被更新。
自动重装载寄存器
这个寄存器是预先装载的,当写或读自动重装载寄存器将访问预装载寄存。
控制 TIMx_CR1 寄存器中的自动重装载预装载使能位(ARPE),如果置 1,预装载
寄存器的内容在每次更新事件时传递给影子寄存器;如果置 0,修改 TIMx_ARR
的值后马上有效。
STM32 技术开发手册
www.ing10bbs.com
重复次数寄存器
高级控制定时器相比基本和通用定时器多了重复计数器在基本和通用定时
器上溢或者下溢直接产生更新事件,而高级控制定时器只能在重复次数达到 0 的
时候产生更新事件。重复计数器是自动加载的 ,当向上溢出、向下溢出和中央
对其模式下每次上溢或下溢时,重复计数器的值递减。
4. ④输入捕获
每一个捕获/比较通道都是围绕着一个捕获/比较寄存器(包含了影子寄存
器)
,包含输入部分和输出部分。输入部分有数字滤波、多路复用和预分频器;
输出部分有比较器和输出控制。
我们可以看到图 3-5,输入部分对相对应的 TIx 输入信号进行采样,滤波后
产生一个信号 TIxF。然后一个带极性选择的边缘检测器产生一个信号(TIxFPx),
它可以作为从模式控制器的输入触发或者作为捕获控制。信号预分频进入捕获寄
存器。
图 3-5 输入捕获通道
5. ⑤输出比较
输出比较就是通过定时器的外部引脚对外输出控制信号。可以设置成不同的
模式,有冻结、将通道 X(X=1,2,3,4)设置为匹配时输出有效电平。将通道 X 设
置为匹配时输出无效电平、翻转、强置为无效电平、强置为有效电平、PWM1 和
STM32 技术开发手册
www.ing10bbs.com
PWM2 这八种模式。具体由寄存器 CCMRx 的位 OcxM[2:0]设置。其中 PWM 模式
是我们常用的。
输出部分有比较器,当 CNT 的值跟比较寄存器的值相等的时候,输出参考信
号 OCxREF 的信号极性就会改变,其中 OCxREF=1 称为有效电平,OCxREF=0 称为
无效电平,并且会产生比较中断 CCxI,相应的标志位 CCxIF(SR 寄存器中)会置
位。然后 OCxREF 再经过一系列的控制之后就成为输出信号 OCx/OcxN。简单的一
句话理解就是:输出部分产生一个中间波形 OCxREF(高有效)作为基准,链的
末端决定最终输出信号的极性。
图 3-6 输出通道 1~3
图 3-7 输出通道 4
在图 3-6 和图 3-7 中,可以看到输出模式控制器,参考信号 OCxREF 在经过
死区发生器之后会产生两路带死区互补信号 OCx_DT 和 OCxN_DT(通道 1~3 才有
STM32 技术开发手册
www.ing10bbs.com
互补信号,通道 4 没有),这两路带死区的互补信号进入输出控制电路,如果没
有加入死区控制,那么进入输出控制电路的信号就直接是 OCxREF。
那么什么是死区,为什么要设计死区。其实死区这个概念范围很广,很多的
地方都会提到,可以理解为某个处于相对无效状态的时间和空间。在变频电源驱
动中有使用到,两路 PWM 分别控制开关管,开关管交替导通截止之间就是要插
入一个死区,不然两个同时导通就 Over 了。
带死区的信号进入控制电路会被分成两路,一路是原始信号,一路是被反向
的信号,具体的由寄存器 CCER 的位 CCxP 和 CCxNP 控制。经过极性选择的信号
是否由 OCx 引脚输出到外部引脚则有寄存器 CCER 的位 CxE/CxNE 配置。
如果加入断路(刹车)功能,则断路和死区寄存器 BDTR 的 MOE、OSSI 和
OSSR 这三个位会共同影响输出的信号。
6. ⑥断路功能
断路功能就是电机的刹车功能,使能相应的控制位(TIMx_BDTR 寄存器中的
MOE、OSSI 和 OSSR 位,TIMx_CR2 寄存器中的 OISx 和 OISxN 位),但是无论何时,
OCx 和 OcxN 输出不能在同一时间同时处于有效电平上。
刹车源可以说是外部断路输入引脚,也可以是一个时钟失败事件(由复位时
钟控制器中的时钟安全系统产生)。
系统复位默认禁止刹车电路,MOE 位为低。设置 TIMx_BDTR 寄存器中的 BKE
位可以使能刹车功能,刹车输入信号的极性可以通过配置同一个寄存器中的 BKP
位选择。
3.2
输入捕获模式
前面的一节已经介绍了根据基本功能框图,大家对高级控制定时器应该有个
认识,下面是更深入地对各个模式的介绍。
STM32 技术开发手册
www.ing10bbs.com
首先是输入捕获模式,是输入捕获模式下,当检测到 ICx 信号上的相应边沿
后,计数器的当前值被锁存到捕获/比较寄存器(TIMx_CCRx)中。如果开启了中
断或者 DMA 请求,那么将产生中断或 DMA 请求。
使用输入捕获模式可以实现测量频率。如图 3-8,当捕获通道 TIx 出现上升
沿时,发生第一次捕获,计数器 CNT 的值会被锁存到捕获寄存器 CCR 中,进入
捕获中断,在中断服务程序中记录一次捕获(可以用一个标志变量来记录),并
把捕获寄存器中的值读到 value1 中。当出现第二次上升沿时,发生第二次捕获,
计数器 CNT 的值会再次被锁存到捕获寄存器 CCR 中,并再次进入捕获中断,在
中断中,将捕获寄存器的值读取到 value3 中,并清除捕获记录标志。此时,利用
value1 和 value3 的差值我们就可以算出信号的周期。
图 3-8 频率测量示意图
3.3
PWM 输入模式
该输入模式是捕获模式的一个特例,可以看到图 3-9,每个定时器都有四个
输入捕获通道 IC1、IC2、IC3、IC4。两个 ICx 信号被映射至同一个 TIx 输入,同时,
这两个信号的边沿有效,但是极性相反。
我们以输入通道 TI1 为例,PWM 信号由输入通道 TI1 进入,因为是 PWM 输
入,信号被分为两路,一路是 TI1FP1,另一路是 TI2FP2,根据程序设置的触发输
入一路作为周期,另一路就是占空比。设置触发输入的极性,可选择下降沿捕获
还是上升沿捕获。那么另外一路硬件见就会自动配置为相反的极性捕获,无需软
件的设置。简单的说就是,选定输入通道,确定触发信号,设置极性。
STM32 技术开发手册
www.ing10bbs.com
当其中一个 TIXFP 信号被作为触发输入信号时,从模式控制器被配置成复位
模式(置 TIMx_SMCR 中的 SMS=100)
图 3-9 PWM 输入模式
我们看 PWM 的时序图,如图 3-10,PWM 信号由 TI1 进入,配置 TI1FP1 为
触发信号,上升沿捕获。当上升沿的时候 IC1 和 IC2 同时捕获,计数器 CNT 清零,
到了下降沿的时候,IC2 捕获,此时计数器 CNT 的值被锁存到捕获寄存器 CCR2
中,到了下一个上升沿的时候,IC1 捕获,计数器 CNT 的值被锁存到捕获寄存器
CCR1 中。其中 CCR2 测量的是脉宽,CCR1 测量的是周期。
可以看到,使用 PWM 输入模式测量脉宽和周期更加的容易,但是需要用到
两个捕获寄存器。
STM32 技术开发手册
www.ing10bbs.com
图 3-10 PWM 输入模式时序图
强置输出模式
3.4
此模式在是输出模式下,输出比较信号能够直接由软件强置为有效或无效状
态,而不依赖于输出比较寄存器和计数器间的比较结果。
例如置 TIMx_CCMRx 寄存器中相应的 OCxM=101,即可强置输出比较信号
(OCxREF/OCx)为有效状态。这样 OCxREF 被强置为高电平,同时 OCx 得到 CCxP
极性相反的信号。
该模式下,在 TIMx_CCRx 影子寄存器和计数器直接的比较仍然在进行,相应
的标志也会被修改。因此仍然会产生相应的中断和 DMA 请求。
输出比较模式
3.5
常用此模式来控制一个输出波形,或者指示一段给定的时间已经到时。
在前面的功能框图中已经有介绍,简单来说就是当计数器与捕获/比较寄存
器的值匹配时,输出比较功能做出不同的动作。具体的配置步骤可以参考编程手
册。
STM32 技术开发手册
www.ing10bbs.com
3.6
PWM 模式
脉冲宽度调制模式,具体来说就是利用微处理器的数字输出来对模拟电路进
行控制的一种技术。简单来说就是对脉冲宽度的控制,常用来控制直流有刷电机
的速度等等。利用此功能可以输出一个可调占空比(例如在一串理想的方波中,
正脉冲的持续时间与总周期的比值)的方波信号,由 TIMx_ARR 寄存器确定频率,
由 TIMx_CCRx 确定占空比。
STM32 的 PWM 模式有两种,根据 TIMx_CCMRx 寄存器中的 OcxM 位来确定
(“110 为模式 1”,“111”为模式 2)。其区别如下:
 110:PWM 模式 1,在向上计数时,TIMx_CNT<TIMx_CCR1 时通道 1 为有效电
平,否则为无效电平;在向下计数时,TIMx_CNT>TIMx_CCR1 时通道 1 为无
效电平,否则为有效电平。
 111:PWM 模式 2,在向上计数时,TIMx_CNT<TIMx_CCR1 时通道 1 为无效电
平,否则为有效电平;在向下计数时,TIMx_CNT>TIMx_CCR1 时通道 1 为有
效电平,否则为无效电平。
上面两个模式看起来复杂,其实是两个互补,正好是相反的,理解记住其中
一个即可。
根据 TIMx_CR1 寄存器中 CMS 位的状态,定时器能够产生边沿对齐的 PWM
信号或者中央对齐的 PWM 信号。一般的电机控制用的都是边沿对齐模式,FOC
电机一般用中央对齐模式。
PWM 边沿对齐模式
在边沿对齐模式下,计数器 CNT 只工作在递增或者递减其中的一种模式下。
我们以递增为例子。如图 3-11,ARR=8,CCR=4,计数器从 0 开始计数,当 CNT<CRR
的值时,OCxREF 为有效的高电平,此时,比较重大寄存器 CCxIF 置位。当
CCR=<CNT<=ARR 时,OCxREF 为有效的低电平。然后,计数器 CNT 又从 0 开始计
数,并生成上溢事件,以此不断循环。
STM32 技术开发手册
www.ing10bbs.com
图 3-11 PWM1 模式的边沿对齐波形
PWM 中央对齐模式
在中心对齐模式下(ARR=8),计数器 CNT 是工作在递增/递减模式下。
图 3-12 PWM1 模式的中央对齐波形
可以看到图 3-12,计数器 CNT 从 0 开始计数到自动重装载值-1(ARR-1),
生成计数器上溢事件,然后从自动重装载值开始向下计数到 1,生成计数器下溢
事件。之后又从 0 开始,循环。
在第一阶段(包含①②),计数器 CNT 在递增模式下,从 0 开始计数,当
CNT<CCR 的值时。OCxREF 为有效的高电平,当 CCR=<CNT<<ARR 时,OCxREF 为无
效的低电平。
在第二阶段(包含③④),计数器 CNT 在递减模式,从 ARR 值开始递减,
当 CNT>CCR 时,OCxREF 为无效的低电平,当 CCR=>CNT>=1 时,OCxREF 为有效
的高电平。
中央对齐模式可以分为三种,根据 CMS 位的不同,比较中断的中断标志可
以在向上计数时被置 1、在计数器向下计数时被置 1、或在计数器向上和向下计
数时被置 1。
STM32 技术开发手册
www.ing10bbs.com
3.7
互补输出和死区插入
高级控制定时器能够产生两路的互补信号输出,并且能够管理输出的瞬时关
断和接通。这段时间通常被称为死区,我们可以根据连接的输出器件和它们的特
性来调整死区时间。
通过对 TIMx_CCER 寄存器中的 CCxP 和 CCxNP 位的配置,可以独立地选择每
一个输出极性(主输出 OCx 或互补输出 OCxN)。具体的寄存器控制可以参考
《STM32F10xxx 编程参考手册 2010(中文)》
在图 3-13 中,假设已经配置好各个寄存器,显示出死区发生器的输出信号
和当前参考信号 OCxREF 之间的关系。OCx 输出信号与参考信号相同,知识它的
上升沿相对于参考信号的上升沿有一个延迟;OCxN 输出信号与参考信号相反,
知识它的上升沿相对于参考信号的下降沿有一个延迟
每一个通道都有一个 10 位的死区发生器,每一个通道额死区延时都是相同
的,是由 TIMx_BDTR 寄存器中的 DTG 位编程配置。
图 3-13 带死区插入的互补输出
3.8
在外部事件时清除 OCxREF 信号
可通过给 ETRF 输入加高电平将一个给定通道的 OCxREF 信号拉低(相应的
TIMx_CCMRx 寄存器中的 OcxCE 使能位被置为 1)。OCxREF 信号将一直为低直到
下一个更新事件 UEV 发生。此项功能只可在输出比较和 PWM 模式下使用,在强
置模式下不起作用。
如图 3-14,定时器 TIMx 被置于 PWM 模式,当 ETRF 输入变为高时,对应不
同 OCxCE 的值,OCxREF 信号的动作。
STM32 技术开发手册
www.ing10bbs.com
图 3-14 清除 TIMx 的 OCxREF
3.9
产生六步 PWM 输出
前面已经介绍过,STM32 高级控制定时器有互补输出的功能,我们便可以利
用定时器 TIM1 来产生 3 对 6 路的互补 PWM 输出。
首先我们先介绍下 COM 事件,它是用于同时控制所有通道的输出转换,在
电机控制中同时转换所有通道的输出是十分必要的,比如无刷电机转向时,一般
是三相要同时转向的,但是你在软件里设置换向时肯定是一次只能设置一相,这
就达不到三相同时换向的目的。利用 STM32 的 COM 事件,先逐个设置好每相的
换向,然后再调用 COM 事件,此时三相就可以同时的换向。COM 事件发生在
STM32 高级控制定时器的“六步 PWM 的产生”,用于驱动三相电机,对应着直流
无刷电机的六步换相。
六步 PWM 产生:当在一个通道上应用了互补输出时,OCxM、CCxE 和 CCxNE
位的预载位有效,这些预装载位被传送到影子寄存器,因此可以预先设置好下一
步的配置,并在同一时间更改所有通道的配置。COM 事件可以通过硬件(在 TRGI
的上升沿)设置或者软件修改 TIM1_EGR 寄存器的 COM 位来产生。当 COM 事件
发生时会设置一个标志位(TIM1_SR 寄存器中的 COMIF 位),这时如果已设置了
TIM1_DIER 寄存器的 COMIE 位,则产生一个中断;如果已设置了 TIMx_DIER 寄存
器的 COMDE 位,则产生一个 DMA 请求。
STM32 技术开发手册
www.ing10bbs.com
3.10 单脉冲模式
此模式是前面众多模式中的一个特例。通过一个激励启动计数器,在一个程
序可控的延时之后产生一个脉宽可程序控制的脉冲。从模式启动计数器,在输出
比较模式和 PWM 模式下产生波形。设置 TIMx_CR1 寄存器中的 OPM 位将选择单
脉冲模式,这样可以让计数器自动地在产生下一个更新事件 UEV 时停止。
如图 3-15,在 TI2 输入引脚检测到一个上升沿,延迟 tDELAY 之后,在 OC1 上
产生一个长度为 tPULSE 的正脉冲。
图 3-15 单脉冲模式例子
3.11 编码器接口模式
首先我们先来认识下什么是编码器,专业性的术语是将信号或数据进行编制、
转换为可用以通讯、传输和存储的信号形式的设备。简单点的理解就是把机械位
移转变成电信号的传感器,按码盘的刻孔可分为增量型和绝对值型两种。增量型
编码器是将位移转换成周期性的电信号,再把这个电信号转变成计数脉冲,用脉
冲的个数表示位移的大小。绝对值编码器的每一个位置对应一个确定的数字码,
因此它的示值只与测量的起始和终止位置有关,而与测量的中间过程无关。常用
光电型编码器原理:编码器内部有一个转动的码盘,带若干个透明和不透明的窗
STM32 技术开发手册
www.ing10bbs.com
口,用光电接收器收集断续的光束,这样,就把光脉冲转换成了电脉冲,然后由
电子输出线路进行处理并输出。
STM32 高级控制定时器有编码器接口模式,两个输入 TI1 和 TI2 倍用来作为
编码器的接口,设置 TIMx_SMCR 寄存器中 SMS=001,则计数器只在 TI2 的边沿
计数;设置 SMS=010,只在 TI1 的边沿计数;设置 SMS=011,则计数器同时在 TI1
和 TI2 边沿计数。
参考表 3-1,假设 TI1 和 TI2 不同时变换。TI1FP1 和 TI2FP2 是 TI1 和 TI2 在通
过滤波器和极性控制后的信号,如果没有滤波和变相,则 TI1FP1=TI1,
TI2FP2=TI2。
根据两个输入信号的跳变顺序,产生计数脉冲和方向信号。此模式下,计数器根
据编码器的速度和方向自动的修改,计数器内容始终指示这编码器的位置。
表 3-1 计数方向与编码器信号的关系
有效边沿
相对信号的电平
(TI1FP1 对应 TI2,
TI1FP1 信号
TI2FP2 信号
上升
下降
上升
下降
向下计数
向上计数
不计数
不计数
向下计数
向上计数
向上计数
向下计数
不计数
不计数
向上计数
向下计数
不计数
不计数
向上计数
向下计数
向上计数
向下计数
不计数
不计数
向下计数
向上计数
向下计数
向上计数
TI2FP2 对应 TI1)
仅在 TI1 计
数
仅在 TI2 计
数
在 TI2 和
TI2 上计数
高
低
高
低
高
低
编码器接口模式基本上相当于一个使用了一个带有方向选择的外部时钟。此
时计数器只在 0 到 TIMx_ARR 寄存器的自动装载值之间连续计数。所以,在开始
计数前需要配置好 TIMx_ARR,还有捕获器、比较器、预分频器、重复计数器、
触发输出特性等。另外,编码器接口模式和外部时钟模式 2 不兼容,不能同时操
作。
如图 3-16,已经配置好了各个寄存器,使能计数器后。可以看到,当 TI 波
形先于 TI2 波形 90°时,每遇到一个边沿变化,计数器加 1,一个光栅,计数 4
次。TI1 波形后于 TI2 波形 90°时,每遇到一次边沿变化,计数器减 1。
当定时器配置成编码器接口模式时,提供传感器当前位置的信息。使用第二
个配置在捕获模式的定时器,可以测量两个编码器事件的间隔,获得速度、加速
度、减速度等信息。
STM32 技术开发手册
www.ing10bbs.com
图 3-16 编码器模式下计数器操作实例
3.12 与霍尔传感器的接口
当我们使用高级控制定时器产生 PWM 信号驱动电机时,可以用另一个通用
定时器作为“接口定时器”来连接霍尔传感器,如图 3-17,3 个定时器输入引脚
(OC1、OC2、OC3)通过一个异或门连接到 TI1 输入通道,也可以查看图 3-1 中
定时器输入部分。而“接口定时器”则可以捕获这个信号。
“接口定时器”可以用来在输出模式产生一个脉冲,这个脉冲可以(通过触发
一个 COM 事件)用于改变高级控制定时器 TIM1 和 TIM8 各个通道的属性,而高
级控制定时器产生 PWM 信号驱动电机。
图 3-17,霍尔输入连接到 TIMx 定时器,要求每次任一霍尔输入上变化之后
的一个指定的时刻,改变高级控制定时器 TIMx 的 PWM 配置。
STM32 技术开发手册
www.ing10bbs.com
图 3-17 霍尔传感器接口实例
3.13 TIMx 定时器和外部触发的同步
TIMx 定时器能够在多种模式下和一个外部的触发同步:复位模式、门控模
式和触发模式,在这三个模式下,都处于从模式,前面的一个章节已经讲了从模
式的概念,其实简单的说法就是每个定时器都可以由外部触发而启动,此时受触
发的定时器触发从模式。
STM32 技术开发手册
www.ing10bbs.com
复位模式
能够在发生触发输入事件时,计数器和它的预分频器复位。同时,如果
TIMx_CR1 寄存器的 URS 位为低,还产生一个更新事件 UEV,然后所有的预装载
寄存器都被更新。
图 3-18 中显示了在 TI1 输入端的上升沿导致向上计数器被清零,产生更新
事件后,触发中断。
图 3-18 复位模式下的控制电路
门控模式
按照选中的输入端电平使能计数器。在图 3-19 中,计数器只在 TI1 为低时
向上计数。只要 TI1 为低,计数器开始依据内部时钟计数,一旦 TI1 变高则停止
计数,当计数器开始或停止时都设置 TIMx_SR 中的 TIF(触发中断标志)标志。
图 3-19 门控模式下的控制电路
触发模式
输入端上选择的事件使能计数器。在图 3-20 中,计数器在 TI2 输入的上升
沿开始向上计数,当 TI2 出现一个上升沿时,计数器开始在内部时钟驱动下计数,
STM32 技术开发手册
www.ing10bbs.com
同时设置 TIF 标志。可以看到 TI2 上升沿和计数器启动计数之间存在延时,这是
取决于 TI2 输入端的重新同步电路。
图 3-20 触发模式下的控制电路
外部时钟模式 2+触发模式
外部时钟模式 2 可以和另一种从模式(外部时钟模式 1 和编码器模式除外)
一起使用。此时,ETR 信号被用作外部时钟的输入,在复位模式、门控模式或触
发模式可以选择另一个输入作为触发输入。
在图 3-21 中,TI1 出现一个上升沿时,TIF 标志被设置,计数器开始在 ETR
的上升沿计数,ETR 的每一个上升沿,计数器计数一次。
图 3-21 外部时钟模式 2+触发模式下的控制电路
3.14 STM32CubeMX 生成工程
高级控制定时器的应用有很多,功能也很强大,下面是介绍基于 HAL 库的一
个 PWM 输出例程。
1) 引脚和外设功能选择,见图 3-22。选择定时器 1 的四个通道以及三个互
补输出通道(PWM 输出模式)。
STM32 技术开发手册
www.ing10bbs.com
图 3-22 定时器的选择
2) 时钟方面就不讲了,按照原先的配置即可。下面是高级控制定时器 TIM1
的配置。我们在前面的选项已经选定输出 PWM,需要输出什么样子的,
那么就需要在这里配置了,可以看到图 3-23,
我们将计数周期设为 1000,
将电平跳变值“Pulse”四个通道分别设置不同的值,前三个通道是互补输
出,极性相反,通道 1 的电平跳变值为 900,意味着它从 0~900 都是高
电平,900~1000 都是低电平,这样周期循环,实现 PWM 的输出。
STM32 技术开发手册
www.ing10bbs.com
图 3-23 定时器输出 PWM 配置
定时器中断暂时用不着,这里就不多介绍了,其实定时器主要是要理解它的
运作原理,根据寄存器的相关位,那么就会很快的理解了,其次是多看看例程,
多比较。
3.15 高级控制定时器外设结构体
代码 3-1 定时器主从模式
01 typedef struct {
02
uint32_t MasterOutputTrigger;
03
uint32_t MasterSlaveMode;
04 } TIM_MasterConfigTypeDef;
第一个变量,有 8 种选择,对应的是 TIMx_CR2 寄存器中的 MMS 位,我们
设置为复位模式,发生触发输入事件时计数器和预分频器能重新初始化。
第二个变量是设置是否使用定时器的被动触发,这里不使能。
代码 3-2 刹车和死区时间配置结构体
01 typedef struct {
02
uint32_t OffStateRunMode;
03
uint32_t OffStateIDLEMode;
04
uint32_t LockLevel;
05
uint32_t DeadTime;
STM32 技术开发手册
www.ing10bbs.com
06
uint32_t BreakState;
07
uint32_t BreakPolarity;
08
uint32_t AutomaticOutput;
09 } TIM_BreakDeadTimeConfigTypeDef;
 OffStateRunMode:设置运行模式下非工作状态选项。这里有两种状态可选,
使能和失能 TIMx OSSR 状态。
 OffStateIDLEMode:设置在空转状态下非工作状态选项。也有两种状态可选,
使能和失能 TIMx OSSI 状态。
 LockLevel:设置了锁电平参数,有四个选项,这里选择不锁任何位
 DeadTime:指定了输出打开和关闭之间的延时,也就是死区时间设置
 BreakState:使能或失能 TIMx 刹车输入。
 BreakPolarity:设置 TIMx 刹车输入管脚极性。
 AutomaticOutput:使能和失能自动输出功能。
代码 3-3 输出比较配置结构体
01 typedef struct {
02
uint32_t OCMode;
03
uint32_t Pulse;
04
uint32_t OCPolarity;
05
uint32_t OCNPolarity;
06
uint32_t OCFastMode;
07
uint32_t OCIdleState;
08
uint32_t OCNIdleState;
09 } TIM_OC_InitTypeDef;
 OCmode:输出比较模式的选择,具体的可以参考《STM32F10xxx Cortex-M3
编程手册》关于高级控制定时器篇的寄存器介绍,对应的是 TIMx_CCMR1 寄
存器的 OC1M 位。
 Pulse:设置电平跳变值,此值加载到捕获/比较寄存器中。
 OCPolarity:设置输出比较极性。
 OCNPolarity:设置互补输出比较极性。
 OCFastMode:输出比较快速使能和失能。
 OCIdleState:选择空闲状态下的非工作状态(OC1 输出)。
 OCNdleState:设置空闲状态下的非工作状态(OC1N 输出)。
STM32 技术开发手册
www.ing10bbs.com
3.16 高级控制定时器生成 PWM 编程流程分析
1) 使能定时器通道各个引脚端口时钟。
2) 对于高级控制定时器 TIM1 的各个通道引脚进行初始化,配置好输出模
式和输出速度。
3) 根据 HAL 库的函数进行定时器的配置,包括周期、计数方向、预分频等。
4) 设置各个通道的电平跳变值,以及输出通道和互补输出通道的极性。
5) 使能外设时钟,调用函数输出 PWM。
3.17 高级控制定时器生成 PWM 代码实现
bsp_AdvancedTIM.h 文件内容
代码 3-4 相关宏定义
01 #define ADVANCED_TIMx
TIM1
02 #define ADVANCED_TIM_RCC_CLK_ENABLE()
__HAL_RCC_TIM1_CLK_ENABLE()
03 #define ADVANCED_TIM_RCC_CLK_DISABLE()
__HAL_RCC_TIM1_CLK_DISABLE()
04 #define ADVANCED_TIM_GPIO_RCC_CLK_ENABLE()
{__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();}
05 #define ADVANCED_TIM_CH1_PORT
GPIOA
06 #define ADVANCED_TIM_CH1_PIN
GPIO_PIN_8
07 #define ADVANCED_TIM_CH2_PORT
GPIOA
08 #define ADVANCED_TIM_CH2_PIN
GPIO_PIN_9
09 #define ADVANCED_TIM_CH3_PORT
GPIOA
10 #define ADVANCED_TIM_CH3_PIN
GPIO_PIN_10
11 #define ADVANCED_TIM_CH4_PORT
GPIOA
12 #define ADVANCED_TIM_CH4_PIN
GPIO_PIN_11
13
14 #define ADVANCED_TIM_CH1N_PORT
GPIOB
15 #define ADVANCED_TIM_CH1N_PIN
GPIO_PIN_13
16 #define ADVANCED_TIM_CH2N_PORT
GPIOB
17 #define ADVANCED_TIM_CH2N_PIN
GPIO_PIN_14
18 #define ADVANCED_TIM_CH3N_PORT
GPIOB
19 #define ADVANCED_TIM_CH3N_PIN
GPIO_PIN_15
20
21
22 // 定义定时器预分频,定时器实际时钟频率为:72MHz/(ADVANCED_TIMx_PRESCALER+1)
23 #define ADVANCED_TIM_PRESCALER
0 // 实际时钟频率为:72MHz
24
25 //
26 定义定时器周期,当定时器开始计数到 ADVANCED_TIMx_PERIOD 值并且重复计数寄存器为 0 时更新定
时
27 //并生成对应事件和中断
28 #define ADVANCED_TIM_PERIOD
1000 // 定时器产生中断频率为:1MHz/1000=1KHz,
即 1ms 定时周期
29 // 定义高级定时器重复计数寄存器值,
30 #define ADVANCED_TIM_REPETITIONCOUNTER
0
STM32 技术开发手册
www.ing10bbs.com
此文件对高级控制定时器 TIM1 的各个引脚进行了宏定义,方便我们对程序
进行移植和修改。同时对预分频、周期和重复计数寄存器的值也进行宏定义,一
目了然。
bsp_AdvancedTIM.c 文件内容
代码 3-5 定时器硬件初始化配置
01 void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim)
02 {
03
GPIO_InitTypeDef GPIO_InitStruct;
04
if (htim->Instance==ADVANCED_TIMx) {
05
/* 定时器通道功能引脚端口时钟使能 */
06
ADVANCED_TIM_GPIO_RCC_CLK_ENABLE();
07
08
/* 定时器通道 1 功能引脚 IO 初始化 */
09
GPIO_InitStruct.Pin = ADVANCED_TIM_CH1_PIN;
10
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
11
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
12
HAL_GPIO_Init(ADVANCED_TIM_CH1_PORT, &GPIO_InitStruct);
13
/* 定时器通道 1 互补通道功能引脚 IO 初始化 */
14
GPIO_InitStruct.Pin = ADVANCED_TIM_CH1N_PIN;
15
HAL_GPIO_Init(ADVANCED_TIM_CH1N_PORT, &GPIO_InitStruct);
16
17
/* 定时器通道 2 功能引脚 IO 初始化 */
18
GPIO_InitStruct.Pin = ADVANCED_TIM_CH2_PIN;
19
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
20
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
21
HAL_GPIO_Init(ADVANCED_TIM_CH2_PORT, &GPIO_InitStruct);
22
/* 定时器通道 2 互补通道功能引脚 IO 初始化 */
23
GPIO_InitStruct.Pin = ADVANCED_TIM_CH2N_PIN;
24
HAL_GPIO_Init(ADVANCED_TIM_CH2N_PORT, &GPIO_InitStruct);
25
26
/* 定时器通道 3 功能引脚 IO 初始化 */
27
GPIO_InitStruct.Pin = ADVANCED_TIM_CH3_PIN;
28
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
29
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
30
HAL_GPIO_Init(ADVANCED_TIM_CH3_PORT, &GPIO_InitStruct);
31
/* 定时器通道 3 互补通道功能引脚 IO 初始化 */
32
GPIO_InitStruct.Pin = ADVANCED_TIM_CH3N_PIN;
33
HAL_GPIO_Init(ADVANCED_TIM_CH3N_PORT, &GPIO_InitStruct);
34
35
/* 定时器通道 4 功能引脚 IO 初始化 */
36
GPIO_InitStruct.Pin = ADVANCED_TIM_CH4_PIN;
37
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
38
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
39
HAL_GPIO_Init(ADVANCED_TIM_CH4_PORT, &GPIO_InitStruct);
40
}
41 }
代码 3-5 这段代码是对高级控制定时器 TIM1 的七个通道进行初始化配置,
使能时钟后,对各个输出引脚进行设置。
代码 3-6 定时功能配置
STM32 技术开发手册
www.ing10bbs.com
01 void ADVANCED_TIMx_Init(void)
02 {
03
TIM_ClockConfigTypeDef sClockSourceConfig;
04
TIM_MasterConfigTypeDef sMasterConfig;
05
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;
06
TIM_OC_InitTypeDef sConfigOC;
07
08
htimx.Instance = ADVANCED_TIMx;
09
htimx.Init.Prescaler = ADVANCED_TIM_PRESCALER;
10
htimx.Init.CounterMode = TIM_COUNTERMODE_UP;
11
htimx.Init.Period = ADVANCED_TIM_PERIOD;
12
htimx.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
13
htimx.Init.RepetitionCounter = ADVANCED_TIM_REPETITIONCOUNTER;
14
HAL_TIM_Base_Init(&htimx);
15
16
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
17
HAL_TIM_ConfigClockSource(&htimx, &sClockSourceConfig);
18
19
HAL_TIM_PWM_Init(&htimx);
20
21
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
22
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
23
HAL_TIMEx_MasterConfigSynchronization(&htimx, &sMasterConfig);
24
25
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
26
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
27
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
28
sBreakDeadTimeConfig.DeadTime = 0;
29
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
30
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
31
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
32
HAL_TIMEx_ConfigBreakDeadTime(&htimx, &sBreakDeadTimeConfig);
33
34
sConfigOC.OCMode = TIM_OCMODE_PWM1;
35
sConfigOC.Pulse = 900;
36
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
37
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_LOW;
38
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
39
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
40
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
41
HAL_TIM_PWM_ConfigChannel(&htimx, &sConfigOC, TIM_CHANNEL_1);
42
43
sConfigOC.Pulse = 600;
44
HAL_TIM_PWM_ConfigChannel(&htimx, &sConfigOC, TIM_CHANNEL_2);
45
46
sConfigOC.Pulse = 300;
47
HAL_TIM_PWM_ConfigChannel(&htimx, &sConfigOC, TIM_CHANNEL_3);
48
49
sConfigOC.Pulse = 100;
50
HAL_TIM_PWM_ConfigChannel(&htimx, &sConfigOC, TIM_CHANNEL_4);
51
52
HAL_TIM_MspPostInit(&htimx);
53
54 }
代码 3-6 中已经对我们输出 PWM 进行了配置,首先是结构体的声明,然后
是定时器的基本配置,然后是时钟源的选择,触发方式,死区设置,PWM 输出
模式等。
STM32 技术开发手册
www.ing10bbs.com
mian.c 文件内容
main.c 文件中有两个函数:main 函数和 systemClock 函数,时钟的函数大家
已 经 很 熟 悉 了 。 高 级 控 制 定 时 器 控 制 输 出 PWM 波 形 直 接 调 用
HAL_TIMx_PWM_Start 即可,互补输出是调用 HAL_TIMEx_PWMN_Start 函数。
01 int main(void)
02 {
03
/* 复位所有外设,初始化 Flash 接口和系统滴答定时器 */
04
HAL_Init();
05
/* 配置系统时钟 */
06
SystemClock_Config();
07
08
/* 高级控制定时器初始化并配置 PWM 输出功能 */
09
ADVANCED_TIMx_Init();
10
11
/* 启动通道 PWM 输出 */
12
HAL_TIM_PWM_Start(&htimx,TIM_CHANNEL_1);
13
HAL_TIM_PWM_Start(&htimx,TIM_CHANNEL_2);
14
HAL_TIM_PWM_Start(&htimx,TIM_CHANNEL_3);
15
//HAL_TIM_PWM_Start(&htimx,TIM_CHANNEL_4);
16
17
/* 启动定时器互补通道 PWM 输出 */
18
HAL_TIMEx_PWMN_Start(&htimx,TIM_CHANNEL_1);
19
HAL_TIMEx_PWMN_Start(&htimx,TIM_CHANNEL_2);
20
HAL_TIMEx_PWMN_Start(&htimx,TIM_CHANNEL_3);
21
22
/* 无限循环 */
23
while (1) {
24
}
25 }
实验现象
使用开发板配套的 MINI USB 线连接到开发板标示“调试串口”字样的 MIMI
USB 接口为开发板供电。将编译无错误的程序下载至开发板,根据程序宏定义将
TIM1 对应的引脚连接至示波器,设置示波器的相关参数,可以观察到有七道
PWM 波形。
STM32 技术开发手册
www.ing10bbs.com
(二) 电机驱动
第二部分内容讲详细介绍步进电机、舵机、有刷直流电机和无刷直流电机内
容,分析电机驱动实现方法,并最终实现使用 stm32 控制电机旋转。
第4章 有刷直流电机
有刷直流电机部分我们重点介绍直流减速电机。
4.1
直流减速电机
直流减速电机,即齿轮减速电机,是在普通直流电机的基础上,加上配套齿
轮减速箱,实物见图 4-1。齿轮减速箱的作用是,提供较低的转速,较大的力矩。
同时,齿轮箱不同的减速比可以提供不同的转速和力矩。这大大提高了,直流电
机在自动化行业中的使用率。减速电机是指减速机和电机(马达)的集成体。这
种集成体通常也可称为齿轮马达或齿轮电机。通常由专业的减速机生产厂进行集
成组装好后成套供货。减速电机广泛应用于钢铁行业、机械行业等。使用减速电
机的优点是简化设计、节省空间。
图 4-1 直流减速电机
STM32 技术开发手册
www.ing10bbs.com
减速电机的特色首要有以下几点:
 减速电机节约空间,牢靠经用,能承受一定的过载能力,功率能满意的需求;
 减速电机能耗低,性能优越;
 减速电机振荡小,噪音低,节能高,选用优质锻钢资料,刚性铸铁箱体,齿
轮外表颠末高频热出来;
 颠末精细加工,包管定位精度,这一切构成了齿轮传动总成的齿轮减速电机
装备了各类电机,形成了机电一体化,彻底包管了产物的运用质量特征;
 减速电机采用了系列化、模块化的设计,有很广泛的适应性。同时可组合其
他多种电机、装置方位和布局计划、可按实际需要挑选任意转速和各种布局
方式。
4.2
减速电机参数分析
减速电机有几个重要参数在选型时作为参考值。图 4-2 为一个带编码器的
25GA370 直流减速电机的实物图,表格 4-1 为它的参数表格。
图 4-2 25GA370 电机实物图
STM32 技术开发手册
www.ing10bbs.com
表格 4-1 25GA370 减速电机参数表
空载转速:电机正常通电无负载状态的转速(单位:rpm 或转/分钟或 r/min) ;
空载电流:电机正常通电无负载状态的电流(单位:mA 毫安);
负载力矩:电机负载测试时候的额定扭矩,仅用于测试参考(单位:g-cm 克
每厘米或 kg-cm 公斤每厘米);
负载转速:电机在负载力矩下的转速(单位:rpm 或转/分钟或 r/min);
负载电流:电机在负载力矩下的电流(单位:mA 毫安或 A 安);
堵转力矩:又叫启动扭力,为电机所能承受的最大扭力标准,超过该扭力,
电机将停转或堵转(单位:g-cm 克每厘米或 kg-cm 公斤每厘米);
堵转电流:也叫启动电流,为电机遭到堵转停止时候的最大电流;(单位:
mA 毫安或 A 安);
减速比:减速装置的传动比,由减速齿轮结构决定;减速电机输出轴转速与
直流电机转速之比;
STM32 技术开发手册
www.ing10bbs.com
霍尔分辨率:电机输出轴旋转一圈霍尔编码器输出的脉冲数,有关编码器内
容在后面还会详细介绍。
很多时候我们更加关系电机的转速问题,下面我们分析电机转速影响因素:
U=CeΦn+IaRa
n=(U-IaRa)/(CeΦ)
其中 n 为转速,U 为电机端电压,Ia 为电枢电流,Ra 为电机电枢绕组电阻
Ce 为电机常数,与电机结构有关,Φ为电机气隙磁通。对一个电机来说,电机出
厂时 Ce 和Φ这两个参数值已经是确定的。所以,很多时候通过调节电机电压来
达到调速的目的。
一般认为:直流电机的转速和电压成正比;
电机一般还有一个最小启动电压,就是可以使得电机开始旋转的电压值。为
保证电机正常工作,一般需要接到电机两端的电压值范围为:最小启动电压至额
定电压。并且在这个电压值范围内才认为转速与电压成正比。
电机线圈是有铜导线绕线而成的,所以其电机电枢绕组电阻一般都是非常小,
这样回路中电流一般都是比较大的。这对我们电机驱动设计有很大的影响。
另外,电机还有一个比较重要的参数:扭矩。
简化理解扭矩就是电机可以带动外部部件旋转的力量,大扭矩可以带动比较
重的东西。
一般认为:直流电机的扭矩和电流成正比;
关于减速齿轮的简易说明:
直流减速电机由两部分组成:减速齿轮+直流电机。一般直流电机的空载转
速是很高的(上千上万转每分钟),但实际应用中可能需要转速慢的电机,合适
的做法是为电机加速减速齿轮。
通过控制直流电机的转速就可以控制减速电机输出轴的转速。
STM32 技术开发手册
www.ing10bbs.com
直流减速电机驱动设计
4.3
4.3.1 驱动电路设计基本原理
我们希望微控制器可以方便的调整电机速度,但微控制器的 IO 接口电压和
电流一般都是非常有限的,所以为方便控制需要在微控制器和电机直接添加一个
驱动电路板,该电机驱动板有两种输入线:电源输入线和控制信号输入线。电源
输入线一般要求是可以提供电机额定电源的大电流电源,它是给电机提供动力的
来源。控制信号线与微控制器的信号线连接,是实现调速的方法。电机驱动板还
有一个输出线,有两个端口,它与直流电机的引脚直接连接。注意,这里的电机
驱动板输出线是应该一系列电路之后才输出的,也就是通过输入信号调制后的输
出线。
我们先来看看最简单的可以控制电机正反转的电路,见图 4-3。
图 4-3 简易直流电机控制电路
当开关 A 和 D 闭合、B 和 C 断开时直流电机正常旋转,记该旋转方向为正方
向。
STM32 技术开发手册
www.ing10bbs.com
当开关 B 和 C 闭合、A 和 D 断开时直流电机正常旋转,记该旋转方向为反方
向。
当开关 A 和 C 闭合、B 和 D 断开或者当开关 B 和 D 闭合、A 和 C 断开时直
流电机不旋转。此时可以认为电机处于“刹车”状态,电机惯性转动产生的电势将
被短路,形成阻碍运动的反电势,形成“刹车”作用。
当开关 A 和 B 闭合或者当开关 C 和 D 闭合时直接电源短路,会烧毁电源,
这种情况严禁出现。
当开关 A、B、C 和 D 四个开关都断开时候,认为电机处于“惰行”状态,电机
惯性所产生的电势将无法形成电路,从而也就不会产生阻碍运动的反电势,电机
将惯性转动较长时间。
这样简单的控制开关状态就可以控制电机的选择方向。从上图中可以看到,
其形状类似于字母“H”,而作为负载的直流电机是像“桥”一样架在上面的,所
以称之为“H 桥驱动”。4 个开关所在位置就称为“桥臂”。
在电路中可以做电子开关的有三极管和 MOS 管。可以使用这两种器件代替
开关从而实现电路可控的效果,见图 4-4 和图 4-5。
图 4-4 三极管搭建 H 桥电路
STM32 技术开发手册
www.ing10bbs.com
图 4-5 MOS 管搭建 H 桥电路
4.3.2 H 桥电路分析
下面开始以三极管搭建的 H 桥电路解释电机正反转控制。要使电机运转,必
须使对角线上的一对三极管导通。例如,如图 4-6 所示,当 Q1 管和 Q4 管导通
时,电流就从电源正极经 Q1 从左至右穿过电机,然后再经 Q4 回到电源负极。
按图中电流箭头所示,该流向的电流将驱动电机顺时针转动。当三极管 Q1 和 Q4
导通时,电流将从左至右流过电机,从而驱动电机按特定的方向转动。
图 4-6 H 桥电路电机正转
STM32 技术开发手册
www.ing10bbs.com
图 4-7 所示为另一对三极管 Q2 和 Q3 导通的情况,电流从右至左流过电机。
当三极管 Q2 和 Q3 导通时,电流将从右至左流过电机,从而驱动电机沿另一方
向转动。
图 4-7 H 桥电路电机反转
这里需要注意的是,电机一般会引出两个极,但并无正负之分,所谓的正反
转也只是我们人为定义,具体要看实际的应用和安装情况。
驱动电机时,保证 H 桥上两个同侧的三极管不会同时导通非常重要,如果三
极管 Q1 和 Q2 同时导通,那么电流就会从正极穿过两个三极管直接回到负极,
此时电路中除了三极管外没有其它任何负载,因此电路上的电流就可能达到最大
值(该电流仅受电源性能限制),甚至烧坏三极管。基于上述原因,在实际驱动
电路中通常要用硬件电路方便地控制三极管的开关。
图 4-8 所示就是基于这种考虑的改进电路,它在基本的 H 桥电路的基础上
增加了 4 个与门和 2 个非门。4 个与门同一个使能导通信号相接,这样,用这一
个信号就能控制整个电路的开关。而 2 个非门通过提供一种方向输入,可以保证
任何时候在 H 桥的同侧都只有一个三极管导通。
STM32 技术开发手册
www.ing10bbs.com
图 4-8 H 桥改进电路
采用以上方法,电机的运转只需要三个信号控制,如:两个方向信号和一个
使能信号。
如果 DIR-L 信号为 0,DIR-R 信号为 1,并且使能信号是 1,那么三极管 Q1
和 Q4 导通,电流从左至右流经电机,如图 4-9 所示;
如果 DIR-L 信号变为 1,而 DIR-R 信号变为 0。那么 Q2 和 Q3 将导通,电
流则反向流过电机。
图 4-9 H 桥改进电路控制分析
4.3.3 电机驱动芯片选型
H 桥电路虽然有着许多的优点,但是在实际的制作过程中,由于元件较多,
电路和搭建也较为麻烦,增加了硬件设计的复杂度。所绝大多数制作中通常直接
选用专用的驱动芯片。目前市面上专用的驱动芯片很多,如 L298N、BST7970、
STM32 技术开发手册
www.ing10bbs.com
MC33886 等,但到底我们应该选用哪咱芯片呢,当然每种芯片有自己的优势,我
们应该根据设计需要从价格和性能上综合考虑才行,这里谈三个方面。
1. 驱动效率的转化
所谓驱动效率高,就是要将输入的能量尽量多的输出给负载,而驱动电路本
身最好不消耗或少消耗能量,具体到 H 桥上,也就是 4 个桥臂在导通时最好没
有压降,越小越好。从电路上看,这主要取决于“开关”上的压降,其消耗为流
过的电流乘以压降,电流大小主要取决于负载电机的需要,所以对于设计来说重
点应考虑尽量减小开关上的电阻从而提高效率,而在选用驱动芯片时应当考虑所
选用的芯片压降是否满足电机驱动力的需要。
2. 能够通过的驱动电流
每个芯片都有自身承受的最大电流,在设计时应保证电机的工作电流不会造
成芯片的烧毁。
3. 芯片的价格
对于器件的价格,一般在业余的制作基本不会考虑太多,但真正在产品的设
计中,价格却是除了性能外必须考虑的另一个关键因素。
4.4
L298N 电机驱动芯片
L298N 内部的组成其就是上面讲的 H 桥驱动电路,见图 4-10,
STM32 技术开发手册
www.ing10bbs.com
图 4-10 L298N 芯片内部结构
它内部集成了两个 H 桥电路,至于工作原理我以上介绍的 H 桥相同,这里
我们不在叙述,在使用时重点要了解其引脚的功能和主要的性能参数。引脚图如
图 4-11 所示。
图 4-11 L298N 芯片引脚
L298N 是 ST 公司生产的一种高电压,大电流的电机驱动芯片。该芯片采用
15 脚封装。主要特点是:工作电压高,最高工作电压可达 46V,输出电流大,瞬
间峰值可达 3A,持续工作电流为 2A;额定功率为 25W。内含两个 H 桥的高电压
STM32 技术开发手册
www.ing10bbs.com
大电流全桥式驱动器,可以用来驱动直流电机和步进电机、继电器线圈等感性负
载;采用标准逻辑电平信号控制;具有两个用控制端,在不受输入信号影响的情
况下允许或禁止器件工作有一个逻辑电源输入端,使内部逻辑电路部分在低电压
下工作;可以外接检测电阻,将变化量反馈给控制电路。使用 L298N 芯片驱动电
机,该芯片可以驱动一台两相步进电机和四相步进电机,也可以两台直流电机。
L298N 模块的驱动电路图如图 4-12 所示。
图 4-12 L298N 电机驱动电路
对于以上电路图有以下几点说明:
1) 电路图中有两个电流,一路为 L298N 工作需要的 5V 电源 VCC,一路为驱
动电机用的电池电源 VSS。
2) 1 脚和 15 脚有的电路在中间串接了大功率的电阻,可以不加。
3) 八个续流二极管是为了消除电机转动时的尖峰电压保护电机而设计,简
化电路可以不加。
4) 6 脚和 11 脚为两路电机通道的使能开关,高电平使能所以可以直接接高
电平,也可以交由单片机控制。
5) 由于工作时 L298 的功率较大,可以适当加装散热片。
STM32 技术开发手册
www.ing10bbs.com
该电机驱动电路可以驱动 2 路直流电机,使能端 ENA、ENB 为高电平有效,
控制方法和电机状态如表格 4-2 所示:
表格 4-2 控制引脚与输出状态关系
ENA
0
1
1
1
1
IN1
IN2
任
任
意
意
0
0
1
1
0
1
0
1
直流电机状态
(OUT1 和
OUT2)
停止
刹车
正转
反转
刹车
类似的,ENB、IN3 和 IN4 对应控制 OUT3 和 OUT4 状态。
图 4-13 L298N 电机驱动模块实物图
4.5
PWM—脉冲宽度调制
PWM(Pulse Width Modulation)是指将输出信号的基本周期固定,通过调整基
本周期内工作周期的大小来控制输出功率的方法。在 PWM 驱动控制的调整系统
中,按一个固定的频率来接通和断开电源,并根据需要改变一个周期内“接通”和
STM32 技术开发手册
www.ing10bbs.com
“断开”时间的长短。因此,PWM 又被称为“开关驱动装置”。脉冲作用下,当
电机通电时,速度增加;电机断电时,速度逐渐减少。只要按一定规律改变通、
断电的时间,即可让电机转速得到控制。
图 4-14 PWM 信号
设电机始终接通电源时,电机转速最大为𝑉𝑚𝑎𝑥 。
设占空比为:
D=
𝑡
× 100%
𝑇
则电机的平均速度为:
𝑉𝑑 = 𝑉𝑚𝑎𝑥 × D
式中:𝑉𝑑 表示电机的平均速度;𝑉𝑚𝑎𝑥 表示电机全通电时的速度(最大);D 是
占空比。由公式可见,当改变占空比 D 时,就可以得到不同的电机平均速度,从
而达到调速的目。
占空比 D 表示了在一个周期 T 里边开关管导通的时间 t 与周期的百分比。可
见 D 的变化范围为:0≤D≤1。
在外部供电电源不变情况下(对应𝑉𝑚𝑎𝑥 不变),输出电压的平均值(对应𝑉𝑑
大小)取决于占空比 D 的大小,改变 D 值也就改变输出电压的平均值,从而达
到控制电机转速的目的,这就是 PWM 调速。
这里可能有部分同学可能觉得:这样信号总是通断通断…会不会造成电机抖
动?实际上,这个问题在脉冲频率很低(时间周期大)才可能存在的,一般我们
给电机控制的 PWM 信号频率都是比较高的(非常高也不行,每个芯片都有最高
STM32 技术开发手册
www.ing10bbs.com
的频率)
,另外一点,电机本身就是感性部件,所以一般不会存在因为 PWM 信
号导致的抖动问题的。
占空比 D 的大小由 t 和 T 两个数值大小决定,所以一般有几种方法可以改变
D 的大小:定宽调频法(t 不变、T 改变)、定频调宽法(t 改变、T 不变)和调宽
调频法(t 改变、T 改变)。在一般的微控制器中,都是非常任意生成 PWM 信号
的,一般使用定频调宽法来改变占空比大小。
STM32 技术开发手册
www.ing10bbs.com
第5章 减速电机旋转控制实现
有了上一章直流减速电机基础,结合之前讲到的定时器内容,现在我们就着
手实现控制 25GA370 直流减速电机旋转。
5.1
25GA370 直流减速电机
25GA370 直流减速电机是一个带编码器的直流减速电机,编码器的作用是测
速,这在下一章讲解。有关 25GA370 直流减速电机的重要参数如下:
1) 额定电压:12V;
2) 空载时转速 320RPM,电流 0.8A;
3) 最大效率点 5.53W,转速 284RPM,电流 0.6A, 1.88kg-cm;
4) 直流电机本身转速 10500 转/s,齿轮减速比为 34;
5) 双通道霍尔编码器为 11 线:直流电机每旋转一周输出 11 个脉冲信号,
减速齿轮输出轴每旋转一周输出 11*34=374 个脉冲;
6) 输出轴直径 4MM,D 型;
7) 外径:25mm。
8) 电机引脚定义如图 5-1:
图 5-1 25GA370 减速电机引脚定义
STM32 技术开发手册
www.ing10bbs.com
1、6 引脚就是直流电机引脚,电机旋转和速度调节只需这两个引脚即可。2、
3、4 和 5 四个引脚是编码器功能引脚,这在下一章我们详细介绍。
5.2
硬件连接
这里以驱动一路直流减速电机旋转为例描述硬件连接方法。整个系统的硬件
框图见图 5-2。
图 5-2 单路减速电机驱动系统框图
这里 L298N 电机驱动模块以使用 IN1、IN2、ENA 配合 OUT1 和 OUT2 形成一
路电机驱动系统;当然,我们可以使用 IN3、IN4、ENB 配合 OUT3 和 OU4 也形成
一路电机驱动系统。实际上,如果我们需要制作智能小车,就可以同时使用这两
路电机驱动系统,驱动两个车轮旋转。当然,这简易起见,以一路电机驱动系统
为例介绍。
结合上一小节内容,25GA370 减速电机与 L298N 电机驱动模块只需要两根
导线连接,即:M1 — OUT1,M2 — OUT2;当然,也可以是:M1 — OUT2,M2
— OUT3;这两个接法只会导致电机旋转方向不同而已。
L298N 模块支持的电源的电压输入范围为:5~35V,而 25GA370 直流电机的
额定电压为 12V,所以这里选择 12V 2A 电源为 L298N 驱动模块供电。
接下来,就只有 L298N 驱动模块与开发板导线连接问题了。
STM32 技术开发手册
www.ing10bbs.com
在图 4-13 中的 L298N 电机驱动模块的 ENA 和 ENB 引脚已经默认通过跳线
帽短路至模块板上的 5V 上,即 ENA 和 ENB 已经默认设置为高电平了,开发板可
以不需要再浪费两个 IO 引脚去控制它们。
YS-F1Pro 开发板上专门预留了一个电机控制接口,见图 5-3。
图 5-3 电机控制接口
其中包括 TIM1 和 TIM3 部分通道功能引脚。使用高级控制定时器的互补通
道功能可以非常方便的控制 H 桥电路。这里,我们就选择使用 TIM1 的 CH1 和
CH1N 连接 L298N 驱动模块的 IN1 和 IN2,这样通过程序控制就可以实现电机旋
转驱动以及调速控制。
当然这里也可以选择 TIM1 的 CH2 和 CH2N 与 IN1 和 IN2 连接,或者选择
TIM1 的 CH3 和 CH3N 与 IN1 和 IN2 连接,这些方案都是可行的,只要我们在程
序稍作修改就可以实现电机控制的。
对于 CH1 和 CH1N 引脚与 IN1 和 IN2 的连接也没有严格的顺序要求,调换连
接会导致电机旋转方向相反而已。
讲了这么多,L298N 驱动模块与开发板连接只需三根导线而已:IN1、IN2 和
GND。注意这里需要连接 GND,起到“共地”作用,不然无法正常控制。
实际上,这里是使用 TIM1 功能控制电机,STM32F10x 芯片还有另外一个高
级控制定时器 TIM8,理论上都是可以实现相同的效果的,STM32F10x 芯片定时
器通道引脚分布见图 5-4。当然,YS-F1Pro 把 TIM8 定时器通道引脚做为其他功
能引脚,所以真的需要使用这个引脚做为电机控制必须非常小心。
STM32 技术开发手册
www.ing10bbs.com
图 5-4 stm32f103zet6 定时器功能引脚
5.3
STM32CubeMX 生成工程
使用 STM32CubeMX 软件总是可以帮助我们减少无谓的新建工程工作。关于
STM32CubeMX 软件的使用和 HAL 库的介绍可以参考我们发布的文档《硬石 YSF1Pro 开发板开发手册(HAL 库版本).pdf》,该文档可以访问我们的论坛获取:
http://www.ing10bbs.com/forum.php?mod=forumdisplay&fid=65
这里只贴出重点操作截图,并做分析。
1) 引脚和外设功能选择。前面的新建 STM32CubeMX 工程步骤就直接省略
了,不熟悉的同学可以参考我们的文档。这里直接进入引脚编辑界面,
正如前面所说的,我们使用 TIM1 的 CH1 和 CH1N 通道控制 L298N 电机
驱动器,这里选择定时器为 PWM 模式,见图 5-5。
STM32 技术开发手册
www.ing10bbs.com
图 5-5 引脚和外设配置
这里不必使能主从模式和触发源,定时器时钟源选择内部时钟即可,定时器
工作模式选择为 PWM 模式。定时器通道引脚分别为:CH1—PA8,CH1N—
PB13。
2) GPIO 配置。定时器通道引脚必须设置为复用功能引脚,这里选择复用功
能推挽输出模式,引脚速度设置为高级别,见图 5-6。
STM32 技术开发手册
www.ing10bbs.com
图 5-6 引脚配置
3) 定时器配置。定时器时钟可以在时钟树界面配置,默认已经配置为 72MHz,
这里就不再列举出来。定时器参数配置界面:设置定时器预分频器为 1,
向上计数模式,自动重装值为 900,内部时钟不分频,重复计数寄存器为
0。触发输出、刹车和死区时间这两部分内容在本实验中没有用到,无需
配置。配置定时器的 PWM 为 PWM 模式 1,通道脉冲数为 200,使能 CH
和 CHN 输出,见图 5-7。
STM32 技术开发手册
www.ing10bbs.com
图 5-7 定时器配置
4) 定时器中断配置。实际上,这里不需要使用任何定时器中断。其他选项
界面没有贴出来的,一般都是采用默认设置即可,见图 5-8,这里的默认
设置请参考《硬石 YS-F1Pro 开发板开发手册(HAL 库版本).pdf》中关于
STM32CubeMX 软件使用的相关章节。
图 5-8 定时器中断配置
STM32 技术开发手册
www.ing10bbs.com
5.4
减速电机旋转驱动编程流程
1) 初始化配置定时器通道引脚:复用推挽输出、高速模式;
2) 配置定时器基本环境:预分频器、计数方向、自动重装寄存器等等;
3) 配置定时器时钟源:内部总线时钟;
4) 不使用定时器主从模式以及刹车和死区时间;
5) 定时器比较输出模式配置:PWM 模式 1,脉冲计数值(占空比)等等。
6) 启动定时器;
7) 启动定时器 PWM 通道输出;
8) 启动定时器 PWM 互补通道输出;
9) 修改定时器比较值从而改变占空比。
5.5
减速电机旋转驱动代码分析
限于篇幅问题,不会把例程所有代码贴出分析,只挑重点程序段分析。具体
的工程代码可以看我们对应的例程。
bsp_L298N.h 文件内容
bsp_L298N.h 文件内容存放了有关 L298N 电机驱动模块相关联的引脚定义以
及驱动工程里边的定时器及其参数的选择。
代码 5-1 减速电机控制相关宏定义
01 #define L298N_TIMx
TIM1
02 #define L298N_TIM_RCC_CLK_ENABLE()
__HAL_RCC_TIM1_CLK_ENABLE()
03 #define L298N_TIM_RCC_CLK_DISABLE()
__HAL_RCC_TIM1_CLK_DISABLE()
04
05 // 输出 PWM 脉冲给电机控制器的的 IN 引脚
06 // CH1 和 CH1N 两个引脚配套使用
07 // 如果电机接在驱动器的 OUT1 和 OUT2 端子上
08 // CH1 和 CH1N 对应接在 IN1 和 IN2
09 // 如果电机接在驱动器的 OUT3 和 OUT4 端子上
10 // CH1 和 CH1N 对应接在 IN3 和 IN4
11 #define L298N_TIM_CH1_GPIO_CLK_ENABLE()
__HAL_RCC_GPIOA_CLK_ENABLE()
12 #define L298N_TIM_CH1_PORT
GPIOA
13 #define L298N_TIM_CH1_PIN
GPIO_PIN_8
14 #define L298N_TIM_CH1N_GPIO_CLK_ENABLE()
__HAL_RCC_GPIOB_CLK_ENABLE()
15 #define L298N_TIM_CH1N_PORT
GPIOB
16 #define L298N_TIM_CH1N_PIN
GPIO_PIN_13
17
STM32 技术开发手册
www.ing10bbs.com
18 // 定义定时器预分频,定时器实际时钟频率为:72MHz/(L298N_TIMx_PRESCALER+1)
19 #define L298N_TIM_PRESCALER
1
// 实际时钟频率为:36MHz
20
21 // 定义定时器周期,PWM 频率为:72MHz/(L298N_TIMx_PRESCALER+1)/(L298N_TIM_PERIOD+1)
22 #define L298N_TIM_PERIOD
900 // PWM 频率为 36MHz/(900+1)=40KHz
23
24 // 定义高级定时器重复计数寄存器值
25 //实际 PWM 频率为:72MHz/(L298N_TIMx_PRESCALER+1)/(L298N_TIM_PERIOD+1)/
(L298N_TIM_REPETITIONCOUNTER+1)
26 #define L298N_TIM_REPETITIONCOUNTER
0
这里选择使用 TIM1 作为生成 PWM 的定时器,当然,这里是可以修改成其
他定时器的,只是后面对应的相关参数必须跟着一起修改。
定时器预分频设置为 1,定时器周期设置为 900,重复计数器为 0 即可。
bsp_L298N.c 文件内容
bsp_L298N.c 文件存放了 L298N 驱动的实现函数,主要是配置定时器输出
PWM 波形。
代码 5-2 L298N_GPIO_Init 函数
01 static void L298N_GPIO_Init(void)
02 {
03
GPIO_InitTypeDef GPIO_InitStruct;
04
05
/* 引脚端口时钟使能 */
06
L298N_TIM_CH1_GPIO_CLK_ENABLE();
07
L298N_TIM_CH1N_GPIO_CLK_ENABLE();
08
09
/* L298N 输出脉冲控制引脚 IO 初始化 */
10
GPIO_InitStruct.Pin = L298N_TIM_CH1_PIN;
11
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
12
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
13
HAL_GPIO_Init(L298N_TIM_CH1_PORT, &GPIO_InitStruct);
14
15
GPIO_InitStruct.Pin = L298N_TIM_CH1N_PIN;
16
HAL_GPIO_Init(L298N_TIM_CH1N_PORT, &GPIO_InitStruct);
17 }
L298N_GPIO_Init 函数配置定时器通道和互补通道引脚,设置为复用推挽输
出模式。
代码 5-3 L298N_TIMx_Init 函数
01 void L298N_TIMx_Init(void)
02 {
03
TIM_ClockConfigTypeDef sClockSourceConfig;
// 定时器时钟
04
TIM_MasterConfigTypeDef sMasterConfig;
// 定时器主模式配置
05
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;
// 刹车和死区时间配置
06
TIM_OC_InitTypeDef sConfigOC;
// 定时器通道比较输出
07
08
/* 定时器基本环境配置 */
09
htimx_L298N.Instance = L298N_TIMx;
// 定时器编号
STM32 技术开发手册
www.ing10bbs.com
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 }
htimx_L298N.Init.Prescaler = L298N_TIM_PRESCALER;
// 定时器预分频器
htimx_L298N.Init.CounterMode = TIM_COUNTERMODE_UP;
// 计数方向:向上计数
htimx_L298N.Init.Period = L298N_TIM_PERIOD;
// 定时器周期
htimx_L298N.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
// 时钟分频
htimx_L298N.Init.RepetitionCounter = L298N_TIM_REPETITIONCOUNTER; // 重复计数器
HAL_TIM_Base_Init(&htimx_L298N);
/* 定时器时钟源配置 */
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
HAL_TIM_ConfigClockSource(&htimx_L298N, &sClockSourceConfig);
// 使用内部时钟源
/* 初始化定时器比较输出环境 */
HAL_TIM_PWM_Init(&htimx_L298N);
/* 定时器主输出模式 */
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htimx_L298N, &sMasterConfig);
/* 刹车和死区时间配置 */
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 0;
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
HAL_TIMEx_ConfigBreakDeadTime(&htimx_L298N, &sBreakDeadTimeConfig);
/* 定时器比较输出配置 */
sConfigOC.OCMode = TIM_OCMODE_PWM1;
// 比较输出模式:反转输出
sConfigOC.Pulse = PWM_Duty;
// 占空比
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
// 输出极性
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
// 互补通道输出极性
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
// 快速模式
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
// 空闲电平
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
// 互补通道空闲电平
HAL_TIM_PWM_ConfigChannel(&htimx_L298N, &sConfigOC, TIM_CHANNEL_1);
/* L298N 相关 GPIO 初始化配置 */
L298N_GPIO_Init();
L298N_TIMx_Init 函数用于配置定时器工作环境。首先是定时器基本工作环
境配置,设置定时器编号、预分频器、计数方向、周期等等信号,然后调用
HAL_TIM_Base_Init 函数完成定时器基本环境配置。
接下来,调用 HAL_TIM_ConfigClockSource 函数设置定时器使用内部时钟源。
HAL_TIM_PWM_Init 函数用于初始化定时器 PWM 环境。
定时器主模式和刹车和死区时间这里并没有用到(当然也是可以用上的)保
持默认配置。
STM32 技术开发手册
www.ing10bbs.com
定时器输出比较配置为 PWM 模式 1,这里脉冲计数用于计算占空比大小,
然后根据需要配置通道极性和空闲电平,最后调用 HAL_TIM_PWM_ConfigChannel
函数完成定时器输出比较模式配置。
最后调用 L298N_GPIO_Init 函数初始化定时器通道引脚。
main.c 文件内容
代码 5-4 main 函数
01 int main(void)
02 {
03
/* 复位所有外设,初始化 Flash 接口和系统滴答定时器 */
04
HAL_Init();
05
/* 配置系统时钟 */
06
SystemClock_Config();
07
08
/* 高级控制定时器初始化并配置 PWM 输出功能 */
09
L298N_TIMx_Init();
10
/* 启动定时器 */
11
HAL_TIM_Base_Start(&htimx_L298N);
12
13
/* 启动定时器通道和互补通道 PWM 输出 */
14
HAL_TIM_PWM_Start(&htimx_L298N,TIM_CHANNEL_1);
15
HAL_TIMEx_PWMN_Start(&htimx_L298N,TIM_CHANNEL_1);
16
17
/* 无限循环 */
18
while (1) {
19
__HAL_TIM_SET_COMPARE(&htimx_L298N,TIM_CHANNEL_1,100);
20
HAL_Delay(4000);
21
__HAL_TIM_SET_COMPARE(&htimx_L298N,TIM_CHANNEL_1,450);
22
HAL_Delay(100);
23
__HAL_TIM_SET_COMPARE(&htimx_L298N,TIM_CHANNEL_1,800);
24
HAL_Delay(4000);
25
__HAL_TIM_SET_COMPARE(&htimx_L298N,TIM_CHANNEL_1,450);
26
HAL_Delay(100);
27
}
28 }
HAL_Init 函数和 SystemClock_Config 函数是初始化 HAL 库和配置系统时钟,
这在《硬石 YS-F1Pro 开发板开发手册(HAL 库版本).pdf》文档中已经有详细讲
解过,这里就不再展开。
L298N_TIMx_Init 函数定义在 bsp_L298N.c 文件中,用于初始化定时器引脚和
配置定时器工作环境。
HAL_TIM_Base_Start 函数是 HAL 库函数,用于启动定时器基本环境,使能定
时器。
STM32 技术开发手册
www.ing10bbs.com
HAL_TIM_PWM_Start 函数用于启动定时器通道 PWM 输出,在调用该函数后
PWM 信号才会在通道引脚输出。它有两个形参,第一个是定时器句柄指针,第
二个是指定通道。
HAL_TIMEx_PWMN_Start 函数用于启动定时器互补通道 PWM 输出,在调用
该函数后 PWM 信号才会在互补通道引脚输出。它有两个形参,第一个是定时器
句柄指针,第二个是指定通道。
调用上面两个函数后在引脚上就有 PWM 信号输出,此时使用示波器可以检
测到的引脚波形如图 5-9:
图 5-9 PWM 信号实际波形
其中绿色的波形图是 CH1 通道的,红色的波形图是 CH1N 通道的,可以看到
这两个波形刚好完全相反的,结合表格 4-2 内容我们可以清楚知道这样的波形
是符合 H 桥控制信号要求的。
我们先分析绿色线的一个周期波形(从图像中间开始),它是先保持一段高
电平,然后变成低电平,这里设定高电平时间为 t,总的周期时间为 T,这样绿
色线的占空比为:t/T*100%。对应到我们的程序,在 L298N_TIMx_Init 函数中我
们设置定时器周期为 900(即宏定义 L298N_TIM_PRESCALER 值),就是这里的 T;
设置比较输出的脉冲数为 200(即 PWM_Duty 变量值),就是这里的 t,所以实际
上绿色线的 PWM 波形占空比为:200/900*100%=22.2%。对应的,红色线的 PWM
波形占空比就为:1-200/900*100%=77.8%。
STM32 技术开发手册
www.ing10bbs.com
根据前面了介绍,红色线的占空比要比绿色线的占空比大,那么 IN2 就要比
IN1 保持高电平实际更长,这样整体的效果就是电机反转。但是,这个反转的速
度相对来说是比较慢的。
如果还不理解,我们举个比较极端的情况,假设绿色线的占空比为 0,对应
的红色线占空比为 1,那么很明显 IN1 为 0,IN2 为 1,这样是全速反转。这就有
别与上面绿色线的占空比不为 0 的情况了,这两个的区别反映到电机上就是反转
的速度是不同的。因为绿色线的占空比为 0 时,可以认为电机两端的电压就是12V 的,这里的负号表示电机是反转的。而对应上面占空比为 22.2%的情况:此
时电机两端电压为:
12V*(22.2%-77.8%)=-6.672V
这里的负号表示电机是反转的。
所以,当占空比为 50%(对应定时器通道比较值为 450)时,电机是停止转
动的。当设置定时器通道比较值为:0~449 时,电机是反转的;当设置定时器通
道比较值为:451~900 时,电机是正转的。实际上,如果真的设置通道比较值为
400,电机是不会反转的,实际上电机是不转的,大家可以根据上面计算方法去
计算得到此时电机两端的电压,该值是比较小的,而电机一般都有个最小启动电
压(堵转电压),小于该电压电机是不会转动的,并且电机长时间工作在此状态
下是很容易烧毁的。
上面是这个定时器 PWM 信号和电机正反转关系的解读过程,这个需要大家
理解明白,如果明白了上面这些理论,后续就非常好搞了。
main 函数,最后,在无限循环中我们调用__HAL_TIM_SET_COMPARE 函数设
置通道的比较数值,实际就修改占空比的大小,进而控制电机速度。
实验操作与现象
根据上面介绍完成电机与 L298N 驱动器连接(实际上就两根线),然后连接
L298N 驱动器和 YS-F1Pro 开发板(实际上总共就三根线,注意需要连接 GND),
然后为 L298N 驱动器提供 12V 电源。使用开发板配套的 MINI USB 线连接到开发
板标示“调试串口”字样的 MIMI USB 接口为开发板提供电源。下载完程序之后,
开发板持续 PWM 脉冲给 L298N 驱动器,电机持续转动。
STM32 技术开发手册
www.ing10bbs.com
5.6
三轴直流减速电机旋转控制实验
上一小节已经实现了一个直流减速电机的旋转控制,实际应用中可能需要同
时控制多个电机,比如比较典型的智能小车就需要两个电机控制,现在我们介绍
三个电机的控制,通过这个实验后想要控制智能小车就非常容易了。
从我们开发板上的原理图,见图 5-3,我们预留的电机驱动接口就有 TIM1
的 3 个通道及其互补通道,现在我们就利用这六个引脚实现三轴电机的控制。
5.6.1 STM32CubeMX 生成工程
1) 功能引脚选择,见图 5-10。这里定时器选择 TIM1,配置使能三个通道及
其互补通道 PWM 输出。
图 5-10 功能引脚配置
2) GPIO 模式配置,见图 5-11。定时器引脚设置为复用推挽输出模式。
STM32 技术开发手册
www.ing10bbs.com
图 5-11 定时器通道引脚模式配置
3) 定时器参数配置,见图 5-12。配置定时器基本工作环境以及比较输出通
道参数。
图 5-12 定时器参数配置
STM32 技术开发手册
www.ing10bbs.com
5.6.2 三轴减速电机旋转控制代码分析
bsp_L298N.h 文件内容
bsp_L298N.h 文件内容存放了有关 L298N 电机驱动模块相关联的引脚定义以
及驱动工程里边的定时器及其参数的选择。
代码 5-5 减速电机控制相关宏定义
01 #define L298N_TIMx
TIM1
02 #define L298N_TIM_RCC_CLK_ENABLE()
__HAL_RCC_TIM1_CLK_ENABLE()
03 #define L298N_TIM_RCC_CLK_DISABLE()
__HAL_RCC_TIM1_CLK_DISABLE()
04
05 // 输出 PWM 脉冲给电机控制器的的 IN 引脚
06 // CH1 和 CH1N 两个引脚配套使用
07 // 如果电机接在驱动器的 OUT1 和 OUT2 端子上
08 // CH1 和 CH1N 对应接在 IN1 和 IN2
09 // 如果电机接在驱动器的 OUT3 和 OUT4 端子上
10 // CH1 和 CH1N 对应接在 IN3 和 IN4
11 /* 第 1 轴 */
12 #define L298N_TIM_CH1_GPIO_CLK_ENABLE()
__HAL_RCC_GPIOA_CLK_ENABLE()
13 #define L298N_TIM_CH1_PORT
GPIOA
14 #define L298N_TIM_CH1_PIN
GPIO_PIN_8
15 #define L298N_TIM_CH1N_GPIO_CLK_ENABLE()
__HAL_RCC_GPIOB_CLK_ENABLE()
16 #define L298N_TIM_CH1N_PORT
GPIOB
17 #define L298N_TIM_CH1N_PIN
GPIO_PIN_13
18
19 /* 第 2 轴 */
20 #define L298N_TIM_CH2_GPIO_CLK_ENABLE()
__HAL_RCC_GPIOA_CLK_ENABLE()
21 #define L298N_TIM_CH2_PORT
GPIOA
22 #define L298N_TIM_CH2_PIN
GPIO_PIN_9
23 #define L298N_TIM_CH2N_GPIO_CLK_ENABLE()
__HAL_RCC_GPIOB_CLK_ENABLE()
24 #define L298N_TIM_CH2N_PORT
GPIOB
25 #define L298N_TIM_CH2N_PIN
GPIO_PIN_14
26
27 /* 第 3 轴 */
28 #define L298N_TIM_CH3_GPIO_CLK_ENABLE()
__HAL_RCC_GPIOA_CLK_ENABLE()
29 #define L298N_TIM_CH3_PORT
GPIOA
30 #define L298N_TIM_CH3_PIN
GPIO_PIN_10
31 #define L298N_TIM_CH3N_GPIO_CLK_ENABLE()
__HAL_RCC_GPIOB_CLK_ENABLE()
32 #define L298N_TIM_CH3N_PORT
GPIOB
33 #define L298N_TIM_CH3N_PIN
GPIO_PIN_15
34
35
36 // 定义定时器预分频,定时器实际时钟频率为:72MHz/(L298N_TIMx_PRESCALER+1)
37 #define L298N_TIM_PRESCALER
1
// 实际时钟频率为:36MHz
38
39 // 定义定时器周期,PWM 频率为:72MHz/(L298N_TIMx_PRESCALER+1)/(L298N_TIM_PERIOD+1)
40 #define L298N_TIM_PERIOD
900 // PWM 频率为 36MHz/(900+1)=40KHz
41
42 // 定义高级定时器重复计数寄存器值
43 //实际 PWM 频率为:72MHz/(L298N_TIMx_PRESCALER+1)/(L298N_TIM_PERIOD+1)/
(L298N_TIM_REPETITIONCOUNTER+1)
44 #define L298N_TIM_REPETITIONCOUNTER
0
这里不再啰嗦,参考代码 5-1 分析就好。
STM32 技术开发手册
www.ing10bbs.com
代码 5-6 L298N_GPIO_Init 函数
01 static void L298N_GPIO_Init(void)
02 {
03
GPIO_InitTypeDef GPIO_InitStruct;
04
05
/* 引脚端口时钟使能 */
06
L298N_TIM_CH1_GPIO_CLK_ENABLE();
07
L298N_TIM_CH1N_GPIO_CLK_ENABLE();
08
09
/* 第 1 轴 */
10
/* L298N 输出脉冲控制引脚 IO 初始化 */
11
GPIO_InitStruct.Pin = L298N_TIM_CH1_PIN;
12
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
13
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
14
HAL_GPIO_Init(L298N_TIM_CH1_PORT, &GPIO_InitStruct);
15
16
GPIO_InitStruct.Pin = L298N_TIM_CH1N_PIN;
17
HAL_GPIO_Init(L298N_TIM_CH1N_PORT, &GPIO_InitStruct);
18
19
/* 第 2 轴 */
20
/* L298N 输出脉冲控制引脚 IO 初始化 */
21
GPIO_InitStruct.Pin = L298N_TIM_CH2_PIN;
22
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
23
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
24
HAL_GPIO_Init(L298N_TIM_CH2_PORT, &GPIO_InitStruct);
25
26
GPIO_InitStruct.Pin = L298N_TIM_CH2N_PIN;
27
HAL_GPIO_Init(L298N_TIM_CH2N_PORT, &GPIO_InitStruct);
28
29
/* 第 3 轴 */
30
/* L298N 输出脉冲控制引脚 IO 初始化 */
31
GPIO_InitStruct.Pin = L298N_TIM_CH3_PIN;
32
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
33
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
34
HAL_GPIO_Init(L298N_TIM_CH3_PORT, &GPIO_InitStruct);
35
36
GPIO_InitStruct.Pin = L298N_TIM_CH3N_PIN;
37
HAL_GPIO_Init(L298N_TIM_CH3N_PORT, &GPIO_InitStruct);
38 }
参考代码 5-2 分析,这里添加了 CH2 和 CH3 及其互补通道引脚初始化。
代码 5-7 L298N_TIMx_Init 函数
01 void L298N_TIMx_Init(void)
02 {
03
TIM_ClockConfigTypeDef sClockSourceConfig;
// 定时器时钟
04
TIM_MasterConfigTypeDef sMasterConfig;
// 定时器主模式配置
05
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;
// 刹车和死区时间配置
06
TIM_OC_InitTypeDef sConfigOC;
// 定时器通道比较输出
07
08
/* 定时器基本环境配置 */
09
htimx_L298N.Instance = L298N_TIMx;
// 定时器编号
10
htimx_L298N.Init.Prescaler = L298N_TIM_PRESCALER;
// 定时器预分频器
11
htimx_L298N.Init.CounterMode = TIM_COUNTERMODE_UP;
// 计数方向:向上
计数
12
htimx_L298N.Init.Period = L298N_TIM_PERIOD;
// 定时器周期
13
htimx_L298N.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
// 时钟分频
14
htimx_L298N.Init.RepetitionCounter = L298N_TIM_REPETITIONCOUNTER; // 重复计数器
STM32 技术开发手册
www.ing10bbs.com
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 }
HAL_TIM_Base_Init(&htimx_L298N);
/* 定时器时钟源配置 */
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
HAL_TIM_ConfigClockSource(&htimx_L298N, &sClockSourceConfig);
// 使用内部时钟源
/* 初始化定时器比较输出环境 */
HAL_TIM_PWM_Init(&htimx_L298N);
/* 定时器主输出模式 */
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htimx_L298N, &sMasterConfig);
/* 刹车和死区时间配置 */
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 0;
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
HAL_TIMEx_ConfigBreakDeadTime(&htimx_L298N, &sBreakDeadTimeConfig);
/* 第 1 轴 */
/* 定时器比较输出配置 */
sConfigOC.OCMode = TIM_OCMODE_PWM1;
// 比较输出模式:反转输出
sConfigOC.Pulse = PWM_Duty;
// 占空比
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
// 输出极性
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
// 互补通道输出极性
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
// 快速模式
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
// 空闲电平
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
// 互补通道空闲电平
HAL_TIM_PWM_ConfigChannel(&htimx_L298N, &sConfigOC, TIM_CHANNEL_1);
/* 第 2 轴 */
HAL_TIM_PWM_ConfigChannel(&htimx_L298N, &sConfigOC, TIM_CHANNEL_2);
/* 第 3 轴 */
HAL_TIM_PWM_ConfigChannel(&htimx_L298N, &sConfigOC, TIM_CHANNEL_3);
/* L298N 相关 GPIO 初始化配置 */
L298N_GPIO_Init();
参考代码 5-3 分析,这里只是添加了调用 HAL_TIM_PWM_ConfigChannel 函
数初始化配置 CH2 和 CH3 的输出比较模式。
代码 5-8 L298N_DCMOTOR_Contrl 函数
01 /**
02
* 函数功能: L298N 直流电机控制
03
* 输入参数: number:电机编号,支持三个电机驱动
04
*
参数:1:对应高级定时器通道 1 和互补通道 1
05
*
2:对应高级定时器通道 2 和互补通道 2
06
*
3:对应高级定时器通道 3 和互补通道 3
07
*
4:三个电机都停机
08
*
direction:电机方向控制
09
*
参数:0:目标电机停机
10
*
1:正转
STM32 技术开发手册
www.ing10bbs.com
11
*
2:反转
12
*
speed:电机速度调节
13
*
参数:0 - 900 :值越大,速度越快
14
* 返 回 值: 无
15
* 说
明:无
16
*/
17 void L298N_DCMOTOR_Contrl(uint8_t number,uint8_t direction,uint16_t speed)
18 {
19
switch (number) {
20
case 1:
21
if (direction==1) {
22
HAL_TIM_PWM_Start(&htimx_L298N,TIM_CHANNEL_1);
23
HAL_TIMEx_PWMN_Stop(&htimx_L298N,TIM_CHANNEL_1);
24
} else if (direction==2) {
25
HAL_TIM_PWM_Stop(&htimx_L298N,TIM_CHANNEL_1);
26
HAL_TIMEx_PWMN_Start(&htimx_L298N,TIM_CHANNEL_1);
27
} else {
28
HAL_TIM_PWM_Stop(&htimx_L298N,TIM_CHANNEL_1);
29
HAL_TIMEx_PWMN_Stop(&htimx_L298N,TIM_CHANNEL_1);
30
}
31
__HAL_TIM_SET_COMPARE(&htimx_L298N,TIM_CHANNEL_1,speed);;
32
break;
33
case 2:
34
if (direction==1) {
35
HAL_TIM_PWM_Start(&htimx_L298N,TIM_CHANNEL_2);
36
HAL_TIMEx_PWMN_Stop(&htimx_L298N,TIM_CHANNEL_2);
37
} else if (direction==2) {
38
HAL_TIM_PWM_Stop(&htimx_L298N,TIM_CHANNEL_2);
39
HAL_TIMEx_PWMN_Start(&htimx_L298N,TIM_CHANNEL_2);
40
} else {
41
HAL_TIM_PWM_Stop(&htimx_L298N,TIM_CHANNEL_2);
42
HAL_TIMEx_PWMN_Stop(&htimx_L298N,TIM_CHANNEL_2);
43
}
44
__HAL_TIM_SET_COMPARE(&htimx_L298N,TIM_CHANNEL_2,speed);;
45
break;
46
case 3:
47
if (direction==1) {
48
HAL_TIM_PWM_Start(&htimx_L298N,TIM_CHANNEL_3);
49
HAL_TIMEx_PWMN_Stop(&htimx_L298N,TIM_CHANNEL_3);
50
} else if (direction==2) {
51
HAL_TIM_PWM_Stop(&htimx_L298N,TIM_CHANNEL_3);
52
HAL_TIMEx_PWMN_Start(&htimx_L298N,TIM_CHANNEL_3);
53
} else {
54
HAL_TIM_PWM_Stop(&htimx_L298N,TIM_CHANNEL_3);
55
HAL_TIMEx_PWMN_Stop(&htimx_L298N,TIM_CHANNEL_3);
56
}
57
__HAL_TIM_SET_COMPARE(&htimx_L298N,TIM_CHANNEL_3,speed);;
58
break;
59
case 4:
// 停止输出 PWM,停机
60
HAL_TIM_PWM_Stop(&htimx_L298N,TIM_CHANNEL_1);
61
HAL_TIMEx_PWMN_Stop(&htimx_L298N,TIM_CHANNEL_1);
62
HAL_TIM_PWM_Stop(&htimx_L298N,TIM_CHANNEL_2);
63
HAL_TIMEx_PWMN_Stop(&htimx_L298N,TIM_CHANNEL_2);
64
HAL_TIM_PWM_Stop(&htimx_L298N,TIM_CHANNEL_3);
65
HAL_TIMEx_PWMN_Stop(&htimx_L298N,TIM_CHANNEL_3);
66
break;
67
}
68 }
L298N_DCMOTOR_Contrl 函数用于 L298N 直流电机控制,有三个形参,具体
意义看函数前面的注释说明。
STM32 技术开发手册
www.ing10bbs.com
L298N_DCMOTOR_Contrl 函 数 多 处 调 用 了 HAL_TIM_PWM_Start 、
HAL_TIM_PWM_Stop、HAL_TIMEx_PWMN_Start 和 HAL_TIMEx_PWMN_Stop 函数,
这四个函数分别对应定时器通道的 PWM 启动、停止、互补通道的启动、停止,
在上个实验中已有介绍过。
还有另外一个函数:__HAL_TIM_SET_COMPARE,用于设置通道比较输出值。
这里特别注意,L298N_DCMOTOR_Contrl 函数调速方式与上个实验编程思想
有所不同,这里是直接控制通道或者互补通道引脚停止输出 PWM,而调节与其
互补通道占空比来调速的。所以,这里占空比为 0 时停止转动,占空比为 100%
时电机全速运行。
main.c 文件内容
代码 5-9 main 函数
01 int main(void)
02 {
03
uint8_t key1_count=1;
04
05
/* 复位所有外设,初始化 Flash 接口和系统滴答定时器 */
06
HAL_Init();
07
/* 配置系统时钟 */
08
SystemClock_Config();
09
10
KEY_GPIO_Init();
11
12
/* 高级控制定时器初始化并配置 PWM 输出功能 */
13
L298N_TIMx_Init();
14
/* 启动定时器 */
15
HAL_TIM_Base_Start(&htimx_L298N);
16
17
/* 启动定时器通道和互补通道 PWM 输出 */
18
L298N_DCMOTOR_Contrl(1,1,400);
19
L298N_DCMOTOR_Contrl(2,1,400);
20
L298N_DCMOTOR_Contrl(3,1,400);
21
22
/* 无限循环 */
23
while (1) {
24
if (KEY1_StateRead()==KEY_DOWN) { // 功能选择
25
key1_count++;
26
if (key1_count==6)
27
key1_count=1;
28
}
29
if (KEY2_StateRead()==KEY_DOWN) { // 功能调节
30
switch (key1_count) {
31
case 1:
// 速度调节:快速
32
PWM_Duty=800;
33
L298N_DCMOTOR_Contrl(1,dir,PWM_Duty);
34
L298N_DCMOTOR_Contrl(2,dir,PWM_Duty);
35
L298N_DCMOTOR_Contrl(3,dir,PWM_Duty);
36
break;
STM32 技术开发手册
www.ing10bbs.com
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 }
case 2:
// 速度调节:慢速
PWM_Duty=300;
L298N_DCMOTOR_Contrl(1,dir,PWM_Duty);
L298N_DCMOTOR_Contrl(2,dir,PWM_Duty);
L298N_DCMOTOR_Contrl(3,dir,PWM_Duty);
break;
case 3:
// 方向调节:正转
dir=1;
L298N_DCMOTOR_Contrl(1,dir,PWM_Duty);
L298N_DCMOTOR_Contrl(2,dir,PWM_Duty);
L298N_DCMOTOR_Contrl(3,dir,PWM_Duty);
break;
case 4:
// 方向调节:反转
dir=2;
L298N_DCMOTOR_Contrl(1,dir,PWM_Duty);
L298N_DCMOTOR_Contrl(2,dir,PWM_Duty);
L298N_DCMOTOR_Contrl(3,dir,PWM_Duty);
break;
case 5:
// 停止输出
L298N_DCMOTOR_Contrl(4,0,PWM_Duty);
break;
}
}
}
HAL_Init 函数和 SystemClock_Config 函数是初始化 HAL 库和配置系统时钟,
这在《硬石 YS-F1Pro 开发板开发手册(HAL 库版本).pdf》文档中已经有详细讲
解过,这里就不再展开。
KEY_GPIO_Init 函数定义在 bsp_key.c 文件中,用于初始化板载 2 个独立按键
引脚配置。
L298N_TIMx_Init 函数定义在 bsp_L298N.c 文件中,用于初始化定时器引脚和
配置定时器工作环境。
HAL_TIM_Base_Start 函数启动定时器。
L298N_DCMOTOR_Contrl 函数定义在 bsp_L298N.c 文件中,在上面也已经分
析了。在调用这三个函数后,用示波器可以测试到此时的波形图见图 5-13:
STM32 技术开发手册
www.ing10bbs.com
图 5-13 三轴减速电机 PWM 波形图
在无限循环中,不断扫描两个按键状态,按键 1 做为功能选择切换,按键 2
做为功能执行,其功能执行都是直接调用 L298N_DCMOTOR_Contrl 函数实现的。
实验操作与现象
根据上面介绍完成两个电机与一个 L298N 驱动器连接(实际上就 4 根线),
然后连接 L298N 驱动器和 YS-F1Pro 开发板(实际上总共就 5 根线,注意需要连
接 GND),然后为 L298N 驱动器提供 12V 电源,另外同样的方法连接另外一个电
机—L298N 驱动器—开发板。使用开发板配套的 MINI USB 线连接到开发板标示
“调试串口”字样的 MIMI USB 接口为开发板提供电源。下载完程序之后,开发
板持续 PWM 脉冲给 L298N 驱动器,电机持续转动。按下按键 1 切换功能,按键
2 调节电机旋转。
STM32 技术开发手册
www.ing10bbs.com
第6章 编码器测速
6.1
编码器
编码器(也称光电编码器),见图 6-1,是将信号或数据进行编制、转换为可
用以通讯、传输和存储的信号形式的设备。编码器把角位移或直线位移转换成电
信号,前者称为码盘,后者称为码尺。他是工业中常用的电机定位设备,可以精
确的测试电机的角位移和旋转位置。光电编码器是集光、机、电技术于一体的数
字化传感器,可以高精度测量被测物的转角或直线位移量。编码器最直接的作用
就是可以测量位移,知道位移了就可以计算得到速度了。
图 6-1 编码器
STM32 技术开发手册
www.ing10bbs.com
6.2
编码器分类及原理
按照工作原理编码器可分为增量式和绝对式两类。增量式编码器是将位移转
换成周期性的电信号,再把这个电信号转变成计数脉冲,用脉冲的个数表示位移
的大小。绝对式编码器的每一个位置对应一个确定的数字码,因此它的示值只与
测量的起始和终止位置有关,而与测量的中间过程无关。
编码器工作原理模型,见图 6-2。
图 6-2 编码器工作原理模型
1. 增量式编码器
增量式编码器通常有 3 个输出口,分别为 A 相、B 相、Z 相(有些也标称为
C 相)输出,A 相与 B 相之间相互延迟 1/4 周期(90 度)的脉冲输出,根据延迟
关系可以区别正反转,而且通过取 A 相、B 相的上升和下降沿可以进行 2 或 4 倍
频;Z 相为单圈脉冲,即每圈发出一个脉冲。
增量测量法的光栅由周期性栅条组成。位置信息通过计算自某点开始的增量
数(测量步距数)获得。由于必须用绝对参考点确定位置值,因此圆光栅码盘还有
一个参考点轨。
将位移转换成周期性的电信号,再把这个电信号转变成计数脉冲,用脉冲的
个数表示位移的大小。
由一个中心有轴的光电码盘,其上有环形通、暗的刻线,有光电发射和接收
器件读取,获得四组正弦波信号组合成 A、B、-A、-B,每个正弦波相差 90 度相位
差(相对于一个周波为 360 度),将 A、B 信号反向,叠加在 A、B 两相上,可增
强稳定信号;另每转输出一个 Z 相脉冲以代表零位参考位,见图 6-3。
STM32 技术开发手册
www.ing10bbs.com
图 6-3 增量式编码器
2. 绝对式编码器
绝对式编码器就是对应一圈,每个基准的角度发出一个唯一与该角度对应二
进制的数值,通过外部记圈器件可以进行多个位置的记录和测量。
通过读取编码盘上的二进制的编码信息来表示绝对位置信息的。
编码盘是按照一定的编码形式制成的圆盘。如中 a)是二进制的编码盘,b)是
格雷码编制,图中空白部分是透光的,用“0”来表示;涂黑的部分是不透光的,
用“1”来表示。通常将组成编码的圈称为码道,每个码道表示二进制数的一位,
其中最外侧的是最低位,最里侧的是最高位。如果编码盘有 4 个码道,可形成 16
个二进制数,因此就将圆盘划分 16 个扇区,每个扇区对应一个 4 位二进制数,
如 0000、0001、…、1111,见图 6-4。
图 6-4 绝对式编码器
格雷码说明:
STM32 技术开发手册
www.ing10bbs.com
由于制造和安装精度的影响,码盘回转在交替码段过程中会产生读数误差,
该误差可用格雷码盘形式避免,该盘特点:任意相邻的两个代码间只有一位代码
变化。
格雷码转二进制:
Bn = B(n+1)异或 Gn
编码器通电时就可立即得到位置值并随时供后续信号处理电子电路读取。无
需移动轴执行参考点回零操作。绝对位置信息来自圆光栅码盘,它由一系列绝对
码组成。单独的增量刻轨信号通过细分生成位置值,同时也能生成供选用的增量
信号。
单圈编码器的绝对位置值信息每转一圈重复一次。多圈编码器也能区分每圈
的位置值。
3. 两者区别
它们存着最大的区别:在增量编码器的情况下,位置是从零位标记开始计算
的脉冲数量确定的,而绝对型编码器的位置是由输出代码的读数确定的。在一圈
里,每个位置的输出代码的读数是唯一的,因此当电源断开时,绝对型编码器并
不与实际的位置分离。如果电源再次接通,那么位置读数仍是当前的、有效的,
不像增量编码器那样,必须去寻找零位标记。
4. 编码器参数
 分辨率:编码器的轴每转一圈所输出的脉冲数。编码器以每旋转 360 度提供
多少的通或暗刻线称为分辨率,也称解析分度、或直接称多少线,一般在每
转分度 5~10000 线。
 最大响应频率:编码器在 1 秒钟内能响应的最大脉冲数。其公式为:
最高响应频率(Hz) = 编码器分辨率 × 轴的转速(r/min)/60
另称 PPS。
 最大转速:是指编码器机械系统能够承受的最高转速。
 绝对编码器信号传输方式:并行、串行输出或总线型输出。输出电路与增量
编码器相似,有集电极开路 PNP、NPN 型、差分驱动、推挽式。
STM32 技术开发手册
www.ing10bbs.com
6.3
增量式编码器脉冲输入模式
由于 A、B 两相相差 90 度,可通过比较 A 相在前还是 B 相在前,以判别编
码器的正转与反转,通过零位脉冲,可获得编码器的零位参考位。见图 6-5。
1) 如单相联接,用于单方向计数,单方向测速。
2) A、B 两相联接,用于正反向计数、判断正反向和测速。
3) A、B、Z 三相联接,用于带参考位修正的位置测量。
4) A、A-,B、B-,Z、Z-连接,由于带有对称负信号的连接,电流对于电缆
贡献的电磁场为 0,衰减最小,抗干扰最佳,可传输较远的距离。
图 6-5 增量式编码器脉冲
6.4
25GA370 减速电机编码器
25GA370 直流减速电机(简称 25 减速电机)上集成了一个简易的测速装置,
这个测速装置与上面讲解的结构有所不同,但它可以实现类似的功能,所以这里
也叫做编码器。25 减速电机的编码器是由一个霍尔传感器+铁氧体磁环组成的装
置。霍尔传感器是根据霍尔效应制作的一个磁场检测开关,见图 6-6,霍尔传感
器有三根引脚,一根是 VCC(一般接 5V 供电),一根是 GND(电源地),还有一
根是信号线,默认情况下该信号线是低电平的,当有磁场接近时(实际就是要求
磁场强度达到一定值后)霍尔传感器的信号线就变为高电平;如果此时把磁场移
开,信号线又变为低电平。
STM32 技术开发手册
www.ing10bbs.com
图 6-6 霍尔传感器
根据霍尔传感器的原理,我们可以设计如图 6-7 的测试结构:
图 6-7 霍尔测速的原理结构
当轴旋转时,固定在轴上面的磁环随之旋转,霍尔传感器附近产生了变化的
磁场,这样在霍尔传感器的信号引脚就可以输出高低电平的脉冲信号。
25 减速电机就集成了这样的编码器:使用了铁氧体磁环;有两个霍尔传感
器,设计时两个霍尔传感器位置与转轴的连线是相差 90 度的;直流电机轴旋转
一圈在霍尔传感器引脚有 11 个脉冲信号输出。这样可以得到一个:有 A 相、B
STM32 技术开发手册
www.ing10bbs.com
相的分辨率为 11 的编码器,同时因为安装结构问题,A 相和 B 相信号存在 90 度
的限位差。25 减速电机编码器实物图见图 6-8。
图 6-8 减速电机编码器实物图
注意,这里的磁环是固定在直流电机转轴上的,与减速电机的输出轴是不一
样的。减速电机的输出轴是经过减速齿轮变换后的,之前有介绍到该电机的减速
比为:1:34,实际精准为:1:34.02。所以,如果减速电机输出轴旋转一圈,实际
上可以检测到的编码器脉冲数量为:11*34.02=374.22 个。这在我们计算实际位
移和速度非常有用。
减速电机的引脚定义如图 6-9:
STM32 技术开发手册
www.ing10bbs.com
图 6-9 减速电机引线定义
第 2、3、4、5 引脚是编码器相关引脚。只是简单的测速可以只使用 A 相或
者 B 相,外加 3.3V 电源和地,三根线搞定。编码器信号检测一般使用 stm32 的
定时器输入捕获功能,使用一般把编码器 A 相或者 B 相引脚接入到 stm32 定时
器通道引脚,见,这里可以选择高级控制定时器或者通用定时器引脚,YS-F1Pro
开发板专门预留了 TIM3 的 CH3 和 CH4(实际是预留了 PB0 和 PB1 引脚)用于编
码器信号捕获。当然,stm32 的高级控制定时器(TIM1 或 TIM8)有一种专门用
于读取编码器信号的工作模式,允许同时接入 A 相和 B 相引线。STM32F10x 芯片
定时器通道引脚分布见图 6-10。
STM32 技术开发手册
www.ing10bbs.com
图 6-10 stm32f103zet6 定时器功能引脚
另外,在控制程序中,处理编码器数据得到速度的一般有两种方法:
T 法:计算一定量的脉冲数量所使用的时间,这个合适用于低速测量;
M 法:计算一定时间内的脉冲数量。例程中我们使用该方法测量电机旋转速
度。
6.5
STM32CubeMX 生成工程
这里只贴出重点操作截图,并做分析。
1) 引脚和外设功能选择,见图 6-11。使用到的外设有 TIM1:用于电机旋转
驱动控制;TIM3:编码器信号获取;USART1:调试串口,打印数据到调
试助手;GPIO:两个按键,用于控制调节电机速度。
STM32 技术开发手册
www.ing10bbs.com
图 6-11 引脚和外设功能选择
2) 时钟树设置,见图 6-12。通用定时器(包括 TIM3)的属于 APB1 总线,
最高频率只有 36MHz,但对于如果做为通用定时器时钟源允许 2 倍频,
得到 72MHz 时钟。
STM32 技术开发手册
www.ing10bbs.com
图 6-12 时钟树配置
3) 引脚模式配置,见图 6-13。对于按键功能引脚、TIM1 功能引脚和调试串
口功能引脚这里就不再列举出来。这里只介绍做为编码器信号检测的功
能引脚:TIM3 的通道 3,对应为 PB0 引脚(注意拔掉 JP3 跳帽:LED1),
作为输入不会功能,设置为输入模式,这里不需要上下拉功能。
STM32 技术开发手册
www.ing10bbs.com
图 6-13 编码器捕获引脚
4) 外设功能设置,见图 6-14。调试串口和用于电机旋转控制的定时器 TIM1
配置见之前相关例程。TIM3 用于捕获编码器信号,这里设置定时器预分
频为 71,可得到 1MHz 的时钟频率,输入捕获功能模式一般设置定时器
周期为 0xFFFF;设置检测编码器信号的上升沿。
STM32 技术开发手册
www.ing10bbs.com
图 6-14 编码器定时器功能设置
5) 定时器中断配置,见图 6-15。使用定时器输入捕获功能一般开启定时器
中断,可以设置使得捕获成功中断,这样可以快速反应处理事件。
图 6-15 定时器中断配置
STM32 技术开发手册
www.ing10bbs.com
6.6
减速电机编码测速编程流程
1) 初始化按键、调试串口、控制电机旋转定时器等等外设;
2) 初始化配置编码器功能定时器通道引脚;
3) 配置编码器功能定时器中断优先级;
4) 配置编码器功能定时器基本工作环境;
5) 配置编码器功能定时器使用内部时钟源;
6) 配置编码器功能定时器输入捕获功能;
7) 启动编码器功能定时器以及输入捕获中断;
8) 启动电机旋转控制定时器以及比较输出;
9) 无限循环扫描按键,处理按键调速事件;
10) 滴答定时器中断每 1s 通过串口发送一次测速结果;
6.7
减速电机编码器测速实现代码分析
限于篇幅问题,不会把例程所有代码贴出分析,只挑重点程序段分析。具体
的工程代码可以看我们对应的例程源码。
bsp_encoder.h 文件内容
bsp_encoder.h 文件内容存放了有关编码器信号检测相关联的引脚定义以及
相关定时器及其参数的选择。
代码 6-1 编码器定时器相关宏定义
01 #define ENCODER_TIMx
TIM3
02 #define ENCODER_TIM_RCC_CLK_ENABLE()
__HAL_RCC_TIM3_CLK_ENABLE()
03 #define ENCODER_TIM_RCC_CLK_DISABLE()
__HAL_RCC_TIM3_CLK_DISABLE()
04
05 #define ENCODER_TIM_CHANNELx
TIM_CHANNEL_3
// 编码器输
入捕获通道
06
07 #define ENCODER_TIM_GPIO_CLK_ENABLE()
__HAL_RCC_GPIOB_CLK_ENABLE()
// 输入捕获
通道引脚
08 #define ENCODER_TIM_PORT
GPIOB
09 #define ENCODER_TIM_PIN
GPIO_PIN_0
10
11
12 // 定义定时器预分频,定时器实际时钟频率为:72MHz/(ENCODER_TIMx_PRESCALER+1)
13 #define ENCODER_TIM_PRESCALER
71
// 实际时钟频率为:1MHz
STM32 技术开发手册
www.ing10bbs.com
14
15 // 定义定时器周期,输入捕获功能设置为:0xFFFF
16 #define ENCODER_TIM_PERIOD
0xFFFF
17
18 #define ENCODER_TIMx_IRQn
TIM3_IRQn
19 #define ENCODER_TIMx_IRQHandler
TIM3_IRQHandler
这里选择 TIM3 的通道 3 做为编码器信号检测引脚,对应于 PB0,大家可以
根据需求修改引脚。
bsp_encoder.c 文件内容
编码器信号检测功能实现底层驱动。
代码 6-2 ENCODER_GPIO_Init 函数
01 static void ENCODER_GPIO_Init(void)
02 {
03
GPIO_InitTypeDef GPIO_InitStruct;
04
05
/* 引脚端口时钟使能 */
06
ENCODER_TIM_GPIO_CLK_ENABLE();
07
08
/* 编码器输入捕获引脚 IO 初始化 */
09
GPIO_InitStruct.Pin = ENCODER_TIM_PIN;
10
GPIO_InitStruct.Mode = GPIO_MODE_AF_INPUT;
11
GPIO_InitStruct.Pull = GPIO_NOPULL;
12
HAL_GPIO_Init(ENCODER_TIM_PORT, &GPIO_InitStruct);
13 }
ENCODER_GPIO_Init 函数是定时器输入捕获功能通道引脚初始化,设置为复
用功能输入模式,无需上下拉。
代码 6-3 ENCODER_NVIC_Conf 函数
01 static void ENCODER_NVIC_Conf(void)
02 {
03
/* 编码器定时器中断配置 */
04
HAL_NVIC_SetPriority(ENCODER_TIMx_IRQn, 1, 0);
05
HAL_NVIC_EnableIRQ(ENCODER_TIMx_IRQn);
06 }
ENCODER_NVIC_Conf 函数用于配置定时器中断优先级并使能中断。
代码 6-4 ENCODER_TIMx_Init 函数
01 void ENCODER_TIMx_Init(void)
02 {
03
TIM_ClockConfigTypeDef sClockSourceConfig;
04
TIM_MasterConfigTypeDef sMasterConfig;
05
TIM_IC_InitTypeDef sConfigIC;
06
07
/* 使能定时器时钟 */
08
ENCODER_TIM_RCC_CLK_ENABLE();
09
10
/* 编码器引脚初始化 */
11
ENCODER_GPIO_Init();
// 定时器时钟
// 定时器主模式配置
// 定时器输入捕获
STM32 技术开发手册
www.ing10bbs.com
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 }
/* 中断优先级配置 */
ENCODER_NVIC_Conf();
/* 定时器基本环境配置 */
htimx_ENCODER.Instance = ENCODER_TIMx;
htimx_ENCODER.Init.Prescaler = ENCODER_TIM_PRESCALER;
htimx_ENCODER.Init.CounterMode = TIM_COUNTERMODE_UP;
htimx_ENCODER.Init.Period = ENCODER_TIM_PERIOD;
htimx_ENCODER.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&htimx_ENCODER);
// 定时器编号
// 定时器预分频器
// 计数方向:向上计数
// 定时器周期
// 时钟分频
/* 定时器时钟源配置 */
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; // 使用内部时钟源
HAL_TIM_ConfigClockSource(&htimx_ENCODER, &sClockSourceConfig);
/* 初始化定时器输入捕获环境 */
//HAL_TIM_IC_Init(&htimx_ENCODER);
/* 定时器主输出模式 */
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htimx_ENCODER, &sMasterConfig);
/* 定时器输入捕获配置 */
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
// 上升沿触发
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
// 通道
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
// 分频
sConfigIC.ICFilter = 0;
// 滤波器
HAL_TIM_IC_ConfigChannel(&htimx_ENCODER, &sConfigIC, ENCODER_TIM_CHANNELx);
ENCODER_TIMx_Init 函数用于初始化配置编码器信号捕获功能。首先,使能
了定时器时钟,然后调用 ENCODER_GPIO_Init 以及 ENCODER_NVIC_Conf 函数初
始化配置了定时器功能引脚以及配置定时器中断优先级。
接下来,初始化配置定时器基本工作环境,配置时钟为 1MHz,向上计数模
式。使用内部时钟做为定时器时钟源。
最后,配置输入捕获功能:上升沿触发,通道选择直通,无需分频和滤波。
main.c 文件内容
代码 6-5 main 函数
01 int main(void)
02 {
03
/* 复位所有外设,初始化 Flash 接口和系统滴答定时器 */
04
HAL_Init();
05
/* 配置系统时钟 */
06
SystemClock_Config();
07
08
KEY_GPIO_Init();
09
MX_DEBUG_USART_Init();
10
11
ENCODER_TIMx_Init();
STM32 技术开发手册
www.ing10bbs.com
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 }
HAL_TIM_Base_Start(&htimx_ENCODER);
/* 高级控制定时器初始化并配置 PWM 输出功能 */
L298N_TIMx_Init();
/* 启动定时器 */
HAL_TIM_Base_Start(&htimx_L298N);
HAL_TIM_IC_Start_IT(&htimx_ENCODER,ENCODER_TIM_CHANNELx);
/* 启动定时器通道和互补通道 PWM 输出 */
L298N_DCMOTOR_Contrl(1,1,PWM_Duty);
start_flag=1;
/* 无限循环 */
while (1) {
if (KEY1_StateRead()==KEY_DOWN) { // 增速
PWM_Duty+=50;
if (PWM_Duty>900)
// PWM_Duty=900 已经对应 100%占空比
PWM_Duty=900;
L298N_DCMOTOR_Contrl(1,1,PWM_Duty);
}
if (KEY2_StateRead()==KEY_DOWN) { // 减速
PWM_Duty-=50;
if (PWM_Duty<200) // 最低速度保证,防止电机阻塞烧毁
PWM_Duty=200;
L298N_DCMOTOR_Contrl(1,1,PWM_Duty);
}
}
main 函数开始初始化了按键、调试串口,然后就调用 ENCODER_TIMx_Init 函
数初始化检测编码器信号的定时器工作环境,调用 HAL_TIM_Base_Start 启动该定
时器。
L298N_TIMx_Init 函数初始化配置控制电机旋转的定时器工作环境,并调用
HAL_TIM_Base_Start 函数启动定时器。
HAL_TIM_IC_Start_IT 函数用于启动定时器输入捕获功能,并且使能定时器中
断,这样在捕获成功后就可以运行中断服务回调函数,执行我们应用代码。
L298N_DCMOTOR_Contrl 函数定义在 bsp_L298N.c 文件中,之前章节已经有
详细介绍过。调用该函数后,使得电机持续旋转。电机一旋转,就带动铁氧体磁
环转动,这样编码器就有脉冲信号输出。
在无限循环中不断检测按键状态,两个按键都用于调速,按键按下是修改
PWM 脉冲的占空比,实现电机速度调节。
代码 6-6 中断服务回调函数
01 /**
02
* 函数功能: 系统滴答定时器中断回调函数
03
* 输入参数: 无
STM32 技术开发手册
www.ing10bbs.com
04
* 返 回 值: 无
05
* 说
明: 每发生一次滴答定时器中断进入该回调函数一次
06
*/
07 void HAL_SYSTICK_Callback(void)
08 {
09
if (start_flag) { // 等待脉冲输出后才开始计时
10
time_count++;
// 每 1ms 自动增一
11
if (time_count==1000) { // 1s
12
printf("输入捕获值:%d\n",CaptureNumber);
13
// 11:编码器线数(转速一圈输出脉冲数)
14
// 34:电机减数比,内部电机转动圈数与电机输出轴转动圈数比,即减速齿轮比
15
printf("电机实际转动速度:%0.2f(圈/s)\n",(float)CaptureNumber/11/34);
16
CaptureNumber=0;
// 重新开始计数
17
time_count=0;
18
}
19
}
20 }
21
22 /**
23
* 函数功能: 定时器输入捕获中断回调函数
24
* 输入参数: htim:定时器句柄
25
* 返 回 值: 无
26
* 说
明: 无
27
*/
28 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
29 {
30
CaptureNumber++;
31 }
HAL_TIM_IC_CaptureCallback 函数是定时器输入捕获中断服务回调函数,在
成功捕获到一个编码器信号时该函数被调用运行一次,在该函数中,我们只是简
单的让 CaptureNumber 变量增加一,CaptureNumber 变量存放了编码器信号的脉
冲数。
HAL_SYSTICK_Callback 函数是系统滴答定时器中断服务回调函数,在每发生
一次滴答定时器中断(这里的周期为 1ms),就好执行该函数一次。这里使用
time_count 变量计数时间,等计数到 1000 时,即 1s 时间长度,调用 printf 函数
将编码器脉冲数“打印”到串口调试助手就,同时还输出当前电机实际转速,转
速计算在之前内容都有详细的解释。最后清除 time_count 和 CaptureNumber 变
量值。
实验操作与现象
根据上面介绍完成电机与 L298N 驱动器连接(实际上就两根线),然后连接
L298N 驱动器和 YS-F1Pro 开发板(实际上总共就三根线,注意需要连接 GND),
然后为 L298N 驱动器提供 12V 电源,使用另外三根导线连接减速电机的编码器
功能引脚与开发板。使用开发板配套的 MINI USB 线连接到开发板标示“调试串
STM32 技术开发手册
www.ing10bbs.com
口”字样的 MIMI USB 接口(需要安装驱动),在电脑端打开串口调试助手工具,
设置参数为 115200 8-N-1。下载完程序之后,电机开始转动,每隔 1s 实际,在串
口调试助手窗口可接收到编码器测速结果,见图 6-16。如果按下两个按键可以
调节电机旋转速度,并编码器测速结果值也随之改变。
图 6-16 串口调试助手截图
STM32 技术开发手册
www.ing10bbs.com
第7章 舵机控制
7.1
概述
舵机最早用于船舶上实现其转向功能,由于可以通过程序连续控制其转角,
因而被广泛应用智能小车以实现转向以及机器人各类关节运动中,如图 7-1 所
示。
图 7-1 舵机
在设计智能小车中,舵机是小车转向的控制机构,具有体积小、力矩大、外
部机械设计简单、稳定性高等特点,无论是在硬件设计还是软件设计,舵机设计
是小车控制部分重要的组成部分,图 7-2 为舵机的外形图。
STM32 技术开发手册
www.ing10bbs.com
图 7-2 舵机的外形图
7.2
舵机的组成
一般来讲,舵机主要由以下几个部分组成:舵盘、减速齿轮组、位置反馈电
位计、直流电机、控制电路等,如图 7-3 和图 7-4 所示。
STM32 技术开发手册
www.ing10bbs.com
图 7-3 舵机的组成示意图
图 7-4 舵机组成
舵机的输入线共有三条,如图 7-5 所示,红色中间,是电源线,一边棕色(有
些是黑色)的是地线,这两根线给舵机提供最基本的能源保证,主要是电机的转
动消耗。电源有两种规格,一是 4.8V,一是 6.0V,分别对应不同的转矩标准,即
输出力矩不同,6.0V 对应的要大一些,具体看应用条件;另外一根线是控制信号
线,一般为桔黄色(有些舵机为白色,主要是不同厂家可能采用不同颜色)。
一般来说,大部分舵机的中间线(红色线)都为电源线,但也不排除部分引
线特殊的舵机,所以一般记住红色线就是电源线这个就不会出错的。
STM32 技术开发手册
www.ing10bbs.com
图 7-5 舵机的输出线
7.3
舵机工作原理
控制电路板接受来自信号线的控制信号,控制电机转动,电机带动一系列齿
轮组,减速后传动至输出舵盘。舵机的输出轴和位置反馈电位计是相连的,舵盘
转动的同时,带动位置反馈电位计,电位计将输出一个电压信号到控制电路板,
进行反馈,然后控制电路板根据所在位置决定电机转动的方向和速度,从而达到
目标停止。其工作流程为:
控制信号→控制电路板→电机转动→齿轮组减速→舵盘转动→位置反馈电
位计→控制电路板反馈。
图 7-6 舵机工作流程
舵机的控制信号周期为 20MS 的脉宽调制(PWM)信号,其中脉冲宽度从
0.5-2.5MS,相对应的舵盘位置为 0-180 度,呈线性变化,见图 7-7。也就是说,
给它提供一定的脉宽,它的输出轴就会保持一定对应角度上,无论外界转矩怎么
STM32 技术开发手册
www.ing10bbs.com
改变,直到给它提供一个另外宽度的脉冲信号,它才会改变输出角度到新的对应
位置上如所求。舵机内部有一个基准电路,产生周期为 20MS,宽度 1.5MS 的基
准信号,有一个比较器,将外加信号与基准信号相比较,判断出方向和大小,从
而生产电机的转动信号。由此可见,舵机是一种位置伺服驱动器,转动范围不能
超过 180 度,适用于那些需要不断变化并可以保持的驱动器中,比如说机器人的
关节、飞机的舵面等。
图 7-7 舵机输出转角与输入脉冲的关系
7.4
舵机选购
市场上的舵机有塑料齿、金属齿、小尺寸、标准尺寸、大尺寸,另外还有薄
的标准尺寸舵机,及低重心的型号。小舵机一般称为微型舵机,扭力都比较小,
市面上 2.5g,3.7g,4.4g,7g,9g 等舵机指的是舵机的重量分别是多少克,体积
和扭力也是逐渐增大。微型舵机内部多数都是塑料齿,9g 舵机有金属齿的型号,
扭力也比塑料齿的要大些。futaba S3003,辉盛 MG995 是标准舵机,体积差不多,
但前者是塑料齿,后者金属齿,两者标称的扭力也差很多。春天 sr403p,Dynamixel
STM32 技术开发手册
www.ing10bbs.com
AX-12+是机器人专用舵机,不同的是前者是国产,后者是韩国产,两者都是金属
齿标称扭力 13kg 以上,但前者只是改改样子的模拟舵机,后者则是 RS485 串口
通信,具有位置反馈,而且还具有速度反馈与温度反馈功能的数字舵机,两者在
性能和价格上相差很大。
除了体积,外形和扭力的不同选择,舵机的反应速度和虚位也要考虑,一般
舵机的标称反应速度常见 0.22 秒/60°,0.18 秒/60°,好些的舵机有 0.12 秒
/60°等的,数值小反应就快。
厂商所提供的舵机规格资料,都会包含外形尺寸(mm)、扭力(kg/cm)、速度
(秒/60°)、测试电压(V)及重量(g)等基本资料。扭力的单位是 kg/cm,意思是在摆
臂长度 1 公分处,能吊起几公斤重的物体。这就是力臂的观念,因此摆臂长度
愈长,则扭力愈小。
图 7-8 扭矩
速度的单位是 sec/60°,意思是舵机转动 60°所需要的时间。 电压会直接
影响舵机的性能,例如 Futaba S-9001 在 4.8V 时扭力为 3.9kg/cm、速度为 0.22
秒/60°,在 6.0V 时扭力为 5.2kg/cm、速度为 0.18 秒/60° 。若无特别注明,
JR 的舵机都是以 4.8V 为测试电压,Futaba 则是以 6.0V 作为测试电压。速度快、
扭力大的舵机,除了价格贵,还会伴随著高耗电的特点。因此使用高级的舵机时,
务必搭配高品质、高容量的电池,能提供稳定且充裕的。
STM32 技术开发手册
www.ing10bbs.com
图 7-9 速度
现在市面上的舵机鱼龙混杂,总体来说仿品不如正品,便宜的不如贵的,塑
料齿的不如金属齿的,老的不如新的,国内的不如外国的等等,大家不必过于追
求极致,根据自身购买力选择够用的就行。
7.5
舵机使用中应注意的事项
1) 常用舵机的额定工作电压为 6V,可以使用 LM1117 等芯片或者 XL6009 升
压模块提供 6V 的电压,如果为了简化硬件上的设计直接使用 5V 的供电
影响也不是很大,但最好和单片机进行分开供电,保证两边都有足够的
电流。
2) 一般来说可以将来信号线连接至单片机的定时器通道引脚,对于 stm32
内部带有定时器的 PWM 模式功能,可以直接输出 PWM 信号,此时将信
号线连于专用的 PWM 输出引脚上,即定时器通道引脚上。
7.6
辉盛 SG90 和 MG996R 舵机简介
由于辉盛 SG90 良好的性价比,因而广泛应用机器人和智能小车制作设计中,
图 7-10 和图 7-11 分别为 SG90 的外观和参数。
STM32 技术开发手册
www.ing10bbs.com
图 7-10 辉盛 SG90 舵机
图 7-11 辉盛 SG90 舵机参数
辉盛还有一个比较有名的舵机是:MG996R,它是大扭力金属铜齿轮,是一
个非常有竞争力的舵机,常用与双足机器人、机械手、大脚车、攀爬车、遥控船。
MG996R 舵机实物图见图 7-12,舵机参数见图 7-13。
STM32 技术开发手册
www.ing10bbs.com
图 7-12 辉盛 MG996R 舵机
图 7-13 辉盛 MG996R 舵机参数
7.7
硬件设计
YS-F1Pro 开发板为驱动舵机预留了一个接口,实物连接和电路设计见。
STM32 技术开发手册
www.ing10bbs.com
图 7-14 舵机接口
PA15 是定时器 2 的通道功能引脚,我们可以使用 TIM2 功能来控制舵机,
这里还需要注意, PA15 是 JTAG 接口引脚,在编程时注意禁用 JTAG 接口功能,
PA15 引脚才可以输出控制舵机的波形,因为禁用了 JTAG 功能,所以不能进行
JTAG 在线仿真,一般都是直接使用示波器测试 PA15 引脚波形即可知道程序效果。
虽然,YS-F1Pro 开发板是预留了 PA15 做为舵机控制接口,但实际上,只要
是定时器引脚都是可以做为舵机控制引脚的,STM32F10x 芯片定时器通道引脚分
布见图 7-15。所以,如果想用 YS-F1Pro 开发板同时控制 4 个、8 个舵机都是可以
实现的,真有需要控制 16 个都是可以的。
图 7-15 stm32f103zet6 定时器功能引脚
STM32 技术开发手册
www.ing10bbs.com
7.8
STM32CubeMX 生成工程
这里只贴出重点操作截图,并做分析。
1) 引脚和外设功能选择,见图 7-16。选择 TIM2 的 CH1 作为舵机的控制引
脚,注意 TIM2 的 CH1 的默认引脚为 PA0,这里我们需要用到 IO 的重映
射功能,所以正确的操作步骤是:先选中 PA15 并单击该引脚弹出引脚功
能选项框,选择 TIM2_CH1,然后才去配置 TIM2 功能。
图 7-16 引脚及外设功能选择
2) 时钟树设置,见图 7-17 图 6-12。通用定时器(包括 TIM2)的属于 APB1
总线,最高频率只有 36MHz,但对于如果做为通用定时器时钟源允许 2
倍频,得到 72MHz 时钟。
STM32 技术开发手册
www.ing10bbs.com
图 7-17 时钟树设置
3) 外设功能设置,见图 7-18。设置定时器预分频器为(720-1)可以得到
100KHz 的定时器计数时钟,设置定时器计数周期为(2000-1),这样定时
器的频率为 50Hz,即可得到 20ms 的周期。设置通道 1 为 PWM 模式 1,
脉冲数为 150,即可得到 1.5ms 的高电平时间。
图 7-18 外设功能设置
STM32 技术开发手册
www.ing10bbs.com
4) 定时器通道引脚配置,见图 7-19。设置 PA15 为复用功能推挽输出模式,
速度设置为高。
图 7-19 定时器通道引脚配置
7.9
MG996R 舵机旋转控制实现
7.9.1 MG996R 舵机旋转控制编程流程
1) 初始化配置编码器功能定时器通道引脚;
2) 配置编码器功能定时器基本工作环境;
3) 配置编码器功能定时器使用内部时钟源;
4) 配置定时器 PWM 输出功能;
5) 设置定时器通道比较值改变 PWM 占空比,控制舵机旋转。
7.9.2 MG996R 舵机旋转控制实现代码分析
限于篇幅问题,不会把例程所有代码贴出分析,只挑重点程序段分析。具体
的工程代码可以看我们对应的例程源码。
STM32 技术开发手册
www.ing10bbs.com
bsp_GeneralTIM.h 文件内容
代码 7-1 定时器相关宏定义
01 #define GENERAL_TIMx
TIM2
02 #define GENERAL_TIM_RCC_CLK_ENABLE()
__HAL_RCC_TIM2_CLK_ENABLE()
03 #define GENERAL_TIM_RCC_CLK_DISABLE()
__HAL_RCC_TIM2_CLK_DISABLE()
04 #define GENERAL_TIM_GPIO_RCC_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
05 #define GENERAL_TIM_CH1_PORT
GPIOA
06 #define GENERAL_TIM_CH1_PIN
GPIO_PIN_15
07
08 // 定义定时器预分频,定时器实际时钟频率为:72MHz/(GENERAL_TIMx_PRESCALER+1)
09 #define GENERAL_TIM_PRESCALER
720-1 // 实际时钟频率为:100KHz
10
11 //
12 定义定时器周期,当定时器开始计数到 GENERAL_TIMx_PERIOD 值是更新定时器并生成对应事件和中断
13 #define GENERAL_TIM_PERIOD
1999 // 定时器产生中断频率为:100KHz/2000=50Hz
这里选择 TIM2 的通道 1 做为舵机控制引脚,对应于 PA15,大家可以根据需
求修改引脚。
bsp_GeneralTIM.c 文件内容
代码 7-2 HAL_TIM_MspPostInit 函数
01 void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim)
02 {
03
GPIO_InitTypeDef GPIO_InitStruct;
04
if (htim->Instance==GENERAL_TIMx) {
05
/* 定时器通道功能引脚端口时钟使能 */
06
GENERAL_TIM_GPIO_RCC_CLK_ENABLE();
07
/* PA15 是缺省 JTAG 接口功能引脚,为做为 TIM 通道引脚需要关闭 JTAG 功能 */
08
__HAL_AFIO_REMAP_SWJ_NOJTAG();
09
10
/* 定时器通道 1 功能引脚 IO 初始化 */
11
GPIO_InitStruct.Pin = GENERAL_TIM_CH1_PIN;
12
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
13
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
14
HAL_GPIO_Init(GENERAL_TIM_CH1_PORT, &GPIO_InitStruct);
15
16
/* 定时器通道引脚重映射:TIM2 通道 1 缺省为 PA0,可以重映射到 PA15 引脚上 */
17
__HAL_AFIO_REMAP_TIM2_PARTIAL_1();
18
}
19 }
HAL_TIM_MspPostInit 函数配置定时器通道引脚工作模式,这里设置 PA15 为
复用功能推挽输出模式,速度设置为高。因为 PA15 引脚为 JTAG 功能引脚,并且
芯片上电默认为 JTAG 功能引脚的,所以现在需要它做为定时器通道引脚,需要
调用__HAL_AFIO_REMAP_SWJ_NOJTAG 函数关闭 JTAG 功能。
STM32 技术开发手册
www.ing10bbs.com
另外,TIM2 通道 1 默认通道引脚为 PA0,不过它允许引脚重映射到 PA15 引
脚,所以这里需要调用__HAL_AFIO_REMAP_TIM2_PARTIAL_1 函数实现重映射功
能。
代码 7-3 GENERAL_TIMx_Init 函数
01 void GENERAL_TIMx_Init(void)
02 {
03
TIM_ClockConfigTypeDef sClockSourceConfig;
04
TIM_MasterConfigTypeDef sMasterConfig;
05
TIM_OC_InitTypeDef sConfigOC;
06
07
htimx.Instance = GENERAL_TIMx;
08
htimx.Init.Prescaler = GENERAL_TIM_PRESCALER;
09
htimx.Init.CounterMode = TIM_COUNTERMODE_UP;
10
htimx.Init.Period = GENERAL_TIM_PERIOD;
11
htimx.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
12
HAL_TIM_Base_Init(&htimx);
13
14
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
15
HAL_TIM_ConfigClockSource(&htimx, &sClockSourceConfig);
16
17
HAL_TIM_PWM_Init(&htimx);
18
19
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
20
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
21
HAL_TIMEx_MasterConfigSynchronization(&htimx, &sMasterConfig);
22
23
sConfigOC.OCMode = TIM_OCMODE_PWM1;
24
sConfigOC.Pulse = 150;
25
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
26
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
27
HAL_TIM_PWM_ConfigChannel(&htimx, &sConfigOC, TIM_CHANNEL_1);
28
29
HAL_TIM_MspPostInit(&htimx);
30 }
GENERAL_TIMx_Init 函数用于配置定时器功能,首先是配置定时器的基本工
作环境,设置定时器编号、预分频器、定时器周期,通过这些设置可以得到 50Hz
的定时频率。
定时器采用内部时钟源做为定时器时钟源。
设置定时器为 PWM 模式 1,通道 1 脉冲数为 150,默认输出 1.5ms 高电平。
main.c 文件内容
代码 7-4 main 函数
01 int main(void)
02 {
03
/* 复位所有外设,初始化 Flash 接口和系统滴答定时器 */
04
HAL_Init();
05
/* 配置系统时钟 */
STM32 技术开发手册
www.ing10bbs.com
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 }
SystemClock_Config();
/* 通用定时器初始化并配置 PWM 输出功能 */
GENERAL_TIMx_Init();
/* 启动通道 PWM 输出 */
HAL_TIM_PWM_Start(&htimx,TIM_CHANNEL_1);
/* 无限循环 */
while (1) {
__HAL_TIM_SET_COMPARE(&htimx,TIM_CHANNEL_1,100);
HAL_Delay(1000);
__HAL_TIM_SET_COMPARE(&htimx,TIM_CHANNEL_1,150);
HAL_Delay(1000);
__HAL_TIM_SET_COMPARE(&htimx,TIM_CHANNEL_1,250);
HAL_Delay(1000);
}
main 函数开始调用 HAL_Init 和 SystemClock_Config 初始化配置系统工作环
境。
GENERAL_TIMx_Init 函数用于初始化定时器工作环境。
HAL_TIM_PWM_Start 函数启动 PWM 信号输出,运行该函数之后在定时器通
道引脚上就有 PWM 信号输出,这个可以使用示波器检测得到。
在无限循环中,调用__HAL_TIM_SET_COMPARE 函数修改 PWM 的占空比,
实现舵机角度调节。
实验操作与现象
将 MG996R 舵机插入到 YS-F1Pro 开发板上的 CN19 接口上,注意引脚要对
应。使用开发板配套的 MINI USB 线连接到开发板标示“调试串口”字样的 MIMI
USB 接口用于为开发板提供电源,当然最好是使用 12V 2A 适配器接在开发板的
DC 接口为开发板供电。下载程序,可以看到舵机旋转着一个角度后,停止 1s 时
间,然后在旋转至另外角度,如此循环。
7.10 四轴舵机旋转控制实现
上个例程是单个舵机旋转控制,正如之前所说的 STM32F10x 芯片要控制 4
个、8 个舵机完全是可以的,现在,我们就来实现 4 个舵机的控制。
STM32 技术开发手册
www.ing10bbs.com
舵机控制只要是一般的定时器通道引脚就可以的,这里选择定时器 3 的四个
通道引脚,在实际应用中注意舵机的接线就好。
编程原理和流程与一个舵机控制实现都是一样的,这里就不再啰嗦。
7.10.1 STM32CubeMX 生成工程
这里只贴出重点操作截图,并做分析。
1) 引脚和外设功能选择,见图 7-20。选择 TIM3 的四个通道作为舵机的控
制引脚。定时器时钟使用内部时钟源。
图 7-20 引脚和外设功能选择
2) 外设功能设置,见图 7-21。设置定时器预分频器为(720-1)可以得到
100KHz 的定时器计数时钟,设置定时器计数周期为(2000-1),这样定时
器的频率为 50Hz,即可得到 20ms 的周期。设置四个定时器通道为 PWM
模式 1,脉冲数为 150,即可得到 1.5ms 的高电平时间。
STM32 技术开发手册
www.ing10bbs.com
图 7-21 外设功能设置
3) 定时器通道引脚配置,见图 7-22。设置为复用功能推挽输出模式,速度
设置为高。
图 7-22 定时器通道引脚配置
STM32 技术开发手册
www.ing10bbs.com
7.10.2 四轴舵机旋转控制实现代码分析
限于篇幅问题,不会把例程所有代码贴出分析,只挑重点程序段分析。具体
的工程代码可以看我们对应的例程源码。
bsp_GeneralTIM.h 文件内容
代码 7-5 定时器相关宏定义
01 #define GENERAL_TIMx
TIM3
02 #define GENERAL_TIM_RCC_CLK_ENABLE()
__HAL_RCC_TIM3_CLK_ENABLE()
03 #define GENERAL_TIM_RCC_CLK_DISABLE()
__HAL_RCC_TIM3_CLK_DISABLE()
04 #define GENERAL_TIM_GPIO_RCC_CLK_ENABLE()
{__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();}
05 #define GENERAL_TIM_CH1_PORT
GPIOA
06 #define GENERAL_TIM_CH1_PIN
GPIO_PIN_6
07 #define GENERAL_TIM_CH2_PORT
GPIOA
08 #define GENERAL_TIM_CH2_PIN
GPIO_PIN_7
09 #define GENERAL_TIM_CH3_PORT
GPIOB
10 #define GENERAL_TIM_CH3_PIN
GPIO_PIN_0
11 #define GENERAL_TIM_CH4_PORT
GPIOB
12 #define GENERAL_TIM_CH4_PIN
GPIO_PIN_1
13
14 // 定义定时器预分频,定时器实际时钟频率为:72MHz/(GENERAL_TIMx_PRESCALER+1)
15 #define GENERAL_TIM_PRESCALER
720-1 // 实际时钟频率为:100KHz
16
17 //
18 定义定时器周期,当定时器开始计数到 GENERAL_TIMx_PERIOD 值是更新定时器并生成对应事件和中断
19 #define GENERAL_TIM_PERIOD
1999 // 定时器产生中断频率为:100KHz/2000=50Hz
定时器选择以及对应引脚配置,不同定时器对应引脚不同,大家可以根据实
际情况选择定时器和引脚。
bsp_GeneralTIM.c 文件内容
代码 7-6 HAL_TIM_MspPostInit 函数
01 void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim)
02 {
03
GPIO_InitTypeDef GPIO_InitStruct;
04
if (htim->Instance==GENERAL_TIMx) {
05
/* 定时器通道功能引脚端口时钟使能 */
06
GENERAL_TIM_GPIO_RCC_CLK_ENABLE();
07
08
/* 定时器通道 1 功能引脚 IO 初始化 */
09
GPIO_InitStruct.Pin = GENERAL_TIM_CH1_PIN;
10
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
11
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
12
HAL_GPIO_Init(GENERAL_TIM_CH1_PORT, &GPIO_InitStruct);
13
14
/* 定时器通道 2 功能引脚 IO 初始化 */
15
GPIO_InitStruct.Pin = GENERAL_TIM_CH2_PIN;
16
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
STM32 技术开发手册
www.ing10bbs.com
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 }
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GENERAL_TIM_CH2_PORT, &GPIO_InitStruct);
/* 定时器通道 3 功能引脚 IO 初始化 */
GPIO_InitStruct.Pin = GENERAL_TIM_CH3_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GENERAL_TIM_CH3_PORT, &GPIO_InitStruct);
/* 定时器通道 4 功能引脚 IO 初始化 */
GPIO_InitStruct.Pin = GENERAL_TIM_CH4_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GENERAL_TIM_CH4_PORT, &GPIO_InitStruct);
}
HAL_TIM_MspPostInit 函数配置定时器通道引脚工作模式,这里设置引脚为
复用功能推挽输出模式,速度设置为高。
代码 7-7 GENERAL_TIMx_Init 函数
01 void GENERAL_TIMx_Init(void)
02 {
03
TIM_ClockConfigTypeDef sClockSourceConfig;
04
TIM_MasterConfigTypeDef sMasterConfig;
05
TIM_OC_InitTypeDef sConfigOC;
06
07
htimx.Instance = GENERAL_TIMx;
08
htimx.Init.Prescaler = GENERAL_TIM_PRESCALER;
09
htimx.Init.CounterMode = TIM_COUNTERMODE_UP;
10
htimx.Init.Period = GENERAL_TIM_PERIOD;
11
htimx.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
12
HAL_TIM_Base_Init(&htimx);
13
14
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
15
HAL_TIM_ConfigClockSource(&htimx, &sClockSourceConfig);
16
17
HAL_TIM_PWM_Init(&htimx);
18
19
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
20
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
21
HAL_TIMEx_MasterConfigSynchronization(&htimx, &sMasterConfig);
22
23
sConfigOC.OCMode = TIM_OCMODE_PWM1;
24
sConfigOC.Pulse = 150;
25
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
26
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
27
HAL_TIM_PWM_ConfigChannel(&htimx, &sConfigOC, TIM_CHANNEL_1);
28
HAL_TIM_PWM_ConfigChannel(&htimx, &sConfigOC, TIM_CHANNEL_2);
29
HAL_TIM_PWM_ConfigChannel(&htimx, &sConfigOC, TIM_CHANNEL_3);
30
HAL_TIM_PWM_ConfigChannel(&htimx, &sConfigOC, TIM_CHANNEL_4);
31
32
HAL_TIM_MspPostInit(&htimx);
33 }
GENERAL_TIMx_Init 函数用于配置定时器功能,首先是配置定时器的基本工
作环境,设置定时器编号、预分频器、定时器周期,通过这些设置可以得到 50Hz
的定时频率。
STM32 技术开发手册
www.ing10bbs.com
定时器采用内部时钟源做为定时器时钟源。
设置定时器为 PWM 模式 1,通道脉冲数为 150,默认输出 1.5ms 高电平。
main.c 文件内容
代码 7-8 main 函数
01 int main(void)
02 {
03
/* 复位所有外设,初始化 Flash 接口和系统滴答定时器 */
04
HAL_Init();
05
/* 配置系统时钟 */
06
SystemClock_Config();
07
08
/* 通用定时器初始化并配置 PWM 输出功能 */
09
GENERAL_TIMx_Init();
10
11
/* 启动通道 PWM 输出 */
12
HAL_TIM_PWM_Start(&htimx,TIM_CHANNEL_1);
13
HAL_TIM_PWM_Start(&htimx,TIM_CHANNEL_2);
14
HAL_TIM_PWM_Start(&htimx,TIM_CHANNEL_3);
15
HAL_TIM_PWM_Start(&htimx,TIM_CHANNEL_4);
16
17
/* 无限循环 */
18
while (1) {
19
__HAL_TIM_SET_COMPARE(&htimx,TIM_CHANNEL_1,100);
20
__HAL_TIM_SET_COMPARE(&htimx,TIM_CHANNEL_2,100);
21
__HAL_TIM_SET_COMPARE(&htimx,TIM_CHANNEL_3,100);
22
__HAL_TIM_SET_COMPARE(&htimx,TIM_CHANNEL_4,100);
23
HAL_Delay(1000);
24
25
__HAL_TIM_SET_COMPARE(&htimx,TIM_CHANNEL_1,150);
26
__HAL_TIM_SET_COMPARE(&htimx,TIM_CHANNEL_2,150);
27
__HAL_TIM_SET_COMPARE(&htimx,TIM_CHANNEL_3,150);
28
__HAL_TIM_SET_COMPARE(&htimx,TIM_CHANNEL_4,150);
29
HAL_Delay(1000);
30
31
__HAL_TIM_SET_COMPARE(&htimx,TIM_CHANNEL_1,200);
32
__HAL_TIM_SET_COMPARE(&htimx,TIM_CHANNEL_2,200);
33
__HAL_TIM_SET_COMPARE(&htimx,TIM_CHANNEL_3,200);
34
__HAL_TIM_SET_COMPARE(&htimx,TIM_CHANNEL_4,200);
35
HAL_Delay(1000);
36
}
37 }
main 函数开始调用 HAL_Init 和 SystemClock_Config 初始化配置系统工作环
境。
GENERAL_TIMx_Init 函数用于初始化定时器工作环境。
HAL_TIM_PWM_Start 函数启动 PWM 信号输出,运行该函数之后在定时器通
道引脚上就有 PWM 信号输出,这个可以使用示波器检测得到。
STM32 技术开发手册
www.ing10bbs.com
在无限循环中,调用__HAL_TIM_SET_COMPARE 函数修改 PWM 的占空比,
实现舵机角度调节。
实验操作与现象
将四个舵机插入到 YS-F1Pro 开发板上对应的引脚。使用 12V 2A 适配器接在
开发板的 DC 接口为开发板供电,下载程序,可以看到舵机旋转着一个角度后,
停止 1s 时间,然后在旋转至另外角度,如此循环。
STM32 技术开发手册
www.ing10bbs.com
第8章 步进电机
8.1
步进电机
8.1.1 步进电机介绍
步进电机是将电脉冲信号转变为角位移或线位移的开环控制元件。在非超载
的情况下,电机的转速、停止的位置只取决于脉冲信号的频率和脉冲数,而不受
负载变化的影响,即给电机加一个脉冲信号,电机则转过一个步距角。这一线性
关系的存在,加上步进电机只有周期性的误差而无累积误差等特点。使得在速度、
位置等控制领域用步进电机来控制变的非常的简单。虽然步进电机已被广泛地应
用,但步进电机并不能象普通的直流电机,交流电机在常规下使用。它必须由双
环形脉冲信号、功率驱动电路等组成控制系统方可使用。因此用好步进电机也非
易事,它涉及到机械、电机、电子及计算机等许多专业知识。
图 8-1 步进电机内部组成结构
简化的内部等效图见图 8-2。
STM32 技术开发手册
www.ing10bbs.com
图 8-2 步进电机的等效结构图
定子可以等效成 A、B、C 和 D 四个线圈绕组,转子可以等效成一对南北极
(S/N)的磁铁。定子的接线方式一般如图 8-3 所示,A2 和 C2、B2 和 D2 分别连
在一起,所以有 A1、A2/C2、C1、B1、B2/D2 和 D1 等 6 根输出线。因为在电气
连接上有 A/C、B/D 两组互不接触的线圈绕组,所以称作“两相式”步进电机,
后面所提到的步进电机都是指两相式步进电机。工作时,只要给 A、B、C 和 D 四个
线圈通上合适的电流,转子就会在磁力的吸引下转动。根据通电方式,步进电机
的驱动分为:单极性驱动和双极性驱动两大类,单极性驱动的电路简单,但转矩
小,双极性驱动的转矩大,但电路复杂。
图 8-3 两相式步进电机的引线图
8.1.2 步进电机分类
步进电机可分为三类:
STM32 技术开发手册
www.ing10bbs.com
 永磁式(PM)
永磁式步进电机一般为两相,转矩和体积较小,步进角一般为 7.5 度或 15
度。
 反应式(VR)
反应式步进电机一般为三相,可实现大转矩输出,步进角一般为 1.5 度,但
噪声和振动都很大。在欧美等发达国家 80 年代已被淘汰。
 混合式(HB)
混合式步进是指混合了永磁式和反应式的优点。它又分为两相和五相:两相
步进角一般为 1.8 度而五相步进角一般为 0.72 度。这种步进电机的应用最为广
泛。
8.1.3 步进电机技术指标
1. 步进电机的静态指标
 相数:
是指电机内部的线圈组数,目前常用的有二相、三相、四相、五相步进电机。
电机相数不同,其步距角也不同,一般二相电机的步距角为 0.9°/1.8°、三相
的为 0.75°/1.5°、五相的为 0.36°/0.72°。在没有细分驱动器时,用户主要靠
选择不同相数的步进电机来满足自己步距角的要求。如果使用细分驱动器,则“相
数”将变得没有意义,用户只需在驱动器上改变细分数,就可以改变步距角。
 步距角:
它表示控制系统每发一个步进脉冲信号,电机所转动的角度。电机出厂时给
出了一个步距角的值,如 57BYG250A 型电机给出的值为 0.9°/1.8°(表示半步
工作时为 0.9°、整步工作时为 1.8°),即电机运动 200 步为一周,这个步距角
可以称之为“电机固有步距角”,它不一定是电机实际工作时的真正步距角,真
正的步距角和驱动器有关。
 拍数:
STM32 技术开发手册
www.ing10bbs.com
完成一个磁场周期性变化所需脉冲数或导电状态,或指电机转过一个步距角
所需脉冲数,以四相电机为例,有四相四拍运行方式即 AB-BC-CD-DA-AB,四相八
拍运行方式即 A-AB-B-BC-C-CD-D-DA-A。
 定位转矩:
电机在不通电状态下,电机转子自身的锁定力矩(由磁场齿形的谐波以及机
械误差造成的)。
 保持转矩:
是指步进电机通电但没有转动时,定子锁住转子的力矩。它是步进电机最重
要的参数之一,通常步进电机在低速时的力矩接近保持转矩。由于步进电机的输
出力矩随速度的增大而不断衰减,输出功率也随速度的增大而变化,所以保持转
矩就成为了衡量步进电机最重要的参数之一。比如,当人们说 2N·m 的步进电
机,在没有特殊说明的情况下是指保持转矩为 2N·m 的步进电机。
2. 步进电机的动态指标
 步距角精度:
步进电机每转过一个步距角的实际值与理论值的误差。用百分比表示:误差
/步距角*100%。不同运行拍数其值不同,四拍运行时应在 5%之内,八拍运行时
应在 15%以内。
 失步:
电机运转时运转的步数不等于理论上的步数。称之为失步。
 失调角:
转子齿轴线偏移定子齿轴线的角度,电机运转必存在失调角,由失调角产生
的误差,采用细分驱动是不能解决的。
 最大空载起动频率:
电机在某种驱动形式、电压及额定电流下,在不加负载的情况下,能够直接
起动的最大频率。
 最大空载运行频率:
电机在某种驱动形式,电压及额定电流下,电机不带负载的最高转速频率。
 运行矩频特性:
STM32 技术开发手册
www.ing10bbs.com
电机在某种测试条件下测得运行中输出力矩与频率关系的曲线称为运行矩
频特性,这是电机诸多动态曲线中最重要的,也是电机选择的根本依据。如图 8-4
所示。
图 8-4 力矩与频率关系曲线
电机一旦选定,电机的静力矩确定,而动态力矩却不然,电机的动态力矩取
决于电机运行时的平均电流(而非静态电流),平均电流越大,电机输出力矩越
大,即电机的频率特性越硬。如图 8-5 所示。
图 8-5 电机矩频特性曲线
其中,曲线 3 电流最大、或电压最高;曲线 1 电流最小、或电压最低,曲线
与负载的交点为负载的最大速度点。要使平均电流大,尽可能提高驱动电压,或
采用小电感大电流的电机。
STM32 技术开发手册
www.ing10bbs.com
 电机的共振点:
步进电机均有固定的共振区域,步进电机的共振区一般在 50 转/分至 80
转/分之间或在 180 转/分左右,电机驱动电压越高,电机电流越大,负载越轻,
电机体积越小,则共振区向上偏移,反之亦然,为使电机输出电矩大,不失步和
整个系统的噪音降低,一般工作点均应偏移共振区较多。因此,在使用步进电机
时应避开此共振区。
8.1.4 步进电机的极性
步进电机分双极性电机和单极性电机。如图 8-6(a)所示双极性电机,电流在
2 个线圈流动时序为 AA–>BB–>AA–>BB ;而单极性电机线圈中电流的流动方向
是固定的,如图 8-6(b)所示,其通电时序为 VA –> VC–> VB–>VD 。
单极性电机的驱动器较双极性电机驱动器简单,但单极性电机的输出力矩较
小。
图 8-6 步进电机线圈的极性
8.2
步进电机工作原理
当电流流过定子绕组时,定子绕组产生一矢量磁场。该磁场会带动转子旋转
一角度,使得转子的一对磁场方向与定子的磁场方向一致。当定子的矢量磁场旋
转一个角度。转子也随着该磁场转一个角度。每输入一个电脉冲,电动机转动一
个角度前进一步。它输出的角位移与输入的脉冲数成正比、转速与脉冲频率成正
STM32 技术开发手册
www.ing10bbs.com
比。改变绕组通电的顺序,电机就会反转。所以可用控制脉冲数量、频率及电动
机各相绕组的通电顺序来控制步进电机的转动。
8.2.1 单极性驱动的原理
单极性驱动分为整步驱动和半步驱动两大类,图 8-7 是单极性整步驱动的工
作原理图,首先如图 8-7(a)所示,给 A 线圈通上从 A2 到 A1 的电流,A 线圈
产生上南(S)下北(N)的磁极,转子的南极(S)被吸引到 A 线圈的下方;接
着如图 8-7(b)所示,给 B 线圈通上从 B2 到 B1 的电流,转子的南极被吸引到
B 线圈的左边;然后如图 8-7(c)所示,给 C 线圈通上从 C2 到 C1 的电流,转子
的南极被吸引到 C 线圈的上方;最后如图 8-7(d)所示,给 D 线圈通上从 D2 到
D1 的电流,转子的南极被吸引到 D 线圈的右方。
这样在 A->B->C->D 的通电顺序下,转子将分 4 步顺时针旋转,如果通
电顺序改成 D->C->B->A,转子将逆时针旋转。在这个过程中,每个线圈的电
流方向固定从“2”到“ 1”,例如从 A2 到 A1,所以称作单极性驱动;转子从一
个线圈一步到位地转到另一个线圈,每一步转过的角度是 90°,所以称作整步
驱动;在任意时刻,只给一个线圈通电,其他三个线圈都没有被通电,单极性整
步驱动比后面介绍的双极性整步驱动的转矩要小。
STM32 技术开发手册
www.ing10bbs.com
图 8-7 单极性整步驱动
单极性半步驱动的方式如图 8-8 所示,它和所示的整步驱动相比,在两个整
步之间插入了一个“半步”,例如图 8-8(b)所示,给 A、B 线圈同时通电,电
流方向分别从 A2 到 A1 和 B2 到 B1,A、B 线圈在靠近转子的一端,同时形成磁
力相等的北极(N),转子的南极(S)在磁力的平衡作用下,停在 A 和 B 的正中
央。
这样在 A->AB->B->BC->C->CD->D->DA 的通电顺序下,转子将分 8
步顺时针旋转,如果通电顺序改成 DA->D->CD->C->BC->B->AB->A,转
子将逆时针旋转。在这个过程中,每个线圈的电流方向也是固定从“2”到“1”,所以
也称作单极性驱动;转子每次只走半步 45°,所以称作半步驱动;和整步驱动相比,半
步驱动把一整步分成两个半步来跑,电机转起来会更顺畅。
STM32 技术开发手册
www.ing10bbs.com
图 8-8 单极性半步驱动
STM32 技术开发手册
www.ing10bbs.com
在驱动过程中,为了让转子的机械速度能够跟上定子的通电速度,每驱动一
步,都要延时一段时间才能驱动下一步,例如在半步驱动中,首先给 A 线圈通电,
接着延时一段时间,等转子转到 A 线圈处,然后同时给 A、B 线圈通电,再延时
一段时间,等转子转到 A、B 线圈的中央,如此类推,改变延时的时间,即可改
变速度。如果延时时间太短,转子还没有转到位,就开始驱动下一步,那么转子
就会出现失步、震荡的情况。步进电机在启动时,如果目标速度较高,必须有加
速过程,即延时时间要逐步减少,让电机的速度一步一步地提高到目标速度为止。
为了简化实验,下面的程序例子都是以低速运行,没有加速过程。
对于实际使用的步进电机,为了减少每步的角度,一般通过增加定子线圈和
转子磁铁的数目而实现。图 8-9 是永磁式步进电机的等效结构图,整步从 90°
减少到 15°,它的转子含有 6 个磁铁,定子含有 A-H 等 8 个线圈,其中 A 和
C、E 和 G、B 和 D、F 和 H 分成 4 组,每组各自并联在一起,工作时可以等效成,
转子可以等效成一对南北极的磁铁,定子等效成 4 个线圈绕组,分析方法相同。
实验中的步进电机的整步是 18°。
图 8-9 实际步进电机的磁极
STM32 技术开发手册
www.ing10bbs.com
8.2.2 双极性驱动的原理
双极性驱动分为整步驱动、半步驱动和细分驱动三大类。图 8-10 是双极性
整步驱动的工作原理图,其中 A2 端和 C2 端、B2 端和 D2 端在生产电机时,已经
在电机内部联通。首先如图 8-10(a)所示,给 C 线圈和 A 线圈通上从 C1 到 A1
的电流,C 线圈和 A 线圈同时产生上南(S)下北(N)的磁极,转子被吸引到上
南(S)下北(N)的位置;接着如图 8-10(b)所示,给 D 线圈和 B 线圈通上从
D1 到 B1 的电流,转子被吸引到左北右南的位置;然后如图 8-10(c)所示,给
A 线圈和 C 线圈通上从 A1 到 C1 的电流,转子被吸引到上北下南的位置;最后如
图 8-10(d)所示,给 B 线圈和 D 线圈通上从 B1 到 D1 的电流,转子被吸引到
左南右北的位置。
这样在 CA->DB->AC->BD 的通电顺序下,转子将分 4 步顺时针旋转,如
果通电顺序改成 BD->AC->DB->CA,转子将逆时针旋转。在这个过程中,每个
线圈的电流方向是双向改变的,例如 A 线圈的电流可以从 A2 到 A1,也可以从
A1 到 A2,所以称作双极性驱动;和单极性整步驱动一样,转子也是从一个线圈
一步到位地转到另一个线圈,每一步转过的角度也是 90°,所以也称作整步驱
动;在任意时刻,和单极性整步驱动相比,有两个线圈被通电,所产生的转矩更
大。
STM32 技术开发手册
www.ing10bbs.com
图 8-10 双极性整步驱动
双极性半步驱动的方式如图 8-11 所示,它和所示的整步驱动相比,在两个
整步之间插入了一个“半步”,例如图 8-11(b)所示,给 A、B、C、D 四个线圈
同时通电,电流方向分别从 C1 到 A1 和 D1 到 B1,A、B 线圈在靠近转子的一端,
同时形成磁力相等的北极(N), C、D 线圈在靠近转子的一端,同时形成磁力相
等的南极(S),转子在磁力的平衡作用下,停在一个整步的正中央。
这样在 CA->CA/DB->DB->DB/AC->AC->AC/BD->BD->BD/CA 的通电
顺序下, 转子将分 8 步顺时针旋转,如果通电顺序改成 BD/CA->BD->AC/BD
->AC->DB/AC->DB->CA/DB->CA,转子将逆时针旋转。在这个过程中,每个
线圈的电流方向也是双向改变的,所以也称作双极性驱动;转子每次只走半步
45°,所以称作半步驱动;在任意时刻,和单极性半步驱动相比,被通电的线圈
的数量多了一倍(2 个或 4 个),所产生的转矩更大。
STM32 技术开发手册
www.ing10bbs.com
图 8-11 双极性半步驱动
STM32 技术开发手册
www.ing10bbs.com
8.2.3 细分驱动的简介
如图 8-12 所示,假设流过 A、C 线圈的电流大小为𝐼𝑎 ,流过 B、D 线圈的电
流大小为𝐼𝑏 ,因为磁场强度和电流的大小成正比,如果𝐼𝑎 等于𝐼𝑏 ,转子将停在相
邻两个线圈的中央,如果𝐼𝑎 不等于𝐼𝑏 ,那么转子将停在电流较大的一侧,转子在
停住时,和水平方向的夹角是:
𝐼𝑎
θ = tan−1 ( )
𝐼𝑏
从中可以看出:改变𝐼𝑎 和𝐼𝑏 的比例,即可控制转子在一个整步中的任何位置
停住。细分驱动的原理就是:改变定子线圈的电流比例,让转子在旋转过程中,
可以停靠在一个整步中不同位置,把一个整步分成多个小步来跑。细分驱动具有:
转动顺畅、精度高、转矩大的特点,但控制复杂,一般需要专用芯片来实现,例
如东芝公司的 TB67S10xA 系列步进电机细分驱动芯片,最多可以把 1 个整步分
成 32 个小步。
图 8-12 细分驱动的原理
STM32 技术开发手册
www.ing10bbs.com
8.2.4 电机损耗
通常见到的各类电机,内部都是有铁芯和绕组线圈的。绕组有电阻,通电会
产生损耗,损耗大小与电阻和电流的平方成正比,这就是我们常说的铜损,如果
电流不是标准的直流或正弦波,还会产生谐波损耗;铁心有磁滞涡流效应,在交
变磁场中也会产生损耗,其大小与材料,电流,频率,电压有关,这叫铁损。
铜损和铁损都会以发热的形式表现出来,从而影响电机的效率。步进电机一
般追求定位精度和力矩输出,效率比较低,电流一般比较大,且谐波成分高,电
流交变的频率也随转速而变化,因而步进电机普遍存在发热情况,且情况比一般
交流电机严重。
8.3
28BYJ-48 步进电机旋转驱动实现
8.3.1 28BYJ-48 步进电机
28BYJ-48 步进电机自带减速器,四相五线,直径为 28mm,实物图见图 8-13。
STM32 技术开发手册
www.ing10bbs.com
图 8-13 28BYJ-48 步进电机实物图
28BYJ-48 电机内部结构等效图参考图 8-14:
图 8-14 28BYJ-48 步进电机线圈绕组示意图
28BYJ-48 步进电机旋转驱动方式见表格 8-1(4-1-2 相驱动):
表格 8-1 28BYJ-48 步进电机旋转驱动方式
导线
颜色
第1步
第2步
第3步
第4步
第5步
第6步
第7步
第8步
STM32 技术开发手册
www.ing10bbs.com
VCC 红
D橙
C黄
B粉
A蓝
5V
GND
5V
GND
GND
5V
5V
5V
5V
5V
5V
GND
GND
GND
GND
GND
GND
GND
GND
GND
28BYJ-48 步进电机主要参数见表格 8-2:
表格 8-2 28BYJ-48 步进电机主要参数
电机
型号
电压
V
相数
相电
阻
±10%
步进
角度
减速比
起动转矩
100PPS
g.cm
起动
频率
PPS
定位
转矩
g.cm
绝缘
耐压
VAC/s
28BYJ48
5
4
300Ω
5.625/6
4
1:64
≥300
≥500
≥300
600
在 28BYJ-48 步进电机主要参数见表格 8-2:
表格 8-2 中可以看到有一个减速比项目:1:64,步进角为 5.625/64 度,如
果需要转动 1 圈,那么需要 360/5.625*64=4096 个脉冲信号。
减速比这个与之前介绍的直流减速电机情况有点类似,所以 28BYJ-48 步进
电机实际上是:减速齿轮+步进电机组成,28BYJ-48 步进电机减速齿轮实物见图
8-15。
图 8-15 28BYJ-48 步进电机减速齿轮
减速齿轮计算方法见图 8-16。
STM32 技术开发手册
www.ing10bbs.com
图 8-16 28BYJ-48 步进电机减速齿轮计算
8.3.2 ULN2003 小型步进电机驱动 IC
ULN2003 是高压大电流达林顿晶体管阵列系列产品,具有电流增益高、工作
电压高、温度范围宽、带负载能力强等特点,适应于各类要求高速大功率驱动的
系统。 ULN2003A 由 7 组达林顿晶体管阵列和相应的电阻网络以及钳位二极管
网络构成,见图 8-17 和图 8-18,具有同时驱动 7 组负载的能力,为单片双极型
大功率高速集成电路。ULN2003 一般用于小型步进电机驱动。
STM32 技术开发手册
www.ing10bbs.com
图 8-17 ULN2003 功能框图
图 8-18 ULN2003A 内部驱动电路
YS-F1Pro 开发板集成了一个 ULN2003A 芯片和对应的接口,电路设计参考
图 8-19,电路专门设计预留接 28BYJ-48 步进电机的接口。
STM32 技术开发手册
www.ing10bbs.com
图 8-19 步进电机驱动
28BYJ-48 步进电机与 YS-F1Pro 开发板实物连接见图 8-20。
图 8-20 步进电机连接实物图
8.3.3 STM32CubeMX 生成工程
这里只贴出重点操作截图,并做分析。
STM32 技术开发手册
www.ing10bbs.com
1) 引脚和外设功能选择,见图 8-21。控制 ULN2003 芯片驱动 28BYJ-48 步
进电机只需要四个普通 IO 接口的输出功能。根据开发板原理图设计,这
里选择设置 PG6、PG7、PG8 和 PG9 四个引脚为输出模式。
图 8-21 引脚和外设功能选择
2) 外设功能设置,见图 8-22。设置四个引脚为普通功能的推挽输出模式,
速度设置为高,启动默认电平为低电平。
STM32 技术开发手册
www.ing10bbs.com
图 8-22 外设功能设置
8.3.4 28BYJ-48 步进电机驱动实现代码分析
限于篇幅问题,不会把例程所有代码贴出分析,只挑重点程序段分析。具体
的工程代码可以看我们对应的例程源码。
bsp_uln2003.h 文件内容
代码 8-1 ULN2003 相关宏定义
01 #define ULN2003_RCC_CLK_ENABLE()
__HAL_RCC_GPIOG_CLK_ENABLE()
02 #define ULN2003_GPIO_PIN
(GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9)
03 #define ULN2003_GPIO_PORT
GPIOG
04
05 #define A_ON
HAL_GPIO_WritePin(ULN2003_GPIO_PORT,GPIO_PIN_6,GPIO_PIN_SET)
06 #define A_OFF
HAL_GPIO_WritePin(ULN2003_GPIO_PORT,GPIO_PIN_6,GPIO_PIN_RESET)
07 #define B_ON
HAL_GPIO_WritePin(ULN2003_GPIO_PORT,GPIO_PIN_7,GPIO_PIN_SET)
08 #define B_OFF
HAL_GPIO_WritePin(ULN2003_GPIO_PORT,GPIO_PIN_7,GPIO_PIN_RESET)
09 #define C_ON
HAL_GPIO_WritePin(ULN2003_GPIO_PORT,GPIO_PIN_8,GPIO_PIN_SET)
10 #define C_OFF
HAL_GPIO_WritePin(ULN2003_GPIO_PORT,GPIO_PIN_8,GPIO_PIN_RESET)
11 #define D_ON
HAL_GPIO_WritePin(ULN2003_GPIO_PORT,GPIO_PIN_9,GPIO_PIN_SET)
12 #define D_OFF
HAL_GPIO_WritePin(ULN2003_GPIO_PORT,GPIO_PIN_9,GPIO_PIN_RESET)
STM32 技术开发手册
www.ing10bbs.com
定义控制 ULN2003 的引脚,使用宏定义方法方便以后代码移植和修改。
bsp_uln2003.c 文件内容
代码 8-2 ULN2003_GPIO_Init 函数
01 void ULN2003_GPIO_Init(void)
02 {
03
/* 定义 IO 硬件初始化结构体变量 */
04
GPIO_InitTypeDef GPIO_InitStruct;
05
06
/* 使能(开启)端口时钟 */
07
ULN2003_RCC_CLK_ENABLE();
08
09
HAL_GPIO_WritePin(ULN2003_GPIO_PORT,ULN2003_GPIO_PIN,GPIO_PIN_RESET);
10
11
/* 设定引脚 IO 编号 */
12
GPIO_InitStruct.Pin = ULN2003_GPIO_PIN;
13
/* 设定引脚 IO 为输出模式 */
14
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
15
/* 设定引脚 IO 操作速度 */
16
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
17
/* 初始化引脚 IO */
18
HAL_GPIO_Init(ULN2003_GPIO_PORT, &GPIO_InitStruct);
19 }
ULN2003_GPIO_Init 函数用于初始化 ULN2003 控制引脚,默认设置为引脚为
低电平。引脚配置为推挽输出模式,速度设置为高速。
main.c 文件内容
代码 8-3 main 函数
01 int main(void)
02 {
03
uint8_t i=0;
04
05
/* 复位所有外设,初始化 Flash 接口和系统滴答定时器 */
06
HAL_Init();
07
/* 配置系统时钟 */
08
SystemClock_Config();
09
10
ULN2003_GPIO_Init();
11
12
/* 无限循环 */
13
while (1) {
14
// 8 个节拍控制:A->AB->B->BC->C->CD->D->DA
15
switch (i) {
16
case 0:
17
A_ON; B_OFF; C_OFF; D_OFF;
18
break;
19
case 1:
20
A_ON; B_ON; C_OFF; D_OFF;
21
break;
22
case 2:
23
A_OFF; B_ON; C_OFF; D_OFF;
STM32 技术开发手册
www.ing10bbs.com
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 }
break;
case 3:
A_OFF; B_ON; C_ON; D_OFF;
break;
case 4:
A_OFF; B_OFF; C_ON; D_OFF;
break;
case 5:
A_OFF; B_OFF; C_ON; D_ON;
break;
case 6:
A_OFF; B_OFF; C_OFF; D_ON;
break;
case 7:
A_ON; B_OFF; C_OFF; D_ON;
break;
}
i++;
// 步进加 1
if (i==8) i=0;
// 重新开始循环步进
HAL_Delay(STEPMOTOR_SPEED);
// 延时一小段时间,时间长短决定电机转速
}
main 函数开始调用 HAL_Init 和 SystemClock_Config 初始化配置系统工作环
境。
ULN2003_GPIO_Init 函数初始化 ULN2003 芯片控制引脚。
在无限循环中,使用 switch 语句执行步进节拍控制,这里采用 8 节拍控制:
A->AB->B->BC->C->CD->D->DA
在执行每个节拍之后调用 HAL_Delay 函数延时一段时间。控制延时时间可以
调整步进电机的转动速度。但这个延时时间不能太过于短,步进电机有一个最快
响应频率,延时太短,步进电机无法正常工作。
实验操作与现象
将 28BYJ-48 步进电机插入到 YS-F1Pro 开发板上的 CN18 接口上。使用 12V 2A
适配器接在开发板的 DC 接口为开发板供电。下载程序,待程序正常运行后步进
电机不断旋转。
8.4
28BYJ-48 步进电机运动控制实现
上一小节是实现 28BYJ-48 步进电机旋转驱动,在实际应用用我们经常需要
控制电机转动速度、方向,根据步进电机特定,还经常需要控制电机的转动圈数
以实现负载物体运动指定距离。这一小节,我们就来实现这些功能。
STM32 技术开发手册
www.ing10bbs.com
8.4.1 STM32CubeMX 生成工程
这里只贴出重点操作截图,并做分析。
1) 引脚和外设功能选择,见图 8-23。控制 ULN2003 芯片驱动 28BYJ-48 步
进电机只需要四个普通 IO 接口的输出功能。根据开发板原理图设计,这
里选择设置 PG6、PG7、PG8 和 PG9 四个引脚为输出模式。这个实验我们
还需要开发板上的两个按键,用来调节电机的状态。
图 8-23 引脚和外设功能选择
2) 外设功能设置,见图 8-24。设置 ULN2003 控制的四个引脚为普通功能的
推挽输出模式,速度设置为高,启动默认电平为低电平。设置两个按键
引脚为输入功能。
STM32 技术开发手册
www.ing10bbs.com
图 8-24 外设功能设置
8.4.2 28BYJ-48 步进电机运动控制代码分析
限于篇幅问题,不会把例程所有代码贴出分析,只挑重点程序段分析。具体
的工程代码可以看我们对应的例程源码。
bsp_uln2003.h 和 bsp_uln2003.c 这两个文件内容与上个实验例程完全相同这
里就不再啰嗦。所以,这里我们直接来分析 main.c 文件内容。
main.c 文件内容
代码 8-4 step_motor_pulse 函数函数
01 static void step_motor_pulse(uint8_t step,uint8_t direction)
02 {
03
uint8_t temp=step;
04
05
if (direction==0) { // 如果为逆时针旋转
06
temp=8-step;
// 调换节拍信号
07
}
08
switch (temp) {
STM32 技术开发手册
www.ing10bbs.com
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 }
// 8 个节拍控制:A->AB->B->BC->C->CD->D->DA
case 0:
A_ON; B_OFF; C_OFF; D_OFF;
break;
case 1:
A_ON; B_ON; C_OFF; D_OF;
break;
case 2:
A_OFF; B_ON; C_OFF; D_OFF;
break;
case 3:
A_OFF; B_ON; C_ON; D_OFF;
break;
case 4:
A_OFF; B_OFF; C_ON; D_OFF;
break;
case 5:
A_OFF; B_OFF; C_ON; D_ON
break;
case 6:
A_OFF; B_OFF; C_OFF; D_ON;
break;
case 7:
A_ON; B_OFF; C_OFF; D_ON;
break;
}
step_motor_pulse 函数用于输出一个数据给 ULN2003 从而实现向步进电机
发送一个步进脉冲。它有两个形参,第一个为 step,指定步进序号,可选值 0~7,
代表步进电机控制信号的 8 个节拍。第二个形参为 direction,指定电机的旋转方
向,可选:1:顺时针,0:逆时针。
step_motor_pulse 函数先判断 direction 参数值,如果为 0,即明白为逆时针
旋转,则求 step 对于 8 的互补数,这里的 8 对应于步进电机的 8 节拍控制。
接下来,就使用 switch 语句执行对应节拍控制引脚电平输出。
代码 8-5 HAL_SYSTICK_Callback 函数
01 void HAL_SYSTICK_Callback(void)
02 {
03
static uint8_t count=0;
// 用于旋转速度控制
04
static uint8_t step=0;
// 当前步进节拍
05
static uint16_t pulse_count=0;
// 脉冲计数,4096 个脉冲电机旋转一圈
06
07
if (Circle_number) {
// 如果等待旋转圈数不为 0
08
count++;
// 增加时间计数
09
if (count==speed) {
// 时间计数与目标速度相对时执行下一节拍输出
10
step_motor_pulse(step,direction); // 输出新节拍信号
11
pulse_count++;
// 脉冲输出数增加
12
step++;
// 节拍数增加
13
if (step==8) step=0;
// 循环开始输出节拍
14
count=0;
// 清零时间计数
15
}
16
STM32 技术开发手册
www.ing10bbs.com
17
18
19
20
21
22
23
24
25
26
27 }
// 如果已经输出了 4096 个脉冲信号,已经转动了一圈
// 脉冲计数清零
// 等待旋转圈数减 1
if (pulse_count==4096) {
pulse_count=0;
Circle_number--;
}
} else {
A_OFF;
B_OFF;
C_OFF;
D_OFF;
// 停机
}
HAL_SYSTICK_Callback 函数是系统滴答定时器中断服务回调函数,根据例程
设置,该函数在每 1ms 就会执行一次。
函数先使用 if 语句判断变量 Circle_number 值是否为 0,变量 Circle_number
用于记录电机需要转动的圈数,如果为 0 表示电机已经达到转动了目标圈数,就
执行 else 内容,即停止电机转动。
如果还需要转动,即进入 if 语句内容,首先 count 变量自加 1,实现的效果
是每 1ms,count 变量就加 1,所以这个变量是用来计时的,实际上是用来实现
步进电机控制节拍之间延时的。又是一个 if 语句,判断 count 与 speed 变量值关
系,speed 用来指定电机选择速度,该值越小,速度越快;该值越大,速度也越
慢。如果 count 等于 speed 就调用 step_motor_pulse 函数输出下一个控制节拍,
然后把 pulse_count 和 step 这两个变量都自加 1,pulse_count 变量用于记录输出
的节拍总数,这是在控制电机转动距离非常重要的变量;step 变量用于记录节拍
编号。
执行完上面 if 语句后,又有一个 if 语句,判断 pulse_count 变量值是否等于
4096,在前面的内容有讲过,28BYJ-48 步进电机的步进角度为:5.625/64,所以
要转动一圈的脉冲数为:360/(5.625/64)=4096。如果 pulse_count 等于 4096,
说明步进电机已经旋转了一圈,我们就清零 pulse_count,让它重新计数,并把等
待旋转圈数 Circle_number 减 1 处理。
代码 8-6 main 函数
01 int main(void)
02 {
03
/* 复位所有外设,初始化 Flash 接口和系统滴答定时器 */
04
HAL_Init();
05
/* 配置系统时钟 */
06
SystemClock_Config();
07
08
KEY_GPIO_Init();
STM32 技术开发手册
www.ing10bbs.com
09
10
11
12
13
14
15
16
17
18
19
20
21
22 }
ULN2003_GPIO_Init();
/* 无限循环 */
while (1) {
if (KEY1_StateRead()==KEY_DOWN) {
Circle_number=STEPMOTOR_CIRCLE_NUMBER;//赋值给目标旋转圈数,重新开始旋转
}
if (KEY2_StateRead()==KEY_DOWN) {
direction=1-direction; // 1 --> 0 ; 0 --> 1 反转方向
}
}
main 函数开始调用 HAL_Init 和 SystemClock_Config 初始化配置系统工作环
境。
KEY_GPIO_Init 函数初始化按键引脚。
ULN2003_GPIO_Init 函数初始化 ULN2003 芯片控制引脚。
在 无 限 循 环 中 , 不 断 扫 描 按 键 状 态 , 当 按 下 KEY1 时 , 重 新 赋 值 给
Circle_number 变量,设置待转动圈数,电机开始旋转;按下 KEY2 时,改变 direction
变量值,电机旋转方向改变。
实验操作与现象
将 28BYJ-48 步进电机插入到 YS-F1Pro 开发板上的 CN18 接口上。使用 12V 2A
适配器接在开发板的 DC 接口为开发板供电。下载程序,待程序正常运行后,步进
电机自动旋转 10 圈后停下,我们可以按下 KEY1 让它再次旋转 10 圈,按下 KEY2
可以改变旋转方向。
8.5
步进电机驱动器
类似 28BYJ-48 这些小尺寸、低转矩的步进电机使用 ULN2003 这类驱动芯片
并配合控制器使用是可行的,但在实际工业应用中,经常使用到 42、57、86、
110(这些参数表示电机尺寸大小)等等系列步进电机,这些步进电机需要驱动
功率大的电路才能使其正常运转,另外,为充分实现发挥细分优势,往往需要更
加复杂的驱动电路。好在,很久之前很多半导体公司已经看到了这部分商机,专
门为驱动步进电机设计了驱动芯片,种类繁多,功能齐全,同时也使得我们控制
步进电机运动非常简单。
STM32 技术开发手册
www.ing10bbs.com
8.5.1 57 步进电机
步进电机在工业设备中发挥着重要作用,这里就以型号为 57BYG250B 的步
进电机为例向大家展示它们的性能。57BYG250B 步进电机(简称 57 步进电机,
下同)是产品实物图见图 8-25。
图 8-25 57 步进电机
57BYG250B 步进电机参数见表格 8-3:
表格 8-3 57BYG250B 步进电机参数
步距
角度
长度
mm
相电
流A
相电
阻Ω
相电
感 mH
扭矩
NM
转动惯量
型号
57BYG250B
1.8
56
2.5
0.9
2.5
1.2
g.cm2
重量
KG
引线
300
0.7
4线
四根引出线定义:黑色 A+ 绿色 A- 红色 B+ 蓝色 B-
STM32 技术开发手册
www.ing10bbs.com
图 8-26 57 步进电机频率特性
8.5.2 步进电机驱动芯片
TOSHIBA 公司的 TB67S109A 芯片是一种配备 PWM 斩波器的两相双极步进电
机驱动器。内置时钟解码器。采用 BiCD 工艺制作,额定值为 50 V/4.0 A。允许全
步、半步、1/4、1/8、1/16、1/32 步运行,即细分最高为 32。TB67S109A 芯片的
内部逻辑电路见图 8-27。关于 TB67S109A 芯片的具体使用方法可以参考芯片的
技术生成,见《附件 2:TB67S109AFTG 芯片中文说明书.pdf》。
STM32 技术开发手册
www.ing10bbs.com
图 8-27 TB67S109A 芯片的内部逻辑电路
实际上,TB67S109A 芯片已经被很多厂家应用并设计出了对应的步进电机驱
动器,并且价格都很容易接受,所以从实际成本来考虑,建议直接购买驱动器使
用就好。
STM32 技术开发手册
www.ing10bbs.com
8.5.3 步进电机驱动器
TB67S109A 步进电机驱动器是一款专业的两相步进电机驱动,见图 8-28。可
实现正反转控制,通过 3 位 拨码开关选择 7 档细分控制(1,2/A,2/B,4,8,16,32,),
通过 3 位拨码开关选 择 8 档电流控制(0.5A,1A,1.5A,2A,2.5A,2.8A,3.0A,3.5A)。
适合驱动 57、42 型两相、四相混合式步进电机。能达到低振动、小噪声、高速
度的效果驱动电机。
图 8-28 步进电机驱动器
1) 驱动器特性见表格 8-4:
表格 8-4 步进电机驱动器电气参数
输入电压 DC9-40V
输入电流 推荐使用开关电源功率 5A
输出电流 0.5-4.0A
最大功耗 160W
1,2/A,2/B,4,8,16,32
细 分
温 度
工作温度-10~45℃
湿 度
不能结露,不能有水珠
重 量
0.2 千克
气 体
禁止有可燃气体和导电灰尘
2) 输入输出端说明
 信号输入端
PUL+:脉冲信号输入正。
PUL-:脉冲信号输入负。
STM32 技术开发手册
www.ing10bbs.com
DIR+:电机正、反转控制正。
EN+:电机脱机控制正。
 电机绕组连接
A+:连接电机绕组 A+相。
B+:连接电机绕组 B+相。
 电源电压连接
VCC:电源正端“+”
DIR-:电机正、反转控制负。
EN-:电机脱机控制负。
A-:连接电机绕组 A-相。
B-:连接电机绕组 B-相。
GND:电源负端“-”
注意:DC9-40V。不可以超过此范围,否则会无法正常工作甚至损坏驱动器。
3) 输入端接线说明
输入信号共有三路,它们是:①步进脉冲信号 PUL+,PUL-;②方向电平信号
DIR+,DIR-③脱机信号 EN+,EN-。输入信号接口有两种接法,用户可根据 需要
采用共阳极接法或共阴极接法。
共阳极接法:分别将 PUL+,DIR+,EN+连接到控制系统的电源上,如果此电
源是+5V/3.3V 则可直接接入,如果此电源大于+5V,则须外部另加限流电阻 R,
保证给驱动器内部光藕提供 8—15mA 的驱动电流。脉冲输入信号通过 PUL-接入,
方向信号通过 DIR-接入,使能信号通过 EN-接入。
共阴极接法:分别将 PUL-,DIR-,EN-连接到控制系统的地线(GND)上。脉
冲输入信号通过 PUL+接入,方向信号通过 DIR+接入,使能信号通过 EN+接入。
如果此控制系统信号线是+5V/3.3V 则可直接接入,如果此信号电压大于+5V,则
须外部另加限流电阻 R,保证给驱动器内部光藕提供 8—15mA 的驱动电流。
配合 YS-F1Pro 开发板使用,采用共阳接法,即 PUL+、DIR+和 EN+接在开发板
上的 5V(3.3V 也是可以的,经过不同驱动器测试使用 5V 效果更好)排针上,
PUL-、DIR-和 EN-接在对应的控制信号线上。此时,信号线必须配置为开漏输出
模式。
步进电机驱动器接线示意图见图 8-29。
STM32 技术开发手册
www.ing10bbs.com
图 8-29 步进电机驱动器接线示意图
4) 细分数设定
细分数是以驱动板上的拨码开关选择设定的,用户可根据驱动器外盒上 的
细分选择表的数据设定(最好在断电情况下设定) 。细分后步进电机步距 角按
下列方法计算:步距角=电机固有步距角/细分数。如:一台固有步距角 为 1.8°
的步进电机在 4 细分下步距角为 1.8°/4=0.45°
驱动板上拨码开关 SW1、SW2、SW3、分别对应 S1、S2、S3。
表格 8-5 细分数设定
首先,我们看细分数为 1,实际上就是没细分功能,如果给驱动器 200 个脉
冲,就可以使得电机旋转一圈。另外,可以看到要驱动电机旋转一圈的脉冲数与
STM32 技术开发手册
www.ing10bbs.com
细分数是成正比的。一旦确定了细分数,控制程序也需要根据细分数进行修改,
可以说细分数与控制程序是息息相关的。
YS-F1Pro 开发板例程默认使用 32 细分数,即设置 SW1、SW2 和 SW3 都为
OFF 状态,这里要特别记住,32 细分数对应是转动一圈需要 6400 个脉冲,这在
控制步进电机转动圈数非常重要。
5) 电流大小设定
驱动板上拨码开关 SW4、SW5、SW6 分别对应 S4、S5、S6。
表格 8-6 电流大小设定
为配合 57BYG250B 步进电机使用这里设置选择 3.5A 电流,即 SW4、SW5 和
SW6 都为 OFF,见图 8-30。
STM32 技术开发手册
www.ing10bbs.com
图 8-30 拨码开关设置
STM32 技术开发手册
www.ing10bbs.com
第9章 步进电机控制
步进电机旋转驱动实现
9.1
上面分别介绍了 57 步进电机及其驱动器,驱动器的拨码开关中细分数设置
就与程序相关,而电流大小设置就只与步进电机型号相关,所以这里要说明一下,
步进电机驱动器不仅可以驱动 57 步进电机,也可以驱动 42、86 步进电机,只要
电机参数在驱动器范围之内就可以,步进电机与驱动器的接线方法一般都是相同
的。
另外,驱动程序只与细分数设置有关,不同细分数,程序参数需要做对应的
修改,实际上,开发板控制器根本就不知道用户接的是 57 步进电机还是 42 或者
86 的步进电机,控制器也不用管这些,因为即使使用不同类型步进电机控制器
的程序都是一样的。
控制器有三组控制线,其中,DIR+,DIR-和 EN+,EN-这两组线只需要使用普
通 IO 控制就好,为方便与 YS-F1Pro 开发板连接,这里选择 PB13 和 PB14 引脚;
PUL+,PUL-这组线一般接在定时器通道引脚,使用 stm32 的定时器功能,持续在
定时器通道输出脉冲信号。stm32f103zet6 芯片的定时器功能引脚分布可以见,
图 5-4 这里选择 TIM1 的 CH1(对应 PA8 引脚)做为脉冲输出引脚。
9.1.1 STM32CubeMX 生成工程
这里只贴出重点操作截图,并做分析。
1) 引脚和外设功能选择,见图 9-1。驱动器脱机信号和方向控制信号使用
普通的输出模式。这里使用 TIM1 作为脉冲生成定时器,设置使用内部时
钟源,设置 CH1 为比较输出模式。
STM32 技术开发手册
www.ing10bbs.com
图 9-1 引脚和外设功能选择
2) GPIO 外设功能设置,见图 9-2。设置驱动器的脱机和方向控制信号为开
漏输出模式,默认输出高电平,速度设置为高。
STM32 技术开发手册
www.ing10bbs.com
图 9-2 GPIO 外设功能设置
3) 定时器功能设置,见图 9-3。设置定时器预分频器为 3,实际可得到 72/
(3+1)=18MHz 的定时器时钟,采用向上计数模式,使用比较输出模式,
定时器计数周期设置为 0xFFFF。设置定时器通道 1 的输出比较模式为翻
转模式,计数脉冲数设置为 500,该值越小输出脉冲频率越快,该值越大
输出脉冲频率越慢,控制步进电机转速就是通过设置该值大小实现的。
STM32 技术开发手册
www.ing10bbs.com
图 9-3 定时器功能设置
4) 定时器中断配置,见图 9-4。这里使能定时器捕获比较中断,即在定时器
计数值与定时器通道脉冲值相等时生成中断。
STM32 技术开发手册
www.ing10bbs.com
图 9-4 定时器中断配置
5) 定时器通道引脚配置,见图 9-5。配置定时器通道引脚功能,设置为复用
功能开漏输出模式,速度设置为高。
图 9-5 定时器通道引脚配置
STM32 技术开发手册
www.ing10bbs.com
6) NVIC 中断优先级配置,见图 9-6。首先选择 NVIC 为 2bit 主优先级和 2bit
次优先级。设置 TIM1 的捕获比较中断优先级为主优先级为 0,次优先级
也为 0。
图 9-6 NVIC 中断优先级配置
9.1.2 步进电机旋转驱动代码分析
限于篇幅问题,不会把例程所有代码贴出分析,只挑重点程序段分析。具体
的工程代码可以看我们对应的例程源码。
bsp_STEPMOTOR.h 文件内容
代码 9-1 步进电机驱动器相关宏定义
01 #define STEPMOTOR_TIMx
02 #define STEPMOTOR_TIM_RCC_CLK_ENABLE()
03 #define STEPMOTOR_TIM_RCC_CLK_DISABLE()
04 #define STEPMOTOR_TIMx_IRQn
05 #define STEPMOTOR_TIMx_IRQHandler
06
07 // 输出控制脉冲给电机驱动器
08 // 对应驱动器的 PUL-(驱动器使用共阳接法)
09 // 而 PLU+直接接开发板的 5V(或者 3.3V)
TIM1
__HAL_RCC_TIM1_CLK_ENABLE()
__HAL_RCC_TIM1_CLK_DISABLE()
TIM1_CC_IRQn
TIM1_CC_IRQHandler
STM32 技术开发手册
www.ing10bbs.com
10 #define STEPMOTOR_TIM_GPIO_CLK_ENABLE()
__HAL_RCC_GPIOA_CLK_ENABLE()
11 #define STEPMOTOR_TIM_PUL_PORT
GPIOA
12 #define STEPMOTOR_TIM_PUL_PIN
GPIO_PIN_8
13
14 // 电机旋转方向控制,如果悬空不接默认正转
15 // 对应驱动器的 DIR-(驱动器使用共阳接法)
16 // 而 DIR+直接接开发板的 5V(或者 3.3V)
17 #define STEPMOTOR_DIR_GPIO_CLK_ENABLE()
__HAL_RCC_GPIOB_CLK_ENABLE()
18 #define STEPMOTOR_DIR_PORT
GPIOB
19 #define STEPMOTOR_DIR_PIN
GPIO_PIN_13
20
21 // 电机脱机使能控制,如果悬空不接默认使能电机
22 // 对应驱动器的 ENA-(驱动器使用共阳接法)
23 // 而 ENA+直接接开发板的 5V(或者 3.3V)
24 #define STEPMOTOR_ENA_GPIO_CLK_ENABLE()
__HAL_RCC_GPIOB_CLK_ENABLE()
25 #define STEPMOTOR_ENA_PORT
GPIOB
26 #define STEPMOTOR_ENA_PIN
GPIO_PIN_14
27
28 #define STEPMOTOR_DIR_FORWARD()
29
HAL_GPIO_WritePin(STEPMOTOR_DIR_PORT,STEPMOTOR_DIR_PIN,GPIO_PIN_SET)
30 #define STEPMOTOR_DIR_REVERSAL()
31
HAL_GPIO_WritePin(STEPMOTOR_DIR_PORT,STEPMOTOR_DIR_PIN,GPIO_PIN_RESET)
32
33 #define STEPMOTOR_OUTPUT_ENABLE()
34
HAL_GPIO_WritePin(STEPMOTOR_ENA_PORT,STEPMOTOR_ENA_PIN,GPIO_PIN_SET)
35 #define STEPMOTOR_OUTPUT_DISABLE()
36
HAL_GPIO_WritePin(STEPMOTOR_ENA_PORT,STEPMOTOR_ENA_PIN,GPIO_PIN_RESET)
37
38
39 // 定义定时器预分频,定时器实际时钟频率为:72MHz/(STEPMOTOR_TIMx_PRESCALER+1)
40 #define STEPMOTOR_TIM_PRESCALER
3 // 实际时钟频率为:18MHz
41
42 // 定义定时器周期,输出比较模式周期设置为 0xFFFF
43 #define STEPMOTOR_TIM_PERIOD
0xFFFF
44 // 定义高级定时器重复计数寄存器值
45 #define STEPMOTOR_TIM_REPETITIONCOUNTER
0
定义使用 TIM1,stm32 有多个定时器,通用定时器和高级控制定时器都有办
法实现输出比较功能,所以是允许选择其他 TIM 作为步进电机控制脉冲生成的
定时器的,只是选择不同定时器对应的程序都需要进行修改。高级控制定时器有
多个中断源,这里选择捕获比较中断。
使用 TIM1 的 CH1 默认引脚为 PA8,然后在定义驱动器的脱机和方向控制引
脚分别为 PB13 和 PB14。
宏定义了 STEPMOTOR_DIR_FORWARD 和 STEPMOTOR_DIR_REVERSAL 用于电
机方向控制,都是通过调用 HAL_GPIO_WritePin 函数实现,实际上就是控制 PB13
引脚输出低、高电平实现的,所以如果旋转方向与你想要的方向相反,直接修改
这两个宏定义内容就好了。
STM32 技术开发手册
www.ing10bbs.com
宏定义了 STEPMOTOR_OUTPUT_ENABLE 和 STEPMOTOR_OUTPUT_DISABLE 用
于设置驱动器的脱机工作,STEPMOTOR_OUTPUT_ENABLE 是表示允许驱动器驱动
电机旋转,这里是设置 PB14 输出高电平,即脱机(不工作)信号无效。
bsp_STEPMOTOR.c 文件内容
代码 9-2 STEPMOTOR_GPIO_Init 函数
01 static void STEPMOTOR_GPIO_Init(void)
02 {
03
GPIO_InitTypeDef GPIO_InitStruct;
04
05
/* 引脚端口时钟使能 */
06
STEPMOTOR_TIM_GPIO_CLK_ENABLE();
07
STEPMOTOR_DIR_GPIO_CLK_ENABLE();
08
STEPMOTOR_ENA_GPIO_CLK_ENABLE();
09
10
/*驱动器输出脉冲控制引脚 IO 初始化 */
11
GPIO_InitStruct.Pin = STEPMOTOR_TIM_PUL_PIN;
12
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
13
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
14
HAL_GPIO_Init(STEPMOTOR_TIM_PUL_PORT, &GPIO_InitStruct);
15
16
/*驱动器方向控制引脚 IO 初始化 */
17
HAL_GPIO_WritePin(STEPMOTOR_DIR_PORT,STEPMOTOR_DIR_PIN,GPIO_PIN_SET);
18
GPIO_InitStruct.Pin = STEPMOTOR_DIR_PIN;
19
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
20
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
21
HAL_GPIO_Init(STEPMOTOR_DIR_PORT, &GPIO_InitStruct);
22
23
/*驱动器使能控制引脚 IO 初始化 */
24
HAL_GPIO_WritePin(STEPMOTOR_ENA_PORT,STEPMOTOR_ENA_PIN,GPIO_PIN_SET);
25
GPIO_InitStruct.Pin = STEPMOTOR_ENA_PIN;
26
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
27
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
28
HAL_GPIO_Init(STEPMOTOR_ENA_PORT, &GPIO_InitStruct);
29 }
STEPMOTOR_GPIO_Init 函数用于初始化驱动器相关 IO。定时器通道功能引脚
设置为复用功能开漏输出模式,速度设置为高;驱动器方向控制和脱机功能控制
引脚设置为普通的开漏输出模式。注意这里三个引脚都不能设置为推挽输出模式。
代码 9-3 STEPMOTOR_TIMx_Init 函数
01 void STEPMOTOR_TIMx_Init(void)
02 {
03
TIM_ClockConfigTypeDef sClockSourceConfig;
// 定时器时钟
04
TIM_MasterConfigTypeDef sMasterConfig;
// 定时器主模式配置
05
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;
// 刹车和死区时间配置
06
TIM_OC_InitTypeDef sConfigOC;
// 定时器通道比较输出
07
08
/* 定时器基本环境配置 */
09
htimx_STEPMOTOR.Instance = STEPMOTOR_TIMx;
// 定时器编号
10
htimx_STEPMOTOR.Init.Prescaler = STEPMOTOR_TIM_PRESCALER;
// 定时器预分频器
STM32 技术开发手册
www.ing10bbs.com
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56 }
htimx_STEPMOTOR.Init.CounterMode = TIM_COUNTERMODE_UP;
// 计数方向:向上计数
htimx_STEPMOTOR.Init.Period = STEPMOTOR_TIM_PERIOD;
// 定时器周期
htimx_STEPMOTOR.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
// 时钟分频
htimx_STEPMOTOR.Init.RepetitionCounter = STEPMOTOR_TIM_REPETITIONCOUNTER; // 重复计数器
HAL_TIM_Base_Init(&htimx_STEPMOTOR);
/* 定时器时钟源配置 */
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
// 使用内部时钟源
HAL_TIM_ConfigClockSource(&htimx_STEPMOTOR, &sClockSourceConfig);
/* 初始化定时器比较输出环境 */
HAL_TIM_OC_Init(&htimx_STEPMOTOR);
/* 定时器主输出模式 */
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htimx_STEPMOTOR, &sMasterConfig);
/* 刹车和死区时间配置 */
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 0;
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
HAL_TIMEx_ConfigBreakDeadTime(&htimx_STEPMOTOR, &sBreakDeadTimeConfig);
/* 定时器比较输出配置 */
sConfigOC.OCMode = TIM_OCMODE_TOGGLE;
// 比较输出模式:反转输出
sConfigOC.Pulse = Toggle_Pulse;
// 脉冲数
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
// 输出极性
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_LOW;
// 互补通道输出极性
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
// 快速模式
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
// 空闲电平
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
// 互补通道空闲电平
HAL_TIM_OC_ConfigChannel(&htimx_STEPMOTOR, &sConfigOC, TIM_CHANNEL_1);
/* STEPMOTOR 相关 GPIO 初始化配置 */
STEPMOTOR_GPIO_Init();
/* 配置定时器中断优先级并使能 */
HAL_NVIC_SetPriority(STEPMOTOR_TIMx_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(STEPMOTOR_TIMx_IRQn);
STEPMOTOR_TIMx_Init 函数用于驱动器定时器初始化配置。首先是配置定时
器的基本工作环境,设置定时器的编号、预分频器、计数模式、周期等等。
设置定时器使用内部时钟。定时器主输出模式和刹车和死区时间功能都不需
要使能。
接下来,配置比较输出参数,设置为比较输出模式为翻转输出,脉冲数由
Toggle_Pulse 变量赋值,定时器计数值达到 Toggle_Pulse 值时就翻转通道引脚。
最后,设置定时器中断优先级并使能中断。
STM32 技术开发手册
www.ing10bbs.com
main.c 文件内容
代码 9-4 HAL_TIM_OC_DelayElapsedCallback 函数
01 void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
02 {
03
__IO uint16_t count;
04
count=__HAL_TIM_GET_COUNTER(&htimx_STEPMOTOR);
05
__HAL_TIM_SET_COMPARE(&htimx_STEPMOTOR,TIM_CHANNEL_1,count+Toggle_Pulse);
06 }
HAL_TIM_OC_DelayElapsedCallback 函数是定时器比较输出中断回调函数。在
之前我们已经使能了定时器捕获比较站点,在定时器计数器值与捕获比较寄存器
值相等时发生中断,就会调用该函数。
__HAL_TIM_GET_COUNTER 函数用于获取当前定时器计数值,并存放在 count
变量内。
__HAL_TIM_SET_COMPARE 函数用于设置定时器通道输出比较值,这里把
count 和 Toggle_Pulse 变量值之和赋给它。
下面我们以通俗一点语言讲解脉冲生成原理:前面我们使能了定时器比较
输出中断,并且设置了定时器通道为比较输出模式:翻转输出,初始化设置了通
道的脉冲数为 Toggle_Pulse(即 500),在启动定时器运行之后,定时器从 0 开始
计数,等计数到 Toggle_Pulse(即 500)值时就会产生中断,翻转通道引脚,并
执行 HAL_TIM_OC_DelayElapsedCallback 函数,在函数内,我们读取当前定时器
计 数 值 保 存 在 变 量 count ( 此 时 该 值 为 500 ), 并 设 置 新 的 比 较 值 为 为
count+Toggle_Pulse(即 500+500=1000)。接下来,定时器继续计数,等到计数值
到 1000 时,就又产生中断,翻转通道引脚,并把比较值设置为 1500(1000+
Toggle_Pulse),如此循环执行…..最终的效果也就在定时器通道引脚输出持续的
脉冲信号,并且脉冲信号的周期为 2* Toggle_Pulse,也就是改变 Toggle_Pulse 值
可以非常方便改变脉冲信号的频率。
代码 9-5 main 函数
01 int main(void)
02 {
03
/* 复位所有外设,初始化 Flash 接口和系统滴答定时器 */
04
HAL_Init();
05
/* 配置系统时钟 */
06
SystemClock_Config();
07
08
/* 高级控制定时器初始化并配置 PWM 输出功能 */
STM32 技术开发手册
www.ing10bbs.com
09
10
11
12
13
14
15
16
17
18
19 }
STEPMOTOR_TIMx_Init();
/* 确定定时器 */
HAL_TIM_Base_Start(&htimx_STEPMOTOR);
/* 启动比较输出并使能中断 */
HAL_TIM_OC_Start_IT(&htimx_STEPMOTOR,TIM_CHANNEL_1);
/* 无限循环 */
while (1) {
}
main 函数开始调用 HAL_Init 和 SystemClock_Config 初始化配置系统工作环
境。
STEPMOTOR_TIMx_Init 函数初始化配置定时器工作环境。
HAL_TIM_Base_Start 函数使能定时器,开始计数。
HAL_TIM_OC_Start_IT 函数使能定时器输出比较功能,并开启中断。在中断
服务回调函数内执行任务程序。
实验操作与现象
根据 bsp_STEPMOTOR.h 文件中引脚定义方法连接开发板和步进电机驱动器,
另外驱动器和步进电机的连接自行根据电机和驱动器标识连接,驱动器需要一个
24V 5A 的直流电源供电。使用开发板配套的 MINI USB 线连接到开发板标示“调
试串口”字样的 MIMI USB 接口为开发板提供电源。下载完程序之后,开发板持
续输出脉冲给步进电机驱动器,步进电机持续转动。
9.2
步进电机运动控制实现
上一个例程,我们实现了程序控制电机驱动器,使其驱动步进电机旋转,但
也只是旋转而已。实际应用中还有很多需要实现,方向控制、脱机控制、速度调
节、定距离运动等等。这个例程我们就实现这些功能,为操作方便,我们直接使
用开发板上的两个独立按键来实现这些功能的选择。
这个例程的 STM32CubeMX 软件操作就不再演示了,实际上跟上个工程几乎
一模一样,贴出来也是没啥意思。这个工程就只是在上个例程基础上添加两个按
键功能。
STM32 技术开发手册
www.ing10bbs.com
bsp_STEPMOTOR.h 和 bsp_STEPMOTOR.c 这两个文件内容也跟上个例程一样
的,这里也没有讲解的必要,还不清楚就倒回去就好。
所以,这一小节内容重点就只分析 main.c 文件内容就好。
main.c 文件内容
代码 9-6 宏定义和变量定义
01 /* 私有宏定义 ----------------------------------------------------------------*/
02 #define STEPMOTOR_MICRO_STEP
32 // 步进电机驱动器细分,必须与驱动器实际设置对应
03
04 /* 私有变量 ------------------------------------------------------------------*/
05 uint8_t dir=0; // 0 :顺时针
1:逆时针
06 uint8_t ena=0; // 0 :正常运行 1:停机
07
08 /* 扩展变量 ------------------------------------------------------------------*/
09 extern __IO uint16_t Toggle_Pulse; /* 步进电机速度控制,可调节范围为 650 -- 3500 ,值越小速度越快
10 */
11
12 /*
13 *
当步进电机驱动器细分设置为 1 时,每 200 个脉冲步进电机旋转一周
14 *
为 32 时,每 6400 个脉冲步进电机旋转一周
15 *
下面以设置为 32 时为例讲解:
16 *
pulse_count 用于记录输出脉冲数量,pulse_count 为脉冲数的两倍,
17 *
比如当 pulse_count=12800 时,实际输出 6400 个完整脉冲。
18 *
这样可以非常方便步进电机的实际转动圈数,就任意角度都有办法控制输出。
19 *
如果步进电机驱动器的细分设置为其它值,pulse_count 也要做相应处理
20 *
21 */
22 __IO uint32_t pulse_count; /* 脉冲计数,一个完整的脉冲会增加 2 */
STEPMOTOR_MICRO_STEP 定义了驱动器的细分数,前面已经介绍到,细分数
与旋转一圈需要的脉冲数成正比,所以细分数在控制电机旋转圈数,也即运动距
离,起着非常重要的作用。实际上,这里这个例程程序的这个宏定义值不可以改
变,只能是 32,因为设置细分数不同,我们也需要修改定时器的时钟频率以便更
好的输出合适频率的脉冲。
dir 和 ena 变量分别定义了当前电机的运动方向和脱机使能情况。
Toggle_Pulse 变量是在 bsp_STEPMOTOR.c 文件中定义的,是定时器通道脉冲
数计数,该值决定脉冲频率。
pulse_count 变量用于记录输出脉冲的数量,每输出一个脉冲给电机驱动器
该变量值就增加 2,这是因为引脚翻转两次才是一个完整的脉冲信号。
代码 9-7 HAL_TIM_OC_DelayElapsedCallback 函数
01 void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
STM32 技术开发手册
www.ing10bbs.com
02 {
03
04
05
06
07 }
__IO uint16_t count;
count=__HAL_TIM_GET_COUNTER(&htimx_STEPMOTOR);
__HAL_TIM_SET_COMPARE(&htimx_STEPMOTOR,TIM_CHANNEL_1,count+Toggle_Pulse);
pulse_count++;
HAL_TIM_OC_DelayElapsedCallback 函数功能及代码实现在上一个例程已经
作了非常详细的说明,这里就不再啰嗦。
相比上个例程,这里只添加了见 pulse_count 变量值自加一的这一条语句,
实现每输出一个完整脉冲,该变量中会增加 2 的效果。
代码 9-8 main 函数
01 int main(void)
02 {
03
uint8_t key1_count=1;
04
05
/* 复位所有外设,初始化 Flash 接口和系统滴答定时器 */
06
HAL_Init();
07
/* 配置系统时钟 */
08
SystemClock_Config();
09
10
KEY_GPIO_Init();
11
12
/* 高级控制定时器初始化并配置 PWM 输出功能 */
13
STEPMOTOR_TIMx_Init();
14
15
/* 确定定时器 */
16
HAL_TIM_Base_Start(&htimx_STEPMOTOR);
17
/* 启动比较输出并使能中断 */
18
HAL_TIM_OC_Start_IT(&htimx_STEPMOTOR,TIM_CHANNEL_1);
19
20
/* 无限循环 */
21
while (1) {
22
if (KEY1_StateRead()==KEY_DOWN) { // 功能选择
23
key1_count++;
24
if (key1_count==5)
25
key1_count=1;
26
}
27
if (KEY2_StateRead()==KEY_DOWN) { // 功能调节
28
switch (key1_count) {
29
case 1:
30
Toggle_Pulse-=50;
31
if (Toggle_Pulse<300) // 最快速度限制
32
Toggle_Pulse=300;
33
break;
34
case 2:
35
Toggle_Pulse+=100;
36
if (Toggle_Pulse>3500)
// 最慢速度限制
37
Toggle_Pulse=3500;
38
break;
39
case 3:
// 方向控制
40
if (dir==0) {
41
STEPMOTOR_DIR_REVERSAL(); // 反转
42
dir=1;
43
} else {
44
STEPMOTOR_DIR_FORWARD();
// 正转
STM32 技术开发手册
www.ing10bbs.com
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66 }
dir=0;
}
break;
case 4:
// 脱机使能控制
if (ena==0) {
STEPMOTOR_OUTPUT_ENABLE(); // 停机
ena=1;
} else {
pulse_count=0;
// 重新计数
STEPMOTOR_OUTPUT_DISABLE(); // 正常运行
ena=0;
}
break;
default:
break;
}
}
if (pulse_count==STEPMOTOR_MICRO_STEP*200*2*10) { // 转动 10 圈后停机
STEPMOTOR_OUTPUT_DISABLE(); // 停机
}
}
相比上个例程,main 函数除了添加了 KEY_GPIO_Init 函数用于初始化两个按
键引脚,之外就是多了无线循环的内容。
在无限循环中,通过 if 语句判断两个按键状态,当有按键按下时则执行对应
的程序。KEY1 只是简单的递增 key1_count 变量值,实际上该变量每个值对应一
种功能,在按下 KEY2 键后才执行功能内容,所以 KEY1 是一个功能选择键,KEY2
是功能确认运行键。
这里总共列举了 4 种功能。首先是加速功能和减速功能,之前不止一次说到
改变 Toggle_Pulse 变量值是可以修改脉冲输出频率的,从而达到控制电机旋转速
度的效果。当 Toggle_Pulse 值变小时,输出脉冲频率越高,电机转速越快,但电
机有一个最快响应频率,所以不能太快,另外,太快的话电机输出转矩也相对较
小。当 Toggle_Pulse 值变大时,输出脉冲频率就越慢,电机转速也越慢。
第三个功能是电机旋转方向控制,通过调用 STEPMOTOR_DIR_REVERSAL 和
STEPMOTOR_DIR_FORWARD 两个宏定义实现。
第四个功能是脱机使能功能,可以方便的控制电机旋转与否。
无限循环中的最后一个 if 语句用于判断输出圈数,如果已经输出 10 圈则停
机。
STM32 技术开发手册
www.ing10bbs.com
实验操作与现象
根据 bsp_STEPMOTOR.h 文件中引脚定义方法连接开发板和步进电机驱动器,
另外驱动器和 57&42 步进电机的连接自行根据电机和驱动器标识连接,驱动器
需要一个 24V 5A 的直流电源供电。使用开发板配套的 MINI USB 线连接到开发板
标示“调试串口”字样的 MIMI USB 接口为开发板提供电源。下载完程序之后,
开发板持续输出脉冲给电机驱动器,步进电机持续转动 10 圈后停止,在其转动
器件可以按下 KEY1 和 KEY2 组合实现电机转速、方向、停止、启动等等控制。
9.3
四轴步进电机运动控制实现
上一例程已经实现了一个步进电机旋转控制,在实际工业应用中一个设备一
般都需要同时使用多个电机,stm32f1x 芯片有 6 个定时器可以实现比较输出功
能,并且每个定时器都有多个通道(四个),所以如果要求 stm32f1x 芯片同时控
制 4 个、8 个、16 个步进电机的简单旋转是完全没问题的。这一例程,我们简单
实现 4 个步进电机旋转控制。实际上,四个电机控制与一个电机控制程序从结构
上几乎完全一样的,这里就算是再次啰嗦吧。
9.3.1 STM32CubeMX 生成工程
这里只贴出重点操作截图,并做分析。
1) 引脚和外设功能选择,见图 9-7 图 9-1。驱动器脱机信号和方向控制信
号使用普通的输出模式。这里使用 TIM1 作为脉冲生成定时器,设置使用
内部时钟源,设置定时器四个通道都为比较输出模式。
STM32 技术开发手册
www.ing10bbs.com
图 9-7 引脚和外设功能选择
2) GPIO 外设功能设置,见图 9-8。设置驱动器的脱机和方向控制信号为开
漏输出模式,默认输出高电平,速度设置为高。
STM32 技术开发手册
www.ing10bbs.com
图 9-8 GPIO 外设功能设置
3) 定时器功能设置,见图 9-9。设置定时器预分频器为 3,实际可得到 72/
(3+1)=18MHz 的定时器时钟,采用向上计数模式,使用比较输出模式,
定时器计数周期设置为 0xFFFF。设置定时器通道 1 的输出比较模式为翻
转模式,计数脉冲数设置为 800,其他通道采用类似的配置。
STM32 技术开发手册
www.ing10bbs.com
图 9-9 定时器功能设置
4) 定时器中断配置,见图 9-10。这里使能定时器捕获比较中断,即在定时
器计数值与定时器通道脉冲值相等时生成中断。
STM32 技术开发手册
www.ing10bbs.com
图 9-10 定时器中断配置
5) 定时器通道引脚配置,见图 9-11。配置定时器通道引脚功能,设置为复
用功能开漏输出模式,速度设置为高。
STM32 技术开发手册
www.ing10bbs.com
图 9-11 定时器通道引脚配置
6) NVIC 中断优先级配置,见图 9-12。首先选择 NVIC 为 2bit 主优先级和 2bit
次优先级。设置 TIM1 的捕获比较中断优先级为主优先级为 0,次优先级
也为 0。
STM32 技术开发手册
www.ing10bbs.com
图 9-12 NVIC 中断优先级配置
9.3.2 步进电机旋转驱动代码分析
限于篇幅问题,不会把例程所有代码贴出分析,只挑重点程序段分析。具体
的工程代码可以看我们对应的例程源码。
bsp_STEPMOTOR.h 文件内容
代码 9-9 步进电机驱动器相关宏定义
01 #define STEPMOTOR_TIMx
TIM1
02 #define STEPMOTOR_TIM_RCC_CLK_ENABLE()
__HAL_RCC_TIM1_CLK_ENABLE()
03 #define STEPMOTOR_TIM_RCC_CLK_DISABLE()
__HAL_RCC_TIM1_CLK_DISABLE()
04 #define STEPMOTOR_TIMx_IRQn
TIM1_CC_IRQn
05 #define STEPMOTOR_TIMx_IRQHandler
TIM1_CC_IRQHandler
06
07 /* 第 1 轴*/
08 // 输出控制脉冲给电机控制器
09 // 对应电机驱动器的 PUL-(控制器使用共阳接法)
10 // 而 PLU+直接接开发板的 5V(或者 3.3V)
11 #define STEPMOTOR_TIM_PUL1_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
12 #define STEPMOTOR_TIM_PUL1_PORT
GPIOA
13 #define STEPMOTOR_TIM_PUL1_PIN
GPIO_PIN_8
14
15 // 电机旋转方向控制,如果悬空不接默认正转
STM32 技术开发手册
www.ing10bbs.com
16 // 对应电机驱动器的 DIR-(控制器使用共阳接法)
17 // 而 DIR+直接接开发板的 5V(或者 3.3V)
18 #define STEPMOTOR_DIR1_GPIO_CLK_ENABLE()
__HAL_RCC_GPIOB_CLK_ENABLE()
19 #define STEPMOTOR_DIR1_PORT
GPIOB
20 #define STEPMOTOR_DIR1_PIN
GPIO_PIN_12
21
22 // 电机使能控制,如果悬空不接默认使能电机
23 // 对应电机驱动器的 ENA-(控制器使用共阳接法)
24 // 而 ENA+直接接开发板的 5V(或者 3.3V)
25 #define STEPMOTOR_ENA1_GPIO_CLK_ENABLE()
__HAL_RCC_GPIOG_CLK_ENABLE()
26 #define STEPMOTOR_ENA1_PORT
GPIOG
27 #define STEPMOTOR_ENA1_PIN
GPIO_PIN_6
28
29 #define STEPMOTOR_DIR1_FORWARD()
30
HAL_GPIO_WritePin(STEPMOTOR_DIR1_PORT,STEPMOTOR_DIR1_PIN,GPIO_PIN_SET)
31 #define STEPMOTOR_DIR1_REVERSAL()
32
HAL_GPIO_WritePin(STEPMOTOR_DIR1_PORT,STEPMOTOR_DIR1_PIN,GPIO_PIN_RESET)
33
34 #define STEPMOTOR_OUTPUT1_ENABLE()
35
HAL_GPIO_WritePin(STEPMOTOR_ENA1_PORT,STEPMOTOR_ENA1_PIN,GPIO_PIN_SET)
36 #define STEPMOTOR_OUTPUT1_DISABLE()
37
HAL_GPIO_WritePin(STEPMOTOR_ENA1_PORT,STEPMOTOR_ENA1_PIN,GPIO_PIN_RESET)
38
39 /*************************************************************
40 *
41 *
省略了第 2、3、4 轴的定义,可以参考源工程文件
42 *
43 *************************************************************/
44
45 // 定义定时器预分频,定时器实际时钟频率为:72MHz/(STEPMOTOR_TIMx_PRESCALER+1)
46 #define STEPMOTOR_TIM_PRESCALER
3 // 实际时钟频率为:18MHz
47
48 // 定义定时器周期,输出比较模式周期设置为 0xFFFF
49 #define STEPMOTOR_TIM_PERIOD
0xFFFF
50 // 定义高级定时器重复计数寄存器值
51 #define STEPMOTOR_TIM_REPETITIONCOUNTER
0
使用 TIM1 的 CH1 默认引脚为 PA8,然后在定义驱动器的脱机和方向控制引
脚分别为 PB12 和 PG6。
宏定义了 STEPMOTOR_DIR1_FORWARD 和 STEPMOTOR_DIR1_REVERSAL 用于
电机方向控制,都是通过调用 HAL_GPIO_WritePin 函数实现,实际上就是控制
PB12 引脚输出低、高电平实现的,所以如果旋转方向与你想要的方向相反,直
接修改这两个宏定义内容就好了。
宏定义了 STEPMOTOR_OUTPUT1_ENABLE 和 STEPMOTOR_OUTPUT1_DISABLE
用于设置驱动器的脱机工作,STEPMOTOR_OUTPUT1_ENABLE 是表示允许驱动器
驱动电机旋转,这里是设置 PG6 输出高电平(高阻态),即脱机(不工作)信号
无效。
其他三个通道都是一样的道理。
STM32 技术开发手册
www.ing10bbs.com
bsp_STEPMOTOR.c 文件内容
代码 9-10 STEPMOTOR_GPIO_Init 函数
01 static void STEPMOTOR_GPIO_Init(void)
02 {
03
GPIO_InitTypeDef GPIO_InitStruct;
04
05
/* 引脚端口时钟使能 */
06
STEPMOTOR_TIM_PUL1_GPIO_CLK_ENABLE();
07
STEPMOTOR_DIR1_GPIO_CLK_ENABLE();
08
STEPMOTOR_ENA1_GPIO_CLK_ENABLE();
09
10
STEPMOTOR_TIM_PUL2_GPIO_CLK_ENABLE();
11
STEPMOTOR_DIR2_GPIO_CLK_ENABLE();
12
STEPMOTOR_ENA2_GPIO_CLK_ENABLE();
13
14
STEPMOTOR_TIM_PUL3_GPIO_CLK_ENABLE();
15
STEPMOTOR_DIR3_GPIO_CLK_ENABLE();
16
STEPMOTOR_ENA3_GPIO_CLK_ENABLE();
17
18
STEPMOTOR_TIM_PUL4_GPIO_CLK_ENABLE();
19
STEPMOTOR_DIR4_GPIO_CLK_ENABLE();
20
STEPMOTOR_ENA4_GPIO_CLK_ENABLE();
21
22
/* 第 1 轴*/
23
/* 电机驱动器输出脉冲控制引脚 IO 初始化 */
24
GPIO_InitStruct.Pin = STEPMOTOR_TIM_PUL1_PIN;
25
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
26
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
27
HAL_GPIO_Init(STEPMOTOR_TIM_PUL1_PORT, &GPIO_InitStruct);
28
29
/* 电机驱动器方向控制引脚 IO 初始化 */
30
HAL_GPIO_WritePin(STEPMOTOR_DIR1_PORT,STEPMOTOR_DIR1_PIN,GPIO_PIN_SET);
31
GPIO_InitStruct.Pin = STEPMOTOR_DIR1_PIN;
32
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
33
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
34
HAL_GPIO_Init(STEPMOTOR_DIR1_PORT, &GPIO_InitStruct);
35
36
/* 电机驱动器使能控制引脚 IO 初始化 */
37
HAL_GPIO_WritePin(STEPMOTOR_ENA1_PORT,STEPMOTOR_ENA1_PIN,GPIO_PIN_SET);
38
GPIO_InitStruct.Pin = STEPMOTOR_ENA1_PIN;
39
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
40
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
41
HAL_GPIO_Init(STEPMOTOR_ENA1_PORT, &GPIO_InitStruct);
42
43
/*************************************************************
44
*
45
*
省略了第 2、3、4 轴部分代码实现,可以参考源工程文件
46
*
47
*************************************************************/
48
49 }
STEPMOTOR_GPIO_Init 函数用于初始化驱动器相关 IO。定时器通道功能引脚
设置为复用功能开漏输出模式,速度设置为高;驱动器方向控制和脱机功能控制
引脚设置为普通的开漏输出模式。注意这里所有引脚都不能设置为推挽输出模式。
STM32 技术开发手册
www.ing10bbs.com
代码 9-11 STEPMOTOR_TIMx_Init 函数
01 void STEPMOTOR_TIMx_Init(void)
02 {
03
TIM_ClockConfigTypeDef sClockSourceConfig;
// 定时器时钟
04
TIM_MasterConfigTypeDef sMasterConfig;
// 定时器主模式配置
05
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;
// 刹车和死区时间配置
06
TIM_OC_InitTypeDef sConfigOC;
// 定时器通道比较输出
07
08
/* 定时器基本环境配置 */
09
htimx_STEPMOTOR.Instance = STEPMOTOR_TIMx;
// 定时器编号
10
htimx_STEPMOTOR.Init.Prescaler = STEPMOTOR_TIM_PRESCALER;
// 定时器预分频器
11
htimx_STEPMOTOR.Init.CounterMode = TIM_COUNTERMODE_UP;
// 计数方向:向上计数
12
htimx_STEPMOTOR.Init.Period = STEPMOTOR_TIM_PERIOD;
// 定时器周期
13
htimx_STEPMOTOR.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
// 时钟分频
14
htimx_STEPMOTOR.Init.RepetitionCounter = STEPMOTOR_TIM_REPETITIONCOUNTER; // 重复计数器
15
HAL_TIM_Base_Init(&htimx_STEPMOTOR);
16
17
/* 定时器时钟源配置 */
18
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
// 使用内部时钟源
19
HAL_TIM_ConfigClockSource(&htimx_STEPMOTOR, &sClockSourceConfig);
20
21
/* 初始化定时器比较输出环境 */
22
HAL_TIM_OC_Init(&htimx_STEPMOTOR);
23
24
/* 定时器主输出模式 */
25
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
26
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
27
HAL_TIMEx_MasterConfigSynchronization(&htimx_STEPMOTOR, &sMasterConfig);
28
29
/* 刹车和死区时间配置 */
30
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
31
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
32
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
33
sBreakDeadTimeConfig.DeadTime = 0;
34
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
35
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
36
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
37
HAL_TIMEx_ConfigBreakDeadTime(&htimx_STEPMOTOR, &sBreakDeadTimeConfig);
38
39
/* 定时器比较输出配置 */
40
sConfigOC.OCMode = TIM_OCMODE_TOGGLE;
// 比较输出模式:反转输出
41
sConfigOC.Pulse = Toggle_Pulse[0];
// 脉冲数
42
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
// 输出极性
43
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_LOW;
// 互补通道输出极性
44
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
// 快速模式
45
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
// 空闲电平
46
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
// 互补通道空闲电平
47
HAL_TIM_OC_ConfigChannel(&htimx_STEPMOTOR, &sConfigOC, TIM_CHANNEL_1);
48
49
sConfigOC.Pulse = Toggle_Pulse[1];
// 脉冲数
50
HAL_TIM_OC_ConfigChannel(&htimx_STEPMOTOR, &sConfigOC, TIM_CHANNEL_2);
51
52
sConfigOC.Pulse = Toggle_Pulse[2];
// 脉冲数
53
HAL_TIM_OC_ConfigChannel(&htimx_STEPMOTOR, &sConfigOC, TIM_CHANNEL_3);
54
55
sConfigOC.Pulse = Toggle_Pulse[3];
// 脉冲数
56
HAL_TIM_OC_ConfigChannel(&htimx_STEPMOTOR, &sConfigOC, TIM_CHANNEL_4);
57
58
/* STEPMOTOR 相关 GPIO 初始化配置 */
STM32 技术开发手册
www.ing10bbs.com
59
60
61
62
63
64 }
STEPMOTOR_GPIO_Init();
/* 配置定时器中断优先级并使能 */
HAL_NVIC_SetPriority(STEPMOTOR_TIMx_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(STEPMOTOR_TIMx_IRQn);
STEPMOTOR_TIMx_Init 函数用于驱动器定时器初始化配置。首先是配置定时
器的基本工作环境,设置定时器的编号、预分频器、计数模式、周期等等。
设置定时器使用内部时钟。定时器主输出模式和刹车和死区时间功能都不需
要使能。
接下来,配置比较输出参数,设置为比较输出模式为翻转输出,脉冲数由
Toggle_Pulse 数组元素赋值,定时器计数值达到对应的 Toggle_Pulse 数组元素值
时就翻转通道引脚。
最后,设置定时器中断优先级并使能中断。
main.c 文件内容
代码 9-12 HAL_TIM_OC_DelayElapsedCallback 函数
01 void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
02 {
03
__IO uint16_t count;
04
count=__HAL_TIM_GET_COUNTER(&htimx_STEPMOTOR);
05
if (htim->Channel==HAL_TIM_ACTIVE_CHANNEL_1) {
06
__HAL_TIM_SET_COMPARE(&htimx_STEPMOTOR,TIM_CHANNEL_1,count+Toggle_Pulse[0]);
07
pulse_count[0]++;
08
}
09
if (htim->Channel==HAL_TIM_ACTIVE_CHANNEL_2) {
10
__HAL_TIM_SET_COMPARE(&htimx_STEPMOTOR,TIM_CHANNEL_2,count+Toggle_Pulse[1]);
11
pulse_count[1]++;
12
}
13
if (htim->Channel==HAL_TIM_ACTIVE_CHANNEL_3) {
14
__HAL_TIM_SET_COMPARE(&htimx_STEPMOTOR,TIM_CHANNEL_3,count+Toggle_Pulse[2]);
15
pulse_count[2]++;
16
}
17
if (htim->Channel==HAL_TIM_ACTIVE_CHANNEL_4) {
18
__HAL_TIM_SET_COMPARE(&htimx_STEPMOTOR,TIM_CHANNEL_4,count+Toggle_Pulse[3]);
19
pulse_count[3]++;
20
}
21 }
HAL_TIM_OC_DelayElapsedCallback 函数功能在前面两个例程都有讲过的,这
里相比上个例程,只是添加了四个 if 语句用于判断是中断的来源,只对发生中断
的通道进行输出比较值进行更新。
代码 9-13 main 函数
01 int main(void)
STM32 技术开发手册
www.ing10bbs.com
02 {
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
uint8_t key1_count=1;
/* 复位所有外设,初始化 Flash 接口和系统滴答定时器 */
HAL_Init();
/* 配置系统时钟 */
SystemClock_Config();
KEY_GPIO_Init();
/* 高级控制定时器初始化并配置 PWM 输出功能 */
STEPMOTOR_TIMx_Init();
/* 确定定时器 */
HAL_TIM_Base_Start(&htimx_STEPMOTOR);
/* 启动比较输出并使能中断 */
HAL_TIM_OC_Start_IT(&htimx_STEPMOTOR,TIM_CHANNEL_1);
HAL_TIM_OC_Start_IT(&htimx_STEPMOTOR,TIM_CHANNEL_2);
HAL_TIM_OC_Start_IT(&htimx_STEPMOTOR,TIM_CHANNEL_3);
HAL_TIM_OC_Start_IT(&htimx_STEPMOTOR,TIM_CHANNEL_4);
/* 无限循环 */
while (1) {
if (KEY1_StateRead()==KEY_DOWN) { // 功能选择
key1_count++;
if (key1_count==5)
key1_count=1;
}
if (KEY2_StateRead()==KEY_DOWN) { // 功能调节
switch (key1_count) {
case 1:
Toggle_Pulse[0]-=50;
if (Toggle_Pulse[0]<300) // 最快速度限制
Toggle_Pulse[0]=300;
Toggle_Pulse[1]-=50;
if (Toggle_Pulse[1]<300) // 最快速度限制
Toggle_Pulse[1]=300;
Toggle_Pulse[2]-=50;
if (Toggle_Pulse[2]<300) // 最快速度限制
Toggle_Pulse[2]=300;
Toggle_Pulse[3]-=50;
if (Toggle_Pulse[3]<300) // 最快速度限制
Toggle_Pulse[3]=300;
break;
/*************************************************************
*
*
省略了第 2、3、4 轴部分代码实现,可以参考源工程文件
*
*************************************************************/
default:
break;
}
}
if (pulse_count[0]==STEPMOTOR_MICRO_STEP*200*2*40) { // 第 1 轴转动 40 圈后停机
STEPMOTOR_OUTPUT1_DISABLE(); // 停机
}
if (pulse_count[1]==STEPMOTOR_MICRO_STEP*200*2*30) { // 第 2 轴转动 30 圈后停机
STEPMOTOR_OUTPUT2_DISABLE(); // 停机
}
if (pulse_count[2]==STEPMOTOR_MICRO_STEP*200*2*20) { // 第 3 轴转动 20 圈后停机
STEPMOTOR_OUTPUT3_DISABLE(); // 停机
STM32 技术开发手册
www.ing10bbs.com
64
65
66
67
68
69 }
}
if (pulse_count[3]==STEPMOTOR_MICRO_STEP*200*2*10) { // 第 4 轴转动 10 圈后停机
STEPMOTOR_OUTPUT4_DISABLE(); // 停机
}
}
这里不想啰嗦了,直接看上个例程的说明吧。
实验操作与现象
根据 bsp_STEPMOTOR.h 文件中引脚定义方法连接开发板和步进电机驱动器,
另外驱动器和 57&42 步进电机的连接自行根据电机和驱动器标识连接,驱动器
需要一个 24V 5A 的直流电源供电。使用开发板配套的 MINI USB 线连接到开发板
标示“调试串口”字样的 MIMI USB 接口为开发板提供电源。下载完程序之后,
开发板持续输出脉冲给电机驱动器,步进电机持续转动一定圈后停止,在其转动
器件可以按下 KEY1 和 KEY2 组合实现电机转速、方向、停止、启动等等控制。
步进电机使用常见问题
9.4
1. 二相与四相混合式的区别?
二相步进电机内部只有二组线圈,外部有 4 根引出接线,标记为 A、A、B、
B。四相步进电机内部线圈有两种形式:二组带中间抽头的有 6 根引出接线,独
立四组线圈的有 8 根引出接线。
2. 步进电机与驱动器怎样选型?
因为步进电机的输出功率与运转速度成反比例,所以选型时必须了解以下的
参数:
负载:单位为 kg·cm 或者 N·M(例:1cm 半径 1kg 力拉动时为 1kg·cm)。
速度:速度越高电机输出力矩越小。参照电机输出曲线选择相应速度时,输
出得达到负载能力的电机。给出一些参考:
表格 9-1 电机选择参考
电 机 电 压 高 电 流小
电 机 电 压 低 电 流大
高速性能
差
好
低速振动
小
大
适配驱动器
价格低
价格高
STM32 技术开发手册
www.ing10bbs.com
3. 步进电机使用时振动大、失步或有声不转动等现象原因?
步进电机与普通交流电机有很大的差别,振动大或失步现象是常见的现象。
分析原因或解决方法有以下几点:
1) 控制脉冲:频率低速时是否处在共振点上(每个型号电机不同),高速时
是否采用梯形或其他曲线加速,控制脉冲频率有无跳动(部分 PLC 机型)。
 解决方法:调整控制脉冲频率或采用步进伺服专用控制器。
2) 驱动器:电机低速时,振动或失步,高速时正常;驱动电压过高。电机
低速时正常,高速时失步;驱动电压过低。电机长时间低速运转无发热
现象(电机正常工作时可高达 80 至 90 度)驱动电流过小时。电机工
作时过热;驱动电流过大。
 解决方法:调节驱动器电流、驱动电压或更换驱动器。
4. 步进电机控制为什么要采用梯形或其他加速方法?
步进电机起步速度根据电机不同一般在 150 至 250RPM 左右,如果希望高于
此速运转就必须先用起步以下速度起步,逐渐加速直至最高速度运行一定距离后
逐渐减速,至起步速度以下时方可停止,否则有高速上不去或失步的现象。常见
加速方法有分级加速、梯形加速、 S 字加速等。
5. 步进电机的外表温度允许达到多少?
步进电机温度过高首先会使电机的磁性材料退磁,从而导致力矩下降乃至于
失步,因此电机外表允许的最高温度应取决于不同电机磁性材料的退磁点;一般
来讲,磁性材料的退磁点都在摄氏 130 度以上,有的甚至高达摄氏 200 度以上,
所以步进电机外表温度在摄氏 80-90 度完全正常。
6. 为什么步进电机的力矩会随转速的升高而下降?
当步进电机转动时,电机各相绕组的电感将形成一个反向电动势;频率越高,
反向电动势越大。在它的作用下,电机随频率(或速度)的增大而相电流减小,
从而导致力矩下降。
STM32 技术开发手册
www.ing10bbs.com
7. 为什么步进电机低速时可以正常运转,但若高于一定速度就无法
启动,并伴有啸叫声?
步进电机有一个技术参数:空载启动频率,即步进电机在空载情况下能够正
常启动的脉冲频率,如果脉冲频率高于该值,电机不能正常启动,可能发生失步
或堵转。在有负载的情况下,启动频率应更低。如果要使电机达到高速转动,脉
冲频率应该有加速过程,即启动频率较低,然后按一定加速度升到所希望的高频
(电机转速从低速升到高速)。
8. 如何克服两相混合式步进电机在低速运转时的振动和噪声?
步进电机低速转动时振动和噪声大是其固有的缺点,一般可采用以下方案来
克服:
A. 如步进电机正好工作在共振区,可通过改变减速比等机械传动避开共振
区;
B. 采用带有细分功能的驱动器,这是最常用的、最简便的方法;
C. 换成步距角更小的步进电机,如三相或五相步进电机;
D. 换成交流伺服电机,几乎可以完全克服震动和噪声,但成本较高;
E. 在电机轴上加磁性阻尼器,市场上已有这种产品,但机械结构改变较大。
9. 细分驱动器的细分数是否能代表精度?
步进电机的细分技术实质上是一种电子阻尼技术(请参考有关文献),其主
要目的是减弱或消除步进电机的低频振动,提高电机的运转精度只是细分技术的
一个附带功能。比如对于步进角为 1.8°的两相混合式步进电机,如果细分驱动
器的细分数设置为 4,那么电机的运转分辨率为每个脉冲 0.45°,电机的精度能
否达到或接近 0.45°,还取决于细分驱动器的细分电流控制精度等其它因素。不
同厂家的细分驱动器精度可能差别很大;细分数越大精度越难控制。
10. 四相混合式步进电机与驱动器的串联接法和并联接法有什么区别?
四相混合式步进电机一般由两相驱动器来驱动,因此,连接时可以采用串联
接法或并联接法将四相电机接成两相使用。串联接法一般在电机转速较低的场合
使用,此时需要的驱动器输出电流为电机相电流的 0.7 倍,因而电机发热小;并
STM32 技术开发手册
www.ing10bbs.com
联接法一般在电机转速较高的场合使用(又称高速接法),所需要的驱动器输出
电流为电机相电流的 1.4 倍,因而电机发热较大。
11. 如何确定步进电机驱动器的直流供电电源?
1) 电压的确定:混合式步进电机驱动器的供电电源电压一般是一个较宽的
范围(12~48VDC),电源电压通常根据电机的工作转速和响应要求来选
择。如果电机工作转速较高或响应要求较快,那么电压取值也高,但注
意电源电压的纹波不能超过驱动器的最大输入电压,否则可能损坏驱动
器。
2) 电流的确定:供电电源电流一般根据驱动器的输出相电流 I 来确定。如
果采用线性电源,电源电流一般可取 I 的 1.1~1.3 倍;如果采用开关电
源,电源电流一般可取 I 的 1.5~2.0 倍。
12. 混合式步进电机驱动器的脱机信号 FREE 一般在什么情况下使用?
当脱机信号 FREE 为低电平时,驱动器输出到电机的电流被切断,电机转子
处于自由状态(脱机状态)。在有些自动化设备中,如果在驱动器不断电的情况
下要求直接转动电机轴(手动方式),就可以将 FREE 信号置低,使电机脱机,进
行手动操作或调节。手动完成后,再将 FREE 信号置高,以继续自动控制。
13. 如何用简单的方法调整两相步进电机通电后的转动方向?
只需将电机与驱动器接线的 A+和 A-(或者 B+和 B-)对调即可。
14. 关于驱动器的细分原理及一些相关说明
在国外,对于步进系统,主要采用二相混合式步进电机及相应的细分驱动器。
但在国内,广大用户对“细分”还不是特别了解,有的只是认为,细分是为了提
高精度,其实不然,细分主要是改善电机的运行性能,现说明如下:步进电机的
细分控制是由驱动器精确控制步进电机的相电流来实现的,以二相电机为例,假
如电机的额定相电流为 3A,如果使用常规驱动器(如常用的恒流斩波方式)驱
动该电机,电机每运行一步,其绕组内的电流将从 0 突变为 3A 或从 3A 突变到
0,相电流的巨大变化,必然会引起电机运行的振动和噪音。如果使用细分驱动
器,在 10 细分的状态下驱动该电机,电机每运行一微步,其绕组内的电流变化
STM32 技术开发手册
www.ing10bbs.com
只有 0.3A 而不是 3A,且电流是以正弦曲线规律变化,这样就大大的改善了电机
的振动和噪音,因此,在性能上的优点才是细分的真正优点。由于细分驱动器要
精确控制电机的相电流,所以对驱动器要有相当高的技术要求和工艺要求,成本
亦会较高。注意,国内有一些驱动器采用“平滑”来取代细分,有的亦称为细分,
但这不是真正的细分,望广大用户一定要分清两者的本质不同:
 “平滑”并不精确控制电机的相电流,只是把电流的变化率变缓一些,所以
“平滑”并不产生微步,而细分的微步是可以用来精确定位的。
 电机的相电流被平滑后,会引起电机力矩的下降,而细分控制不但不会引起
电机力矩的下降,相反,力矩会有所增加。
STM32 技术开发手册
www.ing10bbs.com
第10章 直流无刷电机
10.1 直流无刷电机介绍
传统的电动机分成同步电动机(SM)、异步电动机(IM)和直流电动机(DCM)三
大类。它们的基本特点和区别可以这样描述:
1) 同步电动机的转子转速由供电交流电源的频率决定,增大负载时转子速
度不变。或者说转子角速度与交流电源的角频率同步。
2) 异步电动机的转子转速也主要取决于供电交流电源的频率,但转子角速
度只有在理想空载情况下才与电源角频率同步,实际上总小于同步角速
度,即有一定的转差,且转差随负载增加而增大。
3) 直流电动机的转子转速取决于加在电枢上直流电压的值,负载增大时,
转速也随着下降。
异步电动机为鼠笼型结构,没有电刷;小容量同步电动机大部分为永磁转子
结构,也没有电刷;传统的直流电动机则无一例外地都是有刷结构。因为电刷和
换向器是直流电动机中将产生交流电势的电枢绕组与直流电源联接的枢纽,是直
流电动机构成的关键组成部分,可以理解为将电枢绕组的交流电势整流成直流电
势与电源相联,也可以理解为将电源的直流电压逆变成多相交流电压与电枢绕组
相联。可见,对于传统的电动机,同步和异步电动机——交流电动机基本上是无
刷电动机,而直流电动机则无例外地为有刷电动机。
近代新发展起来的 BLDCM(Brushless Direct Current Motor)是指没有电刷但具有
DCM 特性的电动机。与传统的 DCM 相比,BLDCM 用电子换向取代 DCM 的机构
换向,取消了电刷和换向器;并将原有 DCM 中的定转子颠倒,即电枢绕组在定子
上,与静止的电子换相电路联接方便,励磁在转子上,为永磁体,不需要励磁绕
组,也更不需要向转子通电的滑环和电刷;在 DCM 中,换向器在转子上,它能
保证当电枢导体从一个定子磁极下转到另一个极下时其中的电流同步改变方向,
BLDCM 电枢绕组中电流方向的改变由功率管的开关来控制,为保证开关信号与
转子磁极转过的位置同步,需要有检测转子位置角的传感器。基本组成部分如图
STM32 技术开发手册
www.ing10bbs.com
10-1 所示,可以看出 BLDCM 是 1 台反装的 DCM ,用电子换向,作用原理基本
不变,基本特性自然就相一致。
图 10-1 BLDCM 的基本组成部分
10.2 方波和正弦波驱动
BLDCM 的电子换向,基本的方式与 DCM 中完全一致,相绕组中的电流改变
方向与绕组轴线所处磁极下位置极性改变同步,称为方波驱动的 BLDCM,相绕组
基本上为方波。但是电子换向毕竟与机械换向不一样,它具有更灵活的可控性,
电子换向不仅可实现随磁极位置同步改变相绕组内电流的方向,必要时还可以实
现电流波形的控制,例如让相绕组电流为正弦波形,称为正弦波驱动的 BLDCM。
尽管相绕组电流为正弦波形,看起来与传统 DCM 有些不同,可是这种带角位置
传感器和电子换向电路的永磁电动机,它的作用原理和基本特性仍与 DCM 相一
致,应归类为 BLDCM 。
在英美的文献中,把这类正弦波驱动的 BLDCM 称为“永磁同步电动机(PM
SM:Permanent-Magnet Synchronous Motors )” 或者“无刷交流电动机 (BLACM )” ,
在日本和欧洲则大多数情况下称为“交流伺服电动机(AC servo)” ,国内基本上
也多数采用 AC servo 的名称。这些名称在工业和商业领域通用以后很难加以改
变,但是从学术的角度应作一些澄清, 否则会造成混淆。转子上没有位置传感器,
STM32 技术开发手册
www.ing10bbs.com
或不通过位置讯号换向的永磁电动机,才具有同步电动机的基本特性,是名符其
实的 PMSM。通过位置讯号进行电子换向的永磁电动机,既使相绕组电流波形为
正弦波与交流电动机一样,但基本特性 DCM 相一致,应归类为 DCM,而不是 SM。
把它称为 BLACM 就更不清楚了,难道传统的 ACM 都有刷吗?传统的鼠笼型异步
电动机和永磁电动机都是无刷结构,都是 BLACM。通常所说的 AC servo,如前所
说,它不是同步电动机的工作特性,又不具备异步电动机的基本结构,所以从本
质上讲不是 1 台通常意义上的 AC 电动机,只是从形式上看,绕组电流为正弦波
形,与通常 ACM 的绕组电流情况相同,实际上称作无刷直流伺服电动机(BLDC
servo)较为恰当。
虽然上面这么解释,但是就目前国内类似,大家普遍这样称谓:
PMSM:永磁同步电机(正弦波驱动)
BLDC:直流无刷电机(方波驱动)
BLDC 和 PMSM 电机比较:
BLDC 电机
PMSM 电机
固定定子线圈和活动转子永磁体
固定定子线圈和活动转子永磁体
电源电压呈梯形
电源电压呈正弦形式
反电动式呈梯形
反电动式呈正弦形式
每过 60 度,定子磁链位置会换向
定子磁链位置连续变化
铁损较高
铁损较低
控制算法相对简单
控制算法复杂
BLDC 和 PMSM 从最本质的区别就是定子线圈绕组的绕线方法不同。
BLDC 驱动相对简单,我们接下来讲解,PMSM 电机驱动需要复杂的算法:
FOC 支持,这就放在后面高级篇讲解。
BLDC 驱动需要用到前面定时器章节内容,并且是需要对定时器功能很了解
的程度,所以需要大家可以重新仔细阅读定时器相关章节内容。
STM32 技术开发手册
www.ing10bbs.com
10.3 BLDC 工作原理及硬件设计
10.3.1 BLDC 电机工作的基本原理
1) 通电导体产生磁场,特别的,通电线圈的磁场和磁体类似,见图 10-2。
图 10-2 电生磁
2) 磁体异性相吸、同性相斥,通电线圈和永磁体之间同样存在这样的现象,
见图 10-3。
图 10-3 异性相吸、同性相斥
有了上边这两个基本理论下面就好解释了。
STM32 技术开发手册
www.ing10bbs.com
无刷直流电机利用了通电线圈和永磁体的相互作用原理,BLDC 内部结构实
物图见图 10-4。
图 10-4 BLDC 内部实物结构图
根据实物图,我们可以画出无刷直流电机的逻辑结构,见图 10-5。
STM32 技术开发手册
www.ing10bbs.com
图 10-5 BLDC 逻辑结构
为简化分析,可以做成直流无刷电机的简化逻辑结构,见图 10-6。
图 10-6 简化逻辑结构
STM32 技术开发手册
www.ing10bbs.com
这样,通电的线圈会产生各自的磁场,他们的合成磁场满足矢量合成的原则,
见图 10-7。
图 10-7 合成磁场
直流无刷电机的 6 拍工作方式,线圈产生旋转磁场,见图 10-8。
图 10-8 6 拍工作方式
STM32 技术开发手册
www.ing10bbs.com
通过上面的演示过程,我们可以明显看出,想要控制 BLDC 旋转,根本的问
题就是产生这 6 拍工作方式的电压信号(称为 BLDC 的六步控制)。举个例子来
说明,假定一个 BLDC 的额定电压为 24V,电机三根线就定义为 A、B、C:
(1)为 A 接 24V、B 悬空、C 接 GND,此时对应图 10-8 中的,电机转轴
被固定在一个位置;
(2)在(1)的基础上,我们修改接线方式,为 A 接 24V、B 接 GND、C 悬
空,此时对应图 10-8 中的,电机转轴就在(1)基础上旋转一个角度,达到另
外一个位置;
(3)在(2)的基础上,我们修改接线方式,为 A 悬空、B 接 GND、C 接 24V,
此时对应图 10-8 中的,电机转轴就在(2)基础上旋转一个角度,达到另外一
个位置;
(4)在(3)的基础上,我们修改接线方式,为 A 接 GND、B 悬空、C 接 24V,
此时对应图 10-8 中的,电机转轴就在(3)基础上旋转一个角度,达到另外一
个位置;
(5)在(4)的基础上,我们修改接线方式,为 A 接 GND、B 接 24V、C 悬
空,此时对应图 10-8 中的,电机转轴就在(4)基础上旋转一个角度,达到另
外一个位置;
(6)在(5)的基础上,我们修改接线方式,为 A 悬空、B 接 24V、C 接 GND,
此时对应图 10-8 中的,电机转轴就在(6)基础上旋转一个角度,达到另外一
个位置。
10.3.2 BLDC 驱动硬件设计
有了上面的原理分析,现在想让 BLDC 旋转起来的一个问题就是如何任意的
控制 A、B、C 线的电压,如果你之前有认真看本文档前面 4.3 小节关于的直流减
速电机驱动设计,就会马上想到可以用三个半桥(三条桥臂)电机一个三相逆变
器,这里的每个桥臂都有两个电子开关,电子开关可以选择是功率 MOSFET 或者
IGBT,IGBT 用于超大功率电机驱动。最终搭建起来的电路见图 10-9:
STM32 技术开发手册
www.ing10bbs.com
图 10-9 BLDC 电机驱动原理
这样,最终,我们可以让 STM32 控制 A+、A-、B+、B-、C+以及 C-这六个 MOS
管的通断情况就可以让电机旋转起来。当然,STM32 引脚直接接入到 MOS 管引
脚控制是不行的,因为要使 MOS 管导通需要一定的条件,直接使用 STM32 引脚
电平是达不到这个条件的,一般 MOS 管控制是需要专用的驱动电路来实现的,
使用专用的 MOS 管驱动 IC 来实现。
上面是解决了绕组电压控制,还有一个问题就是究竟什么时候要给哪个绕组
正电压、给哪个绕组负电压以及哪个绕组悬空,就是具体当前时刻要选择图 10-9
中“哪一步”?所以,BLDC 驱动还需要一个非常重要的参数,只有知道了这个参
数信息我们才有可能正常的控制 BLDC 旋转,这个重要参数就是转子的位置信息,
只有知道了当前转子所处位置,我们才能很好的控制电机旋转,如果毫无目的根
据图 10-9 中顺序为绕组给电,最终只能看到电机乱转。
转子的位置信息一般可以为 2 种方法取得,一种是有霍尔传感器模式,另外
一种是无传感器模式。
STM32 技术开发手册
www.ing10bbs.com
10.3.3 霍尔传感器模式
霍尔效应原理:磁场会对位于其中的带电导体内运动的电荷载流子施加一个
垂直于其运动方向的力,该力会使得正负电荷分别积聚到导体的两侧。这在薄而
平的导体中尤为明显。电荷在导体两侧的积累会平衡磁场的影响,在导体两侧建
立稳定的电势差。产生这一电势差的过程就叫做霍尔效应,有 E.H.Hall 在 1879 年
发现。
霍尔传感器是根据霍尔效应制作的一种磁场传感器,它可以有效的反映通过
霍尔原件的磁密度,见图 10-10。
图 10-10 霍尔传感器
有霍尔传感器的 BLDC 电机其霍尔传感器安装示意图见图 10-11、图 10-12。
图 10-11 BLDC 电机横截面
STM32 技术开发手册
www.ing10bbs.com
图 10-12 霍尔的安装示例
注:
 机械角度是指电机转子的旋转角度,由Θm 表示;
 电角度是指磁场的旋转角度,由Θe 表示。
 当转子为一对极时,Θm = Θe;
 当转子为 n 对极时,n*Θm=Θe。
当霍尔在和电机的转子做相对运动时,会随着转子下磁密度的变化,产生变化
的信号,见图 10-13。
STM32 技术开发手册
www.ing10bbs.com
图 10-13 霍尔信号
电机按一定方向转动时,3 个霍尔的输出会按照 6 步的规律变化,见图 10-14。
图 10-14 霍尔传感器旋转信号
结合之前介绍的 BLDC 六步控制,在每个霍尔信号都对应一个 BLDC 控制步,
使得 BLDC 旋转一个角度,这样可以制作下表:表格 10-1 和表格 10-2:
STM32 技术开发手册
www.ing10bbs.com
表格 10-1 BLDC 的正转控制
表格 10-2 BLDC 的反转控制
特别注意,一般 BLDC 厂家都会给出一个霍尔传感器和绕组得电情况对应关
系表,不一定跟上面两个表都完全对应一致,但是原理分析都是一致的。
上面两个表的对应意思就是:当检测到霍尔传感器信号为某个值时,控制六
个桥臂对应的开关状态。例如,我们想让电机正转,就用表格 10-1 中的对应信
息,假设 STM32 检测到当前的霍尔信号为:霍尔#1、霍尔#2、霍尔#3 分别对应
为 1、0、1,那么此时我们应该让 STM32 控制 A-和 C+桥臂导通,而其他桥臂都
关断,在 A-和 C+桥臂导通情况下,电机的转子会向着一个位置旋转;在旋转到
达目标位置之前,霍尔传感器信号就会发生改变,此时变为:霍尔#1、霍尔#2、
霍尔#3 分别对应为 0、0、1,好了,此时我们马上把 C+桥臂关断,而把 B+桥臂,
即此时 A-和 B+桥臂导通,其他桥臂关断,电机就又向旋转一个角度。这样,如
此循环下去,电机就可以不停的旋转了。
此时,有些人肯定想问:如果我不管霍尔信号变化,就按中任一种其中给电,
电机会怎样的呢?电机会固定在一个位置,实际上,这种情况是很危险的,我们
知道,绕组都是漆包线铜丝,电阻非常小,当总是给电时候,电路中电流就非常
大,严重情况,烧毁电机或者电源。
STM32 技术开发手册
www.ing10bbs.com
特别的,如果直接导通 A+和 A-这两桥臂,或者 B+和 B-这两桥臂,或者 C+和
C-这两桥臂会出现什么情况呢?结果就是电源必烧无疑,这些情况相当于电源正
负极直接短路,所以这要求我们在接线或者电路设计是非常小心!!!
最终,无刷电机、电机驱动电路和控制器组成的系统见图 10-15。
图 10-15 霍尔传感器模式控制框图
10.3.4 无传感器模式
无传感器模式需要外部复杂的采样、处理等电路支持,我们这里只介绍驱动
原理,并且无传感器模式虽然可以减少成本,但是在电机启动阶段和稳定性方面
等问题还是让它只能用于特殊情况,对于高要求的工业应用环境,更多的还是使
用有霍尔传感器模式驱动。
介绍无传感器模式驱动之前,我们还需要了解一个概念:反电动势。BLDC 电
机转动时,每个绕组都会产生叫做反电动势的电压,根据楞次定律,其方向与提
供给绕组的主电压相反。这一反电动势的极性与励磁电压相反。反电动势主要取
决于三个因素:
转子角速度
转子磁体产生的磁场
定子绕组的匝数
STM32 技术开发手册
www.ing10bbs.com
图 10-16 反电动势计算公式
电机设计完毕后,转子磁场和定子绕组的匝数都是固定不变的。如果避免了
定子的磁饱和,或者忽略磁场与温度的相关性(即 B 为常数),则唯一决定反电
动势的因素就是角速度,或者说转子的转速,随着转子转速的提高,反电动势也
随之增加。电机设计规范提供了一个称为反电动势常数(单位:V/RPM)的参数,
可用来估计给定转速下的反电动势的大小。
绕组两端的压降可通过从供电电压中减去反电动势值算出。使得反电动势常
数设计电机的方法如下:当电机以额定转速运行时,反电动势和供电电压间的电
势差足以使电机消耗额定电流,提供额定转矩。如果电机转速超过额定转速,反
电动势会显著增长,从而降低绕组两端的压降,减小电流,从而导致转矩曲线下
降。转速曲线上最后一点表示供电电压等于反电动势与电机中压降损耗之和,此
时电流和转矩都等于 0。
前面我们已经介绍了使用霍尔传感器获取转子位置进行换向,BLDC 电机还
可以通过监视反电动势信号,而不是霍尔传感器信号来换向,霍尔传感器信号和
反电动势之间的关系(相对于相电压)如图 10-17 所示。每次换向,三个绕组都
是其中一个绕组得到正电压,一个得到负电压,另外一个处于悬空状态,通过,
我们可以知道,霍尔传感器信号会在反电动势的电压极性从正变为负或者从负变
为正时候改变状态。在理想状态下,这应在反电动势穿过零值时发生,但实际上
由于绕组特性,会有延时。该延时应由微控制器来补偿。
需要考虑的另一个问题就是电机转速很慢的时候,由于反电动势与转子转速
成正比,在转速很慢的时候反电动势的幅值非常低,此时就非常难检测到过零点。
所以,当电机从静止状态起动是必须采用开环控制,等电机有一定转速了,就可
STM32 技术开发手册
www.ing10bbs.com
以检测过零点了,从而转进入采用反电动势检测控制。可检测到反电动势的最低
转速可通过该电机的反电动势常数算出。
通过检测不通电相绕组上的 BEMF 电压可以确定电机驱动电压的换相时间。
然而,无传感器控制也有一些缺点:
 由于 BEMF 足够大时才能被检测到,因此电机必须运行在最低转速以上
 电机负载突变可能引起 BEMF 驱动环失锁
如果低成本是应用中关注的主要问题,且无需电机在低速下运行,以及预料
电机负载不会发生快速地变化,那么在这种情况下,无传感器梯形波控制就可能
是应用中的较好选择。
STM32 技术开发手册
www.ing10bbs.com
图 10-17 霍尔传感器信号、反电动势、输出转矩和相电流
根据之前的分析,无传感器模式下的电机控制系统框图见图 10-18。
STM32 技术开发手册
www.ing10bbs.com
图 10-18 无传感器模式控制框图
10.3.5 BLDC 速度控制
一般情况下,我们不仅仅要让电机可以旋转,还必须控制它的速度,类似之
前讲过的直流减速电机控速方法,BLDC 电机控速也是用到脉冲宽度调制技术
(PWM),见图 10-19。
图 10-19 PWM 调制控速
关于 PWM 的详细说明可以阅读本文档前面部分章节内容,这里不再啰嗦。
现在我们就上图具体说明调速过程,注意,上图的 PWM 调制控速对应表格 10-2
情况,即反转模式。现在我们就看霍尔传感器信号情况为:霍尔#1、霍尔#2、霍
STM32 技术开发手册
www.ing10bbs.com
尔#3 分别对应 1、0、1 情况(其他五种情况都是相同原理),我们先看前面表格
10-2 可以知道此时应该是 B+和 C-导通,其他桥臂关断,并且此时是 100%导通 B+
和 C-这两个个桥臂,不用 PWM 控制其导通时间,那么此时绕组中电流非常大,
转子转动速度非常高。显然这种情况不是我们在实际上应用用常常用到的,在实
际生产中,我们总是需要控制电机的转速。现在引入 PWM 控制后什么情况呢?
PWM 中一个常用到的名词就是占空比,就是高电平时间占总周期时间的比例,
这样我们将 B+和 C-直接导通代换成高频率(一般 10 几 KHz 或者几十 KHz)的
PWM,保证 B+和 C-的 PWM 频率相等,并且周期起始位置相同,这样我们可以
非常方便调整占空比的大小,来控制 B+和 C-这两臂的实际导通时间,加上电机
绕组本身是感性负载,这样整体加在电机绕组上的电压就是 0V 到电源正电压
(24V)之间,最终实现控制电机转动速度。综上,通过控制导通的两个桥臂的
PWM 占空比我们可以非常方便控制转速了。
霍尔传感器信号情况为:霍尔#1、霍尔#2、霍尔#3 分别对应 1、0、1 情况为
例,图 10-19 中是同时使用 PWM 控制 B+和 C-桥臂,称之为 pwm-pwm 型调制
方式,实际上,还是有另外几种调制方式,比如 H_on-L_pwm 型、H_pwm-L_on
型、pwm_on 型和 on_pwm 型等等不同的调制方式,不同控制方式在性能上有不
同的效果,当然针对实际的应用场合可以尝试多种调制方式,然后选择最优方式。
STM32 技术开发手册
www.ing10bbs.com
图 10-20 PWM 调制方式
10.3.6 无刷驱动电路
上面是 BLDC 驱动原理分析,现在,我们介绍电机驱动板的原理图。
电机驱动设计
首先是六个桥臂的驱动,见图 10-22,我们驱动板选择的 MOS 管型号是:
IRFS3607,贴片封装,如有需要可以自己加散热片。IRFS3607 的特色参数见图
10-21:
STM32 技术开发手册
www.ing10bbs.com
图 10-21 IRFS3607 的特色参数
IR 的 HEXFET 功率场效应管 IRFS3607 采用先进的工艺技术制造,具有极低的
导通阻抗。
IRFS3607 这种特性,加上快速的转换速率,和以坚固耐用著称的 HEXFET
设计,使得 IRFS3607 成为极其高效可靠、应用范围超广的器件。简单来说,
IRFS3607 性能优越。
然后,MOS 管驱动 IC 这里用到 IR2110S。R2110 芯片体积小(SOIC-16),集成
度高(可驱动同一桥臂两路),响应快( ton /tof = 120/94 n s ),偏值电压高(<600 V ),
驱动能力强,内设欠压封锁,而且易于调试,并设有外部保护封锁端口。尤其是
上管驱动采用外部自举电容上电,使得驱动电源路数目较其他 IC 驱动大大减小。
对于 BLDC 驱动需要 6 个桥臂,需要用到 3 片 IR2110S 来驱动,虽然如此也是仅
需要一路 10~20V 电源,从而大大减小了控制变压器的体积和电源数目,降低了产
品成本,提高了系统的可靠性。
NMOS 管的导通基本条件就是 VGS 大于一定的阈值电压 VGS(th),IRFS3607 的
VGS(th)是 4V(最大值)。我们为 IR2110S 设计的电源电压为 11V(VCC),IR2110S 的
低端驱动,即驱动 Q6 的 IRFS3607,很容易就实现 NMOS 管驱动条件。对于高端
驱动,即驱动 Q5 的 IRFS3607,就需要“自举电路”的支持,自举电路通俗点就是
升压电路,电路中的 D7 二极管和 C13 电容用于自举电路,简单来说在该电路中,
自举电路的作用是使得 IR2110S 高端驱动,即 IR2110S 的第 8 引脚 HO 输出信号
可以满足大于 VGS(th) 。
IR2110S 芯片的左边部分是控制信号输入,这里我们使用到东芝公司的 5M
高速光耦:TLP715,它起到信号隔离和升压的作用。电机驱动都是大电压大电流,
隔离保护控制器是非常有必要的。STM32 的引脚电压为 3.3V,直接接 IR2110S 显
STM32 技术开发手册
www.ing10bbs.com
然不是很好的选择,这里我们选择把 3.3V 的 PWM 信号转换成 VCC 的信号,这
样更加合适做为 IR2110s 的输入。
IR2110S 芯片还有一个关断引脚(SD),用于禁止输出动作,这可以用于紧急情
况的保护。当 Ctrl_SHUTDOWN 信号为高电平或者悬空时,光耦不导通,在 R9 下
拉电阻作用下,IR2110S 的 SD 引脚(三个 IR2110S 的 SD 引脚是直接连接在一起
的)为低电平,可以正常输出 PWM 信号。当 Ctrl_SHUTDOWN 信号为低电平时,
光耦导通,IR2110S 的 SD 引脚为 VCC 电平,关闭输出 PWM 信号。
图 10-22 桥臂驱动
霍尔传感器接口电路设计
霍尔传感器接口电路就简单很多,这只做一些简单的上拉和滤波处理,见图
10-23。特别的,很多情况下上拉电阻是必须的,这个一般电机厂家有特别的说
明。为合适 STM32 开发板电平要求,采用 3.3V 的上拉。
STM32 技术开发手册
www.ing10bbs.com
图 10-23
霍尔传感器接口电路
电源电路设计
这里用到 11V 和 5V 电源,考虑到 PMSM 电机的驱动,我们这里还设计了
3.3V 电源。11V 电源使用 DC-DC 芯片 TPS54360 得到,该芯片最大输入电压为 60V,
最大电流为 3.5A,这样使得我们驱动板可以兼容 12V、24V、36V、48V 和 60V 的
电机驱动,大大增加驱动板的应用范围。5V 电源由 LM2359 芯片得到,3.3V 使
用 AMS1117-3.3 得到。电路设计参考图 10-24。
图 10-24
电源电路设计
总线电压和温度传感器
我们的驱动板上还添加了总线电压检测电路和环境温度检测电路,见图
10-25,这些都可以起到过压、欠压和高温等有效的保护作用,当然这些都是需要
程序编程支持的。
STM32 技术开发手册
www.ing10bbs.com
图 10-25 总线电压和温度检测
实际上,我们驱动板上是可以实现线电流检测的,从而实现过流保护,这个
需要比较复杂的程序支持,并且该部分功能更多用于 PMSM 电机驱动,所以这
部分电路分析放在 PMSM 电机驱动部分介绍。
控制和反馈信号接口设计
BLDC 控制比较简单,简单控制只需要 6 根控制线和 3 根霍尔传感器信号,
6 根 PWM 信号控制 6 个桥臂,还有 3 根霍尔传感器信号用于反馈转子的位置。
我们驱动板还预留了其他的信号接口,见图 10-26,这在后面例程用到会有说明。
STM32 技术开发手册
www.ing10bbs.com
图 10-26 控制和反馈信号接口
10.4 BLDC 旋转驱动程序
BLDC 驱动最基本就是 6 步 PWM,
6 步 PWM 一般使用高级控制定时器生成。
我们的 BLDC 例程一般都是 H_pwm-L_on 或者 H_on-L-pwm 的 PWM 调制方式,
根据图 10-20,这两种方式可以简化为 3 个 PWM 输出+3 个电平输出控制实现,
虽然如此,我们的例程还是直接使用定时器功能,输出 6 个 PWM 控制。
考虑到 ST 官方的 FOC 电机库是基于 3.5 标准固件库实现的,这里我们就也
以标准库例程来讲解。我们还有基于 HAL 库的 BLDC 例程,有兴趣的可以去看看。
10.4.1 6 步 PWM 程序实现
这里我们先讲 6 步 PWM 输出例程(以“YSF1_BLDC-001. 6 步 PWM 输出
(H_PWN_L_ON)”例程代码讲解),然后在下个例程把霍尔传感器信号加入进入从
而实现 BLDC 电机转动控制。
bsp_BLDCTIM.h 文件
该文件存放了相关宏定义内容,定时器及其功能引脚定义、PWM 频率相关
参数定义,以及函数声明。
代码 10-1 BLDC 相关宏定义
01 #ifndef __BLDC_TIM_H__
STM32 技术开发手册
www.ing10bbs.com
02 #define __BLDC_TIM_H__
03
04 /* 包含头文件 ----------------------------------------------------------------*/
05 #include <stm32f10x.h>
06
07 /* 类型定义 ------------------------------------------------------------------*/
08 /* 宏定义 --------------------------------------------------------------------*/
09 /********************高级定时器 TIM 参数定义,只限 TIM1 & TIM8************/
10 #define BLDC_TIMx
TIM1
11 #define BLDC_TIM_APBxClock_FUN
RCC_APB2PeriphClockCmd
12 #define BLDC_TIM_CLK
RCC_APB2Periph_TIM1
13
14 #define BLDC_TIM_GPIO_APBxClock_FUN
RCC_APB2PeriphClockCmd
15 #define BLDC_TIM_GPIO_CLK
(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB)
16 #define BLDC_TIM_CH1_PORT
GPIOA
17 #define BLDC_TIM_CH1_PIN
GPIO_Pin_8
18 #define BLDC_TIM_CH2_PORT
GPIOA
19 #define BLDC_TIM_CH2_PIN
GPIO_Pin_9
20 #define BLDC_TIM_CH3_PORT
GPIOA
21 #define BLDC_TIM_CH3_PIN
GPIO_Pin_10
22
23 #define BLDC_TIM_CH1N_PORT
GPIOB
24 #define BLDC_TIM_CH1N_PIN
GPIO_Pin_13
25 #define BLDC_TIM_CH2N_PORT
GPIOB
26 #define BLDC_TIM_CH2N_PIN
GPIO_Pin_14
27 #define BLDC_TIM_CH3N_PORT
GPIOB
28 #define BLDC_TIM_CH3N_PIN
GPIO_Pin_15
29
30 #define BLDC_TIM_BKIN_PORT
GPIOB
31 #define BLDC_TIM_BKIN_PIN
GPIO_Pin_12
32
33 #define BLDC_TIM_PWM_FREQ
14400 // PWM 频率
34
35 // 定义定时器预分频,定时器实际时钟频率为:72MHz/(BLDC_TIMx_PRESCALER+1)
36 #define BLDC_TIM_PRESCALER
0 // 实际时钟频率为:72MHz
37
38 // 定义定时器周期,当定时器开始计数到 BLDC_TIMx_PERIOD 值
39 //并且重复计数寄存器为 0 时更新定时器并生成对应事件和中断
40 #define BLDC_TIM_PERIOD
(uint16_t)(SystemCoreClock/(BLDC_TIM_PRESCALER+1)/BLDC_TIM_PWM_FREQ)
41
42 // 定义高级定时器重复计数寄存器值,
43 #define BLDC_TIM_REPETITIONCOUNTER
0
44
45 /* 扩展变量 ------------------------------------------------------------------*/
46 /* 函数声明 ------------------------------------------------------------------*/
47 void BLDC_TIMx_PWM_Init(void);
48
49 #endif /* __BLDC_TIM_H__ */
这里选择 TIM1,PWM 频率设置为 14400HZ。
bsp_BLDCTIM.c 文件内容
该文件存放定时器初始化配置程序。
代码 10-2BLDC_TIMx_GPIO_Config 函数
01 /**
02
* 函数功能: 配置 TIMx 复用输出 PWM 时用到的 I/O
STM32 技术开发手册
www.ing10bbs.com
03
* 输入参数: 无
04
* 返 回 值: 无
05
* 说
明:无
06
*/
07 static void BLDC_TIMx_GPIO_Config(void)
08 {
09
GPIO_InitTypeDef GPIO_InitStructure;
10
11
/* 使能定时器始终 */
12
BLDC_TIM_APBxClock_FUN(BLDC_TIM_CLK, ENABLE);
13
14
/* 使能定时器通道引脚 GPIO 时钟 */
15
BLDC_TIM_GPIO_APBxClock_FUN(BLDC_TIM_GPIO_CLK|RCC_APB2Periph_AFIO, ENABLE);
16
17
/* 配置定时器通道 1 输出引脚模式:复用推挽输出模式 */
18
GPIO_InitStructure.GPIO_Pin = BLDC_TIM_CH1_PIN;
19
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
20
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
21
GPIO_Init(BLDC_TIM_CH1_PORT, &GPIO_InitStructure);
22
23
/* 配置定时器通道 2 输出引脚模式 */
24
GPIO_InitStructure.GPIO_Pin = BLDC_TIM_CH2_PIN;
25
GPIO_Init(BLDC_TIM_CH2_PORT, &GPIO_InitStructure);
26
27
/* 配置定时器通道 3 输出引脚模式 */
28
GPIO_InitStructure.GPIO_Pin = BLDC_TIM_CH3_PIN;
29
GPIO_Init(BLDC_TIM_CH3_PORT, &GPIO_InitStructure);
30
31
/* 配置定时器互补通道 1 输出引脚模式 */
32
GPIO_InitStructure.GPIO_Pin = BLDC_TIM_CH1N_PIN;
33
GPIO_Init(BLDC_TIM_CH1N_PORT, &GPIO_InitStructure);
34
35
/* 配置定时器互补通道 2 输出引脚模式 */
36
GPIO_InitStructure.GPIO_Pin = BLDC_TIM_CH2N_PIN;
37
GPIO_Init(BLDC_TIM_CH2N_PORT, &GPIO_InitStructure);
38
39
/* 配置定时器互补通道 3 输出引脚模式 */
40
GPIO_InitStructure.GPIO_Pin = BLDC_TIM_CH3N_PIN;
41
GPIO_Init(BLDC_TIM_CH3N_PORT, &GPIO_InitStructure);
42
43
/* Configuration: BKIN pin */
44
GPIO_InitStructure.GPIO_Pin = BLDC_TIM_BKIN_PIN;
45
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
46
GPIO_Init(BLDC_TIM_BKIN_PORT, &GPIO_InitStructure);
47 }
初始化配置定时器功能引脚,配置为复用推挽输出模式;这里还初始化了定
时器的刹车引脚,配置为浮空输入模式。
代码 10-3 BLDC_TIMx_Configuration 函数
01 static void BLDC_TIMx_Configuration(void)
02 {
03
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
04
TIM_OCInitTypeDef TIM_OCInitStructure;
05
TIM_BDTRInitTypeDef TIM_BDTRInitStructure;
06
07
/* 定时器基本参数始终 */
08
TIM_TimeBaseStructure.TIM_Period = BLDC_TIM_PERIOD;
09
/* 设置预分频:不预分频,即为 72MHz */
STM32 技术开发手册
www.ing10bbs.com
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
TIM_TimeBaseStructure.TIM_Prescaler = BLDC_TIM_PRESCALER;
/* 设置时钟分频系数:不分频(这里用不到) */
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1 ;
/* 向上计数模式 */
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
/* 重复计算器 */
TIM_TimeBaseStructure.TIM_RepetitionCounter = BLDC_TIM_REPETITIONCOUNTER;
TIM_TimeBaseInit(BLDC_TIMx, &TIM_TimeBaseStructure);
/* 定时器输出通道 1 模式配置 */
/* 模式配置:PWM 模式 1 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
/* 输出状态设置:使能输出 */
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
/* 互补通道输出状态设置:使能输出 */
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
/* 设置跳变值,当计数器计数到这个值时,电平发生跳变 */
TIM_OCInitStructure.TIM_Pulse = 0;
/* 当定时器计数值小于 CCR1_Val 时为高电平 */
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCNPolarity= TIM_OCNPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Set;
/* 初始化定时器通道 1 输出 PWM */
TIM_OC1Init(BLDC_TIMx, &TIM_OCInitStructure);
/* 定时器输出通道 2 模式配置 */
/* 设置通道 2 的电平跳变值,输出另外一个占空比的 PWM */
TIM_OCInitStructure.TIM_Pulse = 0;
/* 初始化定时器通道 2 输出 PWM */
TIM_OC2Init(BLDC_TIMx, &TIM_OCInitStructure);
/* 定时器输出通道 3 模式配置 */
/* 设置通道 3 的电平跳变值,输出另外一个占空比的 PWM */
TIM_OCInitStructure.TIM_Pulse = 0;
/* 初始化定时器通道 3 输出 PWM */
TIM_OC3Init(BLDC_TIMx, &TIM_OCInitStructure);
/* Automatic Output enable, Break, dead time and lock configuration*/
TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_OFF;
TIM_BDTRInitStructure.TIM_DeadTime = 5;
TIM_BDTRInitStructure.TIM_Break = TIM_Break_Disable;
TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High;
TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
TIM_BDTRConfig(BLDC_TIMx, &TIM_BDTRInitStructure);
TIM_OC1PreloadConfig(BLDC_TIMx,TIM_OCPreload_Enable);
TIM_OC2PreloadConfig(BLDC_TIMx,TIM_OCPreload_Enable);
TIM_OC3PreloadConfig(BLDC_TIMx,TIM_OCPreload_Enable);
/* 使能定时器重载寄存器 ARR */
TIM_ARRPreloadConfig(BLDC_TIMx, ENABLE);
/* 使能定时器 */
TIM_Cmd(BLDC_TIMx, ENABLE);
/* TIM 主输出使能 */
TIM_CtrlPWMOutputs(BLDC_TIMx, ENABLE);
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCx_Disable);
STM32 技术开发手册
www.ing10bbs.com
72
73
74
75
76
77 }
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCxN_Disable);
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCx_Disable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCxN_Disable);
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCx_Disable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCxN_Disable);
好吧,该函数内容都是非常常规的定时器配置,如果还看不懂上边程序代码,
只能让你倒回去看看定时器相关章节内容了。
bsp_SysTick.c 和 bsp_SysTick.h 这两个文件内容这里不打算贴出来,都是很常
规的配置,然后有不清楚的地方可以看看我们相关文档。
main.c 文件内容
该文件主要存放了两个函数 main 函数和 BLDC_PHASE_CHANGE 函数。
BLDC_PHASE_CHANGE 函数是 6 步换相函数。
代码 10-4 BLDC_PHASE_CHANGE 函数
01 void BLDC_PHASE_CHANGE(void)
02 {
03
switch (uwStep) {
04
case 4: //B+ C05
/* Next step: Step 2 Configuration -------------------------------------- */
06
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCx_Disable);
07
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCxN_Disable);
08
09
/* Channel1 configuration */
10
/* Channel2 configuration */
11
TIM_SetCompare2(BLDC_TIMx,BLDC_TIM_PERIOD*speed_duty/100);
12
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCx_Enable);
13
/* Channel3 configuration */
14
TIM_SetCompare3(BLDC_TIMx,BLDC_TIM_PERIOD);
15
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCxN_Enable);
16
17
uwStep=5;
18
break;
19
case 5: //B+ A20
/* Next step: Step 3 Configuration -------------------------------------- */
21
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCx_Disable);
22
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCxN_Disable);
23
24
/* Channel1 configuration */
25
TIM_SetCompare1(BLDC_TIMx,BLDC_TIM_PERIOD);
26
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCxN_Enable);
27
28
/* Channel2 configuration */
29
TIM_SetCompare2(BLDC_TIMx,BLDC_TIM_PERIOD*speed_duty/100);
30
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCx_Enable);
31
/* Channel3 configuration */
32
33
uwStep=1;
34
break;
35
case 1: //C+ A36
/* Next step: Step 4 Configuration -------------------------------------- */
37
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCx_Disable);
STM32 技术开发手册
www.ing10bbs.com
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCxN_Disable);
/* Channel1 configuration */
TIM_SetCompare1(BLDC_TIMx,BLDC_TIM_PERIOD);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCxN_Enable);
/* Channel2 configuration */
/* Channel3 configuration */
TIM_SetCompare3(BLDC_TIMx,BLDC_TIM_PERIOD*speed_duty/100);
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCx_Enable);
uwStep=3;
break;
case 3: //C+ B/* Next step: Step 5 Configuration -------------------------------------- */
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCx_Disable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCxN_Disable);
/* Channel1 configuration */
/* Channel2 configuration */
TIM_SetCompare2(BLDC_TIMx,BLDC_TIM_PERIOD);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCxN_Enable);
/* Channel3 configuration */
TIM_SetCompare3(BLDC_TIMx,BLDC_TIM_PERIOD*speed_duty/100);
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCx_Enable);
uwStep=2;
break;
case 2: //A+ B/* Next step: Step 6 Configuration -------------------------------------- */
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCx_Disable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCxN_Disable);
/* Channel1 configuration */
TIM_SetCompare1(BLDC_TIMx,BLDC_TIM_PERIOD*speed_duty/100);
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCx_Enable);
/* Channel2 configuration */
TIM_SetCompare2(BLDC_TIMx,BLDC_TIM_PERIOD);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCxN_Enable);
/* Channel3 configuration */
uwStep=6;
break;
case 6: //A+ C/* Next step: Step 1 Configuration -------------------------------------- */
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCx_Disable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCxN_Disable);
/* Channel1 configuration */
TIM_SetCompare1(BLDC_TIMx,BLDC_TIM_PERIOD*speed_duty/100);
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCx_Enable);
/* Channel2 configuration */
/* Channel3 configuration */
TIM_SetCompare3(BLDC_TIMx,BLDC_TIM_PERIOD);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCxN_Enable);
uwStep=4;
break;
default:
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCx_Disable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCxN_Disable);
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCx_Disable);
STM32 技术开发手册
www.ing10bbs.com
101
102
103
104
105
106 }
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCxN_Disable);
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCx_Disable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCxN_Disable);
break;
}
首先说明下,我们的例程使用 H_pwm-L_on 的 PWM 调制方式。
该函数里边就是一个 switch 语句,判断 uwStep 变量值,控制定时器相关通
道输出 PWM 以及关闭其他通道 PWM。比如,当 uwStep 为 4 时,控制定时器通
道 2 输出 PWM,对应 B+桥臂,这里默认占空比是 15%;控制定时器通道 3 的互
补通道输出 PWM,对应 C-桥臂,这里默认占空比为 100%,实现了“L_on”的效果;
同 时 把 其 他 通 道 全 部 关 闭 。 最 后 , 把 uwStep 变 量 值 改 为 5 , 下 次 运 行
BLDC_PHASE_CHANGE 函数就执行对应的程序。
代码 10-5 main 函数
01 int main(void)
02 {
03
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
04
SysTick_Init();
05
06
BLDC_TIMx_PWM_Init();
07
08
uwStep=4;
09
10
/* 无限循环 */
11
while (1) {
12
BLDC_PHASE_CHANGE();
13
HAL_Delay(4);
14
}
15 }
该 函 数 主 要 看 while(1) 无 限 循 环 , 在 无 限 循 环 中 不 断 调 用
BLDC_PHASE_CHANGE 函数,并且延时一段时间。
实验操作与现象
使用开发板配套的 MINI USB 线连接到开发板标示“调试串口”字样的 MIMI
USB 接口为开发板提供电源。下载完程序之后,用过示波器可以观察到定时器 6
个通道引脚波形,见图 10-27。
STM32 技术开发手册
www.ing10bbs.com
图 10-27 6 步 PWM 实际波形
10.4.2 BLDC 驱动实现
上面介绍了 6 步 PWM 输出的实现,现在我们介绍 BLDC 的旋转驱动,这里
以 “ YSF1_BLDC-003. 霍 尔 传 感 器 接 口 ( 开 环 )” 例 程 做 介 绍 。 该 例 程 用 到
bsp_BLDCTIM.c、bsp_SysTick.c 和 bsp_key.c 这几个文件都是以前工程用到过的,
这里就不再介绍。我们这里主要讲霍尔传感器信号获取、处理过程。
之前定时器章节我们有大概介绍了定时器的霍尔传感器接口功能,简单来描
述霍尔传感器接口功能:霍尔传感器一般都是三根线,这三根线接入到定时器的
三个输入通道(必须是对应 CH1、CH2 和 CH3),配置定时器为霍尔传感器接口
功能,可以检测任一霍尔传感器状态发生改变,当检测到任一个霍尔传感器状态
发生改变时候就运行 6 步 PWM 换相函数改变桥臂状态。
霍尔传感器接口具体配置过程如下:
1) 置 TIMx_CR2 寄存器的 TI1S 位为’1’,配置三个定时器输入逻辑异或到
TI1 输入,见图 10-28;
STM32 技术开发手册
www.ing10bbs.com
代码 10-6 配置三个定时器输入逻辑异或输入
2) 时基编程:置 TIMx_ARR 为其最大值 0xFFFF(计数器必须通过 TI1 的变化
清零)。设置预分频器得到一个最大的计数器周期,它长于传感器上的两
次变化的时间间隔。
3) 设置通道 1 为捕获模式(选中 TRC):置 TIMx_CCMR1 寄存器中 CC1S=01,
如果需要,还可以设置数字滤波器。
4) 设置通道 2 为 PWM2 模式,并具有要求的延时:置 TIMx_CCMR1 寄存器
中的 OC2M=111 和 CC2S=00。
5) 选择 OC2REF 作为 TRGO 上的触发输出:置 TIMx_CR2 寄存器中的
MMS=101。
图 10-28 霍尔传感器接口的实例
STM32 技术开发手册
www.ing10bbs.com
当霍尔传感器接口任一信号状态发生改变时候,计数器(CNT)重新计数,CCR1
寄存器存放上次的计数值。如果使能设置了 CCR2 寄存器,在 CNT 值等于 CCR2
时候,TRGO 会产生来着 OC2REF 的触发信号。这个触发信号可以用来触发高级
控制定时器进行 6 步换相。
实际上,霍尔传感器接口编程最重要的地方就比普通的输入捕获功能加了
“配置三个定时器输入逻辑异或到 TI1 输入”。上面的第 4 和 5 步是附加的功能,
程序中是可选的,我们的例程就不用这个。下面开始讲解霍尔传感器接口程序。
bsp_hall.h 文件内容
bsp_hall.h 存放霍尔信号检测对应定时器相关定义。
代码 10-7 霍尔传感器相关宏定义
01 #ifndef __BSP_HALL_H__
02 #define __BSP_HALL_H__
03
04 /* 包含头文件 ----------------------------------------------------------------*/
05 #include <stm32f10x.h>
06
07 /* 类型定义 ------------------------------------------------------------------*/
08 /* 宏定义 --------------------------------------------------------------------*/
09 #define HALL_TIMx
TIM3
10 #define HALL_TIM_APBxClock_FUN
RCC_APB1PeriphClockCmd
11 #define HALL_TIM_CLK
RCC_APB1Periph_TIM3
12 #define HALL_TIM_PERIOD
0xFFFF
//定时器重载值
13 #define HALL_TIM_PRESCALER
71
14 #define HALL_TIM_Channel_x
TIM_Channel_1
//通道
15
16 #define HALL_TIM_GPIO_REMAP
GPIO_FullRemap_TIM3
17 #define HALL_TIM_GPIO_CLK
RCC_APB2Periph_GPIOC
18 #define HALL_TIM_CH1_PIN
GPIO_Pin_6
19 #define HALL_TIM_CH1_GPIO
GPIOC
20 #define HALL_TIM_CH2_PIN
GPIO_Pin_7
21 #define HALL_TIM_CH2_GPIO
GPIOC
22 #define HALL_TIM_CH3_PIN
GPIO_Pin_8
23 #define HALL_TIM_CH3_GPIO
GPIOC
24
25 #define HALL_TIM_IRQn
TIM3_IRQn
26 #define HALL_TIM_IRQHANDLER
TIM3_IRQHandler
27
28 /* 扩展变量 ------------------------------------------------------------------*/
29 /* 函数声明 ------------------------------------------------------------------*/
30 void HALL_TIMx_Init(void);
31
32 #endif // __BSP_HALL_H__
33
34 /******************* (C) COPYRIGHT 2015-2020 硬石嵌入式开发团队 *****END OF FILE****/
这里定义霍尔传感器定时器相关信息。
STM32 技术开发手册
www.ing10bbs.com
bsp_hall.c 文件内容
该文件存放了霍尔传感器接口配置相关代码。
代码 10-8 霍尔传感器 GPIO 初始化和中断优先级
01 /**
02
* 函数功能: 霍尔传感器引脚初始化.
03
* 输入参数: 无
04
* 返 回 值: 无
05
* 说
明:使用宏定义方法代替具体引脚号,方便程序移植,只要简单修改 bsp_HALL.h
06
*
文件相关宏定义就可以方便修改引脚。
07
*/
08 static void HALL_TIMx_GPIO_Init(void)
09 {
10
/* 定义 IO 硬件初始化结构体变量 */
11
GPIO_InitTypeDef GPIO_InitStructure;
12
13
/* 为启用 IO 引脚中断功能需要使能复用功能时钟 */
14
RCC_APB2PeriphClockCmd(HALL_TIM_GPIO_CLK|RCC_APB2Periph_AFIO,ENABLE);
15
16
/* 设定引脚 IO 编号 */
17
GPIO_InitStructure.GPIO_Pin = HALL_TIM_CH1_PIN;
18
/* 设定引脚 IO 为输入模式 */
19
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
20
/* 初始化 KEY2 对应引脚 IO */
21
GPIO_Init(HALL_TIM_CH1_GPIO, &GPIO_InitStructure);
22
23
GPIO_InitStructure.GPIO_Pin = HALL_TIM_CH2_PIN;
24
GPIO_Init(HALL_TIM_CH2_GPIO, &GPIO_InitStructure);
25
26
GPIO_InitStructure.GPIO_Pin = HALL_TIM_CH3_PIN;
27
GPIO_Init(HALL_TIM_CH3_GPIO, &GPIO_InitStructure);
28
/* 定时器通道引脚重映射 */
29
GPIO_PinRemapConfig(HALL_TIM_GPIO_REMAP,ENABLE);
30 }
31
32 /**
33
* 函数功能: 配置嵌套向量中断控制器 NVIC
34
* 输入参数: 无
35
* 返 回 值: 无
36
* 说
明:无
37
*/
38 static void HALL_TIMx_NVIC_Configuration(void)
39 {
40
NVIC_InitTypeDef NVIC_InitStructure;
41
42
/* 配置 TIM 为中断源 */
43
NVIC_InitStructure.NVIC_IRQChannel = HALL_TIM_IRQn;
44
/* 设置抢占式优先级为 0 */
45
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
46
/* 设置子优先级为 0 */
47
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
48
/* 使能中断通道 */
49
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
50
NVIC_Init(&NVIC_InitStructure);
51 }
STM32 技术开发手册
www.ing10bbs.com
HALL_TIMx_GPIO_Init 函数用于初始化霍尔传感器接口 GPIO,配置为输入模
式,HALL_TIMx_NVIC_Configuration 函数用于设置霍尔传感器接口定时器的中断
优先级。
代码 10-9 HALL_TIMx_Configuration 函数
01 static void HALL_TIMx_Configuration(void)
02 {
03
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
04
TIM_ICInitTypeDef
TIM_ICInitStructure;
05
06
/* 使能定时器时钟 */
07
HALL_TIM_APBxClock_FUN(HALL_TIM_CLK,ENABLE);
08
/* 定时器基本参数始终 */
09
/* 定时周期: HALL_TIM_Period+1 */
10
TIM_TimeBaseStructure.TIM_Period = HALL_TIM_PERIOD;
11
/* 设置预分频:HALL_TIM_Prescaler,输出脉冲频率:72MHz/(HALL_TIM_Prescaler+1)/(HALL_TIM_Period+1) */
12
TIM_TimeBaseStructure.TIM_Prescaler = HALL_TIM_PRESCALER;
13
/* 设置时钟分频系数:不分频(这里用不到) */
14
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1 ;
15
/* 向上计数模式 */
16
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned1;
17
TIM_TimeBaseInit(HALL_TIMx, &TIM_TimeBaseStructure);
18
19
/* 初始化 TIM5 输入捕获参数 */
20
/* CC1S=01
选择输入端 IC1 映射到 TI1 上 */
21
TIM_ICInitStructure.TIM_Channel
= HALL_TIM_Channel_x;
22
/* 上升沿捕获 */
23
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_BothEdge;
24
/* 映射到 TI1 上 */
25
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_TRC;
26
/* 配置输入分频,不分频 */
27
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
28
/* IC1F=0000 配置输入滤波器 不滤波 */
29
TIM_ICInitStructure.TIM_ICFilter
= 0x08;
30
TIM_ICInit(HALL_TIMx, &TIM_ICInitStructure);
31
32
/* 配置 NVIC */
33
HALL_TIMx_NVIC_Configuration();
34
35
TIM_SelectHallSensor(HALL_TIMx,ENABLE);
//使能 TIMx 的霍尔传感器接口
36
TIM_SelectInputTrigger(HALL_TIMx, TIM_TS_TI1F_ED);
//输入触发源选择
37
38
TIM_SelectSlaveMode(HALL_TIMx, TIM_SlaveMode_Reset);
//从模式选择
39
TIM_SelectMasterSlaveMode(HALL_TIMx, TIM_MasterSlaveMode_Enable);
//主从模式选择
40
/* 允许更新中断 ,允许 CC1IE 捕获中断
*/
41
TIM_ITConfig(HALL_TIMx, TIM_IT_Trigger, ENABLE);
42
/* 使能定时器 */
43
TIM_Cmd(HALL_TIMx, ENABLE);
44
TIM_ClearITPendingBit (HALL_TIMx,TIM_IT_Trigger);
45 }
HALL_TIMx_Configuration 函数前面部分是常规的定时器配置,配置定时器输
入捕获通道为:TIM_ICSelection_TRC,实现“异或输入”。
TIM_SelectHallSensor 函数使能定时器霍尔传感器接口功能。
STM32 技术开发手册
www.ing10bbs.com
TIM_SelectInputTrigger 函数选择输入触发源,这里选择:TIM_TS_TI1F_ED,
见图 10-29。
图 10-29 定时器框图部分截图
TIM_SelectSlaveMode 函数设置从模式,设置为复位,使得每次霍尔传感器
信号状态发生改变时 CNT 重新计数。TIM_SelectMasterSlaveMode 函数使能主从
模式,霍尔传感器接口必须开启主从模式。
TIM_ITConfig 配置定时器中断,这里选择定时器的触发中断,这在定时器捕
获到霍尔传感器信号状态发生改变时可以触发中断,从而运行中断服务函数。
最后,TIM_Cmd 函数使能定时器,TIM_ClearITPendingBit 清除中断标志位。
stm32f10x_it.c 文件内容
stm32f10x_it.c 文件用于存放中断服务函数,前面我们使能了霍尔传感器接
口定时器的触发中断,在该文件中我们编写了相关服务函数。
代码 10-10 HALL_TIM_IRQHANDLER 函数
01 void HALL_TIM_IRQHANDLER(void)
02 {
03
/* 确保是否产生了 EXTI Line 中断 */
04
if ( TIM_GetITStatus ( HALL_TIMx, TIM_IT_Trigger ) != RESET ) {
05
/* 清除中断标志位 */
06
TIM_ClearITPendingBit (HALL_TIMx,TIM_IT_Trigger);
07
HALL_TIMx_Callback();
08
}
09 }
//捕获中断
STM32 技术开发手册
www.ing10bbs.com
该函数中我们在判断确认是发生了触发中断后,直接调用了
HALL_TIMx_Callback 函数,这个函数定义在 main.c 文件中,接下来我们会进一步
分析。
main.c 文件内容
该文件存放了霍尔传感器信号和 6 步换相处理。
代码 10-11 HALL_TIMx_Callback 函数
01 void HALL_TIMx_Callback(void)
02 {
03
uint8_t pinstate=0;
04
if (motor_state==STOP)return;
05
06
if ((HALL_TIM_CH1_GPIO->IDR & HALL_TIM_CH1_PIN) != (uint32_t)Bit_RESET) { //霍尔传感器状态获取
07
pinstate |= 0x01;
08
}
09
if ((HALL_TIM_CH2_GPIO->IDR & HALL_TIM_CH2_PIN) != (uint32_t)Bit_RESET) { //霍尔传感器状态获取
10
pinstate |= 0x02;
11
}
12
if ((HALL_TIM_CH3_GPIO->IDR & HALL_TIM_CH3_PIN) != (uint32_t)Bit_RESET) { //霍尔传感器状态获取
13
pinstate |= 0x04;
14
}
15
if (motor_direction==CW) // 方向判断
16
pinstate=7-pinstate;
17
BLDC_PHASE_CHANGE(pinstate);//驱动换相
18
time_count=0;
19 }
该函数是霍尔传感器接口定时器的触发中断服务回调函数,实现对霍尔传感
器传感器信号处理。
函数最开始判断全局变量 motor_state 是否等于 STOP,如果电机处于停止状
态就退出函数。
接下来使用三个 if 语句判断当前霍尔传感器信号,并赋值给 pinstate 变量。
然后做旋转方向处理。
后面调用 BLDC_PHASE_CHANGE 函数进行换向,BLDC_PHASE_CHANGE 函数
与上一个例程代码大同小异,这里不再分析。
time_count 全局变量用于电机堵塞判断,霍尔传感器信号可以触发定时器进
入 HALL_TIMx_Callback 函数说明电机是旋转动作的,而不是处于堵塞状态。
01 void HAL_SYSTICK_Callback(void)
02 {
03
if (motor_state==RUN) {
04
time_count++;
05
if (time_count>2000) { // 2s 超时,电机卡住不运转超过 2s 时间
STM32 技术开发手册
www.ing10bbs.com
06
07
08
09
10
11
12
13
14
15
16
17 }
motor_state=STOP;
TIM_ClearITPendingBit (HALL_TIMx,TIM_IT_Trigger);
NVIC_DisableIRQ(HALL_TIM_IRQn);
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCx_Disable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCxN_Disable);
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCx_Disable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCxN_Disable);
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCx_Disable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCxN_Disable);
}
}
HAL_SYSTICK_Callback 函数是系统滴答定时器中断回调函数,每 1ms 运行该
函数一次。
这里在该函数中执行电机堵转检测功能,并在检测到电机发生堵转,堵转时
长超过 2s,停止定时器输出,从而停止桥臂导通,保护电机。
代码 10-12 main 函数
01 int main(void)
02 {
03
uint8_t key_count=1;
04
05
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
06
SysTick_Init();
07
08
KEY_GPIO_Init();
09
BLDC_TIMx_PWM_Init();
10
11
HALL_TIMx_Init();
12
13
/* 无限循环 */
14
while (1) {
15
if (KEY1_StateRead()==KEY_DOWN) { // 功能选择
16
key_count++;
17
if (key_count>5)
18
key_count=1;
19
}
20
if (KEY2_StateRead()==KEY_DOWN) { // 功能执行
21
switch (key_count) {
22
case 1: // 电机启动
23
if (motor_state==STOP) {
24
motor_state=RUN;
25
HALL_TIMx_Callback();
26
HAL_Delay(4);
// 启动是必须延时一小段时间然后再次调用换行函数
27
NVIC_EnableIRQ(HALL_TIM_IRQn);
28
HALL_TIMx_Callback();
29
}
30
break;
31
case 2: // 加速
32
speed_duty+=5;
33
if (speed_duty>100)
34
speed_duty=100;
35
break;
36
case 3: // 减速
37
speed_duty-=5;
38
if (speed_duty<7)
STM32 技术开发手册
www.ing10bbs.com
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62 }
speed_duty=7;
break;
case 4: // 方向反转
if (motor_direction==CW)
motor_direction=CCW;
else
motor_direction=CW;
break;
case 5: // 停机
motor_state=STOP;
TIM_ClearITPendingBit (HALL_TIMx,TIM_IT_Trigger);
NVIC_DisableIRQ(HALL_TIM_IRQn);
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCx_Disable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCxN_Disable);
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCx_Disable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCxN_Disable);
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCx_Disable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCxN_Disable);
break;
}
}
}
该函数开始先初始化相关外设,然后在无限循环中不断检测按键,KEY1 按
键用于功能选择,通过改变 key_count 变量的值实现。KEY2 则是执行功能,通过
判断 key_count 变量的值执行相应的代码段。
简单描述按键作用:复位开机按下 KEY2,电机旋转;此时按下 KEY1,切换
到增速状态,按下 KEY2,电机转速提高,当然有个最高限制;再按下 KEY1 切换
到减速状态,按下 KEY2,电机转速下降,也有个最低速限制。调速都是控制 PWM
占空比实现的。如果再按下 KEY1,进入方向改变状态,按下 KEY2 可以改变电机
旋转方向,特别注意只有在电机转速较低时才能用改功能。最后按下 KEY1,切换
到停机功能,按下 KEY2 电机停止旋转。
实验操作与现象
根据我们提供的电机驱动板接线指导文档连接好“电机-驱动板”以及“驱动板
-控制板”,接好外部 24V 电源,打开电源后,可以按下 KEY1 和 KEY2 进行电机控
制。特别提醒,电机实验电路电流都比较大,特别稍有不慎可能烧坏电路板,强
烈建议使用带保护功能的电源做测试。
BLDC 基本控制就介绍到这里,接下来是电机高级控制篇,主要涉及到控制
算法问题,后面也会有 BLDC 的 PID 闭环控制讲解。
STM32 技术开发手册
www.ing10bbs.com
(三) 电机高级应用
第11章 基于 PID 算法的直流减速电机控制
自动控制系统可分为开环控制系统和闭环控制系统。一个控制系统包括控制
器﹑传感器﹑变送器﹑执行机构﹑输入输出接口。控制器的输出经过输出接口﹑
执行机构﹐加到被控系统上﹔控制系统的被控量﹐经过传感器﹐变送器﹐通过
输入接口送到控制器。不同的控制系统﹐其传感器﹑变送器﹑执行机构是不一样
的。比如压力控制系统要采用压力传感器。电加热控制系统的传感器是温度传感
器。目前,PID 控制及其控制器或智能 PID 控制器(仪表)已经很多,产品已在
工程实际中得到了广泛的应用,有各种各样的 PID 控制器产品,各大公司均开
发了具有 PID 参数自整定功能的智能调节器(intelligent regulator),其中 PID 控制
器参数的自动调整是通过智能化调整或自校正、自适应算法来实现。
11.1 PID 算法介绍
1. 开环控制系统
开环控制系统(open-loop control system)是指被控对象的输出(被控制量)对控
制器(controller)的输出没有影响。在这种控制系统中,不依赖将被控量反送回来
以形成任何闭环回路。
2. 闭环控制系统
闭环控制系统(closed-loop control system)的特点是系统被控对象的输出(被
控制量)会反送回来影响控制器的输出,形成一个或多个闭环。闭环控制系统有
正反馈和负反馈,若反馈信号与系统给定值信号相反,则称为负反馈( Negative
Feedback),若极性相同,则称为正反馈,一般闭环控制系统均采用负反馈,又称
负反馈控制系统。闭环控制系统的例子很多。比如人就是一个具有负反馈的闭环
控制系统,眼睛便是传感器,充当反馈,人体系统能通过不断的修正最后作出各
种正确的动作。如果没有眼睛,就没有了反馈回路,也就成了一个开环控制系统。
STM32 技术开发手册
www.ing10bbs.com
另例,当一台真正的全自动洗衣机具有能连续检查衣物是否洗净,并在洗净之后
能自动切断电源,它就是一个闭环控制系统。
3. 阶跃响应
阶跃响应是指将一个阶跃输入(step function)加到系统上时,系统的输出。
稳态误差是指系统的响应进入稳态后﹐系统的期望输出与实际输出之差。控制系
统的性能可以用稳、准、快三个字来描述。稳是指系统的稳定性(stability),一个
系统要能正常工作,首先必须是稳定的,从阶跃响应上看应该是收敛的﹔准是指
控制系统的准确性、控制精度,通常用稳态误差来(Steady-state error)描述,它表
示系统输出稳态值与期望值之差﹔快是指控制系统响应的快速性,通常用上升时
间来定量描述。
4. PID 控制的原理和特点
将偏差的比例(Proportion)、积分(Integral)和微分(Differential)通过线
性组合构成控制量,用这一控制量对被控对象进行控制,这样的控制器称 PID 控
制器。
PID 控制器问世至今已有近 70 年历史,它以其结构简单、稳定性好、工作可
靠、调整方便而成为工业控制的主要技术之一。当被控对象的结构和参数不能完
全掌握,或得不到精确的数学模型时,控制理论的其它技术难以采用时,系统控
制器的结构和参数必须依靠经验和现场调试来确定,这时应用 PID 控制技术最为
方便。即当我们不完全了解一个系统和被控对象﹐或不能通过有效的测量手段来
获得系统参数时,最适合用 PID 控制技术。PID 控制,实际中也有 PI 和 PD 控制。
PID 控制器就是根据系统的误差,利用比例、积分、微分计算出控制量进行控制
的。
上面是“官方版”的 PID 算法介绍,下面我们来个“通俗版”的介绍。
以电机转速控制为例。之前的直流减速电机章节已经介绍了调节 PWM 占空
比可以实现电机调试,编码器可以检测当前电机转速。那现在我需要控制电机转
速为 3 圈/s(目标速度),并且是不同负载下都控制在这个速度。
开始电机处于停止状态此时 PWM 占空比为 0,然后我们改变占空比为 45%,
电机旋转,通过编码器我们得到当前的速度只有 2.5 圈/s,此时我们需要加大占
STM32 技术开发手册
www.ing10bbs.com
空比,给到 50%,编码器得到速度才 2.8 圈/s;没办法,我们还需要再加占空比,
改为 55%,编码器得到 3.1 圈/s,惨了给大了,再调,改为 54%,这次幸运了,
编码器速度再 3 圈/s 左右变动,勉强满足要求。
如果现在为电机加了一些负载,本来占空比 54%有 3 圈/s 的速度的,现在下
降为 2.3 圈/s 了,现在为达到 3 圈/s 速度,又要类似上面的尝试修改过程,改为
60%,只有 2.5 圈/s,改为 80%,超了,到了 3.2 圈/s,改为 77%,差一点点,改
为 78%,效果还不错。
上面的占空比修改过程,是通过我们人为根据编码器反馈回来的数据数据经
过我们大脑处理后优化出来的调整过程。如果我现在想要编程实现这个自动调整
过程:就是不管增加负载还是减少负载,都让程序自己调整占空比使得电机转速
控制在 3 圈/s,程序自动调整占空比过程,不外乎当速度小了就加大占空比,速
度大了就减少占空比,主要是问题是究竟大多少或者减多少,我们大脑的一般想
法就是当前速度与目标速度差别大那占空比修改的幅度就大,差别小那就修改幅
度小。但是,这些终究是我们自己想的,在程序里边要怎么实现呢?比较高效的
做法就是使用一个数学计算公式实现,该公式有一个变量:当前速度与目标速度
的速度差值(有正负值之分),公式的计算结果是占空比的修改幅度值(有正负
值之分)。一般在程序中的实现方法都是把这个数学计算公式用一个函数实现。
PID 算法就是解决这个问题的数学公式。实际上,我们不仅仅想通过数学公式实
现占空比自动调整,并且是希望可以在很短的时间内就可以实现稳定在目标速度。
所以,一般 PID 算法要实现:快准狠。
 比例(P)控制
比例控制是一种最简单的控制方式。其控制器的输出与输入误差信号成比例
关系。当仅有比例控制时系统输出存在稳态误差(Steady-state error)
。
 积分(I)控制
在积分控制中,控制器的输出与输入误差信号的积分成正比关系。对一个自
动控制系统,如果在进入稳态后存在稳态误差,则称这个控制系统是有稳态误差
的或简称有差系统(System with Steady-state Error)。为了消除稳态误差,在控制
器中必须引入“积分项”。积分项对误差取决于时间的积分,随着时间的增加,
STM32 技术开发手册
www.ing10bbs.com
积分项会增大。这样,即便误差很小,积分项也会随着时间的增加而加大,它推
动控制器的输出增大使稳态误差进一步减小,直到等于零。因此,比例+积分(PI)
控制器,可以使系统在进入稳态后无稳态误差。
 微分(D)控制
在微分控制中,控制器的输出与输入误差信号的微分(即误差的变化率)成
正比关系。 自动控制系统在克服误差的调节过程中可能会出现振荡甚至失稳。
其原因是由于存在有较大惯性组件(环节)或有滞后(delay)组件,具有抑制误差
的作用,其变化总是落后于误差的变化。解决的办法是使抑制误差的作用的变化
“超前”,即在误差接近零时,抑制误差的作用就应该是零。这就是说,在控制
器中仅引入“比例”项往往是不够的,比例项的作用仅是放大误差的幅值,而目
前需要增加的是“微分项”,它能预测误差变化的趋势,这样,具有比例+微分的
控制器,就能够提前使抑制误差的控制作用等于零,甚至为负值,从而避免了被
控量的严重超调。所以对有较大惯性或滞后的被控对象,比例+微分(PD)控制器能
改善系统在调节过程中的动态特性。
11.2 模拟 PID 控制原理
在模拟控制系统中,控制器最常用的控制规律是 PID 控制。为了说明控制器
的工作原理,先看一个例子。如图 11-1 所示是一个小功率直流电机的调速原理
图。给定速度 n0(t)与实际转速进行比较 n(t),其差值 e(t)=n0(t)-n(t),经过 PID 控
制器调整后输出电压控制信号 u(t),u(t)经过功率放大(电机驱动)后,驱动直流
电动机改变其转速。
图 11-1 小功率直流电机调速系统
常规的模拟 PID 控制系统原理框图如图 11-2 所示。
STM32 技术开发手册
www.ing10bbs.com
图 11-2 模拟 PID 控制系统原理图
该系统由模拟 PID 控制器和被控对象组成。图中,r(t)是给定值,y(t)是系统
的实际输出值,给定值与实际输出值构成控制偏差 e(t)。
公式 11-1 控制偏差
𝐞(𝐭) = 𝐫(𝐭) − 𝐲(𝐭)
e(t)作为 PID 控制的输入,u(t)作为 PID 控制器的输出和被控对象的输入。 所
以模拟 PID 控制器的控制规律为
公式 11-2 模拟 PID 控制器的控制规律
u(t) = Kp[e(t) +
1
𝑡
∫ 𝑒(𝑡)𝑑𝑡 + 𝑇𝑑
𝑇𝑖 0
𝑑𝑒(𝑡)
𝑑𝑡
]
其中:Kp ―― 控制器的比例系数
Ti -- 控制器的积分时间,也称积分系数
Td ―― 控制器的微分时间,也称微分系数
1) 比例部分
比例部分的数学式表示是:
Kp * e(t)
在模拟 PID 控制器中,比例环节的作用是对偏差瞬间作出反应。偏差一旦产生控制
器立即产生控制作用,使控制量向减少偏差的方向变化。控制作用的强弱取决于比例系
数,比例系数越大,控制作用越强,则过渡过程越快,控制过程的静态偏差也就越小;
但是越大,也越容易产生振荡,破坏系统的稳定性。故而,比例系数选择必须恰当,才
能过渡时间少,静差小而又稳定的效果。
2) 积分部分
STM32 技术开发手册
www.ing10bbs.com
积分部分的数学式表示是:
𝑡
∫ 𝑒(𝑡)𝑑𝑡
𝑇𝑖 0
Kp
从积分部分的数学表达式可以知道,只要存在偏差,则它的控制作用就不断
的增加;只有在偏差 e(t)=0 时,它的积分才能是一个常数,控制作用才是一个不
会增加的常数。可见,积分部分可以消除系统的偏差。
积分环节的调节作用虽然会消除静态误差,但也会降低系统的响应速度,增
加系统的超调量。积分常数 Ti 越大,积分的积累作用越弱,这时系统在过渡时不
会产生振荡;但是增大积分常数会减慢静态误差的消除过程,消除偏差所需的时
间也较长,但可以减少超调量,提高系统的稳定性。当 Ti 较小时,则积分的作
用较强,这时系统过渡时间中有可能产生振荡,不过消除偏差所需的时间较短。
所以必须根据实际控制的具体要求来确定 Ti 。
3) 微分部分
微分部分的数学式表示是:
𝐾𝑝 ∗ 𝑇𝑑
𝑑𝑒(𝑡)
𝑑𝑡
实际的控制系统除了希望消除静态误差外,还要求加快调节过程。在偏差出
现的瞬间,或在偏差变化的瞬间,不但要对偏差量做出立即响应(比例环节的作
用),而且要根据偏差的变化趋势预先给出适当的纠正。为了实现这一作用,可
在 PI 控制器的基础上加入微分环节,形成 PID 控制器。
微分环节的作用使阻止偏差的变化。它是根据偏差的变化趋势(变化速度)
进行控制。偏差变化的越快,微分控制器的输出就越大,并能在偏差值变大之前
进行修正。微分作用的引入,将有助于减小超调量,克服振荡,使系统趋于稳定,
特别对髙阶系统非常有利,它加快了系统的跟踪速度。但微分的作用对输入信号
的噪声很敏感,对那些噪声较大的系统一般不用微分,或在微分起作用之前先对
输入信号进行滤波。
微分部分的作用由微分时间常数 Td 决定。Td 越大时,则它抑制偏差 e(t)变
化的作用越强;Td 越小时,则它反抗偏差 e(t)变化的作用越弱。微分部分显然对
系统稳定有很大的作用。
适当地选择微分常数 Td ,可以使微分作用达到最优。
STM32 技术开发手册
www.ing10bbs.com
由于计算机的出现,计算机进入了控制领域。人们将模拟 PID 控制规律引入
到计算机中来。对公式 11-2 的 PID 控制规律进行适当的变换,就可以用软件实
现 PID 控制,即数字 PID 控制。
11.3 数字 PID 控制
数字式 PID 控制算法可以分为位置式 PID 和增量式 PID 控制算法。
1. 位置式 PID
由于计算机控制是一种采样控制,它只能根据采样时刻的偏差计算控制量,
而不能像模拟控制那样连续输出控制量量,进行连续控制。由于这一特点公式
11-2 中的积分项和微分项不能直接使用,必须进行离散化处理。离散化处理的方
法为:以 T 作为采样周期,k 作为采样序号,则离散采样时间 kT 对应着连续时间
t,用矩形法数值积分近似代替积分,用一阶后向差分近似代替微分,可作如下近
似变换:
公式 11-3 位置式 PID 算法近似变换
𝑡 ≈ 𝑘𝑇 (𝑘 = 0,1,2 … … )
𝑡
𝑘
𝑘
∫ 𝑒(𝑡)𝑑𝑡 ≈ T ∑ 𝑒(𝑗𝑇) = 𝑇 ∑ 𝑒𝑗
0
𝑗=0
𝑗=0
𝑑𝑒(𝑡) 𝑒(𝑘𝑇) − 𝑒[(𝑘 − 1)𝑇] 𝑒𝑘 − 𝑒𝑘−1
=
{ 𝑑𝑡 ≈
𝑇
𝑇
上式中,为了表示的方便,将类似于 e(kT )简化成 ek 等。
将公式 11-3 代入公式 11-2,就可以得到离散的 PID 表达式为:
公式 11-4 离散的 PID 表达式 1
𝑘
𝑇
𝑒𝑘 − 𝑒𝑘−1
𝑢𝑘 = Kp[𝑒𝑘 + ∑ 𝑒𝑗 + 𝑇𝑑
]
𝑇𝑖
𝑇
𝑗=0
或者
STM32 技术开发手册
www.ing10bbs.com
公式 11-5 离散的 PID 表达式 2
𝑘
𝑢𝑘 = Kp ∗ 𝑒𝑘 + Ki ∑ 𝑒𝑗 + K𝑑 (𝑒𝑘 − 𝑒𝑘−1 )]
𝑗=0
其中:k——采样序号,k=0,1,2,……;
uk——第 k 次采样时刻的计算机输出值;
ek——第 k 次采样时刻输入的偏差值;
ek-1——第 k-1 次采样时刻输入的偏差值;
Ki——积分系数,Ki=Kp*T/Ti;
Kd——微分系数,Kd=Kp*Td/T;
如果采样周期足够小,则公式 11-4 或公式 11-5 的近似计算可以获得足够
精确的结果,离散控制过程与连续过程十分接近。
公式 11-4 或公式 11-5 表示的控制算法式直接按公式 11-2 所给出的 PID
控制规律定义进行计算的,所以它给出了全部控制量的大小,因此被称为全量式
或位置式 PID 控制算法。
这种算法的缺点是:由于全量输出,所以每次输出均与过去状态有关,计算
时要对 ek 进行累加,工作量大;并且,因为计算机输出的 uk 对应的是执行机构
的实际位置,如果计算机出现故障,输出的 uk 将大幅度变化,会引起执行机构的
大幅度变化,有可能因此造成严重的生产事故,这在实生产际中是不允许的。
增量式 PID 控制算法可以避免着重现象发生。
2. 增量式 PID 算法
所谓增量式 PID 是指数字控制器的输出只是控制量的增量∆uk。当执行机构
需要的控制量是增量,而不是位置量的绝对数值时,可以使用增量式 PID 控制算
法进行控制。
增量式 PID 控制算法可以通过公式 11-4 推导出。由公式 11-4 可以得到控
制器的第 k-1 个采样时刻的输出值为:
公式 11-6 第 k-1 个采样时刻的输出值
STM32 技术开发手册
www.ing10bbs.com
𝑘−1
𝑢𝑘−1
𝑇
𝑒𝑘−1 − 𝑒𝑘−2
= 𝐾𝑝[𝑒𝑘−1 + ∑ 𝑒𝑗 + 𝑇𝑑
]
𝑇𝑖
𝑇
𝑗=0
将公式 11-4 与公式 11-6 相减并整理,就可以得到增量式 PID 控制算法公
式为:
公式 11-7 增量式 PID 控制算法公式
∆𝑢𝑘 = 𝑢𝑘 − 𝑢𝑘−1
𝑇
𝑒𝑘 − 2𝑒𝑘−1 + 𝑒𝑘−2
𝑒𝑘 + T𝑑
)
𝑇𝑖
𝑇
𝑇 𝑇𝑑
2𝑇𝑑
𝑇𝑑
= Kp (1 + + ) 𝑒𝑘 − Kp(1 +
)𝑒𝑘−1 + Kp
𝑒
𝑇𝑖
𝑇
𝑇
𝑇 𝑘−2
= Kp(𝑒𝑘 − 𝑒𝑘−1 +
= A𝑒𝑘 − B𝑒𝑘−1 + C𝑒𝑘−2
其中:
A = Kp (1 +
B = Kp(1 +
C = Kp
𝑇
+
𝑇𝑖
2𝑇𝑑
𝑇
𝑇𝑑
𝑇
)
)
𝑇𝑑
𝑇
由公式 11-7 可以看出,如果计算机控制系统采用恒定的采样周期 T,一旦
确定 A、B、C,只要使用前后三次测量的偏差值,就可以由公式 11-7 求出控制
量。
增量式 PID 控制算法与位置式 PID 算法公式 11-4 相比,计算量小的多,因
此在实际中得到广泛的应用。
而位置式 PID 控制算法也可以通过增量式控制算法推出递推计算公式:
公式 11-8 增量式 PID 推出位置式 PID
𝑢𝑘 = 𝑢𝑘−1 + ∆u𝑘
就是目前在计算机控制中广泛应用的数字递推 PID 控制算法。
STM32 技术开发手册
www.ing10bbs.com
3. 控制器参数整定
控制器参数整定:指决定调节器的比例系数 Kp、积分时间 Ti、微分时间 Td
和采样周期 Ts 的具体数值。整定的实质是通过改变调节器的参数,使其特性和
过程特性相匹配,以改善系统的动态和静态指标,取得最佳的控制效果。
整定调节器参数的方法很多,归纳起来可分为两大类,即理论计算整定法和
工程整定法。理论计算整定法有对数频率特性法和根轨迹法等;工程整定法有凑
试法、临界比例法、经验法、衰减曲线法和响应曲线法等。工程整定法特点不需
要事先知道过程的数学模型,直接在过程控制系统中进行现场整定方法简单、计
算简便、易于掌握。
 凑试法
按照先比例(P)、再积分(I)、最后微分(D)的顺序。
置调节器积分时间 Ti =∞,微分时间 Td =0,在比例系数 Kp 按经验设置的初
值条件下,将系统投入运行,由小到大整定比例系数。求得满意的 1/4 衰减度
过渡过程曲线。
引入积分作用(此时应将上述比例系数 Kp 设置为 5/6 Kp)。将 Ti 由大到小
进行整定。
若需引入微分作用时,则将 Td 按经验值或按 Td=(1/3~1/4)Ti 设置,并由
小到大加入。
 临界比例法
在闭环控制系统里,将调节器置于纯比例作用下,从小到大逐渐改变调节器
的比例系数,得到等幅振荡的过渡过程。此时的比例系数称为临界比例系数 Ku,
相邻两个波峰间的时间间隔,称为临界振荡周期 Tu。
临界比例度法步骤:
1、将调节器的积分时间 Ti 置于最大(Ti =∞),微分时间置零(Td =0),比
例系数 Kp 适当,平衡操作一段时间,把系统投入自动运行。
2、 将比例系数 Kp 逐渐增大,得到等幅振荡过程,记下临界比例系数 Ku 和
临界振荡周期 Tu 值。
STM32 技术开发手册
www.ing10bbs.com
3、根据 Ku 和 Tu 值,采用经验公式,计算出调节器各个参数,即 Kp、Ti 和
Td 的值。
按“先 P 再 I 最后 D”的操作程序将调节器整定参数调到计算值上。若还不
够满意,可再作进一步调整。
临界比例度法整定注意事项:
有的过程控制系统,临界比例系数很大,使系统接近两式控制,调节阀不是
全关就是全开,对工业生产不利。
有的过程控制系统,当调节器比例系数 Kp 调到最大刻度值时,系统仍不产
生等幅振荡,对此,就把最大刻度的比例度作为临界比例度 Ku 进行调节器参数
整定。
 经验法
用凑试法确定 PID 参数需要经过多次反复的实验,为了减少凑试次数,提高
工作效率,可以借鉴他人的经验,并根据一定的要求,事先作少量的实验,以得
到若干基准参数,然后按照经验公式,用这些基准参数导出 PID 控制参数,这就
是经验法。
临界比例法就是一种经验法。这种方法首先将控制器选为纯比例控制器,并
形成闭环,改变比例系数,使系统对阶跃输入的响应达到临界状态,这时记下比
例系数 Ku、临界振荡周期为 Tu,根据 Z-N 提供的经验公式,就可以由这两个
基准参数得到不同类型控制器的参数,如表格 11-1 所示。
表格 11-1 临界比例法确定的模拟控制器参数
控制器类型
P
PI
PID
Kp
0.5Ku
0.45Ku
0.6Ku
Ti
Td
0.85Tu
0.5Tu
0.12Tu
这种临界比例法只针对模拟 PID 控制器,对于数字 PID 控制器,只要采样周
期取的较小,原则上也同样使用。在电动机的控制中,可以先采用临界比例法,
然后在采用临界比例法求得结果的基础上,用凑试法进一步完善。
表格 11-1 的控制参数,实际上是按衰减度为 1/4 时得到的。通常认为 1/4
的衰减度能兼顾到稳定性和快速性。如果要求更大的衰减,则必须用凑试法对参
数作进一步的调整。
STM32 技术开发手册
www.ing10bbs.com
4. 采样周期的选择
采样—数据控制系统中,设采样周期为 Ts,采样速率为 1/Ts,采样角频率为
ω=2π/Ts,采样周期 Ts 是设计者精心选择的重要参数,系统的性能与采样周期
的选择有密切关系。
香农(Shannon)采样定律:为不失真地复现信号的变化,采样频率至少应
大于或等于连续信号最高频率分量的二倍。根据采样定律可以确定采样周期的上
限值。实际采样周期的选择还要受到多方面因素的影响,不同的系统采样周期应
根据具体情况来选择。
采样周期的选择,通常按照过程特性与干扰大小适当来选取采样周期:即对
于响应快、
(如流量、压力)波动大、易受干扰的过程,应选取较短的采样周期;
反之,当过程响应慢(如温度、成份)、滞后大时,可选取较长的采样周期。
采样周期的选取应与 PID 参数的整定进行综合考虑,采样周期应远小于过程
的扰动信号的周期,在执行器的响应速度比较慢时,过小的采样周期将失去意义,
因此可适当选大一点;在计算机运算速度允许的条件下,采样周期短,则控制品
质好;当过程的纯滞后时间较长时,一般选取采样周期为纯滞后时间的 1/4~1/8。
5. 参数调整规则的探索
人们通过对 PID 控制理论的认识和长期人工操作经验的总结,可知 PID 参数
应依据以下几点来适应系统的动态过程。
1、在偏差比较大时,为使尽快消除偏差,提高响应速度,同时为了避免系
统响应出现超调,Kp 取大值,Ki 取零;在偏差比较小时,为继续减小偏差,并防
止超调过大、产生振荡、稳定性变坏,Kp 值要减小,Ki 取小值;在偏差很小时,
为消除静差,克服超调,使系统尽快稳定,Kp 值继续减小,Ki 值不变或稍取大。
2、当偏差与偏差变化率同号时,被控量是朝偏离既定值方向变化。因此,
当被控量接近定值时,反号的比列作用阻碍积分作用,避免积分超调及随之而来
的振荡,有利于控制;而当被控量远未接近各定值并向定值变化时,则由于这两
项反向,将会减慢控制过程。在偏差比较大时,偏差变化率与偏差异号时,Kp 值
取零或负值,以加快控制的动态过程。
STM32 技术开发手册
www.ing10bbs.com
3、偏差变化率的大小表明偏差变化的速率,ek-ek-1 越大,Kp 取值越小,Ki 取
值越大,反之亦然。同时,要结合偏差大小来考虑。
4、微分作用可改善系统的动态特性,阻止偏差的变化,有助于减小超调量,
消除振荡,缩短调节时间 ts,允许加大 Kp,使系统稳态误差减小,提高控制精度,
达到满意的控制效果。所以,在 ek 比较大时,Kd 取零,实际为 PI 控制;在 ek 比
较小时,Kd 取一正值,实行 PID 控制。
11.4 基于增量式 PID 的电机速度调节实现
要使用 PID 算法控制的系统某个参数,必须有该参数的相关反馈变量。比如,
我们现在要控制电机转速,那我们有通过编码器采集得到的反馈数据。
前面相关章节内容已经介绍了直流减速电机驱动和编码器测速方法。现在我
们就在这两个基础上实现增量式 PID 算法,控制电机转速,使得电机在不同负载
状态下还是保持在目标速度附近波动。
这里以“YSF1_HAL_MOTOR-045. 单轴 25GA370 直流电机增量式 PID 旋转控
制(L298N 驱动)”例程代码讲解,该工程代码可以在我们配置光盘资料中找到。
该工程中有用到 bsp_L298N.c 和 bsp_encoder.c 等等文件都是在之前章节已
经用过的,这里就不再介绍,如有不明白的地方可以返回去相关章节查看。这里
就直接讲解 PID 的代码内容
main.c 文件内容
我们把 PID 实现方法都是存放在该文件中,实际上,PID 实现代码是很少的,
非常好理解。
代码 11-1 PID 相关类型和宏定义
01 /* 私有类型定义 --------------------------------------------------------------*/
02 //定义 PID 结构体
03 typedef struct {
04
__IO int
SetPoint;
05
__IO double
Proportion;
06
__IO double
Integral;
07
__IO double
Derivative;
08
__IO int
LastError;
09
__IO int
PrevError;
10 } PID;
//设定目标 Desired Value
//比例常数 Proportional Const
//积分常数 Integral Const
//微分常数 Derivative Const
//Error[-1]
//Error[-2]
STM32 技术开发手册
www.ing10bbs.com
11
12 /* 私有宏定义 ----------------------------------------------------------------*/
13 /*************************************/
14 //定义 PID 相关宏
15 // 这三个参数设定对电机运行影响非常大
16 /*************************************/
17 #define P_DATA
3.2
//P 参数
18 #define I_DATA
1.1
//I 参数
19 #define D_DATA
-0.15
//D 参数
这里为 PID 算法定义了一个结构体 PID,有 6 个成员。SetPoint 设置目标参
数,针对我们控制电机,就是我们的目标速度;Proportion、Integral、Derivative
分别是 P、I、D 参数,对应公式 11-7 中的 A、B、C;LastError 上一次的偏差值,
对应公式 11-7 中的 ek-1;PrevError 上上次的偏差值,对应公式 11-7 中的 ek-2。
宏定义了 P、I、D 三个参数值,这三个参数对应公式 11-7 中的 A、B、C。
代码 11-2 增量式 PID 实现
01 /**************PID 参数初始化********************************/
02 void IncPIDInit(void)
03 {
04
sptr->LastError=0;
//Error[-1]
05
sptr->PrevError=0;
//Error[-2]
06
sptr->Proportion=P_DATA;
//比例常数 Proportional Const
07
sptr->Integral=I_DATA;
//积分常数 Integral Const
08
sptr->Derivative=D_DATA;
//微分常数 Derivative Const
09
sptr->SetPoint=100;
//设定目标 Desired Value
10 }
11 /********************增量式 PID 控制设计************************************/
12 int IncPIDCalc(int NextPoint)
13 {
14
int iError,iIncpid;
//当前误差
15
iError=sptr->SetPoint-NextPoint;
//增量计算
16
iIncpid=(sptr->Proportion * iError)
//E[k]项
17
-(sptr->Integral * sptr->LastError)
//E[k-1]项
18
+(sptr->Derivative * sptr->PrevError); //E[k-2]项
19
20
sptr->PrevError=sptr->LastError;
//存储误差,用于下次计算
21
sptr->LastError=iError;
22
return (iIncpid);
//返回增量值
23 }
IncPIDInit 函数为 PID 结构体变量赋初始化值。开始时候把偏差值都设置为
0。
这里把目标值设定为 100,例程中把 PID 的采样周期设定为 200ms,在编码
器章节我们讲到,我们所使用的直流减速电机的编码器是每旋转一圈输出 374 个
脉冲,这里的把目标设置为 100,那么实际目标速度就是(100*(1000/200)/374)
圈/s。因为我们主要是演示 PID 算法过程,对于速度单位转换这些就不怎么注意,
大家在实际应用中可以自己做单位上的转换。
STM32 技术开发手册
www.ing10bbs.com
IncPIDCalc 函数就是实现公式 11-7 的运算内容。该函数有个形参,该参数传
递了当前量。首先,是求出当前偏差:目标值与当前量的差值,根据公式 11-7 计
算得到结果增量,最后,更新 PrevError 和 LastError 变量值。
代码 11-3 HAL_TIM_IC_CaptureCallback 函数
01 /**
02
* 函数功能: 定时器输入捕获中断回调函数
03
* 输入参数: htim:定时器句柄
04
* 返 回 值: 无
05
* 说
明: 无
06
*/
07 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
08 {
09
CaptureNumber++;
10 }
该函数用于计算编码器输入的脉冲数,每来一个编码器脉冲,CaptureNumber
就自加 1,该变量值可以用来测速和测运动距离。
代码 11-4 HAL_SYSTICK_Callback 函数
01 /**
02
* 函数功能: 系统滴答定时器中断回调函数
03
* 输入参数: 无
04
* 返 回 值: 无
05
* 说
明: 每发生一次滴答定时器中断进入该回调函数一次
06
*/
07 void HAL_SYSTICK_Callback(void)
08 {
09
if (start_flag) { // 等待脉冲输出后才开始计时
10
time_count++;
// 每 1ms 自动增一
11
if (time_count==200) {
12
__IO uint32_t count;
13
__IO int para;
14
__IO double cal;
15
16
/* 得到编码器计数值,数值越大说明速度越大 */
17
count=CaptureNumber;
18
CaptureNumber=0;
// 清零,从零开始计数
19
20
/* 计数得到增量式 PID 的增量数值 */
21
para=IncPIDCalc(count);
22
23
/* 根据增量数值调整当前电机速度 */
24
if ((para<-3)||(para>3)) { // 不做 PID 调整,避免误差较小时频繁调节引起震荡。
25
PWM_Duty +=para;
26
}
27
if (PWM_Duty>899)PWM_Duty=899;
28
29
30
// 11:编码器线数(转速一圈输出脉冲数)
31
// 34:电机减数比,内部电机转动圈数与电机输出轴转动圈数比,即减速齿轮比
32
cal=sptr->SetPoint;
33
printf("\n 设定目标速度 -> 编码器在%ds 时间计数%d 个脉冲\n",time_count,sptr->SetPoint);
34
printf("
相当于实际目标速度为:%0.2f 圈/s\n",cal*(1000/time_count)/11/34);
STM32 技术开发手册
www.ing10bbs.com
35
36
37
38
39
40
41
42
43
44
45
46 }
cal=count;
printf("当前电机速度-> 编码器在%ds 时间计数%d 个脉冲\n",time_count,count);
printf("
相当于当前实际速度为:%0.2f 圈/s\n",cal*(1000/time_count)/11/34);
printf("增量式 PID 算法计数结果值:%d
设置新的占空比为:%d\n",para,PWM_Duty);
L298N_DCMOTOR_Contrl(1,2,PWM_Duty);
time_count=0;
}
}
start_flag 变量是电机运转标志,当电机运行时变量值为 1。time_count 变量
用于时间计数,HAL_SYSTICK_Callback 是每 1ms 就运行一次,当 time_count 自加
到 200 时,刚好是 200ms 的 PID 采样周期。
局部变量 count 保存 200ms 内编码器的脉冲数,并作为 IncPIDCalc 函数的输
入参数。该函数的运算结果保存在 para 变量中,这里我直接使用 para 量值作为
PWM 占空比调整值。把该值向量加到 PWM_Duty 上,并把结果保存在 PWM_Duty
变量中。函数最后调用 L298N_DCMOTOR_Contrl 函数改变 PWM 占空比。
这里特别说些下 IncPIDCalc 函数的使用问题。一般来说,我们认为该函数的
输入参数和输出函数的属性是一样的,比如,count 变量我们认为它是一个速度
标志量,那经 IncPIDCalc 函数运算后应该也是一个速度标志量。但是这里我们却
是直接把它做为占空比改变量。在计算上这些确实是可以行得通的,只要我们修
改 P、I、D 三个参数变量值就可以。在一些其他实际应用上,大家可以尽量保持
该函数输入和输出属性更加紧密联系,然后在输出结果之后才来做属性之间换算。
代码 11-5 main 函数
01 int main(void)
02 {
03
/* 复位所有外设,初始化 Flash 接口和系统滴答定时器 */
04
HAL_Init();
05
/* 配置系统时钟 */
06
SystemClock_Config();
07
08
KEY_GPIO_Init();
09
MX_DEBUG_USART_Init();
10
11
IncPIDInit();
12
13
ENCODER_TIMx_Init();
14
HAL_TIM_Base_Start(&htimx_ENCODER);
15
16
/* 高级控制定时器初始化并配置 PWM 输出功能 */
17
L298N_TIMx_Init();
18
/* 启动定时器 */
STM32 技术开发手册
www.ing10bbs.com
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 }
HAL_TIM_Base_Start(&htimx_L298N);
HAL_TIM_IC_Start_IT(&htimx_ENCODER,ENCODER_TIM_CHANNELx);
/* 启动定时器通道和互补通道 PWM 输出 */
L298N_DCMOTOR_Contrl(1,2,0);
start_flag=1;
printf("增量式 PID 算法控制电机旋转\n");
/* 无限循环 */
while (1) {
if (KEY1_StateRead()==KEY_DOWN) { // 增速
/* 设置目标速度 */
sptr->SetPoint =50;
}
if (KEY2_StateRead()==KEY_DOWN) { // 减速
/* 设置目标速度 */
sptr->SetPoint =300;
}
}
main 函数开始调用相关硬件初始化函数和 PID 参数初始化函数。
HAL_TIM_Base_Start 函数用于启动电机 PWM 输出。HAL_TIM_IC_Start_IT 用
于启动编码器输入捕获。L298N_DCMOTOR_Contrl 控制电机旋转。
在无限循环中,检测 KEY1 和 KEY2 状态,当有按键按下时改变目标转速。
实验操作与现象
根据前面章节内容完成电机与 L298N 驱动器连接,然后连接 L298N 驱动器
和 YS-F1Pro 开发板;使用另外三根导线连接减速电机的编码器功能引脚与开发
板。使用开发板配套的 MINI USB 线连接到开发板标示“调试串口”字样的 MIMI
USB 接口。下载完程序之后,电机开始转动。如果按下两个按键可以调节电机旋
转速度。可以增加电机负载,观察电机转速情况。
11.5 基于位置式 PID 的电机速度调节实现
实现了增量式 PID,要理解位置式 PID 就非常简单了,实现代码都是大同小
异,这里以“YSF1_HAL_MOTOR-046. 单轴 25GA370 直流电机位置式 PID 旋转控
制(L298N 驱动)”例程代码讲解。
main.c 文件内容
与增量式 PID 代码相比就 main.c 文件内容做了一些小修改,这里就只介绍
不同点的地方,更多细节理解可以参考上一小节内容。
STM32 技术开发手册
www.ing10bbs.com
位置式 PID 代码实现公式 11-5 内容。
代码 11-6 PID 结构体
01 //定义 PID 结构体
02 typedef struct {
03
__IO int
SetPoint;
04
__IO long
SumError;
05
__IO double
Proportion;
06
__IO double
Integral;
07
__IO double
Derivative;
08
__IO int
LastError;
09
__IO int
PrevError;
10 } PID;
//设定目标 Desired Value
//误差累计
//比例常数 Proportional Const
//积分常数 Integral Const
//微分常数 Derivative Const
//Error[-1]
//Error[-2]
位置式 PID 结构体比增量式 PID 结构体多了一个 SumError 成员,用于记录
当前量与目标值的累积误差,位置式 PID 是不需要 PrevError 成员的。
代码 11-7 LocPIDCalc 函数
01 /********************位置式 PID 控制设计************************************/
02 unsigned int LocPIDCalc(int NextPoint)
03 {
04
int iError,dError;
05
iError = sptr->SetPoint - NextPoint; //偏差
06
sptr->SumError += iError; //积分
07
dError = iError - sptr->LastError; //微分
08
sptr->LastError = iError;
09
return (sptr->Proportion * iError //比例项
10
+ sptr->Integral * sptr->SumError //积分项
11
+ sptr->Derivative * dError); //微分项
12 }
该函数是位置式 PID 算法实现方法,实现公式 11-5 内容。首先求出目标值
与当前量的偏差保存在 iError 变量中,这个偏差量将做为位置式 PID 运算的比例
项因子;把这个偏差值加到 PID 结构体成员 SumError 计算累积误差,这个累积
误差将做为 PID 运算的积分项因子;把偏差值减去上次偏差值的结果做为 PID 运
算的微分项因子。
代码 11-8 HAL_SYSTICK_Callback 函数
01 void HAL_SYSTICK_Callback(void)
02 {
03
if (start_flag) { // 等待脉冲输出后才开始计时
04
time_count++;
// 每 1ms 自动增一
05
if (time_count==200) {
06
__IO uint32_t count;
07
__IO double cal;
08
09
/* 得到编码器计数值,数值越大说明速度越大 */
10
count=CaptureNumber;
11
CaptureNumber=0;
// 清零,从零开始计数
12
STM32 技术开发手册
www.ing10bbs.com
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 }
/* 计数得到位置式 PID 的位置数值 */
PWM_Duty=LocPIDCalc(count);
if (PWM_Duty>899)PWM_Duty=899;
// 11:编码器线数(转速一圈输出脉冲数)
// 34:电机减数比,内部电机转动圈数与电机输出轴转动圈数比,即减速齿轮比
cal=sptr->SetPoint;
printf("\n 设定目标速度 -> 编码器在%ds 时间计数%d 个脉冲\n",time_count,sptr->SetPoint);
printf("
相当于实际目标速度为:%0.2f 圈/s\n",cal*(1000/time_count)/11/34);
cal=count;
printf("当前电机速度-> 编码器在%ds 时间计数%d 个脉冲\n",time_count,count);
printf("
相当于当前实际速度为:%0.2f 圈/s\n",cal*(1000/time_count)/11/34);
printf("设置新的占空比为:%d\n",PWM_Duty);
L298N_DCMOTOR_Contrl(1,2,PWM_Duty);
time_count=0;
}
}
该函数是系统滴答定时器中断回调函数,每 1ms 运行一次。这里也是记录
200ms 运行一次 PID 计算。与增量式 PID 不同的是,这里是调用 LocPIDCalc 函数
进行位置式 PID 计算,并且把该函数返回值直接赋值给 PWM_Duty 变量,直接决
定了 PWM 的占空比,这个处理方法显然跟增量式为有区别的。
实验操作与现象
根据前面章节内容完成电机与 L298N 驱动器连接,然后连接 L298N 驱动器
和 YS-F1Pro 开发板;使用另外三根导线连接减速电机的编码器功能引脚与开发
板。使用开发板配套的 MINI USB 线连接到开发板标示“调试串口”字样的 MIMI
USB 接口。下载完程序之后,电机开始转动。如果按下两个按键可以调节电机旋
转速度。可以增加电机负载,观察电机转速情况。
11.6 BLDC 闭环控制
前面已经介绍了 BLDC 的旋转驱动,霍尔传感器信号不仅可以检测当前转子
位置信息,我们还可以利用它来检测电机旋转速度,而有了速度反馈信号我们就
可以使用 PID 算法来实现控速。
增量式 PID 在实际应用中非常广泛,这里就占用增量式 PID 实现 BLDC 的闭
环控制。
STM32 技术开发手册
www.ing10bbs.com
这里也着重讲解 main.c 文件内容,其他文件内容可以参考 BLDC 驱动章节理
解。
main.c 文件内容
该文件存放实现 BLDC 控制的代码,类似 IncPIDInit 和 IncPIDCalc 函数已经在
前面介绍过这里就不再介绍了。
代码 11-9 PID 参数
01 #define
02 #define
03 #define
P_DATA
I_DATA
D_DATA
0.5
0.06
0
//P 参数
//I 参数
//D 参数
这里定义用于 PID 算法的三个 P、I、D 参数。三个参数值与直流减速电机控
制参数有较大的差别的,特别的这里把 D 参数设置为 0,即实际上只是 PI 算法。
代码 11-10HALL_TIMx_Callback 函数
01 void HALL_TIMx_Callback(void)
02 {
03
uint8_t pinstate=0;
04
static __IO int8_t pinstate0=0;
05
06
if (bldc_dev.motor_state==STOP)return;
07
08
if ((HALL_TIM_CH1_GPIO->IDR & HALL_TIM_CH1_PIN) != (uint32_t)Bit_RESET) { //霍尔传感器状态获取
09
pinstate |= 0x01;
10
}
11
if ((HALL_TIM_CH2_GPIO->IDR & HALL_TIM_CH2_PIN) != (uint32_t)Bit_RESET) { //霍尔传感器状态获取
12
pinstate |= 0x02;
13
}
14
if ((HALL_TIM_CH3_GPIO->IDR & HALL_TIM_CH3_PIN) != (uint32_t)Bit_RESET) { //霍尔传感器状态获取
15
pinstate |= 0x04;
16
}
17
if (bldc_dev.motor_direction==CCW) // 方向判断
18
pinstate=7-pinstate;
19
BLDC_PHASE_CHANGE(pinstate);//驱动换相
20
if (pinstate0!=pinstate) { // 测试发现有时会连续出现两个相同的数据,这里滤掉重复的
21
bldc_dev.step_counter++;
22
bldc_dev.stalling_count=0;
23
}
24
pinstate0=pinstate;
25 }
该函数相比之前 BLDC 驱动章节的函数添加了 :定义了一个静态变量
pinstate0 用于保存上次霍尔传感器信号,在检测到霍尔传感器信号状态发生改变
时,即电机转子发生旋转,就将变量 bldc_dev.step_counter 自加一。类似直流减
速电机的做法,在固定时间内容,通过判断 bldc_dev.step_counter 值的大小就可
以知道电机的旋转速度。
STM32 技术开发手册
www.ing10bbs.com
代码 11-11 HAL_SYSTICK_Callback 函数
01 void HAL_SYSTICK_Callback(void)
02 {
03
static uint16_t time_count=0;
04
05
bldc_dev.stalling_count++;
06
if (bldc_dev.motor_state==RUN) {
07
time_count++;
08
if (bldc_dev.stalling_count>2000) // 电机卡住超时
09
bldc_dev.motor_state=STOP;
10
} else {
11
time_count=0;
12
}
13
if (time_count>50) { // 50ms
14
int temp;
15
int pid_result;
16
// bldc_dev.step_counter 记录霍尔传感器在 50ms 时间内产生的脉冲个数,
17
// 而电机旋转一圈会有总共 24 个脉冲,
18
// 使用 n=bldc_dev.step_counter/24 为 50ms 内电机转动圈数,为换算为
19
//【转/分钟(rpm)】
20
// n/50 = x/(60*1000) --> x=bldc_dev.step_counter*50
21
temp=bldc_dev.step_counter*50;
22
pid_result=IncPIDCalc(temp);
// 计算增量
23
// *10/25 为转速和占空比一个转换,转速(0~2500),占空比(0~1000)
24
pid_result =pid_result*10/25;
25
if ((pid_result+speed_duty)<70)
26
speed_duty =70;
27
else if ((pid_result+speed_duty)>1000)
28
speed_duty =1000;
29
else
30
speed_duty +=pid_result;
31
time_count=0;
32
bldc_dev.step_counter=0;
33
}
34 }
该 函 数 是 系 统 滴 答 定时 器 中 断 回 调 函 数 ,每 1ms 进 入 该 函 数 一 次 。
bldc_dev.stalling_count 变量用于电机堵转检测。
这里设置 PID 算法的采样周期为 50ms,我们把电机速度单位设定为转/分钟
(RPM)。bldc_dev.step_counter 记录霍尔传感器在 50ms 时间内产生的脉冲个数,
而电机旋转一圈会有总共 24 个脉冲,假设 n=bldc_dev.step_counter/24 为 50ms
内电机转动圈数,为换算为转/分钟单位,可以列出计算公式:n/50 = x/(60*1000),
最后,x=bldc_dev.step_counter*50,例程中是定义局部变量 temp 存放这个速度
值,即当前电机转速。
经过 IncPIDCalc 增量式 PID 运算后得到增量结果 pid_result,可以认为这个
pid_result 就是在当前转速基础上为达到目标转速需要调整的速度值,单位是转/
分钟。所以,现在的一个问题就是知道了需要调整的速度量,怎样转变为占空比
STM32 技术开发手册
www.ing10bbs.com
改变量呢?根据 BLDC 的额定转速是 2500rpm,例程设定占空比数值最大值为
1000,这样我们把需要调整的速度值 pid_result 做个简单的换算:
pid_result =pid_result*10/25;
得到占空比的改变量。speed_duty 变量就是实际的 PWM 占空比,把
pid_result 加到 speed_duty 变量上实现占空比修改,这里还特别对占空比最大值
和最小值进行了限制,占空比 100%肯定是最大了,这里限制最小值为 70(对应
占空比为 7%),为什么不是 0 呢?主要是占空比过小电机会停止旋转,特别如果
有加负载,这个最小值还需要修整。
代码 11-12 main 函数
01 int main(void)
02 {
03
uint8_t key_count=1;
04
05
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
06
SysTick_Init();
07
08
KEY_GPIO_Init();
09
BLDC_TIMx_PWM_Init();
10
11
HALL_TIMx_Init();
12
13
/* 无限循环 */
14
while (1) {
15
if (KEY1_StateRead()==KEY_DOWN) { // 功能选择
16
key_count++;
17
if (key_count>5)
18
key_count=1;
19
}
20
if (KEY2_StateRead()==KEY_DOWN) { // 功能执行
21
switch (key_count) {
22
case 1:
23
if (bldc_dev.motor_state==STOP) { // 电机启动
24
bldc_dev.motor_state=RUN;
25
bldc_dev.step_counter=0;
26
bldc_dev.stalling_count=0;
27
IncPIDInit();
28
if ((bldc_dev.motor_speed*10/25)>70)
29
speed_duty=bldc_dev.motor_speed*10/25;
30
// *10/25 为转速和占空比一个转换,转速(0~2500),占空比(0~1000)
31
else
32
speed_duty=70;
33
NVIC_EnableIRQ(HALL_TIM_IRQn);
34
HALL_TIMx_Callback();
35
}
36
break;
37
case 2: // 加速
38
bldc_dev.motor_speed+=100;
39
if (bldc_dev.motor_speed>MOTOR_MAX_SPEED)
40
bldc_dev.motor_speed=MOTOR_MAX_SPEED;
41
bldc_pid.SetPoint=bldc_dev.motor_speed;
42
break;
STM32 技术开发手册
www.ing10bbs.com
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 }
case 3: // 减速
bldc_dev.motor_speed-=50;
if (bldc_dev.motor_speed<MOTOR_MIN_SPEED)
bldc_dev.motor_speed=MOTOR_MIN_SPEED;
bldc_pid.SetPoint=bldc_dev.motor_speed;
break;
case 4: // 方向反转
if (bldc_dev.motor_direction==CW) {
bldc_dev.motor_direction=CCW;
} else {
bldc_dev.motor_direction=CW;
}
break;
case 5:
// 停机
TIM_ClearITPendingBit (HALL_TIMx,TIM_IT_Trigger);
NVIC_DisableIRQ(HALL_TIM_IRQn);
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCx_Disable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCxN_Disable);
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCx_Disable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCxN_Disable);
TIM_CCxCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCx_Disable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCxN_Disable);
HAL_Delay(5);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCxN_Enable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCxN_Enable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCxN_Enable);
while (bldc_dev.stalling_count<1600);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCxN_Disable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCxN_Disable);
TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCxN_Disable);
bldc_dev.motor_state=STOP;
break;
}
}
}
该函数开始部分是各个外设初始化,在无限循环里边检测两个按键状态,按
键使用方法可以参考 10.4.2 小节内容。
启动电机时,把相关变量值清零,运行 IncPIDInit 函数初始化 PID 参数。特
别的,调用 HALL_TIMx_Callback 函数让电机启动旋转。
加、减速的实现通过改变 PID 参数值 bldc_pid.SetPoint 实现。
实验操作与现象
根据我们提供的电机驱动板接线指导文档连接好“电机-驱动板”以及“驱动板
-控制板”,接好外部 24V 电源,打开电源后,可以按下 KEY1 和 KEY2 进行电机控
制。可以增加电机负载,观察电机转速情况。特别提醒,电机实验电路电流都比
较大,特别稍有不慎可能烧坏电路板,强烈建议使用带保护功能的电源做测试。
STM32 技术开发手册
www.ing10bbs.com
第12章 基于梯形加减速的步进电机控制
步进电机因其无需反馈就能对位置和速度进行控制而在工业自动化设备中
的应用极为广泛,对于速度变化较大的,尤其是加减速频繁的设备,常常发生力
矩不足或者失步的现象,而实际上许多案例中步进电机的选型并没有问题,其问
题在于负载位置对控制电路没有反馈,步进电机就必须正确响应每次励磁变化,
如果励磁频率选择不当,电机不能够移到新的位置,那么实际的负载位置相对控
制器所期待的位置出现永久误差,即发生失步现象或过冲现象,因此在速度变化
较大的步进电机控制系统中,防止失步和过冲是开环控制系统能否正常运行的关
键。
失步和过冲现象分别出现在步进电机启动和停止的时候,一般情况下,系统
的极限启动频率比较低,而要求的运行速度往往比较高,如果系统以要求的运行
速度直接启动,因为该速度已超过极限启动频率而不能正常启动,轻则可能发生
丢步,重则根本不能启动,产生堵转,系统运行起来以后,如果达到终点时立即
停止发送脉冲串,令其立即停止,则由于系统惯性作用,电机转子会转过平衡位
置,如果负载的惯性很大,会使步进电机转子转到接近终点平衡位置的下一个平
衡位置,并在该位置停下。
12.1 步进电机曲线加减速
步进电机只能够由数字信号控制运行的,当脉冲提供给驱动器时,在过于短
的时间里,控制系统发出的脉冲数太多,也就是脉冲频率过高,将导致步进电机
堵转。要解决这个问题,一般采用加减速的办法。就是说,在步进电机起步时,
要给逐渐升高的脉冲频率,减速时的脉冲频率需要逐渐减低。这就是我们常说的
“加减速”方法。
步进电机转速度,是根据输入的脉冲信号的变化来改变的。从理论上讲,给
驱动器一个脉冲,步进电机就旋转一个步距角(细分时为一个细分步距角)。实
际上,如果脉冲信号变化太快,步进电机由于内部的反向电动势的阻尼作用,转
子与定子之间的磁反应将跟随不上电信号的变化,将导致堵转和丢步。
STM32 技术开发手册
www.ing10bbs.com
所以步进电机在高速启动时,需要采用脉冲频率升速的方法,在停止时也要
有降速过程,以保证实现步进电机精密定位控制。加速和减速的原理是一样的。
下面就加速实例加以说明:
加速过程,是由基础频率(低于步进电机的直接起动最高频率)与跳变频率
(逐渐加快的频率)组成加速曲线(降速过程反之)。跳变频率是指步进电机在
基础频率上逐渐提高的频率,此频率不能太大,否则会产生堵转和丢步。
加减速曲线一般为直线(梯形)、指数或者“S”型曲线等。使用单片机或者
PLC,都能够实现加减速控制。对于不同负载、不同转速,需要选择合适的基础
频率与跳变频率,才能够达到最佳控制效果。
通常,完成步进电机的加减速时间为 300ms 以上。如果使用过于短的加减
速时间,对绝大多数步进电机来说,就会难以实现步进电机的高速旋转。
表格 12-1 梯形加减速和 S 型曲线比较
直线(梯形)
义
“S”型曲线
加速/减速开始时速度比较缓
慢,然后逐渐加快。在加速/减速接
指按直线方式(从启动速度到
定
近结束时速度再次减慢下来,从而
目标速度的加减速),以一定的比
使移动较为稳定。S 字加减速的类
例进行加速/减速
型有 Sin 曲线、2 次曲线、循环曲
线、3 次曲线
曲
线
加
减 速
段 曲
线
STM32 技术开发手册
www.ing10bbs.com
曲
线 类
一次曲线
复杂曲线
型
加
不变
不同的运动点加速度值不同
速度
实
现 程
容易
难
度
运
启动、停止、高速运动段会产
启动、停止、高速运动段会产
动 效
生很大的冲击和振动及噪音
生很小的冲击和振动及噪音
果
应
精密的工件搬运,精密的工件
用 场
简单的定长送料
建工
合
12.2 梯形加减速算法原理分析
梯形加减速实现方法比较简单,所以广泛应用中工业控制中。接下来,我们
以参考《AVR446_Linear speed control of stepper motor.pdf》(附件 3)内容,介绍
实现梯形加减速控制方法,并在下一小节实现算法编程。
介绍梯形加减速算法之前需要大家可以先回顾之前步进电机章节(步进电机
第 8 章 ),对步进电机基本控制有个大概的了解。
1. 步进电机基础方程
如果要步进电机以恒定的速度旋转,我们就需要以固定的频率发送脉冲, 我
们通过控制器的定时器功能来实现脉冲的发送,如图 12-1 所示,𝑡0 为脉冲发送
的起始时刻,𝑡1 为发送第二个脉冲的时刻,𝑡2 为发送第三个脉冲的时刻。𝑡0 与𝑡1
之间的时间间隔(时间延时)为𝛿t= 𝑐0𝑡𝑡,其中𝑐0 为定时器在𝑡0 与𝑡1 这段时间的定
时器计数值,𝑡𝑡为定时器的计数周期。𝑡1 与𝑡2 之前的时间间隔为𝛿𝑡 = 𝑐1𝑡𝑡,其中𝑐1
为定时器在𝑡1 与𝑡2 这段时间的计数值,𝑡𝑡为定时器的计数周期。比如说我们在程
序中配置定时器预分频器为 35,那定时器时钟频率为𝑓𝑡=72MHz/(35+1)=2MHz,
1
那么周期𝑡𝑡值就是2𝑀,c 就是以𝑡𝑡为基本单位的一个完整步进脉冲的定时器计数
值。在第 8 章 中我们是让配置定时器通道为翻转输出,比如当定时器计数到 num
值时发生翻转,翻转 2 次得到一个完整脉冲,即总计数值为 2*num。
STM32 技术开发手册
www.ing10bbs.com
图 12-1 步进电机控制脉冲
我们可以假定产生脉冲的定时器的计数频率为𝑓𝑡,那么𝑡𝑡 =
1
𝑓𝑡
,可以推出以
下公式(中括号里边为单位,下同):
公式 12-1 脉冲时间间隔
𝛿𝑡 = 𝑐𝑡𝑡 =
𝑐
[𝑠]
𝑓𝑡
另外,我们可以得到步进电机步距角α、位置θ和速度ω计算方法:
公式 12-2 步距角𝛂、位置𝛉和速度𝛚计算方法
𝛼=
2𝜋
[𝑟𝑎𝑑]
𝑠𝑝𝑟
𝜃 = 𝑛𝛼[𝑟𝑎𝑑]
𝜔=
=
𝛼
[𝑟𝑎𝑑/𝑠𝑒𝑐]
𝛿𝑡
𝛼𝑓𝑡
[𝑟𝑎𝑑/𝑠𝑒𝑐]
𝑐
其中:spr:steps per round,步进电机旋转一圈脉冲数,为与电机相关常数
n:脉冲数
rad:弧度单位
1𝑟𝑎𝑑/𝑠𝑒𝑐 = 60/2𝜋 ≈ 9.55𝑟𝑝𝑚
rpm:转每分钟(rounds per minutes),常用转速单位
STM32 技术开发手册
www.ing10bbs.com
2. 直线加减速
为让步进电机尽量不出现丢步和过冲情况,在电机启动和停止过程使用加减
速是非常有必要的。在加减速阶段,加速度(𝜔̇ )、速度(ω)和位置(θ)对应关系如图
12-2 所示:
图 12-2 加速度、速度和位置对应关系
𝛿t 是两个脉冲之前的时间间隔,所以它的大小将决定着步进电机转速,为让
电机转速符合加减速曲线,一个非常重要的步骤是计算合适时间间隔𝛿t,在加减
速阶段,𝛿t 可以认为是线性变化的,而在平稳速度阶段𝛿t 也是平稳不变的。使用
定时器的计数频率来离散步进控制步进电机运动和处理时间间隔,见图 12-3 和
图 12-4。
STM32 技术开发手册
www.ing10bbs.com
图 12-3 速度曲线与步进电机脉冲/速度
图 12-4 加减速曲线分析
3. 精确计算步进时间间隔
某个时刻的速度可以加速度来求得:
公式 12-3 加速度求出速度
STM32 技术开发手册
www.ing10bbs.com
𝑡
𝜔(𝑡) = ∫ 𝜔̇ 𝑑𝜏 = 𝜔̇ 𝑡
𝜏=0
对应的电机旋转角度(即位置)也是可以求得:
公式 12-4 旋转角度(位置)计算
𝑡
𝜃 (𝑡) = ∫ 𝜔(𝑡)𝑑𝜏 =
𝜏=0
1 2
𝜔̇ 𝑡 = 𝑛𝛼
2
第 n 个步进脉冲后产生的轴偏移角度θ= 𝑛𝛼,所以前 n 个脉冲的总时间和第
n 个步进脉冲的脉冲周期时间:
公式 12-5 第 n 个脉冲相关时间计算
𝑡𝑛 = √
2𝑛𝛼
𝜔̇
𝑐𝑛 𝑡𝑡 = 𝑡𝑛+1 − 𝑡𝑛 = √
2𝛼
(√𝑛 + 1 − √𝑛)
𝜔̇
最终,可以求得第 n 个脉冲实际需求的定时器计数值:
公式 12-6 第 n 个脉冲实际需求的定时器计数值
𝑐𝑛 =
1 2𝛼
√ (√𝑛 + 1 − √𝑛)
𝑡𝑡 𝜔̇
那么,第 1 个脉冲的定时器计数值为 :
公式 12-7 第 1 和 n 个脉冲的定时器计数值
𝑐0 =
1 2𝛼
√
𝑡𝑡 𝜔̇
所以:𝑐𝑛 = 𝑐0 (√𝑛 + 1 − √𝑛)
STM32 技术开发手册
www.ing10bbs.com
由于一般控制器的计算能力是有限的,连续两次计算开方根会很费时,因此
我们必须考虑使用多项式来展开来减少运算。根据泰勒公式的一个特例:麦克劳
林公式:
公式 12-8 麦克劳林公式
√1 ±
1
1
1
1
=1±
− 2 + 𝑂 ( 3)
𝑛
2𝑛 8𝑛
𝑛
根据上式,可以对𝑐𝑛 进行一些转换出来,以期用简单的计算方法得出𝑐𝑛 :
公式 12-9 𝒄𝒏 转换计算
𝑐𝑛
𝑐𝑛−1
=
=
𝑐0 (√𝑛 + 1 − √𝑛)
𝑐0 (√𝑛 − √𝑛 − 1)
1
𝑐0 √𝑛 (√1 + − 1)
𝑛
1
𝑐0 √𝑛 (1 − √1 − )
𝑛
1
1
1
− 2 + 𝑂 ( 3) − 1
2𝑛 8𝑛
𝑛
=
1
1
1
1 − (1 −
− 2 + 𝑂 ( 3 ))
2𝑛 8𝑛
𝑛
1+
1
1
1
− 2 + 𝑂 ( 3)
2𝑛 8𝑛
𝑛
=
1
1
1
+ 2 − 𝑂 ( 3)
2𝑛 8𝑛
𝑛
≈
4𝑛 − 1
4𝑛 + 1
最后,可以得到𝑐𝑛 :
公式 12-10 第 n 个脉冲实际需求的定时器计数值
𝑐𝑛 = 𝑐𝑛−1 (
(4𝑛 + 1) − 2
4𝑛 − 1
2𝑐𝑛−1
) = 𝑐𝑛−1
= 𝑐𝑛−1 −
4𝑛 + 1
4𝑛 + 1
4𝑛 + 1
STM32 技术开发手册
www.ing10bbs.com
这个公式比连续开两次方的计算方式快很多,但是代入原式时发现当 n=1 时
有 0.4485 的偏差,我们可以将𝑐0 乘一个系数 0.676 来解决这个误差。
4. 加速度的变化
根据上面公式 12-3、公式 12-4 可以知道,加速度𝜔̇ 与𝑐0 和 n 相关。如果需
要改变加速度或者减速度,那么就要重新计算一个 n 值。时间𝑡𝑛 和脉冲数 n 作为
加速度𝜔̇ 、速度ω和步距角α的参数,反过来我们可以得到:
公式 12-11 时间𝒕𝒏 和脉冲数 n
𝑡𝑛 =
𝜔𝑛
𝜔̇
𝜔̇ 𝑡𝑛2
𝑛=
2𝛼
联合上面两个式子,可以得到:
公式 12-12 达到最大速度需要的步数与加速度成反比
𝜔𝑛2
𝑛𝜔̇ =
2𝛼
这个式子表示当达到给定的最大速度时需要的步数与加速度成反比,由于电
机加速到最大时跟电机开始减速时的速度是一样的,我们可以得到:
公式 12-13 加速到最大速度等于开始减速速度
𝑛1 𝜔̇ 1 = 𝑛2 𝜔̇ 2
这样我们只需要改变 n 的值就可以改变加速度的值,见图 12-5。
STM32 技术开发手册
www.ing10bbs.com
图 12-5 加减速斜线
步进电机旋转给定的步数,必须在适当的步数时开始减速,使其结束的时候
速度为 0。根据可以得到𝑛1:
公式 12-14 加速段脉冲数
𝑛1 =
(𝑛2 + 𝑛1 )𝜔̇ 2
𝜔̇ 1 + 𝜔̇ 2
5. 算法实现
由以上的数学模型,控制步进电机运动,在给定步数的情况下,速度从零才
是加速,到达既定最大速度后开始匀速运动,运动到一定步数后开始减速,最后
停下来到达给定的步数,速度曲线类似一个梯形的变化的过程,这样可以让电机
启动或者停止更加平滑避免抖动的出现。
加减速运动模型
为实现加减速控制,需要 4 个参数来描述,见图 12-6。
STM32 技术开发手册
www.ing10bbs.com
图 12-6 加减速运行曲线
step:步数,定义电机旋转步数;
accel:加速度;
decal:减速度;
speed:最大速度,即匀速阶段速度。
启动计算
使用浮点数运算会大大限制了代码执行速度和效率,所以把一些参数放大一
定倍数进行计算是非常有必要的,并且保证了计算的精度。代码中把速度 speed、
加速度 accel 以及减速度 decel 做了放大 100 倍处理。另外,将使用到的一些常
量预先定义好,简化运算过程。
速度相关:
由公式 12-2 可以得到:
公式 12-15 速度相关参数
𝑐=
𝛼𝑓𝑡
𝜔
𝐴_𝑇_𝑥100 = 𝛼𝑓𝑡 ∙ 100
上式中𝑓𝑡 控制器定时器频率,可认为是常数,𝛼为步距角也是常数,这里定
义一个常量 A_T_x100,它是𝑓𝑡 和𝛼乘积的 100 倍。根据上图加减速曲线的最大速
STM32 技术开发手册
www.ing10bbs.com
度为 speed,当𝜔=speed 时,此时对应为最小的定时器时间间隔 min_delay,即对
应最小的定时器计数值 c:
公式 12-16 最小的定时器时间间隔
𝑚𝑖𝑛_𝑑𝑒𝑙𝑎𝑦 = 𝑐 =
𝐴_𝑇_𝑥100
𝑠𝑝𝑒𝑒𝑑
由于 A_T_x100 是做了放大 100 倍处理,所以可以认为 speed 也是放大 100
倍处理,即 speed 可以认为它的实际单位为 0.01rad/sec。
加速度相关:
由于数据推算过程的误差,这里𝑐0 会乘 0.676 修正这个误差。结合公式 12-7
可以得到:
公式 12-17 加速度相关参数
𝑇1_𝐹𝑅𝐸𝑄_148 =
0.676𝑓𝑡
100
𝐴_𝑆𝑄 = 2𝛼 ∙ 10000000000
𝑠𝑡𝑒𝑝_𝑑𝑒𝑙𝑎𝑦 = 𝑐0 = 𝑇1_𝐹𝑅𝐸𝑄_148√
𝐴_𝑆𝑄
⁄100
𝑎𝑐𝑐𝑒𝑙
为什么 A_SQ 是放大 10 的 10 次方倍呢?之前有说 accel、decel 以及 speed
都是做了放大 100 倍处理,所以 A_SQ 需要放大 10 的 2 次方倍;𝑇1_𝐹𝑅𝐸𝑄_148
是缩小了 100 倍,那放进计算 step_delay 的公式中的开根号里边就需要放大 10
的 4 次方倍,同样道理,该计算公式后面还缩小了 100 倍,这样在根号里边还需
要再放大 10 的 4 次方倍,这样数数就是 10 的 10 次方倍了。
加减速分析
在加速的过程中,有两种场景计算速度属性:
1) 持续加速直到达到所需的速度
2) 未达到所需的速度就要开始减速
这场景取决于描述速度属性四个变量,首先介绍第一种场景,见图 12-7。
STM32 技术开发手册
www.ing10bbs.com
图 12-7 加速阶段受限于最大速度 speed
首先,我们需要明确知道图中的几个值:
已知参数:step、accel、decel 和 speed,这几个值都是我们人为设定的(程
序里边直接赋值)。
待求参数:max_s_lim、accel_lim、decel_val。
max_s_lim 是加速到最大速度所需步数,根据公式 12-12:
公式 12-18 加速到最大速度所需步数
𝑠𝑝𝑒𝑒𝑑2
𝑚𝑎𝑥 _𝑠_𝑙𝑖𝑚 = 𝑛 =
2𝛼 ∙ 𝑎𝑐𝑐𝑒𝑙 ∙ 100
这里分母乘以 100 是为了保持放大倍数平衡。
accel_lim 是减速开始之前的步数(忽略最大速度限制),根据公式 12-14:
公式 12-19 减速开始之前的步数
𝑎𝑐𝑐𝑒𝑙_𝑙𝑖𝑚 = 𝑛1 =
𝑠𝑡𝑒𝑝 ∙ 𝑑𝑒𝑐𝑒𝑙
𝑎𝑐𝑐𝑒𝑙 + 𝑑𝑒𝑐𝑒𝑙
如果 max_s_lim < accel_lim,加速度是受限于最大的期望速度 speed。由公式
12-13 可以推出减速距离 decel_val,其中负号是因为加速和减速度的方向是相反
的。
STM32 技术开发手册
www.ing10bbs.com
公式 12-20 减速距离
𝑑𝑒𝑐𝑒𝑙_𝑣𝑎𝑙 = −𝑚𝑎𝑥_𝑠_𝑙𝑖𝑚 ∙
𝑎𝑐𝑐𝑒𝑙
𝑑𝑒𝑐𝑒𝑙
第二种场景是运行的步数不足以进行加速到最大速度就要开始减速,见图
12-8。
图 12-8 加速阶段未到达最大速度就开始减速
如果 max_s_lim > accel_lim,加速度是受限于减速的开始。减速距离 decel_val
应该为:
公式 12-21 减速距离
𝑑𝑒𝑐𝑒𝑙_𝑣𝑎𝑙 = −(step − accel_lim)
定时器中断处理
定时器中断产生步脉冲并且只有在步进电机移动时进入。这个中断处理速度
属性的四个不同的状态,分别为 stop—accel—run—decel—stop。如图 12-9 所示。
STM32 技术开发手册
www.ing10bbs.com
图 12-9 加减速运行的四个速度状态
速度的这种行为通过状态机在定时器中断中实现。如图 12-10 所示。STOP
为停止状态,ACCEL 为加速状态,RUN 为匀速状态(对应最大速度 speed),DECEL
为减速状态。
图 12-10 定时器中断中的状态机
当应用程序启动或者步进电机停止状态机处于 STOP 状态。当输入移动步数
建立计算完成,一个新状态被置位同时定时器的中断被使能。当运行的步数超过
STM32 技术开发手册
www.ing10bbs.com
1 步状态机进入 ACCEL 状态,如果只移动 1 步,那么状态直接变为 DECEL,因为
只移动一步是不需要加速的。当状态变为 ACCEL,应用程序一直加速
步进电机达到期望的最大速度,这时状态变为 RUN,
或者减速必须开始,状态变为 DECEL。
当状态为 RUN 时候,步进电机会持续保持最大速度 speed 旋转,直到必须
开始减速然后把状态改变为 DECEL。
它会一直保持 DECEL 状态并一直减速到期望的步数并且速度为 0。然后状态
变为 STOP。
定时器的计数值
在加速和减速过程中的每一步新的时间间隔必须计算得出。这个计算结果会
包括一个系数和一个余数,为了提高精度余数保留并包含在下一次计算当中。 由
公式 12-10 可以得出公式 12-22,其中 rest 为余数,首次计算值为 0。new_rest
就是保存除不尽的余数参与下一次计算。
公式 12-22 每一步新的时间间隔和余数
𝑛𝑒𝑤_𝑠𝑡𝑒𝑝_𝑑𝑒𝑙𝑎𝑦 = 𝑠𝑡𝑒𝑝_𝑑𝑒𝑙𝑎𝑦 −
2 ∙ 𝑠𝑡𝑒𝑝_𝑑𝑒𝑙𝑎𝑦 + 𝑟𝑒𝑠𝑡
4 ∙ 𝑎𝑐𝑐𝑒𝑙_𝑐𝑜𝑢𝑛𝑡 + 1
𝑛𝑒𝑤_𝑟𝑒𝑠𝑡 = (2 ∙ 𝑠𝑡𝑒𝑝_𝑑𝑒𝑙𝑎𝑦 + 𝑟𝑒𝑠𝑡)(𝑚𝑜𝑑(4 ∙ 𝑎𝑐𝑐𝑒𝑙_𝑐𝑜𝑢𝑛𝑡 + 1))
当状态改变时为了保持位移的轨迹,一些辅助计数变量是必不可少的。见图
12-11。
STM32 技术开发手册
www.ing10bbs.com
图 12-11 在延时中的计数变量
step_count 为计算步数,在 ACCEL 状态从零开始,在 DECEL 状态完成时结束,
记录的步数应该与命令控制的步数相同。
accel_count 用于控制加速或者减速。在 ACCEL 状态时,它从零开始每一步都
会增加直到 ACCEL 状态结束。在 DECEL 状态时,它设置为 decel_val,并且为负
数,每一步都会增加直到它的值为 0,运动结束,状态设置为 STOP。
decel_start 指示减速开始。当 step_count 与 decel_start 相等时,状态设置为
DECEL。
12.3 梯形加减速算法实现
上面介绍的加减速算法都是参考《AVR446_Linear speed control of stepper
motor.pdf》
(附件 3)内容实现,该文档根据它当时的时间电机驱动电路,代码中
把速度 speed、加速度 accel 以及减速度 decel 做了放大 100 倍处理。而现在我们
是使用我们自己的驱动电路和电机,这在程序控制上有点区别,特别的,最基本
的控制芯片都不同,所以,我们下面要介绍的代码是把速度 speed、加速度 accel
以及减速度 decel 做了放大 10 倍处理,所以注意分析程序。
加减速算法实现步进电机控制比在 9.1 小节中直接驱动步进电机所需要的
硬件并没有不同,即只需要:开发板+步进电机驱动器+步进电机。下面我们开始
STM32 技术开发手册
www.ing10bbs.com
讲解基于梯形加减速算法的步进电机控制代码,该实现代码是在 9.1 小节代码基
础上移植的,所以部分已经在 9.1 小节介绍过的定时器内容在这里就不再介绍
了。
以下代码参考我们的例程:YSF1_HAL_MOTOR-008. 57&42 步进电机梯形加减
速实现。
bsp_StepMotor.h 文件内容
该文件存放定时器相关引脚和配置宏定义,以及加减速算法相关宏定义。
代码 12-1 定时器相关宏定义
01 #define STEPMOTOR_TIMx
TIM1
02 #define STEPMOTOR_TIM_RCC_CLK_ENABLE()
__HAL_RCC_TIM1_CLK_ENABLE()
03 #define STEPMOTOR_TIM_RCC_CLK_DISABLE()
__HAL_RCC_TIM1_CLK_DISABLE()
04 #define STEPMOTOR_TIM_CHANNELn
TIM_CHANNEL_1
05 #define STEPMOTOR_TIM_IT_CCx
TIM_IT_CC1
06 #define STEPMOTOR_TIM_FLAG_CCx
TIM_FLAG_CC1
07 #define STEPMOTOR_TIMx_IRQn
TIM1_CC_IRQn
08 #define STEPMOTOR_TIMx_IRQHandler
TIM1_CC_IRQHandler
09 // 输出控制脉冲给电机控制器
10 #define STEPMOTOR_PULSE_GPIO_CLK_ENABLE()
__HAL_RCC_GPIOA_CLK_ENABLE()
11 #define STEPMOTOR_TIM_PULSE_PORT
GPIOA
// 对应 STEPMOTOR 的 PUL12
//(控制器使用共阳接法)
13 #define STEPMOTOR_TIM_PULSE_PIN
GPIO_PIN_8 // 而 PLU+直接接开发板的 5V(或者 3.3V)
14
15 //电机旋转方向控制,如果悬空不接默认正转
16 #define STEPMOTOR_DIR_GPIO_CLK_ENABLE()
__HAL_RCC_GPIOB_CLK_ENABLE()
17 #define STEPMOTOR_DIR_PORT
GPIOB
// 对应 STEPMOTOR 的 DIR18
//(控制器使用共阳接法)
19 #define STEPMOTOR_DIR_PIN
GPIO_PIN_13 // 而 DIR+直接接开发板的 5V(或者 3.3V)
20 #define STEPMOTOR_SETDIR_CW()
STEPMOTOR_DIR_PORT->BSRR =
STEPMOTOR_DIR_PIN
21 #define STEPMOTOR_SETDIR_CCW()
STEPMOTOR_DIR_PORT->BSRR =
(uint32_t)STEPMOTOR_DIR_PIN << 16
22
23 //电机使能控制,如果悬空不接默认使能电机
24 #define STEPMOTOR_ENA_GPIO_CLK_ENABLE()
__HAL_RCC_GPIOB_CLK_ENABLE()
25 #define STEPMOTOR_ENA_PORT
GPIOB // 对应 STEPMOTOR 的 ENA26
// (控制器使用共阳接法)
27 #define STEPMOTOR_ENA_PIN
GPIO_PIN_14 // 而 ENA+直接接开发板的 5V(或者 3.3V)
28 #define STEPMOTOR_OUTPUT_ENABLE()
STEPMOTOR_ENA_PORT->BSRR =
STEPMOTOR_ENA_PIN
29 #define STEPMOTOR_OUTPUT_DISABLE()
STEPMOTOR_ENA_PORT->BSRR =
(uint32_t)STEPMOTOR_ENA_PIN << 16
30
31 // 定义定时器预分频,定时器实际时钟频率为:72MHz/(STEPMOTOR_TIMx_PRESCALER+1)
32 #define STEPMOTOR_TIM_PRESCALER
35 // 实际时钟频率为:2MHz
33
34 // 定义定时器周期,输出比较模式周期设置为 0xFFFF
35 #define STEPMOTOR_TIM_PERIOD
0xFFFF
STM32 技术开发手册
www.ing10bbs.com
这里定义做为电机控制功能定时器通道,定义了电机方向和使能控制引脚。
定时器预分频定义为 35,实际的定时器频率为 2MHz,这个参数是在加减速算法
中会使用到的。
代码 12-2 加减速算法相关定义
01 typedef struct {
02
__IO uint8_t run_state ; // 电机旋转状态
03
__IO uint8_t dir ;
// 电机旋转方向
04
__IO int32_t step_delay; // 下个脉冲周期(时间间隔),启动时为加速度
05
__IO uint32_t decel_start; // 启动减速位置
06
__IO int32_t decel_val;
// 减速阶段步数
07
__IO int32_t min_delay;
// 最小脉冲周期(最大速度,即匀速段速度)
08
__IO int32_t accel_count; // 加减速阶段计数值
09 } speedRampData;
10
11 #define FALSE
0
12 #define TRUE
1
13 #define CW
0 // 顺时针
14 #define CCW
1 // 逆时针
15
16 #define STOP
0 // 加减速曲线状态:停止
17 #define ACCEL
1 // 加减速曲线状态:加速阶段
18 #define DECEL
2 // 加减速曲线状态:减速阶段
19 #define RUN
3 // 加减速曲线状态:匀速阶段
20 #define T1_FREQ
(SystemCoreClock/(STEPMOTOR_TIM_PRESCALER+1)) // 频率 ft 值
21 #define FSPR
200
//步进电机单圈步数
22 #define MICRO_STEP
32
// 步进电机驱动器细分数
23 #define SPR
(FSPR*MICRO_STEP)
// 旋转一圈需要的脉冲数
24
25 // 数学常数
26 #define ALPHA
((float)(2*3.14159/SPR))
// α= 2*pi/spr
27 #define A_T_x10
((float)(10*ALPHA*T1_FREQ))
28 #define T1_FREQ_148
((float)((T1_FREQ*0.676)/10)) // 0.676 为误差修正值
29 #define A_SQ
((float)(2*100000*ALPHA))
30 #define A_x200
((float)(200*ALPHA))
speedRampData 结构体包括了加减速算法计算过程的关键变量。
T1_FREQ 就是定时器频率,这里为 2MHz,注意这里不是周期。我们选用的
电机的步距角为 1.8,那步进电机单圈步数为 200;另外设置驱动器的细分数为
32,那要电机旋转一圈的脉冲数就是 6400。下面几个常数是在加减速算法中用
到的,具体运算过程上一小节中已经做了介绍。
bsp_StepMotor.c 文件内容
该文件存放了定时器基本配置和加减速算法实现代码。
STEPMOTOR_GPIO_Init 函数是定时器通道引脚和电机方向、使能引脚初始化
配置,这里就不再介绍。
STM32 技术开发手册
www.ing10bbs.com
代码 12-3 STEPMOTOR_TIMx_Init 函数
01 void STEPMOTOR_TIMx_Init(void)
02 {
03
TIM_ClockConfigTypeDef sClockSourceConfig;
// 定时器时钟
04
TIM_OC_InitTypeDef sConfigOC;
// 定时器通道比较输出
05
06
STEPMOTOR_TIM_RCC_CLK_ENABLE();
07
08
/* STEPMOTOR 相关 GPIO 初始化配置 */
09
STEPMOTOR_GPIO_Init();
10
11
/* 定时器基本环境配置 */
12
htimx_STEPMOTOR.Instance = STEPMOTOR_TIMx;
// 定时器编号
13
htimx_STEPMOTOR.Init.Prescaler = STEPMOTOR_TIM_PRESCALER;
// 定时器预分频器
14
htimx_STEPMOTOR.Init.CounterMode = TIM_COUNTERMODE_UP;
// 计数方向:向上计数
15
htimx_STEPMOTOR.Init.Period = STEPMOTOR_TIM_PERIOD;
// 定时器周期
16
htimx_STEPMOTOR.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
// 时钟分频
17
HAL_TIM_Base_Init(&htimx_STEPMOTOR);
18
19
/* 定时器时钟源配置 */
20
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
// 使用内部时钟源
21
HAL_TIM_ConfigClockSource(&htimx_STEPMOTOR, &sClockSourceConfig);
22
23
/* 定时器比较输出配置 */
24
sConfigOC.OCMode = TIM_OCMODE_TOGGLE;
// 翻转模式
25
sConfigOC.Pulse = 0xFFFF;
// 脉冲数
26
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
// 输出极性:高电平
27
HAL_TIM_OC_ConfigChannel(&htimx_STEPMOTOR, &sConfigOC, STEPMOTOR_TIM_CHANNELn);
28
/* 使能比较输出通道 */
29
TIM_CCxChannelCmd(STEPMOTOR_TIMx, STEPMOTOR_TIM_CHANNELn, TIM_CCx_DISABLE);
30
31
/* 配置定时器中断优先级并使能 */
32
HAL_NVIC_SetPriority(STEPMOTOR_TIMx_IRQn, 0, 0);
33
HAL_NVIC_EnableIRQ(STEPMOTOR_TIMx_IRQn);
34
35
__HAL_TIM_CLEAR_FLAG(&htimx_STEPMOTOR, STEPMOTOR_TIM_FLAG_CCx);
36
/* 使能定时器比较输出 */
37
__HAL_TIM_ENABLE_IT(&htimx_STEPMOTOR, STEPMOTOR_TIM_IT_CCx);
38
/* Enable the main output */
39
__HAL_TIM_MOE_ENABLE(&htimx_STEPMOTOR);
40
HAL_TIM_Base_Start(&htimx_STEPMOTOR);// 使能定时器
41 }
该函数用于步进电机驱动器定时器初始化。前部分是常规的定时器配置。这
里把给电机驱动器输出脉冲的定时器通道设置为比较输出模式:翻转模式,即在
定时器计数与通道比较寄存器相等时翻转通道引脚,特别的,翻转 2 次才是输出
一个完整的步进电机控制脉冲。接下来配置使能通道输出比较中断,在发生比较
中断后,我们在其中断服务函数是实现加减速计算,并改变下个定时器比较值。
代码 12-4 STEPMOTOR_AxisMoveRel 函数
01 void STEPMOTOR_AxisMoveRel(__IO int32_t step, __IO uint32_t accel, __IO uint32_t decel, __IO uint32_t
speed)
02 {
STM32 技术开发手册
www.ing10bbs.com
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
__IO uint16_t tim_count;
// 达到最大速度时的步数
__IO uint32_t max_s_lim;
// 必须要开始减速的步数(如果加速没有达到最大速度)
__IO uint32_t accel_lim;
if (step < 0) { // 步数为负数
srd.dir = CCW; // 逆时针方向旋转
STEPMOTOR_SETDIR_CCW();
step =-step;
// 获取步数绝对值
} else {
srd.dir = CW; // 顺时针方向旋转
STEPMOTOR_SETDIR_CW();
}
if (step == 1) { // 步数为 1
srd.accel_count = -1;
// 只移动一步
srd.run_state = DECEL; // 减速状态.
srd.step_delay = 1000; // 短延时
} else if (step != 0) { // 如果目标运动步数不为 0
// 我们的驱动器用户手册有详细的计算及推导过程
// 设置最大速度极限, 计算得到 min_delay 用于定时器的计数器的值。
// min_delay = (alpha / tt)/ w
srd.min_delay = (int32_t)(A_T_x10/speed);
// 通过计算第一个(c0) 的步进延时来设定加速度,其中 accel 单位为 0.1rad/sec^2
// step_delay = 1/tt * sqrt(2*alpha/accel)
// step_delay = ( tfreq*0.676/10 )*10 * sqrt( (2*alpha*100000) / (accel*10) )/100
srd.step_delay = (int32_t)((T1_FREQ_148 * sqrt(A_SQ / accel))/10);
// 计算多少步之后达到最大速度的限制
// max_s_lim = speed^2 / (2*alpha*accel)
max_s_lim = (uint32_t)(speed*speed/(A_x200*accel/10));
// 如果达到最大速度小于 0.5 步,我们将四舍五入为 0
// 但实际我们必须移动至少一步才能达到想要的速度
if (max_s_lim == 0) {
max_s_lim = 1;
}
// 计算多少步之后我们必须开始减速
// n1 = (n1+n2)decel / (accel + decel)
accel_lim = (uint32_t)(step*decel/(accel+decel));
// 我们必须加速至少 1 步才能才能开始减速.
if (accel_lim == 0) {
accel_lim = 1;
}
// 使用限制条件我们可以计算出减速阶段步数
if (accel_lim <= max_s_lim) {
srd.decel_val = accel_lim - step;
} else {
srd.decel_val = -(max_s_lim*accel/decel);
}
// 当只剩下一步我们必须减速
if (srd.decel_val == 0) {
srd.decel_val = -1;
}
// 计算开始减速时的步数
srd.decel_start = step + srd.decel_val;
STM32 技术开发手册
www.ing10bbs.com
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 }
// 如果最大速度很慢,我们就不需要进行加速运动
if (srd.step_delay <= srd.min_delay) {
srd.step_delay = srd.min_delay;
srd.run_state = RUN;
} else {
srd.run_state = ACCEL;
}
// 复位加速度计数值
srd.accel_count = 0;
}
MotionStatus = 1; // 电机为运动状态
tim_count=__HAL_TIM_GET_COUNTER(&htimx_STEPMOTOR);
// 设置定时器比较值
__HAL_TIM_SET_COMPARE(&htimx_STEPMOTOR,
STEPMOTOR_TIM_CHANNELn,tim_count+srd.step_delay);
// 使能定时器通道
TIM_CCxChannelCmd(STEPMOTOR_TIMx, STEPMOTOR_TIM_CHANNELn, TIM_CCx_ENABLE);
STEPMOTOR_OUTPUT_ENABLE();
该函数用于启动电机旋转一定步数,以给定的步数移动步进电机,先加速到
最大速度,然后在合适位置开始减速至停止,使得整个运动距离为指定的步数。
如果加减速阶段很短并且速度很慢,那还没达到最大速度就要开始减速。有四个
形参,step:移动的步数(正数为顺时针,负数为逆时针),accel 加速度,实际值为
accel*0.1*rad/sec^2,decel 减速度,实际值为 decel*0.1*rad/sec^2,speed 最大速
度,实际值为 speed*0.1*rad/sec。
这里需要明确 STEPMOTOR_AxisMoveRel 函数里边代码的作用,两个重要作
用:1.计算加减速算法用到的基本参数值,即 speedRampData 结构体成员值;2.
启动定时器通道输出比较。
首先,根据 step 的正负值设置旋转方向,如果是负值求出 step 的绝对值。
接下来判断 step 的值,
一般应用中 step 都不会为 0 和 1 的。然后,计算 min_delay,
它对应目标速度(加减速曲线最大速度)的定时器通道比较值;计算 step_delay,
这里是第一步的定时器通道比较值,该变量值是保持下个脉冲的比较值,实时改
变的;计算 max_s_lim,加速到最大速度所需步数;计算 accel_lim,减速开始之
前的步数(忽略最大速度限制)。比较 max_s_lim 和 accel_lim 的值大小,决定计
算得到 decel_val 值,减速距离,decel_val 是个负值,方便后面计算。接下来计
算 decel_start,开始减速的步数。一般情况下,step_delay 会比 min_delay 大,如
果 step_delay 比 min_delay 比还小,直接进入匀速运行阶段。
函数最后部分就是使能定时器通道比较输出。
STM32 技术开发手册
www.ing10bbs.com
代码 12-5 STEPMOTOR_TIMx_IRQHandler 函数
01 void STEPMOTOR_TIMx_IRQHandler(void)//定时器中断处理
02 {
03
__IO uint16_t tim_count=0;
04
// 保存新(下)一个延时周期
05
uint16_t new_step_delay=0;
06
// 加速过程中最后一次延时(脉冲周期).
07
__IO static uint16_t last_accel_delay=0;
08
// 总移动步数计数器
09
__IO static uint32_t step_count = 0;
10
// 记录 new_step_delay 中的余数,提高下一步计算的精度
11
__IO static int32_t rest = 0;
12
//定时器使用翻转模式,需要进入两次中断才输出一个完整脉冲
13
__IO static uint8_t i=0;
14
15
if (__HAL_TIM_GET_IT_SOURCE(&htimx_STEPMOTOR, STEPMOTOR_TIM_IT_CCx) !=RESET) {
16
// 清楚定时器中断
17
__HAL_TIM_CLEAR_IT(&htimx_STEPMOTOR, STEPMOTOR_TIM_IT_CCx);
18
19
// 设置比较值
20
tim_count=__HAL_TIM_GET_COUNTER(&htimx_STEPMOTOR);
21
__HAL_TIM_SET_COMPARE(&htimx_STEPMOTOR,
STEPMOTOR_TIM_CHANNELn,tim_count+srd.step_delay);
22
23
i++;
// 定时器中断次数计数值
24
if (i==2) { // 2 次,说明已经输出一个完整脉冲
25
i=0;
// 清零定时器中断次数计数值
26
switch (srd.run_state) { // 加减速曲线阶段
27
case STOP:
28
step_count = 0; // 清零步数计数器
29
rest = 0;
// 清零余值
30
// 关闭通道
31
TIM_CCxChannelCmd(STEPMOTOR_TIMx,
STEPMOTOR_TIM_CHANNELn, TIM_CCx_DISABLE);
32
__HAL_TIM_CLEAR_FLAG(&htimx_STEPMOTOR, STEPMOTOR_TIM_FLAG_CCx);
33
STEPMOTOR_OUTPUT_DISABLE();
34
MotionStatus = 0; // 电机为停止状态
35
break;
36
37
case ACCEL:
38
step_count++;
// 步数加 1
39
if (srd.dir==CW) {
40
step_position++; // 绝对位置加 1
41
} else {
42
step_position--; // 绝对位置减 1
43
}
44
srd.accel_count++; // 加速计数值加 1
45
new_step_delay = srd.step_delay - (((2 *srd.step_delay) + rest)/
46
(4 * srd.accel_count + 1));//计算新(下)一步脉冲周期(时间间隔)
47
rest = ((2 * srd.step_delay)+rest)%(4 * srd.accel_count + 1);
48
// 计算余数,下次计算补上余数,减少误差
49
if (step_count >= srd.decel_start) { // 检查是够应该开始减速
50
srd.accel_count = srd.decel_val; // 加速计数值为减速阶段计数值的初始值
51
52
srd.run_state = DECEL;
// 下个脉冲进入减速阶段
53
} else if (new_step_delay <= srd.min_delay) { // 检查是否到达期望的最大速度
54
last_accel_delay = new_step_delay; // 保存加速过程中最后一次延时(脉冲周期)
55
new_step_delay = srd.min_delay;
// 使用 min_delay(对应最大速度 speed)
56
rest = 0;
// 清零余值
STM32 技术开发手册
www.ing10bbs.com
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99 }
srd.run_state = RUN;
}
break;
// 设置为匀速运行状态
case RUN:
step_count++; // 步数加 1
if (srd.dir==CW) {
step_position++; // 绝对位置加 1
} else {
step_position--; // 绝对位置减 1
}
new_step_delay = srd.min_delay;
// 使用 min_delay(对应最大速度 speed)
if (step_count >= srd.decel_start) { // 需要开始减速
srd.accel_count = srd.decel_val; // 减速步数做为加速计数值
new_step_delay = last_accel_delay;
// 加阶段最后的延时做为减速阶段的起始延时(脉冲周期)
srd.run_state = DECEL;
// 状态改变为减速
}
break;
case DECEL:
step_count++; // 步数加 1
if (srd.dir==CW) {
step_position++; // 绝对位置加 1
} else {
step_position--; // 绝对位置减 1
}
srd.accel_count++;
new_step_delay = srd.step_delay - (((2 * srd.step_delay) + rest)/
(4 * srd.accel_count + 1));//计算新(下)一步脉冲周期(时间间隔)
rest = ((2 * srd.step_delay)+rest)%(4 * srd.accel_count + 1);//
计算余数,下次计算补上余数,减少误差
//检查是否为最后一步
if (srd.accel_count >= 0) {
srd.run_state = STOP;
}
break;
}
srd.step_delay = new_step_delay; // 为下个(新的)延时(脉冲周期)赋值
}
}
该函数是定时器中断服务函数,我们在配置定时器时候,开启了通道比较输
出中断,当发生比较中断时就好运行该函数。该函数也有两个重点作用:1.设置
定时器比较值;2.计算下个定时器比较值。
该函数先调用__HAL_TIM_GET_IT_SOURCE 确保是发生通道比较中断,然后清
除该中断标志。然后调用__HAL_TIM_GET_COUNTER 函数获取当前定时器计数值,
保存在 tim_count 中,然后调用__HAL_TIM_SET_COMPARE 函数设置通道的比较
值。
STM32 技术开发手册
www.ing10bbs.com
变量 i 计数值为 2 时说明已经输出一个完整的脉冲,接下计算下个定时器比
较值。
加减速分成 4 个阶段:STOP、ACCEL、RUN 和 DECEL,run_state 的值在
STEPMOTOR_AxisMoveRel 函数中已经给赋值了。
STOP:把相关参数清零,关闭定时器通道输出,电机停止旋转。
ACCEL:把步数计数变量 step_count 加 1,步进电机绝对位置 step_position
根据旋转方向改变,加速计数变量 accel_count 加 1,accel_count 是个正数,计
数下个(新的)比较值 new_step_delay,为保证精度计算中用到余数 rest,接下来
计算余数 rest,用于下次计算 new_step_delay;判断是否需要开始减速,如果需
要开始减速把减速距离 decel_val(负数)赋值给 accel_count,此时它为负数,
run_state 改为 DECEL,下个脉冲开始减速;如果不需要开始减速,判断是否已经
加速到目标速度(对应最大速度 speed),如果已经达到目标速度,保存加速过程
中最后一次比较值到变量 last_accel_delay 中,把下个脉冲的比较值设定为最大
速度,清零余数,下个脉冲进入匀速运行状态。
RUN:把步数计数变量 step_count 加 1,步进电机绝对位置 step_position 根
据旋转方向改变,把下个脉冲的比较值设定为最大速度,判断是否需要开始减速,
如果需要开始减速,把减速步数赋值给加减速计数值,此时 accel_count 为一个
负数,把在加速过程中最后一次比较值赋值给 new_step_delay,用于减速阶段的
起始脉冲比较值,最后让下个脉冲进入减速阶段。
DECEL:把步数计数变量 step_count 加 1,步进电机绝对位置 step_position
根据旋转方向改变,加速计数变量 accel_count 加 1,accel_count 是个负数,计
数下个(新的)比较值 new_step_delay,为保证精度计算中用到余数 rest,接下来
计算余数 rest,用于下次计算 new_step_delay;判断 accel_count 值,如果不是负
数说明已经完成了旋转任务,把状态改为 STOP。
函数最后把 new_step_delay 值赋值给 step_delay,用于下次进入定时器中断
服务函数给定时器通道比较寄存器赋值。
main.c 文件内容
该文档内容非常简单。
STM32 技术开发手册
www.ing10bbs.com
代码 12-6 main 函数
01 int main(void)
02 {
03
/* 复位所有外设,初始化 Flash 接口和系统滴答定时器 */
04
HAL_Init();
05
/* 配置系统时钟 */
06
SystemClock_Config();
07
08
KEY_GPIO_Init();
09
10
STEPMOTOR_TIMx_Init();
11
12
/* 无限循环 */
13
while (1) {
14
if (KEY1_StateRead()==KEY_DOWN) {
15
STEPMOTOR_AxisMoveRel(6400*50, step_accel, step_decel, set_speed);
16
}
17
}
18 }
该函数先初始化硬件环境,在无限循环中扫描 KEY1 状态,然后 KEY1 倍按
下,调用 STEPMOTOR_AxisMoveRel 函数启动电机旋转,这里为步数赋值为
6400*50,实际旋转 50 圈。
实验操作与现象
根据 bsp_StepMotor.h 文件中引脚定义方法连接开发板和步进电机驱动器,
另外驱动器和步进电机的连接自行根据电机和驱动器标识连接,驱动器需要一个
24V 5A 的直流电源供电。使用开发板配套的 MINI USB 线连接到开发板标示“调
试串口”字样的 MIMI USB 接口为开发板提供电源。下载完程序之后,按下开发
板上 KEY1 按键,步进电机实现加减速旋转,运行 50 圈后停止。
STM32 技术开发手册
www.ing10bbs.com
第13章 SPWM 调制原理
13.1 PWM、SPWM 基本概念
PWM 为 Pulse Width Modulation(脉冲宽度调制)缩写,晶体管(常用 MOS、
IGBT 等全控型器件)工作在开关状态,晶体管被触发导通时,电源电压加到电动
机上;晶体管关断时,直流电源与电动机断开;这样通过改变晶体管的导通时间
就可以调节电机电压,从而进行调速。与之相关联最重要的名称就是占空比,占
空比指的是晶体管导通时间长度与单位时间的比值,例如,单位时间是 1s,在这
1
3
秒内有4的时间是导通的,输出电压是峰值电压,另外有4的时间是关闭的,输出
电压是 0V,那么占空比就是 25%,输出的平均电压就是峰值电压的 25%。
图 13-1 占空比定义
SPWM 为 Sinusoidal Pulse Width Modulation(正弦脉宽调制)缩写,通过对一
系列宽窄不等的脉冲进行调制,来等效正弦波形。正弦波的相关参数是幅值和频
率,如果是多相输出,那还需要控制相位差,这些都是通过调制 PWM 的脉宽来
实现。
STM32 技术开发手册
www.ing10bbs.com
图 13-2 方波等效正弦波
13.2 SPWM—正弦脉宽调制理论基础
SPWM,就是在 PWM 的基础上改变了调制脉冲方式,脉冲宽度时间占空比
按正弦规率排列,这样输出波形经过适当的滤波可以做到正弦波输出。
根据采样控制理论中的一个重要结论:冲量相等而形状不同的窄脉冲加在具
有惯性的环节上时,其效果基本相同。冲量即指窄脉冲的面积。这里所说的效果
基本相同,是指环节的输出响应波形基本相同。即当它们分别加在具有惯性的同
一个环节上时,其输出响应基本相同。如果把各输出波形用傅立叶变换分析,则
其低频段非常接近,仅在高频段略有差异。上述原理可以称之为面积等效原理,
见图 14-4,它是 PWM 控制技术的重要理论基础。
STM32 技术开发手册
www.ing10bbs.com
图 13-3 面积等效原理
SPWM 就是以该结论为理论基础,用脉冲宽度按正弦规律变化而和正弦波等
效的 PWM 波形即 SPWM 波形控制逆变电路中开关器件的通断,使其输出的脉冲
电压的面积与所希望输出的正弦波在相应区间内的面积相等,通过改变调制波的
频率和幅值则可调节逆变电路输出电压的频率和幅值。
图 13-4 SPWM 基本原理
STM32 技术开发手册
www.ing10bbs.com
13.3 SPWM 逆变及其控制方法
13.3.1 SPWM 调制波形的极性
如果使用全桥驱动电路,可以生成单相的单极性 SPWM 波形,或者双极性
SPWM 波形,如图 14-6 所示。所谓的单极性或者双极性调制波形,都是针对负
载而言的,对于负载来说,在正弦波的正半轴或者负半轴部分只有单一方向的电
压信号,那就是单极性调制,如果在正半轴或者负半轴同时具有两个方向的电压
信号,那就是双极性调制。
当使用单极性调制波形的时候,正半轴部分可以使𝑉4 导通,然后𝑉1 使用
SPWM 信号控制。当负半轴的时候可以使𝑉2,𝑉3使用 SPWM 信号控制。在使用双
极性控制的时候,就可以使用两组互补 PWM 通道,同时控制 4 个开关器件。𝑉1
与𝑉2使用一组互补 PWM 信号控制,𝑉3与𝑉4使用一组互补 PWM 控制,同时𝑉1与
𝑉4极性相同,也就是𝑉1与𝑉3的 PWM 信号本身也是互补的。
图 13-5 SPWM 调制电路
STM32 技术开发手册
www.ing10bbs.com
图 13-6 正弦波调制方法
13.3.2 异步调制和同步调制
SPWM 的基本概念是将每一正弦周期内的多个脉冲做自然或规则的宽度调
制,使其依次调制出相当于正弦函数值的相位角和面积等效于正弦波的的脉冲序
列,形成等幅不等宽的正弦化信号输出。其中每个周期内的载波所含正弦调制波
输出的脉冲总数之比即为载波比。实际就是载波频率与调制波的频率之比。
公式 13-1 载波比
𝑓𝑡
𝑁=
𝑓𝑠
𝑁 − 载波比;
𝑓𝑡 − 载波频率;
𝑓𝑠 − 调制波频率;
一般情况下,N 远远大于 1。根据载波和调制波是否同步及载波比的变化情
况,PWM 调制方式分为异步调制和同步调制。
STM32 技术开发手册
www.ing10bbs.com
1. 异步调制
载波信号与调制信号不同步的调制方式,通常保持𝑓𝑡 固定不变,当𝑓𝑠 变化时,
载波比𝑁是变化的。在调制信号的半周期内,PWM 的脉冲个数是不固定的,相位
也不固定,正负半轴的脉冲数不对称,半周期内前后 1/4 周期的脉冲也不对称。
当𝑓𝑠 较低时,𝑁较大,一周期内脉冲数较多,脉冲不对称产生的不利影响都较少。
当𝑓𝑠 增高时,𝑁较小,一周期内的脉冲数较少,脉冲不对称的影响就变大。
2. 同步调制
载波信号与调制信号同步的调制方式,当变频时使载波与调制波保持同步,
载波比𝑁等于常数。当𝑓𝑠 变化时,𝑁不变,调制波周期内输出的脉冲数是固定的。
为使单相输出端额 SPWM 正负半轴镜像对称,𝑁应取奇数。当𝑓𝑠 很低时,𝑓𝑡 也很
低,有调制带来的谐波不易滤除,当𝑓𝑠 很高时,𝑓𝑡 会过高,使开关器件难以承受。
3. 分段同步调制
就是异步调制和同步调制的综合应用,把𝑓𝑠 的频率范围内划分为若干个频段,
每个频段内保持载波比𝑁恒定,不同频段的载波比𝑁不同。在𝑓𝑠 较高的频段使用
较低的载波比𝑁使载波频率不至于过高,在𝑓𝑠 较低的频段采用较高的载波比,是
载波频率不至于过低。
可在低频输出时采用异步调制方式,高频输出时切换到同步调制方式,这样
把两者的有点结合起来,和分段同步方式效果接近。
4. 调制比
输出波形电压幅值与载波电压幅值之比就是调制比:
公式 13-2 调制比
𝑉𝑠
𝑅=
𝑉𝑡
R − 调制比;
𝑉𝑠 − 调制波幅值;
STM32 技术开发手册
www.ing10bbs.com
𝑉𝑡 − 载波幅值;
一般情况下0 < 𝑅 < 1,如果𝑅 > 1,那就是过调制。
13.4 SPWM 实现原理
算法的实现方法有很多种,但是常用的就是对称规则采样或者是不对称规则
采样。因为这两种方式可以使用微机编程实现,控制电路简单,计算量少,相较
其他方法更加灵活。
13.4.1 计算法
计算法就是给出正弦波的输出频率、幅值和半个周期内的脉冲数,PWM 波
形中各脉冲的宽度和时间间隔可以准确的计算出来,按照计算结果控制电路中各
个开关器件的通断,就可以得到需要的 SPWM 波形。这种方法称为计算法,可以
看出,计算法是很繁琐的,当需要输出的正弦波的频率、幅值或者相位变化时,
计算结果都要变化。
13.4.2 调制法
调制法是把正弦波作为调制信号,把接受调制的信号作为载波,通过信号波
调制得到所期望的 PWM 波形。通常采用三角波或锯齿波作为载波,其中三角波
用的最多,因为三角波上任一点的水平宽度和高度成线性关系且左右对称,当它
与任何一个平缓变化的调制信号相交时,如果在交点时刻对开关器件的通断进
行控制,就可以得到宽度正比于信号波幅值的脉冲,这正好符合 PWM 控制的要
求。实际应用中可以使用模拟电路构成三角载波和正弦调制波发生电路,用比较
器来确定他们的交点,在交点时刻对开关器件进行控制通断,从而生成 SPWM 波
形。虽然可以使用模拟电路实现,但是目前应用更多的是通过软件来生成 SPWM
波形。
STM32 技术开发手册
www.ing10bbs.com
1. 自然采样法
在正弦波和三角波的自然交点时刻控制功率开关器件的通断,这种生成
SPWM 波形的方法称为自然采样法。自然采样法是最基本的方法,所得到的
SPWM 波形很接近正弦波,但这种方法要求解复杂的超越方程,需要花费大量的
时间,难以在实时控制中在线计算。
图 13-7 自然采样法
2. 规则采样法
规则采样法是一种应用较广的工程使用方法,其效果接近自然采样法,但计
算量却比自然采样法小的多。规则采样法分为对称规则采样法和不对称规则采样
法。
1) 对称规则采样法
对称规则采样就是在每个载波的对称轴(顶点对称轴或者底点对称轴)所对
应的时刻作为采样时刻,过三角波的对称轴与正弦波的交点,做平行 x 轴的直线,
该平行线与三角波的两个交点作为开关器件的通断时刻。因为这两个交点是对称
的,所以称为规则采样法。这种方法实际使用一个阶梯波逼近正弦波,由于在每
个载波周期当中只采样一次,因此计算得到简化。
STM32 技术开发手册
www.ing10bbs.com
图 13-8 对称规则采样
在底点对称轴对正弦波采样,采样值作为载波交点控制开关器件的通断。
三角波的幅值就是𝑈𝑇 ,正弦波的幅值就是𝑈𝑆 ,将正弦波向上平移 1 个单位,
使其在时间轴上方,根据相似三角形原理,可得如下关系式:
公式 13-3 对称规则采样计算
1 + 𝑎 𝑠𝑖𝑛 𝜔𝑡𝐷
2
=
𝛿 ⁄2
𝑇𝐶 ⁄2
𝑈
𝑎是调制波与载波的幅值之比(𝑈𝑆 )。𝜔为正弦波频率,𝑡𝐷 就是载波的采样时
𝑇
刻,𝛿 ⁄2为脉宽的一半,𝑇𝐶 是一个载波周期。如果载波频率与调制波的频率比值
为 N,那么有:
公式 13-4 计算导通时长
𝛿 = 𝑇𝐶 (1 + 𝑎 sin 𝜔𝑡𝐷 )⁄2
𝜔𝑡𝐷 = 2𝑘𝜋/𝑁
K 为一个正弦波周期内的采样计数值。得到的𝛿就是开关时长。
2) 不对称规则采样
STM32 技术开发手册
www.ing10bbs.com
由于对称规则采样时每个载波周期只采样一次,因此形成的阶梯波与正弦波
的逼近程序仍存在较大误差。因此可以每个载波周期采样两次,在三角波的顶点
对称轴或者底点对称轴都采样一次,这样所形成的阶梯波与正弦波的逼近程度会
大大提高。由于这种采样所形成的阶梯波与三角波的交点并不对称,因此称为不
对称规则采样。同样可以利用相似三角形原理得到计算公式:
公式 13-5 不对称规则采样计算公式
𝑡𝑜𝑛
𝑇𝐶
𝑘𝜋
( 1 + 𝑎 sin ) ,
𝑁
= {4
𝑇𝐶
𝑘𝜋
( 1 + 𝑎 sin ) ,
4
𝑁
(𝑘 = 0,2,4 … ,2𝑁 − 2)
(𝑘 = 1,3,5 … ,2𝑁 − 1)
𝑘为偶数时代表顶点采样,为奇数时代表底点采样。其实可以明显看得出来,
计算公式是一样的。
图 13-9 不对称规则采样
STM32 技术开发手册
www.ing10bbs.com
13.4.3 跟踪控制
这种方法是将实际的电压或者电流波形作为反馈信号,通过比较输出的实际
信号与指令信号之间的差值,决定开关器件的通断,使实际输出跟踪指令信号变
化。常用的方法有滞环比较法和三角波比较法。
1) 滞环比较法
跟踪型 PWM 变流电流中,电流跟踪控制应用最多,滞环环宽对跟踪性能有
较大的影响,环宽过宽时,开关动作频率低,但跟踪误差增大;环宽过窄时,跟
踪误差减小,但开关的动作频率过高,甚至超过开关器件的允许频率范围,开关
损耗随之增大。
图 13-10 滞环比较方式电流跟踪控制电路
基本原理:把指令电流𝑖 ∗ 和实际输出电流𝑖的偏差𝑖 ∗ − 𝑖作为滞环比较器的输
入,𝑉1导通时,𝑖增大,𝑉2导通时,𝑖减少。通过环宽为2𝐷𝐼的滞环比较器的控制,
𝑖就在𝑖 ∗ + 𝐷𝐼和𝑖 ∗ − 𝐷𝐼的范围内,呈锯齿状的跟踪指令电流𝑖 ∗ 。
STM32 技术开发手册
www.ing10bbs.com
图 13-11 滞环比较方式的指令电流和输出电流
采用滞环比较方式的电流跟踪型 PWM 交流电路有如下特点:
1. 硬件电路简单。
2. 属于实时控制方式,电流响应快。
3. 不需要载波,输出电压波形中不含有特定的谐波分量。
4. 属于闭环控制,这是各种跟踪型 PWM 变流电路的共同特点
5. 和计算法及调制法相比,相同开关频率时输出电流中的高次谐波含量较
多。
13.5 使用 stm32 实现规则采样算法
stm32 的定时器可以配置为中心对齐模式,计数器既可以向上计数也可以向
下计数,当计数器向上计数到所设定的最大值的时候,就会向下计数,或者反过
来也行,图 13-12 就是计数器从 0 计数到 ARR 的示意图,一张图就可以理解这
个中心对齐模式:
STM32 技术开发手册
www.ing10bbs.com
图 13-12 定时器的中心对齐模式
定时器的 PWM 输出模式有两种,一种是 PWM1,另外一种是 PWM2。PWM1
是在 CNT 计数过程中,向上计数时,当𝑪𝑵𝑻 < 𝑪𝑪𝑹的时候就是高电平,𝑪𝑵𝑻 ≥
𝑪𝑪𝑹的时候就是低电平。向下计数时,当𝑪𝑵𝑻 > 𝑪𝑪𝑹的时候就是低电平,𝑪𝑵𝑻 ≤
𝑪𝑪𝑹的时候就是高电平;PWM2 则反过来,当𝐶𝑁𝑇 < 𝐶𝐶𝑅的时候就是低电平,
𝐶𝑁𝑇 ≥ 𝐶𝐶𝑅的时候就高电平。向下计数时,当𝐶𝑁𝑇 > 𝐶𝐶𝑅的时候就是高电平,
𝐶𝑁𝑇 ≤ 𝐶𝐶𝑅的时候就是低电平。图 13-13 就是在中心对齐模式下的 PWM1 输出
模式。
图 13-13 在中心对齐模式的 PWM1
在使用规则采样法调制 SPWM 的时候,需要在三角波的峰值对正弦波采样,
可以看到当定时器计数器工作在中心对齐模式,并且使用 PWM1 模式输出时,
恰好满足 SPWM 的调制需求,并且定时器可以根据需求设定在计数器上溢事件
触发更新中断或者是在计数器下溢事件触发更新中断,CCR 值决定了脉冲宽度,
STM32 技术开发手册
www.ing10bbs.com
那么就可以利用定时器的这一特性,直接将定时器的计数器作为载波,在计数溢
出更新中断里面计算脉宽,然后设置比较值。
13.5.1 对称规则采样算法实现
当以定时器的计数器为载波时,正弦波的幅值不超过计数器的自动重装载值,
在不考虑初始相位角的情况下,以单位量 1 表示载波的幅值,则正弦波的幅值就
是调制比𝑎,将正弦波向上平移一个单位,得到正弦波的曲线表达式:
公式 13-6 正弦曲线表达式
𝑦 = 𝑎𝑠𝑖𝑛(𝜔𝑡) + 1
此时正弦波和载波都同时位于时间轴的上方,并且正弦波的幅值不超过载波
的幅值,修改调制比就是修改正弦波的幅值。
图 13-14 对称规则采样调制
根据公式 13-3 和公式 13-4 可以知道在载波采样时刻前后的高电平时间是
STM32 技术开发手册
www.ing10bbs.com
公式 13-7 调制 PWM 脉宽
𝛿 = 𝑇𝐶 (1 + 𝑎 sin 𝜔𝑡)⁄2
𝑇𝐶 是 PWM 的周期,也是两倍的 ARR 值,𝛿是高电平的时长,也是两倍的 CCR
值。在换算一下,那就是:
公式 13-8 定时器比较值计算
𝐶𝐶𝑅 =
𝐴𝑅𝑅
2𝑘𝜋
(1 + 𝑎 sin
) , 𝑘 = 0,1,2 … , 𝑁
2
𝑁
假设载波频率是 20Khz,正弦波的频率是 50Hz,那么载波比就是 400,也就
是一个正弦周期内有 400 个脉冲信号,每一个脉冲,对应的弧度就是2𝑘𝜋/400。
对称规则采样是在载波的底点采样,每个 PWM 周期只采样一次,400 次采样之
后就输出一个完整的正弦脉冲。
stm32 的定时器,可以设置为在底点触发中断,然后就可以计算比较值,每
个采样周期𝜔𝑡 = 2𝑘𝜋/400。通过修改载波比𝑁和调制比 a 可以修改正弦波的频率
和幅值,但是值得注意的是计算出来的结果是下一个脉冲的比较值。
实际应用的时候,如图 13-15 在采样点 A 计算出来的比较值,发挥作用的地
方有 B 点和 C 点,但是实际能使用的只有 C 点,B 点是无法使用。为了解决这个
问题,可以利用 stm32 定时器的预装载功能,在 A 点计算出下一个周期的比较值
D/E 存在预装载寄存器当中,在 C、D 点中间的上溢更新事件中触发将预装载寄
存器的值装载到比较器上,这个时候 D 跟 E 都可以控制 PWM 的波形输出。
STM32 技术开发手册
www.ing10bbs.com
图 13-15 采样点与比较值生效时刻
13.5.2 不对称规则采样算法实现
从公式 13-5 可以知道每次采样计算的周期值是:
公式 13-9 不对称规则采样的脉宽
𝛿=
𝑇𝐶
( 1 + 𝑎 sin 𝜔𝑡)
4
式中,𝛿就是这一次采样计算结果,也是比较值 CCR,𝑇𝐶 是 PWM 的周期,也
是两倍的 ARR。计算结果换算一下就是:
公式 13-10 定时器比较值计算
𝐶𝐶𝑅 =
𝐴𝑅𝑅
𝑘𝜋
(1 + 𝑎 sin )𝑘 = 0,1,2 … ,2𝑁
2
𝑁
同样假设载波频率是 20KHz,正弦波频率是 50Hz,载波比是𝑁 = 400,一个
正弦周期内有 400 个脉冲,但是每个脉冲周期有两次采样计算,总共有 800 次采
样,所以有𝜔𝑡 = 𝑘𝜋/𝑁, 𝑘 < 2𝑁。
STM32 技术开发手册
www.ing10bbs.com
图 13-16 不对称规则采样调制
13.5.3 同步调制算法实现
使用规则采样的方法实现 SPWM 输出是属于异步调制,调制波形频率改变
的时候,载波频率是不变的,当调制频率越高,载波比就越低,正弦波的平滑度
就越差。
同步调制就是采用相同的载波比,当调制频率改变的时候同时改变载波频率,
以保证单个正弦周期内的脉冲数一致。调制频率越高,载波频率也越高,对功率
器件的开关频率要求也越高。
同步调制的载波比是固定的,一个正弦周期内的脉冲数是固定的,那么就可
以将一个标准的正弦函数曲线,取一个周期 N 个采样值保存在一个数组当中,在
应用的时候对这个数据的数据点进行变换,得到比较值。这个就是利用查表法调
制 SPWM。
最简单的正弦曲线函数表达式是𝑦 = 𝑠𝑖𝑛(𝜔𝑡),现在将其正半轴部分采样 256
个数据点,数据范围是 0~1 之间的浮点数,在单片机当中浮点数占用 4 个字节,
并不适合用于表格存储,所以将其数值范围换算成 0~255。255 代表正弦波的幅
STM32 技术开发手册
www.ing10bbs.com
值 1,0 代表正弦波的 0。正半轴是 256 个数据点,整个周期就是 512 个数据点,
载波比就是 512。
图 13-17 正弦波函数
图 13-18 正半轴部分
数组里面有正半轴的正弦数据,但是在实际需求如图 13-19,正弦波还需要
向上平移一个单位,在假设调制比为𝑎,定时器的计数值最大值就是𝐴𝑅𝑅,一半
就是ℎ𝐴𝑅𝑅,数组的数据点是𝑠𝑖𝑛[𝑥],经过换算之后实际的数据值是𝐶𝐶𝑅,那么有:
公式 13-11 查表法计算
sin[𝑥]
∗ ℎ𝐴𝑅𝑅)
255
sin[𝑥]
𝐶𝐶𝑅 = 𝑎(ℎ𝐴𝑅𝑅 −
∗ ℎ𝐴𝑅𝑅)
255
𝐶𝐶𝑅 = 𝑎(ℎ𝐴𝑅𝑅 +
负半轴部分就使用减法,正半轴部分就使用加法,在输出 256 个脉冲之后, 𝑥
从 0 重新开始计数。计算过程与定时器的频率无关,只跟定时器的自动重装载值
相关。𝑎取值范围是 0~1,是一个浮点数据,可以使用除法代替,同时由于分母可
控,又可以使用右移代替,所以完整的计算过程如公式 13-12:
STM32 技术开发手册
www.ing10bbs.com
公式 13-12 完整的查表计算过程
𝐶𝐶𝑅 = 𝑎 (ℎ𝐴𝑅𝑅 + 𝑑𝑖𝑟
𝑠𝑖𝑛[𝑥]
∗ ℎ𝐴𝑅𝑅) ≫ 10
256
这个时候的调制比𝑎,取值范围就是 0~1024,𝑑𝑖𝑟是有符号数,用于控制符号
变换,负半轴的时候就是负数,正半轴的时候就是正数。整个计算过程只有加法,
乘法,右移,计算速度会有极大的提升。
图 13-19 调制波形
同步调制除了查表法之外同样可以利用规则采样的方法实现,只是在改变调
制波频率的时候不是改变载波比而是改变载波频率就行。
13.6 对称规则采样代码实现
根据以上算法原理,实现单相 SPWM 输出信号,这里以《SPWM 调制_对称
规则采样》例程为例,介绍具体的实现方法。该例程功能简单,只输出一路 SPWM
信号,仅用于使用示波器观察输出波形。
bsp_AdvancedTIM.h 文件内容
代码 13-1 定时器相关宏定义
00 #define ADVANCED_TIMx
01 #define ADVANCED_TIMx_GPIO_AF
TIM8
GPIO_AF3_TIM8
STM32 技术开发手册
www.ing10bbs.com
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#define ADVANCED_TIM_RCC_CLK_ENABLE()
#define ADVANCED_TIM_RCC_CLK_DISABLE()
#define ADVANCED_TIM_IRQn
#define ADVANCED_TIM_OC_IRQHANDLER
/* PWM 输出通道 */
#define ADVANCED_TIM_CH1_CLK_ENABLE()
)
#define ADVANCED_TIM_CH1_PORT
#define ADVANCED_TIM_CH1_PIN
#define ADVANCED_TIM_CH1N_CLK_ENABLE()
)
#define ADVANCED_TIM_CH1N_PORT
#define ADVANCED_TIM_CH1N_PIN
/* shutdown 控制引脚 */
#define SHUTDOWN_GPIO_RCC_CLK_ENABLE()
)
#define SHUTDOWN_PORT
#define SHUTDOWN_PIN
#endif
__HAL_RCC_TIM8_CLK_ENABLE()
__HAL_RCC_TIM8_CLK_DISABLE()
TIM8_UP_TIM13_IRQn
TIM8_UP_TIM13_IRQHandler
__HAL_RCC_GPIOI_CLK_ENABLE(
GPIOI
GPIO_PIN_5
__HAL_RCC_GPIOH_CLK_ENABLE(
GPIOH
GPIO_PIN_13
__HAL_RCC_GPIOH_CLK_ENABLE(
GPIOH
GPIO_PIN_9
//
定义定时器预分频,定时器实际时钟频率为:168MHz/(A
DVANCED_TIMx_PRESCALER+1)
#define ADVANCED_TIM_PRESCALER
0 //
实际时钟频率为:168MHz
//
定义定时器周期,当定时器开始计数到 ADVANCED_TIMx_PERIOD
值并且重复计数寄存器为 0 时更新定时器并生成对应更新事件和更新中断
#define ADVANCED_TIM_PERIOD
(4200-1) //
定时器更新频率是 20KHz
// 定义高级定时器重复计数寄存器值,(n+1) 次更新事件才触发一次中断
#define ADVANCED_TIM_REPETITIONCOUNTER
1 //
YS-F4Pro 上有两个无刷电机接口,这里使用到无刷电机接口 2 的 PI5 和 PH13
引脚作为输出,为了能方便移植到无刷电机接口 1 上,例程使用了条件编译语句
区分使用的引脚接口宏定义,下面内容将不会介绍例程没有编译的语句。
ADVANCED_TIM_PERIOD:是定时器的自动重装载值,中心对齐模式下,ARR
就是半周期值。
ADVANCED_TIM_REPETITIONCOUNTER:这个宏定义是设置重复计数器的值
设置为 1,表示两次溢出更新事件之后才会触发一次更新中断,因为中心对齐模
式一个周期会有两次溢出事件,分别是向上计数溢出和向下计数溢出,而采样点
是在底点,所以需要在向下计数溢出的时候进入中断计算比较值,设置为 1 就刚
好是在底点触发中断。
STM32 技术开发手册
www.ing10bbs.com
bsp_AdvancedTIM.c 文件内容
代码 13-2 定时器引脚初始化
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 函数功能: 定时器硬件初始化配置
* 输入参数: htim:定时器句柄类型指针
* 返 回 值: 无
* 说
明: 该函数被 HAL 库函数内部函数调用
*/
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* htim)
{
GPIO_InitTypeDef GPIO_InitStruct;
if (htim->Instance==ADVANCED_TIMx) {
/* 定时器通道功能引脚端口时钟使能 */
ADVANCED_TIM_CH1_CLK_ENABLE();
ADVANCED_TIM_CH1N_CLK_ENABLE();
/* 定时器通道 1 功能引脚 IO 初始化 */
GPIO_InitStruct.Pin = ADVANCED_TIM_CH1_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = ADVANCED_TIMx_GPIO_AF;
HAL_GPIO_Init(ADVANCED_TIM_CH1_PORT, &GPIO_InitStruct);
/* 互补通道引脚初始化 */
ADVANCED_TIM_CH1N_CLK_ENABLE();
GPIO_InitStruct.Pin = ADVANCED_TIM_CH1N_PIN;
HAL_GPIO_Init(ADVANCED_TIM_CH1N_PORT, &GPIO_InitStruct);
HAL_NVIC_SetPriority(ADVANCED_TIM_IRQn,1,0);
HAL_NVIC_EnableIRQ(ADVANCED_TIM_IRQn);
SHUTDOWN_GPIO_RCC_CLK_ENABLE();
GPIO_InitStruct.Pin
= SHUTDOWN_PIN;
GPIO_InitStruct.Mode
= GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull
= GPIO_NOPULL;
GPIO_InitStruct.Speed
= GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = 0;
HAL_GPIO_Init(SHUTDOWN_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(SHUTDOWN_PORT, SHUTDOWN_PIN, GPIO_PIN_SET);
}
}
例程使用的而是 PI5 和 PH13,实际上只需要 PI5 引脚就行,PH13 是互补通
道,输出的脉冲信号是反相的。因为例程使用了无刷电机驱动板的 U 相作为输出
端口,所以才需要同时使用 PI5 和 PH13 及 SHUTDOWN 引脚,如果只是单纯的使
用示波器观察波形,可以忽略 PH13/SD 引脚。
无刷电机驱动板是由三组半桥驱动电路组成的三相输出驱动电路,当仅使用
一相时,需要同时控制一组半桥的两个 MOS 管,这样才能正常输出电压,同时
还需要注意 SD 引脚状态不能是低电平,否则就会关断驱动信号,无法控制 MOS
管。
STM32 技术开发手册
www.ing10bbs.com
代码 13-3 定时器初始化
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* 函数功能: 基本定时器初始化
* 输入参数: 无
* 返 回 值: 无
* 说
明: 无
*/
void ADVANCED_TIMx_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig;
TIM_MasterConfigTypeDef sMasterConfig;
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;
TIM_OC_InitTypeDef sConfigOC;
/* 定时器外设时钟使能 */
ADVANCED_TIM_RCC_CLK_ENABLE();
htimx.Instance
= ADVANCED_TIMx;
htimx.Init.Prescaler
= ADVANCED_TIM_PRESCALER;
htimx.Init.CounterMode
= TIM_COUNTERMODE_CENTERALIGNED3;
htimx.Init.Period
= ADVANCED_TIM_PERIOD;
htimx.Init.ClockDivision
= TIM_CLOCKDIVISION_DIV1;
htimx.Init.RepetitionCounter = ADVANCED_TIM_REPETITIONCOUNTER;
HAL_TIM_PWM_Init(&htimx);
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
HAL_TIM_ConfigClockSource(&htimx, &sClockSourceConfig);
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode
= TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htimx, &sMasterConfig);
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel
= TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime
= 0x28;
sBreakDeadTimeConfig.BreakState
= TIM_BREAK_ENABLE;
sBreakDeadTimeConfig.BreakPolarity
= TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE
;
HAL_TIMEx_ConfigBreakDeadTime(&htimx, &sBreakDeadTimeConfig);
= TIM_OCMODE_PWM1;
// PWM 模式
= (ADVANCED_TIM_PERIOD+1)>>1; //
初始脉宽
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_SET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_SET;
sConfigOC.OCMode
sConfigOC.Pulse
/* 使用 PWM 配置输出通道,使能预装载 CCR1 */
HAL_TIM_PWM_ConfigChannel(&htimx, &sConfigOC, TIM_CHANNEL_1);
}
定时器作为载波输出,计数模式选择为中心对齐模式,同时以为使用了无刷
电机驱动板,所以死区时间设置为 0x28,大概是 238ns。CH1 配置为 PWM1 输出
模式,初始的比较值就是周期值的一半。
STM32 技术开发手册
www.ing10bbs.com
注意这里配置通道 1 使用的是 HAL_TIM_PWM_ConfigChannel(),这个函数跟
HAL_TIM_OC_ConfigChannel()的区别在于 PWM 模式会使能比较器的预装载功能,
之后有触发更新事件的时候才会装载比较值的值,也就是只有计数器触发更新事
件时才会修改实际的比较器的值。
main.c 文件内容
代码 13-4 SPWM 调制相关宏定义
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
#define PI
#define PI_X2
(3.1415926f) // π
(2.0f*PI)
// 2π
// 调制波频率
#define MODULATED_FREQ_Hz
50 // 调制波形(正弦波)频率 50Hz
// 载波(三角波)频率
#define CARRIER_FREQ_Hz
(float)(1.68e8/(ADVANCED_TIM_PRESCALER+1)
)/(2*(ADVANCED_TIM_PERIOD+1))//
定时器脉冲频率
// 载波比 R = 载波频率/调制波频率
#define RATIO
(float)(
CARRIER_FREQ_Hz/MODULATED_FREQ_Hz)
// 调制比 输出波形电压幅值与直流电压幅值之比 0<= r
<1
#define MODULATION_RATIO
1.0f //
// 定时器半周期值
#define TIM_HALF_PERIOD
(float)((ADVANCED_TIM_PERIOD+1)>>1)
// 系数 2π/N
#define PI_X2DIVN
(PI_X2/RATIO)
MODULATED_FREQ_Hz:调制波的频率。
CARRIER_FREQ_Hz:载波频率,载波就是定时器的计数器,同时一个三角波
就是一个周期,所以载波频率也就是计数器从 0 计数到 ARR,在从 ARR 计数到 0
的频率。
RATIO:载波比,也就是载波频率与调制波频率的比值,载波比越高,单个
正弦波周期内的脉冲数就越多,输出的波形就是越接近正弦波,但载波比过高,
会频繁的启动功率器件,造成损耗,甚至可能会超过功率器件的极限频率。
MODULATION_RATIO:调制比,输出波形电压幅值和载波电压幅值之比,调
制比大于 0 但小于 1。如果大于 1,则输出的波形将会失真。
代码 13-5 main 函数
00 /**
01 * 函数功能: 主函数.
02 * 输入参数: 无
03 * 返 回 值: 无
04 * 说
明: 无
STM32 技术开发手册
www.ing10bbs.com
05 */
06 int main(void)
07 {
08
/*
09
复位所有外设,初始化 Flash 接口和系统滴答定时器
10
*/
11
HAL_Init();
12
13
/* 配置系统时钟 */
14
SystemClock_Config();
15
16
/* 初始化 LED 控制引脚 */
17
LED_GPIO_Init();
18
19
/* 高级定时器初始化: */
20
ADVANCED_TIMx_Init();
21
22
__HAL_DBGMCU_FREEZE_TIM8();// debug
23
时,遇到 break 会停止 TIM8 时钟源
24
25
StartOutput_SinPWM();
26
27
/* 无限循环 */
28
while (1) {
29
30
}
31 }
32
main 函数只是初始化外设和启动 SPWM 调制。
代码 13-6 启动 SPWM 调制输出
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 函数功能: 启动 SPWM 调制输出
* 输入参数: 无
* 返 回 值: 无
* 说
明:
sin 波形的起始值必然是 0,对应定时器周期的一半
*/
void StartOutput_SinPWM()
{
/* 设置初始的比较值 */
__HAL_TIM_SetCompare(&htimx, TIM_CHANNEL_1, TIM_HALF_PERIOD);
/* 手动触发更新事件,装载比较值和重复计数器
*/
HAL_TIM_GenerateEvent(&htimx, TIM_EVENTSOURCE_UPDATE);
__HAL_TIM_CLEAR_FLAG( &htimx, TIM_FLAG_UPDATE);
/* 使能定时器更新中断 */
//ADVANCED_TIM_REPETITIONCOUNTER 设置为 1,一个周期只有一次中断
__HAL_TIM_ENABLE_IT(&htimx, TIM_IT_UPDATE);
/* 启动 CH1 PWM 输出,第一个脉冲的 CCR1 = ARR/2 */
HAL_TIM_PWM_Start(&htimx, TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Start(&htimx, TIM_CHANNEL_1);
/* 更新下一步的比较值 */
HAL_TIM_PeriodElapsedCallback(&htimx);
}
STM32 技术开发手册
www.ing10bbs.com
启动 SPWM 调制输出,实际上是启动定时器,启动的第一个脉冲需要设置
为周期值的一半,因为正弦波的起始值就是 0,经过向上平移变换之后,也就是
周期值的一半。
在设置比较值之后,需要先触发更新事件,使比较值装载到比较器上,然后
才能启动通道 1 输出 PWM 信号。启动输出之后需要立即配置下一个周期的比较
值。
代码 13-7 定时器更新中断回调函数
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 函数功能: 定时器更新中断会回调函数
* 输入参数: 无
* 返 回 值: 无
* 说
明:
通过测量 LED2 引脚翻转的脉宽,得计算耗时大概是 920ns
(优化等级-O0 Keil)
*
ton = (Tc/2)*( 1+Msin(2*Pi*k/N) ) 0 <= k < N
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
// LED2_TOGGLE;
uint16_t CCR = 0;
static float i = 1; //不是 0,因为第一步初始值可以明确就是 TIM_HALF_PERIOD
CCR = TIM_HALF_PERIOD*( 1.0f + MODULATION_RATIO*sinf( PI_X2DIVN*i))
;
__HAL_TIM_SetCompare(htim, TIM_CHANNEL_1, CCR);
i++;
if (i >= RATIO)
i=0;
// LED2_TOGGLE;
}
在更新中断中计算正弦值,并且设置比较值,比较值是写入预装载寄存器的。
只有在更新事件时才生效。
每进入一次这个函数,i 就累加 1,正弦曲线也向前推进 PI_X2DIVN*i 弧度
(
2𝑘𝜋
𝑁
),计算出来的结果是下一个周期的比较值。当 i 大于载波比(RATIO)的时
候说明已经输出一个完整的正弦周期,然后就从 0 重新开始。
进入这个函数之后翻转 LED2 的引脚电平,在结束这个函数之前也翻转 LED2
的电平,通过测量 LED2 的脉宽就可以知道这个函数的处理耗时,在不优化的情
况下,大概耗时 900ns,例程的载波频率是 20KHz,一个周期是 50us,占用率不
到 2%。
STM32 技术开发手册
www.ing10bbs.com
实验操作与现象
例程主要使用了 PI5 引脚作为 SPWM 调制波形输出,输出的波形是一串连续
变化宽度的方波波形,如果需要观察正弦波形,需要在输出引脚上接上一阶 RC
低通滤波电路,然后才能使用示波器观察正弦曲线。一阶低通滤波电路选用 10k
电阻和 0.1uF 的电容,截止频率是 159Hz,如果正弦波的频率高于 159Hz,将会
出现信号衰减的现象,这个时候可以选取更小的电阻或者电容。
图 13-20 一阶 RC 低通滤波电路与示波器接线示意图
图 13-21 单相 SPWM 调制波形
STM32 技术开发手册
www.ing10bbs.com
图 13-21 就是实际测量的波形图片,CH2 是调制波形,经过一阶低通滤波之
后可以看到正弦波,CH1 是滤波之前的波形,可以看到 CH1 有部分空缺的地方,
那是因为调制比设置 1,占空比存在 100%和 0%的情况。
图 13-22 局部放大
13.7 不对称规则采样代码实现
这里以《SPWM 调制_不对称规则采样》为例,介绍具体的实现方法。
不对称规则采样与对称规则采样的代码实现方法非常接近。具体的底层配置
是几乎一样的,详细可以参考《SPWM 调制_对称规则采样》。
bsp_Advanced.c 文件内容
代码 13-8 定时器初始化
00 /**
01 * 函数功能: 基本定时器初始化
02 * 输入参数: 无
03 * 返 回 值: 无
04 * 说
明: 无
05 */
06 void ADVANCED_TIMx_Init(void)
07 {
08
TIM_ClockConfigTypeDef sClockSourceConfig;
09
TIM_MasterConfigTypeDef sMasterConfig;
10
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;
11
TIM_OC_InitTypeDef sConfigOC;
STM32 技术开发手册
www.ing10bbs.com
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 }
/* 定时器外设时钟使能 */
ADVANCED_TIM_RCC_CLK_ENABLE();
htimx.Instance
htimx.Init.Prescaler
htimx.Init.CounterMode
= ADVANCED_TIMx;
= ADVANCED_TIM_PRESCALER;
= TIM_COUNTERMODE_CENTERALIGNED3;
//TIM_COUNTERMODE_CENTERALIGNED3
htimx.Init.Period
= ADVANCED_TIM_PERIOD;
htimx.Init.ClockDivision
= TIM_CLOCKDIVISION_DIV1;
htimx.Init.RepetitionCounter = ADVANCED_TIM_REPETITIONCOUNTER;
HAL_TIM_OC_Init(&htimx);
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
HAL_TIM_ConfigClockSource(&htimx, &sClockSourceConfig);
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode
= TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htimx, &sMasterConfig);
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel
= TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime
= 0x28;
sBreakDeadTimeConfig.BreakState
= TIM_BREAK_ENABLE;
sBreakDeadTimeConfig.BreakPolarity
= TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE
;
HAL_TIMEx_ConfigBreakDeadTime(&htimx, &sBreakDeadTimeConfig);
sConfigOC.OCMode
= TIM_OCMODE_PWM1;
// PWM 模式
sConfigOC.Pulse
= 2100;
// 初始脉宽
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_SET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_SET;
/* 配置 CH1 为比较输出模式 */
HAL_TIM_OC_ConfigChannel(&htimx, &sConfigOC, TIM_CHANNEL_1);
ADVANCED_TIM_REPETITIONCOUNTER:重复计数器的数值。不对称规则采样
的定时器配置与对称规则采样几乎是一样的,都是中心对齐模式,但是重复计数
器就设置为 0,也就是说每个溢出事件都会触发中断,一个周期内会有两次中断
事件。
在配置 CH1 的时候使用 HAL_TIM_OC_ConfigChannel()函数将 CH1 配置为
PWM1 模式而不是使用 HAL_TIM_PWM_ConfigChannel()函数,区别在于 OC 是不
会使能预装载功能,不对称规则采样时需要配置预装载功能的,所以只能使用
HAL_TIM_OC_ConfigChannel()函数。
STM32 技术开发手册
www.ing10bbs.com
main.c 文件内容
代码 13-9 main 函数
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* 函数功能: 主函数.
* 输入参数: 无
* 返 回 值: 无
* 说
明: 无
*/
int main(void)
{
/*
复位所有外设,初始化 Flash 接口和系统滴答定时器
*/
HAL_Init();
/* 配置系统时钟 */
SystemClock_Config();
/* 初始化 LED 控制引脚 */
LED_GPIO_Init();
/* 高级定时器初始化: */
ADVANCED_TIMx_Init();
/* 启动 CH1 比较输出 */
HAL_TIM_PWM_Start(&htimx, TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Start(&htimx, TIM_CHANNEL_1);
//ADVANCED_TIM_REPETITIONCOUNTER 设置为 0,
一个周期会有两次更新中断
HAL_TIM_Base_Start_IT(&htimx);
__HAL_DBGMCU_FREEZE_TIM8();// debug
时,遇到 break 会停止 TIM8 时钟源
/* 无限循环 */
while (1) {
}
}
main()函数只是初始化配置各个外设,并且使能了 PWM 输出。
代码 13-10 定时器更新中断回调函数
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
/**
* 函数功能: 定时器更新中断会回调函数
* 输入参数: 无
* 返 回 值: 无
* 说
明:
通过测量 LED2 引脚翻转的脉宽,得计算耗时大概是 920ns
(优化等级-O0 Keil)
*
ton = (Tc/2)*( 1+Msin(Pi*k/N) ) 0 <= k < 2N
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
// LED2_TOGGLE;
uint16_t CCR = 0;
static float i = 0;
CCR = TIM_HALF_PERIOD*( 1.0f + MODULATION_RATIO*sinf( PI_DIVN*i) );
STM32 技术开发手册
www.ing10bbs.com
15
16
17
18
19 //
20
21 }
__HAL_TIM_SetCompare(htim, TIM_CHANNEL_1, CCR);
i++;
if (i >= 2*RATIO)
i=0;
LED2_TOGGLE;
不对称规则采样不需要预装载功能,只需要在进入中断之后直接计算比较值,
𝑘𝜋
每次进入这个函数,正弦曲线向前推进 PI_DIVN*i 弧度( 𝑁 ),当 i 计数到 2 倍的载
波比的时候,就是输出一个完整的正弦周期,总共 RATIO 个脉冲。
进入这个函数之后翻转 LED2 的引脚电平,在结束这个函数之前也翻转 LED2
的电平,通过测量 LED2 的脉宽就可以知道这个函数的处理耗时,在不优化的情
况下,大概耗时 900ns,例程的载波频率是 20KHz,一个周期是 50us,占用率不
到 2%。
实验操作与现象
例程主要使用了 PI5 引脚作为 SPWM 调制波形输出,输出的波形是一串连续
变化宽度的方波波形,如果需要观察正弦波形,需要在输出引脚上接上一阶 RC
低通滤波电路,然后才能使用示波器观察正弦曲线。一阶低通滤波电路选用 10k
电阻和 0.1uF 的电容,截止频率是 159Hz,如果正弦波的频率高于 159Hz,将会
出现信号衰减的现象,这个时候可以选取更小的电阻或者电容。
图 13-23 一阶 RC 低通滤波电路与示波器接线示意图
STM32 技术开发手册
www.ing10bbs.com
图 13-24 单相 SPWM 调制波形
图 13-24 就是实际测量的波形图片,CH2 是调制波形,经过一阶低通滤波之
后可以看到正弦波,CH1 是滤波之前的波形,可以看到 CH1 有部分空缺的地方,
那是因为调制比设置 1,占空比存在 100%和 0%的情况。
图 13-25 局部放大
降低调制比为 0.9 之后,由于调制比小了,输出的电压不会是满幅值输出,
所以可以避免出现 100%和 0%占空比的情况。
STM32 技术开发手册
www.ing10bbs.com
图 13-26 调制比为 0.9 的 50Hz 调制波形
13.8 查表法代码实现
这里以《SPWM 调制_查表》例程为例,介绍具体的实现方法。该例程功能
简单,只输出一路 SPWM 信号,仅用于使用示波器观察输出波形。底层的外设初
始化内容与不对称规则采样非常接近,详细可以参考《SPWM 调制_不对称规则
采样》例程。
main.c 文件内容
代码 13-11 全局宏定义
01
02
03
04
05
/* 私有宏定义 ---------------------------------------------------------------*/
#define PI
(3.1415926f) // π
#define SIN_AMPMAX
255 //
标准正弦曲线的幅值最大值
定义了一些常数,SIN_AMPMAX 是标准的正弦曲线数据的幅值。
代码 13-12 私有全局变量
00
01
02
03
04
05
06
/* 私有变量 -----------------------------------------------------------------*/
/* 不适合用于低频 */
uint8_t SinTable[256]= { // 0 ~ π
0x00, 0x03, 0x06, 0x09, 0x0C, 0x0F, 0x12, 0x15,
0x18, 0x1C, 0x1F, 0x22, 0x25, 0x28, 0x2B, 0x2E,
0x31, 0x34, 0x37, 0x3A, 0x3D, 0x40, 0x44, 0x47,
STM32 技术开发手册
www.ing10bbs.com
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
0x4A,
0x61,
0x78,
0x8D,
0xA1,
0xB4,
0xC5,
0xD4,
0xE0,
0xEB,
0xF4,
0xFA,
0xFD,
0xFF,
0xFD,
0xFA,
0xF4,
0xEB,
0xE0,
0xD4,
0xC5,
0xB4,
0xA1,
0x8D,
0x78,
0x61,
0x4A,
0x31,
0x18,
0x4D,
0x64,
0x7A,
0x90,
0xA4,
0xB6,
0xC7,
0xD5,
0xE2,
0xEC,
0xF4,
0xFA,
0xFE,
0xFE,
0xFD,
0xF9,
0xF3,
0xEA,
0xDF,
0xD2,
0xC3,
0xB2,
0x9F,
0x8B,
0x75,
0x5E,
0x47,
0x2E,
0x15,
0x4F,
0x67,
0x7D,
0x92,
0xA6,
0xB8,
0xC9,
0xD7,
0xE3,
0xED,
0xF5,
0xFB,
0xFE,
0xFE,
0xFD,
0xF8,
0xF2,
0xE9,
0xDD,
0xD0,
0xC1,
0xAF,
0x9C,
0x88,
0x72,
0x5B,
0x44,
0x2B,
0x12,
0x52,
0x6A,
0x80,
0x95,
0xA8,
0xBA,
0xCA,
0xD9,
0xE5,
0xEF,
0xF6,
0xFB,
0xFE,
0xFE,
0xFC,
0xF8,
0xF1,
0xE7,
0xDC,
0xCE,
0xBF,
0xAD,
0x9A,
0x85,
0x6F,
0x58,
0x40,
0x28,
0x0F,
0x55,
0x6D,
0x83,
0x97,
0xAB,
0xBC,
0xCC,
0xDA,
0xE6,
0xF0,
0xF7,
0xFC,
0xFE,
0xFE,
0xFC,
0xF7,
0xF0,
0xE6,
0xDA,
0xCC,
0xBC,
0xAB,
0x97,
0x83,
0x6D,
0x55,
0x3D,
0x25,
0x0C,
0x58,
0x6F,
0x85,
0x9A,
0xAD,
0xBF,
0xCE,
0xDC,
0xE7,
0xF1,
0xF8,
0xFC,
0xFE,
0xFE,
0xFB,
0xF6,
0xEF,
0xE5,
0xD9,
0xCA,
0xBA,
0xA8,
0x95,
0x80,
0x6A,
0x52,
0x3A,
0x22,
0x09,
0x5B,
0x72,
0x88,
0x9C,
0xAF,
0xC1,
0xD0,
0xDD,
0xE9,
0xF2,
0xF8,
0xFD,
0xFE,
0xFE,
0xFB,
0xF5,
0xED,
0xE3,
0xD7,
0xC9,
0xB8,
0xA6,
0x92,
0x7D,
0x67,
0x4F,
0x37,
0x1F,
0x06,
0x5E,
0x75,
0x8B,
0x9F,
0xB2,
0xC3,
0xD2,
0xDF,
0xEA,
0xF3,
0xF9,
0xFD,
0xFE,
0xFE,
0xFA,
0xF4,
0xEC,
0xE2,
0xD5,
0xC7,
0xB6,
0xA4,
0x90,
0x7A,
0x64,
0x4D,
0x34,
0x1C,
0x03,
};
int32_t SamplePoint = sizeof(SinTable)/sizeof(SinTable[0]); //
标准正弦波点数
int32_t SinAmp = 1024;
// 输出正弦波的幅值控制变量
int32_t HalfMax = 0x0834; // 输出正弦波的幅值控制变量,
必须等于定时器周期的一半
int16_t SinDir = 1;
// 正弦波正负半轴控制
SinTable 就是标准正弦曲线的正半轴数据表格。SamplePoint 就是正半轴的采
样数据数量。 SinAmp 用于控制输出的正弦波幅值,就是调制比,取值范围是
0~1024,默认值是 1024,调制比为 1。HalfMax 是 ARR 值的一半。SinDir 用于区
分正弦波的正负半轴。
代码 13-13 计算正弦表格
00
01 /**
02 * 函数功能: 计算正弦曲线表格
03 * 输入参数: 无
04 * 返 回 值: 无
05 * 说
明: 曲线范围是 0 ~ π,使用串口打印,
06 然后再复制过来.
07 */
08 void calcSinTable()
09 {
10
float radio = PI/256.0f;
11
uint8_t SinValue = 0;
12
float tmp;
13
printf("uint8_t SinTable[256]={// 0 ~ π \n");
14
STM32 技术开发手册
www.ing10bbs.com
15
16
17
18
19
20
21
22
23
24
25 }
for (uint16_t i=1; i<=256; i++ ) {
tmp = sinf( (i-1)*radio ); // 正半轴的曲线数据点
SinValue = (uint8_t)((SIN_AMPMAX * tmp)); // 数据整型化
SinTable[(i-1)] = SinValue;
printf("0x%02X, ", SinValue) ;
if (((i%8)==0)) {
printf("\n") ;
}
}
printf("};\n");
calcSintable()用于计算标准的正弦表格数据,该函数只需要运行一次,生成
的数据就是 SinTable[]的数组内容,该函数将标准的正弦函数划分为 256 个数据
点,然后计算正弦值,最后乘上 255。得到的数据会被打印到串口上,只需要将
串口的数据复制到代码中就行。
代码 13-14 修改正弦波幅值
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
/**
* 函数功能:
* 输入参数:
* 返 回 值:
* 说
明:
调整正弦波的幅值.
Amplitude: 取值范围 0~1024
无
正弦波的幅值 A=(SinAmp/1024)*sin(x);
设置为 1024 是为了用右移代
替除法
*/
void TuneSinAmp( uint16_t Amplitude)
{
if ( Amplitude >= 1024)
SinAmp = 1024;
else
SinAmp = Amplitude;
}
该函数用于修改 SinAmp,同时限定 SinAmp 最大值只能是 1024。
代码 13-15 修改正弦波频率
00 /**
01 * 函数功能: 调整正弦波的频率.
02 * 输入参数: Frequency:正弦波的频率
03 * 返 回 值: 无
04 * 说
明: 正弦波的频率与载波频率有关,
05 查表法决定了每个正弦周期的数据量是固定的
06 *
要改变频率只能改变载波频率
07 */
08 void TuneSinFreq( float Frequency)
09 {
10
uint32_t tmpARR = 0;
11
float TIM_SrcFreq = 1.68e8;// 时钟源频率//168MHz
12
float tmpPSC = 0 ; // 预分频值
13
// 每个脉冲就是一个数据点,
14
一个正弦周期有 512 个数据点
15
float tmpPulseFreq = Frequency*SamplePoint*2;
16
17
do {
18
tmpARR = TIM_SrcFreq/(tmpPSC+1.0f)/tmpPulseFreq;
19
tmpARR>>=1;
STM32 技术开发手册
www.ing10bbs.com
20
21
22
23
24
25
26
27
28
29
30 }
tmpPSC++;
if ( tmpPSC>65535 ) {
return; // 没有找到合适的预分频值,
不修改定时器频率
}
} while ( tmpARR>65535);
__HAL_TIM_SET_PRESCALER(&htimx, tmpPSC-1);
__HAL_TIM_SET_AUTORELOAD(&htimx, tmpARR);
HalfMax = tmpARR>>1;
由于查表法属于同步调制,修改正弦波频率就是修改定时器的计数频率。
首先根据设定的正弦波频率 Frequency 以及采样数据点数计算出目标的定时
器计数频率,然后在根据计数频率计算出合适预分频值,如果没有找到合适的预
分频值,那直接返回,不作修改。注意计算出来的 tmpARR 是一个周期的计数值,
由于例程使用的还是中心对齐模式,所以还需要右移 1 位,才是实际的 ARR 值。
代码 13-16 main 函数
00 /**
01 * 函数功能: 主函数.
02 * 输入参数: 无
03 * 返 回 值: 无
04 * 说
明: 无
05 */
06 int main(void)
07 {
08
09
/*
10
复位所有外设,初始化 Flash 接口和系统滴答定时器
11
*/
12
HAL_Init();
13
14
/* 配置系统时钟 */
15
SystemClock_Config();
16
17
/* 初始化 LED 控制引脚 */
18
LED_GPIO_Init();
19
/* 初始化按键引脚 */
20
KEY_GPIO_Init();
21
MX_DEBUG_USART_Init();
22
23
calcSinTable();
24
/* 高级定时器初始化: */
25
ADVANCED_TIMx_Init();
26
27
/* 设定正弦波参数 */
28
TuneSinFreq(50); // 频率 50Hz
29
TuneSinAmp(1024);// 满幅值输出
30
31
/* 启动 CH1 比较输出 */
32
HAL_TIM_PWM_Start(&htimx, TIM_CHANNEL_1);
33
HAL_TIMEx_PWMN_Start(&htimx, TIM_CHANNEL_1);
34
35
//ADVANCED_TIM_REPETITIONCOUNTER 设置为 1,一个周期只会有一次更新中断
36
HAL_TIM_Base_Start_IT(&htimx);
37
__HAL_DBGMCU_FREEZE_TIM8();// debug
38
时,遇到 break 会停止 TIM8 时钟源
STM32 技术开发手册
www.ing10bbs.com
39
40
41
42
43
44
45 }
/* 无限循环 */
while (1) {
LED2_TOGGLE;
HAL_Delay(500);
}
main 函数当中设定的正弦波频率是 50Hz,调制比是 1。
代码 13-17 查表法实现 SPWM
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* 函数功能: 正弦波函数映射
* 输入参数: 无
* 返 回 值: 无
* 说
明: 将标准的正弦波数据映射到输出正弦波上,
就是放大 n 倍...
*/
uint16_t map(uint16_t sinx, uint16_t out_max)
{
return sinx * out_max/SIN_AMPMAX;
}
/**
* 函数功能: 定时器更新中断会回调函数
* 输入参数: 无
* 返 回 值: 无
* 说
明:
通过测量 LED2 引脚翻转的脉宽,得计算耗时大概是 350ns
(优化等级-O3,Keil)
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
// LED2_ON;
static int16_t i = 0;
int32_t CCR1 = 0;
CCR1 = map(SinTable[i], HalfMax); //
将标准正弦曲线映射到变换之后的曲线,
得到比较值,调整频率
CCR1 = SinDir * CCR1;
// 正负半周调整
CCR1 += HalfMax;
CCR1 = (SinAmp*CCR1)>>10;
// 右移代替除法,
调整幅值
__HAL_TIM_SetCompare(htim, TIM_CHANNEL_1, (uint16_t)CCR1);
i ++;
/* 正负半轴切换 */
if ( i >= SamplePoint ) {
i = 0;
SinDir = -SinDir;
}
// LED2_OFF;
}
这里有两个函数,一个是 map(),是将标准正弦函数映射到所需的调制函数
上,也就是乘上一个系数,使其输出幅值能满足要求。
HAL_TIM_PeriodElapsedCallback()是中断回调函数,每个脉冲周期会被调用一
次,该函数首先是在 SinTable 里面查找下一个脉冲的正弦值,然后将其乘上一个
STM32 技术开发手册
www.ing10bbs.com
系数作为比较值,整个计算过程完全符合公式 13-12。最后只需要设置比较值就
行了。
实验操作与现象
例程主要使用了 PI5 引脚作为 SPWM 调制波形输出,输出的波形是一串连续
变化宽度的方波波形,如果需要观察正弦波形,需要在输出引脚上接上一阶 RC
低通滤波电路,然后才能使用示波器观察正弦曲线。一阶低通滤波电路选用 10k
电阻和 0.1uF 的电容,截止频率是 159Hz,如果正弦波的频率高于 159Hz,将会
出现信号衰减的现象,这个时候可以选取更小的电阻或者电容。
图 13-27 一阶 RC 低通滤波电路与示波器接线示意图
STM32 技术开发手册
www.ing10bbs.com
图 13-28 单相 SPWM 调制波形
图 13-24 就是实际测量的波形图片,CH2 是调制波形,经过一阶低通滤波之后可
以看到正弦波,CH1 是滤波之前的波形。
STM32 技术开发手册
www.ing10bbs.com
第14章 无刷电机 FOC 控制
14.1 梯形波和正弦波驱动
由于具有体积小、控制方便和高效率的特点,许多消费者和工业应用都采用
BLDC 电机。BLDC 电机愈来愈多地出现在汽车应用中以取代传动带和液压系统,
这样可进一步增强功能和降低油耗。在高性能应用中,如机床设备和低噪声风机
应用中,平稳的转矩输出是至关重要的。
由于采用非正弦分布的定子绕组,使得 BLDC 电机难以应用在需要低转矩脉
动和低噪声运行的场合。如图 14-1 所示,具有非正弦绕组分布的 BLDC 电机将
产生梯形波的反电动势。具有梯形波反电动势的 BLDC 电机专门设计为采用与电
机转子角位置同步的方波电压进行驱动。这种控制方式通常称为六拍换相。
图 14-1 梯形波反电动势
BLDC 电机中绕组的梯形分布将在电机运行过程中导致转矩脉动,这是由于
产生的电流也是梯形分布的。这种转矩脉动将导致小的转速振荡从而产生音频噪
声。另一方面,具有正弦反电动势的 BLDC 电机,也称作永磁同步电机(PMSM),
STM32 技术开发手册
www.ing10bbs.com
采用正弦电流进行驱动,减小了转矩脉动,从而将音频噪声降低到最小限度。图
14-2 显示了正弦绕组分布的 PMSM 电机中产生的正弦波反电动势电压。
图 14-2 正弦波反电动势
前面章节中我们已经介绍了梯形波驱动 BLDC 的方法,图 14-3 对比了在有
霍尔传感器时两种电机的驱动方法:
STM32 技术开发手册
www.ing10bbs.com
图 14-3 BLDC 和 PMSM 驱动方法不同点
梯形波驱动在之前章节已经作了大量的分析,类比到正弦波的驱动,正弦电
压的幅值决定了特定机械负载条件下的电机转速。
很明显,使用正弦波驱动可以提高电机运转性能,高性能一般就需要复杂的
控制程序是支持。正弦波驱动最基本的问题就是如何产生这个正弦波的电压信号,
我们知道电机驱动板主要是:光耦隔离+MOS 驱动 IC+MOS 管,即功率放大作用,
所以输入端什么性质信号输出端也是相同性质信号,所以我们控制板需要给的控
制信号实际上正弦波密切相关的 PWM 信号。
与正弦波密切相关的 PWM 信号有多个专业名词,非常容易混淆,这里就详
细做个介绍。
STM32 技术开发手册
www.ing10bbs.com
14.2 PWM、SPWM 和 SVPWM
PWM 为 Pulse Width Modulation(脉冲宽度调制)缩写,与之相关联最重要的
名称就是占空比。晶体管(常用 MOS、IGBT 等全控型器件)工作在开关状态,晶体
管被触发导通时,电源电压加到电动机上;晶体管关断时,直流电源与电动机断
开;这样通过改变晶体管的导通时间(即调占空比 ton)就可以调节电机电压,
从而进行调速。
14.2.1 SPWM—正弦脉宽调制
SPWM,就是在 PWM 的基础上改变了调制脉冲方式,脉冲宽度时间占空比
按正弦规率排列,这样输出波形经过适当的滤波可以做到正弦波输出。
根据采样控制理论中的一个重要结论:冲量相等而形状不同的窄脉冲加在具
有惯性的环节上时,其效果基本相同。冲量即指窄脉冲的面积。这里所说的效果
基本相同,是指环节的输出响应波形基本相同。即当它们分别加在具有惯性的同
一个环节上时,其输出响应基本相同。如果把各输出波形用傅立叶变换分析,则
其低频段非常接近,仅在高频段略有差异。上述原理可以称之为面积等效原理,
见图 14-4,它是 PWM 控制技术的重要理论基础。
图 14-4 面积等效原理
STM32 技术开发手册
www.ing10bbs.com
SPWM 法就是以该结论为理论基础,用脉冲宽度按正弦规律变化而和正弦波
等效的 PWM 波形即 SPWM 波形控制逆变电路中开关器件的通断,使其输出的脉
冲电压的面积与所希望输出的正弦波在相应区间内的面积相等,通过改变调制波
的频率和幅值则可调节逆变电路输出电压的频率和幅值。
图 14-5 SPWM 基本原理
上面从原理上说明 SPWM 的理论性正确,对于实际控制器来说,还有一个
特别重要的问题:这些 PWM 如何计算得到,如何作用于 MOS 管。现在的一个
命题就是:已知目标正弦波的具体参数,求上图中 PWM 数据?一般的做法如所
示,在正弦波上面叠加一个三角波,相交处用于生成 SPWM,见图 14-6。
STM32 技术开发手册
www.ing10bbs.com
图 14-6 正弦波调制方法
更多 SPWM 理论可以看《SPWM 逆变原理及控制方法.pdf》(附件 4)了解。
14.2.2 SVPWM—空间矢量脉宽调制
SVPWM(Space Vector Pulse Width Modulation),实际上是对应于交流感应电
机或永磁同步电机中的三相电压源逆变器功率器件的一种特殊的开关触发顺序
和脉宽大小的组合,这种开关触发顺序和组合将在定子线圈中产生三相互差 120°
电角度、失真较小的正弦波电流波形。实践和理论证明,与直接的正弦脉宽调制
(SPWM)技术相比,SVPWM 的优点主要有:
1) SVPWM 优化谐波程度比较高,消除谐波效果要比 SPWM 好,实现容易,
并且可以提高电压利用率。
2) SVPWM 比较适合于数字化控制系统。
SVPWM 的主要思想是以三相对称正弦波电压供电时三相对称电动机定子理
想磁链圆为参考标准,以三相逆变器不同开关模式作适当的切换,从而形成 PWM
波,以所形成的实际磁链矢量来追踪其准确磁链圆。传统的 SPWM 方法从电源
的角度出发,以生成一个可调频调压的正弦波电源,而 SVPWM 方法将逆变系统
和异步电机看作一个整体来考虑,模型比较简单,也便于微处理器的实时控制。
STM32 技术开发手册
www.ing10bbs.com
普通的三相全桥是由六个开关器件构成的三个半桥。这六个开关器件组合起
来(同一个桥臂的上下半桥的信号相反)共有 8 种安全的开关状态. 其中 000、
111(这里是表示三个上桥臂的开关状态)这两种开关状态在电机驱动中都不会
产生有效的电流。因此称其为零矢量。另外 6 种开关状态分别是六个有效矢量。
它们将 360 度的电压空间分为 60 度一个扇区,共六个扇区,利用这六个基本有
效矢量和两个零量,可以合成 360 度内的任何矢量。
当要合成某一矢量时先将这一矢量分解到离它最近的两个基本矢量,而后
用这两个基本矢量去表示,而每个基本矢量的作用大小就利用作用时间长短去
代表。用电压矢量按照不同的时间比例去合成所需要的电压矢量。从而保证生成
电压波形近似于正弦波。
在变频电机驱动时,矢量方向是连续变化的,因此我们需要不断的计算矢量
作用时间。为了控制器处理的方便,在合成时一般是定时器计算(如每 0.1ms 计
算一次)。这样我们只要算出在 0.1ms 内两个基本矢量作用的时间就可以了。由
于计算出的两个时间的总和可能并不是 0.1ms(比这小),而那剩下的时间就按情
况插入合适零矢量。 由于在这样处理时,合成的驱动波形和 PWM 很类似。因
此我们还叫它 PWM,又因这种 PWM 是基于电压空间矢量去合成的,所以就叫
它 SVPWM 了。
特别说明,下面内容是对 SVPWM 理论知识讲解,参考了大量网络文档,如
有雷同纯属正常。
14.2.3 SVPWM 基本原理
SVPWM 的理论基础是平均值等效原理,即在一个开关周期内通过对基本电压
矢量加以组合,使其平均值与给定电压矢量相等。在某个时刻,电压矢量旋转到
某个区域中,可由组成这个区域的两个相邻的非零矢量和零矢量在时间上的不同
组合来得到。两个矢量的作用时间在一个采样周期内分多次施加,从而控制各个
电压矢量的作用时间,使电压空间矢量接近按圆轨迹旋转,通过逆变器的不同开
关状态所产生的实际磁通去逼近理想磁通圆,并由两者的比较结果来决定逆变器
的开关状态,从而形成 PWM 波形。逆变电路如图 14-7 示。
STM32 技术开发手册
www.ing10bbs.com
图 14-7 逆变电路
设直流母线侧电压为 Udc,逆变器输出的三相相电压为𝑈𝐴 、𝑈𝐵 、𝑈𝐶 ,其分别
加在空间上互差 120°的三相平面静止坐标系上,可以定义三个电压空间矢量𝒖𝐴 、
𝒖𝐵 、𝒖𝐶 ,它们的方向始终在各相的轴线上,而大小则随时间按正弦规律做变化,
时间相位互差 120°。假设𝑈𝑚 为相电压波峰值,f 为电源频率, θ = ωt = 2πft,
则有:
公式 14-1 三相相电压大小
𝑈𝑚 𝑗𝜃
(𝑒 + 𝑒 −𝑗𝜃 )
2
2𝜋
2𝜋
𝑈𝑚 𝑗(𝜃−2𝜋)
−𝑗(𝜃− 3 )
3
(
)
𝑈𝐵 𝑡 = 𝑈𝑚 cos (𝜃 − ) =
[𝑒
+𝑒
]
3
2
2𝜋
2𝜋
𝑈𝑚 𝑗(𝜃+2𝜋)
−𝑗(𝜃+ 3 )
3
(
)
𝑈
𝑡
=
𝑈
cos
(𝜃
+
)
=
[𝑒
+
𝑒
]
𝑚
{ 𝐶
3
2
𝑈𝐴 (𝑡) = 𝑈𝑚 cos(𝜃 ) =
在三相静止坐标系下:
公式 14-2 三个电压空间矢量
𝒖𝐴 (𝑡) = 𝑈𝐴 (𝑡)𝑒 𝑗0
𝒖𝐵 (𝑡) =
2𝜋
𝑗3
𝑈𝐵 (𝑡)𝑒
4𝜋
{ 𝒖𝐶 (𝑡) = 𝑈𝐶 (𝑡)𝑒 𝑗 3
三相电压空间矢量相加的合成空间矢量𝒖𝑆 (𝑡)就可以表示为:
公式 14-3 合成空间矢量
STM32 技术开发手册
www.ing10bbs.com
𝒖𝑆 (𝑡) = 𝒖𝐴 (𝑡) + 𝒖𝐵 (𝑡) + 𝒖𝐶 (𝑡)
= 𝑈𝐴 (𝑡)𝑒
=
𝑗0
+
2𝜋
𝑗3
𝑈𝐵 (𝑡)𝑒
+
4𝜋
𝑗3
𝑈𝐶 (𝑡)𝑒
3
𝑈𝑚 𝑒 𝑗𝜃
2
可见𝒖𝑆 (𝑡)是一个旋转的空间矢量,它的幅值为相电压峰值的 1.5 倍(𝑈𝑚 为
相电压峰值),且以角频率ω = 2πf按逆时针方向匀速旋转的空间矢量,而空间矢
量𝒖𝑆 (𝑡)在三相坐标轴(a,b,c)上的投影就是对称的三相正弦量。
由于逆变器三相桥臂共有 6 个开关管,为了研究各相上下桥臂不同开关组合
时逆变器输出的空间电压矢量,特定义开关函数𝑆𝑥 ( x = a、b、c)为:
公式 14-4 开关函数定义
1 上桥臂导通
𝑆𝑥 = {
0 下桥臂导通
(Sa、Sb、Sc)的全部可能组合共有八个,包括 6 个非零矢量 U1(001)、U2(010)、
U3(011)、U4(100)、U5(101)、U6(110)、和两个零矢量 U0(000)、U7(111),下面以其
中一种开关组合为例分析,假设 Sx ( x= a、b、c)= (100), 此时如图 14-8:
图 14-8 空间电压矢量 U4(100)组合
由图 14-8 可以列出电压关系式:
公式 14-5 空间电压矢量 U4 电压关系式
STM32 技术开发手册
www.ing10bbs.com
𝑈𝑎𝑏 = 𝑈𝑑𝑐 , 𝑈𝑏𝑐 = 0, 𝑈𝑐𝑎 = −𝑈𝑑𝑐
{𝑈𝑎𝑁 −𝑈𝑏𝑁 = 𝑈𝑑𝑐 , 𝑈𝑎𝑁 −𝑈𝑐𝑁 = 𝑈𝑑𝑐
𝑈𝑎𝑁 +𝑈𝑏𝑁 +𝑈𝑐𝑁 = 0
求解上述方程可得:𝑈𝑎𝑁 = 2𝑈𝑑𝑐 /3、𝑈𝑏𝑁 = −𝑈𝑑𝑐 /3、𝑈𝑐𝑁 = −𝑈𝑑𝑐 /3。同理可
计算出其它各种组合下的空间电压矢量,如表格 14-1:
表格 14-1 8 种组合下的空间电压矢量
Uab
线电压
Ubc
UaN
相电压
UbN
Uca
UcN
U0
0
0
0
0
0
0
0
U4
Udc
0
- Udc
2
𝑈
3 𝑑𝑐
1
− 𝑈𝑑𝑐
3
1
− 𝑈𝑑𝑐
3
1
0
U6
0
Udc
- Udc
1
𝑈
3 𝑑𝑐
1
𝑈
3 𝑑𝑐
2
− 𝑈𝑑𝑐
3
0
1
0
U2
- Udc
Udc
0
1
− 𝑈𝑑𝑐
3
2
𝑈
3 𝑑𝑐
1
− 𝑈𝑑𝑐
3
0
1
1
U3
- Udc
0
Udc
2
− 𝑈𝑑𝑐
3
1
𝑈
3 𝑑𝑐
1
𝑈
3 𝑑𝑐
0
0
1
U1
0
- Udc
Udc
1
− 𝑈𝑑𝑐
3
1
− 𝑈𝑑𝑐
3
2
𝑈
3 𝑑𝑐
1
0
1
U5
Udc
- Udc
0
1
𝑈
3 𝑑𝑐
2
− 𝑈𝑑𝑐
3
1
𝑈
3 𝑑𝑐
1
1
1
U7
0
0
0
0
0
0
Sa
Sb
Sc
矢量
符号
0
0
0
1
0
1
图 14-9 给出了八个基本电压空间矢量的大小和位置。
STM32 技术开发手册
www.ing10bbs.com
图 14-9 电压空间矢量图
其中非零矢量的幅值相同(在𝛂𝛃两轴坐标系下,模长为𝟐𝑼𝒅𝒄 ⁄𝟑;如果是在三
相静止坐标系下,模长为 Udc),相邻的矢量间隔 60°,而两个零矢量幅值为零,
位于中心。在每一个扇区,选择相邻的两个电压矢量以及零矢量,按照伏秒平衡
的原则来合成每个扇区内的任意电压矢量,即:
公式 14-6 期望电压矢量𝑼𝒓𝒆𝒇 计算
𝑇
𝑇𝑥
𝑇𝑥 +𝑇𝑦
∫ 𝑈𝑟𝑒𝑓 𝑑𝑡 = ∫ 𝑈𝑥 𝑑𝑡 + ∫
0
0
𝑇𝑥
𝑇
𝑈𝑦 𝑑𝑡 + ∫
𝑇𝑥 +𝑇𝑦
𝑈0∗ 𝑑𝑡
或者等效成:
𝑈𝑟𝑒𝑓 ∙ T = 𝑈x ∙ 𝑇x + 𝑈𝑦 ∙ 𝑇y + 𝑈0∗ ∙ 𝑇0∗
其中,𝑈𝑟𝑒𝑓 为期望电压矢量;T 为采样周期;𝑇x 、𝑇y 、𝑇0∗ 分别为对应两个非
零电压矢量𝑈x 、𝑈𝑦 和零电压矢量𝑈0∗ 在一个采样周期的作用时间;其中𝑈0∗ 包括了
U0 和 U7 两个零矢量。上式的意义是,矢量𝑈𝑟𝑒𝑓 在 T 时间内所产生的积分效果值
和𝑈x 、𝑈𝑦 、𝑈0∗ 分别在时间𝑇x 、𝑇y 、𝑇0∗ 内产生的积分效果相加总和值相同(由于
STM32 技术开发手册
www.ing10bbs.com
在𝑻𝐬 时间内认为𝑼𝒓𝒆𝒇 的角度是不变的,所以通过计算时间𝑻𝐱 、𝑻𝐲 、𝑻∗𝟎 这种方式实
现的 SVPWM 是一种规则采样)。
由于三相正弦波电压在电压空间向量中合成一个等效的旋转电压,其旋转速
度是输入电源角频率,等效旋转电压的轨迹将是如图 14-9 所示的圆形。所以要
产生三相正弦波电压,可以利用以上电压向量合成的技术,在电压空间向量上,
将设定的电压向量由 U4(100)位置开始,每一次增加一个小增量,每一个小增量
设定电压向量可以用该区中相邻的两个基本非零向量与零电压向量予以合成,如
此所得到的设定电压向量就等效于一个在电压空间向量平面上平滑旋转的电压
空间向量,从而达到电压空间向量脉宽调制的目的。
14.2.4 SVPWM 法则推导
三相电压给定所合成的电压向量旋转角速度为ω = 2πf,旋转一周所需的时
间为T = 1⁄𝑓 ;若载波频率是𝑓𝑠 ,则频率比为R = 𝑓𝑠 ⁄𝑓 。这样将电压旋转平面等 切
割成 R 个小增量,亦即设定电压向量每次增量的角度是:
公式 14-7 小增量角度
𝛾=
2𝜋 2𝜋𝑓 2𝜋𝑇𝑠
=
=
𝑅
𝑓𝑠
𝑇
现在,假设欲合成的电压向量𝑈𝑟𝑒𝑓 在第Ⅰ区中一个增量的位置,如图 14-10
所示,
STM32 技术开发手册
www.ing10bbs.com
图 14-10 电压空间向量在第Ⅰ区的合成与分解
欲用 U4、U6、U0 及 U7 合成,用平均值等效可得:
公式 14-8 第Ⅰ区中的平均值等效
𝑈𝑟𝑒𝑓 ∙ 𝑇𝑧 = 𝑈4 ∙ 𝑇4 + 𝑈6 ∙ 𝑇6
在两相静止参考坐标系(𝛼, 𝛽)中(下文所有𝛂𝛃两轴坐标系下的论述,都以等
幅值变换为前提),令𝑈𝑟𝑒𝑓 和 U4 间的夹角是θ,由向量分解可得:
公式 14-9 向量分解求𝛉关系式
𝑇4
𝑇6
𝜋
|𝑈4 | + |𝑈6 | cos
⋯ 𝛼轴
𝑇s
𝑇s
3
𝑇6
𝜋
|𝑈𝑟𝑒𝑓 | sin 𝜃 = |𝑈6 | sin
⋯ 𝛽轴
{
𝑇s
3
|𝑈𝑟𝑒𝑓 | cos 𝜃 =
或者,由正弦定理可得:
公式 14-10 正弦定理求𝛉关系式
𝑇6
𝑇4
|
|
|𝑈 |
𝑈
6
|𝑈𝑟𝑒𝑓 | 𝑇s
𝑇s 4
=
=
𝜋
2𝜋
sin
𝜃
sin ( − 𝜃)
sin
3
3
因为|𝑈4 | = |𝑈6 | = 2𝑈𝑑𝑐 ⁄3,所以可以得到各矢量的状态保持时间为:
公式 14-11 矢量的状态保持时间
STM32 技术开发手册
www.ing10bbs.com
𝜋
𝑇4 = m𝑇s sin ( − 𝜃)
{
3
𝑇6 = m𝑇s sin 𝜃
式中 m 为 SVPWM 调制系数,𝑚
=
√3|𝑈𝑟𝑒𝑓 |
𝑈𝑑𝑐
。(调制比=调制波基波峰值/
载波基波峰值)
而零电压矢量所分配的时间为:
公式 14-12 零电压矢量所分配的时间
𝑇7 = 𝑇0 =
𝑇𝑠 − 𝑇4 − 𝑇6
2
或者
𝑇7 = 𝑇𝑠 − 𝑇4 − 𝑇6
得到以 U4、U6、U7 及 U0 合成的𝑈𝑟𝑒𝑓 的时间后,接下来就是如何产生实际的
脉宽调制波形。在 SVPWM 调制方案中,零矢量的选择是最具灵活性的,适当选
择零矢量,可最大限度地减少开关次数,尽可能避免在负载电流较大的时刻的开
关动作,最大限度地减少开关损耗。
一个开关周期中空间矢量按分时方式发生作用,在时间上构成一个空间矢量
的序列,空间矢量的序列组织方式有多种,按照空间矢量的对称性分类,可分为
两相开关换流与三相开关换流。下面对常用的序列做分别介绍。
1.
7 段式 SVPWM
我们以减少开关次数为目标,将基本矢量作用顺序的分配原则选定为:在每
次开关状态转换时,只改变其中一相的开关状态,并且对零矢量在时间上进行了
平均分配,以使产生的 PWM 对称,从而有效地降低 PWM 的谐波分量。当 U4(100)
切换至 U0(000)时,只需改变 A 相上下一对切换开关,若由 U4(100)切换至 U7(111)
则需改变 B、C 相上下两对切换开关,增加了一倍的切换损失。因此要改变电压
向量 U4(100)、U2(010)、U1(001)的大小,需配合零电压向量 U0(000),而要改变
U6(110)、U3(011)、U5(100),需配合零电压向量 U7(111)。这样通过在不同区间内
STM32 技术开发手册
www.ing10bbs.com
安排不同的开关切换顺序,就可以获得对称的输出波形,其它各扇区的开关切换
顺序如表格 14-2 所示。
表格 14-2 𝑼𝒓𝒆𝒇 所在的位置和开关切换顺序对照序
𝑼𝒓𝒆𝒇 所在的位置
开关切换顺序
Ⅰ区
(0°≤θ≤60°)
…0-4-6-7-7-6-4-0…
Ⅱ区
(60°≤θ≤120°)
…0-2-6-7-7-6-2-0…
Ⅲ区
(120°≤θ≤180°)
…0-2-3-7-7-3-2-0…
Ⅳ区
(180°≤θ≤240°)
…0-1-3-7-7-3-1-0…
Ⅴ区
(240°≤θ≤300°)
…0-1-5-7-7-5-1-0…
三相波形图
STM32 技术开发手册
www.ing10bbs.com
Ⅵ区
(300°≤θ≤360°)
…0-4-5-7-7-5-4-0…
以第Ⅰ扇区为例,其所产生的三相波调制波形在时间 TS 时段中如表中图所
示,图中电压向量出现的先后顺序为 U0、U4、U6、U7、U6、U4、U0,各电压向量
的三相波形则与中的开关表示符号相对应。再下一个 TS 时段,𝑈𝑟𝑒𝑓 的角度增加一
个𝛾,利用式可以重新计算新的 T0、T4、T6 及 T7 值,得到新的合成三相类似所示
的三相波形;这样每一个载波周期 TS 就会合成一个新的矢量,随着θ的逐渐增大,
𝑈𝑟𝑒𝑓 将依序进入第Ⅰ、Ⅱ、Ⅲ、Ⅳ、Ⅴ、Ⅵ区。在电压向量旋转一周期后,就会
产生 R 个合成矢量。
2.
5 段式 SVPWM(DPWMMAX)
对 7 段而言,发波对称,谐波含量较小,但是每个开关周期有 6 次开关切
换,为了进一步减少开关次数,采用每相开关在每个扇区状态维持不变的序列安
排,使得每个开关周期只有 3 次开关切换,但是会增大谐波含量。具体序列安排
见表格 14-3。
表格 14-3 𝑼𝒓𝒆𝒇 所在的位置和开关切换顺序对照序
𝑼𝒓𝒆𝒇 所在的位置
Ⅰ区
(0°≤θ≤60°)
开关切换顺序
…4-6-7-7-6-4…
三相波形图
STM32 技术开发手册
www.ing10bbs.com
Ⅱ区
(60°≤θ≤120°)
…2-6-7-7-6-2…
Ⅲ区
(120°≤θ≤180°)
…2-3-7-7-3-2…
Ⅳ区
(180°≤θ≤240°)
…1-3-7-7-3-1…
Ⅴ区
(240°≤θ≤300°)
…1-5-7-7-5-1…
Ⅵ区
(300°≤θ≤360°)
…4-5-7-7-5-4…
STM32 技术开发手册
www.ing10bbs.com
14.2.5 SVPWM 控制算法
通过以上 SVPWM 的法则推导分析可知要实现 SVPWM 信号的实时调制,首
先需要知道参考电压矢量𝑈𝑟𝑒𝑓 所在的区间位置,然后利用所在扇区的相邻两电压
矢量和适当的零矢量来合成参考电压矢量。图 14-10 图 14-9 是在静止坐标系
(𝛼, 𝛽)中描述的电压空间矢量图,电压矢量调制的控制指令是矢量控制系统给出
的矢量信号𝑈𝑟𝑒𝑓 ,它以某一角频率ω在空间逆时针旋转,当旋转到矢量图的某个
60°扇区中时,系统计算该区间所需的基本电压空间矢量,并以此矢量所对应的
状态去驱动功率开关元件动作。当控制矢量在空间旋转 360°后,逆变器就能输
出一个周期的正弦波电压。
1. 合成矢量𝑼𝒓𝒆𝒇 所处扇区 N 的判断
空间矢量调制的第一步是判断由𝑈𝛼 和𝑈𝛽 所决定的空间电压矢量所处的扇区。
假定合成的电压矢量落在第Ⅰ扇区,见图 14-10,可知其等价条件如下:
公式 14-13 合成的电压矢量落在第Ⅰ扇区等价条件
𝑈𝛽
0 < arctan ( ) < 60𝑜
𝑈𝛼
𝑜
其中:
𝑈𝛽 = 𝑈𝑟𝑒𝑓 sin 𝜃
𝑈𝛼 = 𝑈𝑟𝑒𝑓 cos 𝜃
以上等价条件再结合矢量图几何关系分析,可以判断出合成电压矢量𝑈𝑟𝑒𝑓 落
在第 X 扇区的充分必要条件,得出表格 14-5:
表格 14-4 𝑼𝒓𝒆𝒇 落在某个扇区的充分必要条件
扇
区
落在此扇区的充分必要条件
I
𝑈𝛼 >0,𝑈𝛽 >0 且𝑈𝛽 /𝑈𝛼 <
Ⅱ
𝑈𝛼 >0 且𝑈𝛽 / |𝑈𝛼 |>
3
3
STM32 技术开发手册
www.ing10bbs.com
Ⅲ
𝑈𝛼 <0,𝑈𝛽 >0 且-𝑈𝛽 /𝑈𝛼 <
3
Ⅳ
𝑈𝛼 <0,𝑈𝛽 <0 且𝑈𝛽 /𝑈𝛼 <
3
Ⅴ
𝑈𝛽 <0 且-𝑈𝛽 /|𝑈𝛼 |>
Ⅵ
𝑈𝛼 >0,𝑈𝛽 <0 且-𝑈𝛽 /𝑈𝛼 <
3
3
若进一步分析以上的条件,有可看出参考电压矢量𝑈𝑟𝑒𝑓 所在的扇区完全由𝑈𝛽 ,
√3𝑈𝛼 − 𝑈𝛽 ,−√3𝑈𝛼 − 𝑈𝛽 三式决定,因此令:
公式 14-14 三个中间计算电压
𝑈1 = 𝑈𝛽
√3𝑈𝛼 𝑈𝛽
−
2
2
√3𝑈𝛼 𝑈𝛽
{𝑈3 = − 2 − 2
𝑈2 =
再定义,若𝑈1 >0,则 A=1,否则 A=0;若𝑈2 >0,则 B=1,否则 B=0;若𝑈3 >0,
则 C=1,否则 C=0。可以看出 A,B,C 之间共有八种组合,但由判断扇区的公式
可知 A,B,C 不会同时为 1 或同时为 0,所以实际的组合是六种,A,B,C 组合
取不同的值对应着不同的扇区,并且是一一对应的,因此完全可以由 A,B,C 的
组合判断所在的扇区。为区别六种状态,令 N=4*C+2*B+A,则可以通过下表计算
参考电压矢量𝑈𝑟𝑒𝑓 所在的扇区。
表格 14-5 N 值与扇区对应关系
N
扇区号
3
Ⅰ
1
Ⅱ
5
Ⅲ
4
Ⅳ
6
Ⅴ
2
Ⅵ
采用上述方法,只需经过简单的加减及逻辑运算即可确定所在的扇区,对于
提高系统的响应速度和进行仿真都是很有意义的。
STM32 技术开发手册
www.ing10bbs.com
2. 基本矢量作用时间计算与三相 PWM 波形的合成
在传统 SVPWM 算法如公式 14-11 中用到了空间角度及三角函数,使得直接
计算基本电压矢量作用时间变得十分困难。实际上,只要充分利用𝑈𝛼 和𝑈𝛽 就可
以使计算大为简化。以𝑈𝑟𝑒𝑓 处在第Ⅰ扇区时进行分析,根据图 14-10 有:
公式 14-15 𝑼𝒓𝒆𝒇 落在第Ⅰ扇区时矢量作用时间计算
𝜋
2
𝑈𝛼
1
cos 𝜃
3] 𝑇 )
[𝑈 ] 𝑇s = 𝑈𝑟𝑒𝑓 [
] 𝑇s = 𝑈𝑑𝑐 ([ ] 𝑇4 + [
𝜋 6
sin 𝜃
0
𝛽
3
sin
3
cos
经过整理后得出:
2
1
𝑈𝑑𝑐 (𝑇4 + 𝑇6 )
3
2
2
√3
𝑈𝛽 𝑇s = 𝑈𝑑𝑐 ( 𝑇6 )
3
2
{
𝑈𝛼 𝑇s =
3𝑈𝛼 𝑇𝑠 1
3𝑈𝛼 𝑇𝑠 1 √3𝑈𝛽𝑇𝑠
𝑇4 =
− 𝑇 =
−
2𝑈𝑑𝑐 2 6
2𝑈𝑑𝑐 2 𝑈𝑑𝑐
√3𝑇𝑠 √3𝑈𝛼 𝑈𝛽
√3𝑇𝑠
=
(
− )=
𝑈 = 𝑲𝑈2
𝑈𝑑𝑐
𝑈𝑑𝑐 2
2
2
√3𝑈𝛽 𝑇
𝑠 = √3𝑇𝑠 𝑈 = 𝑲𝑈
𝑇6 =
1
𝑈𝑑𝑐
𝑈𝑑𝑐 1
𝑇 − 𝑇4 − 𝑇6
𝑇7 = 𝑇0 = 𝑠
(7 段) 或者
2
{ 𝑇7 = 𝑇𝑠 − 𝑇4 − 𝑇6 (5 段)
同理可求得𝑈𝑟𝑒𝑓 在其它扇区中各矢量的作用时间,结果如表所示(以 7 段为
例)
,表中两个非零矢量作用时间的比例系数为𝑲 =
√3𝑇𝑠
。由此可根据公式
𝑈𝑑𝑐
14-14
中的𝑈1 、𝑈2 、𝑈3 判断合成矢量所在扇区,然后查表得出两非零矢量的作用时间,
最后得出三相 PWM 波占空比,表 2-4 可以使 SVPWM 算法编程简易实现。
STM32 技术开发手册
www.ing10bbs.com
表格 14-6 各扇区基本空间矢量的作用时间
扇
时间
区
𝑇4 =
I
√3𝑈𝛽 𝑇
𝑈𝑑𝑐
2
√3𝑈𝛽 𝑇
𝑠
= 𝑲𝑈1
𝑈𝑑𝑐
√3𝑇𝑠
√3𝑈𝛼 𝑈𝛽
𝑇3 =
(−
− ) = 𝑲𝑈3
𝑈𝑑𝑐
2
2
𝑇 𝑠 − 𝑇2 − 𝑇3
𝑇
=
𝑇
=
7
0
{
2
√3𝑇𝑠
𝑈𝑑𝑐
𝑇1 = −
(−
√3𝑈𝛼
√3𝑈𝛽 𝑇
2
+
𝑈𝛽
) = −𝑲𝑈2
2
𝑠
= −𝑲𝑈1
𝑈𝑑𝑐
𝑇 − 𝑇3 − 𝑇1
𝑇 = 𝑇0 = 𝑠
{ 7
2
𝑇1 =
Ⅴ
2
2
√3𝑇𝑠 √3𝑈𝛼 𝑈𝛽
𝑇2 = −
(
− ) = −𝑲𝑈2
𝑈𝑑𝑐
2
2
𝑇𝑠 − 𝑇6 − 𝑇2
𝑇
=
𝑇
=
7
0
{
2
𝑇3 =
Ⅳ
2
√3𝑇𝑠 √3𝑈𝛼 𝑈𝛽
(
+ ) = −𝑲𝑈3
𝑇2 =
Ⅲ
𝑈𝑑𝑐
𝑠
= 𝑲𝑈1
𝑈𝑑𝑐
𝑇 𝑠 − 𝑇4 − 𝑇6
𝑇
=
𝑇
=
7
0
{
2
𝑇6 =
𝑇6 =
Ⅱ
√3𝑇𝑠 √3𝑈𝛼 𝑈𝛽
(
− ) = 𝑲𝑈2
√3𝑇𝑠
√3𝑈𝛼 𝑈𝛽
− ) = 𝑲𝑈3
(−
𝑈𝑑𝑐
2
2
√3𝑇𝑠 √3𝑈𝛼 𝑈𝛽
− ) = 𝑲𝑈2
(
𝑈𝑑𝑐
2
2
𝑇𝑠 − 𝑇1 − 𝑇5
𝑇
=
𝑇
=
7
0
{
2
𝑇5 =
STM32 技术开发手册
www.ing10bbs.com
𝑇5 = −
Ⅵ
𝑇4 =
√3𝑈𝛽 𝑇
𝑈𝑑𝑐
𝑠
= −𝑲𝑈1
√3𝑇𝑠 √3𝑈𝛼 𝑈𝛽
(
+ ) = −𝑲𝑈3
𝑈𝑑𝑐
2
2
𝑇 − 𝑇5 − 𝑇4
𝑇 = 𝑇0 = 𝑠
{ 7
2
由公式 14-15 可知,当两个零电压矢量作用时间为 0 时,一个 PWM 周期内
非零电压矢量的作用时间最长,此时的合成空间电压矢量幅值最大,由图 14-11
可知其幅值最大不会超过图中所示的正六边形边界。而当合成矢量落在该边界之
外时,将发生过调制,逆变器输出电压波形将发生失真。
图 14-11 SVPWM 模式下电压矢量幅值边界
在 SVPWM 调制模式下,逆变器能够输出的最大不失真圆形旋转电压矢量为
√3
2
图 14-11 所示虚线正六边形的内切圆,其幅值为: 2 × 3 𝑈𝑑𝑐 =
√3
𝑈 ,即逆变器
3 𝑑𝑐
√3
输出的不失真最大正弦相电压幅值为 3 𝑈𝑑𝑐 ,而若采用三相 SPWM 调制,逆变器
能输出的不失真最大正弦相电压幅值为
𝑈𝑑𝑐
2
。显然 SVPWM 调制模式下对直流侧电
STM32 技术开发手册
www.ing10bbs.com
√3
压利用率更高,它们的直流利用率之比为 3 𝑈𝑑𝑐 ⁄
𝑈𝑑𝑐
2
= 1.1547,即 SVPWM 法比
SPWM 法的直流电压利用率提高了 15.47%。
如图 14-11 当合成电压矢量端点落在正六边形与外接圆之间时,已发生过调
制,输出电压将发生失真,必须采取过调制处理,这里采用一种比例缩小算法。
定义每个扇区中先发生的矢量用为𝑇𝑁𝑥 ,后发生的矢量为𝑇𝑁𝑦 。当𝑇𝑥 + 𝑇𝑦 ≤ 𝑇𝑠 时,
矢量端点在正六边形之内,不发生过调制;当𝑇𝑥 + 𝑇𝑦 > 𝑇𝑠 时,矢量端点超出正六
边形,发生过调制。输出的波形会出现严重的失真,需采取以下措施:
设将电压矢量轨迹端点拉回至正六边形内切圆内时两非零矢量作用时间分
别为𝑇𝑥′ 、𝑇𝑦′ ,则有比例关系:
公式 14-16 两非零矢量作用时间
𝑇𝑥′ 𝑇𝑦′
=
𝑇𝑥 𝑇𝑦
因此可用下式求得𝑇𝑥′ 、𝑇𝑦′ 、𝑇0 、𝑇7 :
公式 14-17 矢量作用时间
𝑇𝑥 𝑇𝑠
𝑇𝑥 + 𝑇𝑦
𝑇𝑦 𝑇𝑠
𝑇𝑦′ =
𝑇𝑥 + 𝑇𝑦
{ 𝑇0 = 𝑇7 = 0
𝑇𝑥′ =
按照上述过程,就能得到每个扇区相邻两电压空间矢量和零电压矢量的作用
时间。
当𝑈𝑟𝑒𝑓 所在扇区和对应有效电压矢量的作用时间确定后,再根据 PWM 调制
原理,计算出每一相对应定时器比较器的值,其运算关系如下:
公式 14-18 定时器比较器值
STM32 技术开发手册
www.ing10bbs.com
𝑇𝑠 − 𝑇𝑥 − 𝑇𝑦
4
𝑡𝑎𝑜𝑛 + 𝑇𝑥
𝑡𝑏𝑜𝑛 =
2
𝑡𝑎𝑜𝑛 + 𝑇𝑦
𝑡
=
{ 𝑐𝑜𝑛
2
𝑡𝑎𝑜𝑛 =
上式中𝑡𝑎𝑜𝑛 、𝑡𝑏𝑜𝑛 和𝑡𝑐𝑜𝑛 分别是相应的定时器比较器的值,而不同扇区定时器
比较器的值分配见表格 14-7:
表格 14-7 不同扇区定时器比较器值分配
扇区
N
𝑻𝒂
𝑻𝒃
𝑻𝒄
Ⅰ
3
𝑡𝑎𝑜𝑛
𝑡𝑏𝑜𝑛
𝑡𝑐𝑜𝑛
Ⅱ
1
𝑡𝑏𝑜𝑛
𝑡𝑎𝑜𝑛
𝑡𝑐𝑜𝑛
Ⅲ
5
𝑡𝑐𝑜𝑛
𝑡𝑎𝑜𝑛
𝑡𝑏𝑜𝑛
Ⅳ
4
𝑡𝑐𝑜𝑛
𝑡𝑏𝑜𝑛
𝑡𝑎𝑜𝑛
Ⅴ
6
𝑡𝑏𝑜𝑛
𝑡𝑐𝑜𝑛
𝑡𝑎𝑜𝑛
Ⅵ
2
𝑡𝑎𝑜𝑛
𝑡𝑐𝑜𝑛
𝑡𝑏𝑜𝑛
其中,𝑇𝑎 、𝑇𝑏 和𝑇𝑐 分别对应三相比较器的值,将这三个值写入相应的定时器
比较寄存器就完成了整个 SVPWM 算法。
3. SVPWM 物理含义
SVPWM 实质是一种对在三相正弦波中注入了零序分量的调制波进行规则采
样的一种变形 SPWM。但 SVPWM 的调制过程是在空间中实现的,而 SPWM 是在
ABC 坐标系下分相实现的;SPWM 的相电压调制波是正弦波,而 SVPWM 没有明
确的相电压调制波,是隐含的。为了揭示 SVPWM 与 SPWM 的内在联系,需求出
SVPWM 在 ABC 坐标系上的等效调制波方程,也就是将 SVPWM 的隐含调制波显
化。
为此,下面开始对其调制波函数进行了详细的推导。 由表格 14-2 我们知道
了各扇区的矢量发送顺序:
奇数区依次为:𝑈0 ,𝑈𝑘 ,𝑈𝑘+1,𝑈7 ,𝑈𝑘+1,𝑈𝑘 ,𝑈0
偶数区依次为:𝑈0 ,𝑈𝑘+1,𝑈𝑘 ,𝑈7 ,𝑈𝑘 ,𝑈𝑘+1,𝑈0
利用空间电压矢量近似原理,可总结出下式:
公式 14-19 空间电压矢量近似原理
STM32 技术开发手册
www.ing10bbs.com
𝑘𝜋
𝑇
3
[ 𝑘 ] = m𝑇𝑠 [
𝑇𝑘+1
(𝑘 − 1)𝜋
sin
3
sin
𝑘𝜋
cos 𝜃
3
][
]
(𝑘 − 1)𝜋 sin 𝜃
− cos
3
− cos
式中 m 仍为 SVPWM 调制系数,利用以上各式就可得到在第Ⅰ扇区的各相
电压平均值:
公式 14-20 第Ⅰ扇区的各相电压平均值
𝑈𝑑𝑐
𝑇0 𝑇4 𝑇6 𝑇7 𝑇7 𝑇6 𝑇4 𝑇0
(− + + + + + + − )
𝑇𝑠
2
2
2
2
2
2
2
2
𝜋
√3
=
|𝑈𝑟𝑒𝑓 | cos (𝜃 − )
2
6
𝑈𝑑𝑐
𝑇0 𝑇4 𝑇6 𝑇7 𝑇7 𝑇6 𝑇4 𝑇0
𝑈𝑏 (θ) =
(− − + + + + − − )
𝑇𝑠
2
2
2
2
2
2
2
2
3
𝜋
= |𝑈𝑟𝑒𝑓 | sin (𝜃 − )
2
6
𝑈𝑑𝑐
𝑇0 𝑇4 𝑇6 𝑇7 𝑇7 𝑇6 𝑇4 𝑇0
𝑈𝑐 (θ) =
(− − − + + − − − )
𝑇𝑠
2
2
2
2
2
2
2
2
𝜋
√3
=
−
|𝑈
|
cos
(𝜃
−
)
{
2 𝑟𝑒𝑓
6
𝑈𝑎 (θ) =
同样可以推导出其它扇区的调制波函数,其相电压调制函数如下:
公式 14-21 扇区相电压调制函数
𝜋
𝜋
4𝜋
√3
|𝑈𝑟𝑒𝑓 | cos (𝜃 − )
(0 ≤ 𝜃 < 或𝜋 ≤ 𝜃 < )
2
6
3
3
3
𝜋
2𝜋 4𝜋
5𝜋
𝑈𝑎 (θ) =
或
≤𝜃< )
|𝑈 | cos 𝜃
( ≤𝜃<
2 𝑟𝑒𝑓
3
3
3
3
𝜋
2𝜋
5𝜋
√3
cos
+
≤
𝜃
<
𝜋或
≤ 𝜃 < 2𝜋)
|𝑈
|
(𝜃
)
(
𝑟𝑒𝑓
{2
6
3
3
2
𝑈𝑏 (θ) = 𝑈𝑎 (θ − 𝜋)
3
4
(θ) = 𝑈𝑎 (θ − 𝜋)
𝑈
𝑐
{
3
其线电压的调制波函数为:
公式 14-22 扇区线电压的调制波函数
STM32 技术开发手册
www.ing10bbs.com
𝑈𝑎𝑏 (θ) = 𝑈𝑎 (θ) − 𝑈𝑏 (θ) = √3|𝑈𝑟𝑒𝑓 | sin (𝜃 +
2𝜋
)
3
2
𝑈𝑏𝑐 (θ) = 𝑈𝑎𝑏 (θ − 𝜋)
3
4
(θ) = 𝑈𝑎𝑏 (θ − 𝜋)
𝑈
𝑐𝑎
{
3
从相电压调制波函数公式 14-21 来看,输出的是不规则的分段函数,为马鞍
波形,见图 14-12。
图 14-12 相电压的马鞍波形
从线电压调制波函数公式 14-22 来看其输出的则是正弦波形,见图 14-13。
STM32 技术开发手册
www.ing10bbs.com
图 14-13 线电压的正弦波形
14.3 FOC
FOC——Field Oriental Control,磁场定向控制,又称“矢量控制”。
14.3.1 FOC 控制背景
在六步换向法中,定子磁场扭矩只有六个方向,见图 14-14。
STM32 技术开发手册
www.ing10bbs.com
图 14-14 六步换向法
转子在 60°范围内受力方向始终不变,见图 14-15,造成定子磁场形成的扭
力与转子力臂轴的夹角不能维持在 90 度,而且转矩力度也没有精确控制,这样
的控制方法不利于提高电机效率及动态响应。
图 14-15 六步换向法转子受力
FOC 的概念就是,让定子磁场形成的扭力始终与转子力臂轴相垂直,也就是
说,站在转子的角度,有个力量在力臂垂直的角度始终朝一个方向推动它,见图
14-16。
STM32 技术开发手册
www.ing10bbs.com
图 14-16 FOC 控制转子受力
14.3.2 FOC 概述
磁场定向控制(FOC)方法:将某一磁通量(转子、定子或气隙)作为创建
另一磁通量参考坐标系的基准,目的是退去定子电流转矩分量和励磁分量的耦合。
去耦可以简化对复杂三相电机的控制,从而能像以单独励磁控制直流电机那样控
制三相电机。这意味着电枢电流负责转矩的产生,励磁电流负责磁通的产生。例
如可以将转子磁通作为定子和气隙磁通的参考坐标系。
坐标变换(如 Clarke 变换、Park 变换及其反变换)通常称为解耦。该方法是
以电机在转子旋转坐标系下的方程为基础的。当从定子静止坐标系变换到转子旋
转坐标系时,需要确定转子的位置,这个位置可以通过传感器测量或利用无传感
器控制等其他方法估计来确定。
FOC 就是把定子电流分解为与转子磁链同相的直轴分量及正交的交轴分量,
然后“随心所欲”的控制;所以准确叫法为“转子磁链定向控制”,它需要将定
子的静止量(电流)所在的静止坐标系转换到与转子磁链定向的同步旋转坐标系,
即选用的是转子坐标系,所以,这种控制技术至少需要两次坐标变换。还有一种
控制选用定子坐标系,叫定子磁链定向控制,也就是所谓的直接转矩控制(DTC)。
FOC 控制方式的特征是:它把电机(ACIM、PMSM)解析成直流电机一样的
转矩发生机构,按照磁场与其正交电流的积就是转矩这一基本的原理,从理论上
将电动机的一次电流分离成为建立磁场的励磁分量和与磁场正交的产生转矩的
STM32 技术开发手册
www.ing10bbs.com
转矩分量,然后进行控制。其控制思想就是从根本上改造电机,改变其产生转矩
的规律,设法在普通的三相电动机上模拟直流电动机控制转矩的规律。
关于 FOC 控制与 SVPWM 的异同点参考表格 14-8,实际上,FOC 具体实施
的最后一步需要用到 SVPWM。
表格 14-8 FOC 与 SVPWM 比较
FOC 与 SVPWM 的相同点与不同点
在负载、电压、电流等条件稳定的情况下,两者输出均为三相正弦
相同的
波电压,相电流也同样是正弦波。
FOC 以磁场定向为控制依据,通过 PID 运算,使磁场作用力方向始
终跟踪转子位置,三相输出电压的相位、角度在负载突变情况下不
不同点
一定和转子位置同步;而 SVPWM 只是根据转子位置设定三相正弦
波的输出电压,电压的相位、角度始终与转子同步。
备注:三相电机的正弦波,是指电机相线间的波形,而不是相线对地的波形。
14.3.3 矢量控制(FOC)原理
矢量控制基本思想是在普通的三相交流电机上设法模拟直流电机转矩控制
的基本规律,在磁场定向坐标上,将电流矢量分解成产生磁通的励磁电流分量𝒊𝒅
(d 是 direct 的缩写,意思是直接的;d 轴中文翻译为直轴)和产生转矩的转矩电流分量
𝒊𝒒(q 是 quadrature 的缩写,意思是垂直的、正交的、90 度的;q 轴中文翻译为交轴),并使
两分量互相垂直、互相独立,分别进行调节控制。从原理和特性上看,交流电机
的转矩控制与直流电机几近相似。因此,矢量控制的关键仍是对电流矢量的幅值
和空间位置(频率和相位)的控制。
矢量控制既需要控制定子电流的幅值,又需要控制定子电流向量的相位。在
永磁同步电机矢量控制系统中,转子磁极的位置用来决定逆变器的触发信号,以
保证逆变器输出频率始终等于转子角频率。因此,永磁同步电动机的 FOC 控制属
于自控式运行的矢量控制。三相电流𝑖𝑎 ,𝑖𝑏 ,𝑖𝑐 经过由三相静止坐标系 abc 轴到
两相静止坐标系αβ轴,再由两相静止坐标系到两相旋转坐标系dq轴的变换,并规
定d轴沿着转子磁链的方向,则无刷直流电机就变成了由励磁电流分量𝑖𝑑 和转矩
电流分量𝑖𝑞 分开控制的直流电动机。一定的转速和转矩对应于一定的𝒊∗𝒅 和𝒊∗𝒒 ,通
过对这两个电流的控制,使𝑖𝑑 和𝑖𝑞 跟踪指令值𝑖𝑑∗ 和𝑖𝑞∗ ,便实现了电动机的转矩和
STM32 技术开发手册
www.ing10bbs.com
转速的控制。按照直流电动机的控制方法,求得控制量后,再经过坐标反变换,
就能控制电动机的电流,进而控制电机的转速和转矩,对电动机的控制转为对转
子磁链参照系下的直流电机的控制。
进行坐标变换的是电流(代表磁动势)的空间矢量,所以这样通过坐标变换
实现的控制系统就叫做矢量控制系统(Vector Control System)。
14.3.4 矢量控制综述
矢量控制的实现步骤总结如下:
1) 测量 3 相定子电流𝑖𝑎 ,𝑖𝑏 ,𝑖𝑐 ,这三个电流有下面关系:
公式 14-23 3 相定子电流关系式
𝑖𝑎 + 𝑖𝑏 + 𝑖𝑐 = 0
所以有些控制系统就只测量两相电流,根据上式求出第三相电流。
2) 将 3 相电流变换至 2 轴系统。该变换将得到变量𝑖𝛼 和𝑖𝛽 ,它们是由测得
的𝑖𝑎 ,𝑖𝑏 ,𝑖𝑐 值变换而来。从定子角度来看,𝑖𝛼 和𝑖𝛽 是相互正交的时变电
流值。
3) 按照控制环上一次迭代计算出的变换角,来旋转 2 轴系统使之与转子磁
通对齐。𝑖𝛼 和𝑖𝛽 变量经过该变换可得到𝑖𝑑 和𝑖𝑞 。𝑖𝑑 和𝑖𝑞 为变换到旋转坐标
系下的正交电流。在稳态条件下,𝑖𝑑 和𝑖𝑞 是常量。
4) 误差信号由𝑖𝑑 、𝑖𝑞 的实际值和各自的参考值(目标值)𝑖𝑑∗ 和𝑖𝑞∗ 进行比较而
获得。
◼
𝒊𝒅 的参考值控制转子磁通。
◼
𝒊𝒒 的参考值控制电机的转矩。
◼
误差信号是 PI(D)控制器的输入。
◼
控制器的输出为𝑉𝑑 和𝑉𝑞 ,即要施加到电机上的电压矢量。
5) 估算出新的变换角,其中𝑣𝛼 、𝑣𝛽 、𝑖𝛼 和𝑖𝛽 是输入参数。新的角度可告知 FOC
算法下一个电压矢量在何处。
STM32 技术开发手册
www.ing10bbs.com
6) 通过使用新的角度,可将 PI(D)控制器的𝑉𝑑 和𝑉𝑞 输出值逆变到静止参考坐
标系。该计算将产生下一个正交电压值𝑣𝛼 和𝑣𝛽 。
7) 𝑣𝛼 和𝑣𝛽 值经过逆变换得到 3 相值𝑣𝑎 、𝑣𝑏 和𝑣𝑐 。该 3 相电压值可用来计算
新的 PWM 占空比值,以生成所期望的电压矢量。图 14-17 显示了坐标
变换、PI(D)迭代、逆变换以及产生 PWM 的整个过程。
图 14-17 矢量控制框图
14.4 坐标变换
坐标变换理论可以降低马达方程的复杂性。
矢量控制中所用的坐标系有 2 种:一种是静止坐标系,一种是旋转坐标系。
基于定子的三相绕组构成的三相定子 abc 坐标系和由固定在 A 轴上的α轴和与之
垂直的β轴所组成的两相正交静止坐标系,与转子轴线重合的d轴及超前d轴 90°
的q轴组成的旋转坐标系。
STM32 技术开发手册
www.ing10bbs.com
14.4.1 Clarke 变换及其逆变换
三相定子坐标系 abc 与两相定子坐标系αβ之间的变换,称为 Clark 变换,也
叫 3/2 变换。
三相绕组坐标系 abc 与两相绕组坐标系αβ设定如矢量坐标系如图 14-18 所
示,A 相绕组轴线与α轴重合,β轴与α轴相互垂直,三相绕组坐标系 abc 与两相
绕组坐标系αβ都是静止坐标,分别对应的交流电流为𝑖𝑎 ,𝑖𝑏 ,𝑖𝑐 和𝑖𝛼 ,𝑖𝛽 。设三相
绕组每相有效匝数为𝑁3 ,两相绕组每相有效匝数为𝑁2 ,各相磁动势为有效匝数与
电流的乘积,其空间矢量均位于有关相的坐标轴上。由于交流磁动势的大小随时
间在变化着,图中磁动势矢量的长度也是随时变化的。
图 14-18 三相坐标系 abc 向两相静止坐标系𝛂𝛃转换
设磁动势波形是正弦分布的,当三相总磁动势与两相磁动势相等时,两套绕
组瞬间磁动势在α、β轴上的投影都应相等:
公式 14-24 3/2 变换关系式
1
1
𝑁2 𝑖𝛼 = 𝑁3 𝑖𝑎 − 𝑁3 𝑖b cos 60° − 𝑁3 𝑖𝑐 cos 60° = 𝑁3 (𝑖𝑎 − 𝑖𝑏 − 𝑖𝑐 )
2
2
STM32 技术开发手册
www.ing10bbs.com
𝑁2 𝑖𝛽 = 𝑁3 𝑖b sin 60° − 𝑁3 𝑖𝑐 sin 60° =
√3
𝑁 (𝑖 − 𝑖𝑐 )
2 3 𝑏
写成矩阵形式,为:
𝑁3
𝑖𝛼
[𝑖 ] =
𝑁2
𝛽
1
1
−
𝑖𝑎
2
2
[ 𝑖𝑏 ]
√3
√3 𝑖
− ] 𝑐
2
2
1 −
[0
考虑变换前后等幅值(使得变换中的标么量不变),在此前提下,可以证明,
匝数比应为:
公式 14-25 3/2 变换匝数比
𝑁3 2
=
𝑁2 3
结合上面两个式子,并且令𝐶3⁄2 表示从三相坐标系变换到两相坐标系的变换
矩阵,可以得到:
公式 14-26 3/2 变换矩阵
𝐶3⁄2
2 1
=
3
[0
2 1
𝑖𝛼
[𝑖 ] =
𝛽
3
[0
1
1
−
2
2
√3
√3
− ]
2
2
1
1
−
−
𝑖𝑎
𝑖𝑎
2
2
[𝑖𝑏 ] = 𝐶3⁄2 [𝑖𝑏 ]
√3
√3 𝑖
𝑖𝑐
− ] 𝑐
2
2
−
顺便在这里可以求得 Clark 逆变换,令𝐶2⁄3 表示从三相坐标系变换到两相坐
标系的变换矩阵,可以得到:
公式 14-27 2/3 变换矩阵
𝐶2⁄3
1
1
2 −
=
2
3
1
−
[ 2
0
√3
2
√3
− ]
2
STM32 技术开发手册
www.ing10bbs.com
1
1
𝑖𝑎
2 −
[𝑖𝑏 ] =
2
3
𝑖𝑐
1
−
[ 2
0
√3
𝑖𝛼
𝑖𝛼
2 [𝑖 ] = 𝐶2⁄3 [𝑖 ]
𝛽
𝛽
√3
− ]
2
这里,我们再结合公式 14-26 和公式 14-23 可以得到 Clark 变换公式:
公式 14-28 Clark 变换公式
1
𝑖𝛼
1
[𝑖 ] = [
𝛽
√3
0
2 ] [𝑖𝑎 ]
𝑖𝑏
√3
相应的 Clark 逆变换公式:
公式 14-29 Clark 逆变换公式
1
𝑖𝑎
[ ]=[ 1
𝑖𝑏
−
2
0
𝑖𝛼
√3] [𝑖 ]
𝛽
2
按照所采用的条件,电流变换阵也就是电压变换阵,同时还可证明,它们也
是磁链的变换阵。
特别的,可能在其他参考资料得到的 Clark 变换、Clark 逆变换公式跟我们推
导的不同,实际上这是由于所采用的条件不同,我们这里使用:考虑变换前后等
幅值(使得变换中的标么量不变),有部分参考资料考虑变换前后总功率不变条
件,那得到的计算系数就不同了。虽然采用不同条件推导相关公式,但是 FOC 最
终运算结果是一致的,因为 Clark 变换与逆变换是配套使用的,所以要求在整个
FOC 计算采样相同的单位。
14.4.2 Park 变换及其逆(反)变换
从静止两相正交坐标系αβ到旋转正交坐标系dq的变换,称为两相—两相旋
转变换,也称 Park 变换,简称 2s/2r 变换,其中 s 表示静止,r 表示旋转。
STM32 技术开发手册
www.ing10bbs.com
图 14-19 中绘出了αβ和dq坐标系的磁动势矢量,绕组每相有效匝数均为𝑁2 ,
磁动势矢量位于相关的坐标轴上。两相交流电流𝑖𝛼 、𝑖𝛽 和两个直流电流𝑖d 、𝑖q 产生
同样的以角速度ω旋转的合成磁动势F。由于各绕组匝数都相等,可以消去磁动
势中的匝数,直接用电流表示,即F可以直接标成𝑖𝑠 。但必须注意,这里的电流都
是空间矢量,而不是时间相量。
图 14-19 静止两相正交坐标系和旋转正交坐标系中的磁动势矢量
𝐝、𝐪轴和矢量𝐅(𝒊𝒔 )都以转速𝛚旋转,分量𝒊𝐝 、𝒊𝐪 的长短不变,相当于𝐝、
𝐪绕组的直流磁动势。
但α、β轴是静止的,α轴与d轴的夹角φ随时间而变化,因此𝑖𝑠 在α、β轴上的
分量的长短也随时间变化,相当于绕组交流磁动势的瞬时值。
由图可见,𝑖𝛼 、𝑖𝛽 和𝑖d 、𝑖q 之间存在以下关系:
公式 14-30 Park 变换
𝑖d = 𝑖α cos 𝜑 + 𝑖𝛽 sin 𝜑
𝑖q = −𝑖α sin 𝜑 + 𝑖𝛽 cos 𝜑
写成矩阵形式为:
STM32 技术开发手册
www.ing10bbs.com
𝑖𝑑
cos 𝜑
[𝑖 ] = [
− sin 𝜑
𝑞
𝑖α
sin 𝜑 𝑖α
] [𝑖 ] = 𝐶2𝑠⁄2𝑟 [𝑖 ]
cos 𝜑 𝛽
𝛽
所以,静止两相正交坐标系到旋转正交坐标系的交换矩阵,即 Park 变换矩
阵为:
公式 14-31 Park 变换矩阵
cos 𝜑
𝐶2𝑠⁄2𝑟 = [
− sin 𝜑
sin 𝜑
]
cos 𝜑
那么,旋转正交坐标系到静止两相正交坐标系的变换矩阵,,即 Park 逆变换
矩阵为:
公式 14-32 Park 逆变换矩阵
cos 𝜑
𝐶2𝑟⁄2𝑠 = [
sin 𝜑
− sin 𝜑
]
cos 𝜑
即:
𝑖α
𝑖𝑑
cos 𝜑
[𝑖 ] = 𝐶2𝑟⁄2𝑠 [𝑖 ] = [
sin 𝜑
𝛽
𝑞
− sin 𝜑 𝑖𝑑
][ ]
cos 𝜑 𝑖𝑞
同样的,电压和磁链的旋转变换矩阵与电流的旋转变换矩阵是相同的。
综上,Clark 变换和 Park 变换中各变量之间的关系见图 14-20:
STM32 技术开发手册
www.ing10bbs.com
图 14-20 坐标变换变量关系图
仔细观察上图,可以发现里边的变换公式跟我们前面理论分析的都有所不同,
可以说这两组公式都没错,主要是坐标系方向选择不同造成的,但是类似前面的
常数2⁄3选择一样的,虽然公式不同,但是因为 Clarke 及其反变换以及 Park 及其
反变换都是配合使用的,所以即使中间过程不同但是结果都是可靠的。
分析到这里,关于 FOC 控制思想应该要非常清晰才行:假设 PMSM 电机以
一个固定转速旋转,那可以通过电路采集得到电机的三相电流𝑖𝑎 ,𝑖𝑏 ,𝑖𝑐 ,通过
Clark 变换和 Park 变换之后可以得到𝑖d 、𝑖q ,特别的,这两个电流是常量,因为此
时电机转速是固定的。然后呢,我们希望把电机调到另外一个目标速度,通过理
论计算是可以得到一个目标(参考值)𝒊𝐝 、𝒊𝐪 ,如果就把这两个目标电流值与当
前𝑖d 、𝑖q 值进行比较,然后通过 PID 算法算出实际改变量,并且以电压方式输出,
即𝑽𝐝 、𝑽𝐪 。接下来一步就是计算得到新的坐标变换角,有了这个变换角后就可以
使用 Park 逆变换得到𝑣𝛼 、𝑣𝛽 ,然后在使用 Clark 逆变换可以得到𝑣𝑎 ,𝑣𝑏 ,𝑣𝑐 ,最
STM32 技术开发手册
www.ing10bbs.com
后就是根据我们上一节讲到的 SVPWM 技术,运算得到控制 3 个半桥的占空比
值。
14.4.3 PID 控制
使用三个 PID 环分别控制相互影响的三个变量。转子转速、转子磁通和转子
转矩分别由单独的 PID 模块控制。
FOC 控制存在三个相互关联的 PID 控制环,外环控制电机转速,两个内环分
别控制变换后的电机电流𝑖d 和𝑖q 。𝒊𝐝 值控制磁通,而𝒊𝐪 值控制电机转矩。
根据前面 14.3.4 小节中对 FOC 控制流程描述,FOC 控制最重要的几个技术
难点是:坐标变换、PID 算法、SVPWM 技术和新变换角(变相角度)计算,前面
三个难点我们都做了非常详细的介绍,现在还有一个待解决的就是新变换角计算,
实际上就是电机转子位置(𝛉)和速度(𝛚)获取。
14.4.4 电机转子位置检测方式
转子位置的获取有多种方法:无传感器模式、霍尔传感器模式、编码器模式。
1. 无位置传感器控制
无刷直流电机转子位置一般通过霍尔位置传感器检测,但是这样带来了增加
电机体积、增加成本、降低系统可靠性、安装困难等诸多缺点。针对这个问题,
国内外学者提出了许多有关无位置传感器无刷直流电机的位置检测方案,所谓无
位置传感器控制技术主要通过电机内容易获取的电压或电流等信号,经过一定的
算法处理,进一步得到转子位置。目前,转子位置的检测已有多种方法,其中较
为成熟有以下几类:端电压检测法、续流二极管状态检测法、反电势过零点检测
法、状态观测器法、电感法、磁链法等。
采用无位置传感器控制的无刷直流电机一般较难直接启动,因此其启动方法
始终是研究的热点和难点。
STM32 技术开发手册
www.ing10bbs.com
2. 位置传感器
位置传感器在无刷直流电机中起着测定转子磁极位置的作用,能为逻辑开关
电路提供正确的换相信息,即将转子的位置信号转化成电信号,然后去控制定子
绕组换相。位置传感器种类较多,且各具特点。目前在无刷直流电动机中常用的
有以下三种:
1) 增量式光电编码器
增量式编码器是直接利用光电转换原理输出三组方波脉冲 A、B 和 Z 相;A、
B 两组脉冲相位差 90º,从而可方便地判断出旋转方向,而 Z 相为每转一个脉冲,
用于基准点定位。
增量式光电编码器的优点是:输出两路正交脉冲数字量表示增量式位置,信
号容易处理,将光栅部件与信号处理电路装配在一起,信号经长线驱动器输出,
有较强的带负载能力和抗干扰能力,可以实现远距离传输。其缺点是:无法输出
轴转动的绝对位置信息。
增量式编码器转轴旋转时,有相应的脉冲输出,其计数起点任意设定,可实
现多圈无限累加和测量。编码器轴转一圈会输出固定的脉冲,脉冲数由编码器光
栅的线数决定。当需要提高分辨率时,可利用 90°相位差的 A、B 两路信号进
行倍频或更换高分辨率编码器。
2) 光电式位置传感器
光电式传感器原理是:采用 3 个相隔 120°的光电管作位置检测元件,一个
带 180°(电角度)缺口的跟踪转子(缺口数等于电机的极对数),二者组成光电
位置传感器。3 个光电管的位置在空间上互差 120°,通过电子变换电路从这 3
个元件可获得 3 个时间上相差 120°,宽度为 180°的位置信号 A、B、C。
3) 霍尔位置传感器
霍尔元件是一种常用的磁敏元件,在霍尔开关元件的输入端通入控制电流,
当霍尔元件受外磁场的作用时,其输出端会感应出电压信号,当没有外界磁场作
用时,其输出端没有电压信号。在电机非负载轴边安装一个小定子与一个小转子
(跟踪转子),小定子固定在电机端部,在小定子内圆上互隔 120°安装 3 个霍
尔元件,而小转子同心安装在电机转子的延伸轴上,同转子一起旋转,小转子表
STM32 技术开发手册
www.ing10bbs.com
面上安装有同电机转子相同极对数的永磁材料,并在安装时与电机转子的磁极轴
相对齐,这样小转子的磁极位置就直接反映了电机转子的磁极位置并在霍尔元件
上感应出相应的状态信号。
有位置传感器可以非常方便获取得到转子位置,程序处理方法也相对简单。
无传感器模式就需要结合电机参数进行大量计算才能得到。下面从理论上分析一
个可行的无传感器模式获取转子位置信息的方法。
14.5 PMSM 电机的无传感器 FOC 控制
14.5.1 电机模型
设实验中用到的无刷直流电机三相对称,忽略凸极效应;定子绕组为三相星
型接法,每相绕组具有相同的电阻值和电感值(包括自感和互感);每相绕组可以
等效为电阻、电感和反电动势串联而成,见图 14-21。
图 14-21 电机单相模型
在该电机模型中,输入电压可由计算得到。
公式 14-33 数字化电机模型
𝑣𝑠 = R𝑖𝑠 + 𝐿
其中:
𝑑
𝑖 + 𝑒𝑠
𝑑𝑡 𝑠
STM32 技术开发手册
www.ing10bbs.com
𝑖𝑠 :电机电流矢量
𝑣𝑠 :输入电压矢量
𝑒𝑠 :反电动势矢量
R:绕组电阻
L:绕组电感
𝑇𝑠 :控制周期
求解𝑖𝑠 可得到电机电流:
公式 14-34 电机电流
𝑑
𝑅
1
𝑖𝑠 = (− ) 𝑖𝑠 + (𝑣𝑠 − 𝑒𝑠 )
𝑑𝑡
𝐿
𝐿
在数字域中,该方程式为:
𝑖𝑠 (𝑛 + 1) − 𝑖𝑠 (𝑛)
𝑅
1
= (− ) 𝑖𝑠 (𝑛) + (𝑣𝑠 (𝑛) − 𝑒𝑠 (𝑛))
𝑇𝑠
𝐿
𝐿
求解𝑖𝑠 :
𝑅
𝑇𝑠
𝑖𝑠 (𝑛 + 1) = (1 − 𝑇𝑠 ∙ ) 𝑖𝑠 (𝑛) + (𝑣𝑠 (𝑛) − 𝑒𝑠 (𝑛))
𝐿
𝐿
或者
𝑖𝑠 (𝑛 + 1) = 𝐹 ∙ 𝑖𝑠 (𝑛) + 𝐺 ∙ (𝑣𝑠 (𝑛) − 𝑒𝑠 (𝑛))
其中:
𝑅
𝐹 = (1 − 𝑇𝑠 ∙ )
𝐿
𝐺=
𝑇𝑠
𝐿
可以看到想要计算 F 和 G 参数需要三个参数:𝑇𝑠 、R 和 L,对于指定的电机
都是有一个常量值,当然一般不同电机的值也是不一样的,所以需要实际用仪器
测量得到。特别注意,这里的 R 和 L 都是单相的电阻和电感,实际测量中经常是
测量线与线之间的电阻和电感,带入公式计算时需要除以 2 处理。
STM32 技术开发手册
www.ing10bbs.com
14.5.2 电流观测器
位置和速度估算器是基于电流观测器而构建的。该观测器是电机的一个数字
化模型,由公式 14-35 表征。
公式 14-35 反电动势估算模型
𝑑
𝑅
1
𝑖𝑠 = (− ) 𝑖𝑠 + (𝑣𝑠 − 𝑒𝑠 )
𝑑𝑡
𝐿
𝐿
该数字化模型对硬件使用了软件表达方式,然而,为了使测量电流和估算电
流相匹配,数字化电机模型需要使用如图 14-22 的闭环控制来进行校正。在图
14-22 中,z 为输出校正因子电压。
图 14-22 电流观察器框图
考虑用两种方式表示电机,一种是硬件方式(阴影区),一种是软件方式,
两个系统中使用相同的输入电压𝑉𝑠 ,使用模型中的估算电流(𝑖𝑠∗ )来匹配测量电
流(𝑖𝑠 ),我们假设数字化模型的反电动势(𝑒𝑠∗ )与电机的反电动势(𝑒𝑠 )相同。
滑动模式控制器(Slide Mode Controller,SMC)用来对数字化电机模型进行
补偿。SMC 包含一个求和结点,用于计算电机上的测量电流与数字化电机模型上
的估算电流之差的符号。计算出的差值符号(+1 或-1)乘以 SMC 增益(K)。SMC
控制器的输出就是校正因子(z)。该增益被加到数字化模型的电压项,在每一个
控制周期中都重复执行该过程直到测量电流(𝑖𝑠 )和估算电流(𝑖𝑠∗ )的差值为零
(即,直到测量电流与估算电流相同为止)。
STM32 技术开发手册
www.ing10bbs.com
14.5.3 反电动势估算
对数字化模型进行补偿之后,电机模型的输入电压(𝑣𝑠 )和电流(𝑖𝑠∗ )与数
字化电机模型中的值相同。一旦对数字化模型补偿完后,下一步就要通过对校正
因子(z)滤波来估算反电动势(𝑒𝑠∗ ),如图 14-23 所示。随后反电动势的估算值
(𝑒𝑠∗ )反馈给数字化电机模型,以在每个控制周期之后对变量𝑒𝑠∗ 进行更新。𝑒𝛼 和
𝑒𝛽 值(𝑒𝑠 的矢量分量)用于估算𝜃 ∗ 。
图 14-23 反电动势估算模型
14.5.4 反电动势滤波
使用给出的一阶低通滤波器提供滤波功能。
公式 14-36 一阶数字低通滤波器
y(𝑛) = y(𝑛 − 1) + T2π𝑓𝑐 ∙ (𝑥 (𝑛) − 𝑦(𝑛))
为了对 z 滤波以获取𝑒 ∗ ,具体代入公式可以得到:
1
𝑒(𝑛) = e(𝑛 − 1) + (
) 2π𝑓𝑐 ∙ (𝑧(𝑛) − 𝑒(𝑛))
𝑓𝑝𝑤𝑚
其中:
𝑒(𝑛):下一个估算的反电动势值
e(n-1) :上一个估算的反电动势值
𝑓𝑝𝑤𝑚 :计算数字滤波器时的 PWM 频率
STM32 技术开发手册
www.ing10bbs.com
𝑓𝑐 :滤波器的截止频率
z(n):不可滤波的反电动势,为滑动模式控制器的输出
滤波器的截止频率值取决于滑动模式控制器增益的选择,通过尝试对增益进
行调节来设定该值。
第一个滤波器的输出用于两个模块中。第一个模块就是模型自身,用来计算
下一个估算电流(𝑖𝑠∗ ),以及估算的 theta(𝜃 ∗ )。第二个一阶滤波器用来计算来自
电机模型的较为平滑的信号。
14.5.5 反电动势和转子位置的关系
对反电动势进行第二次滤波时,就可计算出θ值。𝑒𝑠 和θ的关系可使用图
14-24 中的图形来说明。
图 14-24 反电动势和𝛉的关系
图中显示了一个与反电动势矢量分量(𝑒𝛼 和𝑒𝛽 )和转子角度(θ)相关的三
角函数。根据反电动势矢量分量计算出来的反余切用来计算θ。说明了该函数如
何用软件实现:
公式 14-37 𝛉计算
STM32 技术开发手册
www.ing10bbs.com
𝜃 = 𝑎𝑟𝑐𝑡𝑎𝑛
𝑒𝛼
𝑒𝛽
在实际实践中使用一种名为 CORDIC(Coordinate Rotation by DIgital Computer,
坐标旋转数字计算机)的数字迭代算法,该算法速度快,占用的内存比浮点算法
还少。
14.5.6 速度计算
由于在计算θ期间应用了滤波函数,所以在使用计算得到的角度给电机绕组
通电之前需要对相位进行补偿。θ补偿量取决于θ的变化速率或电机的速度。θ补
偿由以下两步组成:
首先,通过未补偿的θ来计算电机的速度。然后对计算得到的速度进行过滤,
并计算补偿量,如图 14-25 所示。
图 14-25 速度计算框图
通过将 m 次采样得到的每相邻两个θ值的差进行累加,然后与一个常量值相
乘,即可得到速度值。本应用笔记中使用的计算速度的公式如所示。
公式 14-38 速度计算
𝑚
𝜔=∑
(𝜃𝑛 − 𝜃𝑛−1 ) ∙ 𝐾𝑠𝑝𝑒𝑒𝑑
𝑖=0
其中:
Omega(𝜔):电机角速度
STM32 技术开发手册
www.ing10bbs.com
Theta(𝜃𝑛 ):θ当前值
PrevTheta(𝜃𝑛−1 ):上一个θ值
𝐾𝑠𝑝𝑒𝑒𝑑 :期待的速度范围的放大因子
m:累加的θ增量数
为确保在速度计算时获得较为平滑的信号,可在 Omega(𝜔∗ )上施加一个一
∗
阶滤波器,以获得 FilteredOmega(𝜔𝑓𝑖𝑙𝑡𝑒𝑟𝑒𝑑
)值。一阶滤波器的拓扑与用于反电动
势滤波的滤波器相同。
14.5.7 相位补偿
计算出未补偿的 theta 和滤波后的速度后,必须除去滤波过程所产生的延迟。
这可通过将一个由电机速度来决定的补偿偏置 theta(_OffsetTheta)添加到未补偿
θ中来实现,如下所示:
公式 14-39 相位补偿
∗
𝜃𝑐𝑜𝑚𝑝
= 𝜃 ∗ + 𝜃𝑜𝑓𝑓𝑠𝑒𝑡
建议精确地调节相位补偿以适用于任何特定电机。在本应用中,相位补偿被
划分给 8 个速度范围。每个速度范围有其自己的变化斜率和恒定相位补偿分量。
列示了方程式中使用的相位补偿公式。
表格 14-9 相位补偿公式
速度范围
(FilteredOmega)
相位补偿公式
(__OffsetTheta)
最小值 - 0.09375
𝜃𝑜𝑓𝑓𝑠𝑒𝑡 = 1
0.09375 - 0.1875
𝜃𝑜𝑓𝑓𝑠𝑒𝑡 = 4𝜔𝑓𝑖𝑙𝑡𝑒𝑟𝑒𝑑 + 0.375
0.1875 - 0.2876
𝜃𝑜𝑓𝑓𝑠𝑒𝑡 = 2𝜔𝑓𝑖𝑙𝑡𝑒𝑟𝑒𝑑
0.2876 - 0.38095
𝜃𝑜𝑓𝑓𝑠𝑒𝑡 = 𝜔𝑓𝑖𝑙𝑡𝑒𝑟𝑒𝑑 + 0.2876
0.38095 - 0.475
𝜃𝑜𝑓𝑓𝑠𝑒𝑡 = 𝜔𝑓𝑖𝑙𝑡𝑒𝑟𝑒𝑑 + 0.2876
STM32 技术开发手册
www.ing10bbs.com
0.475 - 0.568
𝜃𝑜𝑓𝑓𝑠𝑒𝑡 = 0.5𝜔𝑓𝑖𝑙𝑡𝑒𝑟𝑒𝑑 + 0.5251
0.568 - 0.756
𝜃𝑜𝑓𝑓𝑠𝑒𝑡 = 0.25𝜔𝑓𝑖𝑙𝑡𝑒𝑟𝑒𝑑 + 0.6671
0.756 - 最大值
𝜃𝑜𝑓𝑓𝑠𝑒𝑡 = 0.125𝜔𝑓𝑖𝑙𝑡𝑒𝑟𝑒𝑑 + 0.748275
14.5.8 流程图
FOC 算法执行流程见图 14-26。
STM32 技术开发手册
www.ing10bbs.com
图 14-26 FOC 算法执行流程
14.5.9 不同电流模式下的控制策略
在矢量控制技术中,电流的控制模式是多种多样的,电流的控制模式和转子
的几何结构影响着永磁同步电动机的性能和变换器的容量。一般 PMSM 的控制
STM32 技术开发手册
www.ing10bbs.com
策略是由电机转矩和电流之间的线性度、控制过程中电机端电压的允许变化幅度、
功率因数和电枢反应的去磁效应等因素来综合决定的。永磁同步电动机用途不同,
电流矢量的控制方法也不相同。可采用的控制方法主要有:最大转矩电流比控制、
id =0 控制、弱磁控制、cosφ=1 控制等。
1. 最大转矩电流比控制
这种控制方式也称单位电流输出最大转矩的控制,该方法在电机输出转矩满
足要求的条件下,通过调节电机 d 轴、q 轴的电流比例,使电机在输出额定转矩
的情况下需要输入的定子电流最小。该方法降低了逆变器的负担,减小了电机的
铜耗,有利于逆变器开关器件工作,降低了控制系统成本。其缺点是电机输出转
矩越大,功率因数越低。
2. id*=0 控制
励磁电流 id=0 控制下,is= iq,使定子电流中只有转矩电流分量,电机电流全
部用来产生转矩,电磁转矩表达式简化为:
公式 14-40 电磁转矩表达式
𝑇𝑒 =
3
𝑃Ѱ 𝑖
2 𝑛 𝑓𝑞
上式中电磁转矩和𝑖𝑞 成线性关系,电磁转矩得到了完全解耦,输出转矩控制
简单,性能好。对于表贴式永磁同步电机,𝑖𝑑 =0 控制就是最大转矩电流比控制,
此时单位定子电流可以获得最大转矩。但对于具有凸极效应的永磁同步电机而言,
该方法没有充分利用电机的磁阻转矩,没有完全发挥电机的转矩输出能力。
3. 弱磁控制
根据交流电机变频调速原理,基速以下为恒转矩运行,基速以上为恒功率运
行。对无刷直流电机来说,恒转矩时保证电机激磁磁场强度不变,采用调节电枢
电压方式实现调压调速,即在无刷直流电机功率电子开关导通时通过脉宽调制
(PWM)来实现。为获得平稳的电磁转矩,此种情况下要求电流与反电势同相,
即控制电枢电流超前电枢反电势的夹角为零。当转速达到基速时,PWM 的占空
比为 1,电枢电压达到最大。随着转速的升高,电机反电势增大,造成电枢电流
STM32 技术开发手册
www.ing10bbs.com
减小。极端情况是反电势等于电枢电压,电枢电流为零,无法产生电磁转矩。为
了在基速以上端电压不变条件下保持一定电枢电流产生所需的转矩,必须设法使
反电势不随转速上升,需要减小电机磁通,即实现弱磁控制。
当无刷直流电机端电压到达极限时,无刷直流电机的励磁磁势由永磁体产生
而无法调节,要想提高无刷直流电机的转速,只能通过调节定子电流,增加直轴
去磁分量来维持高速运行时电压方程的平衡,下面来推导无刷直流电机弱磁扩速
的方法。忽略定子电阻的影响,无刷直流电机电压平衡方程可以写成:
公式 14-41 无刷直流电机电压平衡方程
2
𝑢 = 𝜔𝑟 √(𝐿𝑞 𝑖𝑞 ) + (𝐿𝑞 𝑖𝑞 + Ѱ𝑓 )
2
当u = 𝑢𝑚𝑎𝑥 时,取𝑢𝑚𝑎𝑥 = 2𝑈𝑑𝑐 ⁄𝜋,𝑈𝑑𝑐 是直流母线电压。对公式 14-41 求
解,得到:
公式 14-42 励磁电流𝒊𝒅
Ѱ𝑓
1
𝑢𝑚𝑎𝑥 2
2
𝑖𝑑 = −
+ √(
) − (𝐿𝑞 𝑖𝑞 )
𝐿𝑑 𝐿𝑑
𝜔𝑟
当电机在基速 ω1 以上,想要提高转速到 ω2,如果还是采用 id=0 或者最大转
矩电流比的控制方式,转矩将会掉落非常大。而采用弱磁控制可以提高基速以上
电机的带载能力,进一步扩大电机的转速。
4. cosφ=1 控制
该方法使电机的功率因数恒为 1,逆变器的容量得到充分的利用。由于转子
励磁不能调节,在负载变化时,转矩绕组的总磁链无法保持恒定,所以电枢电流
和转矩之间不能保持线性关系。同时,最大输出转矩小,永磁材料可能被去磁,
造成电机电磁转矩、功率因数和效率的下降。cosφ=1 的控制方法一般只对大功率
电机使用,本文中电机为 400W 表贴式永磁同步电机,所以不采用此方法。
本文选用的控制策略是采用 id=0 的控制方法,也是最大转矩电流比的控制
方法。该方法控制简单,无需电机参数,无去磁效应,输出力矩与定子电流成正
比。
STM32 技术开发手册
www.ing10bbs.com
以上是 FOC 控制原理分析,本人在编写该部分内容时参考了大量的文档资
料,这里列出两个内容丰富的文档,《直接转矩控制与矢量控制.pdf》
(附件 6),
《AN1078_PMSM 电机的无传感器磁场定向控制.pdf》(附件 7)。
最近在查找资料时候发现一篇文章,使得我不管不顾本文档的结构格式问题
也必须歪个楼把文档贴出来,文档有点长,希望大家耐心阅读:
知乎上网友问题解答:
三相对称电流通过向 dq 坐标轴上投影得到的 Id、Iq 与通过 park
变换得到的 Id、Iq 有什么区别和联系么?
问题内容:
大概翻了下导师让看的张兴的博士论文,里面全都是数学推导,很难直接从式子上联系到其
具体问题。最后我翻看电机学和电力系统分析里面涉及到的相关知识,更加弄混淆了。希望能推
荐一些其他讲解 park 变换的前世今生的资料,谢谢。学习过程中还有遇到累死的问题有:
1、三相对称电流的相量表示中的投影得到的 Id、Iq,我可以认为是分别向两个垂直方向上
的投影,得到有功和无功分量。而通过 park 变换得到的 Id、Iq 有什么具体的物理含义?我计算
一个具体的有关整流器的实例,用上面 2 种 Id 得到的有功功率不相等,相差 1.414 倍的样子。
2、看到的 park 变换的推导,好想都是数学推导,那么它为什么又叫做同步两相旋转坐标变
换?同步旋转是什么物理意义?与相量表示中无法表示出来的角频率 w 有什么联系么?
3、还有 clark 变换又叫两相静止坐标系,岂不是就是相量表示中的 dq 坐标轴的投影么?
4、最后一个问题,通过 park 变换将三相对称电流得到的直流量 Id、Iq 是不是只是单纯为
了方便控制系统的设计,其具体的物理含义都是由于数学推导发现各种关系然后赋予的?也就
是说是曲线救国的意思?那我是不是可以自己设计一个新的变换方式,得到新的物理量,然后通
过控制新的物理量达到控制原来的物理量的目的。新物理量的具体物理含义只是通过我自己设
计的变换方式的数学推导赋予的。
(之前问过这问题,很杂,很乱,导致很久没人回答,于是我就删了。结果苦了某些大侠给
我写的答案无处评论。对不起啦!!
)
知乎用户回答内容:
好久都没有认真回答过专业问题了,今天来一发。
STM32 技术开发手册
www.ing10bbs.com
电磁换能魔法师要布阵了!希望这个回答给所有没学电机的童鞋一个简单的认识,给正
在学电机的童鞋一个帮助,给学过电机的以另一个角度。本答案将不含任何公式,从本质上
切入问题。
所有的电机原理都如下图(纯 Visio 手绘,轻拍)
:
两个灰色的轮子一个是定子,一个是转子。具体哪个做定子,哪个做转子。随意。
所有电机的原理都是这么简单!真的只有这么简单,那就是:转动其中一个轮子,另一
个轮子就会跟着转。很熟悉啊有木有,就是初中物理的讲的“异性相吸”啊。可是为什么所
有教材讲的都接近于玄幻呢?因为——我国教育擅长学而不擅长教……如果能上 Youtube
或者看看有几位 IEEE 电机祖师爷写的教材,你就会发现他们写的书真的就跟连环画似的,
好亲民……而目前我还没有看到过任何一本能与之等价的中文读物……
言归正传,参考上图,磁铁都紧紧的吸在了一起,凭直觉,如果错开任何一个角度,比
如下图:
STM32 技术开发手册
www.ing10bbs.com
不稳定啊有木有!有一种如果松手肯定会发生什么的感觉啊有木有。不错!这就是力!
力就这么产生了!力乘以距离就是力矩,在这里我们就称之为转矩。如果我用手转动外面的
壳,里面的轮子如果能动肯定也会跟着转,这样电机就动起来啦~
重要的事情再重复一遍:所有电机的原理都是这样的!就是这么简单!
OK,所有交流电机都是有一个旋转的磁场带动另一个磁场使之旋转~那个旋转的磁场是
三相对称电流产生的,这个我想大家都是懂得。如果不懂,那就去看书吧,这一段教材上写
的还是蛮形象的。这个发明是意大利的一位物理学家最先提出的,后来被特斯拉发扬光大。
接下来的所有例子中,都是外面那个磁铁在转,里面那个轮子跟着动。那么问题来了,从上
面的两张图我们能得到一些什么结论呢?
1. 磁铁完全对着的时候(如第一张图)电机转不起来,对的太正了好稳定的感觉
2. 磁铁错开一点的时候(如第二张图)电机可以转起来
3. 磁铁错开太多的时候……貌似力度不够,带不起来
4. 磁铁一开始就转的特别快,而另一个转速为零,貌似也转不起来(因为刚被前面的
磁铁吸了一下,后面的磁铁就又上来了,又往后吸了一下,前功尽弃的感觉)
,所以电机就
在那一直震啊震
于是,你就会想,如果我想控制里面的转子转的漂亮,我就需要有一套有效的控制外面
的磁场的转动的方法,不能太快,不能太慢,要根据里面的情况,循序渐进的转动外面的环
环~~这就是电机控制方法。不要看公式复杂成天书,本质上就是这么简单~~
好的,感性认识建立起来了吧~现在,我们来看上面提到过的第 3 个问题,两个磁铁离
得太远不行,离得太近不行,那要成什么位置才会有最大的转矩啊~~~这就是这个问题问的
本质,
就有了 park 变换和 DQ 轴啦~~park 变换就是为了进一步探讨空间位置对于力的控制。
STM32 技术开发手册
www.ing10bbs.com
控制电机,说到底,控制的是神马?!其实就是力啊有木有,磁铁相对于转动轴的距离
是固定的,所以转矩直接正比于磁铁之间的电磁力啊(转矩等于所有力乘以距离,距离都一
样)
。那么我们谈控制其实就是在控制两个磁铁之间的力。有人会说电机另一个输出量是转
速,也许要控制。对,可是转速是表面现象,控制转速是通过控制力来间接实现的。转矩大
了自然会转的更快,转矩小了转速不就降下来了嘛~所以归根到底电机控制其实控制的就是
电磁力~就是图上显示的几对磁铁之间的力~
那么,这个力跟什么有关呢?前面我们说了,和两个内外磁铁的空间位置有关:两个磁
铁错开太少没有力,错开太多了力又不够,貌似角度错开成某个值的时候有一个最大值的感
觉。不错,这个结论的得出是在磁铁磁力不变的前提下得到的,如果磁铁的强弱是可以调节
的会发生什么?请看下图:
什么!磁铁怎么这么小?再看原来的图片是不是有一种器大活好的怀旧感?凭直觉,上
图所示的电机感觉上力量就比原来的电机弱,因为他磁铁小啊,力不从心啊有木有。现实中
磁场的强弱就是可调节的,因为外部的旋转磁场是由三相电流产生的啦,所以通过控制正弦
电流的幅值你就可以调节磁场的强弱啦~有的同学可能会有一个问题,那么对于永磁电机来
说,里面的磁铁是不是应该保持不变呀?没错,这个问题我们放一放,一会再说。
综上,我们得到了一个强有力的结论:要想控制电机转的漂亮,有两个因素要把握好:
1. 磁铁磁力的强弱
2. 内外磁铁的空间相对位置
可是这两个问题耦合在一起好难分析啊有木有,因为如果磁铁变大但空间错的很开,那
电磁力矩是变大了还是变小了啊?如果磁铁变小但是错开的又近了一些,那力矩到底是小了
STM32 技术开发手册
www.ing10bbs.com
还是大了啊!?有两个因素都在变,很难单独分析哇。于是我们就想,能不能把这两个因素
通过一种简单的方法解耦呢?这就是 Park 变换和 DQ 轴出现的大背景啦。来看下图:
你看,内部磁铁在空间有一个磁场,外部磁铁会产生一个磁场,说到底,其实力的产生
就是和这两个磁场的大小和方向有关系嘛~
所以(敲黑板状)
,所有电机,注意是所有电机(包括直流电机和任何类型电机),他们
的转矩都是正比于内外两个磁场的叉乘(如上图),就是两个磁场矢量围成的平行四边形的
面积。如果两个磁铁方向重合(如最开始的第一张图),那么平行四边形的面积就是零,没
有力。如果方向错开一点(比如最开始的第二张图)
,那么就会有力(因为平行四边形面积
不为零啦)
。
上面这个公式(电机转矩正比于转子磁场叉乘定子磁场)是电机学最最最最最本质的公
式!我不明白为什么我们的中文教科书都不详细讲这一条!!!
!
好,接着唠。平行四边形什么的最有意思啦,因为他可以被拆成矩形啊~七巧板的感觉:
STM32 技术开发手册
www.ing10bbs.com
你看,一个平行四边形就这么被拆成矩形啦。为什么这么拆?因为好看啊!你不觉得方
方的地砖才是正派么?四边形的地砖都是异端啊。上面两个图形的面积是一样的,所以分析
起来不必担心。我们把原来的 Bin 称之为 d 轴磁场(d 是 direct 的缩写,意思是直接的;d
轴中文翻译为直轴)
,把新拆出来的垂直于 Bin 的磁场称之为 q 轴磁场(q 是 quadrature 的
缩写,意思是垂直的、正交的、90 度的;q 轴中文翻译为交轴)。怎么样,其实这些原理一
开始定义的时候是很好记的!很形象的!哪有那么复杂!1929 年,帕克还在波士顿的通用电
气苦逼的拧螺丝呢,他哪会起什么复杂的名字,完全是为了自己干活好用好嘛~
看!两个磁场,Bd 和 Bq,他们的面积决定了力的大小!决定了电机的输出转矩!控制
这两个值,上面我们分析的所有情况都可以被控制啦!那么磁场是什么产生的?电流产生的。
磁场和电流的关系是什么?
磁场=电流×电感
又是高中物理啊……是啊,你工程再复杂,本质上的物理属性就是这么简单……
所以帕克就说啦,把电机现实中的三相电感如果能转换成 DQ 轴的两相电感,世界就美
好了啊。因为电流我可以测出来,我只要知道了 DQ 轴电感,那我就知道了两个磁场啦,然
后我就又知道力啦,然后我就知道了转矩啦,然后我就可以通过改变电流控制转矩啦,然后
我就可以通过转矩控制转速啦!有一种征服了全世界的感觉啊有木有。
回来了,继续答题。普通青年的思路就像刚才说的这样,有这么一条链条:
电流 → 磁场 → 力 → 转矩 → 速度
所以我们通过控制电流的 D 轴分量和 Q 轴分量就能控制矩形的两条边:Bd 和 Bq 的大小
啦,他们相乘就是转矩啊~ 通过速度和(或)位置反馈,我们就能够有效的控制外面的环环
使之带动里面的轮子转起来啦。DQ 两个分量合成的其实是一个矢量,于是乎这种方法就叫
做矢量控制(Vector Control)
,也有人称之为 FOC(Field Oriented Control),一回事。
这种普通青年的正常思路是 1970 年前后由达姆施塔特工业大学和西门子工程师提出的。
二逼青年可能会这么问:上面那个链条好繁琐,特别是在电流推磁场的过程中,要用到
派克变换,那矩阵太美都不敢看,难道就木有简单的办法吗?
有同学可能就会说,为什么不用一个磁场传感器直接测量磁场呢?这样一来我们不就直
接知道了磁场的位置和方向了嘛~~ 原则上来说确实是这样的,可是!!电机内部气隙太小,
没法安装啊衰。平时大家是怎么测量电机内部磁场的呢?有些人用一根导线在某个定子的齿
(tooth)上缠几圈,直接测量感应出来的电压不就好啦,因为电压就是磁场的微分呀。
STM32 技术开发手册
www.ing10bbs.com
这时候,两个文艺青年站出来了,说:“给你们厉害的,定子绕组本身就是一个传感器
呀”
。众人仿佛发现了什么,一阵蛋疼,纷纷晕倒:
“卧槽!我们为什么就没有想到!!
!!”原
来,定子电压直接积分不就是磁通嘛……没办法,晚一步就是晚一步,那两个文艺青年将这
种方法注册了专利:直接转矩控制(DTC)
,众人再次晕倒。后来 ABB 公司直接买断了这份专
利,众人纷纷散场:
“算了,还是用矢量控制吧”。
于是,西门子和 ABB 两大巨头的带领下,电机控制分为了剑宗和气宗,哦不对,是矢量
控制和直接转矩控制的阵营。矢量控制要走一个长长的链条,所以计算量大。直接转矩控制
简单有效,可惜被买断了……于是众人只能继续用矢量控制……
看到这里,基本上就明白了 DQ 轴和帕克变换了吧~
我们的特别多的中文教材和很多教授(甚至诸多老教授)喜欢把 DQ 变换说成“把交流
电机变换为直流电机分析”的过程,其实这个类比很容易把学生给带迷糊了,尤其是第一次
连直流电机都没弄特别清楚的新生。DQ 变换之后的磁场分析和直流电机的磁场可完全不是
一个问题,只能类比,只能说长得像,千万不能直接套着用。
又是新的一天,继续更新。其实以上基本上能够讲明白 DQ 轴的由来,电机基本原理和
电机控制的基本原理。如果你想了解到这里,以上足够了。以下将深入一些,涉及到异步、
永磁电机的差异,弱磁控制的本质原因和电动汽车电机的自身特点等问题,都是比较热门的
问题,不过原理上还是比较简单的。
就像上面说的,电机转动的本质原因是转子磁场和定子磁场的相互作用,再说白一点,
就是一个磁铁吸着另一个磁铁,使得另一个磁铁所在的转动件转圈:
这样的话就需要两个磁铁,一个主动旋转的磁铁,一个被动被吸附的磁铁。主动旋转的
磁铁我们知道是三相对称电流所产生的旋转磁场。那么被动吸附磁铁是谁?对于同步电机来
说,被动吸附的磁铁是镶嵌在转子上的永磁体或他励线圈产生的“人造永磁体”;对于异步
电机来说好像没有被吸附的永磁铁啊。对,因为异步电机被吸附的永磁体也是旋转磁场产生
的。纳尼?理解不能啊!不要着急,异步电机的转子是个鼠笼,他经历了一些奇妙的事情:
STM32 技术开发手册
www.ing10bbs.com
外面旋转磁场旋转 → 切割了鼠笼的金属导体 → 金属导体产生了电动势 → 由于鼠
笼短接所以金属导体就产生了感应电流 → 感应电流产生了一个磁场(转子磁场)
所以,对于异步电机来说,他的“被吸附的永磁体”就这么产生了!异步电机的“被吸
附磁体”是自己感应出来的!所以异步电机(Asynchronous Motor)的英文名叫做感应电机
(Induction Motor)
。好像有点熟悉?没错,这不就是个变压器嘛~~ 原边产生了一个磁场
然后副边感应了一个电流啊,电能就这么传输过去了。只不过普通的变压器是静态的,这个
变压器是会动的!也就是说这个变压器的铁芯不是一整块,而是两块拼起来的,所以副边缠
绕的那块铁芯就跟着原边缠绕的那块铁芯转起来啦。正因为这样,异步电机的等效电路图其
实就是一个变压器,只不过二次侧的有效电阻是一个和转差率相关的动态元件。
如果你还是对异步电机的内外磁铁不能够明白,我就再放一个实际中的异步电机定子转
子磁场分布图:
那些线条是磁力线,定子的磁场和转子的磁场形成了一个角度,其实就相当于两个磁铁
了,跟前面演示的磁铁图没有任何区别。
这样一来,两种电机的原理基本上就能够理清啦。
终于回来了,继续更新。今天主要说感应电机和永磁电机的磁场控制区别。再次复习一
下重点:电机转矩正比于转子磁场和定子磁场的叉乘,即等价于转子磁场和定子磁场围成的
平行四边形的面积,亦正比于 DQ 轴磁场围成的矩形面积。无论是感应电机还是永磁电机,
控制转矩都是通过控制 DQ 矩形面积来实现的,这一点我们前面说过。可是,由于感应电机
和永磁电机的构造有区别,他们的磁场控制策略也是有区别的。这一点尤其重要,因为感应
电机和永磁电机是现在工业中应用最最广泛的两种电机。这两种电机就像交流电和直流电一
样,就像直接转矩控制和矢量控制一样,几乎平分了市场,各有优缺点。无论你面试特斯拉,
福特还是通用、丰田全电动或混动部,这道面试题是肯定躲不过的。目前所有混合动力汽车
的电机都是无刷永磁电机,而目前纯电动车的电机主要是感应电机(因为纯电动特斯拉占了
很大比例,而特斯拉全部都是感应电机)
。
对于感应电机来说,转子磁场和定子磁场都是由三相电流来决定的,因为感应电机的转
子磁场是感应出来的嘛;而对于永磁电机来说,转子磁场有相当一部分是由镶嵌的永磁体决
STM32 技术开发手册
www.ing10bbs.com
定的,而且这部分磁场是几乎不变的。对于感应电机来说,转子磁场和定子磁场都可以灵活
自如的调节;而对于永磁电机来说,转子磁场的相当一部分是改变不了的(永磁体产生的磁
场是恒定不变的)
。通俗点讲就是:感应电机的两个磁铁都可以变大变小;而永磁电机的转
子侧磁铁基本上改变不了。如下图:
上图中我已经将磁场分解成了垂直的两个磁铁啦,其实和前面斜着摆的是一回事。这两
个磁铁就分别是总的 DQ 轴的磁场啦。由于已经分解了,那么这两个磁场总是垂直的嘛,看,
这下干活是不是方便多啦。感觉上是不是感应电机的劲儿大一些呢?因为人家磁铁好大的感
觉哈。对,如果真的是按图中所画那样,这个感应电机的功率确实比下面的永磁电机高。所
以大家现在还在研究新的永磁体好让图中永磁的转子侧那块的磁铁再大一些嘛~
对于感应电机来说,两个磁铁就像是大圣的如意金箍棒,要大便都大,要小便都小,所
以很灵活呀有木有!但是控制起来很麻烦,因为两边都要操心,累啊!但是真的灵活啊,特
别是有了电力电子技术,那开关啪啪啪,一秒钟就能啪几千次,控制的超级精确呀有木有,
所以控制这一块也还好,并没有那么夸张啦。
STM32 技术开发手册
www.ing10bbs.com
对于永磁电机来说,转子磁铁就是一坨安安静静的永磁体,人家基本上是固定的,而我
只要变动 Q 轴磁场就可以轻松的实现控制,简单省心,尤其是启动的时候,哎妈呀,那真是
简单有效(一会会讲)
。但是还是我的那句话,有利就有弊,正是因为永磁体的磁场不变性,
给后来的控制引入了大麻烦了。
启动的时候,看下图:
从上图看来,感应电机的磁场是两条边同时长的,而永磁电机呢?人家转子侧本来就有
很牛逼的永磁磁场,所以只需要一个 Q 边就可以围出来一个大大的面积。上面两个矩形的面
积是一样的,可是样子却完全不同的。这意味着,此时两种电机的力矩虽然相同,可是消耗
的电流不一样呀。感应电机的两个磁场都要由电流提供,而永磁电机只需要电流提供一个 Q
轴磁场就可以啦。所以在启动的时候,永磁电机的效率狂甩感应电机几条街啊。没办法,什
么叫做天生条件好,你懂了吧,天生大长腿,自然跑的美。但是这个天生的优势在弱磁控制
中却成了永磁电机的致命弱点。这部分下次再更。
STM32 技术开发手册
www.ing10bbs.com
回来啦!今天我们讲解弱磁控制!这个问题是电机的高阶领悟!能修炼到这一层接触这
个问题,说明你已经很厉害啦,这意味着你已经是研究生以上学历的电气工程学者或者是资
深工程师啦。什么是弱磁控制?英文名字叫做 Flux-Weakening Control,直译是“弱磁控
制”
,但我们有些中文学者将这个翻译成了:
“弱磁扩速”!我觉得这个翻译简直神了~~ 因为
弱磁控制就是为了扩速~汉语的博大精深终于体现出来啦。扩速,就是扩展速度,把电机的
运行速度继续向上提。比如,这台电机的额定速度是 3000 转/分,我想提到 5000 转或 7000
转,怎么办?继续加电压?绝缘会击穿吧~ 继续加电流?貌似和加电压是一回事呀~ 那怎
么办?这就是弱磁控制,这是一个“电机到达额定转速之后继续扩速改怎么办”的问题。有
没有办法呢?有的。当然一开始是没有的,一开始没有电力电子技术的时候这种事情想都不
敢想,继续加电压会电弧闪闪放光芒的。后来有了电力电子技术,弱磁扩速才慢慢出现了。
正式开讲弱磁控制之前,我先帮大家回忆一下电机的转矩-速度曲线,这个很经典:
上图第一张图是转矩-转速曲线,第二张图是功率-转速曲线。上电机课程的时候,老师
都会讲:电机有两个状态:一开始是恒转矩,后来是恒功率。但是有些倒霉老师不讲为什么,
坑爹啊……
我来告诉为什么,还是因为一个高中物理公式:
功率=转矩×转速
一开始,电机以最大转矩运行,以最快的方式把电机的速度带上来。这个过程被称为恒
转矩控制。这个过程中,功率从零慢慢增大,就是我前面说的“循序渐进”的启动过程。但
是力量是一直保持不变的。有些同学会问,为什么这个时候不是保持最大功率呢?因为这时
候转速很低啊,要是保持最大功率,参考上面的公式,你觉得你出的力的多大啊骚年。臣妾
力量不足啊。所以这个时候功率慢慢长上来是比较合理的。电机说:我功率有最大值,转矩
STM32 技术开发手册
www.ing10bbs.com
也有最大值,超过哪个我都吃不消啊。这个过程也可以借助于磁铁来描述:一开始两个磁铁
都是最大的,力最大嘛。可是这时候功率不是最大的哟。力和功率这两个量的关系很容易把
人带迷糊。我下面还会详细说。
后来,功率到达最大值,臣妾已无余力继续用力了,这时候电机保持着这个功率,减小
转矩,但是速度却上来了。这个过程就是恒功率调速。这个过程中,电压电流不变所以功率
不变,但是正弦电流的频率慢慢增长。由于磁通正比于电压除以转速,所以随着转速的提升,
磁通也就下降了。正因为如此,这个过程也被称为弱磁控制。因为磁场变弱了嘛。简而言之
就是:磁铁变小了。
什么?磁铁变小了反而转的快了?不符合常理啊?平时不都是力气越大跑的越快么?
怎么磁铁小了力气变小了反而速度上去了呢?想不通啊!
!
如果你有这个疑问,那么你和我当初一样,也把“力”和“功率”两个概念给弄混啦~
力是改变物体运动的原因而不是维持物体运动的原因。力和速度有关系,也没有关系,这个
得看具体情况。一个物体不受外力,照样可以保持匀速直线运动(牛顿第一定律)。而功率
是能量的表现,这个和力是有区别的。磁铁小了,力是变小了,可是速度变大了,那什么变
小了?加速度变小了!
!这意味着物体速度的增长量变小了!这说明速度长得没那么快了!
但是速度还是长的!
!这下你明白了吧?为什么磁铁小了转速变快了?对,平时我们感觉力
越大跑的越快,没错。可是那时启动的时候,那是恒转矩控制的区域。后来到了恒功率区,
这个感觉是平时人类不太能有的,所以这时候注意体会。就像你把卫星扔到了轨道上,卫星
具有速度之后就不需要再用燃料维持速度啦。记住,和力有关的是加速度,而不是速度~
这时候,磁通慢慢变小,等效看来就是磁铁变小了,速度上来了。OK,这就是弱磁控制。
说起来简单,做起来还是不容易的。对于感应电机来说,前面我们说过,他的两个磁铁
就是两个如意金箍棒啊~能大能小,所以要求感应电机做弱磁控制炒鸡简单啊有木有。可是
永磁电机怎么办?D 轴磁场相当一部分是永磁体的,改变不了啊亲。于是平时我们说的弱磁
控制主要是针对永磁电机来说的,因为他的那个永磁铁磁场小不了啊!
这时候,有一些二逼青年说,既然转子磁场不能变,那就干脆让定子磁场注入一个和转
子磁场相反的磁场,使得整个合成磁场的 D 轴净磁场值减小。呵呵哒,能行么?是不是觉得
很作弊啊?没办法,工程学从来都是这样的,看似很作弊,实际用起来还行……所以这就是
永磁电机的弱磁控制本质:注入一个和 D 轴相反的磁场使之合成磁场弱磁。
(评论里有人问
Id 增大为什么转矩减小,我这里算是回答你了。Id 增大,但是方向是和永磁方向相反的,
所以合成的磁场是减小的,矩形面积是减小的)。由于注入了相反的磁场使得相当一部分电
流做起了无用功,所以在弱磁方面,永磁电机的效率比异步电机要低。
以下是我列出的相关资料,欢迎查阅。谢谢。
外文电机教材、文献、参考资料:
1. 电机结构:Electric Machinery Fundamentals, by Stephen Chapman, link:
https://books.google.com/books?id=XGn2pQsQaN4C&source=gbs_book_similarbooks
2. 电机控制:Modern Power Electronics and AC Drives, by Bimal K. Bose, link:
STM32 技术开发手册
www.ing10bbs.com
https://books.google.com/books?id=QWnHQgAACAAJ
3. 电动汽车电机经典文献:Electrical Machines and Drives for Electric, Hybrid,
and Fuel Cell Vehicles, by Z. Q. Zhu and David Howe, link: IEEE Xplore Document
4. 特斯拉真正技术掌门人 Wally Rippel 对于异步电机和永磁电机的见解:Induction
Versus DC Brushless Motors, by Wally Rippel, link: Induction Versus DC Brushless
Motors
5. 直流电机动画原理:
https://www.youtube.com/watch?v=LAtPHANEfQo
6. ABB 交流电机动画原理(黑白):
https://www.youtube.com/watch?v=1nstdw0AuMQ
7. 感应电机动画原理:
https://www.youtube.com/watch?v=LtJoJBUSe28
8. 感应电机动画原理 2:
https://www.youtube.com/watch?v=MnQXnEiIUI8
9. 三相交流发电机动画原理:
https://www.youtube.com/watch?v=F_B17553cTg
10. 同步电机动画原理:
https://www.youtube.com/watch?v=Vk2jDXxZIhs
我只能帮你们到这里了,算是把你们领进门了,再往后就看各位的悟性和决心了。加油
吧。
引用文档到此结束了,下页开始继续我们的文档。
STM32 技术开发手册
www.ing10bbs.com
14.6 FOC 控制硬件设计
在第 10 章 我们已经详细介绍了 BLDC 驱动电路,FOC 控制比 6 步 PWM 控
制最重要的硬件就是添加了 3 相电流采样电路。实际上,我们设计的板子最初的
目的就是同时兼容 BLDC 的方波驱动和 PMSM 的 FOC 控制,而已在第 10 章 中的
BLDC 驱动只是用了驱动板的部分硬件而已,FOC 控制才会用上我们的驱动板的
全部硬件资源。
所以,希望大家可以返回第 10 章 看 BLDC 驱动电路,下面我们只介绍前面
章节没有的电路。
电流采样电路
FOC 控制最基本的要求是获取电流信息,目前可行的电流采样方法有:单电
阻、双电阻、三电阻和 ICS(独立电流传感器)方法。采用较少电阻采样更多是
从成本去考虑,这样反而增加了代码实现难度,我们电路设计为三电阻采样,其
他电阻采样方式在大家对 FOC 控制有深入了解之后可以根据实际项目需求进行
研究。ICS 是使用霍尔电流传感器,这个成本一般较高。
三电阻采样方案:我们在每半桥下臂处都加了一个 0.02 欧 2W(理论上最大
电流 10A)的采样电阻,使用低功耗轨到轨运放 MCP6024 把采样电阻两端电压进
行 1.65V 偏移并且放大 4.02 倍,这里的 4.02 倍是直接由:5.1K/(68+1.2K)计算得
到,这个是最简单的计算方法,当然可以用放大电路的虚断和虚短原理去计算(有
兴趣可以自己做),见图 14-27 和图 14-28,得到一个以 1.65V(理论值)为中点
的放大后的信号,这个信号更加合适 stm32 的 AD 采集。根据这些参数可以算出
允许最大电流为:1.65V/4.02/0.02Ω≈20.52A,如果对应 24V 电源,最大功率大
约为 493W,如果是 60V 电源,最大功率可以达到 1231W。但是,0.02 欧 2W(理
论上最大电流 10A),所以最大功率应该是 24V 时 240W,60V 时 600W,所以如
果大家使用自己的电机功率和最大电流超过这些值,需要考虑更换采样电阻阻值
(电阻的功率非常重要)和运放放大倍数,当然这些参数一更改控制程序也必须
做相应调整。
STM32 技术开发手册
www.ing10bbs.com
图 14-27 采样电阻
图 14-28 采样信号放大处理
1.65V 电压由 3.3V 电源通过两个 3.9K 欧电阻分压而来,见图 14-29。
STM32 技术开发手册
www.ing10bbs.com
图 14-29 1.65V 电压源
霍尔传感器和编码器接口
电流采样电路是 FOC 控制必须的硬件条件。霍尔传感器和编码器接口就不
是 FOC 控制必须的,因为我们可以选择无传感器模式。当然做为一个功能全面的
驱动板,我们肯定预留了传感器接口,只是后期大家在实际应用中可以选择性删
除。实际上,就目前的调试效果来说,有传感器模式比无传感器模式还是有一定
的优势的,特别是工业应用中对稳定性要求非常高,使用传感器模式可以有效的
保证。驱动板的传感器接口见图 14-30。
STM32 技术开发手册
www.ing10bbs.com
图 14-30 霍尔传感器和编码器接口
霍尔传感器接口实际上在 BLDC 章节已经作了分析,主要就是做了简单的滤
波和增加上拉电阻。编码器接口也是比较简单,加了几个限流电阻和下拉电阻。
控制和反馈信号接口设计
BLDC 章节我们只用到了部分接口,FOC 控制用到的 接口就比较多。驱动板
预留的接口见图 14-31,其中第 1、3 和 5 引脚是编码器接口,由驱动板输出编
码器信号给控制器,对应编码器的 A、B 和 Z 相,A 和 B 相在选择编码器模式需
要用到,这两个引脚一般与控制器定时器功能引脚连接,Z 相信号在例程中没有
用到;这里的 Z 相是与制动电阻控制线通过跳线帽选择的,当 JP2 的“1-2”接通时
CN5 的第 5 引脚作为 Z 相功能,信号有编码器传输给控制板;当 JP2 的“2-3”接通
时,CN5 的第 5 引脚作为制动电阻控制线,由控制板输出信号给驱动板。第 2、
4 和 6 引脚是霍尔传感器接口,由驱动板输出编码器信号给控制器,这在霍尔传
STM32 技术开发手册
www.ing10bbs.com
感器模式用到,这几个引脚一般与控制器定时器功能引脚连接。第 7、9 和 11 引
脚是电机相反电动势输出引脚,这是预留给 BLDC 无传感器模式使用,目前还没
调试,后期会增加相关例程和文档。第 8、10 和 12 引脚是 3 相电流输出引脚,
由驱动板输出编码器信号给控制器,这几个引脚一般与控制器 ADC 引脚连接。
第 13 和 14 引脚分别是电源电压检测反馈和环境温度反馈引脚,都是模拟量信
号,一般与控制器 ADC 引脚连接。第 15~20 引脚是 6 个桥臂控制引脚,由控制
器输出 PWM 信号给驱动板,这些引脚一般与控制器高级控制定时器功能引脚连
接。第 22 引脚是 MOS 管驱动 IC(IR2110S)的关断引脚,这里是高电平有效,
即当控制器为这个引脚输出高电平时,3 个 MOS 管驱动 IC(IR2110S)出于关闭
输出状态,6 个桥臂也是出于关闭状态,一般情况下我们把它与 GND 信号连接,
这样 6 个桥臂状态完全由 PWM 信号控制。
图 14-31
控制和反馈信号接口设计
电路设计部分就介绍到这里了,其他驱动电路细节可以参考 10.3.6 小节内
容。
14.7 FOCGUI 软件使用
为了使用户能快速地基于 STM32 开发出无刷马达控制器,ST 公司推出了基
于三相感应马达及 PMSM 马达的应用套件,该套件包含了 ST 马达控制软件库及
相应的评估板。这里我们重要是使用 ST 马达控制软件库,应用到我们自己设计
的板子上。为方便用户快速使用,对应每个版本的马达控制软件库都有配套的上
STM32 技术开发手册
www.ing10bbs.com
位机软件帮助用户快速开发。该上位机软件最主要的目的就是根据用户实际使用
的电机和驱动板参数生成相关头文件(.h 文件),用户在得到这些头文件之后就
可以直接替代原本工程相同文件,达到调试参数的目的。
现在,最新版本的 ST 马达控制软件库(简称电机库,下同)版本是 V4.3,
我们的例程是移植了 V2.0 和 V4.2 版本(主要是我们移植时候 V4.3 还没发布)。
版本越高,程序功能齐全,相应的代码结构复杂。我们都知道电机库虽然是免费
但有部分不开源,所以版本越高,不开源部分封装可能就越多,这样对我们理解
FOC 控制代码实现就越不利,所以本文档以 V2.0 版本来讲解。实际上,不同版
本电机库在原理上都是一脉相承的,在理解了 V2.0 版本之后,后面参考 ST 官方
相关文档就可以非常容易上手高版本的了。
使用 V2.0 电机库上位机软件(简称 FOCGUI,下同)之前需要知道电机和驱
动板的相关参数。
14.7.1 PMSM 电机参数
基本需要知道的有下面这些参数:相电阻、相电感(Ld,Lq)、电机极对数、额
定电流、额定转矩、反电动势常数、转动惯量、额定速度、额定电压等等。我们
所使用的 PMSM 电机参数见表格 14-10:
表格 14-10 PMSM 电机参数
42 永磁同步电机(PMSM)参数
供电电压
额定功率
24 V
64 N.M
额定力矩
峰值力矩
0.2 N.M
0.6 N.M
额定转速
额定电枢电流
力矩系数
3000 RPM
3.13 A
0.057 N.M/A
反电势系数
磁极
4.13 V/KRPM
编码器
1000 线
相电阻
0.89±10%Ω
8(4 对极)
STM32 技术开发手册
www.ing10bbs.com
相电感
0.62±20%mH
这些参数都是电机厂家可以直接提供的,所以如果使用自己的电机,需要跟
厂家拿到上表中相关的参数。
另外,还必须知道驱动板的相电流采样参数:采样电阻为 0.05 欧,运放放
大器倍数为 5.91 倍。
14.7.2 FOCGUI 使用
有了上面的电机参数,我们就可以使用 FOCGUI 软件来生成与电机和驱动板
相关联的头文件了,实际上生成的也只是头文件而已,并不是整个工程文件,ST
官方提供的电机库代码是在 ST 官方实验板测试可行的工程文件,由 FOCGUI 软
件生成的只是其中几个必须的头文件,现在我们需要使用我们自己的驱动板和控
制板,需要对电机库代码工程进行移植,移植步骤下一小节介绍。
我们配套资料里边有不同版本的 ST 官方电机库资料,图 14-32 是 V2.x 版本
的资料截图。
图 14-32 ST 官方电机库 V2.x 资料截图
STM32 技术开发手册
www.ing10bbs.com
focgui.exe 是 V2.0 版本的上位机软件,这里我们需要打开它并安装该软件。
stm32 foc firwmare libraries v2.0.rar 是电机库 V2.0 的官方源代码,我们后面就是
用到这个压缩包里边的工程文件。《STM32F103_永磁同步电机_PMSM_FOC 软件
库_用户手册_中文版.pdf(附录 8)》和《STM32F103xx-PMSM-FOC-software-libraryUM.pdf》这两个文档都是电机库 V2.0 的使用说明,一个是中文版一个是英文版,
这个文档详细介绍了电机库函数的具体使用方法,并且提供了相关计算公式说
明,所以,我们想要理解电机库就必须参考该文档来阅读。无刷电机开发套件演
示说明.mp4 文件是 ST 官方发布的一个电机库使用演示说明。视频里边有详细介
绍 FOCGUI 软件每个选项的具体功能,所以建议在使用电机库之前认真看完该视
频。
不同版本的电机库对于的上位机软件界面是不同的,版本越高电机库资源越
丰富,上位机功能也全面。V4.x 版本的上位机界面跟 V3.x 的上位机差不多,V3.x
和 V4.x 的上位机界面我们不打算讲解,我们可以通过观看 ST 官方针对电机库
V3.x 系列讲座视频(见图 14-33)中观看上位机操作方法。
STM32 技术开发手册
www.ing10bbs.com
图 14-33 ST 官方电机库 V3.x 视频资料截图
无刷电机开发套件演示说明.mp4 视频有详细介绍 FOCGUI 软件的使用方法,
下面我们就以生成:基于霍尔传感器的 FOC 相关头文件(对应 YSF1_PMSM-008.
FOC v2.0_HallSensor_42PMSM 例程)方法大致介绍该软件的操作过程。
1) 首先,打开 FOCGUI 软件,启动界面见图 14-34。
STM32 技术开发手册
www.ing10bbs.com
图 14-34 FOCGUI 软件界面
点击界面右上方位置按钮可以打开软件帮助界面,见图 14-35。
图 14-35 打开帮助界面
在帮助界面中我们可以查阅到 FOCGUI 每个菜单的详细作用,特别当我们不
知道哪一个选项的具体功能时查阅该帮助文档非常有效,见图 14-36。
STM32 技术开发手册
www.ing10bbs.com
图 14-36 帮助文档界面
2) Configuration 菜单,界面见图 14-37。在 Speed feedback 栏目下选择霍尔
传感器;在 FOC methods 栏目下勾选使能弱磁和电流前馈,最后一个是
针对 IPMSM(内嵌式永磁同步电机)的优化功能;在 Current sensing
technique 选择三电阻电流采样模式;Brake technique 是刹车电阻使能,
这里不使能。PID differential terms 栏目是 PID 算法微分项使能,这里我
们勾选它。弱磁控制主要是实现低负载时控制电机转速超额定转速运行;
电流前馈功能在于尽量提高系统本身的动态性能,简单的说就是在扰动
还没有对控制系统作出影响前,直接输出控制分量,把它抵消。这些功
能虽然很好但是也想要增加了代码分析难度,所以在最开始理解电机库
代码时可以先关闭这两个功能简化代码,等熟悉后再加入分析。
STM32 技术开发手册
www.ing10bbs.com
图 14-37 Configuration 菜单
3) Motor and HW 菜单,界面见图 14-38。Current sensing networks 栏目下设
置采样电阻阻值为 0.02 欧,放大倍数为 4.02。MB459 board protections
栏目下,设置过温保护为 70℃,解除过温保护的滞后温度为 5℃,即在
发生过温保护后,但温度下降到(70-5=65℃)时解除过温保护警告。设
置过压(电源电压)保护为 64V,设置低压保护为 15V。在 Motor
characteristics 栏目下设置电机的相电阻为 0.65Ω(虽然表格 14-10 中电
阻值为 0.89±10%Ω,但 0.65Ω 为实测电阻值),设置额定电流为 3.13A,
电感为 0.62mH,对于 SPMSM(表面贴永磁同步电机)Lq=Ld;反电动势
系数为 4.13Vrms/Krpm,即电机转速每提高 1000rpm,反电动势对应提升
的电压,电机极对数为 4 对极(总共 8 磁极),电机最高转速设置为
3000rpm,实际上,空载时候最高转速比这个值要高不少。Voltage sensing
network 栏目下设置直流电源电压为 24V(该值实际上无影响),设置电
源电压分压系数为 0.044。
STM32 技术开发手册
www.ing10bbs.com
图 14-38 Motor and HW 菜单
4) Control Settings 菜单,界面见图 14-39。Driving settings 栏目里设置控制
6 个桥臂的定时器 PWM 频率为 16KHz,死区时间设置为 500ns。Speed
regulation control rate 设置速度调制周期为 2ms(速度环周期)。Torque
and flux control rate 设置力矩矩和励磁调制周期为 1 个 PWM 周期(电流
环周期)。Torque and flux controller 设置电流环调节带宽为 6000rad/s,该
栏目下有 Torque 和 Flux 两个的 PID 参数设置对话框,默认是无法修改
的,里边参数值是 FOCGUI 软件根据我们电机和驱动板参数内部计算的
参考值,实际上,我们可以把下面的“Manual editing enabled”选项勾选上
就可以修改这些参数了,这里我们使用默认参数值。Torque 和 Flux 两个
对话框底部都有一个 Init Ref 值,分别对应 Iq 和 Id 的初始值,单位为 A
(安培)。Speed controller 栏目设置速度环 PID 参数,D 参数是在图 14-37
中勾选上了使能 PID 微分项之后才能修改的,不然是禁止修改的,每个
参数值都由一个分子和一个分母构成,一般分母值都是 2 的 n 次幂值,
很多时候分母值用 FOCGUI 默认值就好,分子值分别改为如图中参数,该
参数是对应我们的配套电机的,如果是其他型号电机一般这些值也是需
要进行修改的,PID 参数调整方法在本文档的前面章节已经有介绍,无刷
STM32 技术开发手册
www.ing10bbs.com
电机开发套件演示说明.mp4 视频中有演示参数整顿详细过程,设置初始
化速度为 1000rpm。Flux weakening controller 设置弱磁 PI 参数,使用默
认值即可。
图 14-39 Control Settings 菜单
5) Debug 菜单,界面见图 14-40。Auxiliary speed feedback 设置辅助速度反
馈,如果是无传感器模式可以选择使用编码器或者霍尔传感器做为辅助
速度反馈,有传感器模式只能选择 None 选项,还可以选择使能状态观
测器增益辅助速度反馈。Flux and torque PI(D) tuning modality 栏目用于速
度环开环情况下调试励磁和转矩 PID 参数,使能了这个宏,就会进入 Id、
Iq 的励磁和转矩 PID 调整模式。最后,还有一个 DAC 调试使能功能,这
里默认是关闭的,电机库程序默认使用 TIM3 的通道 3(默认对应 PB0)
和通道 4(默认对应 PB1),因为我们程序默认将 TIM3 作为霍尔传感器
和编码器的功能定时器所以需要把 DAC 功能关闭,如果想要 DAC 的调试
功能,可以自己把霍尔传感器或编码器功能定时器调整为 TIM8。
STM32 技术开发手册
www.ing10bbs.com
图 14-40 Debug 菜单
6) Speed feedback 菜单,界面见图 14-41。Hall sensors placement 栏目下选
择霍尔传感器三个传感器之间的间隔是 120 度,设置霍尔传感器的电相
移位 120 度。Speed measurement errors management 设置霍尔传感器有
效最大速度为 30000rpm,有效最低速度为 60rpm。
图 14-41 Speed feedback 菜单
STM32 技术开发手册
www.ing10bbs.com
7) Speed feedback 菜单,界面见图 14-42。Start-up settings 用于无传感器模
式的开环启动参数设置。Speed measurement errors management 用于无
传感器模式下电机错误限度设置。A priori computed gains 用于调整观测
器和 PLL 锁相环参数值,在勾选了“Manual editing enabled”之后可以对参
数值进行修改,这里我们不做修改。
图 14-42 Speed feedback 菜单
8) Current sensing 菜单,界面见图 14-43。Noise parameters 设置 ADC 采集
相电流时降噪时间,保持默认值即可;A/D converter sampling time 选择
ADC 采样时间为 125ns;Maxing available modulation index 设置 PWM 最
大占空比百分比,该值受 PWM 频率影响。
STM32 技术开发手册
www.ing10bbs.com
图 14-43 Current sensing 菜单
9) 保存并生成头文件。上面我们设置好了各个菜单参数,就可以保存为一
个工程文件,这样下次直接打开该工程文件就可以做调整了。首先,我
们在电脑桌面上新建一个文件夹(存放路径可以任意选择,要求路径不
要过长,不要有中文路径),命名为“STM32MC_FOC_V2.0”。选择 FOCGUI
界面中的保存按钮,保存 FOC 工程,见图 14-44,并且设置该 FOC 工程
文件为 PMSM_FOC2.0_HALL,可以看到 FOC 工程文件是以.fhg 为后缀名
的。
STM32 技术开发手册
www.ing10bbs.com
图 14-44 保存 FOC 工程文件
接下来,我们头文件生成存放路径,选择 Tools->Options…进入选项配置
界面,设置 Output Directory 为之前新建的文件夹 STM32MC_FOC_V2.0,
见图 14-45,然后就可以生成头文件到我们指定的文件夹内了,点击
FOCGUI 界面上的生成按钮生成文件,见图 14-46,并弹出日志信息对话
框,从该对话框中我们可以看到 0 错误还有 1 个警告,警告的意思就是
我们使能了弱磁功能系统要求开启配置刹车电阻功能,这里我们可以把
弱磁功能去掉或者忽略掉它(这种做法仅限实验测试)。
STM32 技术开发手册
www.ing10bbs.com
图 14-45FOCGUI 工程选项
图 14-46 生成 FOC 工程头文件
最后,STM32MC_FOC_V2.0 文件夹中的文件见图 14-47。
STM32 技术开发手册
www.ing10bbs.com
图 14-47 STM32MC_FOC_V2.0 文件夹
14.8 FOC 工程移植
14.8.1 FOC 电机库文件
解压图 14-32 中的 STM32MC-KIT-v2.0.1.zip,该压缩包里边包括了电机库演
示例程,我们的 FOC 例程就是在此基础上移植而来的,解压该压缩包之后得到的
文件见图 14-48。
STM32 技术开发手册
www.ing10bbs.com
图 14-48 STM32MC-KIT-v2.0.1 压缩包文件
design tools 文件夹存放了几个电机调试参数计算辅助工具,见图 14-49,具
体功能我们可以在《STM32F103_永磁同步电机_PMSM_FOC 软件库_用户手册_中
文版.pdf(附录 8)》文档中的“2.1.4 IPMSM 的最大转矩电流比(MTPA)的控制”
和“4.6.4 IPMSM 驱动优化(MTPA)的额外参数”以及“A.8 MMI(最大调制指数):
自动计算”了解使用。
图 14-49 design tools 文件夹
stm32 foc firwmare libraries v2.0.1 文件夹就是官方演示例程工程文件,见图
14-50,STM32_FOC_ACIM 是交流感应电机驱动例程;STM32_FOC_PMSM 是 PMSM
(永磁同步电机)驱动例程,我们重点使用这个文件夹内文件,STM32F10XFWLIB
是 STM32F1 系列芯片的标准固件库,这个库版本是 V2.0 的,现在一般都是使用
V3.5 版本的标准库,基于这个问题,我们不打算直接使用 stm32 foc firwmare
STM32 技术开发手册
www.ing10bbs.com
libraries v2.0.1 文件夹内的工程代码,我们的例程都是把 FOC 代码移植到 3.5 标
准库上去的,所以我们真正用到的只是 STM32_FOC_PMSM 里边的部分文件,这
也因为我们选择使用 3.5 标准固件库来代替 ST 官方 2.0 版本的演示功能,在 src
和 inc 里边的很多文件都需要更改,实际上,很多文件就是修改下相关#include
头文件(3.5 标准库和 2.0 版本的相关头文件名称有所不同)。
图 14-50 stm32 foc firwmare libraries v2.0.1 文件夹
打开 STM32_FOC_PMSM 文件夹,见图 14-51,EWARM、MULTI 和 RVMDK 是
对应不同编译软件的工程代码。src 和 inc 分别存放了针对 PMSM 电机的 FOC 控
制的部分源代码.c 和.h 文件,这两个文件夹是我们移植的重点。
图 14-51 STM32_FOC_PMSM 文件夹
除了 src 和 inc 这两个文件夹之后,还有两个文件是移植必须,就是 ST 电机
库不开源部分:ST 官方封装成库文件,因为不同编译软件封装库文件格式不同,
STM32 技术开发手册
www.ing10bbs.com
所以这两个不开源库文件存放在对应的编码软件文件夹中,我们使用 keil 软件,
是.lib 文件,见图 14-52。MC_FOC_Methods_lib.lib 是封装了 FOC 方法,主要是针
对 IPMSM 电机的优化控制方法;MC_State_Observer_lib.lib 封装了状态观测器,
这个主要是由相电流推导电机转子位置方法,这个在无传感器模式非常重要。
图 14-52 lib 文件
14.8.2 FOC 例程工程文件结构
ST 官方的电机库演示例程有液晶显示功能,并且有 5 个功能按键,考虑移
植方便,我们直接使用我们提供的现成液晶触摸工程:YSF1-055. LCD 触摸画笔,
我们以该工程文件为基本,通过添加文件并修改实现我们完整的功能。我们提供
的 FOC 例程都是在这个液晶触摸工程基础上移植而来的,因为我们使用的液晶
是带触摸的 3.5 寸液晶屏,相对比较大,我们将液晶屏划分为两部分,上部分用
于显示与 ST 演示例程完全一致的液晶显示内容,下部分我们使用触摸屏功能,
设置了 5 个触摸按键,用来代替 5 个实体的轻触按键,设计的界面见图 14-53。
STM32 技术开发手册
www.ing10bbs.com
图 14-53 FOC 液晶显示界面
5 个按键的功能说明如表格 14-11:
表格 14-11 触摸按键功能说明
触摸按键
功能说明
SEL
KEY1
这三个按键功能完全相同,用于电
机旋转启动和停止
KEY2
UP
参数值向上或者增加
DOWN
参数值向下或者减少
LEFT
界面参数选择左移
RIGHT
界面参数选择右移
整个移植过程细节较多,并且都是非常常规的移植步骤,相信按照各位目前
实力是完全有能力移植该工程,下面我们直接使用现有的例程工程介绍几个移植
问题,这里我们自己复制 YSF1_PMSM-008. FOC v2.0_HallSensor_42PMSM 工程做
为本文档的一个附件(附录 9:YSF1_PMSM-008. FOC v2.0_42PMSM)
,主要是后
面会在该工程里边添加相关中文注释,所以还是重新复制一份好,打开该工程可
以看到工程的文件结构如图 14-54,其中,Startup、CMSIS、StdPeriph_Derive、
User、Bsp、和 Readme 是原本工程 YSF1-055. LCD 触摸画笔的文件,当然部分文
STM32 技术开发手册
www.ing10bbs.com
件也有做了小修改。foc_lib 组件存放了电机库的图 14-52 中的两个 lib 文件,
foc_include 组件存放了 foc 相关头文件,这些文件来着图 14-51 中的 inc 文件夹,
这个组件存放的头文件内容一般是不需要修改的,这些文件更多是函数声明和相
关常数宏定义。foc_source 组件是 foc 源代码部分,我们后面将对部分文件代码
进行分析,这些文件来着图 14-51 中的 src 文件夹。foc_parameters .h files 组件
存放 foc 参数头文件,这些文件内容是随 PMSM 电机参数和驱动板参数而改动
的,我们调试过程一般都是修改这些文件相关宏定义值,这些参数由 FOCGUI 软
件生成见图 14-47,这里我们把图 14-47 中的头文件直接使用,代替掉原本 inc
文件夹里边的同名文件。
STM32 技术开发手册
www.ing10bbs.com
图 14-54 FOC 工程的文件结构
14.8.3 FOC 代码分析
MC_type.h 文件内容
该头文件存放了电机库常用到的几个结构体类型定义。
STM32 技术开发手册
www.ing10bbs.com
代码 14-1 MC_type.h 文件内容
01 typedef struct {
02
s16 qI_Component1;
03
s16 qI_Component2;
04 } Curr_Components;
05
06 typedef struct {
07
s16 qV_Component1;
08
s16 qV_Component2;
09 } Volt_Components;
10
11 typedef struct {
12
s16 hCos;
13
s16 hSin;
14 } Trig_Components;
15
16 typedef struct {
17
s16 hC1;
18
s16 hC2;
19
s16 hC3;
20
s16 hC4;
21
s16 hC5;
22
s16 hC6;
23
s16 hF1;
24
s16 hF2;
25
s16 hF3;
26
s16 PLL_P;
27
s16 PLL_I;
28
s32 wMotorMaxSpeed_dpp;
29
u16 hPercentageFactor;
30 } StateObserver_Const;
31
32 typedef struct {
33
s16 PLL_P;
34
s16 PLL_I;
35
s16 hC2;
36
s16 hC4;
37 } StateObserver_GainsUpdate;
38
39 typedef struct {
40
s16 hsegdiv;
41
s32 wangc[8];
42
s32 wofst[8];
43 } MTPA_Const;
44
45 typedef struct {
46
s16 hKp_Gain;
47
u16 hKp_Divisor;
48
s16 hKi_Gain;
49
u16 hKi_Divisor;
50
s16 hLower_Limit_Output;
//Lower Limit for Output limitation
51
s16 hUpper_Limit_Output;
//Lower Limit for Output limitation
52
s32 wLower_Limit_Integral;
//Lower Limit for Integral term limitation
53
s32 wUpper_Limit_Integral;
//Lower Limit for Integral term limitation
54
s32 wIntegral;
55
// Actually used only if DIFFERENTIAL_TERM_ENABLED is enabled in
56
//stm32f10x_MCconf.h
57
s16 hKd_Gain;
58
u16 hKd_Divisor;
59
s32 wPreviousError;
60 } PID_Struct_t;
STM32 技术开发手册
www.ing10bbs.com
61
62 typedef enum {
63
IDLE, INIT, START, RUN, STOP, BRAKE, WAIT, FAULT
64 } SystStatus_t;
65
66 typedef enum {
67
NO_FAULT, OVER_VOLT, UNDER_VOLT
68 } BusV_t;
Curr_Components 是电流值(Current)结构体,有两个成员,都是 16 位有符
号整形变量,是以 q1.15 格式(参考《STM32F103_永磁同步电机_PMSM_FOC 软
件库_用户手册_中文版.pdf(附录 8)》A.3 固定点数字代表(第 113~114 页))表
示电流值。
Volt_Components 是电压值(Voltage)结构体,类似 Curr_Components 用 q1.15
格式的 16 位有符号整形表达。
Trig_Components 是三角函数值(Trigonometry)结构体,有两个变量,一个
是正弦值一个是余弦值,都是 16 位有符号整形变量。
StateObserver_Const 是状态观测器常数,这在状态观测器中使用,这些成员
值由已确定的宏定义值计算而来,所以这里使用 const 表示。
StateObserver_GainsUpdate 是状态观测器的增益更新,只有在定义了:
OBSERVER_GAIN_TUNING 宏之后这个结构体才有使用到。
MTPA_Const 是 MTPA 的常数,只用于 IPMSM,只有在定义了:IPMSM_MTPA
宏之后这个结构体才有使用到。
PID_Struct_t 是 PID 参数结构体,P、I、D 三个参数值都由一个增益(Gain)
和一个除数(Divisor)构成,还有定义了计算结果值的最高值和最低值限制、积
分 I 参数最大值和最小值限制,以及一个保存上次计算误差值和积分结果值。
SystStatus_t 是系统状态结构体,有:空闲、初始化、启动、运行、停止、刹
车、等待和错误 8 种状态。
BusV_t 是直流电源状态结构体,有无错误、过压和欠压 3 种状态。
MC_Key.c 文件内容
该文件存放了 5 个功能按键(见表格 14-11)的功能实现代码,因为 ST 官
方演示例程是使用 5 个实体按键,而我们这里使用 5 个模拟触摸按键,所以需要
修改部分代码。
STM32 技术开发手册
www.ing10bbs.com
代码 14-2 类型、变量定义和 KEYS_Init 函数
01 enum {
02
TOUCH_KEY_NONE=0,
03
TOUCH_KEY_SEL,
04
TOUCH_KEY_UP,
05
TOUCH_KEY_DOWN,
06
TOUCH_KEY_RIGHT,
07
TOUCH_KEY_LEFT,
08
TOUCH_USER_KEY1,
09
TOUCH_USER_KEY2,
10 };
11
12 volatile u8 touch_key_value=TOUCH_KEY_NONE;
13
14 void KEYS_Init(void)
15 {
16
touch_key_value=TOUCH_KEY_NONE;
17 }
首先是定义一个枚举类型,用于表示当前哪个按键被按下。KEYS_Init 函数只
是为 touch_key_value 赋值为无按键按下状态。
代码 14-3 KEYS_Read 函数
01 u8 KEYS_Read ( void )
02 {
03
u16 x=0xFFFF,y=0xFFFF;
04
05
if (XPT2046_EXTI_Read()!=XPT2046_EXTI_ActiveLevel) {
06
touch_key_value=TOUCH_KEY_NONE;
07
bPrevious_key = NOKEY;
08
return NOKEY;
09
}
10
11
/*获取点的坐标*/
12
XPT2046_Get_TouchedPoint(&y,&x);
13
if ((x==0xFFFF)||(y==0xFFFF)) {
14
bPrevious_key = NOKEY;
15
return NOKEY;
16
}
17
x=480-x;
18
if ((y>250)&&(y<320)) {
19
if (x>408)
20
touch_key_value=TOUCH_USER_KEY2;
21
else if (x>340)
22
touch_key_value=TOUCH_USER_KEY1;
23
else if (x>272)
24
touch_key_value=TOUCH_KEY_RIGHT;
25
else if (x>204)
26
touch_key_value=TOUCH_KEY_LEFT;
27
else if (x>136)
28
touch_key_value=TOUCH_KEY_DOWN;
29
else if (x>68)
30
touch_key_value=TOUCH_KEY_UP;
31
else
32
touch_key_value=TOUCH_KEY_SEL;
33
} else {
34
touch_key_value=TOUCH_KEY_NONE;
35
bPrevious_key = NOKEY;
/* 无触摸 */
STM32 技术开发手册
www.ing10bbs.com
36
return NOKEY;
37
}
38
39
/* "RIGHT" key is pressed */
40
if (touch_key_value==TOUCH_KEY_RIGHT) {
41
if (bPrevious_key == RIGHT) {
42
return KEY_HOLD;
43
} else {
44
bPrevious_key = RIGHT;
45
return RIGHT;
46
}
47
}
48
/* "LEFT" key is pressed */
49
else if (touch_key_value==TOUCH_KEY_LEFT) {
50
if (bPrevious_key == LEFT) {
51
return KEY_HOLD;
52
} else {
53
bPrevious_key = LEFT;
54
return LEFT;
55
}
56
}
57
/* "SEL" key is pressed */
58
if (touch_key_value==TOUCH_KEY_SEL) {
59
if (bPrevious_key == SEL) {
60
return KEY_HOLD;
61
} else {
62
if ( (TB_DebounceDelay_IsElapsed() == FALSE) && ((bKey_Flag & SEL_FLAG) == SEL_FLAG) ) {
63
return NOKEY;
64
} else {
65
if ( (TB_DebounceDelay_IsElapsed() == TRUE) && ( (bKey_Flag & SEL_FLAG) == 0) ) {
66
bKey_Flag |= SEL_FLAG;
67
TB_Set_DebounceDelay_500us(100); // 50 ms debounce
68
} else if ( (TB_DebounceDelay_IsElapsed() == TRUE) && ((bKey_Flag & SEL_FLAG) ==
SEL_FLAG) ) {
69
bKey_Flag &= (u8)(~SEL_FLAG);
70
bPrevious_key = SEL;
71
return SEL;
72
}
73
return NOKEY;
74
}
75
}
76
}
77
/* "USER_KEY1" or "USER_KEY2" key is pressed */
78
else if ((touch_key_value==TOUCH_USER_KEY1)||(touch_key_value==TOUCH_USER_KEY2)) {
79
if (bPrevious_key == SEL) {
80
return KEY_HOLD;
81
} else {
82
if ( (TB_DebounceDelay_IsElapsed() == FALSE) && ((bKey_Flag & SEL_FLAG) == SEL_FLAG) ) {
83
return NOKEY;
84
} else {
85
if ( (TB_DebounceDelay_IsElapsed() == TRUE) && ( (bKey_Flag & SEL_FLAG) == 0) ) {
86
bKey_Flag |= SEL_FLAG;
87
TB_Set_DebounceDelay_500us(100); // 50 ms debounce
88
} else if ( (TB_DebounceDelay_IsElapsed() == TRUE) && ((bKey_Flag & SEL_FLAG) ==
SEL_FLAG) ) {
89
bKey_Flag &= (u8)(~SEL_FLAG);
90
bPrevious_key = SEL;
91
return SEL;
92
}
93
return NOKEY;
94
}
95
}
96
}
STM32 技术开发手册
www.ing10bbs.com
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120 }
/* "UP" key is pressed */
else if (touch_key_value==TOUCH_KEY_UP) {
if (bPrevious_key == UP) {
return KEY_HOLD;
} else {
bPrevious_key = UP;
return UP;
}
}
/* "DOWN" key is pressed */
else if (touch_key_value==TOUCH_KEY_DOWN) {
if (bPrevious_key == DOWN) {
return KEY_HOLD;
} else {
bPrevious_key = DOWN;
return DOWN;
}
}
/* No key is pressed */
else {
bPrevious_key = NOKEY;
return NOKEY;
}
KEYS_Read 函数用于读取按键状态并返回。首先,使用 if 语句判断触摸屏是
否 有 被触摸 ,在 液晶 屏 有触摸 按下 时 XPT2046_EXTI_Read 函数返回值等于
XPT2046_EXTI_ActiveLevel ,在判断 无 触摸 时 函数 直接 返回 NOKEY 并且结束
KEYS_Read 函数。
在确保有触摸按下时,运行 XPT2046_Get_TouchedPoint 函数读取触摸坐标,
接下来就是对获取到的触摸坐标进行计算得到哪个功能按键被按下。
对于 RIGHT、LEFT 、UP 和 DOWN 这 4 个按键只是简单的判断是长按就返回
KEY_HOLD,不是长按就返回对应的按键。对于 SEL、USER_KEY1 和 USER_KEY2(实
际上,这 3 三个按钮功能是完全一样的)如果判断是长按就返回 KEY_HOLD,不
是长按就需要进行消抖处理,这里消抖时间是 50ms。
代码 14-4 KEYS_process 函数
01 void KEYS_process(void)
02 {
03
bKey = KEYS_Read();
// read key pushed (if any...)
04
05
switch (bMenu_index) {
06
case (CONTROL_MODE_MENU_1):
07
switch (bKey) {
08
case UP:
09
case DOWN:
10
wGlobal_Flags ^= SPEED_CONTROL;
11
bMenu_index = CONTROL_MODE_MENU_6;
12
break;
13
STM32 技术开发手册
www.ing10bbs.com
14
case RIGHT:
15
bMenu_index = REF_SPEED_MENU;
16
break;
17
18
case LEFT:
19 #ifdef DAC_FUNCTIONALITY
20
bMenu_index = DAC_PB1_MENU;
21 #elif defined OBSERVER_GAIN_TUNING
22
bMenu_index = I_PLL_MENU;
23 #else
24
bMenu_index = POWER_STAGE_MENU;
25 #endif
26
break;
27
28
case SEL:
29
if (State == RUN) {
30
State = STOP;
31
} else if (State== START) {
32
State = STOP;
33
} else if (State == IDLE) {
34
State = INIT;
35
bMenu_index = REF_SPEED_MENU;
36
}
37
38
break;
39
default:
40
break;
41
}
42
break;
43
/**********************************/
44
...... 中间省略了部分代码 ......
45
/************************************/
46
default:
47
break;
48
}
49 }
KEYS_process 函数是对按键被按下后进行处理。不同菜单界面下,同一按键
往往被赋予不同的意义,所以 KEYS_process 使用 switch 语句对菜单界面进行区
分,然后在每个界面下对 5 个功能按钮进行编程,所以这里使用两个 switch 嵌
套。总共的菜单有 26 个,见代码 14-5。
代码 14-5 菜单宏定义
01 #define CONTROL_MODE_MENU_1
02 #define REF_SPEED_MENU
03
04 #define P_SPEED_MENU
05 #define I_SPEED_MENU
06 #define D_SPEED_MENU
07
08 #define P_TORQUE_MENU
09 #define I_TORQUE_MENU
10 #define D_TORQUE_MENU
11
12 #define P_FLUX_MENU
13 #define I_FLUX_MENU
14 #define D_FLUX_MENU
(u8) 0
(u8) 1
// 控制模式菜单 1
// 参考速度菜单
(u8) 2
(u8) 3
(u8) 4
// 速度 PID 比例参数调整菜单
// 速度 PID 积分参数调整菜单
// 速度 PID 微分参数调整菜单
(u8) 5
(u8) 6
(u8) 7
// 力矩 PID 比例参数调整菜单
// 力矩 PID 积分参数调整菜单
// 力矩 PID 微分参数调整菜单
(u8) 8
// 励磁 PID 比例参数调整菜单
(u8) 9
// 励磁 PID 积分参数调整菜单
(u8) 10 // 励磁 PID 微分
STM32 技术开发手册
www.ing10bbs.com
15
16 #define POWER_STAGE_MENU
17
18 #define CONTROL_MODE_MENU_6
19 #define IQ_REF_MENU
20 #define ID_REF_MENU
21
22 #define FAULT_MENU
23
24 #define WAIT_MENU
25
26 #ifdef OBSERVER_GAIN_TUNING
27 #define K1_MENU
28 #define K2_MENU
29 #define P_PLL_MENU
30 #define I_PLL_MENU
31 #endif
32
33 #ifdef DAC_FUNCTIONALITY
34 #define DAC_PB0_MENU
35 #define DAC_PB1_MENU
36 #endif
37
38 #ifdef FLUX_WEAKENING
39 #define P_VOLT_MENU
40 #define I_VOLT_MENU
41 #define TARGET_VOLT_MENU
42 #endif
(u8) 11
// 功率级菜单
(u8) 12 // 控制模式菜单 6
(u8) 13 // 参考 Iq 菜单
(u8) 14 // 参考 Id 菜单
(u8) 15
// 错误菜单
(u8) 16
// 等待菜单
(u8) 17 // K1 菜单
(u8) 18 // K2 菜单
(u8) 19 // PLL 锁相环 PID 比例参数菜单
(u8) 20 // PLL 锁相环 PID 积分参数菜单
(u8) 21
(u8) 22
// DAC 通道(对应 PB0 引脚)菜单
// DAC 通道(对应 PB1 引脚)菜单
(u8)23 // 弱磁功能 PID 比例参数菜单
(u8)24 // 弱磁功能 PID 积分参数菜单
(u8)25 // 弱磁功能目标电压菜单
关于每个功能在具体菜单中的功能这里就不进一步分析,对界面菜单切换有
兴趣的可以自行深入探究。特别的,KEYS_process 函数并不是完成界面切换功能,
它是完成界面菜单切换准备功能,指定下个显示的界面,真正液晶显示实现是在
MC_Display.c 文件中实现。
代码 14-6 KEYS_ExportbKey 函数
01 u8 KEYS_ExportbKey(void)
02 {
03
return (bKey);
04 }
直接返回当前被触摸按下的按键号,该函数主要是封装 bKey 变量。
MC_Display.c 文件内容
该文件存放了控制液晶显示内容的代码。例程中把液晶显示分成 11 行,默
认只显示 24*12 大小的英文字母和数字,把每行划分为可显示 20 个字母或者数
字,这样划分可以很方便规划显示位置。
代码 14-7 Display_Welcome_Message 函数
01 void Display_Welcome_Message(void)
02 {
STM32 技术开发手册
www.ing10bbs.com
03
04
05
06
07
08
09
10
11
12 }
u8 *ptr = " STM32 Motor Control";
LCD_DisplayStringLine(Line0, ptr);
ptr = " PMSM FOC ver 2.0 ";
LCD_DisplayStringLine(Line1, ptr);
ptr = " <> Move ^| Change ";
LCD_DisplayStringLine(Line9, ptr);
该函数显示欢迎信息。调用了 LCD_DisplayStringLine(显示一行字符串)函数
实现,LCD_DisplayStringLine 函数在 bsp_lcd.c 文件中实现,它有两个形参,第一
个形参指定行数,对应前面说的里边把液晶分成 11 行;第二个参数指定要显示
的字符串内容,最长不能超过 20 个字符长度。
代码 14-8 Display_LCD 函数
01 void Display_LCD(void)
02 {
03
if (TB_DisplayDelay_IsElapsed() == TRUE) {
04
TB_Set_DisplayDelay_500us(500); // refresh LCD every 400*5 = 200 ms
05
06
bPrevious_Visualization = bPresent_Visualization;
07
08
bPresent_Visualization = ComputeVisualization(bMenu_index);
09
10
switch (bPresent_Visualization) {
11
u8 *ptr;
12
s16 temp;
13
14
case (VISUALIZATION_1):
15
if (bPresent_Visualization != bPrevious_Visualization) {
16 #ifdef NO_SPEED_SENSORS
17
ptr = " Sensorless Demo ";
18
LCD_DisplayStringLine(Line2,ptr);
19 #else
20
ptr = " Hall Sensor Demo ";
21
LCD_DisplayStringLine(Line2,ptr);
22 #endif
23
LCD_ClearLine(Line3);
24
25
LCD_ClearLine(Line4);
26
27
ptr = " Target
Measured";
28
LCD_DisplayStringLine(Line5,ptr);
29
30
ptr = "
(rpm)
";
31
LCD_DisplayStringLine(Line7,ptr);
32
33
LCD_ClearLine(Line6);
34
35
LCD_ClearLine(Line8);
36
37
ptr = " <> Move ^| Change ";
38
LCD_DisplayStringLine(Line9, ptr);
39
}
40
41
if (bMenu_index == CONTROL_MODE_MENU_1) {
STM32 技术开发手册
www.ing10bbs.com
42
LCD_SetTextColor(Red);
43
}
44
45
ptr = " Speed control mode";
46
LCD_DisplayStringLine(Line3,ptr);
47
48
if (bMenu_index == CONTROL_MODE_MENU_1) {
49
LCD_SetTextColor(Blue);
50
} else { //REF_SPEED_MENU
51
LCD_SetTextColor(Red);
52
}
53
54
//Compute target speed in rpm
55
temp = (s16)(hSpeed_Reference * 6);
56
Display_5DigitSignedNumber(Line7, CHAR_0, temp);
57
58
if (bMenu_index != CONTROL_MODE_MENU_1) {
59
LCD_SetTextColor(Blue);
60
}
61
62
//Compute measured speed in rpm
63 #ifdef ENCODER
64
temp = (s16)(ENC_Get_Mechanical_Speed() * 6);
65 #endif
66 #if defined HALL_SENSORS
67
temp = (s16)(HALL_GetSpeed() * 6);
68 #endif
69 #if defined NO_SPEED_SENSORS
70
temp = (s16)(STO_Get_Speed_Hz() * 6);
71 #endif
72
Display_5DigitSignedNumber(Line7, CHAR_13, temp);
73
74
break;
75
76
/**********************************/
77
...... 中间省略了部分代码 ......
78
/************************************/
79
80
default:
81
break;
82
}
83
}
84 }
该函数控制液晶显示。首先是使用 if 语句判断是否到了扫描周期,如果
TB_DisplayDelay_IsElapsed 返回值为 TRUE,说明需要进行液晶显示更新,执行 if
语句里边程序。TB_Set_DisplayDelay_500us 函数用于设置液晶扫描周期,当形参
赋值为 500 时,扫描周期为 250ms。bPrevious_Visualization 保存上次液晶界面,
bPresent_Visualization 标记当前液晶界面,由 ComputeVisualization 函数的返回值
赋值。ComputeVisualization 函数是把代码 14-5 中的 26 个菜单合并成 10 个液晶
显示界面,通过菜单序号反馈得到对应的液晶显示界面编号。Display_LCD 函数大
部分内容就是把这 10 个液晶显示界面需要显示的内容全部实现出来,该函数中
还需要调用 Display_5DigitSignedNumber 函数,
STM32 技术开发手册
www.ing10bbs.com
代码 14-9 Display_5DigitSignedNumber 函数
01 void Display_5DigitSignedNumber(u8 Line, u8 bFirstchar, s16 number)
02 {
03
u32 i;
04
u16 h_aux=1;
05
06
if (number<0) {
07
LCD_DisplayChar(Line,(u16)( 12*bFirstchar), '-');
08
number = -number;
09
} else {
10
LCD_DisplayChar(Line,(u16)(12*bFirstchar), ' ');
11
}
12
13
for (i=0; i<4; i++) {
14
LCD_DisplayChar(Line, (u16)(12*(bFirstchar+5-i)),(u8)(((number%(10*h_aux))/h_aux)+0x30));
15
h_aux *= 10;
16
}
17
LCD_DisplayChar(Line,(u16)(12*(bFirstchar+1)), (u8)(((number/10000))+0x30));
18 }
该函数实现显示 1 个符号位加 4 个位数的数值(范围:-9999~9999)
。函数
有三个形参,第一个为要显示行数,第二为数值显示的第一个地址,第三位显示
的数值。该函数通过调用 LCD_DisplayChar 函数显示每个字符。LCD_DisplayChar
函数定义在 bsp_lcd.c 文件中。
stm32f10x_Timebase.c 文件内容
该文件存放了时间周期控制代码,电机控制很多都是周期性控制,比如速度
环控制周期、液晶扫描周期、按键消抖时间等等。本文档代码实现一个 500us 的
定时中断,实现不同时间和周期控制。
代码 14-10 TB_Init 函数
01 void TB_Init(void)
02 {
03
uint32_t prioritygroup = 0x00;
04
05
06
prioritygroup = NVIC_GetPriorityGrouping();
07
08
/* SystemFrequency / 1000
1ms 中断一次
09
* SystemFrequency / 100000
10us 中断一次
10
* SystemFrequency / 1000000 1us 中断一次
11
*/
12
SysTick_Config(36000);
13
NVIC_SetPriority (SysTick_IRQn, NVIC_EncodePriority(\
14
prioritygroup, SYSTICK_PRE_EMPTION_PRIORITY, SYSTICK_SUB_PRIORITY));
15 }
该函数初始化定时器,这里使用系统滴答定时器实现时基功能,设置滴答定
时器中断周期为 500us,设置中断优先级为抢占式为 3,子优先级为 0。
STM32 技术开发手册
www.ing10bbs.com
代码 14-11 TB_Wait 函数
01 void TB_Wait(u16 time)
02 {
03
hTimebase_500us = time;
// delay = 'time' value * 0.5ms
04
while (hTimebase_500us != 0) { // wait and do nothing!
05
}
06 }
该函数实现延时功能,并且一直等待到延时完成才退出函数。函数有一个形
参,用于设置延时时间,时基为 0.5ms。hTimebase_500us 是一个全局变量,在滴
答定时器中断服务函数中不断减少至 0。
代码 14-12 延时函数
01 void TB_Set_Delay_500us(u16 hDelay)
02 {
03
hTimebase_500us = hDelay;
04 }
05
06 bool TB_Delay_IsElapsed(void)
07 {
08
if (hTimebase_500us == 0) {
09
return (TRUE);
10
} else {
11
return (FALSE);
12
}
13 }
14
15 void TB_Set_DisplayDelay_500us(u16 hDelay)
16 {
17
hTimebase_display_500us = hDelay;
18 }
19
20 bool TB_DisplayDelay_IsElapsed(void)
21 {
22
if (hTimebase_display_500us == 0) {
23
return (TRUE);
24
} else {
25
return (FALSE);
26
}
27 }
28
29 void TB_Set_DebounceDelay_500us(u8 hDelay)
30 {
31
hKey_debounce_500us = hDelay;
32 }
33
34 bool TB_DebounceDelay_IsElapsed(void)
35 {
36
if (hKey_debounce_500us == 0) {
37
return (TRUE);
38
} else {
39
return (FALSE);
40
}
41 }
42
43 void TB_Set_StartUp_Timeout(u16 hTimeout)
44 {
STM32 技术开发手册
www.ing10bbs.com
45
hStart_Up_TimeLeft_500us = 2*hTimeout;
46 }
47
48 bool TB_StartUp_Timeout_IsElapsed(void)
49 {
50
if (hStart_Up_TimeLeft_500us == 0) {
51
return (TRUE);
52
} else {
53
return (FALSE);
54
}
55 }
这 八 个 函 数 可 以 分 成 两 类 : void TB_Set_XXX_500us(u16 hDelay) 和 bool
TB_XXX_IsElapsed(void) , 并 且 使 用 对 应 的 这 两 个 函 数 实 现 延 时 功 能 。
TB_Set_XXX_500us()函数实现延时时间设置,时基为 0.5ms。TB_XXX_IsElapsed()用
于反馈延时时间是否完成,如果延时已到返回值为 TRUE,否则返回值为 FALSE。
TB_Set_Delay_500us 用于一般性的延时功能,TB_Set_DisplayDelay_500us 用于液
晶 扫 描 周 期 功 能 , TB_Set_DebounceDelay_500us 用 于 按 键 消 抖 功 能 ,
TB_Set_StartUp_Timeout 用于电机启动完成判断。
代码 14-13 SysTick_Handler 函数
01 void SysTick_Handler(void)
02 {
03
if (hTimebase_500us != 0) {
04
hTimebase_500us --;
05
}
06
07
if (hTimebase_display_500us != 0) {
08
hTimebase_display_500us --;
09
}
10
11
if (hKey_debounce_500us != 0) {
12
hKey_debounce_500us --;
13
}
14
15
if (hStart_Up_TimeLeft_500us != 0) {
16
hStart_Up_TimeLeft_500us--;
17
}
18
19 #ifdef FLUX_TORQUE_PIDs_TUNING
20
if (State == RUN) {
21
if (hTorqueSwapping!=0) {
22
hTorqueSwapping--;
23
} else {
24
hTorqueSwapping = SQUARE_WAVE_PERIOD;
25
hTorque_Reference = - hTorque_Reference;
26
}
27
}
28 #endif
29
30
if (hSpeedMeas_Timebase_500us !=0) {
31
hSpeedMeas_Timebase_500us--;
32
} else {
33
hSpeedMeas_Timebase_500us = SPEED_SAMPLING_TIME;
STM32 技术开发手册
www.ing10bbs.com
34
35 #ifdef ENCODER
36
//ENC_Calc_Average_Speed must be called ONLY every SPEED_MEAS_TIMEBASE ms
37
ENC_Calc_Average_Speed();
38 #ifdef OBSERVER_GAIN_TUNING
39
STO_Calc_Speed();
40
STO_Obs_Gains_Update();
41 #endif
42 #elif (defined HALL_SENSORS && defined OBSERVER_GAIN_TUNING)
43
STO_Calc_Speed();
44
STO_Obs_Gains_Update();
45 #elif defined NO_SPEED_SENSORS
46
STO_Calc_Speed();
47 #ifdef OBSERVER_GAIN_TUNING
48
STO_Obs_Gains_Update();
49 #endif
50
if (State == RUN) {
51
if (STO_Check_Speed_Reliability()==FALSE) {
52
MCL_SetFault(SPEED_FEEDBACK);
53
}
54
}
55 #ifdef VIEW_ENCODER_FEEDBACK
56
//ENC_Calc_Average_Speed must be called ONLY every SPEED_MEAS_TIMEBASE ms
57
ENC_Calc_Average_Speed();
58 #endif
59 #endif
60
61 #ifdef DAC_FUNCTIONALITY
62 #if (defined ENCODER || defined VIEW_ENCODER_FEEDBACK)
63
MCDAC_Update_Value(SENS_SPEED,(s16)(ENC_Get_Mechanical_Speed()*250));
64 #elif (defined HALL_SENSORS || defined VIEW_HALL_FEEDBACK)
65
MCDAC_Update_Value(SENS_SPEED,(s16)(HALL_GetSpeed()*250));
66 #endif
67 #if (defined NO_SPEED_SENSORS || defined OBSERVER_GAIN_TUNING)
68
MCDAC_Update_Value(LO_SPEED,(s16)(STO_Get_Speed()*250));
69 #endif
70 #endif
71
}
72
73
74
if (bPID_Speed_Sampling_Time_500us != 0 ) {
75
bPID_Speed_Sampling_Time_500us --;
76
} else {
77
bPID_Speed_Sampling_Time_500us = PID_SPEED_SAMPLING_TIME;
78
if ((wGlobal_Flags & SPEED_CONTROL) == SPEED_CONTROL) {
79
if (State == RUN) {
80 #ifdef HALL_SENSORS
81
if (HALL_GetSpeed() == HALL_MAX_SPEED) {
82
MCL_SetFault(SPEED_FEEDBACK);
83
}
84 #endif
85
//PID_Speed_Coefficients_update(XXX_Get_Speed(),PID_Speed_InitStructure);
86
FOC_CalcFluxTorqueRef();
87
}
88
} else {
89
if (State == RUN) {
90
FOC_TorqueCtrl();
91
}
92
}
93
}
94 }
STM32 技术开发手册
www.ing10bbs.com
该函数是系统滴答定时器中断服务函数,根据配置,该函数每 500us 会运行
一 次 。该函数首先 是对 变量:hTimebase_500us 、hTimebase_display_500us 、
hKey_debounce_500us、hStart_Up_TimeLeft_500us 进行自减一以实现前面的延时
功能。如果定义了 FLUX_TORQUE_PIDs_TUNING 宏,根据图 14-40 中参数值调整
PID 参数整定周期。hSpeedMeas_Timebase_500us 变量用于限定速度测量,速度
测量这里使用 T 法:单位时间内数脉冲个数求速度。编码器模式、霍尔传感器模
式和无传感器模式都有对应的速度计算函数。在有传感器模式下,如果定义了
OBSERVER_GAIN_TUNING 宏,还计算了由状态观测器得到的速度值。无传感器模
式下,在运行状态下会对测量得到的速度值进行判断以检测速度值是否在正常范
围内。如果使能 DAC 功能,会更新速度值到指定位置,以便 DAC 输出。
SysTick_Handler 函数最后是计算 FOC 控制的新的 Id 和 Iq 值。
MC_Globals.c 文件内容
该文件定义电机库使用到的若干个全局变量。
代码 14-14 电机库全局变量定义
01 Curr_Components Stat_Curr_a_b;
/*Stator currents Ia,Ib*/
02
03 Curr_Components Stat_Curr_alfa_beta;
/*Ialpha & Ibeta, Clarke's
04
transformations of Ia & Ib */
05
06 Curr_Components Stat_Curr_q_d;
/*Iq & Id, Parke's transformations of
07
Ialpha & Ibeta, */
08
09 Volt_Components Stat_Volt_a_b;
/*Stator voltages Va, Vb*/
10
11 Volt_Components Stat_Volt_q_d;
/*Vq & Vd, voltages on a reference
12
frame synchronous with the rotor flux*/
13
14 Volt_Components Stat_Volt_alfa_beta;
/*Valpha & Vbeta, RevPark transformations
15
of Vq & Vd*/
16
17 /*Variable of convenience*/
18
19 #ifdef FLUX_TORQUE_PIDs_TUNING
20 volatile u32 wGlobal_Flags = FIRST_START;
21 #else
22 volatile u32 wGlobal_Flags = FIRST_START | SPEED_CONTROL;
23 #endif
24
25 volatile SystStatus_t State;
26
27 PID_Struct_t PID_Flux_InitStructure;
28 volatile s16 hFlux_Reference;
29
30 PID_Struct_t PID_Torque_InitStructure;
STM32 技术开发手册
www.ing10bbs.com
31 volatile s16 hTorque_Reference;
32
33 PID_Struct_t
PID_Speed_InitStructure;
34 volatile s16 hSpeed_Reference;
Stat_Curr_a_b 、 Stat_Curr_alfa_beta 和 Stat_Curr_q_d 都 是 电流 型变 量 。
Stat_Curr_a_b 是 定 子电 流 Ia 和 Ib , 一 般采 集 这 两 个 Ic 通 过 计 算 得 到 ;
Stat_Curr_alfa_beta 是 Clarke 变换后的𝐼𝛼 和𝐼𝛽 。Stat_Curr_q_d 是 Parker 变换后的
Id 和 Iq。Stat_Volt_a_b、Stat_Volt_q_d 和 Stat_Volt_alfa_beta 是电压型变量。
Stat_Volt_a_b 是定子电压 Va 和 Vb。Stat_Volt_q_d 是与转子磁通同步的参考系上
的电压。Stat_Volt_alfa_beta 是反 Parker 变换后的𝑉𝛼 和𝑉𝛽 。
wGlobal_Flags 是一个全局标志位,一般赋值见代码 14-15。
代码 14-15 系统标志
01 #define SPEED_CONTROL
02 #define FIRST_START
03 #define START_UP_FAILURE
04 #define SPEED_FEEDBACK
05 #define BRAKE_ON
06 #define OVERHEAT
07 #define OVER_CURRENT
08 #define OVER_VOLTAGE
09 #define UNDER_VOLTAGE
(u32)0x0001
(u32)0x0002
(u32)0x0004
(u32)0x0008
(u32)0x0010
(u32)0x0100
(u32)0x0200
(u32)0x0400
(u32)0x0800
State 是一个 SystStatus_t 类型(见代码 14-1)变量,指示系统状态。
PID_Flux_InitStructure、PID_Torque_InitStructure 和 PID_Speed_InitStructure 是
PID 结 构 体 变 量 , 分 别 对 应 励 磁 、 转 矩 和 速 度 , 同 时 hFlux_Reference 、
hTorque_Reference 和 hSpeed_Reference 变量对应这个三个 PID 的参考值(目标
值)。
STM32F10x_MCconf.h
该文件用于整体上配置使用库功能。
代码 14-16 库功能选择定义
01 /********************
Current sampling technique definition
***************/
02 /********************
电流采样技术定义
*********************/
03 /*
Define here the technique utilized for sampling the three-phase current */
04
05 /* Current sensing by ICS (Isolated current sensors) */
06 /* 使用隔离电流传感器完成电流采样 */
07 //#define ICS_SENSORS
08
09 /* Current sensing by Three Shunt resistors */
STM32 技术开发手册
www.ing10bbs.com
10 /* 通过三相分流电阻进行完成电流采样 */
11 #define THREE_SHUNT
12
13 /* Current sensing by Single Shunt resistor */
14 /* 通过单相分流电阻进行完成电流采样 */
15 //#define SINGLE_SHUNT
16
17 /******************* Position sensing technique definition ******************/
18 /******************** 位置传感器技术定义
*********************/
19 /* Define here the type of rotor position sensing utilized for both Field
*/
20 /*
Oriented Control and speed regulation
*/
21
22 /* Position sensing by quadrature encoder */
23 /* 增量编码器用于转子位置和速度测量 */
24 //#define ENCODER
25
26 /* Position sensing by Hall sensors */
27 /* 霍尔传感器用于转子位置和速度测量 */
28 #define HALL_SENSORS
29
30 /* Sensorless position sensing */
31 /* 无传感器模式:FOC 算法中状态观测器提供转子位置信息 */
32 //#define NO_SPEED_SENSORS
33 /* When in sensorless operation define here if you also want to acquire any */
34 /* position sensor information
*/
35 /* 处理来自三个霍尔传感器的信息,通过 DAC 功能显示 */
36 /*(并与来自无传感器转子位置重建算法的信息相比) */
37 //#define VIEW_HALL_FEEDBACK
38 /* 处理来至增量式编码器的信息,将通过 DAC 的功能显示 */
39 /*(并与来自无传感器转子位置重建算法的信息相比) */
40 //#define VIEW_ENCODER_FEEDBACK
41
42 /* When in sensorless operation define here if you want to perform an
*/
43 /* alignment before the ramp-up
*/
44 /* 无传感器模式下,在电机启动前都要使驱动器对转子定位 */
45 //#define NO_SPEED_SENSORS_ALIGNMENT
46
47 /************************** FOC methods **************************************/
48 /********************
FOC 控制方法
*********************/
49 /* Internal Permanent Magnet Motors Maximum-Torque-per-Ampere strategy */
50 /* 利用优化的 MTPA(最大转矩每安培)驱动来设计嵌入式永磁同步电动机(IPMSMs)*/
51 //#define IPMSM_MTPA
52
53 /* Flux weakening operations allowed */
54 /* 弱磁控制:用户需求使用电机转速超过额定速度 */
55 //#define FLUX_WEAKENING
56
57 /* Feed forward current regulation based on known motor parameters */
58 /* 前馈计算功能的标准 PID 电流调节 */
59 //#define FEED_FORWARD_CURRENT_REGULATION
60
61 /**************************
Brake technique
*******************************/
62 /********************
刹车技术定义
*********************/
63 /*
Define here the if you want to enable brake resistor management
*/
64 /*
MANDATORY in case of operation in flux weakening region
*/
65
66 /* Uncomment to enable brake resistor management feature */
67 /* 激活电阻制动软件运行 */
68 //#define BRAKE_RESISTOR
69
70 /********************
PIDs differential terms enabling ********************/
71 /*
Define here if you want to enable PIDs differential terms
*/
STM32 技术开发手册
www.ing10bbs.com
72
73 /* Uncomment to enable differential terms of PIDs */
74 /* 激活 MC_PID_regulators 库模块中的 PID 调节功能微分项 */
75 #define DIFFERENTIAL_TERM_ENABLED
76
77 /******************** PIDs gains tuning operations enabling *****************/
78 /********************
PID 增益整定选项使能
*********************/
79 /* Define here if you want to tune currents (Id, Iq) PIDs, Luenberger State */
80 /*
Observer and PLL gains
*/
81
82 /* Uncomment to enable the generation of a square-wave shaped reference Iq */
83 /* 生成专门用于转矩和磁通 PID 增益调节的软件 */
84 /* 只在有传感器模式下有效 */
85 //#define FLUX_TORQUE_PIDs_TUNING
86
87 /* Uncomment to enable the tuning of Luenberger State Observer and PLL gains*/
88 /* 激活在液晶屏幕上生成用于观测器和 PLL 增益调节的可视界面 */
89 #define OBSERVER_GAIN_TUNING
90
91 /***********************
DAC functionality enabling ************************/
92 /********************
DAC 功能使能
*********************/
93 /*Uncomment to enable DAC functionality feature through TIM3 output channels*/
94 /* 激活 DAC 功能 */
95 //#define DAC_FUNCTIONALITY
我们可以根据实际控制需求选择不同的定义组合,使用 FOCGUI 软件配置可
以快速定义。这里,我们选择霍尔传感器,使用三电阻采样电流方式,使用电流
前馈功能,使能 PID 微分项和观测器增益整定功能。因弱磁功能需要刹车电阻支
持,这里就不使能弱磁控制了。
MC_Control_Param.h 文件内容
该文件定义了硬件控制参数,包括功率器件参数、电流调节参数、电源板保
护阈值、速度环采样时间、PID 控制值等等。
代码 14-17 控制参数定义
01 /*********************** POWER DEVICES PARAMETERS ******************************/
02 /*********************** 功率器件参数 ********************************/
03
04 /**** Power devices switching frequency ****/
05 /* 定义 PWM 频率以赫兹为单位 */
06 #define PWM_FREQ
((u16) 16000) // in Hz (N.b.: pattern type is center aligned)
07
08 /****
Deadtime Value
****/
09 /* 定义死区时间以 ns 为单位,以避免直通调件 */
10 #define DEADTIME_NS
((u16) 500) //in nsec; range is [0...3500]
11
12 /****
Uncomment the Max modulation index
****/
13 /**** corresponding to the selected PWM frequency ****/
14 /* 与 PWM 频率选择对应的最大值允许的调制指数 */
15 #define MAX_MODULATION_97_PER_CENT
16
17 /*********************** CURRENT REGULATION PARAMETERS ************************/
18 /*********************** 电流调节参数 *****************************/
STM32 技术开发手册
www.ing10bbs.com
19
20 /**** ADC IRQ-HANDLER frequency, related to PWM ****/
21 /* 定义定子电流采样频率 */
22 /* 磁链和转矩 PID 调节器采样频率=(2*PWM_FREQ)/(REP_RATE + 1) */
23 #define REP_RATE
(1) // (N.b): Internal current loop is performed every
24 //(REP_RATE + 1)/(2*PWM_FREQ) seconds.
25 // REP_RATE has to be an odd number in case of three-shunt
26 // current reading; this limitation doesn't apply to ICS
27
28 //Not to be modified
29 /* 电流采样频率(下式内容不可更改) */
30 #define SAMPLING_FREQ
((u16)PWM_FREQ/((REP_RATE+1)/2))
// Resolution: 1Hz
31
32 /********************** POWER BOARD PROTECTIONS THRESHOLDS ********************/
33 /********************** 电源板保护阀值 *************************/
34
35 /* 这两个阀值(以°C 表示)是用来设置功率器件工作温度范围 */
36 /* 如果测量温度超过 NTC_THRESHOLD_C 就会产生错误提示,*/
37 /* 直到测得温度低于(NTC_THRESHOLD_C-NTC_HYSTERESIS_C)才会消除 */
38 #define NTC_THRESHOLD_C
70 //癈 on heatsink of MB459 board
39 #define NTC_HYSTERIS_C
5
// Temperature hysteresis (癈)
40
41 /* 这两个阀值(以伏表示)规定的是总线直流电压最低值和最高值 */
42 /* 如果总线电压超过 OVERVOLTAGE_THRESHOLD_V 或者低与 UNDERVOLTAGE_THRESHOLD_V,*/
43 /* 就会产生错误信息,直到总线电压尽保持在允许范围内。*/
44 #define OVERVOLTAGE_THRESHOLD_V
64
//Volt on DC Bus of MB459 board
45 #define UNDERVOLTAGE_THRESHOLD_V
15
//Volt on DC Bus of MB459 board
46
47 /* 定义 ADC 输入电压与总线直流电压之比 */
48 #define BUS_ADC_CONV_RATIO
0.044
//DC bus voltage partitioning ratio
49
50 /*********************** SPEED LOOP SAMPLING TIME *****************************/
51 /*********************** 速度回路的采样时间 ***********************/
52
53 //Not to be modified
54 /* 定义选择速度调节循环频率(下式内容不可更改*/
55 #define PID_SPEED_SAMPLING_500us
0
// min 500usec
56 #define PID_SPEED_SAMPLING_1ms
1
57 #define PID_SPEED_SAMPLING_2ms
3
// (3+1)*500usec = 2msec
58 #define PID_SPEED_SAMPLING_5ms
9
// (9+1)*500usec = 5msec
59 #define PID_SPEED_SAMPLING_10ms
19
// (19+1)*500usec = 10msec
60 #define PID_SPEED_SAMPLING_20ms
39
// (39+1)*500usec = 20msec
61 #define PID_SPEED_SAMPLING_127ms
255
// max (255-1)*500us = 127 ms
62
63 //User should make his choice here below
64 /* 速度回路的采样时间 */
65 #define PID_SPEED_SAMPLING_TIME
(u8)(PID_SPEED_SAMPLING_2ms)
66
67 /******************** SPEED PID-CONTROLLER INIT VALUES************************/
68 /******************** 转速 PID 控制器初始化值 *************************/
69
70 /* default values for Speed control loop */
71 /* 在闭环模式下启动转子时转速定位值以 rpm 为单位定义 */
72 #define PID_SPEED_REFERENCE_RPM
(s16)1000
73 /* 速度循环调节比例值(16 位,从 0 到 32767 可调) */
74 #define PID_SPEED_KP_DEFAULT
(s16)450
75 /* 速度调节循环积分值(16 位,从 0 到 32767 可调) */
76 #define PID_SPEED_KI_DEFAULT
(s16)150
77 /* 速度调节循环微分值((16 位,从 0 到 32767 可调) */
78 #define PID_SPEED_KD_DEFAULT
(s16)5
79
80 /* Speed PID parameter dividers
*/
STM32 技术开发手册
www.ing10bbs.com
81 /* 速度调节循环比例增益的系数(无符号的 16 位双功率值) */
82 #define SP_KPDIV ((u16)(32))
83 /* 速度调节循环积分增益系数(无符号的 16 位双功率值) */
84 #define SP_KIDIV ((u16)(512))
85 /* 速度调节循环微分增益系数(无符号的 16 位双功率值) */
86 #define SP_KDDIV ((u16)(64))
87
88 /************** QUADRATURE CURRENTS PID-CONTROLLERS INIT VALUES **************/
89 /************** 正交电流 PID 控制器的初始化值 ***************************/
90 // With MB459 phase current (A)= (PID_X_REFERENCE * 0.64)/(32767 * Rshunt)
91
92 /* default values for Torque control loop */
93 //* 转矩控制模式下启动时,转矩(Iq)的参考值,(有符号 16 位值) */
94 #define PID_TORQUE_REFERENCE
(s16)638 //(N.b: that's the reference init
95 //value in both torque and speed control)
96 /* 转矩循环调节的比例值(有符号的 16 位,从 0 到 32767) */
97 #define PID_TORQUE_KP_DEFAULT (s16)557
98 /* 转矩循环调节的积分值(有符号的 16 位,从 0 到 32767) */
99 #define PID_TORQUE_KI_DEFAULT (s16)814
100 /* 转矩循环调节的微分值(有符号的 16 位,从 0 到 32767) */
101 #define PID_TORQUE_KD_DEFAULT (s16)50
102
103 /* default values for Flux control loop */
104 /* 在转矩控制模式下启动时,磁通量(Id)的参考值(有符号 16 位值);默认值是 0 */
105 #define PID_FLUX_REFERENCE
(s16)0
106 /* 磁通循环调节的比例值(有符号的 16 位,从 0 到 32767) */
107 #define PID_FLUX_KP_DEFAULT (s16)557
108 /* 磁通循环调节的积分值(有符号的 16 位,从 0 到 32767) */
109 #define PID_FLUX_KI_DEFAULT (s16)814
110 /* 磁通循环调节的微分值(有符号的 16 位,从 0 到 32767) */
111 #define PID_FLUX_KD_DEFAULT (s16)50
112
113 // Toruqe/Flux PID parameter dividers
114 /* 电流循环调节中使用的比例增益系数(无符号的 16 位双功率值)*/
115 #define TF_KPDIV
((u16)(512))
116 /* 电流循环调节中使用的积分增益系数(无符号的 16 位双功率值)*/
117 #define TF_KIDIV
((u16)(8192))
118 /* 电流循环调节中使用的微分增益系数(无符号的 16 位双功率值)*/
119 #define TF_KDDIV
((u16)(8192))
120
121 /* Define here below the period of the square waved reference torque generated
122 when FLUX_TORQUE_PIDs_TUNING is uncommented in STM32F10x_MCconf.h
*/
123 /* 当在 stm32f10x_MCconf.h 中取消对 FLUX_TORQUE_PIDs_TUNING 注释时产生的 */
124 /* 参考转矩的方波(以 ms 为单位);*/
125 #define SQUARE_WAVE_PERIOD
(u16)2000 //in msec
该文件定义具体的程序控制参数,比如 PWM 频率、死区时间、电流采样频
率、速度环周期、PID 参数值定义等等。当我们使用新板子和新电机时候往往调
试最多的就是修改这个文件的参数。PWM_FREQ 是 PWM 频率,一般设置为 16KHz,
死 区 时 间 设 置 为 500ns , 定 义 最 大 允 许 调 整 指 数 为 97% , 即
MAX_MODULATION_97_PER_CENT。这几个参数的设定可以通过电机库配套的小
工具 MMIvsPWMFreq.exe 辅助调试,见图 14-55,实际上,我们可以在 FOCGUI
软件中配置好参数也是可以自动生成合适的配置定义的。
STM32 技术开发手册
www.ing10bbs.com
图 14-55 MMIvsPWMFreq.exe 工具使用
REP_RATE 设置定时器的重复计数器,它决定了定子电流采样频率:磁链和
转矩 PID 调节器采样频率=(2*PWM_FREQ)/(REP_RATE + 1)。
NTC_THRESHOLD_C 为过温保护的阈值温度,NTC_HYSTERIS_C 为解除过温保
护的滞后温度。OVERVOLTAGE_THRESHOLD_V 和 UNDERVOLTAGE_THRESHOLD_V 为
过压和欠压保护阈值,BUS_ADC_CONV_RATIO 为电源电压采样与总线电压比,用
于在获取 AD 转换后的电压值从而计算得到总线电压值。
PID_SPEED_SAMPLING_TIME 定义速度环 PID 的调节周期,在 FOCGUI 软件中
就可配置,这里设置为 2ms。
速度环 PID:PID_SPEED_REFERENCE_RPM 为速度环的目标速度参考值,通俗
说 , 就 是 目 标 速 度 值 , 在 速 度 控 制 模 式 下有 效 。 PID_SPEED_KP_DEFAULT 、
PID_SPEED_KI_DEFAULT 和 PID_SPEED_KD_DEFAULT 是速度环 PID 的 3 个初始化
值,不同电机这三个值可能不同,并且需要通过长期调试后才能得到一个比较好
的参数值,关于 PID 参数调整方法在本文档的前面章节已经有所介绍,这里就不
再啰嗦。为简化过程,可以先使用 FOCGUI 软件生成的默认 PID 参数尝试运转电
机(很多情况下是可以启动运转的),然后在此基础上做调整。SP_KPDIV、SP_KIDIV
和 SP_KDDIV 是 PID 参数对应的增益系数,这几个的数值一般由 FOCGUI 软件生
STM32 技术开发手册
www.ing10bbs.com
成不修改(当然也不是说不能修改)。关于这 6 个值是如何具体应用于代码中的
问题,可以参考后续 MC_PID_regulators.c 文件内容讲解。
转矩环 PID:PID_TORQUE_REFERENCE 是目标转矩,在转矩模式下有效,即
Iq 的 参 考 值 值 。 PID_TORQUE_KP_DEFAULT 、 PID_TORQUE_KI_DEFAULT 和
PID_TORQUE_KD_DEFAULT 是转矩环 PID 的 3 个参数,这三个值对电机正常运转
也是非常重要,是调试时候必须常常修改的,类似速度环 PID 参数也是在 FOCGUI
软件生成的参数值基础上调整。
励磁环 PID:PID_FLUX_REFERENCE 是目标励磁,通常设置为 0,可以获取最
大转矩,当然如果是使能弱磁控制,该值就不为 0。PID_FLUX_KP_DEFAULT、
PID_FLUX_KI_DEFAULT 和 PID_FLUX_KD_DEFAULT 是励磁环 PID 的 3 个参数,这三
个值对电机正常运转也是非常重要,是调试时候必须常常修改的,类似速度环 PID
参数也是在 FOCGUI 软件生成的参数值基础上调整。
转矩环 PID 参数和励磁环 PID 参数还需要配合 TF_KPDIV、TF_KIDIV 和
TF_KDDIV 这三个值使用。
SQUARE_WAVE_PERIOD 用于定于参考方波的周期,只有在代码中定义了宏
FLUX_TORQUE_PIDs_TUNING 才有效。
文件最后,电机库还提供了一个根据电机速度自动调整速度环 PID 参数的算
法,具体方法可以参考《STM32F103_永磁同步电机_PMSM_FOC 软件库_用户手
册_中文版.pdf(附录 8)》文档中的“5.9.4 电机频率与速度定义循环 Ki,Kp,Kd
的调节”小节内容。
MC_encoder_param.h 文件内容
该文件是编码器参数设置代码,用于有使用正交、方波、旋转编码器测量位
置/速度传感器情况,即在 stm32f10x_MCconf.h 文件中定义了 ENCODER 或
VIEW_ENCODER_FEEDBACK。
代码 14-18 编码器参数设置
01 #if ((defined ENCODER)||(defined VIEW_ENCODER_FEEDBACK))
02 /* Define here the 16-bit timer chosen to handle encoder feedback */
03 /*TIMER 2 is the mandatory selection when using STM32MC-KIT */
04 /* 两路传感器信号与 TIMER2 输入引脚连接 */
05 //#define TIMER2_HANDLES_ENCODER
STM32 技术开发手册
www.ing10bbs.com
06 /* 两路传感器信号与 TIMER3 输入引脚连接 */
07 #define TIMER3_HANDLES_ENCODER
08 /* 两路传感器信号与 TIMER4 输入引脚连接 */
09 //#define TIMER4_HANDLES_ENCODER
10 #endif // ENCODER
11
12
13 #if defined(TIMER2_HANDLES_ENCODER)
14 #define ENCODER_TIMER
TIM2
// Encoder unit connected to TIM2
15 #elif defined(TIMER3_HANDLES_ENCODER)
16 #define ENCODER_TIMER
TIM3
// Encoder unit connected to TIM3
17 #else // TIMER4_HANDLES_ENCODER
18 #define ENCODER_TIMER
TIM4
// Encoder unit connected to TIM4
19 #endif
20
21 /***************************** Encoder settings ******************************/
22 /* 定义编码器每转一周单通道产生的脉冲数(实际分辨率是 4 的倍数) */
23 #define ENCODER_PPR
(u16)(1000)
// number of pulses per revolution
24
25 /* Define here the absolute value of the application minimum and maximum speed
26
in rpm unit*/
27 /* 定义转子最小机械转速(转数/分),当小于它时,速度测量在应用中不具备可 */
28 /* 行性和安全性;测量速度低于设定值错误计数器就会累积。为了制止这种累计 */
29 /* 并控制电机速度到零, 用户可以将参数设置为零 */
30 #define MINIMUM_MECHANICAL_SPEED_RPM (u32)60
//rpm
31 /* 定义转子最大机械转速(转数/分),当大于它时,速度测量在应用中不具备可 */
32 /* 行性和安全性;测量速度高于设定值错误计数器就会累积。 */
33 #define MAXIMUM_MECHANICAL_SPEED_RPM (u32)10000 //rpm
34
35 /* Define here the number of consecutive error measurement to be detected
36
before going into FAULT state */
37 /* 在产生错误讯息之前,定义速度测量连续检测出速度错误次数 */
38 #define MAXIMUM_ERROR_NUMBER
(u8)4
39 /* Computation Parameter*/
40 //Number of averaged speed measurement
41 /* 定义检测转速使用的缓冲区大小。2 的倍数便于计算。*/
42 #define SPEED_BUFFER_SIZE
12
// power of 2 required to ease computations
43
44 /*************************** Alignment settings *******************************/
45 /*************************** 校准设置 **************************************/
46 /* 正交编码一个相对位置传感器。考虑磁场定向控制要求绝对位置信息,必需以 */
47 /* 某种方式建立一个零角度位置。这可以通过校准相位完成,并在电机第一个启 */
48 /* 动之前进行,也在错误事件后进行。*/
49
50 //Alignemnt duration
51 /* 定义相位校准时间(单位:ms) */
52 #define T_ALIGNMENT
(u16) 100
// Alignment time in ms
53
54 /* 规定向量方向 */
55 #define ALIGNMENT_ANGLE
(u16) 90 //Degrees [0..359]
56 // 90?<-> Ia = I_ALIGNMENT, Ib = Ic =-I_ALIGNMENT/2)
57
58 // With MB459 and ALIGNMENT_ANGLE equal to 90?
59 //final alignment phase current = (I_ALIGNMENT * 0.64)/(32767 * Rshunt)
60 /* 定义参考 Id 幅值的最终值 */
61 #define I_ALIGNMENT
(u16) 1517
62
63 //Do not be modified
64 #define T_ALIGNMENT_PWM_STEPS
(u32) ((T_ALIGNMENT * SAMPLING_FREQ)/1000)
65 #define ALIGNMENT_ANGLE_S16
(s16)((s32)(ALIGNMENT_ANGLE) * 65536/360)
66 #define MINIMUM_MECHANICAL_SPEED (u16)(MINIMUM_MECHANICAL_SPEED_RPM/6)
67 #define MAXIMUM_MECHANICAL_SPEED (u16)(MAXIMUM_MECHANICAL_SPEED_RPM/6)
STM32 技术开发手册
www.ing10bbs.com
宏定义选择了用于读取编码器数据的功能定时器,设置编码器的线数(单个
编码器信号线在电机旋转一圈输出的脉冲数),设置校准参数。很多情况下,该
文件我们需要修改的只是定时器选择(根据硬件电路)和编码器线数设置(根据
电机)。
MC_hall_prm.h 文件内容
该文件是霍尔传感器参数设置代码,用于有使用霍尔传感器测量位置/速度
传 感 器 情 况 , 即 在 stm32f10x_MCconf.h 文 件 中 定 义 了 HALL_SENSORS 或
VIEW_HALL_FEEDBACK。
代码 14-19 霍尔传感器参数
01 /* Define here the 16-bit timer chosen to handle hall sensors feedback */
02 /* Timer 2 is the mandatory selection when using STM32MC-KIT */
03 /* 三路霍尔传感器信号与 TIMER2 输入引脚相连 */
04 //#define TIMER2_HANDLES_HALL
05 /* 三路霍尔传感器信号与 TIMER3 输入引脚相连 */
06 #define TIMER3_HANDLES_HALL
07 /* 三路霍尔传感器信号与 TIMER4 输入引脚相连 */
08 //#define TIMER4_HANDLES_HALL
09
10 /* HALL SENSORS PLACEMENT ----------------------------------------------------*/
11 /* 以角度位单位定义霍尔传感器之间电位移(物理位移×磁极对数)*/
12 #define DEGREES_120 0
13 #define DEGREES_60 1
14
15 /* Define here the mechanical position of the sensors with reference to an
16
electrical cycle */
17 #define HALL_SENSORS_PLACEMENT DEGREES_120
18
19 /* Define here in degrees the electrical phase shift between the low to high
20 transition of signal H1 and the maximum of the Bemf induced on phase A */
21 /* 以角度定义 0°与 TIMx_CH1 第一个上升沿之间的电角度偏移。*/
22 #define HALL_PHASE_SHIFT (s16) 120
23
24 /* APPLICATION SPEED DOMAIN AND ERROR/RANGE CHECKING -------------------------*/
25
26 /* Define here the rotor mechanical frequency above which speed feedback is not
27 realistic in the application: this allows discriminating glitches for instance
28 */
29 /* 定义转子最大机械转速(转数/分),当大于它时,速度反馈不可用:用于区别故障。*/
30 #define HALL_MAX_SPEED_FDBK_RPM ((u32)10000)
31
32 /* Define here the returned value if measured speed is > MAX_SPEED_FDBK_RPM
33 It could be 0 or FFFF depending on upper layer software management */
34 /* 如果检测到速度大于 HALL_MAX_SPEED_FDBK_RPM 时,函数 HALL_GetSpeed */
35 /*(单位:0.1Hz)返回这个值。 */
36 #define HALL_MAX_SPEED
((u16)5000) // Unit is 0.1Hz
37 /* 如果检测到速度大于 HALL_MAX_SPEED_FDBK_RPM 时,函数 HALL_GetRotorFreq */
38 /* 返回这个值。单位:dpp。 */
39 // With digit-per-PWM unit (here 2*PI rad = 0xFFFF):
40 #define HALL_MAX_PSEUDO_SPEED
((s16)-32768)
STM32 技术开发手册
www.ing10bbs.com
41
42 /* Define here the rotor mechanical frequency below which speed feedback is not
43 realistic in the application: this allows to discriminate too low freq for
44 instance */
45 /* 定义转子最小机械转速(rpm),当小于它时,速度反馈不可用。 */
46 #define HALL_MIN_SPEED_FDBK_RPM ((u16)60)
47
48 /* Max TIM prescaler ratio defining the lowest expected speed feedback */
49 /* -定义可以测量的最低速度(当计数=0xFFFF)*/
50 /* -当电机停止时,防止时钟分频器减小过度。(每个捕获中断以优化时钟分辨 */
51 /* 率时预分频器会自动调整) */
52 #define HALL_MAX_RATIO
((u16)800u)
53
54 /* Number of consecutive timer overflows without capture: this can indicate
55 that informations are lost or that speed is decreasing very sharply */
56 /* This is needed to implement hall sensors time-out. This duration depends on hall sensor
57 timer pre-scaler, which is variable; the time-out will be higher at low speed*/
58 /* 定时器连续溢出的最大次数 */
59 /* 当定时器出现两次以上溢出(意味霍尔传感器在连续有效边缘周期至少增大两倍),*/
60 /* 溢出数目将不再累加。这通常表明数据已经丢失(霍尔传感器超时)或速度的急剧 */
61 /* 下降。对应超时延迟根据选择定时器预分频器不同而不同,是一个变量;预分频器 */
62 /*频率越高(低速),定时器超时时间越长 */
63 #ifdef FLUX_TORQUE_PIDs_TUNING
64 #define HALL_MAX_OVERFLOWS
((u16)4)
65 #else
66 #define HALL_MAX_OVERFLOWS
((u16)2)
67 #endif
68
69 /* ROLLING AVERAGE DEPTH -----------------------------------------------------*/
70 /* 存储最新测量的速度的软件 FIFO 长度。在连续数据块堆栈之间有必要计算滚动平均值。*/
71 #ifdef FLUX_TORQUE_PIDs_TUNING
72 #define HALL_SPEED_FIFO_SIZE ((u8)1)
73 #else
74 #define HALL_SPEED_FIFO_SIZE ((u8)6)
75 #endif
该文件通过宏定义选择读取霍尔传感器信号的功能定时器,选择霍尔传感器
的电位移,已经相关极限参数。通用的,该文件我们一般只设置功能定时器(根
据硬件电路)和霍尔传感器的电位移(根据电机)。
MC_State_Observer_param.h 文件内容
该文件是状态观测器的参数代码,该文件代码在无传感器模式下的调试非常
重要。
在控制理论中一个状态观察器就是一个体系,一个提供了评估内部状态的实
时系统,并测量其输入和输出值。在无传感器模式下,电机的内部状态包括反电
动势和相电流,而输入由相电压供给,输出由相电流压供给,见图 14-56。直流
母线电压测量接收电压命令,并向电机施加相电压。
STM32 技术开发手册
www.ing10bbs.com
图 14-56 无传感器算法模块图
特别是,所观察到的状态通过相电流与实时系统一致性进行对比,其结果通
过增益向量(K1,K2)来调整模块。
电机反电动势定义如下:
公式 14-43 电机反电动势
𝑒𝛼 = 𝛷𝑚 𝑝𝜔𝑟 𝑐𝑜𝑠(𝑝𝜔𝑟 𝑡)
𝑒𝛽 = −𝛷𝑚 𝑝𝜔𝑟 𝑠𝑖𝑛(𝑝𝜔𝑟 𝑡)
可以看出,其中包含转子电角度。接着,反电动势送到作为 PLL 的模块,这
个模块能够重构转子电角度和速度。
图 14-57 是电机在定向控制运行时(顺时针转动)局部截图;黄色的和红色
的波形(C1,C2)分别代表观察到的反电动势𝛼和𝛽 ,蓝色方波(C3)是安装在 a
轴上的霍尔传感器电池传来的信号,绿色正弦波是电流𝑖𝑎 (C4)。
STM32 技术开发手册
www.ing10bbs.com
图 14-57 无传感器的状态观测算法检测的 PMSM 反电动势
(以上内容摘自《STM32F103_永磁同步电机_PMSM_FOC 软件库_用户手册_
中文版.pdf(附录 8)》文档中的“2.2 无传感器的转子位置/速度反馈简介”)
为更好理解观测器原理,还需要仔细研究《 STM32F103_ 永磁同步电机
_PMSM_FOC 软件库_用户手册_中文版.pdf(附录 8)》文档中的“4.5.1 状态观测
器参数”以及附录内容:A.4、A.5 和 A.6。
ST 电机库在无传感器模式下总共有三种方式获取转子位置及速度信息:
 State Observer + PLL
➢ 基于马达的 BEMF,使用相电流及相电压估计马达转子的位置
➢ 适用于马达的转速范围:额定转速的 5% - 100%
➢ 锁相环重构转子电角度和速度
 State Observer + CORDIC
➢ 与 State Observer + PLL 类似,只是在最后得到转子角度时候使用 CORDIC
算法加快运算速度,见 14.5.5 小节。
 高频注入算法 – HFI
➢ 适用于凸极马达(IPMSM, Ld<Lq)
➢ 能实现马达转子位置的精确检测,即使在静止或低速下
➢ 仅 STM32F3 和 STM32F4 系列支持
代码 14-20 状态观测器参数
STM32 技术开发手册
www.ing10bbs.com
01 /******************* 状态观测器参数 **************************/
02 /* 定义等于 ADC 转化的电流值位 32767(最大 16 位),单位安培。 */
03 /* 如果电流是通过分流电阻测得,则:MAX_CURRENT=3.3/(2*Rshunt*Av) */
04 /* Rshunt 是分流电阻值(欧姆); Av 是运算放大器的增益 */
05 #define MAX_CURRENT
20.5
/* max current value, Amps */
06
07 // Values showed on LCD display must be here multiplied by 10
08 /* K1(32 位值)是状态观测器矢量增益参数,当电机运行时,可通过 LCD */
09 /* 接口调节初始值,并评估结果。这样,K1 在 LCD 上显示的值小了 10 倍。 */
10 #define K1 (s32)(-13752)
/* State Observer Gain 1 */
11 // Values showed on LCD display must be here multiplied by 100
12 /* K2(32 位值)是状态观测器矢量增益参数,当电机运行时,可通过 LCD */
13 /* 接口调节初始值,并评估结果。这样,K1 在 LCD 上显示的值小了 100 倍。 */
14 #define K2 (s32)(49789)
/* State Observer Gain 2 */
15
16 /* PLL 相位检测增益 */
17 #define PLL_KP_GAIN
(s16)(202*MOTOR_MAX_SPEED_RPM*POLE_PAIR_NUM/SAMPLING_FREQ)
18 /* PLL 循环过滤增益 */
19 #define PLL_KI_GAIN (s16)(1706742*POLE_PAIR_NUM/SAMPLING_FREQ *
MOTOR_MAX_SPEED_RPM/SAMPLING_FREQ)
20
21
22 /******************* START-UP PARAMETERS ***************************************/
23 /******************* 启动参数 **********************/
24 /*
25
Speed /|\
26 FINAL_START_UP_SPEED |
/
27
|
/
28
|
/
29
|
/
30
|
/
31
|
/
32
| /
33
|/_______________________________________\
34
0
FREQ_START_UP_DURATION
t/
*/
35
36 /* 定义启动允许的总时间,单位毫秒 */
37 #define FREQ_START_UP_DURATION
(u16) 1500 //in msec
38 /* 定义旋转磁通的速度,启动的时间的最后的速度,单位转数/分(这个参数 */
39 /* 设定的是频率线性斜坡的斜率) */
40 #define FINAL_START_UP_SPEED
(u16) 1500 //Rotor mechanical speed (rpm)
41
42 /*
43
|I|/|\
44
|
45
FINAL_I_STARTUP|
__________________
46
|
/
47
|
/
48
| /
49
| /
50
|/
51
FIRST_I_STARTUP|/
52
|_______________________________________________\
53
0 I_START_UP_DURATION FREQ_START_UP_DURATION t /
*/
54
55 // With MB459 phase current = (X_I_START_UP * 0.64)/(32767 * Rshunt)
56 /* 三相电流系统的初始幅值 */
57 #define FIRST_I_STARTUP
(u16) 1358
58 /* 三相电流系统的最终幅值。这幅值的选择应该使产生的磁场力矩与实际应用负载相匹配。
59 */
60 #define FINAL_I_STARTUP
(u16) 2118
61 /* 从幅值的初始到最后,线性电流幅值增长允许时间,单位毫秒 */
STM32 技术开发手册
www.ing10bbs.com
62 #define I_START_UP_DURATION
(u16) 550 //in msec
63
64 // Alignment settings
65 /* 转子校准或预定位设置 */
66 #ifdef NO_SPEED_SENSORS_ALIGNMENT
67
68 //Alignemnt duration
69 /* 校准相位需要的持续时间,单位毫秒 */
70 #define SLESS_T_ALIGNMENT
(u16) 100
// Alignment time in ms
71 /* 指定矢量的方向 */
72 #define SLESS_ALIGNMENT_ANGLE
(u16) 90 //Degrees [0..359]
73 // 90?<-> Ia = SLESS_I_ALIGNMENT, Ib = Ic =-SLESS_I_ALIGNMENT/2)
74
75 // With SLESS_ALIGNMENT_ANGLE equal to 90?final alignment
76 // phase current = (SLESS_I_ALIGNMENT * 1.65/ Av)/(32767 * Rshunt)
77 // being Av the voltage gain between Rshunt and A/D input
78 /* 定义参考 Id 的最终值大小 */
79 #define SLESS_I_ALIGNMENT
(u16) 2117
80
81 #endif
82
83 /**************************** STATISTIC PARAMETERS ****************************/
84 /**************************** 统计参数 *************************************/
85
86 //Threshold for the speed measurement variance.
87 /* 参数设定速度测量方差的门槛 */
88 /* 根 据 公 式 :σ^2 >= μ^2*VARIANCE _THRESHOLD ,当方差观测速度方差大于平均 */
89 /* 值的百分比,就认为无传感算法是不可,σ 和 μ 分别是方差和观测速度平均值(方差 */
90 /* VARIANCE_THRESHOLD 是 0.0625 即平均值的百分比为 25%)。VARIANCE_THRESHOLD 的参 */
91 /* 数越小,故障检测算法就越严格(即安全级别越高),反之亦然。*/
92 #define VARIANCE_THRESHOLD
0.0625 //Percentage of mean value
93
94 // Number of consecutive tests on speed variance to be passed before start-up is
95 // validated. Checked every PWM period
96 /* 在成功启动之前,速度检测方差比 VARIANCE_THRESHOLD 阀值低的连续次数。在这情况下,*/
97 /* 主要状态从启动转化到运行 */
98 #define NB_CONSECUTIVE_TESTS
(u16) 60
99 // Number of consecutive tests on speed variance before the variable containing
100 // speed reliability change status. Checked every SPEED_SAMPLING_TIME
101 /* 转子速度/位置检测方法声明不可靠前,速度检测方差比 VARIANCE_THRESHOLD 阀值高的 */
102 /* 连续次数。在这情况下,主要状态从运转化到故障 */
103 #define RELIABILITY_HYSTERESYS
(u8) 4
104 //Minimum Rotor speed to validate the start-up
105 /* 启动程序终止时的正常运行最小速度以(当转子速度/位置是可靠的),单位转数/分*/
106 #define MINIMUM_SPEED_RPM
(u16) 580
107
108 /* 观测器方程系数,以使电机绕组和电感产生作用 */
109 #define F1 (s16)(16384)
110 #define F2 (s16)(2048)
111
112 //The parameters below shouldn't be modified
113 /*max phase voltage, 0-peak Volts*/
114 #define MAX_VOLTAGE (s16)((3.3/2)/BUS_ADC_CONV_RATIO)
115
116 #define C1 (s32)((F1*RS)/(LS*SAMPLING_FREQ))
117 #define C2 (s32)((F1*K1)/SAMPLING_FREQ)
118 #define C3 (s32)((F1*MAX_BEMF_VOLTAGE)/(LS*MAX_CURRENT*SAMPLING_FREQ))
119 #define C4 (s32)((((K2*MAX_CURRENT)/(MAX_BEMF_VOLTAGE))*F2)/(SAMPLING_FREQ))
120 #define C5 (s32)((F1*MAX_VOLTAGE)/(LS*MAX_CURRENT*SAMPLING_FREQ))
121
122 #define MOTOR_MAX_SPEED_DPP (s32)((1.2*MOTOR_MAX_SPEED_RPM*65536*POLE_PAIR_NUM)\
123
/(SAMPLING_FREQ*60))
STM32 技术开发手册
www.ing10bbs.com
124
125 #define FREQ_STARTUP_PWM_STEPS (u32) ((FREQ_START_UP_DURATION * SAMPLING_FREQ)\
126
/1000)
127 #define FREQ_INC (u16) ((FINAL_START_UP_SPEED*POLE_PAIR_NUM*65536/60)\
128
/FREQ_STARTUP_PWM_STEPS)
129 #define I_STARTUP_PWM_STEPS (u32) ((I_START_UP_DURATION * SAMPLING_FREQ)/1000)
130 #define I_INC (u16)((FINAL_I_STARTUP -FIRST_I_STARTUP)*1024/I_STARTUP_PWM_STEPS)
131 #define PERCENTAGE_FACTOR
(u16)(VARIANCE_THRESHOLD*128)
132 #define MINIMUM_SPEED
(u16) (MINIMUM_SPEED_RPM/6)
MAX_CURRENT 定义了硬件电路支持的最大的电流,K1、K2、PLL_KP_GAIN、
PLL_KI_GAIN、F1 和 F2 这 6 个参数设置了观测器的计算参数。无传感器模式下,
电机只能是开环启动,所以开环阶段参数设置非常重要,一般不同电机型号的启
动参数都是不用,这些参数需要不断调试得到。无传感器模式下,因为转子位置
信号是通过采样相电流后通过状态观测器计算得到的,其可靠性没有使用霍尔传
感器或编码器高,所以一般需要加参数检验得到的位置信息的可靠性,在发现位
置信息不可靠时进行停机控制。
MC_PMSM_motor_param.h 文件内容
该文件存放了 PMSM 参数宏定义,参数值需要根据电机实际情况填充。
代码 14-21 PMSM 参数
01 // Number of motor pair of poles
02 /* 电机磁极对的数目 */
03 #define POLE_PAIR_NUM
(u8) 4
/* Number of motor pole pairs */
04 /* 电机绕阻(相)电阻值,单位欧姆 */
05 #define RS
0.65
/* Stator resistance , ohm*/
06 /* 电机绕阻(相)电感值,单位亨利 */
07 #define LS
0.0006
/* Stator inductance , H */
08
09 // When using Id = 0, NOMINAL_CURRENT is utilized to saturate the output of the
10 // PID for speed regulation (i.e. reference torque).
11 // Whit MB459 board, the value must be calculated accordingly with formula:
12 // NOMINAL_CURRENT = (Nominal phase current (A, 0-to-peak)*32767* Rshunt) /0.64
13 /* 电机额定电流 */
14 #define NOMINAL_CURRENT
(s16)4997 //motor nominal current (0-pk)
15 /* 实际应用电机的最大速度(转数/分) */
16 #define MOTOR_MAX_SPEED_RPM
(u32)3000
//maximum speed required
17 /* 电机恒定电压 Ke(相相 V/krpm RMS),单位伏特 */
18 #define MOTOR_VOLTAGE_CONSTANT
4.1
//Volts RMS ph-ph /kRPM
19 //Demagnetization current
20 /* 电机磁体未被磁化时最大参考电流 Id* */
21 #define ID_DEMAG
-NOMINAL_CURRENT
22
23 #ifdef IPMSM_MTPA
24 //MTPA parameters, to be defined only for IPMSM and if MTPA control is chosen
25 #else
26 #define IQMAX NOMINAL_CURRENT
27 #endif
28
29 #ifdef FLUX_WEAKENING //弱磁运行的额外参数
STM32 技术开发手册
www.ing10bbs.com
30 // 在弱磁控制中定子电压保持恒定的幅值
31 #define FW_VOLTAGE_REF (s16)(985)
//Vs reference, tenth of a percent
32 // 弱磁模块中运行的 PI 调节器的比例增益
33 #define FW_KP_GAIN (s16)(3000)
//proportional gain of flux weakening ctrl
34 // 弱磁模块中运行的 PI 调节器的积分增益
35 #define FW_KI_GAIN (s16)(5000)
//integral gain of flux weakening ctrl
36 // 弱磁模块中运行的 PI 调节器的比例增益系数
37 #define FW_KPDIV ((u16)(32768))
//flux weak ctrl P gain scaling factor
38 // 弱磁模块中运行的 PI 调节器的积分增益系数
39 #define FW_KIDIV ((u16)(32768))
//flux weak ctrl I gain scaling factor
40 #endif
41
42 #ifdef FEED_FORWARD_CURRENT_REGULATION //用于前馈、高性能电流控制的额外参数
43 #define CONSTANT1_Q (s32)(68013)
44 #define CONSTANT1_D (s32)(68013)
45 #define CONSTANT2 (s32)(19769)
46 #endif
47
48 /*not to be modified*/
49 #define MAX_BEMF_VOLTAGE (u16)((MOTOR_MAX_SPEED_RPM*\
50
MOTOR_VOLTAGE_CONSTANT*SQRT_2)/(1000*SQRT_3))
51
52 #define MOTOR_MAX_SPEED_HZ (s16)((MOTOR_MAX_SPEED_RPM*10)/60)
53
54 #define _0_1HZ_2_128DPP (u16)((POLE_PAIR_NUM*65536*128)/(SAMPLING_FREQ*10))
使用 FOCGUI 非常容易得到该文件内的参数值。NOMINAL_CURRENT 是电机
额定电流数字化,计算公式如下:
公式 14-44 数字化电流计算
𝐼 (𝑑𝑖𝑔𝑖𝑡) =
𝐼 (𝐴𝑚𝑝𝑠) × 𝑅𝑠ℎ𝑢𝑛𝑡 × 𝐴𝑣 × 65535
3.3
为计算额定电流,这里的𝐼(𝐴𝑚𝑝𝑠)电机额定电流,值等于 3.13;𝑅𝑠ℎ𝑢𝑛𝑡 为采
样电阻,值等于 0.02;𝐴𝑣 为放大器放大倍数,值等于 4.02。
MOTOR_VOLTAGE_CONSTANT 是电机速度每增加 1000rpm,对应的相-相反电
动势电压增量。
MC_pwm_3shunt_prm.h 文件内容
该文件只用于 3 电阻采样模式,定义了采样硬件参数。
代码 14-22 3 电阻采样硬件参数
01 /////////////////////// PWM Peripheral Input clock ////////////////////////////
02 /////////////////////// PWM 外设输入时钟 ///////////////////////
03 #define CKTIM
((u32)72000000uL)
/* Silicon running at 72MHz Resolution: 1Hz */
04
05 ////////////////////// PWM Frequency ///////////////////////////////////
06 /////////////////////// PWM 频率 ///////////////////////
07 /****
Pattern type is center aligned ****/
08 /****
PWM 采用中心对齐模式 ****/
STM32 技术开发手册
www.ing10bbs.com
09
10 /* PWM 预分频 */
11 #define PWM_PRSC ((u8)0)
12
13 /* Resolution: 1Hz */
14 /* PWM 周期 */
15 #define PWM_PERIOD ((u16) (CKTIM / (u32)(2 * PWM_FREQ *(PWM_PRSC+1))))
16
17 ////////////////////////////// Deadtime Value /////////////////////////////////
18 /* 死区时间*/
19 #define DEADTIME (u16)((unsigned long long)CKTIM/2 \
20
*(unsigned long long)DEADTIME_NS/1000000000uL)
21
22 ///////////////////////////// Current reading parameters //////////////////////
23 ///////////////////////////// 相电流读取参数 //////////////////////
24
25 #define PHASE_A_ADC_CHANNEL
ADC_Channel_11
26 #define PHASE_A_GPIO_PORT
GPIOC
27 #define PHASE_A_GPIO_PIN
GPIO_Pin_1
28
29 #define PHASE_B_ADC_CHANNEL
ADC_Channel_12
30 #define PHASE_B_GPIO_PORT
GPIOC
31 #define PHASE_B_GPIO_PIN
GPIO_Pin_2
32
33 #define PHASE_C_ADC_CHANNEL
ADC_Channel_13
34 #define PHASE_C_GPIO_PORT
GPIOC
35 #define PHASE_C_GPIO_PIN
GPIO_Pin_3
36
37 #define SAMPLING_TIME_NS
200 //200ns
38 //#define SAMPLING_TIME_NS
700 //700ns
39 //#define SAMPLING_TIME_NS 1200 //1.2us
40 //#define SAMPLING_TIME_NS 2450 //2.45us
41
42 #if (SAMPLING_TIME_NS == 200)
43 #define SAMPLING_TIME_CK ADC_SampleTime_1Cycles5
44 #elif (SAMPLING_TIME_NS == 700)
45 #define SAMPLING_TIME_CK ADC_SampleTime_7Cycles5
46 #elif (SAMPLING_TIME_NS == 1200)
47 #define SAMPLING_TIME_CK ADC_SampleTime_13Cycles5
48 #elif (SAMPLING_TIME_NS == 2450)
49 #define SAMPLING_TIME_CK ADC_SampleTime_28Cycles5
50 #else
51 #warning "Sampling time is not a possible value"
52 #endif
53
54 #define TNOISE_NS 2550
55 #define TRISE_NS 2550
56
57 #define SAMPLING_TIME (u16)(((u16)(SAMPLING_TIME_NS) * 72uL)/1000uL)
58 #define TNOISE (u16)((((u16)(TNOISE_NS)) * 72uL)/1000uL)
59 #define TRISE (u16)((((u16)(TRISE_NS)) * 72uL)/1000uL)
60 #define TDEAD (u16)((DEADTIME_NS * 72uL)/1000uL)
61
62 #if (TNOISE_NS > TRISE_NS)
63 #define MAX_TNTR_NS TNOISE_NS
64 #else
65 #define MAX_TNTR_NS TRISE_NS
66 #endif
67
68 #define TW_AFTER ((u16)(((DEADTIME_NS+MAX_TNTR_NS)*72ul)/1000ul))
69 #define TW_BEFORE (((u16)(((((u16)(SAMPLING_TIME_NS)))*72ul)/1000ul))+1)
70
71 ///////////////// Power Stage management Conversions setting ////////////////////////
STM32 技术开发手册
www.ing10bbs.com
72 ///////////////// 功率级管理转换设置 ////////////////////////
73
74 #define TEMP_FDBK_CHANNEL
ADC_Channel_10
75 #define TEMP_FDBK_CHANNEL_GPIO_PORT
GPIOC
76 #define TEMP_FDBK_CHANNEL_GPIO_PIN
GPIO_Pin_0
77
78 #define BUS_VOLT_FDBK_CHANNEL
ADC_Channel_15
79 #define BUS_VOLT_FDBK_CHANNEL_GPIO_PORT
GPIOC
80 #define BUS_VOLT_FDBK_CHANNEL_GPIO_PIN
GPIO_Pin_5
宏定义 CKTIM 为定时器频率,使用 stm32 的高级控制定时器,频率为 72MHz;
PWM_PERIOD 计算表达式的分母中有个 2,主要是考虑到 PWM 使用中心对齐模
式,特别的电机库里边很多地方都是有 2 倍关系,在理解时候如果遇到困难可以
往这个地方考虑。
STM32x_svpwm_3shunt.c 文件内容
该文件是三相电拓扑电流采集和空间矢 PWM 产生,该文件代码主要实现
SVPWM 输出和 3 相电流采集。
代码 14-23 SVPWM_3ShuntInit 函数
01 void SVPWM_3ShuntInit(void)
02 {
03
ADC_InitTypeDef ADC_InitStructure;
04
TIM_TimeBaseInitTypeDef TIM1_TimeBaseStructure;
05
TIM_OCInitTypeDef TIM1_OCInitStructure;
06
TIM_BDTRInitTypeDef TIM1_BDTRInitStructure;
07
NVIC_InitTypeDef NVIC_InitStructure;
08
GPIO_InitTypeDef GPIO_InitStructure;
09
10
/* ADC1, ADC2, DMA, GPIO, TIM1 clocks enabling -----------------------------*/
11
12
/* ADCCLK = PCLK2/6 */
13
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
14
15
/* Enable DMA clock */
16
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
17
18
/* Enable GPIOA, GPIOC, GPIOE, AFIO clocks */
19
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA |
20
RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOB, ENABLE);
21
/* Enable ADC1 clock */
22
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
23
24
/* Enable ADC2 clock */
25
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);
26
27
/* Enable TIM1 clock */
28
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
29
30
/* ADC1, ADC2, PWM pins configurations -------------------------------------*/
31
GPIO_StructInit(&GPIO_InitStructure);
32
/****** Configure PC.00,01,02,03,04,05(ADC Channels [10..15]) as analog input ****/
33
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 |
GPIO_Pin_5;
STM32 技术开发手册
www.ing10bbs.com
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 //
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* TIM1 Peripheral Configuration -------------------------------------------*/
/* TIM1 Registers reset */
TIM_DeInit(TIM1);
TIM_TimeBaseStructInit(&TIM1_TimeBaseStructure);
/* Time Base configuration */
TIM1_TimeBaseStructure.TIM_Prescaler = 0x0;
TIM1_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned1;
TIM1_TimeBaseStructure.TIM_Period = PWM_PERIOD;
TIM1_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV2;
// Initial condition is REP=0 to set the UPDATE only on the underflow
TIM1_TimeBaseStructure.TIM_RepetitionCounter = REP_RATE;
TIM_TimeBaseInit(TIM1, &TIM1_TimeBaseStructure);
TIM_OCStructInit(&TIM1_OCInitStructure);
/* Channel 1, 2,3 in PWM mode */
TIM1_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM1_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM1_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
TIM1_OCInitStructure.TIM_Pulse = 0x505; //dummy value
TIM1_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM1_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
TIM1_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
TIM1_OCInitStructure.TIM_OCNIdleState = LOW_SIDE_POLARITY;
TIM_OC1Init(TIM1, &TIM1_OCInitStructure);
TIM_OC2Init(TIM1, &TIM1_OCInitStructure);
TIM_OC3Init(TIM1, &TIM1_OCInitStructure);
/*Timer1 alternate function full remapping*/
GPIO_PinRemapConfig(GPIO_FullRemap_TIM1,ENABLE);
GPIO_StructInit(&GPIO_InitStructure);
/* GPIOA Configuration: Channel 1, 2, 3 and 4 Output */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* GPIOB Configuration: Channel 1, 1N, 2, 2N, 3, 3N and 4 Output */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* Lock GPIOA Pin8 and Pin9 Pin 10 (High sides) */
GPIO_PinLockConfig(GPIOA, GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10);
GPIO_StructInit(&GPIO_InitStructure);
/* GPIOE Configuration: BKIN pin */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB, &GPIO_InitStructure);
TIM_OCStructInit(&TIM1_OCInitStructure);
/* Channel 4 Configuration in OC */
TIM1_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM1_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM1_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable;
TIM1_OCInitStructure.TIM_Pulse = PWM_PERIOD - 1;
STM32 技术开发手册
www.ing10bbs.com
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
TIM1_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM1_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_Low;
TIM1_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
TIM1_OCInitStructure.TIM_OCNIdleState = LOW_SIDE_POLARITY;
TIM_OC4Init(TIM1, &TIM1_OCInitStructure);
/* Enables the TIM1 Preload on CC1 Register */
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
/* Enables the TIM1 Preload on CC2 Register */
TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);
/* Enables the TIM1 Preload on CC3 Register */
TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable);
/* Enables the TIM1 Preload on CC4 Register */
TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable);
/* Automatic Output enable, Break, dead time and lock configuration*/
TIM1_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
TIM1_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
TIM1_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_1;
TIM1_BDTRInitStructure.TIM_DeadTime = DEADTIME;
TIM1_BDTRInitStructure.TIM_Break = TIM_Break_Disable;
TIM1_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High;
TIM1_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Disable;
TIM_BDTRConfig(TIM1, &TIM1_BDTRInitStructure);
TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update);
TIM_ClearITPendingBit(TIM1, TIM_IT_Break);
//TIM_ITConfig(TIM1, TIM_IT_Break,ENABLE);
/* TIM1 counter enable */
TIM_Cmd(TIM1, ENABLE);
// Resynch to have the Update evend during Undeflow
TIM_GenerateEvent(TIM1, TIM_EventSource_Update);
// Clear Update Flag
TIM_ClearFlag(TIM1, TIM_FLAG_Update);
TIM_ITConfig(TIM1, TIM_IT_Update, DISABLE);
TIM_ITConfig(TIM1, TIM_IT_CC4,DISABLE);
/* ADC1 registers reset ----------------------------------------------------*/
ADC_DeInit(ADC1);
/* ADC2 registers reset ----------------------------------------------------*/
ADC_DeInit(ADC2);
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE);
/* Enable ADC2 */
ADC_Cmd(ADC2, ENABLE);
/* ADC1 configuration ------------------------------------------------------*/
ADC_StructInit(&ADC_InitStructure);
ADC_InitStructure.ADC_Mode = ADC_Mode_InjecSimult;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Left;
STM32 技术开发手册
www.ing10bbs.com
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212 }
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
/* ADC2 Configuration ------------------------------------------------------*/
ADC_StructInit(&ADC_InitStructure);
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Left;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC2, &ADC_InitStructure);
// Start calibration of ADC1
ADC_StartCalibration(ADC1);
// Start calibration of ADC2
ADC_StartCalibration(ADC2);
// Wait for the end of ADCs calibration
while (ADC_GetCalibrationStatus(ADC1) & ADC_GetCalibrationStatus(ADC2)) {
}
SVPWM_3ShuntCurrentReadingCalibration();
/* ADC2 Injected conversions configuration */
ADC_InjectedSequencerLengthConfig(ADC2,2);
ADC_InjectedChannelConfig(ADC2, PHASE_A_ADC_CHANNEL, 1, SAMPLING_TIME_CK);
ADC_InjectedChannelConfig(ADC2, TEMP_FDBK_CHANNEL, 2,SAMPLING_TIME_CK);
/* Configure one bit for preemption priority */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/* Enable the ADC Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = ADC1_2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = ADC_PRE_EMPTION_PRIORITY;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = ADC_SUB_PRIORITY;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* Enable the Update Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = TIM1_UP_PRE_EMPTION_PRIORITY;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = TIM1_UP_SUB_PRIORITY;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* Enable the TIM1 BRK Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM1_BRK_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = BRK_PRE_EMPTION_PRIORITY;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = BRK_SUB_PRIORITY;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
该函数用于配置微处理器外设来读取三相电阻拓扑电流和产生 PWM。函数
初始化 NVIC、ADC、GPIO、TIM1 外设。特别地,ADC 和 TIM1 外设配置成每 PWM
转换周期同时运行两路 A/D 转换。
STM32 技术开发手册
www.ing10bbs.com
首先是时钟配置和 GPIO 初始化,ADC 时钟配置为 12MHz。ADC 数据使用
DMA 传输,还需要开启 DMA 时钟。
定时器配置计数模式为中心对齐模式,这种模式与表格 14-2 中三相波形图
是刚好对应上的。REP_RATE 设置定时器的重复计数器,它决定了定子电流采样
频率:磁链和转矩 PID 调节器采样频率=(2*PWM_FREQ)/(REP_RATE + 1),可以参
考《STM32F103_永磁同步电机_PMSM_FOC 软件库_用户手册_中文版.pdf(附录
8)》文档中的附录内容:A.1 和 A.2 加深理解。接下来,使能高级控制定时器的
3 个输出通道及其互补通道的输出功能并配置相关属性,这里输出状态是需要根
据实际硬件电路设置的,同时也是需要特别注意的,设置出错非常容易导致电源
直接短路。GPIO_PinLockConfig 函数用于锁住 GPIO 功能,这样在后面程序就不能
修改这些 GPIO 的功能了,这可以防止误操作带来的影响。定时器的通道 4 我们
配置它为 PWM2 模式,我们不是用 CH4 的通道引脚来实际输出什么波形,我们
重点是需要它来软件外部触发 AD 转换,简单来说:定子电流不是随意一个时间
就可以采集的,需要在特定的时间才能采集,而这个特定时间就由 CH4 来触发实
现。高级控制定时器还有一个刹车输入功能,我们主要是调试,暂时把刹车功能
禁用,在实际应用中还是很有必要开启刹车功能的。调用 TIM_SelectOutputTrigger
函数实现 TIM1 的 Update 事件作为外部 TRGO(触发输出)。TIM_GenerateEvent
定时器实际生成,使能定时器更新事件。
AD 转换使用 ADC_Mode_InjecSimult(ADC1 和 ADC2 工作在同步注入模式 )
模式,电机库的 AD 转换策略是:AD 转换由定时器触发采集(主要实现在合适时
刻采集电流)
,电流采样时使用 ADC1 和 ADC2 同时采样两路电流(这里是先获取
得到经放大器后的电压值,然后根据电路算出真正的电流值,第三路电流通过公
式 14-23 计算得到)。总线电压和温度模拟量采样会分别跟在 PHASE_B 和
PHASE_A 电流采集完进行。SVPWM_3ShuntCurrentReadingCalibration 函数用于存
储对应零电流的三路模拟电压,用于补偿运放产生的零漂,该函数后面介绍。
函数最后是中断优先级配置。
STM32 技术开发手册
www.ing10bbs.com
关于 3 电阻模式 ADC 和 TIM 功能具体参考《STM32F103_永磁同步电机
_PMSM_FOC 软件库_用户手册_中文版.pdf(附录 8)》文档中“5.1 三相电阻拓扑
电流采集和空间矢量 PWM 产生:stm32f10x_svpwm_3 shunt 模块”内容。
代码 14-24 SVPWM_3ShuntCurrentReadingCalibration 函数
01 void SVPWM_3ShuntCurrentReadingCalibration(void)
02 {
03
static u16 bIndex;
04
05
/* ADC1 Injected group of conversions end interrupt disabling */
06
ADC_ITConfig(ADC1, ADC_IT_JEOC, DISABLE);
07
08
hPhaseAOffset=0;
09
hPhaseBOffset=0;
10
hPhaseCOffset=0;
11
12
/* ADC1 Injected conversions trigger is given by software and enabled */
13
ADC_ExternalTrigInjectedConvConfig(ADC1, ADC_ExternalTrigInjecConv_None);
14
ADC_ExternalTrigInjectedConvCmd(ADC1,ENABLE);
15
16
/* ADC1 Injected conversions configuration */
17
ADC_InjectedSequencerLengthConfig(ADC1,3);
18
ADC_InjectedChannelConfig(ADC1, PHASE_A_ADC_CHANNEL,1,SAMPLING_TIME_CK);
19
ADC_InjectedChannelConfig(ADC1, PHASE_B_ADC_CHANNEL,2,SAMPLING_TIME_CK);
20
ADC_InjectedChannelConfig(ADC1, PHASE_C_ADC_CHANNEL,3,SAMPLING_TIME_CK);
21
22
/* Clear the ADC1 JEOC pending flag */
23
ADC_ClearFlag(ADC1, ADC_FLAG_JEOC);
24
ADC_SoftwareStartInjectedConvCmd(ADC1,ENABLE);
25
26
/* ADC Channel used for current reading are read
27
in order to get zero currents ADC values*/
28
for (bIndex=0; bIndex <NB_CONVERSIONS; bIndex++) {
29
while (!ADC_GetFlagStatus(ADC1,ADC_FLAG_JEOC)) { }
30
31
hPhaseAOffset += (ADC_GetInjectedConversionValue(ADC1,ADC_InjectedChannel_1)>>3);
32
hPhaseBOffset += (ADC_GetInjectedConversionValue(ADC1,ADC_InjectedChannel_2)>>3);
33
hPhaseCOffset += (ADC_GetInjectedConversionValue(ADC1,ADC_InjectedChannel_3)>>3);
34
35
/* Clear the ADC1 JEOC pending flag */
36
ADC_ClearFlag(ADC1, ADC_FLAG_JEOC);
37
ADC_SoftwareStartInjectedConvCmd(ADC1,ENABLE);
38
}
39
40
SVPWM_InjectedConvConfig();
41 }
函数读取用于电流采集的 ADC 通道模拟电压值。因此必须在 PWM 输出前调
用,以便通过逆变器电流为零。这些值存储在 Phase_x_Offset 变量中。
首先关闭 ADC1 注入组转换完成中断,使能软件触发 ADC1 注入组转换,配
置 3 相电流通道作为 ADC1 转换通道,启动 AD 转换,循环 NB_CONVERSIONS=16
次采集。
STM32 技术开发手册
www.ing10bbs.com
最后,调用 SVPWM_InjectedConvConfig 函数配置电机转动时需要的 AD 转换
功能。
代码 14-25 SVPWM_InjectedConvConfig 函数
01 void SVPWM_InjectedConvConfig(void)
02 {
03
/* ADC1 Injected conversions configuration */
04
ADC_InjectedSequencerLengthConfig(ADC1,2);
05
ADC_InjectedSequencerLengthConfig(ADC2,2);
06
07
ADC_InjectedChannelConfig(ADC1, PHASE_B_ADC_CHANNEL, 1, SAMPLING_TIME_CK);
08
ADC_InjectedChannelConfig(ADC1, BUS_VOLT_FDBK_CHANNEL, 2, SAMPLING_TIME_CK);
09
10
/* ADC1 Injected conversions trigger is TIM1 TRGO */
11
ADC_ExternalTrigInjectedConvConfig(ADC1, ADC_ExternalTrigInjecConv_T1_TRGO);
12
13
ADC_ExternalTrigInjectedConvCmd(ADC2,ENABLE);
14
15
/* Bus voltage protection initialization*/
16
ADC_AnalogWatchdogCmd(ADC1,ADC_AnalogWatchdog_SingleInjecEnable);
17
ADC_AnalogWatchdogSingleChannelConfig(ADC1,BUS_VOLT_FDBK_CHANNEL);
18
ADC_AnalogWatchdogThresholdsConfig(ADC1, OVERVOLTAGE_THRESHOLD>>3,0x00);
19
20
21
/* ADC1 Injected group of conversions end and Analog Watchdog interrupts
22
enabling */
23
ADC_ITConfig(ADC1, ADC_IT_JEOC | ADC_IT_AWD, ENABLE);
24 }
函数配置 ADC1 和 ADC2 的注入组转换由 2 个通道,ADC2 的两个通道分别为
PHASE_A_ADC_CHANNEL
和
TEMP_FDBK_CHANNEL ( 这 两 个 以 在
SVPWM_3ShuntInit 函 数 配 置 完 成 ), ADC1 的 两 个 通 道 分 别 为
PHASE_B_ADC_CHANNEL 和 BUS_VOLT_FDBK_CHANNEL。将高级控制定时器的触发
输出做为 ADC1 的启动转换外部触发输入,这个配置跟 SVPWM_3ShuntInit 函数
里边配置刚好是呼应的。电源电压检测对系统保护起着关键作用,这里使用 ADC
模拟量看门狗监控电源电压,这里监控电源的过压情况。
最后,使能 ADC1 的注入组转换完成中断和 ADC 看门狗中断。
代码 14-26 SVPWM_3ShuntGetPhaseCurrentValues 函数
01 Curr_Components SVPWM_3ShuntGetPhaseCurrentValues(void)
02 {
03
Curr_Components Local_Stator_Currents;
04
s32 wAux;
05
06
switch (bSector) {
07
case 4:
08
case 5: //Current on Phase C not accessible
09
// Ia = (hPhaseAOffset)-(ADC Channel 11 value)
10
wAux = (s32)(hPhaseAOffset)- ((ADC1->JDR1)<<1);
STM32 技术开发手册
www.ing10bbs.com
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//Saturation of Ia
if (wAux < S16_MIN) {
Local_Stator_Currents.qI_Component1= S16_MIN;
} else if (wAux > S16_MAX) {
Local_Stator_Currents.qI_Component1= S16_MAX;
} else {
Local_Stator_Currents.qI_Component1= wAux;
}
// Ib = (hPhaseBOffset)-(ADC Channel 12 value)
wAux = (s32)(hPhaseBOffset)-((ADC2->JDR1)<<1);
// Saturation of Ib
if (wAux < S16_MIN) {
Local_Stator_Currents.qI_Component2= S16_MIN;
} else if (wAux > S16_MAX) {
Local_Stator_Currents.qI_Component2= S16_MAX;
} else {
Local_Stator_Currents.qI_Component2= wAux;
}
break;
case 6:
case 1: //Current on Phase A not accessible
// Ib = (hPhaseBOffset)-(ADC Channel 12 value)
wAux = (s32)(hPhaseBOffset)-((ADC1->JDR1)<<1);
//Saturation of Ib
if (wAux < S16_MIN) {
Local_Stator_Currents.qI_Component2= S16_MIN;
} else if (wAux > S16_MAX) {
Local_Stator_Currents.qI_Component2= S16_MAX;
} else {
Local_Stator_Currents.qI_Component2= wAux;
}
// Ia = -Ic -Ib
wAux = ((ADC2->JDR1)<<1)-hPhaseCOffset- Local_Stator_Currents.qI_Component2;
//Saturation of Ia
if (wAux> S16_MAX) {
Local_Stator_Currents.qI_Component1 = S16_MAX;
} else if (wAux <S16_MIN) {
Local_Stator_Currents.qI_Component1 = S16_MIN;
} else {
Local_Stator_Currents.qI_Component1 = wAux;
}
break;
case 2:
case 3: // Current on Phase B not accessible
// Ia = (hPhaseAOffset)-(ADC Channel 11 value)
wAux = (s32)(hPhaseAOffset)-((ADC1->JDR1)<<1);
//Saturation of Ia
if (wAux < S16_MIN) {
Local_Stator_Currents.qI_Component1= S16_MIN;
} else if (wAux > S16_MAX) {
Local_Stator_Currents.qI_Component1= S16_MAX;
} else {
Local_Stator_Currents.qI_Component1= wAux;
}
// Ib = -Ic-Ia;
wAux = ((ADC2->JDR1)<<1) - hPhaseCOffset - Local_Stator_Currents.qI_Component1;
// Saturation of Ib
if (wAux> S16_MAX) {
Local_Stator_Currents.qI_Component2=S16_MAX;
STM32 技术开发手册
www.ing10bbs.com
74
75
76
77
78
79
80
81
82
83
84
85
86 }
} else if (wAux <S16_MIN) {
Local_Stator_Currents.qI_Component2 = S16_MIN;
} else {
Local_Stator_Currents.qI_Component2 = wAux;
}
break;
default:
break;
}
return (Local_Stator_Currents);
该函数从 ADC 获得的电流值,并计算 q1.15 格式中 A 相和 B 相电流值。当
电机处于不同扇区位置,六个桥臂的开关状态不同,根据采样原理,我们需要在
下桥臂打开时采样该桥臂电流,关于该原因具体分析在后面给出。在扇区 4 和 5
不使用 C 相分流采样电阻;在扇区 6 和 1 不使用 A 相分流采样电阻;在扇区 2 和
3 不使用 B 相分流采样电阻。这里我们分析扇区 6 和 1 情况,其他扇区自己类比
推导即可。ib 的值直接读取 ADC1 注入通道的 AD 转换结果值计算得到,
hPhaseBOffset 是在前面 SVPWM_3ShuntCurrentReadingCalibration 已获取得到的
停止状态下的 AD 偏量。wAux 是一个辅助变量,它是相采样电流 AD 偏量与当前
AD 值差值结果,然后对 wAux 进行最大值、最小值限制出来,最后才作为电子电
流数据。在扇区 6 和 1 不使用 A 相分流采样电阻,所以可以自己得到 B 相和 C 相
电流,那么 ia 的获取需要根据公式 14-23 计算得到就可以。hPhaseCOffset ((ADC2->JDR1)<<1)为 C 相电流,B 相电流在前面已经算出,这样很容易算出 A 相
电流。
该函数主要在 FOC_Model 函数中被调用。
代码 14-27 SVPWM_3ShuntCalcDutyCycles 函数
01 void SVPWM_3ShuntCalcDutyCycles (Volt_Components Stat_Volt_Input)
02 {
03
s32 wX, wY, wZ, wUAlpha, wUBeta;
04
u16 hTimePhA=0, hTimePhB=0, hTimePhC=0, hTimePhD=0;
05
u16 hDeltaDuty;
06
07
wUAlpha = Stat_Volt_Input.qV_Component1 * T_SQRT3 ;
08
wUBeta = -(Stat_Volt_Input.qV_Component2 * T);
09
10
wX = wUBeta;
11
wY = (wUBeta + wUAlpha)/2;
12
wZ = (wUBeta - wUAlpha)/2;
13
14
// Sector calculation from wX, wY, wZ
15
if (wY<0) {
16
if (wZ<0) {
STM32 技术开发手册
www.ing10bbs.com
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
bSector = SECTOR_5;
} else // wZ >= 0
if (wX<=0) {
bSector = SECTOR_4;
} else { // wX > 0
bSector = SECTOR_3;
}
} else { // wY > 0
if (wZ>=0) {
bSector = SECTOR_2;
} else // wZ < 0
if (wX<=0) {
bSector = SECTOR_6;
} else { // wX > 0
bSector = SECTOR_1;
}
}
/* Duty cycles computation */
PWM4Direction=PWM2_MODE;
switch (bSector) {
case SECTOR_1:
hTimePhA = (T/8) + ((((T + wX) - wZ)/2)/131072);
hTimePhB = hTimePhA + wZ/131072;
hTimePhC = hTimePhB - wX/131072;
// ADC Syncronization setting value
if ((u16)(PWM_PERIOD-hTimePhA) > TW_AFTER) {
hTimePhD = PWM_PERIOD - 1;
} else {
hDeltaDuty = (u16)(hTimePhA - hTimePhB);
// Definition of crossing point
if (hDeltaDuty > (u16)(PWM_PERIOD-hTimePhA)*2) {
hTimePhD = hTimePhA - TW_BEFORE; // Ts before Phase A
} else {
hTimePhD = hTimePhA + TW_AFTER; // DT + Tn after Phase A
if (hTimePhD >= PWM_PERIOD) {
// Trigger of ADC at Falling Edge PWM4
// OCR update
//Set Polarity of CC4 Low
PWM4Direction=PWM1_MODE;
hTimePhD = (2 * PWM_PERIOD) - hTimePhD-1;
}
}
}
// ADC_InjectedChannelConfig(ADC1, PHASE_B_CHANNEL,1,
//
SAMPLING_TIME_CK);
ADC1->JSQR = PHASE_B_MSK + BUS_VOLT_FDBK_MSK + SEQUENCE_LENGHT;
//ADC_InjectedChannelConfig(ADC2, PHASE_C_CHANNEL,1,
//
SAMPLING_TIME_CK);
ADC2->JSQR = PHASE_C_MSK + TEMP_FDBK_MSK + SEQUENCE_LENGHT;
break;
/**********************************/
...... 中间省略了部分代码 ......
/************************************/
STM32 技术开发手册
www.ing10bbs.com
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97 }
default:
break;
}
if (PWM4Direction == PWM2_MODE) {
//Set Polarity of CC4 High
TIM1->CCER &= 0xDFFF;
} else {
//Set Polarity of CC4 Low
TIM1->CCER |= 0x2000;
}
/* Load compare registers values */
TIM1->CCR1 = hTimePhA;
TIM1->CCR2 = hTimePhB;
TIM1->CCR3 = hTimePhC;
TIM1->CCR4 = hTimePhD; // To Syncronyze the ADC
想要理解该函数内容,需要一定的理论支持。
理论上,定子的反 Park 变换后的𝑉𝛼 和𝑉𝛽 曲线见图 14-58,同时每同步六相空
间矢量区域对应的 SVPWM 见图 14-59。
图 14-58 𝑽𝜶 和𝑽𝜷 定子电压
STM32 技术开发手册
www.ing10bbs.com
图 14-59 SVPWM 相电压波形
这里定义:
公式 14-45 相关中间变量定义
𝑈𝛼 = √3 × 𝑇 × 𝑉𝛼
𝑈𝛽 = −𝑇 × 𝑉𝛽
𝑋 = 𝑈𝛽
𝑌=
𝑈𝛼 + 𝑈𝛽
2
𝑍=
𝑈𝛽 − 𝑈𝛼
2
根据 X、Y 和 Z 值可以区别为不同的扇区:
表格 14-12 扇区判断
Y<0
Z<0
扇区
Ⅴ
Y≥0
Z<0
Z≥0
X≤0
Ⅳ
X>0
Ⅲ
X≤0
Ⅵ
Z≥0
X>0
Ⅰ
Ⅱ
通过下面公式就可以计算在 A、B 和 C 三相上 PWM 需要正脉冲保持时间:
STM32 技术开发手册
www.ing10bbs.com
公式 14-46 正脉冲保持时间
扇区Ⅰ和Ⅳ:
𝑡𝐴 =
𝑇+𝑋−𝑍
2
,𝑡𝐵 = 𝑡𝐴 + 𝑍,𝑡𝐶 = 𝑡𝐵 − 𝑋
扇区Ⅱ和Ⅴ:
𝑡𝐴 =
𝑇+𝑌−𝑍
2
,𝑡𝐵 = 𝑡𝐴 + 𝑍 ,𝑡𝐶 = 𝑡𝐴 − 𝑌
扇区Ⅲ和Ⅵ:
𝑡𝐴 =
𝑇−𝑋+𝑌
2
,𝑡𝐵 = 𝑡𝐶 + 𝑋,𝑡𝐶 = 𝑡𝐴 − 𝑌
其中:T 为 PWM 周期。
考虑到程序使用 PWM 中心对齐模式,并且相电压必须以占空比的 50%为中
心,最后定时器各个通道比较值计算如下:
公式 14-47 定时器各个通道比较值计算
扇区Ⅰ和Ⅳ:
𝑇𝑖𝑚𝑒𝑃ℎ𝐴 =
𝑇 𝑇 ⁄2 + 𝑋 − 𝑍
+
4
2
𝑇𝑖𝑚𝑒𝑃ℎ𝐵 = 𝑇𝑖𝑚𝑒𝑃ℎ𝐴 + 𝑍
𝑇𝑖𝑚𝑒𝑃ℎ𝐶 = 𝑇𝑖𝑚𝑒𝑃ℎ𝐵 − 𝑋
扇区Ⅱ和Ⅴ:
𝑇𝑖𝑚𝑒𝑃ℎ𝐴 =
𝑇 𝑇 ⁄2 + 𝑌 − 𝑍
+
4
2
𝑇𝑖𝑚𝑒𝑃ℎ𝐵 = 𝑇𝑖𝑚𝑒𝑃ℎ𝐴 + 𝑍
𝑇𝑖𝑚𝑒𝑃ℎ𝐶 = 𝑇𝑖𝑚𝑒𝑃ℎ𝐴 − 𝑌
扇区Ⅲ和Ⅵ:
𝑇𝑖𝑚𝑒𝑃ℎ𝐴 =
𝑇 𝑇 ⁄2 + 𝑌 − 𝑋
+
4
2
𝑇𝑖𝑚𝑒𝑃ℎ𝐵 = 𝑇𝑖𝑚𝑒𝑃ℎ𝐴 + 𝑋
STM32 技术开发手册
www.ing10bbs.com
𝑇𝑖𝑚𝑒𝑃ℎ𝐶 = 𝑇𝑖𝑚𝑒𝑃ℎ𝐴 − 𝑌
图 14-60 为逆变器两个桥臂组成的半桥与采样电阻关系示意图,为测量 I 相
电流,需要读取采样电阻 R 两端的电压 V。
图 14-60 逆变器桥臂和采样电阻位置
可以看出,不管电流 I 是正向或者反向只要 T1 管子关闭、T2 管子导通都是
可以通过读取电压 V 计算得到电流 I 大小,当然如果 T2 关闭或者 T2 导通时间非
常短就不能通过读取电压 V 计算电流 I。无刷电机驱动有 3 个半桥,我们可以任
意读取两相采样电阻两端电压从而优先计算得到这两相电流,再结合根据公式
14-23 就可以轻松算出第三相电流,特别地,使用两个 ADC 可以同时读取两相的
电压。既然,我们可以只读取两相电压,那我们肯定选择对应下桥臂导通时间长
的来读取,参考图 14-59,在扇区 4 和 5 不使用 C 相分流采样电阻;在扇区 6 和
1 不使用 A 相分流采样电阻;在扇区 2 和 3 不使用 B 相分流采样电阻。
另外一个问题是 ADC 采样时间点问题。不同扇区情况有点不同,分析原理
还是都通用的,这里就分析扇区Ⅳ情况。有四种可能情况需要分析:
情况 1:A 相下桥臂导通时间大于 DT+TN
DT 为死区时间;
TN 为由于其它相开关导致的在某一相分流电阻电压产生的噪声持续时间;
TS 为 AD 转换采集时间(下列阐述假定 TS< DT + TN)。
STM32 技术开发手册
www.ing10bbs.com
当产生的 SVPWM 调制指数比较小时(<60%)就会发生这种情况。调制指数指
相电压占最大相电压的百分比(0%到 100%)。
图 14-61 低调制指数 SVPWM 波形
图 14-61 展现的是应用于 A 和 B 相下桥臂的 PWM 信号,还有 B 和 C 相接
stm32 的 ADC 引脚测量的模拟电压(时间基准小于 PWM 周期)。注意中电流反
馈为恒定的,因为这是假设 B 和 C 相上的换向是发生在可视化时间之外。此外,
可以观察到,在这种情况下的两个定子电流采样转换可以与计数器溢出同步执行,
图 14-62 如所示。
STM32 技术开发手册
www.ing10bbs.com
图 14-62 A 相下桥臂周期> DT+TN
情况 2:(DT+TN+TS)/2<ΔDutyA<DT+TN 和ΔDutyAB<DT+TR+TS
随着调制指数的增加,ΔDutyA 值小于 DT+TN。将不可能在计数器溢出时采
样电流。这在种情况下,仍在 A 相两低波之间转换采集两路电流,但只有当计数
器溢出之后才行。考虑到为了避免 A 相开关转换对 B 相电流的反馈产生的噪声,
要求等待干扰结束(TN),见图 14-63。
图 14-63 (DT+TN+TS)/2<ΔDutyA<DT+TN 和ΔDutyAB<DT+TR+TS
情况 3:ΔDutyA<(DT+TN+TS)/2 和ΔDutyA-B>DT+TR+TS
在这种情况下,不可能在 A 相下桥臂开关期间采样电流。无论如何,两个电
流可以在 B 相下桥臂导通和 A 相上桥臂关断时采样。因此,必须选择在 A 相上
桥臂关断之前采样电流 TSμs,见图 14-64。
STM32 技术开发手册
www.ing10bbs.com
图 14-64 ΔDutyA<(DT+TN+TS)/2 和ΔDutyA-B>DT+TR+TS
情况 4:ΔDutyA<(DT+TN+TS)/2 和ΔDutyA-B<DT+TR+TS
在这种情况下,A 相周期太短,无法在两个下桥臂导通采样电流。此外如果
B 相和 A 相周期之间的不同,不足以在 B 相下桥臂导通和 A 相上桥臂关断之间运
行 AD 转换,也不可能采样电流(如图 14-65)
。为了避免这种情况,必需减小最
大的调制指数或降低 PWM 频率。
图 14-65 ΔDutyA<(DT+TN+TS)/2 和ΔDutyA-B<DT+TR+TS
SVPWM_3ShuntCalcDutyCycles 函数有一个 Volt_Components 类型的形参,一
般被赋值为反 Park 计算得到的定子新电压𝑉𝛼 和𝑉𝛽 。T 和 T_SQRT3 宏定义见代码
14-28:
代码 14-28 定时器周期相关宏定义
STM32 技术开发手册
www.ing10bbs.com
01 #define SQRT_3
02 #define T
03 #define T_SQRT3
1.732051
(PWM_PERIOD * 4)
(u16)(T * SQRT_3)
定义 T 为定时器周期的 4 倍,这样根据上面公式计算:wUAlpha、wUBeta、
wX、wY 和 wZ 的值。接下来就是根据 wX、wY 和 wZ 这 3 个变量值计算扇区。
使用 switch 语句区分不同扇区时各个通道比较值计算。我们这里就分析第 1
个扇区情况,其他扇区类比分析就可以。hTimePhA 计算中 131072 数值来由:
131072=4*32768=4*(2^15),因为 Volt_Components 结构体的成员值都是 q1.15 格
式,现在这里计算需要转成 q0 格式,所以需要 2^15 做转换系数,这里的 4 是因
为在定义 T 时已经把 PWM_PERIOD 放大 4 倍。我们把 hTimePhA 计算式转换为
q0 格式可以得:
公式 14-48 hTimePhA 值
2𝑇 𝑇 + 𝑌 − 𝑍
+
2
ℎ𝑇𝑖𝑚𝑒𝑃ℎ𝐴 = 4
4
其中 4 为 PWM 周期倍数,可以发现上式跟之前理论分析的计算式子在 T 参
数上还差一个 2 倍关系,实际上,这个 2 倍关系就是使用 PWM 中心对齐模式原
因。得到 hTimePhA 后就可以非常简单求出 hTimePhB 和 hTimePhC。
接下来是定时器的通道 4 的比较值,用于决定合适时间触发 AD 转换。根据
上面的理论分析,通过 if 语句区分不同情况,最后把比较值保存在 hTimePhD 变
量中。
不同扇区需要使用特定的两相采样电流,这样才能达到较好的效果,所以在
这里配置好扇区对应的 ADC 转换通道,比如这里第 1 扇区就配置 B 相和 C 相进
行 AD 转换。同时,这里把电源电压和温度采集配置在 AD 转换中。
SVPWM_3ShuntCalcDutyCycles 函数最后把 hTimePhA、hTimePhB、hTimePhC
和 hTimePhD 分别赋值给定时器的四个通道比较寄存器。
SVPWM_3ShuntCalcDutyCycles 函数功能概况为:实现反 Clarke 变换、设置定
时器通道比较值、配置 AD 转换通道。
特别地,该函数里边使用直接寄存器赋值代替调用库函数实现,主要是考虑
到代码运行效率问题,这在电机库其它地方都是很常见的。
代码 14-29 SVPWM_3ShuntAdvCurrentReading 函数
STM32 技术开发手册
www.ing10bbs.com
01 void SVPWM_3ShuntAdvCurrentReading(FunctionalState cmd)
02 {
03
if (cmd == ENABLE) {
04
// Enable ADC trigger sync with CC4
05
//ADC_ExternalTrigInjectedConvConfig(ADC1, ADC_ExternalTrigInjecConv_T1_CC4);
06
ADC1->CR2 |= 0x00001000;
07
08
// Enable UPDATE ISR
09
// Clear Update Flag
10
TIM_ClearFlag(TIM1, TIM_FLAG_Update);
11
TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);
12
} else {
13
// Disable UPDATE ISR
14
TIM_ITConfig(TIM1, TIM_IT_Update, DISABLE);
15
16
// Sync ADC trigger with Update
17
//ADC_ExternalTrigInjectedConvConfig(ADC1, ADC_ExternalTrigInjecConv_T1_TRGO);
18
ADC1->CR2 &=0xFFFFEFFF;
19
20
// ReEnable EXT. ADC Triggering
21
ADC1->CR2 |=0x00008000;
22
}
23 }
该函数用于超前电流读取的启动和关闭。如果禁止电流超前读取则电流读取
与事件更新同时执行。函数有一个形参,有两种状态可选:ENABLE 或者 DISABLE。
如果设置为 ENABLE,使能定时器 1 的 CC4 事件作为 ADC1 的外部触发事件,使
能定时器的更新中断。如果设置为 DISABLE,关闭定时器 1 的更新中断,使能定
时器 1 的 TRGO 事件作为 ADC1 的外部触发事件。
代码 14-30 SVPWMUpdateEvent 函数
01 void SVPWMUpdateEvent(void)
02 {
03
// ReEnable EXT. ADC Triggering
04
ADC1->CR2 |= 0x00008000;
05
06
// Clear unwanted current sampling
07
ADC_ClearFlag(ADC1, ADC_FLAG_JEOC);
08 }
该函数在定时器更新中断服务函数被调用,它重新启用外部 ADC 触发,并
清除 AD 转换完成标志位。
代码 14-31 SVPWMEOCEvent 函数
01 u8 SVPWMEOCEvent(void)
02 {
03
// Store the Bus Voltage and temperature sampled values
04
h_ADCTemp = ADC_GetInjectedConversionValue(ADC2,ADC_InjectedChannel_2);
05
h_ADCBusvolt = ADC_GetInjectedConversionValue(ADC1,ADC_InjectedChannel_2);
06
07
if ((State == START) || (State == RUN)) {
08
// Disable EXT. ADC Triggering
09
ADC1->CR2 = ADC1->CR2 & 0xFFFF7FFF;
10
}
STM32 技术开发手册
www.ing10bbs.com
11
12 }
return ((u8)(1));
该函数在 AD 转换结束中断服务函数中调用,它计算总线电压和温度传感器
采样禁用外部 ADC 触发。
MC_FOC_Drive.c 文件内容
该文件存放了 FOC 算法实现代码。提供解耦电磁转矩(Te)调节,以及弱磁
控制。此外,它通过 PID 反馈控制提供速度调节。MC_FOC_Drive.c 文件代码通过
调用其他文件函数实现,不直接操作控制器寄存器。
代码 14-32 FOC_Init 函数
01 void FOC_Init (void)
02 {
03 #ifdef FLUX_WEAKENING
04
FOC_FluxRegulatorInterface_Init();
05
06
Stat_Volt_q_d_1.qV_Component1 = 0;
07
Stat_Volt_q_d_1.qV_Component2 = 0;
08
09
hVMagn = 0;
10 #endif
11
12 #ifdef IPMSM_MTPA
13
FOC_MTPAInterface_Init();
14 #endif
15
16 #ifdef FEED_FORWARD_CURRENT_REGULATION
17
Stat_Volt_q_d_3.qV_Component1 = 0;
18
Stat_Volt_q_d_3.qV_Component2 = 0;
19
Stat_Volt_q_d_2.qV_Component1 = 0;
20
Stat_Volt_q_d_2.qV_Component2 = 0;
21
FOC_FF_CurrReg_Init(CONSTANT1_Q,CONSTANT1_D,CONSTANT2);
22 #endif
23
24
Stat_Curr_q_d_ref_ref.qI_Component1 = 0;
25
Stat_Curr_q_d_ref_ref.qI_Component2 = 0;
26
27
Stat_Curr_q_d_ref.qI_Component1 = 0;
28
Stat_Curr_q_d_ref.qI_Component2 = 0;
29 }
该函数初始化所有与 FOC 算法有关的变量值。每次电机启动优先调用。如果
定义了 FLUX_WEAKENING 宏,调用 FOC_FluxRegulatorInterface_Init 函数初始化与
弱磁运行相关的所有变量为合适的值。如果定义了 IPMSM_MTPA 宏,调用
FOC_MTPAInterface_Init 函数初始化所有与 MTPA 轨迹发生器相关的变量为适当
的值。如果定义了 FEED_FORWARD_CURRENT_REGULATION 宏,初始化电流前馈
调节计算用到的变量并且调用 FOC_FF_CurrReg_Init 函数根据所使用的电机和写
STM32 技术开发手册
www.ing10bbs.com
在 MC_PMSM_motor_param.h 中写定的前馈参数,初始化与前馈运行相关的所有
变量(FOC_FF_CurrReg 函数)。
FOC_Init 函数被 MCL_Init 函数(MC_MotorControl_Layer.c 文件)调用。
代码 14-33 FOC_Model 函数
01 void FOC_Model(void)
02 {
03 #ifdef FEED_FORWARD_CURRENT_REGULATION
04
Volt_Components Stat_Volt_q_d_4;
05
s32 wtemp;
06 #endif
07
08 #if defined HALL_SENSORS
09
//Integrate Speed for rotor angle update
10
HALL_IncElectricalAngle();
11 #endif
12
13
/**********STARTS THE VECTOR CONTROL ************************/
14
15
Stat_Curr_a_b = GET_PHASE_CURRENTS();
16
17
Stat_Curr_alfa_beta = Clarke(Stat_Curr_a_b);
18
19
Stat_Curr_q_d = Park(Stat_Curr_alfa_beta,GET_ELECTRICAL_ANGLE);
20
21 #ifdef NO_SPEED_SENSORS
22
STO_Calc_Rotor_Angle(Stat_Volt_alfa_beta,Stat_Curr_alfa_beta,MCL_Get_BusVolt());
23 #endif
24
25 #ifdef FEED_FORWARD_CURRENT_REGULATION
26
/*loads the Torque Regulator output reference voltage Vqs*/
27
Stat_Volt_q_d_4.qV_Component1 = PID_Regulator(Stat_Curr_q_d_ref_ref.qI_Component1,
28
Stat_Curr_q_d.qI_Component1,&PID_Torque_InitStructure);
29
30
31
/*loads the Flux Regulator output reference voltage Vds*/
32
Stat_Volt_q_d_4.qV_Component2 = PID_Regulator(Stat_Curr_q_d_ref_ref.qI_Component2,
33
Stat_Curr_q_d.qI_Component2,&PID_Flux_InitStructure);
34
35
36
wtemp = (s32)(Stat_Volt_q_d_4.qV_Component1 + Stat_Volt_q_d_3.qV_Component1);
37
38
SATURATION_TO_S16(wtemp);
39
40
Stat_Volt_q_d.qV_Component1 = (s16)wtemp;
41
42
wtemp = (s32)(Stat_Volt_q_d_4.qV_Component2 + Stat_Volt_q_d_3.qV_Component2);
43
44
SATURATION_TO_S16(wtemp);
45
46
Stat_Volt_q_d.qV_Component2 = (s16)wtemp;
47
48 #else
49
/*loads the Torque Regulator output reference voltage Vqs*/
50
Stat_Volt_q_d.qV_Component1 = PID_Regulator(Stat_Curr_q_d_ref_ref.qI_Component1,
51
Stat_Curr_q_d.qI_Component1, &PID_Torque_InitStructure);
52
53
STM32 技术开发手册
www.ing10bbs.com
54
/*loads the Flux Regulator output reference voltage Vds*/
55
Stat_Volt_q_d.qV_Component2 = PID_Regulator(Stat_Curr_q_d_ref_ref.qI_Component2,
56
Stat_Curr_q_d.qI_Component2, &PID_Flux_InitStructure);
57 #endif
58
59
//circle limitation
60
RevPark_Circle_Limitation();
61
62
/*Performs the Reverse Park transformation,
63
i.e transforms stator voltages Vqs and Vds into Valpha and Vbeta on a
64
stationary reference frame*/
65
66
Stat_Volt_alfa_beta = Rev_Park(Stat_Volt_q_d);
67
68
/*Valpha and Vbeta finally drive the power stage*/
69
CALC_SVPWM(Stat_Volt_alfa_beta);
70
71 #ifdef FEED_FORWARD_CURRENT_REGULATION
72
Stat_Volt_q_d_2.qV_Component1 = (s16)((Stat_Volt_q_d_2.qV_Component1*
73
(VOLTAGE_SAMPLING_BUFFER-1)+
74
Stat_Volt_q_d_4.qV_Component1)/
75
VOLTAGE_SAMPLING_BUFFER);
76
Stat_Volt_q_d_2.qV_Component2 = (s16)((Stat_Volt_q_d_2.qV_Component2*
77
(VOLTAGE_SAMPLING_BUFFER-1)+
78
Stat_Volt_q_d_4.qV_Component2)/
79
VOLTAGE_SAMPLING_BUFFER);
80 #endif
81 #ifdef FLUX_WEAKENING
82
83
Stat_Volt_q_d_1.qV_Component1 = (s16)((Stat_Volt_q_d_1.qV_Component1*
84
(VOLTAGE_SAMPLING_BUFFER-1)+
85
Stat_Volt_q_d.qV_Component1)/
86
VOLTAGE_SAMPLING_BUFFER);
87
Stat_Volt_q_d_1.qV_Component2 = (s16)((Stat_Volt_q_d_1.qV_Component2*
88
(VOLTAGE_SAMPLING_BUFFER-1)+
89
Stat_Volt_q_d.qV_Component2)/
90
VOLTAGE_SAMPLING_BUFFER);
91 #endif
92 }
该函数的功能是实现 PMSM 转矩和磁通调节,实现 FOC 算法。
∗
∗
在 FOC 算法控制中,𝑖𝑞𝑠 和𝑖𝑑𝑠 分别控制转矩和磁通,参考值𝑖𝑞𝑠
和𝑖𝑑𝑠
是定义在
∗
∗
该函数之外的,当运行在速度控制模式下,参考值𝑖𝑞𝑠
和𝑖𝑑𝑠
由速度和磁通调节器,
通过 FOC_CalcFluxTorqueRef 函数提供;而在力矩控制模式下则由用户通过液晶
操作输入最终由 FOC_TorqueCtrl 函数提供。所以,由于需要电流源,该函数启动
作为 CR-PWM(current-regulated pulse width modulation 脉冲宽度调制电流调节)的功
率转换器。为此,通过参数 REP_RATE (结合 PWM_FREQ)运行高性能同步(d,q)
电 流 调 节 器 , 其 运 行 频 率 已 被 定 义 : 磁 链 和 转 矩 PID 调 节 器 采 样 频 率
=(2*PWM_FREQ)/(REP_RATE + 1)。
STM32 技术开发手册
www.ing10bbs.com
在由 ADC 的 JEOC 触发的中断服务函数里边,FOC_Model 函数被调用,它通
过 ICS 或者采样电阻(我们的例程使用三电阻采样模式)获取定子电流,然后进
行 Clark 和 Park 变换,得到电流𝑖𝑞𝑠 和𝑖𝑑𝑠 ,如图 14-66。
图 14-66 FOC 算法的基本结构,转矩控制
∗
∗
然后这些电流与参考值𝑖𝑞𝑠
和𝑖𝑑𝑠
一起送人 PID 调节器。这时,如果在库配置
中使能了 FEED_FORWARD_CURRENT_REGULATION 宏,PID 调节器输出的电压𝑣𝑞𝑠
和𝑣𝑑𝑠 ,就会加到前馈模块中的输出电压上。它们又反过来作用到定子结构上(通
过反 Park 变换实现),最后驱动功率。为了正确执行 Park 和反 Park 变换,必须
知道转子的位置信息(𝜃𝑟𝑒𝑙 ),这是因为电流需要在相位定向和与转子磁通正交。根
据 stm32f10x_MCconf.h 中设置可以选择通过编码器、霍尔传感器和无传感器模
式获取转子角度。
FOC_Model 函数中,如果定义了 HALL_SENSORS 宏,即选择霍尔传感器获取
转子位置,运行 HALL_IncElectricalAngle 函数累积计算转子角度。
GET_PHASE_CURRENTS 函数获取特定两相定子电流,如果是三电阻采样模式,
实 际 上 就 是 运 行
SVPWM_3ShuntGetPhaseCurrentValues
函 数 ( 见
stm32f10x_MClib.h 文件中相关宏定义)。通过这个函数可以获取得到 ia 和 i(q1.15
b
格式)。Clarke 函数进行 Clarke 变换,由 ia 和 ib 变换得到 iα和 iβ。Park 函数进行
Park 变换,由 iα和 iβ和转子的位置信息(𝜃𝑟𝑒𝑙 )结合计算得到 iq 和 id,这里还是用
STM32 技术开发手册
www.ing10bbs.com
到 GET_ELECTRICAL_ANGLE 宏定义,实际上它也是一个函数,它是获取转子位置
信息的函数。如果是无传感器模式,需要运行 STO_Calc_Rotor_Angle 函数,使用
状态观测器计算转子速度和角度。
如果不定义宏 FEED_FORWARD_CURRENT_REGULATION,即不使能电流前馈
功能,在得到 iq 和 id 之后直接运行 PID_Regulator 函数分别进行转矩和励磁 PID
运算得到 vq 和 vd。PID_Regulator 函数有三个形参,这里 Stat_Curr_q_d_ref_ref 就
∗
∗
是 参 考值 𝑖𝑞𝑠
和𝑖𝑑𝑠
, Stat_Curr_q_d 就 是 iq 和 id , PID_Torque_InitStructure 和
PID_Flux_InitStructure 是两个 PID 参数结构体变量,保存了力矩和励磁 PID 运算
需要的 P、I、D 等参数值。然后运行 RevPark_Circle_Limitation 函数对运行反 Park
变换之前进行参数限制,主要是使得由 vq 和 vd 作用的结果矢量饱和,具体功能
实现等后面介绍 RevPark_Circle_Limitation 函数时详细分析。Rev_Park 函数进行
反 Park 变换,由 vq 和 vd 变换得到𝑣𝛼 和𝑣𝛽 。CALC_SVPWM 函数就是运行反 Clarke
变 换 并 驱 动 功 率 管 得 到 SVPWM 波 形 电 压 , 对 于 三 电 阻 电 流 采 样 模 式 ,
CALC_SVPWM 实际是 SVPWM_3ShuntCalcDutyCycles 函数。
如果定义宏 FEED_FORWARD_CURRENT_REGULATION,即使能电流前馈功能,
需要先定义一个中间赋值变量 Stat_Volt_q_d_4。这里运行 PID_Regulator 函数由
iq 和 id 结合 PID 算法计算得到 vq 和 vd。Stat_Volt_q_d_3 和 Stat_Volt_q_d_2 都是
电流前馈功能赋值变量,Stat_Volt_q_d_3 由 FOC_FF_CurrReg 函数返回值赋值。
SATURATION_TO_S16 是一个限定参数值在 16 位有符号整型大小内的宏。然后就
可以得到 vq 和 vd 这两个已经加入电流前馈功能的电压值,接下来就是同样分别
运行 RevPark_Circle_Limitation、Rev_Park 和 CALC_SVPWM 这三个函数,驱动功率
∗
∗
管输出 SVPWM 波形电压。最后,计算用于电流前馈功能的参考定子电压𝑖𝑞𝑠
和𝑖𝑑𝑠
保存在变量 Stat_Volt_q_d_2 中。
FOC_FF_CurrReg 函数执行前馈电流调节功能,已被电机库内部封装为 lib 文
件(所以看不到源代码)。前馈功能是由电机库提供的,旨在改进电机驱动器 CRPWM 部分的性能。基本上,它预先参考电流 iq**和 id **值计算定子电压 vq*和
vd*在去控制电机。通过这样做,它支持标准 PID 电流调节,见图 14-67。
STM32 技术开发手册
www.ing10bbs.com
前馈功能在同步参考系中工作,需要很好的了解机器参数,如绕组的电感 Ld
和 Lq(或使用 SM-PMSM 则为 LS)以及电机电压常数 Ke。前馈算法的设计是为
了补偿永磁电动机中频率相关的反电动势和交流耦合感应压降。因此,q 轴和 d
轴的 PID 电流控制回路成为线性的,就可以得到高性能的电流控制方法。另外,
根据当时测量的直源电压来补偿计算定子电压 vq*和 vd*,从而实现总线电压纹波
补偿。
图 14-67 电流前馈调节
根据整个系统的一些参数,例如直流电容大小,电频率,电机参数等,前馈
功能可能对电机驱动器产生作用很大,或者很小。因此,建议用户评估所产生的
系统性能,并仅在测量到有价值的效果时启用功能。使用电机库的前馈功能需要
设置三个宏定义参数:CONSTANT1_Q、CONSTANT1_D 和 CONSTANT2,这三个值
可以通过图 14-49 的 PMSM_MTPA_FEEDFORWARD.xls 文件获取得到。
STM32 技术开发手册
www.ing10bbs.com
现在重新回到 FOC_Model 函数中来,该函数的最后是判断是否使能了弱磁
功能(即定义了 FLUX_WEAKENING 宏),如果使能了弱磁,计算用于弱磁功能的
∗
∗
参考定子电压𝑖𝑞𝑠
和𝑖𝑑𝑠
保存在变量 Stat_Volt_q_d_1 中。
代码 14-34 FOC_CalcFluxTorqueRef 函数
01 void FOC_CalcFluxTorqueRef(void)
02 {
03
Stat_Curr_q_d_ref.qI_Component1 = PID_Regulator(hSpeed_Reference,
04
GET_SPEED_0_1HZ,&PID_Speed_InitStructure);
05
06 #ifdef IPMSM_MTPA
07
Stat_Curr_q_d_ref.qI_Component2 = FOC_MTPA(Stat_Curr_q_d_ref.qI_Component1);
08 #else
09
Stat_Curr_q_d_ref.qI_Component2 = 0;
10 #endif
11
12 #ifdef FLUX_WEAKENING
13
{
14
s16 hVoltageLimit_Reference;
15
16
Curr_Components Stat_Curr_q_d_temp;
17
18
hVoltageLimit_Reference = (s16)((hFW_V_Ref*MAX_MODULE)/1000);
19
20
Stat_Curr_q_d_temp = FOC_FluxRegulator(Stat_Curr_q_d_ref,Stat_Volt_q_d_1,
21
hVoltageLimit_Reference);
22
23
PID_Speed_InitStructure.wLower_Limit_Integral =
24
-((s32)(Stat_Curr_q_d_temp.qI_Component1)*SP_KIDIV);
25
PID_Speed_InitStructure.wUpper_Limit_Integral =
26
((s32)(Stat_Curr_q_d_temp.qI_Component1)*SP_KIDIV);
27
28
if (Stat_Curr_q_d_ref.qI_Component1 > Stat_Curr_q_d_temp.qI_Component1) {
29
Stat_Curr_q_d_ref_ref.qI_Component1 = Stat_Curr_q_d_temp.qI_Component1;
30
} else if (Stat_Curr_q_d_ref.qI_Component1 < -Stat_Curr_q_d_temp.qI_Component1) {
31
Stat_Curr_q_d_ref_ref.qI_Component1 = -Stat_Curr_q_d_temp.qI_Component1;
32
} else {
33
Stat_Curr_q_d_ref_ref.qI_Component1 = Stat_Curr_q_d_ref.qI_Component1;
34
}
35
36
Stat_Curr_q_d_ref_ref.qI_Component2 = Stat_Curr_q_d_temp.qI_Component2;
37
}
38
hVMagn = FOC_FluxRegulator_Update(hFW_P_Gain, hFW_I_Gain);
39
40 #else
41
Stat_Curr_q_d_ref_ref = Stat_Curr_q_d_ref;
42 #endif
43
44 #ifdef FEED_FORWARD_CURRENT_REGULATION
45
Stat_Volt_q_d_3 = FOC_FF_CurrReg(Stat_Curr_q_d_ref_ref,Stat_Volt_q_d_2,
46
GET_SPEED_DPP,MCL_Get_BusVolt());
47 #endif
48
49
hTorque_Reference = Stat_Curr_q_d_ref_ref.qI_Component1;
50
hFlux_Reference = Stat_Curr_q_d_ref_ref.qI_Component2;
51 }
STM32 技术开发手册
www.ing10bbs.com
∗
∗
在速度控制模式下,由这个函数提供电流参考值𝑖𝑞𝑠
和𝑖𝑑𝑠
以给 FOC_Model 函
数调用,见图 14-68。
图 14-68 速度环
∗
该函数先运行 PID_Regulator 函数进行 PID 计算得到电流参考值𝑖𝑞𝑠
,保存在
Stat_Curr_q_d_ref 变量中,参数中 hSpeed_Reference 是目标速度参考值,在液晶
屏可以手动调节,GET_SPEED_0_1HZ 是一个定义在 stm32f10x_MClib.h 文件的宏,
用于获取转子频率(以 0.1Hz 单位),对于霍尔传感器实际运行 HALL_GetSpeed 函
数,对于编码器实际运行 ENC_Get_Mechanical_Speed 函数。如果是使用 IPMSM
∗
( 即 定 义 IPMSM_MTPA ) 调 用 FOC_MTPA 函 数 计 数 得 到 𝑖𝑑𝑠
并保存在
∗
Stat_Curr_q_d_ref 变量中;否则直接这里把𝑖𝑑𝑠
赋值为 0,可以获得最大转矩控制。
IPMSM_MTPA 宏定义是针对内嵌式永磁同步电机(IPMSM)做的优化处理,特别
使用了 MTPA(IPMSM 的最大转矩电流比)控制使得控制 IPMSM 更加优秀,但
是 MTPA 模块是被电机库完全封装的,我们完全看不到功能函数源码;另外因为
我们配套的 PMSM 为 SM-PMSM,综上本文档不介绍 MTPA 相关知识内容,有兴
趣的可以看 ST 官方对 MTPA 模块的介绍和参考网络上相关文档。
在很多应用场合下永磁电机负载要比额定负载低,这时弱磁功能可以使永磁
电机运转速度超过额定速度,从而达到扩大运行速度范围的目的。在这里,额定
STM32 技术开发手册
www.ing10bbs.com
转速是在电机可以提供最大扭矩情况下的最高速度。控制直轴电流 id 可以削弱
磁通;给定电机额定电流 In,例如𝐼𝑛 = √𝑖𝑞2 + 𝑖d2 ,如果让 id≠0,则最大可用正交
电流 iq 就会降低。对应在 SM-PMSM 控制中,最大可传递电磁转矩也下降了。
另一方面,对于 IPMSM 机来说,单独控制 id 会引起 MTPA 路径偏差。
现在技术已经达到“闭环”弱磁控制,而且不需要知道电机参数具体值,这
样就大大降低了对参数偏差的敏感性。这种方案对 IPMSM 和 SM-PMSM 都适用。
这种控制循环基于对定子电压进行监控,见。
根据稳定的阈值(“电压等级”参数)检查电流调节器输出 Vs。如果 Vs 超过
限额,调节控制信号 ifw*自动进入弱磁区域,ifw*与 MTPA 控制器的输出 ids*进行
相加。这是通过 PI 调节器(增益可以在液晶界面中实时进行调节)的方式实现,
也为了防止调节电流达到饱和值。很明显,设定的电压等级越高(通过保持电流
规则),获得的效率也就越高,能实现的最大速度也越大。
如果 Vs 比所选的门槛电压小,那么 ifw 就会减小到零,MTPA 模块就会重新
恢复控制。
弱磁控制器输出电流的 ids**必须与 ids 进行核对比较,以避免电机的退磁。
图 14-69 弱磁控制运行图
在 FOC_CalcFluxTorqueRef 函 数中, 如果 不 使能 弱磁 功 能,直 接 把 变量
Stat_Curr_q_d_ref 值赋值给变量 Stat_Curr_q_d_ref_ref 作为真正 FOC 运算的电流
∗
∗
参考值𝑖𝑞𝑠
和𝑖𝑑𝑠
。
STM32 技术开发手册
www.ing10bbs.com
如果使能弱磁控制(即定义了 FLUX_WEAKENING 宏),首先计算用户设定的
“电压等级”阈值保存在 hVoltageLimit_Reference 变量中,调用 FOC_FluxRegulator
函数计数得到电流 iq**和 id**,该函数也是被封装在电机库中(看不到源代码),
有 3 个形参分别为:参考电流 iq*和 id*、参考定子电压 vq*和 vd*,和电压等级阈
值,返回输出 iq**和 id**。根据 iq**值调节速度环 PID 的积分 I 参数最大值和最
小值限制。然后设定变量 Stat_Curr_q_d_ref_ref 作为真正 FOC 运算的电流参考值
∗
∗
𝑖𝑞𝑠
和𝑖𝑑𝑠
。弱磁控制的最后是调用 FOC_FluxRegulator_Update 函数,该函数根据用
户输入,修改用于弱磁模块的 PI 调节器中的比例和积分增益,返回定子电压放
大。
如果使能电流前馈调节,运行 FOC_FF_CurrReg 函数
FOC_CalcFluxTorqueRef 函数最后为转矩参考值 hTorque_Reference 变量和励
磁参考值 hFlux_Reference 变量赋值。
代码 14-35 FOC_TorqueCtrl 函数
01 void FOC_TorqueCtrl(void)
02 {
03
Stat_Curr_q_d_ref_ref.qI_Component1 = hTorque_Reference;
04
Stat_Curr_q_d_ref_ref.qI_Component2 = hFlux_Reference;
05 #ifdef FEED_FORWARD_CURRENT_REGULATION
06
Stat_Volt_q_d_3 = FOC_FF_CurrReg(Stat_Curr_q_d_ref_ref,Stat_Volt_q_d_2,
07
GET_SPEED_DPP,MCL_Get_BusVolt());
08 #endif
09 }
该函数功能类似上面的 FOC_CalcFluxTorqueRef 函数,这是 FOC_TorqueCtrl 函
∗
∗
数是在转矩控制模式下,由它提供电流参考值𝑖𝑞𝑠
和𝑖𝑑𝑠
以给 FOC_Model 函数调用。
这 里 直 接 把 hTorque_Reference 和 hFlux_Reference 两 个 变 量 值 赋 值 给
∗
∗
Stat_Curr_q_d_ref_ref 作为电流参考值𝑖𝑞𝑠
和𝑖𝑑𝑠
。这里的 hTorque_Reference 和
hFlux_Reference 变量的大小是 用户直接通过液晶菜单设置的。最后,调用
FOC_FF_CurrReg 函数计算得到用于电流前馈调节计算的 Stat_Volt_q_d_3 变量值。
代码 14-36 FOC_FluxRegulatorInterface_Init 函数
01 #ifdef FLUX_WEAKENING
02 void FOC_FluxRegulatorInterface_Init(void)
03 {
04
PID_Struct_t PI_Stat_Volt_InitStructure;
05
06
PI_Stat_Volt_InitStructure.hKp_Gain = hFW_P_Gain;
07
PI_Stat_Volt_InitStructure.hKp_Divisor = FW_KPDIV;
STM32 技术开发手册
www.ing10bbs.com
08
PI_Stat_Volt_InitStructure.hKi_Gain = hFW_I_Gain;
09
PI_Stat_Volt_InitStructure.hKi_Divisor = FW_KIDIV;
10
//Lower Limit for Output limitation
11
PI_Stat_Volt_InitStructure.hLower_Limit_Output = ID_DEMAG;
12
//Upper Limit for Output limitation
13
PI_Stat_Volt_InitStructure.hUpper_Limit_Output = 0;
14
PI_Stat_Volt_InitStructure.wLower_Limit_Integral =
15
PI_Stat_Volt_InitStructure.hLower_Limit_Output *
16
PI_Stat_Volt_InitStructure.hKi_Divisor;
//Lower Limit for Integral term limitation
17
PI_Stat_Volt_InitStructure.wUpper_Limit_Integral = 0;
//Lower Limit for Integral term limitation
18
PI_Stat_Volt_InitStructure.wIntegral = 0;
19
20
PI_Stat_Volt_InitStructure.hKd_Gain = FW_KD_GAIN;
21
PI_Stat_Volt_InitStructure.hKd_Divisor = FW_KDDIV;
22
PI_Stat_Volt_InitStructure.wPreviousError = FW_D_TERM_INIT;
23
24
FOC_FluxRegulator_Init(&PI_Stat_Volt_InitStructure,NOMINAL_CURRENT);
25 }
26 #endif
该函数根据所使用的电机和 MC_PMSM_motor_param.h 文件中弱磁参数,初
始化与弱磁运行相关的所有变量(FOC_Flux_Regulator 函数)为合适的值。它只
用 于 弱 磁 控 制 功 能 。 函 数 定 义 了 一 个 PID 结 构 体 变 量 , 并 根 据 文 件
MC_PMSM_motor_param.h 中 的 参 数 设 定 初 始 化 它 , 最 后 调 用 函 数
FOC_FluxRegulator_Init 初始化弱磁算法。
MC_Clarke_Park.c 文件内容
该文件存放了坐标变换的实现代码。包括 Clarke 变换、Park 变换和反 Park
变换。
代码 14-37 Clarke 函数
01 Curr_Components Clarke(Curr_Components Curr_Input)
02 {
03
Curr_Components Curr_Output;
04
05
s32 qIa_divSQRT3_tmp;
06
s32 qIb_divSQRT3_tmp ;
07
08
s16 qIa_divSQRT3;
09
s16 qIb_divSQRT3 ;
10
11
// qIalpha = qIas
12
Curr_Output.qI_Component1= Curr_Input.qI_Component1;
13
14
qIa_divSQRT3_tmp = divSQRT_3 * Curr_Input.qI_Component1;
15
qIa_divSQRT3_tmp /=32768;
16
17
qIb_divSQRT3_tmp = divSQRT_3 * Curr_Input.qI_Component2;
18
qIb_divSQRT3_tmp /=32768;
19
20
qIa_divSQRT3=((s16)(qIa_divSQRT3_tmp));
21
22
qIb_divSQRT3=((s16)(qIb_divSQRT3_tmp));
STM32 技术开发手册
www.ing10bbs.com
23
24
25
26
27
28 }
//qIbeta = -(2*qIbs+qIas)/sqrt(3)
Curr_Output.qI_Component2=(-(qIa_divSQRT3)-(qIb_divSQRT3)-(qIb_divSQRT3));
return (Curr_Output);
该函数将定子电流𝑖𝑎 和𝑖𝑏 (直接沿轴线间隔 120 度)转化为双轴垂直静止参
考坐标(𝛼, β)中的电流𝑖𝛼 和𝑖𝛽 ;α和β轴间隔 90 度角。该函数有一个形参,是定子
电流𝑖𝑎 和𝑖𝑏 (q1.15 格式)。
这里用到公式:
公式 14-49 Clarke 变换公式
𝑖α = 𝑖𝑎
𝑖𝛽 = −
𝑖𝑎 + 2𝑖𝑏
√3
代码中有个宏定义 divSQRT_3,值为 0x49E6,它以 q1.15 格式定义了1⁄√3
(实际值约等于 0.57735027)常数。这里就分析1⁄√3常量的 q1.15 格式值为
0x49E6 的由来,后面其他地方的 q1.15 格式可以类似分析。位权规划方法:①整
数部分:q1.15 是用 1 位表示整数,并且第 0 位必须为负数,其他位为正数,所
以这里第 0 位为-1,类比如果是 q3.13 格式,那么第 0 位为-4,第 1 位为 2,第 2
1
位为 1。②分数部分:q1.15 用 15 位分数,分数是以2𝑛(n 从 1 递增)等比序列,
1
1
1
1
1
1
即2、4、8、16、32、64……。接下来就是决定每个位具体填‘0’还是‘1’,这
个就从第 0 位开始比较就好了,对于 0.5773315 显然第 0 位(权值为-1)必须为
0,第 1 位(权值为 0.5,比目标值小)必须为 1,第 2 位(权值为 0.25)必须为
0,因为 0.5+0.25=0.75 大于目标值,因为第 1 位已经设置为 1,显然第 2 位必须
为 0,后面位类似方法分析,显然最后结果是:这 16 个位的每位权值和该位值
(‘0’或者‘1’)乘积之和非常接近目标值大小,见。程序中就是直接使用这
16 位作为该数值的保存数据。
STM32 技术开发手册
www.ing10bbs.com
表格 14-13 1⁄√3 常数在 q1.15 格式中的存储方法
位数
0
q1.15
0x49
8
2
0.5
0
位数
q1.15
1
-1
0.25
1
9
3
4
0.125
0.0625
0
10
0
11
5
1
12
6
0.03125
7
0.015625 0.0078125
0
13
0
14
1
15
0.00390625 0.00195313 0.00097656 0.00048828 0.00024414 0.00012207 6.1035E-05 3.0518E-05
0xE6
1
1
1
0
0
1
1
结果
0 0.57733154
实际上,简便的计算方法如下式:
公式 14-50 q1.15 格式数值转换
0.57735027 ∗ 32768 ≈ 18918 → 0x49E6
相反的:
0x49E6
≈ 0.57733154
32768
特 别 地 , 上 面 代 码 的 第 15 和 18 行 中 把 qIa_divSQRT3_tmp 和
qIb_divSQRT3_tmp 临时变量都除以 32768 是为了保持格式相等,因为 divSQRT_3
和 Curr_Input 都是以 q1.15 格式,直接相乘后需要做数值格式处理。
代码 14-38 Trig_Functions 函数
01 Trig_Components Trig_Functions(s16 hAngle)
02 {
03
u16 hindex;
04
Trig_Components Local_Components;
05
06
/* 10 bit index computation */
07
hindex = (u16)(hAngle + 32768);
08
hindex /= 64;
09
10
switch (hindex & SIN_MASK) {
11
case U0_90:
12
Local_Components.hSin = hSin_Cos_Table[(u8)(hindex)];
13
Local_Components.hCos = hSin_Cos_Table[(u8)(0xFF-(u8)(hindex))];
14
break;
15
16
case U90_180:
17
Local_Components.hSin = hSin_Cos_Table[(u8)(0xFF-(u8)(hindex))];
18
Local_Components.hCos = -hSin_Cos_Table[(u8)(hindex)];
19
break;
20
21
case U180_270:
22
Local_Components.hSin = -hSin_Cos_Table[(u8)(hindex)];
23
Local_Components.hCos = -hSin_Cos_Table[(u8)(0xFF-(u8)(hindex))];
24
break;
25
26
case U270_360:
27
Local_Components.hSin = -hSin_Cos_Table[(u8)(0xFF-(u8)(hindex))];
28
Local_Components.hCos = hSin_Cos_Table[(u8)(hindex)];
29
break;
STM32 技术开发手册
www.ing10bbs.com
30
31
32
33
34 }
default:
break;
}
return (Local_Components);
该函数用于计算得到输入角度的对应正弦值和余弦值。有一个形参 hAngle
表示角度值,是一个 s16(16 位有符号整数)变量,该值与实际角度(弧度单位)
对应关系如图 14-70 所示。使用整数表达易于 stm32 计算,加快运算速率。
图 14-70 角度表达
同样的,Trig_Functions 函数返回值也是以 s16 格式表示正余弦值,对应的关
系如图 14-71 所示。
STM32 技术开发手册
www.ing10bbs.com
图 14-71 正余弦值表达
Trig_Functions 函数就是根据输入角度然后计算得到正余弦值。这里我们以
一个实例来分析和展示该函数的运算过程,假设 hAngle=20000,实际对应的弧度
值为:(20000 × 𝜋)⁄32768 ≈ 1.9174。hindex 是定义的一个 10bit 索引局部变量
(范围为 0~1024),hindex = (20000 + 32768)⁄64 = 0𝑥338,然后计算角度所在
区间(把 360 度分成 4 等分)范围:hindex&SIN_MASK = 0x338&0x0300 =
0x0300 = U90_180,这样运行 case U90_180 里边代码,hSin_Cos_Table 是一个含
有 256 个元素的常量数组,该数组内容是取正弦函数在 0~𝜋⁄4范围内的离散 256
个点对应的函数值,并且是把数值范围放大到 0x7FFF。这里特别注意(u8)在这里
作用,把相关数值都强制限制为无符号的 8bit 整数。
代码 14-39 Park 函数
01 Curr_Components Park(Curr_Components Curr_Input, s16 Theta)
02 {
03
Curr_Components Curr_Output;
04
s32 qId_tmp_1, qId_tmp_2;
05
s32 qIq_tmp_1, qIq_tmp_2;
06
s16 qId_1, qId_2;
07
s16 qIq_1, qIq_2;
08
09
Vector_Components = Trig_Functions(Theta);
STM32 技术开发手册
www.ing10bbs.com
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 }
//No overflow guaranteed
qIq_tmp_1 = Curr_Input.qI_Component1 * Vector_Components.hCos;
qIq_tmp_1 /= 32768;
//No overflow guaranteed
qIq_tmp_2 = Curr_Input.qI_Component2 *Vector_Components.hSin;
qIq_tmp_2 /= 32768;
qIq_1 = ((s16)(qIq_tmp_1));
qIq_2 = ((s16)(qIq_tmp_2));
//Iq component in Q1.15 Format
Curr_Output.qI_Component1 = ((qIq_1)-(qIq_2));
//No overflow guaranteed
qId_tmp_1 = Curr_Input.qI_Component1 * Vector_Components.hSin;
qId_tmp_1 /= 32768;
//No overflow guaranteed
qId_tmp_2 = Curr_Input.qI_Component2 * Vector_Components.hCos;
qId_tmp_2 /= 32768;
qId_1 = (s16)(qId_tmp_1);
qId_2 = (s16)(qId_tmp_2);
//Id component in Q1.15 Format
Curr_Output.qI_Component2 = ((qId_1)+(qId_2));
return (Curr_Output);
该函数是将静止参考坐标(𝛼, β)中的定子电流𝑖𝛼 和𝑖𝛽 转化为与定子合适定向
同步的参考坐标系电流𝑖𝑞 和𝑖𝑑 。有两个形参,第一个是定子电流𝑖𝛼 和𝑖𝛽 ,第二个是
转子角度𝜃𝑟𝑒𝑙 ,该参数一般由 GET_ELECTRICAL_ANGLE 宏赋值,这个在前面介绍
FOC_Model 函数时已经有介绍。Park 函数调用 Trig_Functions 函数根据输入角返
回三角余弦和正弦函数值。根据 Park 变换公式,见公式 14-51,计算得到电流𝑖𝑞
和𝑖𝑑 。
公式 14-51 Park 变换公式
𝑖d = 𝑖α sin 𝜃 + 𝑖𝛽 cos 𝜃
𝑖q = 𝑖α cos 𝜃 − 𝑖𝛽 sin 𝜃
代码 14-40 RevPark_Circle_Limitation 函数
01 void RevPark_Circle_Limitation(void)
02 {
03
s32 temp;
STM32 技术开发手册
www.ing10bbs.com
04
05
temp = Stat_Volt_q_d.qV_Component1 * Stat_Volt_q_d.qV_Component1
06
+ Stat_Volt_q_d.qV_Component2 * Stat_Volt_q_d.qV_Component2; // min value 0, max
value 2*32767*32767
07
08
if ( temp > (u32)(( MAX_MODULE * MAX_MODULE) ) ) { // (Vd^2+Vq^2) > MAX_MODULE^2 ?
09
u16 index;
10
11
temp /= (u32)(512*32768); // min value START_INDEX, max value 127
12
temp -= START_INDEX ;
// min value 0, max value 127 - START_INDEX
13
index = circle_limit_table[(u8)temp];
14
15
temp = (s16)Stat_Volt_q_d.qV_Component1 * (u16)(index);
16
Stat_Volt_q_d.qV_Component1 = (s16)(temp/32768);
17
18
temp = (s16)Stat_Volt_q_d.qV_Component2 * (u16)(index);
19
Stat_Volt_q_d.qV_Component2 = (s16)(temp/32768);
20
}
21
22 }
FOC 允许单独控制无刷电机的转矩和磁通。在两个新定子电压量(𝑉𝑑 和𝑉𝑞 )
在定子上产生磁通和转矩电流之后,通过磁通和转矩 PID 单独计算,必须在通过
⃗ ∗ | = √𝑉𝑑∗ 2 + 𝑉𝑞∗ 2 ,该函数就是让参
Rev_Park 函数之前使结果矢量幅值饱和等于|𝑉
数达到饱和,最后再传到 SVPWM 模块。这里定义为圆限制。
饱和边界通常由值(S16_MAX=32767)给出,这个值会产生最大输出电压幅
值(对应占空比从 0%到 100%)。然而,当使用单相或三相分流电阻配置时,根
据 PWM 频率,可能需要限制最大 PWM 占空比,即最大允许调整指数,以保证
读取定子电流。介此原因,在单相或三相分流电阻配置下,根据 PWM 开关频率,
饱和边界可以是略小于 S16_MAX 的值。这个在程序中的配置我们在前面已经有
过介绍,见图 14-55。
RevPark_Circle_Limitation 函数执行讨论的定子电压分量饱和,见图 14-72。
STM32 技术开发手册
www.ing10bbs.com
图 14-72 圆限制工作原理
𝑉𝑑 和𝑉𝑞 描述的是传给反 Park 变换的饱和定子电压,而𝑉𝑑∗和𝑉𝑞∗ 是 PID 电流控
制的输出。从几何关系考虑,可以得到下列关系:
公式 14-52 圆限制计算公式
𝑉𝑑
=
𝑉𝑑∗ ∙ 𝑀𝑀𝐼 ∙ 𝑆16_𝑀𝐴𝑋
⃗ ∗|
|𝑉
𝑉𝑞∗ ∙ 𝑀𝑀𝐼 ∙ 𝑆16_𝑀𝐴𝑋
𝑉𝑞 =
⃗ ∗|
|𝑉
为了快速计算上述公式并保证足够的精度,值
𝑀𝑀𝐼∙𝑆16_𝑀𝐴𝑋
∗
|𝑉 |
可以经过计算并对
⃗ ∗ |存储在查询表格中。
不同的值|𝑉
2
RevPark_Circle_Limitation 函数开始先计算𝑉𝑑∗ + 𝑉𝑞∗ 2 保存在 temp 变量中,判
断如果 temp 大于 MAX_MODULE * MAX_MODULE,得需要运行圆限制,先除以
512*32768 限 制 temp 范 围 为 0~127 , 然后 减 去 START_INDEX 得 到 在 数 组
circle_limit_table 的偏移量,最后从数组获取
𝑀𝑀𝐼∙𝑆16_𝑀𝐴𝑋
∗
|𝑉 |
值保存在 index 中,然后
就是计算得到𝑉𝑑 和𝑉𝑞 。其中,如果我们选择 98%最大调制指数,即定义宏
STM32 技术开发手册
www.ing10bbs.com
MAX_MODULATION_98_PER_CENT , 那 么 MAX_MODULE=32768*98%=32111 ,
START_INDEX=MAX_MODULE*MAX_MODULE/ ( 512*32768 ) =61 , 这 样 数 组
circle_limit_table 需要的元素量为:128- START_INDEX=67。
代码 14-41 Rev_Park 函数
01 Volt_Components Rev_Park(Volt_Components Volt_Input)
02 {
03
s32 qValpha_tmp1,qValpha_tmp2,qVbeta_tmp1,qVbeta_tmp2;
04
s16 qValpha_1,qValpha_2,qVbeta_1,qVbeta_2;
05
Volt_Components Volt_Output;
06
07
//No overflow guaranteed
08
qValpha_tmp1 = Volt_Input.qV_Component1 * Vector_Components.hCos;
09
qValpha_tmp1 /= 32768;
10
11
qValpha_tmp2 = Volt_Input.qV_Component2 * Vector_Components.hSin;
12
qValpha_tmp2 /= 32768;
13
14
qValpha_1 = (s16)(qValpha_tmp1);
15
qValpha_2 = (s16)(qValpha_tmp2);
16
17
Volt_Output.qV_Component1 = ((qValpha_1)+(qValpha_2));
18
19
20
qVbeta_tmp1 = Volt_Input.qV_Component1 * Vector_Components.hSin;
21
qVbeta_tmp1 /= 32768;
22
23
qVbeta_tmp2 = Volt_Input.qV_Component2 * Vector_Components.hCos;
24
qVbeta_tmp2 /= 32768;
25
26
qVbeta_1 = (s16)(qVbeta_tmp1);
27
qVbeta_2 = (s16)(qVbeta_tmp2);
28
29
Volt_Output.qV_Component2 = -(qVbeta_1)+(qVbeta_2);
30
31
return (Volt_Output);
32 }
该函数将属于与定子同步的旋转坐标系定子电压𝑉𝑑 和𝑉𝑞 转化为到静止参考
坐标系下的电压,即𝑉𝛼 和𝑉𝛽 ,实现反 Park 变换。该函数用到下面公式:
代码 14-42 反 Park 变换公式
𝑉α = 𝑉𝑞 cos 𝜃 + 𝑉𝑑 sin 𝜃
𝑉𝛽 = −𝑉𝑞 sin 𝜃 + 𝑉𝑑 cos 𝜃
MC_PID_regulators.c 文件内容
该文件存放 PID 算法实现方法,电机库有三个 PID 环,速度环、转矩环和励
磁环,其中转矩环和励磁环都是跟电流密切相关的。本文档前面已经有介绍 PID
STM32 技术开发手册
www.ing10bbs.com
算法内容,并且是比较通俗易懂的,所以在阅读下面内容之前可以先阅读之前 PID
章节内容。
代码 14-43 PID_Init 函数
01 void PID_Init (PID_Struct_t *PID_Torque, PID_Struct_t *PID_Flux, PID_Struct_t *PID_Speed)
02 {
03
hTorque_Reference = PID_TORQUE_REFERENCE;
04
05
PID_Torque->hKp_Gain
= PID_TORQUE_KP_DEFAULT;
06
PID_Torque->hKp_Divisor = TF_KPDIV;
07
08
PID_Torque->hKi_Gain = PID_TORQUE_KI_DEFAULT;
09
PID_Torque->hKi_Divisor = TF_KIDIV;
10
11
PID_Torque->hKd_Gain = PID_TORQUE_KD_DEFAULT;
12
PID_Torque->hKd_Divisor = TF_KDDIV;
13
PID_Torque->wPreviousError = 0;
14
15
PID_Torque->hLower_Limit_Output=S16_MIN;
//Lower Limit for Output limitation
16
PID_Torque->hUpper_Limit_Output= S16_MAX;
//Upper Limit for Output limitation
17
PID_Torque->wLower_Limit_Integral = S16_MIN * TF_KIDIV;
18
PID_Torque->wUpper_Limit_Integral = S16_MAX * TF_KIDIV;
19
PID_Torque->wIntegral = 0;
20
21
PID_Flux->wIntegral = 0; // reset integral value
22
23
hFlux_Reference = PID_FLUX_REFERENCE;
24
25
PID_Flux->hKp_Gain
= PID_FLUX_KP_DEFAULT;
26
PID_Flux->hKp_Divisor = TF_KPDIV;
27
28
PID_Flux->hKi_Gain = PID_FLUX_KI_DEFAULT;
29
PID_Flux->hKi_Divisor = TF_KIDIV;
30
31
PID_Flux->hKd_Gain = PID_FLUX_KD_DEFAULT;
32
PID_Flux->hKd_Divisor = TF_KDDIV;
33
PID_Flux->wPreviousError = 0;
34
35
PID_Flux->hLower_Limit_Output=S16_MIN;
//Lower Limit for Output limitation
36
PID_Flux->hUpper_Limit_Output= S16_MAX;
//Upper Limit for Output limitation
37
PID_Flux->wLower_Limit_Integral = S16_MIN * TF_KIDIV;
38
PID_Flux->wUpper_Limit_Integral = S16_MAX * TF_KIDIV;
39
PID_Flux->wIntegral = 0;
40
41
PID_Speed->wIntegral = 0; // reset integral value
42
43
hSpeed_Reference = PID_SPEED_REFERENCE;
44
45
PID_Speed->hKp_Gain
= PID_SPEED_KP_DEFAULT;
46
PID_Speed->hKp_Divisor = SP_KPDIV;
47
48
PID_Speed->hKi_Gain = PID_SPEED_KI_DEFAULT;
49
PID_Speed->hKi_Divisor = SP_KIDIV;
50
51
PID_Speed->hKd_Gain = PID_SPEED_KD_DEFAULT;
52
PID_Speed->hKd_Divisor = SP_KDDIV;
53
PID_Speed->wPreviousError = 0;
54
55
PID_Speed->hLower_Limit_Output= -IQMAX;
//Lower Limit for Output limitation
STM32 技术开发手册
www.ing10bbs.com
56
57
58
59
60 }
PID_Speed->hUpper_Limit_Output= IQMAX;
//Upper Limit for Output limitation
PID_Speed->wLower_Limit_Integral = -IQMAX * SP_KIDIV;
PID_Speed->wUpper_Limit_Integral = IQMAX * SP_KIDIV;
PID_Speed->wIntegral = 0;
PID_Init 函数用于初始化三个 PID 结构体变量的值。PID_Struct_t 是定义在
MC_type.h 文件的 PID 结构体,结构体成员包括:P、I、D 三个参数值都由一个增
益(Gain)和一个除数(Divisor)构成,还有定义了计算结果值的最高值和最低
值限制、积分 I 参数最大值和最小值限制,以及一个保存上次计算误差值和积分
结果值。
代码 14-44 PID_Speed_Coefficients_update 函数
01 void PID_Speed_Coefficients_update(s16 motor_speed, PID_Struct_t *PID_Struct)
02 {
03
if ( motor_speed < 0) {
04
motor_speed = (u16)(-motor_speed);
// absolute value only
05
}
06
07
if ( motor_speed <= Freq_Min ) { // motor speed lower than Freq_Min?
08
PID_Struct->hKp_Gain = Kp_Fmin;
09
PID_Struct->hKi_Gain = Ki_Fmin;
10
11 #ifdef DIFFERENTIAL_TERM_ENABLED
12
PID_Struct->hKd_Gain =Kd_Fmin;
13 #endif
14
} else if ( motor_speed <= F_1 ) {
15
PID_Struct->hKp_Gain = Kp_Fmin + (s32)(alpha_Kp_1*(motor_speed - Freq_Min) / 1024);
16
PID_Struct->hKi_Gain = Ki_Fmin + (s32)(alpha_Ki_1*(motor_speed - Freq_Min) / 1024);
17
18 #ifdef DIFFERENTIAL_TERM_ENABLED
19
PID_Struct->hKd_Gain = Kd_Fmin + (s32)(alpha_Kd_1*(motor_speed - Freq_Min) / 1024);
20 #endif
21
} else if ( motor_speed <= F_2 ) {
22
PID_Struct->hKp_Gain = Kp_F_1 + (s32)(alpha_Kp_2 * (motor_speed-F_1) / 1024);
23
PID_Struct->hKi_Gain = Ki_F_1 + (s32)(alpha_Ki_2 * (motor_speed-F_1) / 1024);
24
25 #ifdef DIFFERENTIAL_TERM_ENABLED
26
PID_Struct->hKd_Gain = Kd_F_1 + (s32)(alpha_Kd_2 * (motor_speed-F_1) / 1024);
27 #endif
28
} else if ( motor_speed <= Freq_Max ) {
29
PID_Struct->hKp_Gain = Kp_F_2 + (s32)(alpha_Kp_3 * (motor_speed-F_2) / 1024);
30
PID_Struct->hKi_Gain = Ki_F_2 + (s32)(alpha_Ki_3 * (motor_speed-F_2) / 1024);
31
32 #ifdef DIFFERENTIAL_TERM_ENABLED
33
PID_Struct->hKd_Gain = Kd_F_2 + (s32)(alpha_Kd_3 * (motor_speed-F_2) / 1024);
34 #endif
35
} else { // motor speed greater than Freq_Max?
36
PID_Struct->hKp_Gain = Kp_Fmax;
37
PID_Struct->hKi_Gain = Ki_Fmax;
38
39 #ifdef DIFFERENTIAL_TERM_ENABLED
40
PID_Struct->hKd_Gain = Kd_Fmax;
41 #endif
42
}
43 }
STM32 技术开发手册
www.ing10bbs.com
该函数用于根据电机实际转速自动计算速度 PID 调节器的比例、积分和微分
增益,即实现简易 PID 参数自整定。根据电机频率可能会需要不同的 Ki,Kp,Kd
的值,这样可以达到更好的控制电机转速效果。它是基于 4 个设定点画出一条曲
线完成计算,参考图 14-73。横坐标是电机转速,纵坐标是 Kp、Ki 和 Kd 值,由
4 个点分成三段曲线,每个电机转速对应一个不同的 Kp、Ki 和 Kd 值。
图 14-73 计算系数的线性曲线
横坐标值:F_min、F_1、F_2 和 F_max 是以 0.1Hz 单位表示电机速度,比如
设置为 150,实际对应为 15Hz 机械频率(关于速度表示方法在后面介绍霍尔传
感器信号内容再详细说明)。这四个值是用户在代码(MC_Control_Param.h 文件
中)里边指定的。纵坐标:Ki_Fmin、Ki_F_1、Ki_F_2、Ki_Fmax 等等 12 个参数值
也是在代码中指定的。既然知道这 16 个参数值,要计算某个电机转速下对应的
Kp、Ki 和 Kd 值就剩下一次函数计算而已了。PID_Speed_Coefficients_update 函数
计数实现这一次函数计算过程而已,这里就不再啰嗦了。
代码 14-45 PID_Regulator 函数
01 s16 PID_Regulator(s16 hReference, s16 hPresentFeedback, PID_Struct_t *PID_Struct)
02 {
03
s32 wError, wProportional_Term,wIntegral_Term, houtput_32;
04
s64 dwAux;
05 #ifdef DIFFERENTIAL_TERM_ENABLED
06
s32 wDifferential_Term;
07 #endif
08
// error computation
09
wError= (s32)(hReference - hPresentFeedback);
10
STM32 技术开发手册
www.ing10bbs.com
11
// Proportional term computation
12
wProportional_Term = PID_Struct->hKp_Gain * wError;
13
14
// Integral term computation
15
if (PID_Struct->hKi_Gain == 0) {
16
PID_Struct->wIntegral = 0;
17
} else {
18
wIntegral_Term = PID_Struct->hKi_Gain * wError;
19
dwAux = PID_Struct->wIntegral + (s64)(wIntegral_Term);
20
21
if (dwAux > PID_Struct->wUpper_Limit_Integral) {
22
PID_Struct->wIntegral = PID_Struct->wUpper_Limit_Integral;
23
} else if (dwAux < PID_Struct->wLower_Limit_Integral) {
24
PID_Struct->wIntegral = PID_Struct->wLower_Limit_Integral;
25
} else {
26
PID_Struct->wIntegral = (s32)(dwAux);
27
}
28
}
29
// Differential term computation
30 #ifdef DIFFERENTIAL_TERM_ENABLED
31
{
32
s32 wtemp;
33
34
wtemp = wError - PID_Struct->wPreviousError;
35
wDifferential_Term = PID_Struct->hKd_Gain * wtemp;
36
PID_Struct->wPreviousError = wError;
// store value
37
}
38
houtput_32 = (wProportional_Term/PID_Struct->hKp_Divisor+
39
PID_Struct->wIntegral/PID_Struct->hKi_Divisor +
40
wDifferential_Term/PID_Struct->hKd_Divisor);
41
42 #else
43
houtput_32 = (wProportional_Term/PID_Struct->hKp_Divisor+
44
PID_Struct->wIntegral/PID_Struct->hKi_Divisor);
45 #endif
46
47
if (houtput_32 >= PID_Struct->hUpper_Limit_Output) {
48
return (PID_Struct->hUpper_Limit_Output);
49
} else if (houtput_32 < PID_Struct->hLower_Limit_Output) {
50
return (PID_Struct->hLower_Limit_Output);
51
} else {
52
return ((s16)(houtput_32));
53
}
54 }
该函数是 PID 算法的核心内容,实际上如果有阅读了本文档前面的 PID 章节
内容,要理解该函数是非常简单的。该函数有 3 个形参,hReference 是目标参考
值,比如目标速度、𝑖𝑞 或𝑖𝑑 ;hPresentFeedback 是当前系统反馈值,即当前速度、
当前𝑖𝑞 或𝑖𝑑 ;PID_Struct 是 PID 结构体变量指针,存放不同 PID 环计算参数。函
数的返回值是 s16 类型,用于反馈 PID 计算结果。
函数先计算实际与目标的偏差值保存在局部变量 wError 中,计算比例项结
果保存在 wProportional_Term 变量中。接下来计算积分项,如果 hKi_Gain 值为
0(不使能积分项),把累积的积分清零。在 hKi_Gain 不为 0 时,先计算当前偏
STM32 技术开发手册
www.ing10bbs.com
差积分保存在 wIntegral_Term 变量中,结合之前累积的积分保存在 dwAux 变量
中,最后对 dwAux 进行最大、最小值限定处理设定积分项值。
如果不使能微分项,直接根据之前计算得到的比例项 wProportional_Term
和积分项 PID_Struct->wIntegral 计算得到 PID 结果,这里还用到了每个 PID 参
数的除数项,我们知道一般 P、I、D 参数值都是很小,很多时候是浮点数,这里
把这些参数都使用整数表示,所以需要一个除数来把输出值变小,以达到合适的
输出范围。如果使能微分项,需要加上微分项计算,计算方法都是类似的。
函数最后,对最终的输出结果做最大、最小值限制。
通过上面的介绍,可以发现电机库用到的 PID 算法与我们之前介绍的位置式
PID 几乎完全是一致。
stm32f10x_hall.c 文件内容
该文件存放了霍尔传感器信号处理。我们知道 FOC 控制必须获取转子位置,
其中一个方法就是用霍尔传感器,该文件就可以实现转子位置获取,同时可以根
据霍尔传感器信号计算电机转速。
在 10.3.3 小节中我们介绍了霍尔传感器的基本原理,介绍了以间隔为 120
度安装的霍尔传感器,实际上还有另外一种是以 60 度安装的,安装方式不同,
对应的输出波形也不同,见图 14-74。其中,最明显的差别就是以 60 度安装的
波形有全部是 1 和区别是 0 的情况,而 120 度安装的不会出现这两组波形。ST 电
机库是支持者两种霍尔信号的,并且要求霍尔传感器信号接入电机库时是与图
14-74 中波形一致的。
STM32 技术开发手册
www.ing10bbs.com
图 14-74 间隔 60°和 120°安装的霍尔传感器输出波形
那当我们拿到一个新的电机时然后接入到驱动板上呢?最直接的办法就是
看电机厂家给的电机引脚定义。另外一个办法就是按一个方向转动电机看 3 个霍
尔传感器的输出波形,这里用手动转或者其他电机带动转都行,方法只能发挥各
位的丰富想象了,只要保证有较稳定的霍尔型号生成就好的,这样就这样非常方
便得到霍尔传感器波形。得到波形后就查询波形是否有全部是“1”或者全部是“0”
的情况,判断是属于间隔 60°安装的霍尔传感器还是 120°安装的霍尔传感器类
型,得到类型区分之后就看实际的霍尔传感器波形与图 14-74 的波形进行对比,
如果波形不一致,我们就尝试交换示波器通道与电机霍尔传感器其中两根接线方
法,通过多次尝试就可以得到与图 14-74 一致的波形图,最后我们就可以确定图
14-74 中的 H1、H2 和 H3 信号与实际的霍尔传感器线对应关系,得到这个对应关
系是我们使用霍尔传感器的基础,所以必须正确得到。
在图 14-74 中,假设电机正转状态是:State5-> State1-> State3-> State2->
State6-> State4-> State5->…如此循环,那么当电机反转时候,霍尔传感器的变化
过程就为:State4-> State6-> State2-> State6-> State1-> State5-> State4->…。
STM32 技术开发手册
www.ing10bbs.com
另外,关于图 14-74 有个需要注明的地方,图中的 H1、H2 和 H3 就是霍尔
传感器的 3 个信号线,在其他地方可以是标注为 HU、HV 和 HW,也可能是 HA、
HB 和 HC。
在使用 ST FOC 电机库时,当使用 Hall 信号作为位置信号时,需要输入同步
电角度数据,这个数据根据当前使用电机的特性进行输入,会在每次 Hall 信号变
化时同步电角度,如果角度偏差较大时会影响控制效果,可能带来效率或者电机
的震荡,初始测试还是有必要的。
ST FOC 电机库电角度约定
默认电机 A 相的反电动势最高点作为电角度的 0 度;电机 Hall A 的上升沿到
电机 A 相反电动势最高点的延迟角度为同步电角度;
图 14-75 电机库电角度约定
测试准备
如果电机没有虚拟中点接出,需要连接三个相同阻值电阻到电机的三相接线
上,电阻另外一端连接到一起作为虚拟中点;
STM32 技术开发手册
www.ing10bbs.com
图 14-76 虚拟中点
将 Hall 信号接入 5V 电源,并且在 H1 上接入上拉电阻(4.7K 欧即可);接入
示波器,转动电机,测试反向电动势信号以及 Hall 信号。
波形测试及计算结果
下面是举例说明电机测试波形。
测试一个电周期的时间,这个周期对应 360 度:
图 14-77 一个电周期时间测量
测试电机 A 相反向电动势最高点到 H1 的时间:
STM32 技术开发手册
www.ing10bbs.com
图 14-78 电机 A 相反向电动势最高点到 H1 的时间
上图中粉色为电机 A 相反向电动势,红色数字端口 D0 为 H1 信号。
同步电角度计算
该电机同步电角度:
公式 14-53 电机同步电角度
θ=
37.2 − 6.6
× 360° = 296°
37.2
同步角度添加到代码
如果使用 FOCGUI,可以直接设置该同步电角度:
STM32 技术开发手册
www.ing10bbs.com
图 14-79 同步电角度设置
如果直接写入程序中, 则将数据写入 MC_hall_prm.h 文件参数中:
01 /* Define here the mechanical position of the sensors with reference to anelectrical cycle */
02 #define HALL_SENSORS_PLACEMENT DEGREES_120
03
04 /* Define here in degrees the electrical phase shift between the low to high transition of signal H1 and the
05 maximum of the Bemf induced on phase A */
06
07 #define HALL_PHASE_SHIFT (s16)296
在 10.4.2 小节中我们介绍了 STM32F1 系列芯片的霍尔传感器接口及其软件
编程(这里强烈建议大家把 10.4.2 小节内容仔细阅读一遍)。我们知道当寄存器
TIMx_CR2 的 TI1S 位被置位时,在 TIMx_CH1、TIMx_CH2 和 TIMx_CH3 引脚的三
个信号进行异或运算,由此产生的信号输入到 TIMx 的输入捕获中。这样,速度
的测量转换为了一个方波的周期测量,频率比真正的电频率(对应电周期)高出
3 倍。不过,这个可能测量得到速度值大小,对于旋转方向需要我们另外根据霍
尔传感器的信号变化情况分析得出,实际上就是根据图 14-74 中霍尔信号变化
情况进行分析得到,就是将当前状态跟前一个霍尔信号状态进行对比,就可以很
快得出电机旋转方向。
使用定时器的霍尔传感器接口可以非常方便的测量电机旋转速度,就是测量
方波的周期。定时器的周期测量一般是使用普通的输入捕获功能,那我们这个霍
STM32 技术开发手册
www.ing10bbs.com
尔传感器模式就是在这个基础上实现的。关于使用定时器测量周期还有不熟悉的
同学可以先看看本文档前面相关章节内容。
在周期测量中,我们定时器一般使用内部时钟源(72MHz)进行预分频得到
合适的时钟源,这个合适的预分频需要需要根据输入的信号频率范围选择,一般
达到 1:1000 的比例,比如你待测的信号频率大概是 100Hz(这个是待测,真正具
体数值需要测量才可以得到,但是我们一般都是可以估算得到大致的频率范围),
那么我们就可以让定时器的计算频率为 100x1000=100KHz,设置预分频数为 720。
对于电机的霍尔信号,当电机转速不同时,这个霍尔信号的频率变化范围还是很
大的,特别地,即使使用同一个电机,在负载低速和空载最高速这两种极限情况
下,对应的速度可能大概是 100rpm 和 5000rpm,这样如果我们定时器使用同一
个预分频系数可能会造成较大的误差,所以一般为了得到最佳的分辨率,在运行
中需要不断调节定时器的时钟分频器(预分频系数)。其基本原理就是,如果捕
获的值低于某个设定值就加快定时器的时钟计数频率,见图 14-80。
图 14-80 霍尔传感器定时器接口预分频器的下降
如果定时器在连续两个捕获之间溢出,就减慢定时器的计数频率,见图
14-81。
STM32 技术开发手册
www.ing10bbs.com
图 14-81 霍尔传感器定时器接口预分频器的上升
一般在捕捉中断中修改预分频器值,然后利用影子寄存器的缓存功能:只有
在下次捕捉事件中才由硬件更新新的预分频的值,而不会影响到测量。
进一步的细节实现方法参考图 14-82 中的流程图示意,具体是在霍尔传感器
信号的定时器中断服务函数 TIMx_IRQHandler 中实现的。
STM32 技术开发手册
www.ing10bbs.com
图 14-82 霍尔传感器模式 TIMx_IRQHandler 函数流程图
在 TIMx_IRQHandler 中不止完成速度测量。除了测速,异或信号的高低电平
转换使得包含当前电角度的软件变量同步变得可能。事实上,通过图 14-83 可以
看到任意霍尔传感器转化都可以精确地提供转子的位置信息。
STM32 技术开发手册
www.ing10bbs.com
图 14-83 霍尔传感器输出转换
出于这个原因,在程序中,每一次 IC 发生电角度都与依赖霍尔输出状态的
角度同步,指明方向和 PHASE_SHIFT。
此外,使用 FOC 算法需要及时准确获得转子位置信息,包括异或信号两个
连续下降沿(每 120 电角度发生)。所以任何插入电角度信息非常重要。为此,
每次执行 FOC 算法都将最新的脉冲单位测量的速度添加到当前的电角度软件变
量中。
STM32 技术开发手册
www.ing10bbs.com
速度格式
在 FOC 库中有两种速度格式:
⚫
0.1Hz:这个格式通常由速度调节器及软件最高层(用户接口实例)使用。
⚫
每控制周期的角度数字量(dpp):dpp 格式表达的是速度在一个 FOC 控
制周期内随电角度(s16 格式)的变化。对于电角度,0 为 0 度,-32768
为-180 度(S16_MIN)
,+32767(S16_MAX)为 180 度,参考图 14-70。
由于每次控制循环执行时(例如 PWM 更新中断服务程序期间)都可以
累 积 速度 信 息来 计算转 子 角度 位 置, 这种格 式 单位 就 很方 便。令
2π=0xFFFF(这样不需要考虑角度翻转),根据下式 0.1Hz 单位的频率可
以很容易转化成 dpp 格式:
公式 14-54 不同格式转子速度单位转换
𝜔d𝑝𝑝 = 𝜔0.1𝐻𝑧 ∙
65536
10 ∙ 𝑆𝐴𝑀𝑃𝐿𝐼𝑁𝐺_𝐹𝑅𝐸𝑄
其中,SAMPLING_FREQ 是 FOC 采样频率,通过 REP_RATE 和 PWM_FREQ 这两
个宏定义可以自动计算得出。
⚫
电气频率、机械频率、转速之间的关系:
电频率=磁极对数 x 机械频率
RPM 的速度=60 x 机械频率(转速:每分钟转速)
例如:电频率=100 赫兹,电机 8 对磁极对数:
100Hz(电频率)<->100 / 8=12.5Hz(机械频率)<->12.5×60= 750(转速)
代码 14-46 HALL_HallTimerInit 函数
01 void HALL_HallTimerInit(void)
02 {
03
04
TIM_TimeBaseInitTypeDef TIM_HALLTimeBaseInitStructure;
05
TIM_ICInitTypeDef
TIM_HALLICInitStructure;
06
NVIC_InitTypeDef
NVIC_InitHALLStructure;
07
GPIO_InitTypeDef
GPIO_InitStructure;
08
09 #if defined(TIMER2_HANDLES_HALL)
10
/* TIM2 clock source enable */
11
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
12
/* Enable GPIOA, clock */
13
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
14
15
GPIO_StructInit(&GPIO_InitStructure);
16
/* Configure PA.00,01 ,02 as Hall sensors input */
17
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;
STM32 技术开发手册
www.ing10bbs.com
18
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
19
GPIO_Init(GPIOA, &GPIO_InitStructure);
20 #elif defined(TIMER3_HANDLES_HALL)
21
/* TIM3 clock source enable */
22
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
23
24
GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);
25
/* Enable GPIOC, clock */
26
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
27
28
GPIO_StructInit(&GPIO_InitStructure);
29
/* Configure PA.06,07 PB.00 as Hall sensors input */
30
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7|GPIO_Pin_8;
31
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
32
GPIO_Init(GPIOC, &GPIO_InitStructure);
33
34 #else // TIMER4_HANDLES_HALL
35
/* TIM4 clock source enable */
36
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
37
/* Enable GPIOB, clock */
38
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
39
40
GPIO_StructInit(&GPIO_InitStructure);
41
/* Configure PB.06,07,08 as Hall sensors input */
42
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8;
43
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
44
GPIO_Init(GPIOB, &GPIO_InitStructure);
45 #endif
46
47
// Timer configuration in Clear on capture mode
48
TIM_DeInit(HALL_TIMER);
49
50
TIM_TimeBaseStructInit(&TIM_HALLTimeBaseInitStructure);
51
// Set full 16-bit working range
52
TIM_HALLTimeBaseInitStructure.TIM_Period = U16_MAX;
53
TIM_HALLTimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
54
TIM_TimeBaseInit(HALL_TIMER,&TIM_HALLTimeBaseInitStructure);
55
56
TIM_ICStructInit(&TIM_HALLICInitStructure);
57
TIM_HALLICInitStructure.TIM_Channel = TIM_Channel_1;
58
TIM_HALLICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;
59
TIM_HALLICInitStructure.TIM_ICFilter = ICx_FILTER;
60
61
TIM_ICInit(HALL_TIMER,&TIM_HALLICInitStructure);
62
63
// Force the HALL_TIMER prescaler with immediate access (no need of an update event)
64
TIM_PrescalerConfig(HALL_TIMER, (u16) HALL_MAX_RATIO,
65
TIM_PSCReloadMode_Immediate);
66
TIM_InternalClockConfig(HALL_TIMER);
67
68
//Enables the XOR of channel 1, channel2 and channel3
69
TIM_SelectHallSensor(HALL_TIMER, ENABLE);
70
71
TIM_SelectInputTrigger(HALL_TIMER, TIM_TS_TI1FP1);
72
TIM_SelectSlaveMode(HALL_TIMER,TIM_SlaveMode_Reset);
73
74
// Source of Update event is only counter overflow/underflow
75
TIM_UpdateRequestConfig(HALL_TIMER, TIM_UpdateSource_Regular);
76
77
/* Enable the HALL_TIMER IRQChannel*/
78 #if defined(TIMER2_HANDLES_HALL)
79
NVIC_InitHALLStructure.NVIC_IRQChannel = TIM2_IRQn;
80 #elif defined(TIMER3_HANDLES_HALL)
STM32 技术开发手册
www.ing10bbs.com
81
NVIC_InitHALLStructure.NVIC_IRQChannel = TIM3_IRQn;
82 #else // TIMER4_HANDLES_HALL
83
NVIC_InitHALLStructure.NVIC_IRQChannel = TIM4_IRQn;
84 #endif
85
86
NVIC_InitHALLStructure.NVIC_IRQChannelPreemptionPriority =
87
TIMx_PRE_EMPTION_PRIORITY;
88
NVIC_InitHALLStructure.NVIC_IRQChannelSubPriority = TIMx_SUB_PRIORITY;
89
NVIC_InitHALLStructure.NVIC_IRQChannelCmd = ENABLE;
90
91
NVIC_Init(&NVIC_InitHALLStructure);
92
93
// Clear the TIMx's pending flags
94
TIM_ClearFlag(HALL_TIMER, TIM_FLAG_Update + TIM_FLAG_CC1 + TIM_FLAG_CC2 + \
95
TIM_FLAG_CC3 + TIM_FLAG_CC4 + TIM_FLAG_Trigger + TIM_FLAG_CC1OF + \
96
TIM_FLAG_CC2OF + TIM_FLAG_CC3OF + TIM_FLAG_CC4OF);
97
98
// Selected input capture and Update (overflow) events generate interrupt
99
TIM_ITConfig(HALL_TIMER, TIM_IT_CC1, ENABLE);
100
TIM_ITConfig(HALL_TIMER, TIM_IT_Update, ENABLE);
101
102
TIM_SetCounter(HALL_TIMER, HALL_COUNTER_RESET);
103
TIM_Cmd(HALL_TIMER, ENABLE);
104 }
该函数的功能是初始化霍尔传感器反馈处理中涉及的外围设备。特别地,与
霍尔传感器连接的 GPIO 输入引脚被初始化为浮动输入,定时器 TIMx 配置为“捕
捉复位”模式,其异或输入功能使能,预分频器通过 HALL_MAX_RATIO 初始化。
最后,TIMx 输入捕捉(异或信号的负沿)和溢出(更新)事件中断被使能。
函数开始初始化配置霍尔传感器的 GPIO,这里通过#if…#elif…#else…#endif 的
条件编译语句根据我们实际的硬件决定使用哪个定时器读取霍尔传感器信号,这
里用宏定义(在 MC_hall_prm.h 文件中)实现:
01 /* 三路霍尔传感器信号与 TIMER2 输入引脚相连 */
02 //#define TIMER2_HANDLES_HALL
03 /* 三路霍尔传感器信号与 TIMER3 输入引脚相连 */
04 #define TIMER3_HANDLES_HALL
05 /* 三路霍尔传感器信号与 TIMER4 输入引脚相连 */
06 //#define TIMER4_HANDLES_HALL
这里预定义了三个定时器用于霍尔传感器连接,我们根据实际硬件选择
TIM3,即宏定义了 TIMER3_HANDLES_HALL,并把 TIMER2_HANDLES_HALL 和
TIMER4_HANDLES_HALL 注释掉禁用。返回 HALL_HallTimerInit 函数,因为我们使
用 TIM3 连接霍尔传感器,所以使能 TIM3 时钟,初始化 PC6、PC7 和 PC8 这三个
引脚,注意,这里我们是使用了 TIM3 的引脚重映射功能,TIM3 的默认功能引脚
是 PA6、PA7 和 PB0,考虑到开发板实际硬件,我们使用引脚重映射功能,使用
PC6、PC7 和 PC8 这三个引脚与霍尔传感器连接。这里设置三个引脚都为浮空输
STM32 技术开发手册
www.ing10bbs.com
入模式,而实际上,霍尔传感器信号都是有接上拉电阻的,只是这个上拉电阻是
我们在外部接的,这个可以具体看驱动板的霍尔传感器接口电路。
接下来是定时器的相关参数配置,与输入捕获模式一种,霍尔传感器接口的
定时器的周期也是设置为 0xFFFF,预分频先设置为 1 分配,这个分配系数后面会
进行修改,需要修改原因我们已经做了分析的,这两个参数的配置通过调用并运
行 TIM_TimeBaseInit 函数实现 ,该函数第一个形参为 定时器 号, 这里使用
HALL_TIMER 宏定义,根据前面我们选择 TIM3 为霍尔传感器的接口定时器,这里
把 HALL_TIMER 定义为 TIM3。
01 #if defined(TIMER2_HANDLES_HALL)
02 #define HALL_TIMER TIM2
03 #elif defined(TIMER3_HANDLES_HALL)
04 #define HALL_TIMER TIM3
05 #else // TIMER4_HANDLES_HALL
06 #define HALL_TIMER TIM4
07 #endif
然后,设置定时器通道 1 为输入捕获模式,设置滤波器系数为 0x0B,对应
1333 纳秒,设置输入捕获下降沿,虽然这里设置为下降沿,但是因为是霍尔传感
器接口模式,定时器会自动捕获 TI1 的上下边沿的。TIM_PrescalerConfig 函数设
置定时器的预分频系数,该函数有三个形参,第一个为定时器号,这里使用
HALL_TIMER(实际为 TIM3)赋值,第二个参数为预分频数,实际上就是设置
TIMx_PSC 寄存器的值,这里直接以 HALL_MAX_RATIO 值赋值给这个函数使用,
HALL_MAX_RATIO 宏定义在 MC_hall_prm.h 文件中,它实际上就是电机最低转速
时候合适用的定时器分配系数。
01 /* Max TIM prescaler ratio defining the lowest expected speed feedback */
02 /* -定义可以测量的最低速度(当计数=0xFFFF)*/
03 /* -当电机停止时,防止时钟分频器减小过度。(每个捕获中断以优化时钟分辨 */
04 /* 率时预分频器会自动调整) */
05 #define HALL_MAX_RATIO
((u16)800u)
第 三 个 参 数 是 可 行 :
TIM_PSCReloadMode_Update
和
TIM_PSCReloadMode_Immediate 这两个参数值,第一个是需要等到事件更新时预
分频数才有效,第二个是修改预分频值后马上生效。TIM_InternalClockConfig 函
数设置定时器使用内部时钟源(72MHz)。TIM_SelectHallSensor 函数用于使能定
时器用于霍尔传感器接口,这里必须使能。TIM_SelectInputTrigger 函数选择定时
器的触发输入,这里选择 TI1FP1 信号作为触发输入信号源,TI1FP1 是 TI1 信号
STM32 技术开发手册
www.ing10bbs.com
(这里实际为霍尔传感器 3 个信号的异或信号)经过滤波和边沿检测后的信号。
TIM_SelectSlaveMode 函数设置定时器的从模式控制器为复用模式,就是当 TI1FP1
信号源为有效时复位定时器的计算器。TIM_UpdateRequestConfig 函数配置定时
器更新请求中断源,这里配置为计算器发生上溢或者下溢情况、软件设置 UG 位、
以及通过从模式控制器控制发生更新事件。
接下来,HALL_HallTimerInit 函数配置霍尔传感器接口定时器的中断优先级并
在 NVIC 中使能该中断请求,设置为抢占式优先级为 2,响应优先级为 0。
TIM_ClearFlag 函数把相关状态寄存器为清零,复位定时器相关状态。调用两个
TIM_ITConfig 函数配置使能定时器通道 1 的输入捕获中断和定时器的更新中断。
TIM_SetCounter 函数用于设置定时器的计数值,这里初始化把计数值复位为 0,
最后运行 TIM_Cmd 函数启动霍尔传感器接口定时器工作。
代码 14-47 HALL_InitHallMeasure 函数
01 void HALL_InitHallMeasure( void )
02 {
03
// Mask interrupts to insure a clean intialization
04
05
TIM_ITConfig(HALL_TIMER, TIM_IT_CC1, DISABLE);
06
07
RatioDec = FALSE;
08
RatioInc = FALSE;
09
DoRollingAverage = FALSE;
10
InitRollingAverage = FALSE;
11
HallTimeOut = FALSE;
12
13
hCaptCounter = 0;
14
bGP1_OVF_Counter = 0;
15
16
for (bSpeedFIFO_Index=0; bSpeedFIFO_Index < HALL_SPEED_FIFO_SIZE;
17
bSpeedFIFO_Index++) {
18
SensorPeriod[bSpeedFIFO_Index].hCapture = U16_MAX;
19
SensorPeriod[bSpeedFIFO_Index].hPrscReg = HALL_MAX_RATIO;
20
SensorPeriod[bSpeedFIFO_Index].bDirection = POSITIVE;
21
}
22
23
// First measurement will be stored in the 1st array location
24
bSpeedFIFO_Index = HALL_SPEED_FIFO_SIZE-1;
25
26
// Re-initialize partly the timer
27
HALL_TIMER->PSC = HALL_MAX_RATIO;
28
29
HALL_ClrCaptCounter();
30
31
TIM_SetCounter(HALL_TIMER, HALL_COUNTER_RESET);
32
33
TIM_Cmd(HALL_TIMER, ENABLE);
34
35
TIM_ITConfig(HALL_TIMER, TIM_IT_CC1, ENABLE);
STM32 技术开发手册
www.ing10bbs.com
36
37 }
该函数用于初始化霍尔信号测量,在启动电机前调用,初始化速度测量过程。
主要是在最新数据存入之前清除软件的 FIFO 缓存区数据。首先,调用 TIM_ITConfig
函数关闭通道 1 的输入捕获中断。把 RatioDec、RatioInc、DoRollingAverage、
InitRollingAverage 和 HallTimeOut 这 5 个标志位都设置为 FALSE 状态,这 5 个标
志位分别代表着预分频减少、预分频增加、霍尔速度平均值获取状态、初始化霍
尔速度平均值状态以及霍尔传感器信号超时标志。把变量 hCaptCounter(定时器
捕获计数值)和 bGP1_OVF_Counter(定时器连续溢出计数)都清零。然后使用
for 循环为 SpeedMeas_s 结构体类型的数组变量 SensorPeriod 的每个元素赋初始
化值。SpeedMeas_s 结构体类型存放霍尔信息测量相关参数,有三个成员:
hCapture(定时器捕获值),hPrscReg(定时器预分频值)、bDirection(旋转方向);
分 别 初 始化 为 0xFFFF 、 800 ( 以电 机最 慢转 速 赋 值) 和 POSITIVE 。这 里 的
SensorPeriod 是一个数组(结构体类型变量),元素个数为 HALL_SPEED_FIFO_SIZE,
主要是考虑到后面通过求和取平均值方法最后计算速度,这样可以减少误差,平
滑趋势。HALL_SPEED_FIFO_SIZE 在转矩模式下取值为 1,在速度模式下取值为 6。
bSpeedFIFO_Index 变量用于指示霍尔速度 FIFO 缓冲区存放地址索引。
然后,直接为定时器 PSC 寄存器赋值,重新初始化预分频值,这里设置为最
慢速时预分频值。HALL_ClrCaptCounter 函数用于清除捕获计数器值,实际上就是
是把 hCaptCounter 变量值清零。TIM_SetCounter 设置定时器计数值,这里把计数
值清零。HALL_InitHallMeasure 函数最后启动定时器工作并且使能通道 1 的输入
捕获中断。
代码 14-48 HALL_GetSpeed 函数
01 s16 HALL_GetSpeed ( void )
02 {
03
s32 wAux;
04
05
if ( hRotorFreq_dpp == HALL_MAX_PSEUDO_SPEED) {
06
return (HALL_MAX_SPEED);
07
} else {
08
wAux = ((hRotorFreq_dpp* SAMPLING_FREQ * 10)/(65536*POLE_PAIR_NUM));
09
return (s16)wAux;
10
}
11 }
STM32 技术开发手册
www.ing10bbs.com
该函数以 0.1Hz 分辨率计算转子机械频率,它是从存储最新周期测量的 FIFO
缓冲区开始计算转子机械频率,根据以下公式计算:
公式 14-55 转子频率计算
𝜔dpp =
𝐶𝐾𝑇𝐼𝑀 ∙ 10
3 ∙ 𝑃𝑂𝐿𝐸_𝑃𝐴𝐼𝑅_𝑁𝑈𝑀 ∙ 𝑐𝑎𝑝𝑡𝑢𝑟𝑒𝑑 𝑣𝑎𝑙𝑢𝑒 ∙ 𝑝𝑟𝑒𝑠𝑐𝑎𝑙𝑒𝑟 𝑣𝑎𝑙𝑢𝑒
其中:
CKTIM 是定时器频率,72MHz;
POLE_PAIR_NUM 是电机极对数;
captured value 是定时器捕获值;
prescaler value 是预分频值;
常数 10 是 0.1Hz 与 HZ 单位的转换,电机库使用 0.1Hz 单位;
常数 3 是“频率比真正的电频率(对应电周期)高出 3 倍”。
上式得到的是以 dpp 为单位的转子机械频率 hRotorFreq_dpp,还需要结合公
式 14-54 计算才能得到以 0.1Hz 为单位的转子速度。而实际上,HALL_GetSpeed
函数实现的内容只是公式 14-54 的计算。返回值的分辨率为 0.1Hz,这意味着 1234
等于 123.4Hz。
如果预分频器等于最大值或者发生超时情况则返回值是 0,速度过高(或高
频信号)将导致返回预定义的值(HALL_MAX_SPEED)。
代码 14-49 HALL_GetRotorFreq 函数
01 s16 HALL_GetRotorFreq ( void )
02 {
03
PeriodMeas_s PeriodMeasAux;
04
05
if ( DoRollingAverage) {
06
PeriodMeasAux = GetAvrgHallPeriod();
07
} else {
08
// Raw period
09
PeriodMeasAux = GetLastHallPeriod();
10
}
11
12
if (HallTimeOut == TRUE) {
13
hRotorFreq_dpp = 0;
14
} else {
15
if (PeriodMeasAux.bDirection != ERROR)
16
//No errors have been detected during rotor speed information extrapolation
17
{
18
if ( HALL_TIMER->PSC >= HALL_MAX_RATIO ) { /* At start-up or very low freq */
19
/* Based on current prescaler value only */
20
hRotorFreq_dpp = 0;
21
} else {
22
if ( PeriodMeasAux.wPeriod > MAX_PERIOD) { /* Speed is too low */
23
hRotorFreq_dpp = 0;
24
} else {
25
/*Avoid u32 DIV Overflow*/
STM32 技术开发手册
www.ing10bbs.com
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 }
if ( PeriodMeasAux.wPeriod > (u32)SPEED_OVERFLOW ) {
if (HALL_GetCaptCounter()<2) { // First capture must be discarded
hRotorFreq_dpp=0;
} else {
hRotorFreq_dpp = (s16)((u16) (PSEUDO_FREQ_CONV /
PeriodMeasAux.wPeriod));
hRotorFreq_dpp *= PeriodMeasAux.bDirection;
}
} else {
hRotorFreq_dpp = HALL_MAX_PSEUDO_SPEED;
}
}
}
}
}
return (hRotorFreq_dpp);
该函数用差示脉冲极谱法方式计算转子电频率,从最新的测量周期存储数据
开始计算。首先;定义一个 PeriodMeas_s 结构体类型变量 PeriodMeasAux,作为
中间计算变量,存放测得的捕获周期。PeriodMeas_s 结构体类型有两个成员,一
个是定时器周期值,另外一个是方向。
01 typedef struct {
02
u32 wPeriod;
03
s8 bDirection;
04 } PeriodMeas_s;
先判断 DoRollingAverage 标志是否为 TRUE,如果为 TRUE 说明速度周期 FIFO
缓冲区准备好数据可以求出平均速度周期,运行 GetAvrgHallPeriod 函数获取霍尔
信号周期平均值,并保存在 PeriodMeasAux 变量中。如果周期 FIFO 缓冲区数据
还没有准备好,则运行 GetLastHallPeriod 函数获取最新的速度周期信息。
接下来,判断 HallTimeOut 标志是否超时,如果发生霍尔信号超时直接把转
子频率 hRotorFreq_dpp 变量设置为 0 并直接返回,如果没有超时我们就进行转
子频率计算,这里还先用了 if 语句判断旋转方向是否有出错情况,如果有出错我
们不会进行数据处理,在确定没有发生旋转方向输出后,再次使用 if 语句电平定
时器的预分频值是否为大于等于 HALL_MAX_RATIO,如果结果为 TRUE 说明电机
是刚启动或者速度过低,也是直接把转子频率 hRotorFreq_dpp 变量设置为 0 并
直接返回。在确保定时器预分频值小于 HALL_MAX_RATIO 后,还需要检测
PeriodMeasAux.wPeriod(当前霍尔信号周期值)与最大周期值 MAX_PERIOD 比较,
如果大于 MAX_PERIOD 说明速度很慢,直接把 hRotorFreq_dpp 设置为 0 并直接
返回。MAX_PERIOD 是一个宏定义常量,保存转子最低转速时对应的定时器周期:
STM32 技术开发手册
www.ing10bbs.com
01 #define CKTIM
((u32)72000000uL)
02
03 /* 电机磁极对的数目 */
04 #define POLE_PAIR_NUM
(u8) 4
/* Number of motor pole pairs */
05 #define ROTOR_SPEED_FACTOR
((u32)((CKTIM*10)) / 3)
06
07 /* 定义转子最小机械转速(rpm),当小于它时,速度反馈不可用。 */
08 #define HALL_MIN_SPEED_FDBK_RPM
((u16)60)
09 #define HALL_MIN_SPEED_FDBK
(u16)(HALL_MIN_SPEED_FDBK_RPM/6* POLE_PAIR_NUM)
10 #define MAX_PERIOD
((u32)(ROTOR_SPEED_FACTOR / HALL_MIN_SPEED_FDBK))
11
12 /* 定义转子最大机械转速(转数/分),当大于它时,速度反馈不可用:用于区别故障。*/
13 #define HALL_MAX_SPEED_FDBK_RPM
((u32)30000)
14 #define HALL_MAX_SPEED_FDBK
(u16)(HALL_MAX_SPEED_FDBK_RPM/6 * POLE_PAIR_NUM)
15 #define SPEED_OVERFLOW
((u32)(ROTOR_SPEED_FACTOR / HALL_MAX_SPEED_FDBK))
根据相关宏定义,MAX_PERIOD 的计算公式如下:
公式 14-56 霍尔传感器定时器最大周期
MAX_PERIOD =
=
𝐶𝐾𝑇𝐼𝑀 ∗ 10⁄3
𝐻𝐴𝐿𝐿_𝑀𝐼𝑁_𝑆𝑃𝐸𝐸𝐷_𝐹𝐷𝐵𝐾_𝑅𝑃𝑀/6 ∗ 𝑃𝑂𝐿𝐸_𝑃𝐴𝐼𝑅_𝑁𝑈𝑀
72000000 ∗ 10⁄3
60/6 ∗ 4
其中:
CKTIM 为定时器频率数值,一般为 72M;
HALL_MIN_SPEED_FDBK_RPM 为最慢速度(单位为 RPM),默认为 60RPM;
POLE_PAIR_NUM 为电机极对数,我们使用的电机为 4 对极,所以定义为 4;
常数 10 为 0.1Hz 单位转换比率,
常数 3 为电频率与实际频率转行比率,
常数 6 为把 RPM 单位转换为转/10s 单位,这个可以跟 0.1Hz 对等起来。
同样的方法,可以求出电机允许的最大霍尔转速对应的定时器周期
SPEED_OVERFLOW,如果测量到定时器周期比该值还小,说明电机很大可能发生
故障,直接把 HALL_MAX_PSEUDO_SPEED(-32768)赋值给 hRotorFreq_dpp 变量
并返回。
在判断得到的霍尔信号测量周期值在合适范围后,运行
HALL_GetCaptCounter 函数获取霍尔传感器的捕获计数值,该函数可以获取到电
机启动旋转后发生霍尔传感器信号捕获事件次数,如果是第一次发生捕获事件则
丢弃频率计算,直接为 hRotorFreq_dpp 变量赋值为 0 并返回。否则,结合之前得
到的霍尔传感器信号的相关周期信息运行计算得到 hRotorFreq_dpp 值,并且加
上电机旋转方向功能。
公式 14-57 转子电频率计算
STM32 技术开发手册
www.ing10bbs.com
𝜔dpp =
𝐶𝐾𝑇𝐼𝑀 ∙ 216
3 ∙ 𝑆𝐴𝑀𝑃𝐿𝐼𝑁𝐺_𝐹𝑅𝐸𝑄 ∙ 𝑐𝑎𝑝𝑡𝑢𝑟𝑒𝑑 𝑣𝑎𝑙𝑢𝑒 ∙ 𝑝𝑟𝑒𝑠𝑐𝑎𝑙𝑒𝑟 𝑣𝑎𝑙𝑢𝑒
其中:
CKTIM 是定时器频率,72MHz;
SAMPLING_FREQ 是 FOC 控制周期;
captured value 是定时器捕获值;
prescaler value 是预分频值;
常数𝟐𝟏𝟔 是数字化转换常数;
常数 3 是“频率比真正的电频率(对应电周期)高出 3 倍”。
最后,函数返回电频率数值。
代码 14-50 霍尔传感器信号超时相关函数
01 void HALL_ClrTimeOut(void)
02 {
03
HallTimeOut = FALSE;
04 }
05
06 bool HALL_IsTimedOut(void)
07 {
08
return (HallTimeOut);
09 }
HALL_ClrTimeOut 函数是把霍尔传感器信号超时标志复位,就是清除该超时
标志。HALL_IsTimedOut 函数用于返回超时情况。
代码 14-51 霍尔传感器信号捕获信息函数
01 u16 HALL_GetCaptCounter(void)
02 {
03
return (hCaptCounter);
04 }
05
06 void HALL_ClrCaptCounter(void)
07 {
08
hCaptCounter = 0;
09 }
HALL_GetCaptCounter 函 数 用 于 返 回 发 送 霍 尔 传 感 器 信 号 捕 获 次 数 。
HALL_ClrCaptCounter 函数用于把捕获次数记录清零。
代码 14-52 GetLastHallPeriod 函数
01 PeriodMeas_s GetLastHallPeriod(void)
02 {
03
PeriodMeas_s PeriodMeasAux;
04
u8 bLastSpeedFIFO_Index;
05
06
// Store current index to prevent errors if Capture occurs during processing
07
bLastSpeedFIFO_Index = bSpeedFIFO_Index;
08
09
// This is done assuming interval between captures is higher than time
STM32 技术开发手册
www.ing10bbs.com
10
11
12
13
14
15
16 }
// to read the two values
PeriodMeasAux.wPeriod = SensorPeriod[bLastSpeedFIFO_Index].hCapture;
PeriodMeasAux.wPeriod *= (SensorPeriod[bLastSpeedFIFO_Index].hPrscReg + 1);
PeriodMeasAux.bDirection = SensorPeriod[bLastSpeedFIFO_Index].bDirection;
return (PeriodMeasAux);
该函数用于获取最新的霍尔传感器信号的周期值。在 HALL_GetRotorFreq 函
数中被调用。首先,使用 bLastSpeedFIFO_Index 变量保存当前速度 FIFO 缓冲区索
引值,这里定义多了一个变量用来保存索引值并且在后面程序中调用,而不是在
后面程序中直接调用 bSpeedFIFO_Index 变量,是为了防止 bSpeedFIFO_Index 值
刚好发生变化导致数据出现偏差。然后直接把读取 FIFO 缓冲区值作为霍尔传感
器信号周期值,以及保存电机旋转方向。
代码 14-53 GetAvrgHallPeriod 函数
01 PeriodMeas_s GetAvrgHallPeriod(void)
02 {
03
u32 wFreqBuffer, wAvrgBuffer, wIndex;
04
PeriodMeas_s PeriodMeasAux;
05
06
wAvrgBuffer = 0;
07
08
for ( wIndex = 0; wIndex < HALL_SPEED_FIFO_SIZE; wIndex++ ) {
09
// Disable capture interrupts to have presc and capture of the same period
10
HALL_TIMER->DIER &= ~TIM_IT_CC1; // NB:Std libray not used for perf issues
11
12
wFreqBuffer = SensorPeriod[wIndex].hCapture;
13
wFreqBuffer *= (SensorPeriod[wIndex].hPrscReg + 1);
14
15
HALL_TIMER->DIER |= TIM_IT_CC1;
// NB:Std libray not used for perf issue
16
wAvrgBuffer += wFreqBuffer;
// Sum the whole periods FIFO
17
PeriodMeasAux.bDirection = SensorPeriod[wIndex].bDirection;
18
}
19
// Round to upper value
20
wAvrgBuffer = (u32)(wAvrgBuffer + (HALL_SPEED_FIFO_SIZE/2)-1);
21
wAvrgBuffer /= HALL_SPEED_FIFO_SIZE;
// Average value
22
23
PeriodMeasAux.wPeriod = wAvrgBuffer;
24
25
return (PeriodMeasAux);
26 }
该函数用于获取转子霍尔传感器的平均周期,前面介绍的 GetLastHallPeriod
函数是获取最新的霍尔传感器周期,这个函数也是在 HALL_GetRotorFreq 函数被
调用。wAvrgBuffer 变量用来作为计算中间值。使用 for 循环语句累加值然后再求
平均值。在 for 语句里边先空着定时器 DIER 寄存器,关闭输入捕获中断,可以防
止在后面读数据时进入捕获中断导致获取到的预分频和捕获值不一致问题。读取
之前保存的捕获值和预分频值到 wFreqBuffer 变量中,然后可以再开启定时器的
STM32 技术开发手册
www.ing10bbs.com
输入捕获,并把 wFreqBuffer 值累加到 wAvrgBuffer 中以用来计算和数,循环最后
把旋转方向变量保存到返回值变量中。
在运行 for 循环后,可以得到累加和结果,考虑到求平均值向上取整,对和
数进行处理,然后求出平均值,最后把平均值保存到返回值变量中,并且返回该
变量。
代码 14-54 HALL_StartHallFiltering 函数
01 void HALL_StartHallFiltering( void )
02 {
03
InitRollingAverage = TRUE;
04 }
该函数启动霍尔传感器信号滤波器,实际上,就是使能求平均值的方法获取
霍尔传感器信号。使用该滤波器可以平滑霍尔传感器信号数据,把最新的霍尔传
感器信号存放在滤波器 FIFO 缓冲区最顶端,并且会把最先存放的数据挤出 FIFO
缓冲区,这样可以得到最新的平均值。
代码 14-55 ReadHallState 函数
01 u8 ReadHallState(void)
02 {
03
u16 ReadValue;
04 #if defined(TIMER2_HANDLES_HALL)
05
06
ReadValue = GPIO_ReadInputData(GPIOA) & GPIO_MSK;
07
08 #elif defined(TIMER3_HANDLES_HALL)
09
ReadValue = (GPIO_ReadInputData(GPIOC)>>6) & GPIO_MSK;
10
11 #elif defined(TIMER4_HANDLES_HALL)
12
ReadValue = (GPIO_ReadInputData(GPIOB)>>6) & GPIO_MSK;
13
14 #endif
15
16
return ((u8)ReadValue);
17 }
该函数用于获取霍尔传感器信号线的状态。这里通过读取连接霍尔传感器信
号的三个引脚信息得到霍尔传感器信号的真正状态值。这里我们使用 TIM3 并且
是使用了功能引脚重映射功能,对应 PC6、PC7 和 PC8 这三个引脚,所以这里使
用把 GPIOC 右移 6 位读出引脚结果,然后与 GPIO_MSK(常量 0x0007)进行与运
算得到霍尔传感器信号状态。
代码 14-56 电角度获取与处理函数
01 s16 HALL_GetElectricalAngle(void)
STM32 技术开发手册
www.ing10bbs.com
02 {
03
return (hElectrical_Angle);
04 }
05
06 void HALL_IncElectricalAngle(void)
07 {
08
static s16 hPrevRotorFreq;
09
10
if (hRotorFreq_dpp != HALL_MAX_PSEUDO_SPEED) {
11
hElectrical_Angle += hRotorFreq_dpp;
12
hPrevRotorFreq = hRotorFreq_dpp;
13
} else {
14
hElectrical_Angle += hPrevRotorFreq;
15
}
16 }
HALL_GetElectricalAngle 函数用于获取电角度值。电角度可以直接反映出转
子的位置信息。在运行 FOC 算法时被调用,因为转子电角度是定子电流进行 Park
转换必不可少的部分。
HALL_IncElectricalAngle 函数增加包含转子位置信息的电角度变量值。这个函
数在每次 FOC 运费周期都会被调用,以合成速度信息。当速度不是最大速度时,
把 hRotorFreq_dpp 累加到电角度上,这里还使用了 hPrevRotorFreq 存放当前的
转子电频率值。如果转子速度等于最大转速时,使用 hPrevRotorFreq 变量值累加
电角度。
代码 14-57 HALL_Init_Electrical_Angle 函数
01 void HALL_Init_Electrical_Angle(void)
02 {
03
switch (ReadHallState()) {
04
case STATE_5:
05
hElectrical_Angle = (s16)(S16_PHASE_SHIFT+S16_60_PHASE_SHIFT/2);
06
break;
07
case STATE_1:
08
hElectrical_Angle =(s16)(S16_PHASE_SHIFT+S16_60_PHASE_SHIFT+
09
S16_60_PHASE_SHIFT/2);
10
break;
11
case STATE_3:
12
hElectrical_Angle =(s16)(S16_PHASE_SHIFT+S16_120_PHASE_SHIFT+
13
S16_60_PHASE_SHIFT/2);
14
break;
15
case STATE_2:
16
hElectrical_Angle =(s16)(S16_PHASE_SHIFT-S16_120_PHASE_SHIFT17
S16_60_PHASE_SHIFT/2);
18
break;
19
case STATE_6:
20
hElectrical_Angle =(s16)(S16_PHASE_SHIFT-S16_60_PHASE_SHIFT21
S16_60_PHASE_SHIFT/2);
22
break;
23
case STATE_4:
24
hElectrical_Angle =(s16)(S16_PHASE_SHIFT-S16_60_PHASE_SHIFT/2);
25
break;
26
default:
27
break;
STM32 技术开发手册
www.ing10bbs.com
28
29 }
}
霍尔效应传感器是“绝对”的,因此只需读取其输出就可以重建转子位置。
该函数利用这个工作原理,在任何电机启动前,这个函数初始化包含当前电角度
的软件变量。这个函数通过读取 H1、H2 和 H3 的信号状态(由 ReadHallState 函
数执行该任务)和初始化软件变量执行。可获得的最大精度为±30 度,(即
30/POLE_PAIR_NUM 的机械度)。
函数中通过#if…#elif…#endif 的条件编译选择霍尔传感器位置安装为间隔 60°
和 120°两种情况,因为我们使用的电机是 120°安装的,所以这里只分析 120°
的程序,对于 60°程序请自行参考理解。
该函数就一个 switch 语句,判断 ReadHallState 函数返回值执行对应的项目
计算。ReadHallState 函数是获取霍尔传感器信号的实际状态,根据这个时间状态
值可以确定转子的位置分布。接下来为转子电角度进行分项赋值处理。
表格 14-14 120°霍尔传感器安装方式电角度
状
电角度
态
5
1
3
2
6
4
S16_PHASE_SHIFT+S16_60_PHASE_SHIFT/2
=(HALL_PHASE_SHIFT * 65536/360)+ (65536/6/2)
=(120*65536/360)+ (65536/6/2)=(s16)27306
S16_PHASE_SHIFT+S16_60_PHASE_SHIFT+S16_60_PHASE_SHIFT/2
=(HALL_PHASE_SHIFT * 65536/360) + (65536/6)+ (65536/6/2)
=(120*65536/360) + (65536/6)+ (65536/6/2)=(s16)-27307
S16_PHASE_SHIFT+S16_120_PHASE_SHIFT+S16_60_PHASE_SHIFT/2
=(HALL_PHASE_SHIFT * 65536/360) + (65536/3)+ (65536/6/2)
=(120*65536/360) + (65536/3)+ (65536/6/2)= (s16)-16384
S16_PHASE_SHIFT-S16_120_PHASE_SHIFT-S16_60_PHASE_SHIFT/2
=(HALL_PHASE_SHIFT * 65536/360) - (65536/3)- (65536/6/2)
=(120*65536/360) - (65536/3)- (65536/6/2)= (s16)-5461
S16_PHASE_SHIFT-S16_60_PHASE_SHIFT-S16_60_PHASE_SHIFT/2
=(HALL_PHASE_SHIFT * 65536/360) - (65536/6)- (65536/6/2)
=(120*65536/360)- (65536/6)- (65536/6/2)= (s16)5461
S16_PHASE_SHIFT-S16_60_PHASE_SHIFT/2
=(HALL_PHASE_SHIFT * 65536/360) - (65536/6/2)
=(120*65536/360)-- (65536/6/2)= (s16)16384
这里 HALL_PHASE_SHIFT 值是通过测量得到的,测量方法我们在之前已经做
了介绍的。
STM32 技术开发手册
www.ing10bbs.com
代码 14-58 TIM3_IRQHandler 函数
01 void TIM3_IRQHandler(void)
02 {
03
static u8 bHallState;
04
u8 bPrevHallState;
05
06 // Check for the source of TIMx int - Capture or Update Event 07
if ( TIM_GetFlagStatus(HALL_TIMER, TIM_FLAG_Update) == RESET ) {
08
// A capture event generated this interrupt
09
bPrevHallState = bHallState;
10
bHallState = ReadHallState();
11
switch (bHallState) {
12
case STATE_5:
13
if (bPrevHallState == STATE_5) {
14
//a speed reversal occured
15
if (bSpeed<0) {
16
bSpeed = POSITIVE_SWAP;
17
} else {
18
bSpeed = NEGATIVE_SWAP;
19
}
20
} else if (bPrevHallState == STATE_6) {
21
bSpeed = POSITIVE;
22
} else if (bPrevHallState == STATE_3) {
23
bSpeed = NEGATIVE;
24
}
25
// Update angle
26
if (bSpeed<0) {
27
hElectrical_Angle = (s16)(S16_PHASE_SHIFT+S16_60_PHASE_SHIFT);
28
} else if (bSpeed!= ERROR) {
29
hElectrical_Angle = S16_PHASE_SHIFT;
30
}
31
break;
32
33
case STATE_3:
34
if (bPrevHallState == STATE_3) {
35
//a speed reversal occured
36
if (bSpeed<0) {
37
bSpeed = POSITIVE_SWAP;
38
} else {
39
bSpeed = NEGATIVE_SWAP;
40
}
41
} else if (bPrevHallState == STATE_5) {
42
bSpeed = POSITIVE;
43
} else if (bPrevHallState == STATE_6) {
44
bSpeed = NEGATIVE;
45
}
46
// Update of the electrical angle
47
if (bSpeed<0) {
48
hElectrical_Angle = (s16)(S16_PHASE_SHIFT+S16_120_PHASE_SHIFT+
49
S16_60_PHASE_SHIFT);
50
} else if (bSpeed!= ERROR) {
51
hElectrical_Angle =(s16)(S16_PHASE_SHIFT + S16_120_PHASE_SHIFT);
52
}
53
break;
54
55
case STATE_6:
56
if (bPrevHallState == STATE_6) {
57
if (bSpeed<0) {
58
bSpeed = POSITIVE_SWAP;
59
} else {
60
bSpeed = NEGATIVE_SWAP;
STM32 技术开发手册
www.ing10bbs.com
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
}
}
if (bPrevHallState == STATE_3) {
bSpeed = POSITIVE;
} else if (bPrevHallState == STATE_5) {
bSpeed = NEGATIVE;
}
if (bSpeed<0) {
hElectrical_Angle =(s16)(S16_PHASE_SHIFT - S16_60_PHASE_SHIFT);
} else if (bSpeed!= ERROR) {
hElectrical_Angle =(s16)(S16_PHASE_SHIFT - S16_120_PHASE_SHIFT);
}
break;
default:
bSpeed = ERROR;
break;
}
// A capture event occured, it clears the flag
TIM_ClearFlag(HALL_TIMER, TIM_FLAG_CC1);
// used for discarding first capture
if (hCaptCounter < U16_MAX) {
hCaptCounter++;
}
// Compute new array index
if (bSpeedFIFO_Index != HALL_SPEED_FIFO_SIZE-1) {
bSpeedFIFO_Index++;
} else {
bSpeedFIFO_Index = 0;
}
//Timeout Flag is cleared when receiving an IC
HALL_ClrTimeOut();
// Store the latest speed acquisition
if (bGP1_OVF_Counter != 0) {
// There was counter overflow before capture
u32 wCaptBuf;
u16 hPrscBuf;
wCaptBuf = (u32)TIM_GetCapture1(HALL_TIMER);
hPrscBuf = HALL_TIMER->PSC;
while (bGP1_OVF_Counter != 0) {
wCaptBuf += 0x10000uL;// Compute the real captured value (> 16-bit)
bGP1_OVF_Counter--;
// OVF Counter is 8-bit and Capt is 16-bit, thus max CaptBuf is 24-bits
}
while (wCaptBuf > U16_MAX) {
wCaptBuf /= 2;
// Make it fit 16-bit using virtual prescaler
// Reduced resolution not a problem since result just slightly < 16-bit
hPrscBuf = (hPrscBuf * 2) + 1;
if (hPrscBuf > U16_MAX/2) { // Avoid Prsc overflow
hPrscBuf = U16_MAX;
wCaptBuf = U16_MAX;
}
}
SensorPeriod[bSpeedFIFO_Index].hCapture = wCaptBuf;
SensorPeriod[bSpeedFIFO_Index].hPrscReg = hPrscBuf;
SensorPeriod[bSpeedFIFO_Index].bDirection = bSpeed;
STM32 技术开发手册
www.ing10bbs.com
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
if (RatioInc) {
RatioInc = FALSE;
// Previous capture caused overflow
// Don't change prescaler (delay due to preload/update mechanism)
} else {
if ((HALL_TIMER->PSC) < HALL_MAX_RATIO) { // Avoid OVF w/ very low freq
(HALL_TIMER->PSC)++; // To avoid OVF during speed decrease
RatioInc = TRUE;
// new prsc value updated at next capture only
}
}
} else { // No counter overflow
u16 hHighSpeedCapture, hClockPrescaler;
hHighSpeedCapture = (u32)TIM_GetCapture1(HALL_TIMER);
SensorPeriod[bSpeedFIFO_Index].hCapture = hHighSpeedCapture;
SensorPeriod[bSpeedFIFO_Index].bDirection = bSpeed;
// Store prescaler directly or incremented if value changed on last capt
hClockPrescaler = HALL_TIMER->PSC;
// If prsc preload reduced in last capture, store current register + 1
if (RatioDec) { // and don't decrease it again
SensorPeriod[bSpeedFIFO_Index].hPrscReg = (hClockPrescaler)+1;
RatioDec = FALSE;
} else { // If prescaler was not modified on previous capture
if (hHighSpeedCapture >= LOW_RES_THRESHOLD) { // If capture range correct
SensorPeriod[bSpeedFIFO_Index].hPrscReg = hClockPrescaler;
} else {
if (HALL_TIMER->PSC == 0) { // or prescaler cannot be further reduced
SensorPeriod[bSpeedFIFO_Index].hPrscReg = hClockPrescaler;
} else { // The prescaler needs to be modified to optimize the accuracy
SensorPeriod[bSpeedFIFO_Index].hPrscReg = hClockPrescaler;
(HALL_TIMER->PSC)--;
// Increase accuracy by decreasing prsc
// Avoid decrementing again in next capt.(register preload delay)
RatioDec = TRUE;
}
}
}
}
if (InitRollingAverage) {
u16 hCaptBuf, hPrscBuf;
s8 bSpeedAux;
u32 wIndex;
// Read last captured value and copy it into the whole array
hCaptBuf = SensorPeriod[bSpeedFIFO_Index].hCapture;
hPrscBuf = SensorPeriod[bSpeedFIFO_Index].hPrscReg;
bSpeedAux = SensorPeriod[bSpeedFIFO_Index].bDirection;
for (wIndex = 0; wIndex != HALL_SPEED_FIFO_SIZE-1; wIndex++) {
SensorPeriod[wIndex].hCapture = hCaptBuf;
SensorPeriod[wIndex].hPrscReg = hPrscBuf;
SensorPeriod[wIndex].bDirection = bSpeedAux;
}
InitRollingAverage = FALSE;
// Starting from now, the values returned by MTC_GetRotorFreq are averaged
DoRollingAverage = TRUE;
}
//Update Rotor Frequency Computation
hRotorFreq_dpp = HALL_GetRotorFreq();
} else {
TIM_ClearFlag(HALL_TIMER, TIM_FLAG_Update);
STM32 技术开发手册
www.ing10bbs.com
187
188
189
190
191
192
193
194
195
196
197 }
// an update event occured for this interrupt request generation
if (bGP1_OVF_Counter < U8_MAX) {
bGP1_OVF_Counter++;
}
if (bGP1_OVF_Counter >= HALL_MAX_OVERFLOWS) {
HallTimeOut = TRUE;
hRotorFreq_dpp = 0;
}
}
这里是霍尔传感器信号功能定时器的中断服务函数,我们在初始化定时器时
候使能了更新中断和通道 1 的捕获中断。因为我们使用 TIM3 所以上面直接把函
数名称使用 TIM3_IRQHandler,因为在源工程文档中是由条件编译选择的。另外,
在该中断服务函数中有分霍尔传感器信号 60°安装和 120°安装时对应不同的处
理方法,直流只贴出并且分析 120°安装对应的代码,至于 60°安装的电机希望
自行参考理解。
首先,通过 TIM_GetFlagStatus 函数获取中断标志,来区分是发送捕获中断,
还是发送更新中断。对于捕获中断,使用 bPrevHallState 局部变量保存之前的霍
尔传感器信号状态,然后调用 ReadHallState 函数读取当前霍尔传感器信号状态
保存在 bHallState 变量中,该变量是一个静态变量。然后使用 switch 分支选择语
句,分别对霍尔传感器信号的 6 种状态进行独立处理,这里的处理主要有两个目
的,一个是确认电机的旋转方向(保存于 bSpeed 变量中),另外一个是计算电角
度(保存于 hElectrical_Angle 变量中)。bSpeed 变量有五种可能情况:
1) POSITIVE(实际值为 1)
:正转
2) NEGATIVE(实际值为-1):反转
3) POSITIVE_SWAP(实际值为 2):翻转为正转
4) NEGATIVE_SWAP(实际值为-2):翻转为反转
5) ERROR(实际值为 127):方向反馈出错
对霍尔传感器信号状态为 5 时:判断上次霍尔传感器状态是否为 5,如果是,
说明发生电机翻转,当原本是反转,把 bSpeed 设置为 POSITIVE_SWAP;当原本
是正转时,把 bSpeed 设置为 NEGATIVE_SWAP。如果上个霍尔传感器信号不为 5,
上面是正常的旋转,根据图 14-74,上个霍尔信号状态为 6,现在状态改变为 5
这种情况,电机是正转的;如果上个霍尔状态为 3,现在状态改变为 5 这种情况,
STM32 技术开发手册
www.ing10bbs.com
电机是反转的。接下来,进行电角度计算,bSpeed 小于 0,对应 NEGATIVE 和
NEGATIVE_SWAP
这 两 种 可 能 情 况 , 计 算 电 角 度 为
S16_PHASE_SHIFT+S16_60_PHASE_SHIFT (32768);如果 bSpeed 不小于 0 并且不为
ERROR 错误状态,计算电角度为 S16_PHASE_SHIFT(21845)。对于 switch 的其他分
支情况的分析,对应当前霍尔传感器信号不同状态,类似上面状态 5 情况,这里
就不再多费口舌了。switch 语句还有一个 default 情况,是状态出错的。
运行完 switch 语句后,得到旋转方向和电角度。使用 TIM_ClearFlag 函数清
楚定时器通道 1 捕获标志位。hCaptCounter 记录当前发生捕获事件次数,最大值
被限定为 0xFFFF。为 bSpeedFIFO_Index 变量赋值记录 FIFO 缓冲区深度。调用
HALL_ClrTimeOut 函数清除霍尔信号捕获超时标志,因为可以正常捕获到霍尔信
号,所以不会出现超时情况。
接下来是存储最新的速度采集信息。当 bGP1_OVF_Counter 变量不为 0 时,
说明预分频值太小,发生了定时器计数溢出,该变量就保存了溢出的次数。这里
使用 if 语句判断该变量值,如果不为 0,调用 TIM_GetCapture1 函数获取当前定
时器捕获值保存在局部变量 wCaptBuf 中,读取当前预分频值保存在 hPrscBuf 变
量中。通过 while 循环把累积的捕获值合计起来保存在 wCaptBuf 变量中,以为
是发生过溢出情况的,所以需要进行捕获值累加处理。因为发生了计数器溢出情
况,说明定时器的预分频值设置太小了,我们需要把预分频值改大,那究竟需要
改为多大呢?接下来就通过一个 while 循环语句来实现新的预分频计算,这里主
要是把 wCaptBuf 除以 2,把 hPrscBuf 值乘以 2 并加 1 处理,然后测试参数合适
性。
然后,把捕获值、新的预分频值和方向保存到速度周期 FIFO 缓冲区。如果
比率增加标志为正(RatioInc 为 TRUE),即上次已经发生计数溢出情况,把该标
志位清零为 FALSE,就是不需要再次调节预分频值,因为这个是由于预加载/更新
机制导致延迟。如果原本 RatioInc 标志位为 FALSE,并且预分频寄存器值小于预
定的最大比较值(HALL_MAX_RATIO),把预分频器寄存器值加 1 并且把 RatioInc
标志位设置为 TRUE。
STM32 技术开发手册
www.ing10bbs.com
如果 bGP1_OVF_Counter 变量等于 0,说明计数器没有发生溢出,先调用
TIM_GetCapture1 函数读取定时器捕获值保存于 hHighSpeedCapture 局部变量中。
然后就直接把捕获值和方向保存到速度周期 FIFO 缓冲区。读取预分频寄存器值
直接保存于 hClockPrescaler 变量中。如果在最后一次捕获时更改值,则需要增加
该分频值。如果比率减小标志为正(RatioDec 为 TRUE),也是最后捕获中预分频
值减少,就保存当前预分频值+1,并且不需要再减小。如果预分频器在以前的捕
获中未被修改,即 RatioDec 为 FALSE,如果 hHighSpeedCapture(当前捕获值)大
于等于 LOW_RES_THRESHOLD(最小捕获阈值),直接把 hClockPrescaler 保存到速
度周期 FIFO 缓冲区中。如果当前捕获值过小,即 hHighSpeedCapture 小于
LOW_RES_THRESHOLD,如果当前预分频寄存器值为 0,则保存 hClockPrescaler 保
存 到 速 度 周 期 FIFO 缓 冲 区 中 ; 否 则 , 即 预 分 频 寄 存 器 值 大 于 0 , 保 存
hClockPrescaler 保存到速度周期 FIFO 缓冲区中,并且减小预分频寄存器值,和把
比率减小标志设置为 TRUE。
如果 InitRollingAverage 为 TRUE,即初始化环型 FIFO 计算平均值的标志为正,
也即是在算法中调用了 HALL_StartHallFiltering 函数,这里为速度周期 FIFO 缓冲
区初始化操作。先读取当前速度周期 FIFO 缓冲区一个初始值,然后通过 for 循环
语句为缓冲区每个成员都初始化相同的值。然后,把 InitRollingAverage 改为 FALSE
标志初始化完成,并且把 DoRollingAverage 标志改为 TRUE,表示可以之后使用去
平均值方法反馈转子频率,通过 GetAvrgHallPeriod 函数实现霍尔传感器信号周期
平均值计算。
在捕获中断最后,调用 HALL_GetRotorFreq 函数获取转子的电频率,并且保
存在 hRotorFreq_dpp 变量中。
总结上面关于霍尔传感器信号捕获中断服务函数代码,我们可以分析到它要
实现的功能有:转子旋转方向确定、计算电角度、计算新的预分频器值、更新速
度周期 FIFO 缓冲区、转子电频率计算。
我们来看看,霍尔传感器信号定时器发送更新中断时的处理方法:先清除定
时器更新标志位,判断 bGP1_OVF_Counter 变量(记录发生更新中断次数)是否
到 达 255 , 如 果 不 是 就 增 加 该 值 , 如 果 已 经 达 到 用 户 定 义 的 最 大 限 度 值
STM32 技术开发手册
www.ing10bbs.com
HALL_MAX_OVERFLOWS 就 指 示 霍 尔 传 感 器 信 号 超 时 错 误 并 且 清 零
bGP1_OVF_Counter 值。
stm32f10x_encoder.c 文件内容
该文件存放了编码器信号处理。我们知道 FOC 控制必须获取转子位置,前面
我们分析了使用霍尔传感器信号获取方法,现在我们来分析使用编码器获取方法。
该文件就是使用编码器实现转子位置获取,同时计算转子转速。
在第 6 章 中我们介绍了编码器的基本知识,实现了编码器信号读取方法。
实际上,STM32 有专门的编码器接口,使用该编码器接口可以方便的获取当前编
码器信息,包括速度和方向,具体内容可以参考 3.11 。FOC 算法需要知道转子
当前位置和转子的转速,使用编码器可以非常方便获取到转子的角位移,转子角
位移对事件其进行转速;对于转子位置是需要得到绝对位置的,而在为校准之前,
我们读取编码器的位置是相对位置的,即相对与初始位置的角位移,所以我们需
要在故障事件或者系统复位之后,在电机初始启动之前进行转子的预定位,这样
实现从相对位置到绝对位置的转变。
校准设置
正交编码一个相对位置传感器。考虑磁场定向控制要求绝对位置信息,必需
以某种方式建立一个零角度位置。这可以通过校准相位完成,并在电机第一个启
动之前进行,也在故障事件后进行。基本上包括以在固定方向线性增长的零参考
转矩(Iq)和参考磁通(Id)。
如果在相位最后配置正确,定子就会被锁定在一个已知的位置,编码器定时
器计数器也会相应的初始化。
依据电机的惯性和负载情况下面的参数用于制定校准相位:
◆ T_ALIGNMENT(单位:ms)定义相位校准时间;
◆ l ALIGNMENT_ANGLE 规定向量方向(下图中θ角);
STM32 技术开发手册
www.ing10bbs.com
图 14-84 校准角度
◆ I_ALIGNMENT(数字化的)定义参考 Id 幅值的最终值。
根据我们驱动板实际电路,已经 ALIGNMENT_ANGLE 设置为 90 度(默认设
置而已,根据实际可以修改),可以通过下式计算最后 A 相电流值:
校准 A 相最终电流 = (I_ALIGNMENT * 3.3)/(65535*Av*Rshunt)
关于更多的编码器校准知识可以阅读《附录 10:永磁交流伺服电机的编码
器相位为何要与转子磁极相位对齐》
代码 14-59 ENC_Init 函数
01 void ENC_Init(void)
02 {
03
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
04
TIM_ICInitTypeDef TIM_ICInitStructure;
05
06
// Encoder unit connected to TIM3, 4X mode
07
GPIO_InitTypeDef GPIO_InitStructure;
08
NVIC_InitTypeDef NVIC_InitStructure;
09
10
/* TIM3 clock source enable */
11
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
12
/* Enable GPIOA, clock */
13
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO, ENABLE);
14
GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);
15
16
GPIO_StructInit(&GPIO_InitStructure);
17
/* Configure PA.06,07 as encoder input */
18
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
19
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
20
GPIO_Init(GPIOC, &GPIO_InitStructure);
21
STM32 技术开发手册
www.ing10bbs.com
22
/* Enable the TIM3 Update Interrupt */
23
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
24
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = TIMx_PRE_EMPTION_PRIORITY;
25
NVIC_InitStructure.NVIC_IRQChannelSubPriority = TIMx_SUB_PRIORITY;
26
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
27
NVIC_Init(&NVIC_InitStructure);
28
29
/* Timer configuration in Encoder mode */
30
TIM_DeInit(ENCODER_TIMER);
31
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
32
33
TIM_TimeBaseStructure.TIM_Prescaler = 0x0; // No prescaling
34
TIM_TimeBaseStructure.TIM_Period = (4*ENCODER_PPR)-1;
35
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
36
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
37
TIM_TimeBaseInit(ENCODER_TIMER, &TIM_TimeBaseStructure);
38
39
TIM_EncoderInterfaceConfig(ENCODER_TIMER, TIM_EncoderMode_TI12,
40
TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
41
TIM_ICStructInit(&TIM_ICInitStructure);
42
43
TIM_ICInitStructure.TIM_ICFilter = ICx_FILTER;
44
TIM_ICInit(ENCODER_TIMER, &TIM_ICInitStructure);
45
46
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
47
TIM_ICInit(ENCODER_TIMER, &TIM_ICInitStructure);
48
49 // Clear all pending interrupts
50
TIM_ClearFlag(ENCODER_TIMER, TIM_FLAG_Update);
51
TIM_ITConfig(ENCODER_TIMER, TIM_IT_Update, ENABLE);
52
//Reset counter
53
ENCODER_TIMER->CNT = COUNTER_RESET;
54
55
TIM_Cmd(ENCODER_TIMER, ENABLE);
56 }
这个函数的目的是初始化编码器计时器。启动外设时钟、输入引脚和更新中
断使能。定时器以 4 倍频模式配置,这意味着计数器在定时器输入 1 和 2
(TIMx_CH1 和 TIMx_CH2 引脚)的上升/下降沿递增/递减。
在 MC_encoder_param.h 文件中我们定义用于编码器接口的定时器,根据开
发板实际硬件电路,我们使用 TIM3,这个跟霍尔传感器接口是使用同一个 TIM
的,所以一个例程只能用其他一种(实际上也是足够的啦),定时器通道引脚还
是用到 TIM3 的重映射引脚 PC6 和 PC7。
代码 14-60 编码器接口定时器选择
01 #if ((defined ENCODER)||(defined VIEW_ENCODER_FEEDBACK))
02 /* Define here the 16-bit timer chosen to handle encoder feedback */
03 /*TIMER 2 is the mandatory selection when using STM32MC-KIT */
04 /* 两路传感器信号与 TIMER2 输入引脚连接 */
05 //#define TIMER2_HANDLES_ENCODER
06 /* 两路传感器信号与 TIMER3 输入引脚连接 */
07 #define TIMER3_HANDLES_ENCODER
08 /* 两路传感器信号与 TIMER4 输入引脚连接 */
09 //#define TIMER4_HANDLES_ENCODER
STM32 技术开发手册
www.ing10bbs.com
10 #endif // ENCODER
11
12 #if defined(TIMER2_HANDLES_ENCODER)
13 #define ENCODER_TIMER
TIM2
14 #elif defined(TIMER3_HANDLES_ENCODER)
15 #define ENCODER_TIMER
TIM3
16 #else // TIMER4_HANDLES_ENCODER
17 #define ENCODER_TIMER
TIM4
18 #endif
// Encoder unit connected to TIM2
// Encoder unit connected to TIM3
// Encoder unit connected to TIM4
因为我们默认使用 TIM3 作为编码器接口定时器,所以在 ENC_Init 函数中我
们直接贴出 TIM3 部分代码,关于使用 TIM2 或者 TIM4 的可以自行参考理解。
ENC_Init 函数先开启定时器和相应的 GPIO 时钟,初始化编码器接口引脚,
设置定时器中断优先级。配置定时器的相关参数,因为用编码器模式,预分频设
置为 0,周期根据编码器线数(编码器旋转一圈单相输出的脉冲数)设置为
(4*ENCODER_PPR)-1。TIM_EncoderInterfaceConfig 函数配置编码器接口,使用在
TI1 和 TI2 通道输入编码器信号,极性都设置为上升沿检测。然后设置滤波器值。
最后,清楚相关定时器更新标志位,配置使能更新中断,复位定时器计数器
为 COUNTER_RESET , 以 及 启 动 定 时 器 。 这 里 将 定 时 器 计 数 值 设 置 为
COUNTER_RESET,它是与编码器零点校准相关参数。
𝐴𝐿𝐼𝐺𝑁𝑀𝐸𝑁𝑇_𝐴𝑁𝐺𝐿𝐸 ∙ 4 ∙ 𝐸𝑁𝐶𝑂𝐷𝐸𝑅_𝑃𝑃𝑅
−1
360
𝐶𝑂𝑈𝑁𝑇𝐸𝑅_𝑅𝐸𝑆𝐸𝑇 =
𝑃𝑂𝐿𝐸_𝑃𝐴𝐼𝑅_𝑁𝑈𝑀
其中:
ALIGNMENT_ANGLE:为用户收到的校准角度(单位为度);
ENCODER_PPR:编码器线数;
POLE_PAIR_NUM:电机极对数;
常数 4:编码器信号已经被设置为 4 倍频检测的;
常数 360:角度单位转换
代码 14-61 ENC_Get_Electrical_Angle 函数
01 s16 ENC_Get_Electrical_Angle(void)
02 {
03
s32 temp;
04
05
temp = (s32)(TIM_GetCounter(ENCODER_TIMER)) * (s32)(U32_MAX / (4*ENCODER_PPR));
06
temp *= POLE_PAIR_NUM;
07
return ((s16)(temp/65536)); // s16 result
08 }
该函数是转子绝对电角度获取。
公式 14-58 编码器绝对电角度计算
STM32 技术开发手册
www.ing10bbs.com
Electrical_Angle =
counter ∙ 65536 ∗ 𝑃𝑂𝐿𝐸_𝑃𝐴𝐼𝑅_𝑁𝑈𝑀
4 ∙ 𝐸𝑁𝐶𝑂𝐷𝐸𝑅_𝑃𝑃𝑅
其中:
counter:编码器计算值;
POLE_PAIR_NUM:电机极对数;
ENCODER_PPR:编码器线数;
常数 65536:将电角度数字化:
0->0°
S16_MAX(32767)->180°
S16_MIN(-32768)->-180°;
常数 4:编码器信号已经被设置为 4 倍频检测的。
在函数中,调用 TIM_GetCounter 函数获取编码器计数值。
代码 14-62 ENC_Get_Mechanical_Angle 函数
01 s16 ENC_Get_Mechanical_Angle(void)
02 {
03
s32 temp;
04
05
temp = (s32)(TIM_GetCounter(ENCODER_TIMER)) * (s32)(U32_MAX / (4*ENCODER_PPR)) ;
06
return ((s16)(temp/65536)); // s16 result
07 }
该函数是转子绝对机械角度。机械角度是在电角度基础上除以电机极对数
POLE_PAIR_NUM。
⚫
电气频率、机械频率、转速之间的关系:
电频率=磁极对数 x 机械频率
RPM 的速度=60 x 机械频率(转速:每分钟转速)
例如:电频率=100 赫兹,电机 8 对磁极对数:
100Hz(电频率)<->100 / 8=12.5Hz(机械频率)<->12.5×60= 750(转速)
代码 14-63 ENC_ResetEncoder 函数
01 void ENC_ResetEncoder(void)
02 {
03
//Reset counter
04
ENCODER_TIMER->CNT = COUNTER_RESET;
05 }
该函数是复位编码器,实际上就是把编码器计数设置为 COUNTER_RESET。
代码 14-64 ENC_Clear_Speed_Buffer 函数
01 void ENC_Clear_Speed_Buffer(void)
02 {
03
u32 i;
STM32 技术开发手册
www.ing10bbs.com
04
05
06
07
08
09 }
for (i=0; i<SPEED_BUFFER_SIZE; i++) {
hSpeed_Buffer[i] = 0;
}
bIs_First_Measurement = TRUE;
该函数是把编码器速度缓冲区(用于速度平均值计算)内容清零,并指示是
第一次测量。
代码 14-65 ENC_Calc_Rot_Speed 函数
01 s16 ENC_Calc_Rot_Speed(void)
02 {
03
s32 wDelta_angle;
04
u16 hEnc_Timer_Overflow_sample_one, hEnc_Timer_Overflow_sample_two;
05
u16 hCurrent_angle_sample_one, hCurrent_angle_sample_two;
06
signed long long temp;
07
s16 haux;
08
09
if (!bIs_First_Measurement) {
10
// 1st reading of overflow counter
11
hEnc_Timer_Overflow_sample_one = hEncoder_Timer_Overflow;
12
// 1st reading of encoder timer counter
13
hCurrent_angle_sample_one = ENCODER_TIMER->CNT;
14
// 2nd reading of overflow counter
15
hEnc_Timer_Overflow_sample_two = hEncoder_Timer_Overflow;
16
// 2nd reading of encoder timer counter
17
hCurrent_angle_sample_two = ENCODER_TIMER->CNT;
18
19
// Reset hEncoder_Timer_Overflow and read the counter value for the next
20
// measurement
21
hEncoder_Timer_Overflow = 0;
22
haux = ENCODER_TIMER->CNT;
23
24
if (hEncoder_Timer_Overflow != 0) {
25
haux = ENCODER_TIMER->CNT;
26
hEncoder_Timer_Overflow = 0;
27
}
28
29
if (hEnc_Timer_Overflow_sample_one != hEnc_Timer_Overflow_sample_two) {
30
//Compare sample 1 & 2 and check if an overflow has been generated right
31
//after the reading of encoder timer. If yes, copy sample 2 result in
32
//sample 1 for next process
33
hCurrent_angle_sample_one = hCurrent_angle_sample_two;
34
hEnc_Timer_Overflow_sample_one = hEnc_Timer_Overflow_sample_two;
35
}
36
37
if ( (ENCODER_TIMER->CR1 & TIM_CounterMode_Down) == TIM_CounterMode_Down) {
38
// encoder timer down-counting
39
wDelta_angle = (s32)(hCurrent_angle_sample_one - hPrevious_angle 40
(hEnc_Timer_Overflow_sample_one) * (4*ENCODER_PPR));
41
} else {
42
//encoder timer up-counting
43
wDelta_angle = (s32)(hCurrent_angle_sample_one - hPrevious_angle +
44
(hEnc_Timer_Overflow_sample_one) * (4*ENCODER_PPR));
45
}
46
47
// speed computation as delta angle * 1/(speed sempling time)
48
temp = (signed long long)(wDelta_angle * SPEED_SAMPLING_FREQ);
49
temp *= 10; // 0.1 Hz resolution
STM32 技术开发手册
www.ing10bbs.com
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 }
temp /= (4*ENCODER_PPR);
} //is first measurement, discard it
else {
bIs_First_Measurement = FALSE;
temp = 0;
hEncoder_Timer_Overflow = 0;
haux = ENCODER_TIMER->CNT;
// Check if hEncoder_Timer_Overflow is still zero. In case an overflow IT
// occured it resets overflow counter and wPWM_Counter_Angular_Velocity
if (hEncoder_Timer_Overflow != 0) {
haux = ENCODER_TIMER->CNT;
hEncoder_Timer_Overflow = 0;
}
}
hPrevious_angle = haux;
return ((s16) temp);
该函数用于计算并且返回最新的机械频率。首先,判断是否是开始测量,如
果不是开始测量,即 bIs_First_Measurement 为 FALSE,把编码器计数溢出次数值
hEncoder_Timer_Overflow 存放在 hEnc_Timer_Overflow_sample_one 变量中,读
取编码器计数值,存放在 hCurrent_angle_sample_one 变量;然后,再次把编码器
计数溢出次数值存放在 hEnc_Timer_Overflow_sample_two 变量中,并且把再次把
编码器计数溢出次数值 hEncoder_Timer_Overflo 清零,把读取当前计数值存放在
haux 变 量 中 用 于 下 次 测 量 。 比 较 hEnc_Timer_Overflow_sample_one 和
hEnc_Timer_Overflow_sample_two 这两个值是否相等,检查是否在读取编码器计
数值时发生溢出情况。如果是发送溢出情况,把第二次的溢出次数和编码器计数
值赋值给第一次的对应变量。
读取定时器控制寄存器的 DIR 位判断当前定时器的计算方向,如果是向下计
数,变化绝对角度 wDelta_angle 由当前编码器计数减去上一次计数值,再减去计
数溢出导致的计数值;如果是向上计数,变化绝对角度 wDelta_angle 由当前编码
器计数减去上一次计数值,再加上计数溢出导致的计数值。得到变化绝对角度
wDelta_angle 参数后,就可以算出绝对机械频率:
mechanical_speed =
𝑤𝐷𝑒𝑙𝑡𝑎_𝑎𝑛𝑔𝑙𝑒 ∙ 𝑆𝑃𝐸𝐸𝐷_𝑆𝐴𝑀𝑃𝐿𝐼𝑁𝐺_𝐹𝑅𝐸𝑄 ∙ 10
4 ∙ 𝐸𝑁𝐶𝑂𝐷𝐸𝑅_𝑃𝑃𝑅
其中:
wDelta_angle:绝对角度变化量;
SPEED_SAMPLING_FREQ:速度采样频率,也是速度环控制频率;
STM32 技术开发手册
www.ing10bbs.com
ENCODER_PPR:编码器线数;
常数 10:0.1Hz 与 HZ 单位的转换,电机库使用 0.1Hz 单位;
常数 4:编码器信号已经被设置为 4 倍频检测的。
如果是第一次开始测量,先把 bIs_First_Measurement 标志位设置为 FALSE,
标识下次已经不是第一次测量。把 temp 变量值赋值为 0,该值为函数返回值,
即表示绝对机械。读取当前编码器计数值,如果编码器计数溢出次数值
hEncoder_Timer_Overflow 不为 0,把它清零。
函数最后把 haux 变量值赋值给 hPrevious_angle 记录下编码器计数值,以用
于下次计算。
代码 14-66 ENC_Get_Mechanical_Speed 函数
01 s16 ENC_Get_Mechanical_Speed(void)
02 {
03
return (hRot_Speed);
04 }
该函数返回平均的绝对机械频率。该平均值由 ENC_Calc_Average_Speed 函
数算出,并保存在 hRot_Speed 变量中。
01 void ENC_Calc_Average_Speed(void)
02 {
03
s32 wtemp;
04
u16 hAbstemp;
05
u32 i;
06
u8 static bError_counter;
07
08
wtemp = ENC_Calc_Rot_Speed();
09
hAbstemp = ( wtemp < 0 ? - wtemp : wtemp);
10
11
/* Checks for speed measurement errors when in RUN State and saturates if necessary*/
12
if (State == RUN) {
13
if (hAbstemp < MINIMUM_MECHANICAL_SPEED) {
14
if (wtemp < 0) {
15
wtemp = -(s32)(MINIMUM_MECHANICAL_SPEED);
16
} else {
17
wtemp = MINIMUM_MECHANICAL_SPEED;
18
}
19
bError_counter++;
20
} else if (hAbstemp > MAXIMUM_MECHANICAL_SPEED) {
21
if (wtemp < 0) {
22
wtemp = -(s32)(MAXIMUM_MECHANICAL_SPEED);
23
} else {
24
wtemp = MAXIMUM_MECHANICAL_SPEED;
25
}
26
bError_counter++;
27
} else {
28
bError_counter = 0;
29
}
30
31
if (bError_counter >= MAXIMUM_ERROR_NUMBER) {
32
bError_Speed_Measurement = TRUE;
STM32 技术开发手册
www.ing10bbs.com
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 }
} else {
bError_Speed_Measurement = FALSE;
}
} else {
bError_Speed_Measurement = FALSE;
bError_counter = 0;
}
/* Compute the average of the read speeds */
hSpeed_Buffer[bSpeed_Buffer_Index] = (s16)wtemp;
bSpeed_Buffer_Index++;
if (bSpeed_Buffer_Index == SPEED_BUFFER_SIZE) {
bSpeed_Buffer_Index = 0;
}
wtemp=0;
for (i=0; i<SPEED_BUFFER_SIZE; i++) {
wtemp += hSpeed_Buffer[i];
}
wtemp /= SPEED_BUFFER_SIZE;
hRot_Speed = ((s16)(wtemp));
该函数实现机械频率平均值计算,使得机械频率值平滑效果。此函数每
SPEED_SAMPLING_FREQ 频率必须调用一次;它计算最新的测量速度,如果超出
MC_encoder_param.h 指定的速度范围,那么错误计数器增加,速度值饱和。此
外,如果错误计数器的值超过 MAXIMUM_ERROR_NUMBER,就会设置存储错误
状态布尔变量。最后,基于最新的 SPEED_BUFFER_SIZE 测量速度计算平均速度。
首先,调用 ENC_Calc_Rot_Speed 函数获取当前的机械频率保存于 wtemp 变
量中,hAbstemp 缓存 wtemp 的绝对值。如果当前处于电机运行状态,判断当前
机械频率小于最小许可机械频率(MINIMUM_MECHANICAL_SPEED)时,就把绝对
机械频率修改为最小机械频率,并累加错误次数 bError_counter 的值;判断当前
机械频率大于最大许可机械频率(MAXIMUM_MECHANICAL_SPEED),就把当前机
械频率修改为最大许可机械频率,并累加错误次数 bError_counter 的值;如果当
前机械频率在合适范围,就把错误次数值 bError_counter 清零。然后,检测如果
错误次数值 bError_counter 大于最大许可错误次数 MAXIMUM_ERROR_NUMBER
就指示速度测量出错,即把 bError_Speed_Measurement 标志位设置为 TRUE,否
则设置为 FALSE。
STM32 技术开发手册
www.ing10bbs.com
如果转子不是处于运动状态,直接把 bError_Speed_Measurement 标志位设
置为 FALSE,并且清零错误次数值 bError_counter。
接下来,把当前的机械频率保存到速度缓冲区中,并增加索引值,如果索引
值等于缓冲区空间大小,就把索引值设置为 0。接下来就是求和、取平均值处理
得到平均的转子机械频率。
代码 14-67 ENC_ErrorOnFeedback 函数
01 bool ENC_ErrorOnFeedback(void)
02 {
03
return (bError_Speed_Measurement);
04 }
该函数是反馈速度测量出错状态,当速度小于最小许可速度值或者大于最大
许可速度值时候都会累加错误计数器,当该错误计数器大于等于用户设定的最大
出错次数时就提示速度测量出错。
01 void ENC_Start_Up(void)
02 {
03
static u32 wTimebase=0;
04
05
if ( (wGlobal_Flags & FIRST_START) == FIRST_START) {
06
// First Motor start-up, alignment must be performed
07
wTimebase++;
08
if (wTimebase <= T_ALIGNMENT_PWM_STEPS) {
09
hFlux_Reference = I_ALIGNMENT * wTimebase / T_ALIGNMENT_PWM_STEPS;
10
hTorque_Reference = 0;
11
12
Stat_Curr_a_b = GET_PHASE_CURRENTS();
13
Stat_Curr_alfa_beta = Clarke(Stat_Curr_a_b);
14
Stat_Curr_q_d = Park(Stat_Curr_alfa_beta, ALIGNMENT_ANGLE_S16);
15
/*loads the Torque Regulator output reference voltage Vqs*/
16
Stat_Volt_q_d.qV_Component1 = PID_Regulator(hTorque_Reference,
17
Stat_Curr_q_d.qI_Component1, &PID_Torque_InitStructure);
18
19
/*loads the Flux Regulator output reference voltage Vds*/
20
Stat_Volt_q_d.qV_Component2 = PID_Regulator(hFlux_Reference,
21
Stat_Curr_q_d.qI_Component2, &PID_Flux_InitStructure);
22
23
RevPark_Circle_Limitation();
24
25
/*Performs the Reverse Park transformation,
26
i.e transforms stator voltages Vqs and Vds into Valpha and Vbeta on a
27
stationary reference frame*/
28
29
Stat_Volt_alfa_beta = Rev_Park(Stat_Volt_q_d);
30
31
/*Valpha and Vbeta finally drive the power stage*/
32
CALC_SVPWM(Stat_Volt_alfa_beta);
33
} else {
34
wTimebase = 0;
35
ENC_ResetEncoder();
36
Stat_Volt_q_d.qV_Component1 = Stat_Volt_q_d.qV_Component2 = 0;
37
hTorque_Reference = PID_TORQUE_REFERENCE;
STM32 技术开发手册
www.ing10bbs.com
38
hFlux_Reference = PID_FLUX_REFERENCE;
39
wGlobal_Flags &= ~FIRST_START;
// alignment done only once
40
//Clear the speed acquisition of the alignment phase
41
ENC_Clear_Speed_Buffer();
42 #ifdef ENCODER
43
State = RUN;
44 #endif
45
}
46
} else {
47 #ifdef ENCODER
48
State = RUN;
49 #endif
50
}
51 }
该函数是在调整永磁同步电机的相位时,调节转矩和定子磁通电流分量 Iq
和 Id。该功能也会在相位校准结束时更新全局状态(从开始到运行)。
首先,检测电机库全局状态标志位 wGlobal_Flags,确定是第一次启动,即上
电复位启动或者故障 事件之后,才需要进行校准操作。定义一个局部变量
wTimebase 来 记 录 相 位 校 准 时 间 , 该 时 间 是 每 个 电 流 采 样 周 期 加 1 ,
T_ALIGNMENT_PWM_STEPS 是我们用户定义的校准时间,表示在该时间内进行校
准,超过该时间后就不进行校准。进入相位校准,先计算励磁参考值,并把转矩
参考值设定为 0.
公式 14-59 励磁参考值 Id 计算
ℎ𝐹𝑙𝑢𝑥_𝑅𝑒𝑓𝑒𝑟𝑒𝑛𝑐𝑒 =
=
𝐼_𝐴𝐿𝐼𝐺𝑁𝑀𝐸𝑁𝑇 ∙ 𝑤𝑇𝑖𝑚𝑒𝑏𝑎𝑠𝑒
𝑇_𝐴𝐿𝐼𝐺𝑁𝑀𝐸𝑁𝑇_𝑃𝑊𝑀_𝑆𝑇𝐸𝑃𝑆
𝐼_𝐴𝐿𝐼𝐺𝑁𝑀𝐸𝑁𝑇 ∙ 𝑤𝑇𝑖𝑚𝑒𝑏𝑎𝑠𝑒
𝑇_𝐴𝐿𝐼𝐺𝑁𝑀𝐸𝑁𝑇 ∙ 𝑆𝐴𝑀𝑃𝐿𝐼𝑁𝐺_𝐹𝑅𝐸𝑄
1000
其中:
I_ALIGNMENT:相位校准参考 Id 幅值的最终值;
wTimebase:当前相位校准时间;
T_ALIGNMENT:设定的相位校准时间(单位为 ms);
SAMPLING_FREQ:电流采样频率(单位为 Hz);
常数 1000:校准时间单位换算比例。
运行 GET_PHASE_CURRENTS 函数获取当前定子相电流,然后进行 Clarke 变
换 、 Park 变 换 、 PID 运 算 得 到 旋 转 坐 标 系 定 子 电 压 𝑉𝑑 和 𝑉𝑞 , 然 后 执 行
RevPark_Circle_Limitation 函数进行进行反 Park 运算前限制圆计算,执行反 Park
STM32 技术开发手册
www.ing10bbs.com
运行得到𝑉𝛼 和𝑉𝛽 ,最终运行 CALC_SVPWM 函数在定时器通道引脚输出对应的
SVPWM 波形。
如果达到设定的校准时间,说明校准误差,把时间计数 wTimebase 清零,运
行 ENC_ResetEncoder 函数复位编码器,把𝑉𝑑 和𝑉𝑞 清零。把用户设定的励磁和转矩
参考值赋值给 hFlux_Reference 和 hTorque_Reference 用于实际运算。然后清除
wGlobal_Flags 中关于 FIRST_START 的标志位,运行 ENC_Clear_Speed_Buffer 函数
清除编码器缓冲区内容,把电机状态从开始切换到运行状态。
如果不是第一次启动,直接把全局状态设定为运行状态。
代码 14-68 TIM3_IRQHandler 函数
01 void TIM3_IRQHandler(void)
02 {
03
TIM_ClearFlag(ENCODER_TIMER, TIM_FLAG_Update);
04
if (hEncoder_Timer_Overflow != U16_MAX) {
05
hEncoder_Timer_Overflow++;
06
}
07
08 }
该函数是编码器接口定时器中断服务函数,这里直接贴出 TIM3 的,因为我
们例程默认使用 TIM3 作为编码器接口定时器,关于 TIM2 或 TIM4 的代码请自行
参考分析。
该函数实际上就只是累加编码器计数溢出变量,也是表明电机旋转一圈。
MC_State_Observer_Interface.c 文件内容
前面介绍的两个文件内容分别是通过霍尔传感器以及编码器获取电机转子
的实际位置,使用这两种方法获取到的转子位置信息是稳定可靠的,唯一缺点就
是成本和尺寸安装问题。所以人们就想方设法把这两个传感器去除掉,也就是直
接不使用传感器,在各位专家的探究下,有多种无传感器方式驱动无刷电机:端
电压检测法、续流二极管状态检测法、反电势过零点检测法、状态观测器法、电
感法、磁链法等,其中数“状态观测器法”在 FOC 控制中最常用。我们在前面
14.5 小节中有介绍了无传感器模式在 FOC 控制中的实现原理,当然前面介绍是
的通用的原理分析,程序实现就是根据原理内容的,但是,因为无传感器模式涉
及到的内部比较“高深”,所以很大公司在自己实现程序测试 OK 后都不愿意开
STM32 技术开发手册
www.ing10bbs.com
源他们的代码,所以造成现在也没有高质量的无传感器开源代码。当然,ST 公司
也不例外,电机库里边是有可以实现无传感器模式的代码,但是被他们封装为 lib
库文件,我们无处查询代码的具体实现方法,他们只预留了函数接口给用户调用,
所以接下来,我们对 MC_State_Observer_Interface.c 文件内容的分析也只是分析
ST 公司的无传感器模式库文件的相关接口函数的使用方法。
MC_State_Observer 模块是为永磁同步电动机设计的,用于观测反电动势状
态和锁相环(PLL)电路。它能够检测转子角位置和速度。
此外,该模块处理输出数据,通过这样做可以安全检测转子是否卡死或故障。
电机库基于状态观测理论,为无传感器检测转子位置/速度反馈提供了完整
的解决方案。此算法可用于 SM-PM 和 IPM 同步电动机。运行的转子磁通观察器
和经典第六评估程序之间的理论与实践对比已经指出观察器的优势,明显减少对
定子电阻的变化和参数变化整体稳固性的依赖。
在控制理论中一个状态观察器就是一个体系,一个提供了评估内部状态的实
时系统,并测量其输入和输出值。在电机库中,电机的内部状态包括反电动势和
相电流,而输入由相电压供给,输出由相电流压供给,见图 14-85。直流母线电
压测量接收电压命令,并向电机施加相电压
图 14-85 普通无传感器算法模块图
STM32 技术开发手册
www.ing10bbs.com
特别是,所观察到的状态通过相电流与实时系统一致性进行对比,其结果通
过增益向量(K1,K2)来调整模块。
电机反电动势定义如下:
e𝛼 = 𝛷𝑚 𝑝𝜔𝑟 cos(𝑝𝜔𝑟 𝑡)
e𝛽 = −𝛷𝑚 𝑝𝜔𝑟 sin(𝑝𝜔𝑟 𝑡)
可以看出,其中包含转子电角度。接着,反电动势送到作为锁相环 PLL 的模
块,这个模块能够重构转子电角度和速度。
图 14-86 锁相环 PLL 模块
增益向量(K1,K2)值对系统性能影响非常大,需要我们通过实验测试得到
合适的参数值,具体的操作可以参考《STM32F103_永磁同步电机_PMSM_FOC 软
件库_用户手册_中文版.pdf(附录 8)》文档中的附录内容:A.5 和 A.6 进行实验
测试。
代码 14-69 STO_StateObserverInterface_Init 函数
01 void STO_StateObserverInterface_Init(void)
02 {
03
StateObserver_Const StateObserver_ConstStruct;
04
05
StateObserver_ConstStruct.hC1 = C1;
06
StateObserver_ConstStruct.hC3 = C3;
07
StateObserver_ConstStruct.hC5 = C5;
08
09
{
10
s16 htempk;
11
StateObserver_ConstStruct.hF3 = 1;
12
htempk = (s16)((100*65536)/(F2*2*PI));
13
while (htempk != 0) {
14
htempk /=2;
15
StateObserver_ConstStruct.hF3 *=2;
STM32 技术开发手册
www.ing10bbs.com
16
}
17
StateObserver_ConstStruct.hC6 = (s16)((F2*StateObserver_ConstStruct.hF3*2*
18
PI)/65536);
19
}
20
21 #ifdef OBSERVER_GAIN_TUNING
22
/* lines below for debug porpose*/
23
StateObserver_ConstStruct.hC2 = C2;
24
StateObserver_ConstStruct.hC4 = C4;
25
26
StateObserver_ConstStruct.hC2 = (s16)((F1*wK1_LO)/SAMPLING_FREQ);
27
StateObserver_ConstStruct.hC4 = (s16)((((wK2_LO*MAX_CURRENT)/(MAX_BEMF_VOLTAGE
28
))*F2)/(SAMPLING_FREQ));
29
StateObserver_ConstStruct.PLL_P = hPLL_P_Gain;
30
StateObserver_ConstStruct.PLL_I = hPLL_I_Gain;
31 #else
32
StateObserver_ConstStruct.hC2 = C2;
33
StateObserver_ConstStruct.hC4 = C4;
34
StateObserver_ConstStruct.PLL_P = PLL_KP_GAIN;
35
StateObserver_ConstStruct.PLL_I = PLL_KI_GAIN;
36 #endif
37
StateObserver_ConstStruct.hF1 = F1;
38
StateObserver_ConstStruct.hF2 = F2;
39
StateObserver_ConstStruct.wMotorMaxSpeed_dpp = MOTOR_MAX_SPEED_DPP;
40
StateObserver_ConstStruct.hPercentageFactor = PERCENTAGE_FACTOR;
41
42
STO_Gains_Init(&StateObserver_ConstStruct);
43 }
该函数是状态观测器接口初始化,配置观测器相关计算参数,这些参数一般
与电机和驱动板硬件是有密切关系的。
函数首先定义一个 StateObserver_Const 类型(定义在 MC_type.h 文件中:
StateObserver_Const 是状态观测器常数,这在状态观测器中使用,这些成员值由
已确定的宏定义值计算而来,所以这里使用 const 表示。)局部变量,用于配置观
测器需要用到的常数变量,这里的 C1、C2、C3、C4、C5、F1、F2、PLL_KP_GAIN、
PLL_KI_GAIN 是在 MC_State_Observer_param.h 定义的,Cx 变量(x=1..5)是跟我们
电机和驱动板硬件密切相关的常数,用于观测器计算。F1、F2 是参数放大倍数,
都是 2 的 n 次方倍。K1 和 K2 是状态观测器矢量增益参数,PLL_KP_GAIN 是锁相
环相位检测器增益的“先验”确定。PLL_KI_GAIN 是锁相环环路滤波器增益的“先
验”确定。
MOTOR_MAX_SPEED_DPP 是 电 机 机 械 最 大 转 速 , 以 DPP 为 单 位 。
PERCENTAGE_FACTOR 是无传感器模式启动速度百分百阈值。
最后,调用 STO_Gains_Init 函数完成以上参数的配置。当然,STO_Gains_Init
函数是被封装在 lib 文件中,我们是没办法看到它实际的实现代码。
STM32 技术开发手册
www.ing10bbs.com
这 里 还 有 一 个 : OBSERVER_GAIN_TUNING 宏 定 义 , 该 宏 定 义 在
STM32F10x_MCconf.h 文件中决定是否定义,如果有定义该宏定义(即把注释去
掉)那么就是使能龙伯格(Luenberger)观测器和锁相环(PLL)增益,否则是使
用直接估算法。龙伯格(Luenberger)观测器实际上就是全状态观测器,即通过输
出 y 与输入 u 来实现对系统状态(包括可测状态与不可测状态)的观测,其具体实
现方式是通过配置 G 矩阵使得观测器的极点都具有负实部,这样观测器观测到的状
态与系统实际状态之差则可以在有限时间收敛到 0,从而保证观测器的稳定性与准
确性,更多具体内容可查教材《现代控制理论》
。
代码 14-70 STO_Check_Speed_Reliability 函数
01 bool STO_Check_Speed_Reliability(void)
02 {
03
static u8 bCounter=0;
04
bool baux;
05
06
if (STO_IsSpeed_Reliable() == FALSE) {
07
bCounter++;
08
if (bCounter >= RELIABILITY_HYSTERESYS) {
09
bCounter = 0;
10
baux = FALSE;
11
} else {
12
baux = TRUE;
13
}
14
} else {
15
bCounter = 0;
16
baux = TRUE;
17
}
18
return (baux);
19 }
该函数是无传感器模式下检测通过观测器获取到的速度的可靠性。如果不可
靠,可能需要停止电机旋转。函数中调用 STO_IsSpeed_Reliable 函数来获取速度
的可靠程度,STO_IsSpeed_Reliable 函数也是被封装在 lib 文件中,我们无处获取
它的源代码,它会指出无传感算法提供的信息是否可靠的。为了达到此目的,
STO_IsSpeed_Reliable 函数通过 STO_Calc_Speed 检查模块的私有变量。FALSE 状态
表明转子位置重建由于例如:检测或者 PLL 增益选择错误,或者转子被卡死而出
现故障。
如果 STO_IsSpeed_Reliable 函数返回值为 FALSE 说明速度是不可靠的,我们
就把 bCounter 变量加 1,这样每次检查到速度不可靠 bCounter 变量就每次加 1,
如果连续检查到有 RELIABILITY_HYSTERESYS(在 MC_State_Observer_param.h 文件
STM32 技术开发手册
www.ing10bbs.com
定义的一个常数)次速度不可靠,那么就判断获取的速度是不可靠的,
STO_Check_Speed_Reliability 函数就返回:FALSE。如果检查到速度是可靠,函数
就返回一个:TRUE。
STO_Check_Speed_Reliability 函数需要与采样的频率相同的频率调用。
代码 14-71 IsObserverConverged 函数
01 bool IsObserverConverged(void)
02 {
03
s16 hEstimatedSpeed;
04
s16 hUpperThreshold;
05
s16 hLowerThreshold;
06
07
hEstimatedSpeed = STO_Get_Speed_Hz();
08
hEstimatedSpeed = (hEstimatedSpeed < 0 ? -hEstimatedSpeed : hEstimatedSpeed);
09
hUpperThreshold = ((wStart_Up_Freq/65536) * 160)/(POLE_PAIR_NUM * 16);
10
hUpperThreshold = (hUpperThreshold < 0 ? -hUpperThreshold : hUpperThreshold);
11
hLowerThreshold = ((wStart_Up_Freq/65536) *150) / (POLE_PAIR_NUM * 16);
12
hLowerThreshold = (hLowerThreshold < 0 ? -hLowerThreshold : hLowerThreshold);
13
14
// If the variance of the estimated speed is low enough...
15
if (STO_IsSpeed_Reliable() == TRUE) {
16
if (hEstimatedSpeed > MINIMUM_SPEED) {
17
//...and the estimated value is quite close to the expected value...
18
if (hEstimatedSpeed >= hLowerThreshold) {
19
if (hEstimatedSpeed <= hUpperThreshold) {
20
bConvCounter++;
21
if (bConvCounter >= NB_CONSECUTIVE_TESTS) {
22
// ...the algorithm converged.
23
return (TRUE);
24
} else {
25
return (FALSE);
26
}
27
} else {
28
bConvCounter = 0;
29
return (FALSE);
30
}
31
} else {
32
bConvCounter = 0;
33
return (FALSE);
34
}
35
} else {
36
bConvCounter = 0;
37
return (FALSE);
38
}
39
} else {
40
bConvCounter = 0;
41
return (FALSE);
42
}
43 }
该函数用于检查算法收敛。检查速度可靠性和估计速度的值的范围。这里定
义三个局部变量用于计算,先调用 STO_Get_Speed_Hz 函数获取由观测器估算得
到 的 速 度 保 存 在 hEstimatedSpeed 变 量 ( 速 度 单 位 为 HZ ) 中 , 接 下 来 对
STM32 技术开发手册
www.ing10bbs.com
hEstimatedSpeed 变量求绝对值。接下来算出当前启动频率 wStart_Up_Freq 下对
应的最大速度和最小速度阈值,分别保存在 hUpperThreshold 和 hLowerThreshold
变量中,并且都是求出对应的绝对值。
接下来调用 STO_IsSpeed_Reliable 函数判断无传感算法提供的信息是否可靠,
如果是不可靠的,就直接返回 FALSE。在速度信号可靠情况下,对 hEstimatedSpeed
变量与 MINIMUM_SPEED、hLowerThreshold 和 hUpperThreshold 这三个值进行比
较,保证由观测器获取到的速度值在合适的范围之内,如果连续判断
NB_CONSECUTIVE_TESTS 次速度都是在合适的范围内,那就返回 TRUE。
代码 14-72 无感模式速度和电角度获取
01 s16 STO_Get_Speed_Hz(void)
02 {
03
return (s16)((STO_Get_Speed()* SAMPLING_FREQ * 10)/(65536*POLE_PAIR_NUM));
04 }
05
06 s16 STO_Get_Mechanical_Angle(void)
07 {
08
return ((s16)(STO_Get_Electrical_Angle()/POLE_PAIR_NUM));
09 }
STO_Get_Speed_Hz 函数用于返回电机机械速度,并且是以 10*Hz 为单位,
它是通过 STO_Get_Speed 函数获取转子电速度,然后进行单位转换得到以 10*Hz
为单位的速度值。STO_Get_Speed 函数返回转子电速度,作为平均算出的缓存里
的检测速度累积的 STO_Calc_Speed 值。
STO_Get_Mechanical_Angle 函 数 返 回 转 子 机 械 角 度 ( s16 格 式 ), 调 用
STO_Get_Electrical_Angle 函数获取转子电角度,然后除以极对数得到转子的机械
角度。
这里先介绍下一般的无传感器模式启动方法:三段式启动。
由于定子绕组的反电动势与电机的转速成正比,所以电机在静止时反电动势
为零或低速时反电动势很小,此时无法根据反电动势信号确定转子磁极的位置,
因此反电动势法需要采用特殊起动技术,从静止开始加速,直至转速足够大,通
过反电势能检测到过零时,再切换至无刷直流电机运行状态。这个过程称为“三
段式”起动,主要包括转子预定位、加速和运行状态切换三个阶段。这样既可以
使电机转向可控,又可以保证电机达到一定转速后再进行切换,保证了起动的可
靠性。
STM32 技术开发手册
www.ing10bbs.com
电机转子预定位
若要保证无刷直流电机能够正常起动,首先要确定转子在静止时的位置。在
小型轻载条件下,对于具有梯形反电势波形的无刷直流电机来说,一般采用磁制
动转子定位方式。系统起动时,任意给定一组触发脉冲,在气隙中形成一个幅值
恒定、方向不变的磁通,只要保证其幅值足够大,那么这一磁通就能在一定时间
内将电机转子强行定位这个方向上。在应用中,可以在任意一组绕组上通电一定
时间,其中预定位的 PWM 占空比和预定位时间的长短设定值可由具体电机特性
和负载决定,在实际应用中调试而得。在预定位成功后,转子在起动前可达到预
定的位置,为电机起动做好准备。
电机的外同步加速
确定了电机转子的初始位置后,由于此时定子绕组中的反电动势仍为零,所
以必须人为的改变电机的外施电压和换相信号,使电机由静止逐步加速运动,这
一过程称为外同步加速。对于不同的外施电压调整方法和换相信号调整方法,外
同步加速可以划分为三类:换相信号频率不变,逐步增大外施电压使电机加速,
称为恒频升压法。保持外施电压不变,逐渐增高换相信号的频率,使电机逐步加
速,称为恒压升频法。在逐步增大外施电压的同时,增高换相的频率,称为升频
升压法。
电机运行状态的转换
各个方法都有其优点和缺点。如升频升压法是人为地给电机施加一个由低频
到高频不断加速的可控同步切换信号,而且电压也在不断地增加。通过调整电机
换相频率,即可调整电机起动的速度。调整方法比较简单。但是这个过程比较难
实现,切换信号的频率的选择要根据电机的极对数和其它参数来确定,太低电机
无法加速,太高电机转速达不到会有噪声甚至无法启动,算法比较困难。无论哪
种方法,该过程都是在未检测到反电动势信号时进行,因此对于控制系统来说,
此段电机控制是盲区。参数在调试好的时候,可以快速切换至正常运行状态;而
参数不理想时,电流可能不稳甚至电机会抖动。因此,在应用中,应根据电机及
负载特性设定合理的升速曲线,并在尽可能短的时间内完成切换。
STM32 技术开发手册
www.ing10bbs.com
这一步是关键也是比较难实现的一步,有时软件或者硬件设计的不合理都可
能导致起动失败。通常是采用估算的方式来选择切换速度。
通过上面的分析可知,无位置传感器无刷直流电机控制系统的难点就是加速
及切换阶段,当电机顺利起动后,就可以对电机调速操作。其中,无位置传感器
无刷直流电机和有位置传感器电机调速原理一致。但,由于无感三段式起动过程,
转子位置检测无效,因此,对电机进行的速度 PID 闭环控制,需在电机起动顺利
完成后进行。
代码 14-73 STO_Start_Up 函数
01 void STO_Start_Up(void)
02 {
03
s16 hAux;
04 #ifdef NO_SPEED_SENSORS_ALIGNMENT
05
static u32 wAlignmentTbase=0;
06 #endif
07
08
switch (Start_Up_State) {
09
case S_INIT:
10
//Init Ramp-up variables
11
if (hSpeed_Reference >= 0) {
12
hFreq_Inc = FREQ_INC;
13
hI_Inc = I_INC;
14
if (wTime == 0) {
15
wStart_Up_I = FIRST_I_STARTUP *1024;
16
}
17
} else {
18
hFreq_Inc = -(s16)FREQ_INC;
19
hI_Inc = -(s16)I_INC;
20
if (wTime == 0) {
21
wStart_Up_I = -(s32)FIRST_I_STARTUP *1024;
22
}
23
}
24
Start_Up_State = ALIGNMENT;
25
break;
26
27
case ALIGNMENT:
28 #ifdef NO_SPEED_SENSORS_ALIGNMENT
29
wAlignmentTbase++;
30
if (wAlignmentTbase <= SLESS_T_ALIGNMENT_PWM_STEPS) {
31
hFlux_Reference = SLESS_I_ALIGNMENT * wAlignmentTbase /
32
SLESS_T_ALIGNMENT_PWM_STEPS;
33
hTorque_Reference = 0;
34
35
Stat_Curr_a_b = GET_PHASE_CURRENTS();
36
Stat_Curr_alfa_beta = Clarke(Stat_Curr_a_b);
37
Stat_Curr_q_d = Park(Stat_Curr_alfa_beta, SLESS_ALIGNMENT_ANGLE_S16);
38
/*loads the Torque Regulator output reference voltage Vqs*/
39
Stat_Volt_q_d.qV_Component1 = PID_Regulator(hTorque_Reference,
40
Stat_Curr_q_d.qI_Component1, &PID_Torque_InitStructure);
41
/*loads the Flux Regulator output reference voltage Vds*/
42
Stat_Volt_q_d.qV_Component2 = PID_Regulator(hFlux_Reference,
43
Stat_Curr_q_d.qI_Component2, &PID_Flux_InitStructure);
44
STM32 技术开发手册
www.ing10bbs.com
45
RevPark_Circle_Limitation();
46
47
/*Performs the Reverse Park transformation,
48
i.e transforms stator voltages Vqs and Vds into Valpha and Vbeta on a
49
stationary reference frame*/
50
51
Stat_Volt_alfa_beta = Rev_Park(Stat_Volt_q_d);
52
53
/*Valpha and Vbeta finally drive the power stage*/
54
CALC_SVPWM(Stat_Volt_alfa_beta);
55
} else {
56
wAlignmentTbase = 0;
57
Stat_Volt_q_d.qV_Component1 = Stat_Volt_q_d.qV_Component2 = 0;
58
hTorque_Reference = PID_TORQUE_REFERENCE;
59
hFlux_Reference = PID_FLUX_REFERENCE;
60
Start_Up_State = RAMP_UP;
61
hAngle = SLESS_ALIGNMENT_ANGLE_S16;
62
}
63 #else
64
Start_Up_State = RAMP_UP;
65 #endif
66
break;
67
68
case RAMP_UP:
69
wTime ++;
70
if (wTime <= I_STARTUP_PWM_STEPS) {
71
wStart_Up_Freq += hFreq_Inc;
72
wStart_Up_I += hI_Inc;
73
} else if (wTime <= FREQ_STARTUP_PWM_STEPS ) {
74
wStart_Up_Freq += hFreq_Inc;
75
} else {
76
MCL_SetFault(START_UP_FAILURE);
77
//Re_initialize Start Up
78
STO_StartUp_Init();
79
}
80
81
//Add angle increment for ramp-up
82
hAux = wStart_Up_Freq/65536;
83
hAngle = (s16)(hAngle + (s32)(65536/(SAMPLING_FREQ/hAux)));
84
85
Stat_Curr_a_b = GET_PHASE_CURRENTS();
86
Stat_Curr_alfa_beta = Clarke(Stat_Curr_a_b);
87
Stat_Curr_q_d = Park(Stat_Curr_alfa_beta, hAngle);
88
89
hAux = wStart_Up_I/1024;
90
hTorque_Reference = hAux;
91
hFlux_Reference = 0;
92
93
/*loads the Torque Regulator output reference voltage Vqs*/
94
Stat_Volt_q_d.qV_Component1 = PID_Regulator(hTorque_Reference,
95
Stat_Curr_q_d.qI_Component1, &PID_Torque_InitStructure);
96
/*loads the Flux Regulator output reference voltage Vds*/
97
Stat_Volt_q_d.qV_Component2 = PID_Regulator(hFlux_Reference,
98
Stat_Curr_q_d.qI_Component2, &PID_Flux_InitStructure);
99
100
RevPark_Circle_Limitation();
101
102
/*Performs the Reverse Park transformation,
103
i.e transforms stator voltages Vqs and Vds into Valpha and Vbeta on a
104
stationary reference frame*/
105
106
Stat_Volt_alfa_beta = Rev_Park(Stat_Volt_q_d);
107
STM32 技术开发手册
www.ing10bbs.com
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126 }
/*Valpha and Vbeta finally drive the power stage*/
CALC_SVPWM(Stat_Volt_alfa_beta);
STO_Calc_Rotor_Angle(Stat_Volt_alfa_beta,Stat_Curr_alfa_beta,MCL_Get_BusVolt());
if (IsObserverConverged()==TRUE) {
PID_Speed_InitStructure.wIntegral = (s32)(hTorque_Reference*256);
STO_StartUp_Init();
State = RUN;
if ((wGlobal_Flags & SPEED_CONTROL) != SPEED_CONTROL) {
hTorque_Reference = PID_TORQUE_REFERENCE;
hFlux_Reference = PID_FLUX_REFERENCE;
}
}
break;
default:
break;
}
该函数是无传感器模式启动函数,在 AD 中断服务函数里边被调用,这样可
以在每个采样周期就运行一次启动函数。
NO_SPEED_SENSORS_ALIGNMENT 宏定义是在 STM32F10x_MCconf.h 文件中定
义的,如果我们把注释去掉,说明我们使能无传感器模式的启动对准,实际上就
是我们说的启动三段式里边的电机转子预定位。
STO_Start_Up 函数中通过 switch 语句把多个启动过程分开处理:初始化
(S_INIT)、对准(ALIGNMENT)、斜线加速(RAMP_UP)这三个阶段。
首 先 是 初 始 化 阶 段 , 判 断 hSpeed_Reference 变 量 值 的 正 负 极 性 ,
hSpeed_Reference 是目标速度参考值,正负极性代表电机方向,正数为顺时针旋
转,负数为逆时针旋转。因为在三段式的“电机的外同步加速”需要对电机进行
加速运行,所以在初始化阶段我们定义好加速相关参数:hFreq_Inc 为频率增量,
hI_Inc 为电流增量。如果是开机第一次无传感器启动,即 wTime 变量值为 0,还
需要配置开始启动电流。然后就可以进入第二阶段:对准。
在对准阶段,如果我们没有定义 NO_SPEED_SENSORS_ALIGNMENT 宏,即不
需要进行电机对准,那么直接设置进入斜线加速阶段。如果使能了转子对准,
wAlignmentTbase 变量自加 1,用了记录对准时间,当该变量值增加到大于
SLESS_T_ALIGNMENT_PWM_STEPS 时候说明对准完成。在对准阶段,把参考励磁
hFlux_Reference 值设置为与目标对准电流相关值,参考转矩 hTorque_Reference
设置为 0。然后就是一系列的 FOC 控制:获取电流、Clarke 变换、Park 变换、PID
STM32 技术开发手册
www.ing10bbs.com
算法得到 vq 和 vd、反 Park 变换限制圆处理、反 Park 变换得到𝑣𝛼 和𝑣𝛽 ,最后由
CALC_SVPWM 函数输出 SVPWM,实际上这上面过程我们在前面内容都有做了详
细分析,所以直接就不再啰嗦。如果对准时间已经完成,即 wAlignmentTbase 大
于 SLESS_T_ALIGNMENT_PWM_STEPS 的值,那么把相关变量值清零,把参考转矩
还原设置为目标转矩 PID_TORQUE_REFERENCE,把参考励磁还原设置为目标励磁
PID_FLUX_REFERENCE,把角度设置为对准角度,然后就是进入斜线加速阶段。
在斜线加速阶段,我们把 wTime 变量自加 1,标识启动时间,当 wTime 变
量值小于 I_STARTUP_PWM_STEPS 时候,每次都把启动频率 wStart_Up_Freq 和启
动电流 wStart_Up_I 都增加对应一个增量值。如果 wTime 变量自加到大于
I_STARTUP_PWM_STEPS 值但是小于 FREQ_STARTUP_PWM_STEPS 值,说明电流已
经 到 设 置 目 标 值 , 那 么 就 只 需 要 增 大 励 磁 频 率 , 如 果 wTime 变 量 比
FREQ_STARTUP_PWM_STEPS 值还大,说明电流和励磁频率都增加到目标最大值,
但是电机还处于启动阶段,说明电机启动失败,调用 MCL_SetFault 函数反馈无感
模式启动失败,STO_StartUp_Init 函数把相关无感启动参数反初始化,实际上在
无感启动阶段应该是会自动切换到闭环状态,标识无感启动成功,如果在频率加
速到目标频率时都还没切入到闭环状态,说明无感启动失败。接下来就是把电流
和励磁频率真正在定子线圈中执行起来,由 wStart_Up_Freq 变量算出 hAngle 角
度值,由 wStart_Up_I 算出参考转矩,然后还是执行一系列的 FOC 控制:获取电
流、Clarke 变换、Park 变换、PID 算法得到 vq 和 vd、反 Park 变换限制圆处理、反
Park 变换得到𝑣𝛼 和𝑣𝛽 ,最后由 CALC_SVPWM 函数输出 SVPWM。
接下来调用 STO_Calc_Rotor_Angle 函数计算转子角度,该函数是状态传感器
的核心,实施检测状态,该函数调用频率与定子电流采样频率一致,一般是由 FOC
程 序 内 部 调 用 。 STO_Calc_Rotor_Angle 函 数 获 取 测 量 的 定 子 电 流
(Stat_Curr_alfa_beta)
,施加的电压命令(Stat_Volt_alfa_beta)和测量的直流母
线电压(hBusVoltage)当作第 K 步的输入;得出离散状态检测方程的第 K+1 步,从
而实现电机反电动式的计算(𝑒𝛼 和𝑒𝛽 )。从而通过一个数字锁相环的方法,根据
STM32 技术开发手册
www.ing10bbs.com
反电动势计算转子速度和角度。检测到的反电动势、转子角度和速度被写入单元
私有变量中。
然后调用 IsObserverConverged 函数检查速度可靠性和估计速度的值的范围。
在确定状态观测器的速度是收敛,说明无感模式启动成功,执行 if 语句里边的内
容:重新修改 PID 速度环的积分参数值,调用 STO_StartUp_Init 函数把无感启动
参数初始化,把电机状态设置为运行状态(启动成功后不需要再运行
STO_Start_Up 函数),如果不是速度模式,把电流环目标值作为参考转矩和参考
励磁值。
代码 14-74 STO_StartUp_Init 函数
01 void STO_StartUp_Init(void)
02 {
03
//Re_initialize Start Up
04
Start_Up_State = S_INIT;
05
hAngle = 0;
06
wTime = 0;
07
wStart_Up_Freq = 0;
08
bConvCounter = 0;
09 }
该函数是无感模式启动初始化,把相关变量设置为初始化状态。
代码 14-75 STO_Obs_Gains_Update 函数
01 #ifdef OBSERVER_GAIN_TUNING
02 void STO_Obs_Gains_Update(void)
03 {
04
StateObserver_GainsUpdate STO_GainsUpdateStruct;
05
06
STO_GainsUpdateStruct.PLL_P = hPLL_P_Gain;
07
STO_GainsUpdateStruct.PLL_I = hPLL_I_Gain;
08
STO_GainsUpdateStruct.hC2 = HC2_INIT;
09
STO_GainsUpdateStruct.hC4 = HC4_INIT;
10
STO_Gains_Update(&STO_GainsUpdateStruct);
11 }
12 #endif
该函数只有在定义了 OBSERVER_GAIN_TUNING 宏才有效,这个函数的目的
是修改 STO_StateObserverInterface_Init 预先设置的状态检测和 PLL 的增益。函数
中先定义一个 StateObserver_GainsUpdate 结构体变量,主要修改的就是 PLL 的增
益和 C2、C4 参数值,最后调用 STO_Gains_Update 函数实现数据更新。
MC_State_Observer 模块(即 MC_State_Observer_lib.lib 文件)还提供了其他
接口函数,具体可以参考《STM32F103_永磁同步电机_PMSM_FOC 软件库_用户
手册_中文版.pdf(附录 8)》了解。
STM32 技术开发手册
www.ing10bbs.com
MC_MotorControl_Layer.c 文件内容
该文件是与电机 FOC 控制相关函数,存放了给予用户应用程序直接调用的
相关函数。
代码 14-76 MCL_Init 函数
01 void MCL_Init(void)
02 {
03 // reset PID's integral values
04
MCL_Reset_PID_IntegralTerms();
05
FOC_Init();
06
07 #ifdef ENCODER
08
ENC_Clear_Speed_Buffer();
09 #ifdef OBSERVER_GAIN_TUNING
10
STO_Init();
11 #endif
12 #elif defined HALL_SENSORS
13
HALL_InitHallMeasure();
14
HALL_Init_Electrical_Angle();
15 #ifdef OBSERVER_GAIN_TUNING
16
STO_Init();
17 #endif
18 #elif defined NO_SPEED_SENSORS
19
STO_Init();
20 #ifdef VIEW_ENCODER_FEEDBACK
21
ENC_Clear_Speed_Buffer();
22 #elif defined VIEW_HALL_FEEDBACK
23
HALL_InitHallMeasure();
24
HALL_Init_Electrical_Angle();
25 #endif
26 #endif
27
28 #ifdef THREE_SHUNT
29
SVPWM_3ShuntCurrentReadingCalibration();
30 #elif defined ICS_SENSORS
31
SVPWM_IcsCurrentReadingCalibration();
32 #elif defined SINGLE_SHUNT
33
SVPWM_1ShuntCurrentReadingCalibration();
34 #endif
35
36
Stat_Volt_alfa_beta.qV_Component1 = 0;
37
Stat_Volt_alfa_beta.qV_Component2 = 0;
38
CALC_SVPWM(Stat_Volt_alfa_beta);
39
hTorque_Reference = PID_TORQUE_REFERENCE;
40
41
//It generates for 2 msec a 50% duty cycle on the three phases to load Boot
42
//capacitance of high side drivers
43
TB_Set_StartUp_Timeout(4);
44
45
/* Main PWM Output Enable */
46
TIM_CtrlPWMOutputs(TIM1,ENABLE);
47
48
while (!TB_StartUp_Timeout_IsElapsed()) {
49
}
50 #ifdef THREE_SHUNT
51
// Enable the Adv Current Reading during Run state
52
SVPWM_3ShuntAdvCurrentReading(ENABLE);
53 #endif
STM32 技术开发手册
www.ing10bbs.com
54 #ifdef SINGLE_SHUNT
55
// Enable the Adv Current Reading during Run state
56
SVPWM_1ShuntAdvCurrentReading(ENABLE);
57 #endif
58 }
该函数在每次电机启动前初始化电机控制;函数影响 PID 调节器、电流读取
刻度,速度传感器和高边驱动引导电容初始化。
MCL_Reset_PID_IntegralTerms 函数复位 PID 速度环和电流环的积分参数,
FOC_Init 函数初始化 FOC 算法环境,在 MC_FOC_Drive.c 文件中定义,该函数初
始化所有与 FOC 算法有关的变量值。
如果定义了 ENCODER 宏(使用编码器),调用 ENC_Clear_Speed_Buffer 函数
清除编码器的速度数值缓冲器,如果还可以了 OBSERVER_GAIN_TUNING 宏,调用
STO_Init 函数时候无传感器的状态观测器,STO_Init 函数将所有有关状态检测的
变量初始化为合适的值。每次电机启动前都调用一次。
如果是定义了 HALL_SENSORS(使用霍尔传感器),调用 HALL_InitHallMeasure
函数用于初始化霍尔信号测量,在启动电机前调用,初始化速度测量过程。主要
是在最新数据存入之前清除软件的 FIFO 缓存区数据。HALL_Init_Electrical_Angle
函数初始化霍尔传感器模式的电角度。如果还可以了 OBSERVER_GAIN_TUNING 宏,
调用 STO_Init 函数时候无传感器的状态观测器。
如果是定义 NO_SPEED_SENSORS 宏,调用 STO_Init 函数时候无传感器的状态
观测器。在无感模式下,如果还选择了编码器或者霍尔传感器做辅助功能,就还
需要调用相关函数完成传感器接口初始化。
我 们 使 用 三 电 阻 的 电 流 采 样 方 案 , 调 用
SVPWM_3ShuntCurrentReadingCalibration 函数进行零点状态校准。把𝑣𝛼 和𝑣𝛽 变量
值直接初始化为 0,调用 CALC_SVPWM 函数输出初始化状态的 SVPWM 波,同时
把目标转矩赋值给参考转矩变量。这时如果我们使能定时器 PWM 输出功能,它
是可以在通道引脚上输出脉冲的。
TB_Set_StartUp_Timeout 函数用于在三相上产生 4 毫秒的 50%占空比,以加
载高边驱动器的引导电容。该函数定义在 stm32f10x_Timebase.c 文件中,它实际
设置了一个由形参设定的超时功能,超时计数时间单位为 1ms,这里赋值为 4 就
是延时 4ms。调用 TIM_CtrlPWMOutputs 函数使能定时器输出脉冲。然后就在 while
STM32 技术开发手册
www.ing10bbs.com
循环的条件判断中查询 TB_StartUp_Timeout_IsElapsed 函数的结果,总是等待该
函 数 的 结 果 值 为 正 。 TB_StartUp_Timeout_IsElapsed 函 数 是 与 上 面
TB_Set_StartUp_Timeout 函数配置使用的,由 TB_Set_StartUp_Timeout 来配置超
时时间,然后再由 TB_StartUp_Timeout_IsElapsed 函数来查询是否超时,判断到
超时时说明延时时间已经完成。
最后,调用 SVPWM_3ShuntAdvCurrentReading 函数使能三电阻采样模式下的
平均值电流读取,该函数定义在 STM32x_svpwm_3shunt.c 文件中,实际上就是使
能 AD 转换。
代码 14-77 MCL_Init_Arrays 函数
01 void MCL_Init_Arrays(void)
02 {
03
w_Temp_Average = TEMP_ARRAY_INIT;
04
h_BusV_Average = VOLT_ARRAY_INIT;
05 }
该函数初始化阵列,以避免复位后发生错误检测。这里把温度和电压电压值
设置为初始化值。
代码 14-78 MCL_ChkPowerStage 函数
01 void MCL_ChkPowerStage(void)
02 {
03
// check over temperature of power stage
04
if (MCL_Chk_OverTemp() == TRUE) {
05
MCL_SetFault(OVERHEAT);
06
}
07
// check bus under voltage
08
if (MCL_Chk_BusVolt() == UNDER_VOLT) {
09
MCL_SetFault(UNDER_VOLTAGE);
10
}
11
// bus over voltage is detected by analog watchdog
12 }
该函数检测环境是否过温和检查供电电源工作状态。调用
MCL_Chk_OverTemp 函数检测温度是否过高,如果温度过高运行 MCL_SetFault 函
数标记高温警报。调用 MCL_Chk_BusVolt 函数检测供电电源电压是否过低,如果
电压过低运行 MCL_SetFault 函数标记低压保护,这里需要强调:出现电压过低情
况一般是电源短路情况!!!这种情况是非常危险的。出现电源短路情况一般就是
同个半桥的 MOS 管同时导通,这个很大可能是程序控制问题,当然也是可能是
硬件设计错误。对于电源电压过高保护是使用了 ADC 的看门狗功能,因为电压
过高一般是因为电机反电动势变得很大造成的。
STM32 技术开发手册
www.ing10bbs.com
代码 14-79 MCL_SetFault 函数
01 void MCL_SetFault(u16 hFault_type)
02 {
03
TB_Set_Delay_500us(FAULT_STATE_MIN_PERMANENCY);
04
/* Main PWM Output Enable */
05
TIM_CtrlPWMOutputs(TIM1, DISABLE);
06
wGlobal_Flags |= hFault_type;
07
State = FAULT;
08
bMenu_index = FAULT_MENU;
09
// It is required to disable AdvCurrentReading in IDLE to sample DC
10
// Bus Value
11 #ifdef THREE_SHUNT
12
SVPWM_3ShuntAdvCurrentReading(DISABLE);
13 #endif
14 #ifdef SINGLE_SHUNT
15
SVPWM_1ShuntAdvCurrentReading(DISABLE);
16 #endif
17 }
该函数管理故障发生。发生错误事件,函数就会将电机主状态切换到 Fault
状态,并禁用定时器的电机控制输出。TB_Set_Delay_500us 函数用于设置一个以
0.5ms 为单位的超时时间。这里设置一个 FAULT_STATE_MIN_PERMANENCY(默认
值为 600,对应 300ms)的超时时间。调用 TIM_CtrlPWMOutputs 函数禁止 PWM
输 出 , 标 记 故 障 状 态 , 液 晶 也 弹 出 错 误 警 报 信 息 。 运 行
SVPWM_3ShuntAdvCurrentReading 函数关闭三电阻电流采样。
代码 14-80 MCL_ClearFault 函数
01 bool MCL_ClearFault(void)
02 {
03
if (TB_Delay_IsElapsed()) {
04
if ((wGlobal_Flags & OVERHEAT) == OVERHEAT) {
05
if (MCL_Chk_OverTemp()== FALSE) {
06
wGlobal_Flags &= ~OVERHEAT;
07
}
08
}
09
10
if ((wGlobal_Flags & OVER_VOLTAGE) == OVER_VOLTAGE) {
11
if (MCL_Chk_BusVolt()== NO_FAULT) {
12
wGlobal_Flags &= ~OVER_VOLTAGE;
13
}
14
}
15
16
if ((wGlobal_Flags & UNDER_VOLTAGE) == UNDER_VOLTAGE) {
17
if (MCL_Chk_BusVolt()== NO_FAULT) {
18
wGlobal_Flags &= ~UNDER_VOLTAGE;
19
}
20
}
21
22
if ((wGlobal_Flags & OVER_CURRENT) == OVER_CURRENT) {
23
// high level detected on emergency pin?
24
//It checks for a low level on MCES before re-enable PWM
25
//peripheral
26
if (GPIO_ReadInputDataBit(BRK_GPIO, BRK_PIN)) {
STM32 技术开发手册
www.ing10bbs.com
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 }
wGlobal_Flags &= ~OVER_CURRENT;
}
}
if ((wGlobal_Flags & START_UP_FAILURE) == START_UP_FAILURE ) {
wGlobal_Flags &= ~START_UP_FAILURE;
}
if ((wGlobal_Flags & SPEED_FEEDBACK) == SPEED_FEEDBACK ) {
wGlobal_Flags &= ~SPEED_FEEDBACK;
}
}
if (KEYS_ExportbKey() == SEL) {
if ( (wGlobal_Flags & (OVER_CURRENT | OVERHEAT | UNDER_VOLTAGE |
SPEED_FEEDBACK | START_UP_FAILURE | OVER_VOLTAGE)) == 0 ) {
return (TRUE);
} else {
return (FALSE);
}
} else {
return (FALSE);
}
该函数检测产生错误事件是否已经结束。在解除保护后,如果已经按下‘Sel’
键,就会清除相关标志,返回 TRUE 值。否则返回 FALSE 值。
TB_Delay_IsElapsed 函数用于检查超时延时是否接受,它跟 MCL_SetFault 函
数中运行的 TB_Set_Delay_500us 函数配合使用,在确保错误超时 300ms 之后才
能解除错误。如果是发生高温保护,调用 MCL_Chk_OverTemp 检查当前温度是否
下降以期解除高温报警。如果是电源过压和欠压保护,调用 MCL_Chk_BusVolt 函
数检测电压是否在合适范围内,以期解除过压或欠压保护。如果是过流保护,读
取紧急引脚的电平(该引脚是 ST 官方测试版才有,我们硬石驱动板没有设计该
引脚)
,只有当该引脚为高电平才能解除过流保护。如果是启动失败和速度反馈
出错,直接清楚错误标志。
只有我们解除了相关错误之后,然后再按下“Sel”按键 MCL_ClearFault 函数
才能反馈 TRUE 标识错误真正解除,否则返回 FALSE。
代码 14-81 MCL_Chk_OverTemp 函数
01 bool MCL_Chk_OverTemp(void)
02 {
03
bool bStatus;
04
05
w_Temp_Average = ((T_AV_ARRAY_SIZE-1)*w_Temp_Average + h_ADCTemp)
06
/T_AV_ARRAY_SIZE;
07
08
if (w_Temp_Average >= NTC_THRESHOLD) {
09
bStatus = TRUE;
STM32 技术开发手册
www.ing10bbs.com
10
11
12
13
14
15
16
17
18
19
20
21 }
} else if (w_Temp_Average >= (NTC_HYSTERIS) ) {
if ((wGlobal_Flags & OVERHEAT) == OVERHEAT) {
bStatus = TRUE;
} else {
bStatus = FALSE;
}
} else {
bStatus = FALSE;
}
return (bStatus);
该函数是检测是否高温,判断到温度大于设定允许温度返回 TRUE,没有高
温 报 警 才 返 回 FALSE 。 函 数 首 先 是 计 算 温 度 的 平 均 值 w_Temp_Average ,
T_AV_ARRAY_SIZE 是温度数值缓冲器大小,h_ADCTemp 是最新一次采集到的温度
值。把温度平均值 w_Temp_Average 与最高温度阈值 NTC_THRESHOLD 比较,如
果当前平均温度大于设定的最高温度,说明温度过高,直接返回 TRUE。如果当
前温度小于最高温度阈值那就判断是否大于温度滞后值,温度滞后值的作用是体
现在发生过温保护后,限定把温度降低到小于该温度滞后值才能解除警报,这样
说明温度已经降低到一个比较低水平。当前温度大于温度滞后值时候,需要判断
当前是否处于过温警报状态,如果是处于过温警报状态,就标识当时也是处于过
温警报状态,不解除过温警报;如果不处于过温警报状态就可以直接返回 FALSE。
代码 14-82 电源电压检测及处理相关函数
01 void MCL_Calc_BusVolt(void)
02 {
03
h_BusV_Average = ((BUS_AV_ARRAY_SIZE-1)*h_BusV_Average + h_ADCBusvolt)
04
/BUS_AV_ARRAY_SIZE;
05 }
06
07 BusV_t MCL_Chk_BusVolt(void)
08 {
09
BusV_t baux;
10
if (h_BusV_Average > OVERVOLTAGE_THRESHOLD) {
11
baux = OVER_VOLT;
12
} else if (h_BusV_Average < UNDERVOLTAGE_THRESHOLD) {
13
baux = UNDER_VOLT;
14
} else {
15
baux = NO_FAULT;
16
}
17
return ((BusV_t)baux);
18 }
19
20 s16 MCL_Get_BusVolt(void)
21 {
22
return (h_BusV_Average);
23 }
24
25 u16 MCL_Compute_BusVolt(void)
STM32 技术开发手册
www.ing10bbs.com
26 {
27
28 }
return ((u16)((h_BusV_Average * BUSV_CONVERSION)/32768));
这 4 个函数与供电电源(也称总线)电压相关。
MCL_Calc_BusVolt 函 数 计 算 当 前 电 源 平 均 电 压 值 h_BusV_Average ,
BUS_AV_ARRAY_SIZE 是电压采集数据缓冲器的大小,h_ADCBusvolt 是当前 AD 采
集后计算得到的最新电压值。该函数实际上就是个求平均的实现。
MCL_Chk_BusVolt 函数检测是否过压或者欠压情况。函数中把电源平均电压
值 h_BusV_Average 与 OVERVOLTAGE_THRESHOLD 和 UNDERVOLTAGE_THRESHOLD
值分别进行比较,判断是否发生过压或者欠压情况。
MCL_Get_BusVolt 函数获取平均电压值 h_BusV_Average。
MCL_Compute_BusVolt 函数计算当前电源电压值,就是实现 AD 数据向电压
值转换的过程。参考之前的电路设计,我们使用分压电阻+AD 采样方法获取电源
电压值,这里用到 BUSV_CONVERSION 转换比例,该参数是与我们硬件直接相关,
通过分压电阻方程可以方便算出。
代码 14-83 MCL_Compute_Temp 函数
01 u8 MCL_Compute_Temp(void)
02 {
03
u32 temp=w_Temp_Average * TEMP_CONVERSION/32768;
04
05
return ((u8)temp);
06 }
该函数计算得到以摄氏度为单位的温度值。函数中把 w_Temp_Average 值经
过比例运算得到摄氏度单位的温度值,其中 TEMP_CONVERSION 就是我们用到的
转换比例系数。实际上,我们在驱动板上设计了一个 NTC 热敏电阻,一般的热敏
电阻随温度变化不是线性的,我们为了计算方便把它线性化,所以跟实际温度有
点偏差是正常的,主要是对我们这个电机控制系统来说,对温度精度检测要求不
高,所以就可以直接线性处理减少计算量。不同热敏电阻的 TEMP_CONVERSION
是不同的,这个需要根据温度与电阻值变化表去整合得到。
代码 14-84 MCL_Reset_PID_IntegralTerms 函数
01 void MCL_Reset_PID_IntegralTerms(void)
02 {
03
PID_Speed_InitStructure.wIntegral=0;
04
PID_Torque_InitStructure.wIntegral=0;
05
PID_Flux_InitStructure.wIntegral = 0;
STM32 技术开发手册
www.ing10bbs.com
06 }
该函数是把 PID 环的积分量清零。因为我们使用位置式 PID,所以在开始运
行时需要把积分清零。
代码 14-85 制动电阻相关函数
01 #ifdef BRAKE_RESISTOR
02 void MCL_Brake_Init(void)
03 {
04
GPIO_InitTypeDef GPIO_InitStructure;
05
06
/* Enable GPIOD clock */
07
RCC_APB2PeriphClockCmd(BRAKE_GPIO_RCC_CLK, ENABLE);
08
GPIO_DeInit(BRAKE_GPIO_PORT);
09
GPIO_StructInit(&GPIO_InitStructure);
10
11
/* Configure PD.13 as Output push-pull for break feature */
12
GPIO_InitStructure.GPIO_Pin = BRAKE_GPIO_PIN;
13
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
14
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
15
GPIO_Init(BRAKE_GPIO_PORT, &GPIO_InitStructure);
16 }
17
18 void MCL_Set_Brake_On(void)
19 {
20
GPIO_SetBits(BRAKE_GPIO_PORT, BRAKE_GPIO_PIN);
21 }
22
23 void MCL_Set_Brake_Off(void)
24 {
25
GPIO_ResetBits(BRAKE_GPIO_PORT, BRAKE_GPIO_PIN);
26 }
27 #endif
这 3 个函数与制动电阻相关,只有在使用了制动电阻这三个函数才能使用。
我们硬石驱动板没有预留制动电阻接口,不过我们还是介绍下这几个函数内容。
MCL_Brake_Init 函数是制动电阻对应的 IO 初始化,直接设置为普通推挽输
出模式。
MCL_Set_Brake_On 函数使能制动电阻功能,直接运行 GPIO_SetBits 函数输
出高电平使能制动电阻,当然这个跟实际硬件设计密切相关。
MCL_Set_Brake_Off 函数关闭制动电阻功能,与 MCL_Set_Brake_On 函数功能
相反。
stm32f10x_MCdac.c 文件内容
在 stm32F10x_MCconf.h 中启用 DAC 功能后,DAC 就是一个功能强大的调试
工具。实际上呢,虽然这里说是 DAC 功能,但是在代码中实现的并不是用 STM32
STM32 技术开发手册
www.ing10bbs.com
的内部 DAC,而是用定时器输出 PWM 波形(频率为 35KHz),而占空比大小标识
相关值的大小。这样我们在定时器引脚外接上一个一阶低通滤波器(如 10K 欧的
电阻+22nF 电容构成的滤波器)就可以还原出模拟信号值。实际上,我们也可以
自己修改代码实现使用真正的 DAC 外设功能输出。
在 FOC 控制中有很多中间变量:𝑖𝑎 、𝑖𝑏 、𝑖𝛼 、𝑖𝛽 、𝑖𝑞 、𝑖𝑑 、𝑉𝑞 、𝑉𝑑 、𝑉𝛼 、𝑉𝛽 ……
这些变量在我们调试中是有效的参考数据,一般的做法我们可以用串口“打印”
到上位机,不过部分参数是跟电流采样同样频率改变的,使用串口“打印”变量
速度跟不上,所以电机库就专门做了这个 DAC 功能,通过 PWM 输出相关变量
值。
电机库里边定义了 23 个可选参数输出值,通过按键在液晶界面选择可以设
置具体需要查看的变量。当然需要在 STM32F10x_MCconf.h 文件中把相关宏定义
的注释去掉。电机库默认使用 TIM3 的 CH3(对应 PB0)和 CH4(对应 PB1)这两
个引脚做 DAC 输出功能,注意这里的 DAC 不是真正意义上的 DAC,实际是输出
35KHz 的 PWM 波形,PWM 波占空比的值代表待查看变量值的大小,所以一般是
需要使用一阶低通滤波器来还原待查看数值,然后用示波器测量,查看数据变化
曲线。因为我们开发板把 TIM3 作为霍尔传感器或者编码器接口定时器,所以把
DAC 功能关闭的。
代码 14-86 MCDAC_Init 函数
01 void MCDAC_Init (void)
02 {
03
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
04
GPIO_InitTypeDef GPIO_InitStructure;
05
TIM_OCInitTypeDef TIM_OCInitStructure;
06
07
/* Enable GPIOB */
08
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);
09
10
GPIO_StructInit(&GPIO_InitStructure);
11
12
/* Configure PB.00 as alternate function output */
13
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
14
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
15
GPIO_Init(GPIOB, &GPIO_InitStructure);
16
17
/* Enable TIM3 clock */
18
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
19
20
TIM_DeInit(TIM3);
21
STM32 技术开发手册
www.ing10bbs.com
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 }
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_OCStructInit(&TIM_OCInitStructure);
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = 0x800;
TIM_TimeBaseStructure.TIM_Prescaler = 0x0;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
/* Output Compare PWM Mode configuration: Channel3 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0x400; //Dummy value;
TIM_OC3Init(TIM3, &TIM_OCInitStructure);
/* Output Compare PWM Mode configuration: Channel4 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0x400; //Dummy value;
TIM_OC4Init(TIM3, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Disable);
/* Enable TIM3 counter */
TIM_Cmd(TIM3, ENABLE);
该函数是 DAC 功能初始化。首先是引脚端口时钟使能,引脚配置 PB0 和 PB1
设置为复用功能输出。接下来是 TIM3 配置,预分频设置为 0,定时器设置为 72MHz,
周期设置为 0x800,可以得到一个 35KHz(72M/0x800)的 PWM 频率。设置 TIM3
的 CH3(对应 PB0 引脚)和 CH4(对应 PB1 引脚)为 PWM1 输出模式,初始化
比较值为 0x400,占空比为 50%。使能 TIM 的自动重装载功能,最后启动定时器。
代码 14-87 MCDAC_Update_Output 函数
01 void MCDAC_Update_Output(void)
02 {
03 TIM_SetCompare3(TIM3, ((u16)((s16)((hMeasurementArray[OutputVar[bChannel_1_variable]]+32768)/32))));
04 TIM_SetCompare4(TIM3, ((u16)((s16)((hMeasurementArray[OutputVar[bChannel_2_variable]]+32768)/32))));
05 }
该函数设置 DAC 功能输出值,实际上是直接调用 TIM_SetCompare3 函数设
置定时器通道比较值,改变通道占空比。hMeasurementArray 变量是一个 23 个
元素的一维数组,并且是 s16 类型(-32768~32767),分别对应代码 14-88 中的
变量的值。该数组值是由 MCDAC_Update_Value 函数单独更新。
代码 14-88 DAC 输出变量名称
01 u8 *OutputVariableNames[23] = {
02
"0
","Ia
03
"Ialpha
","Ibeta
","Ib
","Iq
",
",
STM32 技术开发手册
www.ing10bbs.com
04
05
06
07
08
09
10 };
"Id
"Vq
"Vbeta
"Observed El Angle
"Observed Ibeta
"User 1
","Iq ref
","Id ref
",
","Vd
","Valpha
","Measured El Angle
","Measured Rotor Speed",
","Observed Rotor Speed","Observed Ialpha
",
","Observed B-emf alpha","Observed B-emf beta ",
","User 2
"
",
OutputVar 是输出变量索引数据,不同模式可以查看的变量有所不同,由该
数组来指示可以查看的变量,该变量定义见代码 14-89。在不同模式下,OutputVar
有不同的数字,这些数值编号与代码 14-88 中的变量名称是一一对应的。
代码 14-89 OutputVar 数组定义
01 #if (defined ENCODER || defined VIEW_ENCODER_FEEDBACK || defined HALL_SENSORS\
02
|| defined VIEW_HALL_FEEDBACK) && (defined NO_SPEED_SENSORS ||\
03
defined OBSERVER_GAIN_TUNING)
04
05 u8 OutputVar[23]= {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22};
06 u8 max_out_var_num = 22;
07
08 #elif (defined NO_SPEED_SENSORS)
09 u8 OutputVar[21]= {0,1,2,3,4,5,6,7,8,9,10,11,12,15,16,17,18,19,20,21,22};
10 u8 max_out_var_num = 20;
11
12 #elif (defined ENCODER || defined HALL_SENSORS)
13 u8 OutputVar[17]= {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,21,22};
14 u8 max_out_var_num = 16;
15 #endif
bChannel_1_variable 和 bChannel_2_variable 变量是两个 DAC 通道的索引值,
提供选择具体哪个变量作为输出源。
这里对 hMeasurementArray 数据做了加 32768 处理,因为 hMeasurementArray
是一个 s16 类型数据,有正反数,这里把数据偏移 32768,这样数据全部是正数,
分别我们赋值给通道比较值,最后还做了除以 32 处理,主要是我们设置 PWM 的
周期值为 0x800,我们需要把比较值设定在比该值还小的范围,所以这里做缩小
32 倍处理。
代码 14-90 MCDAC_Update_Value 函数
01 void MCDAC_Update_Value(u8 bVariable, s16 hValue)
02 {
03
hMeasurementArray[bVariable] = hValue;
04 }
该函数用于设置更新 hMeasurementArray 数组数据,即把当下相关变量值保
持到该数组里边,以供 DAC 功能输出调用。
代码 14-91 MCDAC_Output_Choice 函数
STM32 技术开发手册
www.ing10bbs.com
01 void MCDAC_Output_Choice(s8 bStep, u8 bChannel)
02 {
03
if (bChannel == DAC_CH1) {
04
bChannel_1_variable += bStep;
05
if (bChannel_1_variable > max_out_var_num) {
06
bChannel_1_variable = 1;
07
} else if (bChannel_1_variable == 0) {
08
bChannel_1_variable = max_out_var_num;
09
}
10
} else {
11
bChannel_2_variable += bStep;
12
if (bChannel_2_variable > max_out_var_num) {
13
bChannel_2_variable = 1;
14
} else if (bChannel_2_variable == 0) {
15
bChannel_2_variable = max_out_var_num;
16
}
17
}
18 }
该 函 数 是 DAC 功 能 选 择 , 实 际 上 就 是 改 变 bChannel_1_variable 和
bChannel_2_variable 这两个变量的值来选择 DAC 功能的输出源。这个一般跟按键
配合使用。
代码 14-92 MCDAC_Output_Var_Name 函数
01 u8 *MCDAC_Output_Var_Name(u8 bChannel)
02 {
03
u8 *temp;
04
if (bChannel == DAC_CH1) {
05
temp = OutputVariableNames[OutputVar[bChannel_1_variable]];
06
} else {
07
temp = OutputVariableNames[OutputVar[bChannel_2_variable]];
08
}
09
return (temp);
10 }
该函数返回 DAC 功能输出源的名称,实际上就是通过索引返回当前输出源
对应 OutputVariableNames 数组里边的字符串数据。这个一般与串口“打印”或
者液晶显示配合使用。
stm32f10x_it.c 文件内容
该文件存放了中断服务函数,这里重点用到三个中断服务函数:AD 采集中
断、定时器刹车输入中断和定时器更新中断。
代码 14-93 定时器中断服务函数
01 void TIM1_BRK_IRQHandler(void)
02 {
03
MCL_SetFault(OVER_CURRENT);
04
TIM_ClearITPendingBit(TIM1, TIM_IT_Break);
05 }
06
07 void TIM1_UP_IRQHandler(void)
STM32 技术开发手册
www.ing10bbs.com
08 {
09
// Clear Update Flag
10
TIM_ClearFlag(TIM1, TIM_FLAG_Update);
11
12 #ifndef ICS_SENSORS
13
SVPWMUpdateEvent();
14 #endif
15 }
TIM1_BRK_IRQHandler 函数是定时器刹车输入中断,这个需要我们在程序里
边开启刹车输入中断。我们例程默认没有开启。ST 官方原版例程是有开启刹车输
入中断,并且是在硬件上把过流保护输出接入到刹车输入功能引脚上,所以在中
断服务函数里边标记了过流故障,当然这个跟硬件设计相关。
TIM1_UP_IRQHandler 函数是定时器更新中断,这里运行 SVPWMUpdateEvent
函数,重新启用外部 ADC 触发,并清除 AD 转换完成标志位。
代码 14-94 ADC1_2_IRQHandler 函数
01 void ADC1_2_IRQHandler(void)
02 {
03
//if(ADC_GetITStatus(ADC1, ADC_IT_JEOC) == SET))
04
if ((ADC1->SR & ADC_FLAG_JEOC) == ADC_FLAG_JEOC) {
05
//It clear JEOC flag
06
ADC1->SR = ~(u32)ADC_FLAG_JEOC;
07
08
if (SVPWMEOCEvent()) {
09 #ifdef DAC_FUNCTIONALITY
10 #if (defined OBSERVER_GAIN_TUNING || defined NO_SPEED_SENSORS)
11
MCDAC_Update_Value(LO_ANGLE,(s16)(STO_Get_Electrical_Angle()));
12
MCDAC_Update_Value(LO_I_A, STO_Get_wIalfa_est());
13
MCDAC_Update_Value(LO_I_B, STO_Get_wIbeta_est());
14
MCDAC_Update_Value(LO_BEMF_A, STO_Get_wBemf_alfa_est());
15
MCDAC_Update_Value(LO_BEMF_B,STO_Get_wBemf_beta_est());
16 #endif
17 #endif
18
19
MCL_Calc_BusVolt();
20
switch (State) {
21
case RUN:
22
FOC_Model();
23
break;
24
25
case START:
26 #ifdef NO_SPEED_SENSORS
27 #ifdef VIEW_ENCODER_FEEDBACK
28
ENC_Start_Up();
29
if ( (wGlobal_Flags & FIRST_START) != FIRST_START) {
30
STO_Start_Up();
31
}
32 #else
33
STO_Start_Up();
34 #endif
35 #elif defined ENCODER
36
ENC_Start_Up();
37 #elif defined HALL_SENSORS
38
State = RUN;
STM32 技术开发手册
www.ing10bbs.com
39 #endif
40
break;
41
42
default:
43
break;
44
}
45 #ifdef BRAKE_RESISTOR
46
if ((wGlobal_Flags & BRAKE_ON) == BRAKE_ON) {
47
u16 aux;
48 #ifdef THREE_SHUNT
49
aux = ADC_GetInjectedConversionValue(ADC1, ADC_InjectedChannel_2);
50 #elif defined SINGLE_SHUNT
51
aux = ADC_GetInjectedConversionValue(ADC2, ADC_InjectedChannel_2);
52 #endif
53
if (aux < BRAKE_HYSTERESIS) {
54
wGlobal_Flags &= ~BRAKE_ON;
55
MCL_Set_Brake_Off();
56
}
57
}
58 #endif
59 #ifdef DAC_FUNCTIONALITY
60
MCDAC_Update_Value(I_A,Stat_Curr_a_b.qI_Component1);
61
MCDAC_Update_Value(I_B,Stat_Curr_a_b.qI_Component2);
62
MCDAC_Update_Value(I_ALPHA,Stat_Curr_alfa_beta.qI_Component1);
63
MCDAC_Update_Value(I_BETA,Stat_Curr_alfa_beta.qI_Component2);
64
MCDAC_Update_Value(I_Q,Stat_Curr_q_d.qI_Component1);
65
MCDAC_Update_Value(I_D,Stat_Curr_q_d.qI_Component2);
66
MCDAC_Update_Value(I_Q_REF,hTorque_Reference);
67
MCDAC_Update_Value(I_D_REF,hFlux_Reference);
68
MCDAC_Update_Value(V_Q,Stat_Volt_q_d.qV_Component1);
69
MCDAC_Update_Value(V_D,Stat_Volt_q_d.qV_Component2);
70
MCDAC_Update_Value(V_ALPHA,Stat_Volt_alfa_beta.qV_Component1);
71
MCDAC_Update_Value(V_BETA,Stat_Volt_alfa_beta.qV_Component2);
72 #if (defined ENCODER || defined VIEW_ENCODER_FEEDBACK)
73
MCDAC_Update_Value(SENS_ANGLE,ENC_Get_Electrical_Angle());
74 #elif (defined HALL_SENSORS)
75
if (State != RUN) {
76
HALL_IncElectricalAngle();
77
}
78
MCDAC_Update_Value(SENS_ANGLE,HALL_GetElectricalAngle());
79 #elif (defined VIEW_HALL_FEEDBACK)
80
HALL_IncElectricalAngle();
81
MCDAC_Update_Value(SENS_ANGLE,HALL_GetElectricalAngle());
82 #endif
83 #if ((defined OBSERVER_GAIN_TUNING) && (!defined(NO_SPEED_SENSORS)))
84
STO_Calc_Rotor_Angle(Stat_Volt_alfa_beta,Stat_Curr_alfa_beta,MCL_Get_BusVolt());
85 #endif
86
87
MCDAC_Update_Output();
88 #endif
89
}
90
} else {
91 #ifdef THREE_SHUNT
92
if (ADC_GetITStatus(ADC1, ADC_IT_AWD) == SET)
93 #elif defined SINGLE_SHUNT
94
if (ADC_GetITStatus(ADC2, ADC_IT_AWD) == SET)
95 #endif
96
{
97 #ifdef BRAKE_RESISTOR
98
//Analog watchdog interrupt has been generated
99
MCL_Set_Brake_On();
100
wGlobal_Flags |= BRAKE_ON;
101 #else
STM32 技术开发手册
www.ing10bbs.com
102
MCL_SetFault(OVER_VOLTAGE);
103 #endif
104 #ifdef THREE_SHUNT
105
ADC_ClearFlag(ADC1, ADC_FLAG_AWD);
106 #elif defined SINGLE_SHUNT
107
ADC_ClearFlag(ADC2, ADC_FLAG_AWD);
108 #endif
109
}
110
}
111 }
该函数处理 AD 转换中断请求。有两个中断源:JEOC(注入组转换完成)和
AWD(模拟看门狗)。
JEOC:如果主状态是启动,会触发电机启动程序;要不然,如果主状态是运
行,会触发 FOC 算法执行。如果系统配置包括制动电阻,就会启用滞后开关(以
防过电压)。如果启用了 DAC 功能,就会更新相关变量值。
AWD: 在 过 压 情 况 下 , 根 据 是 否 在 stm32f10x_MCconf.h 中 注 释
BRAKE_RESISTOR,启用制动电阻或产生错误事件(OVER_VOLTAGE)。在处理过程中,
与电流采样方法(单相分流,三相分流或者 ICS)相关的 SVPWMEOCEvent 程序会
被调用。如果电流采样已经完成一个 PWM 周期这个程序就会返回真值,此时也
可以执行相关 FOC 程序。
函数首先判断是否是发生了 JEOC 中断,确认是发生该中断后,执行 if 语句
里边内容。第一步是清除 JEOC 标志位,调用 SVPWMEOCEvent 函数计算总线电
压和温度传感器采样,并且禁用外部 ADC 触发,该函数返回值恒定是 1。这样继
续执行 if 语句代码,如果定义了 DAC_FUNCTIONALITY 宏,即使能了 DAC 功能,
如果是在使能了状态观测器或者无传感器模式下,调用 MCDAC_Update_Value 函
数把状态观测器相关参数值(包括:电角度、相电流、反电动势)保存到 DAC 输
出功能的缓冲器里边。然后,运行 MCL_Calc_BusVolt 函数计算电源电压值。
使用 switch 语句判断电机状态,如果是 RUN 状态,执行 FOC_Model 函数,
进行 FOC 控制,使得电机持续旋转。如果是 START 状态,在无传感器模式下运行
STO_Start_Up 函数启动无传感器模式,并且如果同时使能编码器做辅助传感器,
还需要调用 ENC_Start_Up 函数启动编码器接口;如果是直接是编码器模式,那
么直接调用 ENC_Start_Up 函数启动编码器;如果是霍尔传感器模式,不需要额
外的操作,直接进入 RUN 状态。
STM32 技术开发手册
www.ing10bbs.com
如果我们使用了制动电阻(我们驱动板没有),在已经发生过压情况下,检
测当前电源电压值是否下降到合适范围,如果是,就直接清除制动电阻打开的标
志,并且关闭制动电阻引脚。
如果我们使用了 DAC 功能,把相关变量值更新到 DAC 功能数据缓存区里边,
以提高 DAC 功能使用,当然这里还需要区分不同传感器模式有不同的参数可以
提供给 DAC 功能,最后调用 MCDAC_Update_Output 把选中的参数值更新到功能
引脚上。
这样 JEOC 中断处理就完成了,它这种的重点是 switch 语句里边对 RUN 和
START 这两种状态的处理。
我们再来看 AWD 中断的内容,调用 ADC_GetITStatus 函数判断是否真的发生
了模拟看门狗中断,该中断的发生标记着电源电压过高。确定发生 AWD 中断后,
如果是使用了制动电阻功能,就使能制动电阻引脚,这时制动电阻产生效果,电
源部分能量消耗在制动电阻上,电源电压会回落,程序这里并且标记制动电阻打
开标记;如果没有使用制动电阻功能,直接调用 MCL_SetFault 函数向系统发出电
压过高警报,系统进步故障处理状态。最后,调用 ADC_ClearFlag 函数清除 AWD
标志。
main.c 文件内容
该文档重点是 main 函数,也是我们用户应用程序起始接口函数。该文件除
了 main 函数之外,还有一个引脚配置 GPIO_Configuration 函数。
代码 14-95 GPIO_Configuration 函数
01 void GPIO_Configuration(void)
02 {
03
GPIO_InitTypeDef GPIO_InitStructure;
04
05
/* Enable GPIOC clock */
06
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE);
07
08
GPIO_DeInit(GPIOA);
09
GPIO_StructInit(&GPIO_InitStructure);
10
11
/* Configure PC.06, PC.07, PC.08 and PC.09 as Output push-pull for debugging
12
purposes */
13
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11;
14
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
15
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
16
GPIO_Init(GPIOA, &GPIO_InitStructure);
STM32 技术开发手册
www.ing10bbs.com
17
18
19 }
GPIO_ResetBits(GPIOA,GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11);
该函数是引脚 IO 初始化配置。这里初始化 PA8、PA9、PA10 和 PA11 这四个
引脚为普通推挽输出模式,并且设置输出低电平。这个函数是在 main 函数中优
先调用的,这样可以在上电复位马上被执行。PA8、PA9、PA10 和 PA11 这几个引
脚是定时器 1 的通道引脚,在电路上,是控制三个半桥的上桥臂引脚,在开机时
候马上把这些引脚设置为低电平,可以保障三个上桥臂都是关闭,这样可以避免
上下桥臂同时导通情况,增加安全性。
01 int main(void)
02 {
03
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
04
GPIO_Configuration();
05
06 #ifdef THREE_SHUNT
07
SVPWM_3ShuntInit();
08 #elif defined ICS_SENSORS
09
SVPWM_IcsInit();
10 #elif defined SINGLE_SHUNT
11
SVPWM_1ShuntInit();
12 #endif
13
14 #ifdef ENCODER
15
ENC_Init();
16 #ifdef OBSERVER_GAIN_TUNING
17
STO_StateObserverInterface_Init();
18
STO_Init();
19 #endif
20 #elif defined HALL_SENSORS
21
HALL_HallTimerInit();
22 #ifdef OBSERVER_GAIN_TUNING
23
STO_StateObserverInterface_Init();
24
STO_Init();
25 #endif
26 #elif defined NO_SPEED_SENSORS
27
STO_StateObserverInterface_Init();
28
STO_Init();
29 #ifdef VIEW_ENCODER_FEEDBACK
30
ENC_Init();
31 #elif defined VIEW_HALL_FEEDBACK
32
HALL_HallTimerInit();
33 #endif
34 #endif
35
36 #ifdef DAC_FUNCTIONALITY
37
MCDAC_Init();
38 #endif
39
40
TB_Init();
41
42
PID_Init(&PID_Torque_InitStructure, &PID_Flux_InitStructure, &PID_Speed_InitStructure);
43
44 #ifdef BRAKE_RESISTOR
45
MCL_Brake_Init();
46 #endif
STM32 技术开发手册
www.ing10bbs.com
47
48
KEYS_Init();
49
50
/* TIM1 Counter Clock stopped when the core is halted */
51 // DBGMCU_Config(DBGMCU_TIM1_STOP, ENABLE);
52
53
// Init Bus voltage and Temperature average
54
MCL_Init_Arrays();
55
56
BSP_LCD_Init();
57
LCD_SetTextColor(Blue);
58
LCD_SetBackColor(White);
59
Display_Welcome_Message();
60
61
while (1) {
62
Display_LCD();
63
MCL_ChkPowerStage();
64
//User interface management
65
KEYS_process();
66
67
switch (State) {
68
case IDLE:
// Idle state
69
break;
70
71
case INIT:
72
MCL_Init();
73
TB_Set_StartUp_Timeout(3000);
74
State = START;
75
break;
76
77
case START:
78
//passage to state RUN is performed by startup functions;
79
break;
80
81
case RUN:
// motor running
82 #ifdef ENCODER
83
if (ENC_ErrorOnFeedback() == TRUE) {
84
MCL_SetFault(SPEED_FEEDBACK);
85
}
86 #elif defined HALL_SENSORS
87
if (HALL_IsTimedOut()) {
88
MCL_SetFault(SPEED_FEEDBACK);
89
}
90
if (HALL_GetSpeed() == HALL_MAX_SPEED) {
91
MCL_SetFault(SPEED_FEEDBACK);
92
}
93 #elif defined NO_SPEED_SENSORS
94
95 #endif
96
break;
97
98
case STOP:
// motor stopped
99
// shutdown power
100
/* Main PWM Output Disable */
101
TIM_CtrlPWMOutputs(TIM1, DISABLE);
102
103
State = WAIT;
104
105 #ifdef THREE_SHUNT
106
SVPWM_3ShuntAdvCurrentReading(DISABLE);
107 #endif
108 #ifdef SINGLE_SHUNT
109
SVPWM_1ShuntAdvCurrentReading(DISABLE);
STM32 技术开发手册
www.ing10bbs.com
110 #endif
111
Stat_Volt_alfa_beta.qV_Component1 = Stat_Volt_alfa_beta.qV_Component2 = 0;
112
113 #ifdef ICS_SENSORS
114
SVPWM_IcsCalcDutyCycles(Stat_Volt_alfa_beta);
115 #elif defined THREE_SHUNT
116
SVPWM_3ShuntCalcDutyCycles(Stat_Volt_alfa_beta);
117 #endif
118
TB_Set_Delay_500us(2000); // 1 sec delay
119
break;
120
121
case WAIT:
// wait state
122
if (TB_Delay_IsElapsed() == TRUE) {
123 #ifdef ENCODER
124
if (ENC_Get_Mechanical_Speed() ==0) {
125
State = IDLE;
126
}
127 #elif defined HALL_SENSORS
128
if (HALL_IsTimedOut()) {
129
State=IDLE;
130
}
131 #elif defined NO_SPEED_SENSORS
132
STO_InitSpeedBuffer();
133
State=IDLE;
134 #endif
135
}
136
break;
137
138
case FAULT:
139
if (MCL_ClearFault() == TRUE) {
140
if ((wGlobal_Flags & SPEED_CONTROL) == SPEED_CONTROL) {
141
bMenu_index = CONTROL_MODE_MENU_1;
142
} else {
143
bMenu_index = CONTROL_MODE_MENU_6;
144
}
145 #if defined NO_SPEED_SENSORS
146
STO_InitSpeedBuffer();
147 #endif
148
State = IDLE;
149
wGlobal_Flags |= FIRST_START;
150
}
151
break;
152
153
default:
154
break;
155
}
156
}
157 }
首先是硬件初始化部分。
选择中断优先级组(3bit 抢占式,1bit 响应),调用 GPIO_Configuration 函数
完成配置功率电路三个上桥臂关闭状态。
运行 SVPWM_3ShuntInit 函数初始化 3 电阻采样的定时器和 AD 配置。
如果用到编码器就运行 ENC_Init 函数初始化编码器定时器;如果用到霍尔传
感器就运行 HALL_HallTimerInit 函数初始化霍尔传感器定时器;如果用到无传感
器模式,运行 STO_StateObserverInterface_Init 和 STO_Init 函数初始化状态观测器
STM32 技术开发手册
www.ing10bbs.com
接口以及状态传感器初始化。STO_StateObserverInterface_Init 函数根据电机参数,
默认状态检测增益向量和 PLL 增益初始化无传感算法。STO_Init 函数将所有有关
状态检测的变量初始化为合适的值。每次电机启动前都调用一次。
如果使能 DAC 功能,运行 MCDAC_Init 函数初始化用于 DAC 的定时器。
运行 TB_Init 函数初始化系统滴答定时器配置,初始化时间基准。
运行 PID_Init 函数进行 PID 算法初始化。为三个 PID 环:Torque、Flux 和 Speed
配置初始化参数。
如果使用制动电阻,运行 MCL_Brake_Init 函数初始化制动电阻控制 IO 引脚
状态。
运行 KEYS_Init 函数初始化功能按键,ST 官方原本用 5 个独立按键,需要对
IO 进行初始化,我们硬石使用触摸屏按钮,这部分初始化在液晶初始化部分完
成。
如果有需要可以运行 DBGMCU_Config 函数配置当 Cortex-M3 内核发生故障
时候可以停在 TIM1 的输出。
运行 MCL_Init_Arrays 函数初始化电源电压和温度采样数据缓冲器。
BSP_LCD_Init 函 数 初 始 化 液 晶 屏 , LCD_SetTextColor 设 置 字 体 颜 色 ,
LCD_SetBackColor 设置背景颜色,Display_Welcome_Message 显示欢迎界面(开机
初始化界面)。
接下来就是无限循环里边的代码了。主要是液晶显示、按键扫描、状态管理。
Display_LCD 函数刷新液晶界面显示。MCL_ChkPowerStage 函数检测电源电压
是否过低和是否过温,如果值超出限定范围就向系统发出警报。KEYS_process 函
数进行按键扫描处理,并推动液晶菜单切换。
switch 语句判断系统不同状态执行相应的代码。这个也是对按键扫描结果的
一个处理,按键可能改变系统状态。
IDLE:空闲状态,无需执行任务代码。
INIT:初始化状态,MCL_Init 函数是电机控制层初始化,该函数在每次电机
启动前初始化电机控制;函数影响 PID 调节器、电流读取刻度,速度传感器和高
STM32 技术开发手册
www.ing10bbs.com
边驱动引导电容初始化。TB_Set_StartUp_Timeout 函数设置延时 3s 后才启动,然
后进入 START 状态。
START:启动状态,这里不需要做处理,该状态在 ADC1_2_IRQHandler 函数中
单独做处理。
RUN:运行状态,如果是编码器模式,运行 ENC_ErrorOnFeedback 函数检测
编码器反馈是否正确,如果编码器反馈出错运行 MCL_SetFault 函数标记速度反
馈出错。如果是霍尔传感器模式,运行 HALL_IsTimedOut 函数检测霍尔传感器反
馈是否超时,如果超时运行 MCL_SetFault 函数标记速度反馈出错,再运行
HALL_GetSpeed 获取当前速度,如果当前速度大于最大速度通用标记速度反馈出
错。
STOP : 停 止 状 态 , 关 闭 TIM1 脉 冲 输 出 , 切 换 为 WAIT 状 态 。 调 用
SVPWM_3ShuntAdvCurrentReading 函数关闭 AD 电流采样,清除𝑣𝛼 和𝑣𝛽 为 0,运
行 SVPWM_3ShuntCalcDutyCycles 函数清除 SVPWM 占空比计算。最后,运行
TB_Set_Delay_500us 函数延时 1s 时间。
WAIT:待机状态,运行 TB_Delay_IsElapsed 函数电平是否延时完成,然后等
待电机完全停机,切入到 IDLE 状态。
FAULT:故障状态,运行 MCL_ClearFault 函数判断当前故障时候解除,确认解
除故障后运行 if 语句里边代码,如果是速度控制模式,液晶转换到速度模式界
面,然后进入转矩控制界面。如果是无传感器模式,运行 STO_InitSpeedBuffer 函
数重新初始化速度缓冲区。把状态实在为 IDLE。
以上就是整个 FOC 工程的代码解析。
STM32 技术开发手册
www.ing10bbs.com
第15章 YS-F4Pro 的 FOC4.3 移植
15.1 FOC4.3 MC library 文件目录
首先,要移植例程必须先要有 ST 提供的 FOC4.3 电机控制例程。
ST 官网下载地址:
http://www.st.com/content/st_com/en/products/embeddedsoftware/mcus-embedded-software/stm32-embedded-software/stm32-standardperipheral-library-expansion/stsw-stm32100.html
或者从我们提供的资料里面找到 4.x.zip 文件:
图 15-1 FOC 4.3 压缩包
解压就可以得到 FOC4.3 的软件开发工具包(SDK)。安装过程就直接下一步
就好了,并不需要有什么特殊值得注意的事情,不过建议安装路径不要选在系统
STM32 技术开发手册
www.ing10bbs.com
盘,因为 window 对系统盘是有保护的,在里面进行文件读写操作会造成不必要
的警报,如果已经安装到系统盘的也可以把工程文件复制到其他地方去然后再操
作。设置下载安装之后在安装目录可以看到有 3 个文件夹。
图 15-2 FOC SDK 文件夹目录
从上到下 3 个文件夹分别是:STM32 PMSM FOC Lib(库的核心内容),STMCWB
(ST Motor Control workbench 俗称 foc 的上位机软件),ST Motor Profiler(电机
参数分析工具)。除了库源码之外,另外两个是 PC 端的辅助软件。其中 Motor
Profiler 从功能上看貌似只适配 ST 自己提供的电机控制板,即便使用的是同一个
芯片也无法使用,不过这个并不影响我们将 FOC 移植到 YS-F4Pro 上面。
STMCWB 则是一个电机控制软件,可以使用它来设置电机运行的参数,自动
生成参数文件,添加到工程里面就可以直接使用。还可以连接开发板在线调试运
行参数。
STM32 技术开发手册
www.ing10bbs.com
图 15-3 ST Motor Control Workbench
下面详细讲解 STM32 PMSM FOC LIB 文件夹里面的内容,STM32 PMSM
FOC LIB 文件夹里面同样也是 3 个文件夹:
图 15-4 STM32 PMSM FOC LIB 文件夹目录
Common 是外设标准库的源码,标准库是 ST 很早就推出的固件库,目前已
经停止更新,正在主推 HAL 库,但是最新出的 FOC4.3 却还是使用标准库。
Docs 是文档说明,包括帮助文档,用户手册,开发手册,快速使用说明等
等,还有一个 FAQ 文档解释常见的问题。不过全是英文,有能力的可以去看看,
从这些手册里面是可以获得很多官方提供的有效信息。特别是针对 FOC PMSM 固
件库的开发帮助手册,里面有电机控制库的函数原型。
Web 里面的内容就是我们想要的 FOC 源码还有工程文件,这里面也是需要
重点介绍的内容
STM32 技术开发手册
www.ing10bbs.com
图 15-5 FOC4.3 源代码工程目录
Project st 提供的例程,里面包括有 F0,F1,F3,F4 系列的 Keil 和 IAR 的工程文
件,MDK 的工程文主要集中在 MDK-ARM 文件夹中。这里主要 keil 工程为例介绍
移植到 YS-F4Pro 的方法。其中,UserProject 文件夹里面则是有 FOC 的应用工程。
MC_Library_Compiled 则是 FOC 库的不开源部分,里面是 st 提供的 lib 文件,建
立工程的时候需要添加进去。
STM32 技术开发手册
www.ing10bbs.com
图 15-6 MDK Project 文件夹
Web 下面的 MCApplication、MCLibrary、SystemDrivePamas、UILibrary 就
是 FOC 库开源的核心代码。
15.2 Keil MDK 软件工程目录
这里只是一个移植指导文档,并不会对 FOC 库的内容进行过多讲解,只会讲
述需要改动的那部分内容,所以下面的章节的内容有可能并不会过多的解释原因。
15.2.1 试编译官方例程
在移植之前,先编译一下 st 提供的例程,熟悉一下操作。
首先打开 st 提供的辅助软件,STMCWB(ST Motor Control Workbench)所设
置好的文件,具体路径在 Utilities 里面,如下图:
STM32 技术开发手册
www.ing10bbs.com
图 15-7 STMCWB 文件
选一个 STM32F4 系列的工程文件,注意这里选的是 STM3240G,意思是使用
的主控芯片是 STM32F40xG 系列。这个文件是已经配置好参数的文件,适配 st 提
供的开发板和驱动板还有电机,并不适合我们自己的板子和电机,所以现在并不
需要做任何改动,只需要点击生成就可以了。
图 15-8 STMotor Control Workbench 界面
STM32 技术开发手册
www.ing10bbs.com
点击之后会在 SystemDriveParams 文件里面生成 4 个头文件,这 4 个头文件
包含了有在控制台里面设置好的参数。
现在要打开 keil 的工程文件,文件路径如下:
安装路径:\FOC SDK\v4.3.0\STM32 PMSM FOC LIB\Web\Project\MDK-ARM
打开工作台工程文件,名字是 STM32F4xx_Workspace.uvmpw。这是一个工作
台工程,里面包含有两个 Project,一个是电机的库工程(MC Library),一个是应
用工程(UserProject)
。
图 15-9 工程文件路径
STM32 技术开发手册
www.ing10bbs.com
图 15-10 工程文件目录
库工程一般情况下时不需要改动的,所以这里不做介绍,可以待成功驱动
PMSM 电机之后再来看这部分内容。在第一次打开的时候默认是 UserProject 是
STM32 技术开发手册
www.ing10bbs.com
当前活动工程,需要将 LibraryProject 改为当前活动工程,看下图。然后点击编译
按钮(或者 F7)。
图 15-11 设置为当前活动工程
然后将 UserProject 改为当前活动工程,按照下图设定当前编译目标是
STM324xG-EVAL。最后编译,如果有 st 提供的电路板和电机也可以按照这个方法
直接下载测试。
STM32 技术开发手册
www.ing10bbs.com
图 15-12 更改 Target
15.2.2 重新建立工程文件夹
st 提供的例程里面有很多的工程文件,因为 st 需要兼容其他系列的芯片,
但是我们可能并不需要那么多的工程,可能只需要 F4 的例程或者 F1 的例程就足
够了,并且 st 的例程里面的文件目录也不符合我们的习惯,所以这里就需要在
原有的例程基础上进行修改移植,最终使得整个工程文件夹符合我们的使用习惯。
利用现有的工程模板将会方便很多,可以省掉我们添加标准库的时间。在现
有的 F4 标准库例程的文件夹里面新建一个文件夹用于存放 FOC 库文件,我们将
其命名为 STM32_PMSM_FOC_LIBv4.3。
图 15-13 新建库目录
然后我们可以把 st 的例程里面的库文件复制过来,文件夹里面的内容跟 FOC
库的内容是一致的,都可以在 FOC SDK\v4.3.0\STM32 PMSM FOC LIB\Web 文件夹
里面找到,其中 MC_Library_Compiled 是在\Web\Project\MDK-ARM\里面。
STM32 技术开发手册
www.ing10bbs.com
图 15-14 MC 库源码文件目录
图 15-15 从例程复制源文件
main.c 等文件也是从 st 提供的例程那里复制过来的,把它放在自己的工程的
User 文件夹里面。
图 15-16 复制 main.c 等文件
Bsp 文件夹里面的都是板载设备的源代码,包括搭配有 YS-F4Pro 的液晶屏和
按键的驱动代码。
STM32 技术开发手册
www.ing10bbs.com
然后打开 Keil MDK,这里需要两个工程文件,一个是用于生成 lib 的工程,一
个是应用工程,利用现有的工程模板可以省掉一个,现在还需要另一个工程文件,
点击 Project->New uVision Project,新建一个工程,新建工程的步奏这里省略,就
设置路径,主控芯片选择 STM32F407IGT6 就可以了。
图 15-17 新建工程
新建多工程工作台,将刚才的两个工程添加进去。
图 15-18 新建多工程工作台
STM32 技术开发手册
www.ing10bbs.com
图 15-19 添加工程进工作台
1. 建立库工程
在工作台中选择其中一个工程作为库工程,用于生成 lib 文件。将选择的库
工程设定为当前活动工程,然后按照 st 的例程的文件分组将文件目录组添加进
去。看下图:
STM32 技术开发手册
www.ing10bbs.com
图 15-20 设置为当前活动工程
STM32 技术开发手册
www.ing10bbs.com
图 15-21 添加文件目录组
理论上名字是可以随意的,但是为了更好的对比 st 例程,所以直接与例程一
致。然后对比着 st 的例程将每个分组的文件添加进去,每个分组的名字就是源
文件所在的路径。只需要对比着 st 例程就可以找到文件添加到工程里面。
图 15-22 根据路径添加源文件
STM32 技术开发手册
www.ing10bbs.com
不过其中 MCLibrary_conf 的内容是在 MCLibrary 文件夹根目录里面,而
MCLibrary_lib_single 的内容是在
MC_Library_Compiled-> RVMDK_MCLIB_OBJS_SINGLE 里面。MCLibrary_lib_dual
是双电机才会需要用到,并不是所有的源文件都需要添加进工程。
添加进去之后还不够,还需要设置头文件的路径,和宏定义。
图 15-23 设置宏定义
STM32 技术开发手册
www.ing10bbs.com
图 15-24 设置头文件路径
全部设置好了之后,在 Output 选项卡里面点击 Select Folder for Objects…
选择 MC_Library_Compiled 文件夹下的 STM32F4xx_SD 作为输出文件夹 ,然后选
择 Create Library..然后编译,编译完之后会在刚才选择的输出文件夹里面生成一
个 lib 文件,这个文件会在应用工程里面用到。
图 15-25 输出.lib
STM32 技术开发手册
www.ing10bbs.com
2. 建立应用工程
应用工程是跟库工程再同一个工作台里面的,要改动的时候需要先把应用工
程设定为当前活动工程,这里的应用工程就是上一步说的现有的工程模板。步奏
很简单,就是同样重复上一步的步奏,添加代码文件到工程里面。这里只给出两
个工程的对比,下图右边都是 st 提供的例程的工程文件分组,左边则是新建的
工程分组,将右边的红色框起来的内容添加到左边,其中 Project 的添加到左边
的 User 里面。这里说的内容除了分组以外还有就是里面的 .c 和.h 文件。
System&drive params 分组里面的内容可以不把名字带有 2 的文件添加进来,因
为名字带有 2 的都是双电机的第二电机的参数设置。
图 15-26 复制库的文件目录
值得注意的是有一部分的文件并不是那么容易找到的,很容易就找不到,这
个时候可以使用 window 的搜索文件功能,例如下图的 stm32f4xx_MC_it.c 文件,
可以直接在 st 的例程文件夹里面使用 Ctrl+F 快捷键,然后输入文件名就可以找
到,然后对着文件名字右击,跳转到文件所在位置,就知道这个文件在哪了。
STM32 技术开发手册
www.ing10bbs.com
图 15-27 个别文件
图 15-28 搜索文件位置
STM32 技术开发手册
www.ing10bbs.com
将文件添加进入工程之后,还需要设置头文件路径和宏定义。
图 15-29 添加宏定义和头文件路径
15.2.3 移植液晶和按键
首先把 ascii.h、bsp_lcd.c、bsp_lcd.h、bsp_key.c、bsp_key.h 这五个文件复制
到工程文件夹里面,然后在添加进工程。
图 15-30 将 LCD 和 KEY 添加到工程文件夹
STM32 技术开发手册
www.ing10bbs.com
图 15-31 将 LCD 和 KEY 添加到工程 bsp 目录
st 的例程里面,LCD 的驱动函数是在 LCDVintage_UserInterfaceClass.c 文件里
面,在 keil 中打开这个文件,在第 45 行,将
代码 15-1 包含的头文件
45 #include "stm32_eval.h"
改为
代码 15-2 修改后包含的头文件
45 #include "stm32f4xx.h"
46 #include "bsp/key/bsp_key.h"
47 #include "bsp/lcd/bsp_lcd.h"
在 365 行到 374 行,将
代码 15-3 液晶初始化和按键初始化
01 /* Initialize the LCD */
02 LCD_HW_Init();
04 LCD_Clear(White);
06 LCD_SetBackColor(White);
07 LCD_SetTextColor(Black);
09 /* Initialize Joystick */
10 STM_EVAL_JOYInit();
STM32 技术开发手册
www.ing10bbs.com
改为
代码 15-4 修改后的液晶初始化函数
01 /* 初始化 3.5 寸 TFT 液晶模组,一般优先于调试串口初始化 */
02 BSP_LCD_Init();
04 LCD_Clear(0,0,480,320,WHITE);
06 LCD_SetBackColor(WHITE);
07 LCD_SetTextColor(BLACK);
09 KEY_GPIO_Init();
10 /* Welcome message */
11 Palette_Init();
然后使用快捷键 Ctrl+H 调出 Replace 窗口,在 Find what 栏填上 Red,在 Replace
with 栏填上 RED,就是将小写字母换成大写字母而已。Look in 选择 Current
Document,Find options 选择 Match whole word.最后点击 Replace All。然后重复
这个步奏将 Blue 替换为 BLUE。
在 1454~1459 行,将
代码 15-5 显示字符
1454 uint16_t xpos = 16*Column;
1455 if (LCD_GetXAxesDirection() == LCD_X_AXES_INVERTED)
1456 {
1457
xpos = 320-xpos;
1458 }
替换为:
代码 15-6 修改后的显示字符代码
1453 LCD_DisplayChar(Line, 12*Column, Ascii);
在 bsp_lcd.c 文 件 里 面 找 到 BSP_LCD_Init() 函 数 , 在 函 数 返 回 之 前 加 入
LCD_BK_ON();函数打开 LCD 的背光,如果已经有了这个语句,那可以忽略这个步
奏。
将 1587~1657 行的内容全部删掉。然后将下面的 KEYS_Read()整个函数替换
为:
代码 15-7 按键读取函数
01 uint8_t KEYS_Read ( void )
02 {
03
/* "RIGHT" key is pressed */
04
if (KEY1_StateRead() == KEY_DOWN) {
05
if (bPrevious_key == SEL) {
STM32 技术开发手册
www.ing10bbs.com
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 }
return KEY_HOLD;
} else {
bPrevious_key = SEL;
return SEL;
}
}
/* "LEFT" key is pressed */
else if (KEY2_StateRead() == KEY_DOWN) {
if (bPrevious_key == UP) {
return KEY_HOLD;
}
{
bPrevious_key = UP;
return UP;
}
}
/* "SEL" key is pressed */
else if (KEY3_StateRead() == KEY_DOWN) {
if (bPrevious_key == DOWN) {
return KEY_HOLD;
}
{
bPrevious_key = DOWN;
return DOWN;
}
}
/* "UP" key is pressed */
else if (KEY4_StateRead() == KEY_DOWN) {
if (bPrevious_key == LEFT) {
return KEY_HOLD;
}
{
bPrevious_key = LEFT;
return LEFT;
}
}
/* "DOWN" key is pressed */
else if (KEY5_StateRead() == KEY_DOWN) {
if (bPrevious_key == RIGHT) {
return KEY_HOLD;
}
{
bPrevious_key = RIGHT;
return RIGHT;
}
}
/* No key is pressed */
else {
bPrevious_key = NOKEY;
return NOKEY;
}
最后,在按键状态读取的函数里面,也要将循环等待的 while 语句删掉,其
实也可以不删,这个并不会影响功能实现。
STM32 技术开发手册
www.ing10bbs.com
图 15-32 注释循环等待
STM32 技术开发手册
www.ing10bbs.com
第16章 YS-F4Pro 的 FOC4.3 例程调试和修改参数
16.1 STMCWB 的简介
打开 STMCWB,点击 NewProject,然后点击 OK,就可以使用这个上位机软
件,这是一个 PC 端的代码生成工具,不过只会生成关于电机控制固件库的参数
头文件(.h)。这个上位机软件可以与控制板串口连接,直接调节运行参数。
图 16-1 设置运行参数
在下方是 Log Bar,左侧是 Variable Table,这是对工程中重要的变量数值进
行汇总显示,双击可以打开对应的设置页面进行修改,可以在最后检查设置的参
数。右边则是 Log Message,提示一些已执行的操作或者错误。
STM32 技术开发手册
www.ing10bbs.com
图 16-2Varible Table
点开上位机的 Monitor 功能,可以设置串口号,波特率,然后点击 Connect
就可以连接 YS-F1Pro,前提是 FOC4.3 已经移植到 YS-F1Pro 上面。
图 16-3 点开 Monitor 功能
图 16-4 STMCWB 的 Monitor 功能
Monitor 这个功能界面里左边是状态监控,包括有错误状态,和速度监控。
绿色表示没有错误,橙色表示出现错误但已经被解决,红色表示出现错误没有被
解决;右边则是电机控制,使用不同的按钮有对应的功能,可以有停止,启动,
STM32 技术开发手册
www.ing10bbs.com
错误应答;中间部分就是监控面板,basic 面板显示主要的应用寄存器并允许修
改电机速度,Advanced 面板有总线电压,温度,电机功率和测量速度,目标速度,
也可以设置多个寄存器来控制电机;Registers 显示所有可用的寄存器,可以读取
和更改;Configuration 则是可以修改从 STMCWB 设置的功能,例如可以设置使用
哪一种反馈方式测量速度,总线电压,速度范围等。
图 16-5 Monitor 界面
还可以通过 Plotter(绘图),看到目标速度和测量的速度变化趋势,比较方便
的调试 PID 参数
STM32 技术开发手册
www.ing10bbs.com
图 16-6 Plotter 功能
16.2 电机参数
在使用 STMCWB 软件之前,我们必须获取电机的详细参数,因
为在软件中是需要的,关于电机的参数是直接跟电机厂家拿的,所以
当我们需要调试自己电机时候必须找电机厂家拿资料,当然部分参数
我们也可以通过仪器仪表测量得到。
下面我们贴出我们自己的电机参数,见表格 16-1,后面的演示都是以改电
机参数为例。
STM32 技术开发手册
www.ing10bbs.com
表格 16-1 PMSM 电机参数
42 永磁同步电机(PMSM)参数
供电电压
24 V
额定功率
63W
额定力矩
0.2 N.M
峰值力矩
0.6 N.M
额定转速
3000 RPM
额定电枢电流
3.13 A
力矩系数
0.057 N.M/A
反电势系数
4.13 V/KRPM
磁极
8(4 对极)
编码器
1000 线
相电阻
0.89±10%Ω
相电感
0.62±20%mH
16.3 上位机参数设置
这里说的上位机就是指 STMCWB(ST Motor Control Workbench )。打开
STMCWB,然后选择 New Project,这一页的参数都不是特别重要,可以直接使用
默认就好了。
STM32 技术开发手册
www.ing10bbs.com
图 16-7 新建 STMCWB 工程
然后可以看到有 4 个方面的内容需要设置的,有 Motor、Power Stage、Drive
Management、Control Stage。下面将一个一个的讲解如何设置。
1. Motor
这一个是设置电机的参数的,一般这些参数都是可以在买电机的时候由供应
商提供的。这里所用到的电机是 PMSM 电机,电机参数设置如下:
STM32 技术开发手册
www.ing10bbs.com
图 16-8 设定电机的参数
这个参数如果不跟换电机是不需要改动的。
2. Power Stage
这里设置电机电源的参数,这里给出的电路图就是驱动板上的生成 SVPWM
的电路。字体或者复选框有打“√”的就是有使用到的功能,可以点击进去设置
相应的参数。
AC Input Info
我们使用的是直流电源,所以 AC Input Info 这一项是不需要过多的关注,只
需要有一个比较合适的范围就行了,具体的设置可以看下面的图片。
STM32 技术开发手册
www.ing10bbs.com
图 16-9 设置电机的 AC 电源
Rated Bus Voltage Info
这个是总线电压范围,也就是我们使用的电机电源电压范围。Nominal voltage
设置为我们使用的电机的电压就好了。
图 16-10 设置电机总线上的电压
STM32 技术开发手册
www.ing10bbs.com
Dissipative Brake
制动电阻,通过短路电源总线,使得电机不会有电流流过。
图 16-11 设置制动电阻极性
Bus Voltage Sensing
这里是总线电压检测,检测总线电压是使用分压电阻采样,这里的电阻是跟
驱动板的设计有关,一定要跟驱动板的采样电阻一致。
STM32 技术开发手册
www.ing10bbs.com
图 16-12 设置总线电压采样电阻
Phase U Drive
这里是设置 3 相高端驱动信号和低端驱动信号的极性按照下图设置就好了。
设置一个,其余的一样。
图 16-13 设置 U 相驱动信号极性
STM32 技术开发手册
www.ing10bbs.com
Power Switches
这里设置开关管的参数,包括最小的死区时间,最大的开关频率。
图 16-14 设置开关管的特性
Temperature Sensing
这里设置温度传感器的参数,这些参数是根据热敏电阻来设置的,下半部分
就是关于温度保护的设置,包括使能和温度上限设置。
图 16-15 设置热敏电阻特性
STM32 技术开发手册
www.ing10bbs.com
Over Current Protection
这里设置过流保护,这里的过流保护是根据驱动板上的硬件层上的过流保护,
具体的电路可以去看驱动板的原理图。这里设置比较器电压为 0.5V。
图 16-16 设置过流比较器参数
Current Sensing
这里是电流的传感器的设置,选择电流采样方式为 3 相电阻采样,采样电阻
为 0.02ohm,然后 Calculate 页面计算放大倍数。
图 16-17 设置电流传感器参数
放大网络的设置,可以参考驱动器原理图,采样电阻的功率是 2W。
STM32 技术开发手册
www.ing10bbs.com
图 16-18 设置放大网络的电阻
3. Drive Management
这一项有六项参数需要设置,包括速度位置反馈,驱动设置,传感器使能和
硬件保护,启动参数,附加特性,人机交互方式。
Speed/Positon Feedback Management
这里最主要设置反馈方式,我们使用的 PMSM 是具有传感器和编码器的,
一般只需要一个就可以,但是 FOC 允许使用辅助传感器。下面的图片是以编码器
为例,
STM32 技术开发手册
www.ing10bbs.com
图 16-19 编码器为主传感器
图 16-20 无感模式为辅助传感器
STM32 技术开发手册
www.ing10bbs.com
Drive Settings
驱动参数设置,这一项可以设置 PWM 的输出方式,输出频率空闲状态的关
高端和低端的状态。还有电机默认参数,默认为速度模式,速度 1000RPM,实际
上,速度的设置是有一定的分辨率的,当设置为 1000RPM 的时候,实际只是设
置为 996RPM。下半部分则是关于 PID 调节器的参数设置,按照下图设置就好了。
图 16-21 驱动参数设置
Sensing Enabling and Firware Protections
这里设置驱动器上的传感器,可以单独使能过压或者低压保护。同时左下角
有温度传感器和 AC 输入设置,不过这些都是在之前就设置好了的。
STM32 技术开发手册
www.ing10bbs.com
图 16-22 使能传感器和硬件保护
Start-up Parameters
在这一个项目里面,如果选择的反馈方式不同,那么这一项可以设置的
参数也会不一样,现在已编码器为例,所以这里是编码器校准。
STM32 技术开发手册
www.ing10bbs.com
图 16-23 启动参数
Additional Features and PFC settings
附加特性这一项先不填。
STM32 技术开发手册
www.ing10bbs.com
图 16-24 附加特性功能
User Interface Add-on
人机交互界面,现在先选择使用串口跟上位机通信,还可以选择使用 LCD 进
行脱机调试,所以两个都选了。
STM32 技术开发手册
www.ing10bbs.com
图 16-25 人机界面接口
4. Control Stage
这一项是设置主控制器的参数,也就是我们的 YS-f4Pro 的参数配置。
MCU and Clock Frequency
选择 STM32F4xx 系列的芯片,时钟源选择外部 8MHz 晶振,CPU 的主频选择
168Mhz,电源电压是 3.3V
STM32 技术开发手册
www.ing10bbs.com
图 16-26 设置主控芯片和主频
Analog Input and Protection
这里选择模拟输入引脚,也就是 ADC 引脚分配了,YS-F4Pro 使用的是无
刷电机接口 2,所用到的 ADC 引脚都在这个接口上面。
Phase current feedback 这是相电流反馈通道。过流保护是可选的,如果
选择使用 External Protection,则表示选择了在 Power Srtag 页面设置的过流
保护,如果选择 No Protection,则表示不需要过流保护。在 Pin map 里是可
以选择 U、V、W 三相的采样电阻所连接的 ADC 通道。这些通道在设计驱动
板的时候就已经确定好了的,按照下图去设置就行了。采样时间选择 3 个 ADC
时钟周期。
图 16-27 设置采集相电流的 ADC 通道和引脚
STM32 技术开发手册
www.ing10bbs.com
Bus voltage feedback 这里选择采样电源总线电压反馈通道,Pin map 选择
ADC12_IN10(PC0)。采样周期是 28 个 ADC 时钟周期,外设选择 ADC1。
图 16-28 采集总线电压的 ADC 通道和引脚 ADC 通道和引脚
Temperature 这里选择温度传感器的 ADC 通道。采样周期是 28 个 ADC 时钟
周期,使用 ADC1 的 IN13。
图 16-29 热敏电阻的 ADC 通道和引脚
DAC Functionality 则是设置 DAC 输出通道的
图 16-30 功能输出引脚
STM32 技术开发手册
www.ing10bbs.com
Digital I/O
这里设置了数字信号引脚,包括定时器脉冲输出引脚,和编码器捕获引脚,
串口通信引脚。
YS-F4Pro 使用了 Tim8 作为脉冲控制定时器,TIM2 做编码器输入。
USART1 与上位机通信。
图 16-31 配置数字信号引脚
User Interface
这一部分的内容在 Drive Management 已经设置过了。
全部设置好了以后就可以点击保存,将其保存在我们的工程目录里面。这样每一
需要更改参数的时候可以直接打开修改。
图 16-32 保存 STMCWB 文件
STM32 技术开发手册
www.ing10bbs.com
保存了之后还需要设置生成的头文件路径。设置为库文件目录的
SystemDriveParams 里面,这是一个相对路径,前面是有.\的代表当前路径。这
样每次生成的头文件都会替换掉之前的文件,并且是直接就存放在库里面.
图 16-33 设置 STMCWB 输出路径
前面的都弄好了之后就可以点击 Generation,生成 4 个头文件。
图 16-34 生成参数文件
打开 Keil 工程,点开 System&Drive Params 目录下的 Control Stage Paramters.h、
Drive paramters.h、PMSM motor Parameters.h 或者 Power stage parameters.h 任意
一个文件都可以在文件的开头看到@data 会有这个文件的最近修改日期,就是
使用上位机点击 Generation 的时间,说明生成的文件已经成功的添加进工程里
面。到目前为止,移植已经成功了,现在可以编译程序,然后下载到 YS-F4Pro 上。
STM32 技术开发手册
www.ing10bbs.com
16.4 FOC4.3 上位机调试电机参数
16.4.1 STMCWB 的 Monitor 简介
点开上位机的 Monitor 功能,可以设置串口号,波特率,然后点击 Connect
就可以连接 YS-F4Pro。
图 16-35 STMCWB 的 Monitor 功能
Monitor 这个功能界面里左边是状态监控,包括有错误状态,和速度监控;
右边则是电机控制,使用不同的按钮有对应的功能;中间部分就是监控面板,使
用图像来表示电机当前状态,有总线电压,温度,电机功率和测量速度,目标速
度。通过切换选项卡可以设置更多的内容。
STM32 技术开发手册
www.ing10bbs.com
图 16-36 Monitor 界面
在 Advanced 这一栏里,可以设置控制模式,速度控制,调速周期,PID 控制
参数等功能。
还可以通过绘图,看到目标速度和测量的速度变化趋势,比较方便的调试 PID
参数
STM32 技术开发手册
www.ing10bbs.com
图 16-37 Plotter 功能
点击右边的 Start Motor 按钮,然后通过 Basic 页面的 Measured Speed(rpm)
或者 Plotter 功能观察电机速度运动规律,在 Advanced 的 PIDGains 里面修改 Kp
和 Ki 参数,直到得到自己满意的参数为止,就可以返回到 Drive Management 的
Drive Settings 那里修改成刚才调试 PID 参数,最后生成头文件保存到工程里面再
烧写一次就可以了。如果调试过程发生错误,左边的错误警告灯亮了,这个时候
部分情况是可以通过点击右边的 Fault Ack 按钮应答错误,然后继续调试,如果
不行就要重新启动控制板或者重新烧写程序。
16.4.2 连接上位机
首 先 将 keil 工 程 编 译 下 载 到 YS-F4Pro , 然 后 打 开 ST Motor Control
Workbench->Monitor 功能。点击 connect 连接 YS-F4Pro。
STM32 技术开发手册
www.ing10bbs.com
图 16-38STMCWB 与 YS-F4Pro 成功连接
点击右边的的 Start Motor 按钮,然后在中间的 Advanced 修改 PID 参数。
图 16-39 在 Advanced 里设置 PI 参数和目标值
修改完之后,关闭 Monitor,在 Srive Management 里面设置刚才的 PID 参数,
这里的参数都是可以任意修改的。
STM32 技术开发手册
www.ing10bbs.com
图 16-40 重新设置 PI 参数
最后生成头文件,再重新编译下载就可以了。
16.5 使用液晶屏脱机调试参数
如果不想使用上位机来调整参数,可以使用液晶屏来进行操作,完整移植之
后的显示效果如下图:
图 16-41 FOC4.3 运行状态液晶界面
STM32 技术开发手册
www.ing10bbs.com
第一第二行就是 FOC 库版本号,底下就是按键的标志,表示 KEY1 按键就是
对应着程序中的 SEL 按键,KEY2 对应着 UP 按键,有过实际操作再阅读源码的时
候可以对比着看就知道哪个按键有什么功能了。如同当前界面显示,目前正在速
度模式下,目标值为 996(rpm),测量值为 990(rpm)。
刚开机时,在速度模式下,按下 KEY1 电机就会转动,Measured 就是实际测
量得到的电机速度值,Target 就是在上位机设定的目标速度值,单位是(rpm)。
通过其他 4 个按键可以有不同的操作,字体为红色的时候,表示当前已经选中了
这个选项。
Move 和 Change 是按键的功能,表示如果按下 LEFT、RIGHT 就是 Move,
如果按下 UP、DOWM 就是 Change。如上图中的 Speed control mode,如果要切
换为扭矩模式,就需要按下 KEY2 或者 KEY3。
当需要设置 PID 参数的时候可以通过使用 Move 按键切换到不同的界面,如
下图,在 Speed 界面可以实时调整 PI 参数,通过 Move 按键是的 P 或者 I 的数值
编程红色,然后按下 Change 按键就可以修改,注意这里修改的参数并不会把保
存到 flash 里面,下次重启控制板之后就会丢失数据,所以修改外之后需要重新
使用上位机修改 PID 参数,再编译下载。
图 16-42 速度模式调节 PI 参数
如果发生了错误,会有以下提示:
STM32 技术开发手册
www.ing10bbs.com
图 16-43 低压错误警报
这是驱动板电源总线低压报警,在警报解除之前是不会有下面那一行“Press
‘Key’ to return to menu”出现的,所以必须先检查驱动板电源电压然后才能按 KEY1。
这是只能按 Key1 来解除警报。
STM32 技术开发手册
www.ing10bbs.com
第17章 YS-F1Pro 的 FOC4.3 移植
17.1 FOC4.3 MC library 文件目录
首先,要移植例程必须先要有 ST 提供的 FOC4.3 电机控制例程。
ST 官网下载地址:
http://www.st.com/content/st_com/en/products/embeddedsoftware/mcus-embedded-software/stm32-embedded-software/stm32-standardperipheral-library-expansion/stsw-stm32100.html
或者从我们提供的资料里面找到 4.x.zip 文件:
图 17-1 FOC 4.3 压缩包
STM32 技术开发手册
www.ing10bbs.com
解压就可以得到 FOC4.3 的软件开发工具包(SDK)。安装过程就直接下一步
就好了,并不需要有什么特殊值得注意的事情,不过建议安装路径不要选在系统
盘,因为 window 对系统盘是有保护的,在里面进行文件读写操作会造成不必要
的警报,如果已经安装到系统盘的也可以把工程文件复制到其他地方去然后再操
作。设置下载安装之后在安装目录可以看到有 3 个文件夹。
图 17-2 FOC SDK 文件夹目录
从上到下 3 个文件夹分别是:STM32 PMSM FOC Lib(库的核心内容),STMCWB
(ST Motor Control workbench 俗称 foc 的上位机软件),ST Motor Profiler(电机
参数分析工具)。除了库源码之外,另外两个是 PC 端的辅助软件。其中 Motor
Profiler 从功能上看貌似只适配 ST 自己提供的电机控制板,即便使用的是同一个
芯片也无法使用,不过这个并不影响我们将 FOC 移植到 YS-F4Pro 上面。
STMCWB 则是一个电机控制软件,可以使用它来设置电机运行的参数,自动
生成参数文件,添加到工程里面就可以直接使用。还可以连接开发板在线调试运
行参数。
STM32 技术开发手册
www.ing10bbs.com
图 17-3 ST Motor Control Workbench
下面详细讲解 STM32 PMSM FOC LIB 文件夹里面的内容,STM32 PMSM
FOC LIB 文件夹里面同样也是 3 个文件夹:
图 17-4 STM32 PMSM FOC LIB 文件夹目录
Common 是外设标准库的源码,标准库是 ST 很早就推出的固件库,目前已
经停止更新,正在主推 HAL 库,但是最新出的 FOC4.3 却还是使用标准库。
Docs 是文档说明,包括帮助文档,用户手册,开发手册,快速使用说明等
等,还有一个 FAQ 文档解释常见的问题。不过全是英文,有能力的可以去看看,
从这些手册里面是可以获得很多官方提供的有效信息。特别是针对 FOC PMSM 固
件库的开发帮助手册,里面有电机控制库的函数原型。
Web 里面的内容就是我们想要的 FOC 源码还有工程文件,这里面也是需要
重点介绍的内容
STM32 技术开发手册
www.ing10bbs.com
图 17-5 FOC4.3 源代码工程目录
Project st 提供的例程,里面包括有 F0,F1,F3,F4 系列的 Keil 和 IAR 的工程文
件,MDK 的工程文主要集中在 MDK-ARM 文件夹中。这里主要 keil 工程为例介绍
移植到 YS-F4Pro 的方法。其中,UserProject 文件夹里面则是有 FOC 的应用工程。
MC_Library_Compiled 则是 FOC 库的不开源部分,里面是 st 提供的 lib 文件,建
立工程的时候需要添加进去。
STM32 技术开发手册
www.ing10bbs.com
图 17-6 MDK Project 文件夹
Web 下面的 MCApplication、MCLibrary、SystemDrivePamas、UILibrary 就
是 FOC 库开源的核心代码。
STM32 技术开发手册
www.ing10bbs.com
17.2 Keil MDK 软件工程目录
这里只是一个移植指导文档,并不会对 FOC 库的内容进行过多讲解,只会讲
述需要改动的那部分内容,所以下面的章节的内容有可能并不会过多的解释原因。
17.2.1 试编译官方例程
在移植之前,先编译一下 st 提供的例程,熟悉一下操作。
首先打开 st 提供的辅助软件,STMCWB(ST Motor Control Workbench)所设
置好的文件,具体路径在 Utilities 里面,如下图:
图 17-7 STMCWB 文件
选一个 STM32F10x 系列的工程文件,注意这里选的是 STM32100B,意思是使
用的主控芯片是 STM32F10x 系列。这个文件是已经配置好参数的文件,适配 st
提供的开发板和驱动板还有电机,并不适合我们自己的板子和电机,所以现在并
不需要做任何改动,只需要点击生成就可以了。
STM32 技术开发手册
www.ing10bbs.com
图 17-8 STMotor Control Workbench 界面
点击之后会在 SystemDriveParams 文件里面生成 4 个头文件,这 4 个头文件
包含了有在控制台里面设置好的参数。
现在要打开 keil 的工程文件,文件路径如下:
安装路径:\FOC SDK\v4.3.0\STM32 PMSM FOC LIB\Web\Project\MDK-ARM
打开工作台工程文件,名字是 STM32F10x_Workspace.uvmpw。这是一个工作
台工程,里面包含有两个 Project,一个是电机的库工程(MC Library),一个是应
用工程(UserProject)
。
STM32 技术开发手册
www.ing10bbs.com
图 17-9 工程文件路径
STM32 技术开发手册
www.ing10bbs.com
图 17-10 工程文件目录
库工程一般情况下时不需要改动的,所以这里不做介绍,可以待成功驱动
PMSM 电机之后再来看这部分内容。在第一次打开的时候默认是 UserProject 是
当前活动工程,需要将 LibraryProject 改为当前活动工程,看下图。然后点击编译
按钮(或者 F7)。
STM32 技术开发手册
www.ing10bbs.com
图 17-11 设置为当前活动工程
然后将 UserProject 改为当前活动工程,按照下图设定当前编译目标是
STM3210E-EVAL。最后编译,如果有 st 提供的电路板和电机也可以按照这个方法
直接下载测试。
图 17-12 更改 Target
17.2.2 重新建立工程文件夹
st 提供的例程里面有很多的工程文件,因为 st 需要兼容其他系列的芯片,
但是我们可能并不需要那么多的工程,可能只需要 F4 的例程或者 F1 的例程就足
STM32 技术开发手册
www.ing10bbs.com
够了,并且 st 的例程里面的文件目录也不符合我们的习惯,所以这里就需要在
原有的例程基础上进行修改移植,最终使得整个工程文件夹符合我们的使用习惯。
利用现有的工程模板将会方便很多,可以省掉我们添加标准库的时间。在现
有的 F4 标准库例程的文件夹里面新建一个文件夹用于存放 FOC 库文件,我们将
其命名为 STM32_PMSM_FOC_LIBv4.3。
图 17-13 新建库目录
然后我们可以把 st 的例程里面的库文件复制过来,文件夹里面的内容跟 FOC
库的内容是一致的,都可以在 FOC SDK\v4.3.0\STM32 PMSM FOC LIB\Web 文件夹
里面找到,其中 MC_Library_Compiled 是在\Web\Project\MDK-ARM\里面。
图 17-14 MC 库源码文件目录
STM32 技术开发手册
www.ing10bbs.com
图 17-15 从例程复制源文件
main.c 等文件也是从 st 提供的例程那里复制过来的,把它放在自己的工程的
User 文件夹里面。
图 17-16 复制 main.c 等文件
Bsp 文件夹里面的都是板载设备的源代码,包括搭配有 YS-F1Pro 的液晶屏和
按键的驱动代码。
STM32 技术开发手册
www.ing10bbs.com
然后打开 Keil MDK,这里需要两个工程文件,一个是用于生成 lib 的工程,一
个是应用工程,利用现有的工程模板可以省掉一个,现在还需要另一个工程文件,
点击 Project->New uVision Project,新建一个工程,新建工程的步奏这里省略,就
设置路径,主控芯片选择 STM32F103ZGT6 就可以了。
图 17-17 新建工程
新建多工程工作台,将刚才的两个工程添加进去。
图 17-18 新建多工程工作台
STM32 技术开发手册
www.ing10bbs.com
图 17-19 添加工程进工作台
1. 建立库工程
在工作台中选择其中一个工程作为库工程,用于生成 lib 文件。将选择的库
工程设定为当前活动工程,然后按照 st 的例程的文件分组将文件目录组添加进
去。看下图:
STM32 技术开发手册
www.ing10bbs.com
图 17-20 设置为当前活动工程
图 17-21 添加文件目录组
STM32 技术开发手册
www.ing10bbs.com
理论上名字是可以随意的,但是为了更好的对比 st 例程,所以直接与例程一
致。然后对比着 st 的例程将每个分组的文件添加进去,每个分组的名字就是源
文件所在的路径。只需要对比着 st 例程就可以找到文件添加到工程里面。
图 17-22 根据路径添加源文件
不过其中 MCLibrary_conf 的内容是在 MCLibrary 文件夹根目录里面,而
MCLibrary_lib_single 的内容是在
MC_Library_Compiled-> RVMDK_MCLIB_OBJS_SINGLE 里面。MCLibrary_lib_dual
是双电机才会需要用到,并不是所有的源文件都需要添加进工程。
添加进去之后还不够,还需要设置头文件的路径,和宏定义。
STM32 技术开发手册
www.ing10bbs.com
图 17-23 设置宏定义
STM32 技术开发手册
www.ing10bbs.com
图 17-24 设置头文件路径
全部设置好了之后,在 Output 选项卡里面点击 Select Folder for Objects…
选 择 保 存 路 径 为 Project\MDKARM(uV5)\ STM32F4xx_SD , 然 后 选 择 Create
Library..然后编译,编译完之后会在刚才选择的输出文件夹里面生成一个 lib 文件,
这个文件会在应用工程里面用到。
图 17-25 输出.lib
STM32 技术开发手册
www.ing10bbs.com
2. 建立应用工程
应用工程是跟库工程再同一个工作台里面的,要改动的时候需要先把应用工
程设定为当前活动工程,这里的应用工程就是上一步说的现有的工程模板。步奏
很简单,就是同样重复上一步的步奏,添加代码文件到工程里面。这里只给出两
个工程的对比,下图右边都是 st 提供的例程的工程文件分组,左边则是新建的
工程分组,将右边的红色框起来的内容添加到左边,其中 Project 的添加到左边
的 User 里面。这里说的内容除了分组以外还有就是里面的 .c 和.h 文件。
System&drive params 分组里面的内容可以不把名字带有 2 的文件添加进来,因
为名字带有 2 的都是双电机的第二电机的参数设置。
图 17-26 复制库的文件目录
值得注意的是有一部分的文件并不是那么容易找到的,很容易就找不到,这
个时候可以使用 window 的搜索文件功能,例如下图的 stm32f10x_MC_it.c 文件,
可以直接在 st 的例程文件夹里面使用 Ctrl+F 快捷键,然后输入文件名就可以找
到,然后对着文件名字右击,跳转到文件所在位置,就知道这个文件在哪了。
STM32 技术开发手册
www.ing10bbs.com
图 17-27 个别文件
图 17-28 搜索文件位置
将文件添加进入工程之后,还需要设置头文件路径和宏定义。
STM32 技术开发手册
www.ing10bbs.com
图 17-29 添加宏定义和头文件路径
17.2.3 移植液晶和触摸屏按键
首先把 ascii.h、bsp_lcd.c、bsp_lcd.h、bsp_touch.c 这五个文件复制到工程文
件夹里面,然后在添加进工程。
图 17-30 将 LCD 和触摸屏驱动添加到工程文件夹
STM32 技术开发手册
www.ing10bbs.com
图 17-31 将 LCD 和 KEY 添加到工程 bsp 目录
st 的例程里面,LCD 的驱动函数是在 LCDVintage_UserInterfaceClass.c 文件里
面,在 keil 中打开这个文件,将第 40~46 行删掉,因为那是 st 出的评估板的内
容,应用到这个程序是不适应的。
代码 17-1 包含的头文件
01 #if (defined(USE_STM32303C_EVAL) || defined (P_NUCLEO_IHM001))
02 #include "stm32f30x.h"
03 #include "stm32303c_eval.h"
04 #include "stm32303c_eval_lcd.h"
05 #else
06 #include "stm32_eval.h"
07 #endif
改为
代码 17-2 修改后包含的头文件
45 #include "stm32f10x.h"
46 #include "lcd/bsp_lcd.h"
47 #include "touch/bsp_touch.h"
在 364~374 行,将
代码 17-3 液晶初始化和按键初始化
01 /* Initialize the LCD */
02 LCD_HW_Init();
04 LCD_Clear(White);
06 LCD_SetBackColor(White);
07 LCD_SetTextColor(Black);
09 /* Initialize Joystick */
10 STM_EVAL_JOYInit();
改为
代码 17-4 修改后的液晶初始化函数
01 /* 初始化 3.5 寸 TFT 液晶模组,一般优先于调试串口初始化 */
02 BSP_LCD_Init();
03
04 LCD_Clear(0,0,480,280,WHITE);
STM32 技术开发手册
www.ing10bbs.com
05
06 LCD_SetBackColor(WHITE);
07 LCD_SetTextColor(BLACK);
然后使用快捷键 Ctrl+H 调出 Replace 窗口,在 Find what 栏填上 Red,在 Replace
with 栏填上 RED,就是将小写字母换成大写字母而已。Look in 选择 Current
Document,Find options 选择 Match whole word.最后点击 Replace All。然后重复
这个步奏将 Blue 替换为 BLUE。
在 1454~1459 行,将
代码 17-5 显示字符
1454 uint16_t xpos = 16*Column;
1455 if (LCD_GetXAxesDirection() == LCD_X_AXES_INVERTED)
1456 {
1457
xpos = 320-xpos;
1458 }
替换为:
代码 17-6 修改后的显示字符代码
1453 LCD_DisplayChar(Line, 12*Column, Ascii);
在 bsp_lcd.c 文 件 里 面 找 到 BSP_LCD_Init() 函 数 , 在 函 数 返 回 之 前 加 入
LCD_BK_ON();函数打开 LCD 的背光,如果已经有了这个语句,那可以忽略这个步
奏。
将 1591~1658 行的 JOY_Pressed()函数全部删掉。然后将下面的 KEYS_Read()
整个函数替换为触摸屏读取坐标读取函数:
代码 17-7 按键读取函数
01 uint8_t KEYS_Read ( void )
02 {
03
uint16_t x=0xFFFF,y=0xFFFF;
04
InputKey touch_key_value=KEY_NONE;
05
06
if (XPT2046_EXTI_Read()!=XPT2046_EXTI_ActiveLevel) {
07
bPrevious_key = NOKEY;
08
return NOKEY;
09
}
10
11
/*获取点的坐标*/
12
XPT2046_Get_TouchedPoint(&y,&x);
13
if ((x==0xFFFF)||(y==0xFFFF)) {
14
bPrevious_key = NOKEY;
15
return NOKEY;
16
}
17
x=480-x;
18
if ((y>250)&&(y<320)) {
19
if (x>408)
/* 无触摸 */
STM32 技术开发手册
www.ing10bbs.com
20
touch_key_value=KEY_USER_BUTTON2;
21
else if (x>340)
22
touch_key_value=KEY_USER_BUTTON1;
23
else if (x>272)
24
touch_key_value=KEY_JOYSTICK_RIGHT;
25
else if (x>204)
26
touch_key_value=KEY_JOYSTICK_LEFT;
27
else if (x>136)
28
touch_key_value=KEY_JOYSTICK_DOWN;
29
else if (x>68)
30
touch_key_value=KEY_JOYSTICK_UP;
31
else
32
touch_key_value=KEY_JOYSTICK_SEL;
33
} else {
34
bPrevious_key = NOKEY;
35
return NOKEY;
36
}
37
38
/* "RIGHT" key is pressed */
39
if (touch_key_value==KEY_JOYSTICK_RIGHT) {
40
if (bPrevious_key == RIGHT) {
41
return KEY_HOLD;
42
} else {
43
bPrevious_key = RIGHT;
44
return RIGHT;
45
}
46
}
47
/* "LEFT" key is pressed */
48
else if (touch_key_value==KEY_JOYSTICK_LEFT) {
49
if (bPrevious_key == LEFT) {
50
return KEY_HOLD;
51
} else {
52
bPrevious_key = LEFT;
53
return LEFT;
54
}
55
}
56
/* "SEL" key is pressed */
57
if
((touch_key_value==KEY_JOYSTICK_SEL)||(touch_key_value==KEY_USER_BUTTON1)||(touch_key_value==KEY_
USER_BUTTON2)) {
58
if (bPrevious_key == SEL) {
59
return KEY_HOLD;
60
} else {
61
if ( (TB_DebounceDelay_IsElapsed() == FALSE) && (bKey_Flag & SEL_FLAG == SEL_FLAG) ) {
62
return NOKEY;
63
} else {
64
if ( (TB_DebounceDelay_IsElapsed() == TRUE) && ( (bKey_Flag & SEL_FLAG) == 0) ) {
65
bKey_Flag |= SEL_FLAG;
66
TB_Set_DebounceDelay_500us(100); // 50 ms debounce
67
} else if ( (TB_DebounceDelay_IsElapsed() == TRUE) && ((bKey_Flag & SEL_FLAG) ==
SEL_FLAG) ) {
68
bKey_Flag &= (uint8_t)(~SEL_FLAG);
69
bPrevious_key = SEL;
70
return SEL;
71
}
72
return NOKEY;
73
}
74
}
75
}
76
/* "UP" key is pressed */
77
else if (touch_key_value==KEY_JOYSTICK_UP) {
78
if (bPrevious_key == UP) {
79
return KEY_HOLD;
STM32 技术开发手册
www.ing10bbs.com
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 }
} else {
bPrevious_key = UP;
return UP;
}
}
/* "DOWN" key is pressed */
else if (touch_key_value==KEY_JOYSTICK_DOWN) {
if (bPrevious_key == DOWN) {
return KEY_HOLD;
} else {
bPrevious_key = DOWN;
return DOWN;
}
}
/* No key is pressed */
else {
bPrevious_key = NOKEY;
return NOKEY;
}
STM32 技术开发手册
www.ing10bbs.com
第18章 YS-F1Pro 的 FOC4.3 例程调试和修改参数
18.1 STMCWB 的简介
打开 STMCWB,点击 NewProject,然后点击 OK,就可以使用这个上位机软
件,这是一个 PC 端的代码生成工具,不过只会生成关于电机控制固件库的参数
头文件(.h)。这个上位机软件可以与控制板串口连接,直接调节运行参数。
图 18-1 设置运行参数
在下方是 Log Bar,左侧是 Variable Table,这是对工程中重要的变量数值进
行汇总显示,双击可以打开对应的设置页面进行修改,可以在最后检查设置的参
数。右边则是 Log Message,提示一些已执行的操作或者错误。
STM32 技术开发手册
www.ing10bbs.com
图 18-2Varible Table
点开上位机的 Monitor 功能,可以设置串口号,波特率,然后点击 Connect
就可以连接 YS-F1Pro,前提是 FOC4.3 已经移植到 YS-F1Pro 上面。
图 18-3 点开 Monitor 功能
图 18-4 STMCWB 的 Monitor 功能
Monitor 这个功能界面里左边是状态监控,包括有错误状态,和速度监控。
绿色表示没有错误,橙色表示出现错误但已经被解决,红色表示出现错误没有被
解决;右边则是电机控制,使用不同的按钮有对应的功能,可以有停止,启动,
STM32 技术开发手册
www.ing10bbs.com
错误应答;中间部分就是监控面板,basic 面板显示主要的应用寄存器并允许修
改电机速度,Advanced 面板有总线电压,温度,电机功率和测量速度,目标速度,
也可以设置多个寄存器来控制电机;Registers 显示所有可用的寄存器,可以读取
和更改;Configuration 则是可以修改从 STMCWB 设置的功能,例如可以设置使用
哪一种反馈方式测量速度,总线电压,速度范围等。
图 18-5 Monitor 界面
还可以通过 Plotter(绘图),看到目标速度和测量的速度变化趋势,比较方便
的调试 PID 参数
STM32 技术开发手册
www.ing10bbs.com
图 18-6 Plotter 功能
18.2 电机参数
在使用 STMCWB 软件之前,我们必须获取电机的详细参数,因
为在软件中是需要的,关于电机的参数是直接跟电机厂家拿的,所以
当我们需要调试自己电机时候必须找电机厂家拿资料,当然部分参数
我们也可以通过仪器仪表测量得到。
下面我们贴出我们自己的电机参数,见表格 18-1,后面的演示都是以该电
机参数为例。
STM32 技术开发手册
www.ing10bbs.com
表格 18-1 PMSM 电机参数
42 永磁同步电机(PMSM)参数
供电电压
24 V
额定功率
63W
额定力矩
0.2 N.M
峰值力矩
0.6 N.M
额定转速
3000 RPM
额定电枢电流
3.13 A
力矩系数
0.057 N.M/A
反电势系数
4.13 V/KRPM
磁极
8(4 对极)
编码器
1000 线
相电阻
0.89±10%Ω
相电感
0.62±20%mH
18.3 上位机参数设置
这里说的上位机就是指 STMCWB(ST Motor Control Workbench )。打开
STMCWB,然后选择 New Project,这一页的参数都不是特别重要,可以直接使用
默认就好了。
STM32 技术开发手册
www.ing10bbs.com
图 18-7 新建 STMCWB 工程
然后可以看到有 4 个方面的内容需要设置的,有 Motor、Power Stage、Drive
Management、Control Stage。下面将一个一个的讲解如何设置。
1. Motor
这一个是设置电机的参数的,一般这些参数都是可以在买电机的时候由供应
商提供的。这里所用到的电机是 PMSM 电机,电机参数设置如下:
STM32 技术开发手册
www.ing10bbs.com
图 18-8 设定电机的参数
这个参数如果不跟换电机是不需要改动的。
2. Power Stage
这里设置电机电源的参数,这里给出的电路图就是驱动板上的生成 SVPWM
的电路。字体或者复选框有打“√”的就是有使用到的功能,可以点击进去设置
相应的参数。
AC Input Info
我们使用的是直流电源,所以 AC Input Info 这一项是不需要过多的关注,只
需要有一个比较合适的范围就行了,具体的设置可以看下面的图片。
STM32 技术开发手册
www.ing10bbs.com
图 18-9 设置电机的 AC 电源
Rated Bus Voltage Info
这个是总线电压范围,也就是我们使用的电机电源电压范围。Nominal voltage
设置为我们使用的电机的电压就好了。
STM32 技术开发手册
www.ing10bbs.com
图 18-10 设置电机总线上的电压
Bus Voltage Sensing
这里是总线电压检测,检测总线电压是使用分压电阻采样,这里的电阻是跟
驱动板的设计有关,设置的时候一定要根据驱动板原理图来设置。
STM32 技术开发手册
www.ing10bbs.com
图 18-11 设置总线电压采样电阻
Phase U Drive
这里是设置 3 相高端驱动信号和低端驱动信号的极性按照下图设置就好了。
设置一个,其余的一样。
STM32 技术开发手册
www.ing10bbs.com
图 18-12 设置 U 相驱动信号极性
Power Switches
这里设置开关管的参数,包括最小的死区时间,最大的开关频率。
图 18-13 设置开关管的特性
Temperature Sensing
这里设置温度传感器的参数,这些参数是根据热敏电阻来设置的,下半部分
就是关于温度保护的设置,包括使能和温度上限设置。
STM32 技术开发手册
www.ing10bbs.com
图 18-14 设置热敏电阻特性
Over Current Protection
这里设置过流保护,这里的过流保护是根据驱动板上的硬件层上的过流保护,
具体的电路可以去看驱动板的原理图。这里设置比较器电压为 0.5V。
Current Sensing
这里是电流的传感器的设置,选择电流采样方式为 3 相电阻采样,采样电阻
为 0.05ohm,然后 Calculate 页面计算放大倍数。
STM32 技术开发手册
www.ing10bbs.com
图 18-15 设置电流传感器参数
放大网络的设置,可以参考驱动器原理图,采样电阻的功率是 1/2W。
图 18-16 设置放大网络的电阻
3. Drive Management
这一项有六项参数需要设置,包括速度位置反馈,驱动设置,传感器使能和
硬件保护,启动参数,附加特性,人机交互方式。
STM32 技术开发手册
www.ing10bbs.com
Speed/Positon Feedback Management
这里最主要设置反馈方式,我们使用的 PMSM 是具有传感器和编码器的,
一般只需要一个就可以,但是 FOC 允许使用辅助传感器。下面的图片是以编码器
为例,
图 18-17 编码器为主传感器
STM32 技术开发手册
www.ing10bbs.com
Drive Settings
驱动参数设置,这一项可以设置 PWM 的输出方式,输出频率空闲状态的关
高端和低端的状态。还有电机默认参数,默认为速度模式,速度 1000RPM,实际
上,速度的设置是有一定的分辨率的,当设置为 1000RPM 的时候,实际只是设
置为 996RPM。下半部分则是关于 PID 调节器的参数设置,按照下图设置就好了。
图 18-18 驱动参数设置
Sensing Enabling and Firware Protections
这里设置驱动器上的传感器,可以单独使能过压或者低压保护。同时左下角
有温度传感器和 AC 输入设置,不过这些都是在之前就设置好了的。
STM32 技术开发手册
www.ing10bbs.com
图 18-19 使能传感器和硬件保护
Start-up Parameters
在这一个项目里面,如果选择的反馈方式不同,那么这一项可以设置的
参数也会不一样,现在已编码器为例,所以这里是编码器校准。
图 18-20 启动参数
Additional Features and PFC settings
附加特性这一项先不填。
STM32 技术开发手册
www.ing10bbs.com
图 18-21 附加特性功能
User Interface Add-on
人机交互界面,现在先选择使用串口跟上位机通信,还可以选择使用 LCD 进
行脱机调试,所以两个都选了。
图 18-22 人机界面接口
STM32 技术开发手册
www.ing10bbs.com
4. Control Stage
这一项是设置主控制器的参数,也就是我们的 YS-F1Pro 的参数配置。
MCU and Clock Frequency
选择 STM32F103 大容量系列的芯片,时钟源选择外部 8MHz 晶振,CPU 的主
频选择 72Mhz,电源电压是 3.3V
图 18-23 设置主控芯片和主频
Analog Input and Protection
这里选择模拟输入引脚,也就是 ADC 引脚分配了,YS-F4Pro 使用的是无
刷电机接口 2,所用到的 ADC 引脚都在这个接口上面。
Phase current feedback 这是相电流反馈通道。过流保护是可选的,如果
选择使用 External Protection,则表示选择了在 Power Srtag 页面设置的过流
保护,如果选择 No Protection,则表示不需要过流保护。在 Pin map 里是可
以选择 U、V、W 三相的采样电阻所连接的 ADC 通道。这些通道在设计驱动
板的时候就已经确定好了的,按照下图去设置就行了。采样时间选择 3 个 ADC
时钟周期。
STM32 技术开发手册
www.ing10bbs.com
图 18-24 设置采集相电流的 ADC 通道和引脚
Bus voltage feedback 这里选择采样电源总线电压反馈通道。采样周期是 28
个 ADC 时钟周期,外设选择 ADC1。
图 18-25 采集总线电压的 ADC 通道和引脚 ADC 通道和引脚
Temperature feedback 这里选择温度传感器的 ADC 通道。采样周期是 28 个
ADC 时钟周期,使用 ADC1。
图 18-26 热敏电阻的 ADC 通道和引脚
DAC Functionality 则是设置 DAC 输出通道的
STM32 技术开发手册
www.ing10bbs.com
图 18-27 功能输出引脚
Digital I/O
这里设置了数字信号引脚,包括定时器脉冲输出引脚,和编码器捕获引脚,
串口通信引脚。
YS-F4Pro 使用了 Tim1 作为脉冲控制定时器,TIM3 做编码器输入。
USART1 与上位机通信。
图 18-28 配置数字信号引脚
User Interface
这一部分的内容在 Drive Management 已经设置过了。
全部设置好了以后就可以点击保存,将其保存在我们的工程目录里面。这样每一
需要更改参数的时候可以直接打开修改。
STM32 技术开发手册
www.ing10bbs.com
图 18-29 保存 STMCWB 文件
保存了之后还需要设置生成的头文件路径。设置为库文件目录的
SystemDriveParams 里面,这是一个相对路径,前面是有.\的代表当前路径。这
样每次生成的头文件都会替换掉之前的文件,并且是直接就存放在库里面.
图 18-30 设置 STMCWB 输出路径
前面的都弄好了之后就可以点击 Generation,生成 4 个头文件。
STM32 技术开发手册
www.ing10bbs.com
图 18-31 生成参数文件
打开 Keil 工程,点开 System&Drive Params 目录下的 Control Stage Paramters.h、
Drive paramters.h、PMSM motor Parameters.h 或者 Power stage parameters.h 任意
一个文件都可以在文件的开头看到@data 会有这个文件的最近修改日期,就是
使用上位机点击 Generation 的时间,说明生成的文件已经成功的添加进工程里
面。到目前为止,移植已经成功了,现在可以编译程序,然后下载到 YS-F1Pro 上。
18.4 FOC4.3 上位机调试电机参数
首 先 将 keil 工 程 编 译 下 载 到 YS-F1Pro , 然 后 打 开 ST Motor Control
Workbench->Monitor 功能。点击 connect 连接 YS-F1Pro。
STM32 技术开发手册
www.ing10bbs.com
图 18-32STMCWB 与 YS-F4Pro 成功连接
点击右边的 Start Motor 按钮,然后通过 Basic 页面的 Measured Speed(rpm)
或者 Plotter 功能观察电机速度运动规律,在 Advanced 的 PIDGains 里面修改 Kp
和 Ki 参数,直到得到自己满意的参数为止,就可以返回到 Drive Management 的
Drive Settings 那里修改成刚才调试 PID 参数,最后生成头文件保存到工程里面再
烧写一次就可以了。如果调试过程发生错误,左边的错误警告灯亮了,这个时候
部分情况是可以通过点击右边的 Fault Ack 按钮应答错误,然后继续调试,如果
不行就要重新启动控制板或者重新烧写程序。
STM32 技术开发手册
www.ing10bbs.com
图 18-33 在 Advanced 里设置 PI 参数和目标值
图 18-34 重新设置 PI 参数
最后生成头文件,再重新编译下载就可以了。
18.5 使用液晶屏脱机调试参数
如果不想使用上位机来调整参数,可以使用液晶屏来进行操作,完整移植之
后的显示效果如下图:
STM32 技术开发手册
www.ing10bbs.com
图 18-35 FOC4.3 运行状态液晶界面
第一第二行就是 FOC 库版本号,底下就是按键的标志,表示 KEY1 按键就是
对应着程序中的 SEL 按键,KEY2 对应着 UP 按键,有过实际操作再阅读源码的时
候可以对比着看就知道哪个按键有什么功能了。如同当前界面显示,目前正在速
度模式下,目标值为 996(rpm),测量值为 990(rpm)。
刚开机时,在速度模式下,按下 KEY1 电机就会转动,Measured 就是实际测
量得到的电机速度值,Target 就是在上位机设定的目标速度值,单位是(rpm)。
通过其他 4 个按键可以有不同的操作,字体为红色的时候,表示当前已经选中了
这个选项。
Move 和 Change 是按键的功能,表示如果按下 LEFT、RIGHT 就是 Move,
如果按下 UP、DOWM 就是 Change。如上图中的 Speed control mode,如果要切
换为扭矩模式,就需要按下 KEY2 或者 KEY3。
当需要设置 PID 参数的时候可以通过使用 Move 按键切换到不同的界面,如
下图,在 Speed 界面可以实时调整 PI 参数,通过 Move 按键是的 P 或者 I 的数值
编程红色,然后按下 Change 按键就可以修改,注意这里修改的参数并不会把保
存到 flash 里面,下次重启控制板之后就会丢失数据,所以修改外之后需要重新
使用上位机修改 PID 参数,再编译下载。
STM32 技术开发手册
www.ing10bbs.com
图 18-36 速度模式调节 PI 参数
如果发生了错误,会有以下提示:
图 18-37 低压错误警报
STM32 技术开发手册
www.ing10bbs.com
这是驱动板电源总线低压报警,在警报解除之前是不会有下面那一行“Press
‘Key’ to return to menu”出现的,所以必须先检查驱动板电源电压然后才能按 KEY1。
这是只能按 Key1 来解除警报。
当设置好参数之后,就可以在 STMCWB 里面填好刚才调试得到的参数,生辰
参数头文件,最后编译下载一次,这样下次开机就可以按照设置好的参数去跑电
机了,也可以直接在程序里面做修改。
STM32 技术开发手册
www.ing10bbs.com
第19章 YS-F4Pro 的 FOC v5.0 移植指导
19.1 X-CUBE-MCSDK 开发套件安装
STM32 马达控制软件开发工具套件包括 PMSM FOC 固件库和 ST 马达控制工
作平台(STM32 PMSM FOC SDK 马达驱动固件库的 PC 端 GUI 配置工具)。该软件
包支持 STM32Cube 生态系统,进一步简化在 STM32 上开发高能效电机驱动器的
难度,也减少设计人员的设计时间。
5.0 新版固件库结合 STM32Cube 硬件抽象层(HAL)和底层(LL)架构,简化了电
机驱动电路的开发、定制和调试过程。
在 ST 官网搜索 MCSDK 可有搜索到两个安装包:
图 19-1 在 ST 官网搜索下载
一个后缀带有 FUL 的就是带有全部源码,并且需要注册登录一般都是使用一
个比较全面的一点。在官网需要注册登录才能下载。网上还有很多其他的渠道可
以下载到安装文件。
或者从我们提供的资料里面也可以找到安装文件。
图 19-2 SDK 安装文件
STM32 技术开发手册
www.ing10bbs.com
安装过程都是常规的操作,这里不做介绍。安装完成之后会有一 Motor
Control Workbench v5.0.1 软件。
下面以上位机来代称 ST Motor Control Workbench(电机控制工作台)软件。
安装完上位机之后还需要安装 STM32CubeMX v4.24 软件,这里强调是 v4.24
版本,因为上位机就是使用 v4.24 的 CubeMx 生成代码工程,所以为了不必要的
警告,如下图,建议安装使用 STM32CubeMX v4.24 软件,同时固件库也是建议
使用 STM32Cube FW_F4 v1.19.0 。如果使用更高版本的软件和固件库也是可以,
只是建议使用匹配的版本更方便而已。
图 19-3 版本不兼容提示
STM32Cube FW_F4 v1.19.0 固件库可以在 CubeMx 里面下载。
图 19-4 下载固件库
STM32 技术开发手册
www.ing10bbs.com
19.2 电机参数
在使用 Workbench 软件之前,我们必须获取电机的详细参数,
因为在软件中是需要的,关于电机的参数是直接跟电机厂家拿的,所
以当我们需要调试自己电机时候必须找电机厂家拿资料,当然部分参
数我们也可以通过仪器仪表测量得到。
下面我们贴出我们自己的电机参数,见表格 19-1,后面的演示都是以该电
机参数为例。
表格 19-1 PMSM 电机参数
42 永磁同步电机(PMSM)参数
供电电压
24 V
额定功率
63W
额定力矩
0.2 N.M
峰值力矩
0.6 N.M
额定转速
3000 RPM
额定电枢电流
3.13 A
力矩系数
0.057 N.M/A
反电势系数
4.13 V/KRPM
磁极
8(4 对极)
编码器
1000 线
相电阻
0.89±10%Ω
相电感
0.62±20%mH
STM32 技术开发手册
www.ing10bbs.com
19.3 Workbench 使用说明
这里只是一个移植指导手册,并不会对 FOC 库的内容进行过多讲解,只会讲
述需要改动的那部分内容,所以下面的章节的内容有可能并不会过多的解释原因。
19.3.1 ST Motor Workbench
图 19-5 初始界面
图 19-6 初始功能
刚打开的上位机这个界面可以选择新建工程(New Project),或者加载已经
存在的工程(Load Project),或者可以使用 Motor Profiler 功能,不过 Motor Profiler
只有 ST 推出的特定的板子才能使用,这里不做介绍。
STM32 技术开发手册
www.ing10bbs.com
图 19-7 选择主控芯片
点击 New Project,在对话框中选择 STM3240G-EVAL,然后点击 OK。这里的
STM3240G-EVAL 是 ST 推出的评估板,与 YSF4-Pro 使用同型号的芯片,所以这里
才能方便移植。
STM32 技术开发手册
www.ing10bbs.com
图 19-8 选择面板
这个是选择面板,显示不同类型的参数(电机,电源,驱动,主控)。
图 19-9 日志记录
日志信息记录,左边是变量栏,记录一些重要设置,右边则是日志信息栏。
记录执行的操作,双击错误/警告信息可以跳转到出错的地方修改设置。
STM32 技术开发手册
www.ing10bbs.com
19.3.2 参数设置
1. 电机参数
在这个界面,点击中间的 M 可以设置电机的参数
图 19-10 电机参数设置
电机参数要如实填写,这些参数不同型号的电机都是不一样的。
STM32 技术开发手册
www.ing10bbs.com
图 19-11 PMSM 电机参数
STM32 技术开发手册
www.ing10bbs.com
图 19-12 PMSM 电机传感器参数
电机传感器参数根据电机所带的传感器来填写,如图就是带编码器和霍尔传
感器的 PMSM 电机。霍尔传感器主要设置
STM32 技术开发手册
www.ing10bbs.com
2. 电源参数
图 19-13 设置驱动板的参数
这里只是设置驱动板(电机电源)的参数,这个要根据驱动板原理图来
设置。图中,每个方框白色文件就是代表可以设置的地方。
AC Input Info
这是交流电源输入信息设置,我们的驱动板是使用直流电源,所以这里
可以不用理会。
Rated Bus Voltage Info
总线电压设置,可以分别设置最小额定电压,最大额定电压和正常运行时的
电压。这个是根据驱动板的输入电压范围决定的。
STM32 技术开发手册
www.ing10bbs.com
图 19-14 设置总线电压范围
Bus Voltage Sensing
总线电压传感是使用分压电阻检测电压,板载分压电阻是 2 个 80.4KOhm,和一
个 3.9kOhm。
图 19-15 设置采样电阻
STM32 技术开发手册
www.ing10bbs.com
Power Switched
开关管参数设置,主要是死区时间和频率的设置,这个可以从 datasheet 上
看到。最小死区是 200ns,最大开关频率是 50kHz
图 19-16 设置开关管的死区和频率
Driving Signals Polarity
U 相驱动参数设置,主要是设置 PWM 信号的有效电平,高端开关管有效极性是
高电平有效,低端管也是高电平有效。
STM32 技术开发手册
www.ing10bbs.com
图 19-17 设置 PWM 极性
Temperature Sensing
温度传感器参数设置,板载温度传感器是使用热敏电阻。主要是设置如下,
上半部分是硬件参数,包括 V0,T0,温度电压初始化和最大工作温度等,下半部分
则是保护设置。
STM32 技术开发手册
www.ing10bbs.com
图 19-18 设置热敏电阻参数
Over Current Protection
过流保护参数设置:主要设置比较器电压值,板上是使用比较器做保护的,
超出电流限制范围直接禁止输出。
STM32 技术开发手册
www.ing10bbs.com
图 19-19 设置过流保护参数
Current Sensing
电流传感器设置,需要设置电流采样参数。设置采样电阻。然后电机 Calculate。
图 19-20 设置电流采样参数
STM32 技术开发手册
www.ing10bbs.com
然后设置放大网络的参数,这一部分可以参考驱动板原理图。设置后之后点击
Confirm。
图 19-21 放大网络参数设置
STM32 技术开发手册
www.ing10bbs.com
3. 驱动参数
图 19-22 固件参数设置
设置驱动控制电机相关的参数,包括 PWM,PID,速度反馈等
Speed/Position Feedback Management
速度/位置反馈管理,在这里可以设置使用哪个传感器来作为速度位置反馈。
这里以霍尔传感器为例。这里可以设置主传感器和辅助传感器,但我们没有用到
辅助传感器,只有主传感器设置为霍尔传感器。
STM32 技术开发手册
www.ing10bbs.com
图 19-23 设置主速度传感器
Drive Swttings
驱动设置,可以在设置设置 PWM 和 PID 参数。
STM32 技术开发手册
www.ing10bbs.com
图 19-24 驱动设置
左上部分是 PWM 信号设置,可以设置 PWM 频率,空闲状态,跳变边沿插入死
区时间,死区时间根据开关管的最小死区时间来设置。
图 19-25 PWM 参数设置
左下部分是速度监控,设置执行速度是 2ms,也就是 2ms 监控一次。下面的是 PI
参数。左边分子,右边分母。
图 19-26 速度模式 PI 设置
右上部分则是默认设置,开机默认是速度控制模式,目标速度是 100rpm。
STM32 技术开发手册
www.ing10bbs.com
图 19-27 设置默认运行模式
右下部分是扭矩和弱磁控制,执行速度是 1 个 PWM 周期,斩断频率是 2000rad/s,
下方的 PI 参数不能设置是因为没有吧下方的复选框选上,选上即可设置。
图 19-28 设置扭矩模式 PI
Sensing Enabling and Firmware Protections
可以设置传感器的使能和保护参数。这个页面主要是电线电压的保护设置,
这里使能电压保护,并且可以调整保护电压值上限和下限。左下方有温度传感器
和交流保护设置。
STM32 技术开发手册
www.ing10bbs.com
图 19-29 设置电压总线保护范围
Start-up Parameter
启动参数设置,在不同的模式下有不同的参数那需要设置,在霍尔传感器模
式下不需要设置,但是在编码器模式下需要设置校准电角度,无感模式下需要配
置启动电流等。具体可以参考例程里的文件设置
Additional Features and PFC setting
额外的特性功能设置,这些暂时不需要设置。
STM32 技术开发手册
www.ing10bbs.com
图 19-30 特性设置
User Interface
用户接口,主要设置一些人机交互功能,例如 LCD,串口,启动停止按钮。
但是 FOCv5.0 目前暂不支持 LCD 和按键功能,可以期待下一版本的更新。虽然这
里的启动/停止按键可以选择使能,但是实际上是没有任何效果,与 v4.3 不同。
从代码上看就是只有初始化了按键而已,并没有实际的功能。
STM32 技术开发手册
www.ing10bbs.com
图 19-31 人机交互接口选择
4. 主控芯片设置
图 19-32 主控设置界面
STM32 技术开发手册
www.ing10bbs.com
这里主要设置主控芯片参数。就是引脚功能和 ADC 通道选择。
MCU and Clock Frequrency
选择 MCU 型号和时钟频率。这里的芯片型号,如果一开新建工程的时候没
有选择 STM32F40G-EVAL 评估板则不会有 STM32F407I 这个选项。频率定位
168MHz。
图 19-33 选择主控芯片
Analog Input and Protection
模拟输入和保护设置,设置相电流反馈的 ADC 采样通道,左边是 ADC 采样
周期和时间,左边则是对应的管脚,这个要根据实际原理图来设定。
STM32 技术开发手册
www.ing10bbs.com
图 19-34 设置电流采样通道
电压 ADC 采样设置,跟上面一样,可以设置总线电压的 ADC 采样周期和外
设,引脚等。温度的 ADC 采样跟前面一样。
STM32 技术开发手册
www.ing10bbs.com
图 19-35 设置电压采样通道
DAC funtionality
DAC 功能设置,
STM32 技术开发手册
www.ing10bbs.com
图 19-36 DAC 设置
这个 DAC 功能引脚只能是 PA4,PA4 所以不能修改,能改的只有使能和不使能。
Digital I/O
数字输入输出功能,其实也就是 I/O 配置选择,我们选择【无刷电机接口 2】
所以定时器选择 Timer8,这些都是根据主控板引脚电路来选择的。值得注意的是,
这里的霍尔传感器只能选择 TIM2 和 TIM3,但是【无刷电机接口 2】是没有用
TIM2 和 TIM3 的,这里可以先选择 TIM3,在第 3 章会有详细说明怎样修改。
图 19-37 数字 I/O 接口设置
设置完参数之后,点击保存,将会生成一个文件夹出来记录前面设置过的参
数。保存的时候注意保存路径下不能有中文。
STM32 技术开发手册
www.ing10bbs.com
19.3.3 生成工程文件
在保存文件之后,点击工具栏的输出文件夹选项,配置输出的工程类型。
点击旁边的
可以不生成工程文件,只更新.ioc 文件。
图 19-38 选择生成工程
可以任意选择一个工程类型,例程使用的是 MDK-ARM v5 和 IAR。然后点击
,完成之后会在文件路径之下生成一个工程文件夹。与 v4.3 不同,v4.3 是
只生成.h 文件,v5.0 则是生成.ioc 文件,再后台调用 STM32CubeMX 软件生成整
个代码工程。
图 19-39 直接生成工程文件夹
STM32 技术开发手册
www.ing10bbs.com
19.4 修改工程源码
在上一章中,当选择霍尔传感器或者编码器接口的定时器时会发现,可以选
择的定时器与 YSF4-Pro 的接口是不一样,YSF4-Pro 使用的编码器/霍尔传感器接
口是 TIM2/TIM5,但是上位机只能选择 TIM2/TIM3,所以这里需要对代码做修改
才能使用,无感模式因为没有使用接口定时器,所以可以不用修改,直接生成工
程就能使用。在第 2 章中,选择了 TIM3 作为霍尔传感器接口,这里以 TIM3 为
例说明需要修改的地方,这里有两种修改方式,任意一种都可以:
1. 直接修改源码。在源码的基础上将 TIM3 替换成 TIM5,
2. 使用 CubeMx 修改.ioc 文件。在 CubeMX 修改了 TIM3 之后就可以生成使
用 TIM5 作为霍尔传感器接口的工程,但经过测试,CubeMX 不会对 MC
库做改动,所以即便在 CubeMX 上修改了之后还是需要在源码里面做改
动。
19.4.1 直接修改源码
下面给出修改之前的源码和改动之后的代码。下面给出的代码前面的行号都
是对应着实际代码的行号,所以在修改的时候可以以行号为标志,使用 Ctrl+G 快
捷键(Keil)直接跳转到该行。CubeMx 在生成源码的时候会对用户自定义的代码
做删减,但是会对特定的注释之间的代码做保留,像下面的代码就是在特定的注
释中间添加的,这些注释不能删除。
1. main.c
改动之前部分:
53 /* USER CODE BEGIN Includes */
54
55 /* USER CODE END Includes */
修改之后:
53 /* USER CODE BEGIN Includes */
54 #include "mc_api.h"
55 /* USER CODE END Includes */
STM32 技术开发手册
www.ing10bbs.com
改动之前部分:
69 /* Private variables -------------------------------------------------*/
70
71 /* USER CODE END PV */
修改之后:
69 /* Private variables -------------------------------------------------*/
70 TIM_HandleTypeDef htim5;
71 /* USER CODE END PV */
改动之前:
87 /* USER CODE BEGIN PFP */
88 /* Private function prototypes --------------------------------------*/
89
90 /* USER CODE END PFP */
修改之后
87
88
89
90
91
92
/* USER CODE BEGIN PFP */
/* Private function prototypes ---------------------------------------*/
static void MX_TIM5_Init(void);
static void MX_TIM3_DeInit(void);
static void MX_NVIC_BtnStartInit(void);
/* USER CODE END PFP */
改动之前:
130
131
132
133
134
135
136
137
MX_USART1_UART_Init();
MX_MotorControl_Init();
/* Initialize interrupts */
MX_NVIC_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
改动之后:
132
133
134
135
136
137
138
139
140
141
142
143
144
MX_USART1_UART_Init();
// MX_MotorControl_Init();
/* Initialize interrupts */
MX_NVIC_Init();
/* USER CODE BEGIN 2 */
/* 5.0 上位机只能选择 TIM2 or TIM3 做 HALL
这里修改为 TIM5,配合开发板使用*/
MX_TIM3_DeInit();
MX_TIM5_Init();
MX_MotorControl_Init();
MX_NVIC_BtnStartInit(); // 使能按键中断
/* USER CODE END 2 */
改动之前:
STM32 技术开发手册
www.ing10bbs.com
599/* USER CODE BEGIN 4 */
600
601 /* USER CODE END 4 */
602
改动之后:
主要是在这两段注释中间添加以下函数,这两段注释不能删掉。
606 /* USER CODE BEGIN 4 */
607 /**
608 * 函数功能:TIM5 初始化函数
609 * 输入参数:无
610 * 返 回 值:无
611 * 说
明:使用上位机无法使用 TIM5 作为霍尔传感器输入
612 *
只能将 TIM3 改为 TIM5
613 */
614 static void MX_TIM5_Init(void)
615 {
616
617
TIM_ClockConfigTypeDef sClockSourceConfig;
618
TIM_HallSensor_InitTypeDef sConfig;
619
TIM_MasterConfigTypeDef sMasterConfig;
620
621
htim5.Instance = TIM5;
622
htim5.Init.Prescaler = 0;
623
htim5.Init.CounterMode = TIM_COUNTERMODE_UP;
624
htim5.Init.Period = HALL_TIM_PERIOD;
625
htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
626
if (HAL_TIM_Base_Init(&htim5) != HAL_OK)
627
{
628
_Error_Handler(__FILE__, __LINE__);
629
}
630
631
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
632
if (HAL_TIM_ConfigClockSource(&htim5, &sClockSourceConfig) != HAL_OK)
633
{
634
_Error_Handler(__FILE__, __LINE__);
635
}
636
637
sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
638
sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
639
sConfig.IC1Filter = HALL_IC_FILTER;
640
sConfig.Commutation_Delay = 0;
641
if (HAL_TIMEx_HallSensor_Init(&htim5, &sConfig) != HAL_OK)
642
{
643
_Error_Handler(__FILE__, __LINE__);
644
}
645
646
sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC2REF;
647
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
648
if (HAL_TIMEx_MasterConfigSynchronization(&htim5, &sMasterConfig) !=
HAL_OK)
649
{
650
_Error_Handler(__FILE__, __LINE__);
651
}
652
/* TIM5_IRQn interrupt configuration */
653
HAL_NVIC_SetPriority(TIM5_IRQn, 3, 0);
654
HAL_NVIC_EnableIRQ(TIM5_IRQn);
655 }
656 /**
657 * 函数功能:DeInit TIM3
658 * 输入参数:无
659 * 返 回 值:无
660 * 说
明:使用上位机无法使用 TIM5 作为霍尔传感器输入
661 *
只能将 TIM3 改为 TIM5
STM32 技术开发手册
www.ing10bbs.com
662 */
663 static void MX_TIM3_DeInit(void)
664 {
665
/* Peripheral clock disable */
666
__HAL_RCC_TIM3_CLK_DISABLE();
667
668
/**TIM3 GPIO Configuration
669
PC8
------> TIM3_CH3
670
PC7
------> TIM3_CH2
671
PC6
------> TIM3_CH1
672
*/
673
HAL_GPIO_DeInit(GPIOC, M1_HALL_H3_Pin|M1_HALL_H2_Pin|M1_HALL_H1_Pin);
674
675
/* TIM3 interrupt DeInit */
676
HAL_NVIC_DisableIRQ(TIM3_IRQn);
677 }
678 /**
679 * 函数功能:配置 Start/Stop Button 为按键模式
680 * 输入参数:无
681 * 返 回 值:无
682 * 说
明:使用上位机只能配置为事件模式,这里改为中断模式
683 */
684 static void MX_NVIC_BtnStartInit(void)
685 {
686
GPIO_InitTypeDef GPIO_InitStruct;
687
688
HAL_GPIO_DeInit(Start_Stop_GPIO_Port, Start_Stop_Pin);
689
690
/* 重新将 Start/Stop Button 配置为中断模式 */
691
GPIO_InitStruct.Pin = Start_Stop_Pin;
692
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
693
GPIO_InitStruct.Pull = GPIO_NOPULL;
694
HAL_GPIO_Init(Start_Stop_GPIO_Port, &GPIO_InitStruct);
695
696
HAL_NVIC_SetPriority(STARTBUTTON_IRQn, 3, 0);
697
HAL_NVIC_EnableIRQ(STARTBUTTON_IRQn);
698 }
699 /**
700 * 函数功能:按键中断回调函数
701 * 输入参数:GPIO_Pin | 触发中断的引脚
702 * 返 回 值:无
703 * 说
明:启动和停止电机
704 */
705 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
706 {
707
static uint32_t Key_State = 1;
708
709
if (GPIO_Pin == Start_Stop_Pin)
710
{
711
/* 根据上一次的按键状态启动停止电机 */
712
if
(HAL_GPIO_ReadPin(Start_Stop_GPIO_Port,Start_Stop_Pin)==GPIO_PIN_RESET)
713
{
714
if (Key_State == 1)
715
{
716
Key_State = 0;
717
MC_StartMotor1();
718
}
719
else
720
{
721
Key_State = 1;
722
MC_StopMotor1();
723
}
724
}
725
}
726 }
STM32 技术开发手册
www.ing10bbs.com
727
728 /* USER CODE END 4 */
2. main.h
改动之前:
111 /* USER CODE BEGIN Private defines */
112
113 /* USER CODE END Private defines */
修改之后:
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/* USER CODE BEGIN Private defines */
/* 重新定义 HALL Sensor 引脚 */
#undef M1_HALL_H3_Pin
#define M1_HALL_H3_Pin GPIO_PIN_12
#undef M1_HALL_H3_GPIO_Port
#define M1_HALL_H3_GPIO_Port GPIOH
#undef M1_HALL_H2_Pin
#define M1_HALL_H2_Pin GPIO_PIN_11
#undef M1_HALL_H2_GPIO_Port
#define M1_HALL_H2_GPIO_Port GPIOH
#undef M1_HALL_H1_Pin
#define M1_HALL_H1_Pin GPIO_PIN_10
#undef M1_HALL_H1_GPIO_Port
#define M1_HALL_H1_GPIO_Port GPIOH
#define STARTBUTTON_IRQn
EXTI0_IRQn
#define STARTBUTTON_IRQHandler EXTI0_IRQHandler
/* USER CODE END Private defines */
3. stm32f4xx_hal_msp.c
改动之前:
282 if (htim_base->Instance==TIM3)
283
{
284
/* USER CODE BEGIN TIM3_MspInit 0 */
285
286
/* USER CODE END TIM3_MspInit 0 */
287
/* Peripheral clock enable */
288
__HAL_RCC_TIM3_CLK_ENABLE();
289
290
/**TIM3 GPIO Configuration
291
PC8
------> TIM3_CH3
292
PC7
------> TIM3_CH2
293
PC6
------> TIM3_CH1
294
*/
295
GPIO_InitStruct.Pin =
M1_HALL_H3_Pin|M1_HALL_H2_Pin|M1_HALL_H1_Pin;
296
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
297
GPIO_InitStruct.Pull = GPIO_NOPULL;
298
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
299
GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
300
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
301
302
/* USER CODE BEGIN TIM3_MspInit 1 */
303
304
/* USER CODE END TIM3_MspInit 1 */
305
}
修改之后:
282 if (htim_base->Instance==TIM5)
283
{
STM32 技术开发手册
www.ing10bbs.com
284
/* USER CODE BEGIN TIM3_MspInit 0 */
285
286
/* USER CODE END TIM3_MspInit 0 */
287
/* Peripheral clock enable */
288 //
__HAL_RCC_TIM3_CLK_ENABLE();
289
290
/**TIM3 GPIO Configuration
291
PC8
------> TIM3_CH3
292
PC7
------> TIM3_CH2
293
PC6
------> TIM3_CH1
294
*/
295 //
GPIO_InitStruct.Pin = M1_HALL_H3_Pin|M1_HALL_H2_Pin|M1_HALL_H1_Pin;
296 //
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
297 //
GPIO_InitStruct.Pull = GPIO_NOPULL;
298 //
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
299 //
GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
300 //
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
301
302
/* USER CODE BEGIN TIM3_MspInit 1 */
303
__HAL_RCC_TIM5_CLK_ENABLE();
304
GPIO_InitStruct.Pin =
M1_HALL_H3_Pin|M1_HALL_H2_Pin|M1_HALL_H1_Pin;
305
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
306
GPIO_InitStruct.Pull = GPIO_NOPULL;
307
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
308
GPIO_InitStruct.Alternate = GPIO_AF2_TIM5;
309
HAL_GPIO_Init(GPIOH, &GPIO_InitStruct);
310
/* USER CODE END TIM3_MspInit 1 */
311
}
改动之前:
362 if (htim_base->Instance==TIM3)
363
{
364
/* USER CODE BEGIN TIM3_MspDeInit 0 */
365
366
/* USER CODE END TIM3_MspDeInit 0 */
367
/* Peripheral clock disable */
368
__HAL_RCC_TIM3_CLK_DISABLE();
369
370
/**TIM3 GPIO Configuration
371
PC8
------> TIM3_CH3
372
PC7
------> TIM3_CH2
373
PC6
------> TIM3_CH1
374
*/
375
HAL_GPIO_DeInit(GPIOC,
M1_HALL_H3_Pin|M1_HALL_H2_Pin|M1_HALL_H1_Pin);
376
377
/* TIM3 interrupt DeInit */
378
HAL_NVIC_DisableIRQ(TIM3_IRQn);
379
/* USER CODE BEGIN TIM3_MspDeInit 1 */
380
381
/* USER CODE END TIM3_MspDeInit 1 */
382
}
修改之后:
368 if (htim_base->Instance==TIM5)
369
{
370
/* USER CODE BEGIN TIM3_MspDeInit 0 */
371
372
/* USER CODE END TIM3_MspDeInit 0 */
373
/* Peripheral clock disable */
374 //
__HAL_RCC_TIM3_CLK_DISABLE();
375
376
/**TIM3 GPIO Configuration
STM32 技术开发手册
www.ing10bbs.com
377
PC8
------> TIM3_CH3
378
PC7
------> TIM3_CH2
379
PC6
------> TIM3_CH1
380
*/
381 //
HAL_GPIO_DeInit(GPIOC,
M1_HALL_H3_Pin|M1_HALL_H2_Pin|M1_HALL_H1_Pin);
382
383
/* TIM3 interrupt DeInit */
384 //
HAL_NVIC_DisableIRQ(TIM3_IRQn);
385
/* USER CODE BEGIN TIM3_MspDeInit 1 */
386
__HAL_RCC_TIM5_CLK_DISABLE();
387
HAL_GPIO_DeInit(GPIOH,
M1_HALL_H3_Pin|M1_HALL_H2_Pin|M1_HALL_H1_Pin);
388
HAL_NVIC_DisableIRQ(TIM5_IRQn);
389
390
/* USER CODE END TIM3_MspDeInit 1 */
391
}
4. revup_ctrl.c
修改之后:
201 //
206 //
STO_ResetPLL(pHandle->pSNSL);
IsSpeedReliable = STO_IsVarianceTight(pHandle->pSNSL);
254 //
STO_ForceConvergency1(pHandle->pSNSL);
267 //
STO_ForceConvergency2(pHandle->pSNSL);
316 //
STO_ForceConvergency1(pHandle->pSNSL);
这些都是需要注释掉的,实际上这是使用无感模式的时候才会调用这些函数。
5. parameters_conversion_f4xx.h
改动之前:
181
182
183
184
185
186
/********** SPEED FEEDBACK TIMERS SETTING *************/
#define HALL_TIMER
TIM3
#define HALL_RCC_PERIPHERAL
RCC_APB1Periph_TIM3
#define HALL_IRQ_CHANNEL
TIM3_IRQn
#undef HALL_TIMER_REMAPPING
#define HALL_TIMER_REMAPPING NO_REMAP_TIM2
修改之后:
181
182
183
184
185
186
/********** SPEED FEEDBACK TIMERS SETTING *************/
#define HALL_TIMER
TIM5
#define HALL_RCC_PERIPHERAL
RCC_APB1Periph_TIM5
#define HALL_IRQ_CHANNEL
TIM5_IRQn
#undef HALL_TIMER_REMAPPING
#define HALL_TIMER_REMAPPING NO_REMAP_TIM2
修改之前:
233 #define SPD_TIM_M1_IRQHandler TIM3_IRQHandler
234
改动之后:
233 #define SPD_TIM_M1_IRQHandler TIM5_IRQHandler
STM32 技术开发手册
www.ing10bbs.com
234
6. stm32f4xx_it.c
修改之前:
01 /* USER CODE BEGIN 1 */
02
03 /* USER CODE END 1 */
改动之后:
01
02
03
04
05
06
07
08
09
10
11
12
13
/* USER CODE BEGIN 1 */
void STARTBUTTON_IRQHandler(void)
{
/* USER CODE BEGIN EXTI0_IRQn 0 */
/* USER CODE END EXTI0_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(Start_Stop_Pin);
/* USER CODE BEGIN EXTI0_IRQn 1 */
/* USER CODE END EXTI0_IRQn 1 */
}
/* USER CODE END 1 */
至此,所有的改动已经完成,可以编译下载程序测试了。
7. 第二次生成工程源码
每一次使用上位机生成工程文件都会重新生成源码,并不是说每一次生成工
程都需要将以上操作重新做一次,这些操作大部分都是一次性的,第二次使用上
位机生成工程的时候只需要修改一部分就可以了,以下就是第二次生成工程是需
要修改的地方。
main.c
line 133
注释 // MX_MotorControl_Init();
stm32f4xx_hal_msp.c
line 282
TIM3 修改为 TIM5
line 295~300
将 TIM3 相关的内容全部注释掉
line 368
TIM3 修改为 TIM5
line 374~385
将 TIM3 相关的内容全部注释掉
parameters_conversion_f4xx.h
line 182
TIM3 修改为 TIM5
line 183
RCC_APB1Periph_TIM3 修改为 RCC_APB1Periph_TIM5
STM32 技术开发手册
www.ing10bbs.com
line 184
TIM3_IRQn 修改为 TIM5_IRQn
line 233
TIM3_IRQHandler 修改为 TIM5_IRQHandler
使用 keil MDK 编译调试的时候需要注释以下函数(使用 IAR 编译可以不用):
revup_ctrl.c
line 201
STO_ResetPLL()
line 206
STO_IsVarianceTight()
line 254
STO_ForceConvergency1()
line 267
STO_ForceConvergency2()
line 316
STO_ForceConvergency1()
19.4.2 使用 CubeMX 修改设置
在上位机点击生成代码之后,工作流程是这样的:先生成 CubeMx 格式的.ioc
文件,然后在后台调用 CubeMx 再生成可以使用 Keil 打开的工程源码。所以可以
从 CubeMx 入手,修改 TIM3。
图 19-40 上位机生成代码过程
使用 CubeMx 的时候要先退出有道词典。没有说明的地方说明不需要更改。
1. 修改 Pin out
将 TIM5 修改成 Tim3 一样,然后将 TIM3 设置初始化之前的状态,如下图所
示,同时还需要把 TIM3 占用的引脚设置为 Reset_State
STM32 技术开发手册
www.ing10bbs.com
图 19-41 修改 Pinout
2. Configuration
配置 GPIO
这里还需要配置中断引脚 PE0 设置为中断引脚。
STM32 技术开发手册
www.ing10bbs.com
图 19-42 设置为下降沿中断模式
NVIC Configuration
中断使能及中断代码生成,使能按键和 TIM5 的中断,并将它们的优先级设
定为 3,0,如下图所示
STM32 技术开发手册
www.ing10bbs.com
图 19-43 设置中断优先级
STM32 技术开发手册
www.ing10bbs.com
图 19-44 设置使能中断顺序和初始化代码
TIM5 Configuration
参数设置如下:
STM32 技术开发手册
www.ing10bbs.com
图 19-45 设置 TIM5 参数
GPIO 设置如下:
STM32 技术开发手册
www.ing10bbs.com
图 19-46 设置 TM5 GPIO
修改 CubeMx 之后就可以点击
,生成 Keil 工程源码。
3. 修改源码
这里直接给出改动之后的源码:行号与未改动之前一致,可以根据前后的代
码提示找到改动位置。
main.c
123 /* Initialize all configured peripherals */
124
MX_GPIO_Init();
125
MX_ADC1_Init();
126
MX_ADC2_Init();
127
MX_DAC_Init();
128
MX_TIM8_Init();
129
MX_TIM5_Init();
130
MX_USART1_UART_Init();
131
MX_MotorControl_Init();
STM32 技术开发手册
www.ing10bbs.com
这里是调整了 MX_TIM5_Init()函数的位置,需要在 MX_MotorControl_Init()函
数的前面。
603 /* USER CODE BEGIN 4 */
604 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
605 {
606
static uint32_t Key_State = 1;
607
608
if (GPIO_Pin == Start_Stop_Pin)
609
{
610
/* 根据上一次的按键状态启动停止电机 */
611
if
(HAL_GPIO_ReadPin(Start_Stop_GPIO_Port,Start_Stop_Pin)==GPIO_PIN_RESET)
612
{
613
if (Key_State == 1)
614
{
615
Key_State = 0;
616
MC_StartMotor1();
617
}
618
else
619
{
620
Key_State = 1;
621
MC_StopMotor1();
622
}
623
}
624
}
625 }
626
627 /* USER CODE END 4 */
这段代码是新添加进去的,是按键中断回调函数,要放在/* USER CODE BEGIN
4 */和/* USER CODE END 4 */这两段注释中间。
revup_ctrl.c
201 //
206 //
STO_ResetPLL(pHandle->pSNSL);
IsSpeedReliable = STO_IsVarianceTight(pHandle->pSNSL);
254 //
STO_ForceConvergency1(pHandle->pSNSL);
267 //
STO_ForceConvergency2(pHandle->pSNSL);
316 //
STO_ForceConvergency1(pHandle->pSNSL);
只要不是无感模式这些都是需要注释掉的。实际上程序并没有调用这些函数。
parameters_conversion_f4xx.h
181
182
183
184
185
186
/********** SPEED FEEDBACK TIMERS SETTING *************/
#define HALL_TIMER
TIM5
#define HALL_RCC_PERIPHERAL
RCC_APB1Periph_TIM5
#define HALL_IRQ_CHANNEL
TIM5_IRQn
#undef HALL_TIMER_REMAPPING
#define HALL_TIMER_REMAPPING NO_REMAP_TIM2
STM32 技术开发手册
www.ing10bbs.com
233 #define SPD_TIM_M1_IRQHandler TIM5_IRQHandler
这里要将 182 行~185 行和 233 行与 TIM3 相关的语句改为 TIM5。
4. 第二次生成工程源码
使用第二种方法修改的程序,每一次使用上位机生成工程的时候都会重新修
改.ioc 和工程源码。以上第 1,2,3 步时必然是要重复一次的。
STM32 技术开发手册
www.ing10bbs.com
19.5 使用上位机调试电机参数
设置初始参数可以参考 19.3.2 参数设置。
经过上面移植修改之后下载程序到开发板,使用上位机的监控器
可以在
上位机控制电机转动。
图 19-47 监控器连接成功
点击连接,连接成功可以看到当前下载到开发板的电机控制库版本是 5.0.1。
可以点击右边的
或者
按钮来启动/停止电机转动。
程序中设置默认速度值是 1000,可以通过右下的旋钮改变速度。所有操作都只
能在左边的 Status 栏亮绿灯
启动和停止电机。
⚫
如果左边的 Statue 出现下面这种情况:
才可以操作。同样可以使用 KEY1
STM32 技术开发手册
www.ing10bbs.com
图 19-48 错误状态
很有可能就是先给开发板上电,然后再在驱动板上电,这种情况将导致开发
板检测驱动板的电压过低,是属于误报。可以点击右边的
来消除
警报。
⚫
如果出现其他错误,则 Faults 会亮红灯,解除错误之后变黄灯 ,点击
按钮之后灭灯。
⚫
如果不能连接上位机,可以试试按下开发板的复位键,有可能是刚下载完程
序,还没有开始运行。
在 Advanced 页,可以实时调整电机的 PID 参数。
STM32 技术开发手册
www.ing10bbs.com
图 19-49 实时调整参数
如图所示:可以在运行的时候更换运行模式,从 Speed 模式转换成 Torque 模
式,同时可以修改 PID 增益。
调整得到适合的参数之后可以根据 19.3.2 参数设置重新设置参数然后生成
整个代码工程,或者直接在源码里面修改参数。
STM32 技术开发手册
www.ing10bbs.com
19.6 其他速度反馈模式
例程当中使用的是 YS-F4Pro 当中的【无刷电机接口 2】来与无刷电机驱
动板连接。其中,编码器/霍尔传感器接口使用的是 TIM2/TIM5。但上位机控
制工作台却只能选择 TIM2/TIM3 来作为编码器/霍尔传感器接口,所以只能
在生成源码之后对工程文件做修改才能使用,需要修改的内容也就是第 19.4
19.4 节里面已经有详细的说明。
在将 FOC5.0 移植到 YS-F4Pro 的时候,与三种模式可以使用,一种是编码
器模式,也就是将编码器作为反馈,一种是使用霍尔传感器作为反馈,一种
是无感模式。在第 19.4 19.4 节,使用霍尔传感器模式作为样例讲解了工程
移植的注意事项,这里就讲讲其他的模式。
19.6.1 无感模式
首先是无感模式,无感模式很简单,因为根本就不需要修改,使用上位
机生成例程之后直接就可以下载测试。但是在我们的例程里面添加了启动停
止按钮的功能,在原本的上位机中可以选择启动停止按钮,但是实际上代码
里面没有实现任何功能,仅仅是初始化了而已,所以我们例程添加了这个按
钮的具体功能:启动和停止电机。
具体修改如下:
⚫
设置上位机的时候选择 Enable 启动停止功能,并且选择启动停止按钮是
PE0.
STM32 技术开发手册
www.ing10bbs.com
⚫
在 main.h 添加中断宏定义
126 #define STARTBUTTON_IRQn
EXTI0_IRQn
127 #define STARTBUTTON_IRQHandler EXTI0_IRQHandler
⚫
在 main.c 中的注释代码中间/* USER CODE BEGIN ...*/和/* USER CODE
END ... */添加电机控制接口头文件”mc_api.h”,添加位置如下:
53 /* USER CODE BEGIN Includes */
54 #include "mc_api.h"
55 /* USER CODE END Includes */
⚫
在 main 函数中调用初始化按键中断。
132 /* USER CODE BEGIN 2 */
133 MX_NVIC_BtnStartInit(); // 使能按键中断
134 /* USER CODE END 2 */
⚫
添加初始化按键函数定义和中断回调函数,这些函数也是放在注释代码
中间/* USER CODE BEGIN ...*/和/* USER CODE END ... */
01
02
03
04
05
06
07
08
09
10
11
12
13
/* USER CODE BEGIN 4 */
/**
* 函数功能:配置 Start/Stop Button 为按键模式
* 输入参数:无
* 返 回 值:无
* 说
明:使用上位机只能配置为事件模式,这里改为中断模式
*/
static void MX_NVIC_BtnStartInit(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
HAL_GPIO_DeInit(Start_Stop_GPIO_Port, Start_Stop_Pin);
STM32 技术开发手册
www.ing10bbs.com
14
/* 重新将 Start/Stop Button 配置为中断模式 */
15
GPIO_InitStruct.Pin = Start_Stop_Pin;
16
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
17
GPIO_InitStruct.Pull = GPIO_NOPULL;
18
HAL_GPIO_Init(Start_Stop_GPIO_Port, &GPIO_InitStruct);
19
20
HAL_NVIC_SetPriority(STARTBUTTON_IRQn, 3, 0);
21
HAL_NVIC_EnableIRQ(STARTBUTTON_IRQn);
22 }
23 /**
24 * 函数功能:按键中断回调函数
25 * 输入参数:GPIO_Pin | 触发中断的引脚
26 * 返 回 值:无
27 * 说
明:启动和停止电机
28 */
29 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
30 {
31
static uint32_t Key_State = 1;
32
33
if (GPIO_Pin == Start_Stop_Pin) {
34
/* 根据上一次的按键状态启动停止电机 */
35
if
(HAL_GPIO_ReadPin(Start_Stop_GPIO_Port,Start_Stop_Pin)==GPIO_PIN_RESET) {
36
if (Key_State == 1) {
37
Key_State = 0;
38
MC_StartMotor1();
39
} else {
40
Key_State = 1;
41
MC_StopMotor1();
42
}
43
}
44
}
45 }
46
47 /* USER CODE END 4 */
⚫
在 stm32f4xx_it.c 中添加中断响应函数,这些也是放在注释代码中间/*
USER CODE BEGIN ...*/和/* USER CODE END ... */
01
02
03
04
05
06
07
08
09
10
11
12
/* USER CODE BEGIN 1 */
void STARTBUTTON_IRQHandler(void)
{
/* USER CODE BEGIN EXTI0_IRQn 0 */
/* USER CODE END EXTI0_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(Start_Stop_Pin);
/* USER CODE BEGIN EXTI0_IRQn 1 */
/* USER CODE END EXTI0_IRQn 1 */
}
/* USER CODE END 1 */
19.6.2 编码器模式
在 YS-F4Pro 上编码器模式使用的 TIM2,正好上位机可以选择 TIM2 来作为编
码器模式,所以理论上可以不用修改就可以直接使用生成的工程文件编译下载测
试的,但是实际上还是需要修改一部分源码才能正常编译下载。改动如下:
STM32 技术开发手册
www.ing10bbs.com
⚫
选择正确的编码器接口和引脚
⚫
在这个 revup_ctrl.c 文件中将以下函数注释掉。
201 //
206 //
STO_ResetPLL(pHandle->pSNSL);
IsSpeedReliable = STO_IsVarianceTight(pHandle->pSNSL);
254 //
STO_ForceConvergency1(pHandle->pSNSL);
267 //
STO_ForceConvergency2(pHandle->pSNSL);
316 //
STO_ForceConvergency1(pHandle->pSNSL);
这些都只是在无感模式下才会调用的。
⚫
在 main.h 中的 62 行将宏定义 PULSENBR 修改为 3999
01 #define HALF_PWM_PERIOD 2625
02 #define PULSENBR 3999
03 #define ENC_IC_FILTER 5
原来的宏定义是 1599,默认的编码器线数就是 400,4 倍频之后就是 1600。在
上位机修改编码器线数并不能修改这个参数,只能手动修改。例程使用的电机是
1000 线,所以修改为(1000 * 4 - 1)之后就可以了。
⚫
如果需要使用启动停止按钮的功能,可以参考前面的无感模式。
STM32 技术开发手册
www.ing10bbs.com
19.7 其他事项
第 3 章在修改源码的过程中可以使用两种方式,两种方式都需要在
parameters_conversion_f4xx.h 文件中 将 TIM3 修改为 TIM5。
181
182
183
184
185
186
/********** SPEED FEEDBACK TIMERS SETTING *************/
#define HALL_TIMER
TIM5
#define HALL_RCC_PERIPHERAL
RCC_APB1Periph_TIM5
#define HALL_IRQ_CHANNEL
TIM5_IRQn
#undef HALL_TIMER_REMAPPING
#define HALL_TIMER_REMAPPING NO_REMAP_TIM2
233 #define SPD_TIM_M1_IRQHandler TIM5_IRQHandler
这里要将 182 行~185 行和 233 行与 TIM3 相关的语句改为 TIM5。因为这个
文件是电机控制库的参数头文件,所以 CubeMx 软件是不会去改动这个文件的。
导致每一次重新生成工程的时候都需要手动修改这个参数。
其实还有一个一劳永逸的方法,那就是修改模板文件,源码工程中的电机库
也是从模板里面复制过来的,所以修改模板文件之后就可以不用每一次都去修改
这个头文件。
修改方法如下:
⚫
首先找到模板文件的位置。一般情况下是在
C:\Users\<User Name>\STM32Cube\Repository,这是在 WIN10 下的文件
路径,如果是其他系统可能会有不同,但总的来说,这个电机控制库是
跟 CubeMx 软件的固件库放在同一个文件路径下的,可以在 CubeMx 里
面找到这个路径。在这个文件夹里面有一个 MotorControl 文件夹,打开
之后在里面找到 templates 文件夹,找到 parameters_conversion_f4xx.h.ftl
文件,完整的路径如下:
STM32 技术开发手册
www.ing10bbs.com
⚫
可以使用任意的文本编辑器打开这个文件。打开之后,找到要修改的位
置。
一般的文本编辑器都会有搜索功能,所以这里可以使用 Ctrl+F 快捷键搜索
SPEED FEEDBACK TIMERS SETTING 来达到快速定位。图中可以看到使用了判断语
语句,就是会根据在上位机的选择来决定时候会将下面添加到源码工程里面。
现在就将图中三个红点所在的#define 行里面的 TIM3 改为 TIM5,那么以后
每一次使用上位机选择 TIM3 的时候这部分内容都会被改为 TIM5。
STM32 技术开发手册
www.ing10bbs.com
除了上面的地方还有一处,这里同样也是将红点所在的行里面的
TIM3_IRQHandler 改为 TIM5_IRQHandler。
完成上面的步骤之后就可以不需要再手动修改 parameters_conversion_f4xx.h 这
个文件了。
STM32 技术开发手册
www.ing10bbs.com
第20章 YS-F4Pro FOC5.2源码解释
20.1 工程目录结构
利用MotorControl Workbench 5.2.0生成的项目工程,即使使用不同的IDE,工
程文件结构是一样的,下图是以Keil为例所做的说明。
图 20-1 FOC5.2 Keil工程文件结构目录
图中的STM32F4xx_HAL_Driver被折叠起来了,因为这一部分是HAL库的内容,
所以这一部分省略不提。下面表格则是说明了单独每个源文件的作用。FOC5.2使
用MotorControl Workbench来生成FOC源码,在使用Workbench生成代码的时候,
会根据所使用的速度位置传感器而添加相对应的源文件来处理速度位置反馈。下
面这是根据例程FOC_v5.2_57BLDC_HALLSensor进行解释说明。
⚫
Application
这个分组的内容是和用户的应用紧密相关的程序,通常用户只需要更改这一
部分的代码即可使用FOC电机库进行开发应用。
STM32 技术开发手册
www.ing10bbs.com
表格 20-1 Application分组源文件
main.c
main函数
motorControl.c
电机控制UI相关
motor_control_protocol.c
ui_task.c
user_interface.c
mc_api.c
用户与SDK交互的接口函数
mc_config.c
实例化各组件的Handle的结构体,并初
mc_parameters.c
始化
mc_task.c
电机控制的核心环路,如电流环,转矩
和速度环,状态机等
regular_conversion_manager.c
用户ADC规则转换通道管理
stm32f4xx_mc_it.c
电机控制相关的外设中断服务程序
stm32f4xx_it.c
用户自定义的外设中断服务程序
stm32f4xx_hal_msp.c
CubeMx生成的各外设中断服务程序
⚫
Drivers
这个是MCU的外设驱动代码(HAL库源码),由CubeMx生成,是硬件底层
的驱动代码。
⚫
Middleware
电机控制库中的各个组件的代码和库文件
表格 20-2 Middlewares分组源文件
bus_voltage_sensor.c
总线电压传感器相关
r_rivider_bus_voltage_sensor.c
circle_limitation.c
圆的限制函数(限制输出)
digital_output.c
数字输出控制
hall_speed_pos_fdbk.c
速度和位置反馈
speed_pos_fdbk.c
virtual_speed_sensor.c
STM32 技术开发手册
www.ing10bbs.com
mc_irq_handler.c
电机控制中断请求句柄
mc_math.c
坐标变换等数学函数
mc_interface.c
电机控制接口
motor_power_measurement.c
电机功率测量(Power quality display)
pqd_motor_power_measurement.c
ntc_temperature_sensor.c
ntc温度传感器相关函数
open_loop.c
开环控制相关函数
pid_regulator.c
PID调整器
pwm_curr_fdbk.c
pwm电流反馈相关函数
pwm_common.c
r3_f4xx_pwm_curr_fdbk.c
F4系列3相电阻电流反馈相关函数
ramp_ext_mngr.c
直线加减速管理
revup_ctrl.c
加速控制
speed_torq_ctrl.c
速度扭矩控制
state_machine.c
状态机
dac_ui.c
电机控制UI,主要用 DAC相关功能
dac_common_ui.c
于 debug 和 通 信 控
frame_communication_protocol.c
制。
通信协议相关
usart_frame_communication_protocol.c
ui_irq_handler.c
用户接口中断
请求句柄
20.2 FOC5.2 HALLSensor源码分析
鉴于篇幅原因,这里不会将电机库的源文件直接展示出来,会对源文件中的
一些无关重要的注释和换行删掉。读者可以根据源码进行对比分析。
STM32 技术开发手册
www.ing10bbs.com
20.2.1 Application/User
main.h文件内容
main.h的文件内容并不多,主要是引脚的宏定义,定义了使用到的引脚。这
些并不需要特别注意,需要的时候直接看main.h文件就行。
代码 20-1 main.h宏定义
01 #define Start_Stop_Pin
GPIO_PIN_0
02 #define Start_Stop_GPIO_Port
GPIOE
03 #define Start_Stop_EXTI_IRQn
EXTI0_IRQn
04 #define UART_RX_Pin
GPIO_PIN_7
05 #define UART_RX_GPIO_Port
GPIOB
06 #define UART_TX_Pin
GPIO_PIN_6
07 #define UART_TX_GPIO_Port
GPIOB
08 #define M1_PWM_WH_Pin
GPIO_PIN_7
09 #define M1_PWM_WH_GPIO_Port
GPIOI
10 #define M1_PWM_VH_Pin
GPIO_PIN_6
11 #define M1_PWM_VH_GPIO_Port
GPIOI
12 #define M1_PWM_UH_Pin
GPIO_PIN_5
13 #define M1_PWM_UH_GPIO_Port
GPIOI
14 #define M1_PWM_WL_Pin
GPIO_PIN_15
15 #define M1_PWM_WL_GPIO_Port
GPIOH
16 #define M1_PWM_UL_Pin
GPIO_PIN_13
17 #define M1_PWM_UL_GPIO_Port
GPIOH
18 #define M1_PWM_VL_Pin
GPIO_PIN_14
19 #define M1_PWM_VL_GPIO_Port
GPIOH
20 #define M1_HALL_H3_Pin
GPIO_PIN_12
21 #define M1_HALL_H3_GPIO_Port
GPIOH
22 #define M1_HALL_H2_Pin
GPIO_PIN_11
23 #define M1_HALL_H2_GPIO_Port
GPIOH
24 #define M1_HALL_H1_Pin
GPIO_PIN_10
25 #define M1_HALL_H1_GPIO_Port
GPIOH
26 #define M1_BUS_VOLTAGE_Pin
GPIO_PIN_0
27 #define M1_BUS_VOLTAGE_GPIO_Port
GPIOC
28 #define M1_TEMPERATURE_Pin
GPIO_PIN_3
29 #define M1_TEMPERATURE_GPIO_Port GPIOC
30 #define DBG_DAC_CH1_Pin
GPIO_PIN_4
31 #define DBG_DAC_CH1_GPIO_Port
GPIOA
32 #define M1_CURR_AMPL_U_Pin
GPIO_PIN_6
33 #define M1_CURR_AMPL_U_GPIO_Por t GPIOA
34 #define DBG_DAC_CH2_Pin
GPIO_PIN_5
35 #define DBG_DAC_CH2_GPIO_Port
GPIOA
36 #define M1_CURR_AMPL_W_Pin
GPIO_PIN_1
37 #define M1_CURR_AMPL_W_GPIO_Port GPIOB
38 #define M1_CURR_AMPL_V_Pin
GPIO_PIN_0
39 #define M1_CURR_AMPL_V_GPIO_Port
GPIOB
main.c文件内容
main.c 文件主要是外设的初始化配置函数,实际运行的时候会根据需要更改
部分设置,所以下面的配置并不代表运行的时候一直都是这样。
STM32 技术开发手册
www.ing10bbs.com
图 20-2 main.c 主要函数
main()
main函数作为程序的入口函数,主要内容是初始化配置一些外设,包括HAL
库的初始化,系统时钟配置,GPIO,ADC,DAC,TIM,UART,NVIC,以及电机控
制库的初始化。概括如下:
⚫
系统配置
⚫
外设配置
⚫
电机控制配置
⚫
中断配置
⚫
主循环
代码 20-2 main主函数
01 int main(void)
02 {
03
/* MCU Configuration----------------------------------------------------------*/
04
05
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
06
HAL_Init();
07
08
/* Configure the system clock */
09
SystemClock_Config();
10
11
/* Initialize all configured peripherals */
12
MX_GPIO_Init();
13
MX_ADC1_Init();
14
MX_ADC2_Init();
15
MX_DAC_Init();
16
MX_TIM5_Init();
17
MX_TIM8_Init();
STM32 技术开发手册
www.ing10bbs.com
18
19
20
21
22
23
24
25
26
27 }
MX_USART1_UART_Init();
MX_MotorControl_Init();
/* Initialize interrupts */
MX_NVIC_Init();
/* Infinite loop */
while (1) {
}
SystemClock_Config()
该函数主要是配置MCU的主控时钟频率,系统时钟频率, HCLK时钟频率,
PCLK时钟频率,和SysTick时钟频率。
01 void SystemClock_Config(void)
02 {
03
04
RCC_OscInitTypeDef RCC_OscInitStruct;
05
RCC_ClkInitTypeDef RCC_ClkInitStruct;
06
07
/**Configure the main internal regulator output voltage**/
08
__HAL_RCC_PWR_CLK_ENABLE();
09
10
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
11
12
/**Initializes the CPU, AHB and APB busses clocks**/
13
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
14
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
15
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
16
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
17
RCC_OscInitStruct.PLL.PLLM = 4;
18
RCC_OscInitStruct.PLL.PLLN = 168;
19
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
20
RCC_OscInitStruct.PLL.PLLQ = 4;
21
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
22
_Error_Handler(__FILE__, __LINE__);
23
}
24
25
/**Initializes the CPU, AHB and APB busses clocks**/
26
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
27
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
28
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
29
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
30
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
31
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
32
33
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) {
34
_Error_Handler(__FILE__, __LINE__);
35
}
36
37
/**Enables the Clock Security System**/
38
HAL_RCC_EnableCSS();
39
40
/**Configure the Systick interrupt time**/
41
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
42
43
/**Configure the Systick**/
44
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
STM32 技术开发手册
www.ing10bbs.com
45
46
47
48 }
/* SysTick_IRQn interrupt configuration */
HAL_NVIC_SetPriority(SysTick_IRQn, 4, 0);
代码 20-3 时钟频率配置
01 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
02 RCC_OscInitStruct.HSEState = RCC_HSE_ON;
03 RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
04 RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
05 RCC_OscInitStruct.PLL.PLLM = 4;
06 RCC_OscInitStruct.PLL.PLLN = 168;
07 RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
08 RCC_OscInitStruct.PLL.PLLQ = 4;
从代码上可以看到,时钟类型选择外部高速晶振,晶振的频率输入之后经过
PLL输出得到系统时钟。设置PLLM = 4,PLLN = 168,PLLP = RCC_PLLP_DIV2,板载
晶振频率是8MHz,所以最后的系统时钟频率是8/4*168/2,即168MHz。
代码 20-4 时钟分频
01 RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
02
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
03 RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
04 RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
05 RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
06 RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
各个总线上的时钟频率都是有系统时钟分频得到的,设置分频系数是1,4,
2则配置的AHB,APB1,APB2总线频率分别为168MHz,42MHz,84MHz。这是根
据手册上可以配置的最高频率,没有功耗要求的话,可以直接使用这样的时钟频
率。定时器则是在APB的时钟频率的基础上倍频得到的,所以挂载在APB1上的定
时器时钟频率是84MHz,挂载在APB2上的定时器时钟频率是168MHz。TIM1和
TIM8就是168MHz。
代码 20-5 滴答定时器中断频率配置
01 HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
02
03 /**Configure the Systick **/
04 HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
05
06 /* SysTick_IRQn interrupt configuration */
07 HAL_NVIC_SetPriority(SysTick_IRQn, 4, 0);
最后一段是关于配置滴答定时器的频率和中断相关事项。SYSTICK的时钟频
率是168MHz,跟HCLK是一致的,可以使用HAL_RCC_GetHCLKFreq()函数获取,然
后除以1000,就是配置为1ms中断一次,但实际上在初始电机控制库的时候,还
STM32 技术开发手册
www.ing10bbs.com
会重新更改滴答定时器的中断时间间隔,这里是因为按照CubeMx配置的默认初
始化而已。
MX_NVIC_Init()
这是配置中断优先级的函数,部分外设的中断优先级会在该函数中初始化。
代码 20-6 NVIC中断优先级初始化
01 static void MX_NVIC_Init(void)
02 {
03
/* ADC_IRQn interrupt configuration */
04
HAL_NVIC_SetPriority(ADC_IRQn, 2, 0);
05
HAL_NVIC_EnableIRQ(ADC_IRQn);
06
/* TIM8_UP_TIM13_IRQn interrupt configuration */
07
HAL_NVIC_SetPriority(TIM8_UP_TIM13_IRQn, 0, 0);
08
HAL_NVIC_EnableIRQ(TIM8_UP_TIM13_IRQn);
09
/* TIM8_BRK_TIM12_IRQn interrupt configuration */
10
HAL_NVIC_SetPriority(TIM8_BRK_TIM12_IRQn, 4, 1);
11
HAL_NVIC_EnableIRQ(TIM8_BRK_TIM12_IRQn);
12
/* TIM5_IRQn interrupt configuration */
13
HAL_NVIC_SetPriority(TIM5_IRQn, 3, 0);
14
HAL_NVIC_EnableIRQ(TIM5_IRQn);
15
/* USART1_IRQn interrupt configuration */
16
HAL_NVIC_SetPriority(USART1_IRQn, 3, 1);
17
HAL_NVIC_EnableIRQ(USART1_IRQn);
18
/* EXTI0_IRQn interrupt configuration */
19
HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
20
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
21 }
TIM8的更新中断和按键中断的优先级是最高的,然后是ADC外设的中断,然
后按顺序是TIM5,USART1,TIM8的BRK中断(制动输入中断)。
MX_ADC1_Init()
ADC1外设的初始化配置。ADC1和ADC2使用了多个通道来采集无刷电机的电
流,但不是每次转换的时候都用上了所有的通道,这个是要根据实际运行的时候
决定的。
代码 20-7 ADC1的初始化函数
01 static void MX_ADC1_Init(void)
02 {
03
04
ADC_InjectionConfTypeDef sConfigInjected;
05
ADC_ChannelConfTypeDef sConfig;
06
07
/**Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of
conversion) **/
08
hadc1.Instance = ADC1;
09
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
10
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
STM32 技术开发手册
www.ing10bbs.com
11
hadc1.Init.ScanConvMode = ENABLE;
12
hadc1.Init.ContinuousConvMode = DISABLE;
13
hadc1.Init.DiscontinuousConvMode = DISABLE;
14
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
15
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
16
hadc1.Init.DataAlign = ADC_DATAALIGN_LEFT;
17
hadc1.Init.NbrOfConversion = 2;
18
hadc1.Init.DMAContinuousRequests = DISABLE;
19
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
20
if (HAL_ADC_Init(&hadc1) != HAL_OK) {
21
_Error_Handler(__FILE__, __LINE__);
22
}
23
24
/**Configures for the selected ADC injected channel its corresponding rank in the sequencer and its
sample time **/
25
sConfigInjected.InjectedChannel = ADC_CHANNEL_6;
26
sConfigInjected.InjectedRank = 1;
27
sConfigInjected.InjectedNbrOfConversion = 3;
28
sConfigInjected.InjectedSamplingTime = ADC_SAMPLETIME_3CYCLES;
29
sConfigInjected.ExternalTrigInjecConvEdge = ADC_EXTERNALTRIGINJECCONVEDGE_RISING;
30
sConfigInjected.ExternalTrigInjecConv = ADC_EXTERNALTRIGINJECCONV_T8_CC4;
31
sConfigInjected.AutoInjectedConv = DISABLE;
32
sConfigInjected.InjectedDiscontinuousConvMode = ENABLE;
33
sConfigInjected.InjectedOffset = 0;
34
if (HAL_ADCEx_InjectedConfigChannel(&hadc1, &sConfigInjected) != HAL_OK) {
35
_Error_Handler(__FILE__, __LINE__);
36
}
37
38
/**Configures for the selected ADC injected channel its corresponding rank in the sequencer and its
sample time**/
39
sConfigInjected.InjectedChannel = ADC_CHANNEL_8;
40
sConfigInjected.InjectedRank = 2;
41
if (HAL_ADCEx_InjectedConfigChannel(&hadc1, &sConfigInjected) != HAL_OK) {
42
_Error_Handler(__FILE__, __LINE__);
43
}
44
45
/**Configures for the selected ADC injected channel its corresponding rank in the sequencer and its
sample time
46
*/
47
sConfigInjected.InjectedChannel = ADC_CHANNEL_9;
48
sConfigInjected.InjectedRank = 3;
49
if (HAL_ADCEx_InjectedConfigChannel(&hadc1, &sConfigInjected) != HAL_OK) {
50
_Error_Handler(__FILE__, __LINE__);
51
}
52
53
/**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its
sample time.**/
54
sConfig.Channel = ADC_CHANNEL_10;
55
sConfig.Rank = 1;
56
sConfig.SamplingTime = ADC_SAMPLETIME_56CYCLES;
57
sConfig.Offset = 0;
58
59
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
60
_Error_Handler(__FILE__, __LINE__);
61
}
62
63
/**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its
sample time.**/
64
sConfig.Channel = ADC_CHANNEL_13;
65
sConfig.Rank = 2;
66
sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES;
67
sConfig.Offset = 0;
68
STM32 技术开发手册
www.ing10bbs.com
69
70
71
72
73 }
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
_Error_Handler(__FILE__, __LINE__);
}
ADC1的基础参数配置如下:
代码 20-8 ADC1的基础参数
01 hadc1.Instance = ADC1;
02 hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
03 hadc1.Init.Resolution = ADC_RESOLUTION_12B;
04 hadc1.Init.ScanConvMode = ENABLE;
05 hadc1.Init.ContinuousConvMode = DISABLE;
06 hadc1.Init.DiscontinuousConvMode = DISABLE;
07 hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
08 hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
09 hadc1.Init.DataAlign = ADC_DATAALIGN_LEFT;
10 hadc1.Init.NbrOfConversion = 2;
11 hadc1.Init.DMAContinuousRequests = DISABLE;
12 hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
ADC的时钟频率最高是36MHz,而APB2的时钟频率是84Mhz,所以需要设置
预分频器为ADC_CLOCK_SYNC_PCLK_DIV4(4分频),最后得到的ADC时钟频率是
21MHz。分辨率设置为12位,使用了扫描模式,ADC将会在转换一个结束之后等
待下一次装换的时机,并不会立即停止ADC。由软件触发AD转换,数据对齐格式
是左对齐,在单次转换完成之后生成一个EOC事件。这里的NbrOfConversion参数
指的规则通道的数量,除了时钟分频,分辨率和数据对齐格式之外,都是规则通
道的参数设置。
代码 20-9 注入采样通道配置
01 sConfigInjected.InjectedChannel = ADC_CHANNEL_6;
02 sConfigInjected.InjectedRank = 1;
03 sConfigInjected.InjectedNbrOfConversion = 3;
04 sConfigInjected.InjectedSamplingTime = ADC_SAMPLETIME_3CYCLES;
05 sConfigInjected.ExternalTrigInjecConvEdge = ADC_EXTERNALTRIGINJECCONVEDGE_RISING;
06 sConfigInjected.ExternalTrigInjecConv = ADC_EXTERNALTRIGINJECCONV_T8_CC4;
07 sConfigInjected.AutoInjectedConv = DISABLE;
08 sConfigInjected.InjectedDiscontinuousConvMode = ENABLE;
09 sConfigInjected.InjectedOffset = 0;
代码 20-10 ADC_CHANNEL_8
01 sConfigInjected.InjectedChannel = ADC_CHANNEL_8;
02 sConfigInjected.InjectedRank = 2;
代码 20-11 ADC_CHANNEL_9
01 sConfigInjected.InjectedChannel = ADC_CHANNEL_9;
02 sConfigInjected.InjectedRank = 3;
STM32 技术开发手册
www.ing10bbs.com
ADC的每个转换通道都是独立的,所以需要独立配置。通过sConfigInjected这
个变量来设置ADC_CHANNEL_6,ADC_CHANNEL_8,ADC_CHANNEL_9这三个通道
为注入通道,用于采集三相电流。采样时间是3个时钟周期,由外部上升沿信号
触发,触发源是TIM8的CC4。采样模式是不连续采样模式,即第一次触发转换第
一个通道,第二次触发转换第二个通道,第三次触发转换第三个通道并生成JEOC
事 件 。 ADC_CHANNEL_6 是 第 一 个 要 转 换 的 通 道 。 后 面 的 ADC_CHANNEL_8 和
ADC_CHANNEL_9与ADC_CHANNEL_6是一样的配置,所以可以共用sConfigInjected
这个变量,不过需要将修改采样顺序,通道6,8,9分别对应UVW三相。
ADC_CHANNEL_10用于采样总线电压。ADC_CHANNEL_13用于采样温度传感
器。这两个通道是规则通道,采样时间分别是56和28个时钟周期。使用软件触发。
代码 20-12 ADC_CHANNEL_10 规则通道配置
01 sConfig.Channel = ADC_CHANNEL_10;
02 sConfig.Rank = 1;
03 sConfig.SamplingTime = ADC_SAMPLETIME_56CYCLES;
04 sConfig.Offset = 0;
代码 20-13 ADC_CHANNEL_13 规则通道配置
01 sConfig.Channel = ADC_CHANNEL_13;
02 sConfig.Rank = 2;
03 sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES;
04 sConfig.Offset = 0;
MX_ADC2_Init()
ADC2 的配置与 ADC1 基本一致,但是规则通道则是只有 ADC_CHANNEL_6。
并且采样时间也改为 3 个时钟周期。ADC_CHANNEL_6 一开始是配置为默认的注
入通道,但最后又改为了规则通道。其余注入通道则是完全与 ADC1 相同。在运
行的时候这些 ADC 通道是经常被修改的。
代码 20-14 ADC2 初始化配置
01 static void MX_ADC2_Init(void)
02 {
03
04
ADC_InjectionConfTypeDef sConfigInjected;
05
ADC_ChannelConfTypeDef sConfig;
06
07
/**Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of
conversion) **/
08
hadc2.Instance = ADC2;
09
hadc2.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
10
hadc2.Init.Resolution = ADC_RESOLUTION_12B;
STM32 技术开发手册
www.ing10bbs.com
11
hadc2.Init.ScanConvMode = ENABLE;
12
hadc2.Init.ContinuousConvMode = DISABLE;
13
hadc2.Init.DiscontinuousConvMode = DISABLE;
14
hadc2.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
15
hadc2.Init.ExternalTrigConv = ADC_SOFTWARE_START;
16
hadc2.Init.DataAlign = ADC_DATAALIGN_LEFT;
17
hadc2.Init.NbrOfConversion = 1;
18
hadc2.Init.DMAContinuousRequests = DISABLE;
19
hadc2.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
20
if (HAL_ADC_Init(&hadc2) != HAL_OK) {
21
_Error_Handler(__FILE__, __LINE__);
22
}
23
24
/**Configures for the selected ADC injected channel its corresponding rank in the sequencer and its
sample time **/
25
sConfigInjected.InjectedChannel = ADC_CHANNEL_6;
26
sConfigInjected.InjectedRank = 1;
27
sConfigInjected.InjectedNbrOfConversion = 3;
28
sConfigInjected.InjectedSamplingTime = ADC_SAMPLETIME_3CYCLES;
29
sConfigInjected.ExternalTrigInjecConvEdge = ADC_EXTERNALTRIGINJECCONVEDGE_RISING;
30
sConfigInjected.ExternalTrigInjecConv = ADC_EXTERNALTRIGINJECCONV_T8_CC4;
31
sConfigInjected.AutoInjectedConv = DISABLE;
32
sConfigInjected.InjectedDiscontinuousConvMode = ENABLE;
33
sConfigInjected.InjectedOffset = 0;
34
if (HAL_ADCEx_InjectedConfigChannel(&hadc2, &sConfigInjected) != HAL_OK) {
35
_Error_Handler(__FILE__, __LINE__);
36
}
37
38
/**Configures for the selected ADC injected channel its corresponding rank in the sequencer and its
sample time **/
39
sConfigInjected.InjectedChannel = ADC_CHANNEL_8;
40
sConfigInjected.InjectedRank = 2;
41
if (HAL_ADCEx_InjectedConfigChannel(&hadc2, &sConfigInjected) != HAL_OK) {
42
_Error_Handler(__FILE__, __LINE__);
43
}
44
45
/**Configures for the selected ADC injected channel its corresponding rank in the sequencer and its
sample time **/
46
sConfigInjected.InjectedChannel = ADC_CHANNEL_9;
47
sConfigInjected.InjectedRank = 3;
48
if (HAL_ADCEx_InjectedConfigChannel(&hadc2, &sConfigInjected) != HAL_OK) {
49
_Error_Handler(__FILE__, __LINE__);
50
}
51
52
/**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its
sample time. **/
53
sConfig.Channel = ADC_CHANNEL_6;
54
sConfig.Rank = 1;
55
sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
56
if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK) {
57
_Error_Handler(__FILE__, __LINE__);
58
}
59
60 }
MX_DAC_Init()
DAC 功能,用于电机调试,可以配合 Workbench 使用,输出不同参数的模拟
量。如下图所示。不过在引脚资源有限的情况下只能另外再找方法观察这些参数。
STM32 技术开发手册
www.ing10bbs.com
图 20-3 DAC 功能选择
代码 20-15 DAC 初始配置
01 static void MX_DAC_Init(void)
02 {
03
04
DAC_ChannelConfTypeDef sConfig;
05
06
/**DAC Initialization */
07
hdac.Instance = DAC;
08
if (HAL_DAC_Init(&hdac) != HAL_OK) {
09
_Error_Handler(__FILE__, __LINE__);
10
}
11
12
/**DAC channel OUT1 config */
13
sConfig.DAC_Trigger = DAC_TRIGGER_SOFTWARE;
14
sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;
15
if (HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1) != HAL_OK) {
16
_Error_Handler(__FILE__, __LINE__);
17
}
18
19
/**DAC channel OUT2 config */
20
if (HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_2) != HAL_OK) {
21
_Error_Handler(__FILE__, __LINE__);
22
}
23
24 }
初始化配置很简单,就是设置为软件触发,实际输出的值不固定。
STM32 技术开发手册
www.ing10bbs.com
MX_TIM5_Init()
例程使用 TIM5 作为霍尔传感器接口,霍尔传感器就是将 3 个通道异或,然
后捕获异或之后的信号,每一个信号边沿都可以捕获得到并且产生一个捕获事件,
或者是 COM 事件。可以利用这个功能进行速度反馈,确定转子位置。
代码 20-16 TIM5 霍尔传感器接口配置
01 static void MX_TIM5_Init(void)
02 {
03
04
TIM_ClockConfigTypeDef sClockSourceConfig;
05
TIM_HallSensor_InitTypeDef sConfig;
06
TIM_MasterConfigTypeDef sMasterConfig;
07
08
htim5.Instance = TIM5;
09
htim5.Init.Prescaler = 0;
10
htim5.Init.CounterMode = TIM_COUNTERMODE_UP;
11
htim5.Init.Period = M1_HALL_TIM_PERIOD;
12
htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
13
if (HAL_TIM_Base_Init(&htim5) != HAL_OK) {
14
_Error_Handler(__FILE__, __LINE__);
15
}
16
17
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
18
if (HAL_TIM_ConfigClockSource(&htim5, &sClockSourceConfig) != HAL_OK) {
19
_Error_Handler(__FILE__, __LINE__);
20
}
21
22
sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
23
sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
24
sConfig.IC1Filter = M1_HALL_IC_FILTER;
25
sConfig.Commutation_Delay = 0;
26
if (HAL_TIMEx_HallSensor_Init(&htim5, &sConfig) != HAL_OK) {
27
_Error_Handler(__FILE__, __LINE__);
28
}
29
30
sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC2REF;
31
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
32
if (HAL_TIMEx_MasterConfigSynchronization(&htim5, &sMasterConfig) != HAL_OK) {
33
_Error_Handler(__FILE__, __LINE__);
34
}
35
36 }
初始化的设置就是TIM5的一些基础配置,还有与其他定时器的链接。定时器
的时钟源选择的是内部的时钟。
代码 20-17 TIM5 基础配置
01 htim5.Instance = TIM5;
02 htim5.Init.Prescaler = 0;
03 htim5.Init.CounterMode = TIM_COUNTERMODE_UP;
04 htim5.Init.Period = M1_HALL_TIM_PERIOD;
05 htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
STM32 技术开发手册
www.ing10bbs.com
预分频设置为0,就是定时器的计数频率是84MHz,计数方向是向上计数,
默认的周期值就是65535,即16位计数器的最大值。
代码 20-18 霍尔传感器接口通道配置
01 sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
02 sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
03 sConfig.IC1Filter = M1_HALL_IC_FILTER;
04 sConfig.Commutation_Delay = 0;
霍尔传感器接口的三个通道异或到IC1,触发极性是上升沿,每个霍尔信号
边沿都会引起内部通道触发一个脉冲,捕获上升沿实际是捕获内部脉冲的上升沿
信号。 Commutation_Delay就是用于在触发信号之后延迟多长时间才进入中断,
这个不需要延迟,所以是0。IC1Filter定义了霍尔传感器接口引脚的采样频率和滤
波设置,M1_HALL_IC_FILTER定义为15,则采样频率是84Mhz/32,滤波设置是8次
采样才采集一次数据
代码 20-19 霍尔传感器接口触发输出
01 sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC2REF;
02 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
霍尔传感器接口在捕获到一个信号之后,使用OC2REF作为同步输出信号,将
TRGO通道链接到OC2REF。
MX_TIM8_Init()
TIM8 作为主定时器输出 SVPWM 用于控制无刷电机,具体的输出占空比不
固定。
代码 20-20 TIM8 初始化配置
01 static void MX_TIM8_Init(void)
02 {
03
04
TIM_SlaveConfigTypeDef sSlaveConfig;
05
TIM_MasterConfigTypeDef sMasterConfig;
06
TIM_OC_InitTypeDef sConfigOC;
07
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;
08
09
htim8.Instance = TIM8;
10
htim8.Init.Prescaler = ((TIM_CLOCK_DIVIDER) - 1);
11
htim8.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1;
12
htim8.Init.Period = ((PWM_PERIOD_CYCLES) / 2);
13
htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV2;
14
htim8.Init.RepetitionCounter = (REP_COUNTER);
15
if (HAL_TIM_Base_Init(&htim8) != HAL_OK) {
16
_Error_Handler(__FILE__, __LINE__);
17
}
STM32 技术开发手册
www.ing10bbs.com
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 }
if (HAL_TIM_PWM_Init(&htim8) != HAL_OK) {
_Error_Handler(__FILE__, __LINE__);
}
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_TRIGGER;
sSlaveConfig.InputTrigger = TIM_TS_ITR1;
if (HAL_TIM_SlaveConfigSynchronization(&htim8, &sSlaveConfig) != HAL_OK) {
_Error_Handler(__FILE__, __LINE__);
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim8, &sMasterConfig) != HAL_OK) {
_Error_Handler(__FILE__, __LINE__);
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
if (HAL_TIM_PWM_ConfigChannel(&htim8, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) {
_Error_Handler(__FILE__, __LINE__);
}
if (HAL_TIM_PWM_ConfigChannel(&htim8, &sConfigOC, TIM_CHANNEL_2) != HAL_OK) {
_Error_Handler(__FILE__, __LINE__);
}
if (HAL_TIM_PWM_ConfigChannel(&htim8, &sConfigOC, TIM_CHANNEL_3) != HAL_OK) {
_Error_Handler(__FILE__, __LINE__);
}
sConfigOC.OCMode = TIM_OCMODE_PWM2;
sConfigOC.Pulse = (((PWM_PERIOD_CYCLES) / 2) - (HTMIN));
if (HAL_TIM_PWM_ConfigChannel(&htim8, &sConfigOC, TIM_CHANNEL_4) != HAL_OK) {
_Error_Handler(__FILE__, __LINE__);
}
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_ENABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_ENABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_1;
sBreakDeadTimeConfig.DeadTime = ((DEAD_TIME_COUNTS) / 2);
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_LOW;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
if (HAL_TIMEx_ConfigBreakDeadTime(&htim8, &sBreakDeadTimeConfig) != HAL_OK) {
_Error_Handler(__FILE__, __LINE__);
}
HAL_TIM_MspPostInit(&htim8);
输入SVPWM需要使用到三组互补通道,另外再加上一个CC4用于触发ADC启
动,TIM8所有输出通道都用上了。
STM32 技术开发手册
www.ing10bbs.com
代码 20-21 TIM8 基础配置
01 htim8.Instance = TIM8;
02 htim8.Init.Prescaler = ((TIM_CLOCK_DIVIDER) - 1);
03 htim8.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1;
04 htim8.Init.Period = ((PWM_PERIOD_CYCLES) / 2);
05 htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV2;
06 htim8.Init.RepetitionCounter = (REP_COUNTER);
TIM8预分频是0,计数频率就是168Mhz。计数模式是中心对齐模式,即计数
器递增计数到最大值之后会递减计数。计数的周期值是根据PWM的频率计算出
来的,PWM的频率就是在Workbench设置的,例程是20Khz。
代码 20-22 TIM8 作为从定时器输入配置
01 sSlaveConfig.SlaveMode = TIM_SLAVEMODE_TRIGGER;
02 sSlaveConfig.InputTrigger = TIM_TS_ITR1;
代码 20-23 TIM8 作为主定时器输出配置
01 sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
02 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
TIM8作为从定时器,将ITR1作为输入源,TIM8的ITR1是链接到TIM2的TRGO通
道上,但是例程并没有使用到TIM2。所以这个可以忽略。同样作为主定时器虽然
选择了更新事件作为TRGO,但实际上并没有作用。
代码 20-24 定时器输出通道配置
01 sConfigOC.OCMode = TIM_OCMODE_PWM1;
02 sConfigOC.Pulse = 0;
03 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
04 sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
05 sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
06 sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
07 sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
TIM8的三组互补通道配置是一样的,都是PWM1输出模式,输出极性都是高
电平有效。空闲状态是低电平。
代码 20-25 CC4 输出模式配置
01 sConfigOC.OCMode = TIM_OCMODE_PWM2;
02 sConfigOC.Pulse = (((PWM_PERIOD_CYCLES) / 2) - (HTMIN));
TIM_CHANNEL_4是作为ADC的触发源,配置为PWM2输出模式,但实际上没
有使能复用引脚,也就是根本就没有输出脉冲,只是利用TIM_CHANNEL_4作为计
STM32 技术开发手册
www.ing10bbs.com
时作用而已。TIM_CHANNEL_4的触发时刻需要避开上升沿和下降沿的噪声时间,
ADC的采样时间,死区时间,这些时间都是可以通过workbench来设置,。
代码 20-26 刹车制动与死区时间配置
01 sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_ENABLE;
02 sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_ENABLE;
03 sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_1;
04 sBreakDeadTimeConfig.DeadTime = ((DEAD_TIME_COUNTS) / 2);
05 sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
06 sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_LOW;
07 sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
这段主要是配置运行模式的关闭状态和空闲模式的关闭状态。同时锁定级别
为TIM_LOCKLEVEL_1,不能对定时器的部分寄存器的部分控制位进行写操作。这
是防止定时器寄存器被篡改的措施。然后是死区时间的设置,死区时间的设置是
根据在Workbench设置的MOS管最小死区时间来计算得到的,可以需要根据MOS
管的手册来确定。刹车制动功能并没有使用到。
MX_USART1_UART_Init()
串口通信初始化,串口主要是与 Workbench 通信,在掌握了通信协议之后,
可以自行制作上位机控制。
代码 20-27 USART1 通信初始化
01 static void MX_USART1_UART_Init(void)
02 {
03
04
huart1.Instance = USART1;
05
huart1.Init.BaudRate = 115200;
06
huart1.Init.WordLength = UART_WORDLENGTH_8B;
07
huart1.Init.StopBits = UART_STOPBITS_1;
08
huart1.Init.Parity = UART_PARITY_NONE;
09
huart1.Init.Mode = UART_MODE_TX_RX;
10
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
11
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
12
if (HAL_UART_Init(&huart1) != HAL_OK) {
13
_Error_Handler(__FILE__, __LINE__);
14
}
15
16 }
使用的串口是USART1,也就是板载的USB调试串口,波特率使用115200,数
据位是8位,停止位是1位,无奇偶校验,使能发送和接受模式,不使用流控制模
式,16倍的过采样采集数据。
STM32 技术开发手册
www.ing10bbs.com
MX_GPIO_Init()
启动按键配置,该函数是使用一个按键来启动或者停止电机转动。用的是下
降沿中断模式。
代码 20-28 按键中断配置
01 static void MX_GPIO_Init(void)
02 {
03
04
GPIO_InitTypeDef GPIO_InitStruct;
05
06
/* GPIO Ports Clock Enable */
07
__HAL_RCC_GPIOE_CLK_ENABLE();
08
__HAL_RCC_GPIOB_CLK_ENABLE();
09
__HAL_RCC_GPIOI_CLK_ENABLE();
10
__HAL_RCC_GPIOH_CLK_ENABLE();
11
__HAL_RCC_GPIOC_CLK_ENABLE();
12
__HAL_RCC_GPIOA_CLK_ENABLE();
13
14
/*Configure GPIO pin : Start_Stop_Pin */
15
GPIO_InitStruct.Pin = Start_Stop_Pin;
16
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
17
GPIO_InitStruct.Pull = GPIO_NOPULL;
18
HAL_GPIO_Init(Start_Stop_GPIO_Port, &GPIO_InitStruct);
19
20 }
_Error_Handler()
在使用过程中出现异常可以进入这个函数来判断出现在哪个文件和行数。
代码 20-29 错误处理函数
01 void _Error_Handler(char *file, int line)
02 {
03
/* USER CODE BEGIN Error_Handler_Debug */
04
/* User can add his own implementation to report the HAL error return state */
05
while (1) {
06
}
07
/* USER CODE END Error_Handler_Debug */
08 }
Motorcontrol.h 文件内容
这个文件没太多内容,只是定义了一个全局宏定义,这个宏定义了电机控制
库使用的是HAL库作为底层控制。
代码 20-30
Motorcontrol.h 文件内容
01 /* Initializes the Motor Control Subsystem */
02 void MX_MotorControl_Init(void);
03
04 /* Do not remove the definition of this symbol. */
05 #define MC_HAL_IS_USED
STM32 技术开发手册
www.ing10bbs.com
声明电机控制初始化函数,定义了MC_HAL_IS_USED。
motorcontrol.c 文件内容
该文件是电机控制初始化,主要是初始化电机的一些结构体和组件参数,还
有用户接口的任务。该文件只有一个函数。
代码 20-31 固件版本号宏定义
01 #define FIRMWARE_VERS "HALL Sensor ST MC SDK\tVer.5.2.0"
02 const char s_fwVer[64] = FIRMWARE_VERS;
03
04 MCI_Handle_t* pMCI[NBR_OF_MOTORS];
05 MCT_Handle_t* pMCT[NBR_OF_MOTORS];
06 uint32_t wConfig[NBR_OF_MOTORS] = {UI_CONFIG_M1,UI_CONFIG_M2};
宏 FIRMWARE_VERS 定义了 FOC5.2 的固件版本号,可以利用这个进行识别
判断当前使用的电机库固件版本。定义了两个全局指针,一个是 pMCI,另一个
是 pMCT,pMCI 是电机控制接口,pMCT 是电机实时调整控制。在对电机控制库
进行二次开发的时候,主要常用的就是 pMCI 这个变量,这个是电机库的对外接
口。
MX_MotorControl_Init()
该函数修改了 Systick 的中断频率,初始化电机控制子系统,初始化用户接
口任务。
代码 20-32 电机控制初始化函数
01 void MX_MotorControl_Init(void)
02 {
03
/* Reconfigure the SysTick interrupt to fire every 500 us. */
04
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/SYS_TICK_FREQUENCY);
05
/* Initialize the Motor Control Subsystem */
06
MCboot(pMCI,pMCT);
07
mc_lock_pins();
08
09
/* Initialize the MC User Interface */
10
UI_TaskInit(wConfig,NBR_OF_MOTORS,pMCI,pMCT,s_fwVer);
11 }
Systick 的中断时间间隔修改为 500us 一次。配置好电机之后会将引脚锁定,
防止被篡改。该函数需要在所有外设已经初始化配置但并没有使能中断的时候调
用,也就是需要在调用初始化外设的函数之后才调用这个函数。CubeMx 在外设
初始化之后调用 NVIC 配置函数之前调用的这个函数。
STM32 技术开发手册
www.ing10bbs.com
mc_api.c
该文件包含有电机控制库软件开发工具包的编程接口函数。这是一个电机控
制的接口文件,允许执行电机驱动的基本操作,有一系列的函数来实现电机的控
制,这些函数是用户和 SDK 之间的桥梁。
正常情况下,对电机库进行二次开发的时候主要常用的函数就是该文件的函
数,因为这是预留出来的接口函数,专门用于开发使用。STM32 Motor Control SDK
可以用一片 STM32 MCU 同时驱动两台电机,为了简化程序,API 为每一台电机
提供一组单独的函数,这样把函数的参数限制到最简化,从函数的名字就可以看
出来是用来控制哪个电机的。其中最常用的函数如下:
⚫ MC_StartMotor1()
⚫ MC_StopMotor1()
⚫ MC_ProgramSpeedRampMotor1()
⚫ MC_ProgramTorqueRampMotor1()
⚫ MC_GetMecSpeedReferenceMotor1()
⚫ MC_GetMecSpeedAverageMotor1()
⚫ MC_GetSTMStateMotor1()
⚫ MC_GetOccurredFaultsMotor1()
⚫ MC_AcknowledgeFaultMotor1()
⚫ MC_GetImposedDirectionMotor1()
STM32 技术开发手册
www.ing10bbs.com
图 20-4 mc_api.c 所有函数
如果在 Workbench 里面配置了使用两个电机,才会在该文件里面出现带有
“Motor2”名称的函数。
MC_StartMotor1()
启动电机转动,进入启动状态,如果电机当前状态是空闲状态,则将会立即执行
启动电机,否则该指令将被丢弃。应用的时候可检查返回值以确认是否执行电机
启动的命令。
STM32 技术开发手册
www.ing10bbs.com
在调用该函数之前,需要先调用以下函数中的任意一个函数,否则将会导致
不可预测的行为:
⚫
MC_ProgramSpeedRampMotor1()
⚫
MC_ProgramTorqueRampMotor1()
⚫
MC_SetCurrentReferenceMotor1()
代码 20-33 启动电机
01 bool MC_StartMotor1(void)
02 {
03
return MCI_StartMotor( pMCI[M1] );
04 }
MC_StartMotor1 仅仅是触发状态机从“IDLE”状态进入“IDLE-START”
,如
果成功执行命令,则该函数返回 True,否则返回 False。要想知道电机是否正常
运行,应检查状态机当前是否处于“RUN”的状态。
在 workbench 上可以找到与该函数相同功能的按钮:
图 20-5 启动电机按钮
MC_StopMotor1()
停止电机转动,进入停止状态。如果 Motor 1 的状态机正处于“RUN”或者
“START”状态,立即执行停止电机命令,否则该命令被丢弃。应用的时候可以
检查返回值以确认是否执行该命令。
STM32 技术开发手册
www.ing10bbs.com
代码 20-34 停止电机
01 bool MC_StopMotor1(void)
02 {
03
return MCI_StopMotor( pMCI[M1] );
04 }
MCI_StopMotor1()仅仅是触发状态机从运行状态进入“ANY_STOP”状态。要
检查电机是否实际停止了,应该检查状态机的状态是否为“IDLE”状态。
在 workbench 上可以找到与该函数相同功能的按钮,见图 20-5。
MC_ProgramSpeedRampMotor1()
该函数用于设置线性速度目标值,该函数有两个参数,一个是最终目标速度
(hFinalSpeed),一个是持续时间(hDurationms),最终目标速度值的单位按照注
释是[0.1𝐻𝑧],转子[0.1𝐻𝑧]也就是[0.1𝑟⁄𝑠]。当电机速度是 996RPM 的时候,该变
量值就是 166,996[𝑟⁄𝑚] = 16.6[𝐻𝑧]*60[𝑠]。hDurationms 的单位则是 ms。运行该
函数之后,电机将运行在 Speed 模式
调用该函数的时候并不一定会立即执行,而是进入缓存队列中等待,只有在
状态机正处于 “START_RUN”或“RUN”的时候才会立即执行。任何时刻都只能
有一个指令在缓存队列中,如果状态机当前正处于其他状态,该指令还没有被执
行,在等待过程中有新的指令,则该指令将会被新的指令替换掉。要想知道当前
指令是否被执行,可以调用 MC_GetCommandStateMotor1()函数获取缓存中最新
的指令的执行状态。
代码 20-35 电机速度目标值线性变化控制
01 void MC_ProgramSpeedRampMotor1( int16_t hFinalSpeed, uint16_t hDurationms )
02 {
03
MCI_ExecSpeedRamp( pMCI[M1], hFinalSpeed, hDurationms );
04 }
该函数使得电机速度的目标值在运行过程中可以线性变化,逐步递增。在程
序中调用该函数,给与参数值分别为 250,1000,参数设置速度是 250,那么目
标速度就是 250 [0.1𝐻𝑧] * 60[𝑠] = 1500 [𝑟/𝑚] ,如果数值是负数,则代表电机反
向转动。执行的效果如下图所示:
STM32 技术开发手册
www.ing10bbs.com
图 20-6 速度值线性变化
红色线条是目标值,白色线条是实际的速度值,一开始的时候电机是停止的,
启动之后,电机速度就几乎与目标速度一致,在这个时候状态机状态是“RUN”,
电机速度是 996RPM。当执行了该函数之后,则电机速度目标值呈现线性递增变
化,逐步增加到所设置的最终目标速度(1500RPM),实际的速度值则会跟随目
标值变化,速度爬坡所用的时间是 1000[𝑚𝑠]。当爬坡时间设置为 0 的时候,目
标速度值将会直接变换成 1500RPM,中间不会有爬坡的效果。在 Workbench 上
也有这样一个功能,见下图:
STM32 技术开发手册
www.ing10bbs.com
图 20-7 速度目标值线性控制
MC_ProgramTorqueRampMotor1()
该函数与 MC_ProgramSpeedRampMotor1()控制效果是一样的,不过作用对象
是电机转矩。当电机以 Torque 模式运行的时候,该函数就用来设置转矩的目标
值。
代码 20-36 扭矩目标值线性控制
01 void MC_ProgramTorqueRampMotor1( int16_t hFinalTorque, uint16_t hDurationms )
02 {
03
MCI_ExecTorqueRamp( pMCI[M1], hFinalTorque, hDurationms );
04 }
两个参数,hFinalTorque 是运行时候的电机转矩最终的目标值,hDurationms
则是持续时间,单位是 ms。调用该函数之后,将会切换成 Torque 模式。
hFinalTorque 是以 16 位有符号数表示的𝐼𝑞 值,转换成安倍(Amps)格式的时候需
要使用以下公式:
公式 20-1 𝑰𝒒 值格式转换
𝐼𝑠16𝐴 =
𝐼𝐴𝑚𝑝 ∗ 65536 ∗ 𝑅𝑠ℎ𝑢𝑛𝑡 ∗ 𝐴𝑜𝑝
𝑉𝑑𝑑
MC_SetCurrentReferenceMotor1()
这是直接设置电机𝐼𝑞 和𝐼𝑑 电流参考值的函数,该函数只会在状态机状态是
“START_RUN”或“RUN”的时候生效,否则将会进入缓存队列,可以通过
MC_GetCommandStateMotor1 ()来确认该指令的执行状态。缓存队列在任意时刻
STM32 技术开发手册
www.ing10bbs.com
只能有一个指令在等待执行,如果该指令没有被执行,并且有一个新的指令,则
该指令将会被新的指令所覆盖。
代码 20-37 设置电流参考值
01 void MC_SetCurrentReferenceMotor1( Curr_Components Iqdref )
02 {
03
MCI_SetCurrentReferences( pMCI[M1], Iqdref );
04 }
参数 Iqdref 类型是 Curr_Components,这是一个带有两个 16 位有符号数的
结构体。如果 qI_Component1 的数值是负数,则代表电机反向转动。
代码 20-38 𝑰𝒒 , 𝑰𝒅 结构体成员
01 /**
02
* @brief Two components stator current type definition
03
*/
04 typedef struct {
05
int16_t qI_Component1;
06
int16_t qI_Component2;
07 } Curr_Components;
该函数的功能与 workbench 上的功能一致,设置如下图。
图 20-8 转矩模式下的电流控制
MC_GetCommandStateMotor1()
该函数返回最新的指令的执行状态。部分指令在调用的时候并不会立即执行,
而是进入缓存队列,待到合适的时候才会被执行,用该函数就可以知道缓存队列
中的最新指令的执行状态。
代码 20-39 获取指令执行状态
01 MCI_CommandState_t MC_GetCommandStateMotor1( void)
02 {
03
return MCI_IsCommandAcknowledged( pMCI[M1] );
04 }
返回值可以是以下类型:
➢ MCI_BUFFER_EMPTY:缓存队列空。
STM32 技术开发手册
www.ing10bbs.com
➢ MCI_COMMAND_NOT_ALREADY_EXECUTED:指令未被执行。
➢ MCI_COMMAND_EXECUTED_SUCCESFULLY:指令成功被执行。
➢ MCI_COMMAND_EXECUTED_UNSUCCESFULLY:指令执行失败。
在指令被执行之后,无论执行指令是否成功,调用该函数都可以将指令状态
设置为 MCI_BUFFER_EMPTY。
MC_StopSpeedRampMotor1()
该函数可以停止目标速度的线性递增。MC_ProgramSpeedRampMotor1()是执
行电机目标速度线性控制,而该函数则停止目标速度线性变化。如果 Speed Ramp
的指令正在被执行,则该指令将会被立即停止,目标速度将会维持在当前值。并
且返回 ture,否则返回 false。
代码 20-40 停止执行 Speed Ramp 指令
01 bool MC_StopSpeedRampMotor1(void)
02 {
03
return MCI_StopSpeedRamp( pMCI[M1] );
04 }
由于在执行 Speed Ramp 指令过程中被强行中止,所以当前的速度目标值是
不确定的。
在 workbench 上可以找到与该函数相同功能的按钮,见图 20-5。
MC_HasRampCompletedMotor1()
用于获取目标速度变化状态。如果已经执行完 Speed Ramp 指令,则返回 true,
否则返回 false。
代码 20-41
01 bool MC_HasRampCompletedMotor1(void)
02 {
03
return MCI_RampCompleted( pMCI[M1] );
04 }
MC_GetMecSpeedReferenceMotor1()
该函数用于获取电机机械转速的实际目标值。在调用线性目标速度控制函数
MC_ProgramSpeedRampMotor1()之后可以调用 MC_StopSpeedRampMotor1()来中
止指令的执行,中止之后,电机速度会有一个目标值,这个目标值是不固定的,
STM32 技术开发手册
www.ing10bbs.com
调用该函数就可以获取当前的实际目标值。返回值是速度目标值,单位是 0.1RPS。
例如返回值是 250,则实际的目标转速就是 250 * 60s = 1500 (PRM)。
代码 20-42 获取电机目标速度值
01 int16_t MC_GetMecSpeedReferenceMotor1(void)
02 {
03
return MCI_GetMecSpeedRef01Hz( pMCI[M1] );
04 }
MC_GetMecSpeedAverageMotor1()
该函数用于获取电机平均机械转速,返回值是电机转速的平均值,单位是
[0.1𝐻𝑧]。例如返回值是 250,则实际的转速就是 250[0.1𝐻𝑧] * 60 [𝑠] = 1500 [𝑟⁄𝑚]
代码 20-43 获取电机平均机械转速
01 int16_t MC_GetMecSpeedAverageMotor1(void)
02 {
03
return MCI_GetAvrgMecSpeed01Hz( pMCI[M1] );
04 }
MC_GetLastRampFinalSpeedMotor1()
该函数返回用户设置的最终速度目标值。该函数并不是获取电机速度值,也
不是获取电机实际速度目标值,而是调用 MC_ProgramSpeedRampMotor1()之后所
设置的最终速度目标值。
代码 20-44 获取电机最终目标值
01 int16_t MC_GetLastRampFinalSpeedMotor1(void)
02 {
03
return MCI_GetLastRampFinalSpeed( pMCI[M1] );
04 }
在调用 MC_ProgramSpeedRampMotor1()函数之后,执行 Speed Ramp 指令,
电机速度的目标值将会按下图中①到②所示变化。如果在爬升的过程中调用
MC_StopSpeedRampMotor1()函数,中止执行 Speed Ramp 指令,则目标值将会是
如图中③的值。
调用 MC_GetMecSpeedReferenceMotor1()函数获取的是图中③的值,也就是
实际的目标值。调用 MC_GetLastRampFinalSpeedMotor1(),获取的是图中②的值,
也就是最终速度目标值。调用 MC_GetMecSpeedAverageMotor1()获取的是实际的
速度值,与图中的曲线无关。
STM32 技术开发手册
www.ing10bbs.com
图 20-9 速度的目标值变化图示
MC_GetControlModeMotor1()
该函数用于获取当前运行模式。电机运行模式有两种,一种是 Torque,一种
是 Speed。
代码 20-45 获取电机运行模式
01 STC_Modality_t MC_GetControlModeMotor1(void)
02 {
03
return MCI_GetControlMode( pMCI[M1] );
04 }
返回值是一个枚举型变量,只能是两种模式的其中一种。
代码 20-46 电机运行模式枚举
01 typedef enum {
02
STC_TORQUE_MODE, /**< @brief Torque mode.*/
03
STC_SPEED_MODE
/**< @brief Speed mode.*/
04 } STC_Modality_t;
MC_GetImposedDirectionMotor1()
该函数用于获取由最新的指令所设置的电机转动方向值。最新的指令指的是
调用控制函数 MC_ProgramSpeedRampMotor1(),MC_ProgramTorqueRampMotor1()
和 MC_SetCurrentReferenceMotor1()。当调用这些函数的其中一个的时候,会设
STM32 技术开发手册
www.ing10bbs.com
置一个目标值,而这个目标值的正负就是电机转动的方向。调用该函数则是获取
电机方向值。
代码 20-47 获取电机方向
01 int16_t MC_GetImposedDirectionMotor1(void)
02 {
03
return MCI_GetImposedMotorDirection( pMCI[M1] );
04 }
返回由最后一个指令设置的 Motor 1 的方向。如果最终的速度或转矩指令为
负函数返回-1,否则函数返回 1。
MC_GetSpeedSensorReliabilityMotor1()
检查速度传感器是否可靠。电机的速度传感器反馈回来的数据如果超过了在
workbench 所设置的数值范围,就会增加速度反馈错误次数,速度反馈错误次数
超过了一定数值,就会认为速度传感器是不可靠的。
代码 20-48 检测速度传感器是否可靠
01 bool MC_GetSpeedSensorReliabilityMotor1(void)
02 {
03
return MCI_GetSpdSensorReliability( pMCI[M1] );
04 }
当速度传感器是不可靠的时候该函数返回 false,否则返回 true。
MC_GetPhaseCurrentAmplitudeMotor1()
该函数用于获取电机相电流(0-to-peak)。
代码 20-49 获取相电流振幅
01 int16_t MC_GetPhaseCurrentAmplitudeMotor1(void)
02 {
03
return MCI_GetPhaseCurrentAmplitude( pMCI[M1] );
04 }
返回值是 16 位符号数(s16A)的格式,s16A 定义为 :
公式 20-2 电流数字量化
1[𝒔𝟏𝟔𝑨] =
𝐼𝑀𝐴𝑋
32768
STM32 技术开发手册
www.ing10bbs.com
𝐼𝑀𝐴𝑋 是可测量的电流最大值,电流采样信号经过运放之后存在偏移值,满
量程是0~𝐼𝑀𝐴𝑋 ,不存在负值,所以数字量化的时候是 32768 而不是 65536,如下
图所示:
图 20-10 电流采样偏移
同样由于连接到 ADC 引脚电流信号电压被抬升了 1.65[𝑉],所以在计算𝐼𝑀𝐴𝑋
的时候需要除以 2,最后得到的就是公式 20-3。
公式 20-3 𝑰𝑴𝑨𝑿 计算公式
𝐼𝑀𝐴𝑋 =
𝑉𝑑𝑑
2 × 𝑅𝑠ℎ𝑢𝑛𝑡 × 𝐴𝑜𝑝
如果需要转换成安培(Amps),则需要以下公式 20-4:
公式 20-4 电流格式转换
𝐼𝐴𝑚𝑝 =
𝐼𝑠16𝐴 ∗ 𝑉𝑑𝑑
65536 ∗ 𝑅𝑠ℎ𝑢𝑛𝑡 ∗ 𝐴𝑜𝑝
MC_GetPhaseVoltageAmplitudeMotor1()
用于获取电机相电压 (0-to-peak)。
代码 20-50 获取相电压振幅
01 int16_t MC_GetPhaseVoltageAmplitudeMotor1(void)
02 {
03
return MCI_GetPhaseVoltageAmplitude( pMCI[M1] );
04 }
返回值是 16 位有符号数(s16V),s16V 格式定义如下:
公式 20-5 电流数字量化
1[𝒔𝟏𝟔𝑽] =
𝑉𝑀𝐴𝑋
32768
STM32 技术开发手册
www.ing10bbs.com
如果需要转换成伏特(Volts),需要以下公式:
公式 20-6 相电压数据格式转换
𝑉𝑉𝑜𝑙𝑡𝑠 =
𝑉𝑠16𝑉 ∗ 𝑉𝑏𝑢𝑠
√3 ∗ 32768
MC_GetIabMotor1()
获取电机𝐼𝑎 ,𝐼𝑏 电流值。
代码 20-51 获取𝑰𝒂 ,𝑰𝒃
01 Curr_Components MC_GetIabMotor1(void)
02 {
03
return MCI_GetIab( pMCI[M1] );
04 }
返回值是 Curr_Components,这是一个带有两个 16 位有符号数的结构体。
代码 20-52 𝑰𝒒 , 𝑰𝒅 结构体成员
01 /**
02
* @brief Two components stator current type definition
03
*/
04 typedef struct {
05
int16_t qI_Component1;
06
int16_t qI_Component2;
07 } Curr_Components;
MC_GetIalphabetaMotor1()
该函数用于获取电流值的𝐼𝛼 ,𝐼𝛽
代码 20-53 获取𝑰𝜶 ,𝑰𝜷电流值
01 Curr_Components MC_GetIalphabetaMotor1(void)
02 {
03
return MCI_GetIalphabeta( pMCI[M1] );
04 }
返回值是类型是 Curr_Components。包含两个 16 位有符号数变量
MC_GetIqdMotor1 ()
该函数用于获取电流值的𝐼𝑞 ,𝐼𝑑
代码 20-54 获取𝑰𝒒 ,𝑰𝒅电流值
01 Curr_Components MC_GetIqdMotor1(void)
02 {
STM32 技术开发手册
www.ing10bbs.com
03
04 }
return MCI_GetIqd( pMCI[M1] );
返回值是类型是 Curr_Components。包含两个 16 位有符号数变量
MC_GetIqdrefMotor1()
该函数用于获取电流值的𝐼𝑞 ,𝐼𝑑 的目标值。
代码 20-55 获取𝑰𝒒 ,𝑰𝒅目标值
代码 20-56 获取
01 Curr_Components MC_GetIqdrefMotor1(void)
02 {
03
return MCI_GetIqdref( pMCI[M1] );
04 }
返回值是类型是 Curr_Components。包含两个 16 位有符号数变量。
MC_GetVqdMotor1()
该函数用于获取电流值的𝑉𝑞 ,𝑉𝑑
代码 20-57 获取𝑽𝒒 ,𝑽𝒅
01 Volt_Components MC_GetVqdMotor1(void)
02 {
03
return MCI_GetVqd( pMCI[M1] );
04 }
返回值是类型是 Volt_Components。包含两个 16 位有符号数变量
代码 20-58 定子电压类型定义
01 typedef struct {
02
int16_t qV_Component1;
03
int16_t qV_Component2;
04 } Volt_Components;
MC_GetValphabetaMotor1()
该函数用于获取电流值的𝑉𝑞 ,𝑉𝑑 。
代码 20-59 获取获取𝑽𝒒 ,𝑽𝒅 目标值
01 Volt_Components MC_GetValphabetaMotor1(void)
02 {
03
return MCI_GetValphabeta( pMCI[M1] );
04 }
返回值是类型是 Volt_Components。包含两个 16 位有符号数变量
STM32 技术开发手册
www.ing10bbs.com
MC_GetElAngledppMotor1
该函数用于获取电机转子的电角度。
代码 20-60 获取电机转子电角度
01 int16_t MC_GetElAngledppMotor1(void)
02 {
03
return MCI_GetElAngledpp( pMCI[M1] );
04 }
返回值是以𝒅𝒑𝒑格式描述的电角度。
下面解释𝒅𝒑𝒑格式:
在 MC API 中使用的角度测量单位是𝒔𝟏𝟔𝒅𝒆𝒈𝒓𝒆𝒆,定义如公式 20-7:
公式 20-7 𝒓𝒂𝒅数字量化
1[𝒔𝟏𝟔𝒅𝒆𝒈𝒓𝒆𝒆] =
2𝜋
[𝒓𝒂𝒅]
65536
速度格式有两种:
𝟎𝟏𝐇𝐳:供速度环 PID(在速度控制模式下)及用户界面层使用,单位是𝐝𝑯𝒛。
表示转子转速,也是前面说的速度单位是 0.1Hz,实际是 0.1RPS。
1[𝑑𝐻𝑧] = 1["01Hz" ] = 0.1[𝐻𝑧]
Digit Per PWM(𝒅𝒑𝒑),表示每个 PWM 周期转子角度的变化量(𝒔𝟏𝟔𝒅𝒆𝒈𝒓𝒆𝒆)。
该格式可直接累加从而得到转子的位置。
公式 20-8 𝒅𝒑𝒑与𝒓𝒂𝒅⁄𝒔的关系
1
[𝒔𝟏𝟔𝒅𝒆𝒈𝒓𝒆𝒆⁄𝒔]
𝑇𝐹𝑂𝐶(𝑠)
2𝜋
=
[𝒓𝒂𝒅⁄𝒔]
65536 × 𝑇𝐹𝑂𝐶(𝑠)
2𝜋
=
× 𝐹𝐹𝑂𝐶(𝐻𝑧) [𝒓𝒂𝒅⁄𝒔]
65536
1𝒅𝒑𝒑 =
𝟎𝟏𝑯𝒛与𝒅𝒑𝒑关系为:
公式 20-9 两种速度格式关系式
𝜔𝑑𝑝𝑝 = 𝜔01𝐻𝑧
65536
10 × 𝐹𝐹𝑂𝐶(𝐻𝑧)
公式 20-9 说明了两种速度单位之间的关系,并不是𝒅𝒑𝒑的定义式。下面对
公式 20-9 做个简单的说明:
STM32 技术开发手册
www.ing10bbs.com
公式 20-8 说明了1𝑑𝑝𝑝与对应了多少𝑟𝑎𝑑⁄𝑠,设𝜔𝑟𝑎𝑑⁄𝑠 单位是𝑟𝑎𝑑⁄𝑠,𝜔𝑑𝑝𝑝 为
𝑑𝑝𝑝格式表示的速度值,那么可以根据这条公式,推导出:
公式 20-10
2𝜋
× 𝐹𝐹𝑂𝐶(𝐻𝑧) [𝒓𝒂𝒅⁄𝒔]
65536
65536
= 𝜔𝑟𝑎𝑑⁄𝑠 ×
[𝒅𝒑𝒑]
2𝜋 × 𝐹𝐹𝑂𝐶(𝐻𝑧)
𝜔𝑟𝑎𝑑⁄𝑠 = 𝜔𝑑𝑝𝑝 ×
𝜔𝑑𝑝𝑝
其中,𝜔𝑟𝑎𝑑⁄𝑠 是以𝑟𝑎𝑑⁄𝑠为单位的角速度,1 转就是2𝜋[𝑟𝑎𝑑],所以有:
公式 20-11 𝟎𝟏𝐇𝐳单位转换
𝜔𝑟⁄𝑠 =
𝜔𝑟𝑎𝑑⁄𝑠 𝜔01𝐻𝑧
=
2𝜋
10
将公式 20-11 代入公式 20-10 中替换掉𝜔𝑟𝑎𝑑⁄𝑠 ,就可以得到公式 20-9。
若已知当前角度为𝜃0 ,角速度为𝜔,那么经过𝑡时间之后,新角度𝜃1 = 𝜃0 +
𝜔 × 𝑡。对于𝜔𝑑𝑝𝑝 来说,分母是 FOC 控制周期,𝑡 也是 FOC 控制周期,则𝜔 × 𝑡的
结果就是𝜔本身的数值,换句话来说,即𝜃1 = 𝜃0 + 𝜔𝑑𝑝𝑝 。简化了计算过程。
MC_GetTerefMotor1()
该函数可以获取转矩的目标值。
代码 20-61 获取转矩目标值
01 int16_t MC_GetTerefMotor1(void)
02 {
03
return MCI_GetTeref( pMCI[M1] );
04 }
返回值是 s16 格式的𝐼𝑞 电流值,数值单位转换公式见公式 20-1。
MC_SetIdrefMotor1()
前面所说的目标值爬坡过程,就是由 FOC 内部提供目标值实现。在目标值爬
坡过程中,正常情况下电流的目标值𝐼𝑑𝑟𝑒𝑓 是由内部计算提供,然后用于电机控制,
而该函数则允许强行修改𝐼𝑑𝑟𝑒𝑓 的值。该函数对于弱磁控制或者 MTPA 使能的时候
是无效的。
代码 20-62 设置𝑰𝒅𝒓𝒆𝒇
01 void MC_SetIdrefMotor1( int16_t hNewIdref )
02 {
STM32 技术开发手册
www.ing10bbs.com
03
04 }
MCI_SetIdref( pMCI[M1], hNewIdref );
MC_Clear_IqdrefMotor1()
该函数可以将𝐼𝑞 ,𝐼𝑞 的目标值重新初始化为默认值。
代码 20-63 复位𝑰𝒒 , 𝑰𝒅 目标值
01 void MC_Clear_IqdrefMotor1(void)
02 {
03
MCI_Clear_Iqdref( pMCI[M1] );
04 }
MC_AcknowledgeFaultMotor1()
用户调用这个函数前,如果电机发生了故障。电机将停留在“FAULT_OVER”
状态,并保留故障代码。在调用了这个函数之后,状态机将清除故障代码的记录,
并恢复到 IDLE 状态。
代码 20-64 应答错误
01 bool MC_AcknowledgeFaultMotor1( void )
02 {
03
return MCI_FaultAcknowledged( pMCI[M1] );
04 }
如果在调用该函数的时候状态机不是在错误状态,则该函数返回 false,否则
返回 true。
在 workbench 上可以找到与该函数相同功能的按钮,见图 20-5。
MC_GetOccurredFaultsMotor1()
该函数用于获取状态机进入“FAULT_NOW”状态之后发生过的故障的代码。
代码 20-65 获取出现过的错误
01 uint16_t MC_GetOccurredFaultsMotor1(void)
02 {
03
return MCI_GetOccurredFaults( pMCI[M1] );
04 }
返回一个 16 位数的故障代码,0 表示没有错误。
表格 20-3 错误列表
状态
MC_NO_ERROR
MC_NO_FAULTS
MC_FOC_DURATION
故障代码
0x0000
0x0000
0x0001
简单解释
无错误
无错误t
FOC计算时间超时
STM32 技术开发手册
www.ing10bbs.com
MC_OVER_VOLT
MC_UNDER_VOLT
MC_OVER_TEMP
MC_START_UP
MC_SPEED_FDBK
MC_BREAK_IN
MC_SW_ERROR
0x0002
0x0004
0x0008
0x0010
0x0020
0x0040
0x0080
总线过压
总线低压
过热
启动失败
速度反馈传感器不可靠
硬件刹车信号输入
软件故障
在 workbench 上可以找到与该函数相同功能的指示灯:
图 20-11 错误提示
MC_GetCurrentFaultsMotor1()
该函数用于获取当前出现的错误状态。
代码 20-66 获取当前出现的错误状态
01 uint16_t MC_GetCurrentFaultsMotor1(void)
02 {
03
return MCI_GetCurrentFaults( pMCI[M1] );
04 }
返回当前的错误状态。错误列表见表格 20-3。
MC_GetSTMStateMotor1()
该函数用于获取当前状态。
代码 20-67 获取状态机当前的状态
01 State_t
02 {
MC_GetSTMStateMotor1(void)
STM32 技术开发手册
www.ing10bbs.com
03
return MCI_GetSTMState( pMCI[M1] );
04 }
返回 State_t 枚举类型的数据。下面列表给出一些常用的电机状态。
表格 20-4 状态机常用状态
状态
IDLE
START
START_RUN
RUN
ANY_STOP
STOP
STOP_IDLE
FAULT_NOW
FAULT_OVER
代码
0
4
5
6
7
8
9
10
11
简单解释
空闲状态
启动进程状态
启动进程状态
正常运行状态
停止状态
停止状态
停止状态
错误状态
错误状态
motor_control_protocol.h 文件内容
该头文件是电机控制协议头文件,用于与上位机通信使用,目前上位机使用
的通信方式是串口通信。该头文件声明了通信协议的实现函数,包括帧接收,帧
发送,传送超时消息,发送 CRC 校验码错误消息等功能函数。
首先是声明了一个通信协议结构体,包含了多个与通信协议相关的成员或函
数指针。
代码 20-68 通信协议控制句柄
01 typedef struct MCP_Handle_s {
02
UI_Handle_t _Super;
/**< Handle structure of User Interface. */
05
FCP_Handle_t *pFCP;
06
FCP_SendFct_t fFcpSend;
07
FCP_ReceiveFct_t fFcpReceive;
08
FCP_AbortReceiveFct_t fFcpAbortReceive;
09
uint8_t BufferFrame[FCP_MAX_PAYLOAD_SIZE]; /**< buffer containing data */
10
const char *s_fwVer;
/**< String of FW version used */
11
DAC_UI_Handle_t * pDAC; /**< Pointer on DAC handle structure. */
12
uint8_t BufferSize;
/**< Frame buffer size */
13 } MCP_Handle_t;
⚫
_Super:用户接口句柄结构体,用于对用户接口函数控制使用。
⚫
*pFCP:指向帧通信协议句柄的指针(Frame communication protocol)。主要
用于处理数据帧相关的事项。
⚫
fFcpSend:帧通信协议发送函数,这是一个函数指针。
⚫
fFcpReceive:帧通信协议接收函数,这是一个函数指针
STM32 技术开发手册
www.ing10bbs.com
⚫
fFcpAbortReceive:帧通信协议中止函数,这是一个函数指针。
⚫
BufferFrame:通信帧缓存数组,FCP_MAX_PAYLOAD_SIZE 是最大有效数据大
小,宏定义为 128。
⚫
s_fwVer:指针,指向固件版本。
⚫
pDAC:指向 DAC 句柄结构体
⚫
BufferSize:帧缓存大小
从结构体成员看来,这是一个通信句柄,有发送帧函数指针,接收帧函数指针,
还有数据缓存数组和大小。
代码 20-69 帧通信函数
01 /* Function used to initialize and configure the motor control protocol Component */
02 void MCP_Init( MCP_Handle_t *pHandle,
03
FCP_Handle_t * pFCP,
04
FCP_SendFct_t fFcpSend,
05
FCP_ReceiveFct_t fFcpReceive,
06
FCP_AbortReceiveFct_t fFcpAbortReceive,
07
DAC_UI_Handle_t * pDAC,
08
const char* s_fwVer);
09 void MCP_OnTimeOut(MCP_Handle_t *pHandle);
10
11 /* Function used for data decoding */
12 void MCP_ReceivedFrame(MCP_Handle_t *pHandle, uint8_t Code, uint8_t *buffer, uint8_t Size);
13
14 /* Function used for data transmission */
15 void MCP_SentFrame(MCP_Handle_t *pHandle, uint8_t Code, uint8_t *buffer, uint8_t Size);
16
17 /* Function used to check next reception frame. */
18 void MCP_WaitNextFrame(MCP_Handle_t *pHandle);
19
20 /* Allow to report the overrun error message. */
21 void MCP_SendOverrunMessage(MCP_Handle_t *pHandle);
22
23 /* Allow to report the time out error message. */
24 void MCP_SendTimeoutMessage(MCP_Handle_t *pHandle);
25
26 /* Allow to send an ATR message. */
27 void MCP_SendATRMessage(MCP_Handle_t *pHandle);
28
29 /* Allow to send back a BAD CRC message. */
30 void MCP_SendBadCRCMessage(MCP_Handle_t *pHandle);
31
接下来是函数的声明,这些函数用于初始化和配置电机控制协议功能。
STM32 技术开发手册
www.ing10bbs.com
motor_control_protocol.c 文件内容
该文件定义了多个电机控制协议的回调函数,也就是接收到电机控制指令的
数据帧之后,对数据帧进行解析,处理对应的电机控制相关的功能。包括读写寄
存器,发送数据帧等功能。
该文件首先定义了私有的宏,这些都是一些常数。然后是定义了枚举变量
ERROE_CODE,用于标志错误代码。
代码 20-70 私有宏定义及枚举变量
01 #define ACK_NOERROR 0xF0
02 #define ACK_ERROR
0xFF
03 #define ATR_FRAME_START 0xE0
04
05 #define MC_PROTOCOL_CODE_NONE
0x00
06
07 /* List of error codes */
08 typedef enum ERROR_CODE_e {
09
ERROR_NONE = 0,
/**< 0x00 – 无错误*/
10
ERROR_BAD_FRAME_ID,
/**< 0x01 – 错误帧 ID,无法识别帧 ID */
11
ERROR_CODE_SET_READ_ONLY,
/**< 0x02 – 主设备试图写 只读寄存器. */
12
ERROR_CODE_GET_WRITE_ONLY, /**< 0x03 – 主设备试图读 只写寄存器 */
13
ERROR_CODE_NO_TARGET_DRIVE, /**< 0x04 – 非目标电机,输入错误的控制对象 */
14
ERROR_CODE_WRONG_SET,
/**< 0x05 – 数据帧的数值超出范围. */
15
ERROR_CODE_CMD_ID,
/**< 0x06 - NOT USED */
16
ERROR_CODE_WRONG_CMD,
/**< 0x07 – 错误的命令 ID.无法识别指令. */
17
ERROR_CODE_OVERRUN,
/**< 0x08 – 接收过溢.主设备发送速度过快,接收帧不正确*/
18
ERROR_CODE_TIMEOUT,
/**< 0x09 – 超时错误.数据帧损坏或无法识别. */
19
ERROR_CODE_BAD_CRC,
/**< 0x0A – CRC 错误 */
20
ERROR_BAD_MOTOR_SELECTED,
/**< 0x0B -非目标电机,输入错误的控制对象. */
21
ERROR_MP_NOT_ENABLED
/**< 0x0C - 未启用 Motor Profiler. */
22 } ERROR_CODE;
MCP_init()
该函数主要用于初始化配置电机控制协议相关句柄,将传递过来的参数赋值
到 MCP_Handle_t 句柄里面,使其中的函数指针指向确切的函数地址,也就是给
句柄成员初始化一个确切值。
代码 20-71 电机控制协议初始化
01 void MCP_Init( MCP_Handle_t *pHandle,
02
FCP_Handle_t * pFCP,
03
FCP_SendFct_t fFcpSend,
04
FCP_ReceiveFct_t fFcpReceive,
05
FCP_AbortReceiveFct_t fFcpAbortReceive,
06
DAC_UI_Handle_t * pDAC,
07
const char* s_fwVer )
08 {
09
pHandle->pFCP = pFCP;
10
pHandle->pDAC = pDAC;
STM32 技术开发手册
www.ing10bbs.com
11
12
13
14
15
16
17
18
19
20
21 }
pHandle->s_fwVer = s_fwVer;
FCP_SetClient( pFCP, pHandle,
(FCP_SentFrameCallback_t) & MCP_SentFrame,
(FCP_ReceivedFrameCallback_t) & MCP_ReceivedFrame,
(FCP_RxTimeoutCallback_t) & MCP_OnTimeOut );
pHandle->fFcpSend = fFcpSend;
pHandle->fFcpReceive = fFcpReceive;
pHandle->fFcpAbortReceive = fFcpAbortReceive;
MCP_WaitNextFrame(pHandle);
上面代码中,将 pFCP 赋值给 pHandle->pFCP,就是将 pHandle->pFCP 这个指
针指向一个确切的句柄,同样原理,初始化函数就是将这些形参链接起来。这里
还有调用一个 FCP_SetClient 函数,这个函数也是同样的作用,将本文件下的
MCP_SentFrame,和 MCP_ReceivedFrame,MCP_OnTimeOut 三个函数的地址传递
过去作为 pFCP 的回调函数,分别在发送数据帧完成,接收到数据帧和超时的时
候就会调用这些回调函数。
MCP_OnTimeOut()
用于设置和报告超时的函数。该函数调用 MCP_WaitNextFrame()函数。 实际
上就是超时回调函数,在接收帧超时的时候重新开始等待接收下一帧数据。
代码 20-72 超时处理函数
01 void MCP_OnTimeOut(MCP_Handle_t *pHandle)
02 {
03
MCP_WaitNextFrame(pHandle);
04 }
MCP_WaitNextFrame()
等待下一帧数据。该函数首先是调用了 pHandle 的 fFcpAbortReceive 函数,
pHandle 就是前面所说的电机控制协议句柄实例,专用于处理跟电机控制协议相
关参数接口。在前面提及到的 MCP_Init 函数中,有这样一条语句:
18
pHandle->fFcpAbortReceive = fFcpAbortReceive;
所以,如果需要知道这个函数实现了什么功能,需要找到调用 MCP_Init 的语句,
看参数(fFcpAbortReceive)是哪个实际函数的地址。
代码 20-73 等待下一帧数据帧
01 void MCP_WaitNextFrame(MCP_Handle_t *pHandle)
02 {
03
pHandle->fFcpAbortReceive(pHandle->pFCP); // 调用函数 UFCP_AbortReceive 标记状态位为空闲状态
04
pHandle->BufferSize = FCP_MAX_PAYLOAD_SIZE; //
STM32 技术开发手册
www.ing10bbs.com
05
06 }
pHandle->fFcpReceive(pHandle->pFCP);
// 调用函数 UFCP_Receive 使能接收中断。
然后设置 BufferSize,为默认的最大值。最后调用了 fFcpReceive 函数,这个
跟 fFcpAbortReceive 一样都是函数指针,需要根据调用 MCP_Init 的语句来查询指
向哪一个函数。在代码 20-73 中的注释就已经说明了实际调用的函数和实现的
功能。
MCP_SentFrame()
该函数在程序中注释为发送数据帧,但实际上是调用的等待接收下一帧数据
(MCP_WaitNextFrame()),只是在发送完数据帧之后调用等待下一帧数据,这是
发送完回调函数。
代码 20-74 发送帧
01 void MCP_SentFrame(MCP_Handle_t *pHandle, uint8_t Code, uint8_t *buffer, uint8_t Size)
02 {
03
MCP_WaitNextFrame(pHandle);
04 }
MCP_ReceivedFrame()
这是接收完成回调函数。该函数用于处理接收数据帧,对接收到的数据帧进
行解析处理,并返回错误码。该函数内容比较多,主要是因为使用的 switch 的
case 比较多,但是这些 case 是可以单独的来分析的。所以下面将逐步解析代码。
在本章节中简单的介绍了串口通信协议,详细的通信的协议将另起一章节详细讲
述。
代码 20-75 接收帧解析数据
01 void MCP_ReceivedFrame(MCP_Handle_t *pHandle, uint8_t Code, uint8_t *buffer, uint8_t Size)
02 {
03
bool RequireAck = true;
04
bool bNoError = false; // Default is error
05
uint8_t bErrorCode;
06
uint8_t bMotorSelection = (Code & 0xE0) >> 5; /* Mask: 1110|0000 */
07
if (bMotorSelection != 0) {
08
if (UI_SetReg(&pHandle->_Super, MC_PROTOCOL_REG_TARGET_MOTOR, bMotorSelection - 1)) {
09
Code &= 0x1F; /* Mask: 0001|1111 */
10
11
/* Change also the DAC selected motor */
12
if (pHandle->pDAC) {
13
UI_SetReg(&pHandle->pDAC->_Super, MC_PROTOCOL_REG_TARGET_MOTOR,
bMotorSelection - 1);
14
}
STM32 技术开发手册
www.ing10bbs.com
15
16
17
18
19
20
} else {
Code = MC_PROTOCOL_CODE_NONE; /* Error */
bErrorCode = ERROR_BAD_MOTOR_SELECTED;
}
}
首先该说明的是 Code 是接收到的数据帧的第一个字节,这个字节的高 3 位
代表着电机编号,低 5 位是帧 ID(FRAME_ID)。代码首先是读取 Code 的高 3 位
bits,高 3 位就是所选择的电机编号,电机最多只有 2 个,所以这个值最大只能
是 2,例程只有 1 个电机,实际传输过来是 1(bMotorSelection = (Code & 0xE0) >>
5; )。在编号不为 0 的情况就调用 UI_SetReg()函数,设置所选择的电机编号,标
志当前的操作是针对该编号的电机。
然后取 Code 的低 5 位,低 5 位主要是帧 ID,也是后面的 Switch 语句用于区
分这帧数据是干什么用的(Code &= 0x1F)。帧 ID 的定义如下:
代码 20-76 帧 ID 宏定义
01 #define MC_PROTOCOL_CODE_SET_REG
0x01
02 #define MC_PROTOCOL_CODE_GET_REG
0x02
03 #define MC_PROTOCOL_CODE_EXECUTE_CMD
0x03
04 #define MC_PROTOCOL_CODE_STORE_TOADDR
0x04
05 #define MC_PROTOCOL_CODE_LOAD_FROMADDR
0x05
06 #define MC_PROTOCOL_CODE_GET_BOARD_INFO
0x06
07 #define MC_PROTOCOL_CODE_SET_RAMP
0x07
08 #define MC_PROTOCOL_CODE_GET_REVUP_DATA
0x08
09 #define MC_PROTOCOL_CODE_SET_REVUP_DATA
0x09
10 #define MC_PROTOCOL_CODE_SET_CURRENT_REF
0x0A
11 #define MC_PROTOCOL_CODE_GET_MP_INFO
0x0B
12 #define MC_PROTOCOL_CODE_GET_FW_VERSION
0x0C
在后面的的设置 switch 语句中,根据 code 的值,进入不同的 case 处理不同
的事项。
1) MC_PROTOCOL_CODE_SET_REG:
设置寄存器的值,这里说的寄存器是指电机控制相关的寄存器。bRegID 是一
个枚举变量,列举了所有可控的寄存器,根据 bRegID 针对不同的寄存器进行配
置,因为不同的寄存器,占用数据宽度不同(8bits、16bits、32bits),所以处理
方式有点差异。
STM32 技术开发手册
www.ing10bbs.com
是用 switch 语句来进入不同的 case 选项以设置不同的寄存器。下面的代码
中并没有列举出所有的 case,但是已经将所有的写寄存器的方式都列出来了。与
电机控制相关的寄存器有部分是 8bit 的,也有 16bit 和 32bit 的,所以针对不同
的寄存器,写入的方式有稍微的不同。值得注意的是,第一个 case 里面,是设置
TARGET_MOTOR 寄存器,设置目标电机这个在前面就已经有了,功能上是重复的,
所以也有注释说明是 Deprecated(弃用)
。其他的 case 则是根据寄存器是 8bit 或
者是 16bit 或者是 32bit,调用 UI_SetReg()函数设置对应的寄存器,然后反馈
结果。
代码 20-77 写寄存器
00 case MC_PROTOCOL_CODE_SET_REG:
01 {
02
MC_Protocol_REG_t bRegID = (MC_Protocol_REG_t)buffer[0];
03
bErrorCode = ERROR_CODE_WRONG_SET;
04
05
switch (bRegID) {
06
case MC_PROTOCOL_REG_TARGET_MOTOR: {
07
/* Deprecated */
08
int32_t wValue = (int32_t)(buffer[1]);
09
10
UI_SetReg(&pHandle->pDAC->_Super, bRegID, wValue);
11
bNoError = UI_SetReg(&pHandle->_Super, bRegID,
12
wValue);
13
}
14
break;
15
case MC_PROTOCOL_REG_CONTROL_MODE:
16
case MC_PROTOCOL_REG_SC_PP: {
17
/* 8bit variables */
18
bNoError = UI_SetReg(&pHandle->_Super, bRegID, (
19
int32_t)(buffer[1]));
20
}
21
break;
22
23
case MC_PROTOCOL_REG_DAC_OUT1: {
24
UI_SetDAC(&pHandle->pDAC->_Super, DAC_CH0, (
25
MC_Protocol_REG_t)(buffer[1]));
26
bNoError = true; /* No check inside class return
27
always true*/
28
}
29
break;
30
31
case MC_PROTOCOL_REG_DAC_OUT2: {
32
UI_SetDAC(&pHandle->pDAC->_Super, DAC_CH1, (
33
MC_Protocol_REG_t)(buffer[1]));
34
bNoError = true; /* No check inside class return
35
always true*/
36
}
37
break;
38
39
case MC_PROTOCOL_REG_TORQUE_REF: {
40
/* 16bit variables */
41
int32_t wValue = buffer[1] + (buffer[2] << 8);
42
bNoError = UI_SetReg(&pHandle->_Super, bRegID,
43
wValue);
44
}
STM32 技术开发手册
www.ing10bbs.com
45
break;
46
47
case MC_PROTOCOL_REG_OBSERVER_C1: {
48
/* 32bit variables */
49
int32_t wValue = buffer[1] + (buffer[2] << 8) + (
50
buffer[3] << 16) + (buffer[4] << 24)
51
;
52
bNoError = UI_SetReg(&pHandle->_Super, bRegID,
53
wValue);
54
}
55
break;
56
57
default: {
58
bErrorCode = ERROR_CODE_SET_READ_ONLY;
59
}
60
break;
61
}
62 }
63 break;
2) MC_PROTOCOL_CODE_GET_REG
该指令是获取寄存器的值,同样,先是提取出寄存器 ID,然后根据 ID 索引
寄存器的值。发送的时候使用 fFcpSend 指针,调用 UFCP_Send()函数。UFCP_Send()
函数填充了帧头、数据长度、数据值和校验码,然后使能发送中断,发送是使用
串口发送中断,所以只需要准备好数据,然后启动就行。
代码 20-78 读寄存器
00 case MC_PROTOCOL_CODE_GET_REG:
01 {
02
MC_Protocol_REG_t bRegID = (MC_Protocol_REG_t)buffer[0];
03
bErrorCode = ERROR_CODE_GET_WRITE_ONLY;
04
05
switch (bRegID) {
06
case MC_PROTOCOL_REG_TARGET_MOTOR:
07
08
{
09
/* 8bit variables */
10
int32_t value = UI_GetReg(&pHandle->_Super, bRegID);
11
if (value != (int32_t)(GUI_ERROR_CODE)) {
12
pHandle->fFcpSend(pHandle->pFCP, ACK_NOERROR, (
13
uint8_t*)(&value), 1);
14
bNoError = true;
15
RequireAck = false;
16
}
17
}
18
break;
19
20
case MC_PROTOCOL_REG_DAC_OUT1: {
21
if (pHandle->pDAC) {
22
MC_Protocol_REG_t value = UI_GetDAC(&pHandle->
23
pDAC->_Super, DAC_CH0);
24
pHandle->fFcpSend(pHandle->pFCP, ACK_NOERROR, (
25
uint8_t*)(&value), 1);
26
bNoError = true;
27
RequireAck = false;
28
}
29
}
30
break;
31
32
case MC_PROTOCOL_REG_DAC_OUT2: {
STM32 技术开发手册
www.ing10bbs.com
33
if (pHandle->pDAC) {
34
MC_Protocol_REG_t value = UI_GetDAC(&pHandle->
35
pDAC->_Super, DAC_CH1);
36
pHandle->fFcpSend(pHandle->pFCP, ACK_NOERROR, (
37
uint8_t*)(&value), 1);
38
bNoError = true;
39
}
40
}
41
break;
42
43
case MC_PROTOCOL_REG_SPEED_KP: {
44
int32_t value = UI_GetReg(&pHandle->_Super, bRegID);
45
if (value != (int32_t)(GUI_ERROR_CODE)) {
46
/* 16bit variables */
47
pHandle->fFcpSend(pHandle->pFCP, ACK_NOERROR, (
48
uint8_t*)(&value), 2);
49
bNoError = true;
50
RequireAck = false;
51
}
52
}
53
break;
54
55
case MC_PROTOCOL_REG_SPEED_REF:
56
case MC_PROTOCOL_REG_SPEED_MEAS: {
57
int32_t value = UI_GetReg(&pHandle->_Super, bRegID);
58
if (value != (int32_t)(GUI_ERROR_CODE)) {
59
/* 32bit variables */
60
pHandle->fFcpSend(pHandle->pFCP, ACK_NOERROR, (
61
uint8_t*)(&value), 4);
62
bNoError = true;
63
RequireAck = false;
64
}
65
}
66
break;
67
68
default:
69
bErrorCode = ERROR_CODE_GET_WRITE_ONLY;
70
break;
71
}
72 }
73 break;
如果需要读取当前的速度值,可以找到 MC_PROTOCOL_REG_SPEED_MEAS 这
个 case,然后就可以找到读取速度值的代码。上面代码中的 57~64 行就是读取
速度值的语句,得到的 Value 就是速度值,数据长度是 32bits。
3) MC_PROTOCOL_CODE_EXECUTE_CMD
这个 case 主要是执行一些功能指令,像启动电机、停止电机等一些可以在
workbench 里面设置的功能。
代码 20-79 执行指令
00
01
02
03
04
05
06
case MC_PROTOCOL_CODE_EXECUTE_CMD:
{
uint8_t bCmdID = buffer[0];
bErrorCode = ERROR_CODE_WRONG_CMD;
bNoError = UI_ExecCmd(&pHandle->_Super,bCmdID);
}
break;
STM32 技术开发手册
www.ing10bbs.com
bErrorCode 是用于反馈的错误码,如果调用 UI_ExecCmd 返回 false,则
bErrorCode 就派上用场,否则是没意义的语句。
4) MC_PROTOCOL_CODE_GET_BOARD_INFO
这个 case 用于获取板子的信息主要是用于在 Workbench 上显示 FOC SDK 的
版本号。
代码 20-80 获取板子的信息
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
case MC_PROTOCOL_CODE_GET_BOARD_INFO:
{
/* GetBoardInfo */
unsigned char i;
uint8_t outBuff[32];
for (i = 0; i < 32; i++) {
outBuff[i] = 0;
}
for (i = 0; (i<29) && (pHandle->s_fwVer[i]!='\t'); i++) {
outBuff[3+i] = pHandle->s_fwVer[i];
}
outBuff[0] = pHandle->s_fwVer[i+5];
outBuff[1] = pHandle->s_fwVer[i+7];
outBuff[2] = pHandle->s_fwVer[i+9];
pHandle->fFcpSend(pHandle->pFCP, ACK_NOERROR, outBuff, 32 );
bNoError = true;
}
break;
s_fwVer 所指向的字符串是固件库版本号。
代码 20-81 固件库版本号
00 #define FIRMWARE_VERS "HALL Sensor ST MC SDK\tVer.5.2.0"
5) MC_PROTOCOL_CODE_SET_RAMP
设置爬坡的参数,包括时间周期和最终速度值。
代码 20-82 设置爬坡参数
00
01
02
03
04
05
06
07
08
case MC_PROTOCOL_CODE_SET_RAMP:
{
uint16_t duration = buffer[4] + (buffer[5] << 8);
int32_t rpm = buffer[0] + (buffer[1] << 8) + (buffer[2] <
< 16) + (buffer[3] << 24);
bNoError = UI_ExecSpeedRamp(&pHandle->_Super, rpm,
duration);
}
break;
6) MC_PROTOCOL_CODE_GET_REVUP_DATA
这是无感模式的开环启动参数,包括速度,电流,时间。读取出来主要是在
workbench 上显示出来。有感模式不需要设置开环参数,所以在有感模式中是不
需要理会。
STM32 技术开发手册
www.ing10bbs.com
图 20-12 workbench 中显示开环参数
7) MC_PROTOCOL_CODE_SET_REVUP_DATA
同样也是设置无感模式的开环启动参数。有感模式是不需要理会的。
8) MC_PROTOCOL_CODE_SET_CURRENT_REF
电流目标值设置,在扭矩模式设置的电流值,设置 Id 和 Iq 的目标值。
代码 20-83 设置电流值
00
01
02
03
04
05
06
08
09
10
case MC_PROTOCOL_CODE_SET_CURRENT_REF:
{
int16_t hIqRef;
int16_t hIdRef;
hIqRef = buffer[0] + (buffer[1] << 8);
hIdRef = buffer[2] + (buffer[3] << 8);
UI_SetCurrentReferences(&pHandle->_Super, hIqRef, hIdRef);
bNoError = true;
}
break;
9) MC_PROTOCOL_CODE_GET_MP_INFO
获取 Motor Profiler 的信息,Motor Profiler 功能只能使用 st 的开发板的才可用使
用。这里不做介绍。
10) MC_PROTOCOL_CODE_GET_FW_VERSION
获取固件库版本号。直接反馈代码 20-81,这次是完全将所有的字符反馈到
workbench。
STM32 技术开发手册
www.ing10bbs.com
MCP_SendOverrunMessage()
这是错误反馈函数,当由于 workbench 发送数据帧频率太高导致接收帧不完
全的时候就会调用这个函数。反馈错误信息。实际上,这个函数并没有被调用。
MCP_SendTimeoutMessage()
反馈通信超时错误。实际上没有调用这个函数。
MCP_SendATRMessage()
发送应答信息,实际上没有调用这个函数。
MCP_SendBadCRCMessage()
发送 CRC 校验失败信息,实际上没有调用这个函数。
mc_config.c 文件内容
该文件包含有各个组件的全局变量初始化。这些全局变量全部都是各个组件
的控制句柄,主要是一些常量的初始化,部分变量的初始化是在程序运行期间赋
值的。
1. PQD_MotorPowMeasM1
代码 20-84
00 PQD_MotorPowMeas_Handle_t PQD_MotorPowMeasM1 = {
01
.wConvFact = PQD_CONVERSION_FACTOR/*!< 这是转换系数,将数字量表示的功率转换成
以物理量(W)为单位的数值 */
02 };
03 PQD_MotorPowMeas_Handle_t *pPQD_MotorPowMeasM1 = &PQD_MotorPowMeasM1;
PQD_MotorPowMeasM1 是电机 1 的功率测量组件句柄,wConvFact 就是功
率装换系数,用于计算实际的功率,通过测量得到的数值是数字量,转换成物理
量需要使用这个系数,该系数 PQD_CONVERSION_FACTOR 宏定义
代码 20-85 功率转换系数宏定义
00 #define PQD_CONVERSION_FACTOR (int32_t)((1000 * 3 * MCU_SUPPLY_VO 如下
LTAGE) /\
01
( 1.732 * RSHUNT * AMPLIFICATION_GAIN ))
公式如下:
Factor =
1000 ∗ 3 ∗ 𝑉𝑑𝑑
2
√3 ∗ 𝑅𝑠ℎ𝑢𝑛𝑡 ∗ 𝐴𝑜𝑝
STM32 技术开发手册
www.ing10bbs.com
同时也有一个句柄指针,但并没有发现有使用到这个指针的地方,这不代表
这个系数没有被使用,在计算功率的时候还是有使用这个系数进行计算的。
2. PIDSpeedHandle_M1
PIDSpeedHandle_M1 是速度环的 PID 控制句柄,初始化了 Kp,Ki,Kd 的值,
这些值都是在创建工程的时候在 workbench 上设定的数值,
代码 20-86 速度模式的 PID 控制句柄
00 PID_Handle_t PIDSpeedHandle_M1 = {
01
.hDefKpGain
= (int16_t)PID_SPEED_KP_DEFAULT, /*!< 默认的 Kp 值,用
于初始化 Kp 变量 */
02
.hDefKiGain
= (int16_t)PID_SPEED_KI_DEFAULT, /*!< 默认的 Ki 值,用
于初始化 Kp 变量 */
03
04
.wUpperIntegralLimit = (int32_t)IQMAX * (int32_t)SP_KIDIV, /*!< 积分项
的上限 */
05
.wLowerIntegralLimit = -(int32_t)IQMAX * (int32_t)SP_KIDIV, /*!< 积分项
的下限 */
06
.hUpperOutputLimit = (int16_t)IQMAX,
/*!< PI 项输出上限*/
07
.hLowerOutputLimit = -(int16_t)IQMAX,
/*!< PI 项输出下限*/
08
.hKpDivisor
= (uint16_t)SP_KPDIV,
/*!< Kp 的分母*/
09
.hKiDivisor
= (uint16_t)SP_KIDIV,
/*!< Ki 的分母*/
10
.hKpDivisorPOW2
= (uint16_t)SP_KPDIV_LOG, /*!< hKpDivisor 的对数
(2^n= hKpDivisor)*/
11
.hKiDivisorPOW2
= (uint16_t)SP_KIDIV_LOG, /*!< hKiDivisor 的对数
(2^n= hKiDivisor)*/
12
.hDefKdGain
= 0x0000U, /*!< 默认 Pd 值,用于初始化 Kd 变量*/
13
.hKdDivisor
= 0x0000U, /*!< Kd 的分母 */
14
.hKdDivisorPOW2
= 0x0000U,/*!< hKdDivisor 的对数(2^n= hKdDivisor)*/
15 };
PID 的系数 Kp,Ki,Kd 采用分子除以分母的形式代替浮点数运算,实际上是
一种运算过程的优化,除法的分母也是使用 2 的指数倍,方便使用移位代替除
法,整个 PID 计算过程都是使用经典 PID 算法,并没有什么不同。积分项的上限
和下限则是通过计算出来的,使用输出的最大值乘上分母。
图 20-13 Workbench 上的 PI 设置
STM32 技术开发手册
www.ing10bbs.com
3. PIDIqHandle_M1
这是电流环 Iq 的 PID 控制句柄,介绍同 PIDSpeedHandle_M1。
4. PIDIdHandle_M1
这是电流环 Id 的 PID 控制句柄,介绍同 PIDSpeedHandle_M1。
5. SpeednTorqCtrlM1
速度和扭矩的控制句柄,这里初始化了更新频率,定义了速度和扭矩的数值
限制范围,还有一些默认的数值。
代码 20-87 速度扭矩控制句柄
00 SpeednTorqCtrl_Handle_t SpeednTorqCtrlM1 = {
01
.STCFrequencyHz =
MEDIUM_FREQUENCY_TASK_RATE, /*!< 更新参考值
的频率(控制频率),单位是 Hz */
02
.MaxAppPositiveMecSpeed01Hz = (uint16_t)(MAX_APPLICATION_SPEED/6),
/*!<最大的正值应用速度,单位是 0.1Hz.*/
03
.MinAppPositiveMecSpeed01Hz = (uint16_t)(MIN_APPLICATION_SPEED/6),
/*!< 最小的正值应用速度,单位是 0.1Hz.*/
04
.MaxAppNegativeMecSpeed01Hz = (int16_t)(-MIN_APPLICATION_SPEED/6),
/*!< 最小的负值应用速度,单位是 0.1Hz.*/
05
.MinAppNegativeMecSpeed01Hz = (int16_t)(-MAX_APPLICATION_SPEED/6),
/*!<最大的负值应用速度,单位是 0.1Hz.*/
06
.MaxPositiveTorque =
(int16_t)NOMINAL_CURRENT,/*!< 正值的 Iq 最大
值,使用数字量表达*/
07
.MinNegativeTorque =
-(int16_t)NOMINAL_CURRENT,/*!< 负值的 Iq 最小
值,使用数字量表达*/
08
.ModeDefault =
DEFAULT_CONTROL_MODE,/*!< 默认的速度扭矩控制模式.*/
09
.MecSpeedRef01HzDefault = (int16_t)(DEFAULT_TARGET_SPEED_RPM/6),/*!<
默认的电机目标速度,单位是 0.Hz.*/
10
.TorqueRefDefault =
(int16_t)DEFAULT_TORQUE_COMPONENT,/*!< 默认的电
机目标扭矩,Iq 值,使用数字量表达.*/
11
.IdrefDefault =
(int16_t)DEFAULT_FLUX_COMPONENT, /*!< 默认的 Id 目
标值.*/
12 };
6. RevUpControlM1
开环启动的控制句柄,这是应用在无感模式下的开环控制句柄, 主要是设
置了开环运行时候的参数,在有感模式是不会用到这个句柄的,现在暂不讨论。
7. PWM_Handle_M1
这是 PWM 的控制句柄,首先是各种函数指针,指向各个实例函数。其他的
就是与 PWM 相关的变量,用于控制最终的 PWM 输出。
代码 20-88 PWM 控制句柄
00 PWMC_R3_F4_Handle_t PWM_Handle_M1= {
01
{
02
/*!< 各种函数指针 */
03
.pFctGetPhaseCurrents
= &R3F4XX_GetPhaseCurrents,
STM32 技术开发手册
www.ing10bbs.com
04
.pFctSwitchOffPwm
= &R3F4XX_SwitchOffPWM,
05
.pFctSwitchOnPwm
= &R3F4XX_SwitchOnPWM,
06
.pFctCurrReadingCalib
=
&R3F4XX_CurrentReadingCalibration,
07
.pFctTurnOnLowSides
= &R3F4XX_TurnOnLowSides,
08
.pFctSetADCSampPointSect1
= &R3F4XX_SetADCSampPointSect1,
09
.pFctSetADCSampPointSect2
= &R3F4XX_SetADCSampPointSect2,
10
.pFctSetADCSampPointSect3
= &R3F4XX_SetADCSampPointSect3,
11
.pFctSetADCSampPointSect4
= &R3F4XX_SetADCSampPointSect4,
12
.pFctSetADCSampPointSect5
= &R3F4XX_SetADCSampPointSect5,
13
.pFctSetADCSampPointSect6
= &R3F4XX_SetADCSampPointSect6,
14
.pFctIsOverCurrentOccurred
= &R3F4XX_IsOverCurrentOccurred,
15
.pFctOCPSetReferenceVoltage
= MC_NULL,
16
.pFctRLDetectionModeEnable
= &R3F4XX_RLDetectionModeEnable,
17
.pFctRLDetectionModeDisable
= &R3F4XX_RLDetectionModeDisable,
18
.pFctRLDetectionModeSetDuty
= &R3F4XX_RLDetectionModeSetDuty,
19
.hT_Sqrt3 = (PWM_PERIOD_CYCLES*SQRT3FACTOR)/16384u,
/*!< 包含 PWM 算
法的常量 */
20
.hSector = 0,
/*!< 空间向量扇区号 */
21
.hCntPhA = 0,
/*!< A 相的比较值 */
22
.hCntPhB = 0,
23
.hCntPhC = 0,
24
.SWerror = 0,
/*!< 软件错误编号 */
25
.bTurnOnLowSidesAction = false, /*!< 返回低端 MOS 管的状态. */
26
.hOffCalibrWaitTimeCounter = 0, /*!< 电机电流测量校准之前的等待时间 计数
器 */
27
.bMotor = 0,
/*!< 电机编号 */
28
.RLDetectionMode = false, /*!< RL 检测模式,true if enabled, false if
disabled. */
29
.hIa = 0, /* 上一次的 Ia 测量值 */
30
.hIb = 0, /* 上一次的 Ib 测量值. */
31
.hIc = 0, /* 上一次的 Ic 测量值. */
32
.DTTest = 0,
/* Reserved */
33
.DTCompCnt = 0, /* Reserved */
34
35
.hPWMperiod
= PWM_PERIOD_CYCLES,
/*!< PWM 周期 hPWMPeriod
= Timer Fclk / Fpwm
*/
36
.hOffCalibrWaitTicks = (uint16_t)((SYS_TICK_FREQUENCY *
OFFCALIBRWAIT_MS)/ 1000),/*!<电机电流测量校准之前的等待时间 */
37
.hDTCompCnt
= DTCOMPCNT,
/*!< 死区时间的一
半 hDTCompCnt = (DT(s) * Timer Fclk)/2 */
38
.Ton
= TON,
/*!< Reserved */
39
.Toff
= TOFF
/*!< Reserved */
40
},
41
.wPhaseAOffset = 0, /*!< A 相电流的偏移值/偏差值 */
42
.wPhaseBOffset = 0, /*!< B 相电流的偏移值/偏差值 */
43
.wPhaseCOffset = 0, /*!< C 相电流的偏移值/偏差值 */
44
.wADC1Channel = 0,
/*!< 电机电流的 ADC1 采样通道*/
45
.wADC2Channel = 0,
/*!< 电机电流的 ADC2 采样通道*/ */
46
.Half_PWMPeriod = PWM_PERIOD_CYCLES/2u, /* PWM 周期值的一半 */
47
.bSoFOC = 0,/*!< 用于检测 FOC 的运行情况,在运行 FOC 之前置 0,在定时器更新中断置
1,如果检测到置 1 则说明 FOC 运行频率过高*/
48
.bIndex = 0,
49
.wADCTriggerSet = 0, /*!< 存储 ADC CR2 的配置数值*/
50
.wADCTriggerUnSet = 0, /*!< 存储 ADC CR2 的配置数值*/
51
52
.pParams_str = &R3_F4_ParamsM1,/*!< 电机 1 的定时器硬件参数*/
53
54 };
虽然句柄的成员比较多,但是不难理解,函数指针都是指向固定的函数地址,
这些都是在 FOC 算法运行的时候使用的。
STM32 技术开发手册
www.ing10bbs.com
8. HALL_M1
霍尔传感器的控制句柄。初始化了跟霍尔传感器相关的定时器各项参数,第
一个成员就是霍尔传感器的实际参数。包括极对数,最大可测量速度等参数,还
有使用定时器作为霍尔传感器的接口。
代码 20-89 霍尔传感器控制句柄
00 HALL_Handle_t HALL_M1 = {
01
._Super = {
02
.bElToMecRatio
= POLE_PAIR_NUM,
/*!< 极对数*/
03
.hMaxReliableMecSpeed01Hz =
(uint16_t)(1.15*MAX_APPLICATION_SPEED/6), /*!< 最大可测量速度,单位是 0.1Hz */
04
.hMinReliableMecSpeed01Hz
=
(uint16_t)(MIN_APPLICATION_SPEED/6),/*!< 最小测量速度,单位是 0.1Hz */
05
.bMaximumSpeedErrorsNumber = MEAS_ERRORS_BEFORE_FAULTS,
/*!< 记录多少次无效测量之后反馈测速失败 */
06
.hMaxReliableMecAccel01HzP = 65535, /*!< 测量的加速度最大值,单位是
0.1Hz 每计算周期 */
07
.hMeasurementFrequency = TF_REGULATION_RATE, /*!< 测量速度的频率 */
08
},
09
/* SW Settings */
10
.SensorPlacement
= HALL_SENSORS_PLACEMENT,
11
/*!< 传感器的放置位置,DEGREES_120 or DEGREES_60.*/
12
.PhaseShift
= (int16_t)(HALL_PHASE_SHIFT * 65536/360),/*!< H1 到
A 相反电动势之间的相位差,使用数字量表达.*/
13
14
.SpeedSamplingFreqHz = MEDIUM_FREQUENCY_TASK_RATE, /*!< 计算电机速度的频
率,单位是 Hz,与 SPD_CalcAvrgMecSpeed01Hz 函数被调用的频率相同*/
15
16
.SpeedBufferSize
= HALL_AVERAGING_FIFO_DEPTH, /*!< 霍尔速度值 FIFO 缓
存大小,必须小于 18.*/
17
18
/* HW Settings */
19
.TIMClockFreq
= HALL_TIM_CLK,
/*!< 定时器时钟计数频率 in
Hz.*/
20
21
.TIMx
= HALL_TIM5,
/*!< 定时器霍尔接口.*/
22
23
.H1Port
= M1_HALL_H1_GPIO_Port, /*!< 霍尔 H1 的 GPIO 端口 */
24
.H1Pin
= M1_HALL_H1_Pin,
/*!< 霍尔 H1 的 GPIO 引脚 */
25
.H2Port
= M1_HALL_H2_GPIO_Port,
26
.H2Pin
= M1_HALL_H2_Pin,
27
.H3Port
= M1_HALL_H3_GPIO_Port,
28
.H3Pin
= M1_HALL_H3_Pin,
29 };
9. TempSensorParamsM1
温度传感器参数。这里主要初始化了温度的传感器参数。定义了相关的采样
ADC 通道和高温阈值等参数。
hLowPassFilterBW 翻译为低通滤波系数,实际上是计算平均值的系数
代码 20-90 温度测量传感器参数
00 NTC_Handle_t TempSensorParamsM1 = {
STM32 技术开发手册
www.ing10bbs.com
01
.bSensorType = REAL_SENSOR,
// 传感器类型,为实际的传感器,否则就是虚拟传感
器,虚拟传感器只会反馈期望值
02
.TempRegConv =
// 温度采样的 ADC 相关通道
03
{
04
.regADC = ADC1,
05
.channel = MC_ADC_CHANNEL_13,
06
.samplingTime = M1_TEMP_SAMPLING_TIME,
07
},
08
.hLowPassFilterBW = M1_TEMP_SW_FILTER_BW_FACTOR, // 温度值软件低通滤波系
数
09
.hOverTempThreshold
= (uint16_t)(OV_TEMPERATURE_THRESHOLD_d),// 高
温阈值
10
.hOverTempDeactThreshold = (uint16_t)(OV_TEMPERATURE_THRESHOLD_d OV_TEMPERATURE_HYSTERESIS_d),// 高温阈值,差值
11
.hSensitivity = (uint16_t)(MCU_SUPPLY_VOLTAGE/dV_dT), // 灵敏度,每摄氏
度的数字量变化
12
.wV0 = (uint16_t)(V0_V *65536/ MCU_SUPPLY_VOLTAGE),// 初始的电压值数字量
13
.hT0 = T0_C,
// 初始温度,摄氏度
14 };
10. RealBusVoltageSensorParamsM1
总线电压测量句柄,整体结构跟温度传感器参数句柄比较接近。都只是初始
化了一些系数值。
这里的 LowPassFilterBW,翻译为低通滤波,实际上是计算平均值的系数。
代码 20-91 总线电压传感器参数
00 RDivider_Handle_t RealBusVoltageSensorParamsM1 = {
01
._Super
=
02
{
03
.SensorType
= REAL_SENSOR,
/*!< 实际传感器 */
04
.ConversionFactor
= (uint16_t)(MCU_SUPPLY_VOLTAGE /
BUS_ADC_CONV_RATIO),
05
/*!< 电压转换系数,将数字量转换成实际物理量的系数*/
06
},
07
08
.VbusRegConv = // ADC 采样通道
09
{
10
.regADC = ADC1,
11
.channel = MC_ADC_CHANNEL_10,
12
.samplingTime = M1_VBUS_SAMPLING_TIME,
13
},
14
15
.LowPassFilterBW
= M1_VBUS_SW_FILTER_BW_FACTOR,
/*!< 低通滤波系数
hLowPassFilterBW = VBS_CalcBusReading rate [Hz]/ FilterBandwidth[Hz] */
16
17
.OverVoltageThreshold = OVERVOLTAGE_THRESHOLD_d,
/*!< 过压阈值,数字
量,计算公式为
hOverVoltageThreshold (digit) =
Over Voltage Threshold (V) * 65536/ hConversionFactor */
21
.UnderVoltageThreshold = UNDERVOLTAGE_THRESHOLD_d,
/*!< 低压阈值,数字
量,计算公式为 hUnderVoltageThreshold (digit) =Under Voltage Threshold (V) *
65536/ hConversionFactor */
25 };
11. UI_Params
STM32 技术开发手册
www.ing10bbs.com
代码 20-92 DAC_UI 参数句柄
00 UI_Handle_t UI_Params = {
01
.bDriveNum = 0,
02
.pFct_DACInit = &DAC_Init,
03
.pFct_DACExec = &DAC_Exec,
04
.pFctDACSetChannelConfig
= &DAC_SetChannelConfig,
05
.pFctDACGetChannelConfig
= &DAC_GetChannelConfig,
06
.pFctDACSetUserChannelValue = &DAC_SetUserChannelValue,
07
.pFctDACGetUserChannelValue = &DAC_GetUserChannelValue,
08
09 };
DAC 的 UI 参数初始化比较单调,只是配置了函数指针所指向的实例函数而已。
12. RampExtMngrHFParamsM1
速度爬坡频率管理,这里初始化了速度变化的频率,单位是 Hz,主要应在速
度控制的时候。
代码 20-93 速度管理参数句柄
00 RampExtMngr_Handle_t RampExtMngrHFParamsM1 = {
01
.FrequencyHz = TF_REGULATION_RATE /*!< Execution frequency expressed
in Hz */
02 };
13. CircleLimitationM1
限制圆的参数。在极性坐标变换的时候需要注意限制向量不能超过一个圆的
范围。这里就是初始化一些参数
代码 20-94 圆的限制
00 CircleLimitation_Handle_t CircleLimitationM1 = {
01
.MaxModule
= MAX_MODULE, /*!< Circle limitation maximum allowed
02
module */
03
.Circle_limit_table = MMITABLE,
/*!< Circle limitation table */
04
.Start_index
= START_INDEX, /*!< Circle limitation table indexing
05
start */
06 };
14. pUSART
串口通信句柄,初始化了使用哪一个串口通信和接收超时时间。
代码 20-95 串口通信初始化
00 UFCP_Handle_t pUSART = {
01
._Super.RxTimeout = 0,
02
03
.USARTx
= USART,
04
05 };
STM32 技术开发手册
www.ing10bbs.com
mc_task.c 文件内容
该文件定义了各个电机控制相关任务,主要是 FOC 状态机的实现函数。
下面先看该文件下的宏定义:
代码 20-96 宏定义
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
CHARGE_BOOT_CAP_MS 10
CHARGE_BOOT_CAP_MS2 10
OFFCALIBRWAIT_MS
0
OFFCALIBRWAIT_MS2
0
STOPPERMANENCY_MS 400
STOPPERMANENCY_MS2 400
CHARGE_BOOT_CAP_TICKS (uint16_t)((SYS_TICK_FREQUENCY *
CHARGE_BOOT_CAP_MS)/ 1000)
CHARGE_BOOT_CAP_TICKS2 (uint16_t)((SYS_TICK_FREQUENCY *
CHARGE_BOOT_CAP_MS2)/ 1000)
OFFCALIBRWAITTICKS
(uint16_t)((SYS_TICK_FREQUENCY *
OFFCALIBRWAIT_MS)/ 1000)
OFFCALIBRWAITTICKS2
(uint16_t)((SYS_TICK_FREQUENCY *
OFFCALIBRWAIT_MS2)/ 1000)
STOPPERMANENCY_TICKS (uint16_t)((SYS_TICK_FREQUENCY *
STOPPERMANENCY_MS)/ 1000)
STOPPERMANENCY_TICKS2 (uint16_t)((SYS_TICK_FREQUENCY *
STOPPERMANENCY_MS2)/ 1000)
这些宏定义并不是全部都有使用的,尾部带有 2 的宏表示用于电机 2 的,但是本
例程只有电机 1,所以这一些是可以注释掉的。这些宏主要是定义了一些时间常
数和这些常数用于 Systick 的时候所需要的计数值
MCboot()
该函数是电机启动函数,主要是启动 FOC 控制功能,初始化了定时器、ADC、
状态机等,并且标志电机已经成功初始化,初始化成功之后,状态机会进入 IDLE
状态,等待通讯指令或者外部信号控制启动电机,启动的时候会按照预先设置的
速度值或扭矩值启动,当然这个值是可以在运行的时候实时修改,
代码 20-97 电机启动
00 void MCboot( MCI_Handle_t* pMCIList[NBR_OF_MOTORS],MCT_Handle_t*
pMCTList[NBR_OF_MOTORS] )
01 {
02
bMCBootCompleted = 0;
03
pCLM[M1] = &CircleLimitationM1;
04
05
/**********************************************************/
06
/*
PWM 和电流传感器组件初始化
*/
07
/**********************************************************/
08
pwmcHandle[M1] = &PWM_Handle_M1._Super;
09
R3F4XX_Init(&PWM_Handle_M1);
10
11
/**************************************/
12
/*
同步启动定时器
*/
STM32 技术开发手册
www.ing10bbs.com
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/**************************************/
startTimers();
/**************************************/
/*
状态机初始化
*/
/**************************************/
STM_Init(&STM[M1]);
/******************************************************/
/* PID 组件初始化,速度模式 */
/******************************************************/
PID_HandleInit(&PIDSpeedHandle_M1);
/******************************************************/
/* 主要速度传感器组件初始化
*/
/******************************************************/
pPIDSpeed[M1] = &PIDSpeedHandle_M1;
pSTC[M1] = &SpeednTorqCtrlM1;
HALL_Init (&HALL_M1);
/******************************************************/
/* 速度和扭矩控制初始化
*/
/******************************************************/
STC_Init(pSTC[M1],pPIDSpeed[M1], &HALL_M1._Super);
/********************************************************/
/* PID 初始化,扭矩模式 */
/********************************************************/
PID_HandleInit(&PIDIqHandle_M1);
PID_HandleInit(&PIDIdHandle_M1);
pPIDIq[M1] = &PIDIqHandle_M1;
pPIDId[M1] = &PIDIdHandle_M1;
/********************************************************/
/* 总线电压传感器初始化
*/
/********************************************************/
pBusSensorM1 = &RealBusVoltageSensorParamsM1;
RVBS_Init(pBusSensorM1);
/*************************************************/
/* 功率测量组件初始化 */
/*************************************************/
pMPM[M1] = &PQD_MotorPowMeasM1;
pMPM[M1]->pVBS = &(pBusSensorM1->_Super);
pMPM[M1]->pFOCVars = &FOCVars[M1];
/*******************************************************/
/* 温度测量组件初始化 */
/*******************************************************/
NTC_Init(&TempSensorParamsM1);
pTemperatureSensor[M1] = &TempSensorParamsM1;
/*******************************************************/
/* 各项变量的初始化 */
/*******************************************************/
pREMNG[M1] = &RampExtMngrHFParamsM1;
REMNG_Init(pREMNG[M1]);
FOC_Clear(M1);
FOCVars[M1].bDriveInput = EXTERNAL;
FOCVars[M1].Iqdref = STC_GetDefaultIqdref(pSTC[M1]);
FOCVars[M1].UserIdref = STC_GetDefaultIqdref(pSTC[M1]).qI_Component2;
oMCInterface[M1] = & Mci[M1];
MCI_Init(oMCInterface[M1], &STM[M1], pSTC[M1], &FOCVars[M1]);
MCI_ExecSpeedRamp(oMCInterface[M1],
STC_GetMecSpeedRef01HzDefault(pSTC[M1]),0); /*First command to STC*/
pMCIList[M1] = oMCInterface[M1];
MCT[M1].pPIDSpeed = pPIDSpeed[M1];
STM32 技术开发手册
www.ing10bbs.com
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105 }
MCT[M1].pPIDIq = pPIDIq[M1];
MCT[M1].pPIDId = pPIDId[M1];
MCT[M1].pPIDFluxWeakening = MC_NULL; /* if M1 doesn't has FW */
MCT[M1].pPWMnCurrFdbk = pwmcHandle[M1];
MCT[M1].pRevupCtrl = MC_NULL; /* only if M1 is not sensorless*/
MCT[M1].pSpeedSensorMain = (SpeednPosFdbk_Handle_t *) &HALL_M1;
MCT[M1].pSpeedSensorAux = MC_NULL;
MCT[M1].pSpeedSensorVirtual = MC_NULL;
MCT[M1].pSpeednTorqueCtrl = pSTC[M1];
MCT[M1].pStateMachine = &STM[M1];
MCT[M1].pTemperatureSensor = (NTC_Handle_t *) pTemperatureSensor[M1];
MCT[M1].pBusVoltageSensor = &(pBusSensorM1->_Super);
MCT[M1].pBrakeDigitalOutput = MC_NULL; /* brake is defined, oBrakeM1*/
MCT[M1].pNTCRelay = MC_NULL;
/* relay is defined, oRelayM1*/
MCT[M1].pMPM = (MotorPowMeas_Handle_t*)pMPM[M1];
MCT[M1].pFW = MC_NULL;
MCT[M1].pFF = MC_NULL;
MCT[M1].pSCC = MC_NULL;
MCT[M1].pOTT = MC_NULL;
pMCTList[M1] = &MCT[M1];
/*******************************************************/
/* 标志成功初始化 */
/*******************************************************/
bMCBootCompleted = 1;
在代码 20-97 中,从注释就可以看得到,初始化配置了 PWM 和电流采样、
启动定时器、状态机、PID 参数、总线和温度传感器、功率测量、速度和扭矩控
制等组件。也可以看到有一些参数是设置为 MC_NULL,这说明这些功能是没有
用到的。
MC_RunMotorControlTasks()
运行所有电机控制任务,该函数在系统滴答定时器中断中被调用,也就是从
程序的运行开始就一直会被调用到,但是函数内部使用了 if 语句判断是否已经启
动的电机控制(bMCBootCompleted),所以这个函数实际上就是在电机控制启动
了之后才会生效,并且是每隔固定的周期才会被调用一次。
该函数执行三个任务,一个是中频任务(执行频率为中等),一个是安全任
务,用户接口任务。
代码 20-98 执行所有的电机控制任务
00 void MC_RunMotorControlTasks(void)
01 {
02
if ( bMCBootCompleted ) {
03
/* ** Medium Frequency Tasks ** */
04
MC_Scheduler();
05
06
/* ** Safety Task ** */
07
/* 在执行外中等频率的任务之后执行安全监测任务,
08
* 以便可以在需要的时候,覆盖控制状态,并初始化变量
09
* */
STM32 技术开发手册
www.ing10bbs.com
10
11
12
13
14
15 }
TSK_SafetyTask();
/* ** User Interface Task ** */
UI_Scheduler();
}
MC_Scheduler()
中频任务,执行频率与 Systick 中断频率相同,因为就是在 Systick 中断中调
用的任务。
代码 20-99 中频任务
00 void MC_Scheduler(void)
01 {
02
if (bMCBootCompleted == 1) {
03
if (hMFTaskCounterM1 > 0u) {
04
hMFTaskCounterM1--;
05
} else {
06
TSK_MediumFrequencyTaskM1();
07
hMFTaskCounterM1 = MF_TASK_OCCURENCE_TICKS;
08
}
09
if (hBootCapDelayCounterM1 > 0u) {
10
hBootCapDelayCounterM1--;
11
}
12
if (hStopPermanencyCounterM1 > 0u) {
13
hStopPermanencyCounterM1--;
14
}
15
}
16 }
从代码中可以看得出来,先将 hMFTaskCounterM1 自减,自减到 0 的时候执
行调用 TSK_MediumFrequencyTaskM1()函数,这个函数才是实际的中频任务,这
是状态机的实现函数,状态机的状态更改和功能的执行都是在这个函数里面执行。
MF_TASK_OCCURENCE_TICKS 的这个宏,数值上计算出来是 3,但是是自减到 0 才
执行,所以应该是 4 个定时周期之后才执行一次,每个定时周期是 500us(滴答
定时器的中断时间间隔)。所以是 2ms 执行一次,也是在 workbench 上设置的速
度环执行频率。
TSK_MediumFrequencyTaskM1()
实际上的中频任务,FOC 状态机的实现函数,但是这个函数只是实现了一部
分,FOC 的状态机还有其他的状态是在其他地方实现,像错误处理,编码器模式
的校准对齐状态等等。该函数实现的是电机正常运转的时候的动作流程,包括从
空闲到启动再到停止。
STM32 技术开发手册
www.ing10bbs.com
代码 20-100 状态机实现任务
00 void TSK_MediumFrequencyTaskM1(void)
01 {
02
State_t StateM1;
03
int16_t wAux = 0;
04
05
(void) HALL_CalcAvrgMecSpeed01Hz(&HALL_M1,&wAux);
06
PQD_CalcElMotorPower(pMPM[M1]);
07
StateM1 = STM_GetState(&STM[M1]);
08
switch (StateM1) {
09
case IDLE_START:
10
R3F4XX_TurnOnLowSides(pwmcHandle[M1]);
11
TSK_SetChargeBootCapDelayM1(CHARGE_BOOT_CAP_TICKS);
12
STM_NextState(&STM[M1],CHARGE_BOOT_CAP);
13
break;
14
case CHARGE_BOOT_CAP:
15
if (TSK_ChargeBootCapDelayHasElapsedM1()) {
16
PWMC_CurrentReadingCalibr(pwmcHandle[M1],CRC_START);
17
STM_NextState(&STM[M1],OFFSET_CALIB);
18
}
19
break;
20
case OFFSET_CALIB:
21
if (PWMC_CurrentReadingCalibr(pwmcHandle[M1],CRC_EXEC)) {
22
STM_NextState(&STM[M1],CLEAR);
23
}
24
break;
25
case CLEAR:
26
HALL_Clear(&HALL_M1);
27
if (STM_NextState(&STM[M1], START) == true) {
28
FOC_Clear(M1);
29
R3F4XX_SwitchOnPWM(pwmcHandle[M1]);
30
}
31
break;
32
case START: {
33
STM_NextState(&STM[M1], START_RUN); /* only for sensored*/
34
}
35
break;
36
case START_RUN: {
37
FOC_InitAdditionalMethods(M1);
38
FOC_CalcCurrRef(M1);
39
STM_NextState(&STM[M1], RUN);
40
}
41
STC_ForceSpeedReferenceToCurrentSpeed(pSTC[M1]); /* Init the
reference speed to current speed */
42
MCI_ExecBufferedCommands(oMCInterface[M1]); /* Exec the speed ramp
after changing of the speed sensor */
43
44
break;
45
case RUN:
46
MCI_ExecBufferedCommands(oMCInterface[M1]);
47
FOC_CalcCurrRef(M1);
48
break;
49
case ANY_STOP:
50
R3F4XX_SwitchOffPWM(pwmcHandle[M1]);
51
FOC_Clear(M1);
52
MPM_Clear((MotorPowMeas_Handle_t*)pMPM[M1]);
53
TSK_SetStopPermanencyTimeM1(STOPPERMANENCY_TICKS);
54
STM_NextState(&STM[M1], STOP);
55
break;
56
case STOP:
57
if (TSK_StopPermanencyTimeHasElapsedM1()) {
58
STM_NextState(&STM[M1], STOP_IDLE);
59
}
60
break;
61
case STOP_IDLE:
62
STM_NextState(&STM[M1], IDLE);
STM32 技术开发手册
www.ing10bbs.com
63
64
65
66
67 }
break;
default:
break;
}
从以上代码可以看到,实际执行的时候是先计算出平均的机械速度,然后再
计算出电机功率,再获取当前状态,根据当前状态进入不同的 case 处理。
1. IDLE_START
从空闲状态转入启动状态。首先打开低端 MOS 管,就是要让定时器的 3 个
比较值设置为 0,全部输出低电平,互补通道输出高电平,低端 MOS 管是高电
平有效,所以低端 MOS 管被导通。R3F4XX_TurnOnLowSides 这个函数就是打开了
低端的 MOS 管。目的是要让自举电容充电。TSK_SetChargeBootCapDelayM1 这个
函数就是设置充电时间,设置为 0 就是立即读取,不需要等待。然后将状态转入
CHARGE_BOOT_CAP。
代码 20-101 IDLE_START
09
10
11
12
13
case IDLE_START:
R3F4XX_TurnOnLowSides(pwmcHandle[M1]);
TSK_SetChargeBootCapDelayM1(CHARGE_BOOT_CAP_TICKS);
STM_NextState(&STM[M1],CHARGE_BOOT_CAP);
break;
2. CHARGE_BOOT_CAP
自举电容充电状态。在这里查询等待充电的完成,完成之后就读取三相通道
的电流值,作为偏差值进行校准。然后将状态转入 OFFSET_CALIB
代码 20-102 自举电容充电状态
00
01
02
03
04
05
06
case CHARGE_BOOT_CAP:
if (TSK_ChargeBootCapDelayHasElapsedM1())
{
PWMC_CurrentReadingCalibr(pwmcHandle[M1],CRC_START);
STM_NextState(&STM[M1],OFFSET_CALIB);
}
break;
参数 CRC_START 表示调用这个函数用于初始化偏移校准。在这个状态下,低
端的 3 个 MOS 管是完全导通的。
3. OFFSET_CALIB
在这个状态下,直接调用 PWMC_CurrentReadingCalibr()函数读取电流值,这
次传递的参数是 CRC_EXEC,跟 CRC_START 枚举变量,实际上在上一个状态调用
STM32 技术开发手册
www.ing10bbs.com
PWMC_CurrentReadingCalibr()函数的时候就已经读取了电流值,这次是什么都没
有做。所以其实这一步是没有任何动作的,直接将状态机转入 CLEAR。
代码 20-103 校准电流采样偏移值
00
01
02
03
04
05
case OFFSET_CALIB:
if (PWMC_CurrentReadingCalibr(pwmcHandle[M1],CRC_EXEC))
{
STM_NextState(&STM[M1],CLEAR);
}
break;
4. CLEAR
在这个状态下调用 HALL_CLEAR()函数,初始化了 HALL 传感器控制句柄。然
后接状态转入 START,转入 START 是有条件的,只有在当前状态在 RUN 或者是
ANY_STOP 下才能转入 START。
代码 20-104 初始化 HALL 传感器
00
01
02
03
04
05
06
case CLEAR:
HALL_Clear(&HALL_M1);
if (STM_NextState(&STM[M1], START) == true)
{
FOC_Clear(M1);
R3F4XX_SwitchOnPWM(pwmcHandle[M1]);
}
5. START
这个就没什么好说的了,直接看下面代码即可。
代码 20-105 START 启动电机
00 case START:
01 {
02
STM_NextState(&STM[M1], START_RUN); /* only for sensored*/
03 }
6. START_RUN
在这个状态下计算电流的参考值(Iq),并且将速度的目标值强制设置为当前的
实际转速(STC_ForceSpeedReferenceToCurrentSpeed)
。然后执行指令缓存中上一
条指令(MCI_ExecBufferedCommands)。
这里还调用了 FOC_InitAdditionalMethods()函数,这个函数是空的,什么都没
有。在执行命令过程中,通常接收到新的指令并不是立即就执行,而是放在缓存
STM32 技术开发手册
www.ing10bbs.com
里面,等待到合适的时候才会执行,所以这里就是执行了上一条指令,实际上刚
启动的时候就是执行 speed ramp 这条指令。
代码 20-106 START_RUN 状态
00 case START_RUN:
01 {
02
/* USER CODE BEGIN MediumFrequencyTask M1 1 */
03
04
/* USER CODE END MediumFrequencyTask M1 1 */
05
FOC_InitAdditionalMethods(M1);
06
FOC_CalcCurrRef(M1);
07
STM_NextState(&STM[M1], RUN);
08 }
09 STC_ForceSpeedReferenceToCurrentSpeed(pSTC[M1]); /* Init the reference
speed to current speed */
10 MCI_ExecBufferedCommands(oMCInterface[M1]); /* Exec the speed ramp after
changing of the speed sensor */
11
12 break;
7. RUN
这是电机的运行状态,正常运行的时候是一直在这个状态里面的,这里循环
的执行上一次的指令动作,就是控制 speed ramp。然后再计算电流值。
代码 20-107 RUN 运行状态
00
01
02
03
case RUN:
MCI_ExecBufferedCommands(oMCInterface[M1]);
FOC_CalcCurrRef(M1);
break;
8. ANY_STOP
这个是从任意状态转入 STOP 的前置状态。在任意的状态下关闭转动就会进
入这个状态,在这里关闭 PWM 输出,清空 FOC 控制相关变量,然后调用
TSK_SetStopPermanencyTimeM1()函数设置延时时间,最后转入 STOP 状态。
9. STOP
STOP,在这个状态中等待上一个状态设置的延时时间,然后转入 STOP_IDLE,
10. STOP_IDLE
直接转入 IDLE,等待下一次启动。
STM32 技术开发手册
www.ing10bbs.com
FOC_Clear()
这个函数重新初始化和电压变量,并且清除 PI 控制器,总线电压传感器,
速度扭矩控制器等变量值,就是将与 FOC 控制算法相关的变量都清空。
代码 20-108 清除 FOC 控制相关变量值
00 void FOC_Clear(uint8_t bMotor)
01 {
02
Curr_Components Inull = {(int16_t)0, (int16_t)0};
03
Volt_Components Vnull = {(int16_t)0, (int16_t)0};
04
05
FOCVars[bMotor].Iab = Inull;
06
FOCVars[bMotor].Ialphabeta = Inull;
07
FOCVars[bMotor].Iqd = Inull;
08
FOCVars[bMotor].Iqdref = Inull;
09
FOCVars[bMotor].hTeref = (int16_t)0;
10
FOCVars[bMotor].Vqd = Vnull;
11
FOCVars[bMotor].Valphabeta = Vnull;
12
FOCVars[bMotor].hElAngle = (int16_t)0;
13
14
PID_SetIntegralTerm(pPIDIq[bMotor], (int32_t)0);
15
PID_SetIntegralTerm(pPIDId[bMotor], (int32_t)0);
16
17
STC_Clear(pSTC[bMotor]);
18
19
PWMC_SwitchOffPWM(pwmcHandle[bMotor]);
20 }
FOC_CalcCurrRef()
计算电流值参考值,这里计算的是扭矩和 Iq 的参考值。这是为了要实时改
变电机运行状态而使用的函数。
代码 20-109 计算电流参考值
00 void FOC_CalcCurrRef(uint8_t bMotor)
01 {
02
if (FOCVars[bMotor].bDriveInput == INTERNAL) {
03
FOCVars[bMotor].hTeref = STC_CalcTorqueReference(pSTC[bMotor]);
04
FOCVars[bMotor].Iqdref.qI_Component1 = FOCVars[bMotor].hTeref;
05
}
06 }
TSK_SetChargeBootCapDelayM1()
设置自举电容充电延迟时间。
STM32 技术开发手册
www.ing10bbs.com
代码 20-110 设置电容充电时间
00 void TSK_SetChargeBootCapDelayM1(uint16_t hTickCount)
01 {
02
hBootCapDelayCounterM1 = hTickCount;
03 }
TSK_ChargeBootCapDelayHasElapsedM1()
判断自举电容充电延时时间是否已经结束。
代码 20-111 查询时间是否结束
00 bool TSK_ChargeBootCapDelayHasElapsedM1(void)
01 {
02
bool retVal = false;
03
if (hBootCapDelayCounterM1 == 0) {
04
retVal = true;
05
}
06
return (retVal);
07 }
TSK_SetStopPermanencyTimeM1()
设置电机停止的时候,延时一段时间才转入空闲状态。
代码 20-112 设置停止时间
00 void TSK_SetStopPermanencyTimeM1(uint16_t hTickCount)
01 {
02
hStopPermanencyCounterM1 = hTickCount;
03 }
TSK_StopPermanencyTimeHasElapsedM1()
判断 hStopPermanencyCounterM1 是否为 0,是则返回 true。
代码 20-113 查询时间是否结束
00 bool TSK_StopPermanencyTimeHasElapsedM1(void)
01 {
02
bool retVal = false;
03
if (hStopPermanencyCounterM1 == 0) {
04
retVal = true;
05
}
06
return (retVal);
07 }
TSK_HighFrequencyTask()
FOC 里面的高频任务,这个函数实现的内容是 FOC 算法的核心部分,在这个
任务里面,实现了 FOC 的坐标变换,PID 控制算法,SVPWM 输出。这里调用了
STM32 技术开发手册
www.ing10bbs.com
FOC_CurrController 函 数 , 然 后 根 据 返 回 值 判 断 是 否 出 错 , 如 果 返 回
MC_FOC_DURATION 错误,表示设置的 PWM 频率太快,FOC 无法完整的运行。
所以会报错。STM_FaultProcessing 只是标志错误状态,并不执行任何动作。
代码 20-114 FOC 高频任务
00 uint8_t TSK_HighFrequencyTask(void)
01 {
02
uint8_t bMotorNbr = 0;
03
uint16_t hFOCreturn;
04
05
HALL_CalcElAngle (&HALL_M1);
06
hFOCreturn = FOC_CurrController(M1);
07
if (hFOCreturn == MC_FOC_DURATION) {
08
STM_FaultProcessing(&STM[M1], MC_FOC_DURATION, 0);
09
} else {
10
}
11
return bMotorNbr;
12 }
TSK_SafetyTask()
安全任务,执行的是安全任务,就是检测电机的温度,总线电压,是否过流,
值得一说的是过流保护只是 TIM 的 BKIN 中断标志,FOC 本身没有检测电流是否
过流,只是通过驱动板上的过流输出信号触发 TIM 的 BKIN 中断,然后在中断中
标志过流而已,这个是靠驱动板的硬件支持的,硬件不支持则没有过流保护。
另外还有一个函数,RCM_ExecUserConv(),这个函数在实际应用中并没有起
到任何作用,这个是预留出来给用户使用 ADC 外设的规则转换通道,只有注册
了相应的通道之后才能使用现在暂时不用管它。
代码 20-115 执行安全任务
00 void TSK_SafetyTask(void)
01 {
02
if (bMCBootCompleted == 1) {
03
TSK_SafetyTask_PWMOFF(M1);
04
/* User conversion execution */
05
RCM_ExecUserConv ();
06
}
07 }
TSK_SafetyTask_PWMOFF()
安全任务的具体实现函数,首先是读取平均温度,然后读取是否过流,还有
计算总线电压平均值,根据结果来执行 STM_FaultProcessing()函数,更新状态机
的状态,然后根据错误状态停止 PWM 输出。如果是当前状态下出现错误则停止
STM32 技术开发手册
www.ing10bbs.com
输出 PWM,清空 FOC 变量,清空功率测量句柄变量。如果是上一个状态的错误
遗留到现在,则只是继续执行停止输出 PWM
代码 20-116 安全任务的具体实现
00 void TSK_SafetyTask_PWMOFF(uint8_t bMotor)
01 {
02
uint16_t CodeReturn;
03
04
CodeReturn = NTC_CalcAvTemp(pTemperatureSensor[bMotor]); /* Clock
temperature sensor and check for fault. It returns MC_OVER_TEMP or
MC_NO_ERROR */
05
CodeReturn |= PWMC_CheckOverCurrent(pwmcHandle[bMotor]); /* Clock
current sensor and check for fault. It return MC_BREAK_IN or MC_NO_FAULTS
(for STM32F30x can return MC_OVER_VOLT in case of HW Overvoltage) */
06
07
if (bMotor == M1) {
08
CodeReturn |= RVBS_CalcAvVbus(pBusSensorM1);
09
}
10
11
STM_FaultProcessing(&STM[bMotor], CodeReturn, ~CodeReturn); /* Update
the STM according error code */
12
switch (STM_GetState(&STM[bMotor])) { /* Acts on PWM outputs in case
of faults */
13
case FAULT_NOW:
14
PWMC_SwitchOffPWM(pwmcHandle[bMotor]);
15
FOC_Clear(bMotor);
16
MPM_Clear((MotorPowMeas_Handle_t*)pMPM[bMotor]);
17
break;
18
case FAULT_OVER:
19
PWMC_SwitchOffPWM(pwmcHandle[bMotor]);
20
21
break;
22
default:
23
break;
24
}
25 }
GetMCI()/GetMCT
获 取 MCInterface 和 MCTuning 的 接 口 指 针 。 返 回 的 是 当 前 电 机 的
MCI_Handle_t 类型的指针。实际上并没有使用到这两个函数。
代码 20-117 获取电机控制接口的指针
00 MCI_Handle_t * GetMCI(uint8_t bMotor)
01 {
02
MCI_Handle_t * retVal = MC_NULL;
03
if ((oMCInterface != MC_NULL) && (bMotor < NBR_OF_MOTORS)) {
04
retVal = oMCInterface[bMotor];
05
}
06
return retVal;
07 }
08 MCT_Handle_t* GetMCT(uint8_t bMotor)
09 {
10
MCT_Handle_t* retVal = MC_NULL;
11
if (bMotor < NBR_OF_MOTORS) {
12
retVal = &MCT[bMotor];
13
}
14
return retVal;
STM32 技术开发手册
www.ing10bbs.com
15 }
TSK_HardwareFaultTask()
软件错误任务,这个是在程序进入 HardFault 中断之后调用的函数,停止输
出 PWM,并且标记出现 MC_SW_ERROR 错误。
代码 20-118 HardFault 任务
00 void TSK_HardwareFaultTask(void)
01 {
02
R3F4XX_SwitchOffPWM(pwmcHandle[M1]);
03
STM_FaultProcessing(&STM[M1], MC_SW_ERROR, 0);
04 }
mc_lock_pins()
引脚写保护,将 GPIO 相关寄存器的设置写保护,防止在运行过程中被意外
修改。
代码 20-119 引脚写保护
00 void mc_lock_pins (void)
01 {
02
LL_GPIO_LockPin(M1_PWM_UL_GPIO_Port, M1_PWM_UL_Pin);
03
LL_GPIO_LockPin(M1_PWM_VL_GPIO_Port, M1_PWM_VL_Pin);
04
LL_GPIO_LockPin(M1_PWM_WL_GPIO_Port, M1_PWM_WL_Pin);
05
LL_GPIO_LockPin(M1_PWM_UH_GPIO_Port, M1_PWM_UH_Pin);
06
LL_GPIO_LockPin(M1_PWM_VH_GPIO_Port, M1_PWM_VH_Pin);
07
LL_GPIO_LockPin(M1_PWM_WH_GPIO_Port, M1_PWM_WH_Pin);
08
LL_GPIO_LockPin(M1_HALL_H3_GPIO_Port, M1_HALL_H3_Pin);
09
LL_GPIO_LockPin(M1_HALL_H1_GPIO_Port, M1_HALL_H1_Pin);
10
LL_GPIO_LockPin(M1_HALL_H2_GPIO_Port, M1_HALL_H2_Pin);
11
LL_GPIO_LockPin(M1_CURR_AMPL_U_GPIO_Port, M1_CURR_AMPL_U_Pin);
12
LL_GPIO_LockPin(M1_TEMPERATURE_GPIO_Port, M1_TEMPERATURE_Pin);
13
LL_GPIO_LockPin(M1_CURR_AMPL_V_GPIO_Port, M1_CURR_AMPL_V_Pin);
14
LL_GPIO_LockPin(M1_CURR_AMPL_W_GPIO_Port, M1_CURR_AMPL_W_Pin);
15
LL_GPIO_LockPin(M1_BUS_VOLTAGE_GPIO_Port, M1_BUS_VOLTAGE_Pin);
16 }
regular_conversion_manager.h 文件内容
regular_conversion_manager.c 这个是规则转换管理组件,也就是管理 ADC
规则转换通道的模块。FOC5.2 的代码中预留了 ADC 的规则转换通道给用户使用,
当用户需要使用到与 FOC 电流采样相同的 ADC 外设的时候,就必须是使用规则
转换,因为注入转换会影响到 FOC 的电流采样。所以这个文件就是为了管理所有
STM32 技术开发手册
www.ing10bbs.com
的规则转换通道而存在,统一将用户的 ADC 规则通道和 FOC 的 ADC 规则通道放
在一起管理,防止错误使用 ADC 外设。
如果用户需要使用到 ADC 的规则转换通道,可以按照以下步骤使用。
1) 首先必须使用 RCM_RegisterRegConv_WithCB()或 RCM_RegisterRegConv()
函数注册所使用的 ADC 通道,可以注册多个通道,但是一次只能转换一
个通道。
2) 调用 RCM_RequestUserConv()函数请求进行 ADC 转换,在每次执行完中
频任务的转换之后(也就是总线电压和温度),就会执行用户所请求的
AD 转换。
3) 转换之后,可以使用 RCM_GetUserConvState()函数轮询当前的转换状态,
如果是返回 RCM_USERCONV_EOC 状态,则说明转换结束,就可以使用
RCM_GetUserConv()函数获取需要的 AD 值。
4) 如果使用 RCM_RegisterRegConv_WithCB()来注册转换通道,则会同时注
册一个回调函数,并在转换结束之后调用回调函数,可以不用轮询转换
状态。
首先来看看头文件的主要内容。头文件内容不多,主要是一些类型的声明。
代码 20-120 结构体枚举类型声明
00
01
02
03
04
05
06
07
08
09
10
typedef struct {
ADC_TypeDef * regADC;
uint8_t channel;
uint32_t samplingTime;
} RegConv_t;
typedef enum {
RCM_USERCONV_IDLE,
RCM_USERCONV_REQUESTED,
RCM_USERCONV_EOC
} RCM_UserConvState_t;
声明了一个结构体类型和枚举类型,结构体用于注册 AD 规则转换通道。包
括 ADC 外设,通道,和采样时间。枚举类型则是用户转换通道的状态。
然 后 使 用 typedef 定 义 了 一 个 函 数 指 针 的 别 名 , 后 面 可 以 使 用
RCM_exec_cb_t 来声明函数指针。
STM32 技术开发手册
www.ing10bbs.com
代码 20-121 函数指针类型别名
00 typedef void (*RCM_exec_cb_t)(uint8_t handle, uint16_t data, void
*UserData);
剩下的就是一些函数的声明了。
regular_conversion_manager.c 文件内容
该文件实现规则转换通道的管理函数,包括注册通道,请求转换,转换状态
查询,转换完成回调。
代码 20-122 宏与变量声明
00 #define RCM_MAX_CONV 4
01
02 /* Global variables ---------------------------------------------------------*/
03
04 RegConv_t * RCM_handle_array [RCM_MAX_CONV];
05 RCM_callback_t RCM_CB_array [RCM_MAX_CONV];
06
07 uint8_t RCM_UserConvHandle;
08 uint16_t RCM_UserConvValue;
09 RCM_UserConvState_t RCM_UserConvState;
10
宏 RCM_MAX_CONV 定义了最大的转换通道数量是 4,其中两个被用于总线
电压和温度读取,所以用户只有 2 个 AD 转换通道可以使用。然后定义了两个数
组,一个是转换通道的注册句柄,一个是回调函数指针。剩下的就是普通的变量
而已,RCM_UserConvHandle 是数组索引,RCM_UserConvValue 则是转换结果,数
组索引是全局变量并且不是数组,说明一次只能转换一个通道。
RCM_UserConvState 则是用于标记、查询当前转换状态。
RCM_RegisterRegConv_WithCB()
带回调函数的注册函数,在这个函数里面注册回调函数,并且调用这个函数
的时候将回调函数的地址和接收转换结果的变量都传递过来,赋值到对应的数组
上。
代码 20-123 带回调函数的注册函数
00 uint8_t RCM_RegisterRegConv_WithCB (RegConv_t * regConv, RCM_exec_cb_t
fctCB, void *data)
01 {
02
uint8_t handle;
03
handle = RCM_RegisterRegConv(regConv);
04
if (handle < RCM_MAX_CONV) {
STM32 技术开发手册
www.ing10bbs.com
05
06
07
08
09 }
RCM_CB_array [handle].cb = fctCB;
RCM_CB_array [handle].data = data;
}
return handle;
在这个函数里面是调用注册函数 RCM_RegisterRegConv,获取索引值之后,
在数组里面设置相应的回调函数指针和数据指针。
RCM_RegisterRegConv()
规则通道的注册函数,该函数在两个数组上注册了 ADC 的转换通道,并且
返回在数组上的索引号,在请求 AD 转换的时候,就可以根据索引号来安排 AD
转换、赋值、调用回调函数。
代码 20-124 规则通道注册函数
00 uint8_t RCM_RegisterRegConv(RegConv_t * regConv)
01 {
02
uint8_t handle=255;
03
uint8_t i=0;
04
05
/* Parse the array to be sure that same
06
* conversion does not already exist*/
07
while (i < RCM_MAX_CONV) {
08
if ( RCM_handle_array [i] == 0 && handle > RCM_MAX_CONV) {
09
handle = i; /* First location available, but still looping to
check that this config does not already exist*/
10
}
11
if ((RCM_handle_array [i]->channel == regConv->channel) &&
12
(RCM_handle_array [i]->regADC == regConv->regADC)) {
13
handle =i; /* Reuse the same handle */
14
i = RCM_MAX_CONV; /* we can skip the rest of the loop*/
15
}
16
i++;
17
}
18
if (handle < RCM_MAX_CONV ) {
19
RCM_handle_array [handle] = regConv;
20
RCM_CB_array [handle].cb = NULL; /* if a previous callback was
attached, it is cleared*/
21
if (LL_ADC_IsEnabled(regConv->regADC) == 0 ) {
22
LL_ADC_Enable( regConv->regADC );
23
24
} else {
25
}
26
/* reset regular conversion sequencer length set by cubeMX */
27
LL_ADC_REG_SetSequencerLength( regConv->regADC,
LL_ADC_REG_SEQ_SCAN_DISABLE );
28
/* configure the sampling time (should already be configured by for
non user conversions)*/
29
LL_ADC_SetChannelSamplingTime ( regConv->regADC,
__LL_ADC_DECIMAL_NB_TO_CHANNEL(regConv->channel) ,regConv->samplingTime);
30
} else {
31
/* Nothing to do handle is already set to error value : 255 */
32
}
33
return handle;
STM32 技术开发手册
www.ing10bbs.com
34 }
首先看 while 里面的第一个 if 语句,这个是搜索数组里面第一个等于 0 的索
引值,handle 初始化为 255 恒大于 RCM_MAX_COONV,所以 RCM_handle_array
数组里面出现第一个等于 0 的时候,就记录 handle 索引号。在搜索过程中,还
有对比当前已经注册了的 AD 转换通道是否与需要注册的转换通道一致,如果是
则直接记录当前索引号并退出循环。这个就是 while 循环的内容。
得到索引号之后就是注册了,对 RCM_handle_array 数组进行赋值,记录需
要注册的 ADC 外设,通道号,采样时间;清空回调函数指针,防止程序跳转错
乱;使能 ADC 转换。最后设置不连续采样一个通道,并且设置采样时间。
RCM_ExecRegularConv()
该函数使能规则通道的不连续转换,并且使用软件触发 ADC 转换,然后轮
询等待转换结束,最后读取数据。并且返回 ADC 数据。
代码 20-125 执行规则转换
00 uint16_t RCM_ExecRegularConv (uint8_t handle)
01 {
02
uint16_t retVal;
03
04
LL_ADC_REG_SetSequencerRanks( RCM_handle_array[handle]->regADC,
05
LL_ADC_REG_RANK_1,
06
__LL_ADC_DECIMAL_NB_TO_CHANNEL( RCM_handle_array[handle]->channel ) );
07
08
LL_ADC_REG_ReadConversionData12( RCM_handle_array[handle]->regADC );
09
10
/* Bit banding access equivalent to LL_ADC_REG_StartConversionSWStart
*/
11
BB_REG_BIT_SET ( &RCM_handle_array[handle]->regADC->CR2,
ADC_CR2_SWSTART_Pos );
12
/* Wait until end of regular conversion */
13
while ( LL_ADC_IsActiveFlag_EOCS( RCM_handle_array[handle]->regADC )
== 0u ) {}
14
retVal =
LL_ADC_REG_ReadConversionData12( RCM_handle_array[handle]->regADC );
15
return retVal;
16 }
RCM_RequestUserConv()
该函数将状态标记为正请求转换,并且记录待转换通道在数组的索引号。如
果当前不处于空闲状态则返回 false。
代码 20-126 请求转换用户注册的规则通道
00 bool RCM_RequestUserConv(uint8_t handle)
01 {
STM32 技术开发手册
www.ing10bbs.com
02
03
04
05
right
06
07
08
09
10 }
11
bool retVal = false;
if (RCM_UserConvState == RCM_USERCONV_IDLE) {
RCM_UserConvHandle = handle;
/* must be done last so that RCM_UserConvHandle already has the
value */
RCM_UserConvState = RCM_USERCONV_REQUESTED;
retVal = true;
}
return retVal;
RCM_GetUserConv()
该函数读取用户注册的规则通道的值。并且将状态标记回空闲。
代码 20-127 获取规则转换通道的值
01 uint16_t RCM_GetUserConv(void)
02 {
03
uint16_t hRetVal = 0xFFFFu;
04
if (RCM_UserConvState == RCM_USERCONV_EOC) {
05
hRetVal = RCM_UserConvValue;
06
RCM_UserConvState = RCM_USERCONV_IDLE;
07
}
08
return hRetVal;
09 }
RCM_ExecUserConv()
执行规则通道转换,这是周期性的任务,在执行完安全任务之后就会调用这
个函数,这是在中断里面执行的。如果之前有申请过转换规则通道,那么在执行
这个函数的时候就会启动转换该规则通道。如果有注册回调函数,就调用注册函
数,并且将转换结果传递给回调函数。
代码 20-128 执行用户规则转换
00 void RCM_ExecUserConv ()
01 {
02
if (RCM_UserConvState == RCM_USERCONV_REQUESTED) {
03
RCM_UserConvValue = RCM_ExecRegularConv (RCM_UserConvHandle);
04
RCM_UserConvState = RCM_USERCONV_EOC;
05
if (RCM_CB_array [RCM_UserConvHandle].cb != NULL) {
06
RCM_UserConvState = RCM_USERCONV_IDLE;
07
RCM_CB_array [RCM_UserConvHandle].cb (RCM_UserConvHandle,
RCM_UserConvValue ,RCM_CB_array [RCM_UserConvHandle].data);
08
}
09
}
10 }
RCM_GetUserConvState()
该函数可以获取当前转换状态,可用作轮询判断转换完成。
代码 20-129 获取转换过程状态
00 RCM_UserConvState_t RCM_GetUserConvState(void)
STM32 技术开发手册
www.ing10bbs.com
01 {
02
03 }
return RCM_UserConvState;
返回值:
⚫
UDRC_STATE_IDLE:没有常规转换请求挂起。
⚫
UDRC_STATE_REQUESTED:正则转换已被请求,但尚未完成。
⚫
UDRC_STATE_EOC:已完成常规转换,但尚未从用户读取。
stm32xx_mc_it.c 文件内容
该文件主要是中断服务程序,提供了电机异常响应程序和外设中断服务程序。
FOC 用到的外设有 UART,ADC,GPIO,TIM。这些外设的中断服务程序都在这个
文件里面,并且还包含了有 Hardfault 处理。性质就是相当于 stm32f4xx_it.c 文件
一样,只不过这个是专门针对电机控制的中断函数,中断处理方法也不一样。这
些中断服务函数都是使用宏定义来替换函数别名,所以无需太过在意为什么中断
函数名字的改变。
ADC_IRQHandler()
在 ADC 中断中,如果是转换注入通道转换结束,则在清空标志位之后,直接
调用 TSK_HighFrequencyTask()函数,这个就是高频任务,在这个函数里面实现 FOC
算法的主要内容,包括坐标变换,SVPWM 输出等计算过程。然后在根据执行结
果来更新 DAC 的数值。这里还有条件编译选择是否使用 ADC3,实际例程里面并
没有使用 ADC3。
代码 20-130 ADC 中断服务函数
00 void ADC_IRQHandler(void)
01 {
02
if (LL_ADC_IsActiveFlag_JEOS(ADC1)) {
03
// Clear Flags
04
ADC1->SR &= ~(uint32_t)(LL_ADC_FLAG_JEOS | LL_ADC_FLAG_JSTRT);
05
06
UI_DACUpdate(TSK_HighFrequencyTask()); /*GUI, this section is
present only if DAC is enabled*/
07
}
08 #ifdef ADC3
09
else {
10
// Clear Flags
11
ADC3->SR &= ~(uint32_t)(LL_ADC_FLAG_JEOS | LL_ADC_FLAG_JSTRT);
12
13
UI_DACUpdate(TSK_HighFrequencyTask()); /*GUI, this section is
present only if DAC is enabled*/
14
}
15 #endif
STM32 技术开发手册
www.ing10bbs.com
16 }
TIMx_UP_M1_IRQHandler()
该函数是定时器更新中断函数,在更新中断中等待 ADC 转换完成,并且修
改下次待转换的 AD 通道。
代码 20-131 M1 定时器更新中断服务函数
00 void TIMx_UP_M1_IRQHandler(void)
01 {
02
/* USER CODE BEGIN TIMx_UP_M1_IRQn 0 */
03
04
/* USER CODE END TIMx_UP_M1_IRQn 0 */
05
LL_TIM_ClearFlag_UPDATE(PWM_Handle_M1.pParams_str->TIMx);
06
R3F4XX_TIMx_UP_IRQHandler(&PWM_Handle_M1);
07
08
/* USER CODE BEGIN TIMx_UP_M1_IRQn 1 */
09
/* USER CODE END TIMx_UP_M1_IRQn 1 */
10 }
TIMx_BRK_M1_IRQHandler()
该函数是定时器的 BKIN 函数,也就是刹车中断函数,当外部给一个刹车信号的
时候,STM32 可以立即停止所有 PWM 输出。但是驱动板上并没有预留刹车输出
信号,所以这个函数基本上是没有意义的。但是还可以看看这里是怎么处理刹车
信号的。
代码 20-132 M1 刹车中断服务函数
00 void TIMx_BRK_M1_IRQHandler(void)
01 {
02
if (LL_TIM_IsActiveFlag_BRK(PWM_Handle_M1.pParams_str->TIMx)) {
03
LL_TIM_ClearFlag_BRK(PWM_Handle_M1.pParams_str->TIMx);
04
R3F4XX_BRK_IRQHandler(&PWM_Handle_M1);
05
}
06
/* Systick is not executed due low priority so is necessary to
07
call MC_Scheduler here.*/
08
MC_Scheduler();
09 }
SPD_TIM_M1_IRQHandler()
M1 的速度定时器中断函数,该函数实质上是霍尔传感器接口的定时器中断
函数,该函数处理定时器溢出更新和 CCR1 比较匹配事件。如果是溢出更新中断,
STM32 技术开发手册
www.ing10bbs.com
就代表是当前设置的预分频不合适,那么在比较匹配中断的时候就会动态调整定
时器的预分频值。并且在比较匹配中断中根据霍尔状态获取电角度和捕获值。
代码 20-133 M1 定时器速度中断服务函数
00 void SPD_TIM_M1_IRQHandler(void)
01 {
02
03
/* HALL Timer Update IT always enabled, no need to check enable
04
UPDATE state */
05
if (LL_TIM_IsActiveFlag_UPDATE(HALL_M1.TIMx)) {
06
LL_TIM_ClearFlag_UPDATE(HALL_M1.TIMx);
07
HALL_TIMx_UP_IRQHandler(&HALL_M1);
08
} else {
09
}
10
/* HALL Timer CC1 IT always enabled, no need to check enable CC1
11
state */
12
if (LL_TIM_IsActiveFlag_CC1 (HALL_M1.TIMx)) {
13
LL_TIM_ClearFlag_CC1(HALL_M1.TIMx);
14
HALL_TIMx_CC_IRQHandler(&HALL_M1);
15
} else {
16
/* Nothing to do */
17
}
18 }
USART_IRQHandler()
该函数是串口服务函数,主要负责串口的接收和发送工作,同时还要过溢错
误。
代码 20-134 串口中断服务函数
00 void USART_IRQHandler(void)
01 {
02
if (LL_USART_IsActiveFlag_RXNE(pUSART.USARTx)) { /* Valid data
03
received */
04
uint16_t retVal;
05
retVal = *(uint16_t*)UFCP_RX_IRQ_Handler(&pUSART,
06
LL_USART_ReceiveData8(pUSART.USARTx)); /* Flag 0 = RX
07
*/
08
if (retVal == 1) {
09
UI_SerialCommunicationTimeOutStart();
10
}
11
if (retVal == 2) {
12
UI_SerialCommunicationTimeOutStop();
13
}
14
}
15
16
if (LL_USART_IsActiveFlag_ORE(pUSART.USARTx)) { // Overrun error
17
occurs after SR access and before DR access
18
/* Send Overrun message */
19
UI_SerialCommunicationTimeOutStop();
20
UFCP_OVR_IRQ_Handler(&pUSART);
21
LL_USART_ClearFlag_ORE(pUSART.USARTx);
22
}
23
24
if (LL_USART_IsActiveFlag_TXE(pUSART.USARTx)) {
25
UFCP_TX_IRQ_Handler(&pUSART); /* Flag 1 = TX */
STM32 技术开发手册
www.ing10bbs.com
26
27 }
}
HardFault_Handler()
HardFault 中断服务函数,出现 Hardfault 的时候会关闭输出 PWM,并且会标
记软件错误,然后会进入安全任务等待处理错误。一般出现这种情况都是因为数
组越界或者访问了不该访问的内存地址,这些都是需要从软件上进行处理的错误,
无法在运行的时候处理。
在 HardFault_Handler 中断中,还是会继续处理串口的工作,不过是在循环查
询是否该发送/接收数据,然后再处理,因为优先级要比串口中断高,进入该函
数之后就不会再响应串口中断。
代码 20-135 HardFault 中断服务函数
00 void HardFault_Handler(void)
01 {
02
TSK_HardwareFaultTask();
03
04
/* Go to infinite loop when Hard Fault exception occurs */
05
while (1) {
06
if (LL_USART_IsActiveFlag_ORE(pUSART.USARTx)) { /* Overrun
07
error occurs */
08
/* Send Overrun message */
09
UFCP_OVR_IRQ_Handler(&pUSART);
10
LL_USART_ClearFlag_ORE(pUSART.USARTx); /* Clear overrun
11
flag */
12
UI_SerialCommunicationTimeOutStop();
13
}
14
15
if (LL_USART_IsActiveFlag_TXE(pUSART.USARTx)) {
16
UFCP_TX_IRQ_Handler(&pUSART);
17
}
18
19
if (LL_USART_IsActiveFlag_RXNE(pUSART.USARTx)) { /* Valid data
20
have been received */
21
uint16_t retVal;
22
retVal = *(uint16_t*)(UFCP_RX_IRQ_Handler(&pUSART,
23
LL_USART_ReceiveData8(pUSART.USARTx)));
24
if (retVal == 1) {
25
UI_SerialCommunicationTimeOutStart();
26
}
27
if (retVal == 2) {
28
UI_SerialCommunicationTimeOutStop();
29
}
30
}
31
}
32 }
STM32 技术开发手册
www.ing10bbs.com
SysTick_Handler()
Systick 是系统滴答定时器,用于为系统提供一个基准的时钟信号,在初始化
的时候就设定中断的频率是 500us,现在就是每隔 500us 就进入中断一次,然后
执行 MC_RunMotorControlTasks()函数,这个函数就是在 MC_Task.c 文件里面的电
机控制任务。
周期性的执行电机控制任务,并且每隔 1ms 就执行一次,并且有一个宏定
义 SYSTICK_DIVIDER 决定了调用 HAL_SYSTICK_IRQHandler()函数的频率。Systick 的
中断频率是 2000Hz(中断间隔是 500us),1000 就是 HAL 库的 SysTick 的中断频
率,所以得到的 2 就是对 Systick 的分频系数,在计数 2 次之后就执行一次 HAL
库的 HAL_SYSTICK_IRQHandler()函数。也就说无论设置的中断频率是多少,HAL 库
的 HAL_SYSTICK_IRQHandler()都是 1ms 执行一次。同时,需要修改中断频率的时
候修改的是 SYS_TICK_FREQUENCY 这个宏。
代码 20-136 Systick 的分频系数
00 #define SYS_TICK_FREQUENCY
2000
01 #define SYSTICK_DIVIDER (SYS_TICK_FREQUENCY/1000)
代码 20-137 系统滴答定时器计数中断
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
void SysTick_Handler(void)
{
#ifdef MC_HAL_IS_USED
static uint8_t SystickDividerCounter = SYSTICK_DIVIDER;
if (SystickDividerCounter == SYSTICK_DIVIDER) {
HAL_IncTick();
HAL_SYSTICK_IRQHandler();
SystickDividerCounter = 0;
}
SystickDividerCounter ++;
#endif /* MC_HAL_IS_USED */
MC_RunMotorControlTasks();
}
EXTI0_IRQHandler()
这个是外部中断服务函数,这个是连接到外部启动/停止按钮的引脚的中断
函数,在按下启动/停止按钮之后执行这个函数,然后执行启动和停止电机转动
STM32 技术开发手册
www.ing10bbs.com
的动作。这个是可以在 workbench 上设置的 start/stop Button 上选择引脚,这里
选择的 PE0 是 YS-F4Pro 上的 KEY0 按键。
代码 20-138 外部中断服务函数
00 void EXTI0_IRQHandler (void)
01 {
02
LL_EXTI_ClearFlag_0_31 (LL_EXTI_LINE_0);
03
UI_HandleStartStopButton_cb ();
04 }
图 20-14 启动和停止按钮选择
mc_parameter.c 文件内容
该文件内容比较少,主要是定义一个 const 类型的变量参数,该参数主要用
于 3 相电阻的电流采集。
R3_F4_ParamsM1
电机 1 的参数,前缀 R3,F4 表示这个变量专用于 F4 系列的主控芯片和 3 相
电阻电流采样的方式,表明了这个参数是用于 F4 芯片的 3 相电阻采样相关的控
制。
代码 20-139 电机控制常数
00 const R3_F4_Params_t R3_F4_ParamsM1 = {
01
/* 电机控制参数 --------------------------------------------02
-----------*/
03
.Tw
= MAX_TWAIT,
04
.bFreqRatio
= FREQ_RATIO,
05
.bIsHigherFreqTim
= FREQ_RELATION,
06
07
/* 电流读取,A/D 转换通道 ----------------------------*/
08
.bIaChannel
= MC_ADC_CHANNEL_6,
09
.bIbChannel
= MC_ADC_CHANNEL_8,
10
.bIcChannel
= MC_ADC_CHANNEL_9,
11
12
/* PWM 生成参数 -----------------------------------------------13
--*/
STM32 技术开发手册
www.ing10bbs.com
14
15
16
17
18
19
20
21
22
23
24
25 };
26
.TIMx
.bRepetitionCounter
.hTafter
.hTbefore
= PWM_TIM8,
= REP_COUNTER,
= TW_AFTER,
= TW_BEFORE,
/* PWM 驱动信号初始化(flag) ---------------------*/
.LowSideOutputs
= (LowSideOutputsFunction_t)
LOW_SIDE_SIGNALS_ENABLING,
/* PWM 驱动信号初始化(flag) ---------------------*/
.EmergencyStop
= (FunctionalState) DISABLE,
值得注意的是 LowSideOutputsFunction_t 和 FunctionalState,这两个都只是
标志位而已,LowSideOutputs 是用于区分 st 自身推出的驱动板还是其他的驱动
板,EmergencyStop 则是决定是否使用刹车输入信号。
ui_task.c 文件内容
从名字可以看出,这个文件的主要是处理用户接口的任务,用户接口主要有
DAC、UART、GPIO,DAC 因为引脚关系,没有用到的功能就先不介绍,主要是介
绍 UART 的任务,这个文件的内容并不复杂,因为没有主要核心内容,下面将简
单地介绍这个文件包含的函数。该文件中的部分函数并没有使用到,没有使用的
函数将不作理会。
值得注意的是,这个文件中的一些宏定义和变量,实际上并没有使用到。这
些变量即使注释掉再编译也不会报错。
代码 20-140 没有使用到的宏定义和变量
00
01
02
03
04
05
06
07
08
09
10
11
12
13
};
#define OPT_DACX 0x20 /*!<Bit field indicating that the UI uses SPI
AD7303 DAC.*/
#define VECT_TABLE_BASE 0x080F0000
/* Setup the exported functions see UIExportedFunctions.h enum. */
void* const exportedFunctions[EF_UI_NUMBERS] = {
(void*)(&UI_GetReg),
(void*)(&UI_ExecSpeedRamp),
(void*)(&UI_SetReg),
(void*)(&UI_ExecCmd),
(void*)(&UI_GetSelectedMCConfig),
(void*)(&UI_SetRevupData),
(void*)(&UI_GetRevupData),
(void*)(&UI_SetDAC),
(vo (void*)(&UI_SetCurrentReferences)
除了这些没有使用到的变量之外还有一些全局变量,见代码 20-141,其中
DAC 相关的变量是会在其他文件中使用到,其他的变量的有效作用域都是在这个
STM32 技术开发手册
www.ing10bbs.com
文件内,包括 MCP_UI_Params,并且使用一个指针 pMCP 来代替变量作为接口供
用户操作。
代码 20-141 ui_task.c 文件中的全局变量
00
01
02
03
04
05
06
07
08
DAC_UI_Handle_t * pDAC = MC_NULL;
extern DAC_UI_Handle_t DAC_UI_Params;
MCP_Handle_t * pMCP = MC_NULL;
MCP_Handle_t MCP_UI_Params;
static volatile uint16_t bUITaskCounter;
static volatile uint16_t bCOMTimeoutCounter;
static volatile uint16_t bCOMATRTimeCounter = SERIALCOM_ATR_TIME_TICKS
UI_TaskInit()
任务初始化函数,这个函数初始化接口控制变量,该函数被调用的地方是在
motorcontrol.c 文件中的 MX_MotorControl_Init()函数中。
代码 20-142 调用 UI_TaskInit()传递的参数
09
10
/* Initialize the MC User Interface */
UI_TaskInit(wConfig,NBR_OF_MOTORS,pMCI,pMCT,s_fwVer);
调用的时候传递过来的参数有 5 个,wConfig 是用户界面配置列表,使用 bit
来代表使用到哪里接口。NBR_OF_MOTORS 表示有多少个电机,这里只有 1 个。
pMCI,pMCT 则是控制接口,一个是对外的接口,一个是实际控制的接口。S_fwVer
则是固件库版本号。
wConfig 的主要功能是标志允许使用那些功能:DAC 接口,HALL 传感器,在
速度模式下修改 Id,允许设定 PLL 的 Kp 和 Ki。
代码 20-143 用户接口任务初始化
00 void UI_TaskInit( uint32_t* pUICfg, uint8_t bMCNum, MCI_Handle_t*
01
pMCIList[],
02
MCT_Handle_t* pMCTList[],const char* s_fwVer )
03 {
04
pDAC = &DAC_UI_Params;
05
pDAC->_Super = UI_Params;
06
07
UI_Init(&pDAC->_Super, bMCNum, pMCIList, pMCTList, pUICfg); /*
08
Init UI and link MC obj */
09
UI_DACInit(&pDAC->_Super); /* Init DAC */
10
UI_SetDAC(&pDAC->_Super, DAC_CH0, MC_PROTOCOL_REG_I_A);
11
UI_SetDAC(&pDAC->_Super, DAC_CH1, MC_PROTOCOL_REG_I_B);
12
13
pMCP = &MCP_UI_Params;
14
pMCP->_Super = UI_Params;
15
16
UFCP_Init( & pUSART );
STM32 技术开发手册
www.ing10bbs.com
17
18
19
20
21
22 }
MCP_Init(pMCP, (FCP_Handle_t *) & pUSART, & UFCP_Send, &
UFCP_Receive, & UFCP_AbortReceive, pDAC, s_fwVer);
UI_Init(&pMCP->_Super, bMCNum, pMCIList, pMCTList, pUICfg); /*
Initialize UI and link MC components */
先解释一下基础功能:UI_DACInit()是使能 DAC。UI_SetDAC()设置 DAC 通道
的数值。pMCP->_Super 跟 pDAC->_Super 一样都是相同类型的结构体
该函数前半部分是初始 DAC_UI_Params 这个结构体变量,首先使用 pDAC 指
针指向 DAC_UI_Params。然后将 UI_Params 中的函数地址赋值给 pDAC->_Super 中
的函数指针,pDAC->_Super 其他的结构体成员则使用 UI_Init()赋值。最后使用
UI_DACInit()使能 DAC 并设置通道值;
后半部分则是初始化 MCP_UI_Params,使用 pMCP 指针指向 MCP_UI_Params,
将 UI_Params 中的函数地址赋值给 pMCP->_Super 中的函数指针。然后使用
UFCP_Init()初始化 pUSART->_Super 结构体中的数据帧发送/接收字段。MCP_Init()
则是将 pMCP->FCP 与 pUSART 连接起来(将 pMCP->FCP 指向 pUSART),并且将
pMCP 其他的成员初始化。最后的 UI_Init 就是给 pMCP->_Super 的其他成员赋初
值。
pUSART->_Super 包含的成员是发送帧和接收帧的字段,如图 20-15 所示方
框中的成员。
STM32 技术开发手册
www.ing10bbs.com
图 20-15 初始化发送/接收帧字段
因为这个函数包含了大量的结构体、指针的操作,所以理解起来会比较困难,
但主要知道这个函数是给 3 个结构体变量赋初值就行,所涉及到的函数指针变量
在运行的时候是不会被改变的,所以可以使用仿真器的 debug 功能在线观察变量
值即可知道指针所指向的函数。
UI_Scheduler()
该函数在源码中并没有任何注释,可见有多么简单。这只是一个计数函数,
每调用一次就将一些变量递减一,这变量用于控制任务的执行频率。在执行任务
的时候会查询这些变量是否为 0,只有在计数到 0 之后才执行下一步。从变量名
字可以看的出来,这里控制三个任务的执行频率。这个函数会在滴答定时器中被
调用,也就是执行计数间隔是 500us。
代码 20-144 用户接口时基控制
00 void UI_Scheduler(void)
01 {
02
if (bUITaskCounter > 0u) {
STM32 技术开发手册
www.ing10bbs.com
03
04
05
06
07
08
09
10
11
12
13 }
14
bUITaskCounter--;
}
if (bCOMTimeoutCounter > 1u) {
bCOMTimeoutCounter--;
}
if (bCOMATRTimeCounter > 1u) {
bCOMATRTimeCounter--;
}
UI_DACUpdate()
该函数用于更新 DAC 的输出。
代码 20-145 更新 DAC 输出
00 void UI_DACUpdate(uint8_t bMotorNbr)
01 {
02
if (UI_GetSelectedMC(&pDAC->_Super) == bMotorNbr) {
03
UI_DACExec(&pDAC->_Super); /* Exec DAC update */
04
}
05 }
UI_SerialCommunicationTimeOutStop()
该函数用于停止串口通信超时计数,将计数器设置为 0 就不会执行超时计
数。
代码 20-146 串口通信超时停止
00 void UI_SerialCommunicationTimeOutStop(void)
01 {
02
bCOMTimeoutCounter = 0u;
03 }
UI_SerialCommunicationTimeOutStart()
该函数启动串口通信超时计数,将计数器设置为不为 0 就会启动计数。宏定
义上设置的超时时间是 40ms。
代码 20-147 启动串口通信超时计数
00 void UI_SerialCommunicationTimeOutStart(void)
01 {
02
bCOMTimeoutCounter = SERIALCOM_TIMEOUT_OCCURENCE_TICKS;
03 }
UI_HandleStartStopButton_cb()
启动/停止按钮中断回调函数,在进入外部中断(按键触发)之后就会调用
这个函数。用于启动或者停止电机。
STM32 技术开发手册
www.ing10bbs.com
代码 20-148 启动/停止按钮
00 void UI_HandleStartStopButton_cb (void)
01 {
02
if (MC_GetTMStateMotor1() == IDLE) {
03
/* Ramp parameters should be tuned for the actual motor */
04
MC_StartMotor1();
05
} else {
06
MC_StopMotor1();
07
}
08 }
user_interface.c 文件内容
该文件的内容主要是实现 Motor Control SDK 的用户界面组件,也就是响应
在 workbench 上的操作指令。在接收到来自 workbench 上的指令的时候,就会进
入这个组件里面,实现该指令的功能,例如设置目标速度,各个寄存器的值等。
在 motor_control_protocol.c 文件中接收到串口数据之后所调用的执行函数就是
在这个文件里面。
UI_Init()
用户界面初始化函数,该函数将 UI 的控制句柄与电机的控制句柄连接起来,
pUICfg 是指向用户界面配置列表,主要是标志允许使用那些功能:DAC 接口,
HALL 传感器,在速度模式下修改 Id,允许设定 PLL 的 Kp 和 Ki。bDriveNum 是电
机数量,bSelectedDrive 是运行时所选择的电机(电机 1 或者电机 2),pMCI,
pMCT 则是电机控制句柄。
代码 20-149 用户接口初始化
00 void UI_Init(UI_Handle_t *pHandle, uint8_t bMCNum, MCI_Handle_t **
01
pMCI, MCT_Handle_t** pMCT, uint32_t* pUICfg)
02 {
03
04
pHandle->bDriveNum = bMCNum;
05
pHandle->pMCI = pMCI;
06
pHandle->pMCT = pMCT;
07
pHandle->bSelectedDrive = 0u;
08
pHandle->pUICfg = pUICfg;
09 }
UI_SelectMC()
该函数选择当前需要修改操作的电机,如果参数正确返回 true。
STM32 技术开发手册
www.ing10bbs.com
代码 20-150 选择使用的电机
00 bool UI_SelectMC(UI_Handle_t *pHandle,uint8_t bSelectMC)
01 {
02
bool retVal = true;
03
if (bSelectMC >= pHandle->bDriveNum) {
04
retVal = false;
05
} else {
06
pHandle->bSelectedDrive = bSelectMC;
07
}
08
return retVal;
09 }
UI_GetSelectedMC()
该函数返回当前所选择的电机
代码 20-151 获取当前所选的电机
00 uint8_t UI_GetSelectedMC(UI_Handle_t *pHandle)
01 {
02
return (pHandle->bSelectedDrive);
03 }
04
UI_GetSelectedMCConfig()
返回控制句柄中的 pUICfg 值。
代码 20-152 获取所选电机的用户界面配置列表
00 uint32_t UI_GetSelectedMCConfig(UI_Handle_t *pHandle)
01 {
02
return pHandle->pUICfg[pHandle->bSelectedDrive];
03 }
UI_GetCurrentMCT()
该函数用于获取 pMCT 的地址,MCT 是 MotorControl Tuning,也就是电机控
制调试,是用于控制电机的句柄。
代码 20-153 获取当前电机的调试句柄
00 MCT_Handle_t* UI_GetCurrentMCT(UI_Handle_t *pHandle)
01 {
02
return (pHandle->pMCT[pHandle->bSelectedDrive]);
03 }
STM32 技术开发手册
www.ing10bbs.com
UI_SetReg()
该函数执行 SetReg 的指令,设置寄存器的值。根据 workbench 上的指令来
设定相应的寄存器。该函数可以设置运行模式(Speed/Torque),PID 参数,速度
值等。
代码 20-154 展示了部分函数代码,一些简单的就不介绍了。
代码 20-154 设置寄存器值
00 bool UI_SetReg(UI_Handle_t *pHandle, MC_Protocol_REG_t bRegID, int32_t
01
wValue)
02 {
03
MCT_Handle_t* pMCT = pHandle->pMCT[pHandle->bSelectedDrive];
04
MCI_Handle_t * pMCI = pHandle->pMCI[pHandle->bSelectedDrive];
05
06
bool retVal = true;
07
switch (bRegID) {
08
case MC_PROTOCOL_REG_TORQUE_REF: {
09
Curr_Components currComp;
10
currComp = MCI_GetIqdref(pMCI);
11
currComp.qI_Component1 = (int16_t)wValue;
12
MCI_SetCurrentReferences(pMCI,currComp);
13
}
14
break;
15
16
case MC_PROTOCOL_REG_FLUX_REF: {
17
Curr_Components currComp;
18
currComp = MCI_GetIqdref(pMCI);
19
currComp.qI_Component2 = (int16_t)wValue;
20
MCI_SetCurrentReferences(pMCI,currComp);
21
}
22
break;
23
24
case MC_PROTOCOL_REG_RAMP_FINAL_SPEED: {
25
MCI_ExecSpeedRamp(pMCI,(int16_t)(wValue/6),0);
26
}
27
break;
28
29
default:
30
retVal = false;
31
break;
32
}
33
34
return retVal;
35 }
1) MC_PROTOCOL_REG_TORQUE_REF
设定扭矩的参考值。设定的是 Iq 的值,设定之前还需要先读取当前的电流
值,在调用 MCI_SetCurrentReferences()的时候是同时设置 Iq 和 Id 的,所以需要
执行读改写的操作。写入的是 pMCI 的句柄,这个是用于对用户界面使用的句柄,
并不实际控制电机,所以现在只是写入 pMCI 里面,并且标记指令未执行的状态,
然后等待时机将数据写入到 pMCT 句柄中。
STM32 技术开发手册
www.ing10bbs.com
代码 20-155 设定 Iq 目标值
00
01
02
03
04
05
06
07
case MC_PROTOCOL_REG_TORQUE_REF:
{
Curr_Components currComp;
currComp = MCI_GetIqdref(pMCI);
currComp.qI_Component1 = (int16_t)wValue;
MCI_SetCurrentReferences(pMCI,currComp);
}
break;
2) MC_PROTOCOL_REG_FLUX_REF()
设定弱磁控制目标值,实际就是 Id。
代码 20-156 设定 Id 目标值
00
01
02
03
04
05
06
07
case MC_PROTOCOL_REG_FLUX_REF:
{
Curr_Components currComp;
currComp = MCI_GetIqdref(pMCI);
currComp.qI_Component2 = (int16_t)wValue;
MCI_SetCurrentReferences(pMCI,currComp);
}
break;
3) MC_PROTOCOL_REG_RAMP_FINAL_SPEED()
设定电机转动速度,设置的是 Speed Ramp 的最终速度。详细说明可见电机控
制函数 MC_ProgramSpeedRampMotor1(),所设置的数值并不会立即生效,而是等
待到合适的时基才会生效,并且传递的参数是 wValue/6,说明从 workbench 上设
置的参数是以 ROM 为单位的速度数值。并且 Speed Ramp 的时间为 0,即数值生
效后立即改变转动速度。
代码 20-157 改变速度
00
01
02
03
04
case MC_PROTOCOL_REG_RAMP_FINAL_SPEED:
{
MCI_ExecSpeedRamp(pMCI,(int16_t)(wValue/6),0);
}
break;
UI_GetReg()
执行获取寄存器值的指令。主要用于 workbench 获取寄存器的数值更新用户
界面。
STM32 技术开发手册
www.ing10bbs.com
代码 20-158 获取寄存器的值
00 int32_t UI_GetReg(UI_Handle_t *pHandle, MC_Protocol_REG_t bRegID)
01 {
02
MCT_Handle_t* pMCT = pHandle->pMCT[pHandle->bSelectedDrive];
03
MCI_Handle_t * pMCI = pHandle->pMCI[pHandle->bSelectedDrive];
04
05
int32_t bRetVal = (int32_t)GUI_ERROR_CODE;
06
switch (bRegID) {
07
case MC_PROTOCOL_REG_SPEED_REF: {
08
bRetVal = (int32_t)(MCI_GetMecSpeedRef01Hz(pMCI) * 6);
09
}
10
break;
11
case MC_PROTOCOL_REG_TORQUE_REF: {
12
Curr_Components currComp;
13
currComp = MCI_GetIqdref(pMCI);
14
bRetVal = (int32_t)currComp.qI_Component1;
15
}
16
break;
17
case MC_PROTOCOL_REG_IQ_SPEEDMODE: {
18
Curr_Components currComp;
19
currComp = MCI_GetIqdref(pMCI);
20
bRetVal = (int32_t)currComp.qI_Component2;
21
}
22
break;
23
case MC_PROTOCOL_REG_SPEED_MEAS: {
24
bRetVal = (int32_t)(MCI_GetAvrgMecSpeed01Hz(pMCI) * 6);
25
}
26
break;
27
28
case MC_PROTOCOL_REG_MEAS_EL_ANGLE: {
29
uint32_t hUICfg = pHandle->pUICfg[pHandle->bSelectedDrive];
30
SpeednPosFdbk_Handle_t* pSPD = MC_NULL;
31
if ((MAIN_SCFG_VALUE(hUICfg) == UI_SCODE_ENC) ||
32
(MAIN_SCFG_VALUE(hUICfg) == UI_SCODE_HALL)) {
33
pSPD = pMCT->pSpeedSensorMain;
34
}
35
if ((AUX_SCFG_VALUE(hUICfg) == UI_SCODE_ENC) ||
36
(AUX_SCFG_VALUE(hUICfg) == UI_SCODE_HALL)) {
37
pSPD = pMCT->pSpeedSensorAux;
38
}
39
if (pSPD != MC_NULL) {
40
bRetVal = SPD_GetElAngle(pSPD);
41
}
42
}
43
break;
44
case MC_PROTOCOL_REG_MEAS_ROT_SPEED: {
45
uint32_t hUICfg = pHandle->pUICfg[pHandle->bSelectedDrive];
46
SpeednPosFdbk_Handle_t* pSPD = MC_NULL;
47
if ((MAIN_SCFG_VALUE(hUICfg) == UI_SCODE_ENC) ||
48
(MAIN_SCFG_VALUE(hUICfg) == UI_SCODE_HALL)) {
49
pSPD = pMCT->pSpeedSensorMain;
50
}
51
if ((AUX_SCFG_VALUE(hUICfg) == UI_SCODE_ENC) ||
52
(AUX_SCFG_VALUE(hUICfg) == UI_SCODE_HALL)) {
53
pSPD = pMCT->pSpeedSensorAux;
54
}
55
if (pSPD != MC_NULL) {
56
bRetVal = SPD_GetS16Speed(pSPD);
57
}
58
}
59
break;
60
case MC_PROTOCOL_REG_DAC_USER1: {
61
if (pHandle->pFctDACGetUserChannelValue) {
62
bRetVal = (int32_t) pHandle->pFctDACGetUserChannelValue(
63
pHandle, 0);
64
} else {
STM32 技术开发手册
www.ing10bbs.com
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90 }
bRetVal = 0;
}
}
break;
case MC_PROTOCOL_REG_DAC_USER2: {
if (pHandle->pFctDACGetUserChannelValue) {
bRetVal = (int32_t) pHandle->pFctDACGetUserChannelValue(
pHandle, 1);
} else {
bRetVal = 0;
}
}
break;
case MC_PROTOCOL_REG_RAMP_FINAL_SPEED: {
if (MCI_GetControlMode(pMCI) == STC_SPEED_MODE) {
bRetVal = (int32_t)(MCI_GetLastRampFinalSpeed(pMCI) * 6);
} else {
bRetVal = (int32_t)(MCI_GetMecSpeedRef01Hz(pMCI) * 6);
}
}
break;
default:
break;
}
return bRetVal;
1) MC_PROTOCOL_REG_SPEED_REF
该寄存器是速度目标值寄存器,读取速度值,乘上 6 是因为获取的是转子速
度,单位是[0.1Hz],转换为 RPM 需要乘上 6。
07
08
09
10
case MC_PROTOCOL_REG_SPEED_REF: {
bRetVal = (int32_t)(MCI_GetMecSpeedRef01Hz(pMCI) * 6);
}
break;
2) MC_PROTOCOL_REG_TORQUE_REF
读取扭矩值目标值,实际上是读取 Iq 的目标值。
11
12
13
14
15
16
case MC_PROTOCOL_REG_TORQUE_REF: {
Curr_Components currComp;
currComp = MCI_GetIqdref(pMCI);
bRetVal = (int32_t)currComp.qI_Component1;
}
break;
3) MC_PROTOCOL_REG_IQ_SPEEDMODE
在速度模式读取 Id 电流值。
17
18
19
20
21
22
case MC_PROTOCOL_REG_IQ_SPEEDMODE: {
Curr_Components currComp;
currComp = MCI_GetIqdref(pMCI);
bRetVal = (int32_t)currComp.qI_Component2;
}
break;
4) MC_PROTOCOL_REG_SPEED_MEAS()
获取测量的电机速度,也就是实际的转速,同样是 0.1Hz 为单位的速度值,乘
上系数之后就是 RPM 单位的转速。
23
case MC_PROTOCOL_REG_SPEED_MEAS: {
STM32 技术开发手册
www.ing10bbs.com
24
25
26
bRetVal = (int32_t)(MCI_GetAvrgMecSpeed01Hz(pMCI) * 6);
}
break;
5) MC_PROTOCOL_REG_MEAS_EL_ANGLE
获取实际的电角度,首先判断主传感器和辅传感器是否编码器或者霍尔传感
器,然后使 pSPD 指向速度传感器句柄,如果成功指向任意一个传感器,则读取
电角度值。
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
case MC_PROTOCOL_REG_MEAS_EL_ANGLE: {
uint32_t hUICfg = pHandle->pUICfg[pHandle->bSelectedDrive];
SpeednPosFdbk_Handle_t* pSPD = MC_NULL;
if ((MAIN_SCFG_VALUE(hUICfg) == UI_SCODE_ENC) ||
(MAIN_SCFG_VALUE(hUICfg) == UI_SCODE_HALL)) {
pSPD = pMCT->pSpeedSensorMain;
}
if ((AUX_SCFG_VALUE(hUICfg) == UI_SCODE_ENC) ||
(AUX_SCFG_VALUE(hUICfg) == UI_SCODE_HALL)) {
pSPD = pMCT->pSpeedSensorAux;
}
if (pSPD != MC_NULL) {
bRetVal = SPD_GetElAngle(pSPD);
}
}
break;
6) MC_PROTOCOL_REG_MEAS_ROT_SPEED
获取测量的实际速度值,但是这个速度值是以[S16Speed]格式表示的速度值。
[S16Speed]与[0.1Hz]之间的关系:
公式 20-12 电机转速单位关系
𝑣[𝑺𝟏𝟔𝑺𝒑𝒆𝒆𝒅] =
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
𝑣0.1𝐻𝑧 ∗ 65535
𝑣0.1𝐻𝑧_𝑚𝑎𝑥
case MC_PROTOCOL_REG_MEAS_ROT_SPEED: {
uint32_t hUICfg = pHandle->pUICfg[pHandle->bSelectedDrive];
SpeednPosFdbk_Handle_t* pSPD = MC_NULL;
if ((MAIN_SCFG_VALUE(hUICfg) == UI_SCODE_ENC) ||
(MAIN_SCFG_VALUE(hUICfg) == UI_SCODE_HALL)) {
pSPD = pMCT->pSpeedSensorMain;
}
if ((AUX_SCFG_VALUE(hUICfg) == UI_SCODE_ENC) ||
(AUX_SCFG_VALUE(hUICfg) == UI_SCODE_HALL)) {
pSPD = pMCT->pSpeedSensorAux;
}
if (pSPD != MC_NULL) {
bRetVal = SPD_GetS16Speed(pSPD);
}
}
break;
7) MC_PROTOCOL_REG_DAC_USER1
STM32 技术开发手册
www.ing10bbs.com
获取 DAC 的通道值,DAC 用于设定输出模拟量,DAC 只有两个通道,可以通
过使用变量来决定任意一个通道输出的监控量。pFctDACGetUserChannelValue 不
为 0 就读取 DAC 的值。
60
61
62
63
64
65
66
67
case MC_PROTOCOL_REG_DAC_USER1: {
if (pHandle->pFctDACGetUserChannelValue) {
bRetVal = (int32_t) pHandle->pFctDACGetUserChannelValue(
pHandle, 0);
} else {
bRetVal = 0;
}
}
8) MC_PROTOCOL_REG_RAMP_FINAL_SPEED
获 取 电 机 speed ramp 的 最 终 速 度 。 获 取 的 速 度 值 是 上 一 次 所 设 定 的
RampFinalSpeed,是以 RPM 单位表示,如果当前不是速度模式就返回实际的转
速。
78
79
80
81
82
83
84
85
case MC_PROTOCOL_REG_RAMP_FINAL_SPEED: {
if (MCI_GetControlMode(pMCI) == STC_SPEED_MODE) {
bRetVal = (int32_t)(MCI_GetLastRampFinalSpeed(pMCI) * 6);
} else {
bRetVal = (int32_t)(MCI_GetMecSpeedRef01Hz(pMCI) * 6);
}
}
break;
UI_ExecCmd()
该函数是执行用户命令函数,传递过来的 bCmdID 包含需要动作指令。用于
界面的指令是有限的几个指令,主要是对应着 workbench 的几个控制按钮,但不
完全相同。
代码 20-159 执行用户界面传递过来的命令
00 bool UI_ExecCmd(UI_Handle_t *pHandle, uint8_t bCmdID)
01 {
02
bool retVal = true;
03
04
MCI_Handle_t * pMCI = pHandle->pMCI[pHandle->bSelectedDrive];
05
06
switch (bCmdID) {
07
case MC_PROTOCOL_CMD_START_MOTOR: {
08
/* Call MCI Start motor; */
09
MCI_StartMotor(pMCI);
10
}
11
break;
12
case MC_PROTOCOL_CMD_SC_STOP: {
13
/* Call MCI Stop motor; */
14
MCI_StopMotor(pMCI);
15
}
16
break;
17
case MC_PROTOCOL_CMD_STOP_RAMP: {
STM32 技术开发手册
www.ing10bbs.com
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 }
if (MCI_GetSTMState(pMCI) == RUN) {
MCI_StopSpeedRamp(pMCI);
}
}
break;
case MC_PROTOCOL_CMD_START_STOP: {
/* Queries the STM and a command start or stop depending on
the state. */
if (MCI_GetSTMState(pMCI) == IDLE) {
MCI_StartMotor(pMCI);
} else {
MCI_StopMotor(pMCI);
}
}
break;
case MC_PROTOCOL_CMD_FAULT_ACK: {
MCI_FaultAcknowledged(pMCI);
}
break;
case MC_PROTOCOL_CMD_ENCODER_ALIGN: {
MCI_EncoderAlign(pMCI);
}
break;
case MC_PROTOCOL_CMD_IQDREF_CLEAR: {
MCI_Clear_Iqdref(pMCI);
}
break;
}
return retVal;
图 20-16 workbench 上的控制按钮
主要的指令有启动电机、停止电机、应答错误、停止 RAMP 动作、编码器对
齐。
UI_ExecSpeedRamp()
这 个 函 数 也 就 是 将 速 度 值 转 换 从 [RPM] 转 换 成 [0.1Hz] , 然 后 再 调 用
MCI_ExecSpeedRamp()函数,调用之后也不是立即执行该动作,而是存入指令缓
存里面,等待适当的时机才执行。
代码 20-160 执行 SpeedRamp 动作
00 bool UI_ExecSpeedRamp(UI_Handle_t *pHandle, int32_t wFinalMecSpeedRPM,
01
uint16_t hDurationms)
02 {
03
04
MCI_Handle_t * pMCI = pHandle->pMCI[pHandle->bSelectedDrive];
STM32 技术开发手册
www.ing10bbs.com
05
06
07
08
09 }
/* Call MCI Exec Ramp */
MCI_ExecSpeedRamp(pMCI,(int16_t)(wFinalMecSpeedRPM/6),hDurationms);
return true;
UI_ExecTorqueRamp()
该函数调用 MCI_ExecTorqueRamp()执行 TorqueRamp 动作。同样并不是立即
执行该动作。
代码 20-161 执行 TorqueRamp 动作
00 bool UI_ExecTorqueRamp(UI_Handle_t *pHandle, int16_t hTargetFinal,
01
uint16_t hDurationms)
02 {
03
04
MCI_Handle_t * pMCI = pHandle->pMCI[pHandle->bSelectedDrive];
05
06
/* Call MCI Exec Ramp */
07
MCI_ExecTorqueRamp(pMCI,hTargetFinal,hDurationms);
08
return true;
09 }
UI_GetRevupData()
该函数用于获取无感模式的开环加速参数。在无感模式的时候需要开环加速
启动,这里就是获取所设置的基本参数。包括加速时间和最终的电流。
代码 20-162 获取
00 bool UI_GetRevupData(UI_Handle_t *pHandle, uint8_t bStage, uint16_t*
01
pDurationms,
02
int16_t* pFinalMecSpeed01Hz, int16_t*
03
pFinalTorque )
04 {
05
bool hRetVal = true;
06
07
RevUpCtrl_Handle_t *pRevupCtrl = pHandle->pMCT[pHandle->
08
bSelectedDrive]->pRevupCtrl;
09
if (pRevupCtrl) {
10
*pDurationms = RUC_GetPhaseDurationms(pRevupCtrl, bStage);
11
*pFinalMecSpeed01Hz = RUC_GetPhaseFinalMecSpeed01Hz(pRevupCtrl,
12
bStage);
13
*pFinalTorque = RUC_GetPhaseFinalTorque(pRevupCtrl, bStage);
14
} else {
15
hRetVal = false;
16
}
17
return hRetVal;
18 }
UI_SetRevupData()
设置无感模的开环加速基本参数,包括加速时间,最终电流等。
STM32 技术开发手册
www.ing10bbs.com
代码 20-163 设置开环加速参数
00 bool UI_SetRevupData(UI_Handle_t *pHandle, uint8_t bStage, uint16_t
01
hDurationms,
02
int16_t hFinalMecSpeed01Hz, int16_t hFinalTorque )
03 {
04
RevUpCtrl_Handle_t *pRevupCtrl = pHandle->pMCT[pHandle->
05
bSelectedDrive]->pRevupCtrl;
06
RUC_SetPhaseDurationms(pRevupCtrl, bStage, hDurationms);
07
RUC_SetPhaseFinalMecSpeed01Hz(pRevupCtrl, bStage,
08
hFinalMecSpeed01Hz);
09
RUC_SetPhaseFinalTorque(pRevupCtrl, bStage, hFinalTorque);
10
return true;
11 }
UI_SetCurrentReferences()
设置电流目标值,主要是值 Iq 和 Id。
代码 20-164 设置电流目标值
00 void UI_SetCurrentReferences(UI_Handle_t *pHandle, int16_t hIqRef,
01
int16_t hIdRef)
02 {
03
04
MCI_Handle_t * pMCI = pHandle->pMCI[pHandle->bSelectedDrive];
05
Curr_Components currComp;
06
currComp.qI_Component1 = hIqRef;
07
currComp.qI_Component2 = hIdRef;
08
MCI_SetCurrentReferences(pMCI,currComp);
09 }
UI_DACInit()
这是 DAC 初始化函数实际上是 DAC 的使能控制。调用该函数之后将使能 DAC
外设。这个函数使用了一个 pFct_DACInit()函数指针,实际上调用的函数是
DAC_Init(),使能两个通道。
代码 20-165 DAC 初始化函数
00 void UI_DACInit(UI_Handle_t *pHandle)
01 {
02
if (pHandle->pFct_DACInit) {
03
pHandle->pFct_DACInit(pHandle);
04
}
05 }
UI_DACExec()
更新 DAC 两个通道的值。调用的函数实际是 DAC_Exec()
代码 20-166 更新 DAC 的值
00 void UI_DACExec(UI_Handle_t *pHandle)
01 {
02
if (pHandle->pFct_DACExec) {
STM32 技术开发手册
www.ing10bbs.com
03
04
05 }
pHandle->pFct_DACExec(pHandle);
}
UI_SetDAC()
设置 DAC 的通道,设定某个通道的输出为 bVariable,bVariable 可以是
MC_PROTOCOL_REG_I_A,MC_PROTOCOL_REG_I_B 等监控量,就是设定 DAC 的通
道用于输出什么变量。实际调用的函数是 DAC_SetChannelConfig()。
代码 20-167 设置 DAC 通道
00 void UI_SetDAC(UI_Handle_t *pHandle, DAC_Channel_t bChannel,
01
MC_Protocol_REG_t bVariable)
02 {
03
if (pHandle->pFctDACSetChannelConfig) {
04
pHandle->pFctDACSetChannelConfig(pHandle, bChannel, bVariable);
05
}
06 }
07
UI_GetDAC()
这个函数用于获取当前的 DAC 通道输出的是什么监控量。实际上调用的函
数是。DAC_GetChannelConfig()。
代码 20-168 获取 DAC 当前所选择的监控量
00 MC_Protocol_REG_t UI_GetDAC(UI_Handle_t *pHandle, DAC_Channel_t
01
bChannel)
02 {
03
MC_Protocol_REG_t retVal = MC_PROTOCOL_REG_UNDEFINED;
04
if (pHandle->pFctDACGetChannelConfig) {
05
retVal = pHandle->pFctDACGetChannelConfig(pHandle, bChannel);
06
}
07
return retVal;
08 }
09
UI_SetUserDAC()
该函数用于设定 DAC 的通道输出值。用于单独定义一个通道的输出,实际
调用的函数是 DAC_SetUserChannelValue()。
代码 20-169 设置 DAC 的通道值
00 void UI_SetUserDAC(UI_Handle_t *pHandle, DAC_UserChannel_t
01
bUserChNumber, int16_t hValue)
02 {
03
if (pHandle->pFctDACSetUserChannelValue) {
04
pHandle->pFctDACSetUserChannelValue(pHandle, bUserChNumber,
05
hValue);
06
}
07 }
STM32 技术开发手册
www.ing10bbs.com
stm32f4xx_hal_msp.c 文件内容
该文件主要是实现 HAL 库的 msp 函数,也就是初始化配置了各个外设的引
脚,并且设定了中断优先级,同时使能了各个外设的时钟信号。
20.2.2 Middlewares/MotorControl
bus_voltage_sensor.c 文件内容
这个是总线电压传感器组件,主要是用于获取总线电压,也就是电源的电压。
其中 VBS_GetBusVoltage_d()、VBS_GetAvBusVoltage_d()和 VBS_CheckVbus()这三个
函数并没有被调用,实际上就只有一个是有用的。
VBS_GetAvBusVoltage_V()
该函数用于获取总线电压的平均值,AvBusVoltage_d 是以数字量表示的平均
值。
代码 20-170 获取总线电压的平均值
00 uint16_t VBS_GetAvBusVoltage_V( BusVoltageSensor_Handle_t * pHandle )
01 {
02
uint32_t temp;
03
04
temp = ( uint32_t )( pHandle->AvBusVoltage_d );
05
temp *= pHandle->ConversionFactor;
06
temp /= 65536u;
07
08
return ( ( uint16_t )temp );
09 }
ConversionFactor 则是转换系数,实际上是总线电压的最大值,在初始化的
时候赋值为 3.3/0.0463。0.0463 就是分压电阻的系数。
代码 20-171 电压转换系数
00 .ConversionFactor
01
= (uint16_t)(MCU_SUPPLY_VOLTAGE /
BUS_ADC_CONV_RATIO)
STM32 技术开发手册
www.ing10bbs.com
crcle_limitation.c 文件内容
该文件主要实现一个功能,就是限制输出,使输出电压矢量幅值控制在一个
圆的范围内。在经过 PID 调制之后,得到两个新的定子电压量𝑉𝑞 和𝑉𝑑 ,在定子上
产生磁通和转矩电流,必须保证合成的电压矢量幅值饱和等于√𝑉𝑞2 + 𝑉𝑑2 。
Circle_Limitation()
⃗⃗⃗⃗𝑑∗ 、𝑉
⃗⃗⃗𝑞 和𝑉
⃗ 𝑑 合成电压矢量𝑉
⃗ ,示意图如下,𝑉
⃗⃗⃗⃗𝑞∗ 、𝑉
⃗⃗⃗⃗∗ 是限制之前的向量,
使用𝑉
⃗⃗⃗
⃗ 𝑑 、𝑉
⃗ 则是限制之后的向量。𝑟1是调制之前的圆,𝑟2 则是调制之后的圆,可以
𝑉𝑞 、𝑉
⃗⃗⃗𝑞 、𝑉
⃗ 𝑑 ,𝑀𝑀𝐼是最大
看到是𝑟2 = 𝑟1 ∗ 𝑀𝑀𝐼,然后就可以根据𝑟2 跟𝑟1的关系计算出𝑉
调制系数。原理很简单,就是等比例缩小,电压矢量乘上一个系数缩小,得到目
标矢量,然后利用这个比例计算新的值。
图 20-17 调制圆限制原理
公式 20-13 𝑽𝒒 、𝑽𝒅 计算过程
𝑉𝑞 =
𝑉𝑞∗ ∙ 𝑀𝑀𝐼 ∙ 𝑆16_𝑀𝐴𝑋
⃗⃗⃗⃗∗ |
|𝑉
STM32 技术开发手册
www.ing10bbs.com
𝑉𝑑 =
𝑉𝑑∗ ∙ 𝑀𝑀𝐼 ∙ 𝑆16_𝑀𝐴𝑋
⃗⃗⃗⃗∗ |
|𝑉
为了加快计算速度,可以将一些常数整理成表格,也就是 Circle_limit_table,
如下:
公式 20-14 调制圆表格计算
𝑀𝑀𝐼 ∙ 𝑆16_𝑀𝐴𝑋 2
𝐶𝑖𝑟𝑐𝑙𝑒_𝑙𝑖𝑚𝑖𝑡_𝑡𝑎𝑏𝑙𝑒[𝑥] =
⃗⃗⃗⃗∗ |
|𝑉
代码 20-172 调制圆限制
00 Volt_Components Circle_Limitation( CircleLimitation_Handle_t * pHandle,
01
Volt_Components Vqd )
02 {
03
uint16_t table_element;
04
uint32_t uw_temp;
05
int32_t sw_temp;
06
Volt_Components local_vqd = Vqd;
07
08
sw_temp = ( int32_t )( Vqd.qV_Component1 ) * Vqd.qV_Component1 +
09
( int32_t )( Vqd.qV_Component2 ) * Vqd.qV_Component2;
10
11
uw_temp = ( uint32_t ) sw_temp;
12
13
/* uw_temp min value 0, max value 2*32767*32767 */
14
if ( uw_temp > ( uint32_t )( pHandle->MaxModule ) *
pHandle->MaxModule ) {
15
/* 超出范围,取高 8 位 */
16
uw_temp /= ( uint32_t )( 16777216 );// 右移 24 位 2^24 = 16777216
17
18
/* wtemp min value pHandle->Start_index, max value 127 */
19
uw_temp -= pHandle->Start_index;// 减去偏移量,用于查表
20
21
/* uw_temp min value 0, max value 127 - pHandle->Start_index */
22
table_element = pHandle->Circle_limit_table[( uint8_t )uw_temp];
23
24
sw_temp = Vqd.qV_Component1 * ( int32_t )table_element;
25
local_vqd.qV_Component1 = ( int16_t )( sw_temp / 32768 );
26
27
sw_temp = Vqd.qV_Component2 * ( int32_t )( table_element );
28
local_vqd.qV_Component2 = ( int16_t )( sw_temp / 32768 );
29
}
30
return ( local_vqd );
31 }
函数中先计算𝑉𝑞 和𝑉𝑑 的平方值,然后比较判断是否过饱和,MaxModule 最大
值是√ 327682 ,但是实际上例程是乘上 95%的值,也就是 MMI=95%
⃗⃗⃗⃗∗ |相关的数据,所以对
如果是超过了过饱和值,就进行查表,表格是与|𝑉
uw_temp 进行处理得到索引目录(Index)。
STM32 技术开发手册
www.ing10bbs.com
digital_output.c 文件内容
该文件主要用于输出数字信号,但是实际没有调用该文件的函数,甚至连初
始化配置都没有,所以可以忽略该文件。
hall_speed_pos_fdbk.c 文件内容
霍尔传感器的速度反馈组件,在这个函数中处于与霍尔传感器接口的定时器
相关的功能,包括中断响应,计算速度、电角度等参数。另外要与同目录下的
speed_pos_fdbk.c 文件区分,带有 hall 前缀是用于处理和计算速度等参数,不带
hall 前缀的是速度反馈通用接口,专用于返回各项参数,
首先弄清楚一下概念,在 FOC 计算过程中,角度值使用[S16degree]来表示,
定义如下:
1[𝐒𝟏𝟔𝐝𝐞𝐠𝐫𝐞𝐞] =
360
65536
如果将 360 换成𝟐𝛑,那就是弧度值。
图 20-18 角度值的数字量化
下面先来看私有宏定义。霍尔传感器接口的定时器是可以根据转速实时调整
预分频的,所以如果速度值低于一定值或者计数器溢出,则会调整预分频值以适
应不同的转速。LOW_RES_THRESHOLD 就是低速判断阈值,低于这个值就修改预
STM32 技术开发手册
www.ing10bbs.com
分频。S16_120_PHASE_SHIFT 就是 120°。HALL_MAX_PSEUDO_SPEED 就是最大的
速度限制,每个 PWM 周期测量得到的转速大于这个值就是超速。
代码 20-173 私有宏定义
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* Lower threshold to reques a decrease of clock prescaler */
#define LOW_RES_THRESHOLD ((uint16_t)0x5500u)
#define HALL_COUNTER_RESET ((uint16_t) 0u)
#define S16_120_PHASE_SHIFT (int16_t)(65536/3)
#define S16_60_PHASE_SHIFT (int16_t)(65536/6)
#define
#define
#define
#define
#define
#define
#define
#define
STATE_0
STATE_1
STATE_2
STATE_3
STATE_4
STATE_5
STATE_6
STATE_7
(uint8_t)0
(uint8_t)1
(uint8_t)2
(uint8_t)3
(uint8_t)4
(uint8_t)5
(uint8_t)6
(uint8_t)7
#define
#define
#define
#define
NEGATIVE
POSITIVE
NEGATIVE_SWAP
POSITIVE_SWAP
(int8_t)-1
(int8_t)1
(int8_t)-2
(int8_t)2
/* With digit-per-PWM unit (here 2*PI rad = 0xFFFF): */
#define HALL_MAX_PSEUDO_SPEED
((int16_t)0x7FFF)
#define CCER_CC1E_Set
#define CCER_CC1E_Reset
((uint16_t)0x0001)
((uint16_t)0xFFFE)
宏定义中还有定时器的配置(CCER_CC1E_Set,CCER_CC1E_Reset),主要是为
了能快速的配置而直接操作寄存器。
HALL_Init()
霍尔传感器的初始化。在 main.c 里面也有初始化霍尔传感器的定时器,但
那是专门初始化定时器的底层配置,这个则是初始化霍尔接口句柄相关的功能和
参数。
代码 20-174 霍尔接口初始化核心部分
00 /* Adjustment factor: minimum measurable speed is x time less than the
minimum
01 reliable speed */
02 hMinReliableElSpeed01Hz /= 4u;
03 /* Adjustment factor: maximum measurable speed is x time greather than
the
04 maximum reliable speed */
05 hMaxReliableElSpeed01Hz *= 2u;
06
07 pHandle->OvfFreq = ( uint16_t )( pHandle->TIMClockFreq / 65536u );
08 /* SW Init */
09 if ( hMinReliableElSpeed01Hz == 0u )
10 {
11
/* Set fixed to 150 ms */
STM32 技术开发手册
www.ing10bbs.com
12
pHandle->HallTimeout = 150u;
13 } else
14 {
15
/* Set accordingly the min reliable speed. 10000 comes from mS and
01Hz*/
16
/* 6 comes from the fact that sensors are toggling each 60 deg */
17
pHandle->HallTimeout = 10000u / ( 6u * hMinReliableElSpeed01Hz );
18 }
19
20 /* Compute the prescaler to the closet value of the TimeOut (in mS )*/
21 pHandle->HALLMaxRatio = ( pHandle->HallTimeout * pHandle->OvfFreq ) /
1000 ;
22
23 /* Align MaxPeriod to a multiple of Overflow.*/
24 pHandle->MaxPeriod = ( pHandle->HALLMaxRatio ) * 65536uL;
25
26 pHandle->SatSpeed = hMaxReliableElSpeed01Hz;
27
28 pHandle->PseudoFreqConv = ( ( pHandle->TIMClockFreq / 6u )
29
/ ( pHandle->_Super.hMeasurementFrequency ) ) *
30
65536u;
31
32 pHandle->MinPeriod = ( ( 10u * pHandle->TIMClockFreq ) / 6u )
33
/ hMaxReliableElSpeed01Hz;
34
35 pHandle->PWMNbrPSamplingFreq = ( pHandle->_Super.hMeasurementFrequency /
36
pHandle->SpeedSamplingFreqHz ) - 1u;
从 代 码 中 可以 看 到 实际 计 算 的 时候 是 使用 hMinReliableElSpeed01Hz 和
hMaxReliableElSpeed01Hz,意为最低(最高)可靠电频率,单位是 0.1Hz,就是电
角度的频率,在这个范围内都认为是正常的速度值。这个是单相霍尔信号的频率。
00 pHandle->HallTimeout = 10000u / ( 6u * hMinReliableElSpeed01Hz );
HallTimeout 就是最低转速对应的定时器计数时间,单位是 ms。超过这个时
间还没有捕获到信号则代表转速为 0。乘上 10000 是因为从 0.1Hz 转换为 ms 单
位,乘上 6 是因为 hMinReliableElSpeed01Hz 是单通道信号的频率值,定时器捕获
是捕获三通道异或之后的信号,乘 6 得到的就是两次信号之间的时间间隔。
00 pHandle->HALLMaxRatio = ( pHandle->HallTimeout * pHandle->OvfFreq ) /
1000 ;
HALLMaxRatio 就是计数器预分频值,OvfFreq 是定时器最低的计数频率,时
间是频率的倒数,利用倒数避免除法运算,得到定时器的预分频值。
SatSpeed 即是最大的速度值。PseudoFreqConv 是一个系数,用于计算电频率:
公式 20-15 电频率计算系数
PseudoFreqConv =
𝐶𝐿𝐾𝑇𝐼𝑀 ⁄6
∗ 65536
𝑓𝑠𝑎𝑚𝑝𝑙𝑖𝑛𝑔
𝐶𝐿𝐾𝑇𝐼𝑀 是定时器的时钟频率,在这里单独的介绍系数是没意义的,在后面介
绍测量电频率的时候在详细介绍计算关系。
STM32 技术开发手册
www.ing10bbs.com
MinPeriod 是最小的周期,也就是超速判定值,当捕获值小于这个值就说明
是超速了(当然也可能是毛刺)。乘上 10 是将 0.1Hz 单位转换为 Hz,除以 6 是三
相异或后单通道频率需乘上 6。PWMNbrPSamplingFreq 则是 PWM 频率和速度采
样频率比值,速度采样频率指的是计算速度值的频率,这个跟霍尔中断频率没有
关系的。
HALL_Clear()
该函数的作用就是清除(复位)跟霍尔接口相关的参数变量。
HALL_CalcElAngle()
该函数用于计算电角度,霍尔捕获中断是用于获取电角度,缓存在队列中,
然后使用该函数才是真正的计算电角度,用于执行 FOC 算法。
代码 20-175 计算电角度
00 int16_t HALL_CalcElAngle( HALL_Handle_t * pHandle )
01 {
02
if ( pHandle->_Super.hElSpeedDpp != HALL_MAX_PSEUDO_SPEED ) {
03
pHandle->MeasuredElAngle += pHandle->_Super.hElSpeedDpp;
04
pHandle->TargetElAngle += pHandle->_Super.hElSpeedDpp;
05
pHandle->_Super.hElAngle += pHandle->_Super.hElSpeedDpp + pHandle->
06
CompSpeed;
07
pHandle->PrevRotorFreq = pHandle->_Super.hElSpeedDpp;
08
} else {
09
pHandle->_Super.hElAngle += pHandle->PrevRotorFreq;
10
}
11
12
return pHandle->_Super.hElAngle;
13 }
首先是判断转速是否超速,没有则将计算得到的速度值累加起来,速度值单
位是 dpp,新角度𝜃1 = 𝜃0 + 𝜔 × 𝑡。对于𝜔𝑑𝑝𝑝 来说,分母是 FOC 控制周期,𝑡 也
是 FOC 控制周期,则𝜔 × 𝑡的结果就是𝜔本身的数值,换句话来说,即𝜃1 = 𝜃0 +
𝜔𝑑𝑝𝑝 。简化了计算过程。其中值得注意的是 CompSpeed,这个变量是同步补偿系
数,因为 FOC 控制频率与霍尔捕获中断频率并不是同步的,所以需要加上补偿系
数。这个系数就是用电角度增量除以频率比。
HALL_CalcAvrgMecSpeed01Hz()
该函数计算平均的机械转速,单位是 0.1Hz。在当前转速没有错或者不是过
慢的情况下执行以下主要核心代码:
STM32 技术开发手册
www.ing10bbs.com
代码 20-176 计算平均转速
00 *hMecSpeed01Hz = HALL_CalcAvrgElSpeedDpp( pHandle );
01
02 /* Converto el_dpp to Mec01Hz */
03 *hMecSpeed01Hz = ( int16_t )( ( *hMecSpeed01Hz *
( int32_t )pHandle->_Super.hMeasurementFrequency
* 10 ) /( 65536 * ( int32_t )pHandle->_Super.bElToMecRatio ) );
直接调用 HALL_CalcAvrgElSpeedDpp()获取平均电频率,单位是 dpp。然后再
转换成机械转速,单位是 0.1Hz。
HALL_TIMx_CC_IRQHandler()
定时器捕获中断函数,在这个函数当中,计算电角度和转速。
代码 20-177 获取霍尔传感器状态值
00
01
02
03
04
05
06
07
08
09
/* A capture event generated this interrupt */
bPrevHallState = pHandle->HallState;
if ( pHandle->SensorPlacement == DEGREES_120 )
{
pHandle->HallState = LL_GPIO_IsInputPinSet( pHandle->H3Port,
pHandle->H3Pin ) << 2 | LL_GPIO_IsInputPinSet( pHandle->H2Port,
pHandle->H2Pin ) << 1 | LL_GPIO_IsInputPinSet( pHandle->H1Port,
pHandle->H1Pin );
} else
{
pHandle->HallState = ( LL_GPIO_IsInputPinSet( pHandle->H2Port,
pHandle->H2Pin ) ^ 1 ) << 2| LL_GPIO_IsInputPinSet( pHandle->H3Port,
pHandle->H3Pin ) << 1 | LL_GPIO_IsInputPinSet( pHandle->H1Port,
pHandle->H1Pin );
}
首先是获取霍尔引脚的电平状态,霍尔传感器根据安装位置不同分为 120°
和 60°,如下图所示。从图中可以看的出来,60°只需要更换 H2、H3 排列顺序
(H1,H3,H2),再翻转 H2 的信号就可以使得两种方式输出的霍尔波形一致,即具有
通用性,实际上代码里面也是这样做的。
STM32 技术开发手册
www.ing10bbs.com
图 20-19 不同安装位置的霍尔传感器输出波形
在读取霍尔传感器的状态值之后,就可以知道当前的角度信息、转速、方向
等信息了。
代码 20-178 读取电角度并判断方向
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
switch ( pHandle->HallState )
{
case STATE_5:
if ( bPrevHallState == STATE_4 ) {
pHandle->Direction = POSITIVE;
pHandle->MeasuredElAngle = pHandle->PhaseShift;
} else if ( bPrevHallState == STATE_1 ) {
pHandle->Direction = NEGATIVE;
pHandle->MeasuredElAngle = ( int16_t )( pHandle->PhaseShift +
S16_60_PHASE_SHIFT );
}
break;
/* 中间省略部分代码 */
case STATE_4:
if ( bPrevHallState == STATE_6 ) {
pHandle->Direction = POSITIVE;
pHandle->MeasuredElAngle = ( int16_t )( pHandle->PhaseShift S16_60_PHASE_SHIFT );
} else if ( bPrevHallState == STATE_5 ) {
pHandle->Direction = NEGATIVE;
pHandle->MeasuredElAngle = ( int16_t )( pHandle->PhaseShift );
}
break;
default:
/* Bad hall sensor configutarion so update the speed reliability */
pHandle->SensorIsReliable = false;
break;
}
STM32 技术开发手册
www.ing10bbs.com
当电机往某个方向转动的时候,霍尔传感器的状态值变化是有周期性的规律。
如图 20-19 所示,从 State5->State1......,如果反转则是 State4->State6...... 所以只
需要将上一次的状态值记录下来,就可以判断出当前的电机转向。
以 State5 为例:如果上一步是 State4,那么电机正转,则当前的电角度就是
PhaseShift,PhaseShift 就是测量所得的同步电角度。默认电机 A 相的反电动势最
高点作为电角度的 0°,而 A 相反电动最高点到 H1 的上升沿之间的间隔就是同
步电角度(也就是 PhaseShift)。如下图所示。所以当 State5 的时候就同步到电角
度的 0°,对应下图中 120°霍尔传感器 H1 的上升沿。
图 20-20 同步电角度测量
如果上一步是 State1,说明电机反转,这个时候的电角度就是在 State5 的基
础上加上 S16_60_PHASE_SHIFT,对应上图中 120°霍尔传感器 H3 的下降沿时刻。
其他的状态值同理,电角度的增量是以 0°(State5)为基准进行增加或者减少。
代码 20-179 读取捕获值并调整预分频
00 /* used to validate the average speed measurement */
01 if ( pHandle->BufferFilled < pHandle->SpeedBufferSize )
02 {
STM32 技术开发手册
www.ing10bbs.com
03
pHandle->BufferFilled++;
04 }
05 /* 获取定时器捕获值和预分频 */
06 hHighSpeedCapture = LL_TIM_IC_GetCaptureCH1( TIMx );// 定时器捕获值
07 wCaptBuf = ( uint32_t )hHighSpeedCapture; // uint16_t -> Uint32_t
08 hPrscBuf = LL_TIM_GetPrescaler ( TIMx ); // 预分频值
09
10 /* 捕获值加上溢出的次数,得到两次信号之间的计数间隔 */
11 wCaptBuf += ( uint32_t )pHandle->OVFCounter * 0x10000uL;// 溢出计数
12
13 /* 根据是否溢出来重新计算 wCaptBuf */
14 if ( pHandle->OVFCounter != 0u ) // 溢出
15 {
16
/* 使用预分频修正捕获值 */
17
uint16_t hAux;
18
hAux = hPrscBuf + 1u;
19
wCaptBuf *= hAux; // 预分频乘上捕获值,得到的是定时器计数频率的计数值
就是以 84Mhz 计数得到的计数值
22
23
if ( pHandle->RatioInc ) { //
24
上一次已经修改了预分频但这一次还是溢出了,
25
下一次还需要修改预分频
26
pHandle->RatioInc = false; /* Previous capture caused overflow */
27
/* Don't change prescaler (delay due to preload/update mechanism)
*/
28
} else { // 上一次没有改预分频值但是这一次溢出了,
29
说明这一次需要修改预分频值
30
if ( LL_TIM_GetPrescaler ( TIMx ) < pHandle->HALLMaxRatio ) { /*
31
防止计数频率过低 */
32
LL_TIM_SetPrescaler ( TIMx, LL_TIM_GetPrescaler ( TIMx ) + 1 );
33
pHandle->RatioInc = true;
34
}
35
}
36 } else // 没有溢出
37 {
38
// 没有溢出但是上一次捕获的时候修改了预分频,
39
这一次计算的时候需要调整预分频值
40
if ( pHandle->RatioDec ) { // 预分频改变 flag
41
uint16_t hAux;
42
hAux = hPrscBuf + 2u; // 预分频+2
43
wCaptBuf *= hAux;
// 捕获值×预分频
44
45
pHandle->RatioDec = false;
46
}
47
/* 没有溢出并且上一次没有修改预分频,
48
说明可以调整预分频,提高定时器计数频率 */
49
else {
50
uint16_t hAux = hPrscBuf + 1u;// 预分频+1
51
wCaptBuf *= hAux;
// 捕获值×预分频
52
/* 捕获值小于阈值,说明计数频率比较慢,转速较快,
53
可以修改预分频,提高计数频率,
54
使捕获值在一个合适的范围内 */
55
if ( hHighSpeedCapture < LOW_RES_THRESHOLD ) { /* If capture range
56
correct */
57
/* 避免预分频值等于 0 的情况,修改预分频,
58
并且标记这一次修改了预分频 */
59
if ( LL_TIM_GetPrescaler ( TIMx ) > 0u ) { /* or prescaler
cannot be
60
further reduced */
61
LL_TIM_SetPrescaler ( TIMx, LL_TIM_GetPrescaler ( TIMx ) 1 );
62
pHandle->RatioDec = true;
63
}
STM32 技术开发手册
www.ing10bbs.com
64
65
66 }
}
}
电机的实际应用速度跨度比较大,低速到高速可能是 100rpm 和 3000rpm。
为了能获得最佳的分辨率,在运行中需要不断调节定时器的时钟分频,原理很简
单,就是当转速比较慢的时候(定时器计数溢出),降低定时器的计数频率,使
计数周期足够长,当转速比较高的时候(定时器捕获值小于一定值)就增加定时
器的计数频率,缩短定时器的计数周期。如下图所示:
图 20-21 减少预分频系数
当捕获值小于 0x5500 的时候,说明转速比较高,捕获值过低,这个时候可
以减少预分频值,延长定时器更新周期,但是由于使能了预装载功能,只在下一
步的更新事件中才会更新预分频值。如果在捕获事件发生之前触发了一次溢出更
新事件,说明电机转速过慢,这个事件就需要增加预分频值,调整计数周期以适
应更低的转速。
STM32 技术开发手册
www.ing10bbs.com
图 20-22 增加预分频系数
再来看代码内容,在代码 20-179 中,就是先读取定时器的捕获值和当前的
预分频值,然后再判断捕获事件之前有没有发生溢出更新事件。如果是第二次溢
出,则无需处理,如果是第一次溢出,则增加预分频值。如果没有发生溢出时间
则判断捕获值是否过低。如果过低了并且是第二次检测到过低,则说明当前读取
的预分频值是新的预分频值,再加 1 才是捕获值对应的预分频值,第一次检测捕
获值低于 0x5500 就修改预分频值。这个就是读取捕获值并且修改预分频值的流
程。
代码 20-180 记录捕获值
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
//小于最小的周期值,就是转速过快,超速了
if ( wCaptBuf < pHandle->MinPeriod )
{
pHandle->CurrentSpeed = HALL_MAX_PSEUDO_SPEED;
pHandle->NewSpeedAcquisition = 0;
} else
{
/* 滑动均值滤波 */
// 减去累加器中的速度值,也就是队列中的第一个速度值,
然后存入最后的速度值,再累加
pHandle->ElSpeedSum -= pHandle->SensorSpeed[pHandle->SpeedFIFOIdx];
if ( wCaptBuf >= pHandle->MaxPeriod ) { // 速度过慢
pHandle->SensorSpeed[pHandle->SpeedFIFOIdx] = 0; //设为 0
} else {
/* 捕获值转换成 */
pHandle->SensorSpeed[pHandle->SpeedFIFOIdx] = ( int16_t )
( pHandle->PseudoFreqConv / wCaptBuf );
18
pHandle->SensorSpeed[pHandle->SpeedFIFOIdx] *= pHandle->Direction;
// 方向决定正负
STM32 技术开发手册
www.ing10bbs.com
20
pHandle->ElSpeedSum +=
pHandle->SensorSpeed[pHandle->SpeedFIFOIdx];//电角度速度累加
22
}
23
/* Update pointers to speed buffer */
24
pHandle->CurrentSpeed = pHandle->SensorSpeed[pHandle->SpeedFIFOIdx];
25
//更新最新的速度
26
pHandle->SpeedFIFOIdx++; // 更新目录索引
27
if ( pHandle->SpeedFIFOIdx == pHandle->SpeedBufferSize ) {
28
pHandle->SpeedFIFOIdx = 0u;
29
}
30
/* 标记获得新的速度值 */
31
pHandle->NewSpeedAcquisition = 1;
32 }
在获取了捕获值之后,先判断是否超速,也就是捕获值会不会低于 MinPeriod,
只有转速在可接收范围内才会将数据存入队列中,速度值的计算采用滑动均值滤
波的方案,一开始将数据存进队列中,然后使用累加器将队列所有数据累加起来,
当新采样的数据可用就将数据累加器减去队列中第一个数据,然后存入最新的数
据,并且累加到累加器中,累加器中的数据一直都是队列中所有数据的总和,每
次采样到新的数据只需要执行一次减法,一次加法,一次除法就可以求得前 n 次
采样的平均值。这里并没有执行除法求平均,只是待用到的时候再操作。
00 pHandle->SensorSpeed[pHandle->SpeedFIFOIdx] = ( int16_t ) ( pHandle->
01
PseudoFreqConv / wCaptBuf );
存进𝑆𝑒𝑛𝑠𝑜𝑟𝑆𝑝𝑒𝑒𝑑队列中的数据是每个 FOC 周期的角度变化量(dpp),
𝑃𝑠𝑒𝑢𝑑𝑜𝐹𝑟𝑒𝑞𝐶𝑜𝑛𝑣是在霍尔接口初始化的时候就计算好的系数,对其展开之后:
公式 20-16 电频率计算系数
𝑃𝑠𝑒𝑢𝑑𝑜𝐹𝑟𝑒𝑞𝐶𝑜𝑛𝑣 =
𝐶𝐿𝐾𝑇𝐼𝑀 ⁄6
∗ 65536
𝑓𝑠𝑎𝑚𝑝𝑙𝑖𝑛𝑔
wCaptBuf 是捕获值( 𝐶𝑎𝑝)再乘上预分频值( 𝑃𝑆𝐶)得到的,𝑓𝑠𝑎𝑚𝑝𝑙𝑖𝑛𝑔 同时
也是 FOC 的计算频率, 𝐶𝐿𝐾𝑇𝐼𝑀 则是定时器的时钟频率。稍作整理之后,存进速度
队列中的数据就变成:
公式 20-17 速度计算
𝑆𝑒𝑛𝑠𝑜𝑟𝑆𝑝𝑒𝑒𝑑 =
𝑓𝑇𝐼𝑀
𝑓𝑇𝐼𝑀 ∗ 65536
𝐶𝑎𝑝 ∗ 𝑃𝑆𝐶 ∗ 6 ∗ 𝑓𝐹𝑂𝐶
65536
是霍尔信号的频率𝑓𝐻𝑎𝑙𝑙 ,
𝐶𝑎𝑝∗𝑃𝑆𝐶
6
2π
则是对应着 6 ,也就是 60°,因为每次
霍尔捕获事件就代表着旋转了 60°电角度,总结一下就是速度值ω𝑠16𝑑𝑒𝑔𝑟𝑒𝑒⁄ ,
𝑠
然后再除以𝑓𝐹𝑂𝐶 ,得到的就是每个 FOC 周期的角度变化量(dpp)。在每个 FOC 周
期计算电角度的时候,就将该数据累加,得到的就是电角度值。
STM32 技术开发手册
www.ing10bbs.com
HALL_TIMx_UP_IRQHandler()
霍尔传感器的定时器更新中断处理函数。在这个函数中记录计数溢出的次数,
一旦超出了允许的次数,则认为速度为 0,清空所有变量。由于预分频是实时修
改的,所以允许的最大溢出次数也是根据预分频值进行修改的。
代码 20-181 霍尔接口定时器更新中断函数
00 void * HALL_TIMx_UP_IRQHandler( void * pHandleVoid )
01 {
02
HALL_Handle_t * pHandle = ( HALL_Handle_t * ) pHandleVoid;
03
TIM_TypeDef * TIMx = pHandle->TIMx;
04
05
if ( pHandle->SensorIsReliable ) {
06
uint16_t hMaxTimerOverflow; // 允许的最大溢出次数
07
/* an update event occured for this interrupt request generation */
08
pHandle->OVFCounter++; // 溢出计数
09
10
/* 预分频值越大,允许的次数越少 */
11
hMaxTimerOverflow =
( uint16_t )( ( ( uint32_t )pHandle->HallTimeout *
12
pHandle->OvfFreq )
13
/ ( ( LL_TIM_GetPrescaler ( TIMx ) + 1
14
) * 1000u ) );
15
if ( pHandle->OVFCounter >= hMaxTimerOverflow ) { //
16
超过了允许的最大次数就复位变零
17
/* Set rotor speed to zero */
18
pHandle->_Super.hElSpeedDpp = 0;
19
20
/* 复位变零 */
21
HALL_Init_Electrical_Angle( pHandle );
22
/* Reset the overflow counter */
23
pHandle->OVFCounter = 0u;
24
/* Reset the SensorSpeed buffer*/
25
uint8_t bIndex;
26
for ( bIndex = 0u; bIndex < pHandle->SpeedBufferSize; bIndex++ )
{
27
pHandle->SensorSpeed[bIndex] = 0;
28
}
29
pHandle->BufferFilled = 0 ;
30
pHandle->CurrentSpeed = 0;
31
pHandle->SpeedFIFOIdx = 1;
32
pHandle->ElSpeedSum = 0;
33
}
34
}
35
return MC_NULL;
36 }
hMaxTimerOverflow 就是允许的最大溢出次数,使用 HallTimeout 是以最低
转速运行时两次霍尔信号之间的时间间隔,OvfFreq 除以当前设置的预分频则是
定时器的计数溢出频率,意义是以最低频率计数,在最低转速的霍尔信号间隔之
间溢出的次数,实际溢出次数超过这个数值说明转速为 0。
STM32 技术开发手册
www.ing10bbs.com
HALL_CalcAvrgElSpeedDpp()
计算平均的速度值,单位是 dpp。就是要对捕获中断获得的速度值 dpp 求平
均。如果采样次数没达到队列深度,则直接使用最新的速度值作为平均速度,否
则就计算平均值。
代码 20-182 计算平均值速度值 dpp
00 static int16_t HALL_CalcAvrgElSpeedDpp( HALL_Handle_t * pHandle )
01 {
02
if ( pHandle->NewSpeedAcquisition == 1 ) {
03
if ( pHandle->BufferFilled < pHandle->SpeedBufferSize ) {
04
pHandle->AvrElSpeedDpp = ( int16_t ) pHandle->CurrentSpeed;
05
} else {
06
pHandle->AvrElSpeedDpp = ( int16_t )( pHandle->ElSpeedSum /
(int32_t )(pHandle->SpeedBufferSize ) ); /* Average value */
07
}
08
/* Clear new speed acquisitions flag */
09
pHandle->NewSpeedAcquisition = 0;
10
}
11
return pHandle->AvrElSpeedDpp;
12 }
HALL_Init_Electrical_Angle()
初始化电角度的初始位置。在初始位置的时候,即使可以捕获霍尔传感器的
状态值,但是这个值并不准确,因为两个霍尔状态值之间是间隔了 60°,初始位
置可能在这 60°之间的任意一个位置。所以折中采用 30°的位置作为起始位置。
代码 20-183 电角度初始位置
00 static void HALL_Init_Electrical_Angle( HALL_Handle_t * pHandle )
01 {
02
03
if ( pHandle->SensorPlacement == DEGREES_120 ) {
04
pHandle->HallState = LL_GPIO_IsInputPinSet( pHandle->H3Port,
05
pHandle->H3Pin ) << 2
06
| LL_GPIO_IsInputPinSet( pHandle->H2Port,
07
pHandle->H2Pin )
08
<< 1
09
LL_GPIO_IsInputPinSet( pHandle->H1Port,
10
pHandle->H1Pin );
11
} else {
12
pHandle->HallState = ( LL_GPIO_IsInputPinSet( pHandle->H2Port,
13
pHandle->H2Pin ) ^ 1 ) << 2
14
| LL_GPIO_IsInputPinSet( pHandle->H3Port,
15
pHandle->H3Pin )
16
<< 1
17
| LL_GPIO_IsInputPinSet( pHandle->H1Port,
18
pHandle->H1Pin )
19
;
20
}
21
STM32 技术开发手册
www.ing10bbs.com
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 }
switch ( pHandle->HallState ) {
case STATE_5:
pHandle->_Super.hElAngle = ( int16_t )( pHandle->PhaseShift +
S16_60_PHASE_SHIFT / 2 );
break;
... /* 中间省略部分代码 */
break;
case STATE_4:
pHandle->_Super.hElAngle = ( int16_t )( pHandle->PhaseShift S16_60_PHASE_SHIFT / 2 );
break;
default:
pHandle->SensorIsReliable = false;
break;
}
pHandle->MeasuredElAngle = pHandle->_Super.hElAngle;
mc_irq_handler.c 文件内容
中断处理组件,这个文件里面的函数并没有被实际调用,省略不提。
mc_math.c 文件内容
电机库的数学函数,包括坐标变换函数,Clark 变换、Park 变换、Rev_Park 变
换还有正余弦查表函数。
代码 20-184 mc_math.c 私有宏定义
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
#define SIN_COS_TABLE {\
0x0000,0x00C9,0x0192,0x025B,0x0324,0x03ED,0x04B6,0x057F,\
/* 中间省略部分数据 */
0x7FD8,0x7FE1,0x7FE9,0x7FF0,0x7FF5,0x7FF9,0x7FFD,0x7FFE
}
#define SIN_MASK
0x0300u
#define U0_90
0x0200u // 第一象限扇区
#define U90_180
0x0300u
#define U180_270
0x0000u
#define U270_360
0x0100u
// 0.5773315 * 32767 = 0x49E6
#define divSQRT_3 (int32_t)0x49E6/* 1/sqrt(3) in q1.15 format=
0.5773315*/
π
宏定义里面包含有一个正弦表格𝑆𝐼𝑁_𝐶𝑂𝑆_𝑇𝐴𝐵𝐿𝐸,这个表格代表0~ 2 之间
的正弦数值,共 256 个数据点。将表格的每一个数据除以 32767 就是实际的正弦
数据。
SIN_MASK,U0_90,U90_180,U180_270,U270_360 则是用于判断当前的处
于哪个扇区。
divSQRT_3 只是一个系数,在注释里面有描述了计算公式。
STM32 技术开发手册
www.ing10bbs.com
MCM_Clarke()
Clake 变换,将三相定子电流𝐼𝑎 ,𝐼𝑏 ,𝐼𝑐 变换到𝛼β坐标系下,
图 20-23 Clarke 变换效果图
计算公式:
公式 20-18 Clarke 变换公式
𝑖𝛼 = 𝑖𝑎𝑠
𝑖𝛽 = −
𝑖𝑎𝑠 + 2𝑖𝑏𝑠
√3
代码方面其实没什么好讲的,因为都是按照公式来写的,只不过一些参数需
要在计算完之后为了防止数据溢出,作出了限制而已。
代码 20-185 Clarke 变换
00 Curr_Components MCM_Clarke( Curr_Components Curr_Input )
01 {
02
Curr_Components Curr_Output;
03
int32_t qIa_divSQRT3_tmp, qIb_divSQRT3_tmp ;
04
int32_t wIbeta_tmp;
05
int16_t hIbeta_tmp;
06
07
/* Iα = Ia */
08
Curr_Output.qI_Component1 = Curr_Input.qI_Component1;
09
10
qIa_divSQRT3_tmp = divSQRT_3 * ( int32_t )Curr_Input.qI_Component1;
11
qIb_divSQRT3_tmp = divSQRT_3 * ( int32_t )Curr_Input.qI_Component2;
12
13
/*Iβ = -(2*Ib + Ia)/sqrt(3) */
14
wIbeta_tmp = ( -( qIa_divSQRT3_tmp ) - ( qIb_divSQRT3_tmp ) 15
( qIb_divSQRT3_tmp ) ) >> 15;
16
17
/* 检查 Iβ 是否饱和 */
18
if ( wIbeta_tmp > INT16_MAX ) {
19
hIbeta_tmp = INT16_MAX;
20
} else if ( wIbeta_tmp < ( -32768 ) ) {
21
hIbeta_tmp = ( -32768 );
STM32 技术开发手册
www.ing10bbs.com
22
23
24
25
26
27
28
29
30
31 }
} else {
hIbeta_tmp = ( int16_t )( wIbeta_tmp );
}
Curr_Output.qI_Component2 = hIbeta_tmp;// Iβ
if ( Curr_Output.qI_Component2 == ( int16_t )( -32768 ) ) {
Curr_Output.qI_Component2 = -32767;
}
return ( Curr_Output );
MCM_Park()
Park 变换,将静止的𝑖𝛼 𝑖𝛽 变换到旋转的𝑑𝑞坐标系下。
图 20-24 Park 变换效果图
计算公式如下:
公式 20-19 Park 变换公式
𝑖𝑞𝑠 = 𝑖𝑎 cos 𝜃𝑟 − 𝑖𝛽 sin 𝜃𝑟
𝑖𝑑𝑠 = 𝑖𝑎 sin 𝜃𝑟 − 𝑖𝛽 cos 𝜃𝑟
代码 20-186 Park 变换
00 Curr_Components MCM_Park( Curr_Components Curr_Input, int16_t Theta )
01 {
02
Curr_Components Curr_Output;
03
int32_t qId_tmp_1, qId_tmp_2, qIq_tmp_1, qIq_tmp_2;
04
Trig_Components Local_Vector_Components;
05
int32_t wIqd_tmp;
06
int16_t hIqd_tmp;
07
08
Local_Vector_Components = MCM_Trig_Functions( Theta );
09
/* Iα * cos(θ) */
10
qIq_tmp_1 = Curr_Input.qI_Component1 * ( int32_t )
11
Local_Vector_Components.hCos;
12
/* Iβ * sin(θ) */
13
qIq_tmp_2 = Curr_Input.qI_Component2 * ( int32_t )
STM32 技术开发手册
www.ing10bbs.com
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
Local_Vector_Components.hSin;
/*Iq component in Q1.15 Format */
wIqd_tmp = ( qIq_tmp_1 - qIq_tmp_2 ) >> 15;
/* Check saturation of Iq */
if ( wIqd_tmp > INT16_MAX ) {
hIqd_tmp = INT16_MAX;
} else if ( wIqd_tmp < ( -32768 ) ) {
hIqd_tmp = ( -32768 );
} else {
hIqd_tmp = ( int16_t )( wIqd_tmp );
}
Curr_Output.qI_Component1 = hIqd_tmp;
if ( Curr_Output.qI_Component1 == ( int16_t )( -32768 ) ) {
Curr_Output.qI_Component1 = -32767;
}
/* Iα * sin(θ) */
qId_tmp_1 = Curr_Input.qI_Component1 * ( int32_t )
Local_Vector_Components.hSin;
/* Iβ *cos(θ) */
qId_tmp_2 = Curr_Input.qI_Component2 * ( int32_t )
Local_Vector_Components.hCos;
/*Id component in Q1.15 Format */
wIqd_tmp = ( qId_tmp_1 + qId_tmp_2 ) >> 15;
/* Check saturation of Id */
if ( wIqd_tmp > INT16_MAX ) {
hIqd_tmp = INT16_MAX;
} else if ( wIqd_tmp < ( -32768 ) ) {
hIqd_tmp = ( -32768 );
} else {
hIqd_tmp = ( int16_t )( wIqd_tmp );
}
Curr_Output.qI_Component2 = hIqd_tmp;
if ( Curr_Output.qI_Component2 == ( int16_t )( -32768 ) ) {
Curr_Output.qI_Component2 = -32767;
}
return ( Curr_Output );
52 }
MCM_Rev_Park()
Rev_Park 变换,就是 Park 变换的逆变换。在 Park 变换之后得到𝑖𝑞 、𝑖𝑑 ,通过
PID 控制计算得到电压分量𝑉𝑞 、𝑉𝑑 。再使用反 Park 变换就得到两向旋转正交坐标
系下的𝑉𝑎 、𝑉𝛽 .
计算公式如下:
公式 20-20 Rev_Park 变换公式
𝑉𝑎 = 𝑉𝑞𝑠 cos 𝜃𝑟 + 𝑉𝑑𝑠 sin 𝜃𝑟
𝑉𝛽 = −𝑉𝑞𝑠 sin 𝜃𝑟 + 𝑉𝑑𝑠 cos 𝜃𝑟
STM32 技术开发手册
www.ing10bbs.com
图 20-25 Rev_Park 变换示意图
MCM_Trig_Functions()
正余弦查表函数,输入参数是角度值,返回该角度的正余弦函数值。
代码 20-187 正余弦查表函数
00 Trig_Components MCM_Trig_Functions( int16_t hAngle )
01 {
02
int32_t shindex;
03
uint16_t uhindex;
04
Trig_Components Local_Components;
05
/* 10 bit index computation */
06
shindex = ( ( int32_t )32768 + ( int32_t )hAngle );
07
uhindex = ( uint16_t )shindex;
08
uhindex /= ( uint16_t )64;
09
10
switch ( ( uint16_t )( uhindex ) & SIN_MASK ) {
11
case U0_90:
12
Local_Components.hSin = hSin_Cos_Table[( uint8_t )( uhindex )];
13
Local_Components.hCos = hSin_Cos_Table[( uint8_t )( 0xFFu - (
14
uint8_t )( uhindex ) )];
15
break;
16
/* 中间省略部分代码 */
17
default:
18
break;
19
}
20
return ( Local_Components );
21 }
形参 hAngle 是角度值,单位是 s16degree,数值范围是 0~65536。首先划分
象限,一般情况下,角度值直接除以 90°,再对结果取整,就可以知道角度所在
象限,90°换算成 s16degree 就是 16384。
06
07
08
shindex = ( ( int32_t )32768 + ( int32_t )hAngle );
uhindex = ( uint16_t )shindex;
uhindex /= ( uint16_t )64;
STM32 技术开发手册
www.ing10bbs.com
首先是角度值加上 32768,角度偏移 180°,以第三象限 180°为起点,对
应着宏定义 U180_270 为 0 的情况。然后除以 64,缩小 64 倍,得到 10bit 的角
度值,然后再按位&SIN_MASK,得到的结果就是舍弃掉低 8 位,相当于除以 256,
64*256 =16384,所以实质上,就是角度值除以 16384,剩余的 2 位就是除以 16384
之后剩余的数据。同时因为角度偏移了 180°,所以第三象限为 0,第四象限为
1,第一象限为 2,第二象限为 3。
07
08
09
10
#define
#define
#define
#define
U0_90
U90_180
U180_270
U270_360
0x0200u // 第一象限扇区
0x0300u
0x0000u
0x0100u
这个时候的角度值已经是 10bit 的数字量,现在需要做的就是将完整的角度
π
数据缩小到0~ 2 以内。先举个例子:假设现在角度是 361°,现在需要转换成
0~360°就是 361%360 = 1,就是 1 圈之后多了 1°。那么转换成 0~90°,就是
361%90=1,所以对 90°求余即可。现在角度值已经缩小 64 倍,数值范围是 0~1024,
那么 90°就是 256。对 256 求余,只取低 8 位即可。也就是查表的时候对于角度
值只取低 8 位作为索引值。
mc_interface.c 文件内容
电机控制库的接口函数,用于实际电机提供的接口函数,使用这个文件下的
函数可以控制电机转速,扭矩等,同样也可以获取用来读取转速,𝐼𝑞 ,𝐼𝑑 等参数。
该文件用于对外提供接口函数,即是不使用也不影响电机库的使用。
MCI_Init()
初始化接口函数。将接口函数的控制句柄与电机库的控制句柄链接起来,并
且复位了控制指令和参数。
代码 20-188 MCI_Init()
00 void MCI_Init( MCI_Handle_t * pHandle, STM_Handle_t * pSTM,
01
SpeednTorqCtrl_Handle_t * pSTC, pFOCVars_t pFOCVars )
02 {
03
pHandle->pSTM = pSTM;
04
pHandle->pSTC = pSTC;
05
pHandle->pFOCVars = pFOCVars;
06
07
/* Buffer related initialization */
08
pHandle->lastCommand = MCI_NOCOMMANDSYET;
09
pHandle->hFinalSpeed = 0;
10
pHandle->hFinalTorque = 0;
STM32 技术开发手册
www.ing10bbs.com
11
12
pHandle->hDurationms = 0;
pHandle->CommandState = MCI_BUFFER_EMPTY;
13 }
MCI_ExecSpeedRamp()
在速度模式下执行 Ramp 动作,Ramp 翻译为爬坡,也就是电机匀加速/减速。
输入参数有两个,一个是 hFinalSpeed:最终的速度值;一个是 hDurationms:执
行时间,也就是从当前转速到目标速度所需的时间。
这个函数并不是立即执行指令动作,而是将指令存入指令缓存,然后标记需
要执行这个指令,并且配置了速度和时间。只有在电机运行的时候才会执行指令,
如果执行了这个指令,电机将会运行在速度模式。
代码 20-189 MCI_ExecSpeedRamp()
00 void MCI_ExecSpeedRamp( MCI_Handle_t * pHandle, int16_t hFinalSpeed,
01
uint16_t hDurationms )
02 {
03
pHandle->lastCommand = MCI_EXECSPEEDRAMP;
04
pHandle->hFinalSpeed = hFinalSpeed;
05
pHandle->hDurationms = hDurationms;
06
pHandle->CommandState = MCI_COMMAND_NOT_ALREADY_EXECUTED;
07
pHandle->LastModalitySetByUser = STC_SPEED_MODE;
08 }
MCI_ExecTorqueRamp()
在扭矩模式下执行 Ramp 动作。输入参数有两个,一个是 hFinalTorque:最
终的扭矩值;一个是 hDurationms:执行时间,也就是从当前转速到目标扭矩所
需的时间。
代码 20-190 MCI_ExecTorqueRamp()
00 void MCI_ExecTorqueRamp( MCI_Handle_t * pHandle, int16_t hFinalTorque,
01
uint16_t hDurationms )
02 {
03
pHandle->lastCommand = MCI_EXECTORQUERAMP;
04
pHandle->hFinalTorque = hFinalTorque;
05
pHandle->hDurationms = hDurationms;
06
pHandle->CommandState = MCI_COMMAND_NOT_ALREADY_EXECUTED;
07
pHandle->LastModalitySetByUser = STC_TORQUE_MODE;
08 }
MCI_SetCurrentReferences()
设置电机的目标值,设置𝐼𝑞 ,𝐼𝑑 。该函数与 MCI_ExecSpeedRamp()一样, 都
是设置了指令,但是不是立即执行,而是在运行过程中执行。会调用这个函数说
STM32 技术开发手册
www.ing10bbs.com
明是使用外部的控制指令设置电流参数,例如使用 workbench 来控制电机的时
候。
代码 20-191 MCI_SetCurrentReferences()
00 void MCI_SetCurrentReferences( MCI_Handle_t * pHandle, Curr_Components
01
Iqdref )
02 {
03
pHandle->lastCommand = MCI_SETCURRENTREFERENCES;
04
pHandle->Iqdref.qI_Component1 = Iqdref.qI_Component1;
05
pHandle->Iqdref.qI_Component2 = Iqdref.qI_Component2;
06
pHandle->CommandState = MCI_COMMAND_NOT_ALREADY_EXECUTED;
07
pHandle->LastModalitySetByUser = STC_TORQUE_MODE;
08 }
09
MCI_StartMotor()
将状态机从 IDLE 中转入 IDLE _START,启动电机转动。在启动之前,应该要
执行 MCI_ExecSpeedRamp、MCI_ExecTorqueRamp、MCI_SetCurrentReferences 中
的任意一个函数设定目标值。
代码 20-192 MCI_StartMotor()
00 bool MCI_StartMotor( MCI_Handle_t * pHandle )
01 {
02
bool RetVal = STM_NextState( pHandle->pSTM, IDLE_START );
03
04
if ( RetVal == true ) {
05
pHandle->CommandState = MCI_COMMAND_NOT_ALREADY_EXECUTED;
06
}
07
08
return RetVal;
09 }
MCI_StopMotor()
停止电机动作,直接修改状态机状态值,使电机停止。
代码 20-193 MCI_StopMotor()
00 bool MCI_StopMotor( MCI_Handle_t * pHandle )
01 {
02
return STM_NextState( pHandle->pSTM, ANY_STOP );
03 }
MCI_FaultAcknowledged()
错误应答,对于出现错误之后,需要人工确认,然后才能消除警告。对应着
workbench 上的 Fault Ack 按钮,用来消除警告。
STM32 技术开发手册
www.ing10bbs.com
代码 20-194 MCI_FaultAcknowledged()
00 bool MCI_FaultAcknowledged( MCI_Handle_t * pHandle )
01 {
02
return STM_FaultAcknowledged( pHandle->pSTM );
03 }
MCI_EncoderAlign()
编码器模式的对齐转子功能,对应 workbench 上的 Encoder Align 按钮,将状
态机切入对齐转子的状态。
代码 20-195 MCI_EncoderAlign()
00 bool MCI_EncoderAlign( MCI_Handle_t * pHandle )
01 {
02
return STM_NextState( pHandle->pSTM, IDLE_ALIGNMENT );
03 }
MCI_ExecBufferedCommands()
执行缓存中的指令,该函数在状态机中的 RUN、START_RUN 状态,才会被执
行。电机运行的时候才会被调用,也就是说所有的指令,都只能在电机转动的过
程中被执行。
代码 20-196 MCI_ExecBufferedCommands()
00 void MCI_ExecBufferedCommands( MCI_Handle_t * pHandle )
01 {
02
if ( pHandle != MC_NULL ) {
03
if ( pHandle->CommandState == MCI_COMMAND_NOT_ALREADY_EXECUTED
04
) {
05
bool commandHasBeenExecuted = false;
06
switch ( pHandle->lastCommand ) {
07
case MCI_EXECSPEEDRAMP: {
08
pHandle->pFOCVars->bDriveInput = INTERNAL;
09
STC_SetControlMode( pHandle->pSTC, STC_SPEED_MODE );
10
commandHasBeenExecuted = STC_ExecRamp( pHandle->pSTC,
11
pHandle->hFinalSpeed, pHandle12
>hDurationms );
13
}
14
break;
15
case MCI_EXECTORQUERAMP: {
16
pHandle->pFOCVars->bDriveInput = INTERNAL;
17
STC_SetControlMode( pHandle->pSTC, STC_TORQUE_MODE );
18
commandHasBeenExecuted = STC_ExecRamp( pHandle->pSTC,
19
pHandle->hFinalTorque,
20
pHandle->hDurationms );
21
}
22
break;
23
case MCI_SETCURRENTREFERENCES: {
24
pHandle->pFOCVars->bDriveInput = EXTERNAL;
25
pHandle->pFOCVars->Iqdref = pHandle->Iqdref;
26
commandHasBeenExecuted = true;
27
}
28
break;
29
default:
STM32 技术开发手册
www.ing10bbs.com
30
31
32
33
34
35
36
37
38
39
40 }
break;
}
if ( commandHasBeenExecuted ) {
pHandle->CommandState = MCI_COMMAND_EXECUTED_SUCCESFULLY;
} else {
pHandle->CommandState = MCI_COMMAND_EXECUTED_UNSUCCESFULLY;
}
}
}
MCI_IsCommandAcknowledged()
查询缓存中是否有指令执行,无论执行还是没有执行完都清空缓存。
代码 20-197 MCI_IsCommandAcknowledged()
00 MCI_CommandState_t MCI_IsCommandAcknowledged( MCI_Handle_t * pHandle )
01 {
02
MCI_CommandState_t retVal = pHandle->CommandState;
03
04
if ( ( retVal == MCI_COMMAND_EXECUTED_SUCCESFULLY ) |
05
( retVal == MCI_COMMAND_EXECUTED_UNSUCCESFULLY ) ) {
06
pHandle->CommandState = MCI_BUFFER_EMPTY;
07
}
08
return retVal;
09 }
10
MCI_GetSTMState()
获取状态机的状态值。
代码 20-198 MCI_GetSTMState()
00 State_t MCI_GetSTMState( MCI_Handle_t * pHandle )
01 {
02
return STM_GetState( pHandle->pSTM );
03 }
04
MCI_GetOccurredFaults()
获取当前出现的错误状态。
代码 20-199 MCI_GetOccurredFaults()
00 uint16_t MCI_GetOccurredFaults( MCI_Handle_t * pHandle )
01 {
02
return ( uint16_t )( STM_GetFaultState( pHandle->pSTM ) );
03 }
MCI_GetCurrentFaults()
获取当前出现的错误状态。
STM32 技术开发手册
www.ing10bbs.com
代码 20-200 MCI_GetCurrentFaults()
00 uint16_t MCI_GetCurrentFaults( MCI_Handle_t * pHandle )
01 {
02
return ( uint16_t )( STM_GetFaultState( pHandle->pSTM ) >> 16 );
03 }
MCI_GetControlMode()
获取当前的运动控制模式,STC_TORQUE_MODE 或者是 STC_SPEED_MODE。
代码 20-201 MCI_GetControlMode()
00 STC_Modality_t MCI_GetControlMode( MCI_Handle_t * pHandle )
01 {
02
return pHandle->LastModalitySetByUser;
03 }
MCI_GetImposedMotorDirection()
获取当前电机转动的方向。
代码 20-202 MCI_GetImposedMotorDirection()
00 int16_t MCI_GetImposedMotorDirection( MCI_Handle_t * pHandle )
01 {
02
int16_t retVal = 1;
03
04
switch ( pHandle->lastCommand ) {
05
case MCI_EXECSPEEDRAMP:
06
if ( pHandle->hFinalSpeed < 0 ) {
07
retVal = -1;
08
}
09
break;
10
case MCI_EXECTORQUERAMP:
11
if ( pHandle->hFinalTorque < 0 ) {
12
retVal = -1;
13
}
14
break;
15
case MCI_SETCURRENTREFERENCES:
16
if ( pHandle->Iqdref.qI_Component1 < 0 ) {
17
retVal = -1;
18
}
19
break;
20
default:
21
break;
22
}
23
return retVal;
24 }
MCI_GetLastRampFinalSpeed()
获取速度模式 Ramp 的目标速度。
代码 20-203 MCI_GetLastRampFinalSpeed()
00 int16_t MCI_GetLastRampFinalSpeed( MCI_Handle_t * pHandle )
01 {
02
int16_t hRetVal = 0;
STM32 技术开发手册
www.ing10bbs.com
03
04
05
06
07
08
09 }
/* Examine the last buffered commands */
if ( pHandle->lastCommand == MCI_EXECSPEEDRAMP ) {
hRetVal = pHandle->hFinalSpeed;
}
return hRetVal;
MCI_RampCompleted()
判断 Ramp 指令是否执行完毕。
代码 20-204 MCI_RampCompleted()
00 bool MCI_RampCompleted( MCI_Handle_t * pHandle )
01 {
02
bool retVal = false;
03
04
if ( ( STM_GetState( pHandle->pSTM ) ) == RUN ) {
05
retVal = STC_RampCompleted( pHandle->pSTC );
06
}
07
08
return retVal;
09 }
MCI_StopSpeedRamp()
停止执行 Ramp 指令。
代码 20-205 MCI_StopSpeedRamp()
00 bool MCI_StopSpeedRamp( MCI_Handle_t * pHandle )
01 {
02
return STC_StopSpeedRamp( pHandle->pSTC );
03 }
MCI_GetSpdSensorReliability()
检查当前的速度传感器是否可靠,当传感器的速度超出了最低速和最高速的
时候就是不可靠。
代码 20-206 MCI_GetSpdSensorReliability()
00 bool MCI_GetSpdSensorReliability( MCI_Handle_t * pHandle )
01 {
02
SpeednPosFdbk_Handle_t * SpeedSensor = STC_GetSpeedSensor( pHandle03
>pSTC );
04
05
return ( SPD_Check( SpeedSensor ) );
06 }
MCI_GetAvrgMecSpeed01Hz()
获取电机的平均转速,单位是 0.1Hz。
STM32 技术开发手册
www.ing10bbs.com
代码 20-207 MCI_GetAvrgMecSpeed01Hz()
00 int16_t MCI_GetAvrgMecSpeed01Hz( MCI_Handle_t * pHandle )
01 {
02
SpeednPosFdbk_Handle_t * SpeedSensor = STC_GetSpeedSensor( pHandle03
>pSTC );
04
05
return ( SPD_GetAvrgMecSpeed01Hz( SpeedSensor ) );
06 }
MCI_GetMecSpeedRef01Hz()
获取电机的目标转速,单位是 0.1Hz。
代码 20-208 MCI_GetMecSpeedRef01Hz()
00 int16_t MCI_GetMecSpeedRef01Hz( MCI_Handle_t * pHandle )
01 {
02
return ( STC_GetMecSpeedRef01Hz( pHandle->pSTC ) );
03 }
MCI_GetIab()
获取电机电流𝐼𝑞 ,𝐼𝑑 。
代码 20-209 MCI_GetIab()
00 Curr_Components MCI_GetIab( MCI_Handle_t * pHandle )
01 {
02
return ( pHandle->pFOCVars->Iab );
03 }
MCI_GetIalphabeta()
获取电机电流𝐼𝛼 ,𝐼𝛽 。
代码 20-210 MCI_GetIalphabeta()
00 Curr_Components MCI_GetIalphabeta( MCI_Handle_t * pHandle )
01 {
02
return ( pHandle->pFOCVars->Ialphabeta );
03 }
MCI_GetIqd()
获取电机电流𝐼𝑞 ,𝐼𝑑 。
代码 20-211 MCI_GetIqd()
00 Curr_Components MCI_GetIqd( MCI_Handle_t * pHandle )
01 {
02
return ( pHandle->pFOCVars->Iqd );
03 }
STM32 技术开发手册
www.ing10bbs.com
MCI_GetIqdHF()
返回高频注入模式下的𝐼𝑞 ,𝐼𝑑 。
代码 20-212 MCI_GetIqdHF()
00 Curr_Components MCI_GetIqdHF( MCI_Handle_t * pHandle )
01 {
02
return ( pHandle->pFOCVars->IqdHF );
03 }
MCI_GetIqdref()
获取电机电流𝐼𝑞 ,𝐼𝑑 的目标值。
代码 20-213 MCI_GetIqdref()
00 Curr_Components MCI_GetIqdref( MCI_Handle_t * pHandle )
01 {
02
return ( pHandle->pFOCVars->Iqdref );
03 }
MCI_GetVqd()
获取电机电压𝑉𝑞 ,𝑉𝑑 。
代码 20-214 MCI_GetVqd()
00 Volt_Components MCI_GetVqd( MCI_Handle_t * pHandle )
01 {
02
return ( pHandle->pFOCVars->Vqd );
03 }
MCI_GetValphabeta()
获取电机电压𝑉𝛼 ,𝑉𝛽 。
代码 20-215 MCI_GetValphabeta()
00 Volt_Components MCI_GetValphabeta( MCI_Handle_t * pHandle )
01 {
02
return ( pHandle->pFOCVars->Valphabeta );
03 }
MCI_GetElAngledpp()
获取电角度,单位是 dpp,每 FOC 周期的电角度变化量
代码 20-216 MCI_GetElAngledpp()
00 int16_t MCI_GetElAngledpp( MCI_Handle_t * pHandle )
01 {
02
return ( pHandle->pFOCVars->hElAngle );
03 }
STM32 技术开发手册
www.ing10bbs.com
MCI_GetTeref()
获取扭矩目标值。
代码 20-217 MCI_GetTeref()
00 int16_t MCI_GetTeref( MCI_Handle_t * pHandle )
01 {
02
return ( pHandle->pFOCVars->hTeref );
03 }
MCI_GetPhaseCurrentAmplitude()
获取相电流振幅。使用 s16A 格式表示,计算公式如下:
公式 20-21 计算相电流幅值
𝐼𝑠16𝐴 = √𝐼𝛼2 + 𝐼𝛽2
𝐼𝐴𝑚𝑝 =
𝐼𝑠16𝐴 ∗ 𝑉𝑑𝑑
65536 ∗ 𝑅𝑠ℎ𝑢𝑛𝑡 ∗ 𝐴𝑜𝑝
𝐼𝐴𝑚𝑝 是安培为单位的电流值,𝐴𝑜𝑝 则是运放的放大倍数.
代码 20-218 MCI_GetPhaseCurrentAmplitude()
00 int16_t MCI_GetPhaseCurrentAmplitude( MCI_Handle_t * pHandle )
01 {
02
Curr_Components Local_Curr;
03
int32_t wAux1, wAux2;
04
05
Local_Curr = pHandle->pFOCVars->Ialphabeta;
06
wAux1 = ( int32_t )( Local_Curr.qI_Component1 ) * Local_Curr.
07
qI_Component1;
08
wAux2 = ( int32_t )( Local_Curr.qI_Component2 ) * Local_Curr.
09
qI_Component2;
10
11
wAux1 += wAux2;
12
wAux1 = MCM_Sqrt( wAux1 );
13
14
if ( wAux1 > INT16_MAX ) {
15
wAux1 = ( int32_t ) INT16_MAX;
16
}
17
18
return ( ( int16_t )wAux1 );
19 }
MCI_GetPhaseVoltageAmplitude()
获取相电压振幅。使用 s16V 格式表示,计算公式如下:
公式 20-22 计算相电压幅值
𝑉𝑠16𝑉 = √𝑉𝛼2 + 𝑉𝛽2
𝑉𝑉 =
𝑉𝑠16𝑉 ∗ 𝑉𝑏𝑢𝑠
32767√3
STM32 技术开发手册
www.ing10bbs.com
𝑉𝑉 是以(V)为单位的电压值,𝑉𝑏𝑢𝑠 是总线电压。
代码 20-219 MCI_GetPhaseVoltageAmplitude()
00 int16_t MCI_GetPhaseVoltageAmplitude( MCI_Handle_t * pHandle )
01 {
02
Volt_Components Local_Voltage;
03
int32_t wAux1, wAux2;
04
05
Local_Voltage = pHandle->pFOCVars->Valphabeta;
06
wAux1 = ( int32_t )( Local_Voltage.qV_Component1 ) * Local_Voltage.
07
qV_Component1;
08
wAux2 = ( int32_t )( Local_Voltage.qV_Component2 ) * Local_Voltage.
09
qV_Component2;
10
11
wAux1 += wAux2;
12
wAux1 = MCM_Sqrt( wAux1 );
13
14
if ( wAux1 > INT16_MAX ) {
15
wAux1 = ( int32_t ) INT16_MAX;
16
}
17
18
return ( ( int16_t ) wAux1 );
19 }
MCI_SetIdref()
设置𝐼𝑑 目标值。
代码 20-220 MCI_SetIdref()
00 void MCI_SetIdref( MCI_Handle_t * pHandle, int16_t hNewIdref )
01 {
02
pHandle->pFOCVars->Iqdref.qI_Component2 = hNewIdref;
03
pHandle->pFOCVars->UserIdref = hNewIdref;
04 }
MCI_Clear_Iqdref()
复位𝐼𝑑 目标值。
代码 20-221 MCI_Clear_Iqdref()
00 void MCI_Clear_Iqdref( MCI_Handle_t * pHandle )
01 {
02
pHandle->pFOCVars->Iqdref = STC_GetDefaultIqdref( pHandle->pSTC );
03 }
motor_power_measurement.c 文件内容
该文件主要用于计算电机功率的,在运行的时候实时监控电机。
MPM_Clear()
复位缓存数据。
STM32 技术开发手册
www.ing10bbs.com
代码 20-222 MPM_Clear()
00 void MPM_Clear( MotorPowMeas_Handle_t * pHandle )
01 {
02
uint16_t i;
03
for ( i = 0u; i < MPM_BUFFER_LENGHT; i++ ) {
04
pHandle->hMeasBuffer[i] = 0;
05
}
06
pHandle->hNextMeasBufferIndex = 0u;
07
pHandle->hLastMeasBufferIndex = 0u;
08 }
MPM_CalcElMotorPower()
计算电机的功率平均值。单位是 W.
代码 20-223 MPM_CalcElMotorPower()
00 int16_t MPM_CalcElMotorPower( MotorPowMeas_Handle_t * pHandle, int16_t
01
CurrentMotorPower )
02 {
03
uint16_t i;
04
int32_t wAux = 0;
05
06
/* Store the measured values in the buffer.*/
07
pHandle->hMeasBuffer[pHandle->hNextMeasBufferIndex]=CurrentMotorPower;
09
pHandle->hLastMeasBufferIndex = pHandle->hNextMeasBufferIndex;
08
pHandle->hNextMeasBufferIndex++;
09
if ( pHandle->hNextMeasBufferIndex >= MPM_BUFFER_LENGHT ) {
10
pHandle->hNextMeasBufferIndex = 0u;
11
}
12
/* Compute the average measured motor power */
13
for ( i = 0u; i < MPM_BUFFER_LENGHT; i++ ) {
14
wAux += ( int32_t )( pHandle->hMeasBuffer[i] );
15
}
16
wAux /= ( int32_t )MPM_BUFFER_LENGHT;
17
pHandle->hAvrgElMotorPowerW = ( int16_t )( wAux );
18
/* Return the last measured motor power */
19
return CurrentMotorPower;
20 }
MPM_GetElMotorPowerW()
读取最新的测量值。单位是 W。
代码 20-224 MPM_GetElMotorPowerW
00 int16_t MPM_GetElMotorPowerW( MotorPowMeas_Handle_t * pHandle )
01 {
02
return ( pHandle->hMeasBuffer[pHandle->hLastMeasBufferIndex] );
03 }
MPM_GetAvrgElMotorPowerW()
读取测量值的平均值。
代码 20-225 MPM_GetAvrgElMotorPowerW
00 int16_t MPM_GetAvrgElMotorPowerW( MotorPowMeas_Handle_t * pHandle )
STM32 技术开发手册
www.ing10bbs.com
01 {
02
03 }
return ( pHandle->hAvrgElMotorPowerW );
ntc_temperatrure_sensor.c 文件内容
NTC 热敏电阻器,温度传感器组件,这个文件主要处理温度传感器的功能。
热敏电阻计算公式如下:
公式 20-23 温度传感器计算公式
𝑉𝑜𝑢𝑡 = 𝑉0 +
𝑑𝑉
× (𝑇 − 𝑇0 )
𝑑𝑇
这是一个通用的计算公式,使用 NTC 热敏电阻都是使用 ADC 采集热敏电阻
的电压,再从电压计算出温度值,首先测量出初始状态的电压值作为起始值,然
𝑑𝑉
后再测量出温升电压(温度升高引起的电压变化量𝑑𝑇 ),根据这两个条件就可以
知道两个时间点的温差。这两个条件是需要提前测量好的,另外还要假定初始温
度值,这些是可以在 workbench 上设定的。
图 20-26 温度参数设置
NTC_SetFaultState()
设置温度传感器的错误状态,如果是过热,则标记 MC_OVER_TEMP。
代码 20-226 NTC_SetFaultState()
00 uint16_t NTC_SetFaultState( NTC_Handle_t * pHandle )
01 {
02
uint16_t hFault;
03
04
if ( pHandle->hAvTemp_d > pHandle->hOverTempThreshold ) {
05
hFault = MC_OVER_TEMP;
06
} else if ( pHandle->hAvTemp_d < pHandle->hOverTempDeactThreshold )
STM32 技术开发手册
www.ing10bbs.com
07
08
09
10
11
12
13 }
{
hFault = MC_NO_ERROR;
} else {
hFault = pHandle->hFaultState;
}
return hFault;
NTC_Init()
初始化温度传感器,主要是注册 ADC 规则转换通道,FOC 的规则采样是统一
由 RCM 组件管理(Regular Conversion Manager),首先需要注册一个规则通道。
代码 20-227 NTC_Init()
00 void NTC_Init( NTC_Handle_t * pHandle )
01 {
02
if ( pHandle->bSensorType == REAL_SENSOR ) {
03
/* Need to be register with RegularConvManager */
04
pHandle->convHandle = RCM_RegisterRegConv(&pHandle->
05
TempRegConv);
06
NTC_Clear( pHandle );
07
} else { /* case VIRTUAL_SENSOR */
08
pHandle->hFaultState = MC_NO_ERROR;
09
pHandle->hAvTemp_d = pHandle->hExpectedTemp_d;
10
}
11 }
另外可以看到,还有一个虚拟传感器,如果是使用虚拟传感器,则每次读取
温度值都会返回一个固定的数值,实际上没什么作用。
NTC_CalcAvTemp()
计算平均温度。这里使用了低通滤波算法,使用软件编程实现普通硬件 RC
低通滤波器的功能。一阶低通滤波的算法公式:
公式 20-24 一阶低通滤波算法
𝑌(𝑛) = 𝑎𝑋(𝑛) + (1 − 𝑎)𝑌(𝑛 − 1)
𝑋(𝑛)是第𝑛次的采样值,𝑌(𝑛 − 1)是𝑛 − 1次的滤波结果,𝑌(𝑛)是第𝑛次的滤
波结果。𝑎是滤波系数,数值越小,滤波结果越平稳,但是灵敏度越低。
在这里 hLowPassFilterBW 就是滤波系数,hAvTemp_d 就是上一次和这一次的
滤波结果,wtemp 只是一个临时变量。
代码 20-228 NTC_CalcAvTemp
00 uint16_t NTC_CalcAvTemp( NTC_Handle_t * pHandle )
STM32 技术开发手册
www.ing10bbs.com
01 {
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19 }
uint32_t wtemp;
uint16_t hAux;
if ( pHandle->bSensorType == REAL_SENSOR ) {
hAux = RCM_ExecRegularConv(pHandle->convHandle);
if ( hAux != 0xFFFFu ) {
wtemp = ( uint32_t )( pHandle->hLowPassFilterBW ) - 1u;
wtemp *= ( uint32_t ) ( pHandle->hAvTemp_d );
wtemp += hAux;
wtemp /= ( uint32_t )( pHandle->hLowPassFilterBW );
pHandle->hAvTemp_d = ( uint16_t ) wtemp;
}
pHandle->hFaultState = NTC_SetFaultState( pHandle );
} else { /* case VIRTUAL_SENSOR */
pHandle->hFaultState = MC_NO_ERROR;
}
return ( pHandle->hFaultState );
NTC_GetAvTemp_d()
用于获取温度值。单位是 u16Celsius。
代码 20-229 NTC_GetAvTemp_d()
00 uint16_t NTC_GetAvTemp_d( NTC_Handle_t * pHandle )
01 {
02
return ( pHandle->hAvTemp_d );
03 }
NTC_GetAvTemp_C()
用于获取温度值。单位是摄氏度(℃)。下面代码就是公式 20-23 的逆运算,
得到的是实际的温度值。
代码 20-230 NTC_GetAvTemp_C()
00 int16_t NTC_GetAvTemp_C( NTC_Handle_t * pHandle )
01 {
02
int32_t wTemp;
03
04
if ( pHandle->bSensorType == REAL_SENSOR ) {
05
wTemp = ( int32_t )( pHandle->hAvTemp_d );
06
wTemp -= ( int32_t )( pHandle->wV0 );
07
wTemp *= pHandle->hSensitivity;
08
wTemp = wTemp / 65536 + ( int32_t )( pHandle->hT0 );
09
} else {
10
wTemp = pHandle->hExpectedTemp_C;
11
}
12
return ( ( int16_t )wTemp );
13 }
NTC_CheckTemp ()
检查温度传感器的状态。
STM32 技术开发手册
www.ing10bbs.com
代码 20-231 NTC_CheckTemp()
00 uint16_t NTC_CheckTemp( NTC_Handle_t * pHandle )
01 {
02
return ( pHandle->hFaultState );
03 }
open_loop.c 文件内容
这是 FOC 开环控制组件,主要是设定了电流环的目标值。所谓的开环实际上
也是闭环控制,只不过反馈值是有控制系统给定而不是通过实际测量得到的。在
这个文件里面的函数没有被调用,因为 HALL 传感器不需要开环控制。就此略过。
pid_regulator.c 文件内容
PID 算法控制组件,其实这里只是 PI 控制器,不涉及微分项。PID 控制算法
如下:
公式 20-25 PID 控制算法
𝑡
𝑢(𝑡) = 𝐾𝑝 𝑒(𝑡) + 𝐾𝑖 ∫ 𝑒(𝜏)𝑑𝜏 + 𝐾𝑑
0
𝑑𝑒(𝑡)
𝑑𝑡
其中的微分项是可以不使用的。数字离散化之后就是:
𝜏
𝑢(𝑡) = 𝐾𝑝 𝑒(𝑡) + 𝐾𝑖 ∑ 𝑒(𝜏) + 𝐾𝑑 (𝑒(𝑡) − 𝑒(𝑡 − 1))
0
𝐾𝑝 、𝐾𝑖 、𝐾𝑑 就是常说的 PID 参数,每个参数都是由增益和除数组成,使用整
数除法运算代替了浮点运算,并且可以控制除数为 2 的 n 次方,就可以使用右移
n 位来代替除法。
公式 20-26 PID 参数
𝐾𝑝𝑔
𝐾𝑝𝑑
𝐾𝑖𝑔
𝐾𝑖 =
𝐾𝑖𝑑
𝐾𝑑𝑔
𝐾𝑑 =
𝐾𝑑𝑑
𝐾𝑝 =
PID 算法的每一个参数都是可以独立设置。
PID_HandleInit()
该函数只是将 PID 参数初始化为默认值。
STM32 技术开发手册
www.ing10bbs.com
代码 20-232 PID 参数初始化
00 void PID_HandleInit( PID_Handle_t * pHandle )
01 {
02
pHandle->hKpGain = pHandle->hDefKpGain;
03
pHandle->hKiGain = pHandle->hDefKiGain;
04
pHandle->hKdGain = pHandle->hDefKdGain;
05
pHandle->wIntegralTerm = 0x00000000UL;
06
pHandle->wPrevProcessVarError = 0x00000000UL;
07 }
PID_SetKP()
该函数可以独立设置比例增益。
代码 20-233 单独设置𝑲𝒑 增益
00 void PID_SetKP( PID_Handle_t * pHandle, int16_t hKpGain )
01 {
02
pHandle->hKpGain = hKpGain;
03 }
PID_SetKI()
该函数可以独立设置积分增益。
代码 20-234 单独设置𝑲𝒊 增益
00 void PID_SetKI( PID_Handle_t * pHandle, int16_t hKiGain )
01 {
02
pHandle->hKiGain = hKiGain;
03 }
PID_GetKP()
该函数可以读取𝐾𝑝 增益。
代码 20-235 读取𝑲𝒑 增益
00 int16_t PID_GetKP( PID_Handle_t * pHandle )
01 {
02
return ( pHandle->hKpGain );
03 }
PID_GetKI()
该函数可以读取𝐾𝑖 增益。
代码 20-236 读取𝑲𝒊 增益
00 int16_t PID_GetKI( PID_Handle_t * pHandle )
01 {
02
return ( pHandle->hKiGain );
03 }
STM32 技术开发手册
www.ing10bbs.com
PID_GetDefaultKP()
读取默认的𝐾𝑝 增益
代码 20-237 读取默认的𝑲𝒑 增益
00 int16_t PID_GetDefaultKP( PID_Handle_t * pHandle )
01 {
02
return ( pHandle->hDefKpGain );
03 }
PID_GetDefaultKI()
该函数读取默认的𝐾𝒊 增益。
代码 20-238 读取默认的𝑲𝒊 增益
00 int16_t PID_GetDefaultKI( PID_Handle_t * pHandle )
01 {
02
return ( pHandle->hDefKiGain );
03 }
PID_SetIntegralTerm()
𝑡
设置积分项计算结果,这是积分项的累加和𝐾𝑖 ∫0 𝑒(𝜏)𝑑𝜏。
代码 20-239 单独设置积分项的累加和
00 void PID_SetIntegralTerm( PID_Handle_t * pHandle, int32_t
01
wIntegralTermValue )
02 {
03
pHandle->wIntegralTerm = wIntegralTermValue;
04
return;
05 }
PID_GetKPDivisor()
该函数可以单独读取𝐾𝑝 的分母项。
代码 20-240 单独读取𝑲𝒑 的分母
00 uint16_t PID_GetKPDivisor( PID_Handle_t * pHandle )
01 {
02
return ( pHandle->hKpDivisor );
03 }
PID_SetKPDivisorPOW2()
以指数的形式配置𝐾𝑝 的分母项。输入参数为 2 的 n 次方。
STM32 技术开发手册
www.ing10bbs.com
代码 20-241 配置𝑲𝒑 的分母项
00 void PID_SetKPDivisorPOW2( PID_Handle_t * pHandle, uint16_t
01
hKpDivisorPOW2 )
02 {
03
pHandle->hKpDivisorPOW2 = hKpDivisorPOW2;
04
pHandle->hKpDivisor = ( ( uint16_t )( 1u ) << hKpDivisorPOW2 );
05 }
PID_GetKIDivisor
该函数用于读取𝐾𝑖 的分母项。
代码 20-242 读取𝑲𝒊 的分母
00 uint16_t PID_GetKIDivisor( PID_Handle_t * pHandle )
01 {
02
return ( pHandle->hKiDivisor );
03 }
PID_SetKIDivisorPOW2
以指数的形式配置𝐾𝑖 的分母项。输入参数为 2 的 n 次方。同时修改积分上限
和积分下限。
代码 20-243 配置𝑲𝒊 的分母项
00 void PID_SetKIDivisorPOW2( PID_Handle_t * pHandle, uint16_t
01
hKiDivisorPOW2 )
02 {
03
int32_t wKiDiv = ( ( int32_t )( 1u ) << hKiDivisorPOW2 );
04
pHandle->hKiDivisorPOW2 = hKiDivisorPOW2;
05
pHandle->hKiDivisor = ( uint16_t )( wKiDiv );
06
PID_SetUpperIntegralTermLimit( pHandle, ( int32_t )INT16_MAX *
07
wKiDiv );
08
PID_SetLowerIntegralTermLimit( pHandle, ( int32_t ) - INT16_MAX *
09
wKiDiv );
10 }
PID_SetLowerIntegralTermLimit()
该函数用于设置积分下限。积分上限和积分下限的作用在于防止积分项过饱
和。
代码 20-244 设置积分下限
00 void PID_SetLowerIntegralTermLimit( PID_Handle_t * pHandle, int32_t
01
wLowerLimit )
02 {
03
pHandle->wLowerIntegralLimit = wLowerLimit;
04 }
STM32 技术开发手册
www.ing10bbs.com
PID_SetUpperIntegralTermLimit()
该函数用于设置积分上限。积分上限和积分下限的作用在于防止积分项过饱
和。
代码 20-245 设置积分上限
00 void PID_SetUpperIntegralTermLimit( PID_Handle_t * pHandle, int32_t
01
wUpperLimit )
02 {
03
pHandle->wUpperIntegralLimit = wUpperLimit;
04 }
PID_SetLowerOutputLimit()
该函数用于设置输出的下限。PID 输出不能无限增长,只能在计算完之后限
制输出。
代码 20-246 设置输出下限
00 void PID_SetLowerOutputLimit( PID_Handle_t * pHandle, int16_t
01
hLowerLimit )
02 {
03
pHandle->hLowerOutputLimit = hLowerLimit;
04 }
05
PID_SetUpperOutputLimit()
该函数用于设置输出的上限。PID 输出不能无限增长,只能在计算完之后限
制输出。
代码 20-247 设置输出上限
00 void PID_SetUpperOutputLimit( PID_Handle_t * pHandle, int16_t
01
hUpperLimit )
02 {
03
pHandle->hUpperOutputLimit = hUpperLimit;
04 }
PID_SetPrevError()
该函数用于设置前一次采样的误差项,用于计算。
代码 20-248 设置前一次的误差项
00 void PID_SetPrevError( PID_Handle_t * pHandle, int32_t
01
wPrevProcessVarError )
02 {
03
pHandle->wPrevProcessVarError = wPrevProcessVarError;
04
return;
05 }
STM32 技术开发手册
www.ing10bbs.com
PID_SetKD()
该函数用于设置𝑲𝒅 增益。
代码 20-249 单独设置𝑲𝒅 增益
00 void PID_SetKD( PID_Handle_t * pHandle, int16_t hKdGain )
01 {
02
pHandle->hKdGain = hKdGain;
03 }
PID_GetKD()
该函数用于读取𝐾𝑑 增益。
代码 20-250 读取𝑲𝒅 增益
00 int16_t PID_GetKD( PID_Handle_t * pHandle )
01 {
02
return pHandle->hKdGain;
03 }
PID_GetKDDivisor()
该函数用于读取𝐾𝑑 的分母。
代码 20-251 读取𝑲𝒅 的分母
00 uint16_t PID_GetKDDivisor( PID_Handle_t * pHandle )
01 {
02
return ( pHandle->hKdDivisor );
03 }
PI_Controller()
PI 控制器。主要使用了比例项和积分项,并且对积分项作出了积分限制。输
入参数是当前的误差和 PID 控制句柄,PID 控制句柄可以是不同的控制对象,包
括速度,电流等。
代码 20-252 PI 控制器
00 int16_t PI_Controller( PID_Handle_t * pHandle, int32_t
01
wProcessVarError )
02 {
03
int32_t wProportional_Term, wIntegral_Term, wOutput_32,
04
wIntegral_sum_temp;
05
int32_t wDischarge = 0;
06
int16_t hUpperOutputLimit = pHandle->hUpperOutputLimit;
07
int16_t hLowerOutputLimit = pHandle->hLowerOutputLimit;
08
09
/* 比例项*/
10
wProportional_Term = pHandle->hKpGain * wProcessVarError;
11
12
/* 积分项*/
13
if ( pHandle->hKiGain == 0 ) {
STM32 技术开发手册
www.ing10bbs.com
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 }
pHandle->wIntegralTerm = 0;
} else {
wIntegral_Term = pHandle->hKiGain * wProcessVarError;
wIntegral_sum_temp = pHandle->wIntegralTerm + wIntegral_Term;
/* 判断数据是否溢出,对数据边界做限制 */
if ( wIntegral_sum_temp < 0 ) {
if ( pHandle->wIntegralTerm > 0 ) {
if ( wIntegral_Term > 0 ) {
wIntegral_sum_temp = INT32_MAX;
}
}
} else {
if ( pHandle->wIntegralTerm < 0 ) {
if ( wIntegral_Term < 0 ) {
wIntegral_sum_temp = -INT32_MAX;
}
}
}
/* 积分限制,防止过饱和 */
if ( wIntegral_sum_temp > pHandle->wUpperIntegralLimit ) {
pHandle->wIntegralTerm = pHandle->wUpperIntegralLimit;
} else if ( wIntegral_sum_temp < pHandle->wLowerIntegralLimit )
{
pHandle->wIntegralTerm = pHandle->wLowerIntegralLimit;
} else {
pHandle->wIntegralTerm = wIntegral_sum_temp;
}
}
/* 除以各项各自的分母 */
wOutput_32 = ( wProportional_Term >> pHandle->hKpDivisorPOW2 ) + (
pHandle->wIntegralTerm >> pHandle->hKiDivisorPOW2 );
/* 输出限制 */
if ( wOutput_32 > hUpperOutputLimit ) {
wDischarge = hUpperOutputLimit - wOutput_32;
wOutput_32 = hUpperOutputLimit;
} else if ( wOutput_32 < hLowerOutputLimit ) {
wDischarge = hLowerOutputLimit - wOutput_32;
wOutput_32 = hLowerOutputLimit;
}
pHandle->wIntegralTerm += wDischarge;// 累加代替积分
return ( ( int16_t )( wOutput_32 ) );
PID_Controller()
PID 控制器。实际上并没有使用到这个函数,全部都是使用 PI 控制器,这个
函数主要是先计算出微分项,然后在调用 PI 控制器,再将两个结果相加,就得
到最终的输出。
代码 20-253
00 int16_t PID_Controller( PID_Handle_t * pHandle, int32_t
01
wProcessVarError )
02 {
03
int32_t wDifferential_Term;
04
int32_t wDeltaError;
05
int32_t wTemp_output;
06
07
if ( pHandle->hKdGain != 0 ) { /* derivative terms not used */
08
wDeltaError = wProcessVarError - pHandle->wPrevProcessVarError;
09
wDifferential_Term = pHandle->hKdGain * wDeltaError;
10
STM32 技术开发手册
www.ing10bbs.com
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 }
wDifferential_Term >>= pHandle->hKdDivisorPOW2;
pHandle->wPrevProcessVarError = wProcessVarError;
wTemp_output = PI_Controller( pHandle, wProcessVarError ) +
wDifferential_Term;
if ( wTemp_output > pHandle->hUpperOutputLimit ) {
wTemp_output = pHandle->hUpperOutputLimit;
} else if ( wTemp_output < pHandle->hLowerOutputLimit ) {
wTemp_output = pHandle->hLowerOutputLimit;
}
} else {
wTemp_output = PI_Controller( pHandle, wProcessVarError );
}
return ( ( int16_t ) wTemp_output );
pqd_motor_power_measurement.c 文件内容
这个文件里面只有一个函数,就是计算电机功率,被 FOC 电机库周期性的调
用,实时计算电机功率。然后再调用 MPM_CalcElMotorPower 计算平均值。
代码 20-254 计算电机功率
00 void PQD_CalcElMotorPower( PQD_MotorPowMeas_Handle_t * pHandle )
01 {
02
int32_t wAux, wAux2, wAux3;
03
Curr_Components Iqd = pHandle->pFOCVars->Iqd;
04
Volt_Components Vqd = pHandle->pFOCVars->Vqd;
05
wAux = ( ( int32_t )Iqd.qI_Component1 * ( int32_t )Vqd.
06
qV_Component1 ) +
07
( ( int32_t )Iqd.qI_Component2 * ( int32_t )Vqd.
08
qV_Component2 );
09
wAux /= 65536;
10
wAux2 = pHandle->wConvFact * ( int32_t )VBS_GetAvBusVoltage_V(
11
pHandle->pVBS );
12
wAux2 /= 600; /* 600 is max bus voltage expressed in volt.*/
13
wAux3 = wAux * wAux2;
14
wAux3 *= 6; /* 6 is max bus voltage expressed in thousend of volt.
15
*/
16
wAux3 /= 10;
17
wAux3 /= 65536;
18
MPM_CalcElMotorPower( &pHandle->_super, wAux3 );
19 }
pwm_curr_fdbk.c 文件内容
PWM 和电流反馈组件,主要包含两个功能,一个是计算三相 PWM 占空比,
生成 SVPWM;一个是电机电流采样。同时也是在计算占空比的时候确定下一个
周期的采样时刻。
STM32 技术开发手册
www.ing10bbs.com
PWMC_GetPhaseCurrents()
读取三相电流值𝐼𝑎 、𝐼𝑏 、𝐼𝑐 ,调用了一个指针函数来读取电流,指针函数是为
了兼容不同的硬件。实际返回值是𝐼𝑎 、𝐼𝑏 ,𝐼𝑐 是可以利用公式求得:
公式 20-27 相定子电流关系式
𝐼𝑐 = −𝐼𝑎 − 𝐼𝑏
代码 20-255 获取相电流
00 void PWMC_GetPhaseCurrents( PWMC_Handle_t * pHandle, Curr_Components *
01
pStator_Currents )
02 {
03
pHandle->pFctGetPhaseCurrents( pHandle, pStator_Currents );
04 }
PWMC_SetPhaseVoltage()
该函数用于设置三相占空比,输出 SVPWM,主要是根据𝑉𝛼 、𝑉𝛽 计算所在扇
区和三相占空比。
代码 20-256 计算三相占空比
00 uint16_t PWMC_SetPhaseVoltage( PWMC_Handle_t * pHandle,
01
Volt_Components Valfa_beta )
02 {
03
int32_t wX, wY, wZ, wUAlpha, wUBeta, wTimePhA, wTimePhB, wTimePhC;
04
PWMC_SetSampPointSectX_Cb_t pSetADCSamplingPoint;
05
06
wUAlpha = Valfa_beta.qV_Component1 * ( int32_t )pHandle->hT_Sqrt3;
07
wUBeta = -( Valfa_beta.qV_Component2 * ( int32_t )( pHandle->
08
hPWMperiod ) ) * 2;
09
10
wX = wUBeta;
11
wY = ( wUBeta + wUAlpha ) / 2;
12
wZ = ( wUBeta - wUAlpha ) / 2;
13
14
/* Sector calculation from wX, wY, wZ */
15
if ( wY < 0 ) {
16
if ( wZ < 0 ) {
17
pHandle->hSector = SECTOR_5;
18
wTimePhA = ( int32_t )( pHandle->hPWMperiod ) / 4 + ( ( wY
19
- wZ ) / ( int32_t )262144 );
20
wTimePhB = wTimePhA + wZ / 131072;
21
wTimePhC = wTimePhA - wY / 131072;
22
pSetADCSamplingPoint = pHandle->pFctSetADCSampPointSect5;
23
} else /* wZ >= 0 */
24
if ( wX <= 0 ) {
25
pHandle->hSector = SECTOR_4;
26
wTimePhA = ( int32_t )( pHandle->hPWMperiod ) / 4 + ( (
27
wX - wZ ) / ( int32_t )262144 );
28
wTimePhB = wTimePhA + wZ / 131072;
29
wTimePhC = wTimePhB - wX / 131072;
30
pSetADCSamplingPoint = pHandle->
31
pFctSetADCSampPointSect4;
STM32 技术开发手册
www.ing10bbs.com
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95 }
} else { /* wX > 0 */
pHandle->hSector = SECTOR_3;
wTimePhA = ( int32_t )( pHandle->hPWMperiod ) / 4 + ( (
wY - wX ) / ( int32_t )262144 );
wTimePhC = wTimePhA - wY / 131072;
wTimePhB = wTimePhC + wX / 131072;
pSetADCSamplingPoint = pHandle->
pFctSetADCSampPointSect3;
}
} else { /* wY > 0 */
if ( wZ >= 0 ) {
pHandle->hSector = SECTOR_2;
wTimePhA = ( int32_t )( pHandle->hPWMperiod ) / 4 + ( ( wY
- wZ ) / ( int32_t )262144 );
wTimePhB = wTimePhA + wZ / 131072;
wTimePhC = wTimePhA - wY / 131072;
pSetADCSamplingPoint = pHandle->pFctSetADCSampPointSect2;
} else /* wZ < 0 */
if ( wX <= 0 ) {
pHandle->hSector = SECTOR_6;
wTimePhA = ( int32_t )( pHandle->hPWMperiod ) / 4 + ( (
wY - wX ) / ( int32_t )262144 );
wTimePhC = wTimePhA - wY / 131072;
wTimePhB = wTimePhC + wX / 131072;
pSetADCSamplingPoint = pHandle->
pFctSetADCSampPointSect6;
} else { /* wX > 0 */
pHandle->hSector = SECTOR_1;
wTimePhA = ( int32_t )( pHandle->hPWMperiod ) / 4 + ( (
wX - wZ ) / ( int32_t )262144 );
wTimePhB = wTimePhA + wZ / 131072;
wTimePhC = wTimePhB - wX / 131072;
pSetADCSamplingPoint = pHandle->
pFctSetADCSampPointSect1;
}
}
pHandle->hCntPhA = ( uint16_t )wTimePhA;
pHandle->hCntPhB = ( uint16_t )wTimePhB;
pHandle->hCntPhC = ( uint16_t )wTimePhC;
if ( pHandle->DTTest == 1u ) {
/* Dead time compensation */
if ( pHandle->hIa > 0 ) {
pHandle->hCntPhA += pHandle->DTCompCnt;
} else {
pHandle->hCntPhA -= pHandle->DTCompCnt;
}
if ( pHandle->hIb > 0 ) {
pHandle->hCntPhB += pHandle->DTCompCnt;
} else {
pHandle->hCntPhB -= pHandle->DTCompCnt;
}
if ( pHandle->hIc > 0 ) {
pHandle->hCntPhC += pHandle->DTCompCnt;
} else {
pHandle->hCntPhC -= pHandle->DTCompCnt;
}
}
return ( pSetADCSamplingPoint( pHandle ) );
hT_Sqrt3 是一个用于计算 PWM 的系数,展开之后如下所示:
STM32 技术开发手册
www.ing10bbs.com
公式 20-28 用于计算 SVPWM 的系数
ℎ𝑇_𝑆𝑞𝑟𝑡3 = 𝑇𝑃𝑊𝑀 × √3 × 2
那么就可以看得出来,𝑈𝛼 、𝑈𝛽 、𝑋、𝑌、𝑍这些中间量的计算公式:
公式 20-29 中间量的计算
𝑈𝛼 = 2√3𝑉𝛼 × 𝑇𝑃𝑊𝑀
𝑈𝛽 = −(𝑉𝛽 × 𝑇𝑃𝑊𝑀 ) × 2
𝑋 = 𝑈𝛽
𝑈𝛽 + 𝑈𝛼
2
𝑈𝛽 − 𝑈𝛼
𝑍=
2
𝑌=
然后再根据中间量𝑋、𝑌、𝑍判断目标电压矢量所在扇区,如下图所示:
图 20-27 中间量扇区判断
从前面的坐标转换计算函数那里可以看到𝑉𝛽 是比𝑉𝛼 滞后 90°,𝑈𝛽 = −𝑉𝛽 表
示𝑈𝛽 比𝑉𝛼 提前 90°,那么就用𝑈𝛼 代替𝑉𝛼 、𝑈𝛽 代替−𝑉𝛽 ,组成正交坐标系。对𝑈𝛼
和𝑈𝛽 去掉相同的系数(𝑇𝑃𝑊𝑀 和 2,因为这些不影响扇区判断),再代入之后,得
到新的𝑋、𝑌、𝑍中间量:
公式 20-30 虚拟坐标轴
𝑋 = 𝑈𝛽
1
√3
𝑈𝛼 + 𝑈𝛽 = 𝑈𝛼 cos 30° + 𝑈𝛽 sin 30°
2
2
1
√3
𝑍 = 𝑈𝛽 −
𝑈 = 𝑈𝛽 cos 60° − 𝑈𝛼 sin 60°
2
2 𝛼
𝑌=
从公式 20-30 可以看的出来, 𝑋、𝑌、𝑍实际上就是三个坐标轴,可以通过对
𝑈𝛼 、 𝑈𝛽 进行两次坐标旋转之后得到。如下图所示:
STM32 技术开发手册
www.ing10bbs.com
图 20-28 扇区判断
⃗⃗⃗⃗⃗⃗⃗⃗⃗⃗⃗
⃗⃗⃗⃗⃗⃗⃗⃗⃗⃗⃗ ⃗⃗⃗⃗⃗⃗⃗⃗⃗⃗⃗ ⃗⃗⃗⃗⃗⃗⃗⃗⃗⃗⃗
𝒀与𝑼
𝑼𝟏 𝑼𝟔是无关向量,无需理会)。
𝟐 𝑼𝟓 组成正交坐标系, 𝐙与𝑼𝟏 𝑼𝟔(𝑼𝟐 𝑼𝟓 、
当𝒀小于 0 的时候就代表当前正处于 Sector3,4,5 中的任意一个。然后再判断𝑍小
于 0 的时候,同时符合𝒀 < 0,𝒁 < 0这两个条件的只有 Sector5。其他扇区的判
断同理。这是在几何图形上对扇区判断的公式作出的另外一种解释。
判断扇区之后,就是代入公式计算各相输出的占空比。实际的应用公式如下:
代码 20-257 占空比计算过程
𝑆𝑒𝑐𝑡𝑜𝑟 I, IV ∶ 𝑡𝐴 =
𝑇+𝑋−𝑍
2
𝑡𝐵 = 𝑡𝐴 + 𝑍
𝑡𝐶 = 𝑡𝐵 + 𝑋
𝑆𝑒𝑐𝑡𝑜𝑟 II, V ∶ 𝑡𝐴 =
𝑇+𝑋−𝑍
2
𝑡𝐵 = 𝑡𝐴 + 𝑍
𝑡𝐶 = 𝑡𝐵 + 𝑋
STM32 技术开发手册
www.ing10bbs.com
𝑆𝑒𝑐𝑡𝑜𝑟 III, VI: 𝑡𝐴 =
𝑇+𝑋−𝑍
2
𝑡𝐵 = 𝑡𝐴 + 𝑍
𝑡𝐶 = 𝑡𝐵 + 𝑋
以扇区𝐕为例,可以看得出来实际使用的跟公式是有一定的差别,其实这些
差别主要体现在系数上,前面有提到系数 hT_Sqrt3 的展开式。将其展开式的系
数再提取出来。
代码 20-258 扇区𝐕的占空比计算
17
18
19
20
21
pHandle->hSector = SECTOR_5;
wTimePhA = ( int32_t )( pHandle->hPWMperiod ) / 4 + ( ( wY
- wZ ) / ( int32_t )262144 );
wTimePhB = wTimePhA + wZ / 131072;
wTimePhC = wTimePhA - wY / 131072;
由于定时器使用中心对齐模式,所以计算出来的占空比在应用的时候,需要
除以 2 才是实际的比较值,所以有:
𝑇 𝑋−𝑍
+
4
4
𝑍
𝑡𝐵 = 𝑡𝐴 +
2
𝑌
𝑡𝐶 = 𝑡𝐴 −
2
𝑡𝐴 =
hT_Sqrt3 是一个用于计算 PWM 的系数,展开之后如下所示:
公式 20-31 用于计算 SVPWM 的系数
ℎ𝑇_𝑆𝑞𝑟𝑡3 = 𝑇𝑃𝑊𝑀 × √3 × 2
那么就可以看得出来,在计算中间量(X,Y,Z)的时候多了个系数 2,所以
实际的中间量(wX,wY,wZ)是前面公式中的 2 陪。
公式 20-32 实际的中间量
𝑤𝑋 = 2𝑋
𝑤𝑌 = 2𝑌
𝑤𝑍 = 2𝑍
那么代码中的占空比实际就是:
公式 20-33 实际的占空比计算公式
𝑤𝑇𝑖𝑚𝑒𝑃ℎ𝐴 =
𝑇 𝑤𝑋 − 𝑤𝑍
+
4
8
STM32 技术开发手册
www.ing10bbs.com
𝑤𝑍
4
𝑤𝑌
𝑤𝑇𝑖𝑚𝑒𝑃ℎ𝐶 = 𝑤𝑇𝑖𝑚𝑒𝑃ℎ𝐴 −
4
𝑤𝑇𝑖𝑚𝑒𝑃ℎ𝐵 = 𝑤𝑇𝑖𝑚𝑒𝑃ℎ𝐴 +
因为 Volt_Components 结构体成员值都是 Q1.15 格式,现在这里计算需要转
换成 Q0 格式,所以需要215 (32768)做转换系数,最后就变成了代码中的那样
了。
公式 20-34 实际应用代码
𝑤𝑇𝑖𝑚𝑒𝑃ℎ𝐴 =
𝑇
+ (𝑤𝑋 − 𝑤𝑍)⁄262144
4
𝑤𝑇𝑖𝑚𝑒𝑃ℎ𝐵 = 𝑤𝑇𝑖𝑚𝑒𝑃ℎ𝐴 + 𝑤𝑍⁄131072
𝑤𝑇𝑖𝑚𝑒𝑃ℎ𝐶 = 𝑤𝑇𝑖𝑚𝑒𝑃ℎ𝐴 − 𝑤𝑌⁄131072
其他扇区可做同样的推导。另外需要一提的是,定时器计数器计数模式是中
心对齐模式,三个 PWM 输出通道则是 PWM1 模式,控制高端 mos 管的 PWM 波
形总是关于中心对齐,如下所示。高电平段就是上式计算出来的比较值。
图 20-29 关于中心对称的 PWM 波形
PWMC_SwitchOffPWM()
该函数用于关闭 PWM 通道,为了适配不同的芯片,所以调用了一个函数指
针,指针所指向的实际函数与所用的芯片有关,但主要是实现一个功能,就是关
断输出通道,停止输出 PWM,关断上桥臂,这个时候的下桥臂是导通的。
代码 20-259 关断 PWM 输出
01 void PWMC_SwitchOffPWM( PWMC_Handle_t * pHandle )
02 {
03
pHandle->pFctSwitchOffPwm( pHandle );
04 }
STM32 技术开发手册
www.ing10bbs.com
PWMC_SwitchOnPWM()
启动 PWM 通道输出,解释见 PWMC_SwitchOffPWM()。
代码 20-260 使能 PWM 输出
01 void PWMC_SwitchOnPWM( PWMC_Handle_t * pHandle )
02 {
03
pHandle->pFctSwitchOnPwm( pHandle );
04 }
PWMC_CurrentReadingCalibr()
电流读取校准,在每次启动电机之前都是会执行这个函数,用于读取静止状
态的电流值,作为校准值。
代码 20-261 电流读取校准函数
01 bool PWMC_CurrentReadingCalibr( PWMC_Handle_t * pHandle, CRCAction_t
action )
02 {
03
bool retVal = false;
04
if ( action == CRC_START ) {
05
PWMC_SwitchOffPWM( pHandle );
06
pHandle->hOffCalibrWaitTimeCounter = pHandle->hOffCalibrWaitTicks;
07
if ( pHandle->hOffCalibrWaitTicks == 0u ) {
08
pHandle->pFctCurrReadingCalib( pHandle );
09
retVal = true;
10
}
11
} else if ( action == CRC_EXEC ) {
12
if ( pHandle->hOffCalibrWaitTimeCounter > 0u ) {
13
pHandle->hOffCalibrWaitTimeCounter--;
14
if ( pHandle->hOffCalibrWaitTimeCounter == 0u ) {
15
pHandle->pFctCurrReadingCalib( pHandle );
16
retVal = true;
17
}
18
} else {
19
retVal = true;
20
}
21
} else {
22
}
23
return retVal;
24 }
这是一个可以设置在采样前等待多长时间的函数,在第一次进入这个函数的
时候,关断 PWM,导通下桥臂,然后初始化计数器,如果计数器值是 0,就立即
采样,如果不是零则不采样。后续进入这个函数的时候,计数器自减,计数器为
0 的时候才采样。通过周期性的调用这个函数,可以达到控制采样前的延迟时间。
主要目的还是为了能准确采样。
PWMC_CheckOverCurrent()
检查是否过流。
STM32 技术开发手册
www.ing10bbs.com
代码 20-262 检查是否过流
01 uint16_t PWMC_CheckOverCurrent( PWMC_Handle_t * pHandle )
02 {
03
return pHandle->pFctIsOverCurrentOccurred( pHandle );
04 }
在 pwm_curr_fdbk.c 文件里面还有部分没有被调用的函数,这部分函数就不
做解释了,因为实在是没有解释的必要,都是一些看着名字就能理解功能的函数,
并且实际内容也是非常简单,只有一两条语句,所以这个文件的源码解释到此为
止。
revup_ctrl.c 文件内容
该组件主要用于控制电机启动之前的加速过程,也就是控制电机按照预先设
定目标加速曲线进行启动,如下图所示,在加速过程中,周期性地设定目标值。
该组件主要应用在无感模式的 startup 阶段,在开环模式逐步给定电机目标转速。
图 20-30 加速驱动曲线
这个文件其实是属于无感模式的内容,在这里是不打算过多解释的,但是有
部分函数用于与上位机通信,所以下面简单介绍一下这些函数。
RUC_SetPhaseDurationms()
设置阶段性的加速时间,单位是 ms,在 RevUp 阶段,可以设置多段速度曲
线,每一段都可以单独设置目标值和所用时长,所以这个函数就是设定单独一段
曲线的时长。
RUC_SetPhaseFinalMecSpeed01Hz()
设置阶段性的目标机械转速,速度单位是 0.1Hz。
STM32 技术开发手册
www.ing10bbs.com
RUC_SetPhaseFinalTorque()
设置阶段性的目标扭矩。
RUC_GetPhaseDurationms()
获取阶段性的时长。
RUC_GetPhaseFinalMecSpeed01Hz()
获取阶段性的转速。
RUC_GetPhaseFinalTorque()
获取阶段性的目标转矩。
RUC_GetNumberOfPhases()
获取目标曲线的分段数。
Speed_pos_fdbk.c 文件内容
这是速度和位置反馈组件,所谓位置反馈其实主要是电机角度的反馈,这个
组件是一个中间层,底层则是根据传感器类型不同而有所差异,在本例中使用的
传感器是 HALLSensor,所以实际的速度采样和计算是在另外的文件当中完成的
(hall_speed_pos_fdbk.c)。该文件则是提供了一个反馈速度值和角度值的借口。
SPD_GetElAngl()
返回上一次计算的电角度值,数值单位是 s16degree,转换关系是:
公式 20-35 电角度数值转换
1[𝒔𝟏𝟔𝒅𝒆𝒈𝒓𝒆𝒆] =
360°
65536
代码 20-263 获取电机电角度
01 int16_t SPD_GetElAngle( SpeednPosFdbk_Handle_t * pHandle )
02 {
03
return ( pHandle->hElAngle );
04 }
STM32 技术开发手册
www.ing10bbs.com
SPD_GetMecAngle()
返回上一次计算的机械角度,同样数值单位是 s16degree,但是无论是
HallSensor 模式还是无感模式,都不执行机械角度的计算,所以实际这个函数也
没有被调用。
SPD_GetAvrgMecSpeed01Hz()
获取转子平均转速。返回数值单位是 0.1Hz,也就是 0.1rps,转换成 rpm 单
位的速度值只需要乘 6。
代码 20-264 获取转子平均转速
01 int16_t SPD_GetAvrgMecSpeed01Hz( SpeednPosFdbk_Handle_t * pHandle )
02 {
03
return ( pHandle->hAvrMecSpeed01Hz );
04 }
SPD_GetElSpeedDpp()
获取电角度速度,也就是每控制周期的电角度值,返回数值单位是 dpp。
代码 20-265 获取 dpp 单位的速度值
01 int16_t SPD_GetElSpeedDpp( SpeednPosFdbk_Handle_t * pHandle )
02 {
03
return ( pHandle->hElSpeedDpp );
04 }
SPD_Check()
检测速度传感器是否可靠。读取传感器测速错误次数,如果超过允许的最大
错误次数就说明当前的传感器不可靠。这个函数不更新任何变量,所以实际返回
的是上一次的检测结果。
代码 20-266 检测速度传感器
01 bool SPD_Check( SpeednPosFdbk_Handle_t * pHandle )
02 {
03
bool SpeedSensorReliability = true;
04
if ( pHandle->bSpeedErrorNumber ==
05
pHandle->bMaximumSpeedErrorsNumber ) {
06
SpeedSensorReliability = false;
07
}
08
return ( SpeedSensorReliability );
09 }
STM32 技术开发手册
www.ing10bbs.com
SPD_IsMecSpeedReliable()
计算并返回当前的速度传感器是否可靠,输入参数是转子的平均转速,单位
是 0.1Hz,根据转子的平均转速的绝对值和加速度的绝对值判断,无论是过小还
是过大都会标记为速度错误,如果错误次数过多,则说明当前传感器不可靠。
代码 20-267 判断速度传感器是否可靠
01 bool SPD_IsMecSpeedReliable( SpeednPosFdbk_Handle_t * pHandle, int16_t *
pMecSpeed01Hz )
02 {
03
bool SpeedSensorReliability = true;
04
uint8_t bSpeedErrorNumber;
05
uint8_t bMaximumSpeedErrorsNumber =
pHandle->bMaximumSpeedErrorsNumber;
06
07
bool SpeedError = false;
08
uint16_t hAbsMecSpeed01Hz, hAbsMecAccel01HzP;
09
int16_t hAux;
10
11
bSpeedErrorNumber = pHandle->bSpeedErrorNumber;
12
13
/* Compute absoulte value of mechanical speed */
14
if ( *pMecSpeed01Hz < 0 ) {
15
hAux = -( *pMecSpeed01Hz );
16
hAbsMecSpeed01Hz = ( uint16_t )( hAux );
17
} else {
18
hAbsMecSpeed01Hz = ( uint16_t )( *pMecSpeed01Hz );
19
}
20
21
if ( hAbsMecSpeed01Hz > pHandle->hMaxReliableMecSpeed01Hz ) {
22
SpeedError = true;
23
}
24
25
if ( hAbsMecSpeed01Hz < pHandle->hMinReliableMecSpeed01Hz ) {
26
SpeedError = true;
27
}
28
29
/* Compute absoulte value of mechanical acceleration */
30
if ( pHandle->hMecAccel01HzP < 0 ) {
31
hAux = -( pHandle->hMecAccel01HzP );
32
hAbsMecAccel01HzP = ( uint16_t )( hAux );
33
} else {
34
hAbsMecAccel01HzP = ( uint16_t )( pHandle->hMecAccel01HzP );
35
}
36
37
if ( hAbsMecAccel01HzP > pHandle->hMaxReliableMecAccel01HzP ) {
38
SpeedError = true;
39
}
40
41
if ( SpeedError == true ) {
42
if ( bSpeedErrorNumber < bMaximumSpeedErrorsNumber ) {
43
bSpeedErrorNumber++;
44
}
45
} else {
46
if ( bSpeedErrorNumber < bMaximumSpeedErrorsNumber ) {
47
bSpeedErrorNumber = 0u;
48
}
49
}
50
51
if ( bSpeedErrorNumber == bMaximumSpeedErrorsNumber ) {
52
SpeedSensorReliability = false;
53
}
STM32 技术开发手册
www.ing10bbs.com
54
55
56
57
58 }
pHandle->bSpeedErrorNumber = bSpeedErrorNumber;
return ( SpeedSensorReliability );
SPD_GetS16Speed()
获取速度值,数值单位是 s16Speed,数值范围是-INT16_MAX~INT16_MAX。
代表着实际速度范围-hMaxReliableMecSpeed01Hz~ hMaxReliableMecSpeed01Hz。
代码 20-268 获取速度值
01 int16_t SPD_GetS16Speed( SpeednPosFdbk_Handle_t * pHandle )
02 {
03
int32_t wAux = ( int32_t ) pHandle->hAvrMecSpeed01Hz;
04
wAux *= INT16_MAX;
05
wAux /= ( int16_t ) pHandle->hMaxReliableMecSpeed01Hz;
06
return ( int16_t )wAux;
07 }
返回值单位是 s16Speed,与正常的速度值单位的关系:
公式 20-36 速度单位转换关系
𝑆𝑝𝑑𝑠16𝑆𝑝𝑒𝑒𝑑 =
𝑆𝑝𝑑𝐴𝑣𝑔
∗ 𝐼𝑁𝑇16_𝑀𝐴𝑋
𝑆𝑝𝑑𝑀𝑎𝑥
SPD_GetElToMecRatio()
获取电角度转化成转子角度的比例系数,其实就是电机的极对数。
SPD_SetElToMecRatio()
设置电角度转换成转子角度的比例系数,其实就是电机的极对数。
Speed_torq_ctrl.c 文件内容
这是速度和扭矩控制组件,主要用于控制电机转动,包括设置电机速度和扭
矩的目标值等功能。
STC_Init()
初始化函数,在这个函数里面耦合了相关的控制句柄,并且设置了一些控制
参数的默认值。
代码 20-269 速度扭矩控制句柄初始化
01 void STC_Init( SpeednTorqCtrl_Handle_t * pHandle, PID_Handle_t * pPI,
SpeednPosFdbk_Handle_t * SPD_Handle )
02 {
STM32 技术开发手册
www.ing10bbs.com
03
04
pHandle->PISpeed = pPI;
05
pHandle->SPD = SPD_Handle;
06
pHandle->Mode = pHandle->ModeDefault;
07
pHandle->SpeedRef01HzExt = ( int32_t )pHandle->MecSpeedRef01HzDefault
* 65536;
08
pHandle->TorqueRef = ( int32_t )pHandle->TorqueRefDefault * 65536;
09
pHandle->TargetFinal = 0;
10
pHandle->RampRemainingStep = 0u;
11
pHandle->IncDecAmount = 0;
12 }
PISpeed 就是 PI 控制句柄,用于执行 PID 算法,SPD 则是速度传感器控制句
柄,用于读取速度,Mode 是运动控制模式,可选速度和扭矩模式,例程默认是
速度模式。其他的就只是一些初始参数。
STC_SetSpeedSensor()
设置速度传感器的控制句柄,就是重新设置 SPD。
代码 20-270 设置速度传感器
01 void STC_SetSpeedSensor( SpeednTorqCtrl_Handle_t * pHandle,
SpeednPosFdbk_Handle_t * SPD_Handle )
02 {
03
pHandle->SPD = SPD_Handle;
04 }
STC_GetSpeedSensor()
获取速度传感器的控制句柄,返回值是指针。
代码 20-271 获取速度传感器
01 SpeednPosFdbk_Handle_t * STC_GetSpeedSensor( SpeednTorqCtrl_Handle_t *
pHandle )
02 {
03
return ( pHandle->SPD );
04 }
STC_Clear()
清空 PID 的积分值,将 PID 算法的积分项(积分累加值)设置为 0。该函数
只有在速度模式才有效,扭矩模式无效。
STC_GetMecSpeedRef01Hz()
获取转速转速目标值,单位是 0.1Hz。
代码 20-272 获取转速目标值
01 int16_t STC_GetMecSpeedRef01Hz( SpeednTorqCtrl_Handle_t * pHandle )
STM32 技术开发手册
www.ing10bbs.com
02 {
03
04 }
return ( ( int16_t )( pHandle->SpeedRef01HzExt / 65536 ) );
STC_GetTorqueRef()
获取扭矩目标值,实际设置的是𝐼𝑞 的目标值,如果需要转化成安培单位的电
流值,可以参考公式 20-1。
代码 20-273 获取扭矩目标值
01 int16_t STC_GetTorqueRef( SpeednTorqCtrl_Handle_t * pHandle )
02 {
03
return ( ( int16_t )( pHandle->TorqueRef / 65536 ) );
04 }
STC_SetControlMode()
设置控制模式,设置为速度或者扭矩模式,同时清空一个变量,这个变量用
于控制 Ramp 状态的步数。
代码 20-274 设置控制模式
01 void STC_SetControlMode( SpeednTorqCtrl_Handle_t * pHandle,
STC_Modality_t bMode )
02 {
03
pHandle->Mode = bMode;
04
pHandle->RampRemainingStep = 0u; /* Interrupts previous ramp. */
05 }
STC_GetControlMode()
获取控制模式,返回值是当前的运动控制模式,速度模式或扭矩模式
(STC_TORQUE_MODE or STC_SPEED_MODE)
。
代码 20-275 获取控制模式
01 STC_Modality_t STC_GetControlMode( SpeednTorqCtrl_Handle_t * pHandle )
02 {
03
return pHandle->Mode;
04 }
STC_ExecRamp()
执行 Ramp 功能。也就是控制电机速度或者扭矩进行 Ramp 动作。Ramp 指
的是爬坡的动作,应用在电机控制方面,也就是控制电机的转速或者扭矩直线增
加或者直线减少,如图中的速度曲线所示。
STM32 技术开发手册
www.ing10bbs.com
图 20-31 速度爬坡动作
这个过程主要通过周期性的递增/减目标值实现,也就是逐步增加/减少目标
值来实现这样的功能。而这个函数的作用则是计算出在运行过程中执行 Ramp 控
制的次数和每次增加/减少的目标量。
代码 20-276 执行 Ramp 功能
01 bool STC_ExecRamp( SpeednTorqCtrl_Handle_t * pHandle, int16_t
hTargetFinal, uint32_t hDurationms )
02 {
03
bool AllowedRange = true;
04
uint32_t wAux;
05
int32_t wAux1;
06
int16_t hCurrentReference;
07
08
/* Check if the hTargetFinal is out of the bound of application. */
09
if ( pHandle->Mode == STC_TORQUE_MODE ) {
10
hCurrentReference = STC_GetTorqueRef( pHandle );
11 #ifdef CHECK_BOUNDARY
12
if ( ( int32_t )hTargetFinal >
( int32_t )pHandle->MaxPositiveTorque ) {
13
AllowedRange = false;
14
}
15
if ( ( int32_t )hTargetFinal <
( int32_t )pHandle->MinNegativeTorque ) {
16
AllowedRange = false;
17
}
18 #endif
19
} else {
20
hCurrentReference = ( int16_t )( pHandle->SpeedRef01HzExt >> 16 );
21
22 #ifdef CHECK_BOUNDARY
23
if ( ( int32_t )hTargetFinal >
( int32_t )pHandle->MaxAppPositiveMecSpeed01Hz ) {
24
AllowedRange = false;
25
} else if ( hTargetFinal < pHandle->MinAppNegativeMecSpeed01Hz ) {
26
AllowedRange = false;
27
} else if ( ( int32_t )hTargetFinal <
( int32_t )pHandle->MinAppPositiveMecSpeed01Hz ) {
28
if ( hTargetFinal > pHandle->MaxAppNegativeMecSpeed01Hz ) {
29
AllowedRange = false;
30
}
31
} else {}
32 #endif
33
}
34
35
if ( AllowedRange == true ) {
36
/* Interrupts the execution of any previous ramp command */
STM32 技术开发手册
www.ing10bbs.com
37
if ( hDurationms == 0u ) {
38
if ( pHandle->Mode == STC_SPEED_MODE ) {
39
pHandle->SpeedRef01HzExt = ( int32_t )hTargetFinal * 65536;
40
} else {
41
pHandle->TorqueRef = ( int32_t )hTargetFinal * 65536;
42
}
43
pHandle->RampRemainingStep = 0u;
44
pHandle->IncDecAmount = 0;
45
} else {
46
/* Store the hTargetFinal to be applied in the last step */
47
pHandle->TargetFinal = hTargetFinal;
48
49
/* Compute the (wRampRemainingStep) number of steps remaining to
complete
50
the ramp. */
51
wAux = ( uint32_t )hDurationms *
( uint32_t )pHandle->STCFrequencyHz;
52
wAux /= 1000u;
53
pHandle->RampRemainingStep = wAux;
54
pHandle->RampRemainingStep++;
55
56
/* Compute the increment/decrement amount (wIncDecAmount) to be
applied to
57
the reference value at each CalcTorqueReference. */
58
wAux1 = ( ( int32_t )hTargetFinal ( int32_t )hCurrentReference ) * 65536;
59
wAux1 /= ( int32_t )pHandle->RampRemainingStep;
60
pHandle->IncDecAmount = wAux1;
61
}
62
}
63
64
return AllowedRange;
65 }
首先是判断所设置的目标值是否超过了可以设置的范围,这里使用了条件编
译语句,宏定义 CHECK_BOUNDARY 用于检查设定的参数边界范围。
如果是在允许的范围内,那就根据 hDurationms 来执行 Ramp 工作,如果
hDurationms 等于 0,说明不需要 Ramp,直接将速度值设定为最终的目标值就行
了。如果不为 0,则计算在 hDurationms 时间内,会调用几次 STC_ExecRamp()函
数,因为这个过程是周期性的执行,并且执行频率是一开始就固定的,所以很容
易就可以计算出来,然后使用 RampRemainingStep 变量保存,当修改一次目标值
之后就自减 1,直到 0 就停止。IncDecAmount 变量则是用于保存目标增量值。
STC_StopRamp()
停止 Ramp。只需要将执行步数设置为 0,并且将目标增量也设置为 0 就行。
代码 20-277 停止 Ramp
01 void STC_StopRamp( SpeednTorqCtrl_Handle_t * pHandle )
02 {
03
04
pHandle->RampRemainingStep = 0u;
05
pHandle->IncDecAmount = 0;
06 }
STM32 技术开发手册
www.ing10bbs.com
STC_CalcTorqueReference()
Ramp 的实际执行函数,这个函数被周期性的调用执行。首先是读取当前的
目标值,然后对其增/减。如果 RampRemainingStep 为 0 则对目标值不做任何操
作。然后再根据速度或者扭矩模式分别设置目标值。
代码 20-278 执行 Ramp 功能
01 int16_t STC_CalcTorqueReference( SpeednTorqCtrl_Handle_t * pHandle )
02 {
03
int32_t wCurrentReference;
04
int16_t hTorqueReference = 0;
05
int16_t hMeasuredSpeed;
06
int16_t hTargetSpeed;
07
int16_t hError;
08
09
if ( pHandle->Mode == STC_TORQUE_MODE ) {
10
wCurrentReference = pHandle->TorqueRef;
11
} else {
12
wCurrentReference = pHandle->SpeedRef01HzExt;
13
}
14
15
/* Update the speed reference or the torque reference according to the
mode
16
and terminates the ramp if needed. */
17
if ( pHandle->RampRemainingStep > 1u ) {
18
/* Increment/decrement the reference value. */
19
wCurrentReference += pHandle->IncDecAmount;
20
21
/* Decrement the number of remaining steps */
22
pHandle->RampRemainingStep--;
23
} else if ( pHandle->RampRemainingStep == 1u ) {
24
/* Set the backup value of hTargetFinal. */
25
wCurrentReference = ( int32_t )pHandle->TargetFinal * 65536;
26
pHandle->RampRemainingStep = 0u;
27
} else {
28
/* Do nothing. */
29
}
30
31
if ( pHandle->Mode == STC_SPEED_MODE ) {
32
/* Run the speed control loop */
33
34
/* Compute speed error */
35
hTargetSpeed = ( int16_t )( wCurrentReference / 65536 );
36
hMeasuredSpeed = SPD_GetAvrgMecSpeed01Hz( pHandle->SPD );
37
hError = hTargetSpeed - hMeasuredSpeed;
38
hTorqueReference = PI_Controller( pHandle->PISpeed,
( int32_t )hError );
39
40
pHandle->SpeedRef01HzExt = wCurrentReference;
41
pHandle->TorqueRef = ( int32_t )hTorqueReference * 65536;
42
} else {
43
pHandle->TorqueRef = wCurrentReference;
44
hTorqueReference = ( int16_t )( wCurrentReference / 65536 );
45
}
46
47
return hTorqueReference;
48 }
STM32 技术开发手册
www.ing10bbs.com
STC_GetMecSpeedRef01HzDefault()
获取默认的转子转速目标值,单位是 0.1hz
代码 20-279 获取默认的转速目标值
01 int16_t STC_GetMecSpeedRef01HzDefault( SpeednTorqCtrl_Handle_t *
pHandle )
02 {
03
return pHandle->MecSpeedRef01HzDefault;
04 }
STC_GetMaxAppPositiveMecSpeed01Hz
获取最大的应用转速,就是可以设置的速度最大值。单位是 0.1Hz。
STC_GetMinAppNegativeMecSpeed01Hz()
获取最小的应用转速,就是可以设置的速度最小值。单位是 0.1Hz。
STC_RampCompleted()
查询 Ramp 动作是否已经完成。
代码 20-280 查询 Ramp 是否完成
01 bool STC_RampCompleted( SpeednTorqCtrl_Handle_t * pHandle )
02 {
03
bool retVal = false;
04
if ( pHandle->RampRemainingStep == 0u ) {
05
retVal = true;
06
}
07
return retVal;
08 }
STC_GetDefaultIqdref()
获取默认的𝐼𝑞 、𝐼𝑑 目标值。返回值是结构体变量。
STC_SetNominalCurrent()
设置标称电流值。
代码 20-281 设置标称电流值
01 void STC_SetNominalCurrent( SpeednTorqCtrl_Handle_t * pHandle, uint16_t
hNominalCurrent )
02 {
03
pHandle->MaxPositiveTorque = hNominalCurrent;
04
pHandle->MinNegativeTorque = -hNominalCurrent;
05 }
STM32 技术开发手册
www.ing10bbs.com
STC_ForceSpeedReferenceToCurrentSpeed()
将当前的速度值设置为目标值。应用在 START_RUN 初始化速度目标值。
代码 20-282 设置当前的目标值
01 void STC_ForceSpeedReferenceToCurrentSpeed( SpeednTorqCtrl_Handle_t *
pHandle )
02 {
03
pHandle->SpeedRef01HzExt =
( int32_t )SPD_GetAvrgMecSpeed01Hz( pHandle->SPD ) * ( int32_t )65536;
04 }
State_machine.c 文件内容
这个是电机控制状态机实现组件,包含了各个状态的实现功能。
STM_Init()
初始化状态机,初始状态是 IDLE,hFaultNow 和 hFaultOccurred 表示当前出
现的错误和已经出现的错误,都标记为 NO FAULTS。
代码 20-283 状态机初始化
01 void STM_Init( STM_Handle_t * pHandle )
02 {
03
04
pHandle->bState = IDLE;
05
pHandle->hFaultNow = MC_NO_FAULTS;
06
pHandle->hFaultOccurred = MC_NO_FAULTS;
07 }
STM_NextState()
状态机跳转,输入参数是 bState,也就是下一步的状态。在切换状态之前还
会判断当前的状态是否可以进入下一步的状态。如果无法进入下一个状态,就会
标记 MC_SW_ERROR 错误。
代码 20-284 设置下一个状态
01 bool STM_NextState( STM_Handle_t * pHandle, State_t bState )
02 {
03
bool bChangeState = false;
04
State_t bCurrentState = pHandle->bState;
05
State_t bNewState = bCurrentState;
06
07
switch ( bCurrentState ) {
08
case ICLWAIT:
09
if ( bState == IDLE ) {
10
bNewState = bState;
11
bChangeState = true;
12
}
13
break;
14
case IDLE:
STM32 技术开发手册
www.ing10bbs.com
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
if ( ( bState == IDLE_START ) || ( bState == IDLE_ALIGNMENT )
|| ( bState == ICLWAIT ) ) {
bNewState = bState;
bChangeState = true;
}
break;
case IDLE_ALIGNMENT:
if ( ( bState == ANY_STOP ) || ( bState == ALIGN_CHARGE_BOOT_CAP )
|| ( bState == ALIGN_OFFSET_CALIB ) ) {
bNewState = bState;
bChangeState = true;
}
break;
case ALIGN_CHARGE_BOOT_CAP:
if ( ( bState == ALIGN_OFFSET_CALIB ) || ( bState == ANY_STOP ) ) {
bNewState = bState;
bChangeState = true;
}
break;
case ALIGN_OFFSET_CALIB:
if ( ( bState == ALIGN_CLEAR ) || ( bState == ANY_STOP ) ) {
bNewState = bState;
bChangeState = true;
}
break;
case ALIGN_CLEAR:
if ( ( bState == ALIGNMENT ) || ( bState == ANY_STOP ) ) {
bNewState = bState;
bChangeState = true;
}
break;
case ALIGNMENT:
if ( bState == ANY_STOP ) {
bNewState = bState;
bChangeState = true;
}
break;
case IDLE_START:
if ( ( bState == ANY_STOP ) || ( bState == CHARGE_BOOT_CAP ) ||
( bState == START ) ||
( bState == OFFSET_CALIB ) || ( bState == IDLE_ALIGNMENT ) ) {
bNewState = bState;
bChangeState = true;
}
break;
case CHARGE_BOOT_CAP:
if ( ( bState == OFFSET_CALIB ) || ( bState == ANY_STOP ) ) {
bNewState = bState;
bChangeState = true;
}
break;
case OFFSET_CALIB:
if ( ( bState == CLEAR ) || ( bState == ANY_STOP ) ) {
bNewState = bState;
bChangeState = true;
}
break;
case CLEAR:
if ( ( bState == START ) || ( bState == ANY_STOP ) ) {
STM32 技术开发手册
www.ing10bbs.com
83
bNewState = bState;
84
bChangeState = true;
85
}
86
break;
87
88
case START:
89
if ( ( bState == START_RUN ) || ( bState == ANY_STOP ) ) {
90
bNewState = bState;
91
bChangeState = true;
92
}
93
break;
94
95
case START_RUN:
96
if ( ( bState == RUN ) || ( bState == ANY_STOP ) ) {
97
bNewState = bState;
98
bChangeState = true;
99
}
100
break;
101
102
case RUN:
103
if ( bState == ANY_STOP ) {
104
bNewState = bState;
105
bChangeState = true;
106
}
107
break;
108
109
case ANY_STOP:
110
if ( bState == STOP ) {
111
bNewState = bState;
112
bChangeState = true;
113
}
114
break;
115
116
case STOP:
117
if ( bState == STOP_IDLE ) {
118
bNewState = bState;
119
bChangeState = true;
120
}
121
break;
122
123
case STOP_IDLE:
124
if ( ( bState == IDLE ) || ( bState == ICLWAIT ) ) {
125
bNewState = bState;
126
bChangeState = true;
127
}
128
break;
129
default:
130
break;
131
}
132
133
if ( bChangeState ) {
134
pHandle->bState = bNewState;
135
} else {
136
if ( !( ( bState == IDLE_START ) || ( bState == IDLE_ALIGNMENT )
137
|| ( bState == ANY_STOP ) ) ) {
138
/* If new state is not a user command START/STOP raise a
software error */
139
STM_FaultProcessing( pHandle, MC_SW_ERROR, 0u );
140
}
141
}
142
143
return ( bChangeState );
144 }
STM32 技术开发手册
www.ing10bbs.com
STM_FaultProcessing()
错误进程处理,这个函数主要处理一件事情,就是区分当前的错误是不是第
一次发生,如果是第一次发生就是标记当前状态为 FAULT_NOW,如果不是并且
已经没有错误标志了,就是 FAULT_OVER。这两个状态的差别在于 FAULT_NOW 会
执行关断 PWM 输出通道,使上桥臂关闭,并且清空变量,而 FAULT_OVER 则仅
仅是关断 PWM 输出。
代码 20-285 错误进程处理
01 State_t STM_FaultProcessing( STM_Handle_t * pHandle, uint16_t hSetErrors,
uint16_t
02
hResetErrors )
03 {
04
State_t LocalState = pHandle->bState;
05
06
/* Set current errors */
07
pHandle->hFaultNow = ( pHandle->hFaultNow | hSetErrors ) &
( ~hResetErrors );
08
pHandle->hFaultOccurred |= hSetErrors;
09
10
if ( LocalState == FAULT_NOW ) {
11
if ( pHandle->hFaultNow == MC_NO_FAULTS ) {
12
pHandle->bState = FAULT_OVER;
13
LocalState = FAULT_OVER;
14
}
15
} else {
16
if ( pHandle->hFaultNow != MC_NO_FAULTS ) {
17
pHandle->bState = FAULT_NOW;
18
LocalState = FAULT_NOW;
19
}
20
}
21
22
return ( LocalState );
23 }
STM_GetState()
获取当前的状态
代码 20-286 获取状态机状态
01 State_t STM_GetState( STM_Handle_t * pHandle )
02 {
03
return ( pHandle->bState );
04 }
STM_FaultAcknowledged()
错误处理应答,如果当前处于 FAULT_OVER 状态,就清除错误标志,然后切
换成 IDLE 状态。
STM32 技术开发手册
www.ing10bbs.com
代码 20-287 错误应答
01 bool STM_FaultAcknowledged( STM_Handle_t * pHandle )
02 {
03
bool bToBeReturned = false;
04
05
if ( pHandle->bState == FAULT_OVER ) {
06
pHandle->bState = STOP_IDLE;
07
pHandle->hFaultOccurred = MC_NO_FAULTS;
08
bToBeReturned = true;
09
}
10
11
return ( bToBeReturned );
12 }
STM_GetFaultState()
读取当前的错误状态。
代码 20-288
01 uint32_t STM_GetFaultState( STM_Handle_t * pHandle )
02 {
03
uint32_t LocalFaultState;
04
05
LocalFaultState = ( uint32_t )( pHandle->hFaultOccurred );
06
LocalFaultState |= ( uint32_t )( pHandle->hFaultNow ) << 16;
07
08
return LocalFaultState;
09 }
virtual_speed_sensor.c 文件内容
这个是虚拟速度传感器的组件,主要用于在无感模式的时候开环控制的速度
反馈,在有感模式是完全不会用到的。所以这里省略不提。
r3_f4xx_pwm_curr_fdbk.c 文件内容
该文件是用于适配 F4 系列芯片的三相 PWM 和电流采样功能,换句话说就
是实现了使用 F4 进行三相电阻采样电流和 PWM 输出的底层功能。这一组件是
直接操作硬件输出 PWM 和采样电流,部分涉及到寄存器的操作和 LL 库的使用,
所以要求对 STM32F4 系列芯片的寄存器非常熟悉才能理解。
代码 20-289 寄存器位定义
01
02
03
04
05
06
07
08
#define SMPR1_SMP_Set
#define SMPR2_SMP_Set
((uint32_t) (0x00000007u))
((uint32_t) (0x00000007u))
#define
#define
#define
#define
#define
((uint16_t) ~0x1555u)
((uint16_t) 0x555u)
((uint16_t) 0x1000u)
((uint32_t) (0x8))
((uint32_t) (0xC))
TIMxCCER_MASK
TIMxCCER_MASK_CH123
TIMx_CC4E_BIT
CONV_STARTED
CONV_FINISHED
STM32 技术开发手册
www.ing10bbs.com
09 #define FLAGS_CLEARED
((uint32_t) (0x0))
10 #define ADC_SR_MASK
((uint32_t) (0xC))
11 #define NB_CONVERSIONS 16u
12 #define PHASE_A_MSK
(uint32_t)((uint32_t)(pHandle->pParams_str->bIaChannel) << 15)
13 #define PHASE_B_MSK
(uint32_t)((uint32_t)(pHandle->pParams_str->bIbChannel) << 15)
14 #define PHASE_C_MSK
(uint32_t)((uint32_t)(pHandle->pParams_str->bIcChannel) << 15)
15 #define CCMR2_CH4_DISABLE 0x8FFFu
16 #define CCMR2_CH4_PWM1
0x6000u
17 #define CCMR2_CH4_PWM2
0x7000u
这些宏定义是关于寄存器的位定义,具体展开之后的数值是什么意义需要了
解相应的寄存器的每一个位的功能才知道。
R3F4XX_Init()
F4 芯片的三相采样初始化函数,这个函数主要配置了定时器和 ADC 外设。
代码 20-290 底层硬件初始化函数
01 void R3F4XX_Init( PWMC_R3_F4_Handle_t * pHandle )
02 {
03
if ( ( uint32_t )pHandle == ( uint32_t )&pHandle->_Super ) {
04
05
R3F4XX_TIMxInit( pHandle->pParams_str->TIMx, &pHandle->_Super );
06
07
if ( pHandle->pParams_str->TIMx == TIM1 ) {
08
/* TIM1 Counter Clock stopped when the core is halted */
09
LL_DBGMCU_APB2_GRP1_FreezePeriph( LL_DBGMCU_APB2_GRP1_TIM1_STOP );;
10
} else {
11
/* TIM8 Counter Clock stopped when the core is halted */
12
LL_DBGMCU_APB2_GRP1_FreezePeriph( LL_DBGMCU_APB2_GRP1_TIM8_STOP );
13
}
14
15
/* ADC1 and ADC2 registers configuration --------------------------------*/
16
/* Enable ADC1 and ADC2 */
17
LL_ADC_Enable( ADC1 );
18
LL_ADC_Enable( ADC2 );
19
20
/* ADC1 Injected conversions end interrupt enabling */
21
LL_ADC_ClearFlag_JEOS( ADC1 );
22
LL_ADC_EnableIT_JEOS( ADC1 );
23
24
/* reset regular conversion sequencer length set by cubeMX */
25
LL_ADC_REG_SetSequencerLength( ADC1, LL_ADC_REG_SEQ_SCAN_DISABLE );
26
27
/* To pre-compute the following variables is used the configuration
already
28
performed on ADC1 and ADC2. This means that ADC configurations run
from here
29
on out will be overwritten during the context switching.*/
30
if ( pHandle->pParams_str->TIMx == TIM1 ) {
31
/* The following two variables are pre-computed and used to
disable/enable
32
the ADC injected external trigger during the context switching.
*/
33
pHandle->wADCTriggerUnSet = ADC1->CR2 & 0xFFC0FFFFu; /* JEXTEN =
00b (Disable), JEXTSEL = 0000b (TIM1_CC4) */
34
pHandle->wADCTriggerSet = pHandle->wADCTriggerUnSet |
STM32 技术开发手册
www.ing10bbs.com
35
0x00100000u; /* JEXTEN = 01b (Enable),
JEXTSEL = 0000b (TIM1_CC4) */
36
} else {
37
/* The following two variables are pre-computed and used to
disable/enable
38
the ADC injected external trigger during the context switching.
*/
39
pHandle->wADCTriggerUnSet = ADC1->CR2 & 0xFFC0FFFFu; /* JEXTEN =
00b (Disable), JEXTSEL = 0000b (TIM1_CC4 "dummy") */
40
pHandle->wADCTriggerSet = pHandle->wADCTriggerUnSet |
41
0x001E0000u; /* JEXTEN = 01b (Enable),
JEXTSEL = 1110b (TIM8_CC4) */
42
}
43
44
pHandle->OverCurrentFlag = false;
45
pHandle->_Super.DTTest = 0u;
46
pHandle->_Super.DTCompCnt = pHandle->_Super.hDTCompCnt;
47
}
48 }
其实真正的初始化外设是在 main.c 里面完成的,但是由于 main.c 初始化函
数是有 CubeMx 软件自动生成的代码,是按照一个模板生成的,所以有部分的配
置是不符合电机库的应用,或者说使用 CubeMx 生成的代码不够灵活。
LL_DBGMCU_APB2_GRP1_FreezePeriph()用于设定定时器在 debug 状态下如
果碰到 break 点,就冻结定时器时钟,使定时器完全处于停止状态,避免输出
PWM。
LL_ADC_REG_SetSequencerLength()用于重新设定 ADC 转换序列的长度,设置
为 0,也就是 1 次转换。
最后根据使用的是 TIM1 还是 TIM8 配置 ADC 的触发源,就是选择由不同的
定时器通道触发 ADC 采样。这里虽然是直接操作寄存器,但是代码注释已经写
得很清楚了,就是选择 TIM1/TIM8 的 CC4 作为触发源。
R3F4XX_TIMxInit()
这个函数使能了定时器的 4 个通道的比较值预装载寄存器,当修改比较值的
时候将会在下一个更新事件生效。
代码 20-291 配置定时器
01 static void R3F4XX_TIMxInit( TIM_TypeDef * TIMx, PWMC_Handle_t * pHdl )
02 {
03
PWMC_R3_F4_Handle_t * pHandle = ( PWMC_R3_F4_Handle_t * )pHdl;
04
05
/* disable main TIM counter to ensure
06
* a synchronous start by TIM2 trigger */
07
LL_TIM_DisableCounter( TIMx );
08
09
/* Enables the TIMx Preload on CC1 Register */
10
LL_TIM_OC_EnablePreload( TIMx, LL_TIM_CHANNEL_CH1 );
11
/* Enables the TIMx Preload on CC2 Register */
STM32 技术开发手册
www.ing10bbs.com
12
LL_TIM_OC_EnablePreload( TIMx, LL_TIM_CHANNEL_CH2 );
13
/* Enables the TIMx Preload on CC3 Register */
14
LL_TIM_OC_EnablePreload( TIMx, LL_TIM_CHANNEL_CH3 );
15
/* Enables the TIMx Preload on CC4 Register */
16
LL_TIM_OC_EnablePreload( TIMx, LL_TIM_CHANNEL_CH4 );
17
18
if ( ( pHandle->pParams_str->EmergencyStop ) != DISABLE ) {
19
LL_TIM_ClearFlag_BRK( TIMx );
20
LL_TIM_EnableIT_BRK( TIMx );
21
}
22
23
/* Prepare timer for synchronization */
24
LL_TIM_GenerateEvent_UPDATE( TIMx );
25
26
if ( pHandle->pParams_str->bFreqRatio == 2u ) {
27
if ( pHandle->pParams_str->bIsHigherFreqTim == HIGHER_FREQ ) {
28
if ( pHandle->pParams_str->bRepetitionCounter == 3u ) {
29
/* Set TIMx repetition counter to 1 */
30
LL_TIM_SetRepetitionCounter( TIMx, 1u );
31
LL_TIM_GenerateEvent_UPDATE( TIMx );
32
/* Repetition counter will be set to 3 at next Update */
33
LL_TIM_SetRepetitionCounter( TIMx, 3 );
34
}
35
}
36
37
LL_TIM_SetCounter( TIMx, ( uint32_t )( pHandle->Half_PWMPeriod ) 1u );
38
} else { /* bFreqRatio equal to 1 or 3 */
39
if ( pHandle->_Super.bMotor == M1 ) {
40
LL_TIM_SetCounter( TIMx, ( uint32_t )( pHandle->Half_PWMPeriod )
- 1u );
41
}
42
}
43 }
如果由使能刹车功能就使能刹车中断,实际上并没有使用到刹车功能。由于
使能了预装载功能,所以配置完了之后生成更新事件使得前面的配置生效
(LL_TIM_GenerateEvent_UPDATE)。最后是根据设定的比例系数来决定重复计数
器的值,在上位机配置参数的时候有一项参数,如下图所示,决定了 PWM 频率
和 FOC 控制频率比,也就是定时器的重复计数器。
图 20-32 载波频率预执行频率比
STM32 技术开发手册
www.ing10bbs.com
R3F4XX_CurrentReadingCalibration()
电流读取校准函数。这个函数用于采集电流值作为校准值。在每次启动之前
都会执行电流采样校准,读取校准值(偏移值)。
代码 20-292 读取电流校准值
01 void R3F4XX_CurrentReadingCalibration( PWMC_Handle_t * pHdl )
02 {
03
PWMC_R3_F4_Handle_t * pHandle = ( PWMC_R3_F4_Handle_t * )pHdl;
04
TIM_TypeDef * TIMx = pHandle->pParams_str->TIMx;
05
06
pHandle->wPhaseAOffset = 0u;
07
pHandle->wPhaseBOffset = 0u;
08
pHandle->wPhaseCOffset = 0u;
09
10
pHandle->bIndex = 0u;
11
12
/* It forces inactive level on TIMx CHy and CHyN */
13
TIMx->CCER &= TIMxCCER_MASK;
14
15
/* Offset calibration for A & B phases */
16
/* Change function to be executed in ADCx_ISR */
17
pHandle->_Super.pFctGetPhaseCurrents =
&R3F4XX_HFCurrentsCalibrationAB;
18
19
pHandle->wADC1Channel = PHASE_A_MSK;
20
pHandle->wADC2Channel = PHASE_B_MSK;
21
22
R3F4XX_SwitchOnPWM( &pHandle->_Super );
23
24
/* Wait for NB_CONVERSIONS to be executed */
25
while ( pHandle->bIndex < ( NB_CONVERSIONS ) ) {
26
if ( LL_TIM_IsEnabledIT_UPDATE( TIMx ) ) {
27
} else {
28
pHandle->bIndex = NB_CONVERSIONS;
29
}
30
}
31
32
/* Offset calibration for C phase */
33
/* Reset bIndex */
34
pHandle->bIndex = 0u;
35
36
/* Change function to be executed in ADCx_ISR */
37
pHandle->_Super.pFctGetPhaseCurrents = &R3F4XX_HFCurrentsCalibrationC;
38
39
pHandle->wADC1Channel = PHASE_C_MSK;
40
pHandle->wADC2Channel = PHASE_C_MSK;
41
42
R3F4XX_SwitchOnPWM( &pHandle->_Super );
43
44
/* Wait for NB_CONVERSIONS to be executed */
45
while ( pHandle->bIndex < ( NB_CONVERSIONS / 2u ) ) {
46
if ( LL_TIM_IsEnabledIT_UPDATE( TIMx ) ) {
47
} else {
48
pHandle->bIndex = NB_CONVERSIONS;
49
}
50
}
51
52
pHandle->wPhaseAOffset >>= 3;
53
pHandle->wPhaseBOffset >>= 3;
54
pHandle->wPhaseCOffset >>= 3;
55
56
/* Change back function to be executed in ADCx_ISR */
STM32 技术开发手册
www.ing10bbs.com
57
pHandle->_Super.pFctGetPhaseCurrents = &R3F4XX_GetPhaseCurrents;
58
59
/* It over write TIMx CCRy wrongly written by FOC during calibration
so as to
60
force 50% duty cycle on the three inverer legs */
61
/* Disable TIMx preload */
62
TIMx->CCMR1 &= 0xF7F7u;
63
TIMx->CCMR2 &= 0xF7F7u;
64
LL_TIM_OC_SetCompareCH1( TIMx, pHandle->Half_PWMPeriod );
65
LL_TIM_OC_SetCompareCH2( TIMx, pHandle->Half_PWMPeriod );
66
LL_TIM_OC_SetCompareCH3( TIMx, pHandle->Half_PWMPeriod );
67
68
/* Enable TIMx preload */
69
TIMx->CCMR1 |= 0x0808u;
70
TIMx->CCMR2 |= 0x0808u;
71
72
/* It re-enable drive of TIMx CHy and CHyN by TIMx CHyRef*/
73
TIMx->CCER |= 0x555u;
74 }
TIMx->CCER &= TIMxCCER_MASK;这是同时关闭定时器的所有输出通道。
pFctGetPhaseCurrents 指向 AB 相的采样函数,同时 ADC1,ADC2 也修改为采集 AB
相的电流值。R3F4XX_SwitchOnPWM 用于启动定时器,注意这个时候是没有使能
PWM 输出的(所有 MOS 管都是关闭的),只是启动定时器而已,从而触发 ADC
采样,触发 AD 采样之后会进入 ADC 中断,然后按照正常的 FOC 算法执行,但是
这个时候是没有输出的,只是在读取电流的时候调用 pFctGetPhaseCurrents 采样
两相电流。后面的 while 循环则是等待 16 次采样完成(NB_CONVERSIONS=16)。
然后用同样的流程采集 C 相的电流。
然后对三相采样值右移 3 位(在采样的时候已经对采样值累加 16 次)。有很
多人都不能理解这一步,经常会问到为什么采样 16 次却只是右移 3 位。这不是
常用的求平均值的做法,这一点需要结合 STM32F4 的 ADC 数据寄存器来理解。
ADC 的分辨率是 12 位,并且是左对齐,数据位的分布如图 20-33 所示,注
入通道的转换数据将减去 ADC_JOFRx 寄存器中写入的用户自定义偏移量,因此
结果可以是一个负值,SEXT 表示扩展的符号值。
图 20-33 12 位数据的左对齐
STM32 技术开发手册
www.ing10bbs.com
首先假设采样值都非常稳定, 每一次采样都是平均值,设为𝑥𝐴𝑣𝑔 ,最终得
到的偏移值为𝑂𝑓𝑓𝑠𝑒𝑡,右移三位相当于除以 8。
公式 20-37 实际采样偏置值
𝑂𝑓𝑓𝑠𝑒𝑡 =
16𝑥𝐴𝑣𝑔
8
在实际应用的时候,会对实时采样值乘 2 再用于计算电流值。假设实际采样
值是𝑥。那么有:
公式 20-38 实际使用的 AD 采样值
𝑂𝑓𝑓𝑠𝑒𝑡 − 2𝑥 =
16𝑥𝐴𝑣𝑔
− 2𝑥 = 2(𝑥𝐴𝑣𝑔 − 𝑥)
8
16𝑥𝐴𝑣𝑔 是将平均值左移了 4 位,再除以 8 就是右移 3 位,得到的𝑂𝑓𝑓𝑠𝑒𝑡是
一个不包含 SEXT 位的完整 16 位数据,只是低 4 位是 0,数值是平均值的 2 倍。
实际上这样做的原因,应该是在计算平均值之后,为了将数据位扩展到 16 位,
所以再乘 2,结果就变成了右移 3 位,而不是 4 位。在每一次采样之后都会将采
样值乘 2 再计算,最终得到的采样值就可以认为是一个 16 位的完整采样数据(低
4 位没任何意义)。
采集到校准值之后就重新恢复定时器的配置。
R3F4XX_GetPhaseCurrents()
获取相电流。首先根据扇区进行读取 ADC 的采样值,根据七段式 SVPWM 的
开关切换顺序可以知道,每一个扇区都有一相是无法采样到电流的,只能采样两
相电流,所以在 switch 里面就将采样相同的扇区放在一起了。
代码 20-293 读取相电流
01 void R3F4XX_GetPhaseCurrents( PWMC_Handle_t * pHdl, Curr_Components *
pStator_Currents )
02 {
03
uint8_t bSector;
04
int32_t wAux;
05
PWMC_R3_F4_Handle_t * pHandle = ( PWMC_R3_F4_Handle_t * )pHdl;
06
07
/* Deactivate TIMx CH4 to disable next triggers using bit-banding
access */
08
BB_REG_BIT_CLR ( &pHandle->pParams_str->TIMx->CCER,
TIM_CCER_CC4E_Pos );
09
10
/* Reset the SOFOC flag to indicate the start of FOC algorithm*/
11
pHandle->bSoFOC = 0u;
12
13
bSector = pHandle->_Super.hSector;
STM32 技术开发手册
www.ing10bbs.com
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
switch ( bSector ) {
case SECTOR_4:
case SECTOR_5:
/* Current on Phase C is not accessible
*/
/* Ia = PhaseAOffset - ADC converted value) */
wAux = ( int32_t )( ADC1->JDR1 );
wAux *= 2;
wAux = ( int32_t )( pHandle->wPhaseAOffset ) - wAux;
/* Saturation of Ia */
if ( wAux < -INT16_MAX ) {
pStator_Currents->qI_Component1 = -INT16_MAX;
} else if ( wAux > INT16_MAX ) {
pStator_Currents->qI_Component1 = INT16_MAX;
} else {
pStator_Currents->qI_Component1 = ( int16_t )wAux;
}
/* Ib = PhaseBOffset - ADC converted value) */
wAux = ( int32_t )( ADC2->JDR1 );
wAux *= 2;
wAux = ( int32_t )( pHandle->wPhaseBOffset ) - wAux;
/* Saturation of Ib */
if ( wAux < -INT16_MAX ) {
pStator_Currents->qI_Component2 = -INT16_MAX;
} else if ( wAux > INT16_MAX ) {
pStator_Currents->qI_Component2 = INT16_MAX;
} else {
pStator_Currents->qI_Component2 = ( int16_t )wAux;
}
break;
case SECTOR_6:
case SECTOR_1:
/* Current on Phase A is not accessible
*/
/* Ib = PhaseBOffset - ADC converted value) */
wAux = ( int32_t )( ADC1->JDR1 );
wAux *= 2;
wAux = ( int32_t )( pHandle->wPhaseBOffset ) - wAux;
/* Saturation of Ib */
if ( wAux < -INT16_MAX ) {
pStator_Currents->qI_Component2 = -INT16_MAX;
} else if ( wAux > INT16_MAX ) {
pStator_Currents->qI_Component2 = INT16_MAX;
} else {
pStator_Currents->qI_Component2 = ( int16_t )wAux;
}
/* Ia = -Ic -Ib */
wAux = ( int32_t )( ADC2->JDR1 );
wAux *= 2;
wAux -= ( int32_t )pHandle->wPhaseCOffset;
wAux -= ( int32_t )pStator_Currents->qI_Component2;
/* Saturation of Ia */
if ( wAux > INT16_MAX ) {
pStator_Currents->qI_Component1 = INT16_MAX;
} else if ( wAux < -INT16_MAX ) {
pStator_Currents->qI_Component1 = -INT16_MAX;
} else {
pStator_Currents->qI_Component1 = ( int16_t )wAux;
}
break;
case SECTOR_2:
STM32 技术开发手册
www.ing10bbs.com
82
case SECTOR_3:
83
/* Current on Phase B is not accessible
*/
84
/* Ia = PhaseAOffset - ADC converted value) */
85
wAux = ( int32_t )( ADC1->JDR1 );
86
wAux *= 2;
87
wAux = ( int32_t )( pHandle->wPhaseAOffset ) - wAux;
88
89
/* Saturation of Ia */
90
if ( wAux < -INT16_MAX ) {
91
pStator_Currents->qI_Component1 = -INT16_MAX;
92
} else if ( wAux > INT16_MAX ) {
93
pStator_Currents->qI_Component1 = INT16_MAX;
94
} else {
95
pStator_Currents->qI_Component1 = ( int16_t )wAux;
96
}
97
98
/* Ib = -Ic -Ia */
99
wAux = ( int32_t )( ADC2->JDR1 );
100
wAux *= 2;
101
wAux -= ( int32_t )pHandle->wPhaseCOffset;
102
wAux -= ( int32_t )pStator_Currents->qI_Component1;
103
104
/* Saturation of Ib */
105
if ( wAux > INT16_MAX ) {
106
pStator_Currents->qI_Component2 = INT16_MAX;
107
} else if ( wAux < -INT16_MAX ) {
108
pStator_Currents->qI_Component2 = -INT16_MAX;
109
} else {
110
pStator_Currents->qI_Component2 = ( int16_t )wAux;
111
}
112
break;
113
114
default:
115
break;
116
}
117
118
pHandle->_Super.hIa = pStator_Currents->qI_Component1;
119
pHandle->_Super.hIb = pStator_Currents->qI_Component2;
120
pHandle->_Super.hIc = -pStator_Currents->qI_Component1 pStator_Currents->qI_Component2;
121 }
注意这里的采样值是乘上 2 之后再参与计算的。在采样到两相电流之后,就
可以计算出第三相电流值了。
R3F4XX_HFCurrentsCalibrationAB()
这是 AB 相电流转准采样函数,主要是读取 AB 相的电流采样值,并累加起
来。
代码 20-294 读取 AB 相的采样值
01 static void R3F4XX_HFCurrentsCalibrationAB( PWMC_Handle_t * pHdl,
Curr_Components * pStator_Currents )
02 {
03
PWMC_R3_F4_Handle_t * pHandle = ( PWMC_R3_F4_Handle_t * )pHdl;
04
05
/* Deactivate TIMx CH4 to disable next triggers using bit-banding
access */
06
BB_REG_BIT_CLR ( &pHandle->pParams_str->TIMx->CCER,
TIM_CCER_CC4E_Pos );
07
STM32 技术开发手册
www.ing10bbs.com
08
09
10
11
12
13
14
15
16 }
/* Reset the SOFOC flag to indicate the start of FOC algorithm*/
pHandle->bSoFOC = 0u;
if ( pHandle->bIndex < NB_CONVERSIONS ) {
pHandle-> wPhaseAOffset += ADC1->JDR1;
pHandle-> wPhaseBOffset += ADC2->JDR1;
pHandle->bIndex++;
}
R3F4XX_HFCurrentsCalibrationC()
C 相电流校准采样函数。每一次调用就读取两次 C 相的电流采样值。
代码 20-295 读取 C 相的采样值
01 static void R3F4XX_HFCurrentsCalibrationC( PWMC_Handle_t * pHdl,
Curr_Components * pStator_Currents )
02 {
03
PWMC_R3_F4_Handle_t * pHandle = ( PWMC_R3_F4_Handle_t * )pHdl;
04
05
/* Deactivate TIMx CH4 to disable next triggers using bit-banding
access */
06
BB_REG_BIT_CLR ( &pHandle->pParams_str->TIMx->CCER,
TIM_CCER_CC4E_Pos );
07
08
/* Reset the SOFOC flag to indicate the start of FOC algorithm*/
09
pHandle->bSoFOC = 0u;
10
11
if ( pHandle->bIndex < NB_CONVERSIONS / 2u ) {
12
pHandle-> wPhaseCOffset += ADC1->JDR1;
13
pHandle-> wPhaseCOffset += ADC2->JDR1;
14
pHandle->bIndex++;
15
}
16 }
R3F4XX_TurnOnLowSides()
导通低端的 MOS 管,如字面意思,设置占空比位 0,利用互补性质,使低端
的 MOS 管导通。
代码 20-296 导通低端 MOS 管
01 void R3F4XX_TurnOnLowSides( PWMC_Handle_t * pHdl )
02 {
03
PWMC_R3_F4_Handle_t * pHandle = ( PWMC_R3_F4_Handle_t * )pHdl;
04
TIM_TypeDef * TIMx = pHandle->pParams_str->TIMx;
05
06
pHandle->_Super.bTurnOnLowSidesAction = true;
07
08
/* Clear Update Flag */
09
LL_TIM_ClearFlag_UPDATE( TIMx );
10
11
/*Turn on the three low side switches */
12
LL_TIM_OC_SetCompareCH1( TIMx, 0 );
13
LL_TIM_OC_SetCompareCH2( TIMx, 0 );
14
LL_TIM_OC_SetCompareCH3( TIMx, 0 );
15
16
/* Wait until next update */
17
while ( LL_TIM_IsActiveFlag_UPDATE( TIMx ) == RESET ) {
18
}
STM32 技术开发手册
www.ing10bbs.com
19
20
/* Main PWM Output Enable */
21
LL_TIM_EnableAllOutputs( TIMx );
22
if ( ( pHandle->pParams_str->LowSideOutputs ) == ES_GPIO ) {
23
LL_GPIO_SetOutputPin( pHandle->pParams_str->pwm_en_u_port,
pHandle->pParams_str->pwm_en_u_pin );
24
LL_GPIO_SetOutputPin( pHandle->pParams_str->pwm_en_v_port,
pHandle->pParams_str->pwm_en_v_pin );
25
LL_GPIO_SetOutputPin( pHandle->pParams_str->pwm_en_w_port,
pHandle->pParams_str->pwm_en_w_pin );
26
}
27
return;
28 }
29
ES_GPIO 是用于具有使能引脚的驱动芯片,应用于 st 推出的驱动板,所以在
这里是无意义的。
R3F4XX_SwitchOnPWM()
如字面意思,使控制 PWM 的开关切换到 ON 的状态,其实就是使能定时器
输出,但是实际 PWM 输出还有多重因素影响,所以实际并不一点有 PWM 脉冲
输出。
代码 20-297 启动 PWM 输出
01 void R3F4XX_SwitchOnPWM( PWMC_Handle_t * pHdl )
02 {
03
PWMC_R3_F4_Handle_t * pHandle = ( PWMC_R3_F4_Handle_t * )pHdl;
04
TIM_TypeDef * TIMx = pHandle->pParams_str->TIMx;
05
06
pHandle->_Super.bTurnOnLowSidesAction = false;
07
08
/* It clears ADCs JSTRT and JEOC bits */
09
ADC1->SR &= ~ADC_SR_MASK;
10
ADC2->SR &= ~ADC_SR_MASK;
11
12
/* Clear Update Flag */
13
LL_TIM_ClearFlag_UPDATE( TIMx );
14
15
LL_TIM_EnableIT_UPDATE( TIMx );
16
17
/* Main PWM Output Enable */
18
LL_TIM_EnableAllOutputs( TIMx );
19
if ( ( pHandle->pParams_str->LowSideOutputs ) == ES_GPIO ) {
20
if ( ( TIMx->CCER & TIMxCCER_MASK_CH123 ) != 0u ) {
21
LL_GPIO_SetOutputPin( pHandle->pParams_str->pwm_en_u_port,
pHandle->pParams_str->pwm_en_u_pin );
22
LL_GPIO_SetOutputPin( pHandle->pParams_str->pwm_en_v_port,
pHandle->pParams_str->pwm_en_v_pin );
23
LL_GPIO_SetOutputPin( pHandle->pParams_str->pwm_en_w_port,
pHandle->pParams_str->pwm_en_w_pin );
24
} else {
25
/* It is executed during calibration phase the EN signal shall
stay off */
26
LL_GPIO_ResetOutputPin( pHandle->pParams_str->pwm_en_u_port,
pHandle->pParams_str->pwm_en_u_pin );
27
LL_GPIO_ResetOutputPin( pHandle->pParams_str->pwm_en_v_port,
pHandle->pParams_str->pwm_en_v_pin );
28
LL_GPIO_ResetOutputPin( pHandle->pParams_str->pwm_en_w_port,
pHandle->pParams_str->pwm_en_w_pin );
STM32 技术开发手册
www.ing10bbs.com
29
30
31
32 }
}
}
return;
LL_TIM_EnableIT_UPDATE;使能定时器更新中断。LL_TIM_EnableAllOutputs 就
是将定时器的 MOE 位置 1。
R3F4XX_SwitchOffPWM()
关断 PWM 输出。将定时器的所有通道都关断输出,也关断了所有 MOS 管。
代码 20-298 关断 PWM 输出
01 void R3F4XX_SwitchOffPWM( PWMC_Handle_t * pHdl )
02 {
03
PWMC_R3_F4_Handle_t * pHandle = ( PWMC_R3_F4_Handle_t * )pHdl;
04
TIM_TypeDef * TIMx = pHandle->pParams_str->TIMx;
05
06
pHandle->_Super.bTurnOnLowSidesAction = false;
07
08
/* Disable UPDATE ISR */
09
LL_TIM_DisableIT_UPDATE( TIMx );
10
11
TIMx->CCER &= ( uint16_t )( ~TIMxCCER_MASK_CH123 );
12
13
while ( LL_TIM_IsActiveFlag_UPDATE( TIMx ) == RESET ) {
14
if ( LL_TIM_IsEnabledIT_UPDATE( TIMx ) ) {
15
break;
16
}
17
}
18
19
/* Main PWM Output Disable */
20
LL_TIM_DisableAllOutputs( TIMx );
21
if ( ( pHandle->pParams_str->LowSideOutputs ) == ES_GPIO ) {
22
LL_GPIO_ResetOutputPin( pHandle->pParams_str->pwm_en_u_port,
pHandle->pParams_str->pwm_en_u_pin );
23
LL_GPIO_ResetOutputPin( pHandle->pParams_str->pwm_en_v_port,
pHandle->pParams_str->pwm_en_v_pin );
24
LL_GPIO_ResetOutputPin( pHandle->pParams_str->pwm_en_w_port,
pHandle->pParams_str->pwm_en_w_pin );
25
}
26
TIMx->CCER |= TIMxCCER_MASK_CH123;
27
28
return;
29 }
TIMx->CCER &= ( uint16_t )( ~TIMxCCER_MASK_CH123 );用于禁止所有通道输
出,LL_TIM_DisableAllOutputs 用于将 MOE 位置 0。
R3F4XX_WriteTIMRegisters()
写定时器的寄存器,实际是写比较器,也就是设置 PWM 的占空比。
代码 20-299 写定时器的寄存器
01 static uint16_t R3F4XX_WriteTIMRegisters( PWMC_Handle_t * pHdl )
02 {
03
uint16_t hAux;
STM32 技术开发手册
www.ing10bbs.com
04
PWMC_R3_F4_Handle_t * pHandle = ( PWMC_R3_F4_Handle_t * )pHdl;
05
TIM_TypeDef * TIMx = pHandle->pParams_str->TIMx;
06
07
TIMx->CCR1 = pHandle->_Super.hCntPhA;
08
TIMx->CCR2 = pHandle->_Super.hCntPhB;
09
TIMx->CCR3 = pHandle->_Super.hCntPhC;
10
11
/* Limit for update event */
12
/* Check the status of SOFOC flag. If it is set, an update event has
occurred
13
and thus the FOC rate is too high */
14
if ( pHandle->bSoFOC != 0u ) {
15
hAux = MC_FOC_DURATION;
16
} else {
17
hAux = MC_NO_ERROR;
18
}
19
return hAux;
20 }
在定时器的更新中断中会将 bSoFOC 置 1,如果在设置占空比之前就已经被
置 1 了,说明 FOC 算法的计算时间太长,那么就会提示 MC_FOC_DURATION 错
误。
R3F4XX_SetADCSampPointSect1()
R3F4XX_SetADCSampPointSect2()
R3F4XX_SetADCSampPointSect3()
R3F4XX_SetADCSampPointSect4()
R3F4XX_SetADCSampPointSect5()
R3F4XX_SetADCSampPointSect6()
以上六个函数,根据设置的合成电压矢量所在扇区设置下一个 PWM 周期的
采样点,也就是 TIMx 的 CH4 比较值。在任意一个 PWM 周期只采样两相电流,
在条件允许的情况下默认只采样 AB 相,然后计算 C 相电流值,以避免不连续采
样电流。采样时刻总是会在下桥臂导通的时候采样,只有下桥臂导通的时候才会
有电流。
STM32 技术开发手册
www.ing10bbs.com
图 20-34 SVPWM 相电压波形
从图 20-34 可以看到每个扇区都有一相总是处于高占空比输出状态,也就是
该相输出 PWM 占空比总是最大值,所以只能采集另外两个通道的电流(占空比
输出最大值,说明下桥臂导通时间最短,不适合采样)。在扇区 4 和 5 不使用 C
相采样电阻;扇区 6 和 1 不使用 A 相采样电阻;扇区 2 和 3 不使用 B 相采样电
阻。下面以 Sector4 为例说明采样位置。
代码 20-300 Sector4 采样点设置
01 uint16_t R3F4XX_SetADCSampPointSect4( PWMC_Handle_t * pHdl )
02 {
03
uint16_t hCntSmp;
04
uint16_t hDeltaDuty;
05
uint32_t adcTrig = LL_ADC_INJ_TRIG_EXT_RISING;
06
PWMC_R3_F4_Handle_t * pHandle = ( PWMC_R3_F4_Handle_t * )pHdl;
07
TIM_TypeDef * TIMx = pHandle->pParams_str->TIMx;
08
09
pHandle->wADCTriggerSet &= ~LL_ADC_INJ_TRIG_EXT_RISINGFALLING;
10
11
if ( ( uint16_t )( pHandle->Half_PWMPeriod pHandle->_Super.hCntPhC ) > pHandle->pParams_str->hTafter ) {
12
pHandle->_Super.hSector = SECTOR_4;
13
14
hCntSmp = pHandle->Half_PWMPeriod - 1u;
15
16
pHandle->wADC1Channel = PHASE_A_MSK;
17
pHandle->wADC2Channel = PHASE_B_MSK;
18
} else {
19
hDeltaDuty = ( uint16_t )( pHandle->_Super.hCntPhC pHandle->_Super.hCntPhB );
20
STM32 技术开发手册
www.ing10bbs.com
21
if ( hDeltaDuty > ( uint16_t )( pHandle->Half_PWMPeriod pHandle->_Super.hCntPhC ) * 2u ) {
22
hCntSmp = pHandle->_Super.hCntPhC pHandle->pParams_str->hTbefore;
23
} else {
24
hCntSmp = pHandle->_Super.hCntPhC +
pHandle->pParams_str->hTafter;
25
26
if ( hCntSmp >= pHandle->Half_PWMPeriod ) {
27
adcTrig = LL_ADC_INJ_TRIG_EXT_FALLING;
28
29
hCntSmp = ( 2u * pHandle->Half_PWMPeriod ) - hCntSmp - 1u;
30
}
31
}
32
pHandle->wADC1Channel = PHASE_A_MSK;
33
pHandle->wADC2Channel = PHASE_B_MSK;
34
}
35
36
/* set ADC trigger edge */
37
pHandle->wADCTriggerSet |= adcTrig;
38
/* Set TIMx_CH4 value */
39
TIMx->CCR4 = hCntSmp;
40
41
return R3F4XX_WriteTIMRegisters( &pHandle->_Super );
42 }
在 Sector4 只会在下桥臂导通的时候采样 A、B 两相电流,但三相 PWM 通道
的下桥臂导通时间都不一样,具体是什么时刻采样,这个就要分情况来考虑,同
时也要考虑避免由于 MOS 管开关动作而导致的噪声影响。
在代码中,hTafter 是 PWM 死区时间和最大的 MOS 管开关时间之和,
hTbefore 则是 ADC 的采样和转换时间。
⚫
C 相下桥臂导通时间的大于 hTafter 的两倍
一个周期内 C 相下桥臂导通时间的一半用 Half_PWMPeriod- hCntPhC 表示,
定时器是中心对齐模式,Half_PWMPeriod 也就是图 20-35 中 CNT 的最大值。
hCntPhC 则是 CCR3 比较值,定时器的三个通道是 PWM1 模式,当 CNT 小于 CCRx
的时候,CHx 输出高电平,反之则是低电平,当 CHx 是低电平的时候,就是下桥
臂导通的时候,如图 20-35 三相 PWM 有颜色填充那部分就是下桥臂导通时刻。
虽然不采样 C 相电流,但是由于 C 相 MOS 管的开关动作也会对电流波形造
成影响,所以要尽量避免 MOS 管动作的时候采样,而 C 相则是下桥臂导通时间
最短,如果连 C 相的下桥臂导通时间都大于 2 倍的 hTafter,那么说明在计数器
向上计数溢出的时候,电流处于稳定状态,这种情况下,无论是任何扇区,A、
B 相的下桥臂导通时间都十分充足,所以采样点设置为 Half_PWMPeriod – 1,并
且 ADC1/2 采样通道就是 A、B 两相。
STM32 技术开发手册
www.ing10bbs.com
C 相下桥臂导通时间的大于 hTafter 的两倍,其实是指 C 相下桥臂的电流波
形稳定的时间是在 Half_PWMPeriod 之前,这样在这样就有绝对足够的时间对电
流采样,所以判断标准是(Half_PWMPeriod - hCntPhC) > hTafter。采样点设置是
Half_PWMPeriod – 1。图 20-35 中箭头所指就是采样点。
图 20-35 (Half_PWMPeriod- hCntPhC)> hTafter
⚫
C 相下桥臂导通时间小于 hTafter 的两倍
当 C 相下桥臂导通时间趋于最小值的时候,会出现小于 hTafter 两倍的情况,
也就是 hTafter > (Half_PWMPeriod - hCntPhC)。这个时候又分两种情况,其中一
STM32 技术开发手册
www.ing10bbs.com
种就是 C 相下桥臂的导通时长过短,无法在 C 相下桥臂导通的时候对 A、B 相采
样,这个时候可以在 C 相导通之前采样,如图 20-36 所示,在 C 相下桥臂导通之
前就采样的前提是从 B 相下桥臂导通到 C 相下桥臂导通之间有足够长的时间。
使用 hDeltaDuty 表示 B 相下桥臂导通之后到 C 相下桥臂导通的时间,也就
是图 20-36 的 A,(Half_PWMPeriod- hCntPhC)*2 代表 C 相下桥臂导通的时间,也
就是图 20-36 的 B。当 A>B 的时候,就可以设定采样点在 C 相下桥臂导通之前,
所以判断标准是 hDeltaDuty > (Half_PWMPeriod - hCntPhC ) * 2。采样点设置为
hCntPhC – hTbefore。图 20-36 中箭头所指就是采样点。
图 20-36 hDeltaDuty > (Half_PWMPeriod - hCntPhC ) * 2
⚫
C 相下桥臂导通之前不够时间采样
在 B、C 相下桥臂之间如果不满足 hDeltaDuty > ( Half_PWMPeriod - hCntPhC )
* 2 条件,那么就只能在 C 相下桥臂导通之后,下次关闭之前采样,也就是要在
下桥臂导通并且电流波形稳定之后采样,这个时候的采样点是 hCntPhC + hTafter。
并且由于 hTafter > ( Half_PWMPeriod - hCntPhC ),所以这个时候的采样点必然是
STM32 技术开发手册
www.ing10bbs.com
在计数器向下计数的时候,如图 20-37 所示。那么实际的采样点时刻就是 ( 2 *
Half_PWMPeriod ) - hCntSmp – 1。并且由于 CH4 是 PWM2 模式,在计数器向下
计数的时候是下降沿,所以还需要将 ADC 的触发源改为下降沿触发,如图 20-38。
图 20-37 hDeltaDuty < (Half_PWMPeriod - hCntPhC ) * 2
STM32 技术开发手册
www.ing10bbs.com
图 20-38 采样点
R3F4XX_TIMx_UP_IRQHandler()
定时器更新中断处理函数,该函数在定时器更新中断中被调用。由于定时器
是中心对齐模式,在向上计数和向下计数的时候都会触发更新事件,所以重复计
数器设置为 1 之后,就变成了定时器只会在向下计数溢出的时候才触发更新事
件。
bSoFOC 标记着已经触发更新事件,这个标记位在 FOC 算法开始执行(读取
采样电流值)的时候就清 0,在设置定时器比较值的时候判断是否为 1,也就是
说如果在执行 FOC 算法的时候触发更新中断,在下一个 PWM 更新之前还没有计
算好各项通道的比较值,那就报错。可以通过降低 PWM 频率来解决这个问题。
图 20-39 更新中断的时机
STM32 技术开发手册
www.ing10bbs.com
代码 20-301 定时器更新事件处理
01 void * R3F4XX_TIMx_UP_IRQHandler( PWMC_R3_F4_Handle_t * pHandle )
02 {
03
uint32_t wADCInjFlags;
04
TIM_TypeDef * TIMx = pHandle->pParams_str->TIMx;
05
06
/* Set the SOFOC flag to indicate the execution of Update IRQ*/
07
pHandle->bSoFOC = 1u;
08
09
wADCInjFlags = ( ADC1-> SR ) & ADC_SR_MASK;
10
11
if ( wADCInjFlags == CONV_STARTED ) {
12
do {
13
wADCInjFlags = ( ADC1-> SR ) & ADC_SR_MASK;
14
} while ( wADCInjFlags != CONV_FINISHED );
15
} else if ( wADCInjFlags == FLAGS_CLEARED ) {
16
while ( ( TIMx->CNT ) < ( pHandle->pParams_str->Tw ) ) {
17
}
18
wADCInjFlags = ( ADC1-> SR ) & ADC_SR_MASK;
19
20
if ( wADCInjFlags == CONV_STARTED ) {
21
do {
22
wADCInjFlags = ( ADC1-> SR ) & ADC_SR_MASK;
23
} while ( wADCInjFlags != CONV_FINISHED );
24
}
25
} else {}
26
27
/* Switch Context */
28
/* Disabling trigger to avoid unwanted conversion */
29
ADC1->CR2 = pHandle->wADCTriggerUnSet;
30
ADC2->CR2 = pHandle->wADCTriggerUnSet;
31
32
/* Enabling next Trigger */
33
TIMx->CCER |= 0x1000u;
34
35
/* It re-initilize AD converter in run time when using dual MC */
36
ADC1->CR2 = pHandle->wADCTriggerSet;
37
ADC2->CR2 = pHandle->wADCTriggerSet;
38
39
/* Change channels keeping equal to 1 element the sequencer length */
40
ADC1->JSQR = pHandle->wADC1Channel;
41
ADC2->JSQR = pHandle->wADC2Channel;
42
43
return &( pHandle->_Super.bMotor );
44 }
在更改 ADC 寄存器配置之前,先等待当前还在转换的通道转换完成,然后
才重新设置 ADC 的触发源和触发采样通道。
R3F4XX_BRK_IRQHandler()
刹车中断处理,这个函数实际上并没有用到,在使用刹车输入功能的时候,
在固定引脚上输入有效电平,将会立即切断所有 PWM 输出。然后再进入这个函
数,这里会将一下引脚设置为低电平,实际上这些引脚是 st 推出的驱动板上的
PWM 使能输出控制引脚,对于例程根本没有任何影响。
STM32 技术开发手册
www.ing10bbs.com
代码 20-302 刹车中断处理
01 void * R3F4XX_BRK_IRQHandler( PWMC_R3_F4_Handle_t * pHandle )
02 {
03
if ( ( pHandle->pParams_str->LowSideOutputs ) == ES_GPIO ) {
04
LL_GPIO_ResetOutputPin( pHandle->pParams_str->pwm_en_u_port,
pHandle->pParams_str->pwm_en_v_pin );
05
LL_GPIO_ResetOutputPin( pHandle->pParams_str->pwm_en_v_port,
pHandle->pParams_str->pwm_en_u_pin );
06
LL_GPIO_ResetOutputPin( pHandle->pParams_str->pwm_en_w_port,
pHandle->pParams_str->pwm_en_w_pin );
07
}
08
pHandle->OverCurrentFlag = true;
09
10
return &( pHandle->_Super.bMotor );
11 }
R3F4XX_ExecRegularConv()
用于执行某个通道的规则转换,前提是由注册的规则转换,这个函数没有被
调用。
R3F4XX_ADC_SetSamplingTime()
按照注释的意思是给特别的通道设置采样时间,但实际上这个函数没有别调
用。
R3F4XX_IsOverCurrentOccurred()
检查是否过流。虽然这个函数有被调用,但是实际例程没有使用定时器的刹
车输入功能,所以这个函数也是没有意义的。
代码 20-303 检查是否过流
01 uint16_t R3F4XX_IsOverCurrentOccurred( PWMC_Handle_t * pHdl )
02 {
03
PWMC_R3_F4_Handle_t * pHandle = ( PWMC_R3_F4_Handle_t * )pHdl;
04
05
uint16_t retVal = MC_NO_FAULTS;
06
if ( pHandle->OverCurrentFlag == true ) {
07
retVal = MC_BREAK_IN;
08
pHandle->OverCurrentFlag = false;
09
}
10
return retVal;
11 }
12
STM32 技术开发手册
www.ing10bbs.com
R3F4XX_RLDetectionModeEnable()
R3F4XX_RLDetectionModeDisable()
R3F4XX_RLDetectionModeSetDuty()
R3F4XX_RLGetPhaseCurrents()
R3F4XX_RLTurnOnLowSides()
R3F4XX_RLSwitchOnPWM()
以上这里函数用于检测电机的电阻和电感,这些只是一些底层的操作配置而
已,由于缺乏实际应用的算法,这部分内容没有必要去理解。
dac_ui.c 文件内容
该组件属于 UI 功能,主要用于示波器显示 FOC 的计算结果,DAC 的输出引
脚有两个,分别是 PA4,PA5,由于 PA4 在无刷电机接口 2 上,所以基本上是无
法连接的,所以只有 PA5 可用。
DAC_Init()
该函数用于初始化 DAC 外设,使能两个通道输出。
代码 20-304 初始化 DAC 外设
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
void DAC_Init(UI_Handle_t *pHandle)
{
DAC_UI_Handle_t *pDacHandle = (DAC_UI_Handle_t *)pHandle;
if (pDacHandle->hDAC_CH1_ENABLED == ENABLE) {
/* Enable DAC Channel1 */
LL_DAC_Enable(DAC1, LL_DAC_CHANNEL_1);
}
#if defined(DAC_CHANNEL2_SUPPORT)
if (pDacHandle->hDAC_CH2_ENABLED == ENABLE) {
/* Enable DAC Channel2 */
LL_DAC_Enable(DAC1, LL_DAC_CHANNEL_2);
}
#endif
}
STM32 技术开发手册
www.ing10bbs.com
DAC_Exec()
DAC 的底层执行函数,更新 DAC 输出。在每次执行 FOC 算法之后就会被调
用。
代码 20-305 DAC 更新输出
01 void DAC_Exec(UI_Handle_t *pHandle)
02 {
03
DAC_UI_Handle_t *pDacHandle = (DAC_UI_Handle_t *)pHandle;
04
MC_Protocol_REG_t bCh_var;
05
06
if (pDacHandle->hDAC_CH1_ENABLED == ENABLE) {
07
bCh_var = pDacHandle->bChannel_variable[DAC_CH0];
08
LL_DAC_ConvertData12LeftAligned(DAC1, LL_DAC_CHANNEL_1,
09
DACOFF +
((int16_t)UI_GetReg(pHandle,bCh_var)));
10
LL_DAC_TrigSWConversion(DAC1, LL_DAC_CHANNEL_1);
11
}
12 #if defined(DAC_CHANNEL2_SUPPORT)
13
if (pDacHandle->hDAC_CH2_ENABLED == ENABLE) {
14
bCh_var = pDacHandle->bChannel_variable[DAC_CH1];
15
LL_DAC_ConvertData12LeftAligned(DAC1, LL_DAC_CHANNEL_2,
16
DACOFF +
((int16_t)UI_GetReg(pHandle,bCh_var)));
17
LL_DAC_TrigSWConversion(DAC1, LL_DAC_CHANNEL_2);
18
19
}
20 #endif
21 }
dac_common_ui.c 文件内容
DAC 的通用组件功能,就是人机界面的接口。通过这个文件的函数,可以设
置 DAC 输出通道输出的内容。
在上位机上是可以选 DAC 通道的输出内容,这里就是实现选择通道输出的
函数。
图 20-40 DAC 设置
DAC_SetChannelConfig()
设置 DAC 输出通道,bChannel 是 DAC 输出通道,可以是 PA4 或者 PA5。
bVariable 是 DAC 输出参数,也就是图 20-40 所选择的输出内容。
STM32 技术开发手册
www.ing10bbs.com
代码 20-306 设置 DAC 输出通道
01 void DAC_SetChannelConfig(UI_Handle_t *pHandle, DAC_Channel_t bChannel,
02
MC_Protocol_REG_t bVariable)
03 {
04
DAC_UI_Handle_t *pDacHandle = (DAC_UI_Handle_t *)pHandle;
05
pDacHandle->bChannel_variable[bChannel] = bVariable;
06 }
DAC_GetChannelConfig()
获取当前 DAC 输出通道的输出内容。
代码 20-307 获取 DAC 输出内容
01 MC_Protocol_REG_t DAC_GetChannelConfig(UI_Handle_t *pHandle,
DAC_Channel_t bChannel)
02 {
03
DAC_UI_Handle_t *pDacHandle = (DAC_UI_Handle_t *)pHandle;
04
return (pDacHandle->bChannel_variable[bChannel]);
05 }
DAC_SetUserChannelValue()
设置用户自定义的数据值。DAC 输出支持用户自己定义输出数据,这里就是
设置自定义输出的数据值。
代码 20-308 设置用户自定义数据
01 void DAC_SetUserChannelValue(UI_Handle_t *pHandle, uint8_t bUserChNumber,
02
int16_t hValue)
03 {
04
DAC_UI_Handle_t *pDacHandle = (DAC_UI_Handle_t *)pHandle;
05
pDacHandle->hUserValue[bUserChNumber] = hValue;
06 }
DAC_GetUserChannelValue()
DAC 获取用于用户自定义的数据值。
代码 20-309 获取用户通道数据值
01 int16_t DAC_GetUserChannelValue(UI_Handle_t *pHandle, uint8_t
bUserChNumber)
02 {
03
DAC_UI_Handle_t *pDacHandle = (DAC_UI_Handle_t *)pHandle;
04
return (pDacHandle->hUserValue[bUserChNumber]);
05 }
Frame_communication_protocol.c 文件内容
面向帧的传输协议。该协议旨在传输电机控制协议的消息,主要的消息有两
个,一个是设置超时时间,另一个是计算验证 CRC 是否正确。
STM32 技术开发手册
www.ing10bbs.com
FCP_Init()
面向帧协议的初始化,初始化发送帧和接收帧的一些特定状态位。
代码 20-310 面向帧协议初始化
01 void FCP_Init( FCP_Handle_t * pHandle )
02 {
03
pHandle->RxTimeoutCountdown = 0;
04
05
pHandle->TxFrame.Code = 0x0;
06
pHandle->TxFrame.Size = 0;
07
pHandle->TxFrame.FrameCRC = 0;
08
pHandle->TxFrameState = FCP_TRANSFER_IDLE;
09
pHandle->TxFrameLevel = 0;
10
11
pHandle->RxFrame.Code = 0x0;
12
pHandle->RxFrame.Size = 0;
13
pHandle->RxFrame.FrameCRC = 0;
14
pHandle->RxFrameState = FCP_TRANSFER_IDLE;
15
pHandle->RxFrameLevel = 0;
16 }
Code,Size,CRC,还有其他的状态为设置为初始状态。
FCP_SetClient()
设置回调函数指针。
代码 20-311
01 void FCP_SetClient( FCP_Handle_t * pHandle,
02
struct MCP_Handle_s * pClient,
03
FCP_SentFrameCallback_t pSentFrameCb,
04
FCP_ReceivedFrameCallback_t pReceviedFrameCb,
05
FCP_RxTimeoutCallback_t pRxTimeoutCb )
06 {
07
if ( MC_NULL != pHandle ) {
08
pHandle->ClientEntity = pClient;
09
pHandle->ClientFrameSentCallback = pSentFrameCb;
10
pHandle->ClientFrameReceivedCallback = pReceviedFrameCb;
11
pHandle->ClientRxTimeoutCallback = pRxTimeoutCb;
12
}
13 }
FCP_SetTimeout()
设置超时的时间。
代码 20-312 设置超时时间
01 void FCP_SetTimeout( FCP_Handle_t * pHandle, uint16_t Timeout )
02 {
03
if ( MC_NULL != pHandle ) {
04
pHandle->RxTimeout = Timeout;
05
}
06 }
07
STM32 技术开发手册
www.ing10bbs.com
FCP_CalcCRC()
CRC 校验函数。虽然名字是叫 CRC 校验,但是从函数的实质内容来看根本就
不是 CRC 校验算法。
代码 20-313 CRC 计算
01 uint8_t FCP_CalcCRC( FCP_Frame_t * pFrame )
02 {
03
uint8_t nCRC = 0;
04
uint16_t nSum = 0;
05
uint8_t idx;
06
07
if ( MC_NULL != pFrame ) {
08
nSum += pFrame->Code;
09
nSum += pFrame->Size;
10
11
for ( idx = 0; idx < pFrame->Size; idx++ ) {
12
nSum += pFrame->Buffer[idx];
13
}
14
15
nCRC = (uint8_t)(nSum & 0xFF) ; // Low Byte of nSum
16
nCRC += (uint8_t) (nSum >> 8) ; // High Byte of nSum
17
}
18
19
return nCRC ;
20 }
将需要校验的数据累加,然后取高 8 位和低 8 位再相加 1 次,得到的就是校
验码。
FCP_IsFrameValid()
该函数用于判断接收帧校验码是否正确,不正确返回 0,实际上该函数已经
被弃用了。
代码 20-314 判断校验码是否正确
01 uint8_t FCP_IsFrameValid( FCP_Frame_t * pFrame )
02 {
03
if ( MC_NULL != pFrame )
04
return FCP_CalcCRC(pFrame) == pFrame->Buffer[pFrame->Size];
05
else
06
return 0;
07 }
08
ui_irq_handler.c 文件内容
该文件建立一个中断向量表,用于管理 ui 的中断执行函数,实际上这个文
件里面的函数已经被弃用。
STM32 技术开发手册
www.ing10bbs.com
usart_frame_communication_protocol.c 文件内容
该组件用于串口通信管理,包括接收和发送中断处理函数。
代码 20-315 全局宏定义和 const 变量
01
02
03
04
05
06
07
08
09
#define
#define
#define
#define
#define
UFCP_IRQ_FLAG_RX
0
UFCP_IRQ_FLAG_TX
1
UFCP_IRQ_FLAG_OVERRUN 2
UFCP_IRQ_FLAG_TIMEOUT 3
UFCP_IRQ_FLAG_ATR
4
static const uint16_t UFCP_Usart_Timeout_none = 0;
static const uint16_t UFCP_Usart_Timeout_start = 1;
static const uint16_t UFCP_Usart_Timeout_stop = 2;
宏定义是没有用到的,可能是遗留产物,const 变量用于函数返回值。可以
当做是宏来使用。
UFCP_RX_IRQ_Handler()
串口接收处理函数,在串口中断中接收一个字节之后就会调用这个函数。首
先是根据 RxFrameState 判断当时是否允许接收状态。
代码 20-316 串口接收数据处理
01 void * UFCP_RX_IRQ_Handler( UFCP_Handle_t * pHandle, unsigned short
rx_data )
02 {
03
void * ret_val = (void *) & UFCP_Usart_Timeout_none;
04
FCP_Handle_t * pBaseHandle = & pHandle->_Super;
05
uint8_t error_code;
06
07
if ( FCP_TRANSFER_IDLE != pBaseHandle->RxFrameState ) {
08
uint8_t rx_byte = (uint8_t) rx_data;
09
10
switch ( pBaseHandle->RxFrameLevel ) {
11
case 0: // First Byte received --> The Code
12
pBaseHandle->RxFrame.Code = rx_byte;
13
/* Need to ask the caller to start our timeout... TODO: Is this
really useful? */
14
ret_val = (void *) & UFCP_Usart_Timeout_start;
15
16
/* Start Rx Timeout */
17
pBaseHandle->RxTimeoutCountdown = pBaseHandle->RxTimeout;
18
pBaseHandle->RxFrameLevel++;
19
break;
20
21
case 1: // Second Byte received --> Size of the payload
22
pBaseHandle->RxFrame.Size = rx_byte;
23
pBaseHandle->RxFrameLevel++;
24
break;
25
26
default: // In the payload or the "CRC"
27
if ( pBaseHandle->RxFrameLevel < pBaseHandle->RxFrame.Size +
FCP_HEADER_SIZE ) {
28
// read byte is for the payload
29
pBaseHandle->RxFrame.Buffer[pBaseHandle->RxFrameLevel FCP_HEADER_SIZE] = rx_byte;
30
pBaseHandle->RxFrameLevel++;
STM32 技术开发手册
www.ing10bbs.com
31
} else {
32
// read byte is for the "CRC"
33
pBaseHandle->RxFrame.FrameCRC = rx_byte;
34
35
/* Need to ask the caller to stop our timeout... TODO: Is
this really useful? */
36
ret_val = (void *) & UFCP_Usart_Timeout_stop;
37
38
/* Stop Rx Timeout */
39
pBaseHandle->RxTimeoutCountdown = 0;
40
/* Disable the reception IRQ */
41
LL_USART_DisableIT_RXNE(pHandle->USARTx);
42
/* Indicate the reception is complete. */
43
pBaseHandle->RxFrameState = FCP_TRANSFER_IDLE;
44
45
/* Check the Control Sum */
46
if ( FCP_CalcCRC( & pBaseHandle->RxFrame ) ==
pBaseHandle->RxFrame.FrameCRC ) {
47
/* OK. the frame is considered correct. Let's forward to
client. */
48
pBaseHandle->ClientFrameReceivedCallback( pBaseHandle->ClientEntity,
49
pBaseHandle->RxFrame.Code,
50
pBaseHandle->RxFrame.Buffer,
51
pBaseHandle->RxFrame.Size );
52
} else {
53
error_code = FCP_MSG_RX_BAD_CRC;
54
(void) UFCP_Send( pBaseHandle, FCP_CODE_NACK, &
error_code, 1 );
55
}
56
}
57
} /* end of switch ( pBaseHandle->RxFrameLevel ) */
58
} /* end of if ( FCP_TRANSFER_IDLE != pBaseHandle->RxFrameState ) */
59
60
return ret_val;
61 }
接收的字节数据是 rx_byte,RxFrameLevel 用于统计接收的字节数,在 switch
语句中,case0 就是接收的第一个字节, 也就是通信协议的 Code 字段,ret_val
是函数返回值,这个时候返回 FCP_Usart_Timeout_start,说明要开始计时。
Case1 就是接收的第二个字节,这个字节是通信协议的 Size 字段,也就是后
面的数据宽度(字节数)。
Default 则是接收到的有效数据,当 RxFrameLevel < (Size + HeaderSize)的时候
就是接收的有效数据,反之则是接收到最后一个字节:校验码。在接收到校验码
之后 ret_val 返回 UFCP_Usart_Timeout_stop,停止计时。
最后计算校验码是否正确,正确就调用 ClientFrameReceivedCallback 函数处
理 通 信 帧 , ClientFrameReceivedCallback 是 一 个 函 数 指 针 , 最 终 指 向
MCP_ReceivedFrame 函数。
通信协议的定义如下:
STM32 技术开发手册
www.ing10bbs.com
图 20-41 通信协议
⚫
FRAME_START 是帧起始字符,占用一个字节其中,高 3 位是电机编号,
低 5 位时 FRAME_ID。
图 20-42 FRAME_START 字节
⚫
PAYLOAD_LENGTH 是协议帧有效数据的长度,就是后面的 payload 的长
度。
⚫
PAYLOAD_ID 是 payload 的第一个字节,根据通信帧的类型来使用,非必
须的。
⚫
PAYLOAD[n]是有效数据帧,根据通信帧的类型来使用,非必须的。
⚫
CRC 是校验码,虽然名字是 CRC,但是实际上并不符合 COC 校验算法。
校验码计算方法可以看图 20-41。
UFCP_TX_IRQ_Handler()
串口发送处理函数。按照前面所说的通信协议,发送一个字节的数据,发送
完成之后会进入串口发送完成中断,然后再次调用这个函数,然后继续发送,直
到所有数据发送完成。
代码 20-317 发送中断处理函数
01 void UFCP_TX_IRQ_Handler( UFCP_Handle_t * pHandle )
02 {
03
FCP_Handle_t * pBaseHandle = & pHandle->_Super;
04
05
if ( FCP_TRANSFER_IDLE != pBaseHandle->TxFrameState ) {
06
uint16_t tx_data;
07
08
switch ( pBaseHandle->TxFrameLevel ) {
09
case 0:
10
tx_data = (uint16_t) pBaseHandle->TxFrame.Code;
STM32 技术开发手册
www.ing10bbs.com
11
break;
12
13
case 1:
14
tx_data = (uint16_t) pBaseHandle->TxFrame.Size;
15
break;
16
17
default:
18
if ( pBaseHandle->TxFrameLevel < pBaseHandle->TxFrame.Size +
FCP_HEADER_SIZE ) {
19
tx_data = (uint16_t)
pBaseHandle->TxFrame.Buffer[ pBaseHandle->TxFrameLevel - FCP_HEADER_SIZE ];
20
} else {
21
tx_data = (uint16_t) pBaseHandle->TxFrame.FrameCRC;
22
}
23
} /* end of switch ( pBaseHandle->TxFrameLevel ) */
24
25
/* Send the data byte */
26
LL_USART_TransmitData8(pHandle->USARTx, tx_data);
27
28
if ( pBaseHandle->TxFrameLevel < pBaseHandle->TxFrame.Size +
FCP_HEADER_SIZE ) {
29
pBaseHandle->TxFrameLevel++;
30
} else {
31
LL_USART_DisableIT_TXE(pHandle->USARTx);
32
pBaseHandle->TxFrameState = FCP_TRANSFER_IDLE;
33
34
pBaseHandle->ClientFrameSentCallback( pBaseHandle->ClientEntity );
35
}
36
37
} /* end of if ( FCP_TRANSFER_IDLE != pBaseHandle->TxFrameState ) */
38 }
39
UFCP_OVR_IRQ_Handler()
OverRun 接收溢出中断处理函数,当接收数据量过多,并且来不解接收的时
候就会出现 OVR 标志位,然后就会进入这个函数,在这个函数当中标记出现错
误代码,并且发送通信错误帧到上位机。
代码 20-318 通信过载中断处理
01 void UFCP_OVR_IRQ_Handler( UFCP_Handle_t * pHandle )
02 {
03
FCP_Handle_t * pBaseHandle = & pHandle->_Super;
04
uint8_t error_code;
05
06
error_code = UFCP_MSG_OVERRUN;
07
(void) UFCP_Send( pBaseHandle, FCP_CODE_NACK, & error_code, 1 );
08
09 }
UFCP_TIMEOUT_IRQ_Handler()
通信超时中断处理函数。在接收到第一个数据的时候就开始计时,如果超过
时间还没有接收到后续的数据就是超时了。同样会发送通信错误帧到上位机。
STM32 技术开发手册
www.ing10bbs.com
代码 20-319 通信超时处理函数
01 void UFCP_TIMEOUT_IRQ_Handler( UFCP_Handle_t * pHandle )
02 {
03
FCP_Handle_t * pBaseHandle = & pHandle->_Super;
04
uint8_t error_code;
05
06
error_code = FCP_MSG_RX_TIMEOUT;
07
(void) UFCP_Send( pBaseHandle, FCP_CODE_NACK, & error_code, 1 );
08
09 }
UFCP_Receive()
启动接收功能,初始化状态位,然后使能串口接收中断。
代码 20-320 使能串口接收中断
01 uint8_t UFCP_Receive( FCP_Handle_t * pHandle )
02 {
03
uint8_t ret_val;
04
05
if ( FCP_TRANSFER_IDLE == pHandle->RxFrameState ) {
06
UFCP_Handle_t * pActualHandle = (UFCP_Handle_t *) pHandle;
07
08
pHandle->RxFrameLevel = 0;
09
pHandle->RxFrameState = FCP_TRANSFER_ONGOING;
10
11
LL_USART_EnableIT_RXNE(pActualHandle->USARTx);
12
ret_val = FCP_STATUS_WAITING_TRANSFER;
13
} else {
14
ret_val = FCP_STATUS_TRANSFER_ONGOING;
15
}
16
17
return ret_val;
18 }
UFCP_Send()
使用串口发送一帧数据。首先填充一帧数据,然后使能 TXE 中断,由于数据
发送寄存器当前状态下是空的,所以会立即进入中断发送数据,然后就是在中断
中发送所填充的数据帧。
代码 20-321 串口发送数据
01 uint8_t UFCP_Send( FCP_Handle_t * pHandle, uint8_t code, uint8_t *buffer,
uint8_t size)
02 {
03
uint8_t ret_val;
04
05
if ( FCP_TRANSFER_IDLE == pHandle->TxFrameState ) {
06
UFCP_Handle_t * pActualHandle = (UFCP_Handle_t *) pHandle;
07
uint8_t *dest = pHandle->TxFrame.Buffer;
08
09
pHandle->TxFrame.Code = code;
10
pHandle->TxFrame.Size = size;
11
while ( size-- ) *dest++ = *buffer++;
12
pHandle->TxFrame.FrameCRC = FCP_CalcCRC( & pHandle->TxFrame );
13
STM32 技术开发手册
www.ing10bbs.com
14
15
16
17
18
19
20
21
22
23
24 }
pHandle->TxFrameLevel = 0;
pHandle->TxFrameState = FCP_TRANSFER_ONGOING;
LL_USART_EnableIT_TXE(pActualHandle->USARTx);
ret_val = FCP_STATUS_WAITING_TRANSFER;
} else {
ret_val = FCP_STATUS_TRANSFER_ONGOING;
}
return ret_val;
UFCP_AbortReceive()
中止接收功能。修改状态位来暂停接收处理。
代码 20-322 中止接收
01 void UFCP_AbortReceive( FCP_Handle_t * pHandle )
02 {
03
pHandle->RxFrameState = FCP_TRANSFER_IDLE;
04 }
r_rivider_bus_voltage_sensor.c 文件
这个是总线电压传感器组件,总线电压传感器也就是分压电阻,原理图如图
20-43。相信采样原理已经不需要解释了。D21 是为了防止电压过高所做的钳位,
将电压固定在 3.3V 以内。
图 20-43 分压电阻测量总线电压
STM32 技术开发手册
www.ing10bbs.com
RVBS_Init()
初始化函数,在 RegularConvManager 注册一个 ADC 采样通道,FOC 的规则
采样是统一由 RCM 组件管理(Regular Conversion Manager),首先需要注册一个
规则通道。RVBS_Clear()用于设定初始值。
代码 20-323 总线电压传感器初始化
01 void RVBS_Init( RDivider_Handle_t * pHandle )
02 {
03
/* Need to be register with RegularConvManager */
04
pHandle->convHandle = RCM_RegisterRegConv(&pHandle->VbusRegConv);
05
/* Check */
06
RVBS_Clear( pHandle );
07 }
RVBS_Clear()
设定初始值,取总线电压的最小值和最大值的均值作为初始值,然后使用均
值填充缓存。
代码 20-324 清空缓存
01 void RVBS_Clear( RDivider_Handle_t * pHandle )
02 {
03
uint16_t aux;
04
uint16_t index;
05
06
aux = ( pHandle->OverVoltageThreshold +
pHandle->UnderVoltageThreshold ) / 2u;
07
for ( index = 0u; index < pHandle->LowPassFilterBW; index++ ) {
08
pHandle->aBuffer[index] = aux;
09
}
10
pHandle->_Super.LatestConv = aux;
11
pHandle->_Super.AvBusVoltage_d = aux;
12
pHandle->index = 0;
13 }
14
RVBS_ConvertVbusFiltrere()
该函数用于转换滤波计算,这是一个滤波函数,首先是采样 n 次,然后去掉
最大值和最小值,最后求平均。这滤波算法显浅易懂,就不多解释了。
代码 20-325 采样滤波函数
01 static uint16_t RVBS_ConvertVbusFiltrered( RDivider_Handle_t * pHandle )
02 {
03
uint16_t hAux;
04
uint8_t vindex;
05
uint16_t max = 0, min = 0;
06
uint32_t tot = 0u;
STM32 技术开发手册
www.ing10bbs.com
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 }
for ( vindex = 0; vindex < pHandle->LowPassFilterBW; ) {
hAux = RCM_ExecRegularConv(pHandle->convHandle);
if ( hAux != 0xFFFFu ) {
if ( vindex == 0 ) {
min = hAux;
max = hAux;
} else {
if ( hAux < min ) {
min = hAux;
}
if ( hAux > max ) {
max = hAux;
}
}
vindex++;
tot += hAux;
}
}
tot -= max;
tot -= min;
return ( uint16_t )( tot / ( pHandle->LowPassFilterBW - 2u ) );
RVBS_CalcAvVbusFilt()
同样也是一个滤波函数,只不过这个函数就非常简单了,只是求取平均值而
已。
代码 20-326 采样滤波函数
01 uint16_t RVBS_CalcAvVbusFilt( RDivider_Handle_t * pHandle )
02 {
03
uint32_t wtemp;
04
uint16_t hAux;
05
uint8_t i;
06
07
hAux = RVBS_ConvertVbusFiltrered( pHandle );
08
09
if ( hAux != 0xFFFF ) {
10
pHandle->aBuffer[pHandle->index] = hAux;
11
wtemp = 0;
12
for ( i = 0; i < pHandle->LowPassFilterBW; i++ ) {
13
wtemp += pHandle->aBuffer[i];
14
}
15
wtemp /= pHandle->LowPassFilterBW;
16
pHandle->_Super.AvBusVoltage_d = ( uint16_t )wtemp;
17
pHandle->_Super.LatestConv = hAux;
18
19
if ( pHandle->index < pHandle->LowPassFilterBW - 1 ) {
20
pHandle->index++;
21
} else {
22
pHandle->index = 0;
23
}
24
}
25
26
pHandle->_Super.FaultState = RVBS_CheckFaultState( pHandle );
27
28
return ( pHandle->_Super.FaultState );
29 }
STM32 技术开发手册
www.ing10bbs.com
RVBS_CalcAvVbus()
该函数是实际的执行总线电压采样和计算平均值函数。以上两个函数其实都
没有调用到。
代码 20-327 计算总线电压的均值
01 uint16_t RVBS_CalcAvVbus( RDivider_Handle_t * pHandle )
02 {
03
uint32_t wtemp;
04
uint16_t hAux;
05
uint8_t i;
06
07
hAux = RCM_ExecRegularConv(pHandle->convHandle);
08
09
if ( hAux != 0xFFFF ) {
10
pHandle->aBuffer[pHandle->index] = hAux;
11
wtemp = 0;
12
for ( i = 0; i < pHandle->LowPassFilterBW; i++ ) {
13
wtemp += pHandle->aBuffer[i];
14
}
15
wtemp /= pHandle->LowPassFilterBW;
16
pHandle->_Super.AvBusVoltage_d = ( uint16_t )wtemp;
17
pHandle->_Super.LatestConv = hAux;
18
19
if ( pHandle->index < pHandle->LowPassFilterBW - 1 ) {
20
pHandle->index++;
21
} else {
22
pHandle->index = 0;
23
}
24
}
25
26
pHandle->_Super.FaultState = RVBS_CheckFaultState( pHandle );
27
28
return ( pHandle->_Super.FaultState );
29 }
RVBS_CheckFaultState()
检查总线电压是否出错。就是对比采样平均值是否超过阈值范围,包括最高
值和最低值,如果是则标记超压/低压错误。
代码 20-328 检查错误状态
01 uint16_t RVBS_CheckFaultState( RDivider_Handle_t * pHandle )
02 {
03
uint16_t fault;
04
05
if ( pHandle->_Super.AvBusVoltage_d > pHandle->OverVoltageThreshold )
{
06
fault = MC_OVER_VOLT;
07
} else if ( pHandle->_Super.AvBusVoltage_d <
pHandle->UnderVoltageThreshold ) {
08
fault = MC_UNDER_VOLT;
09
} else {
10
fault = MC_NO_ERROR;
11
}
12
fault = MC_NO_ERROR;
13
14
return fault;
STM32 技术开发手册
www.ing10bbs.com
15 }
pwm_common.c 文件内容
这个文件里面只有一个函数,就是启动 TIM2,生成更新事件,以同步其他
定时器启动(其实就是启动 TIM8 启动,如果有双电机的话,那就是同步 TIM1 和
TIM8 启动)。
代码 20-329 启动定时器
01 void startTimers( void )
02 {
03
uint32_t isTIM2ClockOn;
04
uint32_t trigOut;
05
06
isTIM2ClockOn = LL_APB1_GRP1_IsEnabledClock
( LL_APB1_GRP1_PERIPH_TIM2 );
07
if ( isTIM2ClockOn == 0 ) {
08
/* Temporary Enable TIM2 clock if not already on */
09
LL_APB1_GRP1_EnableClock ( LL_APB1_GRP1_PERIPH_TIM2 );
10
LL_TIM_GenerateEvent_UPDATE ( TIM2 );
11
LL_APB1_GRP1_DisableClock ( LL_APB1_GRP1_PERIPH_TIM2 );
12
} else {
13
trigOut = LL_TIM_ReadReg( TIM2, CR2 ) & TIM_CR2_MMS;
14
LL_TIM_SetTriggerOutput( TIM2, LL_TIM_TRGO_UPDATE );
15
LL_TIM_GenerateEvent_UPDATE ( TIM2 );
16
LL_TIM_SetTriggerOutput( TIM2, trigOut );
17
}
18 }
首先检测 TIM2 的时候有没有使能, 如果有,就说明 TIM2 被使用,这个时
候就需要进行读改写操作,以便恢复原有的设置。如果没有就说明 TIM2 没有被
使用,就可以直接使能时钟,然后生成更新事件,触发信号输出,然后再禁止 TIM2
时钟。
20.3 FOC5.2 Encoder 核心源码解析
鉴于 FOC5.2 的 Encoder 与 HALLSensor 两种模式的源码有大量的重复文件,
毕竟是同一个库出来的,所以这里就不再详细解析所有文件,只分析与
HALLSensor 不同的内容,Encoder 模式与 HaLLSensor 模式最大的差异就是速度传
感器的差异,核心的控制部分是一样的,在每一个例程里面,都包含了完整的库
源码文件,只是软件工程根据使用的功能选择不同的源文件。编码器指的是增量
式编码器,目前暂不支持绝对式编码器。
STM32 技术开发手册
www.ing10bbs.com
20.3.1 Application/User
main.c 文件
MX_TIM2_Init()
编码器模式使用 TIM2 作为编码器接口,对应引脚是 PA15,PB3。设定编码
器的 ARR 为 3999,也就是每 4000 个脉冲边沿信号就复位 1 次,刚好就是对应着
电机 1000 线编码器。其他的设置都跟常规一样,没有什么特别需要说明的地方。
代码 20-330 编码器接口定时器初始化
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* TIM2 init function */
static void MX_TIM2_Init(void)
{
TIM_Encoder_InitTypeDef sConfig;
TIM_MasterConfigTypeDef sMasterConfig;
htim2.Instance = TIM2;
htim2.Init.Prescaler = 0;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = M1_PULSE_NBR;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sConfig.IC1Filter = M1_ENC_IC_FILTER;
sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
sConfig.IC2Filter = M1_ENC_IC_FILTER;
if (HAL_TIM_Encoder_Init(&htim2, &sConfig) != HAL_OK) {
_Error_Handler(__FILE__, __LINE__);
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig)
!= HAL_OK) {
_Error_Handler(__FILE__, __LINE__);
}
}
mc_api.c 文件
MC_AlignEncoderMotor1()
该函数用于校准对齐编码器,直接调用 MCI_EncoderAlign()函数。如果当前
状态是 IDLE 那么将会立即执行这个指令,如果不是就放弃这个指令,并返回 false。
STM32 技术开发手册
www.ing10bbs.com
代码 20-331 校准对齐编码器
00 bool MC_AlignEncoderMotor1(void)
01 {
02
return MCI_EncoderAlign( pMCI[M1] );
03 }
mc_task.c 文件
MCboot()
该函数是电机启动函数,主要是启动 FOC 控制功能,初始化了定时器、ADC、
状态机等,并且标志电机已经成功初始化,初始化成功之后,状态机会进入 IDLE
状态,等待通讯指令或者外部信号控制启动电机,启动的时候会按照预先设置的
速度值或扭矩值启动,当然这个值是可以在运行的时候实时修改。与 HALLSensor
模式所不同的主要速度传感器组件变成了编码器模式
代码 20-332 电机启动
00 void MCboot( MCI_Handle_t* pMCIList[NBR_OF_MOTORS],MCT_Handle_t*
pMCTList[NBR_OF_MOTORS] )
01 {
02
bMCBootCompleted = 0;
03
pCLM[M1] = &CircleLimitationM1;
04
05
/**********************************************************/
06
/*
PWM 和电流传感器组件初始化
*/
07
/**********************************************************/
08
pwmcHandle[M1] = &PWM_Handle_M1._Super;
09
R3F4XX_Init(&PWM_Handle_M1);
10
11
/**************************************/
12
/*
同步启动定时器
*/
13
/**************************************/
14
startTimers();
15
16
/**************************************/
17
/*
状态机初始化
*/
18
/**************************************/
19
STM_Init(&STM[M1]);
20
21
/******************************************************/
22
/* PID 组件初始化,速度模式 */
23
/******************************************************/
24
PID_HandleInit(&PIDSpeedHandle_M1);
25
26
/******************************************************/
27
/* 主要速度传感器组件初始化
*/
28
/******************************************************/
29
pPIDSpeed[M1] = & PIDSpeedHandle_M1;
30
pSTC[M1] = &SpeednTorqCtrlM1;
31
ENC_Init (&ENCODER_M1);
32
/******************************************************/
33
/* Main encoder alignment component initialization */
STM32 技术开发手册
www.ing10bbs.com
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/******************************************************/
EAC_Init(&EncAlignCtrlM1,pSTC[M1],&VirtualSpeedSensorM1,&ENCODER_M1);
/******************************************************/
/* Speed & torque component initialization
*/
/******************************************************/
STC_Init(pSTC[M1],pPIDSpeed[M1], &ENCODER_M1._Super);
/****************************************************/
/* Virtual speed sensor component initialization */
/***************************************************/
VSS_Init (&VirtualSpeedSensorM1);
/********************************************************/
/* PID 初始化,扭矩模式 */
/********************************************************/
PID_HandleInit(&PIDIqHandle_M1);
PID_HandleInit(&PIDIdHandle_M1);
pPIDIq[M1] = &PIDIqHandle_M1;
pPIDId[M1] = &PIDIdHandle_M1;
/********************************************************/
/* 总线电压传感器初始化
*/
/********************************************************/
pBusSensorM1 = &RealBusVoltageSensorParamsM1;
RVBS_Init(pBusSensorM1);
/*************************************************/
/* 功率测量组件初始化 */
/*************************************************/
pMPM[M1] = &PQD_MotorPowMeasM1;
pMPM[M1]->pVBS = &(pBusSensorM1->_Super);
pMPM[M1]->pFOCVars = &FOCVars[M1];
/*******************************************************/
/*
温度测量组件初始化 */
/*******************************************************/
NTC_Init(&TempSensorParamsM1);
pTemperatureSensor[M1] = &TempSensorParamsM1;
/*******************************************************/
/* 各项变量的初始化 */
/*******************************************************/
pREMNG[M1] = &RampExtMngrHFParamsM1;
REMNG_Init(pREMNG[M1]);
FOC_Clear(M1);
FOCVars[M1].bDriveInput = EXTERNAL;
FOCVars[M1].Iqdref = STC_GetDefaultIqdref(pSTC[M1]);
FOCVars[M1].UserIdref = STC_GetDefaultIqdref(pSTC[M1]).qI_Component2;
oMCInterface[M1] = & Mci[M1];
MCI_Init(oMCInterface[M1], &STM[M1], pSTC[M1], &FOCVars[M1]);
MCI_ExecSpeedRamp(oMCInterface[M1],
STC_GetMecSpeedRef01HzDefault(pSTC[M1]),0); /*First command to STC*/
pMCIList[M1] = oMCInterface[M1];
MCT[M1].pPIDSpeed = pPIDSpeed[M1];
MCT[M1].pPIDIq = pPIDIq[M1];
MCT[M1].pPIDId = pPIDId[M1];
MCT[M1].pPIDFluxWeakening = MC_NULL; /* if M1 doesn't has FW */
MCT[M1].pPWMnCurrFdbk = pwmcHandle[M1];
MCT[M1].pRevupCtrl = MC_NULL; /* only if M1 is not sensorless*/
MCT[M1].pSpeedSensorMain = (SpeednPosFdbk_Handle_t *) &ENCODER_M1;
MCT[M1].pSpeedSensorAux = MC_NULL;
MCT[M1].pSpeedSensorVirtual = MC_NULL;
MCT[M1].pSpeednTorqueCtrl = pSTC[M1];
MCT[M1].pStateMachine = &STM[M1];
MCT[M1].pTemperatureSensor = (NTC_Handle_t *) pTemperatureSensor[M1];
MCT[M1].pBusVoltageSensor = &(pBusSensorM1->_Super);
STM32 技术开发手册
www.ing10bbs.com
102
MCT[M1].pBrakeDigitalOutput = MC_NULL; /* brake is defined,
oBrakeM1*/
103
MCT[M1].pNTCRelay = MC_NULL;
/* relay is defined,
oRelayM1*/
104
MCT[M1].pMPM = (MotorPowMeas_Handle_t*)pMPM[M1];
105
MCT[M1].pFW = MC_NULL;
106
MCT[M1].pFF = MC_NULL;
107
MCT[M1].pSCC = MC_NULL;
108
MCT[M1].pOTT = MC_NULL;
109
pMCTList[M1] = &MCT[M1];
110
111
/*******************************************************/
112
/* 标志成功初始化 */
113
/*******************************************************/
114
bMCBootCompleted = 1;
115 }
在代码中的注释就可以看得到,初始化配置了 PWM 和电流采样、启动定时
器、状态机、PID 参数、总线和温度传感器、功率测量、速度和扭矩控制等组件。
也可以看到有一些参数是设置为 MC_NULL,这说明这些功能是没有用到的。其
中主速度传感器是编码器,而另外还有一个虚拟速度传感器,这个是用于上电第
一次启动的时候校准对齐编码器用的,主要功能就是模仿实际的速度传感器,返
回电角度值。
stm32F4xx_mc_it.c 文件
SPD_TIM_M1_IRQHandler()
这是速度传感器的的中断入口,编码器的中断只有溢出更新中断,也就是电
机转了一圈之后才会触发中断函数,最终只是调用 ENC_IRQHandler(),标记编码
器计数溢出。
代码 20-333 编码器中断入口
00 void SPD_TIM_M1_IRQHandler(void)
01 {
02
/* Encoder Timer UPDATE IT is dynamicaly enabled/disabled,
03
checking enable state is required */
04
if (LL_TIM_IsEnabledIT_UPDATE (ENCODER_M1.TIMx) &&
05
LL_TIM_IsActiveFlag_UPDATE (ENCODER_M1.TIMx)) {
06
LL_TIM_ClearFlag_UPDATE(ENCODER_M1.TIMx);
07
ENC_IRQHandler(&ENCODER_M1);
08
} else {
09
}
10 }
STM32 技术开发手册
www.ing10bbs.com
20.3.2 Middlewares/MotorControl
enc_align_ctrl.c 文件
编码器对齐校准模块,主要实现编码器校准对齐功能。
EAC_Init()
初始化控制句柄,主要是获取其他模块的控制指针,已经将一些变量清 0。
代码 20-334 编码器校准控制模块初始化
00 void EAC_Init( EncAlign_Handle_t * pHandle, SpeednTorqCtrl_Handle_t *
01
pSTC, VirtualSpeedSensor_Handle_t * pVSS,
02
ENCODER_Handle_t * pENC )
03 {
04
pHandle->pSTC = pSTC;
05
pHandle->pVSS = pVSS;
06
pHandle->pENC = pENC;
07
pHandle->EncAligned = false;
08
pHandle->EncRestart = false;
09 }
EAC_StartAlignment()
启动校准对齐功能,校准对齐功能主要使用到虚拟速度传感器,用虚拟速度
传感器代替实际的编码器。
代码 20-335 启动校准对齐
00 void EAC_StartAlignment( EncAlign_Handle_t * pHandle )
01 {
02
uint32_t wAux;
03
04
/* Set pVSS mechanical speed to zero.*/
05
VSS_SetMecAcceleration( pHandle->pVSS, 0, 0u );
06
07
/* Set pVSS mechanical angle.*/
08
VSS_SetMecAngle( pHandle->pVSS, pHandle->hElAngle );
09
10
/* Set pSTC in STC_TORQUE_MODE.*/
11
STC_SetControlMode( pHandle->pSTC, STC_TORQUE_MODE );
12
13
/* Set starting torque to Zero */
14
STC_ExecRamp( pHandle->pSTC, 0, 0u );
15
16
/* Execute the torque ramp.*/
17
STC_ExecRamp( pHandle->pSTC, pHandle->hFinalTorque, ( uint32_t )(
18
pHandle->hDurationms ) );
19
20
/* Compute hRemainingTicks, the number of thick of alignment phase.
21
*/
22
wAux = ( uint32_t )pHandle->hDurationms * ( uint32_t )pHandle->
23
hEACFrequencyHz;
24
wAux /= 1000u;
25
pHandle->hRemainingTicks = ( uint16_t )( wAux );
26
pHandle->hRemainingTicks++;
STM32 技术开发手册
www.ing10bbs.com
27 }
设置了虚拟速度传感器的加速度为 0,pHandle->hElAngle 就是所设定的对齐
电角度。设定以 Torque 模式运行,并且设定最终的目标 Torque 就是所设定最终
电流目标值,然后执行 Ramp 指令,hRemainingTicks 则是执行 Ramp 指令期间的
步数计数器。Ramp 就是将控制量比喻成一个爬坡的动作,在给定的时间内从当
前值到目标值呈现直线递增的情况。现在就是要让电流值在 hDurationms 内从 0
递增到 hFinalTorque,在这个过程当中,通过不断的递增目标值,然后利用 PID 控
制电机跟随目标值变化,从而达到 Ramp 的动作。而 hRemainingTicks 就是在
hDurationms 内会调整目标值的次数。
EAC_Exec()
这个是状态机执行函数,在 ALIGNMENT 状态当中,执行频率是 2KHz。可以
看到,每执行一次 hRemainingTicks 就会自减 1,当等于 0 的时候就停止。并且将
所设定的对齐电角度换算成实际的机械角度,然后赋值给编码器接口定时器
(TIM2)。
代码 20-336 执行函数
00 bool EAC_Exec( EncAlign_Handle_t * pHandle )
01 {
02
bool retVal = true;
03
04
if ( pHandle->hRemainingTicks > 0u ) {
05
pHandle->hRemainingTicks--;
06
07
if ( pHandle->hRemainingTicks == 0u ) {
08
/* Set pVSS mechanical angle.*/
09
ENC_SetMecAngle ( pHandle->pENC, pHandle->hElAngle / (
10
int16_t )( pHandle->bElToMecRatio ) );
11
pHandle->EncAligned = true;
12
retVal = true;
13
} else {
14
retVal = false;
15
}
16
}
17
18
return retVal;
19 }
pHandle->hElAngle 就是所设置的对齐电角度,在例程里面就是 90°,
pHandle->bElToMecRatio 是电角度转换成机械角度的系数,实际就是极对数。
pHandle->EncAligned 就是标记已经执行对齐功能,下次启动电机就不需要再执行
校准对齐。
STM32 技术开发手册
www.ing10bbs.com
EAC_IsAligned()
一个查询函数,用于查询是否已经执行编码器校准对齐动作,返回
EncAligned。
代码 20-337 查询函数
00 bool EAC_IsAligned( EncAlign_Handle_t * pHandle )
01 {
02
return pHandle->EncAligned;
03 }
EAC_SetRestartState()
设置重新启动,在上电之后第一次启动电机时,EncRestart 会被标记为 true,
等到编码器校准对齐之后,将会 EncRestart 重新启动电机转动。
代码 20-338 重新启动
00 void EAC_SetRestartState( EncAlign_Handle_t * pHandle, bool restart )
01 {
02
pHandle->EncRestart = restart;
03 }
因为在第一次编码器校准对齐之后,电机是会停下来,设置 EncRestart 为 true
就可以在对齐之后直接启动,而无需重复发送启动指令。
Encoder_speed_pos_fdbk.c 文件
这是编码器作为速度传感器反馈速度值得功能模块。在这个文件里面会计算
电机的转速和电角度。
ENC_Init()
编码器功能初始化,主要是计算一些用于计算电角度和速度的系数
代码 20-339 编码器功能初始化
00
01
02
03
04
05
06
07
08
09
10
11
12
13
void ENC_Init( ENCODER_Handle_t * pHandle )
{
TIM_TypeDef * TIMx = pHandle->TIMx;
uint8_t BufferSize;
uint8_t Index;
#ifdef TIM_CNT_UIFCPY
LL_TIM_EnableUIFRemap ( TIMx );
#define ENC_MAX_OVERFLOW_NB
((uint16_t)2048) /* 2^11*/
#else
#define ENC_MAX_OVERFLOW_NB
(1)
#endif
STM32 技术开发手册
www.ing10bbs.com
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 }
/* Reset counter */
LL_TIM_SetCounter ( TIMx, 0 );
/*Calculations of convenience*/
pHandle->U32MAXdivPulseNumber = UINT32_MAX / ( uint32_t )( pHandle>PulseNumber );
pHandle->SpeedSamplingFreqHz = pHandle->SpeedSamplingFreq01Hz /
10u;
LL_TIM_ClearFlag_UPDATE ( TIMx );
LL_TIM_EnableIT_UPDATE ( TIMx );
/* Enable the counting timer*/
LL_TIM_EnableCounter ( TIMx );
/* Erase speed buffer */
BufferSize = pHandle->SpeedBufferSize;
for ( Index = 0u; Index < BufferSize; Index++ ) {
pHandle->DeltaCapturesBuffer[Index] = 0;
}
TIM_CNT_UIFCPY 是一个宏,部分型号的芯片 TIMx->CNT 最高位是溢出标志
位,如果是这样的话溢出次数就可以计数到 2048 次,否则就允许溢出 1 次。现
在例程里面定义的 ENC_MAX_OVERFLOW_NB 为 1。
U32MAXdivPulseNumber 用于将定时器计数值转换实际的机械角度这是一个
系数,在这里解释可能会有点难以理解,所以这个待到实际用到的时候再解释。
SpeedSamplingFreqHz 是采样频率,单位是 Hz。用于计算以 dpp 为单位的速
度值。
这个函数还使能了更新中断,用来统计溢出次数。
ENC_Clear()
这个函数用于将速度缓存复位,也就是数组设置为 0。
代码 20-340 复位速度缓存
00 void ENC_Clear( ENCODER_Handle_t * pHandle )
01 {
02
uint8_t Index;
03
for ( Index = 0u; Index < pHandle->SpeedBufferSize; Index++ ) {
04
pHandle->DeltaCapturesBuffer[Index] = 0;
05
}
06
pHandle->SensorIsReliable = true;
07 }
ENC_CalcAngle()
计算角度值,包括电角度和机械角度。
STM32 技术开发手册
www.ing10bbs.com
代码 20-341 计算电角度
00 int16_t ENC_CalcAngle( ENCODER_Handle_t * pHandle )
01 {
02
int32_t wtemp1;
03
int32_t wtemp2;
04
int16_t htemp1;
05
int16_t htemp2;
06
07
wtemp1 = ( int32_t )( LL_TIM_GetCounter( pHandle->TIMx ) ) *
08
( int32_t )( pHandle->U32MAXdivPulseNumber );
09
10
/*Computes and stores the rotor electrical angle*/
11
wtemp2 = wtemp1 * ( int32_t )pHandle->_Super.bElToMecRatio;
12
htemp1 = ( int16_t )( wtemp2 / 65536 );
13
14
pHandle->_Super.hElAngle = htemp1;
15
16
/*Computes and stores the rotor mechanical angle*/
17
htemp2 = ( int16_t )( wtemp1 / 65536 );
18
19
pHandle->_Super.hMecAngle = htemp2;
20
21
/*Returns rotor electrical angle*/
22
return ( htemp1 );
23 }
U32MAXdivPulseNumber 就是在初始化的时候计算的系数,实际的数值是
𝑈𝐼𝑁𝑇32/𝑃𝑢𝑙𝑠𝑒𝑁𝑢𝑚𝑏𝑒𝑟,𝑃𝑢𝑙𝑠𝑒𝑁𝑢𝑚𝑏𝑒𝑟就是电机转动一圈定时器计数的脉冲数。
𝐸𝑙𝑇𝑜𝑀𝑒𝑐𝑅𝑎𝑡𝑖𝑜就是极对数,再根据后面的𝐸𝑙𝐴𝑛𝑔𝑙𝑒的计算公式可以总结出,一个
整体的公式就是:
公式 20-39 计算电角度
𝐸𝑙𝐴𝑛𝑔𝑙𝑒 =
𝐶𝑁𝑇 ∗ 𝑈𝐼𝑁𝑇32 ∗ 𝐸𝑙𝑇𝑜𝑀𝑒𝑐𝑅𝑎𝑡𝑖𝑜 𝐶𝑁𝑇 ∗ 𝐸𝑙𝑇𝑜𝑀𝑒𝑐𝑅𝑎𝑡𝑖𝑜 ∗ 65536
=
𝑃𝑢𝑙𝑠𝑒𝑁𝑢𝑚𝑏𝑒𝑟 ∗ 65536
𝑃𝑢𝑙𝑠𝑒𝑁𝑢𝑚𝑏𝑒𝑟
从公式 20-39 可以明显看得出来,角度值乘上极对数之后就是电角度。注意
这里的单位都是 S16degree,用 16 位的有符号数代表角度值。
这个函数返回的是电角度值。
ENC_CalcAvrgMecSpeed01Hz()
计算平均的机械转速,单位是 0.1Hz。实际上也计算了以 Dpp 为单位的转速
值。
代码 20-342 计算平均转速
00 bool ENC_CalcAvrgMecSpeed01Hz( ENCODER_Handle_t * pHandle, int16_t *
01
pMecSpeed01Hz )
02 {
03
TIM_TypeDef * TIMx = pHandle->TIMx;
04
int32_t wOverallAngleVariation = 0;
05
int32_t wtemp1;
STM32 技术开发手册
www.ing10bbs.com
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
int32_t wtemp2;
uint8_t bBufferIndex = 0u;
bool bReliability = true;
uint8_t bBufferSize = pHandle->SpeedBufferSize;
uint32_t OverflowCntSample;
uint32_t CntCapture;
uint32_t directionSample;
uint8_t OFbit = 0;
CntCapture = LL_TIM_GetCounter ( TIMx );
OverflowCntSample = pHandle->TimerOverflowNb;
pHandle->TimerOverflowNb = 0;
directionSample = LL_TIM_GetDirection( TIMx );
/* OFbit 就是就是 CNT 的最高位,F407 的计数器最高位
并不是 OverFlowBit,所以就是 OFBit 0 */
if ( ( OverflowCntSample + OFbit ) > ENC_MAX_OVERFLOW_NB ) {
pHandle->TimerOverflowError = true;
}
/*计算角度偏差*/
if ( directionSample == LL_TIM_COUNTERDIRECTION_DOWN ) {
/* 编码器向下计数 */
OverflowCntSample = ( CntCapture > pHandle->PreviousCapture
? 1 : 0;
pHandle->DeltaCapturesBuffer[pHandle->DeltaCapturesIndex] =
( int32_t )( CntCapture ) - ( int32_t )( pHandle->
PreviousCapture ) ( ( int32_t )( OverflowCntSample ) + OFbit ) * ( int32_t
pHandle->PulseNumber );
} else {
/*编码器向上计数*/
OverflowCntSample = ( CntCapture < pHandle->PreviousCapture
? 1 : 0;
pHandle->DeltaCapturesBuffer[pHandle->DeltaCapturesIndex] =
( int32_t )( CntCapture ) - ( int32_t )( pHandle->
PreviousCapture ) +
( ( int32_t )( OverflowCntSample ) + OFbit ) * ( int32_t
pHandle->PulseNumber );
}
)
)(
)
)(
/* 计算平均机械转速 单位是[01Hz], wtemp1*/
for ( bBufferIndex = 0u; bBufferIndex < bBufferSize; bBufferIndex++
) {
wOverallAngleVariation += pHandle->DeltaCapturesBuffer[
bBufferIndex];
}
wtemp1 = wOverallAngleVariation * ( int32_t )( pHandle->
SpeedSamplingFreq01Hz );
wtemp2 = ( int32_t )( pHandle->PulseNumber ) *
( int32_t )( pHandle->SpeedBufferSize );
wtemp1 /= wtemp2;
*pMecSpeed01Hz = ( int16_t )( wtemp1 );
/* 计算平均的加速度,就是前后两次速度值的差值
*/
pHandle->_Super.hMecAccel01HzP = ( int16_t )( wtemp1 pHandle->_Super.hAvrMecSpeed01Hz )
;
/*Stores average mechanical speed [01Hz]*/
pHandle->_Super.hAvrMecSpeed01Hz = ( int16_t )wtemp1;
/* 计算实时的电角度速度,单位是[dpp] electrical
speed [dpp]*/
wtemp1 = pHandle->DeltaCapturesBuffer[pHandle->DeltaCapturesIndex]
*
STM32 技术开发手册
www.ing10bbs.com
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101 }
( int32_t )( pHandle->SpeedSamplingFreqHz ) *
( int32_t )pHandle->_Super.bElToMecRatio;
wtemp1 /= ( int32_t )( pHandle->PulseNumber );
wtemp1 *= ( int32_t )UINT16_MAX;
wtemp1 /= ( int32_t )( pHandle->_Super.hMeasurementFrequency );
pHandle->_Super.hElSpeedDpp = ( int16_t )wtemp1;
/* 更新一些变量 */
pHandle->PreviousCapture = CntCapture;
/*Buffer index update*/
pHandle->DeltaCapturesIndex++;
if ( pHandle->DeltaCapturesIndex == pHandle->SpeedBufferSize ) {
pHandle->DeltaCapturesIndex = 0u;
}
/*检查编码器是否可靠*/
if ( pHandle->TimerOverflowError ) {
bReliability = false;
pHandle->SensorIsReliable = false;
pHandle->_Super.bSpeedErrorNumber = pHandle->_Super.
bMaximumSpeedErrorsNumber;
} else {
bReliability = SPD_IsMecSpeedReliable( &pHandle->_Super,
pMecSpeed01Hz );
}
return ( bReliability );
计算速度值其实并不复杂,这个函数看上去只是语句比较多而已,实际上可
以划分几个功能模块,然后再逐个分析,在分析过程中也要在注意将一些提前计
算好的系数展开,这样才能方便找出内在的关联。
代码 20-343 计算偏差值
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
/*计算角度偏差*/
if ( directionSample == LL_TIM_COUNTERDIRECTION_DOWN )
{
/* 编码器向下计数 */
OverflowCntSample = ( CntCapture > pHandle->PreviousCapture
0;
pHandle->DeltaCapturesBuffer[pHandle->DeltaCapturesIndex] =
( int32_t )( CntCapture ) - ( int32_t )( pHandle->
PreviousCapture ) ( ( int32_t )( OverflowCntSample ) + OFbit ) * ( int32_t
pHandle->PulseNumber );
} else
{
/*编码器向上计数*/
OverflowCntSample = ( CntCapture < pHandle->PreviousCapture
0;
pHandle->DeltaCapturesBuffer[pHandle->DeltaCapturesIndex] =
( int32_t )( CntCapture ) - ( int32_t )( pHandle->
PreviousCapture ) +
( ( int32_t )( OverflowCntSample ) + OFbit ) * ( int32_t
pHandle->PulseNumber );
}
) ? 1 :
)(
) ? 1 :
)(
STM32 技术开发手册
www.ing10bbs.com
首先是根据当前是向上计数还是向下计数,代码 20-343 计算出两次读取计
数值的偏差值,然后保存在速度缓存 DeltaCapturesBuffer 里面。这个计数偏差值
是包含有计数溢出处理的。
代码 20-344 计算平均转速
00
01
02
03
04
05
06
07
08
09
10
11
/* 计算平均机械转速 单位是[01Hz], wtemp1*/
for ( bBufferIndex = 0u; bBufferIndex < bBufferSize; bBufferIndex++ )
{
wOverallAngleVariation += pHandle->DeltaCapturesBuffer[
bBufferIndex];
}
wtemp1 = wOverallAngleVariation * ( int32_t )( pHandle->
SpeedSamplingFreq01Hz );
wtemp2 = ( int32_t )( pHandle->PulseNumber ) *
( int32_t )( pHandle->SpeedBufferSize );
wtemp1 /= wtemp2;
*pMecSpeed01Hz = ( int16_t )( wtemp1 );
可以看到,一开始是将速度缓存里面的数值累加起来,在后面也除以缓存大
小,得到的其实就是平均值,但是中间加入一些系数转换单位。整体的公式是:
公式 20-40 计算平均转速
𝑀𝑒𝑐𝑆𝑝𝑒𝑒𝑑01𝐻𝑧 =
𝑆𝑢𝑚 ∗ 𝑓𝑠𝑎𝑚𝑝𝑙𝑒
𝑃𝑢𝑙𝑠𝑒𝑁𝑢𝑚𝑏𝑒𝑟 ∗ 𝑆𝑝𝑒𝑒𝑑𝐵𝑢𝑓𝑓𝑒𝑟𝑆𝑖𝑧𝑒
除以单位时间,就是相当于乘上对应的频率,所以这个公式其实就是累加值
除以缓存大小,得到平均值,然后除以两次采样之间的时间,得到的速度值,单
位是 0.1Hz,也就是 0.1RPS。
代码 20-345 计算加速度
00
01
02
03
04
05
/* 计算平均的加速度,就是前后两次速度值的差值 */
pHandle->_Super.hMecAccel01HzP = ( int16_t )( wtemp1 pHandle->_Super.hAvrMecSpeed01Hz );
/*Stores average mechanical speed [01Hz]*/
pHandle->_Super.hAvrMecSpeed01Hz = ( int16_t )wtemp1;
加速度就是两次采样之间的速度值偏差。
代码 20-346 计算实时速度值[dpp]
00
01
02
03
04
05
06
07
08
09
/* 计算实时的电角度速度,单位是[dpp] electrical speed [
dpp]*/
wtemp1 = pHandle->DeltaCapturesBuffer[pHandle->DeltaCapturesIndex] *
( int32_t )( pHandle->SpeedSamplingFreqHz ) *
( int32_t )pHandle->_Super.bElToMecRatio;
wtemp1 /= ( int32_t )( pHandle->PulseNumber );
wtemp1 *= ( int32_t )UINT16_MAX;
wtemp1 /= ( int32_t )( pHandle->_Super.hMeasurementFrequency );
pHandle->_Super.hElSpeedDpp = ( int16_t )wtemp1;
STM32 技术开发手册
www.ing10bbs.com
hMeasurementFrequency 是 PWM 的频率,同时也是 FOC 的控制频率,电角
度速度,dpp 为单位的速度值,意思是每个 FOC 周期的速度值,而这个速度值是
以电角度为单位计算,所以需要先计算出电机的实时速度值,然后转换成电角度,
最后除以 FOC 的频率,得到的就是以 dpp 为单位的电角度速度值。总结一下就
得到下面的公式:
公式 20-41 SpeedDpp
ℎ𝐸𝑙𝑆𝑝𝑒𝑒𝑑𝐷𝑝𝑝 =
Δ𝐶𝑎𝑝𝑡𝑢𝑟𝑒𝑠 ∗ 𝑓𝑠𝑎𝑚𝑝𝑙𝑒 ∗ 𝑏𝐸𝑙𝑇𝑜𝑀𝑒𝑐𝑅𝑎𝑡𝑖𝑜
∗ 65536
𝑃𝑢𝑙𝑠𝑒𝑁𝑢𝑚𝑏𝑒𝑟 ∗ 𝑓𝐹𝑂𝐶
代码 20-347 设置当前的机械角度
00 void ENC_SetMecAngle( ENCODER_Handle_t * pHandle, int16_t hMecAngle )
01 {
02
TIM_TypeDef * TIMx = pHandle->TIMx;
03
04
uint16_t hAngleCounts;
05
uint16_t hMecAngleuint;
06
07
if ( hMecAngle < 0 ) {
08
hMecAngle *= -1;
09
hMecAngleuint = 65535u - ( uint16_t )hMecAngle;
10
} else {
11
hMecAngleuint = ( uint16_t )hMecAngle;
12
}
13
14
hAngleCounts = ( uint16_t )( ( ( uint32_t )hMecAngleuint *
15
( uint32_t )pHandle->PulseNumber )
16
/ 65535u );
17
18
TIMx->CNT = ( uint16_t )( hAngleCounts );
19 }
20
这个函数主要用在编码器校准对齐结束的时候,用于设置编码器接口定时器
的计数值,传入的参数是机械角度(s16degree),首先是对角度值预处理,如果
是负数就取其绝对值,然后再计算出对应的计数值。
代码 20-348 编码器接口定时器中断处理
00 void * ENC_IRQHandler( void * pHandleVoid )
01 {
02
ENCODER_Handle_t * pHandle = ( ENCODER_Handle_t * ) pHandleVoid;
03
04
/*Updates the number of overflows occurred*/
05
/* the handling of overflow herror is done in
06
ENC_CalcAvrgMecSpeed01Hz */
07
pHandle->TimerOverflowNb += 1u;
08
09
return MC_NULL;
10 }
定时器只需要使能更新中断,然后中断中累计溢出次数。
STM32 技术开发手册
www.ing10bbs.com
virtual_speed_sensor.c 文件
这是虚拟速度传感器的功能实现模块,与之相关联的有全局结构体变量
VirtualSpeedSensorM1。所谓虚拟速度传感器就是这个模块并不对应着实际的速
度传感器,在编码器模式里面,这个虚拟速度传感器的唯一功能就是在编码器校
准对齐的时候提供一个固定的电角度,使得能够完成校准对齐指令。所以下面也
并不打算详细介绍这个文件的内容,因为大部分函数都是没有被调用到的。
被 调 用 到 的 函 数 有 VSS_Init() , VSS_Clear() , VSS_SetMecAcceleration() ,
VSS_SetMecAngle()。其中 VSS_Init(),VSS_Clear()就是初始化函数,将结构体所有
变量复位为 0 或者是 false。VSS_SetMecAngle()和 VSS_SetMecAcceleration()则是在
EAC_StartAlignment()函数中被调用,这两个函数就是设置加速度、机械角度和电
角度,这些参数都是固定的值,相当于一个宏定义。
代码 20-349 有使用 VSS 模块函数的地方
00 void EAC_StartAlignment( EncAlign_Handle_t * pHandle )
01 {
02
uint32_t wAux;
03
04
/* Set pVSS mechanical speed to zero.*/
05
VSS_SetMecAcceleration( pHandle->pVSS, 0, 0u );
06
07
/* Set pVSS mechanical angle.*/
08
VSS_SetMecAngle( pHandle->pVSS, pHandle->hElAngle );
…
27 }
在 EAC_StartAlignment 设置虚拟速度传感器的加速度为 0,机械角度为
hElAngle,hElAngle 就是设置校准对齐电角度,例程里面就是 90°(s16degree 格
式的数值是 16384)。在 FOC 的控制函数里面有使用到这个 hElAngle。
代码 20-350 FOC 控制函数使用虚拟电角度
00 inline uint16_t FOC_CurrController(uint8_t bMotor)
01 {
02
Curr_Components Iab, Ialphabeta, Iqd;
03
Volt_Components Valphabeta, Vqd;
04
int16_t hElAngle;
05
uint16_t hCodeError;
06
07
hElAngle = SPD_GetElAngle(STC_GetSpeedSensor(pSTC[bMotor]));
…
这里之所以需要调用 STC_GetSpeedSensor()函数来获取速度传感器的控制句
柄,而不是直接使用编码器的控制句柄,就是因为在编码器校准对齐的时候这个
STM32 技术开发手册
www.ing10bbs.com
函数返回的是虚拟速度传感器,在正常运行的时候就是返回实际的编码器控制句
柄(&ENCODER_M1)。
代码 20-351 状态机当中配置速度传感器
00 void TSK_MediumFrequencyTaskM1(void)
01 {
…
81
case ALIGN_CLEAR:
83
STC_SetSpeedSensor(pSTC[M1],&VirtualSpeedSensorM1._Super);
…
89
break;
…
93
break;
94
case ALIGNMENT:
95
if (!EAC_Exec(&EncAlignCtrlM1)) {
…
100
} else {
…
103
STC_SetSpeedSensor(pSTC[M1],&ENCODER_M1._Super);
…
110
}
在状态机的函数当中可以看到只有在 ALIGN_CLEAR 状态才会配置速度传感
器为 VSS,在 ALIGNMENT 结束的时候又将速度传感器配置为&ENCODER_M1。
STM32 技术开发手册
www.ing10bbs.com
STM32 技术开发手册
www.ing10bbs.com
第21章 YS-F4Pro FOC5.4.3 位置控制
在 st 的 FOC 电机库当中,目前最新版是 5.4.3,主版本号不变,次版本号加
1,说明最新版的电机库加入了一些新的功能,但是整体的代码不会有非常大的
改变,功能上也不会有非常大的变化,核心的控制算法也一直都没有变,通过阅
读 5.2.0 的源码足以掌握电机库的控制流程和算法实现。所以下面将会以
FOC5.4.3 的位置控制模式为例子,介绍 FOC5.4.3 的新增的功能和源码主要的改
变(例程名字是 FOC_v5.4.3_42PMSM_EncoderPosition)。
21.1 从 FOC5.2.0 到 5.4.3 的变化
下面是一些比较明显的改变,其实这些都不会难以理解,只要能看懂 5.2.0
的源码就会看懂 5.4.3。
1. __weak 函数
几乎所有的电机库函数都被定义为__weak 函数,表明了这些都是可以被重
构的函数,用户可以在不修改电机库源码的情况下,实现重构电机控制的功能。
例如在获取电角度的时候需要调用 SPD_GetElAngle()函数,该函数是返回速度传
感器所定义的电角度值,当时 FOC 电机库只支持霍尔传感器,增量式编码器这两
种速度传感器,是不支持其他的速度传感器,那么这个时候就可以利用__weak
特性,再重新写一个 SPD_GetElAngle(),从而使得电机库可以支持更多类型的传
感器。当然要更换速度传感器并不是只改一个函数就足够的,还需要有更加详细
的规划才行。
2. 结构体类型
在 FOC5.2.0 当中,关于电流电压的参数采样以下的结构体类型来存储:
代码 21-1 电流电压结构体
00 /**
01 * @brief Two components stator current type definition
02 */
03 typedef struct {
04
int16_t qI_Component1;
05
int16_t qI_Component2;
06 } Curr_Components;
STM32 技术开发手册
www.ing10bbs.com
07
08 /**
09 * @brief Two components stator voltage type definition
10 */
11 typedef struct {
12
int16_t qV_Component1;
13
int16_t qV_Component2;
14 } Volt_Components;
15
可以看到结构体成员都是使用 Componment1,Componment2 这样辨识度不
高的字符来代替𝐼𝑞 、𝐼𝑑 ,𝑉𝑞 、𝑉𝑑 。在 FOC5.4.3 当中就修改了结构体成员的名字,
换成了更容易识别的标识符。
代码 21-2 电流电压结构体
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @brief Two components q, d type definition
*/
typedef struct {
int16_t q;
int16_t d;
} qd_t;
/**
* @brief Two components a,b type definition
*/
typedef struct {
int16_t a;
int16_t b;
} ab_t;
/**
* @brief Two components alpha, beta type definition
*/
typedef struct {
int16_t alpha;
int16_t beta;
} alphabeta_t;
3. 速度单位
代码 21-3 速度值单位宏定义
00
01
02
03
04
05
06
07
/** Revolutions Per Minute: 1 Hz is 60 RPM */
#define _RPM 60
/** Tenth of Hertz: 1 Hz is 10 01Hz */
#define _01HZ 10
/** Hundreth of Hertz: 1 Hz is 100 001Hz */
#define _001HZ 100
#define SPEED_UNIT _01HZ
速度值单位由固定的 01Hz,变为可选单位值。SPEED_UNIT 可选是 0.1Hz,或
者是 0.01Hz,也可以是 RPM,一般默认选择_01Hz,代表数值本身就是放大 10 倍
的,例如 10 (01Hz)就是实际的 1Hz,1RPM 就是 60Hz。另外部分函数的名字也由
原来的 01Hz 改为了 SpeedUnit,意思是通过这个函数得到的速度值单位不再是唯
STM32 技术开发手册
www.ing10bbs.com
一 , 而 是 可 以 改 的 , 例 如 ENC_CalcAvrgMecSpeed01Hz() 函 数 就 改 为 了
ENC_CalcAvrgMecSpeedUnit()。
4. 应用程序接口
由于 FOC5.4.3 新增了位置控制,所以相应的接口也变多了,在 mc_api.c 文
件当中,新增了位置控制接口和位置反馈接口。
代码 21-4 位置控制接口
00 __weak void MC_ProgramPositionCommandMotor1( float fTargetPosition,
01
float fDuration )
02 {
03
MCI_ExecPositionCommand( pMCI[M1], fTargetPosition, fDuration );
04 }
调用 MCI_ExecPositionCommand()函数设定最终的目标值和周期时间,注意
周期 fDuration 的单位是 s,不是 ms,毕竟位置控制到达目标位置是需要一定的
时间的,跟速度和电流的控制周期是不一样的。fTargetPosition 则是以弧度为单
位的目标值,如果需要换算成转数,则可以用 fTargetPosition 除以2𝜋。例如目标
位置是 500r,那么就需要500 ∗ 2𝜋。通常控制使用位置控制的时候就是使用这个
函数。
代码 21-5 位置反馈接口
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* @brief returns the current control position state of Motor 1.
*
*/
__weak PosCtrlStatus_t MC_GetControlPositionStatusMotor1( void )
{
return MCI_GetCtrlPositionState( pMCI[M1] );
}
/**
* @brief returns the alignment state of Motor 1.
*
*/
__weak AlignStatus_t MC_GetAlignmentStatusMotor1( void )
{
return MCI_GetAlignmentStatus( pMCI[M1] );
}
/**
* @brief returns the current position of Motor 1.
*
*/
__weak float MC_GetCurrentPosition1( void )
{
return MCI_GetCurrentPosition( pMCI[M1] );
}
/**
* @brief returns the target position of Motor 1.
*
*/
__weak float MC_GetTargetPosition1( void )
{
STM32 技术开发手册
www.ing10bbs.com
29
30
31
32
33
34
35
36
37
38
39
40
return MCI_GetTargetPosition( pMCI[M1] );
}
/**
* @brief returns the total movement duration to reach the target
position of Motor 1.
*
*/
__weak float MC_GetMoveDuration1( void )
{
return MCI_GetMoveDuration( pMCI[M1] );
}
这些都是跟位置控制相关的接口函数,用于反馈与位置相关的参数。
在 mc_interface.c 当中新增了 MCI_ExecPositionCommand()函数,这个是位置
控制指令实现函数,输入参数主要是 FinalPosition,Duration。在启动之前需要先
调用这个函数设置目标值和周期时间。
代码 21-6 执行位置控制指令
00 __weak void MCI_ExecPositionCommand( MCI_Handle_t * pHandle, float
01
FinalPosition, float Duration )
02 {
03
pHandle->pFOCVars->bDriveInput = INTERNAL;
04
float currentPositionRad = (float)(SPD_GetMecAngle(
05
STC_GetSpeedSensor(pHandle->pSTC))) /
06
RADTOS16;
07
if (Duration > 0) {
08
TC_MoveCommand(pHandle->pPosCtrl, currentPositionRad,
09
FinalPosition - currentPositionRad, Duration);
10
} else {
11
TC_FollowCommand(pHandle->pPosCtrl, FinalPosition);
12
}
13
14
pHandle->LastModalitySetByUser = STC_TORQUE_MODE;
15 }
目标值 FinalPosition 的单位是弧度,周期时间 Duration 单位是 s。其中周期
时间是可以设置为 0,当 Duration 等于 0 的时候,可以立即以最大输出转动到目
标的位置,这个时候的 FinalPosition 是一个绝对值,这种情况称为 FOLLOWING。
当 Duration 不等于 0 的时候,FinalPosition 就是一个相对值,电机将会从当前的
位置转动 FinalPosition 之后就停下,这种情况称为 MOVEMENT。
5. 位置控制
在状态机的 RUN 状态当中,新增了一个函数 TC_PositionRegulation(),这个
函数的主要作用就是实现位置控制。
代码 21-7 状态机运行状态 RUN
00 case RUN:
01 TC_PositionRegulation(pPosCtrl[M1]);
02
STM32 技术开发手册
www.ing10bbs.com
03 MCI_ExecBufferedCommands( oMCInterface[M1] );
04 FOC_CalcCurrRef( M1 );
21.2 编码器模式的轨迹控制
FOC5.4.3 最大的变化莫过于新增的位置控制模式,可以利用编码器的位置反
馈进行轨迹规划。FOC5.4.3 新增了一个轨迹控制的模块,就是 trajectory ctrl,这
个功能是在状态机当中实现的。
Trajectory_ctrl.c 文件
TC_Init()
这是一个初始化函数,就是将结构体的各个成员值设置为 0,其实并没有什
么要特别注意的。
代码 21-8 轨迹控制初始化
00 void TC_Init(PosCtrl_Handle_t *pHandle, PID_Handle_t * pPIDPosReg,
01
SpeednTorqCtrl_Handle_t * pSTC, ENCODER_Handle_t * pENC)
02 {
03
04
pHandle->MovementDuration = 0.0f;
05
pHandle->AngleStep = 0.0f;
06
pHandle->SubStep[0] = 0.0f;
07
pHandle->SubStep[1] = 0.0f;
08
pHandle->SubStep[2] = 0.0f;
09
pHandle->SubStep[3] = 0.0f;
10
pHandle->SubStep[4] = 0.0f;
11
pHandle->SubStep[5] = 0.0f;
12
13
pHandle->SubStepDuration = 0;
14
15
pHandle->Jerk = 0.0f;
16
pHandle->CruiseSpeed = 0.0f;
17
pHandle->Acceleration = 0.0f;
18
pHandle->Omega = 0.0f;
19
pHandle->OmegaPrev = 0.0f;
20
pHandle->Theta = 0.0f;
21
pHandle->ThetaPrev = 0.0f;
22
pHandle->ReceivedTh = 0.0f;
23
pHandle->TcTick = 0;
24
pHandle->ElapseTime = 0.0f;
25
26
pHandle->PositionControlRegulation = DISABLE;
27
pHandle->PositionCtrlStatus = TC_READY_FOR_COMMAND;
28
29
pHandle->pENC = pENC;
30
pHandle->pSTC = pSTC;
31
pHandle->PIDPosRegulator = pPIDPosReg;
32
33
pHandle->MecAngleOffset = 0;
34 }
STM32 技术开发手册
www.ing10bbs.com
TC_MoveCommand()
这个是 MOVEMENT 动作的初始化执行函数,当通过串口通信或者代码设定
目标值,就会 调用 MC_ProgramPositionCommandMotor1()函数, 只要设定的
Duration 不等于 0 就会进入这个函数,在这个函数当中主要是将 Duration 分为 7
段,并且计算出 Jerk 和 Acceleration 用于后续的控制。
代码 21-9 movement 指令
00 bool TC_MoveCommand(PosCtrl_Handle_t *pHandle, float startingAngle,
01
float angleStep, float movementDuration)
02 {
03
04
bool RetConfigStatus = false;
05
float fMinimumStepDuration;
06
07
if (pHandle->PositionCtrlStatus == TC_READY_FOR_COMMAND) {
08
pHandle->PositionControlRegulation = ENABLE;
09
10
fMinimumStepDuration = (9.0f * pHandle->SamplingTime);
11
12
// WARNING: Movement duration value is rounded to the nearest
13
valid value
14
//
[(DeltaT/9) / SamplingTime]: shall be an integer
15
value
16
pHandle->MovementDuration = (float)((int)(movementDuration /
17
fMinimumStepDuration)) *
18
fMinimumStepDuration;
19
20
pHandle->StartingAngle = startingAngle;
21
pHandle->AngleStep = angleStep;
22
pHandle->FinalAngle = startingAngle + angleStep;
23
24
// SubStep duration = DeltaT/9 (DeltaT represents the total
25
duration of the programmed movement)
26
pHandle->SubStepDuration = pHandle->MovementDuration / 9.0f;
27
28
// Sub step of acceleration phase
29
pHandle->SubStep[0] = 1 * pHandle->SubStepDuration; /* Sub30
step 1 of acceleration phase */
31
pHandle->SubStep[1] = 2 * pHandle->SubStepDuration; /* Sub32
step 2 of acceleration phase */
33
pHandle->SubStep[2] = 3 * pHandle->SubStepDuration; /* Sub34
step 3 of acceleration phase */
35
36
// Sub step of deceleration Phase
37
pHandle->SubStep[3] = 6 * pHandle->SubStepDuration; /* Sub38
step 1 of deceleration phase */
39
pHandle->SubStep[4] = 7 * pHandle->SubStepDuration; /* Sub40
step 2 of deceleration phase */
41
pHandle->SubStep[5] = 8 * pHandle->SubStepDuration; /* Sub42
step 3 of deceleration phase */
43
44
// Jerk (J) to be used by the trajectory calculator to
45
integrate (step by step) the target position.
46
// J = DeltaTheta/(12 * A * A * A) => DeltaTheta = final
47
position and A = Sub-Step duration
48
pHandle->Jerk = pHandle->AngleStep / (12 * pHandle->
49
SubStepDuration * pHandle->SubStepDuration *
50
pHandle->SubStepDuration);
51
STM32 技术开发手册
www.ing10bbs.com
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71 }
// Speed cruiser = 2*J*A*A)
pHandle->CruiseSpeed = 2 * pHandle->Jerk * pHandle->
SubStepDuration * pHandle->
SubStepDuration;
pHandle->ElapseTime = 0.0f;
pHandle->Omega = 0.0f;
pHandle->Acceleration = 0.0f;
pHandle->Theta = startingAngle;
pHandle->PositionCtrlStatus = TC_MOVEMENT_ON_GOING;
/* new
trajectory has been programmed
*/
RetConfigStatus = true;
}
return (RetConfigStatus);
其实 MOVEMENT 本质上就是一个 S 型加减速控制算法,曲线模型是七段式
的二次函数加速曲线,也就是在加速阶段,分为 3 段,第一段是加速度直线上升,
第二段是加速度匀速,第三段是加速度直线下降,而减速阶段则刚好与加速阶段
镜像对称。速度、加速度和加速度的变化率(jerk)的曲线变化如图 21-1 所示。
首先是 MovementDuration,这个是输入参数 movementDuration 四舍五入成
采 样 周 期 9𝑛 倍 。 举 个 简 单 的 例 子 , 假 设 采 样 周 期 是 0.01s , 设 定 的
movementDuration 是 2s,那么计算出来的 MovementDuration 就是 1.98s,就是
采样周期的 9*22 倍()
。
SubStepDuration 是每一段时间的时基,数值上等于 𝑀𝑜𝑣𝑒𝑚𝑒𝑛𝑡𝐷𝑢𝑟𝑎𝑡𝑖𝑜𝑛 ⁄9。
就是要将输入参数 Duration 四舍五入之后均分为 9 份,每一份的时间间隔都是
SubStepDuratuin,而每一个 SubStepDuratuin 都是采样时间的𝑛倍,这样做的原因
是要防止实际运行轨迹偏离目标轨迹。从源码再结合图 21-1,可以分析得到其
中的𝑡0 = 𝑡1 = 𝑡2 = 𝑡4 = 𝑡5 = 𝑡6 ,而中间匀速部分的时间则是等于 3 倍的𝑡0 。
SubStep 是用于存储各个时间段的临界值,用于判断是否进入下一段。
Jerk(𝑗)就是加速度的变化率;Acceleration(𝑎)是加速度;CruiseSpeed(𝑉𝑐𝑠 )是巡
航速度,就是匀速段固定的速度值;Omega(𝜔)就是速度,单位是弧度每秒;
Theta(𝜃)就是位置值,单位是弧度;AngleStep(𝑆)是整个过程的位置增量,单位同
样是弧度。首先可以明确的是,速度曲线是由 7 段组成,中间一段是匀速,而变
速部分则分为匀加速和变加速,其中变加速的加速度是沿直线变化的。速度与位
置计算过程如下,其中速度Δ𝑉𝑥 为每一段的速度增量:
STM32 技术开发手册
www.ing10bbs.com
公式 21-1 速度与位置计算
𝑎𝑚𝑎𝑥 = 𝑗𝑡0
𝑡0
1
Δ𝑉0 = ∫ 𝑎 𝑑𝑡 = 𝑗𝑡02
2
0
Δ𝑉1 = 𝑎𝑚𝑎𝑥 𝑡1 = 𝑗𝑡02
𝑡2
1
Δ𝑉2 = ∫ 𝑎 𝑑𝑡 = 𝑗𝑡02
2
𝑡1
𝑉𝑐𝑠 = Δ𝑉0 + Δ𝑉1 + Δ𝑉2 = 2𝑗𝑡02
𝑡0
Δ𝑆0 = ∫ 𝑉0 𝑑𝑡 =
0
1 3
𝑗𝑡
6 0
𝑡1
Δ𝑆1 = 𝑉0 𝑡1 + ∫ 𝑉1 𝑑𝑡 =
𝑡0
1 3 1 3
𝑗𝑡 + 𝑗𝑡
2 0 2 0
𝑡2
1
Δ𝑆2 = 𝑉𝑐𝑠 𝑡2 − ∫ 𝑉2 𝑑𝑡 = 2𝑗𝑡03 − 𝑗𝑡03
6
𝑡1
Δ𝑆3 = 𝑉𝑐𝑠 𝑡3 = 6𝑗𝑡03
𝑆 = 2(Δ𝑆0 + Δ𝑆1 + Δ𝑆2 ) + Δ𝑆3 = 12𝑗𝑡03
这里的时间单位都是 s,位置单位都是弧度。
STM32 技术开发手册
www.ing10bbs.com
图 21-1 速度变化曲线
该函数最后将 PositionCtrlStatus 标志为 TC_MOVEMENT_ON_GOING,就是在
运行的时候会调用 MOVEMENT 相关函数。
TC_FollowCommand()
这是 FOLLOWING 动作初始化执行函数,following 是在设定 duration 为 0 的
时候才会执行的,但这不代表不需要考虑时间的问题,在这个函数当中是分情况
来处理的。
代码 21-10 目标位置 following
00 void TC_FollowCommand(PosCtrl_Handle_t *pHandle, float Angle)
01 {
02
float omega = 0, acceleration = 0, dt = 0;
03
04
// Estimate speed
05
if (pHandle->ReceivedTh > 0) {
06
// Calculate dt
07
dt = pHandle->TcTick * pHandle->SysTickPeriod;
08
pHandle->TcTick = 0;
09
10
omega = (Angle - pHandle->ThetaPrev) / dt;
STM32 技术开发手册
www.ing10bbs.com
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 }
}
// Estimated acceleration
if (pHandle->ReceivedTh > 1) {
acceleration = (omega - pHandle->OmegaPrev) / dt;
}
// Update state variable
pHandle->ThetaPrev = Angle;
pHandle->OmegaPrev = omega;
if (pHandle->ReceivedTh < 2) {
pHandle->ReceivedTh++;
}
pHandle->Acceleration = acceleration;
pHandle->Omega = omega;
pHandle->Theta = Angle;
pHandle->PositionCtrlStatus = TC_FOLLOWING_ON_GOING;
mode has been programmed */
/* follow
return;
ReceivedTh 是用来统计接收目标值的次数,一般情况下位置值通过串口通信
设定目标值,在第一、第二、第三次设定目标值的时候都会有不同的效果。当第
一次设定目标值的时候,ReceivedTh=0,设定的 Theta(θ)就是所设定的角度值,
omega(ω)等于 0。加速度 Acceleration 等于 0。注意这里的控制量 TcTick,这个变
量只有在初始化和这里才会复位为 0,并且会在系统滴答定时器中以 2kHz 的频
率计数。
在第二次设定目标值的时候,ReceivedTh=1,TcTick 就是从初始化到现在的
计数值,乘上滴答定时器的计数周期之后得到时间(𝛥𝑡 = 𝑡3 − 0),并且复位为 0
重新开始计数。速度值 omega(ω)就是第二次设定的目标值与第一次设定的目标
值的差除以𝛥𝑡。也就是说,电机从第一次所设定的目标位置走到第二次所设定的
目标位置花的时间是一样的(𝛥𝑡 = 𝑡4 − 𝑡3 )。
第三次设定目标值的时候,与第二次设定目标值情况差不多,但有根据前后
两次设定的速度值计算加速度。
根据代码和运行效果,可以总结出实际运行的位置-时间关系图:假设第一
次设定目标值 500,第二次设定目标值 1000,第三次设定目标值 1500。
STM32 技术开发手册
www.ing10bbs.com
图 21-2 following 位置曲线
t1,t3,t5 就是设定目标值的时刻,t2,t4,就是到达目标位置的时刻。在第
一次设定目标值的时候,是没有时间限制的,电机以最大输出转动到目标值。在
t2 到达目标位置,并在 t3 设置了第二次的目标值,在 t3 时刻,根据两次设定的
目标值,以及从 0 开始到 t3 的时间,计算出电机的平均速度 omega(ω),t4-t3 的
时间长度与 t3-0 的时间长度是一样的,到达第二次的目标值之后,电机是不会停
下来,然后在 t5 时刻设定第三次目标值,这个时候由于有加速度的存在,电机
运行轨迹就是一条二次方程曲线。
注意这只是目标角度值的变化曲线,实际运行的轨迹根据所设定的参数可以
非常接近这条曲线。
TC_PositionRegulation()
该函数被周期性的调用,也就是会在状态机中被调用,也就是位置控制的执
行 函 数 。 在 这 个 函 数 中 根 据 选 择 的 模 式 执 行 TC_MoveExecution() 或 者
TC_FollowExecution()这两个函数,这两个函数分别是 Movement 和 Following 的
更新目标角度 Theta(𝜃)的函数。
STM32 技术开发手册
www.ing10bbs.com
代码 21-11 位置规划
00 void TC_PositionRegulation(PosCtrl_Handle_t *pHandle)
01 {
02
03
int32_t wMecAngleRef;
04
int32_t wMecAngle;
05
int32_t wError;
06
int32_t hTorqueRef_Pos;
07
08
if ( pHandle->PositionCtrlStatus == TC_MOVEMENT_ON_GOING ) {
09
TC_MoveExecution(pHandle);
10
}
11
12
if ( pHandle->PositionCtrlStatus == TC_FOLLOWING_ON_GOING ) {
13
TC_FollowExecution(pHandle);
14
}
15
16
if (pHandle->PositionControlRegulation == ENABLE) {
17
wMecAngleRef = (int32_t)(pHandle->Theta * RADTOS16);
18
19
wMecAngle = SPD_GetMecAngle(STC_GetSpeedSensor(pHandle->pSTC));
20
wError = wMecAngleRef - wMecAngle;
21
hTorqueRef_Pos = PID_Controller(pHandle->PIDPosRegulator,
22
wError);
23
24
STC_SetControlMode( pHandle->pSTC, STC_TORQUE_MODE );
25
STC_ExecRamp( pHandle->pSTC, hTorqueRef_Pos, 0 );
26
}
27
28 }
如果是设定 duration 是 0,并且只设定一次目标值,那么目标角度值就是恒
定的,这里就只是一个普通的 PID 控制函数,但是如果设定了两次目标值,目标
角度值就是一个沿直线变化的增量,第三次设定目标值之后,Theta(𝜃)就会以二
次曲线增长。
如果设定的 duration 不是 0,那么就是用 TC_MoveExecution()更新 Theta(𝜃)。
𝜃是根据 jerk 计算出来的。
最后是 PID 控制算法根据角度差,计算出扭矩的目标值 hTorqueRef_Pos,
hTorqueRef_Pos 是用于在 FOC 周期当中使用 PID,所以这个是位置电流闭环控制
系统。
TC_MoveExecution()
用于更新角度值,从这个函数当中可以看到,movement 分为 7 段,每一段
只改变 Jerk 的值,然后再根据 Jerk 的值计算累记加速度、速度、角度值,这些参
数都是根据 Jerk 乘上时间计算出来的。 同时在这个函数当中还会判断时间到了
就停止转动。
STM32 技术开发手册
www.ing10bbs.com
代码 21-12 Movement 执行函数
00 void TC_MoveExecution(PosCtrl_Handle_t *pHandle)
01 {
02
03
float jerkApplied = 0;
04
05
if (pHandle->ElapseTime < pHandle->SubStep[0]) {
// 1st
06
Sub-Step interval time of acceleration phase
07
jerkApplied = pHandle->Jerk;
08
} else if (pHandle->ElapseTime < pHandle->SubStep[1]) {
// 2nd
09
Sub-Step interval time of acceleration phase
10
} else if (pHandle->ElapseTime < pHandle->SubStep[2]) {
// 3rd
11
Sub-Step interval time of acceleration phase
12
jerkApplied = -(pHandle->Jerk);
13
} else if (pHandle->ElapseTime < pHandle->SubStep[3]) {
//
14
Speed Cruise phase (after acceleration and before
15
deceleration phases)
16
pHandle->Acceleration = 0.0f;
17
pHandle->Omega = pHandle->CruiseSpeed;
18
} else if (pHandle->ElapseTime < pHandle->SubStep[4]) {
// 1st
19
Sub-Step interval time of deceleration phase
20
jerkApplied = -(pHandle->Jerk);
21
} else if (pHandle->ElapseTime < pHandle->SubStep[5]) {
// 2nd
22
Sub-Step interval time of deceleration phase
23
24
} else if (pHandle->ElapseTime < pHandle->MovementDuration) { //
25
3rd Sub-Step interval time of deceleration phase
26
jerkApplied = pHandle->Jerk;
27
} else {
28
pHandle->Theta = pHandle->FinalAngle;
29
pHandle->PositionCtrlStatus = TC_TARGET_POSITION_REACHED;
30
}
31
32
if ( pHandle->PositionCtrlStatus == TC_MOVEMENT_ON_GOING ) {
33
pHandle->Acceleration += jerkApplied * pHandle->SamplingTime;
34
pHandle->Omega += pHandle->Acceleration * pHandle->
35
SamplingTime;
36
pHandle->Theta += pHandle->Omega * pHandle->SamplingTime;
37
}
38
39
pHandle->ElapseTime += pHandle->SamplingTime;
40
41
if (TC_RampCompleted(pHandle)) {
42
if (pHandle->AlignmentStatus == TC_ZERO_ALIGNMENT_START) {
43
// Ramp is used to search the zero index, if completed
44
there is no z signal
45
pHandle->AlignmentStatus = TC_ALIGNMENT_ERROR;
46
}
47
pHandle->ElapseTime = 0;
48
pHandle->PositionCtrlStatus = TC_READY_FOR_COMMAND;
49
}
50 }
TC_FollowExecution()
Follow 模式的执行函数,在这个函数当中就只是累加速度和角度值。周期性
的更新角度和速度,从而控制电机轨迹。第一次设定目标值的时候,速度、加速
度是 0,角度不会有变化;第二次设定目标值,加速度是 0,但是速度不是 0,角
STM32 技术开发手册
www.ing10bbs.com
度会直线累加;第三次设定目标值,加速度不是 0,速度不是 0,角度值沿二次
曲线累加。
代码 21-13 Follow 执行函数
00 void TC_FollowExecution(PosCtrl_Handle_t *pHandle)
01 {
02
pHandle->Omega += pHandle->Acceleration * pHandle->SamplingTime;
03
pHandle->Theta += pHandle->Omega
* pHandle->SamplingTime;
04 }
TC_EncAlignmentCommand()
则是在 START 状态中被调用的函数,并且只会执行一次,目的是要确定原点
位置。如果编码器是带有 Z 相的编码器,那么就会执行一次 Movement 动作,转
动一圈的距离,搜索原点位置,如果是没有 Z 相的编码器,那么就会直接将当前
位置设定为原点。
代码 21-14 编码器原点对齐函数
00 void TC_EncAlignmentCommand(PosCtrl_Handle_t *pHandle)
01 {
02
int32_t wMecAngleRef;
03
04
if (pHandle->AlignmentStatus == TC_ALIGNMENT_COMPLETED) {
05
// Do nothing - EncAlignment must be done only one time after
06
the power on
07
} else {
08
if (pHandle->AlignmentCfg == TC_ABSOLUTE_ALIGNMENT_SUPPORTED) {
09
// If index is supported start the search of the zero
10
pHandle->EncoderAbsoluteAligned = false;
11
wMecAngleRef = SPD_GetMecAngle(STC_GetSpeedSensor(pHandle->
12
pSTC));
13
TC_MoveCommand(pHandle, (float)(wMecAngleRef) / RADTOS16,
14
Z_ALIGNMENT_NB_ROTATION,
15
Z_ALIGNMENT_DURATION);
16
pHandle->AlignmentStatus = TC_ZERO_ALIGNMENT_START;
17
} else {
18
// If index is not supprted set the alignment angle as
19
zero reference
20
pHandle->pENC->_Super.wMecAngle = 0;
21
pHandle->AlignmentStatus = TC_ALIGNMENT_COMPLETED;
22 //
pHandle->PositionCtrlStatus = TC_READY_FOR_COMMAND;
23
pHandle->PositionControlRegulation = ENABLE;
24
}
25
}
26 }
注意这里的第 22 行的 PositionCtrlStatus= TC_READY_FOR_COMMAND;语句
是被注释掉的,在新生成的 FOC 源码当中是没有被注释掉的,PositionCtrlStatus
用于标记运动控制状态,在 TC_MoveCommand()函数和 TC_FollowCommand()函数
STM32 技术开发手册
www.ing10bbs.com
当中,都会将其标记为 TC_xxx_ON_GOING(xxx 代指 follow 和 Movement),并且在
实际运行的时候也是检测这个状态标志是否为 xxx_ON_GOING 状态,是则进行轨
迹规划,修正目标位置,如果不是则跳过轨迹规划的语句,在
TC_PositionRegulation()函数里面可以明显的看到这个状态标志的作用。
TC_EncAlignmentCommand()这个函数是在状态机第一次进入 START 状态之
后被调用,并且只会执行一次,如果在调用这个函数之前就设定好目标值,也就
是 调 用 了 TC_MoveCommand() 函 数 或 者 TC_FollowCommand() 函 数 使 得
PositionCtrlStatus 被标记为 TC_xxx_ON_GOING,然后再启动电机并进入这个函数
里面,那么将会修改 PositionCtrlStatus 状态值,导致实际运行的时候无法进行轨
迹规划,无法正常运行。表现为:如果是 Follow 模式,则第一次设定的目标值可
以正常运行,因为第一次设定目标值不需要修改速度(ω)和目标位置,当时第二
次和第三次设定目标值都会受到影响;如果是 Movement 模式,则不能够正常启
动,因为无法更新速度值和目标位置。但是如果是在启动之后再设定目标值,那
就不会造成影响。
现在将其注释掉了,无论是启动前设定的目标值还是启动之后再设定目标
值都没有问题。
TC_RampCompleted()
这个函数用于判断 MOVEMENT 动作是否结束。ElapseTime 是在更新目标值
的时候累加采样周期,如果数值大于运行时间,说明电机已经到达目标位置。
代码 21-15 判断 Ramp 是否结束
00 bool TC_RampCompleted(PosCtrl_Handle_t *pHandle)
01 {
02
bool retVal = false;
03
04
// Check that entire sequence (Acceleration - Cruise 05
Deceleration) is completed.
06
if (pHandle->ElapseTime > pHandle->MovementDuration + pHandle->
07
SamplingTime) {
08
retVal = true;
09
}
10
return (retVal);
11 }
TC_EncoderReset()
这个函数并没有被调用到,只能猜测是用于将当前位置设定为起点。
STM32 技术开发手册
www.ing10bbs.com
代码 21-16 EncoderReset
00 void TC_EncoderReset(PosCtrl_Handle_t *pHandle)
01 {
02
if ((!pHandle->EncoderAbsoluteAligned) && (pHandle->
03
AlignmentStatus == TC_ZERO_ALIGNMENT_START)) {
04
pHandle->MecAngleOffset = pHandle->pENC->_Super.hMecAngle;
05
pHandle->pENC->_Super.wMecAngle = 0;
06
pHandle->EncoderAbsoluteAligned = true;
07
pHandle->AlignmentStatus = TC_ALIGNMENT_COMPLETED;
08
pHandle->PositionCtrlStatus = TC_READY_FOR_COMMAND;
09
pHandle->Theta = 0.0f;
10
}
11
12
ENC_SetMecAngle(pHandle->pENC , pHandle->MecAngleOffset);
13 }
其他接口函数
这些函数都不是什么重要的函数,只是反馈一些数值/状态。
代码 21-17 接口函数
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
float TC_GetCurrentPosition(PosCtrl_Handle_t *pHandle)
{
return ((float)( (SPD_GetMecAngle(STC_GetSpeedSensor(pHandle->pSTC)
)) / RADTOS16) );
}
float TC_GetTargetPosition(PosCtrl_Handle_t *pHandle)
{
return (pHandle->FinalAngle);
}
float TC_GetMoveDuration(PosCtrl_Handle_t *pHandle)
{
return (pHandle->MovementDuration);
}
PosCtrlStatus_t TC_GetControlPositionStatus(PosCtrl_Handle_t *pHandle)
{
return (pHandle->PositionCtrlStatus);
}
AlignStatus_t TC_GetAlignmentStatus(PosCtrl_Handle_t *pHandle)
{
return (pHandle->AlignmentStatus);
}
void TC_IncTick(PosCtrl_Handle_t *pHandle)
{
pHandle->TcTick++;
}
STM32 技术开发手册
www.ing10bbs.com
21.3 使用 MC-Test 软件
在 ST Motor Control Workbench 这个软件当中,没有位置控制模式相关的功
能,既不能调试 PID 参数,也不能设定目标值,所以在调试位置控制模式的例程
的时候是不能使用这个软件,所以要么就等下个更新的版本,有或者直接在代码
中编程控制。
为了能方便的调试位置控制模式的 PID 参数,可以使用 MCTest 软件,这个
是根据 st 的电机控制库的通信协议开发的控制软件。功能上跟 st Motor Control
Workbench 相比精简了很多,这只是一个控制用的上位机,不能生成源码,但是
可以用这个软件设定电机目标值和位置环的 PID 参数。在操作上也是跟 st 推出
的控制软件非常接近的。下面将会介绍怎样使用这个软件来控制例程
FOC_v5.4.3_42PMSM_EncoderPosition。
图 21-3 ing10-MCTest preview
STM32 技术开发手册
www.ing10bbs.com
1. MC-Test 功能介绍
连接设备
图 21-4 串口设置
➢ 选择正确的端口号,默认的波特率为 115200、数据位为 8bit、校验
位为 NoParity,停止位为 1。
➢ 连接了控制板之后没有出现对应的端口号,可以点击 刷新按钮刷新
端口号。
➢ 点击打开串口。
端口号是连接了控制设备的通信端口,具体端口可以从设备管理器当中找到。
运行状态
图 21-5 运行状态与标志位
➢ 最上方表示电机运行状态,也就是状态机的状态。
STM32 技术开发手册
www.ing10bbs.com
➢ 下方的是电机的错误状态,如果出现错误则亮红色,如果错误没有被
解除就亮黄色,错误已经解除就亮灰色。
基础控制
图 21-6 基础控制功能
➢ 复位:这个功能需要修改源码才能实现。在 user_interface.c 文件中
的 739 行,找到 switch 语句的分支 case MC_PROTOCOL_CMD_RESET:然后添
加 HAL_NVIC_SystemReset()函数 ,就可以实现复位的功能。发送该指令不会
有任何反馈(例程已经添加该语句)。
图 21-7 添加代码
➢ 启动:启动电机转动,前提是需要给电机合适的目标参数。
➢ 停止:停止电机转动。
➢ 启动/停止:点击一次就启动,再次点击就是停止。
➢ Stop Ramp:停止 Ramp 的动作。
➢ 校准对齐:专用于编码器模式,可以出发电机校准对齐。
➢ Fault Ack:应答错误,当出现错误并且已经排除错误的时候,就可以
点击应答排除错误状态。
STM32 技术开发手册
www.ing10bbs.com
➢ Speed/Torque:控制模式选择,可选速度模式或者扭矩模式。
位置控制
图 21-8 位置控制
➢ Kp、Ki、Kd:这是位置模式的 PID 参数,取值范围是 0~65535。
➢ 目标值:电机转动的圈数,可以是浮点数。
➢ 周期:如果是 0,则表示以 following 模式运行,则电机以全速达到
目标值;如果不为 0,则是以 movement 模式运行,也就是 S 型加减速模式。
单位是 s。
➢ Ramp:发送目标值和周期值。如果多次发送周期为 0 的 Ramp 指令,
则会有不同的效果,具体需要阅读源码理解。
➢ 位置显示:显示当前转动的圈数。
STM32 技术开发手册
www.ing10bbs.com
速度控制
图 21-9 速度控制
➢ Kp、Ki:速度控制的 PID 参数,取值范围是 0~65535。
➢ Spd:单独设置电机的目标转速,单位是 RPM。
➢ 目标值:电机转速的目标值,单位是 RPM。
➢ 周期:电机达到目标值所需要的时间。单位是 ms。
➢ Ramp:发送目标值和周期,执行 Ramp 指令。
➢ 电机转速:显示当前电机的转速。
电流控制
图 21-10 电流控制
➢ Iq Kp、Iq Ki:电流(Torque)控制的 PID 参数,取值范围是 0~65535。
STM32 技术开发手册
www.ing10bbs.com
➢ Id Kp、Id Ki:电流(Flux)控制的 PID 参数,取值范围是 0~65535。
➢ Ref:电流 Iq,Id 的目标值,格式是 s16A。
➢ Torque(Iq):Torque 的测量值,格式是 s16A。。
➢ Flux(Id):Flux 的测量值,格式是 s16A。。
指令通用发送功能
图 21-11 通用指令
➢ 电机_默认:选择控制的电机。
➢ FrameID:数据帧的 ID。
➢ REG_TARGET_MOTOR/CMD:寄存器/指令。根据 FrameID 决定,只能
二选一。
➢ 可编辑文本框:payload,只能输入十六进制字符,是实际的数据,
可以为空。
➢ 不可编辑文本框:用于显示反馈数据。
➢ 发送:发送一帧数据。
这是所有指令的通用发送功能。可通过鼠标操作选择指令发送。如果是需要
写入数据,那么需要在有右边的文本输入框输入数据值。这个功能需要掌握通信
协议才能使用。
Log 显示
图 21-12 Log 记录
STM32 技术开发手册
www.ing10bbs.com
Log:记录每一个操作的通信状态,注意通用的指令不会被记录下来,只会
在状态栏显示。接收到 F0 开头的数据帧则表示这个指令有被正确的接收到,如
果反馈的数据帧是以 FF 开头则表示通信错误。
2. 烧录程序
首先第一步,应该是先解压工程,例程是一个压缩包文件,需要先解压出来,
然后找到*.uvprojx 文件,也就是 Keil MDK 的工程文件,双击打开之后,编译并下
载程序。
图 21-13 工程目录
图 21-14 编译下载按钮
IAR 软件也是同样的操作过程,编译然后下载就行。
注意 IAR 的软件版本不能太低,否则会不能打开工程文件,建议 IAR 更新到
8.30 或以上版本。
STM32 技术开发手册
www.ing10bbs.com
3. 启动电机
图 21-15 设定目标值
控制电机 Following 转动的关键在于设定 duration 为 0,也就是运动周期设
置为 0。然后点击左边的 Ramp 按钮,然后点击启动按钮,电机将会启动,并且
将会在下方显示电机当前的实际位置,如果觉得位置不够准确,可以通过反复调
整 PID 来控制电机位置。
如果需要电机 Movement 转动,需要设定一个周期时间,这个时间是浮点型
数据,单位是 s。但是要注意的是,周期值最少也要是采样周期的 9 倍以上。如
果采样周期是 10ms,那么就需要至少设定周期为 0.09s,最好是与采样周期为整
数倍的关系,如果设置的过大,则会造成电机转动过慢,可能会出现停顿的现象。
例程的采样周期就是 10ms,这个不是固定数值,可以根据运行结果调整的,但
是调整采样周期就需要重新整定 PID 参数。
注意如果点击启动按钮之后报警 Under Voltaget,那可能是 PID 参数过大导
致的,可以尝试将 Kp 设置小一点。
STM32 技术开发手册
www.ing10bbs.com
第22章 FOC5.x 控制算法分析
22.1 控制流程与相关算法
22.1.1 简单介绍面向对象编程
在 FOC5.x 以前的版本,使用完整的面向对象编程的概念,在源码当中使用
大量 this 指针,Objects 等,在 FOC5.x 之后,逐渐抛弃了面向对象编程的做法,
不再隐藏数据,不再定义虚函数,但还是可以看到面向对象的一些影子,就像结
构体中的_Super,原本含义就是一个超类的指针,这就涉及到类的继承了。虽然
源码中关于面向对象编程的地方并不多,也不算太复杂,不过足以影响到源码的
阅读了。所以下面就简单的介绍了面向对象编程的概念,以及如何使用 C 语言实
现面向对象编程,并且给出源码当中的例子,提供了一个对比,这样在阅读源码
的时候就可以迅速理解源码当中的数据结构和函数指针关系。
实际上也可以完全不用管面向对象还是面向过程编程,直接将其视为结构体
和函数指针就行,如果看完面向对象编程的解释之后感到困惑不解,那可以不用
理会这部分内容的。
1. 核心概念
面向对象编程(Object Oriented Programming,简称 OOP),是一种程序设计
思想,OOP 将对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向对象程序设计以对象为核心,认为程序是由一系列对象组成,而类是对象的
抽象化,对象则是类的实例化。
举个简单的例子:
“电机”是一个类,指具有定子、转子、固定外壳等结构,将机械能和电能
之间互相转换的机械装置。将所有的电机都抽象成“电机”这个类。
“直流无刷电机”,
“有刷电机”,
“步进电机”,
“伺服电机”,
“异步电机”等
就是“电机”这个类的实例。每个电机都具有定子,转子,固定外壳这些机械结
构,因为它们都属于电机这一类,所以具有相同的属性,。除了这些静态的属性
STM32 技术开发手册
www.ing10bbs.com
之外,电机还可以将电能转换成机械能,也就是电机会转动,那“电机会转动”
这个动作同样也属于“电机”这个类,称为方法(method)。
“电机”这个类是非常广泛的,按照工作电源种类划分,可分为“直流电机”
和“交流电机”,而“直流电机”又可以根据结构及工作原理划分为“无刷直流
电机”和“有刷直流电机”等等......这一种类的划分实际上就是继承。
“直流有刷
电机”继承了“直流电机”这一类,所以“直流有刷电机”具有“直流电机”的
所有属性和方法,这个时候“直流电机”就是基类(也有称为父类、超类),
“直
流有刷电机”就是派生类(或者叫子类)。派生类具有基类的所有属性和方法,
并且可以拥有自己的属性和方法(基类和派生类是相对而言,基类也可以是另一
个基类的派生类)。一个子类可以继承多个父类。
当“直流有刷电机”这个类落实到具体某个品牌某个型号的电机的时候,那
么这电机就是“直流有刷电机”这个类的对象。而这个电机,具有“直流有刷电
机”,
“直流电机”,
“电机”的所有属性和方法,也就是具有转子、定子、固定结
构、电刷,直流电驱动,可以转动等属性和方法。
图 22-1 基类和派生类关系图解
STM32 技术开发手册
www.ing10bbs.com
2. C 语言实现面向对象编程
类与对象的实现
结构体是一种 struct 关键字声明的自定义数据类型,可以包含任意的数据
类型,可以使用结构体代替类的实现。在 FOC 源码当中,使用了结构体来实现不
同模块的属性定义,例如对于霍尔传感器,则定义了一个 HALL_Handle_t 结构体,
结构体成员包含了所有与 HALL 传感器相关的属性变量,像速度采样频率,电角
度等。
用 HALL_Handle_t 定义了 HALL_M1 这个变量,称为实例化(instances),将
HALL 传感器这个概念实例化了,而 HALL_M1 就是 HALL_Handle_t 的具体对象,
当代码中需要针对霍尔传感器操作的时候,像测量速度、读取电角度等,就需要
访问 HALL_M1 这个对象。如果有两个霍尔传感器,那么就可以使用 HALL_Handle_t
这个类再实例化一个对象 HALL_M2。
代码 22-1 HALL_Handle_t 类定义
01 typedef struct {
02
SpeednPosFdbk_Handle_t _Super;
03
/* SW Settings */
04
uint8_t SensorPlacement; /*!< 定义霍尔传感器安装角度
05
...
06 } HALL_Handle_t;
另外可以看到在 HALL_Handle_t 当中,第一个结构成员是_Super。这个_Super
也是一个具体的对象,不过这个对象就非常抽象了,不代是任何具体的事物,而
是速度位置反馈数据类型,这个是将速度类型和位置类型的数据整合在一个结构
体里面,定义为 SpeednPosFdbk_Handle_t 类。
HALL_Handle_t 除 了 包 含 有 _Super 之 外 , 还 有 其 他 的 数 据 成 员 ( 不 止
SensorPlacement 一个)
,所以说 HALL_Handle_t 是继承 SpeednPosFdbk_Handle_t
的派生类,SpeednPosFdbk_Handle_t 属于基类,HALL_Handle_t 属于派生类。
HALL_M1 可 以 通 过 _Super 来 访 问 基 类 的 属 性 和 方 法 , 而 基 类
SpeednPosFdbk_Handle_t 却无法访问派生类的属性和方法。HALL_Handle_t 也可
以继承多个基类,只要定义结构体成员是其他的基类对象就行。
STM32 技术开发手册
www.ing10bbs.com
虽然_Super 是基类的对象,是本质上还是 HALL_M1 这个对象的成员,如果
_Super 是一个指针,那就是指向另外一个对象了,HALL_Handle_t 就不再是继承
SpeednPosFdbk_Handle_t 了。
另外说一下_Super,本身的含义就是用于访问和调用一个对象的父类上的函
数,这个在单继承上是很便利的。
方法的实现
方法是指:为达成某个特定的目的,可以用来实际操作的模式或者步骤。在
代码中就是一个实现某个功能的函数。类是可以包含有方法的,但是结构体不能,
结构体只是一个数据类型,不能定义一个函数。但是结构体却可以定义函数指针,
当函数指针指向具体的函数地址的时候,就可以通过指针跳转到某个具体的函数,
所以在代码中也使用了大量的函数指针实现类的方法。同时,函数指针还可以将
底层的操作封装起来,作为顶层和底层函数之间的一个接口,这样可以方便移植。
代码 22-2 函数指针类型别名
01 typedef void ( *PWMC_Generic_Cb_t )( PWMC_Handle_t * pHandle );
10 typedef void * ( *PWMC_IrqHandler_Cb_t )( PWMC_Handle_t * pHandle,
unsigned char flag );
19 typedef void ( *PWMC_GetPhaseCurr_Cb_t )( PWMC_Handle_t * pHandle,
Curr_Components * pStator_Currents );
代码 22-2 就显示了函数指针的类型别名,这里是三个指针类型,分别是
PWMC_Generic_Cb_t、PWMC_IrqHandler_Cb_t、PWMC_GetPhaseCurr_Cb_t,形式:
typedef 返回类型(*新类型)
(参数表)。PWMC_Generic_Cb_t 是一个指针类型,
由它所定义的指针指向一个无返回数据的函数,函数参数是 PWMC_Handle_t *
pHandle。例如代码 22-3 就是定义了一个 pFctSwitchOffPwm 的函数指针,指向
R3F4XX_SwitchOffPWM 的函数。
代码 22-3 函数指针
01 PWMC_Generic_Cb_t
pFctSwitchOffPwm;
02 pFctSwitchOffPwm = &R3F4XX_SwitchOffPWM;
03 void R3F4XX_SwitchOffPWM( PWMC_Handle_t * pHdl ) {
04 ...
05}
通过指针函数来实现方法,使得对象与底层分离,只定义了一个框架,具体
的实现方法由底层函数实现,在使用这个指针的时候就是调用了这个函数。
STM32 技术开发手册
www.ing10bbs.com
总结一下:使用结构体实现类,而结构体类型的变量实现对象,而类的属性
和方法就是结构体成员变量和函数指针,这就是 C 语言面向对象编程的基本框
架。虽然还有继承,但是这个并不是什么需要深入讨论的问题。
即使是使用面向对象编程,但是本质上还是 C 语言编程,只要是熟悉 C 语言
的开发者都有足够的能力阅读源码。在阅读源码的时候也可以忽略以上的面向对
象编程的概念,这并不是十分严重的影响。
22.1.2 FOC 电机启动流程
在源码当中,使用了按键和串口通信两种方式控制电机启动和停止,下面就
从这两个方面入手看看具体是如何控制电机启动的。不过在这之前先要了解上位
机与控制板之间的通信协议。
1. 串口通信协议
通信协议分为两层,最外层的称为通信帧协议,底层的称为电机控制协议。
主机发送到从机
最外层如下图,主要包括五个部分:FRAME START,PAYLOAD_LENGTH,
PAYLOAD_ID,PAYLOAD[n],CRC。
图 22-2 通信协议帧
STM32 技术开发手册
www.ing10bbs.com
FRAME START
分为两部分,高 3 位是电机编号,低 5 位是 FRAME ID。电机编号决定了这
一帧数据的控制对象是哪个电机。FRAME ID 则是决定这一帧数据主要用来干嘛
的。
Motor:电机编号,表示这一帧数据是针对哪一个电机。取值如图 22-3。
图 22-3 电机编号
FRAME ID:表示这一帧数据用来干嘛的。具体取值如下:
表格 22-1 FRAME ID 具体取值
宏定义
取值
说明
MC_PROTOCOL_CODE_SET_REG
0x01
写寄存器
MC_PROTOCOL_CODE_GET_REG
0x02
读寄存器
MC_PROTOCOL_CODE_EXECUTE_CMD
0x03
指令命令
MC_PROTOCOL_CODE_STORE_TOADDR
0x04
按地址存储数据
MC_PROTOCOL_CODE_LOAD_FROMADDR
0x05
按地址读取数据
MC_PROTOCOL_CODE_GET_BOARD_INFO
0x06
读取板子信息
STM32 技术开发手册
www.ing10bbs.com
MC_PROTOCOL_CODE_SET_RAMP
0x07
执行 Ramp 命令
MC_PROTOCOL_CODE_GET_REVUP_DATA
0x08
设置 RevUp 参数
MC_PROTOCOL_CODE_SET_REVUP_DATA
0x09
读取 Revup 参数
MC_PROTOCOL_CODE_SET_CURRENT_REF 0x0A
设置电流目标值
MC_PROTOCOL_CODE_GET_MP_INFO
0x0B
获取 MP 信息
MC_PROTOCOL_CODE_GET_FW_VERSION
0x0C
读取固件版本
PAYLOAD LENGTH
Payload Length 就是 Payload 数据段长度,所谓 Payload 就是关键数据。举个
简单的例子解释一下 payload,就比如说现在需要邮寄一个包裹,包裹连带包装
重 1kg,但是实际上真正的货物只有 0.8kg,但是给的费用却是按照 1kg 来算的,
而这 0.8kg 就是 payload,因为给的运费,主要是要运送这 0.8kg 的货物,那 0.2kg
的费用算是额外但是必须要给的,要是运输过程中因为没有包装而损坏 0.8kg 的
货物就得不偿失了。对于数据帧,真正控制电机的是后面的 Payload 数据段,属
于关键数据,而其他的都不是关键数据。但却是为了保证数据完整性而必须的。
Payload 又分为 Payload ID 和 Payload[n]。
Payload
数据段属于电机控制协议,根据 FRAME ID 的差异,可以有不同的数据格式。
Payload ID:表明 Payload[n]是什么数据。需要根据 FRAME ID 来使用。当
FRAME ID 是 MC_PROTOCOL_CODE_SET_REG 和 MC_PROTOCOL_CODE_GET_REG 时,
这个时候的 Payload ID 就是寄存器的 ID,Payload[n]就是要写的值或者是无。具
体的寄存器 ID 有 131 个,可以在 mc_extended_api.h 文件里面找到,这是一个枚
举变量。
表格 22-2 寄存器 ID
枚举
值
枚举
值
MC_PROTOCOL_REG_TARGET_MOTOR
0
MC_PROTOCOL_REG_FLUXWK_BUS_MEAS
33
MC_PROTOCOL_REG_FLAGS
1
MC_PROTOCOL_REG_RUC_STAGE_NBR
34
MC_PROTOCOL_REG_STATUS
2
MC_PROTOCOL_REG_I_A
35
STM32 技术开发手册
www.ing10bbs.com
MC_PROTOCOL_REG_CONTROL_MODE
3
MC_PROTOCOL_REG_I_B
36
MC_PROTOCOL_REG_SPEED_REF
4
MC_PROTOCOL_REG_I_ALPHA
37
MC_PROTOCOL_REG_SPEED_KP
5
MC_PROTOCOL_REG_I_BETA
38
MC_PROTOCOL_REG_SPEED_KI
6
MC_PROTOCOL_REG_I_Q
39
MC_PROTOCOL_REG_SPEED_KD
7
MC_PROTOCOL_REG_I_D
40
MC_PROTOCOL_REG_TORQUE_REF
8
MC_PROTOCOL_REG_I_Q_REF
41
MC_PROTOCOL_REG_TORQUE_KP
9
MC_PROTOCOL_REG_I_D_REF
42
MC_PROTOCOL_REG_TORQUE_KI
10
MC_PROTOCOL_REG_V_Q
43
MC_PROTOCOL_REG_TORQUE_KD
11
MC_PROTOCOL_REG_V_D
44
MC_PROTOCOL_REG_FLUX_REF
12
MC_PROTOCOL_REG_V_ALPHA
45
MC_PROTOCOL_REG_FLUX_KP
13
MC_PROTOCOL_REG_V_BETA
46
MC_PROTOCOL_REG_FLUX_KI
14
MC_PROTOCOL_REG_MEAS_EL_ANGLE
47
MC_PROTOCOL_REG_FLUX_KD
15
MC_PROTOCOL_REG_MEAS_ROT_SPEED
48
MC_PROTOCOL_REG_OBSERVER_C1
16
MC_PROTOCOL_REG_OBS_EL_ANGLE
49
MC_PROTOCOL_REG_OBSERVER_C2
17
MC_PROTOCOL_REG_OBS_ROT_SPEED
50
MC_PROTOCOL_REG_OBSERVER_CR_C1
18
MC_PROTOCOL_REG_OBS_I_ALPHA
51
MC_PROTOCOL_REG_OBSERVER_CR_C2
19
MC_PROTOCOL_REG_OBS_I_BETA
52
MC_PROTOCOL_REG_PLL_KI
20
MC_PROTOCOL_REG_OBS_BEMF_ALPHA
53
MC_PROTOCOL_REG_PLL_KP
21
MC_PROTOCOL_REG_OBS_BEMF_BETA
54
MC_PROTOCOL_REG_FLUXWK_KP
22
MC_PROTOCOL_REG_OBS_CR_EL_ANGLE
55
MC_PROTOCOL_REG_FLUXWK_KI
23
MC_PROTOCOL_REG_OBS_CR_ROT_SPEED
56
MC_PROTOCOL_REG_FLUXWK_BUS
24
MC_PROTOCOL_REG_OBS_CR_I_ALPHA
57
MC_PROTOCOL_REG_BUS_VOLTAGE
25
MC_PROTOCOL_REG_OBS_CR_I_BETA
58
MC_PROTOCOL_REG_HEATS_TEMP
26
MC_PROTOCOL_REG_OBS_CR_BEMF_ALPH
59
A
MC_PROTOCOL_REG_MOTOR_POWER
27
MC_PROTOCOL_REG_OBS_CR_BEMF_BETA
60
MC_PROTOCOL_REG_DAC_OUT1
28
MC_PROTOCOL_REG_DAC_USER1
61
STM32 技术开发手册
www.ing10bbs.com
MC_PROTOCOL_REG_DAC_OUT2
29
MC_PROTOCOL_REG_DAC_USER2
62
MC_PROTOCOL_REG_SPEED_MEAS
30
MC_PROTOCOL_REG_MAX_APP_SPEED
63
MC_PROTOCOL_REG_TORQUE_MEAS
31
MC_PROTOCOL_REG_MIN_APP_SPEED
64
MC_PROTOCOL_REG_FLUX_MEAS
32
MC_PROTOCOL_REG_IQ_SPEEDMODE
65
枚举
值
枚举
值
MC_PROTOCOL_REG_EST_BEMF_LEVEL
66
MC_PROTOCOL_REG_HFI_PI_PLL_KI
99
MC_PROTOCOL_REG_OBS_BEMF_LEVEL
67
MC_PROTOCOL_REG_HFI_PI_TRACK_KP
100
MC_PROTOCOL_REG_EST_CR_BEMF_LEVEL
68
MC_PROTOCOL_REG_HFI_PI_TRACK_KI
101
MC_PROTOCOL_REG_OBS_CR_BEMF_LEVEL
69
MC_PROTOCOL_REG_SC_CHECK
102
MC_PROTOCOL_REG_FF_1Q
70
MC_PROTOCOL_REG_SC_STATE
103
MC_PROTOCOL_REG_FF_1D
71
MC_PROTOCOL_REG_SC_RS
104
MC_PROTOCOL_REG_FF_2
72
MC_PROTOCOL_REG_SC_LS
105
MC_PROTOCOL_REG_FF_VQ
73
MC_PROTOCOL_REG_SC_KE
106
MC_PROTOCOL_REG_FF_VD
74
MC_PROTOCOL_REG_SC_VBUS
107
MC_PROTOCOL_REG_FF_VQ_PIOUT
75
MC_PROTOCOL_REG_SC_MEAS_NOMINALS
108
PEED
MC_PROTOCOL_REG_FF_VD_PIOUT
76
MC_PROTOCOL_REG_SC_STEPS
109
MC_PROTOCOL_REG_PFC_STATUS
77
MC_PROTOCOL_REG_SPEED_KP_DIV
110
MC_PROTOCOL_REG_PFC_FAULTS
78
MC_PROTOCOL_REG_SPEED_KI_DIV
111
MC_PROTOCOL_REG_PFC_DCBUS_REF
79
MC_PROTOCOL_REG_UID
112
MC_PROTOCOL_REG_PFC_DCBUS_MEAS
80
MC_PROTOCOL_REG_HWTYPE
113
MC_PROTOCOL_REG_PFC_ACBUS_FREQ
81
MC_PROTOCOL_REG_CTRBDID
114
MC_PROTOCOL_REG_PFC_ACBUS_RMS
82
MC_PROTOCOL_REG_PWBDID
115
MC_PROTOCOL_REG_PFC_I_KP
83
MC_PROTOCOL_REG_SC_PP
116
MC_PROTOCOL_REG_PFC_I_KI
84
MC_PROTOCOL_REG_SC_CURRENT
117
MC_PROTOCOL_REG_PFC_I_KD
85
MC_PROTOCOL_REG_SC_SPDBANDWIDTH
118
MC_PROTOCOL_REG_PFC_V_KP
86
MC_PROTOCOL_REG_SC_LDLQRATIO
119
MC_PROTOCOL_REG_PFC_V_KI
87
MC_PROTOCOL_REG_SC_NOMINAL_SPEED
120
STM32 技术开发手册
www.ing10bbs.com
MC_PROTOCOL_REG_PFC_V_KD
88
MC_PROTOCOL_REG_SC_CURRBANDWIDTH
121
MC_PROTOCOL_REG_PFC_STARTUP_DURATION
89
MC_PROTOCOL_REG_SC_J
122
MC_PROTOCOL_REG_PFC_ENABLED
90
MC_PROTOCOL_REG_SC_F
123
MC_PROTOCOL_REG_RAMP_FINAL_SPEED
91
MC_PROTOCOL_REG_SC_MAX_CURRENT
124
MC_PROTOCOL_REG_RAMP_DURATION
92
MC_PROTOCOL_REG_SC_STARTUP_SPEED
125
MC_PROTOCOL_REG_HFI_EL_ANGLE
93
MC_PROTOCOL_REG_SC_STARTUP_ACC
126
MC_PROTOCOL_REG_HFI_ROT_SPEED
94
MC_PROTOCOL_REG_SC_PWM_FREQUENCY
127
MC_PROTOCOL_REG_HFI_CURRENT
95
MC_PROTOCOL_REG_SC_FOC_REP_RATE
128
MC_PROTOCOL_REG_HFI_INIT_ANG_PLL
96
MC_PROTOCOL_REG_PWBDID2
129
MC_PROTOCOL_REG_HFI_INIT_ANG_SAT_DIFF
97
MC_PROTOCOL_REG_SC_COMPLETED
130
MC_PROTOCOL_REG_HFI_PI_PLL_KP
98
MC_PROTOCOL_REG_UNDEFINED
131
当 FRAME ID 是 MC_PROTOCOL_CODE_EXECUTE_CMD 的时候,Payload ID 就
是控制指令 ID,具体的指令如表格 22-3 所示。无代表没有找到任何说明。
表格 22-3 控制指令 ID
控制指令
取值
说明
MC_PROTOCOL_CMD_START_MOTOR
0x01
启动电机
MC_PROTOCOL_CMD_STOP_MOTOR
0x02
停止电机
MC_PROTOCOL_CMD_STOP_RAMP
0x03
停止 Ramp 动作
MC_PROTOCOL_CMD_RESET
0x04
复位
MC_PROTOCOL_CMD_PING
0x05
无
MC_PROTOCOL_CMD_START_STOP
0x06
启动/停止
MC_PROTOCOL_CMD_FAULT_ACK
0x07
应答错误
MC_PROTOCOL_CMD_ENCODER_ALIGN 0x08
编码器模式对其
MC_PROTOCOL_CMD_IQDREF_CLEAR
0x09
复位 Iqd ref
MC_PROTOCOL_CMD_PFC_ENABLE
0x0A
使能 PFC
MC_PROTOCOL_CMD_PFC_DISABLE
0x0B
禁止 PFC
MC_PROTOCOL_CMD_PFC_FAULT_ACK
0x0C
PFC 应答错误
STM32 技术开发手册
www.ing10bbs.com
MC_PROTOCOL_CMD_SC_START
0x0D
无
MC_PROTOCOL_CMD_SC_STOP
0x0E
无
PFC:Power Factor Correction,FOC5x 尚不支持。
CRC
校验码。虽然名字叫 CRC,但是根据实际的算法来看,并不是 CRC 校验算法,
具体的计算过程如图 22-4。就是将这一帧的数据,除了 CRC 之外全部累加起来,
然后将得到的 16 位数据的高位字节和低位字节再累加一次,得到的就是 8 位的
CRC 校验码(姑且还是称为 CRC 校验码吧)。
图 22-4 校验算法
从机发送到主机
从机每接收到一帧数据都会返回应答帧。具体的通信协议如图 22-5。
图 22-5 从机返回数据
STM32 技术开发手册
www.ing10bbs.com
虽然同样是由 FRAME START,但是实际内容已经是不一样,Payload Length 同
样是表示后面的 Payload[n]的长度,CRC 也是将前面的数据累加之后再将高低位
累加一次得到的 8 位校验码。
FRAME START
有两种情况,一种是数据帧正确相应,另一种是数据帧错误相应。正常情况
下就返回 0xF0,出错就返回 0xFF。
图 22-6 返回帧的 FRAME_START
Payload
Payload[n]视接收到的数据帧的不同而有所不同,如果发送帧要求读取数据,
那么 Payload[n]则返回相应的数据,如果是控制指令,可以不需要返回数据,可
以是无。但是如果出错了,就反馈错误码(ERROR CODE)。错误码包含了通信错
误和控制指令错误等信息,具体说明可以参考图 22-7。
STM32 技术开发手册
www.ing10bbs.com
图 22-7 Payload[n]的实际内容
主机和从机通信的数据都是无符号整型数据,都是低位字节在前,高位字节
在后。
通讯范例
下面给出一些通信部分常用的数据帧,解释了数据传输格式。
读取寄存器值
图 22-8 读取寄存器值
STM32 技术开发手册
www.ing10bbs.com
从机反馈到主机的时候,寄存器值无论是 16 位还是 32 位的,就是低位字节
在前,高位字节在后,如果有错误,就直接反馈错误码。
执行命令
图 22-9 执行命令
执行 Ramp 指令:
Ramp 动作需要一定的时间实现,在传输的数据当中,包括有最终的速度值,
和所设定的时间,速度值是 32 位的 RPM 值。同样是低位在前,高位在后发送,
接着就是持续时间(duration),单位是 ms,同样是低位在前,高位再后发送。
如图 22-10,FS[x]就是 Final speed,DR 就是 duration。
图 22-10 执行 Ramp 指令
STM32 技术开发手册
www.ing10bbs.com
图 22-11 Ramp 指令参数意义
读取 Revup 参数:
Revup 是无感模式下的启动参数,包括有速度曲线的目标值和持续时间。
Revup 是可以有多段加速,所以可以设置/读取多次。
图 22-12 读取 Revup 数据
FS_[x]是 4 个字节的速度值,低位字节在前,FT 是 Final Torque,两个字节,
DR 是 Duration,也是两个字节,同样是低位字节在前。注意 FT 是数字量,不是
物理量,可以参考公式 20-4 对数值进行转换。State 则是设置不同段的启动曲线
参数,参考图 22-13。
STM32 技术开发手册
www.ing10bbs.com
图 22-13 启动曲线分段
设置电流目标值:
设置电流目标值有两个参数,包括𝐼𝑞 ,𝐼𝑑 ,同样是低位在前,数值转换可以
参考公式 20-4。
图 22-14 设置电流 Iq、Id
2. KEY1 启动电机流程
当按下 KEY1 之后,会进入外部中断回调函数,然后调用 MC_Start_Motor1()
函数,这个函数在 mc_api.c 文件当中,这是电机库的 api(应用程序接口),专门
STM32 技术开发手册
www.ing10bbs.com
用 于 给 其 他 任 务 程 序 调 用 控 制 电 机 的 。 MC_StartMotor1() 函 数 则 是 调 用 了
MCI_StartMotor(),这个是电机控制接口。再接着就是调用 STM_NextState()修改
状态机的状态为 IDLE_START。然后就将控制启动的过程交给状态机,由状态机实
现电机启动。
图 22-15 按键启动电机流程
在状态机当中,首先是进入 IDLE START,这个是从 IDLE 启动的第一个状态,
在这个状态当中,导通下桥臂,给自举电容充电,然后标记下一个状态为 CHARGE
BOOT CAP。
CHARGE BOOT CAP,则是什么都不做,只是在等待充电时间完成,在充电完
成之后就启动 ADC 采样 0 电流值。然后标记下一个状态为 OFFSET CALIB。
OFFSET CALIB,在 OFFSET CALIB 里面读取 0 电流值。然后标记下一个状态为
CLEAR。
CLEAR 只是清空 HALL 和 FOC 核心算法相关的所有变量,以防止数据错误。
然后标记下一个状态为 START。START 其实什么都没有,只是标记下一个状态为
START RUN。
STM32 技术开发手册
www.ing10bbs.com
START RUN,则是计算电流目标值 Iq,设置目标速度值,执行 Ramp 动作,
如果 Ramp 的加速时间是 0,那就是直接将目标值设定的用户所设定的值。下一
个状态是 RUN。
RUN,计算更新电流目标值,并且查询状态是否改变了,查询是否执行新的
指令,例如从 Torque 模式切换成 Speed 模式。
在正常转动的时候就是一直在 RUN 状态当中。只有在再一次按下 KEY1 的时
候,才会修改状态机为 ANY STOP。
ANY STOP,切断了 PWM 的输出,并且复位相关的变量,配置一个计数器的
计数值,然后转入 STOP。
STOP,等到计数器计数为 0,然后转入 STOP IDLE。STOP IDLE 再转入 IDLE。
在状态机的实现函数当中是没有 IDLE 相关语句的,也就是要转入 default。
3. 串口通信启动电机流程
串口通信启动电机,也就是通过 ST Motor Control Workbench 软件(下文以
WB 或者上位机代称),启动控制电机转动,设定目标值等。
首先在代码中看看串口通信的处理流程。
图 22-16 串口接收中断处理
首先进入中断之后会调用 LL_USART_ReceiveData8()读取接收到的一个字节,
将这个字节 rx_data 作为参数交给 UFCP_RX_IRQ_Handler(rx_data),然后根据
RxFrameLevel 判断接收的是哪个数据。如果 RxFrameLevel==0,那就是第一个字
STM32 技术开发手册
www.ing10bbs.com
节,按照通信协议,就是 FRAME START,使用接收缓存器 RxFrame.code 暂存。如
果 RxFrameLevel==1,就是第二个字节,就是 PAYLOAD LENGTH,使用 RxFrame.Size
暂存。其余的就是 PAYLOAD 和 CRC 了,由于 RxFrame.Size 记录着 PAYLOAD 的数
据长度,所以在 default 将固定长度的数据保存在 RxFrame.Buffer,最后一个字节
暂存在 RxFrame.FrameCRC。
在接收到 CRC 之后,立即调用 FCP_CalcCRC()进行 CRC 校验。如果数据帧正
确就进入 MCP_ReceivedFrame()函数处理这一帧数据。
图 22-17 MCP_ReceivedFrame()处理数据帧
在 MCP_ReceviedFrame()当中,首先是判断 Code,也就是 FRAME START,划
分 MOTOR 和 FRAME ID,在设定的电机编号之后,就根据 FRAME ID 进入 switch
语句。FRAME ID 如果是读/写寄存器,那么 RxFrame.buffer[0]就是寄存器的 ID(Reg
ID),只要根据寄存器 ID 进行读写操作就行。如果是 FRAME ID 是执行指令,那
么 RxFrame.buffer[0]就是指令 ID(Cmd ID)。如果是读/写 Revup 数据,那么
RxFrame.buffer[0]就是 State。详细的处理可以参考通信协议说明。
在上位机点击启动按钮 Start Motor,如图 22-18。从机接收到的 Frame ID 就
是 MC_PROTOCOL_CODE_EXECUTE_CMD,在读取 bcmdID 之后,进入执行命令的
的用户接口函数 UI_ExecCmd()。
STM32 技术开发手册
www.ing10bbs.com
图 22-18 上位机控制启动电机
图 22-19 执行命令函数
可以对比图 22-18 和图 22-19 ,这两个功能几乎是一致的,也就是说指令
ID 其实就是上位机按钮。点击 Start Motor 按钮之后进入 MCI_StartMotor()函数。
然后就是修改状态机进入 IDLE_START,从而启动电机。
4. 状态机实现流程
图 22-15 给出了状态机的调用流程,状态机的真正实现方法是在系统滴答定
时器当中,在初始化的时候将滴答定时器中断频率为 2KHz,也就是 500us。在中
STM32 技术开发手册
www.ing10bbs.com
断当中执行了三个任务。2KHz 是执行其他任务的基准频率,其他的任务需要计
数的时候在这里进行计数操作。
图 22-20 SYSTick 中断任务处理
中频任务
首先是 MC_Scheduler(),这里面调用了 TSK_MediumFrequencyTaskM1(),而
调用的频率则是由 hMFTaskCounterM1 计数器决定,只有当 hMFTaskCounterM1
计数为 0 的时候才调用,可以通过宏 MF_TASK_OCCURENCE_TICKS 来设置调用频
率, 例程设置的是 2ms,也就是 MF_TASK_OCCURENCE_TICKS 等于 4。另外还有
其 他 的 计 数 器 , hBootCapDelayCounterM1 用 于 自 举 电 容 充 电 时 间 计 数 ,
hStopPermanencyCounterM1 用于停止状态的持续时间计数。
状态机就是一个中等频率的任务,执行频率是 500Hz。这个也是速度环的控
制频率,在这里调用的 HALL_CalcAvrgMecSpeed01Hz()函数就是用于计算电机转
子转速。TSK_MediumFrequencyTaskM1()当中查询当前的状态位,然后执行相应的
动作。
STM32 技术开发手册
www.ing10bbs.com
图 22-21 状态机实现
在 START RUN 和 RUN 状态当中,调用 FOC_CalcCurrRef ()函数,这是一个更
新目标值的函数,在这个函数里面根据当前运行在 Speed 模式或者是 Torque 模
式,更新相应的目标值,也就是速度环或者电流环,这个是最外层的 PID 控制。
顺带提一下就是底层的是𝐼𝑞 ,𝐼𝑑 的 PID 控制。
在 KEY1 启动电机的章节中有提到状态机的切换流程,状态机由滴答定时器
控制实现,每隔 2ms 就执行一次:
IDLE_START : 这 是 从 IDLE 启 动 的 初 步 状 态 , 首 先 是 调 用 了 函 数
R3F4XX_TurnOnLowSides()设定定时器比较值为 0,并且使能输出,这样导通的下
桥臂,然后复位计数器的值,计数器就是前面提到的 hBootCapDelayCounterM1。
然后就标记下一个状态为 CHARGE_BOOT_CAP。
STM32 技术开发手册
www.ing10bbs.com
CHARGE_BOOT_CAP:等待计数器为 0,也就是大概等了 10ms 之后,关闭
PWM 输出,关闭下桥臂,然后调用 PWMC_CurrentReadingCalibr( CRC_START )开
始采样。标记进入 OFFSET_CALIB。
OFFSET_CALIB:调用 PWMC_CurrentReadingCalibr( CRC_EXEC )执行电流采样
校准。然后标记进入 CLEAR。注意这里的电流采样校准,实际上是按照 FOC 运行
的模式来进行采样的,但是在读取电流值的时候是读取三相的电流值,而真正在
运行 FOC 算法驱动电机的时候读取电流是读取两相的电流值。
CLEAR:可以说是一个过渡状态,这里只是复位了相关的变量,然后调用
R3F4XX_SwitchOnPWM()函数启动定时器的更新中断,使能三相通道输出,就是在
这里定时器开始输出 PWM 信号。
START RUN:FOC_InitAdditionalMethods()是预留用于执行额外的功能,实际
函 数 是 没 有 任 何 东 西 。 调 用 了 FOC_CalcCurrRef() 用 于 设 定 初 始 的 目 标 值 。
MCI_ExecBufferedCommands()用于执行用户指令,就是 Ramp 动作,或者切换
speed 或 torque 模式。然后标记进入 RUN。
RUN:run 是运行状态,只需要监控是否有新的指令,需要执行,或者计算
新的目标值就行了。FOC_CalcCurrRef()用于计算当前的目标值,如果是执行 Ramp
指令,那就是在这个函数里面更新目标值。
ANY_STOP:这是从其他状态切换过来的状态,在这个状态下复位相关变量,
然后调用 TSK_SetStopPermanencyTimeM1(),设置 hStopPermanencyCounterM1 计
数器。然后标记下一次进入 STOP 状态。
STOP:等待 hStopPermanencyCounterM1 计数器为 0,然后进入 IDLE。
以上就是 TSK_MediumFrequencyTaskM1()实现的内容(),但是这个并不是状
态机的全部内容,状态机还有 FAULT_NOW 和 FAULT_OVER 这两种状态(暂且不
论编码器模式),这两状态是在 TSK_SafetyTask()执行的,
安全任务
这是安全任务相关的函数,专门用来判断是否出现故障。这个函数调用了两
个 函 数 , 一 个 是 TSK_SafetyTask_PWMOFF() , 一 个 是 RCM_ExecUserConv() 。
STM32 技术开发手册
www.ing10bbs.com
RCM_ExecUserConv()这个函数是预留给用户执行自定义的 ADC 规则通道转换,不
过前提是需要先注册 ADC 通道,在例程里面用处不是很大。
TSK_SafetyTask_PWMOFF(),在这个函数里面进行温度采样,电流过流查询,
总线电压采样,然后判断是否有故障,如果有,就标记进入 FAULT_NOW 状态,
并且立即关断 PWM 输出,复位相关变量。如果下一次调用这个函数的时候,故
障已经清除,那么就标记进入 FAULT_OVER 状态,只关断 PWM,这个时候是需要
上位机发送 Fault Ack 数据帧才能推退出 FAULT_OVER。如果在 FAULT_OVER 时再
次出现故障,就会又重新进入 FAULT_NOW 状态。
UI 任务
UI_Scheduler()这个是用于给用户接口任务提供时钟计数。也就是将计数器执
行 自 减 操 作 , 这 里 计 数 器 包 括 bUITaskCounter , bCOMTimeoutCounter ,
bCOMATRTimeCounter。从名字上也可以看得出来,这是三个跟串口通信相关的
计数器,在串口通信的时候可能需要用到。(实际并没有被用到)
代码 22-4 UI_Scheduler()
01 void UI_Scheduler(void)
02 {
03
if (bUITaskCounter > 0u) {
04
bUITaskCounter--;
05
}
06
07
if (bCOMTimeoutCounter > 1u) {
08
bCOMTimeoutCounter--;
09
}
10
11
if (bCOMATRTimeCounter > 1u) {
12
bCOMATRTimeCounter--;
13
}
14 }
5. FOC 核心算法流程
前面有提到,无论是使用 KEY1 启动电机,还是使用串口通信启动电机,都
会调用到 MCI_StartMotor()修改状态机,进入 IDLE_Start 状态,然后交由状态机
启 动 定 时 器 输 出 PWM 。 当 状 态 机 进 入 CLEAR 的 时 候 , 会 调 用
R3F4XX_SwitchOnPWM()函数,这个时候才是真正的启动了 FOC 算法,因为这个
函数启动了定时器输出,使能了更新中断,触发 ADC 采样,而 ADC 中断就是进
入 FOC 算法的入口。
STM32 技术开发手册
www.ing10bbs.com
图 22-22 FOC 核心算法流程
在启动定时器之后,会触发两个中断,一个是由 CH4 比较匹配事件触发 ADC
采样完成中断,另一个是定时器的更新中断。这里值得一提的时候,定时器的重
复计数器设置为 1,也就是两次溢出更新事件才会进入一次更新中断,但是由于
定时器是中心对齐模式,所以一个周期内有两次溢出事件,也就是一个 PWM 周
期触发一次更新中断,并且这个更新中断是在计数器向下计数溢出的时候才会进
入。而 ADC 采样和 FOC 算法则是在更新中断之前就完成的。
图 22-23 更新中断触发时刻
STM32 技术开发手册
www.ing10bbs.com
首先是会进入 ADC 完成中断,然后调用 TSK_HighFrequencyTask()函数,在这
个函数当中,调用 HALL_CalcElAngle()计算电角度,电角度单位是 dpp,就是每个
PWM 周期的电角度,然后就是进入 FOC_CurrController()函数。
在这个函数当中就是读取电角度值,然后调用 PWMC_GetPhaseCurrents ()函
数,读取相电流。PWMC_GetPhaseCurrents()函数是调用了一个指针函数用于读取
电流值,在刚启动的时候,这个函数指针指向电流采样校准函数
R3F4XX_HFCurrentsCalibrationAB()和 R3F4XX_HFCurrentsCalibrationC()。在采样得
到三相电流之后执行 FOC 算法计算三相输出占空比和电压矢量所在扇区,只不
过在校准电流阶段是没有实际输出 PWM 而已(MOE 没有置位)。正常情况下采
样电流是只采样两相电流就足够的,但是这个前提是已经知道上一个 PWM 脉冲
所控制的电压矢量所在的扇区和占空比(就是要执行 SVPWM 算法),这样才能
决定对哪两个通道采样。所以在校准阶段就先对三相电阻进行采样,得到三相电
流然后计算初步的占空比和扇区(执行 SVPWM 算法需要电流值),然后就可以
按照正常的采样模式只采样两相电流。
在得到三相电流之后(第三相是计算出来的),就可以进行坐标变换了,调
用 MCM_Clarke()和 MCM_Park()进行 Clark 变换和 Park 变换,得到𝐼𝑞 ,𝐼𝑑 。使用 PI
控制器就得到𝑉𝑞 ,𝑉𝑑 ,PI 控制器的输入参数是𝐼𝑞 ,𝐼𝑑 和用户所设定的目标值𝐼𝑞 𝑟𝑒𝑓,
𝐼𝑑 𝑟𝑒𝑓,输出是𝑉𝑞 ,𝑉𝑑 。这里就需要对 PID 算法有一定的了解了,无论输入量是什
么,输出量必然是电压值,因为 PWM 能控制的只有占空比,也就是电压值而已,
所以输入是电流,输出就变成了电压(其实是可以通过各种的公式证明的,不过
没这个必要而已)。
使用 Circle_Limitation()函数防止电压矢量过调制,将𝑉𝑞 ,𝑉𝑑 等比例调节。然
后调用 MCM_Rev_Park()函数得到𝑉𝛼 ,𝑉𝛽 ,该输入参数还有一个,就是电角度。最
后就是调用 PWMC_SetPhaseVoltage()修改 PWM 的占空比和设置下一个 PWM 周
期的采样点。
另外在电流采样开始之前就将 bSoFOC 复位,然后再最后的设置 PWM 占空
比之前检查这个变量是否是 1,如果是 1 就说明在 FOC 算法执行期间,触发了一
次更新中断,就代表 PWM 频率设置过快了。
STM32 技术开发手册
www.ing10bbs.com
图 22-24 FOC 算法重点内容
其中的重点难点就是电流采样,坐标变换,SVPWM。但是这些都已经有详细
的算法介绍了,具体的实现方法可以参考代码解析。
最后只要更新 DAC 输出就完成了。但是由于 YS-F4Pro 的控制引脚有限,所
以只有 PA5 可以作为 DAC 输出,
22.1.3 HALL 测量电角度和速度
在前面有解释过使用 HALL 传感器测量速度和电角度,具体的代码流程如图
22-26 和图 22-27。为了保证能够得到最大的分辨率,所以在测速的时候是动态
调整预分频值的。在定时器的捕获中断函数可以分为三个部分:第一部分是捕获
霍尔传感器的电平信号,获得电角度;第二部分是根据定时器的计数值,修改预
分频值,保证有较高的分辨率;第三部分是计算速度值。
获取电角度
霍尔传感器有两种放置方法,一种是每隔 60°安装一个霍尔传感器,一个
是每隔 120°安装一个霍尔传感器,可以对其中一个情况读取引脚电平的时候采
取一些措施,使得在软件上兼容两种霍尔传感器,如图 22-26,将 60°的传感器
H2 反相之后就跟 120°的 H3 波形一致。
STM32 技术开发手册
www.ing10bbs.com
图 22-25 不同安装位置的霍尔传感器输出波形
以图中 120°为例,假设当前霍尔信号顺序是 State 5 -> State 1 -> State 3 ->
State 2 -> State 6 -> State 4,在 State5 的时候距离实际的 0 位置(A 相反电动势最
高点)相差 PhaseShift(同步电角度)。以这个同步电角度为基准,每次触发捕获
只需要在 State5 的基础上增加电角度,就可以得到实际的电角度值。
修改预分频值
根据定时器有没有溢出就可以判断当前电机转速是否过慢,如果溢出,也就
是在定时器计数一个周期之后,还没有捕获到任何信号变化,说明电机转速太慢
了,这个时候可以增加预分频值,延长计数周期。如果定时器计数器没有溢出,
这个时候又分两种情况,一种就是电机转速刚刚好在合适的范围内,这个时候就
不需要额外的处理,但是如果电机转速比较快,使得捕获值低于 0x5500,这个时
候就可以减少预分频值,缩短计数周期,使得捕获值增大。
如果是增加预分频值,就标记 RatioInc 为 true,如果是减少预分频值,就标
记 RatioDec 为 true,由于定时器的预分频器是由预装载寄存器的,在修改之后不
会立即生效,而是在下一次的更新事件之后才会生效(也就是下一次的捕获事件
STM32 技术开发手册
www.ing10bbs.com
之后),如果上一次有修改预分频器,那这个时候就需要注意当前的捕获值对应
的实际预分频值。
图 22-26 霍尔信号捕获中断
STM32 技术开发手册
www.ing10bbs.com
图 22-27 修改预分频器及计算速度
计算电角度
FOC 算法是高频任务,最高执行频率与 PWM 的输出频率相当,就是在每个
PWM 周期就要执行一次 FOC 算法,但是每次执行 FOC 算法的时候都需要使用到
电角度值。但是霍尔传感器每次触发捕获都是在 60°的位置,这样一来就不方
便直接使用捕获得到的电角度来作为 FOC 的参数计算了。FOC 执行频率与 HALL
信号频率如图 22-28 所示。
STM32 技术开发手册
www.ing10bbs.com
图 22-28 霍尔信号频率与 FOC 执行频率之间的关系
而代码中就采取平均值的做法来计算每个 FOC 的电角度变化量(由于执行
FOC 算法是在采样电流之后,也就是跟定时器的 PWM 频率相关,所以 FOC 的执
行频率与 PWM 频率是成倍数关系,只不过例程刚好使用相同的频率),然后计
算电角度均值,再每一次执行 FOC 算法的时候就在上一次的电角度值基础上加
上这个均值,采用这种方法来作为当前的实时电角度。这就需要计算出霍尔信号
的频率值,根据霍尔信号的频率值和 FOC 执行频率计算出电角度均值,也就是
dpp 为单位的速度值。
令霍尔传感器的信号跳变频率为𝑓𝐻𝑎𝑙𝑙 ,FOC 的执行频率是𝑓𝐹𝑂𝐶 (代码当中是
使用速度测量频率,速度测量频率是与 FOC 执行频率一致的,这一点值得注意),
定时器的时钟源频率是𝑓𝑡𝑖𝑚_𝑐𝑙𝑘(这里是使用时钟源而不是计时器的计数频率,因
为定时器的预分频器是需要修改的变量,会影响到计数频率)。每个 FOC 周期的
电角度值为𝑆𝑒𝑛𝑠𝑜𝑟𝑆𝑝𝑒𝑒𝑑,那么有:
公式 22-1 每 FOC 周期的电角度
𝑆𝑒𝑛𝑠𝑜𝑟𝑆𝑝𝑒𝑒𝑑 =
60 ∗ 𝑓𝐻𝑎𝑙𝑙
𝑓𝐹𝑂𝐶
STM32 技术开发手册
www.ing10bbs.com
𝑓𝐻𝑎𝑙𝑙 则是可以通过定时器捕获得到,在进入捕获中断的时候,得到捕获值
(𝐶𝐴𝑃)和预分频值(𝑃𝑆𝐶),再根据定时器的时钟源频率,可以计算出霍尔信号
的频率值:
公式 22-2 霍尔信号频率
𝑓𝐻𝑎𝑙𝑙 =
𝑓𝑡𝑖𝑚_𝑐𝑙𝑘
𝐶𝐴𝑃 ∗ 𝑃𝑆𝐶
代码中使用的是数字量而不是物理量来计算,所以实际的 60°就换成为
65536/6。最后整理一下就是:
公式 22-3 dpp 格式的速度值
𝑆𝑒𝑛𝑠𝑜𝑟𝑆𝑝𝑒𝑒𝑑 =
65536 ∗ 𝑓𝑡𝑖𝑚_𝑐𝑙𝑘
6 ∗ 𝐶𝐴𝑃 ∗ 𝑃𝑆𝐶 ∗ 𝑓𝐹𝑂𝐶
将一些常量提取出来作为系数,就只剩下𝐶𝐴𝑃 ∗ 𝑃𝑆𝐶了,而这两个就是需要
在捕获中断得到的值。得到的𝑆𝑒𝑛𝑠𝑜𝑟𝑆𝑝𝑒𝑒𝑑是电角度的速度值,是每个控制周期
的电角度数字量(dpp)
,对角速度积分就是角度,在每次 FOC 运算之前对速度值
累加得到的就是角度值。
电角度补偿
𝑆𝑒𝑛𝑠𝑜𝑟𝑆𝑝𝑒𝑒𝑑是计算所得的每个 FOC 周期的电角度增量,在应用的时候,每
个 FOC 周期都在上一个电角度值上加上𝑆𝑒𝑛𝑠𝑜𝑟𝑆𝑝𝑒𝑒𝑑,作为当前的电角度,用于
SVPWM 计算,那么就将用于 FOC 算法的电角度称为虚拟电角度,而由霍尔传感
器测量得到的电角度称为实际电角度,在运行过程中,由于速度不会稳定不变,
𝑓
计算出来的𝑆𝑒𝑛𝑠𝑜𝑟𝑆𝑝𝑒𝑒𝑑只是一个预测值,如果在执行𝑓 𝐹𝑂𝐶 次 FOC 算法之后还没
𝐻𝑎𝑙𝑙
有检测到下一次的霍尔信号边沿,那么在下一次霍尔信号边沿的时候虚拟电角度
就比实际的电角度要大。那这个时候就需要对虚拟电角度进行补偿。
具体的补偿方法就是在速度测量的时候,将实际电角度和虚拟电角度之间的
角度变化,平均到每个速度测量周期内的每个 FOC 周期内。因为速度测量周期
跟 FOC 周期都是固定的,比值不变,使用速度测量的频率来给实际电角度和虚拟
STM32 技术开发手册
www.ing10bbs.com
电角度同步。代码中就是以𝐶𝑜𝑚𝑝𝑆𝑝𝑒𝑒𝑑作为补偿值,每次累加电角度的时候都同
时累加𝐶𝑜𝑚𝑝𝑆𝑝𝑒𝑒𝑑,而𝐶𝑜𝑚𝑝𝑆𝑝𝑒𝑒𝑑则是在测量速度的时候才会更新一次。
公式 22-4 电角度补偿
𝐶𝑜𝑚𝑝𝑆𝑝𝑒𝑒𝑑 =
𝑀𝑒𝑎𝑠𝑢𝑟𝑒𝑑𝐸𝑙𝐴𝑛𝑔𝑙𝑒 − ℎ𝐸𝑙𝐴𝑛𝑔𝑙𝑒
ℎ𝑀𝑒𝑎𝑠𝑢𝑟𝑒𝑚𝑒𝑛𝑡𝐹𝑟𝑒𝑞𝑢𝑒𝑛𝑐𝑦⁄𝑆𝑝𝑒𝑒𝑑𝑆𝑎𝑚𝑝𝑙𝑖𝑛𝑔𝐹𝑟𝑒𝑞𝐻𝑧
速度格式转换
速度格式有两种:
𝟎𝟏𝐇𝐳:供速度环 PID(在速度控制模式下)及用户界面层使用,单位是𝐝𝑯𝒛。
表示转子转速,转子是 0.1Hz,实际是 0.1RPS。
1[𝑑𝐻𝑧] = 1["01Hz" ] = 0.1[𝐻𝑧]
Digit Per Period(𝒅𝒑𝒑),表示每个 FOC 周期转子角度的变化量(𝒔𝟏𝟔𝒅𝒆𝒈𝒓𝒆𝒆)。
该格式可直接累加从而得到转子的电角度。
公式 22-5 𝒅𝒑𝒑与𝒓𝒂𝒅⁄𝒔的关系
1
[𝒔𝟏𝟔𝒅𝒆𝒈𝒓𝒆𝒆⁄𝒔]
𝑇𝐹𝑂𝐶(𝑠)
2𝜋
=
[𝒓𝒂𝒅⁄𝒔]
65536 × 𝑇𝐹𝑂𝐶(𝑠)
2𝜋
=
× 𝐹𝐹𝑂𝐶(𝐻𝑧) [𝒓𝒂𝒅⁄𝒔]
65536
1𝒅𝒑𝒑 =
𝟎𝟏𝑯𝒛与𝒅𝒑𝒑关系为:
公式 22-6 两种速度格式关系式
𝜔𝑑𝑝𝑝 = 𝜔01𝐻𝑧
65536
10 × 𝐹𝐹𝑂𝐶(𝐻𝑧)
公式 22-6 说明了两种速度单位之间的关系,并不是𝒅𝒑𝒑的定义式。下面对
公式 22-6 做个简单的说明:
公式 22-5 说明了1𝑑𝑝𝑝与对应了多少𝑟𝑎𝑑⁄𝑠,设𝜔𝑟𝑎𝑑⁄𝑠 单位是𝑟𝑎𝑑⁄𝑠,𝜔𝑑𝑝𝑝 为
𝑑𝑝𝑝格式表示的速度值,那么可以根据这条公式,推导出:
公式 22-7 转速单位转换
2𝜋
× 𝐹𝐹𝑂𝐶(𝐻𝑧) [𝒓𝒂𝒅⁄𝒔]
65536
65536
= 𝜔𝑟𝑎𝑑⁄𝑠 ×
[𝒅𝒑𝒑]
2𝜋 × 𝐹𝐹𝑂𝐶(𝐻𝑧)
𝜔𝑟𝑎𝑑⁄𝑠 = 𝜔𝑑𝑝𝑝 ×
𝜔𝑑𝑝𝑝
STM32 技术开发手册
www.ing10bbs.com
其中,𝜔𝑟𝑎𝑑⁄𝑠 是以𝑟𝑎𝑑⁄𝑠为单位的角速度,1 转就是2𝜋[𝑟𝑎𝑑],所以有:
公式 22-8 𝟎𝟏𝐇𝐳单位转换
𝜔𝑟⁄𝑠 =
𝜔𝑟𝑎𝑑⁄𝑠 𝜔01𝐻𝑧
=
2𝜋
10
将公式 22-8 代入公式 22-7 中替换掉𝜔𝑟𝑎𝑑⁄𝑠 ,就可以得到公式 22-6。
在系统滴答定时器的中断中调用 HALL_CalcAvrgMecSpeed01Hz()函数计算转
子转速,如代码 22-5,就是使用公式 22-6 进行单位转换。
代码 22-5 转换 dpp 和 01Hz
01 /* Converto el_dpp to Mec01Hz */
02 *hMecSpeed01Hz = ( int16_t )( ( *hMecSpeed01Hz *
03
( int32_t )pHandle->_Super.hMeasurementFrequency * 10 ) /
04
( 65536 *
( int32_t )pHandle->_Super.bElToMecRatio ) );
Download