/*********************************************************************************** ;功能说明:SHT11和STC89C51温湿度采集和测量系统Proteus仿真 ;文件名称:DU.c ;版本号:v1.0.0 ;微处理器:STC89C51 ;编译环境:Keil uVision V4.10 ;作 者:Cui Xinghai ;创建日期:2022.2.24 ;***********************************************************************************/ /*************定义接口******************** P0------DB0~DB7 (LCD1602) P2.0------RS (LCD1602) P2.1------RW (LCD1602) P2.2------E (LCD1602) P2.6------SCK (STH11) P2.7------DATA (STH11) P1.5------SET //定义调整键 P1.6------ADD //定义添加键 P1.7------DEC //定义减少键 P3.7------ESC //定义退出键 P1.4------BEEP //定义蜂鸣器 P1.0------t_green ///温度过低 P1.1------t_red ///温度过高 P1.2------rh_green //湿度过低 P1.3------rh_red //湿度过高 *****************************************/ #include <reg52.h> #include <intrins.h> //应用程序设计,一般出C在51单片机编程中,一般程序需要空指令_nop_()_crol_等时使用。 #include <math.h> //Keil library #include <stdio.h> //Keil library //*********************第一部分LCD1602设置 START**************************************** #define LCD_DB P0 //Data Bus数据总线,这里我们要做 1602 液晶程序,因此,首先对一声明使用的总线接口 sbit LCD_RS=P2^0; sbit LCD_RW=P2^1; sbit LCD_E =P2^2; /******定义函数****************/ #define uchar unsigned char #define uint unsigned int void LCD_init(void); ///初始化函数 void LCD_write_command(uchar command); //写指令函数 void LCD_write_data(uchar dat); //写数据函数 void LCD_disp_char(uchar x,uchar y,uchar dat); ///在屏幕位置显示字符,X(0-15),y(1-2) void LCD_disp_str(uchar x,uchar y,uchar *str); //LCD1602显示字符串函数 void delay_n10us(uint n); ///延迟函数 /*-------------------------------------- ;模块名称:LCD_init(); ;功 能:初始化LCD1602 ;-------------------------------------*/ void LCD_init(void) //括号中的void这个函数的参数是空的 { delay_n10us(10); LCD_write_command(0x38);//指令6工作方式设置指令,0x0011 1000设置8位格式数据接口,16位*2行显示,5x7点阵 delay_n10us(10); LCD_write_command(0x0c);//指令4: //整体显示,关光标,不闪烁,BCD0x00001 100 //整体显示,开光标, 闪烁,BCD0x00001 111 delay_n10us(10); LCD_write_command(0x06)//指令3:设置输入模式增量不移位, 0x00000110 delay_n10us(10); LCD_write_command(0x01);//指令1:清除屏幕显示,将光标复位到地址00H位置 delay_n10us(100); //延时清屏,延时函数,延时约n个10us } /*-------------------------------------- ;模块名称:LCD_write_command(); ;功 能:LCD1602写指令函数 ;占用资源: P2.0--RS(LCD_RS),P2.1--RW(LCD_RW),P2.2--E(LCD_E). ;参数说明:dat写命令参数 ;-------------------------------------*/ void LCD_write_command(uchar dat) { delay_n10us(10); LCD_RS=0; //指令 LCD_RW=0; //写入 LCD_E=1; //允许 LCD_DB=dat; delay_n10us(10); //实践证明,我的LCD1602上,用for普通写指令可以循环一次。 LCD_E=0; ///阅读撤销使能后,防止液晶输出数据干扰 P0 总线 delay_n10us(10); //实践证明,LCD1602上,用for普通写指令可以循环一次。 } /*-------------------------------------- ;模块名称:LCD_write_data(); ;功 能:LCD1602写数据函数 ;占用资源: P2.0--RS(LCD_RS),P2.1--RW(LCD_RW),P2.2--E(LCD_E). ;参数说明:dat写数据参数 ;-------------------------------------*/ void LCD_write_data(uchar dat) { delay_n10us(10); LCD_RS=1; //数据 LCD_RW=0; //写入 LCD_E=1; //允许 LCD_DB=dat; delay_n10us(10); LCD_E=0; delay_n10us(10); } /*-------------------------------------- ;模块名称:LCD_disp_char(); ;功 能:光标位置,LCD1602显示一个字符函数,在某个屏幕位置上显示一个字符,X(0-15),y(1-2)。 ;参数说明:X列值为1602(取值范围为0-15),y行值为1602(取值范围为1-2),dat显示字符对应的地址参数。 ;-------------------------------------*/ void LCD_disp_char(uchar x,uchar y,uchar dat) { uchar address; if(y==1) //0x80和00H区别:指令7 address=0x80 x; //二进制码第一行首地址为100000转换16x80,第二行的首要地址是1.1万万转换16xC0 加上地址是为了让LCD显示从第一位开始。 else address=0xc0 x; LCD_write_command(address); LCD_write_data(dat); } /*-------------------------------------- ;模块名称:LCD_disp_str(); ;功 能:LCD1602显示字符串函数,在屏幕的起始位置{X(0-15),y(1-2)}上显示字符串。 ;参数说明:X列值为1602(取值范围为0-15),y行值为1602(取值范围为1-2),str显示字符串对应的指针参数。 ;-------------------------------------*/ void LCD_disp_str(uchar x,uchar y,uchar *str) { uchar address; if(y==1) address=0x80 x; else address=0xc0 x; LCD_write_command(address); //连续写入字符串数据,直到检测到结束符 while(*str!='\0') { LCD_write_data(*str); //另一种写法LCD_write_data(*str )指针在这行语句中 str 我们必须彻底理解操作。首先,我们必须彻底理解它 str取出指向数据,然后 str 再加 1 指向下一个数据,这是一种非常常用的简写方法 str ; } } /*-------------------------------------- ;模块名称:delay_n10us(); ;功 能:延迟函数,延时约n个10us ;修改日期:2022.02.26 ;修改说明:修改更准确的延迟函数,"_nop_()"延时1us@12M晶体振动代表机器周期的运行。如果单片机的晶振是12M的,然后这个调代码会运行1US; ;-------------------------------------*/ void delay_n10us(uint n) //延迟n10us@12M晶振 { uint i; for(i=n;i>0;i--) { _nop_();_nop_();_nop_();_nop_();_nop_();_nop_(); } } //********************第一部分LCD1602设置 END****************************************
//*********************第二部分SHT11设置 START****************************************
sbit SCK = P2^6; //定义通讯时钟端口
sbit DATA = P2^7; //定义通讯数据端口
typedef union //定义了两个共用体:如果没有typedef那么就是普通的定义了匿名联合的一个变量value.加了typedef后, 定义的就是类型别名, 当类型一样用
{
unsigned int i; //i表示测量得到的温湿度数据(int 形式保存的数据)
float f; //f表示测量得到的温湿度数据(float 形式保存的数据)
} value;
enum {TEMP,HUMI}; //enum 枚举名 {枚举元素1,枚举元素2,……};TEMP=0,HUMI=1 枚举:https://www.runoob.com/cprogramming/c-enum.html
#define noACK 0 //用于判断是否结束通讯
#define ACK 1 //结束数据传输
//adr command r/w
#define STATUS_REG_W 0x06 //000 0011 0
#define STATUS_REG_R 0x07 //000 0011 1
#define MEASURE_TEMP 0x03 //000 0001 1
#define MEASURE_HUMI 0x05 //000 0010 1
#define RESET 0x1e //000 1111 0
/****************定义函数****************/
void s_transstart(void); //启动传输函数
void s_connectionreset(void); //连接复位函数
char s_write_byte(unsigned char value);//写函数
char s_read_byte(unsigned char ack); //读函数
char s_measure(unsigned char *p_value, unsigned char *p_checksum, unsigned char mode);//测量温湿度函数
void calc_SHT11(float *p_humidity ,float *p_temperature);//温湿度补偿
/*--------------------------------------
;模块名称:s_transstart();
;功 能:启动传输函数
;-------------------------------------*/
void s_transstart(void)
// generates a transmission start (SHT11发送命令,启动传输时序)
// _____ ________
// DATA: |_______|
// ___ ___
// SCK : ___| |___| |______
{
DATA=1; SCK=0; //Initial state
_nop_();
SCK=1;
_nop_();
DATA=0;
_nop_();
SCK=0;
_nop_();_nop_();_nop_();
SCK=1;
_nop_();
DATA=1;
_nop_();
SCK=0;
}
/*--------------------------------------
;模块名称:s_connectionreset();
;功 能:连接复位函数
;-------------------------------------*/
void s_connectionreset(void)
// 通讯复位时序:如果与 SHT11 通讯中断,下列信号时序可以复位串口:DATA 保持高电平时,触发 SCK 时钟 9 次或更多。
// _____________________________________________________ ________
// DATA: |_______|
// _ _ _ _ _ _ _ _ _ ___ ___
// SCK : __| |__| |__| |__| |__| |__| |__| |__| |__| |______| |___| |______
{
unsigned char i;
DATA=1; SCK=0; //Initial state
for(i=0;i<9;i++) //9 SCK cycles,DATA保持高,SCK时钟触发9次,发送启动传输,通迅即复位
{
SCK=1;
SCK=0;
}
s_transstart(); //transmission start
}
/*--------------------------------------
;模块名称:s_write_byte();
;功 能:SHT11写函数
;-------------------------------------*/
char s_write_byte(unsigned char value)
// 写入一个字节,并检查确认
{
unsigned char i,error=0;
for (i=0x80;i>0;i/=2) //0x1000 0000用于掩码的移位,高位为1,循环右移 https://zhidao.baidu.com/question/2144112204880805988.html
{
if (i & value) DATA=1; //和要发送的数相与,结果为发送的位
else DATA=0;
SCK=1;
_nop_();_nop_();_nop_();
SCK=0;
}
DATA=1; //释放数据线
SCK=1; //clk #9 for ack
error=DATA; //检查应答信号,确认通讯正常
_nop_();_nop_();_nop_();
SCK=0;
DATA=1; //
return error; //error=1 in case of no acknowledge //返回:0成功,1失败
}
/*--------------------------------------
;模块名称:s_read_byte();
;功 能:SHT11读函数
;-------------------------------------*/
char s_read_byte(unsigned char ack)
{
unsigned char i,val=0;
DATA=1; //释放数据线
for (i=0x80;i>0;i/=2)
{
SCK=1; //clk for SENSI-BUS
if (DATA) val=(val | i); //读一位数据线的值
_nop_();_nop_();_nop_(); //pulswith approx. 3 us
SCK=0;
}
if(ack==1)DATA=0; //in case of "ack==1" pull down DATA-Line
else DATA=1; //如果是校验(ack==0),读取完后结束通讯
_nop_();_nop_();_nop_(); //pulswith approx. 3 us
SCK=1; //clk #9 for ack
_nop_();_nop_();_nop_(); //pulswith approx. 3 us
SCK=0;
_nop_();_nop_();_nop_(); //pulswith approx. 3 us
DATA=1; //release DATA-line
return val;
}
/*--------------------------------------
;模块名称:s_measure();
;功 能:测量温湿度函数
;-------------------------------------*/
char s_measure(unsigned char *p_value, unsigned char *p_checksum, unsigned char mode)
// 进行温度或者湿度转换,由参数mode决定转换内容
{
unsigned error=0;
unsigned int i;
s_transstart(); //transmission start
switch(mode){ //选择发送命令
case TEMP : error+=s_write_byte(MEASURE_TEMP); break;
case HUMI : error+=s_write_byte(MEASURE_HUMI); break;
default : break;
}
for (i=0;i<65535;i++) if(DATA==0) break; //等待测量结束
if(DATA) error+=1; // 如果长时间数据线没有拉低,说明测量错误
*(p_value) =s_read_byte(ACK); //读第一个字节,高字节 (MSB)
*(p_value+1)=s_read_byte(ACK); //读第二个字节,低字节 (LSB)
*p_checksum =s_read_byte(noACK); //read checksum
return error;
}
/*--------------------------------------
;模块名称:calc_SHT11();
;功 能:温湿度补偿函数
;备 注:SHT11湿度测量范围:0~100%RH;温度测量范围:-40~+123.8℃
;-------------------------------------*/
void calc_SHT11(float *p_humidity ,float *p_temperature)
// calculates temperature [C] and humidity [%RH]
// input : humi [Ticks] (12 bit)
// temp [Ticks] (14 bit)
// output: humi [%RH]
// temp [C]
//const表示常量,不允许修改里面的内容
{ const float C1=-4.0; // 12位湿度精度 修正公式
const float C2=+0.0405; // 12位湿度精度 修正公式
const float C3=-0.0000028; // 12位湿度精度 修正公式
const float T1=+0.01; // 14位温度精度 5V条件 修正公式
const float T2=+0.00008; // 14位温度精度 5V条件 修正公式
float rh=*p_humidity; // rh: Humidity [Ticks] 12 Bit
float t=*p_temperature; // t: Temperature [Ticks] 14 Bit
float rh_lin; // rh_lin: Humidity linear
float rh_true; // rh_true: Temperature compensated humidity
float t_C; // t_C : Temperature [C]
t_C=t*0.01 - 40; //补偿温度,14位温度精度 5V条件 修正公式
rh_lin=C3*rh*rh + C2*rh + C1; //相对湿度非线性补偿
rh_true=(t_C-25)*(T1+T2*rh)+rh_lin-3; //相对湿度对于温度依赖性补偿
if(rh_true>100)rh_true=100; //湿度最大修正
if(rh_true<0.1)rh_true=0.1; //湿度最小修正
*p_temperature=t_C; //返回温度结果
*p_humidity=rh_true; //返回湿度结果
}
//*********************第二部分STH11设置 END****************************************
//*********************第三部分报警设置 START****************************************
#define N 0x00
sbit SET = P1^5; //定义调整键
sbit ADD = P1^6; //定义增加键
sbit DEC = P1^7; //定义减少键
sbit ESC = P3^7; //定义调整键
sbit BEEP= P1^4; //定义蜂鸣器
sbit t_green = P1^0;
sbit t_red = P1^1;
sbit rh_green = P1^2;
sbit rh_red = P1^3;
int t_shangxian = 20; //上限报警温度,默认值为 20C
int t_xiaxian = 10; //下限报警温度,默认值为 10C
int rh_shangxian= 50; //上限报警湿度,默认值为50%
int rh_xiaxian = 40; //下限报警湿度,默认值为40%
uchar flag=0;
/****************定义函数****************/
void led_control( float *WENDU, float *SHIDU);
void key();
void delay(uint j);
/*--------------------------------------
;模块名称:led_control();
;功 能:LED状态控制
;-------------------------------------*/
void led_control( float *WENDU, float *SHIDU)
{
//温度过低报警指示
if(*WENDU <= t_xiaxian) {t_green = 0;}
else {t_green=1;}
//温度过高报警指示
if(*WENDU >= t_shangxian) {t_red = 0;}
else {t_red=1;}
//湿度过低报警指示
if(*SHIDU <= rh_xiaxian) {rh_green= 0;}
else {rh_green=1;}
//湿度过高报警指示
if(*SHIDU >= rh_shangxian) {rh_red = 0;}
else {rh_red=1;}
//蜂鸣器警示
if(*WENDU <= t_xiaxian||*WENDU >= t_shangxian||*SHIDU <= rh_xiaxian||*SHIDU >= rh_shangxian)
{
BEEP = 0;
}
else
{
BEEP = 1;
}
}
/*--------------------------------------
;模块名称:key();
;功 能:按键控制
;-------------------------------------*/
void key()
{
/****************设置键设置****************/
if(SET == 0)
{
delay(50); //按键消抖
if(SET == 0)
{
flag++;
if (flag==5)flag = 0; //没有发生抖动
while(SET==0); //若一直按下,循环
LCD_init();
s_connectionreset();
LCD_disp_str(0,1,"TE:");
LCD_disp_str(0,2,"RH:");
LCD_disp_str(6,1,"-");
LCD_disp_str(6,2,"-");
LCD_disp_str(10,1,"C");
LCD_disp_str(10,2,"%");
delay_n10us(20);
}
}
//SHT11湿度测量范围:0~100%RH;温度测量范围:-40~+123.8℃;
/****************温度下限设置****************/
if (flag==1)
{
if(ADD==0)
{
delay(50);
t_xiaxian++;
if (t_xiaxian>(t_shangxian-1)) {t_xiaxian=(t_shangxian-1);}
while(ADD==0); //加上此句必须松按键才处理
}
if (DEC==0)
{
delay(50);
if(t_xiaxian<=-40) {t_xiaxian=t_shangxian;} //超过实际范围,重新赋值温度下限
else t_xiaxian--;
while(DEC==0); //加上此句必须松按键才处理
}
//温度下限显示
LCD_disp_char(3,1,t_xiaxian/100+'0'); //例如123/100=1
LCD_disp_char(4,1,abs(t_xiaxian%100/10)+'0'); //例如123%100=23,23/10 =2 ,-12取绝对值12
LCD_disp_char(5,1,abs(t_xiaxian%10)+'0'); //例如123%10 =3
if(t_xiaxian<0) {LCD_disp_str(12,2,"nega");} //温度下限为负数,显示nega
else {LCD_disp_str(12,2," ");} //去除负数提醒nega
}
/****************温度上限设置****************/
if (flag==2)
{
if(ADD==0)
{
delay(150);
t_shangxian++;
if (t_shangxian>123) {t_shangxian=123;} //上限值达到最大值 123时,上限值保持为123
while(ADD==0); //加上此句必须松按键才处理
}
if (DEC==0)
{
delay(150);
t_shangxian--;
if (t_shangxian<(t_xiaxian+1)) {t_shangxian=(t_xiaxian+1);}//确保设定的上限值不比下限低
while(DEC==0); //加上此句必须松按键才处理
}
//温度上限显示
LCD_disp_char(7,1,t_shangxian/100+'0');
LCD_disp_char(8,1,(t_shangxian%100)/10+'0');
LCD_disp_char(9,1,(t_shangxian%10)+'0');
}
/****************湿度下限设置****************/
if (flag==3)
{
if(ADD==0)
{
delay(150);
rh_xiaxian++;
if (rh_xiaxian>(rh_shangxian-1)) {rh_xiaxian=(rh_shangxian-1);}
while(ADD==0); //加上此句必须松按键才处理
}
if (DEC==0)
{
delay(150);
if (rh_xiaxian<=0) {rh_xiaxian=0;} //下限值达到最小值0时,下限值保持为0
else rh_xiaxian--;
while(DEC==0); //加上此句必须松按键才处理
}
//湿度下限显示
LCD_disp_char(3,2,rh_xiaxian/100+'0');
LCD_disp_char(4,2,(rh_xiaxian%100)/10+'0');
LCD_disp_char(5,2,(rh_xiaxian%10)+'0');
}
/****************湿度上限设置****************/
if (flag==4)
{
if(ADD==0)
{
delay(150);
rh_shangxian++;
if (rh_shangxian>99) {rh_shangxian=99;} //上限值达到最大值 99时,上限值保持为 99
while(ADD==0); //加上此句必须松按键才处理
}
if (DEC==0)
{
delay(150);
rh_shangxian--;
if (rh_shangxian<(rh_xiaxian+1)) {rh_shangxian=(rh_xiaxian+1);}//确保设定的上限值不比下限低
while(DEC==0); //加上此句必须松按键才处理
}
//湿度上限显示
LCD_disp_char(7,2,rh_shangxian/100+'0');
LCD_disp_char(8,2,(rh_shangxian%100)/10+'0');
LCD_disp_char(9,2,(rh_shangxian%10)+'0');
}
/****************退出键设置****************/
if(ESC==0)
{
delay(10);
if(ESC==0)
{
while(ESC==0);
flag=0;
LCD_init();
s_connectionreset();
}
}
}
/*--------------------------------------
;模块名称:delay();
;功 能:延迟函数
;-------------------------------------*/
void delay(uint j) //1ms,j取20
{
uchar i=250;
for(;j>0;j--)
{
while(--i);
i=249;
while(--i);
i=250;
}
}
//*********************第三部分报警设置 END****************************************
//*********主函数*****************
void main(void)
{
value humi_val,temp_val; //185行,定义两个共同体,一个用于湿度,一个用于温度
unsigned char error,checksum; //用于检验是否出现错误
unsigned int wendu,shidu; //最终,一位小数温湿度的值
LCD_init();
s_connectionreset();
//*********初始化温度显示区*********
LCD_disp_str(0,1,"TE:");
LCD_disp_str(3,1,"TTT.TC");
//*********初始化湿度显示区*********
LCD_disp_str(0,2,"RH:");
LCD_disp_str(3,2,"RRR.R%");
delay_n10us(2000); //延时0.2s
while(1)
{
LCD_disp_char(13,1,flag+'0');
key();
if(flag==0)
{
error=0; //初始化error=0,即没有错误
error+=s_measure((unsigned char*) &humi_val.i,&checksum,HUMI); //measure humidity
error+=s_measure((unsigned char*) &temp_val.i,&checksum,TEMP); //measure temperature
if(error!=0) s_connectionreset(); //in case of an error: connection reset
else
{
humi_val.f=(float)humi_val.i; //converts integer to float
temp_val.f=(float)temp_val.i; //converts integer to float
calc_SHT11(&humi_val.f,&temp_val.f); //calculate humidity, temperature
LCD_disp_str(0,1,"TE:");
LCD_disp_str(0,2,"RH:");
LCD_disp_str(6,1,".");
LCD_disp_str(6,2,".");
LCD_disp_str(8,1,"C ");
LCD_disp_str(8,2,"% ");
wendu=10*temp_val.f; //例如温度109.1→1091
if(10*temp_val.f<0) {LCD_disp_str(12,2,"nega");}//实时温度为负数时,显示提示符nega
else {LCD_disp_str(12,2," ");} //实时温度为正数时,去除负数提醒nega
LCD_disp_char(3,1,abs(wendu)/1000+'0'); //显示温度百位,加“0”是为了将字符的ASCII码大于48(即字符0的ASCII值),一般是将数字0,1,2……,9转换为字符“0”,“1”……,“9”;
LCD_disp_char(4,1,abs(wendu)%1000/100+'0'); //显示温度十位
LCD_disp_char(5,1,abs(wendu)%100/10+'0'); //显示温度个位
LCD_disp_char(7,1,abs(wendu)%10+'0'); //显示温度小数点后第一位
shidu=10*humi_val.f;
LCD_disp_char(3,2,shidu/1000+'0'); //显示湿度百位
LCD_disp_char(4,2,(shidu%1000)/100+'0'); //显示湿度十位
LCD_disp_char(5,2,(shidu%100)/10+'0'); //显示湿度个位
LCD_disp_char(7,2,(shidu%10)+'0'); //显示湿度小数点后第一位
led_control(&temp_val.f,&humi_val.f);
}
//----------wait approx. 0.8s to avoid heating up SHT11------------------------------
delay_n10us(800); //延时约0.8s
}
}
}