原理分析
WS2812B/WS2815B均爲RGB三色燈珠,WS2815B是WS2812B的升級版,區別在於兩點,首先是供電電壓由5V變爲了12V供電,有效的降低了整個像素點的工作電流,降低線路板壓降,最大限度保證像素點在很遠距離傳輸時達到良好的混光一致性。其次是額外增加了一路信號線,在單個像素點損壞的情況下,不影響整體顯示效果。
WS2815B多了一個BIN引腳,這個引腳接前一個燈珠的DI腳(燈帶第一個燈珠接地)。BIN端接收到數據信號丟棄24bit數據後,再將DIN接收的數據信號與BIN斷進行比較,若DIN端無信號,BIN端有接收到信號,切換到BIN端接收輸入信號,這種措施可以確保在單個燈珠損壞時不至於影響到其餘的燈珠,但是如果連續兩個燈珠損壞,依然會導致後邊的燈珠不受控制。
兩種燈珠需要不同的燈板(燈珠封裝不同),但是兩種燈珠需要的嵌入式軟件是一樣的(數據的定義以及歸零碼的碼制可以是一樣的)
嵌入式代碼
在嵌入式傳輸代碼的實現上。一般都存在兩種方式,一種爲IO口模擬,這種方式一般見以前玩51單片機的嵌入式工程師,諸如I2C,SPI等常見的通信協議總線都習慣用IO口去模擬時序。對於WS281XB的通信協議,沒有像SPI這種硬件幫我們實現的通信接口,這麼看來用IO口去模擬是一個擺在桌面的實現方式。但是IO口模擬存在一個致命弱點,那就中斷會打斷你的時序模擬。以10個燈珠的控制爲例,當你的代碼正在模擬時序發到控制第10個燈還沒有發的時候,中斷來了,這個時候IO口正好被模擬程序控制爲低,然後芯片去執行相應的中斷處理程序,執行了超過280μs(RESET),執行完再回來繼續發送第十個燈珠的數據,這時你會發現,你發送控制的第10個燈珠的數據其實發送給了第一個燈珠,因爲兩個數據之間因爲中斷的原因夾雜了一個RESET碼。
我們採取的是SPI+DMA的方法來實現,使用芯片內部的SPI控制器去發送燈珠的控制數據,又因爲我們採用的是DMA發送,能夠保證中斷來的時候芯片能依然準確的按照時序發送我們控制燈珠的數據。
我的實現是在STM32F10x系列的MCU,時鐘的情況如下:
我使用的是SPI3,初始化代碼如下:
void SPI3_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);;//初始化SPI發送IO口
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3,ENABLE);
SPI_I2S_DeInit(SPI3);
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;//SPI單線發送
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//發送數據寬爲8Bit
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;//36/4=9M,則傳輸1Bit時間=111ns
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 10;
SPI_Init(SPI3, &SPI_InitStructure);
SPI3->CR1 &= ((uint16_t)0xDFFF);//禁止CRC發送
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
DMA_DeInit(DMA2_Channel2);
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&SPI3->DR; //外設地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //DMA傳輸方向
DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)PixelBuffer;
DMA_InitStructure.DMA_BufferSize = 0; //需要發送的大小爲0,初始不執行發送操作
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外設爲發送數據8bit寬
DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte; //Ram存儲數據8Bit寬
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High ;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA2_Channel2, &DMA_InitStructure);
SPI_I2S_DMACmd(SPI3,SPI_I2S_DMAReq_Tx,ENABLE); //使能SPI使用DMA通道發送
SPI_Cmd(SPI3, ENABLE);//使能SPI控制器
}
當我們發送使用上述配置的SPI發送0xe0(11100000)時,SPI發送引腳高電平持續時間爲111ns3=333ns,低電平時間持續的時間爲111ns5=550,因爲SPI爲逐字節發送,用示波器量得SPI發送字節之間的間隙時間大約是100ns左右,則連續發送8字節數,則低電平持續的時間爲550ns+100ns=650ns,正符合WS281xB對0碼的要求。
於是,我們用發送一字節數據0xe0來模擬發送一個0碼。同理可得用0xFC發送1碼。用連續310個0x00來模擬發送RESET碼。代碼如下:
unsigned char PixelBuffer[PixelNumber*24+310] = {0};
void DMA2_Star_SPI_TX()
{
DMA2_Channel2->CNDTR=(PixelNumber*24+310);
DMA2_Channel2->CMAR=(uint32_t)PixelBuffer;
DMA_Cmd(DMA2_Channel2,ENABLE); //使能SPI3的DMA發送
while(!DMA_GetFlagStatus(DMA2_FLAG_TC2)); //循環等待發送完成,此時如果被中斷打斷,並不影響發送
DMA_Cmd(DMA2_Channel2,DISABLE);
DMA_ClearFlag(DMA2_FLAG_TC2);
return;
}
void Set_All_Pixel_Color(uint8_t r, uint8_t g, uint8_t b)
{
int i=0;
for (i = 0; i < 64; i++)//燈帶上有64個燈珠
{
Ws281x_Set_Pixel(Color_Show(r,g,b),i);
}
}
void Ws281x_Set_Pixel(uint32_t color,uint32_t position)//
{
unsigned int positionin=position*3;//一個燈珠3種顏色
uint8_t Red, Green, Blue;
Red = color>>16;
Green = color>>8;
Blue = color;
Ws281x_Set_Bits(Green,positionin);
Ws281x_Set_Bits(Red,positionin+1);
Ws281x_Set_Bits(Blue,positionin+2);
}
void Ws281x_Set_Bits(uint8_t bits,uint32_t position)
{
unsigned int positionin=0;
int zero = 0xe0; //11100000
int one = 0xfC; //11111100
int i = 0x00;
int j = 0x00;
positionin=position*8;//一個燈珠上的一種顏色,需要8位數表示亮度
for (i = 0x80; i >= 0x01; i >>= 1)
{
PixelBuffer[position+j]=((bits & i) ? one : zero);
j++;
}
}
uint32_t Color_Show(uint8_t r, uint8_t g, uint8_t b)
{
return ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
}
下面的代碼片是我們測試燈帶的主函數,主要實現的是三色循環點亮。
int main(void)
{
int i=0;
unsigned int PixColorDa=0;
uint8_t Red, Green, Blue;
SPI3_Init();//初始化SPI3
while(1)
{
for(i=0;i<3;i++)
{
PixColorDa=(0xff<<(8*i));//逐次點亮紅綠藍
Blue=(PixColorDa>>16)&0xff;
Green=(PixColorDa>>8)&0xff;
Red=(PixColorDa)&0xff;
Set_All_Pixel_Color(Red,Green,Blue);//設置燈帶上所有燈的顏色爲紅色
DMA2_Star_SPI_TX();//發送數據
sleep(1);//睡眠1秒,自己實現此函數
}
}
}