/* This file is part of Espruino, a JavaScript interpreter for Microcontrollers * adapted from source written by Chris Osborn * 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 * * 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 #include #include #include #include #include #include #include #if ESP_IDF_VERSION_MAJOR>=4 #else #include #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,0,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,0,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