ESP32S3基于FreeRTOS实时操作系统控制舵机

这段代码是一个基于ESP32的舵机控制示例,通过MCPWM模块配置定时器、操作符、比较器和发生器,生成特定脉冲宽度的PWM信号,控制舵机在 -60度到60度之间以2度为步长往复转动。

1. 源码部分
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/mcpwm_prelude.h"

static const char *TAG = "example";

// Please consult the datasheet of your servo before changing the following parameters
#define SERVO_MIN_PULSEWIDTH_US 500  // Minimum pulse width in microsecond
#define SERVO_MAX_PULSEWIDTH_US 2500  // Maximum pulse width in microsecond
#define SERVO_MIN_DEGREE        -90   // Minimum angle
#define SERVO_MAX_DEGREE        90    // Maximum angle

#define SERVO_PULSE_GPIO             1        // GPIO connects to the PWM signal line
#define SERVO_TIMEBASE_RESOLUTION_HZ 1000000  // 1MHz, 1us per tick
#define SERVO_TIMEBASE_PERIOD        20000    // 20000 ticks, 20ms

static inline uint32_t example_angle_to_compare(int angle)
{
    
    
    return (angle - SERVO_MIN_DEGREE) * (SERVO_MAX_PULSEWIDTH_US - SERVO_MIN_PULSEWIDTH_US) / (SERVO_MAX_DEGREE - SERVO_MIN_DEGREE) + SERVO_MIN_PULSEWIDTH_US;
}

void app_main(void)
{
    
    
    ESP_LOGI(TAG, "Create timer and operator");
    mcpwm_timer_handle_t timer = NULL;
    mcpwm_timer_config_t timer_config = {
    
    
        .group_id = 0,
        .clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
        .resolution_hz = SERVO_TIMEBASE_RESOLUTION_HZ,
        .period_ticks = SERVO_TIMEBASE_PERIOD,
        .count_mode = MCPWM_TIMER_COUNT_MODE_UP,
    };
    ESP_ERROR_CHECK(mcpwm_new_timer(&timer_config, &timer)); // 创建定时器

    mcpwm_oper_handle_t oper = NULL;
    mcpwm_operator_config_t operator_config = {
    
    
        .group_id = 0, // operator must be in the same group to the timer
    };
    ESP_ERROR_CHECK(mcpwm_new_operator(&operator_config, &oper)); // 创建操作符,负责管理定时器、比较器和发生器之间的交互

    ESP_LOGI(TAG, "Connect timer and operator");
    ESP_ERROR_CHECK(mcpwm_operator_connect_timer(oper, timer)); // 将操作符连接到定时器上

    ESP_LOGI(TAG, "Create comparator and generator from the operator");
    mcpwm_cmpr_handle_t comparator = NULL;
    mcpwm_comparator_config_t comparator_config = {
    
    
        .flags.update_cmp_on_tez = true,
    };
    ESP_ERROR_CHECK(mcpwm_new_comparator(oper, &comparator_config, &comparator)); // 创建比较器

    mcpwm_gen_handle_t generator = NULL;
    mcpwm_generator_config_t generator_config = {
    
    
        .gen_gpio_num = SERVO_PULSE_GPIO,
    };
    ESP_ERROR_CHECK(mcpwm_new_generator(oper, &generator_config, &generator)); // 创建发生器

    // set the initial compare value, so that the servo will spin to the center position
    ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, example_angle_to_compare(0))); // 设置比较器的初始值,这样舵机会旋转到中位位置
 
    ESP_LOGI(TAG, "Set generator action on timer and compare event");
    // go high on counter empty
    // 这行代码的主要功能是设置MCPWM(Motor Control PWM)发生器在特定定时器事件发生时的动作。
    // 具体来说,当定时器向上计数到零(即定时器计数器重置为零)时,发生器的输出电平将被设置为高电平。
    ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(generator,
        MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); // 设置发生器在定时器计数到零时的动作
    // go low on compare threshold
    // 这两行代码的功能是设置MCPWM发生器,当定时器向上计数达到比较器的比较值时,发生器的输出电平将变为低电平。
    // 在舵机控制的场景中,这有助于生成特定脉冲宽度的PWM信号,从而精确控制舵机的转动角度。
    ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(generator,
        MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, comparator, MCPWM_GEN_ACTION_LOW))); // 设置发生器在比较器阈值时的动作

    ESP_LOGI(TAG, "Enable and start timer");
    ESP_ERROR_CHECK(mcpwm_timer_enable(timer)); // 能MCPWM定时器。使能定时器后,定时器会开始按照之前配置的参数进行计数。
    // 来控制定时器的启动与停止。这里使用 MCPWM_TIMER_START_NO_STOP 作为参数,意味着定时器启动后不会自动停止,会持续运行。
    ESP_ERROR_CHECK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));

    int angle = 0;
    int step = 2;
    while (1) {
    
    
        ESP_LOGI(TAG, "Angle of rotation: %d", angle);
        ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, example_angle_to_compare(angle)));
        //Add delay, since it takes time for servo to rotate, usually 200ms/60degree rotation under 5V power supply
        vTaskDelay(pdMS_TO_TICKS(500));
        if ((angle + step) > 60 || (angle + step) < -60) {
    
    
            step *= -1;
        }
        angle += step;
    }
}

