跳转至

rocketpi_ws2812b

效果展示

视频待更新。。。。。。。。。。。。。。。

功能说明

面向 RocketPI STM32F401RE 开发板的 WS2812B 灯带演示工程。主要特性:

  • 使用 TIM3 + DMA 驱动 WS2812B,一次刷新整条灯带。
  • 提供 driver_ws2812b 基础驱动,可自行设置任意 RGB 值。
  • driver_ws2812b_test 内置多种灯效算法(呼吸、彩虹、追逐等),并支持按毫秒控制运行时长。

硬件连接

  • WS2812B 数据线:PA6(TIM3_CH1,推挽,Very High)。
  • 电源:灯带接 5V

CubeMX配置

固定频率800Khz+pwm mode1

image-20251115221044114

DMA配置 TRIG 方向,内存至外设,优先级非常高

image-20251115221143757

GPIO 配置 推挽+速度Very HIGH

image-20251115221321487

程序经过逻辑分析仪验证通过

image-20251115221818493

驱动以及测试代码

Core/Src/main.c
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "tim.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "driver_ws2812b.h"
#include "driver_ws2812b_test.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
  const uint16_t led_count = 30U;
  if (!ws2812b_init(led_count)) {
    Error_Handler();
  }

  ws2812b_test_blink(0x40U, 0x40U, 0x40U, 200U, 200U, 2000U);
  ws2812b_test_chase(0xFFU, 0U, 0U, 40U, 1500U);
  ws2812b_test_rainbow(20U, 3000U);
  ws2812b_test_breathe(0xFFU, 0x20U, 0x20U, 5U, 2000U);
  ws2812b_test_theater_chase(0x00U, 0xFFU, 0x00U, 60U, 2500U);
  ws2812b_test_gradient_wipe(0x00U, 0x00U, 0xFFU, 0xFFU, 0x80U, 0x00U, 15U, 0U);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 84;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
bsp/ws2812b/driver_ws2812b.c
#include "driver_ws2812b.h"

#include <string.h>

#include "tim.h"

/**
 * @file driver_ws2812b.c
 * @brief DMA-based WS2812B strip driver implementation.
 * @author rocket
 * @copyright Copyright (c) 2025 rocket. Authorized use only.
 */

#define WS2812B_MAX_LEDS       60U   /**< Maximum number of LEDs supported by this build. */
#define WS2812B_RESET_SLOTS    80U   /**< Number of low slots appended to provide reset (>50us). */
#define WS2812B_T0H_TICKS      32U   /**< Timer ticks representing a logic-0 high pulse. */
#define WS2812B_T1H_TICKS      70U   /**< Timer ticks representing a logic-1 high pulse. */
#define WS2812B_TIMER_CHANNEL  TIM_CHANNEL_1 /**< TIM channel used for PWM output. */

static TIM_HandleTypeDef *ws_tim = NULL;
static uint16_t ws_led_count = 0U;
static uint8_t ws_pixels[WS2812B_MAX_LEDS][3];
static uint16_t ws_dma_buf[WS2812B_MAX_LEDS * 24U + WS2812B_RESET_SLOTS];
static volatile bool ws_dma_busy = false;

/**
 * @brief Encode a byte (MSB first) into DMA halfwords.
 * @param value Byte to emit.
 * @param ptr Moving DMA buffer pointer.
 */
static void ws_pack_byte(uint8_t value, uint16_t **ptr)
{
    for (int bit = 7; bit >= 0; --bit) {
        **ptr = (value & (1U << bit)) ? WS2812B_T1H_TICKS : WS2812B_T0H_TICKS;
        (*ptr)++;
    }
}

/**
 * @brief Populate the DMA buffer from the staging pixel array.
 * @return Number of halfwords populated.
 */
static uint16_t ws_build_buffer(void)
{
    uint16_t *p = ws_dma_buf;
    for (uint16_t i = 0; i < ws_led_count; ++i) {
        uint8_t red = ws_pixels[i][0];
        uint8_t green = ws_pixels[i][1];
        uint8_t blue = ws_pixels[i][2];
        ws_pack_byte(green, &p);
        ws_pack_byte(red, &p);
        ws_pack_byte(blue, &p);
    }

    for (uint16_t i = 0; i < WS2812B_RESET_SLOTS; ++i) {
        *p++ = 0U;
    }

    return (uint16_t)(p - ws_dma_buf);
}

