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

相关推荐
智商偏低5 小时前
单片机之helloworld
单片机·嵌入式硬件
青牛科技-Allen6 小时前
GC3910S:一款高性能双通道直流电机驱动芯片
stm32·单片机·嵌入式硬件·机器人·医疗器械·水泵、
森焱森8 小时前
无人机三轴稳定控制(2)____根据目标俯仰角,实现俯仰稳定化控制,计算出升降舵输出
c语言·单片机·算法·架构·无人机
白鱼不小白8 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D9 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术12 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt12 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
嘿·嘘13 小时前
第七章 STM32内部FLASH读写
stm32·单片机·嵌入式硬件
Meraki.Zhang13 小时前
【STM32实践篇】:I2C驱动编写
stm32·单片机·iic·驱动·i2c
几个几个n15 小时前
STM32-第二节-GPIO输入(按键,传感器)
单片机·嵌入式硬件