一、简介

      为基于微控制器的系统设计的,旨在实现温度和电压的实时测量并将结果显示在LCD屏幕上。核心部件包括DS1621数字温度传感器和MAX1241 A/D转换器:DS1621用于温度测量,而MAX1241则负责电压测量。通过精确读取这些传感器的数据,处理这些数据,并以易于读取的格式在LCD上展示出来,定义相关接口用于初始化和设置一些与LCD及可能的I2C设备相关的硬件接口。

二、头文件选择,变量定义

    在8051微控制器上基于嵌入式C语言的程序片段。定义一系列的宏定义、全局变量和特殊功能位(sbit),用于简化寄存器操作和提高代码可读性。

2.1 头文件:

这是8051微控制器编程中常用的头文件,包括了特定于8051的寄存器定义和sfr(特殊功能寄存器)的宏定义。

2.2 宏定义:

uchar 和 uint 分别为 unsigned char 和 unsigned int 的简化形式,用于声明变量。

High 和 Low 分别定义了逻辑高电平(1)和低电平(0),用于提高代码的可读性。

_nop 定义了一个空操作,通常对应于汇编语言中的NOP指令,这里被定义为空花括号。

2.3 LCD外设地址:

PAGEADD、TIERADD 和 DIS_STARADD 是LCD显示相关的地址,用于LCD内存映射或寄存器操作。

2.4 全局变量:

key_value: 用于存储按键扫描的结果或类似功能的数值。

voltage_display: 一个数组,用来存储要在LCD上显示的电压值。

2.5 特殊功能位:

DI, E, CS1, CS2, RW, cs, sclk 和 dout 是与LCD显示和其它外设通信的控制线,对应于端口1的不同位。

I2C_SCL 和 I2C_SDA 是与I2C总线通信的串行时钟和数据线,这显示了程序可能包含与I2C兼容设备的通信。

2.6 代码:

A

三、定义字符表

       定义一个字符表,其中包含了ASCII字符和一些中文字符的显示数据。通过使用这个字符表,可以在液晶模块上显示出对应的字符,字符表中的每个字符对应一个8x8的字模,每个字模使用8个字节来表示。

其中,table1定义了ASCII字符的字模,table2定义了一些中文字符的字模。

       每个字符在字符表中的索引位置,以及对应的字符,使用这个字符表时,可以根据需要从字符表中取出相应的字模数据,然后根据液晶模块的显示方式进行显示。以下是字符数组

