温湿度传感器采用AOSONG的DHT12温湿度传感器;该传感器兼容单总线和标准的IIC通信协议,在本文中将叙述IIC通信协议获取温湿度数据,通过STM32的普通GPIO模拟IIC协议驱动DHT12;
以下将从IIC协议到DHT12驱动逐步进行详细介绍,并附有iic.c、iic.h、dht12.c、dht12.h源代码
IIC协议(Inter-integrated circuit) interface:
IIC(集成电路总线)简化了信号传输总线接口,是由PHILIPS公司设计的一种双向、二进制、同步串行总线,主要是用来连接整体电路,IIC是一种多向控制总线——多个设备可以连接到同一个总线结构下,同时设备都可以作为实时数据传输的控制源。
1. 基本参数&术语
1) 发送器:发送数据到总线的器件;
2) 接收器:从总线接收数据的器件;
3) 主机:发起/停止数据传输、提供时钟信号的器件;
4) 从机:被主机寻址的器件;
5) 多主机:可以由多个主机试图去控制总线,但是不会被破坏;
6) 仲裁:当多个主机试图去控制总线时,通过仲裁可以使得只有一个主机获得总线控制权,并且它传输的信息不被破坏;
7) 同步:多个器件同步时钟信号的过程;
2. IIC协议
1) IIC数据传送的位数必须是8bit,总线能挂载设备的总数由总线最大电容400pF限制,但不超过7bit寻址设备数27=128个;
2) 硬件设计时SDA、SCL都需要上拉电阻上拉,在没有主机控制时默认为高;
3) IIC数据从高位开始传输(MSB);
4) 开始信号(S):SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据;
5) 结束信号(P):SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据;
6) 应答信号(ACK):发送器发完8bit数据后就不再驱动总线了(SDA引脚变为输入),此时SDA总线为高;接收器在接收到8位数据后,在第9个时钟周期,拉低SDA电平;
7) 数据传送接收:SDA上传输的数据必须在SCL为高电平期间保持稳定,由此接收器在SCL为高时接收SDL的数据就不会产生紊乱;SDL上的数据只能在SCL为低电平期间翻转变化;
8) 数据寻址传输格式:首先发送7bit从设备地址和1bitwrite/read标志位,收到应答后发送8bit数据;
9) 如果从机要完成一些功能后(例如一个内部终端服务程序)才能继续接受或发送下一个字节,从机可以拉低SCL迫使主机进入等待状态。当从机准备好接收下一个数据并释放SCL后,数据传输继续。如果主机在传输数据器件也需要完成一些其他功能(例如一个内部终端服务程序)也可以拉低SCL以占总线;
10) NO ACK处理机制
a) 当从机不能相应从机地址时(例如它忙于其他事而无法相应IIC总线的操作,或这个地址没有对应的主机),在第9个SCL周期内SDA线没有被拉低,即没有ACK信号。这时,主机发出一个P信号终止传输或者重新发出有个S信号开始新的传输;
b) 如果从机接收器在传输过程中不能接收更多的数据时,它也不会发出Ack信号。这样,主机就可以意识到这点,从而发出一个P信号终止传输或者发出一个新的S信号开始新的传输。
c) 主机接收器在接收到最后一个字节后,也不会发出ACK信号。于是,从机发送器释放SDA线,允许主机发出P信号结束传输;
3. 引脚
1) SDA:双向数据线;所有接到IIC总线设备的SDA都接到总线SDA;
2) SCL:时钟信号线;所有接到IIC总线设备的SCL都接到总线SCL;
iic.c源文件
//File Name: iic.c
#include "iic.h"
#include "delay.h"
//SDA:PA1
void SDA_IN()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU ; //上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
//SDA:PA1
void SDA_OUT()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //开漏输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
//初始化IIC SDA:PA1 SCL: PA4
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //开漏输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
IIC_SCL=1;//高电平
IIC_SDA=1;
}
//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); //SDA线设为输出;
IIC_SDA=1; //SDA输出高电平;
IIC_SCL=1; //SCL输出高电平;
delay_us(4);
IIC_SDA=0; //开始信号:当SCL为高,SDA由高变低;
delay_us(4);
IIC_SCL=0; //钳住I2C总线,准备发送或接收数据
delay_us(4);
}
//产生IIC停止信号
//当SCL为hight时,SDA从low变hight
void IIC_Stop(void)
{
SDA_OUT();
IIC_SCL=0;
IIC_SDA=0;
delay_us(4);
IIC_SCL=1;
delay_us(4);
IIC_SDA=1; //发送I2C总线结束信号
delay_us(4);
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
uint8_t IIC_Wait_Ack(void)
{
uint8_t ucErrTime=0;
SDA_IN(); //SDA设置为输入
delay_us(4);
IIC_SCL=1;
delay_us(4);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
delay_us(1);
}
IIC_SCL=0; //时钟输出0
delay_us(50);
return 0;
}
//产生ACK应答
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(5);
IIC_SCL=1;
delay_us(5);
IIC_SCL=0;
}
//不产生ACK应答
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(5);
IIC_SCL=1;
delay_us(5);
IIC_SCL=0;
}
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void IIC_Send_Byte(uint8_t txd)
{
uint8_t t;
SDA_OUT();
IIC_SCL=0; //拉低时钟开始数据传输
for(t=0;t<8;t++)
{
IIC_SDA=(txd&0x80)>>7;
txd<<=1;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
uint8_t IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN(); //SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(50);
IIC_SCL=1;
delay_us(50);
receive<<=1;
if(READ_SDA)
receive++;
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
iic.h源文件
#ifndef __IIC_H
#define __IIC_H
#include "sys.h"
//IO操作函数
#define IIC_SCL PAout(4) //SCL
#define IIC_SDA PAout(1) //SDA
#define READ_SDA PAin(1) //输入SDA
void SDA_IN(void);
void SDA_OUT(void);
//IIC所有操作函数
void IIC_Init(void); //初始化IIC的IO口
void IIC_Start(void); //发送IIC开始信号
void IIC_Stop(void); //发送IIC停止信号
void IIC_Send_Byte(uint8_t txd); //IIC发送一个字节
uint8_t IIC_Read_Byte(unsigned char ack); //IIC读取一个字节
uint8_t IIC_Wait_Ack(void); //IIC等待ACK信号
void IIC_Ack(void); //IIC发送ACK信号
void IIC_NAck(void); //IIC不发送ACK信号
#endif
DHT12硬件连接
DHT12的SDA和SCL直接与STM32的普通IO连接。同时,SCL、SDA均需通过1K—10K的电阻上拉;DHT12支持的电源范围为2.7V – 5.5V,直接连5V或3.3V均可。安装在电路板上尽可能将传感器原理电子元件,并安装在热源下方,同时保持外壳的良好通风。进行手动焊接时,最高300℃条件下接触时间少于5s;
DHT12数据获取协议分析
1) IIC的地址为0xB8(DEV SEL);IIC的通信速率不能高于400KHz,对SDA和SCL所连总线的IO进行初始化时,IO的速率设置为2MHz;
BYTE ADDR |
R/W |
Desc. |
Note |
0x00 |
R |
湿度整数位 |
相对湿度值 |
0x01 |
R |
湿度小数位 |
|
0x02 |
R |
温度整数位 |
温度值 |
0x03 |
R |
温度小数位 |
|
0x04 |
R |
|
校验和 |
2) 每次读取传感器间隔大于2s即可获得准确的数据;
3) 输出的40位数据中,温度的小数位的第8Bit为1则表示采样得出的温度为负值;
DHT12驱动代码
#include "dht12.h"
#include "delay.h"
uint8_t dat[5]; //接收数据
static uint8_t check; //用于保存数据校验的结果
//初始化IIC接口
void DHT12_Init(void)
{
IIC_Init();
}
void DHT12_ReadByte()
{
uint8_t i;
IC_Start( ); //主机发送总线开始信号;
IIC_Send_Byte(0xB8);
IIC_Wait_Ack( );
IIC_Send_Byte(0x00);
IIC_Wait_Ack( );
IIC_Start( ); //进入接收模式 ,接收0xB8的数据
IIC_Send_Byte(0xB9);
IIC_Wait_Ack( );
for(i=0;i<4;i++)
{
dat[i]=IIC_Read_Byte(1); //读前四个,发送ACK
}
dat[i]=IIC_Read_Byte(0); //读第5个发送NACK
IIC_Stop();//产生一个停止条件
}
//温湿度诗句读取校验,成功返回 TURE,错误返回 FALSE
void Data_Check()
{
DHT12_ReadByte();
if(dat[4] == (dat[0]+dat[1]+dat[2]+dat[3]))
check = 0xff; //读取成功
else
check = 0; //读取失败
}
//读取湿度值
uint16_t Humid_Read( )
{
uint16_t V_Humid;
Data_Check( );
if(check)
{
V_Humid = ((dat[0]*10+dat[1])-200);//湿度的取值范围为20%-95%,取增量为-20,相对值范围为0-750
//计算的结果是相对值 V_Humid =(整数位+小数位)/分辨率+增量;
return V_Humid;
}
else
return 0;
}
//读取温度值;
uint16_t Temper_Read( )
{
uint16_t V_Temper;
if(check)
{
if(dat[3]&0x80)
V_Temper = (200-(dat[2]*10+(dat[3]&0x7F)));//温度为负数,温度取值范围是-20—60,增量为200;
else
V_Temper = (200+(dat[2]*10+(dat[3]&0x7F)));//温度为正数;
check = 0;
return V_Temper;
}
else
return 0;
}
dht12.h源文件
#ifndef __DHT12_H
#define __DHT12_H
#include "iic.h"
#include "stm32f10x.h"
extern uint8_t Humid[3];
extern uint8_t Temper[4]; //第一位是正负号
typedef enum
{
FALSE=0,
TURE
}bool;
void DHT12_Init(void); //初始化IIC
//读取温湿度并转换为字符串 0失败 1成功
uint16_t Humid_Read(void);
uint16_t Temper_Read(void);
#endif