前言
最近公司打算引进新的平台(华大)来做产品开发,板子到手后,老大让我先点个流水灯看看效果。本来以为只是一个简单的IO操作加上一些应用逻辑就可以实现的功能,但了解到了ws2812的操作时序要求后,也是花了点功夫才实现相应的功能。
一、WS2812是什么?
WS2812是一种集成了控制电路与发光电路于一体的LED光源。对于WS2812的具体介绍网上有很多资料,这里就不再赘述,下面我们从数据手册中罗列出我们编程所需要的关键内容。
主要特点:
● IC控制电路与LED点光源共用一个电源。
● 每个通道工作电流5mA.
● 控制电路与RGB芯片集成在一个2020封装的元器件中,构成一个完整的外控像素点。
● 内置信号整形电路,任何一个像素点收到信号后经过波形整形再输出,保证线路波形畸变不会累加。
● 内置上电复位和掉电复位电路。
● 每个像素点的三基色颜色可实现256级亮度显示,完成16777216种颜色的全真色彩显示。
● 端口扫描频率2KHz/s。
● 串行级联接口,能通过一根信号线完成数据的接收与解码。
● 任意两点传输距离在不超过5米时无需增加任何电路。
● 当刷新速率30帧/秒时,级联数不小于1024点。
● 数据发送速度可达800Kbps。
● 光的颜色高度一致,性价比高。
引脚及功能如下:
数据传输方法:
数据结构:
数据传输时间
时序波形图
从以上这些我们可以了解到,WS2812是可以多个器件同时级联的,通过串行数据传输可以同时对多个器件进行控制。控制一个WS2812所需要的字节数是三个字节(GRB),而从时序波形图中,我们可以了解到码0和码1所对应的波形图,相应的波形需要的时序达到ns级别的,因此,按照原来的想法,直接控制IO翻转操作来发送是达不到这样的时序要求的,因此需要通过其他方法来发送0码和1码。
二、通过SPI控制WS2812
1.原理
上网查找了一些资料,对于WS2812的控制,大部分人都是采用SPI的方式来控制的,我们也采用此方法。
从前面的码型图我们可以知道,0码和1码都是由不同占比的高低电平时间来表示的,相应的高低电平时间也有一定的冗余量,因此,我们可以通过调整SPI发送一帧数据的时钟频率以及发送的数据,来实现对应的0码和1码。简单的举个例子,我们可以通过控制分频系数来使得SPI的MOSI脚在1.25us内输出一帧数据(8byte),假设我们输出的数据是0xF0,这个时候,这帧数据的高低电平占比刚好是1:1,也就是高低电平持续的时间都是600ns左右,这刚好对应ws2812的1码,假设我们输出的数据是0xC0,同理,对应于0码。通过这种方式输出0码和1码,我们就可以很简单的实现操作ws2812。
2.功能实现
代码如下:
首先是SPI的初始化,我们需要先配置好SPI的时钟频率,这里根据板子不同配置也不一样。
/*宏定义*/
/* SPI_MOSI Port/Pin definition */
#define SPI_MOSI_PORT (PortB)
#define SPI_MOSI_PIN (Pin09)
#define SPI_MOSI_FUNC (Func_Spi3_Mosi)
/* SPI unit and clock definition */
#define SPI_UNIT (M4_SPI3)
#define SPI_UNIT_CLOCK (PWC_FCG1_PERIPH_SPI3)
/* SPI DMA unit and channel definition */
#define SPI_DMA_UNIT (M4_DMA1)
#define SPI_DMA_CLOCK_UNIT (PWC_FCG0_PERIPH_DMA1)
#define SPI_DMA_TX_CHANNEL (DmaCh1)
#define SPI_DMA_TX_TRIG_SOURCE (EVT_SPI3_SPTI)
void WS2812C_Init(void)
{
stc_spi_init_t stcSpiInit;
/* configuration structure initialization */
MEM_ZERO_STRUCT(stcSpiInit);
/* Configuration peripheral clock */
PWC_Fcg1PeriphClockCmd(SPI_UNIT_CLOCK, Enable);
/* Configuration SPI pin */
PORT_SetFunc(SPI_MOSI_PORT, SPI_MOSI_PIN, SPI_MOSI_FUNC, Disable);
/* Configuration SPI structure */
stcSpiInit.enClkDiv = SpiClkDiv4;
stcSpiInit.enFrameNumber = SpiFrameNumber1;
stcSpiInit.enDataLength = SpiDataLengthBit16; //一帧数据16个字节
stcSpiInit.enFirstBitPosition = SpiFirstBitPositionMSB;
stcSpiInit.enSckPolarity = SpiSckIdleLevelLow;
stcSpiInit.enSckPhase = SpiSckOddChangeEvenSample;
stcSpiInit.enReadBufferObject = SpiReadReceiverBuffer;
stcSpiInit.enWorkMode = SpiWorkMode3Line;
stcSpiInit.enTransMode = SpiTransFullDuplex;
stcSpiInit.enCommAutoSuspendEn = Disable;
stcSpiInit.enModeFaultErrorDetectEn = Disable;
stcSpiInit.enParitySelfDetectEn = Disable;
stcSpiInit.enParityEn = Disable;
stcSpiInit.enParity = SpiParityEven;
stcSpiInit.enMasterSlaveMode = SpiModeMaster;
stcSpiInit.stcDelayConfig.enSsSetupDelayOption = SpiSsSetupDelayCustomValue;
stcSpiInit.stcDelayConfig.enSsSetupDelayTime = SpiSsSetupDelaySck1;
stcSpiInit.stcDelayConfig.enSsHoldDelayOption = SpiSsHoldDelayCustomValue;
stcSpiInit.stcDelayConfig.enSsHoldDelayTime = SpiSsHoldDelaySck1;
stcSpiInit.stcDelayConfig.enSsIntervalTimeOption = SpiSsIntervalCustomValue;
stcSpiInit.stcDelayConfig.enSsIntervalTime = SpiSsIntervalSck6PlusPck2;
SPI_Init(SPI_UNIT, &stcSpiInit);
Spi_DmaConfig();
}
配置DMA功能
static uint8_t u8TxBuffer[48] = {0};
static uint16_t u16BufferLen = 48;
static uint16_t u16BufferCnt = 0;
static void Spi_DmaConfig(void)
{
stc_dma_config_t stcDmaCfg;
/* configuration structure initialization */
MEM_ZERO_STRUCT(stcDmaCfg);
/* Configuration peripheral clock */
PWC_Fcg0PeriphClockCmd(SPI_DMA_CLOCK_UNIT, Enable);
PWC_Fcg0PeriphClockCmd(PWC_FCG0_PERIPH_AOS, Enable);
/* Configure TX DMA */
stcDmaCfg.u16BlockSize = 1u;
stcDmaCfg.u16TransferCnt = u16BufferLen;
stcDmaCfg.u32SrcAddr = (uint32_t)(&u8TxBuffer[0]);
stcDmaCfg.u32DesAddr = (uint32_t)(&SPI_UNIT->DR);
stcDmaCfg.stcDmaChCfg.enSrcInc = AddressIncrease;
stcDmaCfg.stcDmaChCfg.enDesInc = AddressFix;
stcDmaCfg.stcDmaChCfg.enTrnWidth = Dma8Bit;
stcDmaCfg.stcDmaChCfg.enIntEn = Disable;
DMA_InitChannel(SPI_DMA_UNIT, SPI_DMA_TX_CHANNEL, &stcDmaCfg);
DMA_SetTriggerSrc(SPI_DMA_UNIT, SPI_DMA_TX_CHANNEL, SPI_DMA_TX_TRIG_SOURCE);
/* Enable DMA. */
DMA_Cmd(SPI_DMA_UNIT, Enable);
}
输出码0和码1
#define WS2812_LOW (0xF000)
#define WS2812_HIGH (0xFF00)
static void RGB_Set_Up(void)
{
u8TxBuffer[u16BufferCnt] = (WS2812_HIGH >> 8 & 0xFF);
u8TxBuffer[u16BufferCnt+1] = WS2812_HIGH & 0xFF;
u16BufferCnt += 2;
}
static void RGB_Set_Down(void)
{
u8TxBuffer[u16BufferCnt] = (WS2812_LOW >> 8 & 0xFF);
u8TxBuffer[u16BufferCnt+1] = WS2812_LOW & 0xFF;
u16BufferCnt += 2;
}
ws2812控制接口
void WS2812C_SetRGB(uint32_t RGB888)
{
int8_t i = 0;
uint8_t byte = 0;
u16BufferCnt = 0;
for(i = 23; i >= 0; i--)
{
byte = ((RGB888>>i)&0x01);
if(byte == 1)
{
RGB_Set_Up();
}
else
{
RGB_Set_Down();
}
}
DMA_SetSrcAddress(SPI_DMA_UNIT, SPI_DMA_TX_CHANNEL, (uint32_t)(&u8TxBuffer[0]));
DMA_SetTransferCnt(SPI_DMA_UNIT, SPI_DMA_TX_CHANNEL, u16BufferLen);
/* Enable DMA channel */
DMA_ChannelCmd(SPI_DMA_UNIT, SPI_DMA_TX_CHANNEL, Enable);
/* Enable SPI to start DMA */
SPI_Cmd(SPI_UNIT, Enable);
while (Reset == DMA_GetIrqFlag(SPI_DMA_UNIT, SPI_DMA_TX_CHANNEL, TrnCpltIrq))
{
}
DMA_ClearIrqFlag(SPI_DMA_UNIT, SPI_DMA_TX_CHANNEL, TrnCpltIrq);
/* Disable SPI */
SPI_Cmd(SPI_UNIT, Disable);
}
最后是一个流水灯的灯效
//先定义好一个数组并做好初始化
static uint32_t color_group[WS2812C_LED_NUM] = {0};
/* init color_group */
for(i = 0; i < WS2812C_LED_NUM; i++)
{
if(i < 6)
{
for(j = 0; j < 6; j++)
color_group[i] = LED_COLOR_BLUE & (~(0x01<<j));
}
else if(i < 12)
{
for(j = 0; j < 6; j++)
color_group[i] = LED_COLOR_RED & (~(0x01<<j));
}
else if(i < 18)
{
for(j = 0; j < 6; j++)
color_group[i] = LED_COLOR_GREEN & (~(0x01<<j));
}
else if(i < 24)
{
for(j = 0; j < 6; j++)
color_group[i] = LED_COLOR_YELLOW & (~(0x01<<j));
}
else if(i < 30)
{
for(j = 0; j < 6; j++)
color_group[i] = LED_COLOR_CYAN & (~(0x01<<j));
}
else
{
for(j = 0; j < 6; j++)
color_group[i] = LED_COLOR_PURPLE & (~(0x01<<j));
}
}
//流水灯
void Led_Cycle_Run(void)
{
uint8_t i = 0;
static uint8_t index = 0;
for(i = 0; i < WS2812C_LED_NUM; i++)
{
WS2812C_SetRGB(color_group[(index + i)%WS2812C_LED_NUM]);
}
index ++;
index %= WS2812C_LED_NUM;
Ddl_Delay1ms(50);
}
总结
至此,通过HC32控制ws2812的功能已实现,具体效果图就不放上来了,后续有其他东西再分享。