WS2812B是一个集控制电路与发光电路于一体的智能外控LED光源。具有较小体积,其外型尺寸与一个3535LED灯珠相同,每个元件即为一个像素点。像素点内部包含了智能数字接口数据锁存信号整形放大驱动电路,还包含有高精度的内部振荡器和可编程定电流控制部分,有效保证了像素点光的颜色高度一致。
数据协议采用单线归零码的通讯方式,像素点在上电复位以后,DIN端接受从控制器传输过来的数据,首先送过来的24bit数据被第一个像素点提取后,送到像素点内部的数据锁存器,剩余的数据经过内部整形处理电路整形放大后通过DO端口开始转发输出给下一个级联的像素点,每经过一个像素点的传输,信号减少24bit。像素点采用自动整形转发技术,使得该像素点的级联个数不受信号传送的限制,仅仅受限信号传输速度要求。
高达 2KHz 的端口扫描频率,在高清摄像头的捕捉下都不会出现闪烁现象,非常适合高速移动产品的使用。280μs以上的 RESET 时间,出现中断也不会引起误复位,可以支持更低频率,价格便宜的MCU。LED具有低电压驱动、环保节能、亮度高、散射角度大、一致性好超、低功率及超长寿命等优点。将控制电路集成于LED上面,电路变得更加简单,体积小,安装更加简便。
在这个文档里面我们可以看到,要想驱动ws2812,我们就需要给灯发送24位的数据,这24位的数据代表的就是灯的颜色, 这里我们需要注意的是灯的颜色是GRB的顺序.
我们该如何发送这些数据呢? 在上图中我们可以看到数据虽然是24位, 但是每一位要么是0,要么是1, 我们只需要按照规则发送0码和1码即可.
- 0码:
- T0H高电平时间范围: 220ns~380ns
- T0L低电平时间范围: 580ns~1000ns
- 1码:
- T1H高电平时间范围: 580ns~1000ns
- T0L低电平时间范围: 580ns~1000ns
从上图输入码型我们可以看到, 0码和1码高低电平时间之和是相同的, 所以我们可以考虑按照一定周期去控制高低电平.
我们假定周期时长为1050ns, 那么我们该如何得到这么长的周期呢? 答案显而易见,我们可以考虑使用定时器.
已经GD32F470定时器的主频为240Mhz, 我们可以来推导一下,要多少个计数值才能达到1050ns
GD32F470频率为240Mhz
240mhz = 240 000 000
1000ms/240 000 000
1000 000us/240 000 000
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
有了这些参数之后, 下面我们就可以来初始化我们的定时器配置啦
/*
GD32F470频率为240Mhz
240mhz = 240 000 000
1000ms/240 000 000
1000 000us/240 000 000
10 00 000 000 ns/2 40 000 000 = 4.16ns
每隔5ns,计数值+1
ws2812 周期为1250ns 所以在不分频的情况下, 需要1050ns/4.16 = 252个计数值
300*0.68 高电平 204
300*0.32 低电平 96
*/
timer_oc_parameter_struct timer_ocintpara;
timer_parameter_struct timer_initpara;
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
rcu_periph_clock_enable(RCU_TIMER0);
timer_deinit(TIMER0);
timer_struct_para_init(&timer_initpara);
timer_initpara.period = WS2812_CODE1 + WS2812_CODE0;
timer_init(TIMER0,&timer_initpara);
/* CH0 configuration in PWM mode */
timer_channel_output_struct_para_init(&timer_ocintpara);
timer_ocintpara.outputstate = TIMER_CCX_ENABLE;
timer_channel_output_config(TIMER0,TIMER_CH_0,&timer_ocintpara);
timer_channel_output_pulse_value_config(TIMER0,TIMER_CH_0,0);
timer_channel_output_mode_config(TIMER0,TIMER_CH_0,TIMER_OC_MODE_PWM0);
timer_channel_output_shadow_config(TIMER0,TIMER_CH_0,TIMER_OC_SHADOW_ENABLE);
timer_primary_output_config(TIMER0,ENABLE);
timer_dma_enable(TIMER0, TIMER_DMA_CH0D);
timer_auto_reload_shadow_enable(TIMER0);
timer_enable(TIMER0);
为了提高控制效率, 我们使用配置一下DMA, 当数据发生变化之后, 自动更新数据到外设
void ws2812_dma_config(){
//1.DMA单数据结构体
dma_single_data_parameter_struct dma_init_struct;
//2.开启DMA时钟
rcu_periph_clock_enable(RCU_DMA1);
//3.初始化DMA通道
dma_deinit(WS2812_DMA,WS2812_DMA_CHANNEL);
//4
// 外设地址
dma_init_struct.periph_addr = (uint32_t)(TIMER0 + 0x34U);
// 不使用增量模式,为固定模式
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
// 内存地址(用来接收数据)
dma_init_struct.memory0_addr = (uint32_t)(ws2812_buffer);
// 增量模式
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
// 一次传输长度8bit
dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_32BIT;
// 关闭循环模式
dma_init_struct.circular_mode = DMA_CIRCULAR_MODE_ENABLE;
// 外设到内存
dma_init_struct.direction = DMA_MEMORY_TO_PERIPH;
// 要传输的数据量
dma_init_struct.number = TRANSFER_NUM;
// 超高优先级
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
//5.初始化DMA结构体
dma_single_data_mode_init(WS2812_DMA,WS2812_DMA_CHANNEL,&dma_init_struct);
dma_circulation_enable(WS2812_DMA, WS2812_DMA_CHANNEL);
//6.使能通道外设
dma_channel_subperipheral_select(WS2812_DMA,WS2812_DMA_CHANNEL,DMA_SUBPERI6);
//7.使能DMA通道
dma_channel_enable(WS2812_DMA,WS2812_DMA_CHANNEL);
}
控制灯的色彩
void ws2812_setColor_Index(uint8_t index,uint32_t color){
uint32_t rgb_value = color;
for(uint8_t i=0;i<24;i++)
{
if((rgb_value<<i)&0x800000) //高位先发,此时高位为1时
{
ws2812_buffer[i+index*24] = WS2812_CODE1; //68%占空比170 748ns
}
else
{
ws2812_buffer[i+index*24] = WS2812_CODE0; //32%占空比80 300ns
}
}
}