/*****************************
          字符表 
	 显示"Welcome Use"
******************************/
uchar code table1[]={
0x00,0x3e,0x51,0x49,0x45,0x3e,0x00,0x00,//0(0)
0x00,0x00,0x42,0x7f,0x40,0x00,0x00,0x00,//1
0x00,0x42,0x61,0x51,0x49,0x46,0x00,0x00,//2
0x00,0x21,0x41,0x45,0x4b,0x31,0x00,0x00,//3
0x00,0x18,0x14,0x12,0x7f,0x10,0x00,0x00,//4
0x00,0x27,0x45,0x45,0x45,0x39,0x00,0x00,//5
0x00,0x3c,0x4a,0x49,0x49,0x30,0x00,0x00,//6
0x00,0x01,0x01,0x79,0x05,0x03,0x00,0x00,//7
0x00,0x36,0x49,0x49,0x49,0x36,0x00,0x00,//8
0x00,0x06,0x49,0x49,0x29,0x1e,0x00,0x00,//9
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// (10)
0x00,0x00,0x00,0x47,0x00,0x00,0x00,0x00,//!
0x00,0x23,0x13,0x08,0x64,0x62,0x00,0x00,//%
0x00,0x36,0x49,0x55,0x22,0x50,0x00,0x00,//&
0x00,0x14,0x08,0x3e,0x08,0x14,0x00,0x00,//*
0x00,0x08,0x08,0x3e,0x08,0x08,0x00,0x00,//+
0x00,0x08,0x08,0x08,0x08,0x08,0x00,0x00,//-
0x00,0x00,0x60,0x60,0x00,0x00,0x00,0x00,//.
0x00,0x20,0x10,0x08,0x04,0x02,0x00,0x00,///
0x00,0x00,0x36,0x36,0x00,0x00,0x00,0x00,//:(19)
0x00,0x14,0x14,0x14,0x14,0x14,0x00,0x00,//=
0x00,0x02,0x01,0x51,0x09,0x06,0x00,0x00,//?
0x00,0x32,0x49,0x79,0x41,0x3E,0x00,0x00,//@(22)
0x00,0x7e,0x11,0x11,0x11,0x7f,0x00,0x00,//A
0x00,0x41,0x7f,0x49,0x49,0x36,0x00,0x00,//B
0x00,0x3e,0x41,0x41,0x41,0x22,0x00,0x00,//C
0x00,0x41,0x7f,0x41,0x41,0x3e,0x00,0x00,//D
0x00,0x7f,0x49,0x49,0x49,0x49,0x00,0x00,//E
0x00,0x7f,0x09,0x09,0x09,0x01,0x00,0x00,//F
0x00,0x3e,0x41,0x41,0x49,0x7a,0x00,0x00,//G(29)
0x00,0x7f,0x08,0x08,0x08,0x7f,0x00,0x00,//H
0x00,0x00,0x41,0x7f,0x41,0x00,0x00,0x00,//I
0x20,0x40,0x41,0x3f,0x01,0x01,0x00,0x00,//J
0x00,0x7f,0x08,0x14,0x22,0x41,0x00,0x00,//K
0x00,0x7f,0x40,0x40,0x40,0x40,0x00,0x00,//L
0x00,0x7f,0x02,0x0c,0x02,0x7f,0x00,0x00,//M
0x00,0x7f,0x06,0x08,0x30,0x7f,0x00,0x00,//N
0x00,0x3e,0x41,0x41,0x41,0x3e,0x00,0x00,//O
0x00,0x7f,0x09,0x09,0x09,0x06,0x00,0x00,//P
0x00,0x3e,0x41,0x51,0x21,0x5e,0x00,0x00,//Q(39)
0x00,0x7f,0x09,0x19,0x29,0x46,0x00,0x00,//R
0x00,0x26,0x49,0x49,0x49,0x32,0x00,0x00,//S
0x00,0x01,0x01,0x7f,0x01,0x01,0x00,0x00,//T
0x00,0x3f,0x40,0x40,0x40,0x3f,0x00,0x00,//U
0x00,0x1f,0x20,0x41,0x20,0x1f,0x00,0x00,//V
0x00,0x7f,0x20,0x80,0x20,0x7f,0x00,0x00,//W
0x00,0x63,0x14,0x08,0x14,0x63,0x00,0x00,//X
0x00,0x07,0x08,0x70,0x08,0x07,0x00,0x00,//Y
0x00,0x61,0x51,0x49,0x45,0x43,0x00,0x00,//Z
0x00,0x20,0x54,0x54,0x54,0x78,0x00,0x00,//a(49)
0x00,0x20,0x54,0x54,0x54,0x78,0x00,0x00,//b
0x00,0x38,0x44,0x44,0x44,0x28,0x00,0x00,//c
0x00,0x38,0x44,0x44,0x48,0x7f,0x00,0x00,//d	
0x00,0x38,0x54,0x54,0x54,0x18,0x00,0x00,//e
0x00,0x00,0x08,0x7e,0x09,0x02,0x00,0x00,//f
0x00,0x0c,0x52,0x52,0x4c,0x3e,0x00,0x00,//g
0x00,0x7f,0x08,0x04,0x04,0x78,0x00,0x00,//h
0x00,0x00,0x44,0x7d,0x40,0x00,0x00,0x00,//i
0x00,0x20,0x40,0x44,0x3d,0x00,0x00,0x00,//j
0x00,0x00,0x7f,0x10,0x28,0x44,0x00,0x00,//k(59)
0x00,0x00,0x41,0x7f,0x40,0x00,0x00,0x00,//l
0x00,0x7c,0x04,0x78,0x04,0x78,0x00,0x00,//m
0x00,0x7c,0x08,0x04,0x04,0x78,0x00,0x00,//n
0x00,0x38,0x44,0x44,0x44,0x38,0x00,0x00,//o
0x00,0x7e,0x0c,0x12,0x12,0x0c,0x00,0x00,//p
0x00,0x0c,0x12,0x12,0x0c,0x7e,0x00,0x00,//q
0x00,0x7C,0x08,0x04,0x04,0x08,0x00,0x00,//r
0x00,0x58,0x54,0x54,0x54,0x64,0x00,0x00,//s
0x00,0x04,0x3f,0x44,0x40,0x20,0x00,0x00,//t
0x00,0x3c,0x40,0x40,0x3c,0x40,0x00,0x00,//u(69)
0x00,0x1c,0x20,0x40,0x20,0x1c,0x00,0x00,//v
0x00,0x3c,0x40,0x30,0x40,0x3c,0x00,0x00,//w
0x00,0x44,0x28,0x10,0x28,0x44,0x00,0x00,//x
0x00,0x1c,0xa0,0xa0,0x90,0x7c,0x00,0x00,//y
0x00,0x44,0x64,0x54,0x4c,0x44,0x00,0x00,//z(74)
};

