WS2812B数据手册重点摘录 硬件电路 Remote Control Transceive(RMT)概念 RMT的相关API函数 一段简单的控制WS2812B的应用举例 总结 WS2812B数据手册重点摘录 WS2812的数据手册还是很好找的,可以直接去立创商城输入型号就可以找到。 重点一:引脚结构 VDD:供电输入;DI:数据输入;DO:数据输出(用于级联);GND:供电地 重点二:供电 如图供电5V足够了 重点三:数据结构 每个颜色数据为8bit 重点四:数据传输波形 波形图还是很明了易懂的,类似于NEC编码方式。根据数据传输定义,可以知道咱们需要给它的控制信号是1MHz的。 重点五:厂商给的设计建议 一定要看,厂商一般都会做大量的测试,他们的建议是最好的参考标准。这个器件的手册还是很详细的,如图的1、2条说明仔细看。 电路图 根据器件的数据手册,可以很轻松的绘制电路图 Remote Control Transceive(RMT)概念 背景 :还记得在上次的呼吸灯么?通过ESP32自带的LEDC控制器实现的呼吸灯还是很轻松的。通过对WS2812B的数据手册阅读,可以发现对它的驱动其实也是PWM,是否也可用LEDC呢?答案是不行的,在使用LEDC产生PWM的时候可以发现,PWM的占空比是不可控的(无法控制其之发送一段PWM)。由WS2812B的传输波形可知,其需要的波形是一段的且每个周期是可控的。 引入 :根据WS2812B的传输波形特征,发现ESP32中有一个Remote Control Transceive的外设控制器,结合安可信官网的知识解读,这个RMT是控制WS2812B的不二之选。且在RMT的案例库中有一个控制WS2812这类LED的例程,路径如下: 例程内容较为复杂,初学者一时半会很难明白其中的逻辑。在此,我做一个简单的实现例程, RMT的概念 :RMT是一个红外发射和接收控制器,数据格式灵活,可以发送或接收多种类型的信号。它有自己的数据模式,叫做RMT符号。每个符号由两对两个值组成,每对中的第一个值是一个 15 位的值,表示信号持续时间,以 RMT 滴答计。每对中的第二个值是一个 1 位的值,表示信号的逻辑电平,即高电平或低电平。这个格式非常适合控制WS2812B . 其他具体信息可以查看官方手册 https://docs.espressif.com/projects/esp-idf/zh_CN/v5.4.1/esp32/api-reference/peripherals/rmt.html RMT的相关API函数 目前只用到了RMT的发送,所以只说明发送的API函数 RMT发送通道配置结构体 typedef struct { gpio_num_t gpio_num; /*所要映射的GPIO口,不使用时设置为-1*/ rmt_clock_source_t clk_src; /*RMT通道所使用的时钟源 */ uint32_t resolution_hz; /*此通道的时钟分辨率 */ size_t mem_block_symbols; /*内存块大小,如果启用了DMA,则该字段可以控制内部DMA缓冲区大小,建议设置1024;如果未启用DMA,则至少设置为64。不是所有通道都支持DMA,具体要查看芯片的TRM文档*/ size_t trans_queue_depth; /*设置后台等待处理的事务数量*/ int intr_priority; /*设置中断优先级 */ struct { uint32_t invert_out: 1 ; /*设置是否反转输出信号*/ uint32_t with_dma: 1 ; /*是否开启DMA */ uint32_t io_loop_back: 1 ; /*是否返回给输入,作为换回测试 */ uint32_t io_od_mode: 1 ; /*设置GPIO是否作为开漏模式 */ } flags; /*! TX channel config flags */ } rmt_tx_channel_config_t ; 分配和初始化TX通道 esp_err_t rmt_new_tx_channel ( const rmt_tx_channel_config_t *config, rmt_channel_handle_t *ret_chan) /*第一个参数是 rmt_tx_channel_config_t定义并填充的结构体变量,第二个参数是此函数返回的RMT通道句柄,需要使用rmt_channel_handle_t先声明一个变量*/ 使能通道和禁用通道 //使能 esp_err_t rmt_enable ( rmt_channel_handle_t channel) //参数为rmt_new_tx_channel返回的RMT通道句柄 //禁用 esp_err_t rmt_disable ( rmt_channel_handle_t channel) //参数为rmt_new_tx_channel返回的RMT通道句柄 设置RMT符号格式,即输出什么样的信号 typedef struct { rmt_symbol_word_t bit0; /*RMT符号的bit0字节格式 */ rmt_symbol_word_t bit1; /*RMT符号的bit1字节格式*/ struct { uint32_t msb_first: 1 ; /*最先编最高有效位*/ } flags; /*编码配置状态 */ } rmt_bytes_encoder_config_t ; typedef union { struct { uint16_t duration0 : 15 ; /* level0 持续时间*/ uint16_t level0 : 1 ; /*Level0的电平值*/ uint16_t duration1 : 15 ; /*level1 持续时间*/ uint16_t level1 : 1 ; /*Level1的电平值 */ }; uint32_t val; /* 与RMT 符号等价的无符号值 */ } rmt_symbol_word_t ; 创建字节编码器 esp_err_t rmt_new_bytes_encoder ( const rmt_bytes_encoder_config_t *config, //创建好的 rmt_bytes_encoder_config_t的结构体变量 rmt_encoder_handle_t *ret_encoder //返回的编码器句柄,需要在调用前创建 rmt_encoder_handle_t变量 ) 发送配置结构体 typedef struct { int loop_count; /*循环次数 ,设置为-1则无限循环*/ struct { uint32_t eot_level : 1 ; /*发送结束后输出的电平 */ uint32_t queue_nonblocking : 1 ; /*设置当传输队列满的时候该函数是否需要等待 */ } flags; /*! Transmit specific config flags */ } rmt_transmit_config_t ; 启动发送 esp_err_t rmt_transmit ( rmt_channel_handle_t tx_channel, //发送通道句柄 rmt_encoder_handle_t encoder, //编码器句柄 const void *payload, //指向需要发送的值 size_t payload_bytes, //发送的值的大小 const rmt_transmit_config_t *config //发送的配置结构体 ) 一段简单的控制WS2812B的应用举例 /** * Copyright (C) 2024-2034 HalfMoon2. * All rights reserved. * * @file Filename without the absolute path * @brief Brief description * @author HalfMoon2 * @date 2025-06-05 * @version v0.1 * * @revision history: * 2025-06-05 - Initial version. */ # include stdio.h # include "driver/rmt_common.h" # include "driver/rmt_tx.h" # include "driver/rmt_encoder.h" # include freertos/FreeRTOS.h # include freertos/task.h void app_main ( void ) { uint8_t value ={ 0x0f , 0xb0 , 0x01 }; rmt_channel_handle_t tx_chan = NULL ; rmt_tx_channel_config_t tx_chan_config = { .clk_src = RMT_CLK_SRC_DEFAULT, // 选择时钟源 .gpio_num = 48 , // GPIO 编号 .mem_block_symbols = 64 , // 内存块大小,即 64 * 4 = 256 字节 .resolution_hz = 10 * 1000 * 1000 , // 10 MHz 滴答分辨率,即 1 滴答 = 100ns .trans_queue_depth = 4 , // 设置后台等待处理的事务数量 .flags.invert_out = false , // 不反转输出信号 .flags.with_dma = false , // 不需要 DMA 后端 }; ESP_ERROR_CHECK ( rmt_new_tx_channel (tx_chan_config, tx_chan)); rmt_encoder_handle_t rmt_encoder= NULL ; rmt_bytes_encoder_config_t rmt_bytes_encoder={ .bit0={ .level0= 1 , .duration0= 0.3 * 10000000 / 1000000 , //T0H .level1= 0 , .duration1= 0.6 * 10000000 / 1000000 , //T0L }, .bit1={ .level0= 1 , .duration0= 0.6 * 10000000 / 1000000 , //T1H .level1= 0 , .duration1= 0.3 * 10000000 / 1000000 , //T1L }, .flags={ .msb_first= 1 , } }; rmt_new_bytes_encoder (rmt_bytes_encoder,rmt_encoder); rmt_transmit_config_t rmt_transmit_config={ .loop_count= 0 , }; ESP_ERROR_CHECK ( rmt_enable (tx_chan)); ESP_ERROR_CHECK ( rmt_transmit (tx_chan,rmt_encoder,value, sizeof (value),rmt_transmit_config)); } 从波形到颜色,说明这个简单的操作是可以很轻松实现WS2812B的控制的。 总结 整篇文章结构可以看出,如果要控制一个外设要做到以下步骤: 仔细阅读数据手册, 知道硬件电路的搭建, 看懂控制外设的协议(就是时序图), 匹配主控芯片可用的外设控制器, 阅读官方指导文档,查看当前需要的API函数 一步步填充API函数及相关结构体 这样一个简单的控制程序就完成了。当然最后的结果就跟我粘贴的代码一样,都放在了main函数里了,业界称为屎山。这个时候就需要通过面向对象的编程思想以及设计模式等知识,将代码规范化、增强其可移植性等。 要记住的是,只有理解了其基本的原理,才能万变不离其中。