为什么先全彩灯,因为全彩灯简单
【数据手册要看自己买的那个,不同的地方买的ws2812b,里面需要控制的时间可能不同,时间不准,可能会颜色不对。】
先确定下灯要怎么用。
这里看到每个灯需要24个数据使它知道要显示什么颜色;注意是GRB的顺序而不是RGB。
灯内部是有逻辑处理的,比如我们用了4个灯,发送了1234这四个24位数据,在经过1灯后,一灯用1这24个数据,然后把1删掉,把234这三个数据传给下一个灯。
The data transfer protocol use single NZR communication mode. After the pixel power-on reset, the DINport receivedata from controller, the first pixel collect initial 24bit data then sent to the internal data latch, the other datawhichreshaping by the internal signal reshaping amplification circuit sent to the next cascade pixel through the DOport. Aftertransmission for each pixel, the signal to reduce 24bit. pixel adopt auto reshaping transmit technology, makingthepixelcascade number is not limited the signal transmission, only depend on the speed of signal transmission.
以此类推,把数据给到所有灯。
而给灯的数据,也不是简单的0和1,要用到这里的0码和1码。
0码:先给220-380ns的高电平,再给580-1000ns的低电平。
1码:先给580-1000ns的高电平,再给580-1000ns的低电平。
RESET:就一直低电平就行,300us的低电平。
上面这手册是我再立创商城随便看了一个,
有的手册是这样的,立创商城上面几个都没有写TH+TL=1.25us,但是很多其他地方找的数据手册都写了这个,所以尽量取=1.25us吧,比如0码300+900,1码600+600ns。
注意事项
我复制粘贴的,原文在这
以上时间差距会让颜色不准,在使用的时候要注意。
除了手册差异(数据差异)导致的问题,还有不同写法io反转速度差异引起的时间不准。
1.用库函数
GPIO_SetBits(GPIOB,GPIO_Pin_5);
GPIO_ResetBits(GPIOB,GPIO_Pin_5);
这样写的反转频率大约为2.7MHz,周期361ns
2.用寄存器GPIOx_ODR
GPIOB->ODR |= GPIO_Pin_5;
GPIOB->ODR &=~ GPIO_Pin_5;
这样写的反转频率小于18MHz,周期大于55.55ns
3、用寄存器 GPIOB->BRR和GPIOB->BSRR
GPIOB->BRR = GPIO_Pin_5;
GPIOB->BSRR = GPIO_Pin_5;
这样写的反转频率大约为18MHz,周期55.55ns
总之就用BSRR寄存器了,直接操作ODR不太好(会被中断打断),库函数太慢。
代码
我用的HAL库,所以操作的是BSRR,而不是BSRRL与BSRRH,看到这个自己改一下就行。
从上面确定了0码300+900,1码600+600ns的高低电平时间,那么要怎么给出这个时间呢,就用到__nop()函数。
__nop()是一个空指令,就什么都不干,占用一个指令周期
这里用的是72MHZ的时钟,一个周期就是1/72MHZ=0.01389us。
那使用BSRR寄存器又用多长时间,我不太清楚,所以可以DEUG看一下(不看也行,不大碍事)(DBUG需要SWD或者JTAG,没有的话看看就行了,反正我的和你们的应该一样,只要都是这个72MHZ)
因为__nop时间比较准确,所以用这个,如果你想用其他的方法,比如循环,来控制时间,也可以,不过这就需要在debug里面多调试几次试出来需要的时间了。
查看延时时间(这个是不太准的,还是用上面计算的时间比较好)
先在这里设置一下,这个地方设置的多少,后面计算时间就按这里设置的显示。
然后去写一下代码(如果不清楚代码意思的话,去看一下GPIO寄存器里面的BSRR部分)
#include "led.h"
/*
这里是单纯把设置PB9高低电平写成了宏定义,上面置一就是让BSRR寄存器第九个变高,置0就是让标准库对应的BSRRH的第九位变高。
*/
#define SET1 GPIOB->BSRR |=1<<9;
#define SET0 GPIOB->BSRR |=1<<9<<16;
void Write0()
{
SET1;
__nop();
SET0;
}
SWD连接好电路板。文件build一下。
然后再这里打好断点,进DEBUG看看,再主函数调用一下。
然后一步一步的运行,查看命令占用的时间。
看完了,停止debug就行了
(我把时间写明白点,好看 0.000 000 220s)
能看到时间分别变成了12ns,14ns,22ns,也就是说SET1用了120ns,__nop()用了20ns,SET0用了80ns。
然后再中间加入更多__nop()让时间达到置1 300ns,置0 900ns就行了
那么SET1后面要180ns就是9个__nop()
SET0后面要820ns,就是41个__nop()
0码1码的实现
我试了,这么测的__nop()时间是不准的,准的是之前计算得到的,也就是1/72MHZ=0.01389us。按14ns算就好
0码
那么SET1后面要加180/14≈13个__nop()
SET0后面820/14≈58个__nop()
void Write0()
{
SET1;
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();
SET0;
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();
}
1码
1码的置0和置1时间都是600ns,
SET1后面跟420/14=30个__nop();,SET0后面跟520/14≈37个__nop()
void Write1()
{
SET1;
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
SET0;
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();
}
reset码
置0一共300us(有的只要50us)
300us就很长了,如果写__nop,要21428个以上,要累死,所以用一个循环吧,反正也不用精确。
如果你是用标准库,标准库是有微秒级的延时函数的,delay_us,你可以用这个。HAL库没有,HAL库只有ms级的延时,如果要自己写,还要用定时器,很麻烦。
上面的问题
聪明的小伙伴就看出来了,上面我说的不大对劲,我写着写着就反应过来了。
比如WRITE0的SET1的时候,我把还没置0的那120ns也算进去了,所以我实际置0的时间是__nop的时间+SET1没结束的时间,也就是14*13+80=262ns。实际上还少一点。所以如果在你那里不对劲的话,可以加几个__nop让时间对的上。
怎么在主函数调用
我脑子长泡了我写这玩意,能看这个的谁不会
首先在主函数引用led.h文件
然后led.h文件里面要定义一下在.c文件里面写的函数。
然后再主函数调用,注意下放在GPIO_INIT后面,如果放前面,GPIO还没初始化了,没用。
向灯写入8位数据
为什么是8位,因为一个灯要24位数据,也就是8个G,8个R,8个B,先实现一个写入8位的函数,然后在下面调用他,做一个分别写入GRB三个一共24位数据的函数。
想一下之前这个灯的数据是什么样的,
(先写入的是高位,也就是写入G7,G6……B1,B0这个顺序)
我们给函数一个8位的参数data,函数内部要得到参数data每一位是0是1,然后从高位写入数据。
这里我们 一步一步的 从他的高位开始判断当前位是1是0。
那么我们给出一个固定的置0x80(1000 0000)
让data的与这个置相与,如果想与后是1,那么最高位就是1,
然后data左移一位,让次高位变成最高位,再与0x80想与,循环往复就得到了所有的数据。
void RGB_WriteByte(uint8_t data)
{
int i;
for(i=0;i<8;i++)
{
if(((data<<i)&0x80)==0x80) //左移0位判断最高位,左移1位判断次高位……
RGB_Write1();
else
RGB_Write0();
}
}
向灯写入RGB数据
再创建一个函数,有三个参数,red,green,blue,里面调用3此上面的RGB_WriteByte函数,就成功发送了一组RGB数据了
void RGB_Color(uint8_t red,uint8_t green,uint8_t blue)
{
RGB_WriteByte(green);
RGB_WriteByte(red);
RGB_WriteByte(blue);
}
在主函数调用,让WS2812B工作
我在上面写了怎么调用,如果不会可以去看一看,应该都会
在led.h文件里面声明一下,然后去主函数里面#include “led.h”,调用RGB_Color就好了
第一个给第一个灯,第四个给第四个灯
RGB_Color(33,125,155);
RGB_Color(255,0,0);
RGB_Color(0,255,0);
RGB_Color(0,0,255);
结果
别问我为什么有个灯不亮,应该是没焊好,我手残