找回密碼
 立即注冊(cè)

QQ登錄

只需一步,快速開始

搜索
查看: 44141|回復(fù): 30
打印 上一主題 下一主題
收起左側(cè)

第九章 步進(jìn)電機(jī)和蜂鳴器

  [復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
#
ID:1 發(fā)表于 2013-7-31 02:40 | 只看該作者 |只看大圖 回帖獎(jiǎng)勵(lì) |正序?yàn)g覽 |閱讀模式

對(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ì)波形的影響

綜合考慮,我們常用的上下拉電阻值大多選取在1k10k之間,具體到底多大通常要根據(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毫米

——表示是步進(jìn)電機(jī)

——表示是永磁式

——表示是減速型

48——表示四相八拍

1.3.2 28BYJ-48型步進(jìn)電機(jī)原理詳解

28BYJ-484相永磁式減速步進(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)子上的03號(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、BC、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.0P1.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.0P1.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口可以直接輸出0V5V的電壓,但是電流驅(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];  //buf4位改為相應(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];  //buf4位改為相應(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.6844076。那么就把上面程序中電機(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];  //buf4位改為相應(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)閉中斷,等賦值完成后再打開中斷;而如果我們使用的是charbit型變量的話,因?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];  //buf4位改為相應(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改為charint型,然后對(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)部是不帶振蕩源的,要讓他響必須給500Hz4.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ú)立按鍵和矩陣按鍵
下一課:

評(píng)分

參與人數(shù) 2黑幣 +10 收起 理由
無風(fēng)的晚上 + 5 很給力!
秋葉原48 + 5 贊一個(gè)!

查看全部評(píng)分

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

相關(guān)帖子

回復(fù)

使用道具 舉報(bào)

30#
ID:966468 發(fā)表于 2022-6-23 09:23 | 只看該作者
相當(dāng)不錯(cuò)的實(shí)例
回復(fù)

使用道具 舉報(bào)

29#
ID:815563 發(fā)表于 2020-10-28 16:35 | 只看該作者
學(xué)會(huì)單片機(jī)控制步進(jìn)電機(jī)原理,就把握了智能機(jī)械程控,進(jìn)入機(jī)器人編程控制的樂園。
回復(fù)

使用道具 舉報(bào)

28#
ID:702974 發(fā)表于 2020-3-13 11:01 | 只看該作者
步進(jìn)電機(jī)編程需要有一定程度的電機(jī)知識(shí)
回復(fù)

使用道具 舉報(bào)

27#
ID:380141 發(fā)表于 2018-8-27 18:03 | 只看該作者
關(guān)于音樂,應(yīng)介紹得再詳細(xì)些,可激發(fā)大家學(xué)習(xí)單片機(jī)的興趣。
回復(fù)

使用道具 舉報(bào)

26#
ID:93625 發(fā)表于 2018-7-3 08:30 | 只看該作者
繼續(xù)來學(xué)習(xí),這幾天時(shí)間比較緊張,能原來學(xué)習(xí)的時(shí)間少了。
回復(fù)

使用道具 舉報(bào)

25#
ID:320487 發(fā)表于 2018-5-5 07:07 | 只看該作者
終于到最后一章啦
先慢慢消化這幾章  
回復(fù)

使用道具 舉報(bào)

24#
ID:267915 發(fā)表于 2017-12-27 19:15 | 只看該作者

好帖子,
謝謝樓主
回復(fù)

使用道具 舉報(bào)

23#
ID:162190 發(fā)表于 2017-8-10 16:31 來自手機(jī) | 只看該作者
與非門 發(fā)表于 2016-3-7 11:58
void MotorDrive()
{
    unsigned char buf;

我說說我的理解吧,如果是有符號(hào)型就是-1,無符號(hào)就是255,兩者二進(jìn)制形式是一模一樣的,所以無所謂
回復(fù)

使用道具 舉報(bào)

22#
ID:162190 發(fā)表于 2017-6-15 16:31 | 只看該作者
關(guān)于步距角,B相導(dǎo)通后上下對(duì)齊,然后C導(dǎo)通,轉(zhuǎn)一個(gè)角度,D導(dǎo)通,又一個(gè)角度,A導(dǎo)通,右下對(duì)齊,
應(yīng)該是總共轉(zhuǎn)了三個(gè)角度,三個(gè)脈沖而不應(yīng)該是四拍啊
回復(fù)

使用道具 舉報(bào)

21#
ID:162190 發(fā)表于 2017-6-15 16:19 | 只看該作者
B-C-D-A這四次其實(shí)只轉(zhuǎn)動(dòng)了三次,B通電后上下才對(duì)齊到A通電總共三個(gè)脈沖,45度,為什么按4個(gè)脈沖?
第一次給B通電時(shí)0-3并未在上下對(duì)齊位置,要4個(gè)脈沖的話要把這個(gè)角度也算上把
回復(fù)

使用道具 舉報(bào)

20#
ID:202374 發(fā)表于 2017-5-18 23:17 來自手機(jī) | 只看該作者
這么棒的網(wǎng)站,我來晚了,感謝樓主
回復(fù)

使用道具 舉報(bào)

19#
ID:147475 發(fā)表于 2017-1-14 10:24 | 只看該作者
路人甲111 發(fā)表于 2016-4-22 16:37
這段是不是有點(diǎn)問題

改成:
   for (beat=0; beat<sizeof(TwoTigerNote); )  //用節(jié)拍索引作為循環(huán)變量
就行了
回復(fù)

使用道具 舉報(bào)

18#
ID:91274 發(fā)表于 2016-4-22 16:37 | 只看該作者
這段是不是有點(diǎn)問題

捕獲.PNG (16.86 KB, 下載次數(shù): 284)

捕獲.PNG
回復(fù)

使用道具 舉報(bào)

17#
ID:110961 發(fā)表于 2016-3-27 09:52 | 只看該作者
很詳細(xì)的教程,需要用心學(xué)習(xí),贊一個(gè)
回復(fù)

使用道具 舉報(bào)

16#
ID:98839 發(fā)表于 2016-3-7 11:58 | 只看該作者

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口
請(qǐng)問這個(gè)地方的setp是不是該定義成有符號(hào)字符型?
回復(fù)

使用道具 舉報(bào)

15#
ID:88716 發(fā)表于 2015-8-28 22:51 | 只看該作者
求直流電機(jī)用法
回復(fù)

使用道具 舉報(bào)

14#
ID:78515 發(fā)表于 2015-5-11 16:28 | 只看該作者
這個(gè)是金沙工作室的手把手教你學(xué)單片機(jī)C語言教程,寫的不錯(cuò)
回復(fù)

使用道具 舉報(bào)

13#
ID:78174 發(fā)表于 2015-4-29 22:51 | 只看該作者
好帖子,很受用
回復(fù)

使用道具 舉報(bào)

12#
ID:74245 發(fā)表于 2015-4-2 16:21 | 只看該作者
耐心看了,講得很詳細(xì)很易懂,適合我這種新手,辛苦了。
回復(fù)

使用道具 舉報(bào)

11#
ID:74784 發(fā)表于 2015-3-31 14:41 | 只看該作者
樓主寫的很好
我也買了一個(gè)28BYJ-48的步進(jìn)電機(jī)
正在學(xué)習(xí)呢
回復(fù)

使用道具 舉報(bào)

10#
ID:74982 發(fā)表于 2015-3-24 00:56 | 只看該作者
寫得好,容易懂!謝謝樓主!
回復(fù)

使用道具 舉報(bào)

9#
ID:74521 發(fā)表于 2015-3-11 21:24 | 只看該作者
真詳細(xì),樓主很有見地
回復(fù)

使用道具 舉報(bào)

8#
ID:73216 發(fā)表于 2015-2-3 18:22 | 只看該作者
感謝分享!
回復(fù)

使用道具 舉報(bào)

7#
ID:71391 發(fā)表于 2014-12-30 22:59 | 只看該作者
謝謝樓主 應(yīng)該不錯(cuò)
回復(fù)

使用道具 舉報(bào)

6#
ID:64575 發(fā)表于 2014-8-1 18:41 | 只看該作者
給力,非常好
回復(fù)

使用道具 舉報(bào)

5#
ID:60906 發(fā)表于 2014-4-20 17:48 | 只看該作者
努力學(xué)習(xí)中。。
帥氣,直流電機(jī)那怎么用。
回復(fù)

使用道具 舉報(bào)

地板
ID:59964 發(fā)表于 2014-4-3 16:44 | 只看該作者
講得得非常好,向樓主學(xué)習(xí)。
回復(fù)

使用道具 舉報(bào)

板凳
ID:8222 發(fā)表于 2013-11-10 21:26 | 只看該作者
講得得非常好!
回復(fù)

使用道具 舉報(bào)

沙發(fā)
ID:32572 發(fā)表于 2013-8-6 08:55 | 只看該作者
很給力啊,你可以去大學(xué)當(dāng)教授了
回復(fù)

使用道具 舉報(bào)

本版積分規(guī)則

手機(jī)版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表