/****************************
        中文字符表 
	显示"欢迎使用液晶模块"
*****************************/
uchar code table2[]={
0x14,0x24,0x44,0x84,0x64,0x1c,0x20,0x18,
0x0f,0xe8,0x08,0x08,0x28,0x18,0x08,0x00,
0x20,0x10,0x4c,0x43,0x43,0x2c,0x20,0x10,
0x0c,0x03,0x06,0x18,0x30,0x60,0x20,0x00,//欢(0)
0x40,0x41,0xce,0x04,0x00,0xfc,0x04,0x02,
0x02,0xfc,0x04,0x04,0x04,0xfc,0x00,0x00,
0x40,0x20,0x1f,0x20,0x40,0x47,0x42,0x41,
0x40,0x5f,0x40,0x42,0x44,0x43,0x40,0x00,//迎(1)
0x40,0x20,0xf0,0x1c,0x07,0xf2,0x94,0x94,
0x94,0xff,0x94,0x94,0x94,0xf4,0x04,0x00,
0x00,0x00,0x7f,0x00,0x40,0x41,0x22,0x14,
0x0c,0x13,0x10,0x30,0x20,0x61,0x20,0x00,//使(2)
0x00,0x00,0x00,0xfe,0x22,0x22,0x22,0x22,
0xfe,0x22,0x22,0x22,0x22,0xfe,0x00,0x00,
0x80,0x40,0x30,0x0f,0x02,0x02,0x02,0x02,
0xff,0x02,0x02,0x42,0x82,0x7f,0x00,0x00,//用(3)
0x10,0x61,0x06,0xe0,0x18,0x84,0xe4,0x1c,
0x84,0x65,0xbe,0x24,0xa4,0x64,0x04,0x00,
0x04,0x04,0xff,0x00,0x01,0x00,0xff,0x41,
0x21,0x12,0x0c,0x1b,0x61,0xc0,0x40,0x00,//液(4)
0x00,0x00,0x00,0x00,0x7e,0x2a,0x2a,0x2a,
0x2a,0x2a,0x2a,0x7e,0x00,0x00,0x00,0x00,
0x00,0x7f,0x25,0x25,0x25,0x25,0x7f,0x00,
0x00,0x7f,0x25,0x25,0x25,0x25,0x7f,0x00,//晶(5)
0x10,0xd0,0xff,0x50,0x90,0x04,0xf4,0x54,
0x5f,0x54,0x54,0x5f,0xf4,0x04,0x00,0x00,
0x03,0x00,0xff,0x00,0x00,0x84,0x85,0x45,
0x35,0x0f,0x15,0x25,0x65,0xc4,0x44,0x00,//模(6)
0x10,0x10,0xff,0x10,0x10,0x00,0x08,0x08,
0xff,0x08,0x08,0x08,0xf8,0x00,0x00,0x00,
0x08,0x18,0x0f,0x04,0x85,0x41,0x31,0x0d,
0x03,0x05,0x09,0x11,0x31,0x61,0x21,0x00,//块(7)
0x00,0x00,0xF8,0x48,0x48,0x48,0x48,0xFF, 
0x48,0x48,0x48,0x48,0xF8,0x00,0x00,0x00,
0x00,0x00,0x0F,0x04,0x04,0x04,0x04,0x3F, 
0x44,0x44,0x44,0x44,0x4F,0x40,0x70,0x00,//电(8)
0x00,0x00,0xFE,0x02,0x42,0x42,0x42,0x42,
0xFA,0x42,0x42,0x42,0x62,0x42,0x02,0x00,
0x20,0x18,0x27,0x20,0x20,0x20,0x20,0x20, 
0x3F,0x20,0x21,0x2E,0x24,0x20,0x20,0x00,//压(9)
0x10,0x21,0x86,0x70,0x00,0x7E,0x4A,0x4A, 
0x4A,0x4A,0x4A,0x7E,0x00,0x00,0x00,0x00, 
0x02,0xFE,0x01,0x40,0x7F,0x41,0x41,0x7F,
0x41,0x41,0x7F,0x41,0x41,0x7F,0x40,0x00,//温(10)
0x00,0x00,0xFC,0x04,0x24,0x24,0xFC,0xA5,
0xA6,0xA4,0xFC,0x24,0x24,0x24,0x04,0x00,
0x80,0x60,0x1F,0x80,0x80,0x42,0x46,0x2A, 
0x12,0x12,0x2A,0x26,0x42,0xC0,0x40,0x00,//度(11)
0x08,0x31,0x86,0x60,0x00,0xFE,0x02,0xF2, 
0x02,0xFE,0x00,0xF8,0x00,0x00,0xFF,0x00, 
0x04,0xFC,0x03,0x00,0x80,0x47,0x30,0x0F, 
0x10,0x67,0x00,0x07,0x40,0x80,0x7F,0x00,//测(12)
0x40,0x40,0x40,0xDF,0x55,0x55,0x55,0xD5,
0x55,0x55,0x55,0xDF,0x40,0x40,0x40,0x00, 
0x40,0x40,0x40,0x57,0x55,0x55,0x55,0x7F, 
0x55,0x55,0x55,0x57,0x50,0x40,0x40,0x00,//量(13)
};

四、各种函数介绍

4.1 延时函数

定义一个名为`Delay_nms`的函数,它用于延迟或休眠一段时间。这个函数接收一个无符号字符类型的参数`n`,表示需要延迟的次数。

Delay_nms(uchar n)
{
    // 定义一个无符号字符类型的变量a,用于循环计数
    uchar a;
    // 开始一个空循环,条件是n大于0
    for(;n>0;n--)
    {
        // 开始另一个循环,循环次数为100次
        for(a=0;a<100;a++)
        {
            // 四个_nop指令,它们是NOP(No Operation)指令,不会执行任何操作,只是占用了指令周期,因此可以用于延迟。
            _nop;
            _nop;
            _nop;
            _nop;
        }
    }
}

注释解释:

uchar a:定义一个无符号字符类型的变量a,用于在内部的循环中进行计数。

for(;n>0;n--):一个空循环,只要n大于0就会继续循环,直到n小于或等于0时停止。这可以看作是一个等待或延迟的过程,其中n表示需要等待的次数。

for(a=0;a<100;a++):内部循环,循环次数为100次。这是通过不断增加变量a的值来实现的。由于是无符号字符类型,最大值是255。

_nop; _nop; _nop; _nop;:这是四个NOP指令,它们不会执行任何操作,只是占用了指令周期。因此,它们在这里的作用是延迟,即让CPU等待一段时间。这些NOP指令在这里被重复使用100次,意味着总的延迟时间是4个NOP指令的延迟时间的100倍。

4.2 DEM12864B状态检测,从EDM12864B读出数据检测标志位BF

        主要与液晶显示(LCD)设备的通信有关。具体来说,这是用于检测LCD设备是否忙的函数。这个函数会检查LCD设备是否正在忙(例如,是否有其他程序正在写入LCD),如果是,它将等待直到LCD变得可用。这在需要同步两个或更多程序对同一LCD设备进行操作的情况下非常有用。

 

void LCD_Busy()
 {
    // 定义一个无符号字符类型的变量busy,用于表示LCD设备是否忙碌
    uchar busy;
    // 将E端口设置为低电平,表示发送器(发送数据到LCD)处于低电平状态
    E=Low;
    
    // 将DI端口设置为低电平,表示数据线(数据输入到LCD)处于低电平状态
    DI=Low;
    
    // 将RW端口设置为高电平,表示读取/写入控制线处于高电平状态
    RW=High;
    
    // 进入一个无限循环,持续检测LCD的状态
    while(1)
    {
        // 将E端口设置为高电平,表示发送器(发送数据到LCD)处于高电平状态,并且不发送任何数据(_nop指令在这里被使用,它们不会执行任何操作,只是占用了指令周期)
        E=High;
        _nop;
        _nop; // 这两个_nop指令在这里是为了延迟,让CPU等待一段时间
        // 从P2端口读取状态标志寄存器的值到busy变量中
        busy=P2;//读状态标志寄存器
        _nop; // 这里的_nop指令也是为了延迟,等待P2端口返回的状态值
        // 将E端口设置为低电平,表示发送器(发送数据到LCD)处于低电平状态
        E=Low;
        // 如果busy和P2的值都为0,表示可进行写入操作(检测BF和RES位都为零时)
        if((busy&0x90)==0)//检测BF和RES位,两者都为零时表示可进行写入
        break; // 跳出循环,返回上一个函数调用处
    };
}

