招新題流程簡介(WS2812)
22物電科協軟件招新題學習流程
有錯誤或者不當的地方請在評論區指出??
題目簡介
使用stm32驅動單一ws2812b燈珠實現呼吸燈效果,驅動及實現方法不限
演示效果

快速入門,在stm32核心板上點燈
單片機介紹
采用超大規模集成電路技術把具有數據處理能力的中央處理器CPU、隨機存儲器RAM、只讀存儲器ROM、多種I/O口和中斷系統、定時器/計數器等功能(可能還包括顯示驅動電路、脈寬調制電路、模擬多路轉換器、A/D轉換器等電路)集成到一塊硅片上構成的一個小而完善的微型計算機系統
入門階段可以粗略的將其認為一個可以控制電路的小計算機
招新題所用的單片機型號為stm32f103c6t6,開發語言為c語言
前置軟件
- 組合一:stm32cubeide
- 組合二:stm32cubemx+keil5
- 可選軟件:flymcu
軟件介紹
- 32單片機程序編寫分為配置和寫程序,組合二中配置過程我們選擇stm32cubemx,這是一個可以圖形化配置軟件,比較直觀,不需要自己寫配置代碼,軟件會根據你的圖形配置自己生成,寫程序采用keil5,這是一個面向單片機C語言軟件開發系統,集成了完善的開發環境,使用時需要破解。
- 組合一中的cubeide則集成了兩個功能,具有圖形化配置界面,配置生成的代碼可以直接開始編寫,較為方便,缺點是使用非官方的stlink燒寫程序較為麻煩,下面的教程的環境均為stm32cubeide。
- flymcu為一款串口燒錄軟件,如果同學們選擇串口燒錄則可以選擇下載這個軟件。
- cubeide官網 https://www.st.com/zh/development-tools/stm32cubeide.html
cubemx官網 https://www.st.com/en/development-tools/stm32cubemx.html
flymcu與keil5群文件
程序燒錄
這里介紹的是編寫好程序后的燒寫方式,可以先學習完程序的編寫再學習如何燒寫
燒寫/燒錄是指將我們在電腦上寫的程序寫入我們的單片機,從而讓單片機可以執行我們的程序
燒寫工具
- USB轉TTL

- 正版stlink

- 盜版stlink

淘寶購買即可
燒寫方法
- 使用正版stlink下載,寫完程序并連接好單片機后點擊run即可
- 使用盜版stlink下載(在keil5上可以直接使用(應該),在cubeide上如果點擊run后顯示需要更新固件,要先把stlink與單片機斷開,然后open upgrade mode 進行更新,如果報錯就refresh一下再次更新,直到下方出現進度條開始更新,更新完畢即可,然后再連接單片機,此時就可以正常下載程序了)
- 使用串口下載,首先確定代碼生成的hex文件,cubeide可以點擊工程屬性>c/c++ build>setting>MCU Post build outputs>打鉤生成hex,然后單片機需要接usb轉TTL,RX接PA9,TX接PA10,5V,gnd分別與單片機5v,gnd燒錄前將boot0接為1,然后使用flymcu下載,flymcu下方編程到flash時寫入選項字節取消打鉤,然后開始編程,燒錄成功后將boot0改為0即可
開始點燈
配置程序
這一部分網上也有很多教程,這里提供的是我的配置過程,更詳細的也可以參考其他教程,這里提供一個博客
- 打開軟件,建立一個新的工程
選擇Flie》new》stm32 project (qq截圖不了這個地方,自己找一下這幾個的位置) - 選擇我們的單片機型號

然后點擊next - 填入我們工程的名字
建議用英文,防止可能出現的路徑錯誤,工程地址可以自己修改,也可以使用默認

然后直接點擊finish即可 - 配置圖形化頁面

這個就是我們的單片機芯片了,是一個很直觀的圖形頁面,可以自由拖動和配置,我們首先來配置單片機的時鐘

將RCC配置為如圖所示,這里是使用的外部晶振,更加穩定

將SYS配置為如圖所示,便于串口調試
下面來配置IO口

將PC13 設置為GPIO_Output,即輸出電平,這里就設計到我們點燈的原理了
點燈原理

在我們單片機上的led連接方法是這樣的,我們知道二極管具有單向導通性,而led也就是發光二極管也具有這樣的性質,因此,當led2為0v也就是接地時,led導通,燈亮,當led2為3.3V時,led兩端電壓一樣,不會導通,燈滅。而我們的GPIO_output正是可以控制輸出電壓,控制的方法就是控制高低電平,我們已經學過的C/C++,二進制是由0,1構成,而在32單片機中,0就代表0v,1就代表3.3V(有誤差),當我們把這個引腳設置為0時,led便會亮起。
5. 生成代碼
直接點擊保存或者ctrl+s即可,彈出窗口點解generation code

