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度之间来回转动。

FreeRTOS本身并不直接提供PWM (脉宽调制) 功能,这是因为PWM通常是依赖于硬件特性的功能,并由微控制器的具体外设模块来完成。然而,在嵌入式系统设计中,通常我们会结合FreeRTOS的任务调度机制和特定MCU的PWM驱动库或寄存器操作来配置并使用PWM。 对于如何基于FreeRTOS环境设置PWM信号输出的问题,可以分为以下几个步骤: 1. **初始化硬件**:首先要按照所使用的单片机参考手册说明对定时器或其他负责生成PWM波形的外围设备进行必要的初始化工作;这一步骤包括选择时钟源、预分频值设定等关键参数的选择; 2. **创建任务**:利用 FreeRTOS API 创建用于管理 PWM 输出状态转换的新线程(即"task") ,在这个进程中编写控制算法比如改变占空比大小以达到调节亮度/速度的目的; 3. **同步机制**:如果应用程序中有多个组件需要协调一致地影响同一个PWM通道,则考虑采用互斥量(mutex),队列(queue)等方式保证数据一致性; 4. **中断处理**(可选): 如果涉及到快速响应外部事件的需求,那么可以在适当位置安装服务程序(ISR),并在其中通过API唤醒对应任务来进行后续动作. 具体到某一款STM32系列芯片上实现的话可能会更具体一些: ```c #include "stm32f1xx_hal.h" #include "cmsis_os.h" TIM_HandleTypeDef htim3; // 假定我们用的是 TIM3 void MX_TIM3_PWM_Init(void) { __HAL_RCC_TIM3_CLK_ENABLE(); /* Enable the timer clock */ htim3.Instance = TIM3; htim3.Init.Prescaler = ... ; // 计算合适的预分频系数 htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = ... ; // 设置自动重装载周期 if(HAL_TIM_PWM_Init(&htim3)!= HAL_OK){ Error_Handler(); } } // 定义一个简单的任务函数更新PWM的占空比 static void pwmUpdateTaskFunction(void const * argument) { uint32_t dutyCycle = 50; // 初始占空比为50% while (true) { __HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,dutyCycle*(htim3.Init.Period)/100); // 更新CCRx 寄存器 osDelay(1000); // 等待一秒再调整下一次 dutyCycle += 10; if(dutyCycle >= 100) dutyCycle=0; } } int main() { // 初始化HAL 库 和 System Clock... ... MX_TIM3_PWM_Init(); // 启动FreeRTOS 调度以及我们的PWM更新任务 osThreadDef(pwmUpdater,pwmUpdateTaskFunction ,osPriorityNormal,0,configMINIMAL_STACK_SIZE); osThreadCreate(osThread(pwmUpdater),NULL); osKernelStart(); } ``` 上述例子展示了一个简单的方式去定期修改指定定时器产生的PWM波形之占空率。需要注意实际应用当中应该根据实际情况做出相应更改如检查返回码是否成功之类的细节之处以免出错。 --
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值