Files
ESPC3-wireless/app/drivers/ws2812_spi/ws2812_spi.c
2025-02-13 17:17:07 +08:00

252 lines
7.1 KiB
C
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @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 <stdlib.h>
#include <string.h>
#include <math.h>
#include <sys/cdefs.h>
#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;
}