仿真原理图如下(proteus仿真工程文件可到本帖附件中下载)
单片机源程序如下:
Mob Qing:
#include <reg52.h>
#include <intrins.h>
typedef unsigned char u8; // 重命名类型u8简化代码编写
typedef unsigned int u16;
#define LCD1602_DATA_PORT P0 // LCD1602的8位数据端口
#define PCF8591 0x90 //PCF8591 地址
#define NOP() _nop_() /* 定义空指令 */
#define _Nop() _nop_() /*定义空指令*/
sbit gLcd1602_E = P2^7; // LCD1602控制总线的使能信号
sbit gLcd1602_RW = P2^6; // LCD1602控制总线的读写选择信号
sbit gLcd1602_RS = P2^5; // LCD1602控制总线的数据/命令选择信号
sbit SCL = P2^0; //I2C 时钟
sbit SDA = P2^1; //I2C 数据
sbit Dj = P3^1; //电机控制信号
sbit gIO = P3^7; // DS18B20的单总线IO接在P3.7上
bit ack; /*应答标志位*/
u16 tDisp = 0; // 用来存储乘以10倍后的温度值
/*************** 注意下面的是延时函数 ******************/
void delay15us(void)
{
unsigned char a;
for(a=6;a>0;a--);
}
void delay45us(void)
{
unsigned char a;
for(a=21;a>0;a--);
}
void delay70us(void)
{
unsigned char a,b;
for(b=1;b>0;b--)
for(a=32;a>0;a--);
}
void delay750us(void)
{
unsigned char a,b;
for(b=83;b>0;b--)
for(a=3;a>0;a--);
}
void delay1ms(void)
{
unsigned char a,b,c;
for(c=1;c>0;c--)
for(b=142;b>0;b--)
for(a=2;a>0;a--);
}
void delay5ms(void)
{
unsigned char a,b;
for(b=19;b>0;b--)
for(a=130;a>0;a--);
}
void delay250ms(void) //误差 0us
{
unsigned char a,b,c;
for(c=11;c>0;c--)
for(b=92;b>0;b--)
for(a=122;a>0;a--);
}
/*******************************************************************
起动总线函数
函数原型: void Start_I2c();
功能: 启动I2C总线,即发送I2C起始条件.
********************************************************************/
void Start_I2c()
{
SDA=1; /*发送起始条件的数据信号*/
_Nop();
SCL=1;
_Nop(); /*起始条件建立时间大于4.7us,延时*/
_Nop();
_Nop();
_Nop();
_Nop();
SDA=0; /*发送起始信号*/
_Nop(); /* 起始条件锁定时间大于4μs*/
_Nop();
_Nop();
_Nop();
_Nop();
SCL=0; /*钳住I2C总线,准备发送或接收数据 */
_Nop();
_Nop();
}
/*********************************************************
Mob Qing:
/*******************************************************************
结束总线函数
函数原型: void Stop_I2c();
功能: 结束I2C总线,即发送I2C结束条件.
********************************************************************/
void Stop_I2c()
{
SDA=0; /*发送结束条件的数据信号*/
_Nop(); /*发送结束条件的时钟信号*/
SCL=1; /*结束条件建立时间大于4μs*/
_Nop();
_Nop();
_Nop();
_Nop();
_Nop();
SDA=1; /*发送I2C总线结束信号*/
_Nop();
_Nop();
_Nop();
_Nop();
}
/*******************************************************************
字节数据发送函数
函数原型: void SendByte(UCHAR c);
功能: 将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,并对
此状态位进行操作.(不应答或非应答都使ack=0)
发送数据正常,ack=1; ack=0表示被控器无应答或损坏。
********************************************************************/
void SendByte(unsigned char c)
{
unsigned char BitCnt;
for(BitCnt=0;BitCnt<8;BitCnt++) /*要传送的数据长度为8位*/
{
if((c<<BitCnt)&0x80)SDA=1; /*判断发送位*/
else SDA=0;
_Nop();
SCL=1; /*置时钟线为高,通知被控器开始接收数据位*/
_Nop();
_Nop(); /*保证时钟高电平周期大于4μs*/
_Nop();
_Nop();
_Nop();
SCL=0;
}
_Nop();
_Nop();
SDA=1; /*8位发送完后释放数据线,准备接收应答位*/
_Nop();
_Nop();
SCL=1;
_Nop();
_Nop();
_Nop();
if(SDA==1)ack=0;
else ack=1; /*判断是否接收到应答信号*/
SCL=0;
_Nop();
_Nop();
}
/*******************************************************************
字节数据接收函数
函数原型: UCHAR RcvByte();
功能: 用来接收从器件传来的数据,并判断总线错误(不发应答信号),
发完后请用应答函数应答从机。
********************************************************************/
unsigned char RcvByte()
{
unsigned char retc;
unsigned char BitCnt;
retc=0;
SDA=1; /*置数据线为输入方式*/
for(BitCnt=0;BitCnt<8;BitCnt++)
{
_Nop();
SCL=0; /*置时钟线为低,准备接收数据位*/
_Nop();
_Nop(); /*时钟低电平周期大于4
Mob Qing:
SCL=0;
_Nop();
_Nop();
return(retc);
}
Mob Qing:
应答子函数
函数原型: void Ack_I2c(bit a);
功能: 主控器进行应答信号(可以是应答或非应答信号,由位参数a决定)
********************************************************************/
void Ack_I2c(bit a)
{
if(a==0)SDA=0; /*在此发出应答或非应答信号 */
else SDA=1;
_Nop();
_Nop();
_Nop();
SCL=1;
_Nop();
_Nop(); /*时钟低电平周期大于4μs*/
_Nop();
_Nop();
_Nop();
SCL=0; /*清时钟线,钳住I2C总线以便继续接收*/
_Nop();
_Nop();
}
/*********************************************************************
* 函 数 名 : Lcd1602WaitNoBusy
* 函数功能 : 阻塞等待LCD1602直到不忙状态
* 参数列表 : 无
* 函数输出 : 无
*********************************************************************/
void Lcd1602WaitNoBusy(void) //忙检测函数,判断bit7是0,允许执行;1禁止
{
u8 sta = 0; //
LCD1602_DATA_PORT = 0xff;
gLcd1602_RS = 0;
gLcd1602_RW = 1;
do
{
gLcd1602_E = 1;
sta = LCD1602_DATA_PORT;
gLcd1602_E = 0; //使能,用完就拉低,释放总线
}while(sta & 0x80);
}
/*********************************************************************
* 函 数 名 : Lcd1602WriteCmd
* 函数功能 : 按照LCD1602低层时序向LCD内部写入8位命令字
* 参数列表 : cmd - 待写入的8位命令字
* 函数输出 : 无
*********************************************************************/
Mob Qing:
void Lcd1602WriteCmd(u8 cmd)
{
Lcd1602WaitNoBusy(); // 先等待LCD1602处于不忙状态
gLcd1602_E = 0; // 禁止LCD
gLcd1602_RS = 0; // 选择发送命令模式
gLcd1602_RW = 0; // 选择写入模式
LCD1602_DATA_PORT = cmd; // 将1字节命令字放入8位并行数据端口
gLcd1602_E = 1; // 使能LED
gLcd1602_E = 0; // 禁止LCD
}
/*********************************************************************
* 函 数 名 : Lcd1602WriteData
* 函数功能 : 按照LCD1602低层时序向LCD内部写入8位数据
* 参数列表 : cmd - 待写入的8位命令字
* 函数输出 : 无
*********************************************************************/
void Lcd1602WriteData(u8 dat)
{
Lcd1602WaitNoBusy(); // 先等待LCD1602处于不忙状态
gLcd1602_E = 0; // 禁止LCD
gLcd1602_RS = 1; // 选择发送数据模式
gLcd1602_RW = 0; // 选择写入模式
LCD1602_DATA_PORT = dat; // 将1字节命令字放入8位并行数据端口
gLcd1602_E = 1; // 使能LED
gLcd1602_E = 0; // 禁止LCD
}
/************* 上面是底层时序函数,下面是高层时序函数 **************/
/*********************************************************************
* 函 数 名 : Lcd1602SetCursor
* 函数功能 : 本函数用来设置当前光标位置,其实就是设置当前正在编辑
* 的位置,其实就是内部的数据地址指针,其实就是RAM显存
* 的偏移量
* 参数列表 : x - 横向坐标,范围是0-15
* y - 纵向坐标,0表示上面一行,1表示下面一行
* 函数输出 : 无
*********************************************************************/
Mob Qing:
void Lcd1602SetCursor(u8 x, u8 y) // 坐标显示
{
u8 addr = 0;
switch (y)
{
case 0: // 上面一行
addr = 0x00 + x; break;
case 1: // 下面一行
addr = 0x40 + x; break;
default:
break;
}
Lcd1602WriteCmd(addr | 0x80);
}
/********************************************************************
* 名称 : write_sfm2(uchar hang,uchar add,uchar date)
* 功能 : 显示2位十进制数,如果要让第一行,第五个字符开始显示"23" ,调用该函数如下
write_sfm1(1,5,23)
* 输入 : 行,列,需要输入1602的数据
* 输出 : 无
***********************************************************************/
void write_sfm2(u8 hang,u8 add,u16 date)
{
if(hang==1)
Lcd1602WriteCmd(0x80+add);
else
Lcd1602WriteCmd(0x80+0x40+add);
Lcd1602WriteData(0x30+date/10%10);
Lcd1602WriteData(0x30+date%10);
}
/*********************************************************************
* 函 数 名 : Lcd1602ShowStr
* 函数功能 : 从坐标(x,y)开始显示字符串str,注意这个函数不能跨行
* 显示,因为显存地址是不连续的。
* 参数列表 : x - 横向坐标,范围是0-15
* y - 纵向坐标,0表示上面一行,1表示下面一行
* pStr - 指向待显示的字符串的指针
* 函数输出 : 无
*********************************************************************/
void Lcd1602ShowStr(u8 x, u8 y, u8 *pStr) //显示字符串
{
Lcd1602SetCursor(x, y); //当前字符的坐标
while (*pStr != '\0')
{
Lcd1602WriteData(*pStr++);
}
}
/*********************************************************************
Mob Qing:
* 函 数 名 : Lcd1602Init
* 函数功能 : 按照LCD1602低层时序进行初始化序列
* 参数列表 : 无
* 函数输出 : 无
*********************************************************************/
void Lcd1602Init(void)
{
Lcd1602WriteCmd(0x38); // 按照数据手册的初始化时序,先发送38H
delay5ms(); // 延时5ms
Lcd1602WriteCmd(0x38); // 按照数据手册的初始化时序,先发送38H
delay5ms(); // 延时5ms
Lcd1602WriteCmd(0x38); // 按照数据手册的初始化时序,先发送38H
delay5ms(); // 延时5ms
Lcd1602WriteCmd(0x38); // 显示模式设置
Lcd1602WriteCmd(0x08); // 关闭显示
Lcd1602WriteCmd(0x01); // 清屏(同时清数据指针)
Lcd1602WriteCmd(0x06); // 读写后指针自动加1,写1个字符后整屏显示不移动
Lcd1602WriteCmd(0x0c); // 开显示,不显示光标
}
void write_sfm(u8 hang,u8 add,u16 date)
{
if(hang==1)
Lcd1602WriteCmd(0x80+add);
else
Lcd1602WriteCmd(0x80+0x40+add);
Lcd1602WriteData('L');
Lcd1602WriteData('e');
Lcd1602WriteData('v');
Lcd1602WriteData('e');
Lcd1602WriteData('l');
Lcd1602WriteData(':');
Lcd1602WriteData(0x30+date/100);
Lcd1602WriteData(0x30+date%100/10);
Lcd1602WriteData(0x30+date%10);
Lcd1602WriteData('C');
Lcd1602WriteData('m');
}
void write_wendu(u8 hang,u8 add,u16 date) //显示温度用
{
if(hang==1)
Lcd1602WriteCmd(0x80+add);
else
Lcd1602WriteCmd(0x80+0x40+add);
Lcd1602WriteData('W');
Lcd1602WriteData('e');
Lcd1602WriteData('n');
Lcd1602WriteData('d');
Lcd1602WriteData('u');
Lcd1602WriteData(':');
Lcd1602WriteData(0x30+date%1000/100);
Lcd1602WriteData(0x30+date%100/10);
Lcd1602WriteData('.');
Lcd1602WriteData(0x30+date%10);
Lcd1602WriteData(0xdf);
Lcd1602WriteData('C');
}
/*******************************************************************
Mob Qing:
ADC发送字节[命令]数据函数
*******************************************************************/
bit ISendByte(unsigned char sla,unsigned char c)
{
Start_I2c(); //启动总线
SendByte(sla); //发送器件地址
if(ack==0)return(0);
SendByte(c); //发送数据
if(ack==0)return(0);
Stop_I2c(); //结束总线
return(1);
}
/*******************************************************************
ADC读字节数据函数
*******************************************************************/
unsigned char IRcvByte(unsigned char sla)
{ unsigned char c;
Start_I2c(); //启动总线
SendByte(sla+1); //发送器件地址
if(ack==0)return(0);
c=RcvByte(); //读取数据0
Ack_I2c(1); //发送非就答位
Stop_I2c(); //结束总线
return(c);
}
/*********************************************************************
* 函 数 名 : Ds18b20Init
* 函数功能 : 按照DS18B20底层时序要求进行传感器初始化
* 参数列表 : 无
* 函数输出 : 若初始化成功则返回0,否则返回1
*********************************************************************/
Mob Qing:
u8 Ds18b20Init(void)
{
u8 i = 0;
gIO = 0; // 时序要求将总线拉低480us~960us
delay750us(); // 实际延时750us,符合480-960之间的条件
gIO = 1; // 然后拉高总线,如果DS18B20做出反应会将在15us~60us后总线被拉低
i = 0;
while (gIO) // 等待DS18B20拉低总线
{
i++;
if(i>5) // 等待 15*5=75us,如果还没拉低则可以认为初始化失败了
{
return 1; // 初始化失败
}
delay15us(); // 隔15us查看一下是否收到DS18B20的回应
}
return 0; //初始化成功
}
/*********************************************************************
* 函 数 名 : Ds18b20WriteByte
* 函数功能 : 按照DS18B20底层时序要求向DS18B20写入1字节数据
* 参数列表 : dat - 待写入的1字节数据
* 函数输出 : 无
*********************************************************************/
static void Ds18b20WriteByte(u8 dat)
{
u16 i = 0, j = 0;
for (j=0; j<8; j++)
{
gIO = 0; // 每写入一位数据之前先把总线拉低1us
i++;
gIO = dat & 0x01; // 然后写入一个数据,从最低位开始
delay70us(); // 时序要求最少60us
gIO = 1; // 然后释放总线,至少1us给总线恢复时间才能接着写入第二个数值
dat >>= 1;
}
}
/*********************************************************************
* 函 数 名 : Ds18b20ReadByte
* 函数功能 : 按照DS18B20底层时序要求从DS18B20中读取1字节数据
* 参数列表 : 无
* 函数输出 : 返回读取到的1字节数据
*********************************************************************/
u8 Ds18b20ReadByte(void)
{
u8 byte = 0, bi = 0;
u16 i = 0, j = 0;
for (j=8; j>0; j--)
{
gIO = 0; // 先将总线拉低1us
i++;
gIO = 1; // 然后释放总线
i++;
i++; // 延时6us等待数据稳定
bi = gIO; // 读取数据,从最低位开始读取
/*将byte左移一位,然后与上右移7位后的bi,注意移动之后移掉那位补0。*/
byte = (byte >> 1) | (bi << 7);
//byte |= (bi << (8-j));
delay45us();
}
return byte;
}
Mob Qing:
void Ds18b20TempConvertCmd(void)
{
Ds18b20Init();
delay1ms();
Ds18b20WriteByte(0xcc); // 跳过ROM操作命令
Ds18b20WriteByte(0x44); // 温度转换命令
delay250ms(); // 等待转换成功,750ms肯定够了
}
void Ds18b20TempReadCmd(void)
{
Ds18b20Init();
delay1ms();
Ds18b20WriteByte(0xcc); // 跳过ROM操作命令
Ds18b20WriteByte(0xbe); // 发送读取温度命令
}
/*********************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 参数列表 : 无
* 函数输出 : 无
*********************************************************************/
u16 Adc_value = 0;
void main(void)
{
u8 water_level = 0; //液位显示
u16 temp = 0; // 用来暂存12位的AD值
u8 tmh = 0, tml = 0; // 用来暂存2个8位值
double wendu = 0; // 用来存储转换后以摄氏度为单位的温度值
Lcd1602Init();
Dj = 0;
while (1)
{
ISendByte(PCF8591,0x41);
IRcvByte(PCF8591); //ADC1
ISendByte(PCF8591,0x42);
Adc_value=IRcvByte(PCF8591); //ADC1
water_level = Adc_value/2;
if(water_level>120)water_level=120;
write_sfm(1,0,water_level);
Ds18b20TempConvertCmd(); // 先写入转换命令
Ds18b20TempReadCmd(); // 然后等待转换完后发送读取温度命令
tml = Ds18b20ReadByte(); // 读取温度值共16位,先读低字节
tmh = Ds18b20ReadByte(); // 再读高字节
temp = tml | (tmh << 8); // 默认是12位分辨率,前面4个S位是符号位
wendu = temp * 0.0625;
tDisp = (u16)(wendu * 10); // 为方便显示将温度值乘以10后强转为u16
write_wendu(2,0,tDisp);//温度实时显示后
}
}
全部资料51hei下载地址:
PCF8591水位模拟温度采集.zip
(154.2 KB, 下载次数: 126)
|