對(duì)于技術(shù)的學(xué)習(xí),我希望大家一定要有足夠的耐性和韌性。如果你決定從事單片機(jī)這門技術(shù),那就一定要堅(jiān)持學(xué)習(xí)下去,不能半途而廢,當(dāng)你堅(jiān)持學(xué)習(xí)一段時(shí)間后你會(huì)發(fā)現(xiàn)自己慢慢會(huì)喜歡這些玩意,對(duì)這些東西有了濃厚的興趣和感情,那你離成功就不遠(yuǎn)了。學(xué)到第九課了,鼓勵(lì)鼓勵(lì)自己,再加把勁哦!
1.1 單片機(jī)的IO口結(jié)構(gòu)
上節(jié)課我們提到了單片機(jī)的IO口的其中一種“準(zhǔn)雙向IO”的內(nèi)部結(jié)構(gòu),實(shí)際上我們的單片機(jī)IO口還有另外三種狀態(tài),分別是開漏、推挽、高阻態(tài),我們通過圖9-1來看下三種狀態(tài)。

圖9-1 單片機(jī)IO口狀態(tài)示意圖
前邊我們簡單介紹“準(zhǔn)雙向IO”的時(shí)候,我們是用三極管來說明的,出于嚴(yán)謹(jǐn)?shù)膽B(tài)度,我們這里按照實(shí)際情況用MOS管畫圖示意。實(shí)際上三極管是靠電流導(dǎo)通的,而MOS管是靠電壓導(dǎo)通的,具體緣由和他們的內(nèi)部構(gòu)造有關(guān)系,在這里我們暫且不必關(guān)心,如果今后有必要了解可以直接查找模擬電子書或者百度相關(guān)資料進(jìn)行細(xì)致學(xué)習(xí)。在單片機(jī)IO口狀態(tài)這一塊內(nèi)容上,我們可以把MOS管當(dāng)三極管來理解。在我們的圖9-1中,T1相當(dāng)于一個(gè)PNP三極管,T2相當(dāng)于一個(gè)NPN三極管。
其中準(zhǔn)雙向IO口原理已經(jīng)講過了,開漏輸出和準(zhǔn)雙向IO的唯一區(qū)別,就是開漏輸出把內(nèi)部的上拉電阻去掉了。開漏輸出如果要輸出高電平時(shí),T2關(guān)斷,IO電平要靠外部的上拉電阻才能拉成高電平,如果沒有外部上拉電阻IO電平就是一個(gè)不確定態(tài)。標(biāo)準(zhǔn)51單片機(jī)的P0口默認(rèn)就是開漏輸出,如果要用的時(shí)候外部需要加上拉電阻。而強(qiáng)推挽輸出就有比較強(qiáng)的驅(qū)動(dòng)能力,如圖9-1中第三張小圖,當(dāng)內(nèi)部輸出一個(gè)高電平時(shí),通過MOS管直接輸出電流,沒有電阻的限流,電流輸出能力也比較大;如果內(nèi)部輸出一個(gè)低電平,那反向電流也可以很大,強(qiáng)推挽的一個(gè)特點(diǎn)就是驅(qū)動(dòng)能力強(qiáng)。
單片機(jī)IO還有一種狀態(tài)叫高阻態(tài)。通常我們用來做輸入引腳的時(shí)候,可以將IO口設(shè)置成高阻態(tài),高阻態(tài)引腳本身如果懸空,用萬用表測(cè)量的時(shí)候可能是高可能是低,他的狀態(tài)完全取決于外部輸入引腳的電平,高阻態(tài)引腳對(duì)GND的電阻很大相當(dāng)于一個(gè)無窮大,所以稱之為高阻。
這就是單片機(jī)的IO口的四種狀態(tài),在我們51單片機(jī)學(xué)習(xí)過程中,我們的主要應(yīng)用是準(zhǔn)雙向IO口,隨著我們學(xué)習(xí)的深入,其他狀態(tài)也會(huì)有接觸,在這里介紹給大家學(xué)習(xí)一下。
1.2 上下拉電阻
前邊似乎我們很多次提到了上拉電阻,下拉電阻,具體到底什么樣的電阻算是上下拉電阻,上下拉電阻都有何作用呢?
上拉電阻就是將不確定的信號(hào)通過一個(gè)電阻拉到高電平,同時(shí)此電阻也起到一個(gè)限流作用,下拉就是下拉到低電平。
比如我們的IO設(shè)置為開漏輸出高電平或者是高阻態(tài)時(shí),默認(rèn)的電位是不確定的,外部經(jīng)一個(gè)電阻接到VCC,也就是上拉電阻,那么相應(yīng)的引腳就是高電平;經(jīng)一個(gè)電阻到GND,也就是下拉電阻,那么相應(yīng)的引腳就是一個(gè)低電平。
上拉電阻應(yīng)用很多,都可以起到什么作用呢?我們現(xiàn)在主要先了解最常用的以下4點(diǎn)。
1、OC門要輸出高電平,必須外部加上拉電阻才能正常使用,其實(shí)OC門就相當(dāng)于單片機(jī)IO的開漏輸出,其原理可參照?qǐng)D9-1中的開漏電路。
2、加大普通IO口的驅(qū)動(dòng)能力。標(biāo)準(zhǔn)51單片機(jī)的內(nèi)部IO口的上拉電阻,一般都是在幾十K歐,比如STC89C52RC內(nèi)部是20K的上拉電阻,所以最大輸出電流是250uA,因此外部加個(gè)上拉電阻,可以形成和內(nèi)部上拉電阻的并聯(lián)結(jié)構(gòu),增大高電平時(shí)電流的輸出能力。
3、在電平轉(zhuǎn)換電路中,比如我們前邊講的5V轉(zhuǎn)12V的電路中,上拉電阻其實(shí)起到的是限流電阻的作用,如圖9-2所示。