4.2 写数据

用于向LCD写入指令和数据。

 

首先,我们看到这个代码块是分成了几个函数,每个函数都有一个明确的名称,例如`WRCommand_L`、`WRCommand_R`和`WRdata_L`、`WRdata_R`。这些函数都接受一个参数,例如一个命令或数据字节。

1. `WRCommand_L(uchar command)` 和 `WRCommand_R(uchar command)`:

 

      这两个函数看是用来向LCD写入指令的。它们通过将命令发送到LCD的P2端口来实现这一点。这些命令可能控制LCD的显示模式、移动光标、更改文本等

      在发送指令之前,这两个函数首先通过设置CS1和CS2引脚来选择LCD的哪个端口(通常是左或右端口)。然后调用`LCD_Busy()`来检查LCD是否正在忙碌。

      接着,通过设置DI、RW和E引脚,来告诉LCD正在进行读/写操作。E引脚在发送命令之前设置为高电平,这表示开始传输数据。

      最后,将命令字节发送到LCD的P2端口。

/*********************************
写指令
**********************************/
// WRCommand_L函数用于向LCD的左端口写入指令
void WRCommand_L(uchar command) {  // 将CS1引脚设置为高电平,选择LCD的左端口
  CS1=High;  // 将CS2引脚设置为低电平,使能LCD并启动操作
  CS2=Low;  // 调用LCD_Busy函数,等待LCD设备从忙状态中恢复可用状态
  LCD_Busy();  // 将DI引脚设置为低电平,表示数据线处于低电平状态,即将写入数据到LCD的左端口
  DI=Low;  // _nop指令,不执行任何操作,只是占用了指令周期,用于延迟
  _nop;  // 将RW引脚设置为低电平,表示正在进行读/写操作
  RW=Low;  // _nop指令,延迟执行
  _nop;  // 将E引脚设置为高电平,表示发送器(发送数据到LCD)处于高电平状态,准备发送数据
  E=High;  // _nop指令,延迟执行
  _nop;  // 将命令字节写入到LCD的左端口
  P2=command;  // _nop指令,延迟执行
  _nop;  // 将E引脚设置为低电平,表示发送器(发送数据到LCD)处于低电平状态,结束传输数据
  E=Low;  // _nop指令,延迟执行
  _nop;
}

2. `WRdata_L(uchar ucdata)` 和 `WRdata_R(uchar ucdata)`:

 

    这两个函数是用来向LCD写入数据的。它们将数据字节发送到LCD的P2端口。

    在发送数据之前,这两个函数首先通过设置CS1和CS2引脚来选择LCD的哪个端口(通常是左或右端口)。

    然后调用`LCD_Busy()`来检查LCD是否正在忙碌。

    接着,通过设置DI和RW引脚,来告诉LCD正在进行读/写操作。RW引脚在写入数据时设置为低电平,表示正在写入数据。

    最后,将数据字节发送到LCD的P2端口。

void WRdata_L(uchar ucdata)   // 写入数据到LCD的左端口
{
   CS1=High;                  // 将CS1引脚设置为高电平,选择LCD的左端口
   CS2=Low;                  // 将CS2引脚设置为低电平,使能LCD并启动操作
   LCD_Busy();               // 调用LCD_Busy函数,等待LCD设备从忙状态中恢复可用状态

   // DI引脚设置为高电平,表示数据线处于高电平状态,即将写入数据到LCD的左端口
   DI=High;

   // _nop指令,不执行任何操作,只是占用了指令周期,用于延迟
   _nop;

   // 将RW引脚设置为低电平,表示正在进行读/写操作
   RW=Low;

   // _nop指令,延迟执行
   _nop;

   // 将E引脚设置为高电平,表示发送器(发送数据到LCD)处于高电平状态,准备发送数据
   E=High;

   // _nop指令,延迟执行
   _nop;

   // 将数据字节写入到LCD的左端口
   P2=ucdata;

   // _nop指令,延迟执行
   _nop;

   // 将E引脚设置为低电平,表示发送器(发送数据到LCD)处于低电平状态,结束传输数据并释放端口。
   E=Low;                    // 这是写操作的关键步骤之一,它允许数据在LCD上显示出来。
}

void WRdata_R(uchar ucdata)   // 写入数据到LCD的右端口
{
   CS1=Low;                   // 将CS1引脚设置为低电平,选择LCD的右端口
   CS2=High;                 // 将CS2引脚设置为高电平,禁用LCD并结束操作
   LCD_Busy();               // 调用LCD_Busy函数,等待LCD设备从忙状态中恢复可用状态

   // DI引脚设置为高电平,表示数据线处于高电平状态,即将写入数据到LCD的右端口
   DI=High;

   // _nop指令,延迟执行
   _nop;

   // 将RW引脚设置为低电平,表示正在进行读/写操作
   RW=Low;

   // _nop指令,延迟执行
   _nop; 
   E=High;                   // 将E引脚设置为高电平,这可能也会引发问题。可能也需要一个延迟以确保写操作完成。                   
   E=High;                   // 这可能是对上一个E=High语句的一个重复。但是在此代码段中可能需要添加延迟以确保正确完成写操作。 之后应该再设为低电平结束传输数据并释放端口。 				 					 					 					 				
}

4.3 读显示数据

`RDdata_L` 和 `RDdata_R`,以及一个额外的函数 `I2C_delay`。

 

RDdata_L()

 

