声明

本文版权归作者bxgj所有,未经作者授权,本文禁止以任何形式在任何平台(包括但不限于各网站、论坛、博客、微博、公众号等)部分或全部地转载,禁止二次修改后声明原创。授权转载内容请注明出处(如作者:xxx,转载自xxx),并标明本站网址。文中程序仅供学习使用,本人不承担任何由使用文中代码产生的法律责任。


ws2812相信有不少人都用过,大家对这款彩色LED真的是又爱又恨,爱的是它它使用简单,采用单总线通信方式,节约IO口,而且可以多级串联。而普通的彩色LED不是共阴就是共阳,每个颜色一个引脚,一般都是用PWM驱动,想要控制亮度、颜色就要分别控制每个引脚上的PWM占空比,想要驱动多个LED就更麻烦了。恨的是ws2812对时序的要求比较高,对低速单片机不太友好。今天我们就详细谈一谈ws2812的驱动。

不想看分析过程的直接跳到最后看总结。

拿到一款芯片,第一件事就是找datasheet,找datasheet很简单,找一份靠谱的datasheet有时很困难,特别是国内一些翻译后datasheet往往有错误,我被坑过很多次。说真的我也不想看英文手册,但是没办法,除非有官方中文版,还是要尽量看原版英文手册。就这款芯片来说,我找到了多个版本,不管是国外,国内的,官网上的,还是某些文库中的,关于时序的定义竟然完全没有一样的!!!!这里直接把时序部分摘出来给大家看一下。

第一种

第二种

第三种

第四种

第五种

第六种

看完这些datasheet我完全不知道该相信哪个,总的来说,第一张图片中的参数似乎比较平均,所以我就以它为基准来编写驱动。

在和大家分享我的驱动程序之前,先来看看别人的驱动程序。目前在网上能找到的驱动大多是STM32、STM8、Arduino的驱动,很少有STC单片机的驱动。
STM32的驱动方式目前我见到的有三种,第一种直接控制IO口,并精确调整延时,这个没什么好说的,略过;第二种将SPI的时钟调整为8MHz,发送一字节正好是1.25us,给ws2812发送0即通过SPI总线发送11000000b,发送1即通过SPI总线发送11111100b,非常巧妙的一种方式;第三种方式使用PWM,周期设置为3MHz,发送0就把占空比设置为33%,发送1就把占空比设置为66%,也是一种不错的方式。关于Arduino我不想说了,网上代码太多了,核心部分都是用汇编写的。STC的驱动也有一部分,但我真的不敢恭维,有的就是无脑堆_nop_();,有的要求时钟必须为24MHz,总之能用就行,没有一篇文章会详细分析时序,我真的无力吐槽。

所成我给我定个小目标:写一个简单好用的STC单片机ws2812驱动。

我们常用的主频一般是12MHz或11.0592MHz,我的驱动要争取能在这个主频下工作,况且不是所有的场合都要用24MHz这么高的频率,比如低功耗的场合,如果单片机中的某些代码精确依赖时钟,改变主频对整个代码的改动也比较大,比如定时器、串口的波特率都需要改。如果你没什么追求,只要能用就行,那么可以略过中间的分析过程直接跳到最后看结论。最后说一句,杠精自重!

先看看我们能不能借鉴别人的驱动方案,STC15的SPI速率最高为SYSCLK/4,也就是说主频要达到32MHz才可以,软件中最高只能设置到30MHz,何况这个主频超过我的目标,SPI方案PASS。PWM方案应该有可行性,但是8DIP封装的单片机没有PWM接口,也没有SPI接口,这个方案我也考虑。那么最后就只有控制IO口这一个方案了。

根据这个思路,我们会编写类似以下代码(这部分代码只做演示,杠精自重)

