本文以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号