2017-04-06 22:59:19 +02:00
|
|
|
/* ****************************************************************************
|
|
|
|
*
|
|
|
|
* ESP32 platform interface for WS2812 LEDs.
|
|
|
|
*
|
|
|
|
* Copyright (c) 2017, Arnim Laeuger
|
|
|
|
*
|
|
|
|
* Permission is hereby granted, 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 "platform.h"
|
2019-05-31 23:34:15 +02:00
|
|
|
#include "platform_rmt.h"
|
2017-04-06 22:59:19 +02:00
|
|
|
|
|
|
|
#include "driver/rmt.h"
|
|
|
|
#include "driver/gpio.h"
|
|
|
|
#include "esp_log.h"
|
2021-08-11 09:11:11 +02:00
|
|
|
#include "soc/periph_defs.h"
|
2023-01-31 07:07:54 +01:00
|
|
|
#include "rom/gpio.h" // for gpio_matrix_out()
|
|
|
|
#include "soc/gpio_periph.h"
|
2024-02-07 23:56:17 +01:00
|
|
|
#include "soc/rmt_reg.h"
|
2017-04-06 22:59:19 +02:00
|
|
|
|
|
|
|
#undef WS2812_DEBUG
|
|
|
|
|
|
|
|
// divider to generate 100ns base period from 80MHz APB clock
|
|
|
|
#define WS2812_CLKDIV (100 * 80 /1000)
|
|
|
|
|
|
|
|
// descriptor for a ws2812 chain
|
|
|
|
typedef struct {
|
|
|
|
bool valid;
|
2023-04-30 19:46:07 +02:00
|
|
|
bool needs_reset;
|
2017-04-06 22:59:19 +02:00
|
|
|
uint8_t gpio;
|
2024-02-07 23:56:17 +01:00
|
|
|
rmt_item32_t reset;
|
|
|
|
rmt_item32_t bits[2];
|
2017-04-06 22:59:19 +02:00
|
|
|
const uint8_t *data;
|
|
|
|
size_t len;
|
2024-02-07 23:56:17 +01:00
|
|
|
uint8_t bitpos;
|
2017-04-06 22:59:19 +02:00
|
|
|
} ws2812_chain_t;
|
|
|
|
|
|
|
|
// chain descriptor array
|
|
|
|
static ws2812_chain_t ws2812_chains[RMT_CHANNEL_MAX];
|
|
|
|
|
2023-04-30 19:46:07 +02:00
|
|
|
static void ws2812_sample_to_rmt(const void *src, rmt_item32_t *dest, size_t src_size, size_t wanted_num, size_t *translated_size, size_t *item_num)
|
2017-04-06 22:59:19 +02:00
|
|
|
{
|
2024-02-07 23:56:17 +01:00
|
|
|
size_t cnt_in;
|
|
|
|
size_t cnt_out;
|
|
|
|
const uint8_t *pucData;
|
|
|
|
uint8_t ucData;
|
|
|
|
uint8_t ucBitPos;
|
|
|
|
esp_err_t tStatus;
|
|
|
|
void *pvContext;
|
|
|
|
ws2812_chain_t *ptContext;
|
|
|
|
uint8_t ucBit;
|
|
|
|
|
|
|
|
cnt_in = 0;
|
|
|
|
cnt_out = 0;
|
|
|
|
if( dest!=NULL && wanted_num>0 )
|
|
|
|
{
|
|
|
|
tStatus = rmt_translator_get_context(item_num, &pvContext);
|
|
|
|
if( tStatus==ESP_OK )
|
|
|
|
{
|
|
|
|
ptContext = (ws2812_chain_t *)pvContext;
|
|
|
|
|
|
|
|
if( ptContext->needs_reset==true )
|
|
|
|
{
|
|
|
|
dest[cnt_out++] = ptContext->reset;
|
|
|
|
ptContext->needs_reset = false;
|
|
|
|
}
|
|
|
|
if( src!=NULL && src_size>0 )
|
|
|
|
{
|
|
|
|
ucBitPos = ptContext->bitpos;
|
|
|
|
|
|
|
|
/* Each bit of the input data is converted into one RMT item. */
|
|
|
|
|
|
|
|
pucData = (const uint8_t*)src;
|
|
|
|
/* Get the current byte. */
|
|
|
|
ucData = pucData[cnt_in] << ucBitPos;
|
|
|
|
|
|
|
|
while( cnt_in<src_size && cnt_out<wanted_num )
|
|
|
|
{
|
|
|
|
/* Get the current bit. */
|
|
|
|
ucBit = (ucData & 0x80U) >> 7U;
|
|
|
|
/* Translate the bit to a WS2812 input code. */
|
|
|
|
dest[cnt_out++] = ptContext->bits[ucBit];
|
|
|
|
/* Move to the next bit. */
|
|
|
|
++ucBitPos;
|
|
|
|
if( ucBitPos<8U )
|
|
|
|
{
|
|
|
|
ucData <<= 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ucBitPos = 0U;
|
|
|
|
++cnt_in;
|
|
|
|
ucData = pucData[cnt_in];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ptContext->bitpos = ucBitPos;
|
|
|
|
}
|
2017-04-06 22:59:19 +02:00
|
|
|
}
|
|
|
|
}
|
2024-02-07 23:56:17 +01:00
|
|
|
*translated_size = cnt_in;
|
|
|
|
*item_num = cnt_out;
|
2017-04-06 22:59:19 +02:00
|
|
|
}
|
|
|
|
|
2024-02-07 23:56:17 +01:00
|
|
|
int platform_ws2812_setup( uint8_t gpio_num, uint32_t reset, uint32_t t0h, uint32_t t0l, uint32_t t1h, uint32_t t1l, const uint8_t *data, size_t len )
|
2017-04-06 22:59:19 +02:00
|
|
|
{
|
|
|
|
int channel;
|
|
|
|
|
2024-02-07 23:56:17 +01:00
|
|
|
if ((channel = platform_rmt_allocate( 1, RMT_MODE_TX )) >= 0) {
|
2017-04-06 22:59:19 +02:00
|
|
|
ws2812_chain_t *chain = &(ws2812_chains[channel]);
|
2024-02-07 23:56:17 +01:00
|
|
|
rmt_item32_t tRmtItem;
|
|
|
|
uint32_t half;
|
2017-04-06 22:59:19 +02:00
|
|
|
|
|
|
|
chain->valid = true;
|
|
|
|
chain->gpio = gpio_num;
|
|
|
|
chain->len = len;
|
|
|
|
chain->data = data;
|
2024-02-07 23:56:17 +01:00
|
|
|
chain->bitpos = 0;
|
|
|
|
|
|
|
|
// Send a reset if "reset" is not 0.
|
|
|
|
chain->needs_reset = (reset != 0);
|
|
|
|
|
|
|
|
// Construct the RMT item for a reset.
|
|
|
|
tRmtItem.level0 = 0;
|
|
|
|
tRmtItem.level1 = 0;
|
|
|
|
// The reset duration must fit into one RMT item. This leaves 2*15 bit,
|
|
|
|
// which results in a maximum of 0xfffe .
|
|
|
|
if (reset>0xfffe)
|
|
|
|
{
|
|
|
|
reset = 0xfffe;
|
|
|
|
}
|
|
|
|
if (reset>0x7fff)
|
|
|
|
{
|
|
|
|
tRmtItem.duration0 = 0x7fff;
|
|
|
|
tRmtItem.duration1 = reset - 0x7fff;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
half = reset >> 1U;
|
|
|
|
tRmtItem.duration0 = half;
|
|
|
|
tRmtItem.duration1 = reset - half;
|
|
|
|
}
|
|
|
|
chain->reset = tRmtItem;
|
|
|
|
|
|
|
|
// Limit the bit times to the available 15 bits.
|
|
|
|
// The values must not be 0.
|
|
|
|
if( t0h==0 )
|
|
|
|
{
|
|
|
|
t0h = 1;
|
|
|
|
}
|
|
|
|
else if( t0h>0x7fffU )
|
|
|
|
{
|
|
|
|
t0h = 0x7fffU;
|
|
|
|
}
|
|
|
|
if( t0l==0 )
|
|
|
|
{
|
|
|
|
t0l = 1;
|
|
|
|
}
|
|
|
|
else if( t0l>0x7fffU )
|
|
|
|
{
|
|
|
|
t0l = 0x7fffU;
|
|
|
|
}
|
|
|
|
if( t1h==0 )
|
|
|
|
{
|
|
|
|
t1h = 1;
|
|
|
|
}
|
|
|
|
else if( t1h>0x7fffU )
|
|
|
|
{
|
|
|
|
t1h = 0x7fffU;
|
|
|
|
}
|
|
|
|
if( t1l==0 )
|
|
|
|
{
|
|
|
|
t1l = 1;
|
|
|
|
}
|
|
|
|
else if( t1l>0x7fffU )
|
|
|
|
{
|
|
|
|
t1l = 0x7fffU;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Construct the RMT item for a 0 bit.
|
|
|
|
tRmtItem.level0 = 1;
|
|
|
|
tRmtItem.duration0 = t0h;
|
|
|
|
tRmtItem.level1 = 0;
|
|
|
|
tRmtItem.duration1 = t0l;
|
|
|
|
chain->bits[0] = tRmtItem;
|
|
|
|
|
|
|
|
// Construct the RMT item for a 1 bit.
|
|
|
|
tRmtItem.level0 = 1;
|
|
|
|
tRmtItem.duration0 = t1h;
|
|
|
|
tRmtItem.level1 = 0;
|
|
|
|
tRmtItem.duration1 = t1l;
|
|
|
|
chain->bits[1] = tRmtItem;
|
2017-04-06 22:59:19 +02:00
|
|
|
|
|
|
|
#ifdef WS2812_DEBUG
|
|
|
|
ESP_LOGI("ws2812", "Setup done for gpio %d on RMT channel %d", gpio_num, channel);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return PLATFORM_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
return PLATFORM_ERR;
|
|
|
|
}
|
|
|
|
|
|
|
|
int platform_ws2812_release( void )
|
|
|
|
{
|
|
|
|
for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX; channel++) {
|
|
|
|
ws2812_chain_t *chain = &(ws2812_chains[channel]);
|
|
|
|
|
|
|
|
if (chain->valid) {
|
|
|
|
rmt_driver_uninstall( channel );
|
|
|
|
|
|
|
|
platform_rmt_release( channel );
|
2017-06-20 23:09:14 +02:00
|
|
|
chain->valid = false;
|
2017-04-06 22:59:19 +02:00
|
|
|
|
|
|
|
// attach GPIO to pin, driving 0
|
|
|
|
gpio_set_level( chain->gpio, 0 );
|
|
|
|
gpio_set_direction( chain->gpio, GPIO_MODE_DEF_OUTPUT );
|
|
|
|
gpio_matrix_out( chain->gpio, SIG_GPIO_OUT_IDX, 0, 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return PLATFORM_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
int platform_ws2812_send( void )
|
|
|
|
{
|
|
|
|
rmt_config_t rmt_tx;
|
|
|
|
int res = PLATFORM_OK;
|
|
|
|
|
|
|
|
// common settings
|
|
|
|
rmt_tx.mem_block_num = 1;
|
|
|
|
rmt_tx.clk_div = WS2812_CLKDIV;
|
2022-05-28 21:06:45 +02:00
|
|
|
rmt_tx.flags = 0;
|
2017-04-06 22:59:19 +02:00
|
|
|
rmt_tx.tx_config.loop_en = false;
|
|
|
|
rmt_tx.tx_config.carrier_en = false;
|
|
|
|
rmt_tx.tx_config.idle_level = 0;
|
|
|
|
rmt_tx.tx_config.idle_output_en = true;
|
|
|
|
rmt_tx.rmt_mode = RMT_MODE_TX;
|
|
|
|
|
|
|
|
// configure selected RMT channels
|
|
|
|
for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX && res == PLATFORM_OK; channel++) {
|
|
|
|
if (ws2812_chains[channel].valid) {
|
|
|
|
rmt_tx.channel = channel;
|
|
|
|
rmt_tx.gpio_num = ws2812_chains[channel].gpio;
|
|
|
|
if (rmt_config( &rmt_tx ) != ESP_OK) {
|
|
|
|
res = PLATFORM_ERR;
|
|
|
|
break;
|
|
|
|
}
|
2019-05-31 23:34:15 +02:00
|
|
|
if (rmt_driver_install( channel, 0, PLATFORM_RMT_INTR_FLAGS ) != ESP_OK) {
|
2017-04-06 22:59:19 +02:00
|
|
|
res = PLATFORM_ERR;
|
|
|
|
break;
|
|
|
|
}
|
2023-04-30 19:46:07 +02:00
|
|
|
if (rmt_translator_init(channel, ws2812_sample_to_rmt) != ESP_OK) {
|
|
|
|
res = PLATFORM_ERR;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (rmt_translator_set_context(channel, &ws2812_chains[channel]) != ESP_OK) {
|
|
|
|
res = PLATFORM_ERR;
|
|
|
|
break;
|
|
|
|
}
|
2017-04-06 22:59:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-07 23:56:17 +01:00
|
|
|
// Try to add all channels to a group. This moves the start of all RMT sequences closer
|
|
|
|
// together.
|
|
|
|
#if SOC_RMT_SUPPORT_TX_SYNCHRO
|
|
|
|
for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX && res == PLATFORM_OK; channel++) {
|
|
|
|
if (ws2812_chains[channel].valid) {
|
|
|
|
if (rmt_add_channel_to_group( channel ) != ESP_OK) {
|
|
|
|
res = PLATFORM_ERR;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2017-04-06 22:59:19 +02:00
|
|
|
// start selected channels one by one
|
|
|
|
for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX && res == PLATFORM_OK; channel++) {
|
|
|
|
if (ws2812_chains[channel].valid) {
|
2023-04-30 19:46:07 +02:00
|
|
|
// ws2812_sample_to_rmt takes care of translating the data to rmt_item32_t
|
|
|
|
// format, as well as prepending the reset sequence.
|
|
|
|
if (rmt_write_sample(channel, ws2812_chains[channel].data, ws2812_chains[channel].len, false) != ESP_OK) {
|
2017-04-06 22:59:19 +02:00
|
|
|
res = PLATFORM_ERR;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// wait for all channels to finish
|
|
|
|
for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX; channel++) {
|
|
|
|
if (ws2812_chains[channel].valid) {
|
2017-09-12 22:21:33 +02:00
|
|
|
rmt_wait_tx_done( channel, portMAX_DELAY );
|
2017-04-06 22:59:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-07 23:56:17 +01:00
|
|
|
#if SOC_RMT_SUPPORT_TX_SYNCHRO
|
|
|
|
for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX; channel++) {
|
|
|
|
if (ws2812_chains[channel].valid) {
|
|
|
|
if (rmt_remove_channel_from_group( channel ) != ESP_OK) {
|
|
|
|
res = PLATFORM_ERR;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2017-04-06 22:59:19 +02:00
|
|
|
return res;
|
|
|
|
}
|
2017-06-20 23:09:14 +02:00
|
|
|
|
|
|
|
void platform_ws2812_init( void )
|
|
|
|
{
|
|
|
|
for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX; channel++) {
|
|
|
|
ws2812_chains[channel].valid = false;
|
|
|
|
}
|
|
|
|
}
|