吽吽吽 发表于 2023-1-3 00:02:19

DMA_SPI 驱动 WS2812B 全彩LED 的简单应用 | 梁工是说不用加反向器 ?

DMA_SPI, STC8H/STC32G, 驱动WS2812B全彩LED的简单应用
一位新人的工作之余琢磨的,供大家参考
芯片:STC8H4K64TLCD
功能:利用 DMA_SPI 驱动WS2812B(5颗)
限制:10颗以内,主频固定28Mhz
优点:无需反向器
硬件:第一颗WS2812B, DI接MOIS脚,(我接的是P3.4),
          MOSI 接10K下拉电阻。
程序:
/*******我也不知道为什么丢这么多头文件*************/
#include<STC8H.H>
#include<absacc.h>
#include<stdio.h>
#include<math.h>
#include <intrins.h>
#include <string.h>

/**********WS2812B数据,0XF0对应1,0X00对应0************************/
unsigned char xdata GRB={ 0xF0,0xF0,0xF0,0xF0, 0xF0,0xF0,0xF0,0xF0,   0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,      0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,   // 绿色对应WS2812数据:1111 1111 0000 0000 0000 0000
                                             0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,   0xF0,0xF0,0xF0,0xF0, 0xF0,0xF0,0xF0,0xF0,      0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,    // 红色对应WS2812数据: 0000 0000 1111 1111 0000 0000
                                             0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,   0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,      0xF0,0xF0,0xF0,0xF0, 0xF0,0xF0,0xF0,0xF0,    //蓝色   对应WS2812数据:0000 0000 0000 00001111 1111
                                             0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,   0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,      0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,};   //全灭对应WS2812数据:0000 0000 0000 0000 0000 0000
                                                                                                                                       
unsigned char xdata LED_DATA={0xF0,0xF0,0xF0,0xF0, 0xF0,0xF0,0xF0,0xF0, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,   0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,      //0-23对应第一颗24bit数据,初始绿色
                                                      0xF0,0xF0,0xF0,0xF0, 0xF0,0xF0,0xF0,0xF0, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,   0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,       //24-47对应第二颗24bit数据,初始绿色
                                                      0xF0,0xF0,0xF0,0xF0, 0xF0,0xF0,0xF0,0xF0, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,   0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,      //48-71 对应第三颗24bit数据,初始绿色
                                                      0xF0,0xF0,0xF0,0xF0, 0xF0,0xF0,0xF0,0xF0, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,   0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,      //72-95 对应第四颗24bit数据,初始绿色
                                                      0xF0,0xF0,0xF0,0xF0, 0xF0,0xF0,0xF0,0xF0, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,   0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,};         //96-120 对应第五颗24bit数据,初始绿色


int xdata Di=0;       //定义LED_DATA数据位,0 第一颗LED数据第一位,24第二颗数据第一位,依次按24倍数
int xdata Yrgb=0;    //定义GRB数据位,0 绿色数据第一位 ,24红色数据第一位,48蓝色数据第一位,72关闭LED数据第一位         
int xdata ENSPI=0;    //DMA_SPI中断标志位


/**********延时 80us用于WS2812B置零,实际运用中可考虑其他方式 1000MS方便观察现象************/


void Delay80us()                //@28MHz
{
      unsigned char i, j;

      _nop_();
      i = 3;
      j = 230;
      do
      {
                while (--j);
      } while (--i);
}



void Delay1000ms()                //@28MHz
{
      unsigned char i, j, k;

      _nop_();
      _nop_();
      i = 143;
      j = 12;
      k = 64;
      do
      {
                do
                {
                        while (--k);
                } while (--j);
      } while (--i);
}





/******SW2812B数据*****************/
void LEDRGB(Di,Yrgb)
{
memmove(LED_DATA+Di,GRB+Yrgb,24);       //从GRB第Yrgb+1位复制到LED_DATA的Di+1位,一共复制24个字符
}


/**********DMA_SPI**************************/

void SPI()
{
ENSPI=DMA_SPI_CR; //读DMA_SPI控制寄存器
ENSPI&=0xC0;          //取第7.6位   
if(ENSPI==0)            //0,说明DMA_SPI未使用,
{
      P34=0;                              //MOSI低电平      
      Delay80us();                        //延时WS2812B置零,开始接收数据,                        
      DMA_SPI_AMT=119;            //传输120字节。24*5=120字节
      DMA_SPI_TXA=LED_DATA;   //数据起始位。
      SPCTL=0xD3;                     //1101 00116.使能SPI5主机模式,4空闲时钟线低电平,3前沿写 2后沿读 1:0主时钟2分频 14Mhz”                        
      DMA_SPI_CR=0xC0;             //11000000 开始DMA_SPI
}      
}

