MR16/User/device/ws2812.c
2025-12-10 20:18:07 +08:00

373 lines
9.2 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "ws2812.h"
#include "tim.h"
#include "stdlib.h"
#include "bsp/pwm.h"
/**
* @file ws2812.c
* @brief WS2812 LED驱动实现
* @note 使用PWM+DMA方式驱动WS2812灯带
*/
/* 动态内存指针 */
static uint32_t* Pixel_Buf = NULL;
static uint16_t Pixel_NUM = 0;
static uint8_t Is_Initialized = 0;
/* WS2812时序码定义
* WS2812 时序要求 (800kHz, 1.25μs per bit):
* - 1码: 高电平 0.8μs (64%), 低电平 0.4μs (32%)
* - 0码: 高电平 0.4μs (32%), 低电平 0.8μs (64%)
* ARR = 89, 周期 = 90 计数
*/
#define CODE_1 ((uint32_t)(90 * 0.64f)) /*!< 1码: 58 */
#define CODE_0 ((uint32_t)(90 * 0.32f)) /*!< 0码: 29 */
/**
* @brief 初始化WS2812驱动分配内存
* @param led_num: LED数量
* @retval 0成功1失败
*/
uint8_t WS2812_Init(uint16_t led_num)
{
if (led_num == 0 || led_num > 1024) return 1; // 限制最大LED数量
// 如果已经初始化,先释放旧的内存
if (Is_Initialized)
{
WS2812_DeInit();
}
Pixel_NUM = led_num;
uint32_t buf_size = (Pixel_NUM + 1) * 24; // 每个LED 24位 + 1行RESET
Pixel_Buf = (uint32_t*)malloc(buf_size * sizeof(uint32_t));
if (Pixel_Buf == NULL)
{
Pixel_NUM = 0;
return 1;
}
// 初始化缓冲区为0
for (uint32_t i = 0; i < buf_size; i++)
{
Pixel_Buf[i] = 0;
}
Is_Initialized = 1;
// TIM1是高级定时器必须使能主输出
__HAL_TIM_MOE_ENABLE(&htim1);
return 0;
}
/**
* @brief 释放内存停止DMA
* @retval None
*/
void WS2812_DeInit(void)
{
if (Pixel_Buf != NULL)
{
free(Pixel_Buf);
Pixel_Buf = NULL;
}
Pixel_NUM = 0;
Is_Initialized = 0;
BSP_PWM_Stop(BSP_PWM_WS2812);
}
/**
* @brief 设置单个LED的颜色
* @param LedId: LED序号
* @param R: 红色分量 0-255
* @param G: 绿色分量 0-255
* @param B: 蓝色分量 0-255
* @retval None
*/
void WS2812_SetColor(uint8_t LedId, uint8_t R, uint8_t G, uint8_t B)
{
if (!Is_Initialized || LedId >= Pixel_NUM || Pixel_Buf == NULL) return;
uint32_t* led_data = Pixel_Buf + LedId * 24;
// WS2812颜色顺序GRB
for (uint8_t i = 0; i < 8; i++)
led_data[i] = (G & (1 << (7 - i))) ? CODE_1 : CODE_0;
for (uint8_t i = 8; i < 16; i++)
led_data[i] = (R & (1 << (15 - i))) ? CODE_1 : CODE_0;
for (uint8_t i = 16; i < 24; i++)
led_data[i] = (B & (1 << (23 - i))) ? CODE_1 : CODE_0;
}
/**
* @brief 设置多个LED的颜色
* @param PixelArray: 颜色数组
* PixelArray[i][0]: LED的ID
* PixelArray[i][1]: R值
* PixelArray[i][2]: G值
* PixelArray[i][3]: B值
* @retval None
*/
void WS2812_SetColors(uint8_t PixelArray[][4], uint16_t ArraySize)
{
if (!Is_Initialized || Pixel_Buf == NULL) return;
for (uint16_t i = 0; i < ArraySize; i++)
{
uint8_t id = PixelArray[i][0];
uint8_t R = PixelArray[i][1];
uint8_t G = PixelArray[i][2];
uint8_t B = PixelArray[i][3];
if (id < Pixel_NUM)
{
WS2812_SetColor(id, R, G, B);
}
}
}
/**
* @brief 发送数据到WS2812灯带
* @note 会自动添加RESET码
* @retval None
*/
void WS2812_SendArray(void)
{
if (!Is_Initialized || Pixel_Buf == NULL) return;
// 设置RESET行最后24个数据为0
uint32_t* reset_row = Pixel_Buf + Pixel_NUM * 24;
for (uint8_t i = 0; i < 24; i++)
{
reset_row[i] = 0;
}
// 直接使用HAL库函数,绕过BSP层
// HAL_TIM_PWM_Start_DMA(
// &htim1,
// TIM_CHANNEL_4,
// Pixel_Buf,
// (Pixel_NUM + 1) * 24
// );
BSP_PWM_Start_DMA(
BSP_PWM_WS2812,
Pixel_Buf,
(Pixel_NUM + 1) * 24
);
}
/**
* @brief 清空所有LED设置为黑色
* @retval None
*/
void WS2812_Clear(void)
{
if (!Is_Initialized || Pixel_Buf == NULL) return;
// 将所有LED数据清零
for (uint32_t i = 0; i < Pixel_NUM * 24; i++)
{
Pixel_Buf[i] = 0;
}
}
/**
* @brief 设置所有LED为同一颜色
* @param R: 红色分量 0-255
* @param G: 绿色分量 0-255
* @param B: 蓝色分量 0-255
* @retval None
*/
void WS2812_SetAll(uint8_t R, uint8_t G, uint8_t B)
{
if (!Is_Initialized || Pixel_Buf == NULL) return;
for (uint16_t i = 0; i < Pixel_NUM; i++)
{
WS2812_SetColor(i, R, G, B);
}
}
/**
* @brief 获取当前LED数量
* @retval LED数量
*/
uint16_t WS2812_GetLedCount(void)
{
return Pixel_NUM;
}
/*----------------------------------------------------------------------------
状态指示灯功能实现 (低功耗/低干扰设计)
*---------------------------------------------------------------------------*/
/* 状态指示灯控制结构 */
static WS2812_StatusCtrl_t StatusCtrl = {
.status = WS2812_STATUS_IDLE,
.lastStatus = WS2812_STATUS_IDLE,
.blinkState = 0,
.lastUpdateTick = 0,
.lastCommTick = 0,
.brightness = 25 /* 默认25%亮度,减少功耗对射频干扰 */
};
/* 通信超时时间(ms) */
static uint32_t CommTimeout_ms = 1000;
/* 闪烁周期(ms) */
#define BLINK_PERIOD_MS 500
/* LED更新最小间隔(ms) - 避免频繁DMA操作 */
#define LED_UPDATE_MIN_INTERVAL_MS 50
/**
* @brief 设置LED状态指示
* @param status: LED状态 (WS2812_Status_t枚举)
*/
void WS2812_SetStatus(WS2812_Status_t status)
{
StatusCtrl.status = status;
}
/**
* @brief 获取当前LED状态
* @retval 当前状态
*/
WS2812_Status_t WS2812_GetStatus(void)
{
return StatusCtrl.status;
}
/**
* @brief 通信成功通知
*/
void WS2812_NotifyCommSuccess(void)
{
StatusCtrl.lastCommTick = HAL_GetTick();
}
/**
* @brief 设置LED亮度
* @param brightness: 亮度百分比 (0-100)
*/
void WS2812_SetBrightness(uint8_t brightness)
{
if (brightness > 100) brightness = 100;
StatusCtrl.brightness = brightness;
}
/**
* @brief 设置通信超时时间
* @param timeout_ms: 超时时间(ms)
*/
void WS2812_SetCommTimeout(uint32_t timeout_ms)
{
CommTimeout_ms = timeout_ms;
}
/**
* @brief 内部函数: 应用亮度并设置颜色
* @param R,G,B: 原始颜色值
*/
static void WS2812_SetColorWithBrightness(uint8_t R, uint8_t G, uint8_t B)
{
if (!Is_Initialized || Pixel_Buf == NULL) return;
/* 应用亮度缩放 */
uint8_t adjR = (uint8_t)((uint16_t)R * StatusCtrl.brightness / 100);
uint8_t adjG = (uint8_t)((uint16_t)G * StatusCtrl.brightness / 100);
uint8_t adjB = (uint8_t)((uint16_t)B * StatusCtrl.brightness / 100);
WS2812_SetColor(0, adjR, adjG, adjB);
}
/**
* @brief 状态指示灯任务(非阻塞)
* @param currentTick: 当前系统tick(ms)
* @note 自动管理闪烁、超时检测,对射频影响极小
*/
void WS2812_StatusTask(uint32_t currentTick)
{
if (!Is_Initialized || Pixel_Buf == NULL) return;
/* 控制更新频率,避免频繁DMA操作干扰射频 */
if (currentTick - StatusCtrl.lastUpdateTick < LED_UPDATE_MIN_INTERVAL_MS) {
return;
}
/* 检测通信超时: TX_OK/RX_OK状态下超时自动切换为ERROR */
if (StatusCtrl.status == WS2812_STATUS_TX_OK ||
StatusCtrl.status == WS2812_STATUS_RX_OK) {
if (currentTick - StatusCtrl.lastCommTick > CommTimeout_ms) {
StatusCtrl.status = WS2812_STATUS_ERROR;
}
}
/* 计算闪烁状态 */
uint8_t newBlinkState = ((currentTick / BLINK_PERIOD_MS) % 2) ? 1 : 0;
/* 判断是否需要更新LED (状态变化或闪烁状态变化) */
uint8_t needUpdate = 0;
if (StatusCtrl.status != StatusCtrl.lastStatus) {
needUpdate = 1;
StatusCtrl.lastStatus = StatusCtrl.status;
} else if (StatusCtrl.status == WS2812_STATUS_TX_OK ||
StatusCtrl.status == WS2812_STATUS_RX_OK ||
StatusCtrl.status == WS2812_STATUS_DEBUG) {
/* 闪烁状态: 检查是否需要切换 */
if (newBlinkState != StatusCtrl.blinkState) {
needUpdate = 1;
}
}
if (!needUpdate) return;
StatusCtrl.blinkState = newBlinkState;
StatusCtrl.lastUpdateTick = currentTick;
/* 根据状态设置颜色 */
switch (StatusCtrl.status) {
case WS2812_STATUS_IDLE:
/* 空闲: LED熄灭 */
WS2812_SetColor(0, 0, 0, 0);
break;
case WS2812_STATUS_TX_OK:
case WS2812_STATUS_RX_OK:
/* 正常通信: 绿灯闪烁 */
if (StatusCtrl.blinkState) {
WS2812_SetColorWithBrightness(0, 255, 0); /* 绿色 */
} else {
WS2812_SetColor(0, 0, 0, 0); /* 熄灭 */
}
break;
case WS2812_STATUS_ERROR:
/* 异常: 红灯常亮 */
WS2812_SetColorWithBrightness(255, 0, 0); /* 红色 */
break;
case WS2812_STATUS_DEBUG:
/* 调试: 黄灯闪烁 */
if (StatusCtrl.blinkState) {
WS2812_SetColorWithBrightness(255, 180, 0); /* 黄色 */
} else {
WS2812_SetColor(0, 0, 0, 0); /* 熄灭 */
}
break;
default:
WS2812_SetColor(0, 0, 0, 0);
break;
}
/* 发送数据到LED */
WS2812_SendArray();
}