圖9-2 上拉電阻R2
4、比如單片機(jī)總線引腳,不使用的引腳懸空的時(shí)候,容易受到電磁干擾而處于一個(gè)紊亂狀態(tài),加上一個(gè)對(duì)VCC的上拉電阻或者一個(gè)對(duì)GND的下拉電阻后,可以有效的抵抗電磁干擾。
我們?cè)谶M(jìn)行電路設(shè)計(jì)的時(shí)候,如何正確選擇合適的上下拉電阻的阻值呢?
1、從節(jié)約功耗的方面考慮應(yīng)當(dāng)足夠大,因?yàn)殡娮柙酱,電流越小?/span>
2、從確保足夠的引腳驅(qū)動(dòng)電流考慮應(yīng)當(dāng)足夠小,電阻小了,電流才能大。
3、在開漏輸出時(shí),過大的上拉電阻會(huì)導(dǎo)致信號(hào)上升沿變緩。我們來解釋一下:實(shí)際電平的變化都是需要時(shí)間的,雖然很小,但永遠(yuǎn)都達(dá)不到零,而開漏輸出時(shí)上拉電阻的大小就直接影響了這個(gè)上升過程所需要的時(shí)間,如圖9-3所示。想一下,如果電阻很大,而信號(hào)頻率又很快的話,最終將導(dǎo)致信號(hào)還沒等上升到高電平就又變?yōu)榈土,于是信?hào)就無法正確傳送了。
圖9-3 上拉電阻對(duì)波形的影響
綜合考慮,我們常用的上下拉電阻值大多選取在1k到10k之間,具體到底多大通常要根據(jù)實(shí)際需求來選,通常情況下在標(biāo)準(zhǔn)范圍內(nèi)就可以了,不一定是一個(gè)固定的值。
1.3 28BYJ-48型步進(jìn)電機(jī)詳解與實(shí)例
1.3.1 電機(jī)的分類
電機(jī)的分類方式有很多,從用途的角度可劃分電機(jī)分為驅(qū)動(dòng)類電機(jī)和控制類電機(jī)。直流電機(jī)屬于驅(qū)動(dòng)類電機(jī),這種電機(jī)是將電能轉(zhuǎn)換成機(jī)械能,主要應(yīng)用在電鉆、小車輪子、電風(fēng)扇、洗衣機(jī)等等設(shè)備上。步進(jìn)電機(jī)屬于控制類電機(jī),它是將脈沖信號(hào)轉(zhuǎn)換成一個(gè)轉(zhuǎn)動(dòng)角度的電機(jī),在非超載的情況下,電機(jī)的轉(zhuǎn)速、停止的位置只取決于脈沖信號(hào)的頻率和脈沖數(shù),主要應(yīng)用在自動(dòng)化儀表、機(jī)器人、自動(dòng)生產(chǎn)流水線、空調(diào)扇葉轉(zhuǎn)動(dòng)等設(shè)備。
步進(jìn)電機(jī)分為反應(yīng)式、永磁式和混合式三種。
反應(yīng)式步進(jìn)電機(jī):結(jié)構(gòu)簡單成本低,但是動(dòng)態(tài)性能差、效率低、發(fā)熱大、可靠性難以保證,所以現(xiàn)在基本已經(jīng)被淘汰。
永磁式步進(jìn)電機(jī):動(dòng)態(tài)性能好、輸出力矩較大,但誤差相對(duì)來說大一些,因其價(jià)格低廣泛應(yīng)用于消費(fèi)性產(chǎn)品。
混合式步進(jìn)電機(jī):綜合了反應(yīng)式和永磁式的優(yōu)點(diǎn),力矩大、動(dòng)態(tài)性能好、步距角小,精度高,但是結(jié)構(gòu)相對(duì)來說復(fù)雜,價(jià)格也相對(duì)高,主要應(yīng)用于工業(yè)。
我們本章內(nèi)容主要講解28BYJ-48這款步進(jìn)電機(jī),其中
28——步進(jìn)電機(jī)的有效最大外徑是28毫米
B ——表示是步進(jìn)電機(jī)
Y ——表示是永磁式
J ——表示是減速型
48——表示四相八拍
1.3.2 28BYJ-48型步進(jìn)電機(jī)原理詳解
28BYJ-48是4相永磁式減速步進(jìn)電機(jī),其外觀如下圖所示:
 
圖9-4 步進(jìn)電機(jī)外觀
我們先來解釋什么是“4相永磁式”的概念,28BYJ-48的內(nèi)部結(jié)構(gòu)示意圖9-2所示。先看里圈,它上面有6個(gè)齒,分別標(biāo)注為0-5,這個(gè)叫做轉(zhuǎn)子,顧名思義,它是要轉(zhuǎn)動(dòng)的,轉(zhuǎn)子的每個(gè)齒上都帶有永久的磁性,是一塊永磁體,這就是“永磁式”的概念;再看外圈,這個(gè)就是定子,它是保持不動(dòng)的,實(shí)際上它是跟電機(jī)的外殼固定在一起的,它上面有8個(gè)齒,而每個(gè)齒上都纏上了一個(gè)線圈繞組,正對(duì)著的2個(gè)齒上的繞組又是串聯(lián)在一起的,也就是說正對(duì)著的2個(gè)繞組總是會(huì)同時(shí)導(dǎo)通或關(guān)斷的,如此就形成了4相,在圖中分別標(biāo)注為A-B-C-D,這就是“4相”的概念。

圖9-5 步進(jìn)電機(jī)內(nèi)部結(jié)構(gòu)示意圖
現(xiàn)在我們分析一下它的工作原理:
假定電機(jī)的起始狀態(tài)就如上圖所示,起始時(shí)是B相繞組的開關(guān)閉合,B相繞組導(dǎo)通,那么導(dǎo)通電流就會(huì)在正上和正下兩個(gè)定子齒上產(chǎn)生磁性,這兩個(gè)定子齒上的磁性就會(huì)對(duì)轉(zhuǎn)子上的0和3號(hào)齒產(chǎn)生最強(qiáng)的吸引力,就會(huì)如圖所示的那樣,轉(zhuǎn)子的的0號(hào)齒在正上、3號(hào)齒在正下而處于平衡狀態(tài);此時(shí)我們會(huì)發(fā)現(xiàn),轉(zhuǎn)子的1號(hào)齒與右上的定子齒也就是C相的一個(gè)繞組呈現(xiàn)一個(gè)很小的夾角,2號(hào)齒與右邊的定子齒也就是D相繞組呈現(xiàn)一個(gè)稍微大一點(diǎn)的夾角,很明顯這個(gè)夾角是1號(hào)齒和C繞組夾角的2倍,同理,左側(cè)的情況也是一樣的。
接下來,我們把B相繞組斷開,而使C相繞組導(dǎo)通,那么很明顯,右上的定子齒將對(duì)轉(zhuǎn)子1號(hào)齒產(chǎn)生最大的吸引力,而左下的定子齒將對(duì)轉(zhuǎn)子4號(hào)齒,產(chǎn)生最大的吸引力,在這個(gè)吸引力的作用下,轉(zhuǎn)子1、4號(hào)齒將對(duì)齊到右上和左下的定子齒上而保持平衡,如此,轉(zhuǎn)子就轉(zhuǎn)過了起始狀態(tài)時(shí)1號(hào)齒和C相繞組那個(gè)夾角的角度。
再接下來,斷開C相繞組,導(dǎo)通D相繞組,過程與上述的情況完全相同,最終將使轉(zhuǎn)子2、5號(hào)齒與定子D相繞組對(duì)齊,轉(zhuǎn)子又轉(zhuǎn)過了上述同樣的角度。
那么很明顯,當(dāng)A相繞組導(dǎo)通,即完成一個(gè)B-C-D-A的四節(jié)拍操作后,轉(zhuǎn)子的0、3號(hào)齒將由原來的對(duì)齊到上下2個(gè)定子齒,而變?yōu)榱藢?duì)齊到左上和右下的兩個(gè)定子齒上,即轉(zhuǎn)子轉(zhuǎn)過了一個(gè)定子齒的角度。依此類推,再來一個(gè)四節(jié)拍,轉(zhuǎn)子就將再轉(zhuǎn)過一個(gè)齒的角度,8個(gè)四節(jié)拍以后轉(zhuǎn)子將轉(zhuǎn)過完整的一圈,而其中單個(gè)節(jié)拍使轉(zhuǎn)子轉(zhuǎn)過的角度就很容計(jì)算出來了,即360度÷(8×4)=11.25度,這個(gè)值就叫做步進(jìn)角度。而上述這種工作模式就是步進(jìn)電機(jī)的單四拍模式——單相繞組通電四節(jié)拍。
我們?cè)賮碇v解一種具有更優(yōu)性能的工作模式,那就是在單四拍的每兩個(gè)節(jié)拍之間再插入一個(gè)雙繞組導(dǎo)通的中間節(jié)拍,組成八拍模式。比如,在從B相導(dǎo)通到C項(xiàng)導(dǎo)通的過程中,假如一個(gè)B相和C相同時(shí)導(dǎo)通的節(jié)拍,這個(gè)時(shí)候,由于B、C兩個(gè)繞組的定子齒對(duì)它們附近的轉(zhuǎn)子齒同時(shí)產(chǎn)生相同的吸引力,這將導(dǎo)致這兩個(gè)轉(zhuǎn)子齒的中心線對(duì)比到B、C兩個(gè)繞組的中心線上,也就是新插入的這個(gè)節(jié)拍使轉(zhuǎn)子轉(zhuǎn)過了上述單四拍模式中步進(jìn)角度的一半,即5.625度。這樣一來,就使轉(zhuǎn)動(dòng)精度增加了一倍,而轉(zhuǎn)子轉(zhuǎn)動(dòng)一圈則需要8×8=64拍了。另外,新增加的這個(gè)中間節(jié)拍,還會(huì)在原來單四拍的兩個(gè)節(jié)拍引力之間又加了一把引力,從而可以大大增加電機(jī)的整體扭力輸出,使電機(jī)更“有勁”了。
除了上述的單四拍和八拍的工作模式外,還有一個(gè)雙四拍的工作模式——雙繞組通電四節(jié)拍。其實(shí)就是把八拍模式中的兩個(gè)繞組同時(shí)通電的那四拍單獨(dú)拿出來,而舍棄掉單繞組通電的那四拍而已。其步進(jìn)角度同單四拍是一樣的,但由于它是兩個(gè)繞組同時(shí)導(dǎo)通,所以扭矩會(huì)比單四拍模式大,在此就不做過多解釋了。
八拍模式是這類4相步進(jìn)電機(jī)的最佳工作模式,能最大限度的發(fā)揮電機(jī)的各項(xiàng)性能,也是絕大多數(shù)實(shí)際工程中所選擇的模式,因此我們就重點(diǎn)來講解如何用單片機(jī)程序來控制電機(jī)按八拍模式工作。
1.3.3 如何讓電機(jī)轉(zhuǎn)動(dòng)
再重新看一下上面的步進(jìn)電機(jī)外觀圖和內(nèi)部結(jié)構(gòu)圖:步進(jìn)電機(jī)一共有5根引線,其中紅色的是公共端,連接到5V電源,接下來的橙、黃、粉、藍(lán)就對(duì)應(yīng)了A、B、C、D相;那么如果要導(dǎo)通A相繞組,就只需將橙色線接地即可,B相則黃色接地,依此類推;再根據(jù)上述單四拍和八拍工作過程的講解,可以得出下面的繞組控制順序表:
表9-1 八拍模式繞組控制順序表
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
P1-紅
|
VCC
|
VCC
|
VCC
|
VCC
|
VCC
|
VCC
|
VCC
|
VCC
|
P2-橙
|
GND
|
GND
|
|
|
|
|
|
GND
|
P3-黃
|
|
GND
|
GND
|
GND
|
|
|
|
|
P4-粉
|
|
|
|
GND
|
GND
|
GND
|
|
|
P5-藍(lán)
|
|
|
|
|
|
GND
|
GND
|
GND
|
我們板子上控制步進(jìn)電機(jī)部分是和板子上的顯示控制的74HC138譯碼器部分復(fù)用的P1.0到P1.3,這個(gè)部分我們?cè)?ldquo;全板子測(cè)試視頻”里邊已經(jīng)講過了,可以通過調(diào)整跳線帽實(shí)現(xiàn)步進(jìn)電機(jī)的控制,如圖9-6所示。
圖9-6 顯示譯碼器和步進(jìn)電機(jī)接口跳線帽
如果大家使用電機(jī)的話,需要把4個(gè)跳線帽都調(diào)到跳線組的左側(cè),即左側(cè)針和中間針連通,就可以使用P1.0到P1.3控制步進(jìn)電機(jī)了,要再使用顯示部分的話,就要再換回到右側(cè)了。那如果大家既想讓顯示部分正常工作,又想讓電機(jī)工作該怎么辦呢?跳線帽保持在右側(cè),用杜邦線把步進(jìn)電機(jī)的控制引腳(即左側(cè)的排針)連接到其它的暫不使用的單片機(jī)IO上即可。
再來看一下我們步進(jìn)電機(jī)的原理圖,步進(jìn)電機(jī)的控制電路如下:

圖9-7 步進(jìn)電機(jī)控制電路
誠然,單片機(jī)的IO口可以直接輸出0V和5V的電壓,但是電流驅(qū)動(dòng)能力,也就是帶載能力非常有限,所以我們?cè)诿肯嗟目刂凭上都增加一個(gè)三極管來提高驅(qū)動(dòng)能力。由圖中可以看出,若要使A相導(dǎo)通,則必須是Q2導(dǎo)通,此時(shí)A相也就是橙色線就相當(dāng)于接地了,于是A相繞組導(dǎo)通,此時(shí)單片機(jī)P1口低4位應(yīng)輸出0b1110,即0xE;如要A、B相同時(shí)導(dǎo)通,那么就是Q2、Q3導(dǎo)通,P1口低4位應(yīng)輸出0b1100,即0xC,依此類推,我們可以得到下面的八拍節(jié)拍的IO控制代碼數(shù)組:
unsigned char code BeatCode[8] = { 0xE, 0xC, 0xD, 0x9, 0xB, 0x3, 0x7, 0x6 };
到這里,似乎所有的邏輯問題都解決了,循環(huán)將這個(gè)數(shù)組內(nèi)的值送到P1口就行了。但是,只要再深入想一下就會(huì)發(fā)現(xiàn)還有個(gè)問題:多長時(shí)間送一次數(shù)據(jù),也就是說一個(gè)節(jié)拍要持續(xù)多長時(shí)間合適呢?是隨意的嗎?當(dāng)然不是了,這個(gè)時(shí)間是由步進(jìn)電機(jī)的啟動(dòng)頻率決定的。啟動(dòng)頻率,就是步進(jìn)電機(jī)在空載情況下能夠正常啟動(dòng)的最高脈沖頻率,如果脈沖頻率高于該值,電機(jī)就不能正常啟動(dòng)。下表是由廠家提供的步進(jìn)電機(jī)參數(shù)表:
表9-2 28BYJ-48步進(jìn)電機(jī)參數(shù)表
供電電壓
|
相數(shù)
|
相電阻
Ω
|
步進(jìn)角度
|
減速比
|
啟動(dòng)頻率
P.P.S
|
轉(zhuǎn)矩
g.cm
|
噪聲
dB
|
絕緣介
電強(qiáng)度
|
5V
|
4
|
50±10%
|
5.625/64
|
1:64
|
≥550
|
≥300
|
≤35
|
600VAC
|
表中給出的參數(shù)是≥550,單位是P.P.S,即每秒脈沖數(shù),這里的意思就是說:每個(gè)電機(jī)保證在你每秒給出550個(gè)步進(jìn)脈沖的情況下,電機(jī)可以啟動(dòng)。換算成單節(jié)拍持續(xù)時(shí)間就是1s÷550=1.8ms,那為了讓電機(jī)能夠啟動(dòng),我們控制節(jié)拍刷新時(shí)間大于1.8ms就可以了。有了這個(gè)參數(shù),我們就可以動(dòng)手寫出最簡單的電機(jī)轉(zhuǎn)動(dòng)程序了,如下:
#include<reg52.h>
unsigned char code BeatCode[8] = { //步進(jìn)電機(jī)節(jié)拍對(duì)應(yīng)到IO控制電平的代碼
0xE, 0xC, 0xD, 0x9, 0xB, 0x3, 0x7, 0x6
};
void delay(unsigned int cnt);
void main()
{
unsigned char buf;
unsigned char step = 0;
while(1)
{
buf = P1 & 0xF0; //用buf暫存P1口的高4位,而低4位清零
buf |= BeatCode[step]; //buf低4位改為相應(yīng)的節(jié)拍代碼值
P1 = buf; //修改后完畢后的值送回到P1口
step++; //步進(jìn)節(jié)拍遞增
step &= 0x07; //用“與”方式實(shí)現(xiàn)到8歸零
delay(200); //延時(shí)2ms,即2ms執(zhí)行一拍
}
}
void delay(unsigned int cnt)
{
while (cnt--);
}
趕快編譯下載到板子上試試吧!看看電機(jī)轉(zhuǎn)了沒有?記得換跳線哦!
1.3.4 轉(zhuǎn)動(dòng)精度與深入分析
轉(zhuǎn)是轉(zhuǎn)了,但是不是感覺有點(diǎn)不太對(duì)勁呢?太慢了?別急,咱們繼續(xù)。根據(jù)本章開頭講解的原理,八拍模式時(shí),步進(jìn)電機(jī)轉(zhuǎn)過一圈是需要64個(gè)節(jié)拍,而我們程序中是每個(gè)節(jié)拍持續(xù)2ms,那么轉(zhuǎn)一圈就應(yīng)該是128ms,即1秒鐘轉(zhuǎn)7圈多,可怎么看上去它好像是7秒多才轉(zhuǎn)了一圈呢?
那么,是時(shí)候來了解“永磁式減速步進(jìn)電機(jī)”中這個(gè)“減速的概念了”。下圖是這個(gè)28BYJ-48步進(jìn)電機(jī)的拆解圖,從圖中可以看到,位于最中心的那個(gè)白色小齒輪才是步進(jìn)電機(jī)的轉(zhuǎn)子輸出,64個(gè)節(jié)拍只是讓這個(gè)小齒輪轉(zhuǎn)了一圈,然后它帶動(dòng)那個(gè)淺藍(lán)色的大齒輪,這就是一級(jí)減速。大家看一下右上方的白色齒輪的結(jié)構(gòu),除電機(jī)轉(zhuǎn)子和最終輸出軸外的3個(gè)傳動(dòng)齒輪都是這樣的結(jié)構(gòu),一層多齒和一層少齒構(gòu)成,而每一個(gè)齒輪都用自己的少齒層去驅(qū)動(dòng)下一個(gè)齒輪的多齒層,這樣每2個(gè)齒輪都構(gòu)成一級(jí)減速,一共就有了4級(jí)減速,那么總的減速比是多少呢?即轉(zhuǎn)子要轉(zhuǎn)多少圈最終輸出軸才轉(zhuǎn)一圈呢?

圖9-8 步進(jìn)電機(jī)內(nèi)部齒輪示意圖
回頭看一下電機(jī)參數(shù)表中的減速比這個(gè)參數(shù)吧——1:64,轉(zhuǎn)子轉(zhuǎn)64圈,最終輸出軸才會(huì)轉(zhuǎn)一圈,也就是需要64×64=4096個(gè)節(jié)拍輸出軸才轉(zhuǎn)過一圈,2ms×4096=8192ms,8秒多才轉(zhuǎn)一圈呢,是不是更剛才的實(shí)驗(yàn)結(jié)果正好吻合了?4096個(gè)節(jié)拍轉(zhuǎn)動(dòng)一圈,那么一個(gè)節(jié)拍轉(zhuǎn)動(dòng)的角度——步進(jìn)角度就是360/4096,看一下表中的步進(jìn)角度參數(shù)5.625/64,算一下就知道這兩個(gè)值是相等的,一切都已吻合了。
OK,關(guān)于基本的控制原理本該到這里就全部結(jié)束了,但是,我們希望大家都能培養(yǎng)一種“實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn)”的思維方式!回想一下,步進(jìn)電機(jī)最大的特點(diǎn)是什么?精確控制轉(zhuǎn)動(dòng)量!那么我們是不是應(yīng)該檢驗(yàn)一下它到底是不是能精確呢?精確到什么程度呢?怎么來檢驗(yàn)?zāi)?讓它轉(zhuǎn)過90度,然后量一下準(zhǔn)不準(zhǔn)?也行,但是如果它只差了1度甚至不到1度,你能準(zhǔn)確測(cè)量出來嗎?在沒有精密儀器的情況很難。我們還是讓它多轉(zhuǎn)幾個(gè)整圈,看看它最后停下的位置還是不是原來的位置。對(duì)應(yīng)的,我們把程序修改一下,以方便控制電機(jī)轉(zhuǎn)過任意的圈數(shù)。
#include<reg52.h>
void delay(unsigned int cnt);
void TrunMotor(unsigned long angle);
void main()
{
TrunMotor(360*25); //360度*25,即25圈
while(1);
}
void TrunMotor(unsigned long angle)
{
unsigned char buf;
unsigned char step = 0;
unsigned long beats = 0;
unsigned char code BeatCode[8] = { //步進(jìn)電機(jī)節(jié)拍對(duì)應(yīng)到IO控制電平的代碼
0xE, 0xC, 0xD, 0x9, 0xB, 0x3, 0x7, 0x6
};
beats = (angle * 4096) / 360; //計(jì)算需要的節(jié)拍數(shù),4096拍對(duì)應(yīng)一圈
while (beats--)
{
buf = P1 & 0xF0; //用buf暫存P1口的高4位,而低4位清零
buf |= BeatCode[step]; //buf低4位改為相應(yīng)的節(jié)拍代碼值
P1 = buf; //修改后完畢后的值送回到P1口
step++; //步進(jìn)節(jié)拍遞增
step &= 0x07; //用“與”方式實(shí)現(xiàn)到8歸零
delay(200); //延時(shí)2ms,即2ms執(zhí)行一拍
}
P1 |= 0x0F; //關(guān)閉電機(jī)所有的相
}
void delay(unsigned int cnt)
{
while (cnt--);
}
上述程序中,我們先編寫了一個(gè)控制電機(jī)轉(zhuǎn)過指定角度的函數(shù),這個(gè)角度值由函數(shù)的形式參數(shù)給出,然后在主函數(shù)中就可以方便的通過更改調(diào)用時(shí)的實(shí)際參數(shù)來控制電機(jī)轉(zhuǎn)過任意的角度了。我們用了360*25,也就是25圈,當(dāng)然你也可以隨意改為其它的值,看看是什么結(jié)果。我們的程序會(huì)執(zhí)行25*8=200秒的時(shí)間,先記下輸出軸的初始位置,然后上電并耐心等它執(zhí)行完畢,看一下,是不是……有誤差?怎么回事,哪兒出問題了,不是說能精確控制轉(zhuǎn)動(dòng)量嗎?
這個(gè)問題其實(shí)是出在了減速比上,再來看一下,廠家給出的減速比是1:64,不管是哪個(gè)廠家生產(chǎn)的電機(jī),只要型號(hào)是28BYJ-48,其標(biāo)稱的減速比就都是1:64。但實(shí)際上呢?經(jīng)過我們的拆解計(jì)算發(fā)現(xiàn):真實(shí)準(zhǔn)確的減速比并不是這個(gè)值1:64,而是1:63.684!得出這個(gè)數(shù)據(jù)的方法也很簡單,實(shí)際數(shù)一下每個(gè)齒輪的齒數(shù),然后將各級(jí)減速比相乘,就可以得出結(jié)果了,實(shí)測(cè)的減速比為(32/9)*(22/11)*(26/9)*(31/10)≈63.684,從而得出實(shí)際誤差為0.0049,即約為百分之0.5,轉(zhuǎn)100圈就會(huì)差出半圈,那么我們剛才轉(zhuǎn)了25圈,是不是就差了八分之一圈了,也就是45度,看一下剛才的誤差是45度吧。那么按照1:63.684的實(shí)際減速比,可以得出轉(zhuǎn)過一圈所需要節(jié)拍數(shù)是64*63.684≈4076。那么就把上面程序中電機(jī)驅(qū)動(dòng)函數(shù)里的4096改成4076再試一下吧。是不是看不出絲毫的誤差了?但實(shí)際上誤差還是存在的,因?yàn)樯厦娴挠?jì)算結(jié)果都是約等得出的,實(shí)際誤差大約是0.000056,即萬分之0.56,轉(zhuǎn)一萬圈才會(huì)差出半圈,已經(jīng)可以忽略不計(jì)了。
那么廠家的參數(shù)為什么會(huì)有誤差呢?難道廠家不知道嗎?要解釋這個(gè)問題,我們得回到實(shí)際應(yīng)用中,步進(jìn)電機(jī)最通常的目的是控制目標(biāo)轉(zhuǎn)過一定的角度,通常都是在360度以內(nèi)的,而這個(gè)28BYJ-48最初的設(shè)計(jì)目的是用來控制空調(diào)的扇葉的,扇葉的活動(dòng)范圍是不會(huì)超過180度的,所以在這種應(yīng)用場(chǎng)合下,廠商給出一個(gè)近似的整數(shù)減速比1:64已經(jīng)足夠精確了,這也是合情合理的。然而,正如我們的程序那樣,我們不一定是要用它來驅(qū)動(dòng)空調(diào)扇葉,我們可以讓它轉(zhuǎn)動(dòng)很多圈來干別的,這個(gè)時(shí)候就需要更為精確的數(shù)據(jù)了,這也是我們希望讀者都能了解并掌握的,就是說我們要能自己“設(shè)計(jì)”系統(tǒng)并解決其中發(fā)現(xiàn)的問題,而不要被所謂的“現(xiàn)成的方案”限制住思路。
1.3.5 編寫實(shí)用程序的基礎(chǔ)
解決了精度問題,讓我們?cè)俅位氐轿覀兊碾姍C(jī)控制程序上吧。上面給出的兩個(gè)例程都不是實(shí)用的程序,為什么?因?yàn)槌绦蛑写嬖诖蠖蔚难訒r(shí),而在延時(shí)的時(shí)候是什么其它的事都干不了的,想想第二個(gè)程序,整整200秒什么別的事都干不了,這在實(shí)際的控制系統(tǒng)中是絕對(duì)不允許的。那么怎么改造一下呢?當(dāng)然還是用定時(shí)中斷來完成了,既然每個(gè)節(jié)拍持續(xù)時(shí)間是2ms,那我們直接用定時(shí)器定時(shí)2ms來刷新節(jié)拍就行了。關(guān)于定時(shí)器的一些寄存器的使用和設(shè)置教程可到 http://www.torrancerestoration.com 去看第17課里面有詳解,改造后的程序如下:
#include<reg52.h>
unsigned long beats = 0;
void TrunMotor(unsigned long angle);
void main()
{
//配置T0工作在模式1,定時(shí)2ms
TMOD = 0x01;
TH0 = 0xF8;
TL0 = 0xCD;
TR0 = 1;
ET0 = 1;
EA = 1;
TrunMotor(360*2+180); //控制電機(jī)轉(zhuǎn)動(dòng)2圈半
while(1);
}
void TrunMotor(unsigned long angle)
{
//在計(jì)算前關(guān)閉中斷,完成后再打開,以避免中斷打斷計(jì)算過程而造成錯(cuò)誤
EA = 0;
beats = (angle * 4076) / 360; //實(shí)測(cè)為4076拍轉(zhuǎn)動(dòng)一圈
EA = 1;
}
void InterruptTimer0() interrupt 1
{
unsigned char buf;
static unsigned char step = 0; //使用靜態(tài)變量以保存住前一次的值
unsigned char code BeatCode[8] = { //步進(jìn)電機(jī)節(jié)拍對(duì)應(yīng)到IO控制電平的代碼
0xE, 0xC, 0xD, 0x9, 0xB, 0x3, 0x7, 0x6
};
TH0 = 0xF8; //溢出后進(jìn)入中斷重新賦值
TL0 = 0xCD;
if (beats != 0)
{
buf = P1 & 0xF0; //用buf暫存P1口的高4位,而低4位清零
buf |= BeatCode[step]; //buf低4位改為相應(yīng)的節(jié)拍代碼值
P1 = buf; //修改后完畢后的值送回到P1口
step++; //步進(jìn)節(jié)拍遞增
step &= 0x07; //用“與”方式實(shí)現(xiàn)到8歸零
beats--;
}
else
{
P1 |= 0x0F; //關(guān)閉電機(jī)所有的相
}
}
程序還是比較簡單的,電機(jī)轉(zhuǎn)動(dòng)的啟動(dòng)函數(shù)TrunMotor只負(fù)責(zé)計(jì)算一個(gè)需要的總節(jié)拍數(shù)beats,然后在中斷函數(shù)內(nèi)檢測(cè)這個(gè)變量,不為0時(shí)就執(zhí)行節(jié)拍刷新操作,同時(shí)將其減1,直到減到0位置。
這里,我們要特別說明一下的是TrunMotor函數(shù)中對(duì)EA的兩次操作。我們可以看到對(duì)beats的賦值計(jì)算語句是夾在EA=0;EA=1;這兩行語句中間的,也就是說這行賦值計(jì)算語句在執(zhí)行前先關(guān)閉了中斷;而等它執(zhí)行完后,才又重新打開了中斷;在它執(zhí)行過程中CPU是不會(huì)響應(yīng)中斷的,即中斷函數(shù)InterruptTimer0不會(huì)被執(zhí)行;即使這時(shí)候定時(shí)器溢出了,中斷發(fā)生了,也只能等待在EA重新置1后,才能得到響應(yīng),中斷函數(shù)InterruptTimer0才會(huì)被執(zhí)行。
那么為什么要這么做呢?我們來想一下:一開始就提到了,我們所使用的STC89C52單片機(jī)是8位單片機(jī),這個(gè)8位的概念就是說單片機(jī)操作數(shù)據(jù)時(shí)都是按8位即1個(gè)字節(jié)進(jìn)行的,那么要操作多個(gè)字節(jié)(不論是讀還是寫)就必須分多次進(jìn)行了;而我們程序中定義的beats這個(gè)變量是unsigned long型,它要占用4個(gè)字節(jié),那么對(duì)它的賦值最少也要分4次才能完成了;我們想象一下,假如在完成了其中第一個(gè)字節(jié)的賦值后,恰好中斷發(fā)生了,InterruptTimer0函數(shù)得到執(zhí)行,而這個(gè)函數(shù)內(nèi)可能會(huì)對(duì)beats進(jìn)行減1的操作,減法就有可能發(fā)生借位,借位就會(huì)改變其它的字節(jié),但因?yàn)榇藭r(shí)其它的字節(jié)還沒有被賦入新值,于是錯(cuò)誤就會(huì)發(fā)生了,減1所得到的結(jié)果就不是預(yù)期的值了!所以要避免這種錯(cuò)誤的發(fā)生就得先暫時(shí)關(guān)閉中斷,等賦值完成后再打開中斷;而如果我們使用的是char或bit型變量的話,因?yàn)樗鼈兌际窃?/font>CPU的一次操作中就完成的,所以即使不關(guān)中斷,也不會(huì)發(fā)生錯(cuò)誤。問題分析清楚了,如何取舍還得根據(jù)實(shí)際情況來,遇上這類問題的時(shí)候多多考慮考慮吧。
1.3.6 包含綜合應(yīng)用的實(shí)用程序
上面我們雖然完成了用中斷控制轉(zhuǎn)動(dòng)的程序,但實(shí)際上這個(gè)程序還是沒多少實(shí)用價(jià)值的,我們不能每次想讓它轉(zhuǎn)動(dòng)的時(shí)候都上下電啊,是吧。還有它不但得能正轉(zhuǎn)還得能反轉(zhuǎn)啊,也就是說不但能轉(zhuǎn)過去,還得能轉(zhuǎn)回來呀。好吧,我們就來做一個(gè)實(shí)例程序吧,結(jié)合第八章的按鍵程序,我們?cè)O(shè)計(jì)這樣一個(gè)程序:按數(shù)字鍵1-9,控制電機(jī)轉(zhuǎn)過1-9圈;配合上下鍵改變轉(zhuǎn)動(dòng)方向,按向上鍵后正向轉(zhuǎn)1-9圈,向下鍵則反向轉(zhuǎn)1-9圈;左鍵固定正轉(zhuǎn)90度,右鍵固定反轉(zhuǎn)90;Esc鍵終止轉(zhuǎn)動(dòng)。程序如下:
#include <reg52.h>
sbit KEY_IN_1 = P2^4; //矩陣按鍵的掃描輸入引腳1
sbit KEY_IN_2 = P2^5; //矩陣按鍵的掃描輸入引腳2
sbit KEY_IN_3 = P2^6; //矩陣按鍵的掃描輸入引腳3
sbit KEY_IN_4 = P2^7; //矩陣按鍵的掃描輸入引腳4
sbit KEY_OUT_1 = P2^3; //矩陣按鍵的掃描輸出引腳1
sbit KEY_OUT_2 = P2^2; //矩陣按鍵的掃描輸出引腳2
sbit KEY_OUT_3 = P2^1; //矩陣按鍵的掃描輸出引腳3
sbit KEY_OUT_4 = P2^0; //矩陣按鍵的掃描輸出引腳4
const unsigned char code KeyCodeMap[4][4] = { //矩陣按鍵編號(hào)到PC標(biāo)準(zhǔn)鍵盤鍵碼的映射表
{ '1', '2', '3', 0x26 }, //數(shù)字鍵1、數(shù)字鍵2、數(shù)字鍵3、向上鍵
{ '4', '5', '6', 0x25 }, //數(shù)字鍵4、數(shù)字鍵5、數(shù)字鍵6、向左鍵
{ '7', '8', '9', 0x28 }, //數(shù)字鍵7、數(shù)字鍵8、數(shù)字鍵9、向下鍵
{ '0', 0x1B, 0x0D, 0x27 } //數(shù)字鍵0、ESC鍵、 回車鍵、 向右鍵
};
unsigned char KeySta[4][4] = { //全部矩陣按鍵的當(dāng)前狀態(tài)
{1, 1, 1, 1},
{1, 1, 1, 1},
{1, 1, 1, 1},
{1, 1, 1, 1}
};
signed long beats = 0; //步進(jìn)電機(jī)轉(zhuǎn)動(dòng)的總節(jié)拍數(shù)
void KeyAction(unsigned char keycode);
void main(void)
{
unsigned char i, j;
unsigned char backup[4][4] = { //按鍵值備份,保存前一次的值
{1, 1, 1, 1},
{1, 1, 1, 1},
{1, 1, 1, 1},
{1, 1, 1, 1}
};
//配置T0工作在模式1,定時(shí)1ms
TMOD = 0x01;
TH0 = 0xFC;
TL0 = 0x67;
TR0 = 1;
ET0 = 1;
EA = 1;
while(1)
{
//檢索按鍵狀態(tài)的變化
for (i=0; i<4; i++)
{
for (j=0; j<4; j++)
{
if (backup[i][j] != KeySta[i][j])
{
if (backup[i][j] == 0) //按鍵彈起時(shí)執(zhí)行動(dòng)作
{
KeyAction(KeyCodeMap[i][j]);
}
backup[i][j] = KeySta[i][j];
}
}
}
}
}
void TrunMotor(signed long angle)
{
//在計(jì)算前關(guān)閉中斷,完成后再打開,以避免中斷打斷計(jì)算過程而造成錯(cuò)誤
EA = 0;
beats = (angle * 4076) / 360; //實(shí)測(cè)為4076拍轉(zhuǎn)動(dòng)一圈
EA = 1;
}
void StopMotor()
{
EA = 0;
beats = 0;
EA = 1;
}
void KeyAction(unsigned char keycode)
{
static bit dirMotor = 0; //電機(jī)轉(zhuǎn)動(dòng)方向
if ((keycode>='1') && (keycode<='9')) //控制電機(jī)轉(zhuǎn)動(dòng)1-9圈
{
if (dirMotor == 0)
{
TrunMotor(360*(keycode-'0'));
}
else
{
TrunMotor(-360*(keycode-'0'));
}
}
else if (keycode == 0x26) //向上鍵,控制轉(zhuǎn)動(dòng)方向?yàn)檎D(zhuǎn)
{
dirMotor = 0;
}
else if (keycode == 0x28) //向下鍵,控制轉(zhuǎn)動(dòng)方向?yàn)榉崔D(zhuǎn)
{
dirMotor = 1;
}
else if (keycode == 0x25) //向左鍵,固定正轉(zhuǎn)90度
{
TrunMotor(90);
}
else if (keycode == 0x27) //向右鍵,固定反轉(zhuǎn)90度
{
TrunMotor(-90);
}
else if (keycode == 0x1B) //Esc鍵,停止轉(zhuǎn)動(dòng)
{
StopMotor();
}
}
void MotorDrive()
{
unsigned char buf;
static unsigned char step = 0; //使用靜態(tài)變量以保存住前一次的值
unsigned char code BeatCode[8] = { //步進(jìn)電機(jī)節(jié)拍對(duì)應(yīng)到IO控制電平的代碼
0xE, 0xC, 0xD, 0x9, 0xB, 0x3, 0x7, 0x6
};
if (beats != 0)
{
if (beats > 0)
{
step++; //正轉(zhuǎn)時(shí)步進(jìn)節(jié)拍遞增
step &= 0x07; //用“與”方式實(shí)現(xiàn)到8歸零
beats--; //正轉(zhuǎn)時(shí)節(jié)拍計(jì)數(shù)遞減
}
else
{
step--; //反轉(zhuǎn)時(shí)步進(jìn)節(jié)拍遞增
step &= 0x07; //用“與”方式同樣可以實(shí)現(xiàn)到-1時(shí)歸7
beats++; //反轉(zhuǎn)時(shí)節(jié)拍計(jì)數(shù)遞增
}
buf = P1 & 0xF0; //用buf暫存P1口的高4位,而低4位清零
buf |= BeatCode[step]; //buf低4位改為相應(yīng)的節(jié)拍代碼值
P1 = buf; //修改后完畢后的值送回到P1口
}
else
{
P1 |= 0x0F; //節(jié)拍計(jì)數(shù)到0時(shí)關(guān)閉電機(jī)所有的相
}
}
void KeyScan()
{
unsigned char i;
static unsigned char keyout = 0; //矩陣按鍵掃描輸出計(jì)數(shù)器
static unsigned char keybuf[4][4] = { //按鍵掃描緩沖區(qū),保存一段時(shí)間內(nèi)的掃描值
{0xFF, 0xFF, 0xFF, 0xFF},
{0xFF, 0xFF, 0xFF, 0xFF},
{0xFF, 0xFF, 0xFF, 0xFF},
{0xFF, 0xFF, 0xFF, 0xFF}
};
//將一行的4個(gè)按鍵值移入緩沖區(qū)
keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;
keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;
keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;
keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;
//消抖后更新按鍵狀態(tài)
for (i=0; i<4; i++) //每行4個(gè)按鍵,所以循環(huán)4次
{
if ((keybuf[keyout][i] & 0x0F) == 0x00)
{ //連續(xù)4次掃描值為0,即16ms(4*4ms)內(nèi)都只檢測(cè)到按下狀態(tài)時(shí),可認(rèn)為按鍵已按下
KeySta[keyout][i] = 0;
}
else if ((keybuf[keyout][i] & 0x0F) == 0x0F)
{ //連續(xù)4次掃描值為1,即16ms(4*4ms)內(nèi)都只檢測(cè)到彈起狀態(tài)時(shí),可認(rèn)為按鍵已彈起
KeySta[keyout][i] = 1;
}
}
//執(zhí)行下一次的掃描輸出
keyout++;
keyout &= 0x03;
switch (keyout)
{
case 0:
KEY_OUT_4 = 1;
KEY_OUT_1 = 0;
break;
case 1:
KEY_OUT_1 = 1;
KEY_OUT_2 = 0;
break;
case 2:
KEY_OUT_2 = 1;
KEY_OUT_3 = 0;
break;
case 3:
KEY_OUT_3 = 1;
KEY_OUT_4 = 0;
break;
default:
break;
}
}
void InterruptTimer0() interrupt 1
{
static bit div = 0;
TH0 = 0xFC; //溢出后進(jìn)入中斷重新賦值,定時(shí)1ms
TL0 = 0x67;
KeyScan(); //執(zhí)行按鍵掃描
//用一個(gè)靜態(tài)bit變量實(shí)現(xiàn)二分頻,即2ms定時(shí),用于驅(qū)動(dòng)電機(jī)
div = ~div;
if (div == 1)
{
MotorDrive();
}
}
這個(gè)程序是第八章和本章知識(shí)的一個(gè)綜合——用按鍵控制步進(jìn)電機(jī)轉(zhuǎn)動(dòng)。程序中有這么幾點(diǎn)值得注意,我們分述如下:
1、針對(duì)電機(jī)要完成正轉(zhuǎn)和反轉(zhuǎn)兩個(gè)不同的操作,我們并沒有使用正轉(zhuǎn)啟動(dòng)函數(shù)和反轉(zhuǎn)啟動(dòng)函數(shù)這么兩個(gè)函數(shù)來完成,也沒有在啟動(dòng)函數(shù)定義的時(shí)候增加一個(gè)形式參數(shù)來指明其方向。我們這里的啟動(dòng)函數(shù)void TrunMotor(signed long angle)與單向正轉(zhuǎn)時(shí)的啟動(dòng)函數(shù)唯一的區(qū)別就是把形式參數(shù)angle的類型從unsigned long改為了signed long,我們用有符號(hào)數(shù)固有的正負(fù)特性來區(qū)分正轉(zhuǎn)與反轉(zhuǎn),正數(shù)表示正轉(zhuǎn)angle度,負(fù)數(shù)就表示反轉(zhuǎn)angle度,這樣處理是不是很簡潔又很明了呢?而你對(duì)有符號(hào)數(shù)和無符號(hào)數(shù)的區(qū)別用法是不是也更有體會(huì)了?
2、針對(duì)終止電機(jī)轉(zhuǎn)動(dòng)的操作,我們定義了一個(gè)單獨(dú)的StopMotor函數(shù)來完成,盡管這個(gè)函數(shù)非常簡單,盡管它也只在Esc按鍵分支內(nèi)被調(diào)用了,但我們?nèi)匀话阉鼏为?dú)提出來作為了一個(gè)函數(shù)。而這種做法就是基于這樣一條編程原則:盡可能用單獨(dú)的函數(shù)來完成硬件的某種操作,當(dāng)一個(gè)硬件包含多個(gè)的操作時(shí),把這些操作函數(shù)組織在一起,形成一個(gè)對(duì)上層的統(tǒng)一接口。這樣的層次化處理,會(huì)使得整個(gè)程序條理清晰,即有利于程序的調(diào)試維護(hù),又有利于功能的擴(kuò)充。
3、中斷函數(shù)中要處理按鍵掃描和電機(jī)驅(qū)動(dòng)兩件事情,而為了避免中斷函數(shù)過于復(fù)雜,我們就又分出了按鍵掃描和電機(jī)驅(qū)動(dòng)兩個(gè)函數(shù)(這也同樣符合上述2的編程原則),而中斷函數(shù)的邏輯就變得簡潔而清晰了。這里還有個(gè)矛盾,就是按鍵掃描我們選擇的定時(shí)時(shí)間是1ms,而本章之前的實(shí)例中電機(jī)節(jié)拍持續(xù)時(shí)間都是2ms;很顯然,用1ms的定時(shí)可以定出2ms的間隔,而用2ms的定時(shí)卻得不到準(zhǔn)確的1ms間隔;所以我們的做法就是,定時(shí)器依然定時(shí)1ms,然后用一個(gè)bit變量做標(biāo)志,沒1ms改變一次它的值,而我們只選擇值為1的時(shí)候執(zhí)行一次動(dòng)作,這樣就是2ms的間隔了;如果我要3ms、4ms……呢,把bit改為char或int型,然后對(duì)它們遞增,判斷到哪個(gè)值該歸零,就可以了;這就是在硬件定時(shí)器的基礎(chǔ)上實(shí)現(xiàn)準(zhǔn)確的軟件定時(shí),其實(shí)類似的操作我們?cè)谥v數(shù)碼管的時(shí)候也用過了,回想一下吧。
1.4 蜂鳴器的學(xué)習(xí)
蜂鳴器從結(jié)構(gòu)區(qū)分分為壓電式蜂鳴器和電磁式蜂鳴器。壓電式為壓電陶瓷片發(fā)音,電流比較小一些,電磁式蜂鳴器為線圈通電震動(dòng)發(fā)音,體積比較小。
按照驅(qū)動(dòng)方式分為有源蜂鳴器和無源蜂鳴器。這里的有源和無源不是指電源,而是振蕩源。有源蜂鳴器內(nèi)部帶了振蕩源,如圖9-9所示中,給了BUZZ引腳一個(gè)低電平,蜂鳴器就會(huì)直接響。而無源蜂鳴器內(nèi)部是不帶振蕩源的,要讓他響必須給500Hz到4.5kHz之間的脈沖頻率信號(hào)來驅(qū)動(dòng)它才會(huì)響。有源蜂鳴器往往比無源蜂鳴器貴一些,因?yàn)槔镞叾嗔苏袷庪娐,?qū)動(dòng)發(fā)音也簡單,靠電平就可以驅(qū)動(dòng),而無源蜂鳴器價(jià)格比較便宜,此外無源蜂鳴器聲音頻率可以控制,而音階與頻率又有確定的對(duì)應(yīng)關(guān)系,因此就可以做出來“do re mi fa sol la si”的效果,可以用它制作出簡單的音樂曲目,比如生日歌、兩只老虎等等。

