一、单片机简介
单片机是一种集成电路芯片,采用超大规模技术把具有数据处理能力(如算术运算,逻辑运算、数据传送、中断处理)的微处理器(CPU),随机存取数据存储器(RAM),只读程序存储器(ROM),输入输出电路(I/O口),可能还包括定时计数器,串行通信口(SCI),显示驱动电路(LCD或LED驱动电路),脉宽调制电路(PWM),模拟多路转换器及A/D转换器等电路集成到一块单块芯片上,构成一个最小然而完善的计算机系统。这些电路能在软件的控制下准确、迅速、高效地完成程序设计者事先规定的任务。 由此来看,单片机有着微处理器所不具备的功能,它可单独地完成现代工业控制所要求的智能化控制功能,这是单片机最大的特征。
然而单片机又不同于单板机,芯片在没有开发前,它只是具备功能极强的超大规模集成电路,如果赋予它特定的程序,它便是一个最小的、完整的微型计算机控制系统,它与单板机或个人电脑(PC机)有着本质的区别,单片机的应用属于芯片级应用,需要用户了解单片机芯片的结构和指令系统以及其它集成电路应用技术和系统设计所需要的理论和技术,用这样特定的芯片设计应用程序,从而使该芯片具备特定的功能。
不同的单片机有着不同的硬件特征和软件特征,即它们的技术特征均不尽相同,硬件特征取决于单片机芯片的内部结构,用户要使用某种单片机,必须了解该型产品是否满足需要的功能和应用系统所要求的特性指标。这里的技术特征包括功能特性、控制特性和电气特性等等,这些信息需要从生产厂商的技术手册中得到。软件特征是指指令系统特性和开发支持环境,指令特性即我们熟悉的单片机的寻址方式。
二、设计任务和要求
(一).设计目的
1.掌握单片机拓展总线的工作原理和时序操作;
2.掌握单总线协议的基本特点及通信过程;
3.掌握数字温度传感器DS18B20的基本特点及单总线控制协议;
4.掌握单片机IO端口模拟单总线时序控制程序的编写方法;
5.掌握LCD液晶显示器的显示驱动方法。
(二).设计要求
1.单片机P0.0和DS18B20的数据端相连;
2.编写单片机通过IO端口模拟单总线时序控制DS18B20的程序,读出温度;
3.在1602字符点阵液晶显示模块上显示温度;
4.单片机拓展总线的地址线和LCD的控制线的编写方法。
三、设计方案的总体设计框图
3.1硬件电路框图
3.2硬件电路概述
硬件:PC机,
nKDE—51单片机实验教学系统
1)将CPU板上的单片机P1.0~P1.3(J2或J6的1~4号引脚)和模拟总线接口IO板上的J4相连。
2)将CPU板上的单片机P2(J3或J7)和模拟总线接口IO板上的J5相连,注意P2.0和J5的第1脚相连。
3)将CPU板上的单片机P0.0(J1或J5的1号引脚)和模拟总线接口IO板上DS18B20的DBUS(J9的1号引脚)相连;将CPU板上的COM1和PC机相连。
4)将CPU板上的单片机P3.7(J4或J8的8号引脚)和基本IO板上的蜂鸣器驱动位(J8的最低位)相连。
5)将CPU板上的单片机P0(J1或J5)和基本IO板上的LED灯(J4)相连。
3.3主控电路
主程序需要调用2个子程序,分别为;
1).实时温度显示子程序;驱动数码管把实时温度值送出在LED数码管显示
2).温度显示、报警子程序;1602字符点阵液晶显示温度,当温度超过设定的报警温度值时产生报警,即驱动蜂鸣器鸣叫
3.4显示电路
温度显示模块需要调用10个子程序,分别为;
1).LCM延时程序
2).查询LCM的忙标志/当前AC地址程序
3).LCM清屏程序
4).向LCM写入控制字程序
5).向LCM写入数据程序
6).LCM初始化程序
7).移动光标道X行、Y行程序
8).在指定位置显示一个字符程序
9).从指定的位置开始显示字符串程序
10).将指定位置显示的字符闪烁
3.5报警温度调节电路
当温度小于22摄氏度时,只显示温度。温度大于等于22摄氏度小于23摄氏度,二极管全亮,显示屏警告。温度大于23摄氏度小于25摄氏度时,流水灯低速率循环亮,蜂鸣器Be一声间隔响。温度大于25摄氏度小于27摄氏度时,流水灯高速率循环亮,蜂鸣器Be一声间隔响,间隔时间变短。温度大于27摄氏度时,二极管常亮,蜂鸣器长响。
3.6温度传感器DS18B20测温原理
数字温度传感器模块
1.DS18B20性能
1).独特的单线接口仅需一个端口引脚进行通信
2).简单的多点分布应用
3).无需外部器件
4).可通过数据线供电
5).零待机功耗
6).测温范围-55~+125℃,以0.5℃递增
7).可编程的分辨率为9~12位,对应的可分辨温度分别为0.5℃、0.25℃、0.125℃和0.0625℃
8).温度数字量转换时间200ms,12位分辨率时最多在750ms内把温度转换为数字
9).应用包括温度控制、工业系统、消费品、温度计和任何热感测系统
10).负压特性:电源极性接反时,传感器不会因发热而烧毁,但不能正常工作
2.DS18B20时序图
主机使用时间隙来读写DS18B20的数据位和写命令字的位。
初始化时序如下图:
3. 7硬件电路连接引脚说明
1)将CPU板上的单片机P1.0~P1.3(J2或J6的1~4号引脚)和模拟总线接口IO板上的J4相连。
2)将CPU板上的单片机P2(J3或J7)和模拟总线接口IO板上的J5相连,注意P2.0和J5的第1脚相连。
3)将CPU板上的单片机P0.0(J1或J5的1号引脚)和模拟总线接口IO板上DS18B20的DBUS(J9的1号引脚)相连;将CPU板上的COM1和PC机相连。
4)将CPU板上的单片机P3.7(J4或J8的8号引脚)和基本IO板上的蜂鸣器驱动位(J8的最低位)相连。
5)将CPU板上的单片机P0(J1或J5)和基本IO板上的LED灯(J4)相连。
3.8单片机硬件连接实物图及实验结果
连接实物图:
实验结果:
(1)低于22摄氏度时,正常:LED灯不亮,蜂鸣器不响,显示屏不警报
(2)22~23摄氏度时,二极管常亮,显示屏警报,蜂鸣器不响
(3) 温度大于23摄氏度小于25摄氏度时,流水灯低速率循环亮,蜂鸣器Be一声间隔响。温度大于25摄氏度小于27摄氏度时,流水灯高速率循环亮,蜂鸣器Be一声间隔响,间隔时间变短。
(4) 温度大于27摄氏度时,二极管常亮,蜂鸣器长响。
(5)串口显示:超过21摄氏度,警报,低于21摄氏度,警报消除
四、系统软件算法设计
4.1主程序
主程序mian.c需要调用2个子程序,分别为:
1、实时温度显示子程序:驱动数码管把实时温度值送出在LED数码管显示
2、温度显示、报警子程序:1602字符点阵液晶显示温度,当温度超过设定的报警温度值时产生报警,即驱动蜂鸣器鸣叫。
见附录一;
4.2 子程序
1、实时温度显示子程序:驱动数码管把实时温度值送出在LED数码管显示
2、温度显示、报警子程序:1602字符点阵液晶显示温度,当温度超过设定的报警温度值时产生报警,即驱动蜂鸣器鸣叫。
见附录二;
五、总结与体会
5.1 课程设计过程中遇到的问题及解决方法总结
(1)出现 requires ANSI-styleprototype
分析:函数声明出错,声明函数时,需要注意大小写的问题keil软件中,无法自动识别。
解决:仔细查看,修改。
(2)多余函数未调用,UNCALLEDSEGMENT,IGNORED FOR OVERLAY PROCESS
分析:教材上定义函数,应该都利用到了,此时应该思考,是否打错问题。
解决:由于惯性思维,空函数(_nop()_)定义头文件是,打的strings.h。
所以出现了,函数未调用,占用多余空间的问题。
(3)出现 illegal pointerconversion 非法指针转换
分析:可疑指针转换
解决:查看被调用的函数原型,检查入口参数是否是与原型一致的函数。
(4)显示屏无法正常显示
分析;无法显示的几种原因:
1)程序错误;2)器件损坏;3)电路链接错误;4)串口链接失败
解决:1)将程序导入其他单片机系统,运行成功,排除。
2)换线和显示屏,还是显示不出来。
3)仔细查看电路链接,发现控制显示屏的IO 板上J4与单片机J1相连接,正确连接之后,显示屏正常显示。
(5)串口显示杂乱
分析:可以通过换行,使得想要查询的内容,即温度,更清晰的看见。
解决:修改程序,进行换行处理:
printf("\r\nTemperature code HI=%02bX,LO=%02bX ",tmph,tmpl);
if(t.z>=22)
printf("\nWarning!!!Temperature = %d.%04d",t.z,t.x) ; //大于22度温度警告
else
printf("\r\n Temperature = %d.%04d",t.z,t.x);
5.2 课程设计体会
在本次课程设计中,让我受益匪浅,无论是专业知识上还是学习态度上,都有很大改变。
专业知识上,之前都是在课上听老师讲课,和对着课本记忆各种指令,程序,用法,通过这次课设,真正接触到了单片机,它各个接口的含义,在课程设计中渐渐得到更深的了解,记忆也更加深刻。
学习态度上,首先,不能惯性思维,在接触到新的知识的时候,仔细研究,而不是根据以往经验,泛泛而谈;其次,不能粗心大意,课程设计的程序很多,稍作不注意就会拼写错误,或者大小写出问题,以至于Target not creat;最后,遇到问题,要善于去解决,,在每次弄清楚自己的错误,解决运行成功的时候,很是满足,很有成就感,相信做其他事也是这样,不断探索,会有意想不到的收获。
这次课程设计,得到了老师和同学的帮助,很是感谢。
六、参考文献
[1]李广弟等.单片机基础[M].北京航空航天出版社,2001.
[2]王东峰等.单片机C语言应用100例[M].电子工业出版社,2009.
[3]陈海宴.51单片机原理及应用[M].北京航空航天大学出版社,2010.
[4]刘守义等.单片机技术基础[M].西安电子科技大学出版社,2007.
附录一:
main.c:
#include <reg51.h>
#include <intrins.h>
#include <stdio.h>
#include "lcm16x2p.h"
#include "DS18B20.h"
#define OSC 11059200 //晶振频率
#define BAUDRATE 9600 //波特率
#define LCM_DB P2
sbit BUZ_CON = P3^7;
sbit LED =P0;
void main(void){
unsigned charucTH,ucTL,Ticks; //中断程序
unsigned int i;
unsigned char tmph,tmpl;
Temp t;
unsigned charstrTemp[6]; //显示到屏幕的温度数据
LCM_BLC = 0; //开背光
TMOD = 0x21; //选择方式2作为波特率发生器
SCON = 0x50; //串口方式1,允许中断
PCON |= 0x80; //SMOD=1
TL1 = 256 -(OSC/12/16/BAUDRATE);
TH1 = 256 -(OSC/12/16/BAUDRATE);
TR1 = 1; //启动定时器
TI = 1; //TI有效
//检测DS18B20温度传感器是否存在并复位传感器
if(DSReset())
printf("\r\Temp sensor ResetOK!");
else printf("\r\Temp Sensor Notready!");
while(1){
DSReset(); //复位传感器
Delay(12);
DSWriteByte(SkipROM); //跳读 省时
DSWriteByte(StartConvert); //温度转换
for(i=0;i<40000;i++);
DSReset();
Delay(12);
DSWriteByte(SkipROM);
DSWriteByte(ReadMemory); //读RAM程序
tmpl = DSReadByte();
tmph = DSReadByte();
printf("\r\nTemperature code HI=%02bX,LO=%02bX ",tmph,tmpl);
DSReadTemp(&t);
//准备输出到显示屏的数据
strTemp[0]=t.z/10+0x30; //十位
strTemp[1]=t.z%10+0x30; //个位
strTemp[2]='.'; //小数点
strTemp[3]=t.x/1000+0x30; //十分位
strTemp[4]=t.x%1000/100+0x30; //百分位
strTemp[5]='C';
if(t.z>=22)
printf("\nWarning!!!Temperature= %d.%04d ",t.z,t.x) ; //大于22度温度警告
else
printf("\r\nTemperature = %d.%04d",t.z,t.x);
if(t.z>=22&&t.z<23){
P0=0x00; //大于22小于23度LED灯全亮
}
else
{
P0=0xff; //灯灭
}
if(t.z>=23){
BUZ_CON=0;
LCMDelay(100);
BUZ_CON=1;
P0=0xfe;
LCMDelay(60);
P0=0xfd;
LCMDelay(60);
P0=0xfb;
LCMDelay(60);
P0=0xf7;
LCMDelay(60);
P0=0xef;
LCMDelay(60);
P0=0xdf;
LCMDelay(60);
P0=0xbf;
LCMDelay(60);
P0=0x7f;
LCMDelay(60);
P0=0xff; //大于23度流水灯亮,蜂鸣器响
if(t.z>=25)
{
BUZ_CON=0;
LCMDelay(100);
BUZ_CON=1;
P0=0xfe;
LCMDelay(20);
P0=0xfd;
LCMDelay(20);
P0=0xfb;
LCMDelay(20);
P0=0xf7;
LCMDelay(20);
P0=0xef;
LCMDelay(20);
P0=0xdf;
LCMDelay(20);
P0=0xbf;
LCMDelay(20);
P0=0x7f;
LCMDelay(20);
P0=0xff; //大于25度流水灯速率变快蜂鸣器间隔时间变短
}
if(t.z>=27)
{
BUZ_CON=0;
P0=0x00; //大于27度时,流水灯长亮,蜂鸣器长响
}
}
else{
BUZ_CON=1; //蜂鸣器不响
}
/* 初始化*/
EA = 0; // 停止所有中断
Ticks = 0;
ucTH =(65536-OSC/12/20)/256; // 计算 50ms 定时的时间常数
ucTL =(65536-OSC/12/20)%256;
TMOD = 0x01; // T0:模式 1,16 位定时器
TH0 = ucTH;
TL0 = ucTL;
ET0 = 1; // T0 允许中断
TR0 = 1; // 启动定时器
EA = 1; // 打开总中断允许
/*输出温度数据到显示屏 */
LCMInit(); //初始化LCM显示器
LCMClear(); //清屏
if(t.z>=22){
LCMDisplayString(0,0,"TempWarning!!!"); //大于22度警告
LCMDisplayString(1,0,"Temp:");
LCMDisplayString(1,6,strTemp);
}
else{
LCMDisplayString(0,0,"Temperature:");
LCMDisplayString(1,0,strTemp);
}
//闪烁(刷新)
for(i = 0; i < 16;i++)
LCMBlink(0,i,BLINK);
for(i = 0; i < 16;i++)
LCMBlink(1,i,BLINK);
}
}
附录二:
DS18B20.h
#ifndef __DS18B20H__
#define __DS18B20H__
#define ReadROM 0x33
#define MatchROM 0x55
#define ReadMemory 0xBE
#define SkipROM 0xCC
#define SearchROM 0xF0
#define StartConvert 0x44 //变换指令
typedef struct{
int z; //整数部分
int x; //小数部分
}Temp;
extern unsigned char ucTH,ucTL,Ticks; //外部引用
void Delay(unsigned int);
bit DSReset(void);
void DSWriteByte(char);
unsigned char DSReadByte(void);
void DSReadTemp(Temp *t);
#endif
DS18B20.c
#include "DS18B20.h"
#include <reg51.h>
#include <intrins.h>
sbit DQ = P0^0;
/*
Delay
通过循环计时
参数:int,表示要延时的 毫秒 数
*/
void Delay(unsigned int i)
{
while(i--);
}
/*
DSReset
复位DS18B20并返回是否存在
*/
bit DSReset(void)
{
bit x;
DQ = 1;
Delay(8);
DQ = 0; // 主机拉低总线
Delay(80); // 延时约 500 个机器周期
DQ = 1; // 主机释放总线
Delay(8); // 延时 60 个机器周期
x = DQ;
Delay(20);
if(x == 0)
return 1;
else return 0;
}
/*
DSWriteByte
向 DS18820 写入一个字节
*/
void DSWriteByte(unsigned char c)
{
unsigned char ic;
for( ic = 0; ic < 8;ic++ )
{
DQ = 0; //主机拉低总线,开始写位
DQ = c&0x01;
Delay(5); //延时60个机器周期
DQ = 1; //释放总线
c >>= 1;
}
}
/*
DSReadByte
从温度传感器读出一个字节
返回:读出的字节
*/
unsigned char DSReadByte(void)
{
unsigned char c,ic;
c = 0;
for(ic = 0; ic < 8;ic++)
{
DQ = 0;
c >>= 1; //右移
DQ = 1;
if(DQ )
c |= 0x80;
Delay(4);
}
return c;
}
/*
DSReadTemp
读出温度
参数:温度(由整数和小数部分构成)
*/
void DSReadTemp(Temp *t)
{
unsigned char tmpl,tmph;
DSReset();
Delay(12);
DSWriteByte(SkipROM);
DSWriteByte(StartConvert);
DSReset();
Delay(12);
DSWriteByte(SkipROM);
DSWriteByte(ReadMemory);
tmpl = DSReadByte();
tmph = DSReadByte();
t->z =(int)((tmph&0x07)<<4 | (tmpl&0xf0)>>4);
t->x =(int)((tmpl&0x0f)*625);
}
/*
T0ISR
50ms中断服务程序
*/
void T0ISR(void) interrupt 1
{
unsigned charucTH,ucTL,Ticks;
TH0 = ucTH;
TL0 = ucTL;
TR0 = 1;
Ticks ++;
if(Ticks == 20)
{
Ticks = 0;
}
}
lcm16x2p.h
#ifndef LCM16X2_H
#define LCM16X2_H
#define BUSYFLAG 0x80 //忙标志
#define BLINK 0x01 //闪烁
#define NOBLINK 0x00 //不闪烁
sbit LCM_RS = P1 ^ 0;
sbit LCM_RW = P1 ^ 1;
sbit LCM_E = P1 ^ 2;
sbit LCM_BLC = P1 ^ 3;
unsigned char LCMReadState(void);
void LCMDelay(int);
void LCMWriteCmd(unsigned char);
void LCMWriteData(unsigned char);
void LCMInit(void);
void LCMClear(void);
void LCMGotoXY(unsigned char, unsigned char);
void LCMDisplayString(unsigned char, unsigned char, unsigned char*);
void LCMBlink(unsigned char, unsigned char, unsigned char);
#endif
lcm16x2p.c
#include <reg51.h>
#include <intrins.h> //因为程序中出现了_nop_()空指令
#include "lcm16x2p.h"
#define LCM_DB P2
/*
LCMDelay
通过循环方式延时
int 表示要延时的毫秒数
*/
void LCMDelay(int ms) {
unsigned int dataDelayConst = 125;
unsigned int i, cnt;
cnt = DelayConst * ms;
for (i = 0; i < cnt;i++);
}
/*
LCMReadState
查询LCM的忙标志/当前AC地址
返回:BYTE,最高bit为1表示忙,为0表示闲
*/
unsigned char LCMReadState(void){
unsigned char state;
LCM_E = 0; //E为总线周期有效显示,低电平无效
LCM_RS = 0; //RS为寄存器选择线,低电平指令寄存器操作
LCM_RW = 1; //RW为读写选择线,高电平读操作
LCM_E = 1; //高电平有效
_nop_(); //空指令,较为精确的控制延迟时间
_nop_();
state = LCM_DB;
LCM_E = 0;
return state;
}
/*
LCMClear
清屏
*/
void LCMClear(void){
LCMDelay(1);
LCM_E = 0;
LCM_RS = 0; //指令寄存器
LCM_RW = 0; //写操作
LCM_DB = 0x01;
LCM_E = 1;
_nop_(); //空指令,较为精确的控制延迟时间
_nop_();
LCM_E = 0;
LCMDelay(1);
}
/*
LCMWriteCmd
向LCM写入控制字
BYTE,命令字节。写入前不判断忙表示(因为初始化过程中不能判断)
*/
void LCMWriteCmd(unsigned char cmd){
LCMDelay(1);
LCM_E = 0;
LCM_RS = 0;
LCM_RW = 0;
LCM_DB = cmd;
LCM_E = 1;
_nop_();
_nop_();
LCM_E = 0;
}
/*
LCMWriteData
向LCM写入数据
BYTE,将要写入数据
*/
void LCMWriteData(unsigned char dc){
LCM_RS = 1;
LCM_RW = 0;
LCM_DB = dc;
LCM_E = 1; //高电平有效
_nop_();
_nop_();
LCM_E = 0;
}
/*
LCMInit
初始化LCM
DWORD,晶体频率(Hz),供计算延时参数
*/
void LCMInit(void)
{
LCMDelay(60); //延时60ms,等待LCM复位
LCMWriteCmd(0x38); //功能设置:8位接口,2行,5x7字符点阵
LCMDelay(5); //延时
LCMWriteCmd(0x38); //第二次
LCMDelay(1); //延时
LCMWriteCmd(0x38); //此后可以通过监测忙标志判断指令执行情况
while(LCMReadState()& BUSYFLAG);
LCMWriteCmd(0x08); //关闭显示
while(LCMReadState()& BUSYFLAG);
LCMWriteCmd(0x01); //清屏
while(LCMReadState()& BUSYFLAG);
LCMWriteCmd(0x06); //显示地址自动增量,整体不移位
while(LCMReadState()& BUSYFLAG);
LCMWriteCmd(0x0e); //开显示,开光标,不闪烁
while(LCMReadState()& BUSYFLAG);
}
/*
LCMGotoXY
移动光标到X行Y列
BYTE,X行(0,1)Y列(横向,取值0~0x0f)
*/
void LCMGotoXY(unsigned char x, unsigned char y) // x;行(0~1)y;列(0~F)
{
unsigned char cmd;
if(x == 0){
cmd = 0x80 | y;
}
else{
cmd = 0x80 | 0x40| y;
}
LCMWriteCmd(cmd);
while(LCMReadState()& BUSYFLAG);
}
/*
LCMDisplayChar
在指定位置显示一个字符
X行(0,1)Y列(横向,取值0~0x0f),ch表示将要显示的字符(ASSCII码)
*/
void LCMDisplayChar(unsigned char x, unsigned char y, unsigned charch){
LCMGotoXY(x,y);
LCMWriteData(ch);
}
/*
LCMDisplayString
从指定位置开始显示字符
X行(0,1)Y列(横向,取值0~0x0f),*str为指向将要显示的字符串的指针
*/
void LCMDisplayString(unsigned char x, unsigned char y, unsignedchar* str){
unsigned char ptr;
ptr = 0;
while(*(str+ptr) != 0)
{
LCMDisplayChar(x, (y+ptr), *(str+ptr));
ptr++;
}
}
/*
LCMBlink
将指定位置显示的字符闪烁(同时关闭光标)
X行(0,1)Y列(横向,取值0~0x0f),cmd=BLINK:闪烁, NOBLINK:不闪烁
*/
void LCMBlink(unsigned char x, unsigned char y, unsigned char cmd)
{
LCMGotoXY(x,y);
if(cmd == BLINK)
{
LCMWriteCmd(0x0d);
}
else
{
LCMWriteCmd(0x0c);
}
}