Skip to content

RGB LED CODE

Component Architecture

- driver
    - node_rgb
        - include
            - node_rgb.h
        - node_rgb.c
        - CMakeLists.txt

driver/node_rgb/CMakeLists.txt

set(src_dirs
    .
)

set(include_dirs
    include
)

set(requires
    driver
    esp_driver_gpio
)

idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})

Note

Note that in the drivers, we used gpio from the ESP-IDF builtin driver library, therefore, we need to indicate this dependency in the REQUIRES field of the CMakeLists.txt file.

node_rgb.h

/**
 * @file node_rgb.h
 * @brief SK6812MINI-C (SK6812-class) one-wire RGB: low-level fill, clear, optional chase step.
 *
 * No FreeRTOS tasks or delays here; use vTaskDelay (etc.) in the application for timing and animation.
 */

#pragma once

#include "esp_err.h"
#include <stdbool.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

/** Data line GPIO */
#define NODE_RGB_GPIO_DEFAULT (3)

typedef struct {
    int gpio_num;
    int num_leds;
    uint8_t brightness; /**< 0–255 scale */
} node_rgb_config_t;

#define NODE_RGB_CONFIG_DEFAULT()           \
    {                                       \
        .gpio_num = NODE_RGB_GPIO_DEFAULT,  \
        .num_leds = 1,                      \
        .brightness = 96,                   \
    }

esp_err_t node_rgb_init(const node_rgb_config_t *cfg);
void node_rgb_deinit(void);
bool node_rgb_is_initialized(void);

void node_rgb_set_brightness(uint8_t v);

/** Solid R,G,B (0–255) on all LEDs, then refresh */
void node_rgb_rgb(uint8_t r, uint8_t g, uint8_t b);

/** Named color string (MQTT/CLI): trims ASCII space; English case-insensitive; UTF-8 Chinese aliases supported. */
esp_err_t node_rgb_str(const char *s);

void node_rgb_clear(void);

/**
 * Advance chase by one step (multi-LED: moving bright spot; single LED: cycles R→G→B internally).
 * Call rate is controlled by the application.
 */
void node_rgb_chase_step(uint8_t r, uint8_t g, uint8_t b);

/** Reset chase position (e.g. restart multi-LED chase from the beginning) */
void node_rgb_chase_reset(void);

#ifdef __cplusplus
}
#endif

node_rgb.c

/**
 * @file node_rgb.c
 * @brief SK6812MINI-C via espressif/led_strip (RMT); no RTOS usage inside this module.
 */

#include "node_rgb.h"
#include "led_strip.h"
#include "esp_log.h"
#include <ctype.h>
#include <string.h>

static const char *TAG = "node_rgb";

static led_strip_handle_t s_strip = NULL;
static int s_num_leds = 1;
static uint8_t s_brightness = 96;
static uint32_t s_chase_pos = 0;
static uint8_t s_single_phase = 0;
static bool s_inited = false;

static inline uint8_t scale(uint8_t c)
{
    return (uint8_t)(((uint16_t)c * s_brightness) / 255);
}

typedef struct {
    const char *name;
    uint8_t r;
    uint8_t g;
    uint8_t b;
} node_rgb_named_color_t;

/* UTF-8 Chinese literals; ASCII keys compared case-insensitively in code. */
static const node_rgb_named_color_t s_named_colors[] = {
    { "red", 255, 0, 0 },
    { "红", 255, 0, 0 },
    { "green", 0, 255, 0 },
    { "绿", 0, 255, 0 },
    { "blue", 0, 0, 255 },
    { "蓝", 0, 0, 255 },
    { "yellow", 255, 255, 0 },
    { "黄", 255, 255, 0 },
    { "cyan", 0, 255, 255 },
    { "aqua", 0, 255, 255 },
    { "青", 0, 255, 255 },
    { "magenta", 255, 0, 255 },
    { "fuchsia", 255, 0, 255 },
    { "品红", 255, 0, 255 },
    { "purple", 128, 0, 128 },
    { "violet", 148, 0, 211 },
    { "紫", 128, 0, 128 },
    { "orange", 255, 140, 0 },
    { "橙", 255, 140, 0 },
    { "pink", 255, 105, 180 },
    { "粉", 255, 105, 180 },
    { "white", 255, 255, 255 },
    { "白", 255, 255, 255 },
    { "black", 0, 0, 0 },
    { "off", 0, 0, 0 },
    { "none", 0, 0, 0 },
    { "黑", 0, 0, 0 },
    { "关", 0, 0, 0 },
    { "gold", 255, 215, 0 },
    { "金", 255, 215, 0 },
    { "brown", 139, 69, 19 },
    { "褐", 139, 69, 19 },
};

