写点什么

ESP32-C3 入门教程 基础篇(五、RMT 应用 — 控制 SK6812 全彩 RGB 灯)

作者:矜辰所致
  • 2022 年 9 月 21 日
    江苏
  • 本文字数:7668 字

    阅读完需:约 25 分钟

ESP32-C3入门教程 基础篇(五、RMT应用 — 控制SK6812全彩RGB 灯)
测试第五课,本来是准备测试一下PWM驱动 SK6812 RGB灯,但是研究了一段时间,发现在ESP32-C3 有更好而且现成的方式 实现 SK6812 的控制,使用PWM也不是不可以,只是对于初学者,需要多花好多时间,所以本文还是先以ESP32-C3内置的 RMT 进行 SK6812 的控制,毕竟有现成的示例
复制代码


前言

在开发板上面,我画了一个 SK6812 RGB 灯,当时因为对于 SK6812 没有进一步的了解,所以写的是 PWMLED ,现在已经改过来了:



本文我们来学习一下 SK6812 全彩 RGB 的使用以及 ESP32-C3 如何控制它。


测试使用的开发板:

自己画一块 ESP32-C3 的开发板(第一次使用立创 EDA)(PCB 到手)

https://xie.infoq.cn/article/30387388381a0d915b2494f91

测试使用的开发环境:

ESP32-C3 VScode 开发环境搭建(基于乐鑫官方 ESP-IDF——Windows 和 Ubuntu 双环境)

https://xie.infoq.cn/article/5b639e112cabba00cc1b8941a

基础篇系列相关博文:

ESP32-C3 入门教程 基础篇(一、ADC 采样)

https://xie.infoq.cn/article/78eff739dd2ed4971f445272a

ESP32-C3 入门教程 基础篇(二、GPIO 中断、按键驱动测试)

https://xie.infoq.cn/article/7d090d74fb0a9449986954810

ESP32-C3 入门教程 基础篇(三、UART 模块 — 与 Enocean 无线模块串口通信)

https://xie.infoq.cn/article/55d8a9cbd211b4d99b53935ee

ESP32-C3 入门教程 基础篇(四、I2C 总线 — 与 SHT21 温湿度传感器通讯)

https://xie.infoq.cn/article/75a22c7d3499c2d5428f726e8

1、GPIO 示例测试

一、 SK6812 LED 基础介绍

SK6812 灯珠集成了 控制电路与发光电路与一体的智能外控 LED 光源。 外形与 5050 LED 灯珠是一样的。 但是与普通的 LED 不同的是,他不是简单的通过高低电平来控制亮灭, 它通过 单线就能控制 RGB 三色的亮灭,采用了一个叫 单极性归零码 数据协议的通讯方式。

1.1 SK6812 控制原理

基础介绍在使用的产品的手册中都有,这里就截取我开发板使用的产品手册中的图片来说明一下:



对于使用者来说,我们需要知道的主要是理解这个协议,然后实现手册中提到的 “0”码 和“1”码,然后每一个灯珠,是由 24 bit 的数据结构组成。

(注意这里说的 24 bit 是数据结构,举个例子, 如果我们使用 SPI 实现“0” 、“1” 码,SPI 总线发送一个字节,只是实现了一个 码,只是上面 24 bit 数据结构中的 1bit !后面会更加详细的说明这一点)


我们把对应需要了解的参数都截图说明(根据自己选用的产品规格书来确定具体参数):





额外添加点说明,不同比例的三原色光相加得到彩色称为相加混色,比如:


红+绿=黄红+蓝=紫蓝+绿=青红+蓝+绿=白


举个例子:显示黄色,其 RGB 值为 255, 255, 0。 那么上面的 24bit 数据结构为:1111 1111 1111 1111 0000 0000 (其中的 1 和 0 是上面说的 “1”码 和“0”码)


综合上面,不管使用哪一种方式,其中都是需要在 DIN 端给出符合时间要求的高低电平,才能正确的控制 SK6812:


1.2 SK6812 控制方案

知道了 SK6812 的控制原理,在开发板上面,我们使用一个 IO 口 连接 DIN 端,作为信号的输入端,那么就需要实现 这个 IO 口实现符合时序的 高低电平,那么有哪些实现方式呢?

1.2.1 GPIO 翻转

