ESP32驱动WS2812

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 配置选择)。

相关推荐
lantiandianzi5 小时前
基于单片机的多功能跑步机控制系统
单片机·嵌入式硬件
哔哥哔特商务网5 小时前
高集成的MCU方案已成电机应用趋势?
单片机·嵌入式硬件
跟着杰哥学嵌入式5 小时前
单片机进阶硬件部分_day2_项目实践
单片机·嵌入式硬件
电子科技圈5 小时前
IAR与鸿轩科技共同推进汽车未来
科技·嵌入式硬件·mcu·汽车
东芝、铠侠总代136100683937 小时前
浅谈TLP184小型平面光耦
单片机·嵌入式硬件·物联网·平面
lantiandianzi7 小时前
基于单片机中医药柜管理系统的设计
单片机·嵌入式硬件
嵌入式知识大讲堂7 小时前
HDMI数据传输三种使用场景
单片机
黑客呀7 小时前
[系统安全]Rootkit基础
stm32·单片机·系统安全
小A1598 小时前
STM32完全学习——使用SysTick精确延时(阻塞式)
stm32·嵌入式硬件·学习
楚灵魈8 小时前
[STM32]从零开始的STM32 HAL库环境搭建
stm32·单片机·嵌入式硬件