/**
 * @brief Initialize driver resources.
 */
bool ws2812b_init(uint16_t led_count)
{
    if ((led_count == 0U) || (led_count > WS2812B_MAX_LEDS)) {
        return false;
    }

    ws_tim = &htim3;
    ws_led_count = led_count;
    ws_dma_busy = false;
    memset(ws_pixels, 0, sizeof(ws_pixels));

    return true;
}

/**
 * @brief Set a single pixel in the staging buffer.
 */
bool ws2812b_set_pixel(uint16_t index, uint8_t red, uint8_t green, uint8_t blue)
{
    if ((index >= ws_led_count) || (ws_tim == NULL)) {
        return false;
    }

    ws_pixels[index][0] = red;
    ws_pixels[index][1] = green;
    ws_pixels[index][2] = blue;
    return true;
}

/**
 * @brief Fill entire staging buffer with a single color.
 */
void ws2812b_fill(uint8_t red, uint8_t green, uint8_t blue)
{
    for (uint16_t i = 0; i < ws_led_count; ++i) {
        (void)ws2812b_set_pixel(i, red, green, blue);
    }
}

/**
 * @brief Commit current pixel buffer via DMA.
 */
bool ws2812b_refresh(void)
{
    if ((ws_tim == NULL) || (ws_led_count == 0U) || ws_dma_busy) {
        return false;
    }

    uint16_t payload = ws_build_buffer();
    ws_dma_busy = true;

    if (HAL_TIM_PWM_Start_DMA(ws_tim,
                              WS2812B_TIMER_CHANNEL,
                              (uint32_t *)ws_dma_buf,
                              payload) != HAL_OK) {
        ws_dma_busy = false;
        return false;
    }

    return true;
}

/**
 * @brief Check DMA busy state.
 */
bool ws2812b_is_busy(void)
{
    return ws_dma_busy;
}

/**
 * @brief Return configured LED count.
 */
uint16_t ws2812b_get_led_count(void)
{
    return ws_led_count;
}

/**
 * @brief HAL callback invoked at DMA completion.
 */
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
    if ((ws_tim != NULL) && (htim == ws_tim)) {
        (void)HAL_TIM_PWM_Stop_DMA(ws_tim, WS2812B_TIMER_CHANNEL);
        __HAL_TIM_SET_COMPARE(ws_tim, WS2812B_TIMER_CHANNEL, 0U);
        ws_dma_busy = false;
    }
}
bsp/ws2812b/driver_ws2812b.h
#pragma once

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

/**
 * @file driver_ws2812b.h
 * @brief Minimal WS2812B strip driver API.
 * @author rocket
 * @copyright Copyright (c) 2025 rocket. Authorized use only.
 */

/**
 * @brief Initialize the driver and internal buffers.
 * @param led_count Total number of LEDs on the strip (1-60 supported by this build).
 * @return true when the driver is ready, false on invalid LED count or unavailable resources.
 */
bool ws2812b_init(uint16_t led_count);

/**
 * @brief Update one pixel in the staging buffer.
 * @param index Zero-based LED index to update.
 * @param red Red intensity (0-255).
 * @param green Green intensity (0-255).
 * @param blue Blue intensity (0-255).
 * @return true on success, false if index is out of range or driver not initialized.
 */
bool ws2812b_set_pixel(uint16_t index, uint8_t red, uint8_t green, uint8_t blue);

/**
 * @brief Fill the staging buffer with a single RGB color.
 * @param red Red intensity (0-255).
 * @param green Green intensity (0-255).
 * @param blue Blue intensity (0-255).
 */
void ws2812b_fill(uint8_t red, uint8_t green, uint8_t blue);

/**
 * @brief Kick off a DMA transfer that pushes the staging buffer to the strip.
 * @return true if DMA started, false if the driver is busy or uninitialized.
 */
bool ws2812b_refresh(void);

/**
 * @brief Query whether a DMA transfer is still in progress.
 * @return true while DMA is active, otherwise false.
 */
bool ws2812b_is_busy(void);

/**
 * @brief Retrieve the LED count configured at init time.
 * @return Number of LEDs currently supported by the driver (0 if uninitialized).
 */