static const char *skip_ascii_space(const char *s)
{
    while (*s != '\0' && isspace((unsigned char)*s)) {
        s++;
    }
    return s;
}

static esp_err_t copy_trimmed_name(const char *name, char *out, size_t out_sz)
{
    if (!name) {
        return ESP_ERR_INVALID_ARG;
    }
    const char *p = skip_ascii_space(name);
    const char *end = p + strlen(p);
    while (end > p && isspace((unsigned char)end[-1])) {
        end--;
    }
    size_t n = (size_t)(end - p);
    if (n == 0) {
        return ESP_ERR_INVALID_ARG;
    }
    if (n + 1 > out_sz) {
        return ESP_ERR_INVALID_ARG;
    }
    memcpy(out, p, n);
    out[n] = '\0';
    return ESP_OK;
}

static int ascii_lower(int c)
{
    if (c >= 'A' && c <= 'Z') {
        return c - 'A' + 'a';
    }
    return c;
}

/** @a entry must be ASCII-only (7-bit) for case-insensitive match. */
static bool name_eq_ascii_ci(const char *a, const char *entry)
{
    for (;;) {
        unsigned char ca = (unsigned char)*a++;
        unsigned char ce = (unsigned char)*entry++;
        if (ce == '\0') {
            return ca == '\0';
        }
        if (ascii_lower((int)ca) != ascii_lower((int)ce)) {
            return false;
        }
    }
}

static bool lookup_named_rgb(const char *buf, uint8_t *r, uint8_t *g, uint8_t *b)
{
    for (size_t i = 0; i < sizeof(s_named_colors) / sizeof(s_named_colors[0]); i++) {
        const char *nm = s_named_colors[i].name;
        bool match;
        if ((unsigned char)nm[0] >= 0x80) {
            match = (strcmp(buf, nm) == 0);
        } else {
            match = name_eq_ascii_ci(buf, nm);
        }
        if (match) {
            *r = s_named_colors[i].r;
            *g = s_named_colors[i].g;
            *b = s_named_colors[i].b;
            return true;
        }
    }
    return false;
}

static void apply_fill(uint8_t r, uint8_t g, uint8_t b)
{
    if (!s_strip) {
        return;
    }
    r = scale(r);
    g = scale(g);
    b = scale(b);
    for (int i = 0; i < s_num_leds; i++) {
        led_strip_set_pixel(s_strip, (uint32_t)i, r, g, b);
    }
    led_strip_refresh(s_strip);
}

esp_err_t node_rgb_init(const node_rgb_config_t *cfg)
{
    if (s_inited) {
        return ESP_OK;
    }
    if (!cfg || cfg->gpio_num < 0) {
        return ESP_ERR_INVALID_ARG;
    }

    s_num_leds = cfg->num_leds;
    if (s_num_leds < 1) {
        s_num_leds = 1;
    }
    if (s_num_leds > 256) {
        s_num_leds = 256;
    }
    s_brightness = cfg->brightness ? cfg->brightness : 96;

    led_strip_config_t strip_cfg = {
        .strip_gpio_num = cfg->gpio_num,
        .max_leds = (uint32_t)s_num_leds,
        .led_pixel_format = LED_PIXEL_FORMAT_GRB,
        .led_model = LED_MODEL_SK6812,
        .flags.invert_out = false,
    };

    led_strip_rmt_config_t rmt_cfg = {
        .clk_src = RMT_CLK_SRC_DEFAULT,
        .resolution_hz = 10 * 1000 * 1000,
        .flags.with_dma = false,
    };

    esp_err_t err = led_strip_new_rmt_device(&strip_cfg, &rmt_cfg, &s_strip);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "led_strip_new_rmt_device: %s", esp_err_to_name(err));
        return err;
    }

    led_strip_clear(s_strip);
    s_chase_pos = 0;
    s_single_phase = 0;
    s_inited = true;
    ESP_LOGI(TAG, "SK6812 GPIO%d x %d, brightness %u", cfg->gpio_num, s_num_leds, (unsigned)s_brightness);
    return ESP_OK;
}