最终目的是需要实现规定时间的高低电平,那么最直接想到的就是 直接把 GPIO 翻转速度设置成最大,然后直接置位 复位 GPIO 实现高低电平。 = =!


但是仔细想一下上面的时间要求是 us 级别的,那么对于不同的芯片,不仅是因为主频不同,IO 口的翻转速度当然也会不同,在网络上查看到(仅供参考):


1、STM32


  • 看到一篇博文直接操作寄存器 222ns 采用库函数指令会延迟 300ns 左右,参考博文 无聊测一下IO口翻转速度 STM32F103RCT6

  • 以主频为 72MHz 为例,指令控制 GPIO 翻转,最高可达 18MHz。

  • 直接操作寄存器,单指令周期的,407 超频 200M 的时候,IO 口刚好 100M


上述事件自己通过公式算一下即可:f=1/T。(T 的单位是秒(s),f 的单位是赫兹(Hz))。


2、ESP32


  • ESP32 的 IO 口速度,简单查找没有找到说明。


3、ESP8266


  • ESP8266 的 GPIO 有效翻转大约须要 2.5us(0.4MHz)

  • ESP8266 的 GPIO0 的翻转速度最快,配合寄存器操作可以实现


算下来,目前来说 MCU 的发展,还是有能够直接控制 IO 口电平实现的条件,这种简单粗暴的方式需要经过反复的测试调整,因为对于时间的控制还需要考虑很多因素。所以这里介绍一下,不过多探究。

1.2.2 SPI 方式

SPI 方式,SPI 通讯的速度目前器件可以达到大几十 Mbps,一般情况下,SPI 模块的最大时钟频率为系统时钟频率的 1/2。SPI 的基础知识网上很多,在我博文《总线协议记录》也有记录。


所以通过 SPI 总线发送是比较可行的一种方式。 只要将 SPI 的时钟调整为 8MHz 左右(小于等于 8Mhz),这样不同的 MCU 下,都可以实现。


采用 8Mhz SPI,发送一个字节所需时间 1.25us,满足上面 SK6812 的 24bit 数据结构 一个 bit 的时间:


再根据 “1” 码 < 1us && >0.6us 的 高电平, >0.2us 的低电平,得出,SPI 发送一个字节 11111100b 即表示 “1” 码,0XF0。


同理可得,SPI 发送字节 11000000b 即表示 “0” 码,0xC0。


那么还是按照上面原理部分举的例子,显示黄色的 24bit 数据结构为:1111 1111 1111 1111 0000 0000


那么 SPI 总线发送如下的 24 个字节数据(十六进制),就能使得 LED 显示为黄色:


F0 F0 F0 F0 F0 F0 F0 F0 F0 F0 F0 F0 F0 F0 F0 F0 C0 C0 C0 C0 C0 C0 C0 C0
复制代码

1.2.2 PWM 方式

PWM 方式也是一种常见的控制高低电平的方式,通过上面我们得知,“1”码 和 “0” 码的高低电平比例为 3:1. 那么就是调节占空比 ,“1”码的占空比为 75% ,“0”码的占空比为 25%。 那么剩下的只需要把 PWM 的周期设置成 SK6812 的码元周期 > 1.2us 左右。 注意占空比多少,可以根据实际情况调整。


给出 2 个网上的结论,仅供参考:


  • cycle(周期)=1.2us,占空比=50%为 1,占空比=30%为 0;(833Khz ,感觉还行)

  • 周期设置为 3MHz,占空比 = 66% 为 1,占空比=33%为 0;(0.33us 周期,估计写错了,1MHZ 还差不多, 1us)


关于 ESP32 -C3 使用 PWM 方式,不确定可不可以用,毕竟 ESP32 -C3 的 PWM 按照我们的博文流程我们还没有学习测试,下一篇博文写一下 ESP32 -C3 的 PWM 学习测试记录。

1.2.3 RMT 方式(ESP32)

RMT,是 ESP32 系列特有的一个红外发送和接收控制器,红外协议转化为信号,体现在 IO 上也就是高低电平。


将上面讲到的 “1” 码 和 “0” 码当成红外信号,也可以实现 SK6812 的控制。下面就先来了解下 ESP32-C3 的 RMT。

二、 ESP32-C3 RMT 介绍

2.1 RMT 基础介绍

在乐鑫官方 ESP32-C3 芯片手册《esp32-c3_technical_reference_manual_cn》文档中对于 RMT 有详细的介绍:



在官方网站也有关于 RMT 相关 API 的详细介绍:乐鑫官方ESP32-C3 RMT部分说明


所以详细的资料还是可以通过上面的途径查看,这里我们需要关注的一点就是:


RMT 是如何控制 SK6812 的?


通过前面的 SK6812 控制原理我们知道了,控制 SK2812 就是实现符合时间规定的高低电平,那么在 ESP32-C3 芯片手册中,有提到 RMT 是如何实现此功能的,对于部分如下图:(当然如果要了解更深还是要好好查看官方的资料)



结合官网图片就能更容易理解:


2.2 RMT 使用介绍(API 相关)

RMT 的使用基本步骤如下,但是本文我们是需要控制 SK6812 ,所以只需要了解发送相关的配置及使用:



首先要了解的是一个结构体,发送配置的结构体rmt_tx_config_t



上述结构体内容 依次是:RMT 载波频率、RMT 输出的电平、空闲电平状态、占空比、最大循环计数、载波使能、循环发送使能、空闲电平输出使能。


通过初始化结构体的示例,可以更好的理解:



RMT 输出结构体默认配置如下:



对于控制 SK6812,目前了解到 RMT 的输入配置就可以了。

三、 RMT 示例测试

3.1 IDF 示例测试

在 IDF 示例程序中,官方提供了控制 WS2812 的示例 RMT Transmit Example -- LED Strip



程序的过程比较简单,SK6812 的驱动和 ws2812 的驱动是一样的,相关的代码在components/led_strip/src/led_strip_rmt_ws2812.c 文件中。


针对自己的开发板,然后对于示例工程,简单修改一下既可以看到效果,因为示例大家都一样,这里就使用截图表示需要修改的地方:




在示例中EXAMPLE_CHASE_SPEED_MS 太快了,闪得我眼睛有点花,把这个时间改成了 300:


#define EXAMPLE_CHASE_SPEED_MS (300)//
复制代码


在我的开发板上面,本来确实是只有一个 LED,但是为了测试,我飞线焊接了一个:



测试结果,示例的现象就是,LED 不同颜色的交替闪烁,并没有渐变效果,这里上几张图勉强看看:


3.2 示例改渐变效果

最开始也没有一点一点的去分析驱动代码,示例代码也就看看 RMT 的配置,后面的 SK6812 驱动部分并没有仔细研究,所以测试是闪烁效果,后来想想还是不得劲,不渐变闪烁,这不得亮瞎眼= =!


所以还是得改改,所以看了看示例,其实也就是简单的修改(最后一个vTaskDelay(50)不需要,这里是以前改过的代码忘了去掉了):



根据上面图示的说明,把所有时间改成如下,是基于例程基础 最平滑 最快速的渐变了:



没视频看不到= =! 上张图勉强应付一下:


四、 SK6812 驱动代码说明

2022/6/16 更新   by 矜辰所致
复制代码


最近 ESP32-C3 的学习博主已经更新完了 蓝牙 GATT 篇章,正准备写一篇蓝牙的小应用,计划要通过手机与开发板进行蓝牙连接,控制板子上的灯,能够渐变当然是最好了,忽然发现 SK6812 的驱动函数忘了怎么用了……


在官方示例中,给了最原始的驱动,但是感觉当时没有理解透彻,所以回过头来重新看一看。

4.1 驱动函数简析

我们在使用中,需要定义一个 LED 变量,比如:


static led_strip_t *strip;
复制代码


我们来看一看 led_strip_t ,他是 led_strip_s 结构体类型:


struct led_strip_s {    /**    设置灯的颜色    */    esp_err_t (*set_pixel)(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue);
/** 更新灯的颜色 */ esp_err_t (*refresh)(led_strip_t *strip, uint32_t timeout_ms);
/** 清除灯的颜色 */ esp_err_t (*clear)(led_strip_t *strip, uint32_t timeout_ms);
/** 删除灯这个对象 */ esp_err_t (*del)(led_strip_t *strip);};
复制代码


在示例中我们都使用到了这几个函数,简单记录一下这几个函数的说明:


设置灯的颜色:


/*参数含义:灯的句柄,我们开始定义的变量需要设置的灯的位置下标,从0开始,如果有很多灯,一般都是使用for循环赋值红色的值绿色的值蓝色的值
*/static esp_err_t ws2812_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
复制代码