以下是对 文件中代码的详细解释:

2. 头文件包含

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/mcpwm_prelude.h"
  • freertos/FreeRTOS.hfreertos/task.h:引入FreeRTOS实时操作系统的相关头文件,用于任务管理和调度。
  • esp_log.h:引入ESP-IDF的日志功能头文件,用于输出调试信息。
  • driver/mcpwm_prelude.h:引入MCPWM(Motor Control PWM)驱动的头文件,用于控制舵机所需的PWM信号。

3. 日志标签定义

static const char *TAG = "example";

定义一个日志标签 TAG,用于在日志输出中标识该代码模块。

4. 舵机参数宏定义

// Please consult the datasheet of your servo before changing the following parameters
#define SERVO_MIN_PULSEWIDTH_US 500  // Minimum pulse width in microsecond
#define SERVO_MAX_PULSEWIDTH_US 2500  // Maximum pulse width in microsecond
#define SERVO_MIN_DEGREE        -90   // Minimum angle
#define SERVO_MAX_DEGREE        90    // Maximum angle

#define SERVO_PULSE_GPIO             1        // GPIO connects to the PWM signal line
#define SERVO_TIMEBASE_RESOLUTION_HZ 1000000  // 1MHz, 1us per tick
#define SERVO_TIMEBASE_PERIOD        20000    // 20000 ticks, 20ms
  • SERVO_MIN_PULSEWIDTH_USSERVO_MAX_PULSEWIDTH_US:定义舵机控制所需的最小和最大脉冲宽度(单位:微秒)。
  • SERVO_MIN_DEGREESERVO_MAX_DEGREE:定义舵机的最小和最大转动角度。
  • SERVO_PULSE_GPIO:定义连接到PWM信号线路的GPIO引脚编号。
  • SERVO_TIMEBASE_RESOLUTION_HZ:定义PWM定时器的分辨率(1MHz,即每个时钟周期为1微秒)。
  • SERVO_TIMEBASE_PERIOD:定义PWM定时器的周期(20000个时钟周期,即20ms)。

5. 角度转换函数

static inline uint32_t example_angle_to_compare(int angle)
{
    
    
    return (angle - SERVO_MIN_DEGREE) * (SERVO_MAX_PULSEWIDTH_US - SERVO_MIN_PULSEWIDTH_US) / (SERVO_MAX_DEGREE - SERVO_MIN_DEGREE) + SERVO_MIN_PULSEWIDTH_US;
}

这是一个内联函数,用于将输入的角度值转换为对应的PWM比较值。通过线性映射的方式,将角度范围 [SERVO_MIN_DEGREE, SERVO_MAX_DEGREE] 映射到脉冲宽度范围 [SERVO_MIN_PULSEWIDTH_US, SERVO_MAX_PULSEWIDTH_US]

6. 主函数 app_main

