这里使用的是乐鑫的 ESP32-S3-DevKitC-1 开发板,搭载的是 Wi-Fi + Bluetooth® LE 模组 ESP32-S3-WROOM-1 ,内置芯片配置是 ESP32-S3FN16R8,开发板组件包含一下内容
接着给大家讲的是 esp-idf 对 RGB-LED的驱动
参考来自官网的原理图纸:https://dl.espressif.com/dl/SCH_ESP32-S3-DEVKITC-1_V1_20210312C.pdf
抽取关键部分对其进行分析,可以发现模组是通过 GPIO48 对 RGB-LED 实现控制,并且 RGB-LED 除了 DIN 还有 DOUT 引脚,有点奇怪。因为一般我们见的 RGB ,如果想产生不同的颜色,会有3个IO实现多种脉冲宽度调制,搭配产生多种颜色;按原理理解,肯定需要给不同的脉冲给 RGB,我们才能显示多颜色,那么这里的一个 DIN 数据,怎么能产生出多种脉冲宽度,猜测里面会有个串行数据转并行数据的 IC,帮我们把输入的数据并行传输到三个LED里。
我们可以通过搜索RGB型号,来找到这个RGB相应的驱动信息,SK68XXMINI-HS,以 SK6812 为例
到这里基本可以验证了我们的猜想
因为对于模组而言,这个驱动 RGB 的程序驱动,是属于外设,一般都会有例程支持,所以我们在 esp-idf 的 example/peripherals 中进行寻找,或者直接进行关键字 “led” 的搜索,可以发现和 led 控制有关的分别是 ledc 文件夹中的 ledc_basic 和 ledc_fade ,除此外还有 rmt 文件夹中的 led_strip
另外,结合官网手册理解这几个 example 分别是对于什么的应用,首先,我们找到的是 ledc(LED控制器)https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32s3/api-reference/peripherals/ledc.html
那么在这个位置,我们可以发现,它确实能控制 RGB LED,但下面看到驱动时需要绑定相应的GPIO,看到这里就可以知道,它输出的脉冲应该是针对单个 IO 的脉冲宽度调制,而不是我们所说的串行数据转并行实现灯的控制。
现在我们来看一下 rmt 文件夹中的 led_strip。
led_strip 中文翻译成 LED 灯带,属于多个 LED 的同时控制。我们的原理图除了 DIN 管脚,还有一个 DOUT 管脚,虽然板子上没有接东西,但是这个管脚既然命名为 DOUT,说明它是属于数据输出,还可以接下一级控制,那么这个 led_strip 也就和我们当前需要的案例,更接近了。
通过查找资料得知,SK6812 内置的 IC 和 WS2812 内置 IC 型号不同,WS2812 比 SK6812 早推出市场,但 SK6812 做了防反接保护。使用上,程序控制部分是兼容的,测试的时候 SK6812 刷新率比 WS2812 稍微要好,传言两个厂家开始联合开发后,再分开用自己的技术进行生产。
RMT(远程控制)模块驱动程序可用于发送和接收红外远程控制信号。由于RMT模块的灵活性,驱动器还可以用来生成或接收许多其他类型的信号。
该信号由一系列脉冲组成,由RMT的发射机根据一系列值生成。这些值定义了脉冲持续时间和二进制电平。发射机还可以提供载波,并用提供的脉冲对其进行调制。
ESP32S3 这个 RMT 外设不仅可以灵活地映射到不同 IO、可以改变通道,还可以设置不同的时钟用于驱动,更改分频等灵活配置,只需要按 SK68xx 手册上描述的电平时序设定 RMT 外设的时钟以及分频值,做简单的数据传输简直易如反掌。
/*****************************************************************************************
Function Description : 初始化 led 灯串
Function name : led_strip_t *led_strip_init(uint8_t channel, uint8_t gpio, uint16_t led_num);
Function Parameter :
@Para1 : channel-RMT 通道
@Para2 : gpio-实现功能的gpio
@Para3 : led_num-led灯珠数量
eg : pUserLED_Strip = led_strip_init(RMT_TX_CHANNEL, RMT_TX_NUM, LED_STRIP_NUM);
*****************************************************************************************/
led_strip_t *led_strip_init(uint8_t channel, uint8_t gpio, uint16_t led_num)
{
static led_strip_t *pStrip;
rmt_config_t config = RMT_DEFAULT_CONFIG_TX(gpio, channel);
// set counter clock to 40MHz
config.clk_div = 2;
ESP_ERROR_CHECK(rmt_config(&config));
ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, 0));
// install ws2812 driver
led_strip_config_t strip_config = LED_STRIP_DEFAULT_CONFIG(led_num, (led_strip_dev_t)config.channel);
pStrip = led_strip_new_rmt_ws2812(&strip_config);
if (!pStrip)
{
ESP_LOGE(TAG, "install WS2812 driver failed");
return NULL;
}
// Clear LED strip (turn off all LEDs)
ESP_ERROR_CHECK(pStrip->clear(pStrip, 100));
return pStrip;
}
函数原型
/*****************************************************************************************
Function Description : 设置 led 灯珠颜色
Function name : static esp_err_t ws2812_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
Function Parameter :
@Para1 : strip-led 控制句柄
@Para2 : index-当前控制灯珠所属下标
@Para3 : red-R 值
@Para4 : green-G 值
@Para5 : blue-B 值
*****************************************************************************************/
static esp_err_t ws2812_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
由于函数原型是static类型,作用域只在当前文件,所以用户无法直接调用它进行控制;但是在 led_strip_rmt_ws2812.c 源文件里,实现了对应用 API 的抽象管理,单独用函数指针实现指向,用户直接使用函数指针进行调用,可以不去涉及原函数的操作,另外,将来有更方便的 IC 出现的时候,可以继续增加对底层驱动的管理,重新实现指针指向后可以更少地修改应用层逻辑。
led_strip_t *led_strip_new_rmt_ws2812(const led_strip_config_t *config)
{
led_strip_t *ret = NULL;
STRIP_CHECK(config, "configuration can't be null", err, NULL);
// 24 bits per led
uint32_t ws2812_size = sizeof(ws2812_t) + config->max_leds * 3;
ws2812_t *ws2812 = calloc(1, ws2812_size);
STRIP_CHECK(ws2812, "request memory for ws2812 failed", err, NULL);
uint32_t counter_clk_hz = 0;
STRIP_CHECK(rmt_get_counter_clock((rmt_channel_t)config->dev, &counter_clk_hz) == ESP_OK, "get rmt counter clock failed", err, NULL);
// ns -> ticks
float ratio = (float)counter_clk_hz / 1e9;
ws2812_t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS);
ws2812_t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS);
ws2812_t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS);
ws2812_t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS);
// set ws2812 to rmt adapter
rmt_translator_init((rmt_channel_t)config->dev, ws2812_rmt_adapter);
ws2812->rmt_channel = (rmt_channel_t)config->dev;
ws2812->strip_len = config->max_leds;
ws2812->parent.set_pixel = ws2812_set_pixel;
ws2812->parent.refresh = ws2812_refresh;
ws2812->parent.clear = ws2812_clear;
ws2812->parent.del = ws2812_del;
return &ws2812->parent;
err:
return ret;
}
#include "driver/gpio.h"
#include "esp_err.h"
#include "led_strip.h"
#include "rmt_types.h"
led_strip_t *pUserLED_Strip;
#define LED_ON \
{ \
pUserLED_Strip->set_pixel(pUserLED_Strip, 0, 0xFF, 0x00, 0x00); \
pUserLED_Strip->refresh(pUserLED_Strip, 10); \
}
#define LED_OFF \
{ \
pUserLED_Strip->clear(pUserLED_Strip, 50); \
vTaskDelay(50 / portTICK_PERIOD_MS); \
}
void LED_Init(void)
{
pUserLED_Strip = led_strip_init(RMT_TX_CHANNEL, RMT_TX_NUM, LED_STRIP_NUM);
}
/******************************************************
****** Function : void LED_START_Indicate(void)
****** Detail IO :
****** Description : LED 开机指示
****** Step :
*******************************************************/
void LED_START_Indicate(void)
{
LED_ON;
vTaskDelay(500 / portTICK_PERIOD_MS);
LED_OFF;
vTaskDelay(500 / portTICK_PERIOD_MS);
LED_ON;
vTaskDelay(500 / portTICK_PERIOD_MS);
LED_OFF;
vTaskDelay(500 / portTICK_PERIOD_MS);
LED_ON;
vTaskDelay(500 / portTICK_PERIOD_MS);
LED_OFF;
vTaskDelay(1000 / portTICK_PERIOD_MS);
}