这个函数的目的是从LCD显示器的左半部分读取数据。

 

1. 定义局部变量 `receiver` 来存储从LCD读取的数据。

2. 设置 `CS1` 为高电平(High),`CS2` 为低电平(Low),以选中LCD左边的片选。

3. 调用 `LCD_Busy()` 函数(未提供细节),可能用于检查LCD是否准备好数据传输。

4. 设置 `DI` 为高电平,以指定接下来的操作是数据读取而非命令读取。

5. 执行一个空操作 `_nop`,其目的通常是提供时间延迟以满足硬件时序要求。

6. 设置 `WR` 为高电平,`WR` 可能指“写”操作,这里可能是指示开始数据读取操作。

7. 再次执行空操作 `_nop`。

8. 设置 `E` 为高电平,`E` 是LCD的使能信号,用来触发数据的传送。

9. 再次执行空操作 `_nop`,等待数据稳定。

10. 将端口 `P0` 的值读入 `receiver`,`P0` 通常是8051微控制器的一个数据端口。

11. 设置 `E` 为低电平,结束数据传送。

12. 返回接收到的数据。

 

RDdata_R()

 

这个函数与 `RDdata_L` 类似,但是用于从LCD显示器的右半部分读取数据。步骤与 `RDdata_L` 相似,但是片选信号 `CS1` 和 `CS2` 的状态相反,确保选中了LCD右边部分。

 

I2C_delay()

 

`I2C_delay` 函数用于在I2C通信中提供必要的时钟延迟。I2C协议中有严格的时序要求,这个函数通过多次执行空操作 `_nop` 来实现简短的延时。函数内部使用一个循环,循环计数器 `n` 初始化为5,每次循环执行两次空操作。这能够产生一个大约是微控制器一个或几个机器周期长度的延时,具体取决于微控制器的时钟频率。

/************************************
    读显示数据
*************************************/

/********************************************
RDdata_L()
  {
   uchar receiver;
   CS1=High;
   CS2=Low;
   LCD_Busy();
   DI=High;
   _nop;
   WR=High;
   _nop;
   E=High;
   _nop;
   receiver=P0;
   E=Low;
   return(receiver);
  }


RDdata_R()
  {
   uchar receiver;
   CS1=Low;
   CS2=High;
   LCD_Busy();
   DI=High;
   _nop;
   WR=High;
   _nop;
   E=High;
   _nop;
   receiver=P0;
   E=Low;
   return(receiver);
  }

/*****************************************

/**************************************
   I2C时钟时序要求的数据建立时间,
   nop_可以用其它短延时替代,比如喂狗
***************************************/

I2C_delay()
   {
    uchar n=5;
    while(n--)
      {
	   _nop;
	   _nop;
	   }
    }

4.4 I2C

一些用来实现I2C通信的函数和DS1621温度传感器的控制函数,实现对DS1621温度传感器的控制和数据的读取。

 

I2C通信的函数:

    I2C_clock()函数用于在时钟信号上发送和接收数据。

    I2C_ACK()函数用于发送确认信号。

    I2C_NACK()函数用于发送非确认信号。

    I2C_start()函数用于发送开始信号。

    I2C_stop()函数用于发送停止信号。

    I2C_init()函数用于初始化I2C总线。

 

I2C发送和接收数据的函数:

    I2C_send()函数用于发送一个字节的数据,并返回应答位。

    I2C_receive()函数用于接收一个字节的数据,并发送应答位。

DS1621温度传感器进行控制的函数:

    DS1621_start()函数用于发送开始信号,并通过判断从机的应答位来确定是否成功开始通信。

    DS1621_stop()函数用于发送停止信号,并通过判断从机的应答位来确定是否成功停止通信。

    read_count_remain()函数用于读取DS1621传感器的计数器剩余值。

    read_count_c()函数用于读取DS1621传感器的计数器值。

    read_temperature()函数用于读取DS1621传感器的温度值。

    DS1621_state()函数用于判断DS1621传感器的状态。

    DS1621_th()和DS1621_tl()函数用于设定DS1621传感器的温度上下限。

    DS1621_init()函数用于初始化DS1621传感器。

/*****************************************

/**************************************
   I2C时钟时序要求的数据建立时间,
   nop_可以用其它短延时替代,比如喂狗
***************************************/

I2C_delay()
   {
    uchar n=5;
    while(n--)
      {
	   _nop;
	   _nop;
	   }
    }

/*********************************************
     I2C的时钟信号可发送和接收.
     接收时通过判断sample对寄存器的值加.
     发送的时候只调用程序产生时序.
**********************************************/

bit I2C_clock(void)
    {
     bit sample;
     I2C_delay();
     I2C_SCL=High;
     I2C_delay();
     sample=I2C_SDA;
     I2C_SCL=Low;
     I2C_delay();
     return(sample);
    }

/********************************************
   I2C确定接收有效的应答信号Acknowledge
*********************************************/

I2C_ACK()
   {
    I2C_SDA=Low;
    I2C_clock();
    I2C_SDA=High;
   }

/*****************************************
I2C的非应答信号
******************************************/

I2C_NACK()
   {
    I2C_SDA=High;
    I2C_clock();
    I2C_SDA=High;
   }

/*************************************************
     I2C的开始信号.发送和接受数据时,必须先调用.
 *************************************************/

I2C_start()
   { 
    I2C_SDA=High;
    I2C_delay();
    I2C_SCL=High;
    I2C_delay();
    I2C_SDA=Low;
    I2C_delay();
    I2C_SCL=Low;
    I2C_delay();
  }

/***************************************************
    I2C停止信号.发送和接受数据时结束时,需要调用.
****************************************************/

I2C_stop()
   {
    I2C_SDA=Low;
    I2C_delay();
    I2C_SCL=High;
    I2C_delay();
    I2C_SDA=High;//产生停止控制信号,并且释放数据线
    I2C_delay();
   }

