Compare commits

6 Commits

Author SHA1 Message Date
36788ac5a6 comments and update readme 2026-02-14 21:33:55 -06:00
335452cc6d simplify driver to be stateless 2026-02-14 21:09:15 -06:00
532d93d5d7 reorganize project structure 2026-02-14 19:05:26 -06:00
e3090bf1f5 flesh out wsled interface 2026-02-14 13:19:41 -06:00
99e73d5d6b Merge branch 'feature/ssd-driver' 2026-02-14 12:04:24 -06:00
Preston McGee
1248a0ac39 Merge pull request #2 from Blitblank/feature/ssd-driver
Feature/ssd driver
2025-12-14 18:58:12 -06:00
25 changed files with 195 additions and 133 deletions

View File

@@ -5,7 +5,11 @@ set(PROJECT_MAIN_COMPONENT ${CMAKE_CURRENT_LIST_DIR}/src)
set(EXTRA_COMPONENT_DIRS set(EXTRA_COMPONENT_DIRS
${CMAKE_CURRENT_LIST_DIR}/config ${CMAKE_CURRENT_LIST_DIR}/config
${CMAKE_CURRENT_LIST_DIR}/src ${CMAKE_CURRENT_LIST_DIR}/src/drivers
${CMAKE_CURRENT_LIST_DIR}/src/main
${CMAKE_CURRENT_LIST_DIR}/src/tasks
${CMAKE_CURRENT_LIST_DIR}/src/ssd
${CMAKE_CURRENT_LIST_DIR}/src/wsled
) )
# For the esp-idf configuration # For the esp-idf configuration

View File

@@ -13,8 +13,8 @@ Scripts are in scripts directory, named appropriately.
- [x] Build setup, get working hello-world program. - [x] Build setup, get working hello-world program.
- [x] Develop 7-segment display driver for the soburg-v2 board, which uses 32-bit shift registers. - [x] Develop 7-segment display driver for the soburg-v2 board, which uses 32-bit shift registers.
Ensures functional hardware interaction and interface design. Ensures functional hardware interaction and interface design.
- [ ] Develop WS2812b driver, would be nice to have some kind of testing for this. - [x] Develop WS2812b driver, would be nice to have some kind of testing for this.
- [ ] Develop WS2812b interface. I like the FastLed library for Arduino so might have a lot of similar functionality. - [x] Develop WS2812b interface. I like the FastLed library for Arduino so might have a lot of similar functionality.
- [ ] Implement proper app structure, with error checking and performance profiling - [ ] Implement proper app structure, with error checking and performance profiling
- [ ] Add a couple LED effects- the random blink and rainbow sweep are easy ones. - [ ] Add a couple LED effects- the random blink and rainbow sweep are easy ones.
- [ ] Create a mock LED panel that executes natively to test LED effects outside of hardware. (SDL2 or something) - [ ] Create a mock LED panel that executes natively to test LED effects outside of hardware. (SDL2 or something)
@@ -35,3 +35,11 @@ After cloning: $ ./scripts/repo-setup # installs esp-idf, sets up target configu
To build: $ ./scripts/build.sh\ To build: $ ./scripts/build.sh\
To flash: $ ./scripts/flash.sh # note: flash.sh automatically builds\ To flash: $ ./scripts/flash.sh # note: flash.sh automatically builds\
To monitor: $ ./scripts/monitor.sh\ To monitor: $ ./scripts/monitor.sh\
## TODOs for developers
- [ ] Move all hardware specifications to config/Kconfig.projbuild file. (things like NUM_LEDS, pins.h, ssd_digits, etc.)
- [ ] Create an effect base class for effects to inherit from
- [ ] Attach interrupts for hardware inputs
- [ ] Human I/O (like from buttons, knobs) needs to be in its own io task. Other tasks will be injected with a general I/O interface
- [ ] Investigate putting the wsled task on a separate core from io and such
- [ ] Wsled interface needs a platform for matrix creation

View File

@@ -1,53 +0,0 @@
#include "DemoTask.hpp"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "SsdInterface.hpp"
#include "pins.hpp"
#include "drivers/wsled.h"
DemoTask::DemoTask() {
}
void DemoTask::run() {
ESP_LOGI(__FILE__, "Demo Task: run()");
// ssd device
ssd_595_t ssdDev = { gpio_ssd_data, gpio_ssd_clk, gpio_ssd_latch, true };
SsdInterface ssd(&ssdDev, ssdDigits);
// wsled device
size_t ledCount = 4;
wsled_t wsledDev = { gpio_ws2812b, WS2812B, ledCount};
// TODO: wsled interface
CRGB ledBuffer[4];
wsledInit(&wsledDev, (CRGB**)&ledBuffer);
uint32_t delay = 500; // ms
uint8_t digit = 0;
while(1) {
ssd.writeRaw(&digitMap[digit], 1);
digit++;
if(digit >= 16) digit = 0;
//ledBuffer[0] = CRGB{100, 0, 80};
wsledFill(CRGB{100, 20, 20});
wsledUpdate();
vTaskDelay(delay / portTICK_PERIOD_MS);
//ledBuffer[0] = CRGB{0, 0, 10};
wsledFill(CRGB{20, 20, 100});
wsledUpdate();
vTaskDelay(delay / portTICK_PERIOD_MS);
}
}

