环境:Keil Uv4 C51   STC8G1K08@12MHz   Optimize Level = 8
驱动效果

关于WS2812

参考Datasheet:链接

简述

WS2812是一个集控制电路与发光电路于一体的智能外控 LED 光源,通过单总线协议传输数据,支持无限级联,支持256级亮度和16777216色显示。

引脚分配

2812的5050封装具有四个引脚,包括VSS/VDD/DIN/DOUT。其中VSS和VDD为电源引脚,供电电压3.5~5.3V;DIN为数据输入引脚,DOUT为级联数据输出引脚。

无限级联原理

当2812通过 DIN -> DOUT -> DIN -> DOUT … 这种方式连接起来时,第一组24bits数据传输完成前,第一个2812的DOUT引脚不会有任何输出,24bits传输完成后,第一个2812不再进行数据接收,而将数据通过DOUT脚输出至下一个2812的DIN。通过这种循环,便可以实现2812的无限级连。

在Datasheet中有更加详细的解释和配图,读者可自行查阅。

需要注意的是,当RESET CODE传输前,2812会因认为数据没有传输完成而不点亮。

驱动相关

注意:由于单总线协议大多对时序要求严格,所以需要用汇编进行书写或严格对照编译器所编译的字节码进行计算。同时不同型号的STC单片机指令集可能不同,其指令的执行周期也有一定差别,需要对照指令集进行计算,本文依据的指令集为STC-Y6。

通过对Datasheet的查阅,我们得出:WS2812的显示需要三个字节来表示颜色,发送顺序为G-R-B,高位先行。由此可以写出以下原型代码:

//sbit WS2812_IO = P3 ^ 3;
void WS2812_SendByte(unsigned char dat) {
	unsigned char i = 8;

	dat <<= 1;
	while(i) {
		WS2812_IO = 1;
		delay_ns(...);
		WS2812_IO = CY;
		delay_ns(...);
		WS2812_IO = 0;
		dat <<= 1;
		i--;
	}
}

将原型中的delay注释后在Keil中进行编译后运行debug,在反汇编窗口中可得到如下代码:

反汇编结果

Tips:CY为8051中的进位标志,当最后一次算术操作产生进位(加法)或借位(减法)时,该位置 1。在此处用作取最高位。

该函数完整的反汇编结果如下:

;    41: void WS2812_SendByte(unsigned char dat) { 
;    42:         unsigned char i = 8; 
;    43:  
;C:0x0093    7E08
MOV      R6,#0x08

;    44:         dat <<= 1; 
;C:0x0095    EF
MOV      A,R7
;C:0x0096    25E0
ADD      A,ACC(0xE0)
;C:0x0098    FF
MOV      R7,A

;    45:         while(i) { 
;    46:                 WS2812_IO = 1; 
;    47:                 //delay_ns(...); 
;C:0x0099    D2B3
SETB     WS2812_IO(0xB0.3)

;    48:                 WS2812_IO = CY; 
;    49:                 //delay_ns(...); 
;C:0x009B    92B3
MOV      WS2812_IO(0xB0.3),C

;    50:                 WS2812_IO = 0; 
;C:0x009D    C2B3
CLR      WS2812_IO(0xB0.3)

;    51:                 dat <<= 1; 
;C:0x009F    EF
MOV      A,R7
;C:0x00A0    25E0
ADD      A,ACC(0xE0)
;C:0x00A2    FF
MOV      R7,A

;    52:                 i--; 
;C:0x00A3    DEF4
DJNZ     R6,C:0099

;    53:         } 
;    54: } 

通过查阅 STC8 的数据手册我们可以了解到,SETB bitMOV bit,CCLR bitMOV A,RnADD A,#dataMOV Rn,A的指令周期均为1机器周期,而DJNZ Rn,rel在条件满足时需要三个机器周期 ,不满足时需要两个机器周期 。2 / 3可不是2 / 3周期!ao多单周期指令,我们stc真的太厉害啦!

而在 12MHz 的晶振下,一个时钟周期的时间为 1/12 us ,即为 0.083us,83.3ns。STC8 为 1T 机,1时钟周期 = 1机器周期。这样可以换算出每条指令所需要的时间。

对指令时间进行计算,可以换算出每个 delay() 等效于多少的 _nop_()

经过多次实验,当 [katex]TxH+TxL=1.417\mu s[/katex];且1码与0码高低电平时长分别如下表时,可以正常驱动手头的2812进行显示,实验细节在下文不表,仅基于实验结果分析过程。

(us)HL
T ‘0’0.41671.0003
T ‘1’0.83330.5837

将上文反汇编中的while内代码提取出来,我们可以列出如下表格:

指令机器周期所需时间
SETB WS2812_IO(0xB0.3)183.3ns
NOP11x
MOV WS2812_IO(0xB0.3),C183.3ns
NOP21x
CLR WS2812_IO(0xB0.3)183.3ns
MOV A,R7183.3ns
ADD A,ACC(0xE0)183.3ns
MOV R7,A183.3ns
DJNZ R6,C:00992 / 3249.9ns

首先分析0码:0码的高电平时间最短,为0.416us。在MOV WS2812_IO(0xB0.3),C执行后,数据线将被该语句拉低。可以得出算式[katex](1+x)\times 0.083=0.416[/katex],解方程可以得到[katex]x=4[/katex],所以NOP1的数量为4。

1码的高电平时间为0.833us。在 CLR WS2812_IO(0xB0.3) 执行后,数据线将被该语句拉低。可以得出算式[katex](6+x)\times 0.083=0.833[/katex],解方程可以得到[katex]x=4[/katex],所以NOP2的数量也为4。

由上面的步骤,我们可以得出下表。

指令机器周期所需时间
SETB WS2812_IO(0xB0.3)183.3ns
NOP11 * 4 = 4333.2ns
MOV WS2812_IO(0xB0.3),C183.3ns
NOP21 * 4 = 4333.2ns
CLR WS2812_IO(0xB0.3)183.3ns
MOV A,R7183.3ns
ADD A,ACC(0xE0)183.3ns
MOV R7,A183.3ns
DJNZ R6,C:00992 / 3249.9ns

至此,该程序已经可以正常使用了,但实际情况下可能还需要分析下来的低电平时长,在此做简略叙述。(总时长由以下因素决定)

0码时,在MOV WS2812_IO(0xB0.3),C执行后,数据线将被该语句拉低。之后的语句一共需要12个机器周期执行后才会将数据线拉高,[katex]12\times 0.083 = 0.9996\mu s[/katex] ;[katex]0.9996+0.4167=1.4163\approx 1.417\mu s[/katex]。可能有时需要对这里进行调整。1码以此类推。

下图是逻辑分析仪抓到的数据传输过程。

0x00
0xff

12/18:闲着没事试了一下,基于STC8H8K64S4U平台的nop数量,Y6应该可以直接拿走用。

频率范围(MHz)nop个数
5.5296 – 6.81
6.8 – 10.82
10.8 – 21.44
21.4 – 29.56
29.5 – 35.18
35.1 – 40.710
40.7 – 46.812
46.8 – 4814
Y6指令集下不同频率可用NOP个数
2020/8/8