單片機課設----基於紅外和超聲波的手動/自動調速風扇系統

1、前言

  本系統爲基於紅外和超聲波的手動/自動調速風扇系統,風扇轉速的調節模式可分爲自動模式與手動模式:在自動模式下,由超聲波檢測人與風扇的距離,根據距離調節風扇轉速;在手動模式下,可經過紅外遙控的按鍵調節風扇轉速。相應參數信息經過LCD液晶顯示屏顯示。本系統的主控芯片採用STC89C52單片機,測距採用HC-SR04超聲波模塊,風扇電機由L298N電機驅動模塊驅動,遙控部分用傳統的紅外遙控器,顯示部分用LCD1602液晶顯示屏。電機驅動模塊採用12V供電,單片機及其餘各部分採用5V供電。ide

  該項目是筆者在大一暑假時完成的,在大三上學期又把代碼整理、優化了一次,拿去充當了一次課程設計。正好遇上這兩天有空,決定把這個小玩意整理成博客。一來這個東西確實是當時用心作了的,且之後筆者可能也不會再碰單片機相關的東西了,整理出來留做念想;二來但願能在學弟學妹們作課設的時候提供一些思路,拋磚引玉;僅此而已。這裏先附醜圖一張:函數

2、思路分析

2.1 系統供電問題

  STC89C52單片機及超聲波傳感器、紅外遙控接收頭、液晶顯示屏均爲+5V標準供電,能夠直接使用電腦USB接口引出電壓。但考慮到電機用到PWM調速,須要大電壓和大電流,所以決定使用電池盒額外供電。優化

2.2 自動/手動模式的切換

  主函數內部用一個while大循環,超聲波數據採集及電機驅動等程序均放在循環內部。在while內部有兩段程序,一段爲手動模式,一段爲自動模式,分別放在if…else…的兩個分支內。定義全局變量flag,在紅外遙控中斷內部可改變flag的值,經過flag的值控制if…else…選擇結構的走向,進而實現兩種模式的切換。ui

2.3 PWM信號的產生

  電機轉速調節需用到PWM信號,需由單片機內部產生。有兩種可行方案:其一爲經過軟件延時,不斷地改變某一引腳電平的高低,由該引腳向外輸出PWM信號;其二爲經過中斷計時,計滿後進入中斷服務程序,在中斷服務程序中改變某一引腳電平的高低,由該引腳向外輸出PWM信號。考慮到系統較爲複雜,用方案一在時間上會佔用單片機的大量資源,影響到系統的穩定性和實時性,所以採用方案二。編碼

2.4 單片機內部資源的分配 

  在本系統中,用到兩個定時器和兩個中斷:超聲波測距時等待返回波用到一個定時器,控制PWM信號的發生用到一個定時器;紅外遙控的響應用到一個外部中斷,PWM信號的發生用到定時器中斷。考慮到系統的實時性,給紅外遙控分配優先級最高的外部中斷0,PWM信號發生使用定時器T0並開中斷,超聲波測距使用定時器T1,不開中斷。spa

3、硬件搭建

  因爲硬件部分中的不少模塊在仿真軟件中都沒有,且各模塊之間的鏈接關係比較簡單,所以在這裏不提供電路圖,僅用語言描述各引腳之間的鏈接關係。設計

3.1 單片機最小系統 

  對51 系列單片機來講, 最小系統通常應該包括: 單片機、時鐘電路、復位電路、輸入/ 輸出設備等。最小系統的焊接有一套標準的流程,爲基本功,這裏不作贅述。3d

3.2 電機驅動模塊

   本系統電機驅動模塊使用常見的L298N電機驅動模塊。L298N芯片能夠驅動兩個二相電機,也能夠驅動一個四相電機,輸出電壓最高可達50V,能夠直接經過電源來調節輸出電壓;能夠直接用單片機的IO口提供信號;並且電路簡單,使用比較方便。指針

  在本系統中,只使能了EnA來驅動一個電機,其中EnA接單片機引腳P20,IN1接P21,IN2接P22。L298N電機驅動模塊實物圖以下所示:code

 