/*****************************************
    I2C初始化.在main函数中必须先调用它.
     进行初始化后才能调用其他函数.
******************************************/

void I2C_init(void)
   {
    I2C_SDA=High;
    I2C_SCL=High;
    I2C_delay();
    I2C_stop();
   }

/*****************************************
    I2C总线写一个字节,即发送一个字节
   发送结束后,需要返回一个检测应答位
******************************************/

bit I2C_send(uchar I2C_data)
   {
    uchar a;
    for(a=0;a<8;a++)
      {
       I2C_SDA=(bit)(I2C_data&0x80);
       I2C_data=I2C_data<<1;
       I2C_clock();
      };
    I2C_SDA=High;
    I2C_delay();
    return(I2C_clock()); //返回应答信号
   }

/***********************************************
   I2C总线读一个字节,即接收一个字节数据
   接收结束后需要发送一个应答位acknowledge
***********************************************/

uchar I2C_receive()
     {
      uchar a,receive_data;
      for(a=0;a<8;a++)
         {
          receive_data=receive_data<<1;
          if(I2C_clock())
           {
            receive_data++;
           }; 
         };
 //I2C_ACK();
      return(receive_data);
      }


 DS1621_start()
   {
	do
	{
     I2C_start();
	 }
    while(I2C_send(0x90));//发送数据完地址字节的数据时,检测从机的应答位        
	if(I2C_send(0xee)==0)
	  {
	   I2C_stop();
	   return 1;
	   }
    else
	  return 0;			        
	  }

 DS1621_stop()
   {
    do
	{
	 I2C_start();
	 //I2C_send(0x90);
	 }
	while(I2C_send(0x90));
	if(I2C_send(0x22)==0)
     {
	  I2C_stop();
	  return 1;
	  }
	else
	  return 0;	    
	}

char read_count_remain()
      {
	   char temp_remain=0;
	   do
	   	{
	     I2C_start();
	     }
	   while(I2C_send(0x90));//是否应答
	   if(I2C_send(0xa8)==0)
	     {
		  I2C_start();
	      if(I2C_send(0x91)==0)
	        {
			temp_remain=I2C_receive();
			I2C_NACK();
		    I2C_stop();
			return temp_remain;
			}
		  else
		    return 0;
	      }
		else
		  return 0;
	   }
char read_count_c()
      {
	   char temp_c=0;
	   do
	   	{
	     I2C_start();
	     }
	   while(I2C_send(0x90));//是否应答
	   if(I2C_send(0xa9)==0)
	     {
		  I2C_start();
	      if(I2C_send(0x91)==0)
	        {
			temp_c=I2C_receive();
			I2C_NACK();
		    I2C_stop();
			return temp_c;
			}
		  else
		    return 0;
	      }
		else
		  return 0;
	   }
bit read_temperature(char *point1)
   {
	do
	{
	I2C_start();
	}
	while(I2C_send(0x90));
    if(I2C_send(0xaa)==0)
	  {
	   do{
	     I2C_start();
	     }
	     while(I2C_send(0x91));
	   (*point1)=I2C_receive();
	   I2C_ACK();
	   (*(point1+1))=I2C_receive();
	   I2C_NACK();
	   I2C_stop();
	   return 1;
	   }
	else
	return 0;
	}

 DS1621_state()
   {
	do
	{
	 I2C_start();
	 }
	while(I2C_send(0x90));
	if(I2C_send(0xac)==0)
      {
	   do
		{
	     I2C_start();
		}
	   while(I2C_send(0x90));
	   if(I2C_send(0x00)==0)
	   //if(I2C_send(0x01)==0)
		 {
		 I2C_stop();
		 return 1;
		  }
	   else
	   return 0;
		}
	else
	  return 0;	    
	}

 DS1621_th()
   {
	 
    }
 DS1621_tl()
   {
	 
    }
 DS1621_init()
   {
	while(DS1621_stop()==0)
	{};
	while(DS1621_state()==0)
	{};
	DS1621_th();
	DS1621_tl();
    }

4.5 像素的显示屏上显示字符

在16x16像素的显示屏上显示字符。其中,左侧部分显示"欢迎",右侧部分显示"使用"。

 

函数说明如下:

    C_display_L()函数用于在左侧显示屏上显示中文字符。参数C_Pagenum和C_Tiernum用于指定页地址和行地址,参数C_Temp用于指定显示的中文字符。函数先写入前16个字节的数据,然后写入后16个字节的数据。

    C_display_R()函数用于在右侧显示屏上显示中文字符。参数和功能与C_display_L()函数相同,只是操作的是右侧显示屏。

    E_Display_L()函数用于在左侧显示屏上显示英文字符。参数E_Pagenum和E_Tiernum用于指定页地址和行地址,参数E_Temp用于指定显示的英文字符。函数根据字模表table1中的数据写入8个字节的英文字符数据。

    E_Display_R()函数用于在右侧显示屏上显示英文字符。参数和功能与E_Display_L()函数相同,只是操作的是右侧显示屏。

/****************************************************
       字符为16*16显示,分为两个部分写入32个字节
       前16个字节写入第一页(16*8),后16个字节写入
       第二页(16*8)
       左侧写入"欢迎",右侧写入"使用"
*****************************************************/
C_display_L(uchar C_Pagenum,uchar C_Tiernum,uchar C_Temp)
  {
   uchar k;
   C_Pagenum=PAGEADD|C_Pagenum;
   C_Tiernum=TIERADD|C_Tiernum;
   WRCommand_L(C_Pagenum);
   WRCommand_L(C_Tiernum);
   for(k=0;k<16;k++)
      {
       WRdata_L(table2[C_Temp*32+k]);
	     };
   C_Pagenum=C_Pagenum+1;
   WRCommand_L(C_Pagenum);
   WRCommand_L(C_Tiernum);
   for(k=0;k<16;k++)
      {
       WRdata_L(table2[C_Temp*32+k+16]);
	   };
   }