//DM_SPI中断
void DMA_SPI_Routine(void)interrupt 49
{
SPCTL=0x00;
P34=0;                         //WS2812B数据空闲低电平
DMA_SPI_CR=0x00;    //关闭DMA_SPI
DMA_SPI_STA=0x00;//中断标志位清零      
}



void main()
{
CLKSEL=0;//内部高速时钟 下载设置25.6MHZ
EA=1; //打开总中断
//------------------I/O设置,MOIS设置为推挽输出,设置为准双向口时,实测高电平为一斜线----------
P0M0 = 0x00; P0M1 = 0x00;
P1M0 = 0x00; P1M1 = 0x00;
P2M0 = 0x00; P2M1 = 0x7f;
P3M0 = 0x10; P3M1 = 0x00;
P34=0;      
P4M0 = 0x00; P4M1 = 0x00;
P5M0 = 0x00; P5M1 = 0x00;
//---------------------SPI设置-----------------------------------------
P_SW2|=128;         //1000 0000 允许访问片内SFR0FA00H~0FFFFHFB50-FB6F 触摸阈值寄存器      
P_SW1=12;             //00-- **0- MOSI输出设置P3.4 **=11
AUXR=0;            //允许访问内部扩展RAM      
DMA_SPI_CFG2=4;    //00000100 不使用SS脚 SS脚P1.2”
DMA_SPI_CFG=0xCF;    //1100 1111,允许DMA_SPI中断,允许发送禁止接收数据,中断优先最高 访问级最大      

while(1)
{
//-----------全部显示绿色-----------
Delay1000ms();
LEDRGB(0,0);   //从GRB第1个字符开始,依次复制24个字符复制到LED_DATA的第1-24字符,第一颗LED数据
LEDRGB(24,0);   //从GRB第1个字符开始,依次复制24个字符复制到LED_DATA的第25-48字符,第二颗LED数据
LEDRGB(48,0);   //从GRB第1个字符开始,依次复制24个字符复制到LED_DATA的第49-72字符,第三颗LED数据               
LEDRGB(72,0);   //从GRB第1个字符开始,依次复制24个字符复制到LED_DATA的第73-96字符,第三颗LED数据      
LEDRGB(96,0);   //从GRB第1个字符开始,依次复制24个字符复制到LED_DATA的第97-120字符,第三颗LED数据      
SPI();
//-----------全部显示红色-----------

Delay1000ms();
LEDRGB(0,24);   
LEDRGB(24,24);
LEDRGB(48,24);                           
LEDRGB(72,24);
LEDRGB(96,24);
SPI();

//-----------全部显示蓝色-----------
Delay1000ms();
LEDRGB(0,48);   
LEDRGB(24,48);
LEDRGB(48,48);                           
LEDRGB(72,48);
LEDRGB(96,48);      
SPI();
//-----------全部关闭-----------
Delay1000ms();
LEDRGB(0,72);   
LEDRGB(24,72);
LEDRGB(48,72);                           
LEDRGB(72,72);
LEDRGB(96,72);      
SPI();      

}
}


思路:
DMA_SPI在进行数据传输时,2个字符直接有间隙,间隙期间MOSI为高电平,
而WS2812B判断1与0是依据高电平时间,间隙时长与主频有关,40Mhz时候,
间隙时长约280nS,28Mhz间隙时长约400nS.
         
如果增加反相器,可以无需考虑这段时长,因为我的PCB已经成型不方便增加反相器,
所以利用2个字符的间隙高电平时间,让WS2812B判断为0,通过字符高位设1延长
高电平时间让WS2812B判断为1。

最后,强烈建议使用DMA_SPI驱动WS2812B时候,MOSI脚增加反相器。
小白一枚,如果有什么错误,欢迎指出,谢谢

DMA_SPI 字符 间隙


主频28MHz MOSI 波形


吽吽吽 发表于 2023-1-3 00:04:48


上面测试,没有增加反向器
另外,无论采取什么方式,DMA_SPI数据传输完成后,MOSI要有大于50uS的低电平时间。

芯LYS 发表于 2023-1-5 10:45:27

感谢应用的分享   

梁工 发表于 2023-1-5 11:21:29

