打印

启明智显分享| ESP32学习笔记参考--LED PWM 控制器与MCPWM

[复制链接]
1058|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 启明智显 于 2022-3-22 16:20 编辑

--提示:启明智显专为智能产品提供一站式彩屏显示+连接+云端服务+APP软件开发、维护等解决方案,帮厂商快速实现硬件的智能化。作为启明云端旗下方案公司,我们用心整理了开发小伙伴在开发过程中可能会遇到的问题以及快速上手的简明教程,同时也用心整理了连接+显示应用的新方案!希望你能第一时间了解并快速用上好的方案和产品!--

一、LED PWM 控制器

1、简介

LED 控制器 (LEDC) 主要用于控制 LED,也可产生 PWM 信号用于其他设备的控制。 该控制器有 16 路通道,可以产生独立的波形来驱动 RGB LED 等设备。

LEDC 通道共有两组,分别为 8 路高速通道和 8 路低速通道。高速通道模式在硬件中实现,可以自动且无干扰地改变 PWM 占空比。低速通道模式下,PWM 占空比
需要由软件中的驱动器改变。每组通道都可以使用不同的时钟源。

LED PWM 控制器可在无需 CPU 干预的情况下自动改变占空比,实现亮度和颜色渐变。
[参考](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html)

设置 LEDC 通道在 [高速模式或低速模式](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#ledc-api-high-low-speed-mode) 下运行,需要进行如下配置:

(1) [定时器配置](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#ledc-api-configure-timer) 指定 PWM 信号的频率和占空比分辨率。
(2)[通道配置](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#ledc-api-configure-channel) 绑定定时器和输出 PWM 信号的 GPIO。
(3)[改变 PWM 信号](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#ledc-api-change-pwm-signal) 输出 PWM 信号来驱动 LED。可通过软件控制或使用硬件渐变功能来改变 LED 的亮度。

另一个可选步骤是可以在渐变终端设置一个中断。



在解释ESP32上的PWM功能之前,让我们讨论一些与PWM相关的术语。

●  **TON(导通时间)**:信号为高电平时的持续时间。

●  **TOFF(关断时间)**:信号为低电平时的持续时间。

●  **周期**:PWM信号的导通时间和关断时间之和。

●  **占空比**:PWM信号周期内信号为高电平的时间百分比。


例如,如果总周期为10ms的脉冲保持 ON(高)5ms。然后,占空比将为:占空比 = 5/10 * 100 = 50%



●  **PWM的频率:**PWM信号的频率决定了PWM完成一个周期的速度。一个周期是一个PWM信号的完整ON和OFF,如上图所示。

2、配置定时器

要设置定时器,可调用函数 [`ledc_timer_config()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv417ledc_timer_configPK19ledc_timer_config_t),并将包括如下配置参数的数据结构 [`ledc_timer_config_t`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv419ledc_timer_config_t) 传递给该函数:

- 速度模式 [`ledc_mode_t`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv411ledc_mode_t)
- 定时器索引 [`ledc_timer_t`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv412ledc_timer_t)
- PWM 信号频率
- PWM 占空比分辨率
- 时钟源 [`ledc_clk_cfg_t`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv414ledc_clk_cfg_t)


频率和占空比分辨率相互关联。PWM 频率越高,占空比分辨率越低,反之亦然。如果 API 不是用来改变 LED 亮度,而是用于其它目的,这种相互关系可能会很重要。更多信息详见 [频率和占空比分辨率支持范围](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#ledc-api-supported-range-frequency-duty-resolution) 一节。

时钟源同样可以限制PWM频率。选择的时钟源频率越高,可以配置的PWM频率上限就越高。

时钟名称时钟频率 速度模式 时钟功能
APB_CLK 80 MHz 高速 / 低速 /
REF_TICK  1 MHz 高速 / 低速 支持动态调频(DFS)功能
  RTC8M_CLK   ~8 MHz    低速 支持动态调频(DFS)功能,支持Light-sleep模式


-通道配置-

定时器设置好后,请配置所需的通道([`ledc_channel_t`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv414ledc_channel_t) 之一)。配置通道需调用函数 [`ledc_channel_config()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv419ledc_channel_configPK21ledc_channel_config_t)。

通道的配置与定时器设置类似,需向通道配置函数传递包括通道配置参数的结构体 [`ledc_channel_config_t`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv421ledc_channel_config_t) 。

此时,通道会按照 [`ledc_channel_config_t`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv421ledc_channel_config_t) 的配置开始运作,并在选定的 GPIO 上生成由定时器设置指定的频率和占空比的 PWM 信号。在通道运作过程中,可以随时通过调用函数 [`ledc_stop()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv49ledc_stop11ledc_mode_t14ledc_channel_t8uint32_t) 将其暂停。


3、实例

-- ESP32 下面是一个呼吸灯的整理,可以直接使用,欢迎参考。

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/ledc.h"
#include "esp_err.h"
#define LEDC_HS_TIMER          LEDC_TIMER_0
#define LEDC_HS_MODE           LEDC_HIGH_SPEED_MODE
#define LEDC_HS_CH0_CHANNEL    LEDC_CHANNEL_0
#define LEDC_HS_CH0_GPIO       (2)
#define LEDC_TEST_DUTY         (4000)
#define LEDC_TEST_FADE_TIME    (3000)
void app_main(void)
{
    //1. PWM: 定时器配置

    ledc_timer_config_t ledc_timer = {
        .duty_resolution = LEDC_TIMER_13_BIT, // resolution of PWM duty
        .freq_hz = 5000,                      // frequency of PWM signal
        .speed_mode = LEDC_HS_MODE,           // timer mode
        .timer_num = LEDC_HS_TIMER,            // timer index
        .clk_cfg = LEDC_AUTO_CLK,              // Auto select the source clock
    };

    ledc_timer_config(&ledc_timer);
    //2. PWM:通道配置
    ledc_channel_config_t ledc_channel= {
        .channel    = LEDC_HS_CH0_CHANNEL,
        .duty       = 0,
        .gpio_num   = LEDC_HS_CH0_GPIO, //这里是SDK带的呼吸灯案例,你看下能看懂不,我现在给你修改个引脚,你的灯就用上了, 2表示你板子上的那个灯 2引脚的那个
        .speed_mode = LEDC_HS_MODE,
        .hpoint     = 0,
        .timer_sel  = LEDC_HS_TIMER   
    };

    ledc_channel_config(&ledc_channel);
    //3.PWM:使用硬件渐变
    ledc_fade_func_install(0);
    //4. 输出PWM信号控制灯

    while (1) {
        //灯:由灭慢慢变亮
        printf("1. LEDC fade up to duty = %d\n", LEDC_TEST_DUTY);
        ledc_set_fade_with_time(ledc_channel.speed_mode,
                ledc_channel.channel, LEDC_TEST_DUTY, LEDC_TEST_FADE_TIME);
        ledc_fade_start(ledc_channel.speed_mode,
                ledc_channel.channel, LEDC_FADE_NO_WAIT);
        vTaskDelay(LEDC_TEST_FADE_TIME / portTICK_PERIOD_MS);
        //灯:由亮慢慢变灭
        printf("2. LEDC fade down to duty = 0\n");
        ledc_set_fade_with_time(ledc_channel.speed_mode,
                ledc_channel.channel, 0, LEDC_TEST_FADE_TIME);
        ledc_fade_start(ledc_channel.speed_mode,
                ledc_channel.channel, LEDC_FADE_NO_WAIT);
        vTaskDelay(LEDC_TEST_FADE_TIME / portTICK_PERIOD_MS);
    }
}


二、Motor Control Pulse Width Modulator(MCPWM)

1、简介

ESP32有两个MCPWM单元,可用于控制不同类型的电机。每个单元有三对PWM输出。

(1)作用:

- PWM输出
- 每个单元还能够收集诸如同步信号等输入,检测电机过电流或过电压等故障信号,
- 以及在例如转子位置上获得捕获信号的反馈。


每个A/B对可由三个定时器中的任何一个MCPWM定时器0、1和2中的任何一个时钟。(相同的定时器可用于时钟多对PWM输出)



从上图我们不难发现,MCPWM具有的功能(上图彩色的虚线框)有:

- `OPERATOR `操作器模块
- `CAPTURE `状态捕获模块
- `FAULT DETECT`故障处理器模块
- `CLOCK/TIMER`时钟、时钟预分频器模块
  (黑色虚线框指的是GPIO矩阵 (GPIO Matrix))


(2)操作器模块 Operator

操作员 (Operator) 用于操作连接到MCPWM单元的电机。**例如改变旋转方向(顺时针或逆时针),或改变转速。

操作员输出一共有 3 对,我们可以对其施加控制信号。标记为“A”和“B”的称为一对。A、B均有自己对应的名为“Generator”的子模块来驱动诸如PWM的输出信号。

为了提供PWM信号,每个Operator本身由三个可用的定时器(MCPWM Timer)中的任何一个进行计时。  

为了简化API,API会 自动关联 具有相同索引`Timer`以驱动`Operator`。例如`Timer 0`与`Operator 0`关联。


(3)捕获模块 Capture

捕获模块在功能上相当于由沿中断控制的捕获定时器

对于无刷直流电机,控制的要求之一是感应转子位置。

为了完成这一任务,每个 MCPWM单元提供三个传感输入以及专用的硬件。该硬件能够检测输入信号的边缘,并测量信号之间的时间。

因此,控制软件更简单,CPU功率可能用于其他任务。

注意:3个Capture可以在不使用PWM输出时**单独使用**,即只配置Capture实现边缘捕获功能。**因此MCPWM还可用于非电机外设**。例如,使用MCPWM的Capture0去捕获**HC-SR04**超声波模块ECHO引脚的高电平时间,进而实现测距。


(4)故障处理器模块 Fault Detect

MCPWM的每个单元都能够感知外部信号,包括有关电机、电机驱动器或连接到MCPWM的任何其他设备的故障信息。每个单元有三个错误输入,可以路由到用户可选择的GPIO。当接收到故障信号时,MCPWM可以配置为对A/B输出执行四种预定义的动作之一:


- 锁定输出的当前状态
- 设置低输出
- 设置高输出
- 开关输出


用户应确定电机可能的故障模式以及在检测到特定故障时应采取的行动。

例如:对有刷电机驱动所有输出为低,或对步进电机锁定电流状态等。这个动作会使电机处于安全状态,以减少故障造成的损坏的可能性。


(5)载波 Carrier和中断 Interrupts

MCPWM有一个载波子模块,如果使用互感原理(如通过变压器)向电机驱动传递A/B输出信号(例如需要让电机驱动器输入电流与ESP32 GPIO输出电流相互隔离)。任何A和B输出信号都可以100%占空,并且当电机在满载时需要稳定运行时,信号不会改变。


通过调用`mcpwm_isr_register()`可以注册MCPWM中断处理程序。
注意,如果使用了`mcpwm_capture_enable_channel()`,那么将安装一个默认的ISR例程来实现简化API的回调。因此,如果使用了`mcpwm_capture_enable_channel()`,请不要再调用mcpwm_isr_register()这个函数来注册中断。


2、使用 MCPWM 输出 PWM 信号

2.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()  //初始化一个GPIO

```c
esp_err_t mcpwm_gpio_init(mcpwm_unit_t mcpwm_num, mcpwm_io_signals_t io_signal, int gpio_num)   
//mcpwm_num类型为:mcpwm_unit_t;MCPWM单元
//io_signal类型为:mcpwm_io_signals_t;MCPWM功能signal,如MCPWM0A表示某MCPWM的A输出
//gpio_num类型为:int;表示想要配置为哪个GPIO
```

**mcpwm_set_pin()**   //配置所有与MCPWM有关的GPIO

```c
esp_err_t mcpwm_set_pin(mcpwm_unit_t mcpwm_num, const mcpwm_pin_config_t *mcpwm_pin)
//mcpwm_num类型为:mcpwm_unit_t;表示MCPWM单元索引
//*mcpwm_pin类型为:mcpwm_pin_config_t指针;表示一个结构体,包含所有与MCPWM功能对于的GPIO
```


-*2,配置mcpwm参数

通过函数`mcpwm_init()`,传递一个`mcpwm_config_t`结构体指针

**mcpwm_init()**


```c
esp_err_t mcpwm_init(mcpwm_unit_t mcpwm_num,
                     mcpwm_timer_t timer_num,
                     const mcpwm_config_t *mcpwm_conf)
//mcpwm_num类型为:mcpwm_unit_t;表示MCPWM索引
//timer_num类型为:mcpwm_timer_t;表示初始化哪个MCPWM定时器,对应与其相同索引的Operator
//*mcpwm_conf类型为:const 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_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.2  PWM信号控制

- **1、设置为全速(非PWM信号)**
  我们可以使用`mcpwm_set_signal_high()`或`mcpwm_set_signal_low()`函数来驱动特定的信号稳定为高或低。这将使电机以最大速度旋转或停止。

   **mcpwm_set_signal_high(或low) ()**   // 设置MCPWM的某个generator信号为高[或低]

  ```c
  esp_err_t mcpwm_set_signal_high(mcpwm_unit_t mcpwm_num,
                                  mcpwm_timer_t timer_num,
                                  mcpwm_generator_t gen)
  //mcpwm_num类型为:mcpwm_unit_t;表示MCPWM单元
  //timer_num类型为:mcpwm_timer_t;表示哪组MCPWM Operator
  //gen类型为:mcpwm_generator_t;表示对应的A还是B
  ```


- **2、设置PWM信号**
  若要更改PWM的占空比,调用`mcpwm_set_duty()`并以%为单位提供占空比的百分数值。如果您希望以微秒为单位设置任务,则可以选择调用`mcpwm_set_duty_in_us()`。可以通过调用`mcpwm_set_duty_type()`来改变PWM占空比的模式(占空比数值对应高还是对应低)。

  **mcpwm_set_duty (in_us) ()** //设置占空比

  ```c
  esp_err_t mcpwm_set_duty(mcpwm_unit_t mcpwm_num,
                           mcpwm_timer_t timer_num,
                           mcpwm_generator_t gen,
                           float duty)
  //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()**   //设置占空比类型,并恢复PWM输出

  ```c
  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)   
  //mcpwm_num类型为:mcpwm_unit_t;表示MCPWM单元
  //timer_num类型为:mcpwm_timer_t;表示哪组MCPWM输出
  //gen类型为:mcpwm_generator_t;表示A输出还是B输出   
  //duty_type类型为:mcpwm_duty_type_t;表示占空比
  ```


- **3、启动输出**
  通过调用`mcpwm_start()`或`mcpwm_stop()`来驱动PWM信号的输出。 当使用`mcpwm_init()`后,ESP32 会自动调用`mcpwm_start()`启动电机

  **mcpwm_start(或stop)()** // 启动[或关闭]MCPWM输出

  ```c
  esp_err_t mcpwm_start(mcpwm_unit_t mcpwm_num,
                        //mcpwm_num类型为:mcpwm_unit_t;表示MCPWM单元
                        mcpwm_timer_t timer_num)
                        //timer_num类型为:mcpwm_timer_t;表示哪组MCPWM
  ```


3、示例

使用mcpwm驱动直流电机


```c
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/mcpwm.h"

#define GPIO_PWM0A_OUT 15 //设置 GPIO 15 作为 PWM0A
#define GPIO_PWM0B_OUT 16 //设置 GPIO 16 作为 PWM0B

//---------电机向前移动
static void brushed_motor_forward(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, float duty_cycle)
{
    mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_B);
    mcpwm_set_duty(mcpwm_num, timer_num, MCPWM_OPR_A, duty_cycle);
    mcpwm_set_duty_type(mcpwm_num, timer_num, MCPWM_OPR_A, MCPWM_DUTY_MODE_1);
}

//--------- 电机向后移动
static void brushed_motor_backward(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, float duty_cycle)
{
    mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_A);
    //设置MCPWM的某个generator信号为高[或低]
    mcpwm_set_duty(mcpwm_num, timer_num, MCPWM_OPR_B, duty_cycle);
    //设置占空比
    //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(mcpwm_num, timer_num, MCPWM_OPR_B, MCPWM_DUTY_MODE_1);
    // 设置占空比类型,并恢复PWM输出
}

//---------电机停止
static void brushed_motor_stop(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num)
{
    mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_A);
    mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_B);
}