3.3 超聲波測距模塊

  在自動調速模式下,需用到超聲波模塊採集距離信息。本系統採用HC-SR04超聲波模塊, HC-SR04超聲波測距模塊可提供2cm-400cm的非接觸式距離感測功能,測距精度可達高到3mm;模塊包括超聲波發射器、接收器與控制電路。基本工做原理:

(1)採用I0口TRIG觸發測距,給至少10us的高電平信號;

(2)模塊自動發送8個40khz的方波,自動檢測是否有信號返回;

(3)有信號返回,經過I0口ECHO輸出一個高電平,高電平持續的時間就是超聲往返所用的時間。

(4)根據聲音在空氣中的速度爲344米/秒,便可計算出所測的距離。

  在本系統中,超聲波模塊的Trig腳接單片機引腳P36,Echo腳接單片機引腳P22。HC-SR04工做時序圖以下所示:

3.4 紅外遙控模塊

  根據使用的編碼芯片不一樣,紅外遙控編碼的格式也不一樣,較廣泛的有NEC標準和PHILIPS標準。最經常使用的是NEC標準,本系統採用的也是NEC標準。

  NEC標準:遙控載波的頻率爲38KHz(佔空比1:3)當某個鍵按下時,發射端首先發射一個完整的全碼,若是按鍵超過108ms仍未鬆開,接下來發射的代碼(連發代碼)將由起始碼(9ms)和結束碼(2.5ms)組成,並每隔108ms重複。

  一個完整的全碼由引導碼、用戶碼、用戶碼、數據碼、數據碼以及數據反碼共同組成。其中,引導碼高電平9ms,低電平4.5ms;系統碼8位,數據碼8位,共32位;其中前16位爲用戶識別碼,能區別不一樣的紅外遙控設備,以防止不一樣的機種遙控碼互相干擾。後16位爲8位的操做碼和8位的操做反碼,用於覈對數據是否接收準確。收端根據數據碼作出應該執行上面動做的判斷。連發代碼是在持續按鍵時發送的碼。它告知接收端。某鍵是在被連續的按着。

  NEC標準下的發射碼錶示:發射數據0時用「0.56ms高電平 + 0.565ms低電平 = 1.125ms」表示;發射數據1用「0.56ms高電平 + 1.69ms低電平 = 2.25ms」表示。 

  在本系統中,紅外接收器的INIR腳接單片機引腳P32。NEC標準完整碼組成及NEC標準發射碼以下所示:

3.5 液晶顯示模塊

  本系統的液晶顯示部分採用LCD1602液晶顯示屏。1602液晶也叫1602字符型液晶,它是一種專門用來顯示字母、數字、符號等的點陣型液晶模塊 它有若干個5X7或者5X11等點陣字符位組成,每一個點陣字符位均可以顯示一個字符。每位之間有一個點距的間隔,每行之間也有也有間隔,起到了字符間距和行間距的做用。

  LCD1602是指顯示的內容爲16X2,便可以顯示兩行,每行16個字符液晶模塊(顯示字符和數字)。目前市面上字符液晶絕大多數是基於HD44780液晶芯片的,控制原理是徹底相同的,所以基於HD44780寫的控制程序能夠很方便地應用於市面上大部分的字符型液晶。

  在本系統中,液晶顯示屏接法以下所示:

3.6 供電模塊

  爲了驅動電機,需採用+12V供電,結合手上現有資源,決定採用4節3.7V的鋰電池串聯供電。串聯後的輸出電壓在+15V左右,使用LM2596S直流降壓模塊,將電壓降至+12V後提供給電機驅動模塊L298N,單片機所需的+5V電可直接從電機驅動模塊中引出。

4、代碼分享

  代碼用C語言編寫,在Keil4環境下開發的。給每一個模塊都寫了驅動,每一個模塊的驅動拿出後略加改動,都能單獨使用。代碼工程結構以下所示:

4.1 總頭文件

  把經常使用的宏定義和硬件的引腳鏈接定義到了reg52.h裏面,改名爲my52.h。因此整個工程代碼中每一個文件都#include "my52.h"而不是 #include "reg52.h"

  1 #ifndef __MY52_H__
  2 #define __MY52_H__
  3 
  4 /*  BYTE Registers  */
  5 sfr P0    = 0x80;
  6 sfr P1    = 0x90;
  7 sfr P2    = 0xA0;
  8 sfr P3    = 0xB0;
  9 sfr PSW   = 0xD0;
 10 sfr ACC   = 0xE0;
 11 sfr B     = 0xF0;
 12 sfr SP    = 0x81;
 13 sfr DPL   = 0x82;
 14 sfr DPH   = 0x83;
 15 sfr PCON  = 0x87;
 16 sfr TCON  = 0x88;
 17 sfr TMOD  = 0x89;
 18 sfr TL0   = 0x8A;
 19 sfr TL1   = 0x8B;
 20 sfr TH0   = 0x8C;
 21 sfr TH1   = 0x8D;
 22 sfr IE    = 0xA8;
 23 sfr IP    = 0xB8;
 24 sfr SCON  = 0x98;
 25 sfr SBUF  = 0x99;
 26 
 27 /*  8052 Extensions  */
 28 sfr T2CON  = 0xC8;
 29 sfr RCAP2L = 0xCA;
 30 sfr RCAP2H = 0xCB;
 31 sfr TL2    = 0xCC;
 32 sfr TH2    = 0xCD;
 33 
 34 
 35 /*  BIT Registers  */
 36 /*  PSW  */
 37 sbit CY    = PSW^7;
 38 sbit AC    = PSW^6;
 39 sbit F0    = PSW^5;
 40 sbit RS1   = PSW^4;
 41 sbit RS0   = PSW^3;
 42 sbit OV    = PSW^2;
 43 sbit P     = PSW^0; //8052 only
 44 
 45 /*  TCON  */
 46 sbit TF1   = TCON^7;
 47 sbit TR1   = TCON^6;
 48 sbit TF0   = TCON^5;
 49 sbit TR0   = TCON^4;
 50 sbit IE1   = TCON^3;
 51 sbit IT1   = TCON^2;
 52 sbit IE0   = TCON^1;
 53 sbit IT0   = TCON^0;
 54 
 55 /*  IE  */
 56 sbit EA    = IE^7;
 57 sbit ET2   = IE^5; //8052 only
 58 sbit ES    = IE^4;
 59 sbit ET1   = IE^3;
 60 sbit EX1   = IE^2;
 61 sbit ET0   = IE^1;
 62 sbit EX0   = IE^0;
 63 
 64 /*  IP  */
 65 sbit PT2   = IP^5;
 66 sbit PS    = IP^4;
 67 sbit PT1   = IP^3;
 68 sbit PX1   = IP^2;
 69 sbit PT0   = IP^1;
 70 sbit PX0   = IP^0;
 71 
 72 /*  P3  */
 73 sbit RD    = P3^7;
 74 sbit WR    = P3^6;
 75 sbit T1    = P3^5;
 76 sbit T0    = P3^4;
 77 sbit INT1  = P3^3;
 78 sbit INT0  = P3^2;
 79 sbit TXD   = P3^1;
 80 sbit RXD   = P3^0;
 81 
 82 /*  SCON  */
 83 sbit SM0   = SCON^7;
 84 sbit SM1   = SCON^6;
 85 sbit SM2   = SCON^5;
 86 sbit REN   = SCON^4;
 87 sbit TB8   = SCON^3;
 88 sbit RB8   = SCON^2;
 89 sbit TI    = SCON^1;
 90 sbit RI    = SCON^0;
 91 
 92 /*  P1  */
 93 sbit T2EX  = P1^1; // 8052 only
 94 sbit T2    = P1^0; // 8052 only
 95              
 96 /*  T2CON  */
 97 sbit TF2    = T2CON^7;
 98 sbit EXF2   = T2CON^6;
 99 sbit RCLK   = T2CON^5;
