一、項(xiàng)目介紹
當(dāng)前文章介紹基于51單片機(jī)和SHT30傳感器設(shè)計(jì)的環(huán)境溫度與濕度檢測(cè)設(shè)備。設(shè)備采用IIC模擬時(shí)序通信協(xié)議,能夠?qū)崟r(shí)監(jiān)測(cè)環(huán)境的溫度和濕度,并將數(shù)據(jù)通過LCD顯示屏顯示出來;可以廣泛應(yīng)用于室內(nèi)環(huán)境監(jiān)測(cè)、氣象觀測(cè)、農(nóng)業(yè)溫室監(jiān)測(cè)等領(lǐng)域。
在本項(xiàng)目中,使用了51單片機(jī)作為主控芯片,SHT30傳感器作為溫濕度傳感器,LCD顯示屏作為數(shù)據(jù)顯示模塊。通過51單片機(jī)的GPIO口模擬IIC通信協(xié)議,實(shí)現(xiàn)了與SHT30傳感器的數(shù)據(jù)通信。
二、硬件設(shè)計(jì)
2.1 硬件構(gòu)成
本次設(shè)計(jì)所需的硬件主要包括以下部分:
2.2 硬件接口及信號(hào)
本次設(shè)計(jì)使用51單片機(jī)通過IIC總線與SHT30傳感器進(jìn)行通信,同時(shí)使用串口與上位機(jī)進(jìn)行數(shù)據(jù)傳輸,并使用液晶顯示屏顯示當(dāng)前溫濕度值。
具體接口和信號(hào)定義如下:
(1) 51單片機(jī)與SHT30傳感器之間的IIC接口:
端口 | 功能 | 說明 |
---|---|---|
P2.0 | SDA | 數(shù)據(jù)線 |
P2.1 | SCL | 時(shí)鐘線 |
P2.2 | RESET | 復(fù)位線 |
(2) 51單片機(jī)與串口通信模塊之間的接口:
端口 | 功能 | 說明 |
---|---|---|
P3.0 | TXD | 發(fā)送線 |
P3.1 | RXD | 接收線 |
P3.2 | GND | 地線 |
(3) 51單片機(jī)與液晶屏之間的接口:
端口 | 功能 | 說明 |
---|---|---|
P1.0-P1.7 | DB0-DB7 | 數(shù)據(jù)線 |
P0.0 | RS | 指令/數(shù)據(jù)選擇線 |
P0.1 | RW | 讀/寫選擇線 |
P0.2 | E | 使能線 |
P0.3 | CS | 片選線 |
VCC | 電源正極 | 5V |
GND | 電源地 | 地 |
三、軟件設(shè)計(jì)
3.1 SHT30傳感器代碼
下面代碼讀取SHT30傳感器的值并通過串口打印。
#include <REG52.h>
#include <stdio.h>
#define uchar unsigned char
#define uint unsigned int
sbit SDA=P2^0;
sbit SCL=P2^1;
void delay(int n)
{
int i;
while(n--)
{
for(i=0; i<120; i++);
}
}
void start()
{
SDA = 1;
_nop_();
SCL = 1;
_nop_();
SDA = 0;
_nop_();
SCL = 0;
_nop_();
}
void stop()
{
SDA = 0;
_nop_();
SCL = 1;
_nop_();
SDA = 1;
_nop_();
}
void ack()
{
SDA = 0;
_nop_();
SCL = 1;
_nop_();
SCL = 0;
_nop_();
SDA = 1;
_nop_();
}
void nack()
{
SDA = 1;
_nop_();
SCL = 1;
_nop_();
SCL = 0;
_nop_();
}
void write_byte(uchar dat)
{
uchar i;
for(i=0; i<8; i++)
{
SDA = dat & 0x80;
_nop_();
SCL = 1;
_nop_();
SCL = 0;
_nop_();
dat <<= 1;
}
ack();
}
uchar read_byte()
{
uchar i, dat;
for(i=0; i<8; i++)
{
dat <<= 1;
SCL = 1;
_nop_();
dat |= SDA;
SCL = 0;
_nop_();
}
return dat;
}
void init_sht30()
{
start();
write_byte(0x80);
if(read_byte() != 0x5A)
{
stop();
return;
}
write_byte(0xBE);
if(read_byte() != 0x08 || read_byte() != 0x00)
{
stop();
return;
}
stop();
}
float measure_temp(void)
{
uchar temp_h, temp_l, crc;
float temp;
start();
write_byte(0x80); // 主機(jī)發(fā)送寫地址
write_byte(0x2C); // 選擇開始溫度測(cè)量命令
write_byte(0x06);
stop();
delay(15); // 延時(shí)等待溫度測(cè)量完成
start();
write_byte(0x81); // 主機(jī)發(fā)送讀地址
temp_h=read_byte();
ack();
temp_l=read_byte();
ack();
crc=read_byte();
stop();
temp = ((temp_h<<8)+temp_l)*175.0/0xffff - 45.0; // 溫度值轉(zhuǎn)換公式
return temp;
}
float measure_humi(void)
{
uchar humi_h, humi_l, crc;
float humi;
start();
write_byte(0x80); // 主機(jī)發(fā)送寫地址
write_byte(0x2C); // 選擇開始濕度測(cè)量命令
write_byte(0x06);
stop();
delay(15); // 延時(shí)等待濕度測(cè)量完成
start();
write_byte(0x81); // 主機(jī)發(fā)送讀地址
humi_h=read_byte();
ack();
humi_l=read_byte();
ack();
crc=read_byte();
stop();
humi = ((humi_h<<8)+humi_l)*100.0/0xffff; // 濕度值轉(zhuǎn)換公式
return humi;
}
void main()
{
float temp, humi;
init_sht30(); // SHT30 初始化
TMOD=0x20; // 定時(shí)器0工作方式2,8位定時(shí)器,用于波特率設(shè)置
TH1=0xfd; // 波特率9600
TL1=0xfd;
TR1=1; // 啟動(dòng)定時(shí)器0
SCON=0x50; // 設(shè)置串口工作方式1,允許接收,允許接收中斷
ES=1; // 允許串口中斷
while(1)
{
temp = measure_temp();
humi = measure_humi();
printf("Temperature: %.1fC, Humidity: %.1f%n", temp, humi);
delay(500); // 間隔時(shí)間500ms
}
}
void ser() interrupt 4 using 2
{
if(RI) // 接收到數(shù)據(jù)
{
RI=0; // 清除標(biāo)志位
}
if(TI) // 發(fā)送完畢
{
TI=0; // 清除標(biāo)志位
}
}
在上面的代碼中,定義了兩個(gè)函數(shù) measure_temp
和 measure_humi
,分別用于測(cè)量溫度和濕度值,并返回結(jié)果。在主函數(shù)中,利用這兩個(gè)函數(shù)得到當(dāng)前的溫濕度值,然后通過串口打印出來。
3.2 LCD1602顯示屏代碼
下面代碼是LCD1602驅(qū)動(dòng)代碼,完成數(shù)字字符顯示。
#include <REG52.h>
#define LCD1602_DB P0
sbit RS = P2^5;
sbit RW = P2^6;
sbit E = P2^7;
void delay(int n)
{
int i;
while(n--)
{
for(i=0; i<120; i++);
}
}
void main()
{
//LCD 初始化
delay(1000);
LCD1602_DB = 0x38;
E = 1;
delay(5);
E = 0;
delay(500);
LCD1602_DB = 0x08;
E = 1;
delay(5);
E = 0;
delay(500);
LCD1602_DB = 0x01;
E = 1;
delay(5);
E = 0;
delay(500);
LCD1602_DB = 0x06;
E = 1;
delay(5);
E = 0;
delay(500);
LCD1602_DB = 0x0C;
E = 1;
delay(5);
E = 0;
while(1)
{
//向LCD中寫入數(shù)字12345
RS = 0; //選擇指令寄存器
LCD1602_DB = 0x80; //設(shè)置地址為第一行的第一個(gè)字符位置(0x80 + 0x00)
E = 1;
delay(5);
E = 0;
RS = 1; //選擇數(shù)據(jù)寄存器
LCD1602_DB = 0x31; //寫入數(shù)字1
E = 1;
delay(5);
E = 0;
LCD1602_DB = 0x32; //寫入數(shù)字2
E = 1;
delay(5);
E = 0;
LCD1602_DB = 0x33; //寫入數(shù)字3
E = 1;
delay(5);
E = 0;
LCD1602_DB = 0x34; //寫入數(shù)字4
E = 1;
delay(5);
E = 0;
LCD1602_DB = 0x35; //寫入數(shù)字5
E = 1;
delay(5);
E = 0;
delay(500); //間隔時(shí)間為500ms
}
}
在上面的代碼中,定義了函數(shù) delay
用于延時(shí)等待,并且實(shí)現(xiàn)了LCD1602的初始化和寫入操作。在主函數(shù)中,執(zhí)行LCD1602的初始化操作,然后循環(huán)不斷向LCD中寫入數(shù)字12345,并且間隔時(shí)間為500ms。
3.3 完整代碼
#include<reg52.h>
#include<intrins.h>
#define uchar unsigned char
#define uint unsigned int
sbit SDA = P2^0; //定義SDA引腳
sbit SCL = P2^1; //定義SCL引腳
sbit CS = P0^3; //定義液晶屏片選引腳
sbit RW = P0^1; //定義液晶屏讀/寫引腳
sbit RS = P0^0; //定義液晶屏指令/數(shù)據(jù)引腳
sbit E = P0^2; //定義液晶屏使能引腳
void delay(int n) //延時(shí)函數(shù),n為延時(shí)時(shí)間
{
int i;
while(n--)
{
for(i=0; i<120; i++);
}
}
void start() //開始信號(hào)
{
SDA = 1; //數(shù)據(jù)線高電平
_nop_();
SCL = 1; //時(shí)鐘線高電平
_nop_();
SDA = 0; //數(shù)據(jù)線低電平
_nop_();
SCL = 0; //時(shí)鐘線低電平
_nop_();
}
void stop() //結(jié)束信號(hào)
{
SDA = 0; //數(shù)據(jù)線低電平
_nop_();
SCL = 1; //時(shí)鐘線高電平
_nop_();
SDA = 1; //數(shù)據(jù)線高電平
_nop_();
}
void ack() //應(yīng)答信號(hào)
{
SDA = 0; //數(shù)據(jù)線低電平
_nop_();
SCL = 1; //時(shí)鐘線高電平
_nop_();
SCL = 0; //時(shí)鐘線低電平
_nop_();
SDA = 1; //數(shù)據(jù)線高電平
_nop_();
}
void nack() //非應(yīng)答信號(hào)
{
SDA = 1; //數(shù)據(jù)線高電平
_nop_();
SCL = 1; //時(shí)鐘線高電平
_nop_();
SCL = 0; //時(shí)鐘線低電平
_nop_();
}
void write_byte(uchar dat) //寫一個(gè)字節(jié)
{
uchar i;
for(i=0; i<8; i++)
{
SDA = dat & 0x80;
_nop_();
SCL = 1;
_nop_();
SCL = 0;
_nop_();
dat <<= 1;
}
ack();
}
uchar read_byte() //讀一個(gè)字節(jié)
{
uchar i, dat;
for(i=0; i<8; i++)
{
dat <<= 1;
SCL = 1;
_nop_();
dat |= SDA;
SCL = 0;
_nop_();
}
return dat;
}
void init_sht30() //SHT30初始化
{
start();
write_byte(0x80);
if(read_byte() != 0x5A)
{
stop();
return;
}
write_byte(0xBE);
if(read_byte() != 0x08 || read_byte() != 0x00)
{
stop();
return;
}
stop();
}
void measure() //測(cè)量溫濕度值
{
float humi, temp;
uint i;
start();
write_byte(0x80);
read_byte();
read_byte();
read_byte();
write_byte(0x2C);
write_byte(0x06);
for(i=0; i<40000; i++); //等待測(cè)量結(jié)果
start();
write_byte(0x80);
read_byte();
read_byte();
read_byte();
humi = read_byte() * 256;
humi += read_byte();
temp = read_byte() * 256;
temp += read_byte();
stop();
temp = -45 + (175*temp)/65535; //轉(zhuǎn)化溫度
humi = 100 * humi / 65535; //轉(zhuǎn)化濕度
//將溫濕度值通過串口發(fā)送
printf("Temperature: %.1fCn", temp);
printf("Humidity: %.1f%%RHn", humi);
}
void init_lcd() //液晶屏初始化
{
RW = 0;
RS = 0;
E = 0;
delay(15);
write_byte(0x30);
delay(15);
write_byte(0x30);
delay(5);
write_byte(0x30);
delay(5);
write_byte(0x38);
write_byte(0x08);
write_byte(0x01);
write_byte(0x06);
write_byte(0x0c);
}
void display(float temp, float humi) //顯示溫濕度值
{
uchar i;
uchar temp_str[5];
uchar humi_str[5];
//轉(zhuǎn)化為字符串
sprintf(temp_str, "%.1f", temp);
sprintf(humi_str, "%.1f", humi);
//顯示溫度
RS = 0;
E = 1;
P1 = 0x80; //第一行第一個(gè)字符
E = 0;
RS = 1;
for(i=0; i<5; i++)
{
E = 1;
P1 = temp_str[i];
E = 0;
}
//顯示濕度
RS = 0;
E = 1;
P1 = 0xc0; //第二行第一個(gè)字符
E = 0;
RS = 1;
for(i=0; i<5; i++)
{
E = 1;
P1 = humi_str[i];
E = 0;
}
}
void main()
{
init_sht30(); //SHT30初始化
init_lcd(); //液晶屏初始化
while(1)
{
measure(); //測(cè)量溫濕度值并通過串口發(fā)送
delay(1000);
display(temp, humi); //顯示溫濕度值
}
}