//---------为直流电机配置MCPWM
static void mcpwm_task(void *arg)
{
    //1. mcpwm gpio 初始化
    printf("initializing mcpwm gpio!\n");
    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_PWM0A_OUT); //配置GPIO口
    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, GPIO_PWM0B_OUT);

    //2. 初始化 mcpwm 配置
    printf("Setting mcpwm!\n");
    mcpwm_config_t pwm_config = {
        .frequency = 1000,                //频率 = 500Hz,
        .cmpr_a = 0,                      // PWMxA占空比 = 0
        .cmpr_b = 0,                      // PWMxB占空比 = 0
        .duty_mode = MCPWM_DUTY_MODE_0,   //占空比模式 (对应高还是低)
        .counter_mode = MCPWM_UP_COUNTER, //定时器计数方向
    };
    mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);

    while (1)
    {
        brushed_motor_forward(MCPWM_UNIT_0, MCPWM_TIMER_0, 50.0);
        printf("正转\n");
        vTaskDelay(2000 / portTICK_RATE_MS);
        brushed_motor_backward(MCPWM_UNIT_0, MCPWM_TIMER_0, 30.0);
        printf("逆转\n");
        vTaskDelay(2000 / portTICK_RATE_MS);
        brushed_motor_stop(MCPWM_UNIT_0, MCPWM_TIMER_0);
        printf("停\n");
        vTaskDelay(2000 / portTICK_RATE_MS);
    }
}

void app_main(void)
{
    printf("Testing motor !\n");
    xTaskCreate(mcpwm_task, "Mcpwm_task", 4096, NULL, 5, NULL);
}
```

使用特权

评论回复

相关帖子

发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

66

主题

67

帖子

1

粉丝