#include "ws2812.h" #include "tim.h" #include "stdlib.h" #include "bsp/pwm.h" /** * @file ws2812.c * @brief WS2812 LED驱动实现 * @note 使用PWM+DMA方式驱动WS2812灯带 */ /* 动态内存指针 */ uint32_t* Pixel_Buf = NULL; static uint16_t Pixel_NUM = 0; static uint8_t Is_Initialized = 0; /* DMA传输状态标志 */ static volatile uint8_t WS2812_DMA_Busy = 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) { // 停止DMA传输 BSP_PWM_Stop_DMA(BSP_PWM_WS2812); HAL_TIM_PWM_Stop_DMA(&htim1, TIM_CHANNEL_4); // 重置DMA标志 WS2812_DMA_Busy = 0; 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; // 检查DMA是否忙碌,如果正在传输则直接返回 if (WS2812_DMA_Busy) { return; // DMA还在传输,跳过本次发送 } // 设置RESET行(最后24个数据为0) uint32_t* reset_row = Pixel_Buf + Pixel_NUM * 24; for (uint8_t i = 0; i < 24; i++) { reset_row[i] = 0; } // 标记DMA为忙碌状态 WS2812_DMA_Busy = 1; // 直接使用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 volatile WS2812_StatusCtrl_t StatusCtrl = { volatile 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 = 3000; /* 闪烁周期(ms) */ #define BLINK_PERIOD_MS 300 /* LED更新最小间隔(ms) - 避免频繁DMA操作 */ #define LED_UPDATE_MIN_INTERVAL_MS 300 /** * @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 (WS2812_DMA_Busy) { return; } /* 控制更新频率,避免频繁DMA操作干扰射频 */ if (currentTick - StatusCtrl.lastUpdateTick < LED_UPDATE_MIN_INTERVAL_MS) { return; } /* 检测通信超时: * - TX_OK/RX_OK状态下超时自动切换为ERROR * - IDLE状态下如果距离上次通信超时也切换为ERROR (用于RX模式未收到数据) */ if (StatusCtrl.status == WS2812_STATUS_TX_OK || StatusCtrl.status == WS2812_STATUS_RX_OK || StatusCtrl.status == WS2812_STATUS_IDLE) { 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; uint8_t statusChanged = 0; if (StatusCtrl.status != StatusCtrl.lastStatus) { needUpdate = 1; statusChanged = 1; StatusCtrl.lastStatus = StatusCtrl.status; } if (newBlinkState != StatusCtrl.blinkState) { /* 闪烁状态变化,需要更新(仅针对需要闪烁的状态) */ if (StatusCtrl.status == WS2812_STATUS_TX_OK || StatusCtrl.status == WS2812_STATUS_RX_OK || StatusCtrl.status == WS2812_STATUS_DEBUG) { needUpdate = 1; } StatusCtrl.blinkState = newBlinkState; // ← 立即更新blinkState,确保switch使用最新值 } if (!needUpdate) return; StatusCtrl.lastUpdateTick = currentTick; /* 读取当前状态到局部变量,确保不被优化 */ volatile WS2812_Status_t currentStatus = StatusCtrl.status; volatile uint8_t currentBlinkState = StatusCtrl.blinkState; /* 根据状态设置颜色 - 使用if-else替代switch,避免跳转表优化问题 */ if (currentStatus == WS2812_STATUS_IDLE) { /* 空闲: LED熄灭 */ WS2812_SetColor(0, 0, 0, 0); } else if (currentStatus == WS2812_STATUS_TX_OK || currentStatus == WS2812_STATUS_RX_OK) { /* 正常通信: 绿灯闪烁 */ if (currentBlinkState) { WS2812_SetColorWithBrightness(0, 255, 0); /* 绿色 */ } else { WS2812_SetColor(0, 0, 0, 0); /* 熄灭 */ } } else if (currentStatus == WS2812_STATUS_ERROR) { /* 异常: 红灯常亮 */ WS2812_SetColorWithBrightness(255, 0, 0); /* 红色 */ } else if (currentStatus == WS2812_STATUS_DEBUG) { /* 调试: 黄灯闪烁 */ if (currentBlinkState) { WS2812_SetColorWithBrightness(255, 180, 0); /* 黄色 */ } else { WS2812_SetColor(0, 0, 0, 0); /* 熄灭 */ } } else { /* 默认: 熄灭 */ WS2812_SetColor(0, 0, 0, 0); } /* 发送数据到LED */ WS2812_SendArray(); } /** * @brief PWM DMA传输完成回调函数 * @param htim: TIM句柄指针 * @note 在DMA传输完成后由HAL库自动调用 * @retval None */ void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim) { // 检查是否是WS2812的定时器(TIM1 CH4) if (htim->Instance == TIM1) { // 清除DMA忙标志,允许下一次传输 WS2812_DMA_Busy = 0; // 停止PWM DMA传输,节省功耗 HAL_TIM_PWM_Stop_DMA(htim, TIM_CHANNEL_4); } } /** * @brief 检查WS2812 DMA是否正在传输 * @retval 1: DMA忙碌中, 0: DMA空闲 */ uint8_t WS2812_IsDMABusy(void) { return WS2812_DMA_Busy; }