kopia lustrzana https://github.com/espressif/esp-idf
490 wiersze
17 KiB
C
490 wiersze
17 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include "math.h"
|
|
#include "sys/time.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "freertos/timers.h"
|
|
#include "esp_log.h"
|
|
#include "soc/ledc_reg.h"
|
|
#include "soc/timer_group_struct.h"
|
|
#include "soc/ledc_struct.h"
|
|
#include "driver/timer.h"
|
|
#include "driver/ledc.h"
|
|
#include "iot_light.h"
|
|
|
|
static const char *TAG = "light";
|
|
|
|
#define IOT_CHECK(tag, a, ret) if(!(a)) { \
|
|
return (ret); \
|
|
}
|
|
#define ERR_ASSERT(tag, param, ret) IOT_CHECK(tag, (param) == ESP_OK, ret)
|
|
#define POINT_ASSERT(tag, param) IOT_CHECK(tag, (param) != NULL, ESP_FAIL)
|
|
#define LIGHT_NUM_MAX 4
|
|
|
|
typedef enum {
|
|
LIGHT_CH_NUM_1 = 1, /*!< Light channel number */
|
|
LIGHT_CH_NUM_2 = 2, /*!< Light channel number */
|
|
LIGHT_CH_NUM_3 = 3, /*!< Light channel number */
|
|
LIGHT_CH_NUM_4 = 4, /*!< Light channel number */
|
|
LIGHT_CH_NUM_5 = 5, /*!< Light channel number */
|
|
LIGHT_CH_NUM_MAX, /*!< user shouldn't use this */
|
|
} light_channel_num_t;
|
|
|
|
typedef struct {
|
|
timer_group_t timer_group;
|
|
timer_idx_t timer_id;
|
|
} hw_timer_idx_t;
|
|
|
|
typedef struct {
|
|
gpio_num_t io_num;
|
|
ledc_mode_t mode;
|
|
ledc_channel_t channel;
|
|
int breath_period;
|
|
int blink_period;
|
|
uint32_t fade_step_num;
|
|
uint32_t *fade_duty_step;
|
|
int fade_duty_counter;
|
|
bool fade_step_up;
|
|
bool fade_once;
|
|
bool fade_start;
|
|
} light_channel_t;
|
|
|
|
typedef struct {
|
|
uint8_t channel_num;
|
|
ledc_mode_t mode;
|
|
ledc_timer_t ledc_timer;
|
|
uint32_t full_duty;
|
|
uint32_t freq_hz;
|
|
ledc_timer_bit_t timer_bit;
|
|
hw_timer_idx_t hw_timer;
|
|
light_channel_t *channel_group[0];
|
|
} light_t;
|
|
|
|
static bool g_fade_installed = false;
|
|
static bool g_hw_timer_started = false;
|
|
static light_t *g_light_group[LIGHT_NUM_MAX] = {NULL};
|
|
static esp_err_t iot_light_duty_set(light_handle_t light_handle, uint8_t channel_id, uint32_t duty);
|
|
|
|
static void iot_timer_create(hw_timer_idx_t *timer_id, bool auto_reload, double timer_interval_sec, timer_isr_handle_t *isr_handle)
|
|
{
|
|
/* Select and initialize basic parameters of the timer */
|
|
timer_config_t config = {
|
|
.divider = HW_TIMER_DIVIDER,
|
|
.counter_dir = TIMER_COUNT_UP,
|
|
.counter_en = TIMER_PAUSE,
|
|
.alarm_en = TIMER_ALARM_EN,
|
|
.intr_type = TIMER_INTR_LEVEL,
|
|
.auto_reload = auto_reload,
|
|
};
|
|
timer_init(timer_id->timer_group, timer_id->timer_id, &config);
|
|
|
|
/* Timer's counter will initially start from value below.
|
|
Also, if auto_reload is set, this value will be automatically reload on alarm */
|
|
timer_set_counter_value(timer_id->timer_group, timer_id->timer_id, 0x00000000ULL);
|
|
|
|
/* Configure the alarm value and the interrupt on alarm. */
|
|
timer_set_alarm_value(timer_id->timer_group, timer_id->timer_id, timer_interval_sec * HW_TIMER_SCALE);
|
|
timer_enable_intr(timer_id->timer_group, timer_id->timer_id);
|
|
timer_isr_register(timer_id->timer_group, timer_id->timer_id, (void *)isr_handle,
|
|
(void *) timer_id->timer_id, ESP_INTR_FLAG_IRAM, NULL);
|
|
}
|
|
|
|
static void iot_timer_start(hw_timer_idx_t *timer_id)
|
|
{
|
|
timer_start(timer_id->timer_group, timer_id->timer_id);
|
|
g_hw_timer_started = true;
|
|
}
|
|
|
|
static void iot_timer_stop(hw_timer_idx_t *timer_id)
|
|
{
|
|
timer_disable_intr(timer_id->timer_group, timer_id->timer_id);
|
|
timer_pause(timer_id->timer_group, timer_id->timer_id);
|
|
g_hw_timer_started = false;
|
|
}
|
|
|
|
static IRAM_ATTR void iot_ledc_ls_channel_update(ledc_mode_t speed_mode, ledc_channel_t channel_num)
|
|
{
|
|
if (speed_mode == LEDC_LOW_SPEED_MODE) {
|
|
LEDC.channel_group[speed_mode].channel[channel_num].conf0.low_speed_update = 1;
|
|
}
|
|
}
|
|
|
|
static IRAM_ATTR esp_err_t iot_ledc_duty_config(ledc_mode_t speed_mode, ledc_channel_t channel_num, int hpoint_val, int duty_val,
|
|
uint32_t duty_direction, uint32_t duty_num, uint32_t duty_cycle, uint32_t duty_scale)
|
|
{
|
|
if (hpoint_val >= 0) {
|
|
LEDC.channel_group[speed_mode].channel[channel_num].hpoint.hpoint = hpoint_val & LEDC_HPOINT_HSCH1_V;
|
|
}
|
|
|
|
if (duty_val >= 0) {
|
|
LEDC.channel_group[speed_mode].channel[channel_num].duty.duty = duty_val;
|
|
}
|
|
|
|
LEDC.channel_group[speed_mode].channel[channel_num].conf1.val = ((duty_direction & LEDC_DUTY_INC_HSCH0_V) << LEDC_DUTY_INC_HSCH0_S) |
|
|
((duty_num & LEDC_DUTY_NUM_HSCH0_V) << LEDC_DUTY_NUM_HSCH0_S) |
|
|
((duty_cycle & LEDC_DUTY_CYCLE_HSCH0_V) << LEDC_DUTY_CYCLE_HSCH0_S) |
|
|
((duty_scale & LEDC_DUTY_SCALE_HSCH0_V) << LEDC_DUTY_SCALE_HSCH0_S);
|
|
iot_ledc_ls_channel_update(speed_mode, channel_num);
|
|
return ESP_OK;
|
|
}
|
|
|
|
static IRAM_ATTR esp_err_t iot_ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty)
|
|
{
|
|
return iot_ledc_duty_config(speed_mode,
|
|
channel, //uint32_t chan_num,
|
|
-1,
|
|
duty << 4, //uint32_t duty_val,the least 4 bits are decimal part
|
|
1, //uint32_t increase,
|
|
1, //uint32_t duty_num,
|
|
1, //uint32_t duty_cycle,
|
|
0 //uint32_t duty_scale
|
|
);
|
|
}
|
|
|
|
static IRAM_ATTR esp_err_t iot_ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel)
|
|
{
|
|
LEDC.channel_group[speed_mode].channel[channel].conf0.sig_out_en = 1;
|
|
LEDC.channel_group[speed_mode].channel[channel].conf1.duty_start = 1;
|
|
iot_ledc_ls_channel_update(speed_mode, channel);
|
|
return ESP_OK;
|
|
}
|
|
|
|
static IRAM_ATTR void breath_timer_callback(void *para)
|
|
{
|
|
int timer_idx = (int) para;
|
|
|
|
/* Retrieve the interrupt status */
|
|
timer_group_get_intr_status_in_isr(HW_TIMER_GROUP);
|
|
timer_group_clr_intr_status_in_isr(HW_TIMER_GROUP, timer_idx);
|
|
/* After the alarm has been triggered
|
|
we need enable it again, so it is triggered the next time */
|
|
timer_group_enable_alarm_in_isr(HW_TIMER_GROUP, timer_idx);
|
|
|
|
for (int i = 0; i < LIGHT_NUM_MAX; i++) {
|
|
if (g_light_group[i] != NULL) {
|
|
light_t *light = g_light_group[i];
|
|
|
|
for (int j = 0; j < light->channel_num; j++) {
|
|
light_channel_t *l_chn = light->channel_group[j];
|
|
|
|
if (l_chn->fade_start == true) {
|
|
if (l_chn->fade_step_up == true) {
|
|
l_chn->fade_duty_counter++;
|
|
} else {
|
|
l_chn->fade_duty_counter--;
|
|
}
|
|
|
|
if ((l_chn->fade_duty_counter >= 0) && (l_chn->fade_duty_counter < l_chn->fade_step_num)) {
|
|
iot_ledc_set_duty(l_chn->mode, l_chn->channel, l_chn->fade_duty_step[l_chn->fade_duty_counter]);
|
|
iot_ledc_update_duty(l_chn->mode, l_chn->channel);
|
|
} else {
|
|
if (l_chn->fade_once != true) {
|
|
l_chn->fade_step_up = 1 - l_chn->fade_step_up;
|
|
} else {
|
|
l_chn->fade_start = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static light_channel_t *light_channel_create(gpio_num_t io_num, ledc_channel_t channel, ledc_mode_t mode, ledc_timer_t timer)
|
|
{
|
|
ledc_channel_config_t ledc_channel = {
|
|
.channel = channel,
|
|
.duty = 0,
|
|
.gpio_num = io_num,
|
|
.intr_type = LEDC_INTR_FADE_END,
|
|
.speed_mode = mode,
|
|
.timer_sel = timer
|
|
};
|
|
ERR_ASSERT(TAG, ledc_channel_config(&ledc_channel), NULL);
|
|
light_channel_t *pwm = (light_channel_t *)calloc(1, sizeof(light_channel_t));
|
|
pwm->io_num = io_num;
|
|
pwm->channel = channel;
|
|
pwm->mode = mode;
|
|
pwm->breath_period = 0;
|
|
pwm->blink_period = 0;
|
|
pwm->fade_step_num = 0;
|
|
pwm->fade_duty_step = NULL;
|
|
pwm->fade_duty_counter = 0;
|
|
pwm->fade_step_up = true;
|
|
pwm->fade_once = false;
|
|
pwm->fade_start = false;
|
|
return pwm;
|
|
}
|
|
|
|
static esp_err_t light_channel_delete(light_channel_t *light_channel)
|
|
{
|
|
POINT_ASSERT(TAG, light_channel);
|
|
|
|
if (light_channel->fade_duty_step != NULL) {
|
|
heap_caps_free(light_channel->fade_duty_step);
|
|
light_channel->fade_duty_step = NULL;
|
|
}
|
|
|
|
free(light_channel);
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t light_channel_fade_clear(light_channel_t *light_channel)
|
|
{
|
|
light_channel->fade_start = false;
|
|
light_channel->fade_step_num = 0;
|
|
light_channel->fade_duty_counter = 0;
|
|
light_channel->fade_step_up = true;
|
|
light_channel->fade_once = false;
|
|
|
|
if (light_channel->fade_duty_step != NULL) {
|
|
heap_caps_free(light_channel->fade_duty_step);
|
|
light_channel->fade_duty_step = NULL;
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
light_handle_t iot_light_create(ledc_timer_t timer, ledc_mode_t speed_mode, uint32_t freq_hz, uint8_t channel_num, ledc_timer_bit_t timer_bit)
|
|
{
|
|
IOT_CHECK(TAG, channel_num != 0, NULL);
|
|
ledc_timer_config_t timer_conf = {
|
|
.timer_num = timer,
|
|
.speed_mode = speed_mode,
|
|
.freq_hz = freq_hz,
|
|
.bit_num = timer_bit
|
|
};
|
|
ERR_ASSERT(TAG, ledc_timer_config(&timer_conf), NULL);
|
|
light_t *light_ptr = (light_t *)calloc(1, sizeof(light_t) + sizeof(light_channel_t *) * channel_num);
|
|
light_ptr->channel_num = channel_num;
|
|
light_ptr->ledc_timer = timer;
|
|
light_ptr->full_duty = (1 << timer_bit) - 1;
|
|
light_ptr->freq_hz = freq_hz;
|
|
light_ptr->mode = speed_mode;
|
|
light_ptr->timer_bit = timer_bit;
|
|
light_ptr->hw_timer.timer_group = HW_TIMER_GROUP;
|
|
light_ptr->hw_timer.timer_id = HW_TIMER_ID;
|
|
|
|
if (g_hw_timer_started == false) {
|
|
iot_timer_create(&(light_ptr->hw_timer), 1, (double)DUTY_SET_CYCLE / 1000, (void *)breath_timer_callback);
|
|
iot_timer_start(&(light_ptr->hw_timer));
|
|
}
|
|
|
|
for (int i = 0; i < channel_num; i++) {
|
|
light_ptr->channel_group[i] = NULL;
|
|
}
|
|
|
|
for (int i = 0; i < LIGHT_NUM_MAX; i++) {
|
|
if (g_light_group[i] == NULL) {
|
|
g_light_group[i] = light_ptr;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (light_handle_t)light_ptr;
|
|
}
|
|
|
|
esp_err_t iot_light_delete(light_handle_t light_handle)
|
|
{
|
|
light_t *light = (light_t *)light_handle;
|
|
POINT_ASSERT(TAG, light_handle);
|
|
|
|
for (int i = 0; i < light->channel_num; i++) {
|
|
if (light->channel_group[i] != NULL) {
|
|
iot_light_duty_set(light, i, 0);
|
|
light_channel_delete(light->channel_group[i]);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < LIGHT_NUM_MAX; i++) {
|
|
if (g_light_group[i] == light) {
|
|
g_light_group[i] = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < LIGHT_NUM_MAX; i++) {
|
|
if (g_light_group[i] != NULL) {
|
|
goto FREE_MEM;
|
|
}
|
|
}
|
|
|
|
ledc_fade_func_uninstall();
|
|
g_fade_installed = false;
|
|
iot_timer_stop(&(light->hw_timer));
|
|
FREE_MEM:
|
|
free(light_handle);
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t iot_light_channel_regist(light_handle_t light_handle, uint8_t channel_idx, gpio_num_t io_num, ledc_channel_t channel)
|
|
{
|
|
light_t *light = (light_t *)light_handle;
|
|
POINT_ASSERT(TAG, light_handle);
|
|
IOT_CHECK(TAG, channel_idx < light->channel_num, FAIL);
|
|
|
|
if (light->channel_group[channel_idx] != NULL) {
|
|
ESP_LOGE(TAG, "this channel index has been registered");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
light->channel_group[channel_idx] = light_channel_create(io_num, channel, light->mode, light->ledc_timer);
|
|
|
|
if (g_fade_installed == false) {
|
|
ledc_fade_func_install(0);
|
|
g_fade_installed = true;
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t iot_light_duty_set(light_handle_t light_handle, uint8_t channel_id, uint32_t duty)
|
|
{
|
|
light_t *light = (light_t *)light_handle;
|
|
POINT_ASSERT(TAG, light_handle);
|
|
IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
|
|
POINT_ASSERT(TAG, light->channel_group[channel_id]);
|
|
light_channel_t *l_chn = light->channel_group[channel_id];
|
|
|
|
light_channel_fade_clear(l_chn);
|
|
|
|
iot_ledc_set_duty(l_chn->mode, l_chn->channel, duty);
|
|
iot_ledc_update_duty(l_chn->mode, l_chn->channel);
|
|
return ESP_OK;
|
|
}
|
|
|
|
uint32_t iot_light_duty_get(light_handle_t light_handle, uint8_t channel_id)
|
|
{
|
|
light_t *light = (light_t *)light_handle;
|
|
POINT_ASSERT(TAG, light_handle);
|
|
IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
|
|
POINT_ASSERT(TAG, light->channel_group[channel_id]);
|
|
light_channel_t *l_chn = light->channel_group[channel_id];
|
|
|
|
return ledc_get_duty(l_chn->mode, l_chn->channel);
|
|
}
|
|
|
|
esp_err_t iot_light_fade_with_time(light_handle_t light_handle, uint8_t channel_id, uint32_t duty, uint32_t fade_period_ms)
|
|
{
|
|
light_t *light = (light_t *)light_handle;
|
|
POINT_ASSERT(TAG, light_handle);
|
|
IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
|
|
POINT_ASSERT(TAG, light->channel_group[channel_id]);
|
|
light_channel_t *l_chn = light->channel_group[channel_id];
|
|
|
|
if (fade_period_ms == 0) {
|
|
return iot_light_duty_set(light, channel_id, duty);
|
|
}
|
|
|
|
light_channel_fade_clear(l_chn);
|
|
|
|
l_chn->fade_step_num = fade_period_ms / DUTY_SET_CYCLE + 1;
|
|
l_chn->fade_duty_step = (uint32_t *)heap_caps_malloc(l_chn->fade_step_num * sizeof(uint32_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
|
|
|
|
uint32_t duty_cur = iot_light_duty_get(light, channel_id);
|
|
int duty_delta = duty - duty_cur;
|
|
|
|
if (duty_delta > 0) {
|
|
float coe = (duty_delta / pow((double)l_chn->fade_step_num, (double)1 / DUTY_SET_GAMMA));
|
|
|
|
for (int i = 0; i < l_chn->fade_step_num; i++) {
|
|
l_chn->fade_duty_step[i] = duty_cur + (int)(coe * pow((double)i, (double)1 / DUTY_SET_GAMMA));
|
|
}
|
|
|
|
l_chn->fade_step_up = true;
|
|
} else {
|
|
duty_delta = 0 - duty_delta;
|
|
float coe = (duty_delta / pow((double)l_chn->fade_step_num, (double)1 / DUTY_SET_GAMMA));
|
|
|
|
for (int i = 0; i < l_chn->fade_step_num; i++) {
|
|
l_chn->fade_duty_step[i] = duty + (int)(coe * pow((double)i, (double)1 / DUTY_SET_GAMMA));
|
|
}
|
|
|
|
l_chn->fade_duty_counter = l_chn->fade_step_num;
|
|
l_chn->fade_step_up = false;
|
|
}
|
|
|
|
l_chn->fade_once = true;
|
|
l_chn->fade_start = true;
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t iot_light_breath_config(light_handle_t light_handle, uint8_t channel_id, uint32_t duty, uint32_t breath_period_ms)
|
|
{
|
|
light_t *light = (light_t *)light_handle;
|
|
POINT_ASSERT(TAG, light_handle);
|
|
IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
|
|
POINT_ASSERT(TAG, light->channel_group[channel_id]);
|
|
light_channel_t *l_chn = light->channel_group[channel_id];
|
|
|
|
light_channel_fade_clear(l_chn);
|
|
|
|
/**
|
|
* @brief control light with nonlinearity fade, the duty and the fade step
|
|
* conform to gamma curve. gamma curve formula: y=a*x^(1/gm) firstly,
|
|
* use the target duty and step number to calculate coefficient `a`,
|
|
* secondly, calculate the duty for every step
|
|
*/
|
|
|
|
l_chn->fade_step_num = (breath_period_ms / 2) / DUTY_SET_CYCLE + 1;
|
|
float coe = (duty / pow((double)l_chn->fade_step_num, (double)1 / DUTY_SET_GAMMA));
|
|
|
|
l_chn->fade_duty_step = (uint32_t *)heap_caps_malloc(l_chn->fade_step_num * sizeof(uint32_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
|
|
|
|
for (int i = 0; i < l_chn->fade_step_num; i++) {
|
|
l_chn->fade_duty_step[i] = (int)(coe * pow((double)i, (double)1 / DUTY_SET_GAMMA));
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t iot_light_blink_config(light_handle_t light_handle, uint8_t channel_id, uint32_t blink_period_ms)
|
|
{
|
|
light_t *light = (light_t *)light_handle;
|
|
POINT_ASSERT(TAG, light_handle);
|
|
IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
|
|
light_channel_t *l_chn = light->channel_group[channel_id];
|
|
|
|
light_channel_fade_clear(l_chn);
|
|
|
|
l_chn->fade_step_num = (blink_period_ms / 2) / DUTY_SET_CYCLE;
|
|
l_chn->fade_duty_step = (uint32_t *)heap_caps_malloc(l_chn->fade_step_num * sizeof(uint32_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
|
|
int i = 0;
|
|
|
|
for (i = 0; i < (l_chn->fade_step_num / 2); i++) {
|
|
l_chn->fade_duty_step[i] = 0;
|
|
}
|
|
|
|
for (; i < l_chn->fade_step_num; i++) {
|
|
l_chn->fade_duty_step[i] = light->full_duty;
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t iot_light_operate_start(light_handle_t light_handle, uint8_t channel_id)
|
|
{
|
|
light_t *light = (light_t *)light_handle;
|
|
POINT_ASSERT(TAG, light_handle);
|
|
IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
|
|
light->channel_group[channel_id]->fade_start = true;
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t iot_light_operate_stop(light_handle_t light_handle, uint8_t channel_id)
|
|
{
|
|
light_t *light = (light_t *)light_handle;
|
|
POINT_ASSERT(TAG, light_handle);
|
|
IOT_CHECK(TAG, channel_id < light->channel_num, ESP_FAIL);
|
|
light->channel_group[channel_id]->fade_start = false;
|
|
// iot_light_duty_set(light, channel_id, 0);
|
|
return ESP_OK;
|
|
}
|