void ws2812_write_byte(u8 dat)
{u8 i = 0;for(i = 0; i < 8; i++){if((dat & 0x80) == 0x80){WS2812_IO = 1;delay_us(0.7);WS2812_IO = 0;delay_us(0.6);}else{WS2812_IO = 1;delay_us(0.35);WS2812_IO = 0;delay_us(0.8);}dat = dat << 1;}
}

显然,这个代码在STC这样的低速单片机上是不能正常工作的,毕竟执行任何语句都需要时间。当然在STM32等高速单片机上这种代码是有可能正常工作的。

当然这个代码有优化空间,我们可以将一些相同的操作提取出来,比如设置IO口还有延时,于是我们可以得到这样的代码

void ws2812_write_byte(u8 dat)
{u8 i = 0;for(i = 0; i < 8; i++){WS2812_IO = 1;delay_us(0.3);if((dat & 0x80) == 0x80){delay_us(0.4);WS2812_IO = 0;delay_us(0.6);}else{WS2812_IO = 0;delay_us(0.8);}dat = dat << 1;}
}

比前一个代码稍微好一些,但还是不实用

听说while循环比for循环效率高,使用自减计数比自加计数效率高,那我们就再改一版(关于循环下文有详细分析,杠精自重!)

void ws2812_write_byte(u8 dat)
{u8 i = 8;while(i--){WS2812_IO = 1;delay_us(0.3);if((dat & 0x80) == 0x80){delay_us(0.4);WS2812_IO = 0;delay_us(0.6);}else{WS2812_IO = 0;delay_us(0.8);}dat = dat << 1;}
}

我们知道51单片机有一种数据类型是其他架构所不一定具备的,那就是布尔型(sbit,或称为位),直接对位进行操作要比使用与逻辑判断更快,零点几微秒的延时真的太短了,如果我们把delay_us函数直接展开成NOP指令还能节省函数调用的时间开销,于是我们得到一个比较复杂,但性能大幅提升的新代码

// 需根据实际情况适当调整NOP语句数量
#define SEND_BIT0 {WS2812_IO=1;_nop_();_nop_();_nop_();WS2812_IO=0;_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();}
#define SEND_BIT1 {WS2812_IO=1;_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();WS2812_IO=0;_nop_();_nop_();_nop_();}u8 bdata LED_DATA;
sbit DAT_bit0 = LED_DATA^0;
sbit DAT_bit1 = LED_DATA^1;
sbit DAT_bit2 = LED_DATA^2;
sbit DAT_bit3 = LED_DATA^3;
sbit DAT_bit4 = LED_DATA^4;
sbit DAT_bit5 = LED_DATA^5;
sbit DAT_bit6 = LED_DATA^6;
sbit DAT_bit7 = LED_DATA^7;void ws2812_write_byte(u8 dat)
{LED_DATA = dat;if(DAT_bit0){SEND_BIT1}else{SEND_BIT0};if(DAT_bit1){SEND_BIT1}else{SEND_BIT0};if(DAT_bit2){SEND_BIT1}else{SEND_BIT0};if(DAT_bit3){SEND_BIT1}else{SEND_BIT0};if(DAT_bit4){SEND_BIT1}else{SEND_BIT0};if(DAT_bit5){SEND_BIT1}else{SEND_BIT0};if(DAT_bit6){SEND_BIT1}else{SEND_BIT0};if(DAT_bit7){SEND_BIT1}else{SEND_BIT0};
}

上面这个代码就比较实用了,但是我实际测试中发现,这个代码在24MHz下可以用,但12MHz下是不能用的(因为分支跳转指令比较耗时,12MHz时分支跳转占用的时间不能满足ws2812时序的要求),走到这一步如果还想继续优化就必须深入分析汇编代码。

本文所用的单片机型号为STC15W4K32S4,所用指令集为STC-Y5指令集,仅适用于STC15Fxx、STC15Lxx、STC15Wxx系列,不包括STC15F104E(A版)、STC15L104E(A版)、STC15F204EA(A版)、STC15L204EA(A版)。

