WS2812B是一款贴片RGB灯。由于采用了单总线通讯,所以需要特别关注下它的通讯时序。
调试细节:
本来以为会是一个比较简单的调试,结果还是花了很长时间才调试完成。
首先是关于ESP32的纳秒级延时确定,当时按照空指令始终调试不出来。之前在STM32平台上的nop()函数也不知道怎么用。
后来发掘出了一个比较简单的办法。就是一个个试,然后在main函数中按照1S的频率打印调试信息来倒推ns级别的延时是否可靠。
注意:在ESP32不能使用空语句加;来进行空指令延时,需要使用操作语句。
unsigned long ns_delay_value = 0;void delay_100_ns(int data)
{unsigned char i;ns_delay_value = 0;for(i = 0; i < data; i++){ns_delay_value++;}
}void delay_1_us()
{delay_100_ns(10);
}void delay_1_ms()
{long i;for(i = 0; i < 1000; i++){delay_1_us();}
}void delay_1_s()
{long i;for(i = 0; i < 1000; i++){delay_1_ms();}
}
然后在main函数中按照1S的频率打印调试信息:
while(1){printf("ws2812B demo system run ...\n");
// vTaskDelay(1000 / portTICK_PERIOD_MS);delay_1_s();}
基本确定了ns级别延时后,就可以按照时序来写ws2812的驱动函数啦。
*WS2812B Drive*/
#define WS2812B_GPIO 8
#define WS2812B_GPIO_ACTIVE_LEVEL 1
#define ws2812b_pin_set() gpio_set_level(WS2812B_GPIO, 1)
#define ws2812b_pin_rst() gpio_set_level(WS2812B_GPIO, 0)void ws2812b_writebyte(unsigned char data)
{unsigned char i;for(i = 0; i < 8; i++){if(data & 0X80){ws2812b_pin_set();delay_100_ns(3);ws2812b_pin_rst();delay_100_ns(3);}else{ws2812b_pin_set();delay_100_ns(1);ws2812b_pin_rst();delay_100_ns(3);}data <<= 1;}
}void ws2812b_write_rgb(unsigned char red_value, unsigned char green_value, unsigned char blue_value)
{ws2812b_writebyte(red_value);ws2812b_writebyte(green_value);ws2812b_writebyte(blue_value);
}
后来发现依然无法驱动,到了晚上才发现自己犯了一个低级错误。ESP32的IO口没有进行初始化配置!
void ws2812b_gpio_init(void)
{gpio_config_t gpio_conf;gpio_conf.intr_type = GPIO_INTR_DISABLE;gpio_conf.mode = GPIO_MODE_OUTPUT;gpio_conf.pin_bit_mask = (1ULL << WS2812B_GPIO);if (WS2812B_GPIO_ACTIVE_LEVEL) {gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;gpio_conf.pull_up_en = GPIO_PULLUP_DISABLE;} else {gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE;}gpio_config(&gpio_conf);
}
配置完IO口就可以进行WS2812B驱动啦。不过还是出现了一些小bug,比如初始化第一次点灯,绿色灯珠总是会不受控制地自动点亮!后来发现在IO初始化函数前预先执行一次点亮指令就可以消除这个bug!Nice!
void app_main(void)
{ws2812b_write_rgb(0, 255, 0);ws2812b_gpio_init();delay_1_s();ws2812b_write_rgb(20, 20, 20);while(1){printf("ws2812B demo system run ...\n");
// vTaskDelay(1000 / portTICK_PERIOD_MS);delay_1_s();}
}
至此,ESP32对于WS2812B的驱动函数就调试完成啦。
对于这个说起来简单但是异常曲折的小demo项目积累了如下经验:
1.ESP32的IO也是需要进行初始化配置的。
2.MCU的单指令确实能够进行粗略的ns级延时,为后续调试一些芯片时序提供了新的方法。
3.ESP32的GPIO8需要使用一个10K电阻上拉3.3V,否则无法进行程序下载。
2023-05-04 细节补充
1.在后续demo项目完善该驱动时,发现会出现初始化后,在别处点灯时,依然会出现亮出绿灯现象。
后来增加了灯珠的复位函数:
void ws2812b_write_reset(void)
{unsigned int i = 0;ws2812b_pin_rst();for(i = 0; i < 300; i++){delay_1_us();}}
然后初始化变更为:
ws2812b_write_rgb(0, 255, 0);
ws2812b_gpio_init();
ws2812b_write_reset();
ws2812b_write_rgb(20, 20, 20);
如果需要在别的函数位置电灯,择执行以下函数即可,亲测有效。
ws2812b_write_rgb(0, 0, 0);
ws2812b_write_reset();
ws2812b_write_rgb(0, 0, 20);