WS2812S的标准时序如下:
TH+TL = 1.25us±150ns, RES>50us
T0H = 0.25us±150ns = 0.10us - 0.40us
T0L = 1.00us±150ns = 0.85us - 1.15us
T1H = 1.00us±150ns = 0.85us - 1.15us
T1L = 0.25us±150ns = 0.10us - 0.40us
两个位数据之间的间隔要小于RES的50us.

用 SPI 传输, 速度3~3.3Mbps,
每个字节高4位和低4位分别对应一个位数据,
1000为数据0, 1110为数据1, 使用 DMA-SPI 传输.

吽吽吽 发表于 2023-1-5 15:30:16

多次提升,反向还是增加外部反向芯片比较稳定,调整如下
频率32MhzSPI 4分频
/*******我也不知道为什么丢这么多头文件*************/

#include<STC8H.H>
#include<absacc.h>
#include<stdio.h>
#include<math.h>
#include <intrins.h>
#include <string.h>


/**********WS2812B数据,0XC0对应1,0XFC对应0************************/


unsigned char xdata GRB={ 0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,    0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,    0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,      
                                              0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,    0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,    0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,      
                                              0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,    0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,    0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,};
                                                                                                                                       
unsigned char xdata LED_DATA={0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,    0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,   0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,   
                                                         0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,    0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,   0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,   
                                                         0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,    0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,   0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,
                                                         0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,    0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,   0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,
                                                         0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,    0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,   0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,};      



int xdata Di=0;       //定义LED_DATA数据位,0 第一颗LED数据第一位,24第二颗数据第一位,依次按24倍数
int xdata Yrgb=0;    //定义GRB数据位,0 绿色数据第一位 ,24红色数据第一位,48蓝色数据第一位,72关闭LED数据第一位      
int xdata SPIIF=0;    //DMA_SPI中断标志位,采用查询方式


/**********延时 80us用于WS2812B置零,实际运用中可考虑其他方式,500MS方便观察现象************/

void Delay80us()                //@32MHz
{
      unsigned char i, j;

      _nop_();
      i = 4;
      j = 80;
      do
      {
                while (--j);
      } while (--i);
}



void Delay500ms()                //@32MHz
{
      unsigned char i, j, k;

      _nop_();
      i = 82;
      j = 43;
      k = 255;
      do
      {
                do
                {
                        while (--k);
                } while (--j);
      } while (--i);
}





/******SW2812B数据*****************/
void LEDRGB(Di,Yrgb)
{
memmove(LED_DATA+Di,GRB+Yrgb,24);       //从GRB第Yrgb+1位复制到LED_DATA的Di+1位,一共复制24个字符
}



void SPI()
{

Delay80us();               //延时WS2812B置零,开始接收数据               
      DMA_SPI_AMT=119;   //传输120字节。24*5=120字节
      DMA_SPI_TXA=LED_DATA;//数据起始位。               
      DMA_SPI_CR=0xC0;    //11000000 开始DMA_SPI
      while(SPIIF==0){SPIIF=DMA_SPI_STA;SPIIF&=0x01;}   //采用查询DMA_SPI中断标志位的方式
      SPIIF=0;
      DMA_SPI_CR=0x00;
       DMA_SPI_STA=0x00;//中断标志位清零      

}
/***********显示不同颜色************/
void LED1_G(){LEDRGB(0, 0);}
void LED1_R(){LEDRGB(0, 28);}
void LED1_B(){LEDRGB(0, 48);}
void LED2_G(){LEDRGB(24,0);}
void LED2_R(){LEDRGB(24,24);}
void LED2_B(){LEDRGB(24,48);}
void LED3_G(){LEDRGB(48,0);}
void LED3_R(){LEDRGB(48,24);}
void LED3_B(){LEDRGB(48,48);}
void LED4_G(){LEDRGB(72,0);}
void LED4_R(){LEDRGB(72,24);}
void LED4_B(){LEDRGB(72,48);}
void LED5_G(){LEDRGB(96,0);}
void LED5_R(){LEDRGB(96,24);}
void LED5_B(){LEDRGB(96,48);}

