本文以CH592測試,使用TMR的PWM功能驅動WS2812,這種方法相對于SPI DMA驅動的方式有點時節省IO資源,但是RAM消耗會比SPI方式大四倍。
下面貼出程序:
1.pwm_ws2812.c文件:
#include "pwm_ws2812.h" /** * @brief 初始化WS2812緩沖區(填充前后復位信號) */ void ws2812_buf_init(uint32_t *buf) { uint16_t i; // 前導復位信號(低電平) for(i = 0; i < WS2812_RESET_CYCLES; i++) { buf[i] = 0; // 占空比0%(低電平) } // 后導復位信號(確保數據發送完成后復位) uint16_t data_end = WS2812_RESET_CYCLES + WS2812_LED_NUM * 24; for(i = data_end; i < WS2812_BUF_LEN; i++) { buf[i] = 0; } } /** * @brief 內部函數:填充單個LED的24bit數據 */ static void ws2812_set_rgb_bits(uint32_t *buf, uint8_t g, uint8_t r, uint8_t b) { uint8_t i; // 綠色分量(8bit,先低后高) for(i = 0; i < 8; i++) { buf[i] = (g & 0x01) ? WS2812_T1H : WS2812_T0H; g >>= 1; } // 紅色分量(8bit,先低后高) for(i = 0; i < 8; i++) { buf[i + 8] = (r & 0x01) ? WS2812_T1H : WS2812_T0H; r >>= 1; } // 藍色分量(8bit,先低后高) for(i = 0; i < 8; i++) { buf[i + 16] = (b & 0x01) ? WS2812_T1H : WS2812_T0H; b >>= 1; } } /** * @brief 設置單個LED的顏色 */ void ws2812_set_single(uint32_t *buf, uint8_t led_idx, uint8_t g, uint8_t r, uint8_t b) { if(led_idx >= WS2812_LED_NUM) return; // 索引越界檢查 // 計算當前LED在緩沖區中的起始位置 uint32_t *led_buf = buf + WS2812_RESET_CYCLES + led_idx * 24; ws2812_set_rgb_bits(led_buf, g, r, b); } /** * @brief 填充所有LED為同一顏色 */ void ws2812_fill_all(uint32_t *buf, uint8_t g, uint8_t r, uint8_t b) { for(uint8_t i = 0; i < WS2812_LED_NUM; i++) { ws2812_set_single(buf, i, g, r, b); } } /** * @brief 發送WS2812數據 */ void ws2812_send(uint32_t *buf) { // 配置DMA傳輸范圍 TMR2_DMACfg(ENABLE, (uint16_t)(uint32_t)buf, (uint16_t)(uint32_t)(buf + WS2812_BUF_LEN ), Mode_Single); // 啟動PWM(低電平起始,單次傳輸) //TMR2_PWMInit(Low_Level, PWM_Times_1); TMR2_PWMEnable(); TMR2_Enable(); } /** * @brief 初始化WS2812硬件(定時器2+GPIO) */ void ws2812_hw_init(void) { // 配置GPIO(PB11,推挽輸出) GPIOA_ModeCfg(GPIO_Pin_11, GPIO_ModeOut_PP_5mA); // GPIOB_ModeCfg(GPIO_Pin_11, GPIO_ModeOut_PP_5mA); // GPIOPinRemap(ENABLE, RB_PIN_TMR2); // 映射定時器2到PB11 // 配置定時器2 PWM周期 TMR2_PWMCycleCfg(WS2812_BIT_CYCLE); // 單bit周期 TMR2_PWMInit(High_Level, PWM_Times_1); }
2.pwm_ws2812.h文件:
#ifndef __PWM_WS2812_H #define __PWM_WS2812_H #include "CH59x_common.h" // WS2812 配置參數(根據實際需求修改) #define WS2812_LED_NUM 10 // LED數量 #define WS2812_CLK_FREQ 60 // 系統時鐘頻率(MHz),需與實際時鐘一致 #define WS2812_RESET_US 50 // 復位信號最小時間(us) // 時序參數(單位:系統時鐘周期,自動計算) #define WS2812_T0H (uint32_t)(0.4 * WS2812_CLK_FREQ) // 0碼高電平(≈0.4us) #define WS2812_T1H (uint32_t)(0.8 * WS2812_CLK_FREQ) // 1碼高電平(≈0.8us) #define WS2812_BIT_CYCLE (uint32_t)(1.25 * WS2812_CLK_FREQ) // 單bit總周期(≈1.25us) #define WS2812_T0L (WS2812_BIT_CYCLE - WS2812_T0H) // 0碼低電平 #define WS2812_T1L (WS2812_BIT_CYCLE - WS2812_T1H) // 1碼低電平 #define WS2812_RESET_CYCLES (uint32_t)((WS2812_RESET_US * WS2812_CLK_FREQ) / 1000) // 復位周期數 // 緩沖區長度計算(前導復位+數據+后導復位) #define WS2812_BUF_LEN (WS2812_RESET_CYCLES + WS2812_LED_NUM * 24 + WS2812_RESET_CYCLES/2) /** * @brief 初始化WS2812緩沖區(填充復位信號) * @param buf: 緩沖區指針(需提前定義為WS2812_BUF_LEN長度) */ void ws2812_buf_init(uint32_t *buf); /** * @brief 設置單個LED的RGB顏色 * @param buf: 緩沖區指針 * @param led_idx: LED索引(0~WS2812_LED_NUM-1) * @param g: 綠色分量(0-255) * @param r: 紅色分量(0-255) * @param b: 藍色分量(0-255) */ void ws2812_set_single(uint32_t *buf, uint8_t led_idx, uint8_t g, uint8_t r, uint8_t b); /** * @brief 填充所有LED為同一顏色 * @param buf: 緩沖區指針 * @param g: 綠色分量(0-255) * @param r: 紅色分量(0-255) * @param b: 藍色分量(0-255) */ void ws2812_fill_all(uint32_t *buf, uint8_t g, uint8_t r, uint8_t b); /** * @brief 發送WS2812數據(啟動DMA和PWM) * @param buf: 緩沖區指針 */ void ws2812_send(uint32_t *buf); /** * @brief 初始化WS2812硬件(定時器+GPIO) */ void ws2812_hw_init(void); #endif
3.main函數:
#include "CH59x_common.h" #include "pwm_ws2812.h" // 定義WS2812緩沖區(按宏定義長度分配) __attribute__((aligned(4))) uint32_t PwmBuf[WS2812_BUF_LEN]; void DebugInit(void) { GPIOA_SetBits(GPIO_Pin_9); GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeIN_PU); GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeOut_PP_5mA); UART1_DefInit(); } int main() { SetSysClock(CLK_SOURCE_PLL_60MHz); DebugInit(); PRINT("WS2812 Test @ChipID=%02X\n", R8_CHIP_ID); // 初始化WS2812硬件和緩沖區 ws2812_hw_init(); ws2812_buf_init(PwmBuf); PRINT("WS2812_BUF_LEN=%d\n",WS2812_BUF_LEN); // 測試流程:依次顯示黃→青→紫→逐個點亮 ws2812_fill_all(PwmBuf, 0xFF, 0xFF, 0x00); // 黃色 for(uint16_t i=0;i<WS2812_BUF_LEN;i++){ PRINT("%d ",PwmBuf[i]); } PRINT("\n"); ws2812_send(PwmBuf); DelayMs(1000); ws2812_fill_all(PwmBuf, 0xFF, 0x00, 0xFF); // 青色 ws2812_send(PwmBuf); DelayMs(1000); ws2812_fill_all(PwmBuf, 0x00, 0xFF, 0xFF); // 紫色 ws2812_send(PwmBuf); DelayMs(1000); while(1) // 無限循環,持續律動 { // 1. 從左到右:目標色依次點亮,背景色保持不變 for(uint8_t j = 0; j < WS2812_LED_NUM; j++) { // 每次循環先重置所有LED為背景色(避免殘留之前的目標色) ws2812_fill_all(PwmBuf, 0x55, 0xAA, 0xFF); // 將當前位置j的LED設為目標色 ws2812_set_single(PwmBuf, j, 0xAA, 0x55, 0x00); // 發送數據,更新LED顯示 ws2812_send(PwmBuf); // 控制律動速度,數值越小越快(可根據需求調整,如300ms) DelayMs(300); } // 2. 從右到左:目標色依次熄滅(恢復背景色),模擬“往回走” for(int8_t j = WS2812_LED_NUM - 2; j >= 0; j--) // 從倒數第二個開始(最后一個已點亮,無需重復) { // 重置所有LED為背景色 ws2812_fill_all(PwmBuf, 0x55, 0xAA, 0xFF); // 將當前位置j的LED設為目標色 ws2812_set_single(PwmBuf, j, 0xAA, 0x55, 0x00); // 發送數據,更新LED顯示 ws2812_send(PwmBuf); // 保持與“從左到右”相同的速度,確保律動流暢 DelayMs(300); } } }
浙公網安備 33010602011771號