1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

drivers/ws281x: add ESP32x hardware support

This commit is contained in:
Gunar Schorcht 2023-03-26 18:34:25 +02:00
parent b2f9cdac23
commit 6d78cef999
3 changed files with 140 additions and 2 deletions

View File

@ -26,6 +26,25 @@ config MODULE_WS281X_ESP32
bool
depends on HAS_ARCH_ESP32
config MODULE_WS281X_ESP32_HW
bool "WS2812/SK6812 RGB LED uses ESP32x RMT"
depends on MODULE_WS281X_ESP32 && HAS_ESP_RMT
default y
help
The driver can use on ESP32x SoCs either the Remote Control (RMT)
peripheral or a bit-banging software implementation to generate the
RGB LED signal. Using the RMT peripheral requires more ROM and RAM but
does not use the CPU to generate the RGB LED signal. Disable this
option if saving ROM and RAM is required.
config MODULE_WS281X_ESP32_SW
bool
depends on MODULE_WS281X_ESP32
default y if !MODULE_WS281X_ESP32_HW
help
Use the bit-banging software implementation to generate the RGB LED
signal.
config HAVE_WS281X
bool
help

View File

@ -15,3 +15,16 @@ endif
ifneq (,$(filter ws281x_atmega,$(USEMODULE)))
FEATURES_REQUIRED += cpu_core_atmega
endif
ifneq (,$(filter ws281x_esp32%,$(USEMODULE)))
FEATURES_REQUIRED += arch_esp32
ifneq (,$(filter ws281x_esp32_sw,$(USEMODULE)))
USEMODULE += ws281x_esp32
else
# use ESP32x RMT hardware implementation by default
FEATURES_REQUIRED += esp_rmt
USEMODULE += esp_idf_rmt
USEMODULE += ws281x_esp32
USEMODULE += ws281x_esp32_hw
endif
endif

View File

@ -1,5 +1,6 @@
/*
* Copyright 2020 Christian Friedrich Coors
* 2023 Gunar Schorcht
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
@ -15,6 +16,7 @@
* @brief Implementation of `ws281x_write_buffer()` for the ESP32x CPU
*
* @author Christian Friedrich Coors <me@ccoors.de>
* @author Gunar Schorcht <gunar@schorcht.net>
*
* @}
*/
@ -23,21 +25,71 @@
#include <stdint.h>
#include <string.h>
#include "board.h"
#include "log.h"
#include "periph_cpu.h"
#include "ws281x.h"
#include "ws281x_params.h"
#include "ws281x_constants.h"
#include "periph_cpu.h"
#include "esp_private/esp_clk.h"
#include "hal/cpu_hal.h"
#include "soc/rtc.h"
#ifdef MODULE_WS281X_ESP32_HW
#include "esp_intr_alloc.h"
#include "driver/rmt.h"
#include "hal/rmt_types.h"
#include "hal/rmt_ll.h"
#include "soc/rmt_struct.h"
#endif
#define ENABLE_DEBUG 0
#include "debug.h"
#ifdef MODULE_WS281X_ESP32_HW
static uint32_t _ws281x_one_on;
static uint32_t _ws281x_one_off;
static uint32_t _ws281x_zero_on;
static uint32_t _ws281x_zero_off;
static uint8_t _rmt_channel(ws281x_t *dev)
{
for (unsigned i = 0; i < RMT_CH_NUMOF; i++) {
if (rmt_channel_config[i].gpio == dev->params.pin) {
return rmt_channel_config[i].channel;
}
}
assert(0);
}
#endif
void ws281x_write_buffer(ws281x_t *dev, const void *buf, size_t size)
{
assert(dev);
#ifdef MODULE_WS281X_ESP32_HW
/* determine used RMT channel from configured GPIO */
uint8_t channel = _rmt_channel(dev);
/* determine the current clock frequency */
uint32_t freq;
if (rmt_get_counter_clock(channel, &freq) != ESP_OK) {
LOG_ERROR("[ws281x_esp32] Could not get RMT counter clock\n");
return;
}
/* compute phase times used in ws2812_rmt_adapter once rmt_write_sample is called */
uint32_t total_cycles = freq / (NS_PER_SEC / WS281X_T_DATA_NS);
_ws281x_one_on = freq / (NS_PER_SEC / WS281X_T_DATA_ONE_NS);
_ws281x_one_off = total_cycles - _ws281x_one_on;
_ws281x_zero_on = freq / (NS_PER_SEC / WS281X_T_DATA_ZERO_NS);
_ws281x_zero_off = total_cycles - _ws281x_zero_on;
rmt_write_sample(channel, (const uint8_t *)buf, size, false);
#else
const uint8_t *pos = buf;
const uint8_t *end = pos + size;
@ -87,8 +139,45 @@ void ws281x_write_buffer(ws281x_t *dev, const void *buf, size_t size)
/* final LOW phase */
current_wait = cpu_hal_get_cycle_count();
/* end of final LOW phase */
#endif
}
#ifdef MODULE_WS281X_ESP32_HW
static void IRAM_ATTR ws2812_rmt_adapter(const void *src,
rmt_item32_t *dest,
size_t src_size,
size_t wanted_num,
size_t *translated_size,
size_t *item_num) {
if ((src == NULL) || (dest == NULL)) {
*translated_size = 0;
*item_num = 0;
return;
}
const rmt_item32_t bit0 = {{{ _ws281x_zero_on, 1, _ws281x_zero_off, 0 }}};
const rmt_item32_t bit1 = {{{ _ws281x_one_on, 1, _ws281x_one_off, 0 }}};
size_t size = 0;
size_t num = 0;
uint8_t *psrc = (uint8_t *)src;
while ((size < src_size) && (num < wanted_num)) {
uint8_t data = *psrc;
for (uint8_t cnt = 8; cnt > 0; cnt--) {
dest->val = (data & 0b10000000) ? bit1.val : bit0.val;
dest++;
num++;
data <<= 1;
}
size++;
psrc++;
}
*translated_size = size;
*item_num = num;
}
#endif
int ws281x_init(ws281x_t *dev, const ws281x_params_t *params)
{
if (!dev || !params || !params->buf) {
@ -98,9 +187,26 @@ int ws281x_init(ws281x_t *dev, const ws281x_params_t *params)
memset(dev, 0, sizeof(ws281x_t));
dev->params = *params;
#ifdef MODULE_WS281X_ESP32_HW
static_assert(RMT_CH_NUMOF <= RMT_CH_NUMOF_MAX,
"[ws281x_esp32] RMT configuration problem");
/* determine used RMT channel from configured GPIO */
uint8_t channel = _rmt_channel(dev);
rmt_config_t cfg = RMT_DEFAULT_CONFIG_TX(params->pin, channel);
cfg.clk_div = 2;
if ((rmt_config(&cfg) != ESP_OK) ||
(rmt_driver_install(channel, 0, 0) != ESP_OK) ||
(rmt_translator_init(channel, ws2812_rmt_adapter) != ESP_OK)) {
LOG_ERROR("[ws281x_esp32] RMT initialization failed\n");
return -EIO;
}
#else
if (gpio_init(dev->params.pin, GPIO_OUT)) {
return -EIO;
}
#endif
return 0;
}