STC-Y5以前的指令集很多指令的执行时间要大于STC-Y5的指令集;最新的STC8Fxx及STC8Axx系列采用的STC-Y6指令集大幅度优化,绝大多数指令被优化为单周期指令,执行速度远远快于STC-Y5。由于ws2812对时序的要求较高,本文中的代码高度依赖于这些指令的执行时间,因此文中代码 完全不保证 在其他单片机上能正常驱动ws2812。

列举并对比程序中用到的几条指令在不同版本指令集上的执行时间

指令集 STC-Y1 STC-Y3 STC-Y5 STC-Y6
代表型号 STC89C52 STC12C5A60S2 STC15W4K32S4 STC8A8K64S4A12
MOV A, Rn
(寄存器送入累加器)
12 1 1 1
MOV Rn, A
(累加器送入寄存器)
12 2 1 1
MOV Rn, #data
(立即数送入寄存器)
12 2 2 1
MOV bit, C
(进位位送入直接地址位)
24 4 3 1
RL A
(累加器循环左移)
12 1 1 1
RLC A
(累加器带进位循环左移)
12 1 1 1
CLR A
(清零累加器)
12 1 1 1
CLR C
(清零进位位)
12 1 1 1
CLR bit
(清零直接地址位)
12 4 3 1
SETB bit
(置一直接地址位)
12 4 3 1
INC Rn
(寄存器加一)
12 3 2 1
DEC Rn
(寄存器减一)
12 3 2 1
ADD A, direct
(直接地址单元中的数据加到累加器)
12 3 2 1
SUBB A, #data
(累加器带借位键立即数)
12 2 2 1
JZ rel
(累加器为零转移)
24 3 4 条件不成立不转移1时钟;条件成立则转移3时钟
JNC rel
(进位为零转移)
24 3 3 条件不成立不转移1时钟;条件成立则转移3时钟
CJNE Rn, #data, rel
(寄存器与立即数相比较,不相等则转移)
24 4 4 条件不成立不转移2时钟;条件成立则转移3时钟
SJMP rel
(相对转移)
24 3 3 3
DJNZ Rn, rel
(寄存器减1,非零转移)
24 4 4 条件不成立不转移2时钟;条件成立则转移3时钟
LCALL addr16
(长调用子程序)
24 6 4 3
RET
(子程序返回)
24 4 4 3
NOP
(空指令)
12 1 1 1

先来分析时序,在ws2812的datasheet中可以看到TH+HL=1.25us,假设主频为12MHz,1.25us即0.00000125/(1/12M)=15个周期,也就是说我们要在15个周期内执行一定数量的指令来完成IO置高、IO置低、数据移位、跳转等所有必要的操作!

略去漫长的调试过程,最后我写出了最精简的汇编代码

void ws2812asm(unsigned char dat)
{
#pragma asmMOV A, R7MOV R6, #0x08
WS2812LOOP:SETB P3.7RLC AMOV P3.7, CNOPCLR P3.7DJNZ R6, WS2812LOOP
#pragma endasm
}

来分析一下这个代码,R7即函数调用时传入的dat,将dat放入累加器A中,然后将8放入R6中作为循环计数,这里我用的是P3.7引脚,用SETB语句将它置为高电平,然后用RLC指令将dat左移一位,最高位进入进位位C,使用MOV语句将进位位的值赋给P3.7引脚,NOP延时,之后将P3.7引脚置为低电平,DJNZ将计数值减一不为零则跳转到WS2812LOOP继续执行。整个代码的循环体耗时为3+1+3+1+3+4=15个CLK,完美!

我们从WS2812LOOP处分析一下时序,假设调用函数前发送了复位信号,此时IO状态为低电平

