基于ch32v003之spi dma实现的ws2812b炫彩灯光效果
本帖最后由 lilijin1995 于 2023-3-4 10:36 编辑背景:项目进度已经基于CH32V003 PWM DMA模式驱动了WS2812B 灯光效果。但是ch32V003有两个定时器,TIM1已经配置为Encoder模式,TIM2要改成PWM输出,所以不能用以驱动WS2812灯了,故而希望以SPI DMA的模式去驱动实现炫彩灯光效果。
软件设计:
因为不确定CH32V003的SPI DMA能否驱动WS2812B,所以这里我创建了独立的工程去验证一下,整个项目的代码这里不方便贴出。
其实WS2812B这类灯光效果的实现国外开源一大堆,比如开源大户Arduino,但是它是8位的AVR芯片,对于我们32位机,优先考虑STM32的,因为方便移植。
如下图:这是我找到的GitHub上开源WS2812驱动:https://github.com/Apex-yuan/STM32f103_WS2812
作者是Apex-yuan,在此表示感谢!!!正式因为大佬的开源,节约了我们大量重复造轮子的时间。轮子有了,接下来是建立自己的工程代码。虽然MRS创建工程很简单,但是有官方例程,创建好的Example干嘛不用呢。
如下图,我们将使用SPI_DMA工程,选择这个工程的原因大家应该已经意会了。熟悉CH32的朋友都知道STM32代码移植到CH32是非常方便的。前提是基于标准库的例程。这次选用的开源例程正式基于标准库的。所以我们直接复制大佬的WS2812驱动到我们自个的工程;最后移植后的代码如下:WS2812.c:
#include "ws2812.h"
#include "debug.h"
uint8_t pixelBuffer ;
void ws281x_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDefSPI_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //PORTA时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); //SPI1时钟使能
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA传输
/* PC6SPI1_MOSI */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;//设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //串行同步时钟的空闲状态为低电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第2个跳变沿(上升或下降)数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; //定义波特率预分频的值:波特率预分频值为16
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI1, &SPI_InitStructure);//根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
SPI_Cmd(SPI1, ENABLE); //使能SPI外设
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
DMA_DeInit(DMA1_Channel3); //将DMA的通道1寄存器重设为缺省值
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &(SPI1 -> DATAR); //cpar;//DMA外设ADC基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)pixelBuffer; //cmar;//DMA内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//数据传输方向,从内存读取发送到外设
DMA_InitStructure.DMA_BufferSize = PIXEL_NUM * 24; //cndtr;//DMA通道的DMA缓存的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//内存地址寄存器递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//数据宽度为8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//工作在正常缓存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//DMA通道x没有设置为内存到内存传输
DMA_Init(DMA1_Channel3, &DMA_InitStructure);//根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器
ws281x_closeAll();//关闭全部的灯
Delay_Ms(100); //关闭全部的灯需要一定的时间
}
void ws281x_closeAll(void)
{
uint16_t i;
uint8_t j;
for(i = 0; i < PIXEL_NUM; ++i)
{
for(j = 0; j < 24; ++j)
{
pixelBuffer = WS_LOW;
}
}
ws281x_show();
}
uint32_t ws281x_color(uint8_t red, uint8_t green, uint8_t blue)
{
return green << 16 | red << 8 | blue;
}
void ws281x_setPixelColor(uint16_t n ,uint32_t GRBcolor)
{
uint8_t i;
if(n < PIXEL_NUM)
{
for(i = 0; i < 24; ++i)
{
pixelBuffer = (((GRBcolor << i) & 0X800000) ? WS_HIGH : WS_LOW);
}
}
}
void ws281x_setPixelRGB(uint16_t n ,uint8_t red, uint8_t green, uint8_t blue)
{
uint8_t i;
if(n < PIXEL_NUM)
{
for(i = 0; i < 24; ++i)
{
pixelBuffer = (((ws281x_color(red,green,blue) << i) & 0X800000) ? WS_HIGH : WS_LOW);
}
}
}
void ws281x_show(void)
{
DMA_Cmd(DMA1_Channel3, DISABLE );//关闭USART1 TX DMA1 所指示的通道
DMA_ClearFlag(DMA1_FLAG_TC3);
DMA_SetCurrDataCounter(DMA1_Channel3,24 * PIXEL_NUM );//DMA通道的DMA缓存的大小
DMA_Cmd(DMA1_Channel3, ENABLE);//使能USART1 TX DMA1 所指示的通道
}
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t ws281x_wheel(uint8_t wheelPos) {
wheelPos = 255 - wheelPos;
if(wheelPos < 85) {
return ws281x_color(255 - wheelPos * 3, 0, wheelPos * 3);
}
if(wheelPos < 170) {
wheelPos -= 85;
return ws281x_color(0, wheelPos * 3, 255 - wheelPos * 3);
}
wheelPos -= 170;
return ws281x_color(wheelPos * 3, 255 - wheelPos * 3, 0);
}
// Fill the dots one after the other with a color
void ws281x_colorWipe(uint32_t c, uint8_t wait) {
for(uint16_t i=0; i<PIXEL_NUM; i++) {
ws281x_setPixelColor(i, c);
ws281x_show();
Delay_Ms(wait);
}
}
void ws281x_rainbow(uint8_t wait) {
uint16_t i, j;
for(j=0; j<256; j++) {
for(i=0; i<PIXEL_NUM; i++) {
ws281x_setPixelColor(i, ws281x_wheel((i+j) & 255));
}
ws281x_show();
Delay_Ms(wait);
}
}
// Slightly different, this makes the rainbow equally distributed throughout
void ws281x_rainbowCycle(uint8_t wait) {
uint16_t i, j;
for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel
for(i=0; i< PIXEL_NUM; i++) {
ws281x_setPixelColor(i,ws281x_wheel(((i * 256 / PIXEL_NUM) + j) & 255));
}
ws281x_show();
Delay_Ms(wait);
}
}
//Theatre-style crawling lights.
void ws281x_theaterChase(uint32_t c, uint8_t wait) {
for (int j=0; j<10; j++) {//do 10 cycles of chasing
for (int q=0; q < 3; q++) {
for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
ws281x_setPixelColor(i+q, c); //turn every third pixel on
}
ws281x_show();
Delay_Ms(wait);
for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
ws281x_setPixelColor(i+q, 0); //turn every third pixel off
}
}
}
}
//Theatre-style crawling lights with rainbow effect
void ws281x_theaterChaseRainbow(uint8_t wait) {
for (int j=0; j < 256; j++) { // cycle all 256 colors in the wheel
for (int q=0; q < 3; q++) {
for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
ws281x_setPixelColor(i+q, ws281x_wheel( (i+j) % 255)); //turn every third pixel on
}
ws281x_show();
Delay_Ms(wait);
for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
ws281x_setPixelColor(i+q, 0); //turn every third pixel off
}
}
}
}
其中ws281x_init初始化代码要注意的是要是能RCC_APB2Periph_GPIOC时钟,并初始化PC6作为GPIO_Mode_AF_PP,因为CH32V003是的PC6是MOSI;
另外DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &(SPI1 -> DATAR); 跟STM32的SPI1 -> DR不太一样;然后再贴出WS2812.h代码
#ifndef __WS2812_H
#define __WS2812_H
#include <ch32v00x.h>
#define PIXEL_NUM 9
//硬件spi模拟ws2811时序(用spi的8位数据模拟ws281x的一位数据)
//_____
// | |___| 11110000high level
//___
// | |_____| 11000000low level
#define WS_HIGH 0XF0
#define WS_LOW0XC0
void ws281x_init(void);
void ws281x_closeAll(void);
void ws281x_rainbowCycle(uint8_t wait);
uint32_t ws281x_color(uint8_t red, uint8_t green, uint8_t blue);
void ws281x_setPixelColor(uint16_t n ,uint32_t GRBcolor);
void ws281x_show(void);
void ws281x_theaterChase(uint32_t c, uint8_t wait);
void ws281x_colorWipe(uint32_t c, uint8_t wait);
void ws281x_rainbow(uint8_t wait);
void ws281x_theaterChaseRainbow(uint8_t wait);
#endif /* __WS2812_H */
大佬用的是56Mhz的系统时钟,8分频算下来是7Mhz,但我们用的是48Mhz的HSI,所以8分频后只有6Mhz,但是这并不影响我们驱动WS2812B,其中:
// | |___| 11110000 high level
// ___
// | |_____| 11000000 low level
#define WS_HIGH 0XF0
#define WS_LOW 0XC0
1码是这样算得:T=1/f=1/6Mhz=0.1666666666666667us,4*T=0.6666666666666667us;
0码是这样算得:T=1/f=1/6Mhz=0.1666666666666667us,2*T=0.3333333333333334us;
再来看WS2812的时序
都是满足的。
最后在main函数调用灯光效果API:
int main(void)
{
u8 i=0;
Delay_Init();
USART_Printf_Init(460800);
printf("SystemClk:%d\r\n", SystemCoreClock);
ws281x_init();
while(1)
{
// Some example procedures showing how to display to the pixels:
ws281x_colorWipe(ws281x_color(255, 0, 0), 50); // Red
ws281x_colorWipe(ws281x_color(0, 255, 0), 50); // Green
ws281x_colorWipe(ws281x_color(0, 0, 255), 50); // Blue
//colorWipe(strip.Color(0, 0, 0, 255), 50); // White RGBW
// Send a theater pixel chase in...
ws281x_theaterChase(ws281x_color(127, 127, 127), 50); // White
ws281x_theaterChase(ws281x_color(127, 0, 0), 50); // Red
ws281x_theaterChase(ws281x_color(0, 0, 127), 50); // Blue
ws281x_rainbow(20);
ws281x_rainbowCycle(10);
ws281x_theaterChaseRainbow(50);
for(i = 0; i < PIXEL_NUM; ++i)
{
ws281x_setPixelColor(i, ws281x_color(0,250,0));
ws281x_show();
Delay_Ms(500);
}
}
}是不是就可以实现了呢?直接编译通过下载的话,我估计是白灯,因为我们的工程用的是48Mhz的HSI,但是SPI_DMA用的是HSE,我们在system_ch32v00x.c中
直接修改一下即可:
下载验证:
最后我们一起欣赏一下实验效果:
https://www.bilibili.com/video/BV1nX4y1D7S4/?vd_source=2bbde87de845d5220b1d8ba075c12fb0
嗯嗯,不错,留着备用。谢谢楼主。 我们使用dma是为了提高工作效率还是为了节省资源呢 这个灯光效果的细腻程度是不是已经由ws2812b限制住了呢 请问这种芯片调光的原理大概是什么呢 看代码的话 如果想要更改灯光就需要凭借经验慢慢更改配比吗 芯片是成熟的芯片了可控制的数量也是很多的 请问一片ws2812b可以控制多少条灯带啊 这个可以通过延时实现的驱动吗 这个是不是需要一个数组才能实现呢 需要建立相关的库吗 最大的驱动速度是多少 WS2812B 是一款基于串行通信接口的 RGB LED 灯珠,它可以通过使用 DMA 实现 SPI 通信来控制 LED 的颜色和亮度。 SK6812和WS2812B的区别大吗? 这个怎么控制时序的呢 ws2812和ws2812b能混用吗 如何实现ws2812b led灯点阵的驱动呢 arduino开发ws2812最简答了。 SPI 使用DMA方式如何来做
页:
[1]
2