void node_rgb_deinit(void)
{
    if (s_strip) {
        led_strip_clear(s_strip);
        led_strip_del(s_strip);
        s_strip = NULL;
    }
    s_inited = false;
}

bool node_rgb_is_initialized(void)
{
    return s_inited;
}

void node_rgb_set_brightness(uint8_t v)
{
    s_brightness = v;
}

void node_rgb_rgb(uint8_t r, uint8_t g, uint8_t b)
{
    apply_fill(r, g, b);
}

esp_err_t node_rgb_str(const char *s)
{
    char buf[48];
    esp_err_t err = copy_trimmed_name(s, buf, sizeof(buf));
    if (err != ESP_OK) {
        return err;
    }
    if (!s_inited || !s_strip) {
        return ESP_ERR_INVALID_STATE;
    }
    uint8_t r, g, b;
    if (!lookup_named_rgb(buf, &r, &g, &b)) {
        return ESP_ERR_NOT_FOUND;
    }
    node_rgb_rgb(r, g, b);
    return ESP_OK;
}

void node_rgb_clear(void)
{
    if (s_strip) {
        led_strip_clear(s_strip);
    }
}

void node_rgb_chase_reset(void)
{
    s_chase_pos = 0;
    s_single_phase = 0;
}

void node_rgb_chase_step(uint8_t r, uint8_t g, uint8_t b)
{
    if (!s_strip) {
        return;
    }

    if (s_num_leds <= 1) {
        uint8_t rr = 0, gg = 0, bb = 0;
        switch (s_single_phase % 3) {
            case 0:
                rr = r;
                break;
            case 1:
                gg = g;
                break;
            default:
                bb = b;
                break;
        }
        s_single_phase++;
        apply_fill(rr, gg, bb);
        return;
    }

    uint8_t R = scale(r);
    uint8_t G = scale(g);
    uint8_t B = scale(b);

    for (int i = 0; i < s_num_leds; i++) {
        led_strip_set_pixel(s_strip, (uint32_t)i, 0, 0, 0);
    }

    int idx = (int)(s_chase_pos % (uint32_t)s_num_leds);
    led_strip_set_pixel(s_strip, (uint32_t)idx, R, G, B);

    int t1 = (idx - 1 + s_num_leds) % s_num_leds;
    int t2 = (idx - 2 + s_num_leds) % s_num_leds;
    led_strip_set_pixel(s_strip, (uint32_t)t1, R / 4, G / 4, B / 4);
    led_strip_set_pixel(s_strip, (uint32_t)t2, R / 16, G / 16, B / 16);

    s_chase_pos++;
    led_strip_refresh(s_strip);
}

main.cpp

/**
 * @file main.cpp
 * @author SHUAIWEN CUI (SHUAIWEN001@e.ntu.edu.sg)
 * @brief 
 * @version 1.0
 * @date 2025-10-22
 * 
 * @copyright Copyright (c) 2024
 * 
 */

/* DEPENDENCIES */
// ESP
#include "esp_system.h" // ESP32 System
#include "nvs_flash.h"  // ESP32 NVS
#include "esp_chip_info.h" // ESP32 Chip Info
#include "esp_psram.h" // ESP32 PSRAM
#include "esp_flash.h" // ESP32 Flash
#include "esp_log.h" // ESP32 Logging

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

// BSP
#include "node_led.h"
#include "node_exit.h"
#include "node_spi.h"
#include "node_timer.h"
#include "node_rtc.h"
#include "node_sdcard.h"
#include "node_wifi.h"
#include "node_mqtt.h"
#include "node_rgb.h"

