From 99111b9a8118b15235697e138014f437c9c1d66f Mon Sep 17 00:00:00 2001 From: Brian Bulkowski Date: Sun, 8 Mar 2020 17:56:33 -0700 Subject: [PATCH] Make the 4-wire LEDs compile. Had to pull in far more of the Arduino HAL. --- README.md | 88 +++-- components/FastLED-idf/CMakeLists.txt | 1 + components/FastLED-idf/hal/esp32-hal-gpio.c | 307 ++++++++++++++++++ components/FastLED-idf/hal/esp32-hal-misc.c | 4 +- components/FastLED-idf/hal/esp32-hal-spi.c | 4 +- .../platforms/esp/32/fastpin_esp32.h | 7 + .../platforms/esp/32/led_sysdefs_esp32.h | 3 +- main/main.cpp | 31 +- 8 files changed, 399 insertions(+), 46 deletions(-) create mode 100644 components/FastLED-idf/hal/esp32-hal-gpio.c diff --git a/README.md b/README.md index c600957..54a47db 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ changing the duty cycle and brightness, it doesn't control color-controlled LEDs Thus, we need FastLED. -# Use of ESP32 hardware +# Use of ESP32 hardware for 3 wire LEDs The ESP32 has an interesting module, called RMT. It's a module that's meant to make arbitrary waveforms on pins, without having to bang each pin at @@ -60,6 +60,25 @@ I have not yet validated if this library correctly uses RMT. No extra commands in `menuconfig` seem necessary. +# Four wire LEDs ( APA102 and similar ) + +Interestingly, four wire LEDs can't use the RMT interface, because +the clock and data lines have to be controled together ( duh ), +and the RMT interface doesn't do that. What does do that is the SPI +interface, and I don't think I've wired that up. + +There are two hardware SPI ports in the system, so that should be +able to be enabled. I haven't tried that. + +Since hardware banging is used ( that's the big ugly warning ), +these LEDs are very likely far slower, and probably do not respond to parallelism +the same way. + +I have pulled in enough of the HAL for the APA102 code to compile, +but I don't know if it works, since I don't have any four-wire LEDs +around. Since it's very much a different code path, I'm not going to make +promises until someone tries it. + # async mode The right way to use a system like this is with some form of async mode, where the CPU @@ -68,27 +87,15 @@ happening. This would allow much better multi-channel work, because the CPU coul one channel, then go off and fill the next channel, etc. For that, you'd have to use the LastLED async interfaces. -Why do you want to use multiple channels? Apply the FadeCandy algos. The reason the fadecandy -is teh awesome is it applies "temporal anti-aliasing", which is to say, it always drives the -LEDs at full speed, and when it's trying to display a certain color, it actually displays a blend -of other colors. This allows far more resolution than the underlying LED hardware might be -capable of. +It turns out this all works just peachy, it's just that the FastLED interface is a little +peculiar. If you see the THREADING document in this directory, you'll +see that this port is enabling multi-channel mode when using 3-wire LEDs, +which means you change all the pixels, and when you call FastLED.show(), they'll +all bang on the RMT hardware, and use very little main CPU. -However, it means you need to drive lots of bytes all the time. The fadecandy appears to drive -400Khz across its 64-led maximum strings _all the time_. If we did that with this system, one core -would drive one string. Sure, we've got two cores, but that's not very fun. - -With an async system, you can drive as many strings as you want in parallel, and let RMT loose. -There are only 8 RMT channels, so you can at least get up to the capacity of a single FadeCandy. - -Still - imagine the possibilities. If you're not applying temporal dithering, you can at least have -a ton more parallel strings. Instead of being limited to about 1k LEDs saturating an entire core -at 30fps with no "temporal dithering", you might be able to get upward of 4k or 8k ( to be measured ). - -In order to do this in the happiest way, you'd like to use FastLED's coordination systems. You'd -like an async call to complete using a C feature, to take mutexes because it might be interrupting -with the scheduler, or to put a message on one of the message queues for service. Doing all -that would be a major expansion of the FastLED interface and would diverge from the "published spec". +It would be nicer if FastLED had some sort of Async mode, but that's not +really the Arduino way, and this code is meant for arduino. Arduino doesn't +have threads of control or message queues or anything like that. # A bit about esp-idf @@ -131,6 +138,10 @@ simply changes the duty cycle on a pin using the RMT interface. It doesn't do pixel color control. It can be an example of using the RMT system, that's it. +There is an example of LED control, using the RMT interface directly. +If you just want to it like that, without all the cool stuff in FastLED, +be everyone's guest! + I did reach out to Espressif. I think they should include or have a FastLED port. In their forums, they said they don't intend to do anything like that. @@ -342,6 +353,8 @@ Note: what's up with registering the driver? I should, right? Make sure that's d menuconfig? Doen't seem to be. There are no settings anywhere in the menuconfig regarding gpio. Should probably look at the examples to see if I have to enable the ISR or something. + + ## using RMT vs I2S Note to self: There is a define in the platforms area that lets a person choose. @@ -389,10 +402,9 @@ After looking a bit, it doesn't seem unreasonable to remove all the memmoves and loops. It's been done in other places of the code. Otherwise, one could say "IF GCC8" or something similar, because I bet it really does matter on smaller systems. Maybe even on this one. -There is a menuconfig to turn off new warnings introduced from GCC6 to GCC8. - -The other thing is to cast these to void, but ewww. Anyone who uses C++ on an embedded system -should go whole hog. +In the upstream code of FastLED, there is an introduction of a void * cast in these two places. +That's the other way of doing it, and the code does "promise" that the classes in question +can be memcpy'd. If someone re-ports the code, they could fix this. ## Don't use C @@ -400,8 +412,28 @@ Had a main.c , and FastLED.h includes nothing but C++. Therefore, all the source that include FastLED have to be using the C++ compiler, and ESP-IDF has the standard rules for using C for C and CPP for CPP because it's not like they are subsets or something. +## CXX_STUFF + +There is some really scary code about guards. It is defined if ESP32, +and seems to override the way the compiler executes a certain kind of guard. +I've turned that off, because I think we should probably trust esp-idf to do the +right and best thing for that + +## pulled in too much of the hal + +In order to get 4-wire LEDs compiling, it's necessary to pull in the HAL gpio.c . +Just to get one lousy function - pinMode. I probably should +have simply had the function call gpio_set_direction(), which is the esp_idf +function, directly. If you have a 4-wire system, I left a breadcrumb in the +fastpin_esp32 directory. If the alternate code works, then you can take the +gpio.c out of the hal compile. + ## message about no hardware SPI pins defined -This appears widely known to be a warning on ESP32 to go look and see if the RMT system -is getting included. Do need to put in some printfs to see if RMT is enabled, -and see if the async system works, which would be the cool part of RMT. +It's true! There are no hardware SPI pins defined. SPI is used for 4-wire LEDs, where +you have to synchronize the clock and data. + +If you have 3-wire LEDs, you'll be using the RMT system, which is super fast anyway. + +The upstream code doesn't seem to use SPI on ESP32 either, so that's just a big hairy +todo, and don't worry overmuch. \ No newline at end of file diff --git a/components/FastLED-idf/CMakeLists.txt b/components/FastLED-idf/CMakeLists.txt index ed375e7..73c530b 100644 --- a/components/FastLED-idf/CMakeLists.txt +++ b/components/FastLED-idf/CMakeLists.txt @@ -12,6 +12,7 @@ set(srcs "power_mgt.cpp" "wiring.cpp" "hal/esp32-hal-misc.c" + "hal/esp32-hal-gpio.c" ) # everything needs the ESP32 flag, not sure why this won't work diff --git a/components/FastLED-idf/hal/esp32-hal-gpio.c b/components/FastLED-idf/hal/esp32-hal-gpio.c new file mode 100644 index 0000000..9c630f3 --- /dev/null +++ b/components/FastLED-idf/hal/esp32-hal-gpio.c @@ -0,0 +1,307 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "esp32-hal-gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp32/rom/ets_sys.h" +#include "esp_attr.h" +#include "esp_intr_alloc.h" +#include "esp32/rom/gpio.h" +#include "soc/gpio_reg.h" +#include "soc/io_mux_reg.h" +#include "soc/gpio_struct.h" +#include "soc/rtc_io_reg.h" + +const int8_t esp32_adc2gpio[20] = {36, 37, 38, 39, 32, 33, 34, 35, -1, -1, 4, 0, 2, 15, 13, 12, 14, 27, 25, 26}; + +// BB +#define GPIO_PIN_COUNT 40 + +const DRAM_ATTR esp32_gpioMux_t esp32_gpioMux[GPIO_PIN_COUNT]={ + {0x44, 11, 11, 1}, + {0x88, -1, -1, -1}, + {0x40, 12, 12, 2}, + {0x84, -1, -1, -1}, + {0x48, 10, 10, 0}, + {0x6c, -1, -1, -1}, + {0x60, -1, -1, -1}, + {0x64, -1, -1, -1}, + {0x68, -1, -1, -1}, + {0x54, -1, -1, -1}, + {0x58, -1, -1, -1}, + {0x5c, -1, -1, -1}, + {0x34, 15, 15, 5}, + {0x38, 14, 14, 4}, + {0x30, 16, 16, 6}, + {0x3c, 13, 13, 3}, + {0x4c, -1, -1, -1}, + {0x50, -1, -1, -1}, + {0x70, -1, -1, -1}, + {0x74, -1, -1, -1}, + {0x78, -1, -1, -1}, + {0x7c, -1, -1, -1}, + {0x80, -1, -1, -1}, + {0x8c, -1, -1, -1}, + {0, -1, -1, -1}, + {0x24, 6, 18, -1}, //DAC1 + {0x28, 7, 19, -1}, //DAC2 + {0x2c, 17, 17, 7}, + {0, -1, -1, -1}, + {0, -1, -1, -1}, + {0, -1, -1, -1}, + {0, -1, -1, -1}, + {0x1c, 9, 4, 8}, + {0x20, 8, 5, 9}, + {0x14, 4, 6, -1}, + {0x18, 5, 7, -1}, + {0x04, 0, 0, -1}, + {0x08, 1, 1, -1}, + {0x0c, 2, 2, -1}, + {0x10, 3, 3, -1} +}; + +typedef void (*voidFuncPtr)(void); +typedef void (*voidFuncPtrArg)(void*); +typedef struct { + voidFuncPtr fn; + void* arg; + bool functional; +} InterruptHandle_t; +static InterruptHandle_t __pinInterruptHandlers[GPIO_PIN_COUNT] = {0,}; + +#include "driver/rtc_io.h" + +extern void IRAM_ATTR __pinMode(uint8_t pin, uint8_t mode) +{ + + if(!digitalPinIsValid(pin)) { + return; + } + + uint32_t rtc_reg = rtc_gpio_desc[pin].reg; + if(mode == ANALOG) { + if(!rtc_reg) { + return;//not rtc pin + } + //lock rtc + uint32_t reg_val = ESP_REG(rtc_reg); + if(reg_val & rtc_gpio_desc[pin].mux){ + return;//already in adc mode + } + reg_val &= ~( + (RTC_IO_TOUCH_PAD1_FUN_SEL_V << rtc_gpio_desc[pin].func) + |rtc_gpio_desc[pin].ie + |rtc_gpio_desc[pin].pullup + |rtc_gpio_desc[pin].pulldown); + ESP_REG(RTC_GPIO_ENABLE_W1TC_REG) = (1 << (rtc_gpio_desc[pin].rtc_num + RTC_GPIO_ENABLE_W1TC_S)); + ESP_REG(rtc_reg) = reg_val | rtc_gpio_desc[pin].mux; + //unlock rtc + ESP_REG(DR_REG_IO_MUX_BASE + esp32_gpioMux[pin].reg) = ((uint32_t)2 << MCU_SEL_S) | ((uint32_t)2 << FUN_DRV_S) | FUN_IE; + return; + } + + //RTC pins PULL settings + if(rtc_reg) { + //lock rtc + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux); + if(mode & PULLUP) { + ESP_REG(rtc_reg) = (ESP_REG(rtc_reg) | rtc_gpio_desc[pin].pullup) & ~(rtc_gpio_desc[pin].pulldown); + } else if(mode & PULLDOWN) { + ESP_REG(rtc_reg) = (ESP_REG(rtc_reg) | rtc_gpio_desc[pin].pulldown) & ~(rtc_gpio_desc[pin].pullup); + } else { + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown); + } + //unlock rtc + } + + uint32_t pinFunction = 0, pinControl = 0; + + //lock gpio + if(mode & INPUT) { + if(pin < 32) { + GPIO.enable_w1tc = ((uint32_t)1 << pin); + } else { + GPIO.enable1_w1tc.val = ((uint32_t)1 << (pin - 32)); + } + } else if(mode & OUTPUT) { + if(pin > 33){ + //unlock gpio + return;//pins above 33 can be only inputs + } else if(pin < 32) { + GPIO.enable_w1ts = ((uint32_t)1 << pin); + } else { + GPIO.enable1_w1ts.val = ((uint32_t)1 << (pin - 32)); + } + } + + if(mode & PULLUP) { + pinFunction |= FUN_PU; + } else if(mode & PULLDOWN) { + pinFunction |= FUN_PD; + } + + pinFunction |= ((uint32_t)2 << FUN_DRV_S);//what are the drivers? + pinFunction |= FUN_IE;//input enable but required for output as well? + + if(mode & (INPUT | OUTPUT)) { + pinFunction |= ((uint32_t)2 << MCU_SEL_S); + } else if(mode == SPECIAL) { + pinFunction |= ((uint32_t)(((pin)==1||(pin)==3)?0:1) << MCU_SEL_S); + } else { + pinFunction |= ((uint32_t)(mode >> 5) << MCU_SEL_S); + } + + ESP_REG(DR_REG_IO_MUX_BASE + esp32_gpioMux[pin].reg) = pinFunction; + + if(mode & OPEN_DRAIN) { + pinControl = (1 << GPIO_PIN0_PAD_DRIVER_S); + } + + GPIO.pin[pin].val = pinControl; + //unlock gpio +} + +extern void IRAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) +{ + if(val) { + if(pin < 32) { + GPIO.out_w1ts = ((uint32_t)1 << pin); + } else if(pin < 34) { + GPIO.out1_w1ts.val = ((uint32_t)1 << (pin - 32)); + } + } else { + if(pin < 32) { + GPIO.out_w1tc = ((uint32_t)1 << pin); + } else if(pin < 34) { + GPIO.out1_w1tc.val = ((uint32_t)1 << (pin - 32)); + } + } +} + +extern int IRAM_ATTR __digitalRead(uint8_t pin) +{ + if(pin < 32) { + return (GPIO.in >> pin) & 0x1; + } else if(pin < 40) { + return (GPIO.in1.val >> (pin - 32)) & 0x1; + } + return 0; +} + +static intr_handle_t gpio_intr_handle = NULL; + +static void IRAM_ATTR __onPinInterrupt() +{ + uint32_t gpio_intr_status_l=0; + uint32_t gpio_intr_status_h=0; + + gpio_intr_status_l = GPIO.status; + gpio_intr_status_h = GPIO.status1.val; + GPIO.status_w1tc = gpio_intr_status_l;//Clear intr for gpio0-gpio31 + GPIO.status1_w1tc.val = gpio_intr_status_h;//Clear intr for gpio32-39 + + uint8_t pin=0; + if(gpio_intr_status_l) { + do { + if(gpio_intr_status_l & ((uint32_t)1 << pin)) { + if(__pinInterruptHandlers[pin].fn) { + if(__pinInterruptHandlers[pin].arg){ + ((voidFuncPtrArg)__pinInterruptHandlers[pin].fn)(__pinInterruptHandlers[pin].arg); + } else { + __pinInterruptHandlers[pin].fn(); + } + } + } + } while(++pin<32); + } + if(gpio_intr_status_h) { + pin=32; + do { + if(gpio_intr_status_h & ((uint32_t)1 << (pin - 32))) { + if(__pinInterruptHandlers[pin].fn) { + if(__pinInterruptHandlers[pin].arg){ + ((voidFuncPtrArg)__pinInterruptHandlers[pin].fn)(__pinInterruptHandlers[pin].arg); + } else { + __pinInterruptHandlers[pin].fn(); + } + } + } + } while(++pin(leds, NUM_LEDS); + // this is a good test because it uses the GPIO ports + //FastLED.addLeds(leds, NUM_LEDS); + printf(" set max power\n"); - FastLED.setMaxPowerInVoltsAndMilliamps(5,1000); + FastLED.setMaxPowerInVoltsAndMilliamps(5,2000); printf("create task for led blinking\n"); xTaskCreatePinnedToCore(&blinkLeds_simple, "blinkLeds", 4000, NULL, 5, NULL, 0); }