100 sbit TCLK   = T2CON^4;
101 sbit EXEN2  = T2CON^3;
102 sbit TR2    = T2CON^2;
103 sbit C_T2   = T2CON^1;
104 sbit CP_RL2 = T2CON^0;
105 
106 /*------------------一下爲添加部分---------------------*/
107 
108 #define uint unsigned int 
109 #define uchar unsigned char
110 
111 #define HighGear 4
112 #define MiddleGear 3
113 #define LowGear 2
114 
115 //電機驅動
116 sbit ENA = P2^0; //PWM輸入端口
117 sbit IN1 = P2^1; //0
118 sbit IN2 = P2^2; //1
119 
120 //超聲波
121 sbit Trig=P3^6;
122 sbit Echo=P3^7;
123 
124 //LCD1602
125 sbit RS=P1^2;  // 數據/命令選擇端(H/L)
126 sbit RW=P1^1;  //讀寫選擇端(H/L)
127 sbit E=P1^0;   //使能信號
128 
129 //紅外遙控
130 sbit IRIN=P3^2;// 紅外接收器端口定義,外部中斷0優先級最高
131 
132 
133 #endif
View Code

4.2 電機驅動及頭文件

  電機驅動文件motor_driver.c

 1 #include "my52.h"
 2 
 3 uchar motor_gear,pwm_num; 
 4 
 5 void motor_run(uchar gear)
 6 {
 7     motor_gear = gear; //擋位設置,分2,3,4檔
 8     TMOD = 0x11; //設置定時器1爲工做方式1
 9     TH1 = (65536-100)/256; //裝初值,每0.1ms中斷一次
10     TL1 = (65536-100)%256;
11     ET1 = 1; //開定時器1中斷
12     TR1 = 1; //啓動定時器1
13 }
14 
15 void T1_PWM() interrupt 3
16 {
17     TH1 = (65536-100)/256; //裝初值
18     TL1 = (65536-100)%256;
19     pwm_num++;
20     if(pwm_num == 5)
21         pwm_num = 0;
22     if(pwm_num <= motor_gear) 
23         ENA = 1;
24     else
25         ENA = 0;
26 }
View Code

  電機驅動頭文件motor_driver.h

1 #ifndef __MOTOR_DRIVER_C__
2 #define __MOTOR_DRIVER_C__
3 extern motor_run(uchar gear); //可填2,3,4,佔空比分別爲0.6,0.8,1
4 #endif
View Code

 4.3 超聲波驅動及頭文件

  超聲波驅動文件sr04_driver.c

 1 #include "my52.h"
 2 #include "motor_driver.h"
 3 #include <intrins.h> // _nop_()延時
 4 
 5 extern uchar InfraredGear;
 6 
 7 uint distance()     //HC-SR04超聲波測距模塊工做函數
 8 {
 9     uint dis = 0;
10     uint time = 0;
11     uchar i = 10;
12 
13     Trig = 0;//初始化
14     Echo = 0;
15 
16 
17     TMOD = 0x11;
18     TH0=0;//給T0裝初值0
19     TL0=0;
20 
21     Trig = 1;
22     while(i--)
23         _nop_();
24     while(Echo==0);
25     TR0=1;//啓動T0
26     while(Echo==1);//等待返回信號的接收完畢
27     time=TH0*256+TL0;//微秒
28     dis=(time*1.7+5)/10;//340米每秒即0.34毫米每微秒,1.7=0.34/2×10,來回除2,四捨五入先乘10
29     TR0=0;//關閉T0
30 
31     return dis;
32 }
33 
34 void sr04_motor(uint dist)
35 {
36     if(dist <= 300)
37         InfraredGear = LowGear;
38     else if(dist > 600)
39         InfraredGear = HighGear;
40     else
41         InfraredGear = MiddleGear;
42 }
View Code

  超聲波驅動頭文件sr04_driver.h

1 #ifndef __SR04_DRIVER_C__
2 #define __SR04_DRIVER_C__
3 extern uint distance(); //單位是毫米
4 extern void sr04_motor(uint dist);
5 #endif
View Code