C_display_R(uchar C_Pagenum,uchar C_Tiernum,uchar C_Temp)
  {
   uchar k;
   C_Pagenum=PAGEADD|C_Pagenum;
   C_Tiernum=TIERADD|C_Tiernum;
   WRCommand_R(C_Pagenum);
   WRCommand_R(C_Tiernum);
   for(k=0;k<16;k++)
      {
       WRdata_R(table2[C_Temp*32+k]);
	     };
   C_Pagenum=C_Pagenum+1;
   WRCommand_R(C_Pagenum);
   WRCommand_R(C_Tiernum);
   for(k=0;k<16;k++)
      {
       WRdata_R(table2[C_Temp*32+k+16]);
	     };
  }

/****************************************
   写入西文字符,共11个字符,左侧写入6个
   右侧写入5个,左侧起始从16开始,右侧从
   0开始
*****************************************/

E_Display_L(uchar E_Pagenum,uchar E_Tiernum,uchar E_Temp)
  {
   uchar k;
   WRCommand_L(PAGEADD|E_Pagenum);
   WRCommand_L(TIERADD|E_Tiernum);
   for(k=0;k<8;k++)
	  {
	   WRdata_L(table1[E_Temp*8+k]);
	   };
   }   
E_Display_R(uchar E_Pagenum,uchar E_Tiernum,uchar E_Temp)
   {
   uchar k;
   WRCommand_R(PAGEADD|E_Pagenum);
   WRCommand_R(TIERADD|E_Tiernum);
   for(k=0;k<8;k++)
	  {
	   WRdata_R(table1[E_Temp*8+k]);
	   };
   	}

4.6 清除所有显示RAM内容

用于清除显示屏的显示内容,即将显示屏的RAM中的数据全部清零。

 

CLR_DisplayRAM()函数用于清除显示屏的RAM数据。

 

首先使用循环遍历8个页地址,然后分别在左右两侧显示屏上进行操作。在每个页地址下,使用循环遍历64个字节,将每个字节的数据都设置为0x00,即清零。

 

通过调用这个函数,可以清除显示屏上之前显示的内容,使得显示屏变为空白。请在需要清除显示内容的时候调用该函数。

 

CLR_DisplayRAM()
   {
    uchar C_page,i,k;
    for(i=0;i<8;i++)
	   {
	    C_page=PAGEADD|i;//清除起始页为0
	    WRCommand_L(C_page);//清除起始页写入
		WRCommand_L(TIERADD);//清除起始行地址写入
		WRCommand_R(C_page);
		WRCommand_R(TIERADD);
		for(k=0;k<64;k++)
		   {		   	
			WRdata_L(0x00);
			WRdata_R(0x00);//lcm的ram自动加一,只许循环64次即可    
		 	};
		 };
    }

4.7 max1241读转换结果

用于从MAX1241模数转换器中读取转换结果,即读取模拟信号的数字化数值。

read_max1241()函数用于读取MAX1241模数转换器的转换结果。首先定义一个变量voltage_temp用于存储读取的数值。

 

然后设置cs为高电平,将sclk设置为低电平。接下来开始进行数据读取。等待dout为高电平,表示转换结束。将sclk设置为高电平,然后通过循环读取12位的数据,每次将数据左移一位并判断dout的状态,如果为高电平则将voltage_temp加1。最后将cs设置为高电平,返回读取到的数值voltage_temp。

 

通过调用这个函数,可以读取MAX1241模数转换器的转换结果,并获取模拟信号的数字化数值。

uint read_max1241()
	 {
	  uint voltage_temp=0;
	  uchar ucloop=12;
	  cs=High;
	  //dout=low;
	  sclk=Low;
	  cs=Low;
      while(dout==0);//EOC信号为高表示转换结束
	  sclk=High;
	  sclk=Low;
      while(ucloop--)
	   {
		sclk=High;//上升沿数据稳定并读出
		voltage_temp<<=1;
	    if(dout==1)
	    voltage_temp+=1;
	    sclk=Low;
		};	 
	  //sclk=low;
	  cs=High;
	  return voltage_temp;
	  }

五、主函数

主要功能是进行LCD显示和读取温度和电压值,通过主函数的循环,可以不断更新并显示温度和电压的测量值。

main()函数是程序的入口函数。首先定义了一些变量和数组,然后进行LCD和DS1621的初始化。

接下来通过循环进行开机显示,并进行一次空读温度值。之后清除显示RAM内容,并进入主循环。

 

主循环部分:

    首先读取温度值,同时读取斜率和计数器的值。

    根据温度值计算温度显示的各个部分,并存储在temperature_display数组中。

    通过read_max1241()函数读取电压值,并计算电压显示的各个部分,并存储在voltage_display数组中。

    在LCD上显示电压测量和温度测量的文字。

    在相应的位置上显示电压值和温度值。

