mirror of
https://github.com/MeowLynxSea/MarsCar.git
synced 2025-07-09 10:44:35 +00:00
123 lines
5.1 KiB
C
123 lines
5.1 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include "esp_log.h"
|
|
#include "esp_err.h"
|
|
#include "driver/ledc.h"
|
|
#include "iot_servo.h"
|
|
|
|
static const char *TAG = "servo";
|
|
|
|
#define SERVO_CHECK(a, str, ret_val) \
|
|
if (!(a)) { \
|
|
ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \
|
|
return (ret_val); \
|
|
}
|
|
|
|
#define SERVO_LEDC_INIT_BITS LEDC_TIMER_10_BIT
|
|
#define SERVO_FREQ_MIN 50
|
|
#define SERVO_FREQ_MAX 400
|
|
|
|
static uint32_t g_full_duty = 0;
|
|
static servo_config_t g_cfg[LEDC_SPEED_MODE_MAX] = {0};
|
|
|
|
static uint32_t calculate_duty(ledc_mode_t speed_mode, float angle)
|
|
{
|
|
float angle_us = angle / g_cfg[speed_mode].max_angle * (g_cfg[speed_mode].max_width_us - g_cfg[speed_mode].min_width_us) + g_cfg[speed_mode].min_width_us;
|
|
ESP_LOGD(TAG, "angle us: %f", angle_us);
|
|
uint32_t duty = (uint32_t)((float)g_full_duty * (angle_us) * g_cfg[speed_mode].freq / (1000000.0f));
|
|
return duty;
|
|
}
|
|
|
|
static float calculate_angle(ledc_mode_t speed_mode, uint32_t duty)
|
|
{
|
|
float angle_us = (float)duty * 1000000.0f / (float)g_full_duty / (float)g_cfg[speed_mode].freq;
|
|
angle_us -= g_cfg[speed_mode].min_width_us;
|
|
angle_us = angle_us < 0.0f ? 0.0f : angle_us;
|
|
float angle = angle_us * g_cfg[speed_mode].max_angle / (g_cfg[speed_mode].max_width_us - g_cfg[speed_mode].min_width_us);
|
|
return angle;
|
|
}
|
|
|
|
esp_err_t iot_servo_init(ledc_mode_t speed_mode, const servo_config_t *config)
|
|
{
|
|
esp_err_t ret;
|
|
SERVO_CHECK(NULL != config, "Pointer of config is invalid", ESP_ERR_INVALID_ARG);
|
|
SERVO_CHECK(config->channel_number > 0 && config->channel_number <= LEDC_CHANNEL_MAX, "Servo channel number out the range", ESP_ERR_INVALID_ARG);
|
|
SERVO_CHECK(config->freq <= SERVO_FREQ_MAX && config->freq >= SERVO_FREQ_MIN, "Servo pwm frequency out the range", ESP_ERR_INVALID_ARG);
|
|
uint64_t pin_mask = 0;
|
|
uint32_t ch_mask = 0;
|
|
for (size_t i = 0; i < config->channel_number; i++) {
|
|
uint64_t _pin_mask = 1ULL << config->channels.servo_pin[i];
|
|
uint32_t _ch_mask = 1UL << config->channels.ch[i];
|
|
SERVO_CHECK(!(pin_mask & _pin_mask), "servo gpio has a duplicate", ESP_ERR_INVALID_ARG);
|
|
SERVO_CHECK(!(ch_mask & _ch_mask), "servo channel has a duplicate", ESP_ERR_INVALID_ARG);
|
|
SERVO_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(config->channels.servo_pin[i]), "servo gpio invalid", ESP_ERR_INVALID_ARG);
|
|
pin_mask |= _pin_mask;
|
|
ch_mask |= _ch_mask;
|
|
}
|
|
|
|
ledc_timer_config_t ledc_timer = {
|
|
.clk_cfg = LEDC_AUTO_CLK,
|
|
.duty_resolution = SERVO_LEDC_INIT_BITS, // resolution of PWM duty
|
|
.freq_hz = config->freq, // frequency of PWM signal
|
|
.speed_mode = speed_mode, // timer mode
|
|
.timer_num = config->timer_number // timer index
|
|
};
|
|
ret = ledc_timer_config(&ledc_timer);
|
|
SERVO_CHECK(ESP_OK == ret, "ledc timer configuration failed", ESP_FAIL);
|
|
for (size_t i = 0; i < config->channel_number; i++) {
|
|
ledc_channel_config_t ledc_ch = {
|
|
.intr_type = LEDC_INTR_DISABLE,
|
|
.channel = config->channels.ch[i],
|
|
.duty = calculate_duty(speed_mode, 0),
|
|
.gpio_num = config->channels.servo_pin[i],
|
|
.speed_mode = speed_mode,
|
|
.timer_sel = config->timer_number,
|
|
.hpoint = 0
|
|
};
|
|
ret = ledc_channel_config(&ledc_ch);
|
|
SERVO_CHECK(ESP_OK == ret, "ledc channel configuration failed", ESP_FAIL);
|
|
}
|
|
g_full_duty = (1 << SERVO_LEDC_INIT_BITS) - 1;
|
|
g_cfg[speed_mode] = *config;
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t iot_servo_deinit(ledc_mode_t speed_mode)
|
|
{
|
|
SERVO_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "LEDC speed mode invalid", ESP_ERR_INVALID_ARG);
|
|
for (size_t i = 0; i < g_cfg[speed_mode].channel_number; i++) {
|
|
ledc_stop(speed_mode, g_cfg[speed_mode].channels.ch[i], 0);
|
|
}
|
|
ledc_timer_rst(speed_mode, g_cfg[speed_mode].timer_number);
|
|
g_full_duty = 0;
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t iot_servo_write_angle(ledc_mode_t speed_mode, uint8_t channel, float angle)
|
|
{
|
|
SERVO_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "LEDC speed mode invalid", ESP_ERR_INVALID_ARG);
|
|
SERVO_CHECK(channel < LEDC_CHANNEL_MAX, "LEDC channel number too large", ESP_ERR_INVALID_ARG);
|
|
SERVO_CHECK(angle >= 0.0f, "Angle can't to be negative", ESP_ERR_INVALID_ARG);
|
|
esp_err_t ret;
|
|
uint32_t duty = calculate_duty(speed_mode, angle);
|
|
ret = ledc_set_duty(speed_mode, (ledc_channel_t)channel, duty);
|
|
ret |= ledc_update_duty(speed_mode, (ledc_channel_t)channel);
|
|
SERVO_CHECK(ESP_OK == ret, "write servo angle failed", ESP_FAIL);
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t iot_servo_read_angle(ledc_mode_t speed_mode, uint8_t channel, float *angle)
|
|
{
|
|
SERVO_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "LEDC speed mode invalid", ESP_ERR_INVALID_ARG);
|
|
SERVO_CHECK(channel < LEDC_CHANNEL_MAX, "LEDC channel number too large", ESP_ERR_INVALID_ARG);
|
|
uint32_t duty = ledc_get_duty(speed_mode, channel);
|
|
float a = calculate_angle(speed_mode, duty);
|
|
*angle = a;
|
|
return ESP_OK;
|
|
}
|