4.4 紅外遙控驅動及頭文件 

  紅外遙控驅動文件infrared_driver.c

 1 #include "my52.h"
 2 #include "delay.h"
 3 
 4 extern uchar InfraredGear;
 5 extern uchar Flag ;
 6 
 7 uchar IrValue[4];//兩位用戶碼,一位數據碼,一位數據反碼
 8 uchar num;
 9 
10 void read() interrupt 0{//紅外中斷讀取檔位數據
11     uchar j,k,t;
12     uint i;
13     num=0;
14     t=IrValue[2];
15     delay_ms(7);//起始碼前9ms爲低電平,在這裏等待7ms
16     if(IRIN==0){//確認真的收到信號後執行如下程序
17         i=1000;    //若是出錯利用i跳出如下等待,以避免程序在這裏死循環
18         while((IRIN==0)&&(i>0)){ //等待前9ms結束
19             delay_10us(1);
20             i--;
21         }
22         if(IRIN==1){//起始碼前9ms結束,後4.5ms爲高電平
23             i=500; //用i防止死循環
24             while((IRIN==1)&&(i>0)){//等待起始碼的後4.5ms高電平
25                 delay_10us(1);
26                 i--;
27             }
28             for(k=0;k<4;k++){//2個用戶碼,1個數據碼,1個數據反碼,共4個字節
29                 for(j=0;j<8;j++){ //每一個字節8位,如下程序用於肯定每位電平的高低
30                     i=60;
31                     while((IRIN==0)&&(i>0)){//等待0.56ms的低電平,每位前面都有0.56ms的低電平
32                         delay_10us(1);            //後面高電平0.565ms(565us)爲0,   1.69ms(1690us)爲1
33                         i--;
34                     }
35                     i=500;
36                     while((IRIN==1)&&(i>0)){//低電平結束,高電平到來後進入,用於計算高電平持續時間
37                         delay_10us(10);//延時100us
38                         num++;
39                         i--;
40                         if(num>30){//超出3000us(3ms),本程序出錯(最大不能超過2.25ms),返回主調函數
41                             return;
42                         }
43                     }
44                     IrValue[k]>>=1;//騰出最高位用於接收本位數據
45                     if(num>=8){//高電平持續時間大於800us,該位爲1
46                         IrValue[k]|=0x80; //給最高位寫1
47                     }
48                     num=0;    //計數變量清零
49                 }
50             }
51         }
52         if(IrValue[2]!=~IrValue[3]){//數據位校驗
53             IrValue[2]=t;
54             return;
55         }
56     }
57     if(IrValue[2]==69)
58         InfraredGear=LowGear;
59     else if(IrValue[2]==70)
60         InfraredGear=MiddleGear;
61     else if(IrValue[2]==71)
62         InfraredGear=HighGear;
63     else if(IrValue[2]==68)      //切換自動模式
64         Flag = 0;
65     else if(IrValue[2]==67)      //切換手動模式
66         Flag = 1;
67     else if(IrValue[2]==64)      //急停
68         IN1 = 1;
69     else if(IrValue[2]==21)
70         IN1 = 0;
71 }
View Code

  紅外遙控驅動頭文件infrared_driver.h

1 #ifndef __INFRARED_DRIVER_C__
2 #define __INFRARED_DRIVER_C__
3 
4 #endif
View Code

