环境: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 bit、MOV bit,C、CLR bit、MOV A,Rn、ADD A,#data、MOV 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) | H | L |
| T ‘0’ | 0.4167 | 1.0003 |
| T ‘1’ | 0.8333 | 0.5837 |
将上文反汇编中的while内代码提取出来,我们可以列出如下表格:
| 指令 | 机器周期 | 所需时间 |
| SETB WS2812_IO(0xB0.3) | 1 | 83.3ns |
| NOP1 | 1 | x |
| MOV WS2812_IO(0xB0.3),C | 1 | 83.3ns |
| NOP2 | 1 | x |
| CLR WS2812_IO(0xB0.3) | 1 | 83.3ns |
| MOV A,R7 | 1 | 83.3ns |
| ADD A,ACC(0xE0) | 1 | 83.3ns |
| MOV R7,A | 1 | 83.3ns |
| DJNZ R6,C:0099 | 2 / 3 | 249.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) | 1 | 83.3ns |
| NOP1 | 1 * 4 = 4 | 333.2ns |
| MOV WS2812_IO(0xB0.3),C | 1 | 83.3ns |
| NOP2 | 1 * 4 = 4 | 333.2ns |
| CLR WS2812_IO(0xB0.3) | 1 | 83.3ns |
| MOV A,R7 | 1 | 83.3ns |
| ADD A,ACC(0xE0) | 1 | 83.3ns |
| MOV R7,A | 1 | 83.3ns |
| DJNZ R6,C:0099 | 2 / 3 | 249.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码以此类推。
下图是逻辑分析仪抓到的数据传输过程。


12/18:闲着没事试了一下,基于STC8H8K64S4U平台的nop数量,Y6应该可以直接拿走用。
| 频率范围(MHz) | nop个数 |
| 5.5296 – 6.8 | 1 |
| 6.8 – 10.8 | 2 |
| 10.8 – 21.4 | 4 |
| 21.4 – 29.5 | 6 |
| 29.5 – 35.1 | 8 |
| 35.1 – 40.7 | 10 |
| 40.7 – 46.8 | 12 |
| 46.8 – 48 | 14 |

西巴西巴?阿尼亚谁哟
啊这