New led control

This commit is contained in:
Martin Bauer
2021-11-16 22:04:22 +01:00
parent 6a5d4c463c
commit c68114dc4c
13 changed files with 961 additions and 19 deletions

View File

@@ -0,0 +1,105 @@
#include "drivers/Esp32DriverRGBW.h"
#include <driver/gpio.h>
#include <driver/rmt.h>
// Timing constants
static constexpr uint16_t DIVIDER = 4;
static constexpr double RMT_DURATION_NS = 12.5; // Minimum time of a single RMT duration based on clock ns
static constexpr uint32_t T0H = 300;
static constexpr uint32_t T0L = 900;
static constexpr uint32_t T1H = 600;
static constexpr uint32_t T1L = 600;
static constexpr uint32_t TRS = 80000;
static constexpr rmt_item32_t bit0Data = {uint32_t(T0H / (RMT_DURATION_NS * DIVIDER)), 1, uint32_t(T0L / (RMT_DURATION_NS * DIVIDER)), 0};
static constexpr rmt_item32_t bit1Data = {uint32_t(T1H / (RMT_DURATION_NS * DIVIDER)), 1, uint32_t(T1L / (RMT_DURATION_NS * DIVIDER)), 0};
// Function registered at the RMT driver that converts regular uint8_t values containing r,g,b,w values
// to rmt_item32_t for each bit. (8 bit -> 32 * 8 bit)
static void IRAM_ATTR uint8ToRmtAdaptor(const void *src, rmt_item32_t *dest, size_t srcSize,
size_t wantedNum, size_t *translatedSize, size_t *itemNum)
{
if (src == NULL || dest == NULL)
{
*translatedSize = 0;
*itemNum = 0;
return;
}
size_t size = 0;
size_t num = 0;
uint8_t *psrc = (uint8_t *)src;
rmt_item32_t *pdest = dest;
while (size < srcSize && num < wantedNum)
{
for (int i = 0; i < 8; i++)
{
// MSB first
const bool isBitSet = *psrc & (1 << (7 - i));
pdest->val = isBitSet ? bit1Data.val : bit0Data.val;
num++;
pdest++;
}
size++;
psrc++;
}
*translatedSize = size;
*itemNum = num;
}
void Esp32DriverRGBW::begin(int gpio, int rmtChannel)
{
rmtChannel_ = rmtChannel;
transmitting_ = false;
rmt_config_t rmt_tx;
rmt_tx.channel = static_cast<rmt_channel_t>(rmtChannel);
rmt_tx.gpio_num = static_cast<gpio_num_t>(gpio);
rmt_tx.rmt_mode = RMT_MODE_TX;
rmt_tx.mem_block_num = 1;
rmt_tx.clk_div = DIVIDER;
rmt_tx.tx_config.loop_en = false;
rmt_tx.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
rmt_tx.tx_config.carrier_en = false;
rmt_tx.tx_config.idle_level = RMT_IDLE_LEVEL_LOW;
rmt_tx.tx_config.idle_output_en = true;
ESP_ERROR_CHECK(rmt_config(&rmt_tx));
ESP_ERROR_CHECK(rmt_driver_install(rmt_tx.channel, 0, 0));
rmt_translator_init((rmt_channel_t)rmtChannel, uint8ToRmtAdaptor);
}
void Esp32DriverRGBW::end()
{
ESP_ERROR_CHECK(rmt_driver_uninstall((rmt_channel_t)rmtChannel_));
}
void Esp32DriverRGBW::writeSync(const uint32_t *rgbwData, int numLeds)
{
waitForTransmissionToFinish();
auto data = reinterpret_cast<const uint8_t *>(rgbwData);
ESP_ERROR_CHECK(rmt_write_sample((rmt_channel_t)rmtChannel_, data, numLeds * 4, true));
}
void Esp32DriverRGBW::writeAsync(const uint32_t *rgbwData, int numLeds)
{
waitForTransmissionToFinish();
auto data = reinterpret_cast<const uint8_t *>(rgbwData);
ESP_ERROR_CHECK(rmt_write_sample((rmt_channel_t)rmtChannel_, data, numLeds * 4, false));
transmitting_ = true;
}
bool Esp32DriverRGBW::waitForTransmissionToFinish(int waitMs)
{
if (!transmitting_)
return true;
auto ret = rmt_wait_tx_done((rmt_channel_t)rmtChannel_, waitMs / portTICK_PERIOD_MS);
if (ret == ESP_OK)
{
transmitting_ = false;
return true;
}
else
return false;
}