CLK 时间/us 语句 IO状态 说明
1 0.083 SETB P3.7 0
2 0.166 SETB P3.7 0
3 0.250 SETB P3.7 0
4 0.333 RLC A 1 SETB语句执行完毕,IO被置高
5 0.417 MOV P3.7, C 1
6 0.500 MOV P3.7, C 1
7 0.583 MOV P3.7, C 1
8 0.667 NOP 0/1 这里IO的状态为0或1
9 0.750 CLR P3.7 0/1 如果发送的数据为0即为0
10 0.833 CLR P3.7 0/1 否则为1
11 0.916 CLR P3.7 0/1
12 1.000 DJNZ R6, WS2812LOOP 0
13 1.083 DJNZ R6, WS2812LOOP 0
14 1.166 DJNZ R6, WS2812LOOP 0
15 1.250 DJNZ R6, WS2812LOOP 0

如果发送的数据为0,则高电平为0.33us,低电平为0.92us

如果发送的数据为1,则高电平为0.67us,低电平为0.58us

这个时序基本符合datasheet中的要求,误差也在±150us以内。

简单验证一下,试试看看究竟能不能驱动ws2812

void main()
{WS2812_IO = 1;Delay100us();WS2812_IO = 0;Delay100us();ws2812asm(100);ws2812asm(0);ws2812asm(0);while(1){}
}

事实证明这个代码完全可以正常驱动ws2812!

但是,等等,好像有哪里不对,我们这里只考虑循环体的执行时间,循环体外有两个MOV语句,函数调用前会有一个MOV语句将参数传入寄存器R7,之后LCALL语句调用函数,调用完之后还有RET语句返回,这样一来整个函数调用的总时间为3+4+1+2+14+4=28个CLK,约2.4us,超过了手册中要求的1.25us,即使算上600ns的误差也还是超时了,但是ws2812却被正常点亮了!也就是说即使时序不完全符合datasheet中的要求也是可以工作的!这是非常重要的一点!最后经过我的反复实验得出一个结论:小于45us的高电平为判定为逻辑0,大于45us的高电平被判定为逻辑1,低电平的时长只要不要超过复位信号的时长都可以完成数据的传输!

有了这个结论,我们的编程工作就会轻松许多,只要我们将循环的跳转、赋值、计算灯珠颜色等耗时的操作放在低电平时就行了。

作为一个有最求的人,我觉不满足于此,汇编代码比C代码要难一些,阅读也有点费劲,那么能不能写出符合时序的C代码呢?来尝试一下。

置位、清位、左移这三个操作时必须的,完全没有优化空间,那么只剩一个循环可以优化。前文说到要详细研究一下各种循环语句,一般处理器中都有为零跳转指令、减一为零跳转等类似指令,使用自减计数只需要一条指令即可完成循环的跳转,若果使用自加计数,一般会被编译为自加、判断、跳转三条指令,因此一般来说while循环配合自减计数是效率最高的。但是,对于GNU等编译器,它们会充分利用目标平台的指令的特点进行优化,甚至将for自加的循环优化为while自减的循环,另外对于PC或其他高性能的平台,一个循环上损失的效率可以忽略不计,编程时不用在这种细节上纠结。杠精自重!

我这里写了6种循环,编译出来逐一进行分析(这里统一采用无符号数,我知道还有有符号数、浮点数等计数方式,>=、<=、==等判断条件的方式,精力有限,有兴趣的自己研究,杠精自重!)

void test1()
{u8 i = 8;while(i){_nop_();i--;}
}C:0x19BE    MOV     R7,#0x08
C:0x19C0    NOP
C:0x19C1    DJNZ    R7,C:19C0
C:0x19C3    RET

汇编代码非常简单,是所有循环中效率最高的,只用一条DJNZ语句就完成了减一、判断和跳转,除去NOP语句,整个循环体耗时为4个CLK。

void test2()
{u8 i = 8;while(i){i--;_nop_();}
}C:0x199C    MOV     R7,#0x08
C:0x199E    MOV     A,R7
C:0x199F    JZ      C:19A5
C:0x19A1    DEC     R7
C:0x19A2    NOP
C:0x19A3    SJMP    C:199E
C:0x19A5    RET

