esp32s3+ws2812灯条控制
·
准备做一个小项目,然后打算开始一个个模块调试,最后集成。
首先找一个esp32s3官方例程,然后烧录编译确认芯片能用。然后去乐鑫官网-模组搜索ws2812,
复制idf.py add-dependency "inapps/ws2812^1.0.0"
到vscode的终端选择esp-idf终端打开,
粘贴,然后重新编译会出现led_strip_encoder.c和led_strip_encoder.h文件。
有点难懂,根据ai一起理解一下。
这是.c文件
#include "esp_check.h"
#include "led_strip_encoder.h"
static const char *TAG = "led_encoder"; // 日志标签,打印日志时会显示 [led_encoder]
typedef struct { // 定义这个编码器的“私有数据盒子”
rmt_encoder_t base; // 基类“身份证”,让系统知道这是一个编码器
rmt_encoder_t *bytes_encoder; // 指向“字节编码器”的指针(负责把数据转成波形)
rmt_encoder_t *copy_encoder; // 指向“拷贝编码器”的指针(负责直接复制复位信号)
int state; // 状态机:0=正在编码颜色数据,1=正在发送复位码
rmt_symbol_word_t reset_code; // 存着复位信号的具体波形(两个低电平段)
} rmt_led_strip_encoder_t; // 这个结构体类型名
RMT_ENCODER_FUNC_ATTR // 告诉编译器这个函数要放在 IRAM 里(加速)
static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel,
const void *primary_data, size_t data_size,
rmt_encode_state_t *ret_state)
{ // 编码函数:RMT 每次要数据时都会调用它
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
// 通过基类指针反推出包含它的外层结构体地址
rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder; // 取出字节编码器句柄
rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder; // 取出拷贝编码器句柄
rmt_encode_state_t session_state = RMT_ENCODING_RESET; // 子编码器返回的状态
rmt_encode_state_t state = RMT_ENCODING_RESET; // 最终要返回的状态
size_t encoded_symbols = 0; // 已经编码的符号数量
switch (led_encoder->state) { // 看当前处于哪个阶段
case 0: // 阶段0:编码颜色数据
encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, primary_data, data_size, &session_state);
// 调用字节编码器,把 primary_data 里的字节转换成 RMT 高低电平符号
if (session_state & RMT_ENCODING_COMPLETE) { // 如果字节编码器说“全部数据都编码完了”
led_encoder->state = 1; // 就把状态切换到阶段1(准备发复位)
}
if (session_state & RMT_ENCODING_MEM_FULL) { // 如果硬件发送缓冲区满了
state |= RMT_ENCODING_MEM_FULL; // 在最终状态里标记“内存满”
goto out; // 立即退出,等下次调用再继续
}
case 1: // 阶段1:发送复位信号(注意没有 break,会从上个 case 滑下来)
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code,
sizeof(led_encoder->reset_code), &session_state);
// 调用拷贝编码器,把提前准备好的 reset_code 直接复制到 RMT 发送缓冲区
if (session_state & RMT_ENCODING_COMPLETE) { // 如果复位信号也发送完了
led_encoder->state = RMT_ENCODING_RESET; //状态机回到初始状态(下次从头开始)
state |= RMT_ENCODING_COMPLETE; // 标记“全部完成”
}
if (session_state & RMT_ENCODING_MEM_FULL) { // 如果缓冲区又满了
state |= RMT_ENCODING_MEM_FULL; // 标记内存满
goto out; // 退出,等下次继续
}
}
out: // 退出标签
*ret_state = state; // 把最终状态传回给调用者
return encoded_symbols; // 返回本次编码的符号总数
}
static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder)
{ // 删除编码器,释放所有资源
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
// 反推出私有结构体地址
rmt_del_encoder(led_encoder->bytes_encoder); // 删除内部的字节编码器
rmt_del_encoder(led_encoder->copy_encoder); // 删除内部的拷贝编码器
free(led_encoder); // 释放私有数据盒子本身
return ESP_OK; // 返回成功
}
RMT_ENCODER_FUNC_ATTR
static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder)
{ // 重置编码器,清空内部状态,准备发新的一帧
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
// 反推出私有结构体
rmt_encoder_reset(led_encoder->bytes_encoder); // 重置字节编码器
rmt_encoder_reset(led_encoder->copy_encoder); // 重置拷贝编码器
led_encoder->state = RMT_ENCODING_RESET; // 把状态机置为 0(从头开始)
return ESP_OK;
}
esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
{ // 公共接口:创建一个 LED 编码器实例
esp_err_t ret = ESP_OK; // 保存错误码
rmt_led_strip_encoder_t *led_encoder = NULL; // 私有数据盒子指针
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
// 检查传入的配置指针和输出指针是否有效,无效就跳转到 err 标签
led_encoder = rmt_alloc_encoder_mem(sizeof(rmt_led_strip_encoder_t));
// 为私有结构体分配内存(可能使用特殊内存池)
ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG, "no memory for LED strip encoder");
// 如果分配失败,跳转到错误处理
led_encoder->base.encode = rmt_encode_led_strip; // 绑定编码函数
led_encoder->base.del = rmt_del_led_strip_encoder; // 绑定删除函数
led_encoder->base.reset = rmt_led_strip_encoder_reset; // 绑定重置函数
rmt_bytes_encoder_config_t bytes_encoder_config = { // 配置字节编码器的时序
.bit0 = { // 定义“0”码的波形
.level0 = 1, // 第一段电平:高
.duration0 = 0.3 * config->resolution / 1000000, // 高电平持续 0.3 微秒(换算成 ticks)
.level1 = 0, // 第二段电平:低
.duration1 = 0.9 * config->resolution / 1000000, // 低电平持续 0.9 微秒
},
.bit1 = { // 定义“1”码的波形
.level0 = 1, // 第一段电平:高
.duration0 = 0.9 * config->resolution / 1000000, // 高电平 0.9 微秒
.level1 = 0, // 第二段电平:低
.duration1 = 0.3 * config->resolution / 1000000, // 低电平 0.3 微秒
},
.flags.msb_first = 1, // 先发送字节的最高位(MSB)
};
ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder),
err, TAG, "create bytes encoder failed");
// 调用官方 API 创建字节编码器,如果失败跳转错误
rmt_copy_encoder_config_t copy_encoder_config = {}; // 拷贝编码器不需要任何配置
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &led_encoder->copy_encoder),
err, TAG, "create copy encoder failed");
// 创建拷贝编码器,失败则跳错
uint32_t reset_ticks = config->resolution / 1000000 * 50 / 2;
// 计算复位信号每个半段的 tick 数:每微秒 tick 数 × 50微秒 ÷ 2(分成两段)
led_encoder->reset_code = (rmt_symbol_word_t) { // 构造复位符号
.level0 = 0, // 第一段电平:低
.duration0 = reset_ticks, // 持续前半段时间
.level1 = 0, // 第二段电平:低
.duration1 = reset_ticks, // 持续后半段时间(总低电平时间 = 50微秒)
};
*ret_encoder = &led_encoder->base; // 把基类指针返回给用户(用户只认这个)
return ESP_OK; // 创建成功
err: // 错误处理标签
if (led_encoder) { // 如果私有盒子已经分配了
if (led_encoder->bytes_encoder) { // 如果字节编码器已经创建了
rmt_del_encoder(led_encoder->bytes_encoder); // 删除它
}
if (led_encoder->copy_encoder) { // 如果拷贝编码器已经创建了
rmt_del_encoder(led_encoder->copy_encoder); // 删除它
}
free(led_encoder); // 释放私有盒子内存
}
return ret; // 返回错误码
}
这是.h文件
#pragma once // 防止这个文件被重复包含(相当于“只读一次,别复制粘贴多次”)
#include <stdint.h> // 包含标准整数类型(比如 uint32_t,就是无符号32位整数)
#include "driver/rmt_encoder.h" // 包含 ESP 官方 RMT 编码器的基类定义(里面定义了 rmt_encoder_handle_t 等类型)
#ifdef __cplusplus // 如果编译器是 C++(而不是 C)
extern "C" { // 就用 C 语言的规则来编译下面的函数(防止 C++ 把函数名字改得乱七八糟)
#endif // 结束条件判断
typedef struct { // 定义一个结构体类型(用来传参数给创建函数)
uint32_t resolution; // 时钟频率,单位是 Hz(比如 10000000 表示 10MHz)
} led_strip_encoder_config_t; // 这个结构体类型名字叫 led_strip_encoder_config_t
esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder);
// 声明的公共函数:创建 LED 编码器
// 参数1:config -> 你填好的配置(时钟频率)
// 参数2:ret_encoder -> 编码器句柄的指针(函数会把创建好的编码器地址填到这里面)
// 返回值:esp_err_t -> ESP_OK 表示成功,其他数字表示失败
#ifdef __cplusplus // 如果编译器是 C++
} // 结束 extern "C" 的大括号
#endif // 结束条件判断
开始写主代码
#include <string.h>
// 包含字符串操作函数,比如 memset()(用于清空内存)
#include "freertos/FreeRTOS.h"
// FreeRTOS 实时操作系统的核心头文件,提供任务管理、延时等基础功能
#include "freertos/task.h"
// FreeRTOS 的任务相关函数,比如 vTaskDelay()(用于延时)
#include "driver/rmt_tx.h"
// ESP-IDF 的 RMT(红外遥控)发送驱动,用于生成精确的时序信号
#include "esp_check.h"
// ESP-IDF 的错误检查宏,比如 ESP_ERROR_CHECK(),用于检查函数返回值
#include "led_strip_encoder.h"
// 自定义的 LED 编码器头文件,它把颜色数据转换成 WS2812 需要的波形
// ============================================================
// 宏定义(编译时常量)
// ============================================================
#define RGB_LED_GPIO 48
// 定义控制 LED 的 GPIO 引脚号,48 对应板载 WS2812 RGB LED
// 如果你要接外接灯条,改成你实际接的引脚(比如 21)
#define RGB_LED_COUNT 15
// 定义灯珠的数量,这里是 15 个(你的灯条有 15 个灯)
#define RMT_LED_RESOLUTION_HZ 10000000
// 定义 RMT 计数器的时钟频率,10 MHz(10,000,000 Hz)
// 这个频率决定了时序的精度,10MHz 足够精确控制 WS2812
// ============================================================
// 全局变量:颜色数据缓冲区
// ============================================================
static uint8_t led_pixels[RGB_LED_COUNT * 3];
// 定义一个静态数组,用来存放所有灯珠的颜色数据
// 每个灯珠需要 3 个字节(G、R、B 各占 1 字节)
// 数组大小 = 15 × 3 = 45 字节
// static 表示这个数组只在本文件内可见
// uint8_t 是无符号 8 位整数,范围 0~255
// ============================================================
// 函数 1:设置所有灯为同一颜色
// ============================================================
static void set_all_rgb(uint8_t red, uint8_t green, uint8_t blue)
// 定义一个静态函数,参数为红、绿、蓝分量(范围 0~255)
// static 表示这个函数只在本文件内使用
// void 表示没有返回值
{
// 用 for 循环遍历每一个灯珠
for (int i = 0; i < RGB_LED_COUNT; i++) {
// i 是灯珠的索引,从 0 开始,到 14 结束
// 计算当前灯珠在数组中的起始位置
// 第 i 个灯珠的起始偏移量 = i * 3
// 因为每个灯珠占 3 个字节
led_pixels[i * 3 + 0] = green;
// 第 0 个字节存放绿色分量(G)
// WS2812 的数据顺序是 G(绿色)→ R(红色)→ B(蓝色)
// 这是 WS2812 芯片的硬件要求,必须按这个顺序
led_pixels[i * 3 + 1] = red;
// 第 1 个字节存放红色分量(R)
led_pixels[i * 3 + 2] = blue;
// 第 2 个字节存放蓝色分量(B)
}
// 循环结束后,所有 15 个灯的颜色数据都已经填充好了
}
// ============================================================
// 函数 2:清空所有灯(熄灭)
// ============================================================
static void clear_all_leds(void)
// 定义一个静态函数,没有参数,没有返回值
{
memset(led_pixels, 0, sizeof(led_pixels));
// 使用 memset() 函数将整个 led_pixels 数组全部设置为 0
// sizeof(led_pixels) 返回数组的总字节数(45 字节)
// 全部设为 0 意味着所有灯珠的 G、R、B 都是 0,即熄灭
}
// ============================================================
// 主函数(程序入口)
// ============================================================
void app_main(void)
// ESP-IDF 程序的入口函数,类似于 Arduino 的 setup() + loop()
// 但这个函数本身不会自动循环,需要自己写 while(1) 循环
{
// ------------------------------------------------------------
// 步骤 1:创建 RMT 发送通道
// ------------------------------------------------------------
rmt_channel_handle_t led_chan = NULL;
// 声明一个 RMT 通道句柄变量,初始值为 NULL
// 这个句柄会在后面被赋值,用来代表一个 RMT 硬件通道
rmt_tx_channel_config_t tx_chan_config = {
// 定义一个 RMT 发送通道的配置结构体,并初始化它的各个字段
// 使用 C99 的指定初始化语法(.字段名 = 值)
.clk_src = RMT_CLK_SRC_DEFAULT,
// 时钟源:使用默认的 RMT 时钟源(通常来自 APB 时钟)
.gpio_num = RGB_LED_GPIO,
// 输出引脚:使用宏定义的 GPIO 48(或你改成的其他引脚)
.mem_block_symbols = 64,
// RMT 内存块大小:64 个符号(每个符号代表一个电平状态)
// 64 个符号足够发送几个灯的数据,对于 15 个灯,编码器会分多次发送
.resolution_hz = RMT_LED_RESOLUTION_HZ,
// RMT 计数器的时钟频率:10 MHz
// 这个值决定了每个计数值对应的时间长度
// 1 / 10,000,000 = 0.1 微秒/计数
.trans_queue_depth = 4,
// 发送队列深度:最多缓存 4 个发送任务
// 如果发送速度比处理速度快,可以暂存在队列中等待
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &led_chan));
// 调用 rmt_new_tx_channel() 函数,根据配置创建一个 RMT 发送通道
// 第一个参数是配置结构体的地址,第二个参数是输出句柄的地址
// ESP_ERROR_CHECK() 会检查返回值是否为 ESP_OK
// 如果不是,会打印错误信息并中止程序
// ------------------------------------------------------------
// 步骤 2:创建 LED 编码器
// ------------------------------------------------------------
rmt_encoder_handle_t led_encoder = NULL;
// 声明一个编码器句柄变量,初始值为 NULL
led_strip_encoder_config_t encoder_config = {
// 定义 LED 编码器的配置结构体
.resolution = RMT_LED_RESOLUTION_HZ,
// 分辨率:10 MHz,与 RMT 通道的时钟频率保持一致
};
ESP_ERROR_CHECK(rmt_new_led_strip_encoder(&encoder_config, &led_encoder));
// 调用自定义的编码器创建函数 rmt_new_led_strip_encoder()
// 这个函数会创建一个编码器,把颜色字节转换成 WS2812 的波形时序
// 包括 0 码、1 码和复位信号(50us 低电平)
// 传入配置和输出句柄的地址
// ------------------------------------------------------------
// 步骤 3:启用 RMT 通道
// ------------------------------------------------------------
ESP_ERROR_CHECK(rmt_enable(led_chan));
// 调用 rmt_enable() 使能 RMT 通道
// 在发送数据之前,必须先启用通道
// 启用后,RMT 硬件就开始准备接收数据了
// ------------------------------------------------------------
// 步骤 4:配置发送参数
// ------------------------------------------------------------
rmt_transmit_config_t tx_config = {
// 定义发送配置结构体
.loop_count = 0,
// 循环次数:0 表示不循环,只发送一次
// 如果设为大于 0,数据会循环发送指定次数
};
// ------------------------------------------------------------
// 步骤 5:主循环(无限循环)
// ------------------------------------------------------------
while (1) {
// ========================================================
// 效果:全部灯亮红色(255,最亮)
// ========================================================
set_all_rgb(0, 255, 0);
// 调用 set_all_rgb(),参数顺序是 (红, 绿, 蓝)
ESP_ERROR_CHECK(rmt_transmit(led_chan, led_encoder, led_pixels, sizeof(led_pixels), &tx_config));
// 调用 rmt_transmit() 开始发送数据
// 参数依次是:
// - led_chan:RMT 通道句柄
// - led_encoder:编码器句柄
// - led_pixels:要发送的数据指针(45 字节的颜色数据)
// - sizeof(led_pixels):数据长度(45 字节)
// - &tx_config:发送配置
// 这个函数会启动异步发送,将数据传给编码器,编码器转换成 RMT 波形并发送
ESP_ERROR_CHECK(rmt_tx_wait_all_done(led_chan, portMAX_DELAY));
// 调用 rmt_tx_wait_all_done() 等待所有数据发送完成
// 参数 portMAX_DELAY 表示无限等待,直到发送完成才返回
// 这是一个阻塞操作,确保数据完全发送到灯带后再继续
vTaskDelay(pdMS_TO_TICKS(500));
// 调用 FreeRTOS 的延时函数,延时 500 毫秒
// pdMS_TO_TICKS(500) 将 500 毫秒转换为 FreeRTOS 的时钟滴答数
// 在延时期间,CPU 可以执行其他任务(如果有的话),进入低功耗状态
// // 全亮绿色
// set_all_rgb(0, 255, 0);
// ESP_ERROR_CHECK(rmt_transmit(led_chan, led_encoder, led_pixels, sizeof(led_pixels), &tx_config));
// ESP_ERROR_CHECK(rmt_tx_wait_all_done(led_chan, portMAX_DELAY));
// vTaskDelay(pdMS_TO_TICKS(500));
// // 全亮蓝色
// set_all_rgb(0, 0, 255);
// ESP_ERROR_CHECK(rmt_transmit(led_chan, led_encoder, led_pixels, sizeof(led_pixels), &tx_config));
// ESP_ERROR_CHECK(rmt_tx_wait_all_done(led_chan, portMAX_DELAY));
// vTaskDelay(pdMS_TO_TICKS(500));
// // 全灭
// clear_all_leds();
// ESP_ERROR_CHECK(rmt_transmit(led_chan, led_encoder, led_pixels, sizeof(led_pixels), &tx_config));
// ESP_ERROR_CHECK(rmt_tx_wait_all_done(led_chan, portMAX_DELAY));
// vTaskDelay(pdMS_TO_TICKS(500));
}
// 主循环结束,程序会一直在这里运行,永远不会退出
}
// app_main 函数结束
好的,点亮一条灯
纯ai手段点亮哈哈哈,但是还是稍微理解一下代码啦~
更多推荐


所有评论(0)