1. 认识温湿度传感器SHT2X
它使用IIC通信,能够测量温度和湿度。虽然看到它的丝印层上写有SHT20/21/25,其实它使用起来是一样的。
2. IIC相关知识点
IIC由SCL和SDA两个数据线来实现通信。
SCL为IIC的时钟线,SDA为IIC的数据线。
IIC的时序:
1. 启动信号
SCL在高电平期间,SDA从高电平跳变为低电平。
2. 停止信号
SCL在高电平期间,SDA从低电平跳变为高电平。
3. 数据的传输
IIC的数据传输是高位先行,先发送高位数据,再发送低位数据。
SCL在低电平期间,设备可以改变SDA的电平(作为要发送的那一位数据,准备发送)。
SCL在高电平期间,设备读取SDA当前的电平(读取到一位数据)。
注意:在数据传输时,SCL在高电平期间,不允许SDA跳变,否则就变成起始/停止信号了。
4. 数据的应答
在一个字节的数据发送后,要等待设备的应答,才可以继续发送下一个字节的数据。(若没有接收到设备的应答信号,则要考虑重发)
3. 使用IIC和SHT2x测量温湿度
相关代码:
iic.c文件内容:
#include "iic.h"
#include "delay.h"
// IIC时钟线使能函数名
#ifndef IIC_SCL_CLK_CMD
#define IIC_SCL_CLK_CMD RCC_APB2PeriphClockCmd
#endif
// IIC时钟线的时钟使能函数要填入的参数
#ifndef IIC_SCL_GPIO_CLK
#define IIC_SCL_GPIO_CLK RCC_APB2Periph_GPIOB
#endif
// IIC时钟线所在的GPIO组
#ifndef IIC_SCL_GPIO_PORT
#define IIC_SCL_GPIO_PORT GPIOB
#endif
// IIC时钟线所在的引脚号
#ifndef IIC_SCL_PIN
#define IIC_SCL_PIN GPIO_Pin_6
#endif
// IIC数据线使能函数名
#ifndef IIC_SDA_CLK_CMD
#define IIC_SDA_CLK_CMD RCC_APB2PeriphClockCmd
#endif
// IIC数据线的时钟使能函数要参入的参数
#ifndef IIC_SDA_GPIO_CLK
#define IIC_SDA_GPIO_CLK RCC_APB2Periph_GPIOB
#endif
// IIC数据线所在的GPIO组
#ifndef IIC_SDA_GPIO_PORT
#define IIC_SDA_GPIO_PORT GPIOB
#endif
// IIC数据线所在的引脚号
#ifndef IIC_SDA_PIN
#define IIC_SDA_PIN GPIO_Pin_7
#endif
// IIC的引脚设置为高/低电平
#define IIC_SCL_GPIO_RST() GPIO_ResetBits(IIC_SCL_GPIO_PORT, IIC_SCL_PIN)
#define IIC_SCL_GPIO_SET() GPIO_SetBits(IIC_SCL_GPIO_PORT, IIC_SCL_PIN)
#define IIC_SDA_GPIO_RST() GPIO_ResetBits(IIC_SDA_GPIO_PORT, IIC_SDA_PIN)
#define IIC_SDA_GPIO_SET() GPIO_SetBits(IIC_SDA_GPIO_PORT, IIC_SDA_PIN)
// IIC延时函数
#define IIC_DELAY() delay_us(5)
#define IIC_SDA_READ() GPIO_ReadInputDataBit(IIC_SDA_GPIO_PORT, IIC_SDA_PIN)
/**
* @brief IIC初始化
*/
void IIC_Init(void)
{
// 使能时钟
GPIO_InitTypeDef GPIO_InitTypeStructure; // GPIO初始化要用到的结构体
IIC_SCL_CLK_CMD(IIC_SCL_GPIO_CLK, ENABLE); // IIC时钟线所在的GPIO组的时钟使能
IIC_SDA_CLK_CMD(IIC_SDA_GPIO_CLK, ENABLE); // IIC数据线所在的GPIO组的时钟使能
// IIC_SCL引脚配置
GPIO_InitTypeStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitTypeStructure.GPIO_Pin = IIC_SCL_PIN;
GPIO_InitTypeStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(IIC_SCL_GPIO_PORT, &GPIO_InitTypeStructure); // 初始化GPIO
// IIC_SDA引脚配置
GPIO_InitTypeStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitTypeStructure.GPIO_Pin = IIC_SDA_PIN;
GPIO_InitTypeStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(IIC_SDA_GPIO_PORT, &GPIO_InitTypeStructure);
// 默认置为高电平(将SCL和SDA都设置为高电平)
IIC_SCL_GPIO_SET();
IIC_SDA_GPIO_SET();
}
/**
* @brief 设置IIC_SDA端口为输出模式
*/
static void __IIC_SDA_SET_OUTMODE(void)
{
GPIO_InitTypeDef GPIO_InitTypeStructure; // GPIO初始化要用到的结构体
// IIC_SDA引脚配置
GPIO_InitTypeStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitTypeStructure.GPIO_Pin = IIC_SDA_PIN;
GPIO_InitTypeStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(IIC_SDA_GPIO_PORT, &GPIO_InitTypeStructure);
}
/**
* @brief 设置IIC_SDA端口为输入模式
*/
static void __IIC_SDA_SET_INMODE(void)
{
GPIO_InitTypeDef GPIO_InitTypeStructure; // GPIO初始化要用到的结构体
// IIC_SDA引脚配置
GPIO_InitTypeStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_InitTypeStructure.GPIO_Pin = IIC_SDA_PIN;
GPIO_InitTypeStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(IIC_SDA_GPIO_PORT, &GPIO_InitTypeStructure);
}
/**
* @brief IIC起始信号
*/
void IIC_Start(void)
{
__IIC_SDA_SET_OUTMODE(); // IIC_SDA设置为输出模式
// 将IIC的SCL和SDA都拉高
IIC_SCL_GPIO_SET();
IIC_SDA_GPIO_SET();
IIC_DELAY(); // IIC延时,等待电平变化完成
// 拉低SDA数据线
IIC_SDA_GPIO_RST();
IIC_DELAY(); // IIC延时,等待电平变化完成
IIC_SCL_GPIO_RST(); // 拉低SCL,钳住IIC总线
}
/**
* @brief IIC停止信号
*/
void IIC_Stop(void)
{
IIC_SCL_GPIO_RST(); // 拉低SCL,钳住IIC总线
IIC_DELAY(); // IIC延时,等待电平变化完成
__IIC_SDA_SET_OUTMODE(); // IIC_SDA设置为输出模式
// 拉低SDA数据线
IIC_SDA_GPIO_RST();
IIC_DELAY(); // IIC延时,等待电平变化完成
IIC_SCL_GPIO_SET(); // 拉高SCL
IIC_DELAY(); // IIC延时,等待电平变化完成
// 拉高SDA,实现在SCL高电平期间,SDA从低电平到高电平的跳变
IIC_SDA_GPIO_SET();
IIC_DELAY(); // IIC延时,等待电平变化完成
}
/**
* @brief IIC发送一个字节的数据
*/
void IIC_SendByte(uint8_t data)
{
uint8_t i; // 循环计数值
IIC_SCL_GPIO_RST(); // 拉低SCL,钳住IIC总线
IIC_DELAY(); // IIC延时,等待电平变化完成
__IIC_SDA_SET_OUTMODE(); // IIC_SDA设置为输出模式
// 通过循环来发送一个字节的数据
for (i = 0; i < 8; i++)
{
if ((data << i) & 0x80)
{
// 如果data当前的最高位与上1之后为非零,说明是数据'1'
IIC_SDA_GPIO_SET(); // SDA设置为高电平
}
else
{
// 如果data当前的最高位与上1之后为零,说明是数据'0'
IIC_SDA_GPIO_RST(); // SDA设置为低电平
}
IIC_DELAY(); // IIC延时,等待电平变化完成
IIC_SCL_GPIO_SET(); // 拉高SCL,让其他设备读取SDA此时的电平
IIC_DELAY(); // IIC延时,等待电平变化完成
IIC_SCL_GPIO_RST(); // 拉低SCL,准备发送下一位数据
IIC_DELAY(); // IIC延时,等待电平变化完成
}
}
/**
* @brief 等待IIC的ACK信号
*/
uint8_t IIC_WaitACK(void)
{
uint8_t count = 250; // 超时计数
IIC_SCL_GPIO_RST(); // 拉低SCL,钳住IIC总线
IIC_DELAY(); // IIC延时,等待电平变化完成
__IIC_SDA_SET_INMODE(); // 设置SDA为输入模式
IIC_SDA_GPIO_SET(); // 设置SDA为高电平,实现上拉
IIC_DELAY(); // IIC延时,等待电平变化完成
IIC_SCL_GPIO_SET(); // 拉高SCL
IIC_DELAY(); // IIC延时,等待电平变化完成
while (IIC_SDA_READ())
{
if (!(count--))
{
/*
如果超时计数计满,读取到的SDA还是高电平,
说明读到的是NACK,返回NACK
*/
{
IIC_Stop();
return NACK;
}
}
}
IIC_SCL_GPIO_RST(); // 钳住SCL
/*
如果读取SDA是低电平,则不会进入while循环
(或是在超时前读到低电平而从while循环中退出),
到这里,返回ACK
*/
return ACK;
}
/**
* @brief IIC发送ACK
*/
void IIC_SendACK(void)
{
IIC_SCL_GPIO_RST(); // 拉低SCL,钳住IIC总线
IIC_DELAY(); // IIC延时,等待电平变化完成
__IIC_SDA_SET_OUTMODE(); // 设置SDA为输出模式
IIC_SDA_GPIO_RST(); // SDA设置为低电平,对应ACK的信号
IIC_DELAY(); // IIC延时,等待电平变化完成
IIC_SCL_GPIO_SET(); // 拉高SCL,发送数据
IIC_DELAY(); // IIC延时,等待电平变化完成
IIC_SCL_GPIO_RST(); // 拉低SCL,钳住IIC总线
}
/**
* @brief IIC发送NACK
*/
void IIC_SendNACK(void)
{
IIC_SCL_GPIO_RST(); // 拉低SCL,钳住IIC总线
IIC_DELAY(); // IIC延时,等待电平变化完成
__IIC_SDA_SET_OUTMODE(); // 设置SDA为输出模式
IIC_SDA_GPIO_SET(); // SDA设置为高电平,对应NACK的信号
IIC_DELAY(); // IIC延时,等待电平变化完成
IIC_SCL_GPIO_SET(); // 拉高SCL,发送数据
IIC_DELAY(); // IIC延时,等待电平变化完成
IIC_SCL_GPIO_RST(); // 拉低SCL,钳住IIC总线
}
/**
* @brief IIC读取一个字节的数据
*/
uint8_t IIC_ReadByte(uint8_t ack)
{
uint8_t i; // 循环计数值
uint8_t data = 0; // 存放读到的数据
IIC_SCL_GPIO_RST(); // 拉低SCL,钳住IIC总线
IIC_DELAY(); // IIC延时,等待电平变化完成
__IIC_SDA_SET_INMODE(); // 设置SDA为输入模式
IIC_SDA_GPIO_SET(); // 设置SDA为高电平,实现上拉
IIC_DELAY(); // IIC延时,等待电平变化完成
for (i = 0; i < 8; i++)
{
IIC_SCL_GPIO_SET(); // 拉高SCL,准备读取数据
IIC_DELAY(); // IIC延时,等待电平变化完成
data <<= 1; // 先将数据容器内的数据左移一位,防止最后一位读到的数据不正确
if (IIC_SDA_READ())
{
// 如果读SDA是高电平,说明接收到的是'1'
data |= 0x01;
}
#if 0
else
{
// 如果读SDA是低电平,说明接收到的是'0'
// 这一步也可以不做,因为data初始值为0
data |= 0x00;
}
#endif // endif else{}
IIC_SCL_GPIO_RST(); // 钳住SCL
IIC_DELAY(); // IIC延时,等待电平变化完成
IIC_DELAY(); // IIC延时,等待电平变化完成
}
if (ack == ACK)
{
IIC_SendACK();
}
else
{
IIC_SendNACK();
}
return data; // 返回读到的1字节数据
}
sht2x.c文件内容:
#include "my_config.h"
#include "sht2x.h"
#include "iic.h"
#include "delay.h"
#include "debug.h"
#include <stdio.h>
#ifndef SHT2x_ADDRESS
#define SHT2x_ADDRESS 0x40
#endif
#ifndef SHT2x_WRITE_ADDRESS
#define SHT2x_WRITE_ADDRESS ((SHT2x_ADDRESS << 1) | 0x00)
#endif
#ifndef SHT2x_READ_ADDRESS
#define SHT2x_READ_ADDRESS ((SHT2x_ADDRESS << 1) | 0x01)
#endif
// 温度监测命令
#ifndef SHT2x_T_CMD
#define SHT2x_T_CMD 0xF3
#endif
// 湿度监测命令
#ifndef SHT2x_RH_CMD
#define SHT2x_RH_CMD 0xF5
#endif
#ifndef SHT2x_IIC_INIT
#define SHT2x_IIC_INIT() IIC_Init()
#endif
#ifndef SHT2x_IIC_START
#define SHT2x_IIC_START() IIC_Start()
#endif
#ifndef SHT2x_IIC_STOP
#define SHT2x_IIC_STOP() IIC_Stop()
#endif
#ifndef SHT2x_IIC_SENDBYTE
#define SHT2x_IIC_SENDBYTE(N) IIC_SendByte(N)
#endif
#ifndef SHT2x_IIC_READBYTE
#define SHT2x_IIC_READBYTE(N) IIC_ReadByte(N)
#endif
#ifndef SHT2x_IIC_WAIT_ACK
#define SHT2x_IIC_WAIT_ACK() IIC_WaitACK()
#endif
#ifndef SHT2x_DELAY_US
#define SHT2x_DELAY_US(N) delay_us(N)
#endif
/**
* @brief SHT2x初始化
*/
void SHT2x_Init(void)
{
SHT2x_IIC_INIT();
}
/**
* @brief SHT2x读取数据
*/
uint16_t SHT2x_ReadData(uint8_t cmd)
{
uint16_t data = 0; // 2个字节的容器,存放温度和湿度信息
// 1. IIC发送启动信号
SHT2x_IIC_START();
// 2. IIC发送SHT2x地址+写操作
SHT2x_IIC_SENDBYTE(SHT2x_WRITE_ADDRESS);
// 3. 等待SHT2x回复ACK信号
if (SHT2x_IIC_WAIT_ACK() != ACK)
{
SHT2x_IIC_STOP();
return 0;
}
// 4. IIc发送监测命令(监测温度或湿度)
SHT2x_IIC_SENDBYTE(cmd);
// 5. 等待SHT2x回复ACK信号
if (SHT2x_IIC_WAIT_ACK() != ACK)
{
SHT2x_IIC_STOP();
return 0;
}
// 6. 等待至少18.5us(如20us)
SHT2x_DELAY_US(20);
// 7. IIC发送停止信号
SHT2x_IIC_STOP();
// 8. IIC发送启动信号
SHT2x_IIC_START();
// 9. IIC发送SHT2x的地址+读操作
SHT2x_IIC_SENDBYTE(SHT2x_READ_ADDRESS);
// 10. 等待SHT2x回复ACK信号(一直等待,直到SHT2x回复)
while (1)
{
// 发送启动信号
SHT2x_IIC_START();
// SHT2x地址+读操作
SHT2x_IIC_SENDBYTE(SHT2x_READ_ADDRESS);
// 等待,直到ACK到来
if (SHT2x_IIC_WAIT_ACK() != ACK)
{
SHT2x_IIC_STOP();
}
else
{
break;
}
}
// 11. 读取SHT2x发送的1个字节数据,回复ACK
data |= SHT2x_IIC_READBYTE(ACK);
data <<= 8;
// 12. 读取SHT2x发送的第2个字节数据,回复NACK
data |= SHT2x_IIC_READBYTE(NACK);
// 13. IIC发送停止信号
SHT2x_IIC_STOP();
return data;
}
/**
* @brief SHT2x获取温度
*/
float SHT2x_GetTemp(uint16_t data)
{
if (data == 0)
{
printf("SHT2x GET TEMP ERR\r\n");
}
return (-46.85 + 175.72 * data / 65536);
}
/**
* @brief SHT2x获取湿度
*/
int SHT2x_GetHumi(uint16_t data)
{
if (data == 0)
{
printf("SHT2x GET HUMI ERR\r\n");
}
return (-6 + 125 * data / 65536);
}