找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 3011|回复: 1
打印 上一主题 下一主题
收起左侧

关于鱼缸水位控制 单片机+PCF8591水位模拟温度采集程序+Proteus仿真

[复制链接]
跳转到指定楼层
楼主
仿真原理图如下(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)

评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏3 分享淘帖 顶 踩
回复

使用道具 举报

沙发
ID:738347 发表于 2020-5-4 15:15 | 只看该作者
受益很多,谢谢!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表