[SPI+DMA] 驱动WS2812B显示时钟

[SPI+DMA] 驱动WS2812B显示时钟

实现原理

本人一个大学萌新,偶然间发现RGB时钟这神奇的玩意,就想试试。目前初步实现时钟显示,第一次写博客,只是为了记录自己的学习成果方便以后查看,大家感兴趣的话也可以看看。
欢迎大家批评指正,如果有好的想法也可以告诉我,大家一起交流交流,一起学习,一起进步。
先贴几篇文章
[STM32]PWM+DMA控制WS2812
STM32F407通过SPI+DMA的方式驱动WS2812
我觉得大家通过这两篇文章大概就能明白点亮RGB灯的原理并成功点亮。

使用的硬件

1.一块淘宝上随便搜的rgb灯板
淘宝上随便搜的rgb灯板
2.一块stm32最小系统板

时钟显示代码实现

显示样式
时钟显示
代码实现

ws2812初始化

void ws281x_init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	SPI_InitTypeDef  SPI_InitStructure;
	DMA_InitTypeDef DMA_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //PORTA时钟使能 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); //SPI1时钟使能 	
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	//使能DMA传输

  /* PA7  SPI1_MOSI */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //PA7复用推挽输出 SPI
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA

	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 -> DR); //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); //关闭全部的灯需要一定的时间  
}

颜色传输及颜色划分

uint32_t ws281x_color(uint8_t red, uint8_t green, uint8_t blue)//颜色传输
{
  return green << 16 | red << 8 | blue;
}

void ws281x_setPixelColor(u8 x,u8 y,uint32_t GRBcolor)//点亮任意单个灯  画点
{
      int n,i;
	if(x>8||y>32)return ;
	   n=rgb[x][y];
	  for(i=0; i < 24; ++i)
	{
      pixelBuffer[n][i] = (((GRBcolor << i) & 0X800000) ? WS_HIGH : WS_LOW);
    }
	ws281x_show();
    delay_ms(42);
}

/ 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);
}

画点及字符显示

void ws281x_setPixelColor(u8 x,u8 y,uint32_t GRBcolor)//点亮任意单个灯  画点
{
      int n,i;
	if(x>8||y>32)return ;
	   n=rgb[x][y];
	  for(i=0; i < 24; ++i)
	{
      pixelBuffer[n][i] = (((GRBcolor << i) & 0X800000) ? WS_HIGH : WS_LOW);
    }
	ws281x_show();
    delay_ms(42);
}

//画点 
//x:0~127
//y:0~63
//t:1 填充 0,清空
//GRBcolor: 颜色设置
void ws281x_DrawPoint(u8 x,u8 y,u8 t,uint32_t GRBcolor)
{
 if(t==1)
  ws281x_setPixelColor(x,y,GRBcolor) ;
else
 ws281x_ResetPixelColor( x,y, GRBcolor);

}

//在指定位置显示一个字符,包括部分字符
//x:0~8
//y:0~32
//mode:0,反白显示;1,正常显示				 
//size:选择字体 8
void ws281x_ShowClock(u8 x,u8 y,u8 chr,u8 t,u8 size,uint32_t GRBcolor)
{   
	u8 temp,s,t1;
	u8 x0=x;
	u8 csize=(size/8+((size%8)?1:0))*(size/2);		//得到字体一个字符对应点阵集所占的字节数
	for(s=0;s<csize;s++)
	{
		if(csize==4)temp= asc2_0804[chr][s]; 
		else if(csize==3)
			temp= asc2_0603[chr][s];
		else return;
		for(t1=0;t1<8;t1++)
		{
		if(temp&0x80)
			{		
			ws281x_DrawPoint(x,y,t,GRBcolor);
			}
		else ws281x_DrawPoint(x,y,!t,GRBcolor);
		temp<<=1;
		x++;
	    if((x-x0)==8)
	       {
		 		x=x0;
				y++;
				break;
		  }
		}
	}
}

字模及取模方式取模格式

uint8_t asc2_0603[95][3]=
{
{0x7F,0x41,0x7F},/*"0",0*/
{0x00,0x7F,0x00},/*"1",1*/
{0x4F,0x49,0x79},/*"2",2*/
{0x49,0x49,0x7F},/*"3",3*/
{0x78,0x08,0x7F},/*"4",4*/
{0x79,0x49,0x4F},/*"5",5*/
{0x7F,0x49,0x4F},/*"6",6*/
{0x40,0x40,0x7F},/*"7",7*/
{0x7F,0x49,0x7F},/*"8",8*/
{0x79,0x49,0x7F},/*"9",5*/
};

将每个灯封装进二维数组里

uint8_t rgb[8][32]=
{
0,15,16,31,32,47,48,63,64,79,80,95,96, 111,112,127,128,143,144,159,160,175,176,191,192,207,208,223,224,239,240,255,
1,14,17,30,33,46,49,62,65,78,81,94,97, 110,113,126,129,142,145,158,161,174,177,190,193,206,209,222,225,238,241,254,
2,13,18,29,34,45,50,61,66,77,82,93,98, 109,114,125,130,141,146,157,162,173,178,189,194,205,210,221,226,237,242,253,
3,12,19,28,35,44,51,60,67,76,83,92,99, 108,115,124,131,140,147,156,163,172,179,188,195,204,211,220,227,236,243,252,
4,11,20,27,36,43,52,59,68,75,84,91,100,107,116,123,132,139,148,155,164,171,180,187,196,203,212,219,228,235,244,251,
5,10,21,26,37,42,53,58,69,74,85,90,101,106,117,122,133,138,149,154,165,170,181,186,197,202,213,218,229,234,245,250,
6,9, 22,25,38,41,54,57,70,73,86,89,102,105,118,121,134,137,150,153,166,169,182,185,198,201,214,217,230,233,246,249,
7,8, 23,24,39,40,55,56,71,72,87,88,103,104,119,120,135,136,151,152,167,168,183,184,199,200,215,216,231,232,247,248,
};

谢谢大家的观看。
精美壁纸奉上
在这里插入图片描述
在这里插入图片描述

根据提供的引用内容,可以了解到控制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、付费专栏及课程。

余额充值