Make the 4-wire LEDs compile.

Had to pull in far more of the Arduino HAL.
This commit is contained in:
Brian Bulkowski
2020-03-08 17:56:33 -07:00
parent 17bffd84e6
commit 99111b9a81
8 changed files with 399 additions and 46 deletions

View File

@ -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.

View File

@ -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

View File

@ -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<GPIO_PIN_COUNT);
}
}
extern void cleanupFunctional(void* arg);
extern void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtrArg userFunc, void * arg, int intr_type, bool functional)
{
static bool interrupt_initialized = false;
if(!interrupt_initialized) {
interrupt_initialized = true;
esp_intr_alloc(ETS_GPIO_INTR_SOURCE, (int)ESP_INTR_FLAG_IRAM, __onPinInterrupt, NULL, &gpio_intr_handle);
}
// if new attach without detach remove old info
if (__pinInterruptHandlers[pin].functional && __pinInterruptHandlers[pin].arg)
{
cleanupFunctional(__pinInterruptHandlers[pin].arg);
}
__pinInterruptHandlers[pin].fn = (voidFuncPtr)userFunc;
__pinInterruptHandlers[pin].arg = arg;
__pinInterruptHandlers[pin].functional = functional;
esp_intr_disable(gpio_intr_handle);
if(esp_intr_get_cpu(gpio_intr_handle)) { //APP_CPU
GPIO.pin[pin].int_ena = 1;
} else { //PRO_CPU
GPIO.pin[pin].int_ena = 4;
}
GPIO.pin[pin].int_type = intr_type;
esp_intr_enable(gpio_intr_handle);
}
extern void __attachInterruptArg(uint8_t pin, voidFuncPtrArg userFunc, void * arg, int intr_type)
{
__attachInterruptFunctionalArg(pin, userFunc, arg, intr_type, false);
}
extern void __attachInterrupt(uint8_t pin, voidFuncPtr userFunc, int intr_type) {
__attachInterruptFunctionalArg(pin, (voidFuncPtrArg)userFunc, NULL, intr_type, false);
}
extern void __detachInterrupt(uint8_t pin)
{
esp_intr_disable(gpio_intr_handle);
if (__pinInterruptHandlers[pin].functional && __pinInterruptHandlers[pin].arg)
{
cleanupFunctional(__pinInterruptHandlers[pin].arg);
}
__pinInterruptHandlers[pin].fn = NULL;
__pinInterruptHandlers[pin].arg = NULL;
__pinInterruptHandlers[pin].functional = false;
GPIO.pin[pin].int_ena = 0;
GPIO.pin[pin].int_type = 0;
esp_intr_enable(gpio_intr_handle);
}
extern void pinMode(uint8_t pin, uint8_t mode) __attribute__ ((weak, alias("__pinMode")));
extern void digitalWrite(uint8_t pin, uint8_t val) __attribute__ ((weak, alias("__digitalWrite")));
extern int digitalRead(uint8_t pin) __attribute__ ((weak, alias("__digitalRead")));
extern void attachInterrupt(uint8_t pin, voidFuncPtr handler, int mode) __attribute__ ((weak, alias("__attachInterrupt")));
extern void attachInterruptArg(uint8_t pin, voidFuncPtrArg handler, void * arg, int mode) __attribute__ ((weak, alias("__attachInterruptArg")));
extern void detachInterrupt(uint8_t pin) __attribute__ ((weak, alias("__detachInterrupt")));

View File

@ -34,7 +34,7 @@
#include "soc/rtc.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/apb_ctrl_reg.h"
#include "rom/rtc.h"
#include "esp32/rom/rtc.h"
#include "esp_task_wdt.h"
#include "esp32-hal.h"

View File

@ -17,10 +17,10 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "rom/ets_sys.h"
#include "esp32/rom/ets_sys.h"
#include "esp_attr.h"
#include "esp_intr.h"
#include "rom/gpio.h"
#include "esp32/rom/gpio.h"
#include "soc/spi_reg.h"
#include "soc/spi_struct.h"
#include "soc/io_mux_reg.h"

View File

@ -17,6 +17,13 @@ public:
typedef volatile uint32_t * port_ptr_t;
typedef uint32_t port_t;
// BB
// It could be better to to the following, then not have to pull in as
// much of the hal:
// inline static void setOutput() { gpio_set_direction(PIN, GPIO_MODE_OUTPUT); }
// inline static void setInput() { gpio_set_direction(PIN, GPIO_MODE_INPUT); }
inline static void setOutput() { pinMode(PIN, OUTPUT); }
inline static void setInput() { pinMode(PIN, INPUT); }

View File

@ -24,7 +24,8 @@ typedef unsigned long prog_uint32_t;
# define INTERRUPT_THRESHOLD 0
#endif
#define NEED_CXX_BITS
// BB - I think I'm compiling in CXX in this project
// #define NEED_CXX_BITS
// These can be overridden
# define FASTLED_ESP32_RAW_PIN_ORDER

View File

@ -23,8 +23,8 @@ extern const TProgmemPalette16 IRAM_ATTR myRedWhiteBluePalette_p;
#include "palettes.h"
#define NUM_LEDS 40
#define DATA_PIN 18
#define BRIGHTNESS 64
#define DATA_PIN 13
#define BRIGHTNESS 80
#define LED_TYPE WS2811
#define COLOR_ORDER RGB
CRGB leds[NUM_LEDS];
@ -100,9 +100,10 @@ CRGB colors[N_COLORS] = {
void blinkLeds_simple(void *pvParameters){
while(1){
printf("blink leds\n");
for (int j=0;j<N_COLORS;j++) {
printf("blink leds\n");
for (int i=0;i<NUM_LEDS;i++) {
leds[i] = colors[j];
}
@ -154,9 +155,13 @@ void blinkLeds_chase(void *pvParameters) {
void app_main() {
printf(" entering app main, call add leds\n");
// the WS2811 family uses the RMT driver
FastLED.addLeds<LED_TYPE, DATA_PIN>(leds, NUM_LEDS);
// this is a good test because it uses the GPIO ports
//FastLED.addLeds<APA102, 13, 15>(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);
}