View File

@@ -0,0 +1,55 @@
// overall layout:
// queue for each led stripe
//
enum class EffectID
{
OFF,
STATIC,
CIRCLE,
CIRCLE_WAVE,
COLOR_FADE,
RAINBOW_FADE,
};
struct EffectCircularConfig
{
float speed; // in degrees per second
float width; // width in degrees
float brightnessFalloffFactor;
};
template <typename TLedStrip>
class AbstractEffect
{
public:
virtual int operator()(TLedStrip &s) = 0;
};
template <typename TLedStrip>
class EffectCircle : public AbstractEffect<TLedStrip>
{
public:
EffectCircle(const EffectCircularConfig &cfg) : config_(cfg) {}
int operator()(TLedStrip &s) override;
private:
EffectCircularConfig config_;
float currentPosition_; // between 0 and 1
};
unsigned char effectStorage[128];
template <typename TLedStrip>
AbstractEffect<TLedStrip> *makeEffect(const char *buffer)
{
const EffectID &effectId = *reinterpret_cast<const EffectID *>(buffer);
if (effectId == EffectID::CIRCLE)
{
auto cfg = reinterpret_cast<const EffectCircularConfig *>(buffer + sizeof(EffectID));
return new (effectStorage) EffectCircle<TLedStrip>(*cfg);
}
// read effect id code from buffer
// read config from buffer
}

View File