4.5 液晶顯示驅動及頭文件

  液晶顯示驅動文件lcd1602_driver.c

 1 #include "my52.h"
 2 
 3 extern uchar InfraredGear;
 4 
 5 void delays(uint i)
 6 {
 7     uchar j;
 8     while(i--)
 9         for(j=50;j>0;j--);
10 }
11 void write_com(uchar com)
12 {
13     RS=0;
14     P0=com;
15     delays(1);
16     E=1;
17     delays(1);
18     E=0;
19 }        
20 void write_date(uchar date)
21 {
22     RS=1;
23     P0=date;
24     delays(1);
25     E=1;
26     delays(1);
27     E=0;
28 }        
29 void lcdinit()
30 {
31     RW=0;
32     E=0;
33     write_com(0x38); //設置16X2顯示,5X7點陣,8位數據接口
34     write_com(0x0c); //設置開顯示,不顯示光標
35     write_com(0x06); //寫一個字符後地址指針加1 
36     write_com(0x01); //顯示清零,數據指針清零
37 }
38 
39 void display0(uint dis) //手動調速下1602顯示
40 {
41     uchar g1,g2,g3,g4;
42     g1=dis%10;
43     g2=(dis/10)%10;
44     g3=(dis/100)%10;
45     g4=dis/1000;
46     lcdinit();
47     write_com(0x80);
48     write_date('M');
49     write_date('o');
50     write_date('d');
51     write_date('e');
52     write_date('l');
53     write_date(':');
54     write_date('A');
55     write_date('U');
56     write_date('T');
57     write_date('O');
58 
59     write_date(' ');//擋位顯示
60     write_date(0x30+InfraredGear-1);
61     
62     write_com(0x80+0x40);//換行顯示
63 
64     write_date('D');//距離顯示
65     write_date('i');
66     write_date('s');
67     write_date(':');
68     write_date(0x30+g4);
69     write_date(0x30+g3);
70     write_date(0x30+g2);
71     write_date(0x30+g1);
72     write_date('m');
73     write_date('m');
74 }
75 
76 void display1()
77 {
78     lcdinit();
79     write_com(0x80);
80 
81     write_date('M');
82     write_date('o');
83     write_date('d');
84     write_date('e');
85     write_date('l');
86     write_date(':');
87     write_date('M');
88     write_date('A');
89     write_date('N');
90     write_date('U');
91 
92     write_date(' ');//擋位顯示
93     write_date(0x30+InfraredGear-1);
94 }
View Code

  液晶顯示驅動頭文件lcd1602_driver.h

1 #ifndef __LCD1602_DRIVER_C__
2 #define __LCD1602_DRIVER_C__
3 extern void delays(uint i);
4 extern void write_com(uchar com);
5 extern void write_date(uchar date);
6 extern void lcdinit();
7 extern void display0(uint dis);
8 extern void display1();
9 #endif
View Code

4.6 延時函數及頭文件

  延時函數所在文件delay.c:

 1 #include "my52.h"
 2 
 3 void delay_ms(uint t)
 4 {
 5     uint i,j;
 6     for(i=0;i<t;i++)
 7         for(j=0;j<114;j++);
 8 }
 9 
10 void delay_10us(uint t)  //延時函數,t=1延時10us
11 { 
12     while(t--);
13 }
View Code

  對應頭文件delay.h:

1 #ifndef __DELAY_C__
2 #define __DELAY_C__
3 extern void delay_ms(uint t);
4 extern void delay_10us(uint t);
5 #endif
View Code

4.7 主函數

 1 #include "my52.h"
 2 #include "motor_driver.h"
 3 #include "sr04_driver.h"
 4 #include "delay.h"
 5 #include "lcd1602_driver.h"
 6 #include "infrared_driver.h"
 7 
 8 uchar InfraredGear = LowGear;//手動調節擋位,紅外驅動文件中改變該值
 9 uchar Flag = 0;//自動0/手動1模式
10 
11 void main()
12 {
13     uint dis;
14     ENA = 1;
15     IN1 = 0;
16     IN2 = 1;
17     EA = 1;//開總中斷
18     EX0 = 1; //開外部中斷0,接收紅外信號
19     while(1)
20     {
21         if(Flag == 0) //自動
22         {
23             dis = distance(); //超聲波測距
24             sr04_motor(dis); //根據距離調節擋位
25             motor_run(InfraredGear);
26             display0(dis);    //液晶顯示
27             delay_ms(100);    //延時,每100ms更新一次數據
28         }
29         else
30         {
31             motor_run(InfraredGear); //手動調節轉速
32             display1();     //液晶顯示
33             delay_ms(100); //延時,每100ms更新一次液晶內容
34         }
35     }    
36 }
View Code
相關文章
相關標籤/搜索