WS2812是一种可编程的LED灯,使用RGB三个通道,可以显示2^24种颜色,而且自带控制芯片,便于控制。数据从DIN进入第一个灯珠时,第一个灯珠会锁存第一个24位数据,然后剩下的数据会从灯珠DOUT引脚输出到下一个灯珠的输入端,依此类推,可达到单总线控制多个灯珠的目的。
ESP32拥有RMT模块,即红外模块,由于拥有良好的时序控制更容易编写程序,而且ESP32可以将其映射到任意IO口。
c
static void configure_led(void)
{
ESP_LOGI(TAG, "Example configured to blink addressable LED!");
/* LED strip initialization with the GPIO and pixels number*/
led_strip_config_t strip_config = {
.strip_gpio_num = BLINK_GPIO,
.max_leds = 10, // at least one LED on board
};
led_strip_rmt_config_t rmt_config = {
.resolution_hz = 10 * 1000 * 1000, // 10MHz
};
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
/* Set all LED off to clear all pixels */
led_strip_clear(led_strip);
}
对代码的逐步分解和详细解释:
函数定义:
c
static void configure_led(void)
这是一个静态函数,其目的是配置LED灯带的GPIO引脚和像素数量。
日志信息:
c
ESP_LOGI(TAG, "Example configured to blink addressable LED!");
使用ESP32的日志系统记录配置信息,表明函数正在配置可寻址LED。
LED灯带初始化配置:
c
led_strip_config_t strip_config = {
.strip_gpio_num = BLINK_GPIO,
.max_leds = 10, // at least one LED on board
};
这里定义了一个led_strip_config_t结构体,设置灯带的GPIO引脚为BLINK_GPIO(在代码中定义为48),最大LED数量设置为10,意味着可以控制最多10个LED。
RMT配置:
c
led_strip_rmt_config_t rmt_config = {
.resolution_hz = 10 * 1000 * 1000, // 10MHz
};
这里定义了一个led_strip_rmt_config_t结构体,设置RMT(遥控发射器)模块的分辨率为10MHz,这是操作LED灯带所需的时钟频率。
创建新的RMT设备:
c
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
调用led_strip_new_rmt_device函数,并将之前配置的结构体传入。这一步执行创建RMT设备,负责后续对LED灯带的控制。ESP_ERROR_CHECK用于检查函数执行的错误。
清除LED灯带状态:
c
led_strip_clear(led_strip);
最后调用led_strip_clear函数,将所有LED的状态设为"关闭",确保在初始化后灯带是熄灭的状态。
这段代码的主要功能是初始化和配置可寻址LED灯带(比如通过GPIO控制的RGB灯带)以便在后续的操作中控制其亮灭和颜色变化。通过设置GPIO引脚、最大LED数量和RMT分辨率,该函数为控制LED提供了必要的前期设置,以确保其能正常工作并且在开始之前将所有LED灯设置为关闭状态。
主要还是使用RMT功能进行配置:
c
esp_err_t led_strip_new_rmt_device(const led_strip_config_t *led_config, const led_strip_rmt_config_t *rmt_config, led_strip_handle_t *ret_strip)
{
led_strip_rmt_obj *rmt_strip = NULL;
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(led_config && rmt_config && ret_strip, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(led_config->led_pixel_format < LED_PIXEL_FORMAT_INVALID, ESP_ERR_INVALID_ARG, err, TAG, "invalid led_pixel_format");
uint8_t bytes_per_pixel = 3;
if (led_config->led_pixel_format == LED_PIXEL_FORMAT_GRBW) {
bytes_per_pixel = 4;
} else if (led_config->led_pixel_format == LED_PIXEL_FORMAT_GRB) {
bytes_per_pixel = 3;
} else {
assert(false);
}
rmt_strip = calloc(1, sizeof(led_strip_rmt_obj) + led_config->max_leds * bytes_per_pixel);
ESP_GOTO_ON_FALSE(rmt_strip, ESP_ERR_NO_MEM, err, TAG, "no mem for rmt strip");
uint32_t resolution = rmt_config->resolution_hz ? rmt_config->resolution_hz : LED_STRIP_RMT_DEFAULT_RESOLUTION;
// for backward compatibility, if the user does not set the clk_src, use the default value
rmt_clock_source_t clk_src = RMT_CLK_SRC_DEFAULT;
if (rmt_config->clk_src) {
clk_src = rmt_config->clk_src;
}
size_t mem_block_symbols = LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS;
// override the default value if the user sets it
if (rmt_config->mem_block_symbols) {
mem_block_symbols = rmt_config->mem_block_symbols;
}
rmt_tx_channel_config_t rmt_chan_config = {
.clk_src = clk_src,
.gpio_num = led_config->strip_gpio_num,
.mem_block_symbols = mem_block_symbols,
.resolution_hz = resolution,
.trans_queue_depth = LED_STRIP_RMT_DEFAULT_TRANS_QUEUE_SIZE,
.flags.with_dma = rmt_config->flags.with_dma,
.flags.invert_out = led_config->flags.invert_out,
};
ESP_GOTO_ON_ERROR(rmt_new_tx_channel(&rmt_chan_config, &rmt_strip->rmt_chan), err, TAG, "create RMT TX channel failed");
led_strip_encoder_config_t strip_encoder_conf = {
.resolution = resolution,
.led_model = led_config->led_model
};
ESP_GOTO_ON_ERROR(rmt_new_led_strip_encoder(&strip_encoder_conf, &rmt_strip->strip_encoder), err, TAG, "create LED strip encoder failed");
rmt_strip->bytes_per_pixel = bytes_per_pixel;
rmt_strip->strip_len = led_config->max_leds;
rmt_strip->base.set_pixel = led_strip_rmt_set_pixel;
rmt_strip->base.set_pixel_rgbw = led_strip_rmt_set_pixel_rgbw;
rmt_strip->base.refresh = led_strip_rmt_refresh;
rmt_strip->base.clear = led_strip_rmt_clear;
rmt_strip->base.del = led_strip_rmt_del;
*ret_strip = &rmt_strip->base;
return ESP_OK;
err:
if (rmt_strip) {
if (rmt_strip->rmt_chan) {
rmt_del_channel(rmt_strip->rmt_chan);
}
if (rmt_strip->strip_encoder) {
rmt_del_encoder(rmt_strip->strip_encoder);
}
free(rmt_strip);
}
return ret;
}
这段代码的功能是创建一个新的LED灯带设备,具体实现通过RMT(遥控传输)模块来控制LED灯的颜色和亮度。下面是对代码的逐步分解与详细解释:
函数签名:
c
esp_err_t led_strip_new_rmt_device(const led_strip_config_t *led_config, const led_strip_rmt_config_t *rmt_config, led_strip_handle_t *ret_strip)
该函数接受三个参数:
led_config:描述LED灯带的配置,包括GPIO引脚等信息。
rmt_config:描述RMT的配置,如时钟源和内存块符号等。
ret_strip:返回的LED灯带句柄,用于后续操作。
变量定义:
c
led_strip_rmt_obj *rmt_strip = NULL;
esp_err_t ret = ESP_OK;
定义一个指向led_strip_rmt_obj结构体的指针rmt_strip,用于存储创建的LED灯带对象,以及一个错误状态变量ret。
参数有效性检查:
c
ESP_GOTO_ON_FALSE(led_config && rmt_config && ret_strip, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
检查传入的参数是否有效。如果无效,则跳转到err标签处理错误。
像素格式与每个像素字节数配置: 根据传入的led_pixel_format来确定每个像素使用的字节数(3字节或4字节)。
内存分配:
c
rmt_strip = calloc(1, sizeof(led_strip_rmt_obj) + led_config->max_leds * bytes_per_pixel);
分配足够的内存以存储LED灯带对象及其包含的像素数据。
设置RMT参数:
c
uint32_t resolution = rmt_config->resolution_hz ? rmt_config->resolution_hz : LED_STRIP_RMT_DEFAULT_RESOLUTION;
rmt_clock_source_t clk_src = RMT_CLK_SRC_DEFAULT;
设置RMT传输的分辨率和时钟源,确保使用后向兼容性。
RMT通道配置: 创建RMT传输通道的配置结构体,并应用必要的参数。
c
ESP_GOTO_ON_ERROR(rmt_new_tx_channel(&rmt_chan_config, &rmt_strip->rmt_chan), err, TAG, "create RMT TX channel failed");
LED编码器配置: 创建LED灯带编码器来根据分辨率和模型配置具体操作。
c
ESP_GOTO_ON_ERROR(rmt_new_led_strip_encoder(&strip_encoder_conf, &rmt_strip->strip_encoder), err, TAG, "create LED strip encoder failed");
设置LED灯带操作函数: 将操作函数(如设置像素、刷新、清除和删除)绑定到rmt_strip对象的基础上,方便后续调用。
返回句柄: 将指向新创建的LED灯带对象的指针赋值给ret_strip。
错误处理: 如果在执行过程中发生错误,会跳转到err标签,在那里释放已分配的资源并返回错误代码。
RMT配置:
c
esp_err_t rmt_new_tx_channel(const rmt_tx_channel_config_t *config, rmt_channel_handle_t *ret_chan)
{
#if CONFIG_RMT_ENABLE_DEBUG_LOG
esp_log_level_set(TAG, ESP_LOG_DEBUG);
#endif
esp_err_t ret = ESP_OK;
rmt_tx_channel_t *tx_channel = NULL;
// Check if priority is valid
if (config->intr_priority) {
ESP_RETURN_ON_FALSE((config->intr_priority) > 0, ESP_ERR_INVALID_ARG, TAG, "invalid interrupt priority:%d", config->intr_priority);
ESP_RETURN_ON_FALSE(1 << (config->intr_priority) & RMT_ALLOW_INTR_PRIORITY_MASK, ESP_ERR_INVALID_ARG, TAG, "invalid interrupt priority:%d", config->intr_priority);
}
ESP_GOTO_ON_FALSE(config && ret_chan && config->resolution_hz && config->trans_queue_depth, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(GPIO_IS_VALID_GPIO(config->gpio_num), ESP_ERR_INVALID_ARG, err, TAG, "invalid GPIO number");
ESP_GOTO_ON_FALSE((config->mem_block_symbols & 0x01) == 0 && config->mem_block_symbols >= SOC_RMT_MEM_WORDS_PER_CHANNEL,
ESP_ERR_INVALID_ARG, err, TAG, "mem_block_symbols must be even and at least %d", SOC_RMT_MEM_WORDS_PER_CHANNEL);
#if SOC_RMT_SUPPORT_DMA
// we only support 2 nodes ping-pong, if the configured memory block size needs more than two DMA descriptors, should treat it as invalid
ESP_GOTO_ON_FALSE(config->mem_block_symbols <= RMT_DMA_DESC_BUF_MAX_SIZE * RMT_DMA_NODES_PING_PONG / sizeof(rmt_symbol_word_t),
ESP_ERR_INVALID_ARG, err, TAG, "mem_block_symbols can't exceed %d",
RMT_DMA_DESC_BUF_MAX_SIZE * RMT_DMA_NODES_PING_PONG / sizeof(rmt_symbol_word_t));
#else
ESP_GOTO_ON_FALSE(config->flags.with_dma == 0, ESP_ERR_NOT_SUPPORTED, err, TAG, "DMA not supported");
#endif
// malloc channel memory
uint32_t mem_caps = RMT_MEM_ALLOC_CAPS;
if (config->flags.with_dma) {
// DMA descriptors must be placed in internal SRAM
mem_caps |= MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA;
}
tx_channel = heap_caps_calloc(1, sizeof(rmt_tx_channel_t) + sizeof(rmt_tx_trans_desc_t) * config->trans_queue_depth, mem_caps);
ESP_GOTO_ON_FALSE(tx_channel, ESP_ERR_NO_MEM, err, TAG, "no mem for tx channel");
// create transaction queues
ESP_GOTO_ON_ERROR(rmt_tx_create_trans_queue(tx_channel, config), err, TAG, "install trans queues failed");
// register the channel to group
ESP_GOTO_ON_ERROR(rmt_tx_register_to_group(tx_channel, config), err, TAG, "register channel failed");
rmt_group_t *group = tx_channel->base.group;
rmt_hal_context_t *hal = &group->hal;
int channel_id = tx_channel->base.channel_id;
int group_id = group->group_id;
// reset channel, make sure the TX engine is not working, and events are cleared
portENTER_CRITICAL(&group->spinlock);
rmt_hal_tx_channel_reset(&group->hal, channel_id);
portEXIT_CRITICAL(&group->spinlock);
// install tx interrupt
// --- install interrupt service
// interrupt is mandatory to run basic RMT transactions, so it's not lazy installed in `rmt_tx_register_event_callbacks()`
// 1-- Set user specified priority to `group->intr_priority`
bool priority_conflict = rmt_set_intr_priority_to_group(group, config->intr_priority);
ESP_GOTO_ON_FALSE(!priority_conflict, ESP_ERR_INVALID_ARG, err, TAG, "intr_priority conflict");
// 2-- Get interrupt allocation flag
int isr_flags = rmt_get_isr_flags(group);
// 3-- Allocate interrupt using isr_flag
ret = esp_intr_alloc_intrstatus(rmt_periph_signals.groups[group_id].irq, isr_flags,
(uint32_t) rmt_ll_get_interrupt_status_reg(hal->regs),
RMT_LL_EVENT_TX_MASK(channel_id), rmt_tx_default_isr, tx_channel,
&tx_channel->base.intr);
ESP_GOTO_ON_ERROR(ret, err, TAG, "install tx interrupt failed");
// install DMA service
#if SOC_RMT_SUPPORT_DMA
if (config->flags.with_dma) {
ESP_GOTO_ON_ERROR(rmt_tx_init_dma_link(tx_channel, config), err, TAG, "install tx DMA failed");
}
#endif
// select the clock source
ESP_GOTO_ON_ERROR(rmt_select_periph_clock(&tx_channel->base, config->clk_src), err, TAG, "set group clock failed");
// set channel clock resolution
uint32_t real_div = group->resolution_hz / config->resolution_hz;
rmt_ll_tx_set_channel_clock_div(hal->regs, channel_id, real_div);
// resolution lost due to division, calculate the real resolution
tx_channel->base.resolution_hz = group->resolution_hz / real_div;
if (tx_channel->base.resolution_hz != config->resolution_hz) {
ESP_LOGW(TAG, "channel resolution loss, real=%"PRIu32, tx_channel->base.resolution_hz);
}
rmt_ll_tx_set_mem_blocks(hal->regs, channel_id, tx_channel->base.mem_block_num);
// set limit threshold, after transmit ping_pong_symbols size, an interrupt event would be generated
rmt_ll_tx_set_limit(hal->regs, channel_id, tx_channel->ping_pong_symbols);
// disable carrier modulation by default, can reenable by `rmt_apply_carrier()`
rmt_ll_tx_enable_carrier_modulation(hal->regs, channel_id, false);
// idle level is determined by register value
rmt_ll_tx_fix_idle_level(hal->regs, channel_id, 0, true);
// always enable tx wrap, both DMA mode and ping-pong mode rely this feature
rmt_ll_tx_enable_wrap(hal->regs, channel_id, true);
// GPIO Matrix/MUX configuration
tx_channel->base.gpio_num = config->gpio_num;
gpio_config_t gpio_conf = {
.intr_type = GPIO_INTR_DISABLE,
// also enable the input path if `io_loop_back` is on, this is useful for bi-directional buses
.mode = (config->flags.io_od_mode ? GPIO_MODE_OUTPUT_OD : GPIO_MODE_OUTPUT) | (config->flags.io_loop_back ? GPIO_MODE_INPUT : 0),
.pull_down_en = false,
.pull_up_en = true,
.pin_bit_mask = 1ULL << config->gpio_num,
};
ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config GPIO failed");
esp_rom_gpio_connect_out_signal(config->gpio_num,
rmt_periph_signals.groups[group_id].channels[channel_id + RMT_TX_CHANNEL_OFFSET_IN_GROUP].tx_sig,
config->flags.invert_out, false);
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[config->gpio_num], PIN_FUNC_GPIO);
tx_channel->base.direction = RMT_CHANNEL_DIRECTION_TX;
tx_channel->base.fsm = RMT_FSM_INIT;
tx_channel->base.hw_mem_base = &RMTMEM.channels[channel_id + RMT_TX_CHANNEL_OFFSET_IN_GROUP].symbols[0];
tx_channel->base.spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
// polymorphic methods
tx_channel->base.del = rmt_del_tx_channel;
tx_channel->base.set_carrier_action = rmt_tx_modulate_carrier;
tx_channel->base.enable = rmt_tx_enable;
tx_channel->base.disable = rmt_tx_disable;
// return general channel handle
*ret_chan = &tx_channel->base;
ESP_LOGD(TAG, "new tx channel(%d,%d) at %p, gpio=%d, res=%"PRIu32"Hz, hw_mem_base=%p, dma_mem_base=%p, ping_pong_size=%zu, queue_depth=%zu",
group_id, channel_id, tx_channel, config->gpio_num, tx_channel->base.resolution_hz,
tx_channel->base.hw_mem_base, tx_channel->base.dma_mem_base, tx_channel->ping_pong_symbols, tx_channel->queue_size);
return ESP_OK;
err:
if (tx_channel) {
rmt_tx_destroy(tx_channel);
}
return ret;
}
函数声明及宏条件编译:
c
esp_err_t rmt_new_tx_channel(const rmt_tx_channel_config_t *config, rmt_channel_handle_t *ret_chan)
函数接收一个配置结构体和一个输出参数,创建新的发送通道。
调试日志配置:
c
#if CONFIG_RMT_ENABLE_DEBUG_LOG
esp_log_level_set(TAG, ESP_LOG_DEBUG);
#endif
如果启用了调试日志,将日志级别设置为调试模式,以便在创建通道时输出详细信息。
局部变量声明:
c
esp_err_t ret = ESP_OK;
rmt_tx_channel_t *tx_channel = NULL;
定义返回状态和发送通道的指针。
配置参数有效性检查: 通过多次调用宏 ESP_RETURN_ON_FALSE 和 ESP_GOTO_ON_FALSE 进行一系列的参数有效性检查,如:
- 验证中断优先级、
- 配置是否有效、
- GPIO 引脚是否合法、
- 内存块符号是否符合要求。
内存分配:
c
tx_channel = heap_caps_calloc(1, sizeof(rmt_tx_channel_t) + sizeof(rmt_tx_trans_desc_t) * config->trans_queue_depth, mem_caps);
为新的发送通道分配内存,如果失败则返回错误。
创建事务队列:
c
ESP_GOTO_ON_ERROR(rmt_tx_create_trans_queue(tx_channel, config), err, TAG, "install trans queues failed");
初始化发送通道的事务队列以支持任务调度。
将通道注册到组:
c
ESP_GOTO_ON_ERROR(rmt_tx_register_to_group(tx_channel, config), err, TAG, "register channel failed");
将新创建的通道添加到 RMT 组中以注册其使用。
重置通道和安装中断服务: 通过 RMT HAL API 重置通道,然后安装中断服务机制以处理发送完成等事件。
时钟源选择和参数配置: 配置通道的时钟频率和其他参数,如:
- 设置内存块数量
- 限制阈值以生成中断
- 配置 GPIO 信号及其模式。
返回通道句柄:
c
*ret_chan = &tx_channel->base;
返回新创建的发送通道的句柄。
错误处理: 如果在任何步骤中失败,释放已分配的资源并返回错误状态。
这段代码的主要功能是创建新的 RMT 发送通道,通过配置传入的参数(如 GPIO 引脚、时钟频率、内存块符号等),并进行一系列的有效性检查和资源分配。如果一切正常,最终会返回新创建的发送通道的句柄,以便后续的操作,如发送数据。该代码确保了在创建 RMT 通道时的灵活性和有效性,支持不同的使用场景(如 DMA 和 GPIO 配置选择)。