@@ -0,0 +1,83 @@
/// a static array of red-green-blue-white values together with free functions to set them
#pragma once
#include "helpers/ColorRGBW.h"
#include "Arduino.h" // TODO
#include <cstdint>
template <int TNumLeds>
class LedStripRGBW
{
public:
static constexpr int NUM_LEDS = TNumLeds;
void set(int idx, uint8_t r, uint8_t g, uint8_t b, uint8_t w)
{
// green: 0
// red: 8
// blue: 16
// white: 24
if (idx < 0 || idx >= NUM_LEDS)
Serial.printf("Out of bounds idx %i\n", idx);
else
data_[idx] = (g << 0) | (r << 8) | (b << 16) | (w << 24);
}
const uint32_t *rawData() const { return data_; }
constexpr static int numLeds() { return TNumLeds; }
private:
uint32_t data_[TNumLeds];
};
template <typename TLedStrip>
constexpr int numLeds()
{
return TLedStrip::NUM_LEDS;
}
template <int TNumLeds>
constexpr int numLeds(const LedStripRGBW<TNumLeds> &)
{
return TNumLeds;
}
template <int TNumLeds>
void setLedRGB(LedStripRGBW<TNumLeds> &s, int beginIdx, int endIdx, uint8_t r, uint8_t g, uint8_t b)
{
for (int i = beginIdx; i < endIdx; ++i)
s.set(i, r, g, b, 0);
}
template <int TNumLeds>
void setLedRGB(LedStripRGBW<TNumLeds> &s, int idx, uint8_t r, uint8_t g, uint8_t b)
{
s.set(idx, r, g, b, 0);
}
template <int TNumLeds>
void setLedRGBW(LedStripRGBW<TNumLeds> &s, int beginIdx, int endIdx, uint8_t r, uint8_t g, uint8_t b, uint8_t w)
{
for (int i = beginIdx; i < endIdx; ++i)
s.set(i, r, g, b, w);
}
template <int TNumLeds>
void setLedRGBW(LedStripRGBW<TNumLeds> &s, int idx, uint8_t r, uint8_t g, uint8_t b, uint8_t w)
{
s.set(idx, r, g, b, w);
}
template <int TNumLeds>
void setLedRGBW(LedStripRGBW<TNumLeds> &s, int idx, const ColorRGBW &c)
{
s.set(idx, c.r, c.g, c.b, c.w);
}
template <int TNumLeds>
void clear(LedStripRGBW<TNumLeds> &s)
{
for (int i = 0; i < TNumLeds; ++i)
s.set(i, 0, 0, 0, 0);
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include "containers/LedStripRGBW.h"
class Esp32DriverRGBW
{
public:
void begin(int gpio, int rmtChannel);
void end();
void writeSync(const uint32_t *rgbwData, int numLeds);
void writeAsync(const uint32_t *rgbwData, int numLeds);
bool waitForTransmissionToFinish(int waitMs = 1000);
private:
int rmtChannel_;
bool transmitting_;
};

View File

@@ -0,0 +1,74 @@
#pragma once
#include "effects/Common.h"
#include "helpers/ColorRGBW.h"
#include "helpers/BellCurve.h"
struct EffectCircularConfig
{
float speed; // in degrees per second
float width; // width in degrees
ColorRGBW color;
};
template <typename TLedStrip>
class EffectCircular
{
public:
static constexpr auto NUM_LEDS = numLeds<TLedStrip>();
static constexpr int DELAY_MS = 10;
EffectCircular(const EffectCircularConfig &cfg, TLedStrip &ledStrip)
: config_(cfg),
ledStrip_(ledStrip),
currentPosition_(0),
widthInLeds_((numLeds(ledStrip) * cfg.width / 360)),
invWidth_(1.0f / widthInLeds_)
{
}
static constexpr int normalizeIdx(int idx)
{
return (idx < 0) ? (idx + NUM_LEDS) : (idx >= NUM_LEDS ? idx - NUM_LEDS : idx);
}
int operator()()
{
int startLed = int(currentPosition_);
float distDown = currentPosition_ - float(startLed);
float distUp = 1.f - distDown;
clear(ledStrip_);
// center
setLedRGBW(ledStrip_, normalizeIdx(startLed),
config_.color * bellCurveApproximation(distDown, invWidth_));
// down
for (int i = 1; i < widthInLeds_ / 2 + 1; ++i)
{
setLedRGBW(ledStrip_, normalizeIdx(startLed - i),
config_.color * bellCurveApproximation(distDown + i, invWidth_));
}
// up
for (int i = 1; i < widthInLeds_ / 2 + 1; ++i)
{
setLedRGBW(ledStrip_, normalizeIdx(startLed + i),
config_.color * bellCurveApproximation(distUp + i - 1, invWidth_));
}
currentPosition_ += config_.speed / 1000 / 360 * NUM_LEDS * DELAY_MS;
if (currentPosition_ > NUM_LEDS)
currentPosition_ -= NUM_LEDS;
//Serial.printf("Current pos %f led %d width %d\n", currentPosition_, normalizeIdx(startLed), widthInLeds_ / 2 + 1);
return DELAY_MS;
}
private:
EffectCircularConfig config_;
TLedStrip &ledStrip_;
float currentPosition_; // between 0 and num leds
int widthInLeds_;
float invWidth_;
};

View File

@@ -0,0 +1,17 @@
enum class EffectID
{
OFF,
STATIC,
CIRCULAR,
CIRCLE_WAVE,
COLOR_FADE,
RAINBOW_FADE,
};
template <typename TLedStrip>
class AbstractEffect
{
public:
virtual int operator()(TLedStrip &s) = 0;
};

View File

@@ -0,0 +1,17 @@
#include <cstdint>
static inline float bellCurveApproximation(float x, float inverseWidth)
{
if (x < 0)
x = -x;
const auto nx = x * inverseWidth * 4;
if (nx > 2)
return 0.0f;
const auto x2 = nx * nx;
const auto x3 = x2 * nx;
const auto res = 1.0f + 0.27606958941084f * x3 - 0.80213917882168f * x2;
return res < 0.0f ? 0.0f : res;
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include <cstdint>
struct ColorRGBW
{
uint8_t r, g, b, w;
ColorRGBW operator*(float s) const
{
return {uint8_t(s * r),
uint8_t(s * g),
uint8_t(s * b),
uint8_t(s * w)};
}
};