/** * @file ws2812_spi.c * @author your name (you@domain.com) * @brief 通过 SPI 硬件模拟 PWM 信号,驱动 WS2812 的程序 * @version 0.1 * @date 2023-10-05 * * @copyright Copyright (c) 2023 * */ #include #include #include #include #include "esp_log.h" #include "esp_attr.h" #include "esp_rom_gpio.h" #include "driver/spi_master.h" #include "driver/gpio.h" #include "soc/spi_periph.h" #include "soc/gpio_periph.h" #include "ws2812_spi.h" #include "drivers/pin_io/pin_io.h" #include "os/os.h" #define CONFIG_SYS_LOG_LEVEL SYS_LOG_LEVEL_INF #define SYS_LOG_DOMAIN "LED_STRIP_SPI" #include "sys_log.h" #define SPI_CLK_HZ 8000000 #define WS_HIGH 0x3 #define WS_LOW 0x3F #define WS_RESET ~0 #define RESET_TIME_US 50 #define RESET_BIT_LENGTH (SPI_CLK_HZ * (RESET_TIME_US) / 1000000) #define _POW_Y 1.3 static struct { os_mutex_t mutex_hdl; } s_cm; /** * @brief 初始化工作环境。这将初始化 SPI 驱动并申请显示缓存到对象中。可重初始化 * * @param led_strip[out] 灯带驱动对象 * @param spi_host SPI peripheral that controls this bus @ref spi_host_device_t * @param max_led_num 最大可驱动灯条共多少个灯珠 */ void ws2812_spi_led_strip_init(ws2812_spi_led_strip_t *led_strip, uint8_t spi_host, uint16_t max_led_num) { static spi_device_handle_t s_spi_hdl; SYS_ASSERT(max_led_num > 0, ""); if (led_strip->led_dma_buffer) { heap_caps_free(led_strip->led_dma_buffer); led_strip->led_dma_buffer = NULL; spi_bus_remove_device(led_strip->spi_handle); spi_bus_free(led_strip->spi_host); } int max_transfer_sz = max_led_num * 24 + RESET_BIT_LENGTH * 2; if (s_spi_hdl == NULL) { esp_err_t ret; spi_bus_config_t buscfg = { .miso_io_num = -1, .mosi_io_num = -1, .sclk_io_num = -1, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = max_transfer_sz}; // Initialize the SPI bus ret = spi_bus_initialize(spi_host, &buscfg, SPI_DMA_CH_AUTO); ESP_ERROR_CHECK(ret); spi_device_interface_config_t devcfg = { .clock_speed_hz = SPI_CLK_HZ, .mode = 0, .spics_io_num = -1, .queue_size = 1, .pre_cb = NULL, .command_bits = 0, .address_bits = 0}; // Attach the LCD to the SPI bus ret = spi_bus_add_device(spi_host, &devcfg, &s_spi_hdl); ESP_ERROR_CHECK(ret); } if (!os_mutex_is_valid(&s_cm.mutex_hdl)) { os_mutex_create(&s_cm.mutex_hdl); } led_strip->led_dma_buffer = heap_caps_malloc(max_transfer_sz, MALLOC_CAP_DMA); // Critical to be DMA memory. memset(led_strip->led_dma_buffer, WS_RESET, max_transfer_sz); led_strip->buffer_size = max_transfer_sz; led_strip->spi_host = spi_host; led_strip->spi_pin = -1; led_strip->spi_handle = s_spi_hdl; led_strip->max_led_num = max_led_num; float max = pow(0x100, _POW_Y) / 0x100; for (int i = 0; i < 0x100; i++) { led_strip->pow_tbl[i] = (int)(pow(i, _POW_Y) / max) + 1; if (led_strip->pow_tbl[i] > 0xFF) { led_strip->pow_tbl[i] = 0xFF; } } led_strip->pow_tbl[0] = 0; /* 使所有数据合法化 */ ws2812_spi_led_strip_clear(led_strip); } /** * @brief 使所有显存清零。注意需要通过 ws2812_spi_led_strip_refresh() 方可把数据刷新到灯带中。 * * @param led_strip 灯带驱动对象 */ void ws2812_spi_led_strip_clear(ws2812_spi_led_strip_t *led_strip) { if (led_strip->led_dma_buffer == NULL) { SYS_LOG_WRN("led strip buffer is null"); return; } memset(led_strip->led_dma_buffer, WS_RESET, led_strip->buffer_size); for (int i = 0; i < led_strip->max_led_num; i++) { ws2812_spi_led_strip_set_pixel(led_strip, i, 0, 0, 0); } } /** * @brief 设一个灯珠为一个像素,设置单个像素的颜色。注意需要通过 ws2812_spi_led_strip_refresh() 方可把数据刷新到灯带中。 * * @param led_strip 灯带驱动对象 * @param index 灯像素序号,0 .. led_strip->max_led_num - 1 * @param red 红基色亮度,0 .. 255 * @param green 绿基色亮度,0 .. 255 * @param blue 蓝基色亮度,0 .. 255 */ void ws2812_spi_led_strip_set_pixel(ws2812_spi_led_strip_t *led_strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue) { if (led_strip->led_dma_buffer == NULL) { SYS_LOG_WRN("led strip buffer is null"); return; } if (index > led_strip->max_led_num) { SYS_LOG_WRN("index: %u > led_strip->max_led_num: %u", index, led_strip->max_led_num); return; } if (red > 255 || green > 255 || blue > 255) { SYS_LOG_WRN("param over range"); return; } /* 套指数表 */ red = led_strip->pow_tbl[red]; green = led_strip->pow_tbl[green]; blue = led_strip->pow_tbl[blue]; uint32_t color = (green & 0xFF) << 16 | (red & 0xFF) << 8 | (blue & 0xFF); /* PWM 编码 */ for (int i = 0; i < 24; i++) { int pos = RESET_BIT_LENGTH + index * 24 + i; led_strip->led_dma_buffer[pos] = ((color << i) & 0x800000) ? WS_HIGH : WS_LOW; } } /** * @brief 执行把显示缓存刷新到灯带 * * @param led_strip 灯带驱动对象 * @param leds 实际刷新的灯珠数 * @param pin 使用的引脚 * @retval 0 操作成功 * @retval != 0 操作失败 */ int ws2812_spi_led_strip_refresh(ws2812_spi_led_strip_t *led_strip, uint32_t leds, uint8_t pin) { if (led_strip == NULL || led_strip->spi_handle == NULL) { SYS_LOG_ERR("led strip spi handler is null"); return -1; } if (leds > led_strip->max_led_num) { SYS_LOG_WRN("leds > led_strip->max_led_num"); leds = led_strip->max_led_num; } os_mutex_lock(&s_cm.mutex_hdl, OS_WAIT_FOREVER); uint32_t empty_bytes = (led_strip->max_led_num - leds) * 24; if (empty_bytes) { memset(&led_strip->led_dma_buffer[led_strip->buffer_size - empty_bytes], WS_RESET, empty_bytes); } spi_transaction_t t; memset(&t, 0, sizeof(t)); t.length = led_strip->buffer_size * 8 - empty_bytes; // length is in bits t.tx_buffer = led_strip->led_dma_buffer; if (led_strip->spi_pin != pin) { if (led_strip->spi_pin > 0) { cfg_board_pin_io_t cfg_pin = {.pin = led_strip->spi_pin, .en_lev = 1}; pin_cfg_output(&cfg_pin); pin_set_valid(&cfg_pin, true); } if (pin > 0) { cfg_board_pin_io_t cfg_pin = {.pin = pin, .en_lev = 1}; pin_cfg_output(&cfg_pin); gpio_set_direction(pin, GPIO_MODE_INPUT_OUTPUT); esp_rom_gpio_connect_out_signal(pin, spi_periph_signal[led_strip->spi_host].spid_out, true, false); } led_strip->spi_pin = pin; } esp_err_t err = spi_device_transmit(led_strip->spi_handle, &t); os_mutex_unlock(&s_cm.mutex_hdl); ESP_ERROR_CHECK(err); return err; }