diff --git a/components/badge23/CMakeLists.txt b/components/badge23/CMakeLists.txt
index 84f8a3084eab6174be7641dd9bd74b6580868f96..ad46a42657c98be4b317a9e471730ff4582a302b 100644
--- a/components/badge23/CMakeLists.txt
+++ b/components/badge23/CMakeLists.txt
@@ -1,15 +1,13 @@
 idf_component_register(
     SRCS
-        apa102LEDStrip.c
         captouch.c
         espan.c
-        leds.c
         spio.c
+        leds.c
     INCLUDE_DIRS
         include
     REQUIRES
         flow3r_bsp
         st3m
-        espressif__led_strip
         bl00mbox
 )
diff --git a/components/badge23/apa102LEDStrip.c b/components/badge23/apa102LEDStrip.c
deleted file mode 100644
index dceba6a6417b25f10428b21dd3b89ea99ee875dd..0000000000000000000000000000000000000000
--- a/components/badge23/apa102LEDStrip.c
+++ /dev/null
@@ -1,50 +0,0 @@
-#include "badge23/apa102LEDStrip.h"
-#include <math.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-void initLEDs(struct apa102LEDStrip *ledObject, short int numLEDs, unsigned char bytesPerLED, unsigned char globalBrightness)
-{  
-  ledObject->_numLEDs = numLEDs;
-  ledObject->_bytesPerLED = bytesPerLED;
-  ledObject->_endFrameLength = 1;//round( (numLEDs/2)/8 );
-  ledObject->_frameLength = (1+numLEDs+ledObject->_endFrameLength)*bytesPerLED;
-  ledObject->_globalBrightness = globalBrightness;
-  ledObject->LEDs = (unsigned char *)malloc(ledObject->_frameLength*sizeof(unsigned char)); 
-  
-  //Start Frame
-  ledObject->LEDs[0] = 0;
-  ledObject->LEDs[1] = 0;
-  ledObject->LEDs[2] = 0;
-  ledObject->LEDs[3] = 0;
-  //Driver frame+PIXEL frames
-  for(ledObject->_counter=ledObject->_bytesPerLED; ledObject->_counter<ledObject->_frameLength-(ledObject->_endFrameLength*ledObject->_bytesPerLED); ledObject->_counter+=ledObject->_bytesPerLED)
-  {
-    ledObject->LEDs[ledObject->_counter] = ledObject->_globalBrightness;
-    ledObject->LEDs[ledObject->_counter+1] = 0;
-    ledObject->LEDs[ledObject->_counter+2] = 0;
-    ledObject->LEDs[ledObject->_counter+3] = 0;
-  }
-  //END frames
-  for(ledObject->_counter=ledObject->_frameLength-(ledObject->_endFrameLength*ledObject->_bytesPerLED); ledObject->_counter<ledObject->_frameLength; ledObject->_counter+=ledObject->_bytesPerLED)
-  {
-    ledObject->LEDs[ledObject->_counter] = 255;
-    ledObject->LEDs[ledObject->_counter+1] = 255;
-    ledObject->LEDs[ledObject->_counter+2] = 255;
-    ledObject->LEDs[ledObject->_counter+3] = 255;
-  }
-}
-void setPixel(struct apa102LEDStrip *ledObject, short int pixelIndex, unsigned char *pixelColour)
-{
-  ledObject->_counter = 4*(pixelIndex+1);
-  ledObject->LEDs[ ledObject->_counter + 1 ] = pixelColour[2];
-  ledObject->LEDs[ ledObject->_counter + 2 ] = pixelColour[1];
-  ledObject->LEDs[ ledObject->_counter + 3 ] = pixelColour[0];
-}
-void getPixel(struct apa102LEDStrip *ledObject, short int pixelIndex, unsigned char *pixelColour)
-{
-  ledObject->_counter = 4*(pixelIndex+1);
-  pixelColour[2] = ledObject->LEDs[ ledObject->_counter + 1 ];
-  pixelColour[1] = ledObject->LEDs[ ledObject->_counter + 2 ];
-  pixelColour[0] = ledObject->LEDs[ ledObject->_counter + 3 ];
-}
diff --git a/components/badge23/include/badge23/apa102LEDStrip.h b/components/badge23/include/badge23/apa102LEDStrip.h
deleted file mode 100644
index da495abf628285dedd3197462267736473c1153e..0000000000000000000000000000000000000000
--- a/components/badge23/include/badge23/apa102LEDStrip.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#ifndef DEF_apa102LEDStrip
-#define DEF_apa102LEDStrip
-
-struct apa102LEDStrip
-{
-    unsigned char *LEDs;
-    short int _frameLength;
-    short int _endFrameLength;
-    short int _numLEDs;
-    unsigned char _bytesPerLED;
-    short int _counter;
-    unsigned char _globalBrightness;
-};
-void initLEDs(struct apa102LEDStrip *ledObject, short int numLEDs, unsigned char bytesPerLED, unsigned char globalBrightness);
-void setPixel(struct apa102LEDStrip *ledObject, short int pixelIndex, unsigned char *pixelColour);
-void getPixel(struct apa102LEDStrip *ledObject, short int pixelIndex, unsigned char *pixelColour);
-#endif
diff --git a/components/badge23/leds.c b/components/badge23/leds.c
index 321910f719f3ba8b24eef8f6a7d30ca69f87ecfd..438f75ff84260cc4b0f74167698b13e6054de40f 100644
--- a/components/badge23/leds.c
+++ b/components/badge23/leds.c
@@ -8,6 +8,11 @@
 #include "badge23/leds.h"
 #include "badge23/lock.h"
 
+#include "flow3r_bsp.h"
+#include "esp_log.h"
+
+static const char *TAG = "badge23-leds";
+
 static uint8_t leds_brightness = 69;;
 static uint8_t leds_slew_rate = 255;
 static bool leds_auto_update = 0;
