373 lines
9.2 KiB
C
373 lines
9.2 KiB
C
#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();
|
||
}
|
||
|