久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 7233|回復: 0
打印 上一主題 下一主題
收起左側

STC15F104W單片機驅動WS2812程序

[復制鏈接]
跳轉到指定樓層
樓主
ID:1015912 發表于 2022-5-15 19:49 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
1,WS2812時序
WS2812是一個集控制電路與發光電路于一體的智能外控LED光源,每個ws2812均含有4個引腳,引腳功能如下圖:

WS2812可采用級聯的方式,將上一個WS2812的DOUT引腳連接到下一個WS2812的DIN引腳,即可實現一個引腳控制多個WS2812。一般自己設計電路時還要在電源輸入處添加一個0.1uF的小電容進行濾波。注意,雖然理論上可以連接足夠多的ws2812,但是使用時要注意驅動電壓和驅動電流是否足夠驅動這些彩燈。

由于只有一個引腳控制ws2812,所以我們只能通過控制引腳輸出高、低電平的時間,來讓WS2812知道我們想讓他顯示哪一個燈,顯示什么顏色。
想要讓1個ws2812顯示我們想要的顏色,我們需要給它發送顏色數據。每一個ws2812的顏色數據都是24位:8位綠色+8位紅色+8位藍色。

如果我們想控制1個ws2812,我們發送24位顏色數據;如果我們想控制2個ws2812,我們需要連續發送48位顏色數據;如果我們想控制n個ws2812,我們需要連續發送n*24位顏色數據。

那么ws2812如何知道我們發送的每一個位的數據是“1”還是“0”呢?
WS2812手冊中對于數據有明確的時間定義:

可以看出,當我們想要發送一個位的數據時,如果這個位是“1”,我們就控制單片機的引腳輸出高電平0.85us,然后控制引腳輸出低電平0.40us;如果這個位是“0”,則控制引腳輸出高電平0.40us,然后控制引腳輸出低電平0.85us。

而如果我們想控制1個燈顯示顏色,我們需要發送24個位的顏色數據給ws2812。如果我們想控制n個燈顯示顏色,我們從第1個燈的顏色數據開始發送,直至發送完n*24個數據。最后我們需要控制單片機引腳輸出低電平超過50us,讓彩燈顯示顏色。

2,寫出驅動程序
注意,STC15F104W只有8個引腳,我們一般采用內部晶振電路。下面的代碼都是基于12MHz的晶振頻率寫的。WS2812對于單片機的引腳時序要求比較高。我們一般調用intrins.h這個頭文件的機器周期函數,nop()函數執行一次占用一個機器周期(此處時鐘周期和機器周期相等),所以每條機器周期時間為1/12MHz=83.3us。(代碼中很多處使用宏定義,宏定義在預編譯階段程序就完成代碼替代工作,不會影響程序的執行時間,且修改方便)
下面是h文件代碼:

#ifndef __WS2812_H
#define __WS2812_H

//頭文件區
#include <STC15F2K60S2.H>
#include <intrins.h>

//用戶修改參數區
//#define WS2812_FREQUENCY
#define RGB_PIN           P33                       //控制彩燈引腳(需要配置為強推挽輸出)
#define WS2812_MAX        25                        //彩燈最大個數
#define WS2812_NUMBERS    8                         //彩燈個數


#define RED               0xff0000                  //紅色
#define GREEN             0x00ff00                  //綠色
#define BLUE              0x0000ff                  //藍色
#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);//發送一個字節數據(@12.000MHz,理論每個機器周期83ns,測試約為76ns)                                                      
void setLedCount(unsigned char count);//設置彩燈數目,范圍0-25.                                                           
unsigned char getLedCount();//彩燈數目查詢函數                                                                     
void rgb_SetColor(unsigned char LedId, unsigned long color);//設置彩燈顏色                                    
void rgb_SetRGB(unsigned char LedId, unsigned long red, unsigned long green, unsigned long blue);//設置彩燈顏色
void rgb_SendArray();//發送彩燈數據                                                                           
#endif                                                                     

接著我們對c代碼進行詳解。
首先調用h文件,然后定義一個數組,用來存放彩燈的顏色數據。這里數據存放在data空間(空間有限,只能存放25個燈左右的數據),后面有需要再優化到xdata空間。ledsCount 記錄實際我們想要控制的彩燈的數目,nbLedsBytes 用來記錄彩燈的數據個數(一個燈需要3個字節)。