這里便是我們的主程序了
下面我們來嘗試按照我們上面的講解點亮這個led
6. 點燈
在main內找到while(1)循環,在前面寫上
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET)
這個函數使將我們的GPIO設置為某一電平,前兩個參數較容易理解,我們選的的是PC13,所以就是C13,第三個參數其實就是低電平

注意,程序一定要寫在注釋的begin與end之間,在這之外的都會被再次配置后的代碼覆蓋
下面我們燒寫程序觀察我們的核心板。
燒寫方法在上面

可以看到燈已經亮起
7. 讓燈閃爍起來
在我們的主程序中,有一個while(1){},我們學習c語言時,都是不會出現死循環的,但是在單片機中死循環是一個非常正常的事情,我們為了讓程序一直循環一直我們設定的東西。
于是我們在主程序中添加如下代碼

HAL_Delay();是延時程序,單位為ms,即讓程序延時1s,如此我們便知道,這個程序的含義是讓燈亮一秒滅一秒,并不停循環。
下面我們燒寫程序觀察我們的核心板

進階——產生一個pwm波
波/信號
下面討論的波/信號可以粗略的認為是電壓的變化,這種變化可以承載著信息的傳遞,比如我們上面提到的二進制,比如我們想用波傳遞一個數字六,六在二進制下是110,那我們可以讓波在兩個時間單位下為高電平,一個時間單位下為低電平,這樣就把信息傳遞了出去
pwm波的定義
PWM就是脈沖寬度調制,也就是占空比可變的脈沖波形。脈沖寬度調制是一種對模擬信號電平進行數字編碼的方法。
可以簡單理解為一個只由高低電平組成的周期波形,高低電平的持續時間可調
pwm波圖示

根據上一節的知識,我們很容易知道ton指的就是高電平,toff就是指的低電平,而這樣一個pwm波有兩個參數是我們需要關注的,第一個是周期/頻率,高中知識我們可以知道周期就是頻率的倒數,即\(T={{1}\over{f}}\),而在圖示中,我們可以知道\(T=ton+toff\),而另一個參數叫做占空比,其定義是一個周期中高電平占整個周期的時間,即\(D={{ton}\over{ton+toff}}={ton \over T}\)
嘗試產生一個pwm波
在上一節的學習中,我們其實已經產生過一個pwm波,即讓燈閃爍的波,根據延時的時間,我們很容易知道這個pwm波的占空比為\(50\%\),周期為\(2s\),頻率為\(0.5HZ\)
下面我們將嘗試產生兩個不同方法下的pwm波,第一個是基于上一種方法使led有呼吸的效果,第二個是使用定時器產生更為精準的pwm波,并為招新題做好基礎。
呼吸燈的定義與實現方法
原理
呼吸燈是指燈光在微電腦的控制之下完成由亮到暗的逐漸變化,感覺好像是人在呼吸。
舉個不太恰當的例子,在生活中有一種可以發光的熒光棒,快速揮舞便可以看出圖案,這個的原理是利用人的視覺暫留,基于人眼的分辨率不超過\(60HZ\)。而呼吸燈的實現也有類似的地方,如果我們以比較高頻率的pwm波驅動led,那么led的閃爍頻率就會高于我們人眼的分辨率,這樣人眼看起來led就不會閃爍,是一直亮的,但是亮與亮之間也有不同。很顯然,占空比90%和占空比10%的燈肯定是前者更亮一點,因為亮的時間更長一點,于是我們就可以利用這個實現呼吸燈——改變pwm的占空比。
實現
將主函數中的while(1)循環修改為

此流程介紹中均不會涉及c語言語法講解,若對循環判斷函數等有疑惑可以自行查閱課本及線上教程,這里提供一個較為全面的線上教程https://www.runoob.com/
根據上面的學習與上一節led原理,我們知道當引腳輸出為低電平時led才會亮,因此占空比低時led亮,占空比高是led暗,因此循環中的兩個for循環可以很容易知道分別是從暗到亮與從亮到暗,因為HAL_Delay中的參數單位是ms,因此這個pwm的周期為20ms,即50hz,這里沒有超過60hz是因為HAL_Delay的分辨率不足的原因,無法實現ms級以下延時,在下面的討論中我們會學習更為精準且分辨率更高的延時。下面是延時效果(手機拍攝效果不佳)

