要想驱动WS2812灯带,必须弄明白其工作原理。这点我们从厂家提供的规格书中很容易整明白。
需要注意的是,不同的厂家,这些参数会有较大的差异,很可能导致无法点亮灯珠,所以我们需要根据不同厂家的产品,调整相应的参数。但数据传输方式是相同的,也就是说我们的代码应该是不用修改的,只是有必要的时候,需要调整相应的参数,比如0码、1码,RES时间等。
WS2812灯珠对时序要求还是比较严格的,虽然原则上可以通过直接写IO口,并用冗余代码的方式来控制时间从而达到控制WS2812灯带的目的,但这种方式几乎没有任何实用性,不同的主频需要修改代码,甚至只是简单的代码优化都需要修改代码,否则就没办法点亮灯珠。尤其是对CH582/CH592这种芯片,我们采用这种芯片的理由大多都是需要BLE功能,而要保留BLE功能就更不可能采用这种驱动方式。
正是因为上述原因,我们采用PWM+DMA的方式来驱动WS2812灯珠,先上代码。
ws2812.h
#include "CH59x_common.h"
#ifndef _WS2812_DEFINE
#define _WS2812_DEFINE
#define WS2812_GPIO_PIN GPIO_Pin_11
#define WS2812_RESET_TIME 280
#define LED_NUMS 60
#define WS2812_LED_BIT 24
#define PIXEL_NUM LED_NUMS
#define WS2812_RST_NUM 240
#define NUM (WS2812_LED_BIT*PIXEL_NUM + WS2812_RST_NUM) // Reset 280us / 1.25us = 224
#define WS1 45
#define WS0 30
#define WS2812_LED_DATA_0 30
#define WS2812_LED_DATA_1 45
#define WS2812_LED_DATA_LEN NUM
enum {
WS2812_LED_LEVEL_0 = 0,
WS2812_LED_LEVEL_1,
WS2812_LED_LEVEL_2,
WS2812_LED_LEVEL_3,
WS2812_LED_LEVEL_4,
WS2812_LED_LEVEL_5,
WS2812_LED_LEVEL_6,
WS2812_LED_LEVEL_7,
WS2812_LED_LEVEL_8,
WS2812_LED_LEVEL_9,
WS2812_LED_LEVEL_10,
WS2812_LED_LEVEL_11,
WS2812_LED_LEVEL_12,
WS2812_LED_LEVEL_13,
WS2812_LED_LEVEL_14,
WS2812_LED_LEVEL_15,
WS2812_LED_LEVEL
};
typedef struct
{
uint8_t G;
uint8_t R;
uint8_t B;
uint8_t phase;
}WS2812_LED_CTRL;
void ws281x_rainbowCycle(void);
void ws2812_Init(void);
void WS2812_LED_Circle(void);
#endif
ws2812.c
#include "ws2812.h"
__attribute__((aligned(4))) uint32_t LED_Data[NUM];
WS2812_LED_CTRL ws2812_led = {0xff,0,0,0};
void ws2812_Init(void)
{
GPIOB_ModeCfg(GPIO_Pin_11, GPIO_ModeOut_PP_5mA);
GPIOPinRemap(ENABLE, RB_PIN_TMR2);
TMR2_PWMCycleCfg(75); // 周期 1.25us
TMR2_DMACfg(ENABLE, (uint16_t)(uint32_t)&LED_Data[0], (uint16_t)(uint32_t)&LED_Data[WS2812_LED_DATA_LEN], Mode_LOOP);
TMR2_PWMInit(High_Level, PWM_Times_1);
/* 开启计数溢出中断,计满1000个周期进入中断 */
TMR2_ClearITFlag(TMR1_2_IT_DMA_END);
PFIC_EnableIRQ(TMR2_IRQn);
TMR2_ITCfg(ENABLE, TMR1_2_IT_DMA_END);
TMR2_PWMEnable();
TMR2_Enable();
for (uint8_t idx = 0; idx < PIXEL_NUM ;idx ++)
LED_Data[idx] = WS0;
}
uint32_t WS281x_Color(uint8_t red, uint8_t green, uint8_t blue) { return red << 16 | green << 8 | blue; }
uint32_t Wheel(uint8_t WheelPos)
{
WheelPos = 255 - WheelPos;
if (WheelPos < 85)
{
return WS281x_Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
if (WheelPos < 170)
{
WheelPos -= 85;
return WS281x_Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
WheelPos -= 170;
return WS281x_Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
void WS281x_SetPixelColor(uint16_t n, uint32_t GRBColor)
{
uint8_t i;
if (n < PIXEL_NUM)
{
for (i = 0; i < WS2812_LED_BIT; ++i)
LED_Data[WS2812_LED_BIT * n + i] = (((GRBColor << i) & 0X800000) ? WS1 : WS0);
}
}
uint16_t phase = 0;
void ws281x_rainbowCycle(void)
{
uint16_t i, j;
j = phase ++;
if (phase > 256)
phase = 0;
for(i=0; i< PIXEL_NUM; i++)
{
WS281x_SetPixelColor(i,Wheel(((i * 256 / PIXEL_NUM) + j) & 255));
}
// DelayMs(wait);
}
void Fill_WS2812_LED_Data(uint8_t G,uint8_t R,uint8_t B,uint16_t Pos)
{
uint32_t indexx=(Pos*WS2812_LED_BIT);
if (Pos > LED_NUMS)
{
for (uint8_t idx = 0; idx < LED_NUMS ;idx ++)
LED_Data[idx] = WS2812_LED_DATA_0;
}
else
{
for (uint8_t i = 0;i < 8;i++)
{
LED_Data[indexx+i] = (G << i) & (0x80)?WS2812_LED_DATA_1:WS2812_LED_DATA_0;
LED_Data[indexx+i + 8] = (R << i) & (0x80)?WS2812_LED_DATA_1:WS2812_LED_DATA_0;
LED_Data[indexx+i + 16] = (B << i) & (0x80)?WS2812_LED_DATA_1:WS2812_LED_DATA_0;
}
}
}
void WS2812_LED_PowerOff(void)
{
for (uint8_t idx = 0; idx < LED_NUMS ;idx ++)
LED_Data[idx] = WS2812_LED_DATA_0;
}
void WS2812_LED_Circle(void)
{
uint16_t idx,i;
// uint8_t level;
for (idx = LED_NUMS - 1; idx > 0; idx --)
{
for (i = 0; i < WS2812_LED_BIT; i ++)
LED_Data[idx* WS2812_LED_BIT + i] = LED_Data[(idx-1) * WS2812_LED_BIT + i];
}
if (ws2812_led.phase < WS2812_LED_LEVEL)
{
ws2812_led.G -= (0xff / WS2812_LED_LEVEL);
ws2812_led.R += (0xff / WS2812_LED_LEVEL);
ws2812_led.phase ++;
if (ws2812_led.phase == WS2812_LED_LEVEL)
{
ws2812_led.G = 0;
ws2812_led.R = 0xff;
}
}
else if (ws2812_led.phase < WS2812_LED_LEVEL * 2)
{
ws2812_led.R -= (0xff / WS2812_LED_LEVEL);
ws2812_led.B += (0xff / WS2812_LED_LEVEL);
ws2812_led.phase ++;
if (ws2812_led.phase == WS2812_LED_LEVEL * 2)
{
ws2812_led.R = 0;
ws2812_led.B = 0xff;
}
}
else if (ws2812_led.phase < WS2812_LED_LEVEL * 3)
{
ws2812_led.B -= (0xff / WS2812_LED_LEVEL);
ws2812_led.G += (0xff / WS2812_LED_LEVEL);
ws2812_led.phase ++;
if (ws2812_led.phase == WS2812_LED_LEVEL * 3)
{
ws2812_led.B = 0;
ws2812_led.G = 0xff;
ws2812_led.phase = 0;
}
}
Fill_WS2812_LED_Data(ws2812_led.G,ws2812_led.R,ws2812_led.B,0);
// Change_WS2812LED_Status();
}
/*********************************************************************
* @fn TMR2_IRQHandler
*
* @brief TMR2中断函数
*
* @return none
*/
__INTERRUPT
__HIGH_CODE
void TMR2_IRQHandler(void)
{
if(TMR2_GetITFlag(TMR1_2_IT_DMA_END))
{
TMR2_ClearITFlag(TMR1_2_IT_DMA_END);
PRINT("DMA end\n");
/* DMA 结束 */
/* 用户可自行添加需要的处理 */
}
}
增加WS2812灯带显示事件:
#define HAL_RGBLED_EVENT 0x0008
添加初始化代码:
void HAL_Init()
{
halTaskID = TMOS_ProcessEventRegister(HAL_ProcessEvent);
HAL_TimeInit();
ws2812_Init();
#if(defined HAL_SLEEP) && (HAL_SLEEP == TRUE)
HAL_SleepInit();
#endif
#if(defined HAL_LED) && (HAL_LED == TRUE)
HAL_LedInit();
#endif
#if(defined HAL_KEY) && (HAL_KEY == TRUE)
HAL_KeyInit();
#endif
#if(defined BLE_CALIBRATION_ENABLE) && (BLE_CALIBRATION_ENABLE == TRUE)
tmos_start_task(halTaskID, HAL_REG_INIT_EVENT, 800); // 添加校准任务,500ms启动,单次校准耗时小于10ms
#endif
tmos_start_task(halTaskID, HAL_RGBLED_EVENT,MS1_TO_SYSTEM_TIME(1000));
// tmos_start_task( halTaskID, HAL_TEST_EVENT, 1600 ); // 添加一个测试任务
}
添加显示事件处理代码:
if(events & HAL_RGBLED_EVENT)
{
WS2812_LED_Circle();
tmos_start_task(halTaskID, HAL_RGBLED_EVENT, MS1_TO_SYSTEM_TIME(100));
return events ^ HAL_RGBLED_EVENT;
}
从规格书中,我们知道TH+TL大概1.1us,这样我们设置PWM的周期为1.25us,频率800K。我们主频为60M,也就是60个脉冲为1us,1.25us需要75个脉冲。
TMR2_PWMCycleCfg(75); // 周期 1.25us
两次数据传输必须发送RESET CODE,实际上就是保持数据线为低电平300us左右,所以我们DMA的数据buff中,需要加上这段时间的传输数据,#define WS2812_RESET_TIME 240,240 * 1.25 = 300。这样我们可以设置DMA方式为循环方式,这样设置后,我们不再需要代码控制WS2812灯珠了。如果需要修改不同的显示效果,只需要修改DMA的数据buff就可以。
灯珠的显示效果大家可以根据自己的需求重写代码,我们这里只是实现了一种跑马灯的效果。代码提供了两种算法,效果略有差异。一种通过调用WS2812_LED_Circle()实现,一种调用ws281x_rainbowCycle()实现。
另外有一点需要注意的是,虽然WS2812灯珠的数据是8位,但CH592的DMA传输DataSize好象只能是32位的,所以DMA的DataSize不能设置为byte,而要设置为WORD,所以DMA的数据buff的类型设置为uint32_t 。
最后需要完整工程文件的可以参考: