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手段点亮哈哈哈,但是还是稍微理解一下代码啦~