使用定時器產生一個pwm波
定時器介紹
定時器最基本的功能就是定時處理事情。比如定時發送USART數據、定時采集AD數據、定時檢測IO口電位、還可以通過IO口輸出波形等。可以實現非常豐富的功能。定時器是一個很強大的外設,不同行業使用的方式不同,知識面很廣。
可以簡單理解為秒表,鬧鐘,倒計時等等等。
利用stm32自帶的pwm功能產生一個波形
定時器的使用這里暫時不會用到,只講如何使用定時器的pwm輸出產生波形,想學習的同學可以自行查閱資料配置學習,這里提供一個講解定時器的博客
- 首先打開我們的圖形化配置界面

- 修改單片機時鐘頻率

圈出地方本來值應該為8,直接修改為72然后點回車確定即可,這個頁面配置的是單片機各個模塊的工作頻率,這里修改的原因是提高定時器的能力,下面也會講這個值對定時器的影響 - 選擇定時器的輸出

注意選擇PWM Generation,不要選擇錯了 - 修改定時器的預分頻系數與計數周期

有關于這里數值選擇與計算,這里提供一個講解的博客
這里也簡單講一下pwm頻率的計算,在第二步的時鐘樹頻率修改,我們可以看到修改后
APB1 Timer的值為72M,這里就是我們定時器的總頻率

而參數一Prescaler便是對這個值的預分頻,如果將這個設為71,那么定時器的頻率就會變成\(72M/(71+1)=1M\),即1MHZ,參數二Counter Period是自動重裝值,即計數到多少時自動重新計數,也可以理解為計數周期,將這個地方設置為999,則定時器的頻率就會變成\(1M/(999+1)=1K\),即1KHZ,這里兩個值都加一是因為單片機定時器計數的方式決定的,類似于for循環中的開區間,對這里理解有困難的同學可以參考上面的博客,也可以先暫且記住,在以后的學習中逐漸理解。 - ctrl+s保存配置并更新代碼
- 修改主函數

這里的函數HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);是打開pwm的輸出,將Start改為Stop即為關閉,函數內的參數與之前的配置相對應。
這里的函數__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 500);是修改pwm的占空比,初始值為0,所以這里要修改,也可以在之前配置的下方的pusle處修改,注意第三個參數是高電平的持續時間,是與計數周期相對比,因此不能大于1000,此時占空比易知\(D={500 \over 1000}*100\%=50\%\)
修改占空比的函數可以在程序內任意地方執行,因此同學們可以嘗試利用這個函數實現呼吸燈 - 燒寫程序,使用示波器觀察
這里我們的pwm輸出口在圖形化配置界面中有,是PAB
有關于示波器的使用這里不會涉及,如果在宿舍想測試可以買一個小蜂鳴器或者喇叭,在下面的內容會簡單說一下去年招新題的內容,就是用pwm驅動喇叭

可以看到這個pwm的占空比為50%,左上角頻率為1KHZ
利用pwm波產生七個音符
我們知道聲音是有頻率的,而我們使用的發聲器件喇叭或者無源蜂鳴器便是使用信號驅動的。如果我們用不同頻率的pwm波驅動喇叭或者蜂鳴器就會發出不同的聲音,下面是C調的七音符對應的頻率

下面是有關這個題的一些點
- 這里占空比對聲音的音調沒有影響,因此默認為50%。
- 去年的招新題是利用七個按鍵控制喇叭發出七種音符,如何用按鍵控制這里暫且不會涉及,可以自行查閱GPIO_Input的使用。
- 需要注意的是,核心板帶載能力有限,喇叭建議一邊接電源一邊接pwm輸出口,或者連接功放或者三極管,或者使用帶有供電端的無源蜂鳴器。
- 因為這個是去年的招新題,因此此處不做演示,有興趣的同學可以自行制作。
- 下面提供的是小星星的簡譜

進進階,點亮N個WS2812燈珠
WS2812燈珠介紹
WS2812B是一款智能控制LED光源,控制電路和RGB芯片集成在一個5050個組件的封裝中。
原理
ws2812有四個腳,一對供電腳與數據輸入DIN與數據輸出DOUT

控制方式為總線控制。
我們知道,顏色的表示方法有很多種,我們這里需要用到的有兩種,第一種是RGB表示,第二種是HSV表示,這里先介紹RGB表示
RGB分別表示RED,GREEN,BLUE,三個的值都在0-255之間,不同的取值對應不同的顏色,類似于美術中的三原色混合,如果我們想表示一個紅色,那么rgb的值就是255,0,0。
而ws2812的控制就是基于rgb值的控制,每一次傳輸24位數據,從前向后每8位分別是G,R,B,8位也正好是rgb的范圍,因此我們想傳輸紅色的值就是00000000 11111111 00000000.
如果我們想要驅動多個ws2812產生不同的顏色,就需要讓其級聯,將第一個的DOUT連接第二個的DIN,因為ws2812的數據傳輸原理,就像切蛋糕一樣,每次經過一個就會切去一片,我們傳輸一個48位的數據,前24位進入第一個燈珠然后切去,后24位就會進入第二個燈珠
在單片機上的實現
原理
這里使用的方法是PWM+DMA傳輸,也有SPI和直接延時等方法,方法不唯一,有興趣的同學可以自行查閱資料
首先我們要確定的是,WS2812的0與1實現方式,與之前點燈時的01不同,燈珠的01碼是根據占空比來判斷的
可以看到,1碼的占空比高,0碼的占空比低,下面是具體的參數范圍

正是因為這種特性,我們才選用pwm的傳輸方式,下面我們來進行一個簡單的計算
如果我們將預分頻設為0,自動重載值設為89,那么pwm的頻率就是\({72M \over {(0+1)*(89+1)}}=800K\),當我們把高電平比較值設置為61時,高電平持續時間就為\({61 \over (89+1)}*{1 \over 800K}=847us\),當我們把高電平比較值設置為28時,高電平持續時間就為\({28 \over (89+1)}*{1 \over 800K}=388us\),兩個高電平持續時間正好滿足1碼與0碼的高電平的范圍,同理,可以計算出低電平的值也符合,因此,我們按照之前學習的方法控制單片機產生這兩種占空比不同的pwm波,就可以傳輸數據
但是,這樣是比較麻煩的實現,并且會有一定的延時,針對這種問題,32單片機提供了一種數據搬運的方法,DMA傳輸
DMA傳輸
介紹
DMA(Direct Memory Access,直接存儲器訪問) 是所有現代電腦的重要特色,它允許不同速度的硬件裝置來溝通,而不需要依賴于 CPU 的大量中斷負載。DMA 傳輸將數據從一個地址空間復制到另外一個地址空間。當CPU 初始化這個傳輸動作,傳輸動作本身是由 DMA 控制器來實行和完成。典型的例子就是移動一個外部內存的區塊到芯片內部更快的內存區。像是這樣的操作并沒有讓處理器工作拖延,反而可以被重新排程去處理其他的工作。DMA 傳輸對于高效能 嵌入式系統算法和網絡是很重要的。
這里我們只要了解如何使用與配置方法即可
配置與程序
- 首先按照上面所說配置預分頻與重載值

- 按照下圖配置DMA

點擊ADD添加DMA,然后選擇通道并如圖配置即可(注意dma的方向是內存到外設,需要更改)
有關如此配置的含義,有興趣的同學可以自行查找資料,這里提供了一個博客 - 配置工程生成單獨.c/.h文件

這樣配置的目的是為了我們下面代碼的結構 - ctrl+s保存并更新代碼
- 在工程的文件結構中添加兩個文件