和test1()函数的区别在于语句的顺序不同,自减、判断和跳转被拆分成三条语句,除去NOP语句,整个循环体耗时1+4+2+3=10个CLK

void test3()
{u8 i = 0;while(i < 8){_nop_();i++;}
}C:0x1993    CLR     A
C:0x19B0    MOV     R7,A
C:0x19B1    NOP
C:0x19B2    INC     R7
C:0x19B3    CJNE    R7,#0x08,C:19B1
C:0x19B6    RET

与前两个函数不同,这里采用自加计数循环,除去NOP语句,循环体耗时2+4=6个CLK

void test4()
{u8 i = 0;while(i < 8){i++;_nop_();}
}C:0x008E    CLR     A
C:0x008F    MOV     R7,A
C:0x0090    MOV     A,R7
C:0x0091    CLR     C
C:0x0092    SUBB    A,#0x08
C:0x0094    JNC     C:009A
C:0x0096    INC     R7
C:0x0097    NOP
C:0x0098    SJMP    C:0090
C:0x009A    RET

同样是自加计数循环,这是所有循环中效率最低的一个,除去NOP语句,整个循环体用了6条语句,耗时1+1+2+3+2+3=12个CLK

void test5()
{u8 i = 8;for(i; i != 0; i--){_nop_();}
}C:0x0080    MOV      R5,#0x08
C:0x0082    NOP
C:0x0088    DJNZ     R5,C:0082
C:0x008A    RET

这个编译出来的结果和test1()一样,略过

void test6()
{u8 i = 0;for(i; i < 8; i++){_nop_();}
}C:0x1965    CLR     A
C:0x1966    MOV     R5,A
C:0x1967    NOP
C:0x196D    INC     R5
C:0x196E    CJNE    R5,#0x08,C:1967
C:0x1971    RET

这个编译出来的结果和test3()一样,略过

所以我们可以得出结论,采用while自减循环的方式效率最高,调试的过程略过,最后我们得到如下代码

void ws2812_write_byte(u8 dat)
{u8 i = 8;dat <<= 1;while(i){WS2812_IO = 1;WS2812_IO = CY;WS2812_IO = 0;dat <<= 1;i--;}
}

来看一下上面的C代码编译后对应的汇编代码,比我写的汇编代码效率稍微低一点,但是完全可以正常工作。我不是针对某些编译器(GNU编译的代码真的让我感叹写出这种编译器的人真牛B),在这里我只说C51这个编译器,它编译出的代码不一定比自己写的更好,我知道设置里面可以选择优化体积或优化速度,我这里采用默认选择第8级优化,Reuse Common Entry Code,优化速度Fever Speed(杠精自重)。

C:0x1AB3    MOV     R6,#0x08
C:0x1AB5    MOV     A,R7
C:0x1AB6    ADD     A,ACC(0xE0)
C:0x1AB8    MOV     R7,A
C:0x1AB9    SETB    P37(0xB0.7)
C:0x1ABB    MOV     P37(0xB0.7),C
C:0x1ABD    CLR     P37(0xB0.7)
C:0x1ABF    MOV     A,R7
C:0x1AC0    ADD     A,ACC(0xE0)
C:0x1AC2    MOV     R7,A
C:0x1AC3    DJNZ    R6,C:1AB9
C:0x1AC5    RET

C语言中的左移、右移依照被操作数是有符号数或无符号数被编译为算数左移、右移或逻辑左移右移指令,C51中的RL、RLC、RR、RRC这4条指令都是循环左移、右移,没有算数左移、右移和逻辑左移右移指令,因此在C51中被编译为MOV A,R7;ADD A,ACC(0xE0);MOV R7,A 这3条指令,执行的时间更长了。ADD A,ACC(0xE0)这句可能不太好理解,单独讲一下,这句是将直接地址单元中的数据加到累加器,这里的直接地址单元就是ACC,它的地址是0xE0,查阅手册我们发现0xE0这个地址就是累加器A本身,也就是说将累加器自己的内容加上自己,也就是相当于乘2,也就是左移一位。

