WS2812B的驅動信息
從WS2812的數據手冊中,可以看出其使用單總線級聯的方式進行通信,一組數據包括所有燈珠的顯示信息,每組數據之間通過RESET分隔。在同一組數據中,數據每過1個燈珠,減少24位,也就是說每個燈珠都讀取收到的前24位數據,作爲自己的顯示信息,其它的數據接著往下傳遞,直到遇到RESET信號,然後重新讀取24爲的數據。
对于每位数据是0还是1,则是通过占空比进行判断的。在这里我选取1.25us为1位数据的周期,300ns的高电平作为T0H,625ns的高电平作为T0L 。
現在需要做的就是不斷的改變定時器PWM的占空比,以實現數據的發送。每發送1位數據就要改變一次占空比的值,如果使用中斷的方式,那麽進入中斷的頻率就會很高,所以使用DMA的方式是比較合適的。
我這裏選擇的方式是,使用定時器的更新事件觸發DMA的數據傳輸,DMA使用循環模式一直發送,這樣編程比較簡單,不需要關注何時發送數據,只需要關注如何修改內存中的數據就行。
對于RESET的發送,從圖中可以看出RESET信號是持續280us以上的低電平,那麽就可以用高電平占空比爲0的PWM信號代替,也就是說224個以1.25us爲周期占空比爲0的PWM就可以作爲RESET信號了。
外設的設置包括3個部分:IO、DMA和定時器。
我使用的是PB4,其AF1的复用功能为TIMER2_CH0 。
rcu_periph_clock_enable(RCU_GPIOB);
gpio_af_set(GPIOB, GPIO_AF_1, GPIO_PIN_4);
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_4);
gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4);
DMA和STM32F1系列不同,GD32L23x中的DMA的通道不再是和外設對應起來的,而是通過複用器去與外設連接,這樣每一個通道都可以對應所有的外設,使用起來更加的靈活。
DMA的請求用的是TIMER2的更新事件
DMA_REQUEST_TIMER2_UP
DMA設置爲循環模式
dma_circulation_enable(DMA_CH0);
void dma_config(void)
{
dma_parameter_struct dma_data_parameter;
rcu_periph_clock_enable(RCU_DMA);
dma_deinit(DMA_CH0);
dma_data_parameter.periph_addr = (uint32_t)TIMER2_CH0CV;
dma_data_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_data_parameter.memory_addr = (uint32_t)(&buffer);
dma_data_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_data_parameter.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;
dma_data_parameter.memory_width = DMA_MEMORY_WIDTH_16BIT;
dma_data_parameter.direction = DMA_MEMORY_TO_PERIPHERAL;
dma_data_parameter.number = SIZE_OF_BUFFER;
dma_data_parameter.priority = DMA_PRIORITY_HIGH;
dma_data_parameter.request = DMA_REQUEST_TIMER2_UP;
dma_init(DMA_CH0, &dma_data_parameter);
dma_circulation_enable(DMA_CH0);
dma_channel_enable(DMA_CH0);
}
定時器的設置包含三部分:
時基單元
輸出模式
DMA事件設置
時基單元配置为1.25us的周期。輸出模式使用的是PWM0的模式,同时使能TIMER2的
TIMER_DMA_UPD
void timer_config(void)
{
timer_oc_parameter_struct timer_ocinitpara;
timer_parameter_struct timer_initpara;
timer_ic_parameter_struct timer_icinitpara;
rcu_periph_clock_enable(RCU_TIMER2);
timer_deinit(TIMER2);
timer_struct_para_init(&timer_initpara);
timer_initpara.prescaler = 0;
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 79;
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_init(TIMER2, &timer_initpara);
timer_channel_output_struct_para_init(&timer_ocinitpara);
timer_ocinitpara.outputstate = TIMER_CCX_ENABLE;
timer_ocinitpara.ocpolarity = TIMER_OC_POLARITY_HIGH;
timer_channel_output_config(TIMER2, TIMER_CH_0, &timer_ocinitpara);
timer_channel_output_pulse_value_config(TIMER2, TIMER_CH_0, 0);
timer_channel_output_mode_config(TIMER2, TIMER_CH_0, TIMER_OC_MODE_PWM0);
timer_dma_enable(TIMER2, TIMER_DMA_UPD);
timer_auto_reload_shadow_enable(TIMER2);
}
驅動代碼包括了两大部分:
使能定時器
將顔色數據放入內存中
注意不要直接使能定時器,而是要先产生1次更新事件,然后再使能定時器。因为这样才能将内存中的第一个数据放入到定时器的中。
timer_event_software_generate(TIMER2,TIMER_EVENT_SRC_UPG);
timer_enable(TIMER2);
需要做的工作有2個:
將顔色的數據轉化成定時器比較值寄存器中的數據
轉化數據格式:常用的顔色數據是RGB數據,而WS2812中顔色的存放順序是GRB。
#define T1H_COUNT 40//對于WS2812輸出位1時,PWM的計數值
#define T0H_COUNT 15//對于WS2812輸出位0時,PWM的計數值
void change_color_to_bit(uint32_t rgb_color,uint16_t* bit)
{
uint32_t temp = rgb_color;
int i;
uint16_t temp_bit;
for(i=0;i<24;i++){
if(temp & 0x00000001){
bit[23-i] = T1H_COUNT;
}else{
bit[23-i] = T0H_COUNT ;
}
temp = temp>>1;
}
for(i=0;i<8;i++){
temp_bit = bit;
bit = bit[8+i];
bit[8+i] = temp_bit;
}
}
上面的代碼將顔色數據轉化成比較值寄存器中的值。
直接拍攝的效果不太好,用一張白紙遮著的效果要好一些。
|