mirror of
https://github.com/espruino/Espruino.git
synced 2025-12-08 19:06:15 +00:00
273 lines
8.7 KiB
C
273 lines
8.7 KiB
C
/*
|
|
This file is part of Espruino, a JavaScript interpreter for Microcontrollers
|
|
* adapted from source written by Chris Osborn <fozztexx@fozztexx.com>
|
|
* http://insentricity.com
|
|
* and https://github.com/cashoefman/ESP32-C3-Rainbow-LED-Strip/blob/master/components/led_strip for C3 port
|
|
*
|
|
* Copyright (C) 2013 Gordon Williams <gw@pur3.co.uk>
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
* ESP32 specific exposed components for neopixel.
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <freertos/FreeRTOS.h>
|
|
#include <freertos/semphr.h>
|
|
#include <soc/rmt_struct.h>
|
|
#include <driver/gpio.h>
|
|
#include <soc/gpio_sig_map.h>
|
|
#include <esp_intr.h>
|
|
#include <driver/rmt.h>
|
|
|
|
#if ESP_IDF_VERSION_MAJOR>=4
|
|
#else
|
|
#include <soc/dport_reg.h>
|
|
#endif
|
|
|
|
#include "esp32_neopixel.h"
|
|
|
|
#define RMTCHANNEL 0
|
|
#define MAX_PULSES 64
|
|
#define BUFFERS 8
|
|
#define DIVIDER 4 /* Above 4, timings start to deviate*/
|
|
#define DURATION 12.5 /* minimum time of a single RMT duration */
|
|
|
|
#define WS2812_T0H_NS (350)
|
|
#define WS2812_T0L_NS (900)
|
|
#define WS2812_T1H_NS (900)
|
|
#define WS2812_T1L_NS (350)
|
|
#define WS2812_RESET_US (280)
|
|
|
|
#define PULSE_T0H ( WS2812_T0H_NS / (DURATION * DIVIDER))
|
|
#define PULSE_T1H ( WS2812_T1H_NS / (DURATION * DIVIDER))
|
|
#define PULSE_T0L ( WS2812_T0L_NS / (DURATION * DIVIDER))
|
|
#define PULSE_T1L ( WS2812_T1L_NS / (DURATION * DIVIDER))
|
|
#define PULSE_TRS (50000 / (DURATION * DIVIDER))
|
|
|
|
typedef union {
|
|
struct {
|
|
uint32_t duration0:15;
|
|
uint32_t level0:1;
|
|
uint32_t duration1:15;
|
|
uint32_t level1:1;
|
|
};
|
|
uint32_t val;
|
|
} rmtPulsePair;
|
|
|
|
static uint8_t *neopixel_buffer = NULL;
|
|
static unsigned int neopixel_pos, neopixel_len, neopixel_bufpart;
|
|
static xSemaphoreHandle neopixel_sem = NULL;
|
|
static intr_handle_t rmt_intr_handle = NULL;
|
|
static rmtPulsePair neopixel_bits[2]= {
|
|
{{PULSE_T0H,1,PULSE_T0L,0}},
|
|
{{PULSE_T1H,1,PULSE_T1L,0}}
|
|
};
|
|
|
|
int neopixelConfiguredGPIO = -1;
|
|
|
|
#if ESP_IDF_VERSION_MAJOR>=4
|
|
/**
|
|
* @brief Convert RGB data to RMT format.
|
|
*
|
|
* @note For WS2812, R,G,B each contains 256 different choices (i.e. uint8_t)
|
|
*
|
|
* @param[in] src: source data, to converted to RMT format
|
|
* @param[in] dest: place where to store the convert result
|
|
* @param[in] src_size: size of source data
|
|
* @param[in] wanted_num: number of RMT items that want to get
|
|
* @param[out] translated_size: number of source data that got converted
|
|
* @param[out] item_num: number of RMT items which are converted from source data
|
|
*/
|
|
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;
|
|
}
|
|
size_t size = 0;
|
|
size_t num = 0;
|
|
uint8_t *psrc = (uint8_t *)src;
|
|
rmt_item32_t *pdest = dest;
|
|
while (size < src_size && num < wanted_num) {
|
|
for (int i = 7; i >= 0; i--) {
|
|
// MSB first
|
|
pdest->val = neopixel_bits[(*psrc>>i) & 1].val;
|
|
pdest++;
|
|
}
|
|
num+=8;
|
|
size++;
|
|
psrc++;
|
|
}
|
|
*translated_size = size;
|
|
*item_num = num;
|
|
}
|
|
#else
|
|
void neopixel_initRMTChannel(int rmtChannel){
|
|
RMT.apb_conf.fifo_mask = 1; //enable memory access, instead of FIFO mode.
|
|
RMT.apb_conf.mem_tx_wrap_en = 1; //wrap around when hitting end of buffer
|
|
RMT.conf_ch[rmtChannel].conf0.div_cnt = DIVIDER;
|
|
RMT.conf_ch[rmtChannel].conf0.mem_size = BUFFERS;//1;
|
|
RMT.conf_ch[rmtChannel].conf0.carrier_en = 0;
|
|
RMT.conf_ch[rmtChannel].conf0.carrier_out_lv = 1;
|
|
RMT.conf_ch[rmtChannel].conf0.mem_pd = 0;
|
|
|
|
RMT.conf_ch[rmtChannel].conf1.rx_en = 0;
|
|
RMT.conf_ch[rmtChannel].conf1.mem_owner = 0;
|
|
RMT.conf_ch[rmtChannel].conf1.tx_conti_mode = 0; //loop back mode.
|
|
RMT.conf_ch[rmtChannel].conf1.ref_always_on = 1; // use apb clock: 80M
|
|
RMT.conf_ch[rmtChannel].conf1.idle_out_en = 1;
|
|
RMT.conf_ch[rmtChannel].conf1.idle_out_lv = 0;
|
|
}
|
|
#endif
|
|
|
|
void neopixel_copy(){
|
|
unsigned int i, j, offset, offset2, len, bit;
|
|
offset = neopixel_bufpart * MAX_PULSES;
|
|
neopixel_bufpart = (neopixel_bufpart+1)%BUFFERS;
|
|
offset2 = neopixel_bufpart * MAX_PULSES;
|
|
len = neopixel_len - neopixel_pos;
|
|
if (len > (MAX_PULSES / 8)) len = (MAX_PULSES / 8);
|
|
if (!len) {
|
|
// for (i = 0; i < MAX_PULSES; i++)
|
|
// RMTMEM.chan[RMTCHANNEL].data32[i + offset].val = 0;
|
|
return;
|
|
}
|
|
for (i = 0; i < len; i++) {
|
|
bit = neopixel_buffer[i + neopixel_pos];
|
|
for (j = 0; j < 8; j++, bit <<= 1) {
|
|
RMTMEM.chan[RMTCHANNEL].data32[j + i * 8 + offset].val = neopixel_bits[(bit >> 7) & 0x01].val;
|
|
}
|
|
if (i + neopixel_pos == neopixel_len - 1)
|
|
RMTMEM.chan[RMTCHANNEL].data32[7 + i * 8 + offset].duration1 = PULSE_TRS;
|
|
}
|
|
for (i *= 8; i < MAX_PULSES; i++) RMTMEM.chan[RMTCHANNEL].data32[i + offset].val = 0;
|
|
RMTMEM.chan[RMTCHANNEL].data32[offset2].val = 0;
|
|
neopixel_pos += len;
|
|
return;
|
|
}
|
|
|
|
void neopixel_handleInterrupt(void *arg)
|
|
{
|
|
// FYI RMT is -> extern rmt_dev_t RMT;
|
|
|
|
portBASE_TYPE taskAwoken = 0;
|
|
|
|
#if CONFIG_IDF_TARGET_ESP32S3
|
|
|
|
if (RMT.int_st.ch0_tx_thr_event_int_st)
|
|
{
|
|
neopixel_copy();
|
|
RMT.int_clr.ch0_tx_thr_event_int_clr = 1;
|
|
}
|
|
else if (RMT.int_st.ch0_tx_end_int_st && neopixel_sem)
|
|
{
|
|
xSemaphoreGiveFromISR(neopixel_sem, &taskAwoken);
|
|
RMT.int_clr.ch0_tx_end_int_clr = 1;
|
|
}
|
|
|
|
#else
|
|
|
|
if (RMT.int_st.ch0_tx_thr_event)
|
|
{
|
|
neopixel_copy();
|
|
RMT.int_clr.ch0_tx_thr_event = 1;
|
|
}
|
|
else if (RMT.int_st.ch0_tx_end && neopixel_sem)
|
|
{
|
|
xSemaphoreGiveFromISR(neopixel_sem, &taskAwoken);
|
|
RMT.int_clr.ch0_tx_end = 1;
|
|
}
|
|
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
void neopixel_init(int gpioNum){
|
|
#if ESP_IDF_VERSION_MAJOR>=4
|
|
if (neopixelConfiguredGPIO != gpioNum) {
|
|
if (neopixelConfiguredGPIO) {
|
|
rmt_driver_uninstall(RMTCHANNEL);
|
|
if (neopixelConfiguredGPIO != -1) {
|
|
gpio_matrix_out(neopixelConfiguredGPIO,SIG_GPIO_OUT_IDX,0,0);
|
|
}
|
|
}
|
|
neopixelConfiguredGPIO = gpioNum;
|
|
rmt_config_t config = RMT_DEFAULT_CONFIG_TX(gpioNum, RMTCHANNEL);
|
|
// set counter clock to 40MHz
|
|
config.clk_div = 2;
|
|
ESP_ERROR_CHECK(rmt_config(&config));
|
|
ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, 0));
|
|
uint32_t counter_clk_hz = 0;
|
|
if (rmt_get_counter_clock(config.channel, &counter_clk_hz) != ESP_OK)
|
|
printf("get rmt counter clock failed\n");
|
|
float ratio = (float)counter_clk_hz / 1e9;
|
|
neopixel_bits[0].duration0 = (uint32_t)(ratio * WS2812_T0H_NS);
|
|
neopixel_bits[0].duration1 = (uint32_t)(ratio * WS2812_T0L_NS);
|
|
neopixel_bits[1].duration0 = (uint32_t)(ratio * WS2812_T1H_NS);
|
|
neopixel_bits[1].duration1 = (uint32_t)(ratio * WS2812_T1L_NS);
|
|
// set ws2812 to rmt adapter
|
|
rmt_translator_init(config.channel, ws2812_rmt_adapter);
|
|
}
|
|
#else
|
|
DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_RMT_CLK_EN);
|
|
DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_RMT_RST);
|
|
|
|
// PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpioNum], 2);
|
|
// gpio_matrix_out(gpioNum, RMT_SIG_OUT0_IDX + RMTCHANNEL, 0, 0);
|
|
// gpio_set_direction(gpioNum, GPIO_MODE_OUTPUT);
|
|
if (neopixelConfiguredGPIO != gpioNum) {
|
|
// detach last pin
|
|
if (neopixelConfiguredGPIO != -1) {
|
|
gpio_matrix_out(neopixelConfiguredGPIO,SIG_GPIO_OUT_IDX,0,0);
|
|
}
|
|
neopixelConfiguredGPIO = gpioNum;
|
|
}
|
|
//gpio_matrix_out(gpioNum, RMT_SIG_OUT0_IDX + RMTCHANNEL, 0, 0);
|
|
rmt_set_pin((rmt_channel_t)RMTCHANNEL, RMT_MODE_TX, (gpio_num_t)gpioNum);
|
|
|
|
neopixel_initRMTChannel(RMTCHANNEL);
|
|
|
|
RMT.tx_lim_ch[RMTCHANNEL].limit = MAX_PULSES;
|
|
RMT.int_ena.ch0_tx_thr_event = 1;
|
|
RMT.int_ena.ch0_tx_end = 1;
|
|
esp_intr_alloc(ETS_RMT_INTR_SOURCE, ESP_INTR_FLAG_INTRDISABLED, neopixel_handleInterrupt, NULL, &rmt_intr_handle);
|
|
#endif
|
|
}
|
|
|
|
bool esp32_neopixelWrite(Pin pin,unsigned char *rgbData, size_t rgbSize){
|
|
if(rgbSize) {
|
|
neopixel_init(pin);
|
|
#if ESP_IDF_VERSION_MAJOR>=4
|
|
if (rmt_write_sample(RMTCHANNEL, rgbData, rgbSize, true) != ESP_OK)
|
|
printf("transmit RMT samples failed\n");
|
|
int timeout_ms = 100;
|
|
return rmt_wait_tx_done(RMTCHANNEL, pdMS_TO_TICKS(timeout_ms));
|
|
#else
|
|
neopixel_buffer = rgbData;
|
|
neopixel_len = rgbSize;
|
|
neopixel_pos = 0;
|
|
neopixel_bufpart = 0;
|
|
neopixel_sem = xSemaphoreCreateBinary();
|
|
for(int i=0;i<BUFFERS-1;i++)
|
|
if (neopixel_pos < neopixel_len)
|
|
neopixel_copy();
|
|
RMT.conf_ch[RMTCHANNEL].conf1.mem_rd_rst = 1;
|
|
esp_intr_enable(rmt_intr_handle);
|
|
RMT.conf_ch[RMTCHANNEL].conf1.tx_start = 1;
|
|
xSemaphoreTake(neopixel_sem, portMAX_DELAY);
|
|
vSemaphoreDelete(neopixel_sem);
|
|
esp_intr_free(rmt_intr_handle);
|
|
neopixel_sem = NULL;
|
|
rmt_intr_handle=0;
|
|
#endif
|
|
}
|
|
return true;
|
|
}
|