SPI DMA驱动和控制WS2812彩色RGB灯
SPI DMA驱动WS2812
官方灯条是比赛的能量机制SK6812灯珠,每米144个灯珠,但真的很贵(
最后还是选了WS2811灯珠。
恶心的是,WS在2811逻辑0和1表达方式中,网上很多图片都是错误的,必须以数据手册为准… 如果逻辑0和逻辑1的发送顺序错误,则整个灯条的颜色混乱,甚至根本不亮
先放一段QA吧
Q1: 这种信号线输出控制的逻辑电平一般需要5V以上,需要3.3V升5V吗
A1: STM虽然32只能输出3.3V,但是控WS2811是可以的。.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工作频率8000KHz,如果将SPI频率设置为6.4MHz,正好是灯条IC芯片的8倍,所以每个字节(8位)对应一个逻辑位,即11110000
代表逻辑1,11000000
代表逻辑0
但也有150个协议ns的兼容性。比如SPI设置为5.25MBits/s(Mbps)(84MHz时钟线16分频据线8Bits),一Bit发送时间为1/5.25=190ns一个字节约1.52us。所以可以通过SPI的MOSI输出信号线发送一个字节Byte(8Bits)的数据模拟 WS2812的一个位。所以发送一个uint8字节0xF8(1111000)x190=950ns在580ns-1us范围之间代表逻辑1;0x80(1万)高电平时间为190ns接近220ns-380ns范围,代表逻辑0
CubeMX 配置
-
开启 SPI因为只用了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);
}
}