圖9-9 蜂鳴器工作原理圖
我們來看一下圖9-9,蜂鳴器電流依然相對(duì)較大,因此需要用三極管驅(qū)動(dòng),并且加了一個(gè)100歐的電阻作為限流電阻。此外還加了一個(gè)D4二極管,這個(gè)二極管叫做續(xù)流二極管。我們的蜂鳴器是感性器件,當(dāng)三極管導(dǎo)通給蜂鳴器供電時(shí),就會(huì)有導(dǎo)通電流流過蜂鳴器。而我們知道,電感的一個(gè)特點(diǎn)就是電流不能突變,導(dǎo)通時(shí)電流是逐漸加大的,這點(diǎn)沒有問題,但當(dāng)關(guān)斷時(shí),經(jīng)“電源-三極管-蜂鳴器-地”這條回路就截?cái)嗔,過不了任何電流了,那么儲(chǔ)存的電流往哪兒去呢,就是經(jīng)過這個(gè)D4和蜂鳴器自身的環(huán)路來消耗掉了,從而就避免了關(guān)斷時(shí)由于電感電流造成的反向沖擊。接續(xù)關(guān)斷時(shí)的電流,這就是續(xù)流二極管名稱的由來。
蜂鳴器經(jīng)常用于電腦、打印機(jī)、萬用表這些設(shè)備上做提示音,提示音一般也很簡單,就是簡單發(fā)出個(gè)聲音就行,我們程序簡單做了個(gè)4kHZ頻率下的發(fā)聲和1kHZ頻率下的發(fā)聲程序,同學(xué)們自己比較一下即可。
#include<reg52.h>
sbit BUZZ = P1^6; //蜂鳴器控制引腳
unsigned char T0LoadH = 0; //T0重載值的高字節(jié)
unsigned char T0LoadL = 0; //T0重載值的低字節(jié)
void OpenBuzz(unsigned int frequ);
void StopBuzz();
void main()
{
unsigned int i=0;
TMOD = 0x01; //配置T0工作在模式1,暫不啟動(dòng)
EA = 1; //使能全局中斷
while (1)
{
OpenBuzz(4000); //以4KHz的頻率啟動(dòng)蜂鳴器
for (i=0; i<40000; i++);
StopBuzz();
for (i=0; i<40000; i++);
OpenBuzz(1000); //以1KHz的頻率啟動(dòng)蜂鳴器
for (i=0; i<40000; i++);
StopBuzz();
for (i=0; i<40000; i++);
}
}
void OpenBuzz(unsigned int frequ)
{
unsigned int reload;
reload = 65536 - (11059200/12) / (frequ*2); //由給定頻率值計(jì)算定時(shí)器重載值
T0LoadH = reload >> 8; //16位重載值分解為高低兩個(gè)字節(jié)
T0LoadL = reload;
TH0 = 0xFF; //設(shè)定一個(gè)接近溢出的初值,以使定時(shí)器馬上投入工作
TL0 = 0xFE;
ET0 = 1; //使能T0中斷
TR0 = 1; //啟動(dòng)T0
}
void StopBuzz()
{
ET0 = 0; //禁用T0中斷
TR0 = 0; //停止T0
}
void InterruptTimer0() interrupt 1
{
TH0 = T0LoadH; //溢出后進(jìn)入中斷重新賦值
TL0 = T0LoadL;
BUZZ = ~BUZZ; //反轉(zhuǎn)蜂鳴器控制電平
}
另外用蜂鳴器來輸出音樂,僅僅是好玩而已,應(yīng)用很少,里邊包含了音階、樂譜的相關(guān)內(nèi)容,程序也有一點(diǎn)復(fù)雜,所以就不詳細(xì)給大家去講解了。我僅僅寫了個(gè)《兩只老虎》的程序,大家下載到板子上玩玩,滿足一下好奇心。
#include<reg52.h>
//中音1-7和高音1-7對(duì)應(yīng)頻率列表
unsigned int code NoteFrequ[] = {
523, 587, 659, 698, 784, 880, 988, //中音1-7
1047, 1175, 1319, 1397, 1568, 1760, 1976 //高音1-7
};
//中音1-7和高音1-7對(duì)應(yīng)的定時(shí)器重載值
unsigned int code NoteReload[] = {
65536 - (11059200/12) / (523*2),
65536 - (11059200/12) / (587*2),
65536 - (11059200/12) / (659*2),
65536 - (11059200/12) / (698*2),
65536 - (11059200/12) / (784*2),
65536 - (11059200/12) / (880*2),
65536 - (11059200/12) / (988*2),
65536 - (11059200/12) / (1047*2),
65536 - (11059200/12) / (1175*2),
65536 - (11059200/12) / (1319*2),
65536 - (11059200/12) / (1397*2),
65536 - (11059200/12) / (1568*2),
65536 - (11059200/12) / (1760*2),
65536 - (11059200/12) / (1976*2),
};
sbit BUZZ = P1^6; //蜂鳴器控制引腳
bit enable = 1; //蜂鳴器發(fā)聲使能標(biāo)志
bit tmrflg = 0; //定時(shí)器中斷完成標(biāo)志
unsigned char T0LoadH = 0xFF; //T0重載值的高字節(jié)
unsigned char T0LoadL = 0x00; //T0重載值的低字節(jié)
void PlayTwoTiger();
void main()
{
unsigned int i=0;
TMOD = 0x01; //配置T0工作在模式1
TH0 = T0LoadH;
TL0 = T0LoadL;
ET0 = 1; //使能T0中斷
TR0 = 1; //啟動(dòng)T0
EA = 1; //使能全局中斷
while (1)
{
PlayTwoTiger();
for (i=0; i<40000; i++);
}
}
void PlayTwoTiger()
{
unsigned char beat; //記錄當(dāng)前節(jié)拍索引
unsigned char note; //當(dāng)前節(jié)拍對(duì)應(yīng)的音符
unsigned int time = 0; //當(dāng)前節(jié)拍計(jì)時(shí)
unsigned int beatTime = 0; //當(dāng)前節(jié)拍總時(shí)間
unsigned int soundTime = 0; //當(dāng)前節(jié)拍需發(fā)聲時(shí)間
unsigned char code TwoTigerNote[] = { //兩只老虎音符表
1, 2, 3, 1, 1, 2, 3, 1, 3, 4, 5, 3, 4, 5,
5,6, 5,4, 3, 1, 5,6, 5,4, 3, 1, 1, 5, 1, 1, 5, 1,
};
unsigned char code TwoTigerBeat[] = { //兩只老虎節(jié)拍表,4表示一拍,1就是1/4拍,8就是2拍
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 4, 4, 8,
3,1, 3,1, 4, 4, 3,1, 3,1, 4, 4, 4, 4, 8, 4, 4, 8,
};
for (beat=0; beat用節(jié)拍索引作為循環(huán)變量
{
while (!tmrflg); //每次定時(shí)器中斷完成后,檢測(cè)并處理節(jié)拍
tmrflg = 0;
if (time == 0)
{
//啟動(dòng)一個(gè)新的節(jié)拍
note = TwoTigerNote[beat] - 1;
T0LoadH = NoteReload[note] >> 8;
T0LoadL = NoteReload[note];
beatTime = (TwoTigerBeat[beat] * NoteFrequ[note]) >> 2; //計(jì)算節(jié)拍總時(shí)間,右移2位相當(dāng)于除4,移位代替除法可以加快執(zhí)行速度
soundTime = beatTime - (beatTime >> 2); //計(jì)算發(fā)聲時(shí)間,為總時(shí)間的0.75,移位原理同上
enable = 1; //指示蜂鳴器開始發(fā)聲
time++;
}
else
{
if (time >= beatTime)
{
time = 0; //當(dāng)前持續(xù)時(shí)間到達(dá)節(jié)拍總時(shí)間時(shí)歸零,并遞增節(jié)拍索引,以準(zhǔn)備啟動(dòng)新節(jié)拍
beat++;
}
else
{
time++;
if (time == soundTime)
{
enable = 0; //當(dāng)前持續(xù)時(shí)間到達(dá)發(fā)聲時(shí)間后,指示關(guān)閉蜂鳴器,以插入0.25*總時(shí)間的靜音間隔,以區(qū)分連續(xù)的節(jié)拍
}
}
}
}
}
void InterruptTimer0() interrupt 1
{
TH0 = T0LoadH; //溢出后進(jìn)入中斷重新賦值
TL0 = T0LoadL;
tmrflg = 1;
if (enable == 1)
{
BUZZ = ~BUZZ; //反轉(zhuǎn)蜂鳴器控制電平
}
else
{
BUZZ = 1; //關(guān)閉蜂鳴器
}
}
1.5 作業(yè)
1、能夠理解清楚單片機(jī)IO口的結(jié)構(gòu)。
2、能夠看懂上下拉電阻的電路應(yīng)用并且熟練使用上下拉電阻。
3、理解28BYJ-48減速步進(jìn)電機(jī)的工作原理。
4、能夠熟練編寫步進(jìn)電機(jī)正反轉(zhuǎn)任意角度的程序。
5、學(xué)會(huì)蜂鳴器發(fā)聲的方法。
上一課:第八章 獨(dú)立按鍵和矩陣按鍵
下一課: |