花了1周时间从零基础搞定C语言基础,再通过51单片机程序仿真--PIC开发板实物操作,以下是本人学习过程中编写的一个源程序
以下源程序是基于MPLAB IDE 8.73开发平台,编译器是PICC。
其它编译器大同小异,
这是一个1602LCD的驱动显示程序和DS18B20的驱动,此源程序实用性不大,只是给初学者一个参考,
温度显示范围从-55度-+125度,精确到0.1度。单片机为PIC16F877A,晶振16MHZ
对于初学者来说,自己学会编写一个驱动程序,也是一件快乐的事情。
********************************************************************************************************
#include
__CONFIG(HS&WDTDIS&LVPDIS);
//////////////////////////////////////////////////////////////
/****************LCD1602液晶显示相关函数*********************/
//////////////////////////////////////////////////////////////
//1602液晶相关I/O设置
#define E RB3 //1602液晶的E脚接在RB3口上
#define RW RB4 //1602液晶的RW脚接在RB4口上
#define RS RB5 //1602液晶的RS脚接在RB5口上
//////////////////////////////////////////////////////////////
/****************DS18B20温度检测相关函数*********************/
//////////////////////////////////////////////////////////////
unsigned char temp1=0; //正负温度判断变量,temp1为1时表示负温,为0时标识正温
float temp=125.0; //温度变量,默认125度
/***********************短延时函数***************************/
/*16MHZ晶振下,FOSE/4分频,单个周期是0.25US*/
/*延时时间为:(10个固定周期2.5US)+(次数*【1.5US(5+1个周期)】=总延时时间)*/
void Delay(unsigned char t) //跳入这个循环需要3个指令周期
{ //跳出这个循环需要3个指令周期
while(t>0) //while第一次判断语句要消耗3个指令周期,之后需要5个
t--; //t=t-1,消耗1个指令周期
} //也就是说t=0时需要10个指令周期
/***********************长延时函数***************************/
/*a=6,c=72时是1MS延时*/
void Time(unsigned int a) //跳入这个循环需要3个指令周期
{ //跳出这个循环需要3个指令周期
unsigned int b,c; //定义int型变量需要2个周期,这里有2个所以需要4个周期
asm("NOP");
asm("NOP");
for(b=0;a>b;b++) //第一次需要10个指令周期,之后需要11个指令周期
//定义int型变量需要2个指令周期,判断需要9个指令周期,++操作需要1个指令周期
for(c=72;c>0;c--) ; //定义int需要2个指令周期,判断需要7个指令周期,--操作需要1个指令周期(C=85,a=1是200US)
} //--循环体内部需要2+8*N+N个指令周期,也就是0.5US+(2US*N)+(0.25US*N)+NPLL(0.25US)
//////////////////////////////////////////////////////////////
/****************LCD1602液晶显示相关函数*********************/
//////////////////////////////////////////////////////////////
/*******************LCD1602忙检测函数************************/
void LCD1602_busy(void)
{
TRISD7=1; //将RD7口设置为输入口,为读做准备
RS=0; //RS=0、RW=1、E=1时,忙信号输出到DB7,由RD7读入
RW=1; //RS=0、RW=1、E=1时,忙信号输出到DB7,由RD7读入
E=1; //RS=0、RW=1、E=1时,忙信号输出到DB7,由RD7读入
while(RD7==1); //由RD7读入1,表示1602液晶忙,需要等待
E=0; //读完以后,恢复E的电平
TRISD7=0; //将RD7口设置为输出口
}
/************LCD1602写指令函数(不带忙检测)*****************/
void LCD1602_Write_com(unsigned char combuf)
{
RS=0; //选择指令寄存器
RW=0; //选择写状态
PORTD=combuf; //将命令字通过RD口送至DB
E=1; //E高电平将命令字写入1602液晶
Delay(0); //添加一个空操作,使高电平至少维持1us
E=0; //写完以后,恢复E的电平
}
/*************LCD1602写指令函数(带忙检测)******************/
void LCD1602_Write_com_busy(unsigned char combuf)
{
LCD1602_busy(); //调用忙检测函数
LCD1602_Write_com(combuf); //调用忙检测函数
}
/*************LCD1602写数据函数(带忙检测)******************/
void LCD1602_Write_data_busy(unsigned char databuf)
{
LCD1602_busy(); //调用忙检测函数
RS=1; //选择数据寄存器
RW=0; //选择写状态
PORTD=databuf; //将命令字通过RD口送至DB
E=1; //E高电平将命令字写入1602液晶
Delay(0); //添加一个空操作,使高电平至少维持1us
E=0; //写完以后,恢复E的电平
}
/*****************LCD1602操作指定地址函数********************/
void LCD1602_Write_address(unsigned char x,unsigned char y)
{
x&=0x0f; //列地址限制在0-15间
y&=0x01; //行地址限制在0-1间
if(y==0) //如果是第一行
LCD1602_Write_com_busy(x|0x80); //将列地址写入
else //如果是第二行
LCD1602_Write_com_busy((x+0x40)|0x80); //将列地址写入
}
/**********************LCD1602初始化*************************/
void LCD1602_init(void)
{
Time(6); //延时1MS
LCD1602_Write_com(0x38); //8位数据总线,两行显示模式,5*7点阵显示
Time(1); //延时约167US
LCD1602_Write_com(0x38); //8位数据总线,两行显示模式,5*7点阵显示
Time(1); //延时约167US
LCD1602_Write_com(0x38); //8位数据总线,两行显示模式,5*7点阵显示
LCD1602_Write_com_busy(0x38); //8位数据总线,两行显示模式,5*7点阵显示
LCD1602_Write_com_busy(0x08); //显示功能关,无光标
LCD1602_Write_com_busy(0x01); //清屏
LCD1602_Write_com_busy(0x06); //写入新的数据后,光标右移,显示屏不移动
LCD1602_Write_com_busy(0x0C); //显示功能开,无光标
}
/***************LCD1602显示指定地址内容函数******************/
void LCD1602_Disp(unsigned char x,unsigned char y,unsigned char buf)
{
LCD1602_Write_address(x,y); //先将地址信息写入
LCD1602_Write_data_busy(buf); //再写入要显示的数据
}
/*********************LCD固定内容显示函数********************/
void Fixed_display(void)
{
LCD1602_Disp(2,0,'D');
LCD1602_Disp(3,0,'S');
LCD1602_Disp(4,0,'1');
LCD1602_Disp(5,0,'8');
LCD1602_Disp(6,0,'B');
LCD1602_Disp(7,0,'2');
LCD1602_Disp(8,0,'0');
LCD1602_Disp(2,1,'T');
LCD1602_Disp(3,1,'e');
LCD1602_Disp(4,1,'m');
LCD1602_Disp(5,1,'p');
LCD1602_Disp(6,1,':');
LCD1602_Disp(11,1,'.');
LCD1602_Disp(13,1,0xdf);
LCD1602_Disp(14,1,'C'); //0x43
}
/*********************LCD动态内容显示函数********************/
void Dynamic_Display(void)
{
unsigned char a=0,b=0,c=0,d=0;
if(temp1==1) //限制负温温度不低于零下55度
{
if(temp>55.0) temp=55.0;
}
else if(temp>125.0) temp=125.0; //限制正温温度不高于125度
d=(unsigned char)temp/100; //提取温度百位数值
c=(unsigned char)temp%100/10; //提取温度十位数值
b=(unsigned char)temp%10; //提取温度个位数值
a=(unsigned char)(temp*10)%10; //提取温度小数数值
if(d==0) //判断百位显示是否为0
{
if(temp1==1) //如果温度为零下摄氏度时
{
if(c==0) //又如果温度不低于零下十度时
{
c='-'-0x30; //十位显示“-”负号
d='_'; //百位显示“_”空格
}
else d='-'-0x30; //百位显示“-”负号
}
else //否则温度为正常摄氏度
{
d='_'; //百位显示“_”空格
if(c==0) c='_'; //又如果十位为零时,十位显示“_”空格
}
}
if(a>9) a=9;
LCD1602_Disp(8,1,d+0x30); //显示温度百位数值
LCD1602_Disp(9,1,c+0x30); //显示温度十位数值
LCD1602_Disp(10,1,b+0x30); //显示温度个位数值
LCD1602_Disp(12,1,a+0x30); //显示温度小数数值
}
//////////////////////////////////////////////////////////////
/****************DS18B20温度检测相关函数*********************/
//////////////////////////////////////////////////////////////
/******************DS18B20上电复位函数***********************/
unsigned char Reset(void)
{
unsigned char i;
TRISA4=0; //设置RA4位输出口
RA4=0; //拉低DQ总线开始复位
Delay(200); //设定602.5US,DS18B20规定RESET复位时间不低于480US,不大于960US
//定时180-240之间
TRISA4=1; //设置RA4为输入口,以释放总线等电阻拉高总线
Delay(20); //设定62.5US,DS18B20规定等待间隔时间为60-240US
//定时20-60之间
if(RA4==0) //读出数据并保存
i=0;
else i=1;
Delay(200); //维持约449.5US,符合总读时隙不低于480US的要求
return i; //k=0为复位成功,k=1为复位失败
}
/*******************DS18B20写字节函数************************/
void WriteByte(unsigned char dat)
{
unsigned char i;
for(i=0;i<8;i++)
{
TRISA4=0; //设置RA4位输出口
if(dat&0x01) //如果写1
{
RA4=0; //拉低总线电平
Delay(1); //维持约4US,符合大于1US小于15US的规范
TRISA4=1; //设置RA4为输入口,以释放总线等电阻拉高总线
Delay(39); //维持约61US,符合不低于60US的要求
}
else //如果写0
{
TRISA4=0; //设置RA4位输出口
RA4=0; //拉低
Delay(39); //维持约61US,符合不低于60US的要求
TRISA4=1; //设置RA4为输入口,以释放总线等电阻拉高总线
Delay(0); //维持约2.5US,符合大于1US规范
}
dat=dat>>1; //写入字节右移1位
}
}
/*******************DS18B20读字节函数************************/
unsigned char ReadByte(void)
{
unsigned char i,buf=0;
for(i=1;i<=8;i++) //接收8次还原一个字节数据
{
buf=buf>>1; //接收前,先将接收缓冲区右移
TRISA4=0; //设置RA4位输出口
RA4=0; //拉低
Delay(2); //维持7US,符合大于1US小于15US的规范
TRISA4=1; //设置RA4为输入口,以释放总线等电阻拉高总线 准备读
Delay(2); //维持约7US,符合大于1US小于15US的规范
if(RA4==1) buf|=0x80; //读出1位数据保存于buf中最高位
Delay(33); //维持约52US,符合总读时隙不低于60US的要求
}
return buf; //退出的同时将接收缓冲区参数返回
}
/******************DS18B20启动转换函数***********************/
unsigned char Convert(void)
{
if(Reset()==0) //如果复位成功
{ Time(3); //延时0.5MS
WriteByte(0xcc); // 跳过多器件识别
WriteByte(0x44); // 启动温度转换
return 1;
}
else
{
return 0;
}
}
/******************DS18B20读温度函数*************************/
void ReadFlash(void)
{
unsigned int i;
unsigned char Lsb,Msb;
if(Reset()==0)
{
Time(3); //延时0.5MS
WriteByte(0xcc); // 跳过多器件识别
WriteByte(0xbe); // 读暂存器
Lsb=ReadByte(); // 低字节
Msb=ReadByte(); // 高字节
i=Msb;
i<<=8;
i=i|Lsb;
if(Msb>0x07) //如果判断温度为零下摄氏度时
{
temp1=1; //把零下标识符temp1置1
i=(~i)+1; //读取温度取反加1
temp=(float)i*0.0625; //转换为真实温度并把结果转换成浮点数
}
else
{
temp1=0;
temp=(float)i*0.0625; //转换为真实温度并把结果转换成浮点数
}
}
else //那么没有采集到温度就把初始温度设置为125度
{
temp=125.0;
}
}
/*************************主函数*****************************/
void main(void)
{
TRISA=0xff; //初始化RA5-RA0的输入输出方向
PORTA=0x00; //初始化RA5-RA0数值
TRISB=0xc7; //初始化对RB口的初始方向进行设置 设置为输出
TRISD=0x00; //初始化RD口的初始方向进行设置,设置为输出
PORTB=0x00; //初始化RB口的数值
PORTD=0x00; //初始化RD口的数值
Time(6); //延时1MS秒,做内容切换显示
LCD1602_init(); //初始化
LCD1602Fixed_display(); //LCD固定内容显示
/**************************死循环****************************/
while(1) //死循环,单片机初始化后,将一直运行这个死循环
{
if(Convert()==1) //启动转换
{
ReadFlash(); //读取温度
Dynamic_Display(); //读取内容显示
}
}
}
********************************************************************************************************