WS2812B是一个集控制电路与发光电路于一体的智能外控LED光源。具有较小体积,其外型尺寸与一个3535LED灯珠相同,每个元件即为一个像素点。像素点内部包含了智能数字接口数据锁存信号整形放大驱动电路,还包含有高精度的内部振荡器和可编程定电流控制部分,有效保证了像素点光的颜色高度一致。
    数据协议采用单线归零码的通讯方式,像素点在上电复位以后,DIN端接受从控制器传输过来的数据,首先送过来的24bit数据被第一个像素点提取后,送到像素点内部的数据锁存器,剩余的数据经过内部整形处理电路整形放大后通过DO端口开始转发输出给下一个级联的像素点,每经过一个像素点的传输,信号减少24bit。像素点采用自动整形转发技术,使得该像素点的级联个数不受信号传送的限制,仅仅受限信号传输速度要求。
    高达 2KHz 的端口扫描频率,在高清摄像头的捕捉下都不会出现闪烁现象,非常适合高速移动产品的使用。280μs以上的 RESET 时间,出现中断也不会引起误复位,可以支持更低频率,价格便宜的MCU。LED具有低电压驱动、环保节能、亮度高、散射角度大、一致性好超、低功率及超长寿命等优点。将控制电路集成于LED上面,电路变得更加简单,体积小,安装更加简便。

    image.png

    在这个文档里面我们可以看到,要想驱动ws2812,我们就需要给灯发送24位的数据,这24位的数据代表的就是灯的颜色, 这里我们需要注意的是灯的颜色是GRB的顺序.
    我们该如何发送这些数据呢? 在上图中我们可以看到数据虽然是24位, 但是每一位要么是0,要么是1, 我们只需要按照规则发送0码和1码即可.
    image.png

    • 0码:
      • T0H高电平时间范围: 220ns~380ns
      • T0L低电平时间范围: 580ns~1000ns
    • 1码:
      • T1H高电平时间范围: 580ns~1000ns
      • T0L低电平时间范围: 580ns~1000ns

    从上图输入码型我们可以看到, 0码和1码高低电平时间之和是相同的, 所以我们可以考虑按照一定周期去控制高低电平.
    我们假定周期时长为1050ns, 那么我们该如何得到这么长的周期呢? 答案显而易见,我们可以考虑使用定时器.

    已经GD32F470定时器的主频为240Mhz, 我们可以来推导一下,要多少个计数值才能达到1050ns

    1. GD32F470频率为240Mhz
    2. 240mhz = 240 000 000
    3. 1000ms/240 000 000
    4. 1000 000us/240 000 000
    5. 10 00 000 000 ns/2 40 000 000 = 4.16ns

    也就是每隔4.16ns, 计数值会增加1,如果我们想要计时1050ns, 那么我们需要 1050ns/4.16ns = 252个计数值
    下面我们进一步推导0码和1码需要的计数器值:
    0码: 高电平对应的计数应为300/4.16 = 72
    1码: 高电平对应的计数应为252 - 72 = 180
    有了这些参数之后, 下面我们就可以来初始化我们的定时器配置啦
    image.png

    image.png

    1. /*
    2. GD32F470频率为240Mhz
    3. 240mhz = 240 000 000
    4. 1000ms/240 000 000
    5. 1000 000us/240 000 000
    6. 10 00 000 000 ns/2 40 000 000 = 4.16ns
    7. 每隔5ns,计数值+1
    8. ws2812 周期为1250ns 所以在不分频的情况下, 需要1050ns/4.16 = 252个计数值
    9. 300*0.68 高电平 204
    10. 300*0.32 低电平 96
    11. */
    12. timer_oc_parameter_struct timer_ocintpara;
    13. timer_parameter_struct timer_initpara;
    14. rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
    15. rcu_periph_clock_enable(RCU_TIMER0);
    16. timer_deinit(TIMER0);
    17. timer_struct_para_init(&timer_initpara);
    18. timer_initpara.period = WS2812_CODE1 + WS2812_CODE0;
    19. timer_init(TIMER0,&timer_initpara);
    20. /* CH0 configuration in PWM mode */
    21. timer_channel_output_struct_para_init(&timer_ocintpara);
    22. timer_ocintpara.outputstate = TIMER_CCX_ENABLE;
    23. timer_channel_output_config(TIMER0,TIMER_CH_0,&timer_ocintpara);
    24. timer_channel_output_pulse_value_config(TIMER0,TIMER_CH_0,0);
    25. timer_channel_output_mode_config(TIMER0,TIMER_CH_0,TIMER_OC_MODE_PWM0);
    26. timer_channel_output_shadow_config(TIMER0,TIMER_CH_0,TIMER_OC_SHADOW_ENABLE);
    27. timer_primary_output_config(TIMER0,ENABLE);
    28. timer_dma_enable(TIMER0, TIMER_DMA_CH0D);
    29. timer_auto_reload_shadow_enable(TIMER0);
    30. timer_enable(TIMER0);

    为了提高控制效率, 我们使用配置一下DMA, 当数据发生变化之后, 自动更新数据到外设
    image.png
    image.png

    1. void ws2812_dma_config(){
    2. //1.DMA单数据结构体
    3. dma_single_data_parameter_struct dma_init_struct;
    4. //2.开启DMA时钟
    5. rcu_periph_clock_enable(RCU_DMA1);
    6. //3.初始化DMA通道
    7. dma_deinit(WS2812_DMA,WS2812_DMA_CHANNEL);
    8. //4
    9. // 外设地址
    10. dma_init_struct.periph_addr = (uint32_t)(TIMER0 + 0x34U);
    11. // 不使用增量模式,为固定模式
    12. dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    13. // 内存地址(用来接收数据)
    14. dma_init_struct.memory0_addr = (uint32_t)(ws2812_buffer);
    15. // 增量模式
    16. dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    17. // 一次传输长度8bit
    18. dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_32BIT;
    19. // 关闭循环模式
    20. dma_init_struct.circular_mode = DMA_CIRCULAR_MODE_ENABLE;
    21. // 外设到内存
    22. dma_init_struct.direction = DMA_MEMORY_TO_PERIPH;
    23. // 要传输的数据量
    24. dma_init_struct.number = TRANSFER_NUM;
    25. // 超高优先级
    26. dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
    27. //5.初始化DMA结构体
    28. dma_single_data_mode_init(WS2812_DMA,WS2812_DMA_CHANNEL,&dma_init_struct);
    29. dma_circulation_enable(WS2812_DMA, WS2812_DMA_CHANNEL);
    30. //6.使能通道外设
    31. dma_channel_subperipheral_select(WS2812_DMA,WS2812_DMA_CHANNEL,DMA_SUBPERI6);
    32. //7.使能DMA通道
    33. dma_channel_enable(WS2812_DMA,WS2812_DMA_CHANNEL);
    34. }

    控制灯的色彩

    1. void ws2812_setColor_Index(uint8_t index,uint32_t color){
    2. uint32_t rgb_value = color;
    3. for(uint8_t i=0;i<24;i++)
    4. {
    5. if((rgb_value<<i)&0x800000) //高位先发,此时高位为1时
    6. {
    7. ws2812_buffer[i+index*24] = WS2812_CODE1; //68%占空比170 748ns
    8. }
    9. else
    10. {
    11. ws2812_buffer[i+index*24] = WS2812_CODE0; //32%占空比80 300ns
    12. }
    13. }
    14. }