uint16_t ws2812b_get_led_count(void);
bsp/ws2812b/driver_ws2812b_test.c
/**
 * @file driver_ws2812b_test.c
 * @brief Predefined visual patterns for WS2812B demo and validation.
 * @author rocket
 * @copyright Copyright (c) 2025 rocket. Authorized use only.
 */

#include "driver_ws2812b_test.h"

#include "main.h"

/**
 * @brief Push staged data to the strip and wait until DMA finishes.
 */
static void ws_test_commit_and_wait(void)
{
    while (!ws2812b_refresh()) {
        while (ws2812b_is_busy()) {
        }
    }
    while (ws2812b_is_busy()) {
    }
}

/**
 * @brief Determine whether a pattern should exit due to runtime limit.
 * @param start_tick HAL tick captured when the pattern started.
 * @param duration_ms Allowed run time in milliseconds (0 => infinite).
 * @return true if duration has elapsed, otherwise false.
 */
static bool ws_test_should_stop(uint32_t start_tick, uint32_t duration_ms)
{
    if (duration_ms == 0U) {
        return false;
    }
    return (HAL_GetTick() - start_tick) >= duration_ms;
}

/**
 * @brief Repeatedly toggle the entire strip between on/off states.
 * @param red Red component for the "on" state (0-255).
 * @param green Green component for the "on" state (0-255).
 * @param blue Blue component for the "on" state (0-255).
 * @param on_time_ms Milliseconds the color stays lit per cycle.
 * @param off_time_ms Milliseconds the strip stays dark per cycle.
 * @param duration_ms Total runtime (0 = infinite loop).
 */
void ws2812b_test_blink(uint8_t red, uint8_t green, uint8_t blue,
                        uint32_t on_time_ms, uint32_t off_time_ms, uint32_t duration_ms)
{
    const uint32_t start_tick = HAL_GetTick();
    while (!ws_test_should_stop(start_tick, duration_ms)) {
        ws2812b_fill(red, green, blue);
        ws_test_commit_and_wait();
        HAL_Delay(on_time_ms);
        if (ws_test_should_stop(start_tick, duration_ms)) {
            break;
        }

        ws2812b_fill(0U, 0U, 0U);
        ws_test_commit_and_wait();
        HAL_Delay(off_time_ms);
    }
}

/**
 * @brief Move a single illuminated pixel along the strip.
 * @param red Red component for the moving pixel.
 * @param green Green component for the moving pixel.
 * @param blue Blue component for the moving pixel.
 * @param step_delay_ms Delay between each pixel hop.
 * @param duration_ms Total runtime (0 = infinite loop).
 */
void ws2812b_test_chase(uint8_t red, uint8_t green, uint8_t blue,
                        uint32_t step_delay_ms, uint32_t duration_ms)
{
    const uint16_t count = ws2812b_get_led_count();
    const uint32_t start_tick = HAL_GetTick();
    while (!ws_test_should_stop(start_tick, duration_ms)) {
        for (uint16_t idx = 0; idx < count; ++idx) {
            ws2812b_fill(0U, 0U, 0U);
            (void)ws2812b_set_pixel(idx, red, green, blue);
            ws_test_commit_and_wait();
            HAL_Delay(step_delay_ms);
            if (ws_test_should_stop(start_tick, duration_ms)) {
                break;
            }
        }
    }
}

/**
 * @brief Convert a wheel position (0-255) to RGB components.
 * @param pos Color wheel index.
 * @param r Output red component pointer.
 * @param g Output green component pointer.
 * @param b Output blue component pointer.
 */
static void ws_test_color_wheel(uint8_t pos, uint8_t *r, uint8_t *g, uint8_t *b)
{
    if (pos < 85U) {
        *r = (uint8_t)(pos * 3U);
        *g = (uint8_t)(255U - pos * 3U);
        *b = 0U;
    } else if (pos < 170U) {
        pos -= 85U;
        *r = (uint8_t)(255U - pos * 3U);
        *g = 0U;
        *b = (uint8_t)(pos * 3U);
    } else {
        pos -= 170U;
        *r = 0U;
        *g = (uint8_t)(pos * 3U);
        *b = (uint8_t)(255U - pos * 3U);
    }
}

/**
 * @brief Cycle a rainbow gradient across the strip.
 * @param step_delay_ms Delay between hue shifts.
 * @param duration_ms Total runtime (0 = infinite loop).
 */
