SPI+DMA驱动和控制WS2812彩色RGB灯

SPI+DMA驱动和控制WS2812彩色RGB灯

SPI+DMA驱动WS2812

比赛的能量机关,官方的灯条是SK6812灯珠,每米144个灯珠,但是真的贵(

最后还是选用了WS2811灯珠。

很恶心的一点是,WS2811的逻辑0和1表达示方式这部分,网上好多图都是不对的,一定要以数据手册为准… 如果逻辑0和逻辑1的发送时序不对,那么整个灯条的颜色都是乱的甚至根本不会亮

ws28121

ws28122

ws28123

先放一段QA吧

Q1: 像这种一根信号线输出控制的逻辑电平一般都要求5V及以上,需要3.3V升5V吗

A1: STM32虽然只能输出3.3V,但是控WS2811是可以的。另外3.3V升到5V,三极管或是MOS管的信号上升时间相对而言太长了,跟不上控制信号的变化,导致几乎输不出信号

Q2: 直接用IO口翻转可以控制吗

A2: 不太行。用封装库函数翻转IO的执行速度,很难满足几百ns到1us级别的时间要求,虽然可以直接操作寄存器,但即使是对着示波器一点点测试NOP空指令的个数,也只是一种时序近似。随着灯条长度的加长和信号发送时间的推移,总会出现逻辑0和1的误识别的情况

Q3: FreeRTOS 会影响控制的时序吗

A3: 配上稍高一些的任务优先级,使用SPI+DMA不会影响

Q4: 一条总线能控多少灯

A4: 测试一条SPI+DMA控了1920个灯珠也依旧能正常控制,更多的灯珠没设备了也就没测。只要时序对了理论上是能控制无限个的

控制方式

单总线,通过总线上高低电平时间长短的不同来区分0和1。

WS2811的工作频率为800KHz,如果将SPI的频率设置为6.4MHz,正好是灯条IC芯片的8倍,那么每一个字节(8位)正好对应一个逻辑位,也就是11110000代表逻辑1,11000000代表逻辑0

不过协议也有150ns的兼容性。比如SPI设置为5.25MBits/s(Mbps)(84MHz时钟线16分频据线是8Bits),一Bit的发送时间为1/5.25=190ns一个字节约1.52us。所以可以通过SPI的MOSI输出信号线发送一个字节Byte(8Bits)的数据模拟 WS2812的一个位。所以发送一个uint8字节0xF8(11111000),高电平时间为5x190=950ns在580ns-1us范围之间,代表逻辑1;0x80(10000000),高电平时间为190ns接近220ns-380ns范围,代表逻辑0

CubeMX 配置

  • 开启 SPI1,因为只用到了MOSI输出信号线,所以模式选择Transmit Only Master 仅发送主模式 就可以。

    • Data Size 设置为 8 Bits。理由是因为上面的控制方式

    • CPHA 设置为 2 Edge。跳变沿为1时,MOSI空闲电平为高电平。跳变沿设置为2时,会延续上次发送的最后电平,因为发送数据的末尾都是低电平,这样 WS2812 不会误判

    • CPOL 设置为 High。总线空闲时SCK的时钟状态为高电平

  • 开启 DMA。Mode设置为Normal,由程序触发DMA发送即可。

程序

#define LED_NUM 64	// LED灯珠个数

// 模拟bit码: 0x80为逻辑0, 0xF8为逻辑1
const uint8_t code[]={0x80,0xF8};

typedef struct {
  uint8_t R;
  uint8_t G;
  uint8_t B;
}RGBColor_TypeDef;	//颜色结构体

// 常见颜色定义
const RGBColor_TypeDef RED = {255, 0, 0};
const RGBColor_TypeDef BLUE = {0, 0, 255};

// 灯颜色缓存
RGBColor_TypeDef RGB_Data[LED_NUM] = {0};

/**
  * @brief			设置灯带颜色发送缓存
  * @param[in]		ID 颜色
  */
void Set_LEDColor(uint16_t LedId, RGBColor_TypeDef Color)
{
    RGB_Data[LedId].G = Color.G;
    RGB_Data[LedId].R = Color.R;
    RGB_Data[LedId].B = Color.B;
}

/**
  * @brief			SPI发送控制ws2812
  * @param[in]		待发送缓存
  */
static void SPI_Send_WS2812(uint8_t *SPI_RGB_BUFFER)
{
    // 判断上次DMA有没有传输完成
    while(HAL_DMA_GetState(&hdma_spi1_tx) != HAL_DMA_STATE_READY);
    // 发送一个24bit的RGB数据
    HAL_SPI_Transmit_DMA(&hspi1, SPI_RGB_BUFFER, 24);
}

/**
  * @brief			控制WS2812
  * @param[in]		待发送缓存
  */
void RGB_Reflash(void)
{
	static uint8_t RGB_BUFFER[24]={0};
	uint8_t dat_b,dat_r,dat_g;
	// 将数组颜色转化为24个要发送的字节数据
    for (uint16_t i = 0; i < LED_NUM; i++) {
        dat_g = RGB_DAT[i].G;
        dat_r = RGB_DAT[i].R;
        dat_b = RGB_DAT[i].B;
        for (uint16_t j = 0; j < 8; j++) {
            RGB_BUFFER[7-j] =code[dat_g & 0x01];
            RGB_BUFFER[15-j]=code[dat_r & 0x01];
            RGB_BUFFER[23-j]=code[dat_b & 0x01];
            dat_g >>=1;
            dat_r >>=1;
            dat_b >>=1;
        }
        SPI_Send_WS2812(RGB_BUFFER);
	}
}
  • 15
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
根据提供的引用内容,可以了解到控制WS2812B需要使用PWM+DMASPI+DMA的方式。而ESP32芯片支持SPI+DMA的方式控制WS2812B,因此可以使用ESP32来实现控制WS2812B的功能。 下面是ESP32+DMA+SPI控制WS2812B的步骤: 1. 首先需要连接ESP32和WS2812B。将WS2812B的DI引脚连接到ESP32的任意一个SPI输出引脚(如GPIO14),将WS2812B的VCC引脚连接到ESP32的3.3V电源引脚,将WS2812B的GND引脚连接到ESP32的GND引脚。 2. 在ESP32上配置SPI控制器。可以使用ESP-IDF提供的spi_master驱动程序来配置SPI控制器。需要设置SPI控制器的时钟频率、数据位宽、传输模式等参数。 3. 准备要发送的数据。WS2812B需要接收24位的数据,每个数据位的高电平时间决定了该位的值。因此需要将要发送的数据转换为24位的二进制数据,并计算出每个数据位的高电平时间。 4. 配置DMA控制器。可以使用ESP-IDF提供的esp32_dma驱动程序来配置DMA控制器。需要设置DMA控制器的通道、传输模式、传输数据长度等参数。 5. 启动DMA传输。将要发送的数据存储在内存中,然后启动DMA传输,将数据发送到SPI控制器中。 下面是一个ESP32+DMA+SPI控制WS2812B的示例代码: ```c #include "driver/spi_master.h" #include "esp32_dma.h" #define WS2812B_NUM_LEDS 10 // WS2812B的颜色数据 typedef struct { uint8_t r; uint8_t g; uint8_t b; } ws2812b_color_t; // 将颜色数据转换为24位的二进制数据 static uint32_t ws2812b_color_to_binary(ws2812b_color_t color) { uint32_t binary = 0; for (int i = 0; i < 8; i++) { binary |= ((color.g >> (7 - i)) & 0x01) << (i * 3); binary |= ((color.r >> (7 - i)) & 0x01) << (i * 3 + 1); binary |= ((color.b >> (7 - i)) & 0x01) << (i * 3 + 2); } return binary; } // 计算每个数据位的高电平时间 static uint16_t ws2812b_calculate_high_time(uint8_t data) { if (data & 0x80) { return 56; } else { return 28; } } // 准备要发送的数据 static void ws2812b_prepare_data(ws2812b_color_t *colors, uint32_t *data) { for (int i = 0; i < WS2812B_NUM_LEDS; i++) { uint32_t binary = ws2812b_color_to_binary(colors[i]); for (int j = 0; j < 24; j++) { uint8_t bit = (binary >> (23 - j)) & 0x01; uint16_t high_time = ws2812b_calculate_high_time(bit); data[i * 24 + j] = high_time; } } } // 配置SPI控制器 static void ws2812b_configure_spi(spi_device_handle_t spi) { spi_bus_config_t bus_config = { .miso_io_num = -1, .mosi_io_num = GPIO_NUM_14, .sclk_io_num = -1, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = WS2812B_NUM_LEDS * 24, }; spi_device_interface_config_t dev_config = { .clock_speed_hz = 8000000, .mode = 0, .spics_io_num = -1, .queue_size = 1, }; spi_bus_initialize(HSPI_HOST, &bus_config, 1); spi_bus_add_device(HSPI_HOST, &dev_config, &spi); } // 配置DMA控制器 static void ws2812b_configure_dma(esp32_dma_chan_t *dma) { dma_config_t dma_config = { .direction = DMA_MEM_TO_PERIPH, .src_buffer = NULL, .dst_buffer = &SPI1W0_REG, .buffer_size = WS2812B_NUM_LEDS * 24 * sizeof(uint32_t), .channel = 1, .irq = 0, .transfer_size = 32, .flags = DMAF_NONE, }; esp32_dma_chan_config(dma, &dma_config); } // 启动DMA传输 static void ws2812b_start_dma(esp32_dma_chan_t *dma, uint32_t *data) { dma_config_t dma_config = esp32_dma_get_config(dma); dma_config.src_buffer = data; esp32_dma_chan_config(dma, &dma_config); esp32_dma_start(dma); } // 控制WS2812B显示颜色 void ws2812b_show_colors(ws2812b_color_t *colors) { spi_device_handle_t spi; esp32_dma_chan_t dma; uint32_t data[WS2812B_NUM_LEDS * 24]; ws2812b_prepare_data(colors, data); ws2812b_configure_spi(spi); ws2812b_configure_dma(&dma); ws2812b_start_dma(&dma, data); esp32_dma_wait_done(&dma); spi_bus_remove_device(spi); spi_bus_free(HSPI_HOST); } // 示例代码 void example() { ws2812b_color_t colors[WS2812B_NUM_LEDS] = { {255, 0, 0}, // 红色 {0, 255, 0}, // 绿色 {0, 0, 255}, // 蓝色 {255, 255, 0}, // 黄色 {255, 0, 255}, // 紫色 {0, 255, 255}, // 青色 {255, 255, 255}, // 白色 {128, 128, 128}, // 灰色 {255, 128, 0}, // 橙色 {0, 128, 255}, // 深蓝色 }; ws2812b_show_colors(colors); } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值