/************主函数**************/
void main()
{

CLKSEL=0;//内部高速时钟
P0M0 = 0x00; P0M1 = 0x00;
P1M0 = 0x00; P1M1 = 0x00;
P2M0 = 0x00; P2M1 = 0x00;
P3M0 = 0x10; P3M1 = 0x00;//MOSI端口推挽输出
P4M0 = 0x00; P4M1 = 0x00;
P5M0 = 0x00; P5M1 = 0x00;
//---------------------SPI设置-----------------------------------------
P_SW2|=128;            //1000 0000 允许访问片内SFR0FA00H~0FFFFHFB50-FB6F 触摸阈值寄存器      
P_SW1=12;               //00-- **0- MOSI输出设置P3.4 **=11
AUXR=0;                     //允许访问内部扩展RAM      
DMA_SPI_CFG2=4;       //00000100 不使用SS脚 SS脚P1.2”
DMA_SPI_CFG=0xCF;    //1100 1111,允许DMA_SPI中断,允许发送禁止接收数据,中断优先最高 访问级最大      
SPCTL=0xD0;               //1101 00006.使能SPI5主机模式,4空闲时钟线低电平,3前沿写 2后沿读 1:0主时钟4分频 8Mhz”               

while(1)
{
LED1_R();      SPI();Delay500ms();
LED1_G();      SPI();Delay500ms();
LED1_B();      SPI();Delay500ms();
LED2_R();      SPI();Delay500ms();
LED2_G();      SPI();Delay500ms();
LED2_B();      SPI();Delay500ms();
LED3_R();      SPI();Delay500ms();
LED3_G();      SPI();Delay500ms();
LED3_B();      SPI();Delay500ms();
LED4_R();      SPI();Delay500ms();
LED4_G();      SPI();Delay500ms();
LED4_B();      SPI();Delay500ms();
LED5_R();      SPI();Delay500ms();
LED5_G();      SPI();Delay500ms();
LED5_B();      SPI();Delay500ms();
}
}


效果(因未设置LED熄灭数据,所以未操作LED最终是蓝色):attach://2001.mp4
波形图:周期约1.35us


反相器电路图
从坏液晶电路板上找了个三态输出缓冲器,在PCB上搭了个反相器。(丝印C255)






Hssa 发表于 2023-8-12 17:39:30

您好,能给一份硬件的原理图嘛

吽吽吽 发表于 2023-8-30 14:19:47

Hssa 发表于 2023-8-12 17:39
您好,能给一份硬件的原理图嘛
就是芯片的随便一个MOSI 脚接SN74LVC1G125的OE脚,然后程序中定义该MOSI脚为推挽输出。

chen 发表于 2023-9-27 15:23:48

梁工 发表于 2023-1-5 11:21
WS2812S的标准时序如下:
TH+TL = 1.25us±150ns, RES>50us
T0H = 0.25us±150ns = 0.10us - 0.40us


我用stc32f也遇到同样问题,关键是stc的spi_dma不支持连续模式,mosi管脚在两个数据之间是好几百nm的高电平,这就导致时序不能满足。想要解决如楼主所说只能反相了。或者说梁工能有更简便的方法

梁工 发表于 2023-9-27 17:54:11

chen 发表于 2023-9-27 15:23
我用stc32f也遇到同样问题,关键是stc的spi_dma不支持连续模式,mosi管脚在两个数据之间是好几百nm的高电 ...
没有问题,用双缓冲,一个缓冲正在传输,另一个缓冲准备数据,传输一个缓冲完成,
DMA中断,切换一个缓冲,几个uS就好了。

chen 发表于 2023-9-27 20:27:35

梁工 发表于 2023-9-27 17:54
没有问题,用双缓冲,一个缓冲正在传输,另一个缓冲准备数据,传输一个缓冲完成,DMA中断,切换一个缓冲 ...

梁工你误解我的意思了,这个不是spi传输快慢的问题,dma+spi速度确实很快,但是要驱动WS2812不是光有速度就行,要让时序满足。stc的spi不支持连续模式就导致两个字节之间有几百纳秒的间隔,spi常规应用这不到0.5us的间隔无所谓,但是用spi驱动2812这种时序敏感的非spi器件就有问题了。
今天我试出MOSI脚位初始化成0的时候,间隔的时候MOSI脚为低电平。具体等明天我驱动一下看有没有问题。

题外话,WS2812这种全彩灯珠用的还是非常多的,STC是不是哪天把这个驱动集成到里面{:lol:}

WS2812这种nm级别的时序,简单点就是看着示波器加nop,这种方式如果只驱动灯的话没问题,实际上一言难尽。基本上现在都是靠DMA+SPI取巧或者DMA+PWM。STC貌似做不到DMA+PWM,想要不占CPU资源貌似只能通过SPI了。
页: [1] 2 3
查看完整版本: DMA_SPI 驱动 WS2812B 全彩LED 的简单应用 | 梁工是说不用加反向器 ?