main()
  {
   uchar a=0,temp=0,b=0,number=0,pagenum=0;
   uchar etable[]={0,0,0,0,0,45,53,60,51,63,61,53,10,69,67,53};//Welcome use
   uchar ttable[]={42,57,61,53,19,10,2,0,0,6,18,1,1,18,0,7};//Time:2006/11/07
   char count_remain=0,count_c=0;
   bit flag=1;
   char temperature_buf[2]={0};
   uchar temperature_display[8]={0};
   //uchar voltage=0; //测试0831时使用
   double voltage=0;
   double temp_buffer=0;
   cs=High;
   sclk=High;
   Init_LCD();
   I2C_init();
   DS1621_init();//初始化
   DS1621_start();//开始温度采集

   /***************************************
   利用开机显示时间(大概三秒)空读一次DS1621
   ***************************************/
   for(b=0;b<2;b++)
      {
      for(number=0;number<20;number++)
	     {
	      //中文显示:欢迎使用液晶模块
     	  pagenum=1;//在第二行开始显示
	      for(a=0;a<4;a++)
	        {
	         C_display_L(pagenum,a*16,a);
	         C_display_R(pagenum,a*16,a+4);
	         }; 
        
		  //英文显示:Welcome use	            
          pagenum=5;//在第六行显示
          for(a=0;a<3;a++)
             {
              E_Display_L(pagenum,(a+5)*8,etable[a+5]);
              };
          for(a=0;a<8;a++)
             { 
              E_Display_R(pagenum,a*8,etable[a+8]);
              };
        
		  //显示制作时间:Time:2006/11/07				
          pagenum=7;//在第八行显示
          for(a=0;a<8;a++)
             {
              E_Display_L(pagenum,a*8,ttable[a]);
              E_Display_R(pagenum,a*8,ttable[a+8]);
              }          
	      Delay_nms(100);
		  };
		  if(flag)
		 	 {
              read_temperature(temperature_buf);//在转到下一个显示界面之前空读一次
			  read_count_remain();
			  read_count_c();
			  DS1621_start();
              flag=0;
   	          };
   	  };  

	WRCommand_L(0x3e);
	WRCommand_R(0x3e);
	CLR_DisplayRAM();  //清除显示ram
	WRCommand_L(0x3f);
	WRCommand_R(0x3f);   
			
    while(1)
	   {
	    while(read_temperature(temperature_buf)==0);//读温度值(两字节有符数),并检测应答位,只使用第一字节
	    count_remain=read_count_remain();//读斜率
		count_c=read_count_c();//读计数器
		temp_buffer = temperature_buf[0] -0.75 + (count_c-count_remain) / (double)count_c;
		DS1621_start();//读完数据后,重新开始一次温度转化

		   	temperature_display[0]=19;
			if(temp_buffer<0)
			  {
			   temperature_display[1]=16;//显示负号
		       temperature_display[2]=(-temp_buffer)/10;//十位
			   temperature_display[3]=((uchar)(-temp_buffer))%10;
			   temperature_display[4]=17;//小数点
			   temperature_display[5]=(int)((-temp_buffer)*10)%10;
			   temperature_display[6]=(int)((-temp_buffer)*100)%10;//小数点后两位
			   }
			else
			  {
			   temperature_display[1]=temp_buffer/100;//百位
		       temperature_display[2]=((uchar)temp_buffer)/10%10;
			   temperature_display[3]=((uchar)temp_buffer)%10;
			   temperature_display[4]=17;
			   temperature_display[5]=((int)(temp_buffer*10))%10;
			   temperature_display[6]=((int)(temp_buffer*100))%10;//小数点后两位
			   }

			// 测试从max1241读出的原始数据
		    /***************************
			//pagenum=3;
	 	    voltage=read_max1241();
		    voltage_display[0]=19;
		    voltage_display[1]= voltage/1000;   
		    voltage_display[2]=(voltage/100)%10;
		    voltage_display[3]=(voltage/10)%10;
                                voltage_display[4]= voltage%10;		  
		    //*******************************/

		//LCD显示第二部分
		for(b=0;b<40;b++)
	       {
		    //*************************
			//更新max1241显示值		    
			voltage=(double)read_max1241();
		    voltage=5000*voltage/4095;
		    voltage_display[0]=19;
		    voltage_display[5]=(uint)voltage %10;//个位
		    voltage_display[2]=17;
		    voltage_display[4]=(uint)(voltage/10)%10;//小数点后1位
		    voltage_display[3]=(uchar)(voltage/100)%10;//小数点后2位
			voltage_display[1]=(uchar)(voltage/1000)%10;//小数点后3位
			//***********************************/
			//显示文字:电压测量		
		    pagenum=2;
		    for(a=0;a<2;a++)
		       C_display_L(pagenum,a*16,a+8);
		    for(a=0;a<2;a++)
		       C_display_L(pagenum,(a+2)*16,a+12);	
		   
		   	//显示文字:温度测量
		    pagenum=5;
		    for(a=0;a<2;a++)
		       C_display_L(pagenum,a*16,a+10);
		    for(a=0;a<2;a++)
		       C_display_L(pagenum,(a+2)*16,a+12);
		    
			//显示1241采样值
			pagenum=3;
		    for(a=0;a<6;a++)
              {
		   	   E_Display_R(pagenum,a*8,voltage_display[a]); 
		       }

			//显示ds1621采样值
			pagenum=6;
		    for(a=0;a<7;a++)
              {
		   	   E_Display_R(pagenum,a*8,temperature_display[a]); 
		       }
          };
	  };	
   }

六、仿真电路图:

6.1 电路图设计

image.png

6.2 运行效果:

image.png

20240429_13515.png

6.3 详细数据分析

image.png

image.pngimage.png

image.png

七、总结

        利用了一个LCD屏幕来显示温度和电压的测量值,能够提供实时的温度和电压信息。通过调用其他函数来读取温度和电压,然后根据需要进行计算和显示。同时,还可以自定义显示文字和格式,以满足特定需求,实现了在一个LCD屏幕上同时显示温度和电压的测量值,并提供了一种简单、实用的方式来监测这两个参数的变化。

        通过该项目,学习如何使用LCD屏幕进行信息显示,了解了如何初始化LCD屏幕,并在屏幕上显示文字和数值。使用LCD屏幕进行信息显示提供了基础。

        通过读取MAX1241模数转换器的转换结果,你了解了模数转换器如何将模拟信号转换为数字化的数值。这对于测量和监测模拟信号至关重要。

        通过使用DS1621温度传感器,学习了如何初始化和读取温度传感器的数值。这对于温度测量和环境监测非常有用。