void app_main(void)
{
    
    
    ESP_LOGI(TAG, "Create timer and operator");
    mcpwm_timer_handle_t timer = NULL;
    mcpwm_timer_config_t timer_config = {
    
    
        .group_id = 0,
        .clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
        .resolution_hz = SERVO_TIMEBASE_RESOLUTION_HZ,
        .period_ticks = SERVO_TIMEBASE_PERIOD,
        .count_mode = MCPWM_TIMER_COUNT_MODE_UP,
    };
    ESP_ERROR_CHECK(mcpwm_new_timer(&timer_config, &timer)); // 创建定时器

    mcpwm_oper_handle_t oper = NULL;
    mcpwm_operator_config_t operator_config = {
    
    
        .group_id = 0, // operator must be in the same group to the timer
    };
    ESP_ERROR_CHECK(mcpwm_new_operator(&operator_config, &oper)); // 创建操作符,负责管理定时器、比较器和发生器之间的交互

    ESP_LOGI(TAG, "Connect timer and operator");
    ESP_ERROR_CHECK(mcpwm_operator_connect_timer(oper, timer)); // 将操作符连接到定时器上

    ESP_LOGI(TAG, "Create comparator and generator from the operator");
    mcpwm_cmpr_handle_t comparator = NULL;
    mcpwm_comparator_config_t comparator_config = {
    
    
        .flags.update_cmp_on_tez = true,
    };
    ESP_ERROR_CHECK(mcpwm_new_comparator(oper, &comparator_config, &comparator)); // 创建比较器

    mcpwm_gen_handle_t generator = NULL;
    mcpwm_generator_config_t generator_config = {
    
    
        .gen_gpio_num = SERVO_PULSE_GPIO,
    };
    ESP_ERROR_CHECK(mcpwm_new_generator(oper, &generator_config, &generator)); // 创建发生器

    // set the initial compare value, so that the servo will spin to the center position
    ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, example_angle_to_compare(0))); // 设置比较器的初始值,这样舵机会旋转到中位位置

    ESP_LOGI(TAG, "Set generator action on timer and compare event");
    // go high on counter empty
    // 这行代码的主要功能是设置MCPWM(Motor Control PWM)发生器在特定定时器事件发生时的动作。
    // 具体来说,当定时器向上计数到零(即定时器计数器重置为零)时,发生器的输出电平将被设置为高电平。
    ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(generator,
        MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); // 设置发生器在定时器计数到零时的动作
    // go low on compare threshold
    // 这两行代码的功能是设置MCPWM发生器,当定时器向上计数达到比较器的比较值时,发生器的输出电平将变为低电平。
    // 在舵机控制的场景中,这有助于生成特定脉冲宽度的PWM信号,从而精确控制舵机的转动角度。
    ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(generator,
        MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, comparator, MCPWM_GEN_ACTION_LOW))); // 设置发生器在比较器阈值时的动作

    ESP_LOGI(TAG, "Enable and start timer");
    ESP_ERROR_CHECK(mcpwm_timer_enable(timer)); // 使能MCPWM定时器。使能定时器后,定时器会开始按照之前配置的参数进行计数。
    // 来控制定时器的启动与停止。这里使用 MCPWM_TIMER_START_NO_STOP 作为参数,意味着定时器启动后不会自动停止,会持续运行。
    ESP_ERROR_CHECK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));

    int angle = 0;
    int step = 2;
    while (1) {
    
    
        ESP_LOGI(TAG, "Angle of rotation: %d", angle);
        ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, example_angle_to_compare(angle)));
        //Add delay, since it takes time for servo to rotate, usually 200ms/60degree rotation under 5V power supply
        vTaskDelay(pdMS_TO_TICKS(500));
        if ((angle + step) > 60 || (angle + step) < -60) {
    
    
            step *= -1;
        }
        angle += step;
    }
}
6.1 定时器和操作符创建
ESP_LOGI(TAG, "Create timer and operator");
mcpwm_timer_handle_t timer = NULL;
mcpwm_timer_config_t timer_config = {
    
    
    .group_id = 0,
    .clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
    .resolution_hz = SERVO_TIMEBASE_RESOLUTION_HZ,
    .period_ticks = SERVO_TIMEBASE_PERIOD,
    .count_mode = MCPWM_TIMER_COUNT_MODE_UP,
};
ESP_ERROR_CHECK(mcpwm_new_timer(&timer_config, &timer)); // 创建定时器

mcpwm_oper_handle_t oper = NULL;
mcpwm_operator_config_t operator_config = {
    
    
    .group_id = 0, // operator must be in the same group to the timer
};
ESP_ERROR_CHECK(mcpwm_new_operator(&operator_config, &oper)); // 创建操作符,负责管理定时器、比较器和发生器之间的交互
  • 创建一个MCPWM定时器,并配置其参数,包括组ID、时钟源、分辨率、周期和计数模式。
  • 创建一个MCPWM操作符,并将其与定时器连接到同一个组中。