void ws2812b_test_rainbow(uint32_t step_delay_ms, uint32_t duration_ms)
{
    const uint16_t count = ws2812b_get_led_count();
    if (count == 0U) {
        return;
    }

    const uint32_t start_tick = HAL_GetTick();
    while (!ws_test_should_stop(start_tick, duration_ms)) {
        for (uint16_t offset = 0; offset < 255U; ++offset) {
            for (uint16_t led = 0; led < count; ++led) {
                uint8_t r, g, b;
                ws_test_color_wheel((uint8_t)((led * 256U / count + offset) & 0xFFU), &r, &g, &b);
                (void)ws2812b_set_pixel(led, r, g, b);
            }
            ws_test_commit_and_wait();
            HAL_Delay(step_delay_ms);
            if (ws_test_should_stop(start_tick, duration_ms)) {
                return;
            }
        }
    }
}

/**
 * @brief Fade a single color up and down to mimic breathing.
 * @param red Base color red component.
 * @param green Base color green component.
 * @param blue Base color blue component.
 * @param step_delay_ms Delay between brightness level changes.
 * @param duration_ms Total runtime (0 = infinite loop).
 */
void ws2812b_test_breathe(uint8_t red, uint8_t green, uint8_t blue,
                          uint32_t step_delay_ms, uint32_t duration_ms)
{
    const uint32_t start_tick = HAL_GetTick();
    while (!ws_test_should_stop(start_tick, duration_ms)) {
        for (uint16_t level = 0; level <= 255U; ++level) {
            ws2812b_fill((uint8_t)((red * level) / 255U),
                         (uint8_t)((green * level) / 255U),
                         (uint8_t)((blue * level) / 255U));
            ws_test_commit_and_wait();
            HAL_Delay(step_delay_ms);
            if (ws_test_should_stop(start_tick, duration_ms)) {
                return;
            }
        }
        for (int level = 255; level >= 0; --level) {
            ws2812b_fill((uint8_t)((red * level) / 255U),
                         (uint8_t)((green * level) / 255U),
                         (uint8_t)((blue * level) / 255U));
            ws_test_commit_and_wait();
            HAL_Delay(step_delay_ms);
            if (ws_test_should_stop(start_tick, duration_ms)) {
                return;
            }
        }
    }
}

/**
 * @brief Theater chase effect with every third pixel lit.
 * @param red Red component for the lit pixels.
 * @param green Green component for the lit pixels.
 * @param blue Blue component for the lit pixels.
 * @param step_delay_ms Delay between offset shifts.
 * @param duration_ms Total runtime (0 = infinite loop).
 */
void ws2812b_test_theater_chase(uint8_t red, uint8_t green, uint8_t blue,
                                uint32_t step_delay_ms, uint32_t duration_ms)
{
    const uint16_t count = ws2812b_get_led_count();
    const uint8_t spacing = 3U;
    const uint32_t start_tick = HAL_GetTick();

    while (!ws_test_should_stop(start_tick, duration_ms)) {
        for (uint8_t offset = 0; offset < spacing; ++offset) {
            ws2812b_fill(0U, 0U, 0U);
            for (uint16_t led = offset; led < count; led += spacing) {
                (void)ws2812b_set_pixel(led, red, green, blue);
            }
            ws_test_commit_and_wait();
            HAL_Delay(step_delay_ms);
            if (ws_test_should_stop(start_tick, duration_ms)) {
                return;
            }
        }
    }
}

/**
 * @brief Sweep the strip while blending from a start to an end color.
 * @param start_r Starting color red channel (0-255).
 * @param start_g Starting color green channel (0-255).
 * @param start_b Starting color blue channel (0-255).
 * @param end_r Ending color red channel (0-255).
 * @param end_g Ending color green channel (0-255).
 * @param end_b Ending color blue channel (0-255).
 * @param step_delay_ms Delay between wipe steps.
 * @param duration_ms Total runtime (0 = infinite loop).
 */