View File

@@ -1,21 +0,0 @@
#include "WsledInterface.hpp"
WsledInterface::WsledInterface(const wsled_t* device) {
}
STATUS WsledInterface::writePixel(CRGB pixel, size_t index) {
return OKAY;
}
STATUS WsledInterface::get(CRGB* pixel, size_t index) {
return OKAY;
}
STATUS WsledInterface::flush() {
return OKAY;
}

View File

@@ -2,13 +2,11 @@
file(GLOB SRC_FILES file(GLOB SRC_FILES
"*.c" "*.c"
"*.cpp" "*.cpp"
"drivers/*.c"
) )
idf_component_register( idf_component_register(
SRCS ${SRC_FILES} SRCS ${SRC_FILES}
PRIV_REQUIRES spi_flash PRIV_REQUIRES spi_flash
REQUIRES esp_driver_gpio REQUIRES esp_driver_gpio esp_driver_spi
REQUIRES esp_driver_spi
INCLUDE_DIRS "." INCLUDE_DIRS "."
) )

View File

@@ -8,12 +8,8 @@
extern "C" { extern "C" {
#endif #endif
uint16_t* dmaBuffer; static uint16_t* dmaBuffer;
CRGB* wsledPixels;
static uint32_t ledCount;
static uint32_t resetDelay;
static size_t dmaBufferSize; static size_t dmaBufferSize;
WsledType type;
static spi_settings_t spiSettings = { static spi_settings_t spiSettings = {
.host = SPI2_HOST, .host = SPI2_HOST,
@@ -37,63 +33,44 @@ static spi_settings_t spiSettings = {
}, },
}; };
// translations from SPI -> ws2812b protocol
// [spi] 0b1110 = [ws2812b]0b1 and [spi] 0b1000 = [ws2812b]0b0
static const uint16_t timingBits[16] = { static const uint16_t timingBits[16] = {
0x1111, 0x7111, 0x1711, 0x7711, 0x1171, 0x7171, 0x1771, 0x7771, 0x1111, 0x7111, 0x1711, 0x7711, 0x1171, 0x7171, 0x1771, 0x7771,
0x1117, 0x7117, 0x1717, 0x7717, 0x1177, 0x7177, 0x1777, 0x7777}; 0x1117, 0x7117, 0x1717, 0x7717, 0x1177, 0x7177, 0x1777, 0x7777};
esp_err_t wsledInit(wsled_t* dev, CRGB** buffer) { // WS2812b can handle shorter reset delays than the WS2815
static inline uint32_t resetDelay(const wsled_t* dev) {
return (dev->type == WS2812B) ? WSLED_12_RESET_TIME : WSLED_15_RESET_TIME;
}
ESP_LOGI(__FILE__, "Initializing wsled device..."); esp_err_t wsledInit(const wsled_t* dev) {
type = dev->type;
ledCount = dev->numLeds;
resetDelay = (dev->type == WS2812B) ? WSLED_12_RESET_TIME : WSLED_15_RESET_TIME;
ESP_LOGI(__FILE__, "mallocing the wsledPixel buffer with size %u bytes", sizeof(CRGB) * ledCount);
// 12 bytes for each led + bytes for initial zero and reset state // 12 bytes for each led + bytes for initial zero and reset state
dmaBufferSize = ledCount * 12 + (resetDelay + 1) * 2; dmaBufferSize = dev->numLeds * 12 + (resetDelay(dev) + 1) * 2;
wsledPixels = malloc(sizeof(CRGB) * ledCount);
if (wsledPixels == NULL) {
ESP_LOGI(__FILE__, "Allocating memory failed");
return ESP_ERR_NO_MEM;
}
*buffer = wsledPixels;
spiSettings.buscfg.mosi_io_num = dev->pin; spiSettings.buscfg.mosi_io_num = dev->pin;
spiSettings.buscfg.max_transfer_sz = dmaBufferSize; spiSettings.buscfg.max_transfer_sz = dmaBufferSize;
ESP_LOGI(__FILE__, "Initializing spi interface...");
if (ESP_OK != spi_bus_initialize(spiSettings.host, &spiSettings.buscfg, spiSettings.dma_chan)) { if (ESP_OK != spi_bus_initialize(spiSettings.host, &spiSettings.buscfg, spiSettings.dma_chan)) {
free(wsledPixels);
ESP_LOGI(__FILE__, "SPI initialization failed"); ESP_LOGI(__FILE__, "SPI initialization failed");
return -1; return -1;
} }
ESP_LOGI(__FILE__, "Adding spi bus device...");
if (ESP_OK != spi_bus_add_device(spiSettings.host, &spiSettings.devcfg, &spiSettings.spi)) { if (ESP_OK != spi_bus_add_device(spiSettings.host, &spiSettings.devcfg, &spiSettings.spi)) {
free(wsledPixels);
ESP_LOGI(__FILE__, "Failed to add spi bus device"); ESP_LOGI(__FILE__, "Failed to add spi bus device");
return -1; return -1;
} }
ESP_LOGI(__FILE__, "heap_caps_malloc() with dmaBufferSize=%u...", dmaBufferSize);
dmaBuffer = heap_caps_malloc(dmaBufferSize, MALLOC_CAP_DMA); dmaBuffer = heap_caps_malloc(dmaBufferSize, MALLOC_CAP_DMA);
if (NULL == dmaBuffer) { if (NULL == dmaBuffer) {
free(wsledPixels);
ESP_LOGI(__FILE__, "Failed to heap_caps_malloc"); ESP_LOGI(__FILE__, "Failed to heap_caps_malloc");
return -1; return -1;
} }
return ESP_OK; return ESP_OK;
} }
esp_err_t wsledFill(CRGB color) { esp_err_t wsledUpdate(const wsled_t* dev, const CRGB* pixels, size_t ledCount) {
for (int i = 0; i < ledCount; i++) {
wsledPixels[i] = color;
}
return 0;
}
esp_err_t wsledUpdate() {
uint32_t n = 0; uint32_t n = 0;
@@ -102,10 +79,10 @@ esp_err_t wsledUpdate() {
for (int i = 0; i < ledCount; i++) { for (int i = 0; i < ledCount; i++) {
CRGB currentPixel = wsledPixels[i]; CRGB currentPixel = pixels[i];
uint8_t b0 = (type == WS2812B) ? currentPixel.g : currentPixel.r; uint8_t b0 = (dev->type == WS2812B) ? currentPixel.g : currentPixel.r;
uint8_t b1 = (type == WS2812B) ? currentPixel.r : currentPixel.g; uint8_t b1 = (dev->type == WS2812B) ? currentPixel.r : currentPixel.g;
uint8_t b2 = currentPixel.b; uint8_t b2 = currentPixel.b;
// Green // Green
@@ -123,7 +100,7 @@ esp_err_t wsledUpdate() {
} }
// reset pulse // reset pulse
for (int i = 0; i < resetDelay; i++) { for (int i = 0; i < resetDelay(dev); i++) {
dmaBuffer[n++] = 0; dmaBuffer[n++] = 0;
} }

View File

@@ -6,7 +6,7 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#define WSLED_12_RESET_TIME 30 #define WSLED_12_RESET_TIME 5
#define WSLED_15_RESET_TIME 30 #define WSLED_15_RESET_TIME 30
#ifdef __cplusplus #ifdef __cplusplus
@@ -38,12 +38,11 @@ typedef struct {
uint32_t numLeds; uint32_t numLeds;
} wsled_t; } wsled_t;
esp_err_t wsledInit(wsled_t* dev, CRGB** buffer); // initializes the wsled device provided the device settings
esp_err_t wsledInit(const wsled_t* dev);
// test function // outputs the pixel data in pixels to the device
esp_err_t wsledFill(CRGB color); esp_err_t wsledUpdate(const wsled_t* dev, const CRGB* pixels, size_t ledCount);
esp_err_t wsledUpdate();
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@@ -7,7 +7,7 @@
#include "esp_log.h" #include "esp_log.h"
#include "sdkconfig.h" #include "sdkconfig.h"
#include "pins.hpp" #include "shared/pins.h"
#include "DemoTask.hpp" #include "DemoTask.hpp"

View File

@@ -3,6 +3,7 @@
#include<stdint.h> #include<stdint.h>
// This class is for managing tasks
class App { class App {
public: public:
@@ -13,9 +14,7 @@ public:
uint32_t main(); uint32_t main();
private: private:
uint32_t ledState = 0;
uint32_t blinkTime = 250;
const char *TAG = "app"; // TODO: instead of this for logging you can use __FILE__ or __func__ const char *TAG = "app"; // TODO: instead of this for logging you can use __FILE__ or __func__
}; };

10
src/main/CMakeLists.txt Normal file
View File

@@ -0,0 +1,10 @@
file(GLOB SRC_FILES
"*.c"
"*.cpp"
)
idf_component_register(
SRCS ${SRC_FILES}
INCLUDE_DIRS "." ".."
)

12
src/ssd/CMakeLists.txt Normal file
View File

@@ -0,0 +1,12 @@
file(GLOB SRC_FILES
"*.c"
"*.cpp"
)
idf_component_register(
SRCS ${SRC_FILES}
PRIV_REQUIRES spi_flash
REQUIRES esp_driver_gpio esp_driver_spi
INCLUDE_DIRS "." ".."
)

View File

@@ -4,7 +4,7 @@
#include "stdint.h" #include "stdint.h"
#include "drivers/ssd.h" #include "drivers/ssd.h"
#include "common.h" #include "shared/common.h"
class SsdInterface { class SsdInterface {

12
src/tasks/CMakeLists.txt Normal file
View File

@@ -0,0 +1,12 @@
file(GLOB SRC_FILES
"*.c"
"*.cpp"
)
idf_component_register(
SRCS ${SRC_FILES}
PRIV_REQUIRES spi_flash
REQUIRES esp_driver_gpio esp_driver_spi
INCLUDE_DIRS "." ".."
)

46
src/tasks/DemoTask.cpp Normal file
View File

@@ -0,0 +1,46 @@
#include "DemoTask.hpp"
#include "esp_log.h"
#include "ssd/SsdInterface.hpp"
#include "wsled/WsledInterface.hpp"
#include "shared/pins.h"
#define NUM_LEDS 4
DemoTask::DemoTask() {
}
void DemoTask::run() {
ESP_LOGI(__FILE__, "Demo Task: run()");
// ssd device
ssd_595_t ssdDev = { gpio_ssd_data, gpio_ssd_clk, gpio_ssd_latch, true };
SsdInterface ssd(&ssdDev, ssdDigits);
// wsled device
wsled_t wsledDev = { gpio_ws2812b, WS2812B, NUM_LEDS};
WsledInterface wsled(&wsledDev);
uint32_t delay = 500; // ms
uint8_t digit = 0;
while(1) {
// the SSD demo shifts a single hex digit over
ssd.writeRaw(&digitMap[digit], 1);
digit++;
if(digit >= 16) digit = 0;
// the wsled demo toggles the entire array between red and blue
CRGB color = (digit % 2) ? CRGB{100, 20, 20} : CRGB{20, 20, 100};
wsled.fill(color);
wsled.flush();
vTaskDelay(delay / portTICK_PERIOD_MS);
}
}

12
src/wsled/CMakeLists.txt Normal file
View File

@@ -0,0 +1,12 @@
file(GLOB SRC_FILES
"*.c"
"*.cpp"
)
idf_component_register(
SRCS ${SRC_FILES}
PRIV_REQUIRES spi_flash
REQUIRES esp_driver_gpio esp_driver_spi
INCLUDE_DIRS "." ".."
)

View File

@@ -0,0 +1,50 @@
#include "WsledInterface.hpp"
WsledInterface::WsledInterface(const wsled_t* device) : device_(device), numLeds_(device->numLeds) {
(void)wsledInit(device);
// who cares if its dynamically allocated we have like 4 Mb of ram
// an led strip 1024 long will use 3kb here and 12kb in the driver for dma
leds_.resize(numLeds_);
// turn all leds off
(void)fill(CRGB(0, 0, 0));
(void)flush();
}
STATUS WsledInterface::writePixel(CRGB pixel, size_t index) {
// is it really that easy
leds_[index] = pixel;
return OKAY;
}
STATUS WsledInterface::get(CRGB* pixel, size_t index) {
// I could just return the pixel but im so cool and C coded
*pixel = leds_[index];
return OKAY;
}
STATUS WsledInterface::flush() {
// man I wrote my driver so well that it really is that easy
wsledUpdate(device_, leds_.data(), numLeds_);
return OKAY;
}
STATUS WsledInterface::fill(CRGB color) {
// I HATE ITERATORS YOU CAN NEVER MAKE ME USE THEM !!!!
for (size_t i = 0; i < numLeds_; i++) {
leds_[i] = color;
}
return OKAY;
}

View File

@@ -2,9 +2,10 @@
#pragma once #pragma once
#include "stdint.h" #include "stdint.h"
#include <vector>
#include "drivers/wsled.h" #include "drivers/wsled.h"
#include "common.h" #include "shared/common.h"
class WsledInterface { class WsledInterface {
@@ -27,12 +28,20 @@ public:
// Returns: execution status // Returns: execution status
STATUS flush(); STATUS flush();
// Below are the helper functions for manipulating the led buffer
// Fills the buffer with a single color
// Returns: execution status
STATUS fill(CRGB color);
// getter for numLeds_
size_t ledCount() { return numLeds_; }
private: private:
const wsled_t* device_; const wsled_t* device_;
size_t numLeds_;
static constexpr size_t numLeds_ = 4; std::vector<CRGB> leds_;
CRGB leds_[numLeds_];
}; };

View File