6.2 连接定时器和操作符
ESP_LOGI(TAG, "Connect timer and operator");
ESP_ERROR_CHECK(mcpwm_operator_connect_timer(oper, timer)); // 将操作符连接到定时器上

将操作符与定时器连接起来,以便操作符可以管理定时器的运行。

6.3 比较器和发生器创建
ESP_LOGI(TAG, "Create comparator and generator from the operator");
mcpwm_cmpr_handle_t comparator = NULL;
mcpwm_comparator_config_t comparator_config = {
    
    
    .flags.update_cmp_on_tez = true,
};
ESP_ERROR_CHECK(mcpwm_new_comparator(oper, &comparator_config, &comparator)); // 创建比较器

mcpwm_gen_handle_t generator = NULL;
mcpwm_generator_config_t generator_config = {
    
    
    .gen_gpio_num = SERVO_PULSE_GPIO,
};
ESP_ERROR_CHECK(mcpwm_new_generator(oper, &generator_config, &generator)); // 创建发生器
  • 创建一个MCPWM比较器,并配置其参数。
  • 创建一个MCPWM发生器,并指定其输出GPIO引脚。
6.4 设置比较器初始值
// set the initial compare value, so that the servo will spin to the center position
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, example_angle_to_compare(0))); // 设置比较器的初始值,这样舵机会旋转到中位位置

将比较器的初始值设置为对应角度为0度的脉冲宽度,使舵机旋转到中位位置。

6.5 设置发生器动作
ESP_LOGI(TAG, "Set generator action on timer and compare event");
// go high on counter empty
// 这行代码的主要功能是设置MCPWM(Motor Control PWM)发生器在特定定时器事件发生时的动作。
// 具体来说,当定时器向上计数到零(即定时器计数器重置为零)时,发生器的输出电平将被设置为高电平。
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(generator,
    MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); // 设置发生器在定时器计数到零时的动作
// go low on compare threshold
// 这两行代码的功能是设置MCPWM发生器,当定时器向上计数达到比较器的比较值时,发生器的输出电平将变为低电平。
// 在舵机控制的场景中,这有助于生成特定脉冲宽度的PWM信号,从而精确控制舵机的转动角度。
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(generator,
    MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, comparator, MCPWM_GEN_ACTION_LOW))); // 设置发生器在比较器阈值时的动作
  • 设置发生器在定时器计数到零时输出高电平。
  • 设置发生器在定时器计数达到比较器的比较值时输出低电平,从而生成特定脉冲宽度的PWM信号。
6.6 启动定时器
ESP_LOGI(TAG, "Enable and start timer");
ESP_ERROR_CHECK(mcpwm_timer_enable(timer)); // 使能MCPWM定时器。使能定时器后,定时器会开始按照之前配置的参数进行计数。
// 来控制定时器的启动与停止。这里使用 MCPWM_TIMER_START_NO_STOP 作为参数,意味着定时器启动后不会自动停止,会持续运行。
ESP_ERROR_CHECK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));

使能并启动MCPWM定时器,使其开始计数。

6.7 循环控制舵机角度
int angle = 0;
int step = 2;
while (1) {
    
    
    ESP_LOGI(TAG, "Angle of rotation: %d", angle);
    ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, example_angle_to_compare(angle)));
    //Add delay, since it takes time for servo to rotate, usually 200ms/60degree rotation under 5V power supply
    vTaskDelay(pdMS_TO_TICKS(500));
    if ((angle + step) > 60 || (angle + step) < -60) {
    
    
        step *= -1;
    }
    angle += step;
}
  • 初始化舵机角度为0度,每次增加2度。
  • 在循环中不断更新比较器的比较值,从而改变PWM信号的脉冲宽度,控制舵机的转动角度。
  • 每次更新角度后,添加500ms的延迟,以确保舵机有足够的时间转动到指定角度。
  • 当角度超过60度或小于 -60度时,改变角度增加的方向。

综上所述,这段代码的主要功能是通过MCPWM模块生成PWM信号,控制舵机在 -60度到60度之间来回转动。