最终我们得出了驱动ws2812最精简的C语言代码,可以正常工作于12MHz或11.0592MHz,其他主频请根据实际情况增减NOP语句的数量。

void ws2812_write_byte(u8 dat)
{u8 i = 8;dat <<= 1;while(i){WS2812_IO = 1;// 如果主频较高可在此处适当增加_nop_():// 将下面的dat <<= 1;移至此处也可以WS2812_IO = CY;WS2812_IO = 0;dat <<= 1;i--;}
}

再次强调,本代码仅用于STC-Y5指令集的单片机,包括STC15Fxx、STC15Lxx、STC15Wxx系列,不包括STC15F104E(A版)、STC15L104E(A版)、STC15F204EA(A版)、STC15L204EA(A版)。由于ws2812对时序的要求较高,本代码高度依赖这些指令的执行时间,对于其他STC单片机或其他架构单片机完全不保证 能正常驱动ws2812,请根据指令的执行周期、MCU主频自行修改!

结论

  1. 复位信号为50us以上的低电平,复位信号不会熄灭已经点亮的灯珠(假设已经发送了5个红色数据,此时复位,然后又发送2个蓝色数据,那么灯珠点亮的状态为蓝蓝红红红,而不是蓝蓝灭灭灭)
  2. 小于0.45us的高电平为逻辑0,大于0.45us的高电平为逻辑1,低电平的时长不要超过复位信号的时长
  3. 每位或每字节或每3字节传输完成后建议保持低电平,如果保持高电平且持续时间大于0.45us就会被认为逻辑1,假设下一个传输的数据是0就会出错,因此只建议在完成所有传输后保持高电平
  4. 因为每位数据传输完成后是低电平,因此发送下一位数据的时间间隔不要超过50us,否则会被判定为复位信号,因此可以充分利用50us以内的低电平的这段时间做一些比较费时的操作,如读取数据、计算下一个灯珠的颜色等
  5. 只有收到完整的24位数据才会点亮一颗灯珠,之后的数据被传送到下一颗灯珠,任意时刻都可以发送复位信号,未传输完成的数据会被丢弃

以上结论仅代表个人观点共大家参考,并不等同于官方说面,虽说是参考,但也非常有意义,其中部分细节datasheet中并未说明,以让大家少走弯路。市场上有ws2812的兼容灯珠,个人精力有限,对于这些兼容灯珠本人不保证此结论的正确性。

ws2812驱动总结(包括对时序的详细分析,代码基于STC15系列单片机)相关推荐

  1. UC3842/UC3843 PWM时序图详细分析

    下面是UC3842/UC3843的内部框图和时序图,并对时序图进行补充分析: R,S触发器的真值表: S R /Q 0 0 保持 1 0 0 0 1 1 1 1 逻辑错误 R连接到电流采样比较器的输出 ...

  2. linux ubuntu基础,linux基础入门详细分析(基于ubuntu)

    不同应用领域的主流操作系统: 桌面(客户端)操作系统 window系列(用户群体大) masOS(适合于开发人员) Linux(应用软件少) 服务器类操作系统 Linux(安全稳定免费,占有率高) W ...

  3. uboot启动流程详细分析(基于i.m6ull)

    uboot介绍 uboot就是一段引导程序,在加载系统内核之前,完成硬件初始化,内存映射,为后续内核的引导提供一个良好的环境.uboot是bootloader的一种,全称为universal boot ...

  4. 2021中青杯数学建模A题思路详细分析 代码?

    A题:汽车组装车间流水线物料配送问题 汽车制造有四大关键工艺--冲压.焊接.涂装和总装,其中总装车间的占地面积最大.工人数量最多,迫切需要数学建模技术帮助降本增效.总装是指将发动机等全部内外饰件装配到 ...

  5. 表达式求值(最详细分析+代码实现+表达式之间的相互转换)

    目录 一.概念 二.前缀表达式的逻辑和实现方式 1.定义 2.前缀表达式的计算机求值 3.例子 4.代码实现 三.中缀表达式的逻辑和实现方式 1.定义 2.中缀表达式规则 3.中缀表达式的计算机求值 ...

  6. ESP8266-Arduino编程实例-WS2812驱动

    WS2812驱动 1.WS2812介绍 WS2812系列是控制电路和RGB芯片集成在5050个元件封装内的智能控制LED光源.它内部包括智能数字端口数据锁存器和信号整形放大驱动电路.有效保证像素点光色 ...

  7. WS2812驱动 SPI+DMA 无需降频 一个灯占用9Byte的RAM

    WS2812驱动 STM32F103 SPI+DMA 无需降频 一个灯占用9Byte的RAM 对于WS2812不了解的朋友可以先看一下这些 ​[STM32]WS2812介绍.使用SPI+DMA发送数据 ...

  8. Dataset之MNIST:MNIST(手写数字图片识别+ubyte.gz文件)数据集简介、下载、使用方法(包括数据增强)之详细攻略

    Dataset之MNIST:MNIST(手写数字图片识别+ubyte.gz文件)数据集简介+数据增强(将已有MNIST数据集通过移动像素上下左右的方法来扩大数据集为初始数据集的5倍) 目录 MNIST ...

  9. linux注册函数机制,Linux可信计算机制模块详细分析之函数实现机制(1)字符设备驱动...

    原标题:Linux可信计算机制模块详细分析之函数实现机制(1)字符设备驱动 2.3 函数实现机制 2.3.1 Linux 字符设备驱动 在linux 3.5.4中,用结构体cdev描述字符设备,cde ...

最新文章

  1. vim中直接打开ipython的方法
  2. 30万奖金等你拿 | “信也科技杯”第五届数据解决方案应用大赛火热报名中!...
  3. 高斯过程回归python_基于python的高斯过程回归训练集数据扩充
  4. 将一个正方形分成4个大小一样的小正方形,再将其中一个小正方形分成4个小正方形,如此类推,分割n次是几个正方形?
  5. 操作系统面试题(二)
  6. 特斯拉model3中控屏怎么关_对话特斯拉首席设计师Franz:设计为效率服务
  7. 【机器人学】当前工业机器人应用中的机械结构设计方法分析
  8. android 8.1.0编译以及卡刷包制作教程
  9. MATLAB Codesys,Matlab程序导入Codesys PLC教程
  10. 数学分析高等代数考研试题不断更新
  11. 牛客编程巅峰赛S1第12场 王者B-上上下下(DP)
  12. YIT-CTF—社工类
  13. java源程序文件的扩展名_使用Java语言编写的源程序保存时的文件扩展名是什么...
  14. QCC3040---DFU(OTA)固件生成方法OTA方法
  15. swiper3D效果
  16. JAVA面向对象编程程序设计——中国象棋
  17. 基于微博评论的文本情感分析与关键词提取的实战案例~
  18. Java 将表格数据导入word文档中
  19. 爱豆教育:掌握这7大技巧,让你的亚马逊店铺快速出单
  20. 天边一朵云-最终章动画化决定,看云卷云舒

热门文章

  1. java基础测试大集合 今天收罗精选一下Java题 适合小白挑战和新手回顾
  2. python 个例程序
  3. python人物代码_Python设计王者荣耀人物结构(64)*
  4. 机器学习算法:kNN和Weighted kNN
  5. 445端口的用处和禁用
  6. Soot -- Soot中的一些语句细节
  7. 什么是内存泄露?该怎么排查?Java内存泄漏策略
  8. Linux tar(打包,解打包)命令
  9. OS模块--批量修改文件名字(一)创建和修改文件路径
  10. Push failed: Unable to access ‘https://github.com........