#ifndef WS2812B_H #define WS2812B_H #include "../../data/Color.h" #include "../../Platforms.h" #include "../../Debug.h" #if ESP8266 #include "../../io/fastGPIO.h" template class WS2812B { static constexpr const char* NAME = "WS2812B"; // #define LED_SET_PIN_TO_OUTPUT GPIO5_OUTPUT_SET // #define LED_SET_PIN_H GPIO5_H // #define LED_SET_PIN_L GPIO5_L //#define NS_PER_TICK ( (1000ul*1000ul*1000ul) / (80ul*1000ul*1000ul) ) /** color-value for each attached LED */ Color colors[numLEDs]; /** enable/disable each led */ bool enabled[numLEDs] = {true}; public: /** ctor */ WS2812B() { init(); } void init() { LED_SET_PIN_TO_OUTPUT; debugMod1(NAME, "init with %d leds", numLEDs); } /** set the color for the given LED */ void setColor(const uint8_t idx, const Color rgb) { colors[idx] = rgb; } /** set the color for all LEDs */ void setColor(const Color rgb) { for (int idx = 0; idx < numLEDs; ++idx) { colors[idx] = rgb; } } /** enable/disable the given LED */ void setEnabled(const uint8_t idx, const bool en) { enabled[idx] = en; } /** enable/disable all LEDs */ void setEnabled(const bool en) { for (int idx = 0; idx < numLEDs; ++idx) { enabled[idx] = en; } } /** is the given LED enabled? */ bool isEnabled(const uint8_t idx) const { return enabled[idx]; } Color& getColor(const uint8_t idx) { return colors[idx]; } /** flush configured changes */ void flush() { LED_SET_PIN_TO_OUTPUT; ets_intr_lock(); // process each LED for (int i = 0; i < numLEDs; ++i) { // send each LEDs 24-bit GRB data if (enabled[i]) { const Color rgb = colors[i]; sendByte(rgb.g); sendByte(rgb.r); sendByte(rgb.b); } else { sendByte(0); sendByte(0); sendByte(0); } } ets_intr_unlock(); reset(); } /** flush configured changes, including global brightness */ void flushBrightness(const uint8_t brightness) { LED_SET_PIN_TO_OUTPUT; ets_intr_lock(); // process each LED for (int i = 0; i < numLEDs; ++i) { // send each LEDs 24-bit GRB data if (enabled[i]) { const Color rgb = colors[i].brightness(brightness); sendByte(rgb.g); sendByte(rgb.r); sendByte(rgb.b); } else { sendByte(0); sendByte(0); sendByte(0); } } ets_intr_unlock(); reset(); } private: inline void sendByte(uint8_t b) { // for (uint8_t i = 0; i < 8; ++i) { // if (b & 0b10000000) { // send1(); // } else { // send0(); // } // b <<= 1; // } if (b & 0b10000000) {send1();} else {send0();} if (b & 0b01000000) {send1();} else {send0();} if (b & 0b00100000) {send1();} else {send0();} if (b & 0b00010000) {send1();} else {send0();} if (b & 0b00001000) {send1();} else {send0();} if (b & 0b00000100) {send1();} else {send0();} if (b & 0b00000010) {send1();} else {send0();} if (b & 0b00000001) {send1();} else {send0();} } __attribute__((always_inline)) inline void send1() { // 800ns high, 450ns low LED_SET_PIN_H; delay800(); LED_SET_PIN_L; delay100(); } __attribute__((always_inline)) inline void send0() { // 400ns high, 850ns low LED_SET_PIN_H; delay100(); LED_SET_PIN_L; delay800(); } __attribute__((always_inline)) inline void reset() { LED_SET_PIN_L; os_delay_us(50); //delayMicroseconds(50); } //#pragma GCC optimize 0 __attribute__((always_inline)) inline void delay50() { asm volatile("nop"); asm volatile("nop"); asm volatile("nop"); asm volatile("nop"); } // 100 ns delay. @80 MHz one nop = 12,5ns __attribute__((always_inline)) inline void delay100() { delay50(); delay50(); } /* __attribute__((always_inline)) inline void delay200() { delay100(); delay100(); } __attribute__((always_inline)) inline void delay400() { delay100(); delay100(); delay100(); delay100(); } __attribute__((always_inline)) inline void delay500() { delay100(); delay100(); delay100(); delay100(); delay100(); } __attribute__((always_inline)) inline void delay600() { delay100(); delay100(); delay100(); delay100(); delay100(); delay100(); } */ __attribute__((always_inline)) inline void delay800() { delay100(); delay100(); delay100(); delay100(); delay100(); delay100(); delay100(); delay100(); } // #pragma GCC reset_options // inline void delayNS(const uint16_t ns) { // const uint16_t ticks = ns / NS_PER_TICK / 2; // for (uint16_t i = 0; i < ticks; ++i) {asm("nop");} // } }; #elif false// ESP32aaa //#include #include #include #include #include template class WS2812B { static constexpr const char* TAG = "WS2812"; static constexpr rmt_channel_t chan = RMT_CHANNEL_0; //#define portDISABLE_INTERRUPTS() do { XTOS_SET_INTLEVEL(XCHAL_EXCM_LEVEL); portbenchmarkINTERRUPT_DISABLE(); } while (0) //#define portENABLE_INTERRUPTS() do { portbenchmarkINTERRUPT_RESTORE(0); XTOS_SET_INTLEVEL(0); } while (0) //#define ENABLE_INTERRUPTS() portENABLE_INTERRUPTS() //#define DISABLE_INTERRUPTS() portDISABLE_INTERRUPTS() static constexpr int numBits = (3*8) * 2; // (r,g,b) each 8 bit NOTE! must be < 64 to fit into the buffer! //static constexpr int numItems = 256;//(numLEDs*3)*8; //static constexpr int numItems = (numLEDs*3)*8; rmt_item32_t items[numBits+1]; // + one entry has two bits + 0-terminator (just like within strings) /** enable/disable each led */ bool enabled[numLEDs] = {true}; /** color-value for each attached LED */ Color colors[numLEDs]; public: /** ctor */ WS2812B() { init(); } void init() { ESP_LOGI(TAG, "init()"); rmt_config_t cfg; cfg.rmt_mode = RMT_MODE_TX; cfg.channel = chan; cfg.gpio_num = outPin; cfg.mem_block_num = 1;//chan+1;//8-chan;//chan+1;//8 - chan; //chan+1;// cfg.clk_div = 8; cfg.tx_config.loop_en = false; cfg.tx_config.carrier_en = false; cfg.tx_config.idle_output_en = true; cfg.tx_config.idle_level = (rmt_idle_level_t)0; cfg.tx_config.carrier_freq_hz = 10000; cfg.tx_config.carrier_level = (rmt_carrier_level_t)1; cfg.tx_config.carrier_duty_percent = 50; ESP_ERROR_CHECK(rmt_config(&cfg)); ESP_ERROR_CHECK(rmt_driver_install(chan, 0, 0)); // setup constant values once for (int idx = 0; idx < numBits; ++idx) { items[idx].duration0 = 0; items[idx].level0 = 1; items[idx].duration1 = 0; items[idx].level1 = 0; } items[numBits].val = 0; // 0 terminator ESP_LOGI(TAG, "init OK!"); } /** set the color for the given LED */ void setColor(const uint8_t idx, const Color rgb) { colors[idx] = rgb; } /** set the color for all LEDs */ void setColor(const Color rgb) { for (int idx = 0; idx < numLEDs; ++idx) { colors[idx] = rgb; } } /** enable/disable the given LED */ void setEnabled(const uint8_t idx, const bool en) { enabled[idx] = en; } /** enable/disable all LEDs */ void setEnabled(const bool en) { for (int idx = 0; idx < numLEDs; ++idx) { enabled[idx] = en; } } /** is the given LED enabled? */ bool isEnabled(const uint8_t idx) const { return enabled[idx]; } Color& getColor(const uint8_t idx) { return colors[idx]; } int nextLED; /** flush configured changes */ IRAM_ATTR void flush() { nextLED = 0; ESP_LOGI(TAG, "sending %d LEDs", numLEDs); // important! otherwise often interrupted within portDISABLE_INTERRUPTS(); waitForTX(); const size_t toWrite = 8+8+8+1;// 8-bit R, 8-bit G, 8-bit B, null-terminator // add null terminator items[toWrite-1].val = 0; // process each LED for (int i = 0; i < numLEDs; i+=1) { int idx = 0; if (enabled[i]) { emplaceByte(colors[i].g, idx); emplaceByte(colors[i].r, idx); emplaceByte(colors[i].b, idx); } else { emplaceByte(0, idx); emplaceByte(0, idx); emplaceByte(0, idx); } // wait for sending to complete using the RMT Unit's status waitForTX(); ESP_ERROR_CHECK(rmt_fill_tx_items(this->chan, items, toWrite, 0)); ESP_ERROR_CHECK(rmt_tx_start(this->chan, true)); // reset for next round //idx = 0; } //waitForTX(); portENABLE_INTERRUPTS(); } private: IRAM_ATTR void waitForTX() { uint32_t status; // status while sending: 16797696 ?? for (int i = 0; i < 2000; ++i) { rmt_get_status(this->chan, &status); if (status == 0) {break;} //if (status != 16797696) {break;} //if ( (status & (1<<24)) == 0 ) {break;} // somewhat OK } // for (int i = 0; i < 1000; ++i) { // asm volatile("nop"); // } } /** send one whole byte */ inline void emplaceByte(const uint8_t b, int& idx) { if (b & 0b10000000) {emplace1(idx);} else {emplace0(idx);} if (b & 0b01000000) {emplace1(idx);} else {emplace0(idx);} if (b & 0b00100000) {emplace1(idx);} else {emplace0(idx);} if (b & 0b00010000) {emplace1(idx);} else {emplace0(idx);} if (b & 0b00001000) {emplace1(idx);} else {emplace0(idx);} if (b & 0b00000100) {emplace1(idx);} else {emplace0(idx);} if (b & 0b00000010) {emplace1(idx);} else {emplace0(idx);} if (b & 0b00000001) {emplace1(idx);} else {emplace0(idx);} } /** emplace a 0 (short 1 pulse, long 0 pulse) */ inline void emplace0(int& idx) { items[idx].duration0 = 2;//1; //items[idx].level0 = 1; items[idx].duration1 = 8;//7; //items[idx].level1 = 0; ++idx; } /** emplace a 1 (long 1 pulse, short 0 pulse) */ inline void emplace1(int& idx) { items[idx].duration0 = 7;//6; //items[idx].level0 = 1; items[idx].duration1 = 3;//2; //items[idx].level1 = 0; ++idx; } }; #elif false //ESP32 #include #include #include #include "driver/timer.h" static constexpr timer_group_t grp = TIMER_GROUP_0; static constexpr timer_idx_t idx = TIMER_0; template class WS2812B; static IRAM_ATTR void myISR(void* arg) { printf("interrupt\n"); //WS2812B* leds = (WS2812B*) arg; //timer_pause(grp, idx); //leds->flushOLD(); } template class WS2812B { static constexpr const char* TAG = "WS2812"; /** enable/disable each led */ bool enabled[numLEDs] = {true}; /** color-value for each attached LED */ Color colors[numLEDs]; public: /** ctor */ WS2812B() { init(); } void init() { ESP_LOGI(TAG, "init()"); gpio_set_direction(outPin, GPIO_MODE_OUTPUT); } /** set the color for the given LED */ void setColor(const uint8_t idx, const Color rgb) { colors[idx] = rgb; } /** set the color for all LEDs */ void setColor(const Color rgb) { for (int idx = 0; idx < numLEDs; ++idx) { colors[idx] = rgb; } } /** enable/disable the given LED */ void setEnabled(const uint8_t idx, const bool en) { enabled[idx] = en; } /** enable/disable all LEDs */ void setEnabled(const bool en) { for (int idx = 0; idx < numLEDs; ++idx) { enabled[idx] = en; } } /** is the given LED enabled? */ bool isEnabled(const uint8_t idx) const { return enabled[idx]; } Color& getColor(const uint8_t idx) { return colors[idx]; } timer_config_t config; /** flush configured changes */ IRAM_ATTR void flush() { printf("flush()\n"); config.alarm_en = 1; config.auto_reload = 1; config.counter_dir = TIMER_COUNT_UP; config.divider = 2; config.intr_type = TIMER_INTR_LEVEL; config.counter_en = TIMER_PAUSE; ESP_ERROR_CHECK(timer_init(grp, idx, &config)); ESP_ERROR_CHECK(timer_set_counter_value(grp, idx, 0)); ESP_ERROR_CHECK(timer_set_alarm_value(grp, idx, 2)); ESP_ERROR_CHECK(timer_enable_intr(grp, idx)); ESP_ERROR_CHECK(timer_isr_register(grp, idx, myISR, (void*)this, ESP_INTR_FLAG_IRAM, NULL)); printf("start()\n"); ESP_ERROR_CHECK(timer_start(grp, idx)); } IRAM_ATTR void flushOLD() { ESP_LOGI(TAG, "sending %d LEDs", numLEDs); // important! otherwise often interrupted within portDISABLE_INTERRUPTS(); //portMUX_TYPE myMutex = portMUX_INITIALIZER_UNLOCKED; //portENTER_CRITICAL(&myMutex); // process each LED for (int i = 0; i < numLEDs; ++i) { portDISABLE_INTERRUPTS(); const volatile Color c = colors[i]; if (enabled[i]) { sendByte(c.g); sendByte(c.r); sendByte(c.b); } else { sendByte(0); sendByte(0); sendByte(0); } } //portEXIT_CRITICAL(&myMutex); portENABLE_INTERRUPTS(); } private: /** send one whole byte */ inline void sendByte(const uint8_t b) { if (b & 0b10000000) {send1();} else {send0();} if (b & 0b01000000) {send1();} else {send0();} if (b & 0b00100000) {send1();} else {send0();} if (b & 0b00010000) {send1();} else {send0();} if (b & 0b00001000) {send1();} else {send0();} if (b & 0b00000100) {send1();} else {send0();} if (b & 0b00000010) {send1();} else {send0();} if (b & 0b00000001) {send1();} else {send0();} } /** send a 0 (short 1 pulse, long 0 pulse) */ inline void send0() { gpio_set_level(outPin, 1); delayShort(); gpio_set_level(outPin, 0); delayLong(); } /** send a 1 (long 1 pulse, short 0 pulse) */ inline void send1() { gpio_set_level(outPin, 1); delayLong(); gpio_set_level(outPin, 0); delayShort(); } inline void delayShort() { volatile int i; i = 0; i = 1; i = 2; i = 3; (void) i; } inline void delayLong() { volatile int j; j = 0; j = 1; j = 2; j = 3; j = 4; j = 5; j = 6; j = 7; j = 8; j = 9; j = 1; j = 2; (void) j; // for (size_t i = 0; i < 16; ++i) { // asm volatile("nop"); // } } }; #elif false //othertest //#include #include #include #include #include template class WS2812B { static constexpr const char* TAG = "WS2812"; static constexpr rmt_channel_t chan = RMT_CHANNEL_0; //#define portDISABLE_INTERRUPTS() do { XTOS_SET_INTLEVEL(XCHAL_EXCM_LEVEL); portbenchmarkINTERRUPT_DISABLE(); } while (0) //#define portENABLE_INTERRUPTS() do { portbenchmarkINTERRUPT_RESTORE(0); XTOS_SET_INTLEVEL(0); } while (0) //#define ENABLE_INTERRUPTS() portENABLE_INTERRUPTS() //#define DISABLE_INTERRUPTS() portDISABLE_INTERRUPTS() static constexpr int numBits = (3*8) * 2; // (r,g,b) each 8 bit NOTE! must be < 64 to fit into the buffer! //static constexpr int numItems = 256;//(numLEDs*3)*8; //static constexpr int numItems = (numLEDs*3)*8; rmt_item32_t items[numBits+1]; // + one entry has two bits + 0-terminator (just like within strings) /** enable/disable each led */ bool enabled[numLEDs] = {true}; /** color-value for each attached LED */ Color colors[numLEDs]; public: /** ctor */ WS2812B() { init(); } void init() { ESP_LOGI(TAG, "init()"); rmt_config_t cfg; cfg.rmt_mode = RMT_MODE_TX; cfg.channel = chan; cfg.gpio_num = outPin; cfg.mem_block_num = 1;//chan+1;//8-chan;//chan+1;//8 - chan; //chan+1;// cfg.clk_div = 8; cfg.tx_config.loop_en = false; cfg.tx_config.carrier_en = false; cfg.tx_config.idle_output_en = true; cfg.tx_config.idle_level = (rmt_idle_level_t)0; cfg.tx_config.carrier_freq_hz = 10000; cfg.tx_config.carrier_level = (rmt_carrier_level_t)1; cfg.tx_config.carrier_duty_percent = 50; ESP_ERROR_CHECK(rmt_config(&cfg)); // disable when using custom ISR //ESP_ERROR_CHECK(rmt_driver_install(chan, 0, 0)); // setup constant values once for (int idx = 0; idx < numBits; ++idx) { items[idx].duration0 = 0; items[idx].level0 = 1; items[idx].duration1 = 0; items[idx].level1 = 0; } items[numBits].val = 0; // 0 terminator ESP_LOGI(TAG, "init OK!"); } /** set the color for the given LED */ void setColor(const uint8_t idx, const Color rgb) { colors[idx] = rgb; } /** set the color for all LEDs */ void setColor(const Color rgb) { for (int idx = 0; idx < numLEDs; ++idx) { colors[idx] = rgb; } } /** enable/disable the given LED */ void setEnabled(const uint8_t idx, const bool en) { enabled[idx] = en; } /** enable/disable all LEDs */ void setEnabled(const bool en) { for (int idx = 0; idx < numLEDs; ++idx) { enabled[idx] = en; } } /** is the given LED enabled? */ bool isEnabled(const uint8_t idx) const { return enabled[idx]; } Color& getColor(const uint8_t idx) { return colors[idx]; } int nextLED; /** flush configured changes */ /* void flush() { nextLED = 0; ESP_LOGI(TAG, "sending %d LEDs", numLEDs); // important! otherwise often interrupted within portDISABLE_INTERRUPTS(); for (int i = 0; i < 1024; ++i) { asm volatile("nop"); } //waitForTX(); const size_t toWrite = 8+8+8+1;// 8-bit R, 8-bit G, 8-bit B, null-terminator // add null terminator items[toWrite-1].val = 0; // process each LED for (int i = 0; i < numLEDs; i+=2) { enqueueNext(); // add null terminator //items[idx].val = 0; // wait for sending to complete using the RMT Unit's status waitForTX(); ESP_ERROR_CHECK(rmt_fill_tx_items(this->chan, items, toWrite, 0)); ESP_ERROR_CHECK(rmt_tx_start(this->chan, true)); // reset for next round //idx = 0; } //waitForTX(); portENABLE_INTERRUPTS(); } */ static constexpr int toWrite = 8+8+8+1; rmt_isr_handle_t isrHandle; void flush() { nextLED = 0; ESP_LOGI(TAG, "sending %d LEDs", numLEDs); enqueueNext(); ESP_ERROR_CHECK(rmt_isr_register(onISR, this, ESP_INTR_FLAG_LEVEL1, &isrHandle)); ESP_ERROR_CHECK(rmt_set_tx_thr_intr_en(this->chan, true, toWrite/2)); //ESP_ERROR_CHECK(rmt_set_tx_intr_en(this->chan, true)); printf("_start\n"); ESP_ERROR_CHECK(rmt_tx_start(this->chan, true)); } private: static IRAM_ATTR void onISR(void* arg) { printf(".\n"); WS2812B* led = (WS2812B*) arg; if (led->nextLED < numLEDs) { led->enqueueNext(); } else { rmt_tx_stop(led->chan); } } void IRAM_ATTR enqueueNext() { printf("e\n"); int idx = 0; if (enabled[nextLED]) { emplaceByte(colors[nextLED].g, idx); emplaceByte(colors[nextLED].r, idx); emplaceByte(colors[nextLED].b, idx); } else { emplaceByte(0, idx); emplaceByte(0, idx); emplaceByte(0, idx); } ESP_ERROR_CHECK(rmt_fill_tx_items(this->chan, items, toWrite, 0)); ++nextLED; } void waitForTX() { uint32_t status; // status while sending: 16797696 ?? for (int i = 0; i < 2000; ++i) { rmt_get_status(this->chan, &status); if (status == 0) {break;} //if (status != 16797696) {break;} //if ( (status & (1<<24)) == 0 ) {break;} // somewhat OK } // for (int i = 0; i < 1000; ++i) { // asm volatile("nop"); // } } /** send one whole byte */ inline void emplaceByte(const uint8_t b, int& idx) { if (b & 0b10000000) {emplace1(idx);} else {emplace0(idx);} if (b & 0b01000000) {emplace1(idx);} else {emplace0(idx);} if (b & 0b00100000) {emplace1(idx);} else {emplace0(idx);} if (b & 0b00010000) {emplace1(idx);} else {emplace0(idx);} if (b & 0b00001000) {emplace1(idx);} else {emplace0(idx);} if (b & 0b00000100) {emplace1(idx);} else {emplace0(idx);} if (b & 0b00000010) {emplace1(idx);} else {emplace0(idx);} if (b & 0b00000001) {emplace1(idx);} else {emplace0(idx);} } /** emplace a 0 (short 1 pulse, long 0 pulse) */ inline void emplace0(int& idx) { items[idx].duration0 = 2;//1; //items[idx].level0 = 1; items[idx].duration1 = 8;//7; //items[idx].level1 = 0; ++idx; } /** emplace a 1 (long 1 pulse, short 0 pulse) */ inline void emplace1(int& idx) { items[idx].duration0 = 7;//6; //items[idx].level0 = 1; items[idx].duration1 = 3;//2; //items[idx].level1 = 0; ++idx; } }; #endif #endif // WS2812B_H