esp32-nmea2000-obp60/lib/queue/obp60task/LedSpiTask.cpp

232 lines
7.4 KiB
C++

#include <FreeRTOS.h>
#include "LedSpiTask.h"
#include "GwHardware.h"
#include "GwApi.h"
#include <driver/spi_master.h>
#include <driver/gpio.h>
#include <esp_rom_gpio.h>
#include <soc/spi_periph.h>
#include "ColorTo3Byte.h"
/*
controlling some WS2812 using SPI
https://controllerstech.com/ws2812-leds-using-spi/
*/
static uint8_t mulcolor(uint8_t f1, uint8_t f2){
uint16_t rt=f1;
rt*=(uint16_t)f2;
return rt >> 8;
}
Color setBrightness(const Color &color,uint8_t brightness){
uint16_t br255=brightness*255;
br255=br255/100;
//very simple for now
Color rt=color;
rt.g=mulcolor(rt.g,br255);
rt.b=mulcolor(rt.b,br255);
rt.r=mulcolor(rt.r,br255);
return rt;
}
static void colorCompTo3Byte(uint8_t comp,uint8_t *buffer){
for (int i=0;i<3;i++){
*(buffer+i)=colorTo3Byte[comp][i];
}
}
//depending on LED strip - handle color order
static size_t ledsToBuffer(int numLeds,const Color *leds,uint8_t *buffer){
uint8_t *p=buffer;
for (int i=0;i<numLeds;i++){
colorCompTo3Byte(leds[i].g,p);
p+=3;
colorCompTo3Byte(leds[i].r,p);
p+=3;
colorCompTo3Byte(leds[i].b,p);
p+=3;
}
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){
LOG_DEBUG(GwLog::ERROR,"unable to set gpio mode for %d: %d",pin,(int)err);
return false;
}
err=gpio_set_level((gpio_num_t)pin,0);
if (err != ESP_OK){
LOG_DEBUG(GwLog::ERROR,"unable to set gpio level for %d: %d",pin,(int)err);
return false;
}
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;
}
//slightly speed up the transactions
//as we are the only ones using the bus we can safely acquire it forever
err=spi_device_acquire_bus(*device,portMAX_DELAY);
if (err != ESP_OK){
LOG_DEBUG(GwLog::ERROR,"unable to acquire 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)
{
//need to send a long reset before
//as on S3 MOSI is high on idle on older frameworks
//see https://github.com/espressif/esp-idf/issues/13974
const int zeroprefix=80; //3.2us per byte
bool ownsBuffer = false;
size_t bufferSize = numLeds * 3 * 3+zeroprefix;
if (buffer == NULL)
{
ownsBuffer = true;
buffer = (uint8_t *)heap_caps_malloc(bufferSize, MALLOC_CAP_DMA|MALLOC_CAP_32BIT);
if (!buffer)
{
LOG_DEBUG(GwLog::ERROR, "unable to allocate %d bytes of DMA buffer", (int)bufferSize);
return false;
}
}
bool rv = true;
for (int i=0;i<zeroprefix;i++)buffer[i]=0;
ledsToBuffer(numLeds, leds, buffer+zeroprefix);
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(void *param){
LedTaskData *taskData=(LedTaskData*)param;
GwLog *logger=taskData->api->getLogger();
LOG_DEBUG(GwLog::ERROR,"spi led task initialized");
spi_host_device_t bus=SPI3_HOST;
bool spiValid=false;
LOG_DEBUG(GwLog::ERROR,"SpiLed task started");
if (! prepareGpio(logger,OBP_FLASH_LED)){
EXIT_TASK;
}
if (! prepareGpio(logger,OBP_BACKLIGHT_LED)){
EXIT_TASK;
}
spi_device_handle_t device;
if (! prepareSpi(logger,bus,&device)){
EXIT_TASK;
}
bool first=true;
LedInterface current;
while (true)
{
LedInterface newLeds=taskData->getLedData();
if (first || current.backlightChanged(newLeds) || current.flasChanged(newLeds)){
first=false;
LOG_DEBUG(GwLog::ERROR,"handle SPI leds");
if (current.backlightChanged(newLeds) || first){
LOG_DEBUG(GwLog::ERROR,"setting backlight r=%02d,g=%02d,b=%02d",
newLeds.backlight[0].r,newLeds.backlight[0].g,newLeds.backlight[0].b);
sendToLeds(logger,OBP_BACKLIGHT_LED,newLeds.backlightLen(),newLeds.backlight,bus,device);
}
if (current.flasChanged(newLeds) || first){
LOG_DEBUG(GwLog::ERROR,"setting flashr=%02d,g=%02d,b=%02d",
newLeds.flash[0].r,newLeds.flash[0].g,newLeds.flash[0].b);
sendToLeds(logger,OBP_FLASH_LED,newLeds.flashLen(),newLeds.flash,bus,device);
}
current=newLeds;
}
delay(50);
}
vTaskDelete(NULL);
}
void createSpiLedTask(LedTaskData *param){
xTaskCreate(handleSpiLeds,"handleLeds",4000,param,3,NULL);
}