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来模拟试试看。只要整体时序对了,也有参考了,那么下一步就会比较容易一些了。

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