WS2812C IO口模擬控制

以前,一直很疑惑,那些掛在樹上的LED燈條,是如何實現流水的效果的。燈條是如此的窄,不可能放下很多的信號線,除非是串行的連接。受限於知識面,一直不太清楚爲什麼可以做到,也算是懶,一直沒去查這方面的資料。

直到有一天,突然發現了某個村產的帶鎖存器的5050 LED燈珠,才恍然大悟,原來如此。

這段時間,由於要做開關面板,並且面板上需要有背光燈,於是就想起了這個神奇的5050燈珠。。於是就將其畫在了原理圖上。

原理圖很簡單,針對我們使用的WS2812C目前使用5V供電,每顆的VCC腳附近,需要放一顆104電容,因爲燈珠內部還有數字電路。另外,就是DI,DOUT的串行數據線。連接在一起即可。

至於前面的電阻電容,只是用來防靜電的一些常用措施。

不同於網絡上常用的WS2812B,我們購買的是WS2812C,也得感謝立創,其實爲我們這些小公司提供了很大的便利。特別是研發階段的物料購買上。

在軟件上,整體時序如下:

1. 系統上電,WS2812操作口,保持低電平。

2. 開始發送數據,空閒時,IO口爲低電平

3. 數據發送完畢後,維持低電平,超過規格書上定義的RESET時間。特別解釋下,只要低電平時間超過規格書上的時間(280uS),即可認爲是reset,而不需要先拉高,再拉低。

如下是官方提供的整體時序圖。我在網絡上搜了很多的BBS和教程,其實大家都很專注在每個bit的實現上,而多數都忽略了整體的時序。也有可能是我的思維方式的問題,我一般喜歡從整體到細節的去掌握一個東西。瞭解了整體時序後,在去看字節組成,再去看位如何實現。

另外,右圖是流程圖。先發數據,後發RESET波形。

如下是每24bit的組成。注意,順序不是RGB888,而是GRB888。一般我們取顏色的數值,都是RGB順序,所以這裏在代碼裏實現的時候,會需要做一下移位。另外,需要注意的是,需要高位先發(MSB)。

可能是以前的移位寄存器用多了,當發現最近的WS2812鎖存的是第一幀的24bit數據,後面的燈珠依次類推,才發現,這顆燈珠做的其實挺人性化的。這點比以前的移位寄存器好多了。

另外,內部自帶整型,所以掛長條燈帶,也不怕波形畸變了。只能說,雖然是村裏出廠的產品,其實考慮了挺多的因素在裏面了。然後,就是確定用什麼樣的波形,來表示bit。波形以及時間參數如下:

用什麼樣的方法去表示bit的波形,網絡上的方法有很多。例如PWM,也有用SPI。。由於是第一次驅動2812,就先別折騰,我暫時就先用IO口模擬來實現吧。

目前選擇的MCU是ST新出的STM32G070,主頻設置爲64MHz。WS2812的IO口連接在PA7上。另外需要注意的是,STM32是3V的電平,WS2812是5V電平,因此這裏需要有一顆常用的電平轉換芯片,我這裏用的是SN74LVC1T45DBVR。電路如下所示:

1. 在GPIO口初始化時,將PA7設置爲輸出,並且輸出爲0.

HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);

GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;//GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF0_SPI1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

2.定義存儲顏色的緩衝區,我這裏定義了兩個數組。一個是存儲原始RGB顏色的數組,另外一個是將RGB拆分成GRB的數組。另外,我的板子上總共有8顆2812,因此定義如下:

#define LED_COUNT     8
unsigned long RGBBuffer[LED_COUNT];
unsigned char GRBBuffer[LED_COUNT*3];

void WS2812_Initial_IO(void) //一個初始化函數:
{
    for(unsigned int i = 0; i < LED_COUNT; i++)
    {
        RGBBuffer[i] = 0;
    }
    
    WS2812_Set_Led_Color(0, 0);
}
//送入的顏色是RGB,所以內部需要轉換一下,Index爲第幾個燈,RGB是顏色參數
void WS2812_Set_Led_Color(unsigned char Index, unsigned long RGB)
{
    unsigned long GRB = 0;
    
    GRB = (RGB << 8) & 0x00ff0000;
    GRB |= ((RGB >> 8) & 0x0000ff00);
    GRB |= (RGB & 0x000000ff);
    
    RGBBuffer[Index] = GRB;
    
    for(unsigned char i = 0; i < LED_COUNT; i++)
    {
        GRBBuffer[i*3] = RGBBuffer[i] >> 16;
        GRBBuffer[i*3 +1] = RGBBuffer[i] >> 8;
        GRBBuffer[i*3 + 2] = RGBBuffer[i] >> 0;
    }
    
    GPIOA->MODER &= 0xffff3fff; //normal gpio
    GPIOA->MODER |= 0x00004000;
    unsigned char i;
    unsigned char j = 0;
    unsigned char Dat = 0;
    
    for(j = 0; j < 3*LED_COUNT; j++)
    {
        Dat = GRBBuffer[j];
        
        for(unsigned char i = 0; i < 8; i++)
        {
            if(Dat & 0x80)
            {
                GPIOA->BSRR = (uint32_t)GPIO_PIN_7;
                __NOP();__NOP();__NOP();__NOP(); //nop的個數,都是通過示波器量出來的
                __NOP();__NOP();__NOP();__NOP();
                __NOP();__NOP();__NOP();__NOP();
                __NOP();__NOP();__NOP();__NOP();
                __NOP();__NOP();__NOP();__NOP();
                __NOP();__NOP();__NOP();__NOP();
                __NOP();__NOP();__NOP();__NOP();
                __NOP();__NOP();__NOP();__NOP();
                GPIOA->BRR = (uint32_t)GPIO_PIN_7;
                __NOP(); __NOP();__NOP();__NOP();
                __NOP();__NOP();__NOP();
            }
            else
            {
                GPIOA->BSRR = (uint32_t)GPIO_PIN_7;
                __NOP();__NOP();__NOP();__NOP();
                __NOP();__NOP();__NOP();__NOP();
                __NOP();__NOP();__NOP();
                GPIOA->BRR = (uint32_t)GPIO_PIN_7;
                __NOP();__NOP();__NOP();__NOP();
                __NOP();__NOP();__NOP();__NOP();
                __NOP();__NOP();__NOP();__NOP();
                __NOP();__NOP();__NOP();__NOP();
                __NOP();__NOP();__NOP();__NOP();
                __NOP();__NOP();__NOP();
            }
            
            Dat = Dat << 1;
        }
    }
    
    HAL_Delay(1); //等待復位結束
}

主函數調用方法如下:

WS2812_Initial_IO();//熄滅所有燈

WS2812_Set_Led_Color(0, 0x0f0000); //第一盞爲紅燈,較暗
WS2812_Set_Led_Color(1, 0xf00000); //第二盞爲紅燈,較亮
WS2812_Set_Led_Color(2, 0x00ff00); //第三盞爲綠燈,全亮
WS2812_Set_Led_Color(3, 0x0000ff); //第四盞爲藍燈,全亮
WS2812_Set_Led_Color(4, 0x00ffff); //第五盞爲藍白燈,全亮

從以上的代碼裏,也可以看出來,燈珠的亮度,是可調的,通過調整送入的數值來調整。

第一步就先做到IO口模擬來控制,後面可以嘗試用SPI+DMA來模擬試試看。只要整體時序對了,也有參考了,那麼下一步就會比較容易一些了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章