#include "ws2812.h"

unsigned char LedsArray[WS2812_MAX * 3];      //定義顏色數據存儲數組
unsigned int ledsCount = WS2812_NUMBERS;      //定義實際彩燈默認個數
unsigned int nbLedsBytes = WS2812_NUMBERS*3;  //定義實際彩燈顏色數據個數

接著我們開始編寫彩燈的設置數目函數,查詢數目函數,以及兩個設置指定彩燈顏色的函數。

//設置彩燈數目,范圍0-25.
void setLedCount(unsigned char count)
{
    ledsCount = WS2812_MAX > count ? count : WS2812_MAX;
    nbLedsBytes = ledsCount*3;
}

//彩燈數目查詢函數
unsigned char getLedCount()
{
    return ledsCount;
}

//設置彩燈顏色(在這里我將綠和紅色進行顛倒,這樣比較符合我們日常生活的紅綠藍的順序)
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;
}

//設置彩燈顏色
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);
}


然后我們對彩燈數據通過引腳發送出去。注意,發送彩燈數據的過程中,請將中斷關閉,否則有可能會導致數據發送到一半被中斷打斷,導致顯示異常。Ws2812b_WriteByte()函數是這個驅動代碼的核心。

//發送彩燈數據
void rgb_SendArray()
{
    unsigned int i;
    bit a=EA;
    //發送數據
    EA=0;
    for(i=0; i<nbLedsBytes; i++)
        Ws2812b_WriteByte(LedsArray[ i]);
    EA=a;
}

下面的Ws2812b_WriteByte函數是我基于stc15f104w調試的。我寫過不同單片機的驅動。不同單片機的每條指令花費的時間都不一樣。如果您想要采用其他系列的單片機,我們只需要修改Ws2812b_WriteByte()函數里面的內容即可。其他的函數可以不做修改。方便代碼的移植。
主要修改的地方是高電平總的拉高時間。這里之所以不對“1”和“0”的內容進行函數封裝,是為了我自己調試的方便。如果您覺得代碼不夠簡短好看,您可以將判斷條件里面的內容用宏定義進行一下封裝。

