|
@@ -9,53 +9,125 @@
|
|
|
#include "luat_log.h"
|
|
#include "luat_log.h"
|
|
|
#define LUAT_LOG_TAG "pwm"
|
|
#define LUAT_LOG_TAG "pwm"
|
|
|
|
|
|
|
|
-static uint8_t luat_pwm_idf[LEDC_TIMER_MAX] = {0};
|
|
|
|
|
|
|
+typedef struct pwm_cont {
|
|
|
|
|
+ luat_pwm_conf_t pc;
|
|
|
|
|
+ uint8_t is_opened;
|
|
|
|
|
+}pwm_conf_t;
|
|
|
|
|
+
|
|
|
|
|
+static pwm_conf_t luat_pwm_idf[LEDC_TIMER_MAX];
|
|
|
|
|
+
|
|
|
|
|
+#define LEDC_LL_FRACTIONAL_BITS (8)
|
|
|
|
|
+#define LEDC_LL_FRACTIONAL_MAX ((1 << LEDC_LL_FRACTIONAL_BITS) - 1)
|
|
|
|
|
+#define LEDC_TIMER_DIV_NUM_MAX (0x3FFFF)
|
|
|
|
|
+#define LEDC_IS_DIV_INVALID(div) ((div) <= LEDC_LL_FRACTIONAL_MAX || (div) > LEDC_TIMER_DIV_NUM_MAX)
|
|
|
|
|
+
|
|
|
|
|
+static inline uint32_t ilog2(uint32_t i)
|
|
|
|
|
+{
|
|
|
|
|
+ assert(i > 0);
|
|
|
|
|
+ uint32_t log = 0;
|
|
|
|
|
+ while (i >>= 1) {
|
|
|
|
|
+ ++log;
|
|
|
|
|
+ }
|
|
|
|
|
+ return log;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static inline uint32_t ledc_calculate_divisor(uint32_t src_clk_freq, int freq_hz, uint32_t precision)
|
|
|
|
|
+{
|
|
|
|
|
+ uint64_t fp = freq_hz;
|
|
|
|
|
+ fp *= precision;
|
|
|
|
|
+ return ( ( (uint64_t) src_clk_freq << LEDC_LL_FRACTIONAL_BITS ) + (fp / 2 ) ) / fp;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf#ledpwm
|
|
|
|
|
+static uint32_t ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq)
|
|
|
|
|
+{
|
|
|
|
|
+ uint32_t div = (src_clk_freq + timer_freq / 2) / timer_freq; // rounded
|
|
|
|
|
+ uint32_t duty_resolution = ilog2(div);
|
|
|
|
|
+ if ( duty_resolution >= SOC_LEDC_TIMER_BIT_WIDTH) {
|
|
|
|
|
+ duty_resolution = SOC_LEDC_TIMER_BIT_WIDTH - 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ uint32_t div_param = ledc_calculate_divisor(src_clk_freq, timer_freq, 1 << duty_resolution);
|
|
|
|
|
+ if (LEDC_IS_DIV_INVALID(div_param)) {
|
|
|
|
|
+ div = src_clk_freq / timer_freq; // truncated
|
|
|
|
|
+ duty_resolution = ilog2(div);
|
|
|
|
|
+ if ( duty_resolution > SOC_LEDC_TIMER_BIT_WIDTH - 1) {
|
|
|
|
|
+ duty_resolution = SOC_LEDC_TIMER_BIT_WIDTH - 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ div_param = ledc_calculate_divisor(src_clk_freq, timer_freq, 1 << duty_resolution);
|
|
|
|
|
+ if (LEDC_IS_DIV_INVALID(div_param)) {
|
|
|
|
|
+ duty_resolution = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return duty_resolution;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
int luat_pwm_setup(luat_pwm_conf_t *conf){
|
|
int luat_pwm_setup(luat_pwm_conf_t *conf){
|
|
|
- int resolution = 0;
|
|
|
|
|
|
|
+ int duty_resolution = 0;
|
|
|
int timer = -1;
|
|
int timer = -1;
|
|
|
int ret = -1;
|
|
int ret = -1;
|
|
|
if (conf->channel < 0)
|
|
if (conf->channel < 0)
|
|
|
return -1;
|
|
return -1;
|
|
|
- for (size_t i = 0; i < LEDC_TIMER_MAX; i++){
|
|
|
|
|
- if (luat_pwm_idf[i]==0 || luat_pwm_idf[i]==conf->channel+1){
|
|
|
|
|
|
|
+ for (size_t i = 0; i < LEDC_TIMER_MAX; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (luat_pwm_idf[i].is_opened && luat_pwm_idf[i].pc.channel == conf->channel) {
|
|
|
|
|
+ timer = i;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!luat_pwm_idf[i].is_opened) {
|
|
|
timer = i;
|
|
timer = i;
|
|
|
|
|
+ break;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
if (timer < 0) {
|
|
if (timer < 0) {
|
|
|
LLOGE("too many PWM!!! only %d channels supported", LEDC_TIMER_MAX);
|
|
LLOGE("too many PWM!!! only %d channels supported", LEDC_TIMER_MAX);
|
|
|
return -1;
|
|
return -1;
|
|
|
}
|
|
}
|
|
|
- resolution = ceil(log2(conf->precision));
|
|
|
|
|
- if (luat_pwm_idf[timer] == 0){
|
|
|
|
|
|
|
+ if (conf->pulse > conf->precision) {
|
|
|
|
|
+ conf->pulse = conf->precision;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ duty_resolution = ledc_find_suitable_duty_resolution(80*1000*1000, conf->period);
|
|
|
|
|
+
|
|
|
|
|
+ int duty = (conf->pulse * (1 << duty_resolution)) / conf->precision;
|
|
|
|
|
+ // LLOGD("freq=%d, pulse=%d, precision=%d, resolution=%08X, duty=%08X", (int)conf->period, (int)conf->pulse, (int)conf->precision, (int)(1 << duty_resolution), (int)duty);
|
|
|
|
|
+
|
|
|
|
|
+ int speed_mode = LEDC_LOW_SPEED_MODE;
|
|
|
|
|
+
|
|
|
|
|
+ // 判断一下是否需要完全重新配置
|
|
|
|
|
+ if (conf->period != luat_pwm_idf[timer].pc.period || // 频率是否相同
|
|
|
|
|
+ conf->precision != luat_pwm_idf[timer].pc.precision || // 占空比精度是否相同
|
|
|
|
|
+ conf->pnum != luat_pwm_idf[timer].pc.pnum // 输出脉冲数是否相同
|
|
|
|
|
+ ) {
|
|
|
|
|
+ // LLOGD("need to reconfig channel %d period %d", conf->channel, conf->period);
|
|
|
ledc_timer_config_t ledc_timer = {
|
|
ledc_timer_config_t ledc_timer = {
|
|
|
- .speed_mode = LEDC_LOW_SPEED_MODE,
|
|
|
|
|
|
|
+ .speed_mode = speed_mode,
|
|
|
.timer_num = timer,
|
|
.timer_num = timer,
|
|
|
.freq_hz = conf->period,
|
|
.freq_hz = conf->period,
|
|
|
.clk_cfg = LEDC_AUTO_CLK,
|
|
.clk_cfg = LEDC_AUTO_CLK,
|
|
|
|
|
+ .duty_resolution = duty_resolution
|
|
|
};
|
|
};
|
|
|
- ledc_timer.duty_resolution = resolution;
|
|
|
|
|
-
|
|
|
|
|
|
|
+ // LLOGD("duty_resolution %d %04X", duty_resolution, 1 << duty_resolution);
|
|
|
ret = ledc_timer_config(&ledc_timer);
|
|
ret = ledc_timer_config(&ledc_timer);
|
|
|
if (ret) {
|
|
if (ret) {
|
|
|
return -1;
|
|
return -1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
ledc_channel_config_t ledc_channel = {
|
|
ledc_channel_config_t ledc_channel = {
|
|
|
- .speed_mode = LEDC_LOW_SPEED_MODE,
|
|
|
|
|
|
|
+ .speed_mode = speed_mode,
|
|
|
.channel = timer,
|
|
.channel = timer,
|
|
|
.timer_sel = timer,
|
|
.timer_sel = timer,
|
|
|
.intr_type = LEDC_INTR_DISABLE,
|
|
.intr_type = LEDC_INTR_DISABLE,
|
|
|
.gpio_num = conf->channel,
|
|
.gpio_num = conf->channel,
|
|
|
- .duty = 0,
|
|
|
|
|
|
|
+ .duty = duty,
|
|
|
.hpoint = 0,
|
|
.hpoint = 0,
|
|
|
};
|
|
};
|
|
|
ledc_channel_config(&ledc_channel);
|
|
ledc_channel_config(&ledc_channel);
|
|
|
- luat_pwm_idf[timer] = conf->channel+1;//避免使用0
|
|
|
|
|
}
|
|
}
|
|
|
- ledc_set_freq(LEDC_LOW_SPEED_MODE, timer, conf->period);
|
|
|
|
|
- ledc_set_duty(LEDC_LOW_SPEED_MODE, timer, pow(2, resolution) * conf->pulse / conf->precision );
|
|
|
|
|
- ledc_update_duty(LEDC_LOW_SPEED_MODE, timer);
|
|
|
|
|
|
|
+ ledc_set_duty(speed_mode, timer, duty);
|
|
|
|
|
+ ledc_update_duty(speed_mode, timer);
|
|
|
|
|
+ memcpy( &luat_pwm_idf[timer].pc, conf, sizeof(luat_pwm_conf_t));
|
|
|
|
|
+ luat_pwm_idf[timer].is_opened = 1;
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -64,7 +136,7 @@ int luat_pwm_close(int channel){
|
|
|
if (channel < 0)
|
|
if (channel < 0)
|
|
|
return -1;
|
|
return -1;
|
|
|
for (size_t i = 0; i < LEDC_TIMER_MAX; i++){
|
|
for (size_t i = 0; i < LEDC_TIMER_MAX; i++){
|
|
|
- if (luat_pwm_idf[i]==channel+1){
|
|
|
|
|
|
|
+ if (luat_pwm_idf[i].is_opened && luat_pwm_idf[i].pc.channel == channel){
|
|
|
timer = i;
|
|
timer = i;
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
@@ -77,7 +149,8 @@ int luat_pwm_close(int channel){
|
|
|
return -1;
|
|
return -1;
|
|
|
}
|
|
}
|
|
|
gpio_reset_pin(channel);
|
|
gpio_reset_pin(channel);
|
|
|
- luat_pwm_idf[timer] = 0;
|
|
|
|
|
|
|
+ luat_pwm_idf[timer].is_opened = 0;
|
|
|
|
|
+ memset(&luat_pwm_idf[timer].pc, 0, sizeof(luat_pwm_conf_t) );
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|