在工业环境监测、智慧农业或仓储管理系统中,温湿度是两个最基础也最关键的参数。它们直接关系到产品质量、设备安全甚至能耗成本。六、七年前,当我需要一个既精准又稳定的数字温湿度方案时,我选择了SHT20这款传感器,并通过最简洁的IIC总线与PIC或MSP430单片机连接。
今天,我想分享这个经过多年项目验证的方案。它不仅成本低廉、线路简单,更重要的是,在干扰复杂的现场,它表现出了出色的可靠性。
一、 为什么选择SHT20与IIC总线?
在众多传感器中做出选择,我主要基于以下几点工业应用的考量:
- SHT20的核心优势:
- 高精度与稳定性:典型精度可达±3%RH(湿度)和±0.3°C(温度),且长期漂移极小,适合需要连续监测的场合。
- 全量程标定:出厂已校准,无需用户再次标定,节省了生产环节的工序和成本。
- 数字输出,抗干扰强:直接输出数字信号,避免了模拟信号在长距离传输中的衰减和干扰问题,这是工业现场最看重的特性。
- IIC总线的实用性:
- 接线极致简单:仅需两根线(SDA数据线、SCL时钟线),加上电源和地,即可实现双向通信,极大简化了布线。
- 节省单片机资源:可以挂载多个IIC设备,共用总线,非常适合需要采集多路参数的系统。
下图是我在多个低成本项目中使用的核心电路,基于PIC12F1822这款仅有8个引脚的单片机
电路解读与设计要点:
- 核心极简:连接关系一目了然,单片机的两个通用I/O口(RA0, RA2)分别连接SHT20的SDA和SCL。
- 关键上拉电阻:SDA和SCL线上的10kΩ上拉电阻(R1, R2)是必不可少的。IIC总线是开漏输出,必须依靠上拉电阻将电平拉高,其阻值大小会影响总线速度和抗干扰能力,在一般应用中,4.7kΩ-10kΩ是常用范围。
- 电源去耦:为SHT20的VDD引脚就近放置一个0.1μF的瓷片电容(C1),能有效滤除电源噪声,保证传感器内部ADC的稳定工作。
二、 软件核心:精准的IIC时序模拟
对于PIC12F1822这类没有硬件IIC模块的小封装单片机,我们需要用软件“模拟”出标准的IIC时序。这是整个驱动的基础,其关键在于极精确的微秒级延时。
以下是经过多年锤炼的IIC底层驱动代码,你可以将它视为一个可靠的模板。
#include<pic12f1822.h>
__CONFIG(0x083c); //内部晶振
__CONFIG(0x3612);
#define uchar unsigned char
#define uint unsigned int
#define SHTSCK_SET RA2=1
#define SHTSCK_CLR RA2=0
#define SHTSCK_DIR_OUT TRISA2=0
#define SHTSCK_DIR_IN TRISA2=1
#define SHTSCK_IN RA2
#define SHTDA_SET RA0=1
#define SHTDA_CLR RA0=0
#define SHTDA_DIR_OUT TRISA0=0
#define SHTDA_DIR_IN TRISA0=1
#define SHTDA_IN RA0
#define SHTWRITE 0x80 //地址1000 000 0表示写
#define SHTREAD 0x81 // 1表示读
void StateI2C(void);
void STOPI2C(void);
void Response(uchar a);
void SHT_Master_byte2(uchar byte);
uchar SHT_Master_byte(uchar byte);
uchar SHT_Receiver_byte(void);
unsigned int temp();
unsigned int humi();
//uchar write_Register(uchar aaa);
void delay(unsigned int v)
{
while(v--){};
}
void delay2us(void)
{
asm("nop");//也可以用for循环,我当时测试时觉得用这么多个nop比较合适,可以在此基础上加减几个。
asm("nop");
asm("nop");
asm("nop");
asm("nop");
asm("nop");
asm("nop");
asm("nop");
asm("nop");
asm("nop");
asm("nop");
asm("nop");
asm("nop");
asm("nop");
asm("nop");
asm("nop");
}
void StateI2C(void)
{
SHTDA_DIR_OUT;
SHTSCK_DIR_OUT;
SHTDA_SET;
SHTSCK_SET;
delay2us();
SHTDA_CLR;
delay2us();
SHTSCK_CLR;
// delay2us();
}
//******************************STOPI2C**************************************
//**************************单片机结束信号***********************************
void STOPI2C(void)
{
SHTSCK_CLR;
SHTDA_CLR;
delay2us();
SHTSCK_SET;
delay2us();
SHTDA_SET;
delay2us();
}
//******************************STOPI2C**************************************
//***************************单片机回应从机**********************************
void Response(uchar a) //应答 SDA为低电平,SCL为高电平
{
if(a==0) SHTDA_CLR; //主机“应答”继续传输
else SHTDA_SET; //“非应答”表示数据传输结束
SHTSCK_SET;
delay2us();
SHTSCK_CLR;
// delay2us();
}
//********************单片机输出 1byte 数据******************************
//uchar SHT_Master_byte(uchar byte)
void SHT_Master_byte2(uchar byte)
{
uchar i;
// uchar ack=0;
SHTSCK_CLR;
SHTDA_CLR;
SHTDA_DIR_OUT;
SHTSCK_DIR_OUT;
for(i=0;i<8;i++)
{
if(byte&0X80) SHTDA_SET;
else SHTDA_CLR;
SHTSCK_SET;
delay2us();
SHTSCK_CLR;
delay2us();
byte*=2; //左移一位
}
// SHTDA_DIR_IN;
// _NOP();
SHTSCK_SET;
// if(P1IN&BIT4) ack=1; //回应
delay2us();
SHTSCK_CLR;
// return(ack);
}
//********************单片机输出 1byte 数据******************************
uchar SHT_Master_byte(uchar byte)
{
uchar i;
uchar ack=0;
SHTSCK_CLR;
SHTDA_CLR;
SHTDA_DIR_OUT;
SHTSCK_DIR_OUT;
for(i=0;i<8;i++)
{
if(byte&0X80) SHTDA_SET;
else SHTDA_CLR;
SHTSCK_SET;
delay2us();
SHTSCK_CLR;
delay2us();
byte*=2; //左移一位
}
SHTDA_DIR_IN;
// _NOP();
SHTSCK_SET;
delay2us();
if(RA0) ack=1; //回应
SHTSCK_CLR;
return(ack);
}
//********************从机输出1byte 数据******************************
uchar SHT_Receiver_byte(void)
{
uchar aaa=0,i;
for(i=0;i<8;i++)
{
SHTDA_DIR_IN;
aaa*=2;
SHTSCK_SET;
delay2us();
if(SHTDA_IN) aaa+=1;
SHTSCK_CLR;
delay2us();
}
return(aaa);
}
//**************************非主机模式温度测试*****************************
unsigned int temp()
{
uint aaa=0;
uchar ack;
// unsigned long bbb;
uchar tem1,tem2;
// uchar test;
StateI2C();
SHT_Master_byte2(0x80); //地址+写
SHT_Master_byte2(0xf3); //命令:非主机温度测试
do
{
asm("nop");
// delay2us();
StateI2C();
ack = SHT_Master_byte(0x81); //命令:读取数据。。等待回应
}
while(ack==1);
delay2us();
SHTDA_DIR_IN;
delay2us();
tem1=SHT_Receiver_byte(); //读取数据高八位
delay2us();
SHTDA_DIR_OUT;
Response(0); //单片机回应
SHTDA_DIR_IN;
tem2 = SHT_Receiver_byte(); //读取数据低八位
SHTDA_DIR_OUT;
// Response(1);
// SHTDA_DIR_IN;
// test = SHT_Receiver_byte();
STOPI2C();
aaa = (aaa|tem1)*256+tem2;
return(aaa);
}
//**************************非主机模式湿度测试*****************************
unsigned int humi()
{
uint aaa=0;
uchar ack;
uchar tem1,tem2;
// uchar test;
StateI2C();
SHT_Master_byte2(0x80); //地址+写
SHT_Master_byte2(0xf5); //命令:非主机湿度测试
do
{
asm("nop");
delay2us();
StateI2C();
ack = SHT_Master_byte(0x81); //命令:读取数据。。等待回应
}
while(ack==1);
delay2us();
SHTDA_DIR_IN;
delay2us();
tem1=SHT_Receiver_byte(); //读取数据高八位
delay2us();
SHTDA_DIR_OUT;
Response(0); //单片机回应
SHTDA_DIR_IN;
tem2=SHT_Receiver_byte(); //读取数据低八位
SHTDA_DIR_OUT;
// Response(1);
// SHTDA_DIR_IN;
// test = SHT_Receiver_byte();
STOPI2C();
aaa = (aaa|tem1)*256+tem2;
return(aaa);
}
void main()
{
unsigned char i=0;
TRISA=0x28;
PORTA=0x00;
APFCON=0x8c;//选择串口UART管脚
ANSELA=0x0;//管脚不用于模拟量
PIE1=0x01;
INTCON=0x40;
OSCCON=0x7b; //16MHZ内部晶振
WDTCON=0x1c; //看门狗16秒
//其他初始化程序
while(1)
{
//加入采集温湿度程序
}
}
三、 实战经验与避坑指南
在多个现场应用后,我总结出以下几点关键经验:
- 上拉电阻是灵魂:阻值过大会导致上升沿缓慢,在高速模式或总线电容大时容易出错;阻值过小则增加功耗,且可能超出I/O口拉电流能力。4.7kΩ-10kΩ是典型值,布线较长时可适当减小,实测10k时IIC总线不超过30cm。
- 时序必须精确:IIC协议对时序有严格要求。文章中的
delay2us()函数是针对16MHz主频的PIC12F1822校准的。如果更换单片机或主频,必须重新校准延时,确保SCL时钟周期满足传感器要求。 - 长线传输问题:当传感器远离单片机(超过1米)时,总线电容增大,可能导致信号畸变。此时应:
- 使用更小的上拉电阻(如2.2kΩ)增强驱动能力。
- 考虑改用RS-485接口的变送器。
四、 移植到MSP430单片机
将代码移植到MSP430(如MSP430F415)非常简单:
- 修改引脚宏定义,指向MSP430的对应引脚(如
P1OUT,P1DIR,P1IN)。 - 重写
delay2us()函数。MSP430的指令周期与PIC不同,需要根据其主频重新计算NOP指令数量。 - MSP430有的拥有硬件IIC模块,如果对速度或CPU占用率有更高要求,可以配置使用硬件模块,代码结构会更简洁。
结语
通过PIC/MSP430的普通I/O口模拟IIC总线来驱动SHT20,是一个在成本、精度和可靠性之间取得绝佳平衡的方案。它不需要昂贵的专用芯片,却能提供媲美高端仪表的稳定数据。
掌握一种总线的本质,比会用十个不同的库更重要。 IIC如此,RS-485、SPI亦如此。这份用时间打磨出来的代码,希望能在你的下一个项目中发挥作用。
另:本文介绍的温湿度采集也可以做一个小板子,集成LDO、SHT20和RS485总线,可以安装在采集现场,通过RS485总线连接到控制柜,需要精度更高的传感器可以采用SHT30等。
你用过哪些IIC总线模块或者在IIC驱动方面遇到过哪些问题,欢迎关注我在评论区留言!
后续干货不断,咱们一起在单片机的世界里,共同进步。
4342

被折叠的 条评论
为什么被折叠?



