本文章 来自原创专栏《ESP32教学专栏 (基于ESP-IDF)》,讲解如何使用 ESP-IDF 构建 ESP32 程序,发布文章并会持续为已发布文章添加新内容! 每篇文章都经过了精打细磨!
↓↓↓通过下方对话框进入专栏目录页↓↓↓
CSDN 请求进入目录 _ O x
是否进入ESP32教学导航(基于ESP-IDF)?
确定
文章目录
一、电机控制器MCPWM的模块简介
ESP32有两个MCPWM单元,可用于控制不同类型的电机。每个单元有三对PWM输出。(如下图,每对输出标记为A、B。共六对PWM输出)
除了PWM输出之外,MCPWM还具有其他功能。比如每个单元还能够收集诸如同步信号等输入,检测电机过电流或过电压等故障信号,以及在例如转子位置上获得捕获信号的反馈。
MCPWM单元的更详细的框图如下所示:
每个A/B对可由三个定时器中的任何一个MCPWM定时器0、1和2中的任何一个时钟。(相同的定时器可用于时钟多对PWM输出)
从上图我们不难发现,MCPWM具有的功能(上图彩色的虚线框)有:
- OPERATOR操作器模块
- CAPTURE状态捕获模块
- FAULT DETECT故障处理器模块
- CLOCK/TIMER时钟、时钟预分频器模块
(黑色虚线框指的是GPIO矩阵 (GPIO Matrix))
下面详细介绍每个部分
1、操作器模块 Operator
操作员 (Operator) 用于操作连接到MCPWM单元的电机。
例如改变旋转方向(顺时针或逆时针),或改变转速。
操作员输出一共有 3 对,我们可以对其施加控制信号。标记为“A”和“B”的称为一对。A、B均有自己对应的名为“Generator”的子模块来驱动诸如PWM的输出信号。
为了提供PWM信号,每个Operator本身由三个可用的定时器(MCPWM Timer)中的任何一个进行计时。
为了简化API,API会 自动关联 具有相同索引
Timer
以驱动Operator
。
例如Timer 0
与Operator 0
关联。
2、捕获模块 Capture
捕获模块在功能上相当于由沿中断控制的捕获定时器
对于无刷直流电机,控制的要求之一是感应转子位置。
为了完成这一任务,每个 MCPWM单元提供三个传感输入以及专用的硬件。该硬件能够检测输入信号的边缘,并测量信号之间的时间。
因此,控制软件更简单,CPU功率可能用于其他任务。下图为一个无刷直流电动机控制实例(CAP1
CAP2
CAP3
是Capture的输入)
注意:3个Capture可以在不使用PWM输出时单独使用,即只配置Capture实现边缘捕获功能。因此MCPWM还可用于非电机外设。
例如,使用MCPWM的Capture0去捕获HC-SR04超声波模块ECHO引脚的高电平时间,进而实现测距。
3、故障处理器模块 Fault Detect
MCPWM的每个单元都能够感知外部信号,包括有关电机、电机驱动器或连接到MCPWM的任何其他设备的故障信息。
每个单元有三个错误输入,可以路由到用户可选择的GPIO。当接收到故障信号时,MCPWM可以配置为对A/B输出执行四种预定义的动作之一:
- 锁定输出的当前状态
- 设置低输出
- 设置高输出
- 开关输出
用户应确定电机可能的故障模式,以及在检测到特定故障时应采取的行动。
例如对有刷电机驱动所有输出为低,或对步进电机锁定电流状态等。这个动作会使电机处于安全状态,以减少故障造成的损坏的可能性。
4、*载波 Carrier
MCPWM有一个载波子模块,如果使用互感原理(如通过变压器)向电机驱动传递A/B输出信号(例如需要让电机驱动器输入电流与ESP32 GPIO输出电流相互隔离)。任何A和B输出信号都可以100%占空,并且当电机在满载时需要稳定运行时,信号不会改变。
由于非交流信号无法与变压器等互感原理的电子元件相耦合,因此信号被载波子模块调制以产生交流波形,即可使耦合成为可能。
5、*中断 Interrupts
通过调用mcpwm_isr_register()
可以注册MCPWM中断处理程序。
在 ESP-IDF v4.3 及之前,实现捕获模块
Capture
需要中断实现。
但是 ESP-IDFv4.3 之后(不包括v4.3)。对MCPWM的 Capture API 进行了优化和简化。
下图为2021年8月11日
的Github esp-idf仓库下的esp-idf\examples\peripherals
文件夹截图。可以看到,6天前对MCPWM的Capture
示例进行了更新
注意,如果使用了mcpwm_capture_enable_channel()
,那么将安装一个默认的ISR例程来实现简化API的回调。因此,如果使用了mcpwm_capture_enable_channel()
,请不要再调用mcpwm_isr_register()
这个函数来注册中断。
二、使用 MCPWM 输出 PWM 信号
1、使用方法
(1)MCPWM 输出初始化
初始化MCPWM需要的步骤:
- 配置GPIO口
- 在一个
mcpwm_config_t
结构体中设置定时器频率和初始任务的设置。 - 非必须:设置定时器分辨率(默认为
10,000,000
)。使用函数mcpwm_group_set_resolution()
和mcpwm_timer_set_resolution()
- 使用上述参数调用
mcpwm_init()
以使配置生效。
1,配置GPIO口:使用函数mcpwm_gpio_init()
或函数mcpwm_set_pin()
。两者的区别是前者为指定的功能配置GPIO,而后者是一次性配置所有的GPIO。
前者:
函数名 | mcpwm_gpio_init() |
---|---|
函数原型 | esp_err_t mcpwm_gpio_init(mcpwm_unit_t mcpwm_num, mcpwm_io_signals_t io_signal, int gpio_num) |
含义 | 初始化一个GPIO |
返回值 | esp_err_t |
参数 | mcpwm_num类型为:mcpwm_unit_t ;MCPWM单元io_signal类型为: mcpwm_io_signals_t ;MCPWM功能signal,如MCPWM0A 表示某MCPWM 的 A 输出 gpio_num类型为: int ;表示想要配置为哪个GPIO |
后者: | |
函数名 | mcpwm_set_pin() |
- | :- |
函数原型 | esp_err_t mcpwm_set_pin(mcpwm_unit_t mcpwm_num, const mcpwm_pin_config_t *mcpwm_pin) |
含义 | 配置所有与MCPWM有关的GPIO |
返回值 | esp_err_t |
参数 | mcpwm_num类型为:mcpwm_unit_t ;表示MCPWM单元索引*mcpwm_pin类型为: mcpwm_pin_config_t 指针;表示一个结构体,包含所有与MCPWM功能对于的GPIO |
2,配置mcpwm参数
通过函数mcpwm_init()
,传递一个mcpwm_config_t
结构体指针
函数名 | mcpwm_init() |
---|---|
函数原型 | esp_err_t mcpwm_init(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, const mcpwm_config_t *mcpwm_conf) |
含义 | 初始化MCPWM |
返回值 | esp_err_t |
参数 | mcpwm_num类型为:mcpwm_unit_t ;表示MCPWM索引timer_num类型为: mcpwm_timer_t ;表示初始化哪个MCPWM定时器,对应与其相同索引的Operator*mcpwm_conf类型为: const mcpwm_config_t ;表示配置结构体指针 |
其中,结构体mcpwm_config_t
的成员如下:
typedef struct {
uint32_t frequency;//频率
float cmpr_a;//A输出的占空比
float cmpr_b;//B输出的占空比
mcpwm_duty_type_t duty_mode;//占空比模式 (对应高还是低)
mcpwm_counter_type_t counter_mode;//定时器计数方向
}
注意:使用mcpwm_init()
初始化的MCPWM等效于
mcpwm_set_frequency()
mcpwm_set_duty[_in_us]()
mcpwm_set_duty_type()
例如:
mcpwm_config_t mcpwmConfig = {
.frequency = 1000,
.cmpr_a = 0,
.cmpr_b = 0,
.counter_mode = MCPWM_UP_COUNTER,
.duty_mode = MCPWM_DUTY_MODE_0,
};
mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &mcpwmConfig);
(2)★PWM信号控制【重点】
1)设置为全速(非PWM信号)
我们可以使用mcpwm_set_signal_high()
或mcpwm_set_signal_low()
函数来驱动特定的信号稳定为高或低。这将使电机以最大速度旋转或停止。
2)设置PWM信号
若要更改PWM的占空比,调用mcpwm_set_duty()
并以%为单位提供占空比的百分数值。如果您希望以微秒为单位设置任务,则可以选择调用mcpwm_set_duty_in_us()
。可以通过调用mcpwm_set_duty_type()
来改变PWM占空比的模式(占空比数值对应高还是对应低)。
3)启动输出
通过调用mcpwm_start()
或mcpwm_stop()
来驱动PWM信号的输出。
当使用
mcpwm_init()
后,ESP32 会自动调用mcpwm_start()
启动电机
4)API简介
函数名 | mcpwm_set_signal_high[或low]() |
---|---|
函数原型 | esp_err_t mcpwm_set_signal_high(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, mcpwm_generator_t gen) |
含义 | 设置MCPWM的某个generator信号为高[或低] |
返回值 | esp_err_t |
参数 | mcpwm_num类型为:mcpwm_unit_t ;表示MCPWM单元timer_num类型为: mcpwm_timer_t ;表示哪组MCPWM Operatorgen类型为: mcpwm_generator_t ;表示对应的A还是B |
函数名 | mcpwm_set_duty[_in_us]() |
---|---|
函数原型 | esp_err_t mcpwm_set_duty(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, mcpwm_generator_t gen, float duty) |
含义 | 设置占空比 |
返回值 | esp_err_t |
参数 | mcpwm_num类型为:mcpwm_unit_t ;表示MCPWM单元timer_num类型为: mcpwm_timer_t ;表示哪组MCPWM输出gen类型为: mcpwm_generator_t ;表示A输出还是B输出duty[_in_us]类型为: float ;表示占空比百分数%[或微秒] |
函数名 | mcpwm_set_duty_type() |
---|---|
函数原型 | esp_err_t mcpwm_set_duty_type(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, mcpwm_generator_t gen, mcpwm_duty_type_t duty_type) |
含义 | 设置占空比类型,并恢复PWM输出 |
返回值 | esp_err_t |
参数 | mcpwm_num类型为:mcpwm_unit_t ;表示timer_num类型为: mcpwm_timer_t ;表示gen类型为: mcpwm_generator_t ;表示duty_type类型为: mcpwm_duty_type_t ;表示 |
函数名 | mcpwm_start[或stop]() |
---|---|
函数原型 | esp_err_t mcpwm_start(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num) |
含义 | 启动[或关闭]MCPWM输出 |
返回值 | esp_err_t |
参数 | mcpwm_num类型为:mcpwm_unit_t ;表示MCPWM单元timer_num类型为: mcpwm_timer_t ;表示哪组MCPWM |
2、代码示例(C语言)
使用 ESP32 的 MCPWM 来驱动有刷电机的函数
- 正转
moto_forward()
- 反转
moto_backward()
- 停转
moto_stop()
- 初始化
moto_init()
#include "driver/mcpwm.h"
void moto_forward(float duty){
mcpwm_set_signal_high(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_GEN_A);
mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_GEN_B, duty);
mcpwm_set_duty_type(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_GEN_B, MCPWM_DUTY_MODE_0);
}
void moto_backward(float duty){
mcpwm_set_signal_high(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_GEN_B);
mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_GEN_A, duty);
mcpwm_set_duty_type(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_GEN_A, MCPWM_DUTY_MODE_0);
}
void moto_stop(){
mcpwm_set_signal_high(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_GEN_A);
mcpwm_set_signal_high(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_GEN_B);
}
void moto_init(){
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, 15);
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, 16);
mcpwm_config_t mcpwmConfig = {
.frequency = 1000,
.cmpr_a = 0,
.cmpr_b = 0,
.counter_mode = MCPWM_UP_COUNTER,
.duty_mode = MCPWM_DUTY_MODE_0,
};
mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &mcpwmConfig);
}
三、MCPWM 捕获模块
1、使用方法(ESP-IDFv4.3及更早版本)
- 设置IO口用于捕获电机状态:
mcpwm_gpio_init()
或者mcpwm_set_pin()
// 设置MCPWM单元0的 Capture0 为 GPIO16
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM_CAP_0, 16);
- 通过调用
mcpwm_capture_enable()
来启用该功能本身,从mcpwm_capture_signal_t
选择所需的信号输入,使用mcpwm_capture_on_edge_t
和信号计数预分配器设置信号边缘。
在 ESP-IDF4.3 版本之前(包含4.3版本),实现捕获需要用中断实现。涉及到利用寄存器读取中断原因进行分别操作,较为复杂。本文只写明思路
思路分析
第二步的过程创建了一个32位捕获定时器(由APB)时钟驱动。在每个捕获事件上,捕获计时器的值存储在时间戳寄存器中,然后通过调用mcpwm_capture_signal_get_value()
来读取该寄存器,通过mcpwm_capture_signal_get_edge()
检查最后一个信号的边缘类型。
因此我们需要用mcpwm_isr_register()
注册一个中断,在isr函数中通过寄存器读取中断原因,再利用上一段提到的两个函数读取到的值。
以下为ESP-IDF 4.3 以上 (不含4.3版本) 的 新内容:
2、使用方法(ESP-IDFv4.3之后的版本)
-
设置捕获输入 GPIO 口(与旧版一致)
-
通过调用
mcpwm_capture_enable_channel()
来启用该功能本身,从mcpwm_capture_channel_id_t
选择所需的信号输入,在mcpwm_capture_config_t
中设置信号边缘、信号计数预分配器和用户回调。
在上面的启用步中,启用了一个由APB时钟驱动的32位捕获计时器,时钟频率通常为
80
M
H
z
\rm80\ MHz
80 MHz。在每个捕获事件上,捕获计时器的值存储在时间戳寄存器中,然后可以通过调用mcpwm_capture_signal_get_value()
检查该寄存器。可以使用mcpwm_capture_signal_get_edge()
检查最后一个信号的边缘。
(对于ESP-IDFv4.3的更高版本)回调函数内部也提供cap_event_data_t
数据结构体来作为回调函数参数传递。
四、电机故障检测
1、理论部分
- 配置gpio作为故障信号输入。这是通过类似的方法完成的,如上节中描述的捕获信号。它包括设置信号级别以触发
mcpwm_fault_input_level_t
中定义的故障。 - 通过调用
mcpwm_fault_set_oneshot_mode()
或mcpwm_fault_set_cyc_mode()
初始化错误处理程序。这些功能设置了一旦故障信号变为非活动状态时MCPWM应运行的模式。有两种可能的模式:- MCPWM单元的状态将被锁定,直到重置
mcpwm_fault_set_oneshot_mode()
。
- 一旦故障信号变得不活动,MCPWM将恢复操作
mcpwm_fault_set_cyc_mode()
。
- MCPWM单元的状态将被锁定,直到重置
函数调用参数包括选择mcpwm_fault_signal_t
中定义的三个错误输入中的一个,以及mcpwm_action_on_pwmxa_t
和mcpwm_action_on_pwmxb_t
中定义的对输出A和B的特定操作。
通过调用mcpwm_fault_deinit()
可以在运行时禁用特定的错误信号。
五、载波
请参见ESP-IDF官方文档API参考/外设/MCPWM -> Carrier
点击下方按钮传送