添加方式為右鍵創建一個文本文檔,并將原本的文件名XX.TXT改為ws2812.c/ws2812.h(不顯示后綴自行搜索如何顯示后綴) - 在我們的cubeide中打開這兩個文件,并寫入如下代碼(已經掌握原理的同學可以根據下面的講解自行寫,這里僅作為參考)
//ws2812.c
#include "ws2812.h"
unsigned char color_data[24*led_num+4];//實際GRB數據
void show()
{
HAL_TIM_PWM_Start_DMA(&htim1,TIM_CHANNEL_1,(uint32_t *)(&color_data),sizeof(color_data));
}
void color_set(unsigned short int index,unsigned char r,unsigned char g,unsigned char b)
{
unsigned char j;
if(index >led_num)
return;
for(j = 0; j < 8; j++)
{
color_data [24 * index + j+3] = (g & (0x80 >> j)) ? bit1 : bit0; //G 將高位先發
color_data [24 * index + j + 8+3] = (r & (0x80 >> j)) ? bit1 : bit0; //R將高位先發
color_data [24 * index + j + 16+3] = (b & (0x80 >> j)) ? bit1 : bit0; //B將高位先發
}
}
void hsv_to_rgb(int h,int s,int v,float *R,float *G,float *B)
{
float C = 0,X = 0,Y = 0,Z = 0;
int i=0;
float H=(float)(h),S=(float)(s)/100.0,V=(float)(v)/100.0;
if(S == 0)
*R = *G = *B = V;
else
{
H = H/60;
i = (int)H;
C = H - i;
X = V * (1 - S);
Y = V * (1 - S*C);
Z = V * (1 - S*(1-C));
switch(i){
case 0 : *R = V; *G = Z; *B = X; break;
case 1 : *R = Y; *G = V; *B = X; break;
case 2 : *R = X; *G = V; *B = Z; break;
case 3 : *R = X; *G = Y; *B = V; break;
case 4 : *R = Z; *G = X; *B = V; break;
case 5 : *R = V; *G = X; *B = Y; break;
}
}
*R = *R *255;
*G = *G *255;
*B = *B *255;
}
//ws2812.h
#ifndef WS2812
#define WS2812
#include "tim.h"
#define bit1 61 //1碼比較值為61-->850us
#define bit0 28 //0碼比較值為28-->400us
#define led_num 6 //燈的數量
void color_set(unsigned short int index,unsigned char r,unsigned char g,unsigned char b);
void show(void);
void hsv_to_rgb(int h,int s,int v,float *R,float *G,float *B);
#endif
下面來解釋代碼內容
- 首先,創建這兩個文件的目的是為了將對顏色的控制封裝在一起,然后在主函數中調用,從而讓整個代碼結構更加簡潔明了
- 然后來看.h文件,頭文件tim.h是因為這里要用定時器,所以要引用這個,前兩行的作用是為了重復引用。然后是宏定義部分,比較明了,是前面講的部分。然后是一個數組,這個數組就是數據傳輸的數組,我們知道每個燈有24位,所以是led_num*24,而這里加4是為了讓程序運行更加穩定,開始三位為0,代表reset碼,清除之前的顏色,最后一位為0,代表控制顏色結束,所以是3+1=4,多了四位。下面的是函數的定義,這里的hsv_to_rgb暫且不講,是呼吸燈用到的內容
- 最后來看.c文件,show()函數便是傳輸數據,用DMA的方式傳輸,數組中的值代表的就是pwm的比較值,比如數組中是0,0,0,61,28,61,61,28……,那么就是傳輸的10110……。下面的color_set()便是對顏色的修改,這里三目運算符與二進制運算的使用留給同學們自己研究,可以檢驗一下自己c語言學習是否清除扎實
- 修改主函數的內容

調用我們之前寫的庫,注意寫在注釋的begin與end之間

控制第一個燈為紅色,注意這里傳入的值0為第一個,以此類推,同時,color_set并不會使燈的顏色修改,必須要在后面加上show()才能傳輸 - 燒寫程序并觀察
這里效果可以參考文章最開始的效果,這里也附上文章開始的gif程序實現
for(int i=1;i<=6;i++){
color_set(i-1,i*50,0,255);
show ();
HAL_Delay (500);
}
for(int i=1;i<=6;i++)
color_set(i-1,255,0,255);
show ();
HAL_Delay (500);
for(int i=1;i<=6;i++)
color_set(i-1,0,255,255);
show ();
HAL_Delay (500);
for(int i=1;i<=6;i++)
color_set(i-1,255,255,0);
show ();
HAL_Delay (500);
呼吸燈的實現
原理
根據之前的學習,我們有一種方式使燈珠產生呼吸的效果,即不停地對燈珠進行點亮與reset,控制明暗產生呼吸的效果,同學們可以自行嘗試這種方法。下面介紹的是另一個較為簡單的方法,利用顏色的HSV值
HSV
HSV(Hue, Saturation, Value)是根據顏色的直觀特性由 A. R. Smith 在 1978 年創建的一種顏色空間, 也稱六角錐體模型(Hexcone Model)。這個模型中顏色的參數分別是色調(H)、飽和度(S)和明度(V)。
因此,我們可以通過控制V的值來實現顏色的明暗變化,這里也很好理解,顏色本來就有明暗之分,比如亮紅與暗紅。
HSV與RGB的轉換
下面提供的是HSV向RGB的轉換公式

根據轉換公式,我們便得到了之前提到的hsv_to_rgb函數
參數的范圍h(0~360),s(0~100),v(0~100)
修改主函數實現呼吸燈

這里HAL_Delay的目的是降低呼吸頻率,可以計算得到這個程序的呼吸時間為1.2s
燒寫程序觀察

這里也提供了一個常見顏色對照表,同學們可以選擇喜歡的顏色實現呼吸燈的效果
結語
本次招新題的個人建議流程到這里也就結束了,主要是講了stm32的配置與pwm波的產生,具體實現細節還需要同學們實踐學習,有疑問的地方也歡迎提問。對于本題的發揮部分這里不做講解,同學們可以自行查閱資料學習,培養好的自學能力是大學生活很重要的一環,這里也提供幾個我大一學習時參考的博客。
串口收發
OLED配置
ADC采集

浙公網安備 33010602011771號