Allows disabling backwards compatibility, which is not enabled by default since IDF 5. Since these symbols have been stable for many years now, it's time to let them go. - portTICK_RATE_MS renamed to portTICK_PERIOD_MS - xSemaphoreHandle renamed to SemaphoreHandle_t - xTaskHandle renamed to TaskHandle_t
427 lines
13 KiB
C
427 lines
13 KiB
C
/*
|
|
* ESPRESSIF MIT License
|
|
*
|
|
* Copyright (c) 2020 <ESPRESSIF SYSTEMS (SHANGHAI) CO., LTD>
|
|
*
|
|
* Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in
|
|
* which case, it is free of charge, to any person obtaining a copy of this
|
|
* software and associated documentation files (the "Software"), to deal in the
|
|
* Software without restriction, including without limitation the rights to use,
|
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
* the Software, and to permit persons to whom the Software is furnished to do
|
|
* so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*
|
|
*/
|
|
|
|
#include "periph_ws2812.h"
|
|
|
|
#include <driver/rmt.h>
|
|
#include <string.h>
|
|
|
|
#include "audio_mem.h"
|
|
#include "audio_sys.h"
|
|
#include "esp_log.h"
|
|
#include "esp_peripherals.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/semphr.h"
|
|
#include "freertos/task.h"
|
|
#include "soc/dport_access.h"
|
|
#if !defined CONFIG_IDF_TARGET_ESP32C3 && !defined CONFIG_IDF_TARGET_ESP32C6
|
|
#include "soc/dport_reg.h"
|
|
#endif
|
|
#include "audio_idf_version.h"
|
|
#include "driver/rmt.h"
|
|
|
|
static const char *TAG = "PERIPH_WS2812";
|
|
|
|
#define DIVIDER 4
|
|
#define DURATION 12.5
|
|
#define RMTCHANNEL 0
|
|
#define MAX_PULSES 32
|
|
#define PULSE_T0H ((uint32_t)(350 / (DURATION * DIVIDER)) & 0x00007FFF)
|
|
#define PULSE_T1H ((uint32_t)(900 / (DURATION * DIVIDER)) & 0x00007FFF)
|
|
#define PULSE_T0L ((uint32_t)(900 / (DURATION * DIVIDER)) & 0x00007FFF)
|
|
#define PULSE_T1L ((uint32_t)(350 / (DURATION * DIVIDER)) & 0x00007FFF)
|
|
#define PULSE_TRS (50000 / (DURATION * DIVIDER))
|
|
#define PULSE_BIT0 \
|
|
(((uint32_t)PULSE_T0L << 16) + (((uint32_t)1) << 15) + (PULSE_T0H))
|
|
#define PULSE_BIT1 \
|
|
(((uint32_t)PULSE_T1L << 16) + (((uint32_t)1) << 15) + (PULSE_T1H))
|
|
|
|
#define FADE_STEP 30
|
|
#define INTERVAL_TIME_MS 10
|
|
|
|
typedef union {
|
|
struct __attribute__((packed)) {
|
|
uint8_t r, g, b;
|
|
};
|
|
uint32_t num;
|
|
} rgb_value;
|
|
|
|
typedef struct {
|
|
periph_rgb_value color;
|
|
periph_ws2812_mode_t mode;
|
|
uint32_t time_on_ms;
|
|
uint32_t time_off_ms;
|
|
long long tick;
|
|
uint32_t loop;
|
|
bool is_on;
|
|
bool is_set;
|
|
} periph_ws2812_state_t;
|
|
|
|
typedef struct {
|
|
uint32_t pos;
|
|
uint32_t half;
|
|
uint8_t *buffer;
|
|
} periph_ws2812_process_t;
|
|
|
|
typedef struct periph_ws2812 {
|
|
periph_rgb_value *color;
|
|
uint32_t led_num;
|
|
TimerHandle_t timer;
|
|
SemaphoreHandle_t sem;
|
|
intr_handle_t rmt_intr_handle;
|
|
periph_ws2812_state_t *state;
|
|
periph_ws2812_process_t process;
|
|
} periph_ws2812_t;
|
|
|
|
static esp_err_t ws2812_init_rmt_channel(int rmt_channel, int gpio_num) {
|
|
rmt_config_t rmt_tx;
|
|
rmt_tx.channel = rmt_channel;
|
|
rmt_tx.gpio_num = gpio_num;
|
|
rmt_tx.mem_block_num = 1;
|
|
rmt_tx.clk_div = DIVIDER;
|
|
rmt_tx.tx_config.loop_en = false;
|
|
rmt_tx.tx_config.carrier_level = 1;
|
|
rmt_tx.tx_config.carrier_en = 0;
|
|
rmt_tx.tx_config.idle_level = 0;
|
|
rmt_tx.tx_config.idle_output_en = true;
|
|
rmt_tx.rmt_mode = RMT_MODE_TX;
|
|
rmt_config(&rmt_tx);
|
|
rmt_driver_install(rmt_tx.channel, 0, 0);
|
|
|
|
rmt_set_tx_thr_intr_en(RMTCHANNEL, true, MAX_PULSES);
|
|
rmt_set_mem_block_num(RMTCHANNEL, 1);
|
|
rmt_set_mem_pd(RMTCHANNEL, false);
|
|
rmt_set_tx_loop_mode(RMTCHANNEL, false);
|
|
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0))
|
|
rmt_set_source_clk(RMTCHANNEL, RMT_BASECLK_DEFAULT);
|
|
#else
|
|
rmt_set_source_clk(RMTCHANNEL, RMT_BASECLK_APB);
|
|
#endif
|
|
#if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 3, 0))
|
|
rmt_set_intr_enable_mask(BIT(0) | BIT(24));
|
|
#endif
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t ws2812_data_copy(periph_ws2812_t *ws) {
|
|
unsigned int i, j, len, bit;
|
|
|
|
len = ws->led_num * 3;
|
|
; // - ws->process.pos;
|
|
rmt_item32_t *rmt_data = malloc(sizeof(rmt_item32_t) * len * 8);
|
|
|
|
for (i = 0; i < len; i++) {
|
|
bit = ws->process.buffer[i];
|
|
for (j = 0; j < 8; j++, bit <<= 1) {
|
|
if ((bit >> 7) & 0x01) {
|
|
rmt_data[j + i * 8].val = PULSE_BIT1;
|
|
} else {
|
|
rmt_data[j + i * 8].val = PULSE_BIT0;
|
|
}
|
|
}
|
|
if (i + ws->process.pos == ws->led_num * 3 - 1) {
|
|
rmt_data[7 + i * 8].duration1 = PULSE_TRS;
|
|
}
|
|
}
|
|
|
|
rmt_write_items(RMTCHANNEL, rmt_data, len * 8, portMAX_DELAY);
|
|
|
|
if (rmt_data) {
|
|
free(rmt_data);
|
|
rmt_data = NULL;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
static void rmt_handle_tx_end(rmt_channel_t channel, void *arg) {
|
|
portBASE_TYPE taskAwoken = 0;
|
|
periph_ws2812_t *ws = (periph_ws2812_t *)(arg);
|
|
xSemaphoreGiveFromISR(ws->sem, &taskAwoken);
|
|
}
|
|
|
|
static esp_err_t ws2812_set_colors(periph_ws2812_t *ws) {
|
|
AUDIO_NULL_CHECK(TAG, ws, return ESP_FAIL);
|
|
|
|
ws->process.buffer = audio_malloc(ws->led_num * 3 * sizeof(uint8_t));
|
|
AUDIO_NULL_CHECK(TAG, ws->process.buffer, return ESP_FAIL);
|
|
|
|
for (int i = 0; i < ws->led_num; i++) {
|
|
rgb_value rgb = {.num = ws->color[i]};
|
|
ws->process.buffer[0 + i * 3] = rgb.g;
|
|
ws->process.buffer[1 + i * 3] = rgb.r;
|
|
ws->process.buffer[2 + i * 3] = rgb.b;
|
|
}
|
|
ws->process.pos = 0;
|
|
ws->process.half = 0;
|
|
|
|
ws2812_data_copy(ws);
|
|
xSemaphoreTake(ws->sem, portMAX_DELAY);
|
|
if (ws->process.buffer) {
|
|
audio_free(ws->process.buffer);
|
|
ws->process.buffer = NULL;
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
static void ws2812_timer_handler(TimerHandle_t tmr) {
|
|
esp_periph_handle_t periph = (esp_periph_handle_t)pvTimerGetTimerID(tmr);
|
|
periph_ws2812_t *periph_ws2812 = esp_periph_get_data(periph);
|
|
periph_ws2812_state_t *st = periph_ws2812->state;
|
|
for (int i = 0; i < periph_ws2812->led_num; i++) {
|
|
switch (st[i].mode) {
|
|
case PERIPH_WS2812_ONE:
|
|
if (st[i].is_on) {
|
|
periph_ws2812->color[i] = st[i].color;
|
|
ws2812_set_colors(periph_ws2812);
|
|
st[i].is_on = false;
|
|
st[i].loop = 0;
|
|
}
|
|
break;
|
|
|
|
case PERIPH_WS2812_BLINK:
|
|
if (st[i].is_set == false) {
|
|
continue;
|
|
}
|
|
if (st[i].loop == 0) {
|
|
periph_ws2812->color[i] = LED2812_COLOR_BLACK;
|
|
ws2812_set_colors(periph_ws2812);
|
|
st[i].is_set = false;
|
|
}
|
|
|
|
if (st[i].is_on &&
|
|
audio_sys_get_time_ms() - st[i].tick > st[i].time_off_ms) {
|
|
if (st[i].loop > 0) {
|
|
st[i].loop--;
|
|
} else {
|
|
continue;
|
|
}
|
|
st[i].is_on = false;
|
|
st[i].tick = audio_sys_get_time_ms();
|
|
periph_ws2812->color[i] = st[i].color;
|
|
ws2812_set_colors(periph_ws2812);
|
|
} else if (!st[i].is_on &&
|
|
audio_sys_get_time_ms() - st[i].tick > st[i].time_on_ms) {
|
|
st[i].is_on = true;
|
|
st[i].tick = audio_sys_get_time_ms();
|
|
periph_ws2812->color[i] = LED2812_COLOR_BLACK;
|
|
ws2812_set_colors(periph_ws2812);
|
|
}
|
|
break;
|
|
|
|
case PERIPH_WS2812_FADE:
|
|
if (st[i].is_set == false) {
|
|
continue;
|
|
}
|
|
if (st[i].loop == 0) {
|
|
periph_ws2812->color[i] = LED2812_COLOR_BLACK;
|
|
ws2812_set_colors(periph_ws2812);
|
|
st[i].is_set = false;
|
|
continue;
|
|
}
|
|
|
|
if (st[i].is_on && (audio_sys_get_time_ms() - st[i].tick >
|
|
((st[i].time_on_ms / FADE_STEP)))) {
|
|
st[i].tick = audio_sys_get_time_ms();
|
|
rgb_value rgb = {.num = st[i].color};
|
|
rgb_value rgb1 = {.num = periph_ws2812->color[i]};
|
|
rgb1.r -= (uint8_t)rgb.r / FADE_STEP;
|
|
rgb1.g -= (uint8_t)rgb.g / FADE_STEP;
|
|
rgb1.b -= (uint8_t)rgb.b / FADE_STEP;
|
|
ws2812_set_colors(periph_ws2812);
|
|
periph_ws2812->color[i] = rgb1.num;
|
|
if ((rgb1.r <= (uint8_t)rgb.r / FADE_STEP) &&
|
|
(rgb1.g <= (uint8_t)rgb.g / FADE_STEP) &&
|
|
(rgb1.b <= (uint8_t)rgb.b / FADE_STEP)) {
|
|
st[i].is_on = false;
|
|
st[i].loop--;
|
|
}
|
|
} else if ((st[i].is_on == false) &&
|
|
(audio_sys_get_time_ms() - st[i].tick >
|
|
((st[i].time_off_ms / FADE_STEP)))) {
|
|
st[i].tick = audio_sys_get_time_ms();
|
|
rgb_value rgb = {.num = st[i].color};
|
|
rgb_value rgb1 = {.num = periph_ws2812->color[i]};
|
|
rgb1.r += (uint8_t)rgb.r / FADE_STEP;
|
|
rgb1.g += (uint8_t)rgb.g / FADE_STEP;
|
|
rgb1.b += (uint8_t)rgb.b / FADE_STEP;
|
|
ws2812_set_colors(periph_ws2812);
|
|
periph_ws2812->color[i] = rgb1.num;
|
|
if ((((uint8_t)rgb.r - rgb1.r) <= (uint8_t)rgb.r / FADE_STEP) &&
|
|
(((uint8_t)rgb.g - rgb1.g) <= (uint8_t)rgb.g / FADE_STEP) &&
|
|
(((uint8_t)rgb.b - rgb1.b) <= (uint8_t)rgb.b / FADE_STEP)) {
|
|
st[i].is_on = true;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
ESP_LOGW(TAG, "The ws2812 mode[%d] is invalid", st[i].mode);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static esp_err_t _ws2812_run(esp_periph_handle_t periph,
|
|
audio_event_iface_msg_t *msg) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t _ws2812_init(esp_periph_handle_t periph) { return ESP_OK; }
|
|
|
|
static esp_err_t _ws2812_destroy(esp_periph_handle_t periph) {
|
|
periph_ws2812_t *periph_ws2812 = esp_periph_get_data(periph);
|
|
AUDIO_NULL_CHECK(TAG, periph_ws2812, return ESP_FAIL);
|
|
|
|
if (periph_ws2812) {
|
|
periph_ws2812_state_t *st = periph_ws2812->state;
|
|
for (int i = 0; i < periph_ws2812->led_num; i++) {
|
|
st[i].color = LED2812_COLOR_BLACK;
|
|
st[i].is_on = true;
|
|
st[i].mode = PERIPH_WS2812_ONE;
|
|
}
|
|
ws2812_set_colors(periph_ws2812);
|
|
|
|
if (periph_ws2812->color) {
|
|
audio_free(periph_ws2812->color);
|
|
periph_ws2812->color = NULL;
|
|
}
|
|
|
|
if (periph_ws2812->state) {
|
|
audio_free(periph_ws2812->state);
|
|
periph_ws2812->state = NULL;
|
|
}
|
|
|
|
esp_periph_stop_timer(periph);
|
|
rmt_tx_stop(RMTCHANNEL);
|
|
rmt_driver_uninstall(RMTCHANNEL);
|
|
vSemaphoreDelete(periph_ws2812->sem);
|
|
|
|
audio_free(periph_ws2812);
|
|
periph_ws2812 = NULL;
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_periph_handle_t periph_ws2812_init(periph_ws2812_cfg_t *config) {
|
|
AUDIO_NULL_CHECK(TAG, config, return NULL);
|
|
|
|
esp_periph_handle_t periph =
|
|
esp_periph_create(PERIPH_ID_WS2812, "periph_ws2812");
|
|
periph_ws2812_t *periph_ws2812 = audio_calloc(1, sizeof(periph_ws2812_t));
|
|
AUDIO_NULL_CHECK(TAG, periph_ws2812, goto ws2812_init_err);
|
|
|
|
periph_ws2812->led_num = config->led_num;
|
|
periph_ws2812->timer = NULL;
|
|
periph_ws2812->sem = xSemaphoreCreateBinary();
|
|
periph_ws2812->rmt_intr_handle = NULL;
|
|
|
|
periph_ws2812->color =
|
|
audio_malloc(sizeof(periph_rgb_value) * periph_ws2812->led_num);
|
|
AUDIO_NULL_CHECK(TAG, periph_ws2812->color, goto ws2812_init_err);
|
|
|
|
for (int i = 0; i < periph_ws2812->led_num; i++) {
|
|
periph_ws2812->color[i] = LED2812_COLOR_BLACK;
|
|
}
|
|
|
|
periph_ws2812->state =
|
|
audio_malloc(sizeof(periph_ws2812_state_t) * (periph_ws2812->led_num));
|
|
AUDIO_NULL_CHECK(TAG, periph_ws2812->state, goto ws2812_init_err);
|
|
|
|
ws2812_init_rmt_channel(RMTCHANNEL, (gpio_num_t)config->gpio_num);
|
|
esp_periph_set_data(periph, periph_ws2812);
|
|
rmt_register_tx_end_callback(rmt_handle_tx_end, (void *)periph_ws2812);
|
|
|
|
esp_periph_set_function(periph, _ws2812_init, _ws2812_run, _ws2812_destroy);
|
|
ws2812_set_colors(periph_ws2812);
|
|
ESP_LOGD(TAG, "periph ws2812 init");
|
|
return periph;
|
|
|
|
ws2812_init_err:
|
|
if (periph_ws2812->sem) {
|
|
vSemaphoreDelete(periph_ws2812->sem);
|
|
periph_ws2812->sem = NULL;
|
|
}
|
|
if (periph_ws2812->color) {
|
|
audio_free(periph_ws2812->color);
|
|
periph_ws2812->color = NULL;
|
|
}
|
|
if (periph_ws2812->state) {
|
|
audio_free(periph_ws2812->state);
|
|
periph_ws2812->state = NULL;
|
|
}
|
|
if (periph_ws2812) {
|
|
audio_free(periph_ws2812);
|
|
periph_ws2812 = NULL;
|
|
}
|
|
if (periph) {
|
|
audio_free(periph);
|
|
}
|
|
periph = NULL;
|
|
return periph;
|
|
}
|
|
|
|
esp_err_t periph_ws2812_control(esp_periph_handle_t periph,
|
|
periph_ws2812_ctrl_cfg_t *control_cfg,
|
|
void *ctx) {
|
|
periph_ws2812_t *periph_ws2812 = esp_periph_get_data(periph);
|
|
|
|
AUDIO_NULL_CHECK(TAG, periph_ws2812, return ESP_FAIL);
|
|
AUDIO_NULL_CHECK(TAG, control_cfg, return ESP_FAIL);
|
|
|
|
for (int i = 0; i < periph_ws2812->led_num; i++) {
|
|
periph_ws2812->state[i].color = control_cfg[i].color;
|
|
periph_ws2812->color[i] = control_cfg[i].color;
|
|
periph_ws2812->state[i].time_on_ms = control_cfg[i].time_on_ms;
|
|
periph_ws2812->state[i].time_off_ms = control_cfg[i].time_off_ms;
|
|
periph_ws2812->state[i].tick = audio_sys_get_time_ms();
|
|
periph_ws2812->state[i].loop = control_cfg[i].loop;
|
|
periph_ws2812->state[i].is_on = true;
|
|
periph_ws2812->state[i].is_set = true;
|
|
periph_ws2812->state[i].mode = control_cfg[i].mode;
|
|
}
|
|
|
|
esp_periph_start_timer(periph, INTERVAL_TIME_MS / portTICK_PERIOD_MS,
|
|
ws2812_timer_handler);
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t periph_ws2812_stop(esp_periph_handle_t periph) {
|
|
periph_ws2812_t *periph_ws2812 = esp_periph_get_data(periph);
|
|
|
|
AUDIO_NULL_CHECK(TAG, periph_ws2812, return ESP_FAIL);
|
|
|
|
periph_ws2812_state_t *st = periph_ws2812->state;
|
|
for (int i = 0; i < periph_ws2812->led_num; i++) {
|
|
st[i].color = LED2812_COLOR_BLACK;
|
|
st[i].is_on = true;
|
|
st[i].mode = PERIPH_WS2812_ONE;
|
|
}
|
|
ws2812_set_colors(periph_ws2812);
|
|
return ESP_OK;
|
|
}
|