void ws2812b_test_gradient_wipe(uint8_t start_r, uint8_t start_g, uint8_t start_b,
                                uint8_t end_r, uint8_t end_g, uint8_t end_b,
                                uint32_t step_delay_ms, uint32_t duration_ms)
{
    const uint16_t count = ws2812b_get_led_count();
    if (count == 0U) {
        return;
    }

    const uint32_t start_tick = HAL_GetTick();
    while (!ws_test_should_stop(start_tick, duration_ms)) {
        for (uint16_t head = 0; head < count; ++head) {
            for (uint16_t led = 0; led <= head; ++led) {
                float ratio = (head == 0U) ? 1.0f : (float)led / (float)head;
                uint8_t r = (uint8_t)(start_r + (end_r - start_r) * ratio);
                uint8_t g = (uint8_t)(start_g + (end_g - start_g) * ratio);
                uint8_t b = (uint8_t)(start_b + (end_b - start_b) * ratio);
                (void)ws2812b_set_pixel(led, r, g, b);
            }
            ws_test_commit_and_wait();
            HAL_Delay(step_delay_ms);
            if (ws_test_should_stop(start_tick, duration_ms)) {
                return;
            }
        }
    }
}
bsp/ws2812b/driver_ws2812b_test.h
#pragma once

#include <stdint.h>

#include "driver_ws2812b.h"

/**
 * @file driver_ws2812b_test.h
 * @brief Ready-to-use pattern helpers for WS2812B demo sequences.
 * @author rocket
 * @copyright Copyright (c) 2025 rocket. Authorized use only.
 */

/**
 * @brief Simple on/off blinking pattern across the entire strip.
 * @param red Red intensity (0-255).
 * @param green Green intensity (0-255).
 * @param blue Blue intensity (0-255).
 * @param on_time_ms Time to stay lit per cycle (milliseconds).
 * @param off_time_ms Time to stay dark per cycle (milliseconds).
 * @param duration_ms Total runtime of the pattern (0 => run forever).
 */
void ws2812b_test_blink(uint8_t red, uint8_t green, uint8_t blue,
                        uint32_t on_time_ms, uint32_t off_time_ms, uint32_t duration_ms);

/**
 * @brief Single-pixel chase effect that marches along the strip.
 * @param red Red intensity (0-255).
 * @param green Green intensity (0-255).
 * @param blue Blue intensity (0-255).
 * @param step_delay_ms Delay between pixel moves (milliseconds).
 * @param duration_ms Total runtime (0 => run forever).
 */
void ws2812b_test_chase(uint8_t red, uint8_t green, uint8_t blue,
                        uint32_t step_delay_ms, uint32_t duration_ms);

/**
 * @brief Continuous rainbow cycle shifting through the strip.
 * @param step_delay_ms Delay between hue shifts (milliseconds).
 * @param duration_ms Total runtime (0 => run forever).
 */
void ws2812b_test_rainbow(uint32_t step_delay_ms, uint32_t duration_ms);

/**
 * @brief "Breathing" effect that fades a color in and out.
 * @param red Red intensity (0-255).
 * @param green Green intensity (0-255).
 * @param blue Blue intensity (0-255).
 * @param step_delay_ms Delay between brightness steps (milliseconds).
 * @param duration_ms Total runtime (0 => run forever).
 */
void ws2812b_test_breathe(uint8_t red, uint8_t green, uint8_t blue,
                          uint32_t step_delay_ms, uint32_t duration_ms);

/**
 * @brief Theater-style sparse chase (every third pixel lit).
 * @param red Red intensity (0-255).
 * @param green Green intensity (0-255).
 * @param blue Blue intensity (0-255).
 * @param step_delay_ms Delay before shifting the offsets (milliseconds).
 * @param duration_ms Total runtime (0 => run forever).
 */
void ws2812b_test_theater_chase(uint8_t red, uint8_t green, uint8_t blue,
                                uint32_t step_delay_ms, uint32_t duration_ms);

/**
 * @brief Progressive wipe that blends from a start color to an end color.
 * @param start_r Start color red component (0-255).
 * @param start_g Start color green component (0-255).
 * @param start_b Start color blue component (0-255).
 * @param end_r Target color red component (0-255).
 * @param end_g Target color green component (0-255).
 * @param end_b Target color blue component (0-255).
 * @param step_delay_ms Delay between wipe steps (milliseconds).
 * @param duration_ms Total runtime (0 => run forever).
 */
void ws2812b_test_gradient_wipe(uint8_t start_r, uint8_t start_g, uint8_t start_b,
                                uint8_t end_r, uint8_t end_g, uint8_t end_b,
                                uint32_t step_delay_ms, uint32_t duration_ms);