[SPI+DMA] 驱动WS2812B显示时钟
实现原理
本人一个大学萌新,偶然间发现RGB时钟这神奇的玩意,就想试试。目前初步实现时钟显示,第一次写博客,只是为了记录自己的学习成果方便以后查看,大家感兴趣的话也可以看看。
欢迎大家批评指正,如果有好的想法也可以告诉我,大家一起交流交流,一起学习,一起进步。
先贴几篇文章
[STM32]PWM+DMA控制WS2812
STM32F407通过SPI+DMA的方式驱动WS2812
我觉得大家通过这两篇文章大概就能明白点亮RGB灯的原理并成功点亮。
使用的硬件
1.一块淘宝上随便搜的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,
};
谢谢大家的观看。
精美壁纸奉上