Skip to content

Commit

Permalink
allow for different GPIO pins to drive multiple led stripes with one SPI
Browse files Browse the repository at this point in the history
  • Loading branch information
andreas committed Sep 7, 2024
1 parent 500a58e commit 52b25dd
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 60 deletions.
196 changes: 137 additions & 59 deletions lib/ledspitask/GwLedSpiTask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ static size_t ledsToBuffer(int numLeds,const Color *leds,uint8_t *buffer){
return p-buffer;
}

/**
* prepare a GPIO pin to be used as the data line for an led stripe
*/
bool prepareGpio(GwLog *logger, uint8_t pin){
esp_err_t err=gpio_set_direction((gpio_num_t)pin,GPIO_MODE_OUTPUT);
if (err != ESP_OK){
Expand All @@ -118,6 +121,106 @@ bool prepareGpio(GwLog *logger, uint8_t pin){
}
return true;
}
/**
* initialize the SPI bus and add a device for the LED output
* it still does not attach any PINs to the bus
* this will be done later when sending out
* this way we can use one hardware SPI for multiple led stripes
* @param bus : the SPI bus
* @param device: <out> the device handle being filled
* @return false on error
*/
bool prepareSpi(GwLog *logger,spi_host_device_t bus,spi_device_handle_t *device){
spi_bus_config_t buscfg = {
.mosi_io_num = -1,
.miso_io_num = -1,
.sclk_io_num = -1,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 0,
.flags=SPICOMMON_BUSFLAG_GPIO_PINS
};
esp_err_t err=spi_bus_initialize(bus,&buscfg,SPI_DMA_CH_AUTO);
if (err != ESP_OK){
LOG_DEBUG(GwLog::ERROR,"unable to initialize SPI bus %d,mosi=%d, error=%d",
(int)bus,-1,(int)err);
return false;
}
spi_device_interface_config_t devcfg = {
.command_bits = 0,
.address_bits = 0,
.dummy_bits = 0,
.mode = 0,
.duty_cycle_pos = 128,
.cs_ena_pretrans = 0,
.cs_ena_posttrans =0,
.clock_speed_hz = 2500000, //2.5 Mhz
.input_delay_ns =0,
.spics_io_num = -1, //CS pin
.queue_size = 1 //see https://github.com/espressif/esp-idf/issues/9450
};
err=spi_bus_add_device(bus,&devcfg,device);
if (err != ESP_OK){
LOG_DEBUG(GwLog::ERROR,"unable to add device to SPI bus %d,mosi=%d, error=%d",
(int)bus,-1,(int)err);
return false;
}
return true;
}

/**
* send out a set of Color values to a connected led stripe
* this method will block until sen dis complete
* But as the transfer is using DMA the CPU is not busy during the wait time
* @param pin: the IO pin to be used. Will be attached to the SPI device before and deattached after
* @param numLeds: the number of Color values
* @param leds: pointer to the first Color value
* @param bus: the SPI bus
* @param device: the SPI device handle
**/
bool sendToLeds(GwLog *logger, uint8_t pin, int numLeds, Color *leds, spi_host_device_t bus, spi_device_handle_t &device, uint8_t *buffer = NULL)
{
bool ownsBuffer = false;
size_t bufferSize = numLeds * 3 * 3;
if (buffer == NULL)
{
ownsBuffer = true;
buffer = (uint8_t *)heap_caps_malloc(bufferSize, MALLOC_CAP_DMA);
if (!buffer)
{
LOG_DEBUG(GwLog::ERROR, "unable to allocate %d bytes of DMA buffer", (int)bufferSize);
return false;
}
}
bool rv = true;
ledsToBuffer(numLeds, leds, buffer);
struct spi_transaction_t ta = {
.flags = 0,
.cmd = 0,
.addr = 0,
.length = bufferSize * 8,
.rxlength = 0,
.tx_buffer = buffer};
int64_t now = esp_timer_get_time();
esp_rom_gpio_connect_out_signal(pin, spi_periph_signal[bus].spid_out, false, false);
esp_err_t ret = spi_device_transmit(device, &ta);
esp_rom_gpio_connect_out_signal(pin, SIG_GPIO_OUT_IDX, false, false);
int64_t end = esp_timer_get_time();
if (ret != ESP_OK)
{
LOG_DEBUG(GwLog::ERROR, "unable to send led data: %d", (int)ret);
rv = false;
}
else
{
LOG_DEBUG(GwLog::DEBUG, "successfully send led data for %d leds, %lld us", numLeds, end - now);
}
if (ownsBuffer)
{
heap_caps_free(buffer);
}
return rv;
}

#define EXIT_TASK delay(50);vTaskDelete(NULL);return;
void handleSpiLeds(GwApi *api){
Expand Down Expand Up @@ -150,53 +253,22 @@ void handleSpiLeds(GwApi *api){
EXIT_TASK;
}
LOG_DEBUG(GwLog::ERROR,"SpiLed task started");
uint8_t ledPin=GWLED_PIN;
spi_bus_config_t buscfg = {
.mosi_io_num = -1,
.miso_io_num = -1,
.sclk_io_num = -1,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 0,
.flags=SPICOMMON_BUSFLAG_GPIO_PINS
};
esp_err_t err=spi_bus_initialize(bus,&buscfg,SPI_DMA_CH_AUTO);
if (err != ESP_OK){
LOG_DEBUG(GwLog::ERROR,"unable to initialize SPI bus %d,mosi=%d, error=%d",
(int)bus,ledPin,(int)err);

if (! prepareGpio(logger,GWLED_PIN)){
EXIT_TASK;
}
spi_device_interface_config_t devcfg = {
.command_bits = 0,
.address_bits = 0,
.dummy_bits = 0,
.mode = 0,
.duty_cycle_pos = 128,
.cs_ena_pretrans = 0,
.cs_ena_posttrans =0,
.clock_speed_hz = 2500000, //2.5 Mhz
.input_delay_ns =0,
.spics_io_num = -1, //CS pin
.queue_size = 1 //see https://github.com/espressif/esp-idf/issues/9450
};
spi_device_handle_t device;
err=spi_bus_add_device(bus,&devcfg,&device);
if (err != ESP_OK){
LOG_DEBUG(GwLog::ERROR,"unable to add device to SPI bus %d,mosi=%d, error=%d",
(int)bus,ledPin,(int)err);
#ifdef GWLED_PIN2
if (! prepareGpio(logger,GWLED_PIN2)){
EXIT_TASK;
}
if (! prepareGpio(logger,ledPin)){
#endif
spi_device_handle_t device;
if (! prepareSpi(logger,bus,&device)){
EXIT_TASK;
}
const int NUMLEDS=2;
Color leds[NUMLEDS];
size_t bufferSize=NUMLEDS*3*3;
uint8_t *outbuffer=(uint8_t*) heap_caps_malloc(bufferSize,MALLOC_CAP_DMA);
if (! outbuffer){
LOG_DEBUG(GwLog::ERROR,"unable to allocate %d bytes of DMA buffer",(int)bufferSize);
EXIT_TASK;
}

uint8_t brightness=api->getConfig()->getInt(GwConfigDefinitions::ledBrightness,128);
GwLedMode currentMode=LED_GREEN;
bool first=true;
Expand All @@ -205,6 +277,10 @@ void handleSpiLeds(GwApi *api){
int seccount=0;
int modeIndex=0;
GwLedMode modes[4]={LED_WHITE,LED_GREEN,LED_RED,LED_BLUE};
/*
currently there is somple simple testcode in the loop
that changes colors every 5s
*/
while (true)
{
delay(50);
Expand All @@ -216,6 +292,11 @@ void handleSpiLeds(GwApi *api){
}
GwLedMode newMode = currentMode;
/*
by using the task API you can easily build a safe communication between
another task and this one
So the other task could set the colors and this task would just output them
*/
/*
IButtonTask buttonState = api->taskInterfaces()->get<IButtonTask>(apiResult);
if (apiResult >= 0 )
{
Expand Down Expand Up @@ -249,27 +330,24 @@ void handleSpiLeds(GwApi *api){
first=false;
leds[0] = setBrightness(colorFromMode(newMode),brightness);
leds[1] = leds[0];
//swap g and r for led1 to see differences
uint8_t t=leds[1].g;
leds[1].g=leds[1].r;
leds[1].r=t;
LOG_DEBUG(GwLog::DEBUG,"(%d) mode=%d,setting color g=%d,r=%d,b=%d",modeIndex,(int)newMode,leds[0].g,leds[0].r,leds[0].b);
ledsToBuffer(NUMLEDS,leds,outbuffer);
struct spi_transaction_t ta = {
.flags = 0,
.cmd = 0,
.addr = 0,
.length = bufferSize*8,
.rxlength = 0,
.tx_buffer = outbuffer
};
int64_t now=esp_timer_get_time();
esp_rom_gpio_connect_out_signal(ledPin,spi_periph_signal[bus].spid_out,false,false);
esp_err_t ret = spi_device_transmit(device, &ta);
esp_rom_gpio_connect_out_signal(ledPin,SIG_GPIO_OUT_IDX,false,false);
int64_t end=esp_timer_get_time();
if (ret != ESP_OK){
LOG_DEBUG(GwLog::ERROR,"unable to send led data: %d",(int)ret);
}
else{
LOG_DEBUG(GwLog::DEBUG,"successfully send led data for %d leds, %lld us",NUMLEDS,end-now);
}
/**
* by defining GWLED_PIN2 you can control 2 different stripes on different GPIOs
* in this example it would be just 1 led on each
* if GWLED_PIN2 is not defined both leds are assumed to be connected to one GPIO
* the second LED will always have r<->g swapped to see that they are working independently
*/
#ifdef GWLED_PIN2
//send the 2 leds separately to different gpios
sendToLeds(logger,GWLED_PIN,1,leds,bus,device);
sendToLeds(logger,GWLED_PIN2,1,&leds[1],bus,device);
#else
sendToLeds(logger,GWLED_PIN,NUMLEDS,leds,bus,device);
#endif
currentMode = newMode;
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/ledspitask/GwLedSpiTask.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
//task function
void handleSpiLeds(GwApi *param);

DECLARE_USERTASK(handleSpiLeds);
DECLARE_USERTASK_PARAM(handleSpiLeds,4000);
#endif
1 change: 1 addition & 0 deletions lib/ledspitask/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ build_flags =
-D BOARD_HOMBERGER
-D GWLED_SPI=1
-D GWLED_PIN=33
-D GWLED_PIN2=32
${env.build_flags}
upload_port = /dev/esp32
upload_protocol = esptool

0 comments on commit 52b25dd

Please sign in to comment.