@@ -16,14 +21,6 @@ static uint8_t gamma_red[256];
 static uint8_t gamma_green[256];
 static uint8_t gamma_blue[256];
 
-#if defined(CONFIG_BADGE23_HW_GEN_P1)
-#define LED_SPI_PORT
-#elif defined(CONFIG_BADGE23_HW_GEN_P3) || defined(CONFIG_BADGE23_HW_GEN_P4) || defined(CONFIG_BADGE23_HW_GEN_P6)
-#define LED_ASYNC_PORT
-#else
-#error "leds not implemented for this badge generation"
-#endif
-
 typedef struct leds_cfg {
     int             leaf;
     size_t          size;
@@ -127,99 +124,18 @@ struct RGB HSVToRGB(struct HSV hsv) {
     return rgb;
 }
 
-#ifdef LED_SPI_PORT
-#include "driver/spi_master.h"
-#include "badge23/apa102LEDStrip.h"
-
-#define totalPixels 40
-#define bytesPerPixel 4
-
-static struct apa102LEDStrip leds;
-static spi_device_handle_t spi_led;
-static spi_transaction_t spiTransObject;
-static esp_err_t ret;
-static spi_bus_config_t buscfg;
-static spi_device_interface_config_t devcfg;
-
-#define maxSPIFrameInBytes 8000
-#define maxSPIFrequency 10000000
-
-void renderLEDs()
-{
-    spi_device_queue_trans(spi_led, &spiTransObject, portMAX_DELAY);
-}
-
-static int setupSPI()
-{
-    //Set up the Bus Config struct
-    buscfg.miso_io_num=-1;
-    buscfg.mosi_io_num=18;
-    buscfg.sclk_io_num=8;
-    buscfg.quadwp_io_num=-1;
-    buscfg.quadhd_io_num=-1;
-    buscfg.max_transfer_sz=maxSPIFrameInBytes;
-
-    //Set up the SPI Device Configuration Struct
-    devcfg.clock_speed_hz=maxSPIFrequency;
-    devcfg.mode=0;
-    devcfg.spics_io_num=-1;
-    devcfg.queue_size=1;
-
-    //Initialize the SPI driver
-    ret=spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
-    ESP_ERROR_CHECK(ret);
-    //Add SPI port to bus
-    ret=spi_bus_add_device(SPI2_HOST, &devcfg, &spi_led);
-    ESP_ERROR_CHECK(ret);
-    return ret;
-}
-
-static void set_single_led(uint8_t index, uint8_t c[3]){
-    setPixel(&leds, index, c);
-}
-
-static void _leds_init() {
-    memset(active_leds, 0 , sizeof(active_leds));
-
-    setupSPI();
-    initLEDs(&leds, totalPixels, bytesPerPixel, 255);
-
-    //Set up SPI tx/rx storage Object
-    memset(&spiTransObject, 0, sizeof(spiTransObject));
-    spiTransObject.length = leds._frameLength*8;
-    spiTransObject.tx_buffer = leds.LEDs;
-
-
-    //TaskHandle_t handle;
-    //xTaskCreate(&leds_task, "LEDs player", 4096, NULL, configMAX_PRIORITIES - 2, &handle);
-}
-#endif
-
-#ifdef LED_ASYNC_PORT
-#include "../../espressif__led_strip/include/led_strip.h"
-led_strip_t *led_strip_init(uint8_t channel, uint8_t gpio, uint16_t led_num);
-
-#define LED_GPIO_NUM 14
-#define LED_RMT_CHAN 0
-
-led_strip_t * led_strip;
-
-static void _leds_init(){
-    memset(active_leds, 0 , sizeof(active_leds));
-    led_strip = led_strip_init(LED_RMT_CHAN, LED_GPIO_NUM, 40);
-}
-
 void set_single_led(uint8_t index, uint8_t c[3]){
     index = ((39-index) + 1 + 32)%40;
-    led_strip->set_pixel(led_strip, index, c[0], c[1], c[2]);
+    flow3r_bsp_leds_set_pixel(index, c[0], c[1], c[2]);
 }
 
 static void renderLEDs(){
-    led_strip->refresh(led_strip, 1000);
+    esp_err_t ret = flow3r_bsp_leds_refresh(portMAX_DELAY);
+    if (ret != ESP_OK) {
+        ESP_LOGE(TAG, "LED refresh failed: %s", esp_err_to_name(ret));
+    }
 }
 
-#endif
-
 uint8_t led_get_slew(int16_t old, int16_t new, int16_t slew){
     if(new > old + slew){
         return old + slew;
@@ -309,7 +225,14 @@ void leds_init(){
         gamma_green[i] = i;
         gamma_blue[i] = i;
     }
-    _leds_init();
+
+    esp_err_t ret = flow3r_bsp_leds_init();
+    if (ret != ESP_OK) {
+        ESP_LOGE(TAG, "LED initialization failed: %s", esp_err_to_name(ret));
+        abort();
+    }
+
+    ESP_LOGI(TAG, "LEDs initialized");
 }
 
 void leds_set_brightness(uint8_t b){
diff --git a/components/espressif__led_strip/CMakeLists.txt b/components/espressif__led_strip/CMakeLists.txt
deleted file mode 100644
index fe4ca413a21fb71e5db88c15f8958a1cb5756c78..0000000000000000000000000000000000000000
--- a/components/espressif__led_strip/CMakeLists.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-idf_component_register(SRCS "led_strip_rmt_ws2812.c"
-                    INCLUDE_DIRS "include"
-                    PRIV_REQUIRES "driver"
-                    )
diff --git a/components/espressif__led_strip/README.md b/components/espressif__led_strip/README.md
deleted file mode 100644
index abf07898a573224b1c692df8cbba9a67c954a580..0000000000000000000000000000000000000000
--- a/components/espressif__led_strip/README.md
+++ /dev/null
@@ -1,16 +0,0 @@
-# LED Strip Component
-
-This directory contains an implementation for addressable LEDs using the RMT peripheral.
-
-It's compatible with:
-
-* [WS2812](http://www.world-semi.com/Certifications/WS2812B.html)
-* SK68XX
-
-This component is used as part of the following ESP-IDF examples:
-- [Blink Example](../../get-started/blink).
-- [LED Strip Example](../../peripherals/rmt/led_strip).
-
-To learn more about how to use this component, please check API Documentation from header file [led_strip.h](./include/led_strip.h).
-
-Please note that this component is not considered to be a part of ESP-IDF stable API. It may change and it may be removed in the future releases.
diff --git a/components/espressif__led_strip/idf_component.yml b/components/espressif__led_strip/idf_component.yml
deleted file mode 100644
index 8d2fc6533719f36a7c4bcfa590c8d37bb63b9ba6..0000000000000000000000000000000000000000
--- a/components/espressif__led_strip/idf_component.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-version: "1.1.0-alpha"
-description: C driver, based on RMT peripheral, for WS2812 and SK6812 smart RGB diodes
-
-dependencies:
-  # Required IDF version
-  idf:
-    version: ">=4.0"
diff --git a/components/espressif__led_strip/include/led_strip.h b/components/espressif__led_strip/include/led_strip.h
deleted file mode 100644
index dcf7a62f8a9eaab4a5ccd092eda4a62647bf2636..0000000000000000000000000000000000000000
--- a/components/espressif__led_strip/include/led_strip.h
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2019 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.
-#pragma once
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include "esp_err.h"
-
-/**
-* @brief LED Strip Type
-*
-*/
-typedef struct led_strip_s led_strip_t;
-
-/**
-* @brief LED Strip Device Type
-*
-*/
-typedef void *led_strip_dev_t;
-
-/**
-* @brief Declare of LED Strip Type
-*
-*/
-struct led_strip_s {
-    /**
-    * @brief Set RGB for a specific pixel
-    *
-    * @param strip: LED strip
-    * @param index: index of pixel to set
-    * @param red: red part of color
-    * @param green: green part of color
-    * @param blue: blue part of color
-    *
-    * @return
-    *      - ESP_OK: Set RGB for a specific pixel successfully
-    *      - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters
-    *      - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred
-    */
-    esp_err_t (*set_pixel)(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue);
-
-    /**
-    * @brief Refresh memory colors to LEDs
-    *
-    * @param strip: LED strip
-    * @param timeout_ms: timeout value for refreshing task
-    *
-    * @return
-    *      - ESP_OK: Refresh successfully
-    *      - ESP_ERR_TIMEOUT: Refresh failed because of timeout
-    *      - ESP_FAIL: Refresh failed because some other error occurred
-    *
-    * @note:
-    *      After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip.
-    */
-    esp_err_t (*refresh)(led_strip_t *strip, uint32_t timeout_ms);
-
-    /**
-    * @brief Clear LED strip (turn off all LEDs)
-    *
-    * @param strip: LED strip
-    * @param timeout_ms: timeout value for clearing task
-    *
-    * @return
-    *      - ESP_OK: Clear LEDs successfully
-    *      - ESP_ERR_TIMEOUT: Clear LEDs failed because of timeout
-    *      - ESP_FAIL: Clear LEDs failed because some other error occurred
-    */
-    esp_err_t (*clear)(led_strip_t *strip, uint32_t timeout_ms);
-
-    /**
-    * @brief Free LED strip resources
-    *
-    * @param strip: LED strip
-    *
-    * @return
-    *      - ESP_OK: Free resources successfully
-    *      - ESP_FAIL: Free resources failed because error occurred
-    */
-    esp_err_t (*del)(led_strip_t *strip);
-};
-
-/**
-* @brief LED Strip Configuration Type
-*
-*/
-typedef struct {
-    uint32_t max_leds;   /*!< Maximum LEDs in a single strip */
-    led_strip_dev_t dev; /*!< LED strip device (e.g. RMT channel, PWM channel, etc) */
-} led_strip_config_t;
-
-/**
- * @brief Default configuration for LED strip
- *
- */
-#define LED_STRIP_DEFAULT_CONFIG(number, dev_hdl) \
-    {                                             \
-        .max_leds = number,                       \
-        .dev = dev_hdl,                           \
-    }
-
-/**
-* @brief Install a new ws2812 driver (based on RMT peripheral)
-*
-* @param config: LED strip configuration
-* @return
-*      LED strip instance or NULL
-*/
-led_strip_t *led_strip_new_rmt_ws2812(const led_strip_config_t *config);
-
-/**
- * @brief Init the RMT peripheral and LED strip configuration.
- *
- * @param[in] channel: RMT peripheral channel number.
- * @param[in] gpio: GPIO number for the RMT data output.
- * @param[in] led_num: number of addressable LEDs.
- * @return
- *      LED strip instance or NULL
- */
-led_strip_t *led_strip_init(uint8_t channel, uint8_t gpio, uint16_t led_num);
-
-/**
- * @brief Denit the RMT peripheral.
- *
- * @param[in] strip: LED strip
- * @return
- *     - ESP_OK
- *     - ESP_FAIL
- */
-esp_err_t led_strip_denit(led_strip_t *strip);
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/components/espressif__led_strip/led_strip_rmt_ws2812.c b/components/espressif__led_strip/led_strip_rmt_ws2812.c
deleted file mode 100644
index 08c3dab499f59303bb584b1cfb4be127cd07e075..0000000000000000000000000000000000000000
--- a/components/espressif__led_strip/led_strip_rmt_ws2812.c
+++ /dev/null
@@ -1,208 +0,0 @@
-// Copyright 2019 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 <stdlib.h>
-#include <string.h>
-#include <sys/cdefs.h>
-#include "esp_log.h"
-#include "esp_attr.h"
-#include "led_strip.h"
-#include "driver/rmt.h"
-
-#define RMT_TX_CHANNEL RMT_CHANNEL_0
-
-static const char *TAG = "ws2812";
-#define STRIP_CHECK(a, str, goto_tag, ret_value, ...)                             \
-    do                                                                            \
-    {                                                                             \
-        if (!(a))                                                                 \
-        {                                                                         \
-            ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
-            ret = ret_value;                                                      \
-            goto goto_tag;                                                        \
-        }                                                                         \
-    } while (0)
-
-#define WS2812_T0H_NS (350)
-#define WS2812_T0L_NS (1000)
-#define WS2812_T1H_NS (1000)
-#define WS2812_T1L_NS (350)
-#define WS2812_RESET_US (280)
-
-static uint32_t ws2812_t0h_ticks = 0;
-static uint32_t ws2812_t1h_ticks = 0;
-static uint32_t ws2812_t0l_ticks = 0;
-static uint32_t ws2812_t1l_ticks = 0;
-
-typedef struct {
-    led_strip_t parent;
-    rmt_channel_t rmt_channel;
-    uint32_t strip_len;
-    uint8_t buffer[0];
-} ws2812_t;
-
-/**
- * @brief Conver RGB data to RMT format.
- *
- * @note For WS2812, R,G,B each contains 256 different choices (i.e. uint8_t)
- *
- * @param[in] src: source data, to converted to RMT format
- * @param[in] dest: place where to store the convert result
- * @param[in] src_size: size of source data
- * @param[in] wanted_num: number of RMT items that want to get
- * @param[out] translated_size: number of source data that got converted
- * @param[out] item_num: number of RMT items which are converted from source data
- */
-static void IRAM_ATTR ws2812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size,
-        size_t wanted_num, size_t *translated_size, size_t *item_num)
-{
-    if (src == NULL || dest == NULL) {
-        *translated_size = 0;
-        *item_num = 0;
-        return;
-    }
-    const rmt_item32_t bit0 = {{{ ws2812_t0h_ticks, 1, ws2812_t0l_ticks, 0 }}}; //Logical 0
-    const rmt_item32_t bit1 = {{{ ws2812_t1h_ticks, 1, ws2812_t1l_ticks, 0 }}}; //Logical 1
-    size_t size = 0;
-    size_t num = 0;
-    uint8_t *psrc = (uint8_t *)src;
-    rmt_item32_t *pdest = dest;
-    while (size < src_size && num < wanted_num) {
-        for (int i = 0; i < 8; i++) {
-            // MSB first
-            if (*psrc & (1 << (7 - i))) {
-                pdest->val =  bit1.val;
-            } else {
-                pdest->val =  bit0.val;
-            }
-            num++;
-            pdest++;
-        }
-        size++;
-        psrc++;
-    }
-    *translated_size = size;
-    *item_num = num;
-}
-
-static esp_err_t ws2812_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
-{
-    esp_err_t ret = ESP_OK;
-    ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent);
-    STRIP_CHECK(index < ws2812->strip_len, "index out of the maximum number of leds", err, ESP_ERR_INVALID_ARG);
-    uint32_t start = index * 3;
-    // In thr order of GRB
-    ws2812->buffer[start + 0] = green & 0xFF;
-    ws2812->buffer[start + 1] = red & 0xFF;
-    ws2812->buffer[start + 2] = blue & 0xFF;
-    return ESP_OK;
-err:
-    return ret;
-}
-
-static esp_err_t ws2812_refresh(led_strip_t *strip, uint32_t timeout_ms)
-{
-    esp_err_t ret = ESP_OK;
-    ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent);
-    STRIP_CHECK(rmt_write_sample(ws2812->rmt_channel, ws2812->buffer, ws2812->strip_len * 3, true) == ESP_OK,
-                "transmit RMT samples failed", err, ESP_FAIL);
-    return rmt_wait_tx_done(ws2812->rmt_channel, pdMS_TO_TICKS(timeout_ms));
-err:
-    return ret;
-}
-
-static esp_err_t ws2812_clear(led_strip_t *strip, uint32_t timeout_ms)
-{
-    ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent);
-    // Write zero to turn off all leds
-    memset(ws2812->buffer, 0, ws2812->strip_len * 3);
-    return ws2812_refresh(strip, timeout_ms);
-}
-
-static esp_err_t ws2812_del(led_strip_t *strip)
-{
-    ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent);
-    free(ws2812);
-    return ESP_OK;
-}
-
-led_strip_t *led_strip_new_rmt_ws2812(const led_strip_config_t *config)
-{
-    led_strip_t *ret = NULL;
-    STRIP_CHECK(config, "configuration can't be null", err, NULL);
-
-    // 24 bits per led
-    uint32_t ws2812_size = sizeof(ws2812_t) + config->max_leds * 3;
-    ws2812_t *ws2812 = calloc(1, ws2812_size);
-    STRIP_CHECK(ws2812, "request memory for ws2812 failed", err, NULL);
-
-    uint32_t counter_clk_hz = 0;
-    STRIP_CHECK(rmt_get_counter_clock((rmt_channel_t)config->dev, &counter_clk_hz) == ESP_OK,
-                "get rmt counter clock failed", err, NULL);
-    // ns -> ticks
-    float ratio = (float)counter_clk_hz / 1e9;
-    ws2812_t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS);
-    ws2812_t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS);
-    ws2812_t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS);
-    ws2812_t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS);
-
-    // set ws2812 to rmt adapter
-    rmt_translator_init((rmt_channel_t)config->dev, ws2812_rmt_adapter);
-
-    ws2812->rmt_channel = (rmt_channel_t)config->dev;
-    ws2812->strip_len = config->max_leds;
-
-    ws2812->parent.set_pixel = ws2812_set_pixel;
-    ws2812->parent.refresh = ws2812_refresh;
-    ws2812->parent.clear = ws2812_clear;
-    ws2812->parent.del = ws2812_del;
-
-    return &ws2812->parent;
-err:
-    return ret;
-}
-
-led_strip_t *led_strip_init(uint8_t channel, uint8_t gpio, uint16_t led_num)
-{
-    static led_strip_t *pStrip;
-
-    rmt_config_t config = RMT_DEFAULT_CONFIG_TX(gpio, channel);
-    // set counter clock to 40MHz
-    config.clk_div = 2;
-
-    ESP_ERROR_CHECK(rmt_config(&config));
-    ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, 0));
-
-    // install ws2812 driver
-    led_strip_config_t strip_config = LED_STRIP_DEFAULT_CONFIG(led_num, (led_strip_dev_t)config.channel);
-
-    pStrip = led_strip_new_rmt_ws2812(&strip_config);
-
-    if ( !pStrip ) {
-        ESP_LOGE(TAG, "install WS2812 driver failed");
-        return NULL;
-    }
-
-    // Clear LED strip (turn off all LEDs)
-    // This will fail for inverted output
-    //ESP_ERROR_CHECK(pStrip->clear(pStrip, 100));
-
-    return pStrip;
-}
-
-esp_err_t led_strip_denit(led_strip_t *strip)
-{
-    ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent);
-    ESP_ERROR_CHECK(rmt_driver_uninstall(ws2812->rmt_channel));
-    return strip->del(strip);
-}
diff --git a/components/espressif__led_strip/license.txt b/components/espressif__led_strip/license.txt
deleted file mode 100644
index d645695673349e3947e8e5ae42332d0ac3164cd7..0000000000000000000000000000000000000000
--- a/components/espressif__led_strip/license.txt
+++ /dev/null
@@ -1,202 +0,0 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   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.
diff --git a/components/flow3r_bsp/CMakeLists.txt b/components/flow3r_bsp/CMakeLists.txt
index fb597b10e5e6190bd9877177a99834701de6f01b..7d165308ddb41193fe16794ee21d2e6eaffe97a4 100644
--- a/components/flow3r_bsp/CMakeLists.txt
+++ b/components/flow3r_bsp/CMakeLists.txt
@@ -6,6 +6,9 @@ idf_component_register(
 		flow3r_bsp_hwconfig.c
 		flow3r_bsp_i2c.c
 		flow3r_bsp_max98091.c
+		flow3r_bsp_leds.c
+		flow3r_bsp_rmtled.c
+		flow3r_bsp_spiled.c
     INCLUDE_DIRS
 		.
 )