更新灯的值:


使用上面函数设置完 LED 颜色值后,需要调用ws2812_refresh 将颜色更新到灯条:



static esp_err_t ws2812_refresh(led_strip_t *strip, uint32_t timeout_ms)
复制代码


清除灯的颜色:


等于把等熄灭:


static esp_err_t ws2812_clear(led_strip_t *strip, uint32_t timeout_ms)
复制代码


SK2812 设备注册函数:


另外还有一个函数需要注意,就是用来注册新的 SK2812 设备的函数:


led_strip_t *led_strip_new_rmt_ws2812(const led_strip_config_t *config)
复制代码


使用此函数来注册设备,比如:


4.2 设置指定颜色

设置指定延时,我们可以直接使用 ws2812_set_pixel 函数,对于我们定义的结构体变量,根据示例使用即可,设置指定颜色就很简单了:



上图颜色 绿色 和 红色 搞反了!set_pixel(strip, 0, 255, 0, 0)是绿色,第二个是红色 !

红色 和 绿色 对应位置好像反了,是因为 ESP-IDF 驱动中的颜色处理驱动函数是反过来的:



为了与正常的颜色值对应,改成如下即可:



当然为了对应颜色代码(比如 #0033FF 形式)我们可以简单的做几个函数处理一下:


struct WS2812_COLOR{  uint8_t lamp;  uint8_t ligth_rank;  uint8_t lamp_speed;  uint32_t red;  uint32_t green;  uint32_t blue;};
static led_strip_t *strip;
struct WS2812_COLOR WS2812_RGB;

void RGB16for10(struct WS2812_COLOR *RGB, uint32_t reb_16){ uint32_t rgb_16 = reb_16; RGB->blue = rgb_16 & 0Xff;
rgb_16 = rgb_16 >> 8; RGB->green = rgb_16 & 0xff;
rgb_16 = rgb_16 >> 8;
RGB->red = rgb_16 & 0xff;}
void set_rgb(uint32_t rgb_24bit, uint8_t ligth_rank){ RGB16for10(&WS2812_RGB, rgb_24bit); ligth_rank = 21 - ligth_rank; for (int i = 0; i < LED_STRIP_NUM; i++) { strip->set_pixel(strip, i, WS2812_RGB.red / ligth_rank, WS2812_RGB.green / ligth_rank, WS2812_RGB.blue / ligth_rank); }
strip->refresh(strip, 10);}
复制代码


在使用的时候,可以直接使用 set_rgb 函数:



如果设置其他颜色,需要查对应的表格了,在网上找了一张表格,简单看看,网上也有很多 RGB 查询工具,一搜索就可以出来:


4.2 几个渐变驱动

渐变的示例,通过上文官方的示例也可以做到,我这里发现一片好的文章,文章博主写了几种好的方式,博文连接如下:


ESP32使用外设RMT控制WS2812灯条


程序选自上面推荐博文:


程序一:


/** * @brief sin()函数从0到2π的样本值,一共255个点,最大值为255,最小值为0 *  * 离散信号函数:SinValue(k)=(255*sin(2*k*π/255)+255)/2 (四舍五入取整数) *  */uint8_t  const SinValue[256]={  128,   131,   134,   137,   140,   143,   147,   150,   153,   156,                                159,   162,   165,   168,   171,   174,   177,   180,   182,   185,                                188,   191,   194,   196,   199,   201,   204,   206,   209,   211,                                214,   216,   218,   220,   223,   225,   227,   229,   230,   232,                                234,   236,   237,   239,   240,   242,   243,   245,   246,   247,                                248,   249,   250,   251,   252,   252,   253,   253,   254,   254,                                255,   255,   255,   255,   255,   255,   255,   255,   255,   254,                                254,   253,   253,   252,   251,   250,   249,   249,   247,   246,                                245,   244,   243,   241,   240,   238,   237,   235,   233,   231,                                229,   228,   226,   224,   221,   219,   217,   215,   212,   210,                                208,   205,   203,   200,   198,   195,   192,   189,   187,   184,                                181,   178,   175,   172,   169,   166,   163,   160,   157,   154,                                151,   148,   145,   142,   139,   136,   132,   129,   126,   123,                                120,   117,   114,   111,   107,   104,   101,    98,    95,    92,                                89,    86,    83,    80,    77,    74,    72,    69,    66,    63,                                 61,    58,    55,    53,    50,    48,    45,    43,    41,    38,                                 36,    34,    32,    30,    28,    26,    24,    22,    21,    19,                                 17,    16,    14,    13,    12,    10,     9,     8,     7,     6,                                 5,     4,     4,     3,     2,     2,     1,     1,     1,     0,                                  0,     0,     0,     0,     1,     1,     1,     2,     2,     3,                                3,     4,     5,     6,     6,     7,     9,    10,    11,    12,                                  14,    15,    17,    18,    20,    21,    23,    25,    27,    29,                                 31,    33,    35,    37,    40,    42,    44,    47,    49,    52,                                 54,    57,    59,    62,    65,    67,    70,    73,    76,    79,                                 82,    85,    88,    91,    94,    97,   100,   103,   106,   109,                                  112,   115,   118,   121,   125,   128};  void WS2812B_ColourGradualChange1(led_strip_t *strip, uint16_t LED_Number, uint16_t GradualChangeRate){    uint32_t Green=0,Red=0,Blue=0;  uint8_t i,ir,ib;  for(i=0;i<255;i++)  {    ir=i+85;    ib=i+170;    Green=SinValue[i];    Red=SinValue[ir];    Blue=SinValue[ib];        for(int j=0; j < LED_Number; j ++){            // 设置ws2812的RGB的值            ESP_ERROR_CHECK(strip->set_pixel(strip, j, Red, Green, Blue));        }         // 给WS2812发送RGB的值        ESP_ERROR_CHECK(strip->refresh(strip, 100));        vTaskDelay(pdMS_TO_TICKS(GradualChangeRate));  }}
复制代码


程序二:


// 必须包含math.h库#include <math.h> void WS2812B_ColourGradualChange2(led_strip_t *strip,uint16_t LED_Number,uint16_t GradualChangeRate){    uint32_t Green=0,Red=0,Blue=0;    for(uint16_t i=0; i<628; i++)    {        // 使用sin()函数分别计算三原色的值        Green = (int)(127*sin(i/100.0)+127);        Red = (int)(127*sin((i+209.3)/100)+127);        Blue = (int)(127*sin((i+418.7)/100)+127);        for(int j=0; j < LED_Number; j ++){            // 设置ws2812的RGB的值            ESP_ERROR_CHECK(strip->set_pixel(strip, j, Red, Green, Blue));        }         // 给WS2812发送RGB的值        ESP_ERROR_CHECK(strip->refresh(strip, 100));        vTaskDelay(pdMS_TO_TICKS(GradualChangeRate));    }}
复制代码


第三个参数为渐变时间,测试过最小时间可能需要设置为 10 ,也就是 10ms(设置为 5ms 灯不会亮……)


程序三:


void WS2812B_TrottingHorseLamp1(led_strip_t *strip, uint16_t LED_Number, uint16_t GradualChangeRate){    uint32_t Green=0,Red=0,Blue=0;  uint8_t i,ir,ib;  for(i=0;i<256;i++)  {    ir=i+85;    ib=i+170;        for(int j=0; j < LED_Number; j ++){            // 根据灯珠的位置计算RGB的值        Green = SinValue[(i+10*j)%256];        Red = SinValue[(ir+10*j)%256];        Blue = SinValue[(ib+10*j)%256];            // 设置ws2812的RGB的值            ESP_ERROR_CHECK(strip->set_pixel(strip, j, Red, Green, Blue));        }         // 给WS2812发送RGB的值        ESP_ERROR_CHECK(strip->refresh(strip, 100));        vTaskDelay(pdMS_TO_TICKS(GradualChangeRate));  }}
复制代码


最后这个自己没有测试过,有机会再用起来。

结语

第一次的时候求速度,回头再看一遍感触颇多。


学习就得做到温故而知新 O(∩_∩)O

发布于: 22 小时前阅读数: 25
用户头像

矜辰所致

关注

CSDN、知乎、微信公众号: 矜辰所致 2022.08.02 加入

不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开! 为了活下去的嵌入式工程师,画画板子,敲敲代码,玩玩RTOS,搞搞Linux ...

评论

发布
暂无评论
ESP32-C3入门教程 基础篇(五、RMT应用 — 控制SK6812全彩RGB 灯)_ESP32-C3_矜辰所致_InfoQ写作社区