
一、 题目分析 2
二、 硬件设计 5
三、 软件设计 7
四、 调试及运行 21
五、 心得体会 22
1、题目分析
1、直流电机正反转及调速
直流电机正反转控制现在主要是利用分立元件和芯片驱动,分立元件主要是利用H桥驱动电路,芯片驱动主要是利用L298,采用分立元件和芯片原理是大致相同的。集成芯片L298内部有两个H桥,可以同时控制两个直流电机, 图1中所示为一个典型的直流电机控制电路。电路得名于“H桥驱动电路”是因为它的形状酷似字母H。4个三极管组成H的4条垂直腿,而电机就是H中的横杠,如图所示,H桥式电机驱动电路包括4个三极管和一个电机。要使电机运转,必须导通对角线上的一对三极管。根据不同三极管对的导通情况,电流可能会从左至右或从右至左流过电机,从而控制电机的转向。
图1
要使电机运转,必须使对角线上的一对三极管导通。例如,如图2所示,当Q1管和Q4管导通时,电流就从电源正极经Q1从左至右穿过电机,然后再经Q4回到电源负极。按图中电流箭头所示,该流向的电流将驱动电机顺时针转动。当三极管Q1和Q4导通时,电流将从左至右流过电机,从而驱动电机按特定方向转动(电机周围的箭头指示为顺时针方向)。
图2 顺时针旋转
图3所示为另一对三极管Q2和Q3导通的情况,电流将从右至左流过电机。当三极管Q2和Q3导通时,电流将从右至左流过电机,从而驱动电机沿另一方向转动(电机周围的箭头表示为逆时针方向)。
图3逆时针旋转
驱动电机时,保证H桥上两个同侧的三极管不会同时导通非常重要。如果三极管Q1和Q2同时导通,那么电流就会从正极穿过两个三极管直接回到负极。此时,电路中除了三极管外没有其他任何负载,因此电路上的电流就可能达到最大值(该电流仅受电源性能),甚至烧坏三极管。基于上述原因,在实际驱动电路中通常要用硬件电路方便地控制三极管的开关。可采用下图作为驱动电路。
实际使用的时候,用分立元件制作H桥是很麻烦的,好在现在市面上有很多封装好的H桥集成电路,接上电源、电机和控制信号就可以使用了,在额定的电压和电流内使用非常方便可靠。比如常用的L293D、L298N、TA7257P、SN754410等。
直流电机调速主要采用的是脉宽调制(PWM)通过改变方波的占空比来调节加在小电机上的平均电压,从而改变平均功率。
2、温度测量及显示(LCD)
此次实训所采用的测温元件是DS18B20,显示器件用的是LCD1602,所以要实现温度的测量及显示,可以通过单片机将DS18B20所测得的温度数据取出来并进行处理,然后通过LCD显示出来,所以首先就应该要了解DS18B20的特性,DS18B20 内部结构主要由四部分组成: 位光刻 ROM 、温度传感器、非挥发的温度报警触发器 TH 和 TL 、配置寄存器。光刻 ROM 中的 位序列号是出厂前被光刻好的,它可以看作是该 DS18B20 的地址序列码。 位光刻 ROM 的排列是:开始 8 位(地址: 28H )是产品类型标号,接着的 48 位是该 DS18B20 自身的序列号,并且每个 DS18B20 的序列号都不相同,因此它可以看作是该 DS18B20 的地址序列码;最后 8 位则是前面 56 位的循环冗余校验码( CRC=X8+X5+X4+1 )。由于每一个 DS18B20 的 ROM 数据都各不相同,因此微控制器就可以通过单总线对多个 DS18B20 进行寻址,从而实现一根总线上挂接多个 DS18B20 的目的。DS18B20的管脚排列
1. GND为电源 地;
2. DQ为数字信号输入/输出端;
3. VDD为外接供电电源输入端
(在寄生电源接线方式时接地)
对DS18B20的电路接法,常用的是如下图的接法:
LCD特性了解
LCD本身并不发光,是利用液晶经过处理后能改变光线通过方向的特性,从而达到白底黑字或黑底白字显示的目的。
液晶显示器具有省电、抗干扰能力强等优点,广泛应用在智能仪器仪表和单片机测控系统中。
由于LCD的面板较为脆弱,制造商已将LCD控制器、驱动器、RAM、ROM和LCD显示器用PCB连接到一起,称为液晶显示模块,只需购买现成的液晶显示模块即可
单片机控制LCM时,只要向LCM送入相应的命令和数据就可显示需要的内容。
2、硬件设计
1、直流电机正反转及调速
通过以上的分析,可以看出要实现直流电机正反转及调速可以通过单片机的I/0口输出高低电平来实现,通过单片机输出的占空比可调的方波,可以调节直流电机的速度,所以可以用如下的电路来实现:
当PWM1端输入波形而PWM2端置为低电平时,Q14,Q15导通,此时电机逆时针旋转,当PWM2端输入波形而PWM1端置为低电平时,Q13,Q16导通,此时电机顺时针旋转,此时只要将PWM1和PWM2两端接在单片机的I/O口上,可以很容易的实现控制,硬件整体电路如下:
上图中按键用于调节波形输出的占空比和切换正反转,通过H桥驱动直流电机,二极管用于续流,防止直流电机产生的电势将三极管击穿损坏。
2、温度测量及显示(LCD)
硬件整体电路图如下:
通过DS18B20测量温度,然后经过单片机的处理以后再通过LCD1602显示出来,电路图中的滑动变阻器用于调节LCD显示的对比度,发光二级管用于显示单片机当前处于工作状态,由后面的程序可以看出,发光二级管闪烁的时间间隔正好是读一次温度并且显示的时间,当然显示后有100微秒的延时。
3、软件设计
1、直流电机正反转及调速
直流电机正反转调速,软件设计的思想是利用单片机的外部中断0来变更标志位,从而实现正反转的控制,通过设置基本的定时时间来调节占空比,即首先设置一个单位时间,然后通过计数的方式来确定高电平的时间和低电平的时间,这样,输出波形不但频率可调,而且占空比也可调。具体实现如下:
#include unsigned char a=5; //定义变量 unsigned char b=5; unsigned char AA=5; unsigned char BB=5; sfr p2=0xa0; //特殊功能寄存器绝对地址定义 sfr p1=0x90; //同上 sbit p2_0=p2^0; //特殊功能寄存器位定义 sbit p2_1=p2^1; sbit p1_0=p1^0; sbit p1_1=p1^1; sbit p1_2=p1^2; sbit p1_3=p1^3; bit flag1=0; //定义位变量为标志位 void saomiao() //按键扫描子程序 { if(p1_0==0) //按键按下,则高电平持续时间增加 {a++;} else if(p1_1==0) //按键按下,则低电平持续时间增加 { b++;} else if(p1_2==0) //按键按下,则高电平持续时间减少 {a--; } else if(p1_3==0) //按键按下,则低电平持续时间减少 {b--; } AA=a; BB=b; } void int_r() interrupt 0 //外部中断0服务程序,中断号为0 {flag1=!flag1; } //变更标志位,实现正反转切换 void init(void) //程序的初始化 { EA=1; //开总中断 EX0=1; //开外部中断 IT0=1; //中断方式置为边沿触发方式 TMOD=0x10; //定时器1工作方式1 TH1=0xFE; //计数初值高位,计时0.5ms TL1=0x0C; //计数初值低位,计时0.5ms TR1=1; //启动定时器 p2_0=0; //端口初始化 p2_1=0; //端口初始化 } void main() { init(); //调用初始化程序 while(1) { while(TF1==0); //等待定时时间到 if(flag1==0) //判断标志位 { if(p2_0==1) { AA--; TF1=0; //标志位清零 TH1=0xFE; //重新赋初值 TL1=0x0C; if(AA==0) //看高电平持续时间有没有到 { p2_0=0; //取反 p2_1=0; //其他位为零 saomiao(); //扫描看有没有按键按下 } } else if(p2_0==0) { BB--; TF1=0; TH1=0xFE; TL1=0x0C; if(BB==0) { p2_0=1; //取反 p2_1=0; //其他位为零 saomiao(); //扫描看有没有按键按下 } } } else if(flag1==1) //判断标志位 { if(p2_1==1) { AA--; TF1=0; TH1=0xFE; TL1=0x0C; if(AA==0) { p2_0=0; //其他位为零 p2_1=0; //取反 saomiao(); //扫描看有没有按键按下 } } else if(p2_1==0) { BB--; TF1=0; TH1=0xFE; TL1=0x0C; if(BB==0) { p2_0=0; //其他位为零 p2_1=1; //取反 saomiao(); //扫描看有没有按键按下 } } } } } 2、温度测量及显示(LCD) 软件首先进行LCD的初始化,然后进温度的读取,温度值的读取又包括DS18B20的初始化及命令的写入和数据的读取,然后进行温度值的显示,温度显示包括LCD的命令写入及数据的写入,其中用到一些器件的命令及寄存器知识。 上面所示的是LCD的显示位置的地址,通过命令可以调整LCD显示的字母的位置,下面是LCD各个引脚的功能及定义: 第1脚:VSS为地电源。 第2脚:VDD接5V正电源。 第3脚:VL为液晶显示器对比度调整端,接正电源时对比度最弱,接地时对比度最高,对比度过高时会产生“鬼影”,使用时可以通过一个10K的电位器调整对比度。 第4脚:RS为寄存器选择,高电平时选择数据寄存器、低电平时选择指令寄存器。 第5脚:R/W为读写信号线,高电平时进行读操作,低电平时进行写操作。当RS和R/W共同为低电平时可以写入指令或者显示地址,当RS为低电平R/W为高电平时可以读忙信号,当RS为高电平R/W为低电平时可以写入数据。 第6脚:E端为使能端,当E端由高电平跳变成低电平时,液晶模块执行命令。 第7~14脚:D0~D7为8位双向数据线。 第15脚:背光源正极。 第16脚:背光源负极。 1602液晶模块内部的控制器共有11条控制指令,如表10-14所示: DS18B20的一线工作协议流程是: 初始化→ROM操作指令→存储器操作指令→数据传输。 其工作时序包括: 初始化时序 写时序 读时序 总程序如下: #include #include #define uint unsigned int //定义变量 #define uchar unsigned char #define delayNOP() {_nop_();_nop_();_nop_();_nop_();} //定义4条空语句延时 sbit DQ = P2^2; //定义特殊功能寄存器的位变量 sbit LCD_RS = P3^0; //同上 sbit LCD_RW = P3^1; sbit LCD_EN = P3^2; sbit flag=P3^4; //定义位变量,为标志位,显示单片机已经工作 uchar code Temp_Disp_Title[]={"Temperature valu"}; //定义数组为标题 uchar Current_Temp_Display_Buffer[]={"e is : "}; //定义数组温度数据暂存 uchar Current_Temp_unit[]={"C "}; //温度单位 uchar code df_Table[]= {0,1,1,2,3,3,4,4,5,6,6,7,8,8,9,9}; uchar CurrentT = 0; uchar Temp_Value[]={0x00,0x00}; //温度暂存 uchar Display_Digit[]={0,0,0,0}; //转换为十进制后温度各个位的值存放数组 bit DS18B20_IS_OK = 1; //DS18B20是否已经响应 void DelayXus(uint x) //延时程序 { uchar i; while(x--) {for(i=0;i<200;i++);} } bit LCD_Busy_Check() //根据上面的表,检测LCD是否处于忙状态 { bit result; LCD_RS = 0; LCD_RW = 1; LCD_EN = 1; delayNOP(); result = (bit)(P1&0x80); LCD_EN=0; return result; } void Write_LCD_Command(uchar cmd) //写命令给LCD { while(LCD_Busy_Check()); LCD_RS = 0; LCD_RW = 0; LCD_EN = 0; _nop_(); _nop_(); P1 = cmd; delayNOP(); LCD_EN = 1; delayNOP(); LCD_EN = 0; } void Write_LCD_Data(uchar dat) //写数据给LCD { while(LCD_Busy_Check()); LCD_RS = 1; LCD_RW = 0; LCD_EN = 0; P1 = dat; delayNOP(); LCD_EN = 1; delayNOP(); LCD_EN = 0; } void LCD_Initialise() //LCD初始化 { Write_LCD_Command(0x01); //清显示 DelayXus(5); Write_LCD_Command(0x38); //置功能 DelayXus(5); Write_LCD_Command(0x0c); //显示开关控制 DelayXus(5); Write_LCD_Command(0x06); //置输入模式 DelayXus(5); } void Set_LCD_POS(uchar pos) //置显示的位置 {Write_LCD_Command(pos|0x80);} //置存储器地址 void Delay(uint x) //延时程序 {while(--x);} uchar Init_DS18B20() //DS28B20初始化 { bit status; DQ = 1; Delay(8); DQ = 0; Delay(90); DQ = 1; Delay(8); status=DQ; Delay(8); DQ = 1; return status; //返回DS18B20响应状态 } uchar ReadOneByte() //从DS18B20读取值 { uchar i,dat=0; DQ = 1; _nop_(); for(i=0;i<8;i++) { DQ = 0; dat >>= 1; DQ = 1; _nop_(); _nop_(); if(DQ) dat |= 0X80; Delay(30); DQ = 1; } return dat; } void WriteOneByte(uchar dat) //写命令到DS18B20 { uchar i; for(i=0;i<8;i++) { DQ = 0; DQ = dat& 0x01; Delay(5); DQ = 1; dat >>= 1; } } void Read_Temperature() { if(Init_DS18B20()==1) //为1则说明DS18B20没有响应 DS18B20_IS_OK=0; //没有取得温度值 else { WriteOneByte(0xcc); //跳过ROM WriteOneByte(0x44); //启动DS18B20进行温度转换 Init_DS18B20(); //主机发出复位操作并接收DS18B20的应答(存在)脉冲。 WriteOneByte(0xcc); //跳过ROM WriteOneByte(0xbe); //读暂存器 Temp_Value[0] = ReadOneByte(); //读取温度低字节 Temp_Value[1] = ReadOneByte(); //读取温度高字节 DS18B20_IS_OK=1; //已经取得温度值 } } void Display_Temperature() { uchar i; uchar t = 150, ng = 0; if((Temp_Value[1]&0xf8)==0xf8) //取温度值高字节的高5位,如果高五位都是1,则温度位负值 { Temp_Value[1] = ~Temp_Value[1]; //将温度值高字节取反 Temp_Value[0] = ~Temp_Value[0]+1; //将温度值低字节取反加一 if(Temp_Value[0]==0x00) //如果低位有进位,则高位加1 Temp_Value[1]++; ng = 1; //标志位,表示温度为负值 } Display_Digit[0] = df_Table[Temp_Value[0]&0x0f]; //取温度小数部分 CurrentT = ((Temp_Value[0]&0xf0)>>4) | ((Temp_Value[1]&0x07)<<4); //拼字,将温度低字节的高4位,和高字节的低3位组合成一个字节 Display_Digit[3] = CurrentT/100; //取温度值的百位 Display_Digit[2] = CurrentT%100/10; //取温度值的十位 Display_Digit[1] = CurrentT%10; //取温度值的个位 Current_Temp_Display_Buffer[11] = Display_Digit[0] + '0'; //接下来将温度值的各个位及小数点放入显示值暂存数组 Current_Temp_Display_Buffer[10] = '.'; Current_Temp_Display_Buffer[9] = Display_Digit[1] + '0'; Current_Temp_Display_Buffer[8] = Display_Digit[2] + '0'; Current_Temp_Display_Buffer[7] = Display_Digit[3] + '0'; if(Display_Digit[3] == 0) //如果温度值百位为零,则第7个位置上为空 Current_Temp_Display_Buffer[7] = ' '; if(Display_Digit[2] == 0&&Display_Digit[3]==0) //如果温度值十位,百位均为0,则第8位上为空 Current_Temp_Display_Buffer[8] = ' '; if(ng) //如果温度值为负,则在前面加负号 { if(Current_Temp_Display_Buffer[8] == ' ') Current_Temp_Display_Buffer[8] = '-'; else if(Current_Temp_Display_Buffer[7] == ' ') Current_Temp_Display_Buffer[7] = '-'; else Current_Temp_Display_Buffer[6] = '-'; } Set_LCD_POS(0x00); //置显示的位置 for(i=0;i<17;i++) {Write_LCD_Data(Temp_Disp_Title[i]); } //显示标题 Set_LCD_POS(0x40); //置显示的位置 for(i=0;i<16;i++) {Write_LCD_Data(Current_Temp_Display_Buffer[i]); } //显示标题及温度值 Set_LCD_POS(0x4c); Write_LCD_Data(' '); Set_LCD_POS(0x4D); Write_LCD_Data(0xdf); //显示单位 Set_LCD_POS(0x4e); for(i=0;i<2;i++) {Write_LCD_Data(Current_Temp_unit[i]);} //显示温度单位 } void main() { LCD_Initialise(); //LCD初始化 Read_Temperature(); //读取温度值 Delay(50000); //延时 Delay(50000); while(1) { flag=!flag; //灯闪烁 Read_Temperature(); //读取温度值 if(DS18B20_IS_OK) //DS18B20响应则显示温度值 Display_Temperature(); //显示温度值 DelayXus(100); } } 4、调试及运行 1、直流电机正反转及调速 此次直流电机正反转及调速是进行了仿真,仿真过程还算顺利没有遇到很大的问题,只是在使用PROTEUS时,有些地方还显得不够熟练,例如仿真模型,但是后来经过和老师交流,还是弄出来了,得到了最后的结果。 2、温度测量及显示(LCD) 温度测量及显示主要面对的问题是LCD1602各个引脚的功能及连线以及程序的编写。但是通过对各个引脚功能的查询,最终还是弄清楚了。 5、心得体会 进行了为期三周的单片机技能实训,我掌握了很多新的东西,例如单片机矩阵键盘程序的编写过程,如何利用单片机进行直流电机的正反转控制及调速,理解了PWM脉宽调制的工作原理,学习了如何利用ADC0809进行AD转换,如何利用DAC0832进行DA转换,还学习了如何利用单片机来进行点阵的显示及移动,过程中,老师还教我们如何控制步进电机,虽然只是在电脑上进行仿真,但是我觉得在编写程序方面自己还是有了很大的提高,至少能利用C语言将功能实现,过程中也锻炼了使用C语言编程的能力,现在感觉利用C语言编写单片机程序有很多的优势,它比汇编更加的简洁明了,现在习惯了用C语言编写单片机程序,感觉再回头用汇编编写都不适应了,觉得汇编没有C语言思路清晰,但是通过对比两种编程语言,我发现,只有先学习汇编才能更加深刻的理解单片机各个寄存器的结构和功能,如果一来就用C语言编写程序,我想就不会很清楚的知道单片机更多底层的东西, 进行了仿真练习以后,我们还进行了实物的制作及调试,这次我选的是温度的测量及显示,通过实物的制作,感觉比仿真更能深刻理解DS18B20,LCD1602的功能及编程使用方法,这次我觉得很有收获的就是传感器DS18B20和LCD1602的编程方法,我觉得通过对这两种器件的编程,可以推而广之,以后再遇到类似的器件,就可以进行类似的编程。做到举一反三,现在做过的这些模块,在以后用到的时候可以直接就拿来用,这些都是模块化的东西,但主要的还是要知道它的意思,这样就能熟练的拿来用,有些程序只需要进行简单的修改,就可以移植到新的程序当中,这些模块化的经典程序可以在网上找到,通过对比,可以将这些程序中最经典的保存下来,这样在以后做到相同器件时,就可以直接用了,在整个实训过程中,我还通过上网欣赏了其他人做的基于单片机的项目,觉得自己视野变得开阔了,当我看到有人居然能过利用单片机来编写一个自己觉得很繁杂的推箱子游戏时,觉得自己真的需要学习更多,同时也感受到单片机的强大功能,和这个学期学习的PLC相比有独特的优势,PLC的特点是稳定,不会受到外界的干扰,例如,电磁波,热源等噪声的干扰,而在这方面单片机就不如PLC了,这就是为什么现代工厂都是采用昂贵的PLC作为控制器件而不是采用廉价的单片机来控制的原因。 这次实训感觉很短暂,但是我从中学习到了很多,这次实训只是个开始,我想在接下来的学习中对我会有很大的帮助。
表中引脚接口说明表编号 符号 引脚说明 编号 符号 引脚说明 1 VSS 电源地 9 D2 数据 2 VDD 电源正极 10 D3 数据 3 VL 液晶显示偏压 11 D4 数据 4 RS 数据/命令选择 12 D5 数据 5 R/W 读/写选择 13 D6 数据 6 E 使能信号 14 D7 数据 7 D0 数据 15 BLA 背光源正极 8 D1 数据 16 BLK 背光源负极
DS18B20读取温度后温度是以16进制存储的,其存储结构如下图,其中低字节的低四位存储温度的小数部分,高字节的高五位S为符号位,其余位为温度的个位、十位、百位。序号 指令 RS R/W D7 D6 D5 D4 D3 D2 D1 D0 1 清显示 0 0 0 0 0 0 0 0 0 1 2 光标返回 0 0 0 0 0 0 0 0 1 * 3 置输入模式 0 0 0 0 0 0 0 1 I/D S 4 显示开/关控制 0 0 0 0 0 0 1 D C B 5 光标或字符移位 0 0 0 0 0 1 S/C R/L * * 6 置功能 0 0 0 0 1 DL N F * * 7 置字符发生存贮器地址 0 0 0 1 字符发生存贮器地址 8 置数据存贮器地址 0 0 1 显示数据存贮器地址 9 读忙标志或地址 0 1 BF 计数器地址 10 写数到CGRAM或DDRAM) 1 0 要写的数据内容 11 从CGRAM或DDRAM读数 1 1 读出的数据内容
