一个可靠的温湿度采集方案:用PIC/MSP430单片机驱动IIC总线SHT20(附代码)

在工业环境监测、智慧农业或仓储管理系统中,温湿度是两个最基础也最关键的参数。它们直接关系到产品质量、设备安全甚至能耗成本。六、七年前,当我需要一个既精准又稳定的数字温湿度方案时,我选择了SHT20这款传感器,并通过最简洁的IIC总线与PICMSP430单片机连接。

今天,我想分享这个经过多年项目验证的方案。它不仅成本低廉、线路简单,更重要的是,在干扰复杂的现场,它表现出了出色的可靠性。

一、 为什么选择SHT20IIC总线?

在众多传感器中做出选择,我主要基于以下几点工业应用的考量:

  1. SHT20的核心优势
    • 高精度与稳定性:典型精度可达±3%RH(湿度)和±0.3°C(温度),且长期漂移极小,适合需要连续监测的场合。
    • 全量程标定:出厂已校准,无需用户再次标定,节省了生产环节的工序和成本。
    • 数字输出,抗干扰强:直接输出数字信号,避免了模拟信号在长距离传输中的衰减和干扰问题,这是工业现场最看重的特性。
  2. IIC总线的实用性
    • 接线极致简单:仅需两根线(SDA数据线、SCL时钟线),加上电源和地,即可实现双向通信,极大简化了布线。
    • 节省单片机资源:可以挂载多个IIC设备,共用总线,非常适合需要采集多路参数的系统。

下图是我在多个低成本项目中使用的核心电路,基于PIC12F1822这款仅有8个引脚的单片机

 

电路解读与设计要点:

  • 核心极简:连接关系一目了然,单片机的两个通用I/O口(RA0, RA2)分别连接SHT20SDASCL
  • 关键上拉电阻SDASCL线上的10kΩ上拉电阻(R1, R2)是必不可少的IIC总线是开漏输出,必须依靠上拉电阻将电平拉高,其阻值大小会影响总线速度和抗干扰能力,在一般应用中,4.7kΩ-10kΩ是常用范围。
  • 电源去耦:为SHT20VDD引脚就近放置一个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)

       {

              //加入采集温湿度程序

       }

}

三、 实战经验与避坑指南

在多个现场应用后,我总结出以下几点关键经验:

  1. 上拉电阻是灵魂:阻值过大会导致上升沿缓慢,在高速模式或总线电容大时容易出错;阻值过小则增加功耗,且可能超出I/O口拉电流能力。4.7kΩ-10kΩ是典型值,布线较长时可适当减小,实测10kIIC总线不超过30cm
  2. 时序必须精确IIC协议对时序有严格要求。文章中的delay2us()函数是针对16MHz主频的PIC12F1822校准的。如果更换单片机或主频,必须重新校准延时,确保SCL时钟周期满足传感器要求。
  3. 长线传输问题:当传感器远离单片机(超过1米)时,总线电容增大,可能导致信号畸变。此时应:
    • 使用更小的上拉电阻(如2.2kΩ)增强驱动能力。
    • 考虑改用RS-485接口的变送器

四、 移植到MSP430单片机

将代码移植到MSP430(如MSP430F415)非常简单:

  1. 修改引脚宏定义,指向MSP430的对应引脚(如P1OUTP1DIRP1IN)。
  2. 重写delay2us()函数MSP430的指令周期与PIC不同,需要根据其主频重新计算NOP指令数量。
  3. MSP430有的拥有硬件IIC模块,如果对速度或CPU占用率有更高要求,可以配置使用硬件模块,代码结构会更简洁。

结语

通过PIC/MSP430的普通I/O口模拟IIC总线来驱动SHT20,是一个在成本、精度和可靠性之间取得绝佳平衡的方案。它不需要昂贵的专用芯片,却能提供媲美高端仪表的稳定数据。

掌握一种总线的本质,比会用十个不同的库更重要。 IIC如此,RS-485SPI亦如此。这份用时间打磨出来的代码,希望能在你的下一个项目中发挥作用。

另:本文介绍的温湿度采集也可以做一个小板子,集成LDOSHT20RS485总线,可以安装在采集现场,通过RS485总线连接到控制柜,需要精度更高的传感器可以采用SHT30等。

你用过哪些IIC总线模块或者在IIC驱动方面遇到过哪些问题,欢迎关注我在评论区留言!

后续干货不断,咱们一起在单片机的世界里,共同进步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值