STM32F407通过SPI+DMA的方式驱动WS2812

关于STM32F407通过SPI+DMA的方式驱动WS2812的讲解与驱动demo

ws2812简介

1、控制电路与RGB芯片集成在一个5050封装的元器件中, 构成一个完整的外控像素点。
2、每个像素点的三基色颜色可实现256级亮度显示, 完成16777216种颜色的全真色彩显示, 扫描频率不低于400Hz/s。
3、串行级联接口, 能通过一根信号线完成数据的接收与解码。
4、任意两点传传输距离在不超过5米时无需增加任何电路。
5、当刷新速率30帧/秒时, 低速模式级联数不小于512点, 高速模式不小于1024点。
6、数据发送速度可达800Kbps。
7、光的颜色高度一致, 性价比高。
ws2812这款灯珠使用起来真的很方便,可以变换很多种颜色。但是,区分度较为明显的颜色不是很多,很多的颜色通过灯珠表现出来后辨识度不高。一般只能用红、橙、黄、绿、蓝、靛、紫、白和洋红这几种辨识度比较高的颜色。
LED的参数特性如下:
STM32F407通过SPI+DMA的方式驱动WS2812_第1张图片

ws2812驱动原理

ws2812数据协议采用单线归零码的通讯方式,像素点在上电复位以后,DIN端接受从控制器传输过来的数据,首先送过来的24bit数据被第一个像素点提取后,送到像素点内部的数据锁存器,剩余的数据经过内部整形处理电路整形放大后通过DO端口开始转发输出给下一个级联的像素点,每经过一个像素点的传输,信号减少24bit。像素点采用自动整形转发技术,使得该像素点的级联个数不受信号传送的限制,仅仅受限信号传输速度要求。
所谓的单线归零码,也称为单极性归零码,是指高电平和零电平分别表示二进制码1 和0,在无电压表示“0”,恒定正电压表示“1”,但持续时间短于一个码元的时间宽度,即发出一个窄脉冲。每个码元时间的中间点是采样时间,判决门限为半幅电平。
单极性归零码的主要优点是可以直接提取同步信号,因此单极性归零码常常用作其他码型提取同步信号时的过渡码型.也就是说其他适合信道传输但不能直接提取同步信号的码型,可先变换为单极性归零码,然后再提取同步信号。
它的具体体现如下图所示:
STM32F407通过SPI+DMA的方式驱动WS2812_第2张图片
看了上面关于单线归零码的介绍,再来看ws2812的驱动原理,就能一眼看懂了,ws2812主要就是靠发送0码和1码来控制颜色。
ws2812的控制原理为通过一定的时序,发送高低电平,其时序波形要求如下:

STM32F407通过SPI+DMA的方式驱动WS2812_第3张图片
ws2812的码型时序要求:
STM32F407通过SPI+DMA的方式驱动WS2812_第4张图片
而这个led的驱动难点也在发送归零码上面,因为它有一个时序的要求,必须要满足它本身的时序才行。看了上面图形的介绍,可以看到信号的周期为1.25us,与前面的800kbps的通信频率正好对应。0码与1码通过不同的占空比进行区分。然而也出现了一个新问题,常用的STM32F1系列单片机,频率只有72MHz,对于高性能的STMF4系列也不过200MHz左右,也就是说,STM32F1的一个指令周期需要大约14ns,而正常情况下用HAL库控制单片机I/O翻转,至少需要30个指令周期,将近500ns才能实现一次高低电平变化,即使是直接用寄存器驱动I/O,也只能达到12MHz的翻转速度,就是F4系列直接用寄存器驱动,虽然可以超频到240MHz,让I/O翻转速率达到80MHz,然而稳定性也是一个方面。所以,STM32以及所有比他性能还低的单片机,用I/O翻转的方式,是很难驱动WS2812系列灯珠的,除非直接上400M的F7,或者你的片子啥事都不干就驱动一串LED。故此,排除掉直接翻转I/O口的方式,采用PWM+DMA或者SPI+DMA的方式驱动它。本文主讲SPI+DMA的驱动方式

SPI+DMA的驱动方式

硬件SPI中有一个MOSI引脚来输出数据,我们就是用MOSI的输出功能,通过发送一个字节或半字的数据,来模拟我们的LED控制信号0码或码1,主要就是配置好SPI的时钟即可。看下图:
STM32F407通过SPI+DMA的方式驱动WS2812_第5张图片
重点在于SPI的配置上面,我们通过控制SPI的时钟频率可以实现SPI传输8个位的数据使用的时间大致等于1.25us,例如STM32f407的SPI2挂载的APB1总线频率为42Mhz,8分频之后得到的SPI2的SCK的频率为5.25Mhz,周期为0.19us左右,传输8个位的时间为1.5us左右,然后进行一些调整,趋近于1.25us即可。接下来就是一些SPI的具体配置:

	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;    //设置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;	                        //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		                          //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;		//42M/8=5.xM
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	                  //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7;	                            //CRC值计算的多项式
	SPI_Init(SPI2, &SPI_InitStructure);                                   //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器

配置好SPI之后,就是DMA的配置了,为什么采用DMA呢。是因为每次调用SPI发送数据是有时间间隔的,并且会被中断打断,这时候DMA的作用就体现出来了。将DMA的内存地址设置为SIP待发送数据的地址,长度设置为发送数据的大小,然后开启DMA发送,等待DMA发送完成标志位置位即可。下面是DMA的配置部分:

  DMA_InitStructure.DMA_Channel = DMA_Channel_0;                            //通道选择
  DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&SPI2->DR;                //DMA外设地址
  DMA_InitStructure.DMA_Memory0BaseAddr = (u32)pixelBuffer;                 //DMA 存储器0地址
  DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;                   //存储器到外设模式
  DMA_InitStructure.DMA_BufferSize = Pixel_S1_NUM * 24;                     //数据传输量 
  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_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;         
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;               //存储器突发单次传输
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;       //外设突发单次传输
  DMA_Init(DMA1_Stream4, &DMA_InitStructure);                               //初始化DMA Stream

SPI和DMA的配置就已经结束了,接下来便是数据的转换了。数据转换主要涉及将RGB转换为LED能识别的信号。下图是数据的传输方式:
STM32F407通过SPI+DMA的方式驱动WS2812_第6张图片
从上面可以看出,24bit的顺序是GRB,这个一定要注意,然后将RGB的颜色转换为对应的24Bit数据即可,RGB转十六进制颜色对照表可以去这儿看:RGB颜色转换
然后就可以了。只需要在点亮某个LED的时候调用具体的函数即可。
后面附上stm32f407通过SPI+DMA方式驱动WS2812的驱动DEMO:
https://download.csdn.net/download/cxieyunsky/12248557
----------------------------------------------------------------------------------------------------------2020/3/14
----------------------------------------------------------------------------------------------------------@曼珠沙华

你可能感兴趣的:(STM32F407通过SPI+DMA的方式驱动WS2812)