/*
//使用12.000MHz頻率,理論每個機器周期83ns,實際測試約為76ns。
//下面都是基于76ns納秒一個機器周期計算的。
//測試時發現可以支持12-20Mhz頻率。
//如果想要使用11.0592Mhz頻率,在h文件取消“#define WS2812_FREQUENCY”的注釋
//在實際測試中發現,ws2812對高電平時間較為敏感,對低電平時間不敏感
//也就是說,低電平時間可以稍微長一些也不會影響程序,但是高電平時間需要控制的比較準確才行。
*/
void Ws2812b_WriteByte(unsigned char byte)
{
#ifndef WS2812_FREQUENCY
    if(byte & 0x80)
    {
        RGB_PIN_H();//4個機器周期(STC15F104W的拉高拉低都需要4個機器周期,其他系列暫時不知道,但是很大可能不是4個機器周期)
        delay7NOP();//7個機器周期
        RGB_PIN_L();//4+8(跳出這個if判斷需要5個機器周期,再進入下一個if判斷需要3個機器周期)
    }
    else
    {
        RGB_PIN_H();
        delay2NOP();//4+2
        RGB_PIN_L();
        delay3NOP();//4+3+5(跳出這個else需要3個機器周期,進入下一個else則需要2個機器周期)
    }
    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生成了一個300ms的延時函數。讓程序每隔300ms更換一下顏色并輸出。紅綠藍三色交替閃爍。

#include <STC15F2K60S2.H>
#include "ws2812.h"
void Delay300ms();//@12.000MHz
void main()
{
    int i=0;
    setLedCount(12);//可以設置0-25之間的任何數目,根據實際情況而定
    rgb_SetRGB(0,0,0,0);//設置第一個燈關閉(這里作為使用示例,下面的rgb_SetColor覆蓋了這個函數的作用)
    while(1)
    {
        for(i=0; i<getLedCount(); i++)//設置所有的燈為紅色
        {
            rgb_SetColor(i,RED);
        }
        rgb_SendArray();//發送給ws2812,顯示顏色
        Delay300ms();
        for(i=0; i<getLedCount(); i++)//設置所有的燈為綠色
        {
            rgb_SetColor(i,GREEN);
        }
        rgb_SendArray();//發送給ws2812,顯示顏色
        Delay300ms();
        for(i=0; i<getLedCount(); i++)//設置所有的燈為藍色
        {
            rgb_SetColor(i,BLUE);
        }
        rgb_SendArray();//發送給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,軟件調試
確保您的芯片型號選擇正確。

填寫軟件調試的晶振頻率,我這里使用16MHz(只是用來軟件計算機器周期,和實際晶振無關)。1/16MHz=62.5ns。如果采用12MHz,我計算機器周期不太方便。

點擊調試。沒有配置時默認是軟件調試模式。
點擊引腳信號的邏輯分析。

在新彈出的窗口中點擊Setup。然后輸入您的控制引腳。

使用調試控件,一邊觀察波形圖。

在程序執行RGB_PIN_H();這一句之前,我用紅色的線定位了此時的時間位置,執行這一句后,我用藍色的線查看它們之間的差值,差值為0.25us。這里我的軟件仿真使用的是16MHz晶振,每個機器周期為62.5ns。0.25us相當于4個機器周期。正如我上面的發送函數里面的機器周期備注一樣。同理可以計算出每行代碼需要花費的機器周期時間,從而準確寫出驅動。

比如,我們需要0.85us的高電平時間,也就是850ns,然后實際上我們使用的晶振是12MHz。每個機器周期的時間是理論是1/12Mhz=83.3ns。那么我們就需要850/83.3=10.2個機器周期。這里取11個機器周期。根據前面我們知道電平拉高需要用去4個機器周期,因此我們還需要讓高電平維持7個機器周期的時間,然后將電平拉低。以此原理,寫出我們的Ws2812b_WriteByte()函數的驅動。

4,硬件調試
實際硬件調試時,我寫了一個測試程序。我用軟件調試時,波形為引腳電平拉高10個機器周期,然后拉低10個機器周期。循環動作。

while(1)
{
        P33=1;//4
        delay6NOP();//6
        P33=0;//4
        delay2NOP();//2+4
}
但是實際用示波器測試時,發現高電平時間和低電平時間均為3.8格*200ns/格=760ns。所以實際上每個機器周期的時間我這里是76ns左右。距離理論值83.3ns相差不多,而且ws2812每個高電平信號都可以有150ns左右的誤差,因此這里按照理論計算和按照實際計算均可。我按理論值計算。
————————————————
版權聲明:本文為CSDN博主「DIY愛好玩家」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_38476200/article/details/115519393

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏4 分享淘帖 頂 踩
回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規則

小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術交流QQ群281945664

Powered by 單片機教程網

快速回復 返回頂部 返回列表
主站蜘蛛池模板: 久久99深爱久久99精品 | 精品国产一区二区三区日日嗨 | 欧美日韩一区在线 | 久久日韩精品一区二区三区 | 精品久久久久久亚洲综合网 | 午夜成人在线视频 | 一级电影免费看 | 日韩精品一区二区在线观看 | 色在线免费视频 | 4hu最新网址 | 午夜在线精品 | av免费观看在线 | 午夜激情在线 | 久久精品国产一区二区电影 | 成人在线免费网站 | 久久伊| 精品久久久久一区二区国产 | 超碰成人在线观看 | 国产一区二区三区在线 | 亚洲精品九九 | 久久久久久久网 | 欧美成人精品一区二区男人看 | 婷婷亚洲综合 | 久久久国产精品 | 国产va| 综合九九 | 欧美成人aaa级毛片在线视频 | 成人精品一区二区三区四区 | 久久久久久久久久久成人 | 亚洲自拍偷拍欧美 | 亚洲美女网站 | 九九九久久国产免费 | 超碰日韩| 久在线视频播放免费视频 | 亚洲人人| 久久久久av| 黄色男女网站 | 日本三级电影在线观看视频 | 欧美精品成人一区二区三区四区 | 天堂亚洲 | 在线免费中文字幕 |