|
1,WS2812時(shí)序
WS2812是一個(gè)集控制電路與發(fā)光電路于一體的智能外控LED光源,每個(gè)ws2812均含有4個(gè)引腳,引腳功能如下圖:
WS2812可采用級(jí)聯(lián)的方式,將上一個(gè)WS2812的DOUT引腳連接到下一個(gè)WS2812的DIN引腳,即可實(shí)現(xiàn)一個(gè)引腳控制多個(gè)WS2812。一般自己設(shè)計(jì)電路時(shí)還要在電源輸入處添加一個(gè)0.1uF的小電容進(jìn)行濾波。注意,雖然理論上可以連接足夠多的ws2812,但是使用時(shí)要注意驅(qū)動(dòng)電壓和驅(qū)動(dòng)電流是否足夠驅(qū)動(dòng)這些彩燈。
由于只有一個(gè)引腳控制ws2812,所以我們只能通過(guò)控制引腳輸出高、低電平的時(shí)間,來(lái)讓W(xué)S2812知道我們想讓他顯示哪一個(gè)燈,顯示什么顏色。
想要讓1個(gè)ws2812顯示我們想要的顏色,我們需要給它發(fā)送顏色數(shù)據(jù)。每一個(gè)ws2812的顏色數(shù)據(jù)都是24位:8位綠色+8位紅色+8位藍(lán)色。
如果我們想控制1個(gè)ws2812,我們發(fā)送24位顏色數(shù)據(jù);如果我們想控制2個(gè)ws2812,我們需要連續(xù)發(fā)送48位顏色數(shù)據(jù);如果我們想控制n個(gè)ws2812,我們需要連續(xù)發(fā)送n*24位顏色數(shù)據(jù)。
那么ws2812如何知道我們發(fā)送的每一個(gè)位的數(shù)據(jù)是“1”還是“0”呢?
WS2812手冊(cè)中對(duì)于數(shù)據(jù)有明確的時(shí)間定義:
可以看出,當(dāng)我們想要發(fā)送一個(gè)位的數(shù)據(jù)時(shí),如果這個(gè)位是“1”,我們就控制單片機(jī)的引腳輸出高電平0.85us,然后控制引腳輸出低電平0.40us;如果這個(gè)位是“0”,則控制引腳輸出高電平0.40us,然后控制引腳輸出低電平0.85us。
而如果我們想控制1個(gè)燈顯示顏色,我們需要發(fā)送24個(gè)位的顏色數(shù)據(jù)給ws2812。如果我們想控制n個(gè)燈顯示顏色,我們從第1個(gè)燈的顏色數(shù)據(jù)開(kāi)始發(fā)送,直至發(fā)送完n*24個(gè)數(shù)據(jù)。最后我們需要控制單片機(jī)引腳輸出低電平超過(guò)50us,讓彩燈顯示顏色。
2,寫出驅(qū)動(dòng)程序
注意,STC15F104W只有8個(gè)引腳,我們一般采用內(nèi)部晶振電路。下面的代碼都是基于12MHz的晶振頻率寫的。WS2812對(duì)于單片機(jī)的引腳時(shí)序要求比較高。我們一般調(diào)用intrins.h這個(gè)頭文件的機(jī)器周期函數(shù),nop()函數(shù)執(zhí)行一次占用一個(gè)機(jī)器周期(此處時(shí)鐘周期和機(jī)器周期相等),所以每條機(jī)器周期時(shí)間為1/12MHz=83.3us。(代碼中很多處使用宏定義,宏定義在預(yù)編譯階段程序就完成代碼替代工作,不會(huì)影響程序的執(zhí)行時(shí)間,且修改方便)
下面是h文件代碼:
#ifndef __WS2812_H
#define __WS2812_H
//頭文件區(qū)
#include <STC15F2K60S2.H>
#include <intrins.h>
//用戶修改參數(shù)區(qū)
//#define WS2812_FREQUENCY
#define RGB_PIN P33 //控制彩燈引腳(需要配置為強(qiáng)推挽輸出)
#define WS2812_MAX 25 //彩燈最大個(gè)數(shù)
#define WS2812_NUMBERS 8 //彩燈個(gè)數(shù)
#define RED 0xff0000 //紅色
#define GREEN 0x00ff00 //綠色
#define BLUE 0x0000ff //藍(lán)色
#define BLACK 0x000000 //熄滅
#define WHITE 0xffffff //白色
#define RGB_PIN_H() RGB_PIN = 1
#define RGB_PIN_L() RGB_PIN = 0
#define delay1NOP() _nop_();
#define delay1NOP() _nop_();
#define delay2NOP() delay1NOP(); _nop_();
#define delay3NOP() delay2NOP();_nop_();
#define delay5NOP() delay3NOP();delay2NOP();
#define delay7NOP() delay5NOP();delay2NOP();
void Ws2812b_WriteByte(unsigned char byte);//發(fā)送一個(gè)字節(jié)數(shù)據(jù)(@12.000MHz,理論每個(gè)機(jī)器周期83ns,測(cè)試約為76ns)
void setLedCount(unsigned char count);//設(shè)置彩燈數(shù)目,范圍0-25.
unsigned char getLedCount();//彩燈數(shù)目查詢函數(shù)
void rgb_SetColor(unsigned char LedId, unsigned long color);//設(shè)置彩燈顏色
void rgb_SetRGB(unsigned char LedId, unsigned long red, unsigned long green, unsigned long blue);//設(shè)置彩燈顏色
void rgb_SendArray();//發(fā)送彩燈數(shù)據(jù)
#endif
接著我們對(duì)c代碼進(jìn)行詳解。
首先調(diào)用h文件,然后定義一個(gè)數(shù)組,用來(lái)存放彩燈的顏色數(shù)據(jù)。這里數(shù)據(jù)存放在data空間(空間有限,只能存放25個(gè)燈左右的數(shù)據(jù)),后面有需要再優(yōu)化到xdata空間。ledsCount 記錄實(shí)際我們想要控制的彩燈的數(shù)目,nbLedsBytes 用來(lái)記錄彩燈的數(shù)據(jù)個(gè)數(shù)(一個(gè)燈需要3個(gè)字節(jié))。
#include "ws2812.h"
unsigned char LedsArray[WS2812_MAX * 3]; //定義顏色數(shù)據(jù)存儲(chǔ)數(shù)組
unsigned int ledsCount = WS2812_NUMBERS; //定義實(shí)際彩燈默認(rèn)個(gè)數(shù)
unsigned int nbLedsBytes = WS2812_NUMBERS*3; //定義實(shí)際彩燈顏色數(shù)據(jù)個(gè)數(shù)
接著我們開(kāi)始編寫彩燈的設(shè)置數(shù)目函數(shù),查詢數(shù)目函數(shù),以及兩個(gè)設(shè)置指定彩燈顏色的函數(shù)。
//設(shè)置彩燈數(shù)目,范圍0-25.
void setLedCount(unsigned char count)
{
ledsCount = WS2812_MAX > count ? count : WS2812_MAX;
nbLedsBytes = ledsCount*3;
}
//彩燈數(shù)目查詢函數(shù)
unsigned char getLedCount()
{
return ledsCount;
}
//設(shè)置彩燈顏色(在這里我將綠和紅色進(jìn)行顛倒,這樣比較符合我們?nèi)粘I畹募t綠藍(lán)的順序)
void rgb_SetColor(unsigned char LedId, unsigned long color)
{
if( LedId > ledsCount )
{
return; //to avoid overflow
}
LedsArray[LedId * 3] = (color>>8)&0xff;
LedsArray[LedId * 3 + 1] = (color>>16)&0xff;
LedsArray[LedId * 3 + 2] = (color>>0)&0xff;
}
//設(shè)置彩燈顏色
void rgb_SetRGB(unsigned char LedId, unsigned long red, unsigned long green, unsigned long blue)
{
unsigned long Color=red<<16|green<<8|blue;
rgb_SetColor(LedId,Color);
}
然后我們對(duì)彩燈數(shù)據(jù)通過(guò)引腳發(fā)送出去。注意,發(fā)送彩燈數(shù)據(jù)的過(guò)程中,請(qǐng)將中斷關(guān)閉,否則有可能會(huì)導(dǎo)致數(shù)據(jù)發(fā)送到一半被中斷打斷,導(dǎo)致顯示異常。Ws2812b_WriteByte()函數(shù)是這個(gè)驅(qū)動(dòng)代碼的核心。
//發(fā)送彩燈數(shù)據(jù)
void rgb_SendArray()
{
unsigned int i;
bit a=EA;
//發(fā)送數(shù)據(jù)
EA=0;
for(i=0; i<nbLedsBytes; i++)
Ws2812b_WriteByte(LedsArray[ i]);
EA=a;
}
下面的Ws2812b_WriteByte函數(shù)是我基于stc15f104w調(diào)試的。我寫過(guò)不同單片機(jī)的驅(qū)動(dòng)。不同單片機(jī)的每條指令花費(fèi)的時(shí)間都不一樣。如果您想要采用其他系列的單片機(jī),我們只需要修改Ws2812b_WriteByte()函數(shù)里面的內(nèi)容即可。其他的函數(shù)可以不做修改。方便代碼的移植。
主要修改的地方是高電平總的拉高時(shí)間。這里之所以不對(duì)“1”和“0”的內(nèi)容進(jìn)行函數(shù)封裝,是為了我自己調(diào)試的方便。如果您覺(jué)得代碼不夠簡(jiǎn)短好看,您可以將判斷條件里面的內(nèi)容用宏定義進(jìn)行一下封裝。
/*
//使用12.000MHz頻率,理論每個(gè)機(jī)器周期83ns,實(shí)際測(cè)試約為76ns。
//下面都是基于76ns納秒一個(gè)機(jī)器周期計(jì)算的。
//測(cè)試時(shí)發(fā)現(xiàn)可以支持12-20Mhz頻率。
//如果想要使用11.0592Mhz頻率,在h文件取消“#define WS2812_FREQUENCY”的注釋
//在實(shí)際測(cè)試中發(fā)現(xiàn),ws2812對(duì)高電平時(shí)間較為敏感,對(duì)低電平時(shí)間不敏感
//也就是說(shuō),低電平時(shí)間可以稍微長(zhǎng)一些也不會(huì)影響程序,但是高電平時(shí)間需要控制的比較準(zhǔn)確才行。
*/
void Ws2812b_WriteByte(unsigned char byte)
{
#ifndef WS2812_FREQUENCY
if(byte & 0x80)
{
RGB_PIN_H();//4個(gè)機(jī)器周期(STC15F104W的拉高拉低都需要4個(gè)機(jī)器周期,其他系列暫時(shí)不知道,但是很大可能不是4個(gè)機(jī)器周期)
delay7NOP();//7個(gè)機(jī)器周期
RGB_PIN_L();//4+8(跳出這個(gè)if判斷需要5個(gè)機(jī)器周期,再進(jìn)入下一個(gè)if判斷需要3個(gè)機(jī)器周期)
}
else
{
RGB_PIN_H();
delay2NOP();//4+2
RGB_PIN_L();
delay3NOP();//4+3+5(跳出這個(gè)else需要3個(gè)機(jī)器周期,進(jìn)入下一個(gè)else則需要2個(gè)機(jī)器周期)
}
if(byte & 0x40)
{
RGB_PIN_H();//
delay7NOP();//4+7
RGB_PIN_L();//4+8
}
else
{
RGB_PIN_H();
delay2NOP();//4+2
RGB_PIN_L();
delay3NOP();//4+3+5
}
if(byte & 0x20)
{
RGB_PIN_H();//
delay7NOP();//4+7
RGB_PIN_L();//4+8
}
else
{
RGB_PIN_H();
delay2NOP();//4+2
RGB_PIN_L();
delay3NOP();//4+3+5
}
if(byte & 0x10)
{
RGB_PIN_H();//
delay7NOP();//4+7
RGB_PIN_L();//4+8
}
else
{
RGB_PIN_H();
delay2NOP();//4+2
RGB_PIN_L();
delay3NOP();//4+3+5
}
if(byte & 0x8)
{
RGB_PIN_H();//
delay7NOP();//4+7
RGB_PIN_L();//4+8
}
else
{
RGB_PIN_H();
delay2NOP();//4+2
RGB_PIN_L();
delay3NOP();//4+3+5
}
if(byte & 0x4)
{
RGB_PIN_H();//
delay7NOP();//4+7
RGB_PIN_L();//4+8
}
else
{
RGB_PIN_H();
delay2NOP();//4+2
RGB_PIN_L();
delay3NOP();//4+3+5
}
if(byte & 0x2)
{
RGB_PIN_H();//
delay7NOP();//4+7
RGB_PIN_L();//4+8
}
else
{
RGB_PIN_H();
delay2NOP();//4+2
RGB_PIN_L();
delay3NOP();//4+3+5
}
if(byte & 0x1)
{
RGB_PIN_H();//
delay7NOP();//4+7
RGB_PIN_L();//4+8
}
else
{
RGB_PIN_H();
delay2NOP();//4+2
RGB_PIN_L();
delay3NOP();//4+3+5
}
#else
if(byte & 0x80)
{
RGB_PIN_H();//
delay5NOP();//4+5
RGB_PIN_L();//4+8
}
else
{
RGB_PIN_H();
delay1NOP();//4+1
RGB_PIN_L();
delay2NOP();//4+2+5
}
if(byte & 0x40)
{
RGB_PIN_H();//
delay5NOP();//4+5
RGB_PIN_L();//4+8
}
else
{
RGB_PIN_H();
delay1NOP();//4+1
RGB_PIN_L();
delay2NOP();//4+2+5
}
if(byte & 0x20)
{
RGB_PIN_H();//
delay5NOP();//4+5
RGB_PIN_L();//4+8
}
else
{
RGB_PIN_H();
delay1NOP();//4+1
RGB_PIN_L();
delay2NOP();//4+2+5
}
if(byte & 0x10)
{
RGB_PIN_H();//
delay5NOP();//4+5
RGB_PIN_L();//4+8
}
else
{
RGB_PIN_H();
delay1NOP();//4+1
RGB_PIN_L();
delay2NOP();//4+2+5
}
if(byte & 0x8)
{
RGB_PIN_H();//
delay5NOP();//4+5
RGB_PIN_L();//4+8
}
else
{
RGB_PIN_H();
delay1NOP();//4+1
RGB_PIN_L();
delay2NOP();//4+2+5
}
if(byte & 0x4)
{
RGB_PIN_H();//
delay5NOP();//4+5
RGB_PIN_L();//4+8
}
else
{
RGB_PIN_H();
delay1NOP();//4+1
RGB_PIN_L();
delay2NOP();//4+2+5
}
if(byte & 0x2)
{
RGB_PIN_H();//
delay5NOP();//4+5
RGB_PIN_L();//4+8
}
else
{
RGB_PIN_H();
delay1NOP();//4+1
RGB_PIN_L();
delay2NOP();//4+2+5
}
if(byte & 0x1)
{
RGB_PIN_H();//
delay5NOP();//4+5
RGB_PIN_L();//4+8
}
else
{
RGB_PIN_H();
delay1NOP();//4+1
RGB_PIN_L();
delay2NOP();//4+2+5
}
#endif
}
main文件里面,我是用stc生成了一個(gè)300ms的延時(shí)函數(shù)。讓程序每隔300ms更換一下顏色并輸出。紅綠藍(lán)三色交替閃爍。
#include <STC15F2K60S2.H>
#include "ws2812.h"
void Delay300ms();//@12.000MHz
void main()
{
int i=0;
setLedCount(12);//可以設(shè)置0-25之間的任何數(shù)目,根據(jù)實(shí)際情況而定
rgb_SetRGB(0,0,0,0);//設(shè)置第一個(gè)燈關(guān)閉(這里作為使用示例,下面的rgb_SetColor覆蓋了這個(gè)函數(shù)的作用)
while(1)
{
for(i=0; i<getLedCount(); i++)//設(shè)置所有的燈為紅色
{
rgb_SetColor(i,RED);
}
rgb_SendArray();//發(fā)送給ws2812,顯示顏色
Delay300ms();
for(i=0; i<getLedCount(); i++)//設(shè)置所有的燈為綠色
{
rgb_SetColor(i,GREEN);
}
rgb_SendArray();//發(fā)送給ws2812,顯示顏色
Delay300ms();
for(i=0; i<getLedCount(); i++)//設(shè)置所有的燈為藍(lán)色
{
rgb_SetColor(i,BLUE);
}
rgb_SendArray();//發(fā)送給ws2812,顯示顏色
Delay300ms();
}
}
void Delay300ms()//@12.000MHz
{
unsigned char i, j, k;
_nop_();
_nop_();
i = 14;
j = 174;
k = 224;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
3,軟件調(diào)試
確保您的芯片型號(hào)選擇正確。
填寫軟件調(diào)試的晶振頻率,我這里使用16MHz(只是用來(lái)軟件計(jì)算機(jī)器周期,和實(shí)際晶振無(wú)關(guān))。1/16MHz=62.5ns。如果采用12MHz,我計(jì)算機(jī)器周期不太方便。
點(diǎn)擊調(diào)試。沒(méi)有配置時(shí)默認(rèn)是軟件調(diào)試模式。
點(diǎn)擊引腳信號(hào)的邏輯分析。
在新彈出的窗口中點(diǎn)擊Setup。然后輸入您的控制引腳。
使用調(diào)試控件,一邊觀察波形圖。
在程序執(zhí)行RGB_PIN_H();這一句之前,我用紅色的線定位了此時(shí)的時(shí)間位置,執(zhí)行這一句后,我用藍(lán)色的線查看它們之間的差值,差值為0.25us。這里我的軟件仿真使用的是16MHz晶振,每個(gè)機(jī)器周期為62.5ns。0.25us相當(dāng)于4個(gè)機(jī)器周期。正如我上面的發(fā)送函數(shù)里面的機(jī)器周期備注一樣。同理可以計(jì)算出每行代碼需要花費(fèi)的機(jī)器周期時(shí)間,從而準(zhǔn)確寫出驅(qū)動(dòng)。
比如,我們需要0.85us的高電平時(shí)間,也就是850ns,然后實(shí)際上我們使用的晶振是12MHz。每個(gè)機(jī)器周期的時(shí)間是理論是1/12Mhz=83.3ns。那么我們就需要850/83.3=10.2個(gè)機(jī)器周期。這里取11個(gè)機(jī)器周期。根據(jù)前面我們知道電平拉高需要用去4個(gè)機(jī)器周期,因此我們還需要讓高電平維持7個(gè)機(jī)器周期的時(shí)間,然后將電平拉低。以此原理,寫出我們的Ws2812b_WriteByte()函數(shù)的驅(qū)動(dòng)。
4,硬件調(diào)試
實(shí)際硬件調(diào)試時(shí),我寫了一個(gè)測(cè)試程序。我用軟件調(diào)試時(shí),波形為引腳電平拉高10個(gè)機(jī)器周期,然后拉低10個(gè)機(jī)器周期。循環(huán)動(dòng)作。
while(1)
{
P33=1;//4
delay6NOP();//6
P33=0;//4
delay2NOP();//2+4
}
但是實(shí)際用示波器測(cè)試時(shí),發(fā)現(xiàn)高電平時(shí)間和低電平時(shí)間均為3.8格*200ns/格=760ns。所以實(shí)際上每個(gè)機(jī)器周期的時(shí)間我這里是76ns左右。距離理論值83.3ns相差不多,而且ws2812每個(gè)高電平信號(hào)都可以有150ns左右的誤差,因此這里按照理論計(jì)算和按照實(shí)際計(jì)算均可。我按理論值計(jì)算。
————————————————
版權(quán)聲明:本文為CSDN博主「DIY愛(ài)好玩家」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_38476200/article/details/115519393
|
|