#ifdef __cplusplus
extern "C" {
#endif

/* Variables */
const char *TAG = "AIoTNode";

/**
 * @brief Entry point of the program
 * @param None
 * @retval None
 */
void app_main(void)
{
    esp_err_t ret;
    uint32_t flash_size;
    esp_chip_info_t chip_info;

    char mqtt_pub_buff[64];
    int count = 0;

    static TickType_t s_rgb_last_tick = 0;
    static uint8_t s_rgb_marquee_i = 0;

    /* Single-LED marquee: cycle named colors (~220 ms per step). */
    static const char *const k_rgb_marquee[] = {
        "red", "orange", "yellow", "green", "cyan", "blue", "purple", "pink",
    };
    const size_t k_rgb_marquee_n = sizeof(k_rgb_marquee) / sizeof(k_rgb_marquee[0]);

    // Initialize NVS
    ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase()); // Erase if needed
        ret = nvs_flash_init();
    }

    // Get FLASH size
    esp_flash_get_size(NULL, &flash_size);
    esp_chip_info(&chip_info);

    // Display CPU core count
    printf("CPU Cores: %d\n", chip_info.cores);

    // Display FLASH size
    printf("Flash size: %ld MB flash\n", flash_size / (1024 * 1024));

    // Display PSRAM size
    printf("PSRAM size: %d bytes\n", esp_psram_get_size());

    // BSP Initialization
    led_init();
    exit_init();
    spi2_init();

    node_rgb_config_t rgb_cfg = NODE_RGB_CONFIG_DEFAULT();
    rgb_cfg.gpio_num = NODE_RGB_GPIO_DEFAULT;
    rgb_cfg.num_leds = 1;
    ret = node_rgb_init(&rgb_cfg);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "node_rgb_init failed: %s", esp_err_to_name(ret));
    } else {
        ESP_LOGI(TAG, "node_rgb: GPIO%u marquee demo (named colors)", (unsigned)NODE_RGB_GPIO_DEFAULT);
        node_rgb_chase_reset();
        (void)node_rgb_str(k_rgb_marquee[0]);
        s_rgb_last_tick = xTaskGetTickCount();
        s_rgb_marquee_i = 1;
    }

    // spiffs_test();                                                  /* Run SPIFFS test */
    while (sd_card_init())                               /* SD card not detected */
    {
        ESP_LOGW(TAG, "SD Card Error!");
        vTaskDelay(500);
        ESP_LOGW(TAG, "Please Check!");
        vTaskDelay(500);
    }

    ESP_LOGI(TAG, "SD Initialized!");

    sd_card_test_filesystem();                                        /* Run SD card test */

    ESP_LOGI(TAG, "SD Tested CSW!");

    // sd_card_unmount();

    vTaskDelay(3000);

    ESP_LOGI(TAG_WIFI, "WiFi STA Test");

    ret = wifi_sta_wpa2_init();
    if(ret == ESP_OK)
    {
        ESP_LOGI(TAG_WIFI, "WiFi STA Init OK");
        ESP_LOGI(TAG_WIFI, "WiFi STA Test OK");
    }
    else
    {
        ESP_LOGE(TAG_WIFI, "WiFi STA Init Failed");
    }

    // only when the ip is obtained, start mqtt
    EventBits_t ev = 0;
    ev = xEventGroupWaitBits(wifi_event_group,CONNECTED_BIT,pdTRUE,pdFALSE,portMAX_DELAY);
    if(ev & CONNECTED_BIT)
    {
        mqtt_app_start();
    }

    while (1)
    {
        if(s_is_mqtt_connected)
        {
            snprintf(mqtt_pub_buff,64,"{\"count\":\"%d\"}",count);
            esp_mqtt_client_publish(s_mqtt_client, MQTT_PUBLISH_TOPIC,
                            mqtt_pub_buff, strlen(mqtt_pub_buff),1, 0);
            count++;
        }
        led_toggle();

        /* RGB marquee: advance by tick, independent of the 1 s loop delay below. */
        if (node_rgb_is_initialized()) {
            const TickType_t rgb_step = pdMS_TO_TICKS(220);
            TickType_t now = xTaskGetTickCount();
            if (s_rgb_last_tick != 0 && (now - s_rgb_last_tick) >= rgb_step) {
                s_rgb_last_tick = now;
                const char *c = k_rgb_marquee[s_rgb_marquee_i % k_rgb_marquee_n];
                if (node_rgb_str(c) != ESP_OK) {
                    node_rgb_rgb(24, 24, 24);
                }
                s_rgb_marquee_i++;
            }
        }

        ESP_LOGI(TAG, "Hello World!");
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

#ifdef __cplusplus
}
#endif