一、前言


  本系统为基于红外和超声波的手动/自动调速风扇系统,风扇转速的调节模式可分为自动模式与手动模式:在自动模式下,由超声波检测人与风扇的距离,根据距离调节风扇转速;在手动模式下,可通过红外遥控的按键调节风扇转速。相应参数信息通过LCD液晶显示屏显示。本系统的主控芯片采用STC89C52单片机,测距采用HC-SR04超声波模块,风扇电机由L298N电机驱动模块驱动,遥控部分用传统的红外遥控器,显示部分用LCD1602液晶显示屏。电机驱动模块采用12V供电,单片机及其他各部分采用5V供电。


  该项目是笔者在大一暑假时完成的,在大三上学期又把代码整理、优化了一次,拿去充当了一次课程设计。正好赶上这两天有空,决定把这个小玩意整理成博客。一来这个东西确实是当时用心做了的,且以后笔者可能也不会再碰单片机相关的东西了,整理出来留作念想;二来希望能在学弟学妹们做课设的时候提供一些思路,抛砖引玉;仅此而已。这里先附丑图一张:



二、思路分析

2.1 系统供电问题


  STC89C52单片机及超声波传感器、红外遥控接收头、液晶显示屏均为+5V标准供电,可以直接使用电脑USB接口引出电压。但考虑到电机用到PWM调速,需要大电压和大电流,因此决定使用电池盒额外供电。

2.2 自动/手动模式的切换


  主函数内部用一个while大循环,超声波数据采集及电机驱动等程序均放在循环内部。在while内部有两段程序,一段为手动模式,一段为自动模式,分别放在if…else…的两个分支内。定义全局变量flag,在红外遥控中断内部可改变flag的值,通过flag的值控制if…else…选择结构的走向,进而实现两种模式的切换。

2.3 PWM信号的产生


  电机转速调节需用到PWM信号,需由单片机内部产生。有两种可行方案:其一为通过软件延时,不断地改变某一引脚电平的高低,由该引脚向外输出PWM信号;其二为通过中断计时,计满后进入中断服务程序,在中断服务程序中改变某一引脚电平的高低,由该引脚向外输出PWM信号。考虑到系统较为复杂,用方案一在时间上会占用单片机的大量资源,影响到系统的稳定性和实时性,因此采用方案二。

2.4 单片机内部资源的分配 


  在本系统中,用到两个定时器和两个中断:超声波测距时等待返回波用到一个定时器,控制PWM信号的发生用到一个定时器;红外遥控的响应用到一个外部中断,PWM信号的发生用到定时器中断。考虑到系统的实时性,给红外遥控分配优先级最高的外部中断0,PWM信号发生使用定时器T0并开中断,超声波测距使用定时器T1,不开中断。

三、硬件搭建

  由于硬件部分中的很多模块在仿真软件中都没有,且各模块之间的连接关系比较简单,因此在这里不提供电路图,仅用语言描述各引脚之间的连接关系。

3.1 单片机最小系统 

  对51 系列单片机来说, 最小系统一般应该包括: 单片机、时钟电路、复位电路、输入/ 输出设备等。最小系统的焊接有一套标准的流程,为基本功,这里不做赘述。

3.2 电机驱动模块


   本系统电机驱动模块使用常见的L298N电机驱动模块。L298N芯片可以驱动两个二相电机,也可以驱动一个四相电机,输出电压最高可达50V,可以直接通过电源来调节输出电压;可以直接用单片机的IO口提供信号;而且电路简单,使用比较方便。

  在本系统中,只使能了EnA来驱动一个电机,其中EnA接单片机引脚P20,IN1接P21,IN2接P22。L298N电机驱动模块实物图如下所示:

 

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电可直接从电机驱动模块中引出。

四、代码分享

  代码用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
int109 #define uchar unsigned char 110 111 #define HighGear 4 112 #define
MiddleGear 3113 #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()
interrupt3 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
 

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信