diff --git a/components/flow3r_bsp/flow3r_bsp.h b/components/flow3r_bsp/flow3r_bsp.h
index 2d538189ce8a699dddc3a1d0ac334987f619452f..8fd857a7ecfd7901d2866b07abce050d6298eca0 100644
--- a/components/flow3r_bsp/flow3r_bsp.h
+++ b/components/flow3r_bsp/flow3r_bsp.h
@@ -121,4 +121,20 @@ esp_err_t flow3r_bsp_audio_read(void *dest, size_t size, size_t *bytes_read, Tic
 esp_err_t flow3r_bsp_audio_write(const void *src, size_t size, size_t *bytes_written, TickType_t ticks_to_wait);
 
 // Write audio codec register. Obviously very unsafe. Have fun.
-void flow3r_bsp_audio_register_poke(uint8_t reg, uint8_t data);
\ No newline at end of file
+void flow3r_bsp_audio_register_poke(uint8_t reg, uint8_t data);
+
+#define FLOW3R_BSP_LED_COUNT 40
+
+// Initialize LEDs.
+esp_err_t flow3r_bsp_leds_init(void);
+
+// Set internal buffer for given LED to an RGB value.
+//
+// Index is a value in [0, FLOW3R_BSP_LED_COUNT).
+//
+// RGB values are in [0, 0xff].
+void flow3r_bsp_leds_set_pixel(uint32_t index, uint32_t red, uint32_t green, uint32_t blue);
+
+// Transmit from internal buffer into LEDs. This will block in case there
+// already is a previous transmission happening.
+esp_err_t flow3r_bsp_leds_refresh(TickType_t timeout_ms);
\ No newline at end of file
diff --git a/components/flow3r_bsp/flow3r_bsp_i2c.c b/components/flow3r_bsp/flow3r_bsp_i2c.c
index f1695b81ec086f5953a8a0bc349f0113b7b53471..a704e31ff55d6144b9f7e2d3eca30542fa1982dd 100644
--- a/components/flow3r_bsp/flow3r_bsp_i2c.c
+++ b/components/flow3r_bsp/flow3r_bsp_i2c.c
@@ -3,6 +3,7 @@
 #include "driver/i2c.h"
 #include "freertos/FreeRTOS.h"
 #include "freertos/semphr.h"
+#include "esp_log.h"
 
 static SemaphoreHandle_t mutex;
 
@@ -70,8 +71,10 @@ void flow3r_bsp_i2c_init(void) {
 	mutex = xSemaphoreCreateMutex();
 	assert(mutex != NULL);
 
-    assert(i2c_param_config(0, &i2c_conf) == ESP_OK);
-	assert(i2c_driver_install(0, i2c_conf.mode, 0, 0, 0) == ESP_OK);
+    assert(i2c_param_config(I2C_NUM_0, &i2c_conf) == ESP_OK);
+	assert(i2c_driver_install(I2C_NUM_0, i2c_conf.mode, 0, 0, 0) == ESP_OK);
+
+	flow3r_bsp_i2c_scan();
 }
 
 // Take I2C bus lock.
@@ -86,14 +89,30 @@ static void flow3r_bsp_i2c_put(void) {
 
 esp_err_t flow3r_bsp_i2c_write_to_device(uint8_t address, const uint8_t *buffer, size_t write_size, TickType_t ticks_to_wait) {
 	flow3r_bsp_i2c_get();
-	esp_err_t res = i2c_master_write_to_device(0, address, buffer, write_size, ticks_to_wait);
+	esp_err_t res = i2c_master_write_to_device(I2C_NUM_0, address, buffer, write_size, ticks_to_wait);
 	flow3r_bsp_i2c_put();
 	return res;
 }
 
 esp_err_t flow3r_bsp_i2c_write_read_device(uint8_t address, const uint8_t *write_buffer, size_t write_size, uint8_t *read_buffer, size_t read_size, TickType_t ticks_to_wait) {
 	flow3r_bsp_i2c_get();
-	esp_err_t res = i2c_master_write_read_device(0, address, write_buffer, write_size, read_buffer, read_size, ticks_to_wait);
+	esp_err_t res = i2c_master_write_read_device(I2C_NUM_0, address, write_buffer, write_size, read_buffer, read_size, ticks_to_wait);
 	flow3r_bsp_i2c_put();
 	return res;
+}
+
+void flow3r_bsp_i2c_scan(void) {
+	ESP_LOGI(TAG, "Scan: starting...");
+	for (uint8_t i = 1; i < 127; i++) {
+		i2c_cmd_handle_t cmd = i2c_cmd_link_create();
+		i2c_master_start(cmd);
+        i2c_master_write_byte(cmd, (i << 1) | I2C_MASTER_WRITE, 1);
+		i2c_master_stop(cmd);
+		esp_err_t ret = i2c_master_cmd_begin(I2C_NUM_0, cmd, 100 / portTICK_RATE_MS);
+        i2c_cmd_link_delete(cmd);
+		if (ret == ESP_OK) {
+			ESP_LOGI(TAG, "Scan: detected %02x", i);
+		}
+	}
+	ESP_LOGI(TAG, "Scan: done.");
 }
\ No newline at end of file
diff --git a/components/flow3r_bsp/flow3r_bsp_i2c.h b/components/flow3r_bsp/flow3r_bsp_i2c.h
index f54ec6428e5cf89b267b8a2e8534015770e769ee..49a14fdbae4395ae7f10a671902245a2afb43663 100644
--- a/components/flow3r_bsp/flow3r_bsp_i2c.h
+++ b/components/flow3r_bsp/flow3r_bsp_i2c.h
@@ -35,4 +35,6 @@ esp_err_t flow3r_bsp_i2c_write_to_device(flow3r_i2c_address address, const uint8
 // Perform a write-then read transaction on an I2C device.
 //
 // This can be called concurrently from different tassks.
-esp_err_t flow3r_bsp_i2c_write_read_device(flow3r_i2c_address address, const uint8_t *write_buffer, size_t write_size, uint8_t *read_buffer, size_t read_size, TickType_t ticks_to_wait);
\ No newline at end of file
+esp_err_t flow3r_bsp_i2c_write_read_device(flow3r_i2c_address address, const uint8_t *write_buffer, size_t write_size, uint8_t *read_buffer, size_t read_size, TickType_t ticks_to_wait);
+
+void flow3r_bsp_i2c_scan(void);
\ No newline at end of file
diff --git a/components/flow3r_bsp/flow3r_bsp_leds.c b/components/flow3r_bsp/flow3r_bsp_leds.c
new file mode 100644
index 0000000000000000000000000000000000000000..55c3bad890a1b79cf2cac11df46d7501e2420514
--- /dev/null
+++ b/components/flow3r_bsp/flow3r_bsp_leds.c
@@ -0,0 +1,37 @@
+#include "flow3r_bsp.h"
+
+#if defined(CONFIG_BADGE23_HW_GEN_P1)
+
+#include "flow3r_bsp_spiled.h"
+
+esp_err_t flow3r_bsp_leds_init(void) {
+	return flow3r_bsp_spiled_init(FLOW3R_BSP_LED_COUNT);
+}
+
+void flow3r_bsp_leds_set_pixel(uint32_t index, uint32_t red, uint32_t green, uint32_t blue) {
+	flow3r_bsp_spiled_set_pixel(index, red, green, blue);
+}
+
+esp_err_t flow3r_bsp_leds_refresh(TickType_t timeout_ms) {
+	return flow3r_bsp_spiled_refresh(timeout_ms);
+}
+
+#elif defined(CONFIG_BADGE23_HW_GEN_P3) || defined(CONFIG_BADGE23_HW_GEN_P4) || defined(CONFIG_BADGE23_HW_GEN_P6)
+
+#include "flow3r_bsp_rmtled.h"
+
+esp_err_t flow3r_bsp_leds_init(void) {
+	return flow3r_bsp_rmtled_init(FLOW3R_BSP_LED_COUNT);
+}
+
+void flow3r_bsp_leds_set_pixel(uint32_t index, uint32_t red, uint32_t green, uint32_t blue) {
+	flow3r_bsp_rmtled_set_pixel(index, red, green, blue);
+}
+
+esp_err_t flow3r_bsp_leds_refresh(TickType_t timeout_ms) {
+	return flow3r_bsp_rmtled_refresh(timeout_ms);
+}
+
+#else
+#error "leds not implemented for this badge generation"
+#endif
\ No newline at end of file
diff --git a/components/flow3r_bsp/flow3r_bsp_rmtled.c b/components/flow3r_bsp/flow3r_bsp_rmtled.c
new file mode 100644
index 0000000000000000000000000000000000000000..ae975403311d55ee8b723d66a430ad354e716f94
--- /dev/null
+++ b/components/flow3r_bsp/flow3r_bsp_rmtled.c
@@ -0,0 +1,157 @@
+// Driver for WS2812-style LEDs, using RMT peripheral.
+//
+// Based on espressif__led_strip.
+//
+// Copyright 2019 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 "flow3r_bsp_rmtled.h"
+
+static const char *TAG = "flow3r-rmtled";
+
+#include "driver/rmt.h"
+#include "esp_log.h"
+
+#define WS2812_T0H_NS (350)
+#define WS2812_T0L_NS (1000)
+#define WS2812_T1H_NS (1000)
+#define WS2812_T1L_NS (350)
+#define WS2812_RESET_US (280)
+
+typedef struct {
+    uint16_t num_leds;
+    rmt_channel_t chan;
+    uint8_t *buffer;
+    bool transmitting;
+
+    uint32_t ws2812_t0h_ticks;
+    uint32_t ws2812_t0l_ticks;
+    uint32_t ws2812_t1h_ticks;
+    uint32_t ws2812_t1l_ticks;
+} flow3r_bsp_rmtled_t;
+
+static flow3r_bsp_rmtled_t rmtled = {0};
+
+static void IRAM_ATTR _rmtled_adapter(const void *src, rmt_item32_t *dest, size_t src_size,
+        size_t wanted_num, size_t *translated_size, size_t *item_num)
+{
+    if (src == NULL || dest == NULL) {
+        *translated_size = 0;
+        *item_num = 0;
+        return;
+    }
+    const rmt_item32_t bit0 = {{{ rmtled.ws2812_t0h_ticks, 1, rmtled.ws2812_t0l_ticks, 0 }}}; //Logical 0
+    const rmt_item32_t bit1 = {{{ rmtled.ws2812_t1h_ticks, 1, rmtled.ws2812_t1l_ticks, 0 }}}; //Logical 1
+    size_t size = 0;
+    size_t num = 0;
+    uint8_t *psrc = (uint8_t *)src;
+    rmt_item32_t *pdest = dest;
+    while (size < src_size && num < wanted_num) {
+        for (int i = 0; i < 8; i++) {
+            // MSB first
+            if (*psrc & (1 << (7 - i))) {
+                pdest->val =  bit1.val;
+            } else {
+                pdest->val =  bit0.val;
+            }
+            num++;
+            pdest++;
+        }
+        size++;
+        psrc++;
+    }
+    *translated_size = size;
+    *item_num = num;
+}
+
+
+esp_err_t flow3r_bsp_rmtled_init(uint16_t num_leds) {
+    if (rmtled.buffer != NULL) {
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    rmt_config_t config = RMT_DEFAULT_CONFIG_TX(14, 0);
+    // set counter clock to 40MHz
+    config.clk_div = 2;
+
+    esp_err_t ret;
+    if ((ret = rmt_config(&config)) != ESP_OK) {
+        ESP_LOGE(TAG, "rmt_config failed: %s", esp_err_to_name(ret));
+        return ret;
+    }
+    if ((ret = rmt_driver_install(config.channel, 0, 0)) != ESP_OK) {
+        ESP_LOGE(TAG, "rmt_driver_install failed: %s", esp_err_to_name(ret));
+        return ret;
+    }
+
+    uint32_t counter_clk_hz = 0;
+    if ((ret = rmt_get_counter_clock(config.channel, &counter_clk_hz)) != ESP_OK) {
+        ESP_LOGE(TAG, "rmt_get_counter_clock failed: %s", esp_err_to_name(ret));
+        return ret;
+    }
+
+    // ns -> ticks
+    float ratio = (float)counter_clk_hz / 1e9;
+    rmtled.ws2812_t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS);
+    rmtled.ws2812_t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS);
+    rmtled.ws2812_t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS);
+    rmtled.ws2812_t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS);
+    rmtled.num_leds = num_leds;
+    rmtled.chan = config.channel;
+    rmtled.buffer = calloc(3, num_leds);
+    if (rmtled.buffer == NULL) {
+        ESP_LOGE(TAG, "buffer allocation failed");
+        return ESP_ERR_NO_MEM;
+    }
+    rmtled.transmitting = false;
+
+    if ((ret = rmt_translator_init(config.channel, _rmtled_adapter)) != ESP_OK) {
+        ESP_LOGE(TAG, "rmt_translator_init: %s", esp_err_to_name(ret));
+        return ret;
+    }
+
+    return ESP_OK;
+}
+
+void flow3r_bsp_rmtled_set_pixel(uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
+{
+    if (rmtled.buffer == NULL) {
+        return;
+    }
+    if (index >= rmtled.num_leds) {
+        return;
+    }
+    uint32_t start = index * 3;
+    rmtled.buffer[start + 0] = green & 0xFF;
+    rmtled.buffer[start + 1] = red & 0xFF;
+    rmtled.buffer[start + 2] = blue & 0xFF;
+}
+
+esp_err_t flow3r_bsp_rmtled_refresh(int32_t timeout_ms) {
+    if (rmtled.buffer == NULL) {
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    if (rmtled.transmitting) {
+        esp_err_t ret = rmt_wait_tx_done(rmtled.chan, pdMS_TO_TICKS(timeout_ms));
+        if (ret != ESP_OK) {
+            ESP_LOGE(TAG, "rmt_wait_tx_done: %s", esp_err_to_name(ret));
+        }
+        rmtled.transmitting = false;
+    }
+
+    esp_err_t ret = rmt_write_sample(rmtled.chan, rmtled.buffer, rmtled.num_leds * 3, true);
+    rmtled.transmitting = true;
+    return ret;
+}
\ No newline at end of file
diff --git a/components/flow3r_bsp/flow3r_bsp_rmtled.h b/components/flow3r_bsp/flow3r_bsp_rmtled.h
new file mode 100644
index 0000000000000000000000000000000000000000..569b8507afbf469afc168a93d7f0918df8f6c40e
--- /dev/null
+++ b/components/flow3r_bsp/flow3r_bsp_rmtled.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include "esp_err.h"
+
+esp_err_t flow3r_bsp_rmtled_init(uint16_t num_leds);
+void flow3r_bsp_rmtled_set_pixel(uint32_t index, uint32_t red, uint32_t green, uint32_t blue);
+esp_err_t flow3r_bsp_rmtled_refresh(int32_t timeout_ms);
\ No newline at end of file
diff --git a/components/flow3r_bsp/flow3r_bsp_spiled.c b/components/flow3r_bsp/flow3r_bsp_spiled.c
new file mode 100644
index 0000000000000000000000000000000000000000..676c20d5e7b4accb0848040cd5d05d8e94c60c8d
--- /dev/null
+++ b/components/flow3r_bsp/flow3r_bsp_spiled.c
@@ -0,0 +1,96 @@
+// Driver for APA102-style LEDs, using SPI peripheral.
+
+#include "flow3r_bsp_spiled.h"
+
+#include <string.h>
+
+#include "driver/spi_master.h"
+#include "esp_log.h"
+
+static const char *TAG = "flow3r-spiled";
+
+static spi_bus_config_t buscfg;
+static spi_device_interface_config_t devcfg;
+static spi_device_handle_t spi_led;
+static spi_transaction_t spi_trans_object;
+
+typedef struct {
+    uint16_t num_leds;
+    uint8_t *buffer;
+} flow3r_bsp_spiled_t;
+
+static flow3r_bsp_spiled_t spiled;
+
+
+esp_err_t flow3r_bsp_spiled_init(uint16_t num_leds) {
+    // Set up the Bus Config struct
+    buscfg.miso_io_num = -1;
+    buscfg.mosi_io_num = 18;
+    buscfg.sclk_io_num = 8;
+    buscfg.quadwp_io_num = -1;
+    buscfg.quadhd_io_num = -1;
+    buscfg.max_transfer_sz = 8000;
+
+    // Set up the SPI Device Configuration Struct
+    devcfg.clock_speed_hz = 10000000;
+    devcfg.mode = 0;
+    devcfg.spics_io_num = -1;
+    devcfg.queue_size = 1;
+
+    // Initialize the SPI driver
+    esp_err_t ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
+    if (ret != ESP_OK) {
+        ESP_LOGE(TAG, "spi_bus_initialize: %s", esp_err_to_name(ret));
+        return ret;
+    }
+    // Add SPI port to bus
+    ret = spi_bus_add_device(SPI2_HOST, &devcfg, &spi_led);
+    if (ret != ESP_OK) {
+        ESP_LOGE(TAG, "spi_bus_add_device: %s", esp_err_to_name(ret));
+        return ret;
+    }
+
+	spiled.num_leds = num_leds;
+    spiled.buffer = calloc(4, num_leds + 2);
+	if (spiled.buffer == NULL) {
+		ESP_LOGE(TAG, "buffer allocation failed");
+		return ESP_ERR_NO_MEM;
+	}
+    // Start Frame
+    spiled.buffer[0] = 0;
+    spiled.buffer[1] = 0;
+    spiled.buffer[2] = 0;
+    spiled.buffer[3] = 0;
+    // Global brightness.
+    for (int i = 0; i < num_leds; i++) {
+        spiled.buffer[i * 4 + 4] = 255;
+    }
+    // End Frame (this only works with up to 64 LEDs, meh).
+    int i = num_leds * 4 + 8;
+    spiled.buffer[i + 0] = 255;
+    spiled.buffer[i + 1] = 255;
+    spiled.buffer[i + 2] = 255;
+    spiled.buffer[i + 3] = 255;
+
+    memset(&spi_trans_object, 0, sizeof(spi_trans_object));
+    spi_trans_object.length = (4 * (num_leds+2))* 8;
+    spi_trans_object.tx_buffer = spiled.buffer;
+    return ESP_OK;
+}
+
+void flow3r_bsp_spiled_set_pixel(uint32_t index, uint32_t red, uint32_t green, uint32_t blue) {
+	if (spiled.buffer == NULL) {
+		return;
+	}
+	if (index >= spiled.num_leds) {
+		return;
+	}
+    uint32_t start = index * 4 + 4;
+    spiled.buffer[start + 1] = blue & 0xFF;
+    spiled.buffer[start + 2] = green & 0xFF;
+    spiled.buffer[start + 3] = red & 0xFF;
+}
+
+esp_err_t flow3r_bsp_spiled_refresh(int32_t timeout_ms) {
+    return spi_device_queue_trans(spi_led, &spi_trans_object, timeout_ms);
+}
\ No newline at end of file
diff --git a/components/flow3r_bsp/flow3r_bsp_spiled.h b/components/flow3r_bsp/flow3r_bsp_spiled.h
new file mode 100644
index 0000000000000000000000000000000000000000..501e718a5c25498be3a5a5417279c8f0913f6f21
--- /dev/null
+++ b/components/flow3r_bsp/flow3r_bsp_spiled.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include "esp_err.h"
+
+esp_err_t flow3r_bsp_spiled_init(uint16_t num_leds);
+void flow3r_bsp_spiled_set_pixel(uint32_t index, uint32_t red, uint32_t green, uint32_t blue);
+esp_err_t flow3r_bsp_spiled_refresh(int32_t timeout_ms);
\ No newline at end of file