1:使用STM32CUBEMX配置IIC模拟总线
#define SHT30_SCL_PORT GPIOC
#define SHT30_SCL_PIN GPIO_PIN_6 // GPIO_MODE_OUTPUT_PP
#define SHT30_SDA_PORT GPIOB
#define SHT30_SDA_PIN GPIO_PIN_15 // GPIO_MODE_OUTPUT_OD
SDA数据脚配置为OD输出,CLK脚配置为PP输出。
2:SHT40命令表
启动读温湿度的命令是0xFD。
3:奉上代码,别看了,直接拿去用,sht30上修改而来,宏定义用的还是SHT30的部分,只有一处区别,看SHT30.c文件中的注释。
sht30.h文件
#ifndef __SHT30_H__
#define __SHT30_H__
#include <stdint.h>
#include "main.h"
#include "stm32f1xx_hal.h"
#define I2C_WRITE (0)
#define I2C_READ (1)
#define SHT30_INQUIRE_CNT (500)
#define SHT30_ADDR (0x44)
#define SHT30_SCL_PORT GPIOC
#define SHT30_SCL_PIN GPIO_PIN_6 // GPIO_MODE_OUTPUT_PP
#define SHT30_SDA_PORT GPIOB
#define SHT30_SDA_PIN GPIO_PIN_15 // GPIO_MODE_OUTPUT_OD
void i2c_init(void);
int read_sht30_data(float *temp, float *humi);
#endif /* sht30.h */
sht30.c文件
/**
* @brief SHT30温湿度传感器相关,使用模拟IIC进行数据的读取
*/
#include <stdint.h>
#include "sht30.h"
#define SHT40
//#define SHT30
/**
* @brief crc8校验函数,多项式为 x^8 + x^5 + x^4 + 1
* @param data 要校验的数据
* @param len 要校验的数据的字节数
* @retval 校验结果
* @note 该校验适合SHT3温湿度传感器的数据校验
*/
static uint8_t Crc8(uint8_t *data, int len)
{
const uint8_t POLYNOMIAL = 0x31;
uint8_t crc = 0xFF;
int i, j;
for (i = 0; i < len; ++i)
{
crc ^= *data++;
for (j = 0; j < 8; ++j)
{
crc = (crc & 0x80) ? (crc << 1) ^ POLYNOMIAL : (crc << 1);
}
}
return crc;
}
/**
* @brief i2c的延时函数,延时时间要 > 4us
* @param 无
* @retval 无
* @note 可用逻辑分析仪测量I2C通讯时的频率工作条件
*/
static void i2c_delay(void)
{
int i;
for (i = 0; i < 10; i++)
{
asm("nop"); // 空语句
}
}
/**
* @brief i2c SCL 拉高
* @param 无
* @retval 无
*/
static void i2c_scl_high(void)
{
HAL_GPIO_WritePin(SHT30_SCL_PORT, SHT30_SCL_PIN, GPIO_PIN_SET);
}
/**
* @brief i2c SCL 拉低
* @param 无
* @retval 无
*/
static void i2c_scl_low(void)
{
HAL_GPIO_WritePin(SHT30_SCL_PORT, SHT30_SCL_PIN, GPIO_PIN_RESET);
}
/**
* @brief i2c SDA 拉高
* @param 无
* @retval 无
*/
static void i2c_sda_high(void)
{
HAL_GPIO_WritePin(SHT30_SDA_PORT, SHT30_SDA_PIN, GPIO_PIN_SET);
}
/**
* @brief i2c SDA 拉低
* @param 无
* @retval 无
*/
static void i2c_sda_low(void)
{
HAL_GPIO_WritePin(SHT30_SDA_PORT, SHT30_SDA_PIN, GPIO_PIN_RESET);
}
/**
* @brief 读取SDA线上的值
* @param 无
* @retval 读取到的值,0或1
*/
static uint8_t i2c_read_sda_value(void)
{
return HAL_GPIO_ReadPin(SHT30_SDA_PORT, SHT30_SDA_PIN);
;
}
/**
* @brief i2c的起始信号
* @param 无
* @retval 无
*/
static void i2c_start(void)
{
/* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */
i2c_scl_high();
i2c_sda_high();
i2c_delay();
i2c_sda_low();
i2c_delay();
i2c_scl_low();
i2c_delay();
}
/**
* @brief i2c的停止信号
* @param 无
* @retval 无
*/
static void i2c_stop(void)
{
/* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
i2c_sda_low();
i2c_scl_high();
i2c_delay();
i2c_sda_high();
}
/**
* @brief i2c 发送一个字节
* @param 要发送的数据
* @retval 无
*/
void i2c_write_byte(uint8_t data)
{
uint8_t i;
i2c_scl_low();
i2c_delay();
/* 先发送字节的高位 bit7 */
for (i = 0; i < 8; i++)
{
if (data & 0x80)
{
i2c_sda_high();
}
else
{
i2c_sda_low();
}
i2c_delay();
i2c_scl_high();
i2c_delay();
i2c_scl_low();
if (i == 7)
{
i2c_sda_high(); // 释放总线
}
data <<= 1; // 左移一个bit
i2c_delay();
}
}
/**
* @brief i2c 读取一个字节
* @param 无
* @retval 无
*/
uint8_t i2c_read_byte(void)
{
uint8_t i;
uint8_t value;
/* 读到第1个bit为数据的bit7 */
value = 0;
for (i = 0; i < 8; i++)
{
value <<= 1;
i2c_scl_high();
i2c_delay();
if (i2c_read_sda_value() != 0)
{
value++;
}
i2c_scl_low();
i2c_delay();
}
return value;
}
/**
* @brief CPU等待从设备的应答信号
* @param 无
* @retval 0表示正确应答,1表示无应答
*/
static uint8_t i2c_wait_ack(void)
{
uint8_t ret;
i2c_sda_high(); /* CPU释放SDA总线 */
i2c_delay();
i2c_scl_high(); /* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
i2c_delay();
if (i2c_read_sda_value() == 1) /* CPU读取SDA口线状态 */
{
ret = 1;
}
else
{
ret = 0;
}
i2c_scl_low();
i2c_delay();
return ret;
}
/**
* @brief CPU产生一个ACK信号
* @param 无
* @retval 无
*/
static void i2c_ack(void)
{
i2c_sda_low(); /* CPU驱动SDA = 0 */
i2c_delay();
i2c_scl_high(); /* CPU产生1个时钟 */
i2c_delay();
i2c_scl_low();
i2c_delay();
i2c_sda_high(); /* CPU释放SDA总线 */
}
/**
* @brief CPU产生一个 NACK 信号
* @param 无
* @retval 无
*/
static void i2c_Nack(void)
{
i2c_sda_high(); /* CPU驱动SDA = 1 */
i2c_delay();
i2c_scl_high(); /* CPU产生1个时钟 */
i2c_delay();
i2c_scl_low();
i2c_delay();
}
/**
* @brief i2c 发送多个字节
* @param 要发送的从机的地址
* @param 指向要写入的的数据的指针
* @param 要写入的数据的长度
* @retval 成功返回0,失败返回-1
*/
int i2c_write_bytes(uint8_t addr, uint8_t *write_buff, uint8_t buff_size)
{
uint16_t i, j;
// 1. 发送一个停止信号
i2c_stop();
/* 通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms
CLK频率为200KHz时,查询次数为30次左右
*/
for (i = 0; i < SHT30_INQUIRE_CNT; i++)
{
// 2. 发起I2C总线启动信号
i2c_start();
// 3. 发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读
i2c_write_byte(addr << 1 | I2C_WRITE);
// 4. 发送一个时钟,判断器件是否正确应答
if (i2c_wait_ack() == 0)
{
break;
}
}
if (i == SHT30_INQUIRE_CNT)
{
goto cmd_fail; // 写超时
}
for (i = 0; i < buff_size; ++i)
{
for (j = 0; j < SHT30_INQUIRE_CNT; j++)
{
// 5. 发送数据
i2c_write_byte(write_buff[i]);
// 6. 等待ACK
if (i2c_wait_ack() == 0)
{
break;
}
}
if (j == SHT30_INQUIRE_CNT)
{
goto cmd_fail; /* 从器件无应答 */
}
}
// 7. 发送停止信号
i2c_stop();
return 0;
cmd_fail:
i2c_stop(); // 发送写超时
return -1;
}
/**
* @brief i2c 发送多个字节
* @param 要发送的从机的地址
* @param 指向要读取的数据的指针
* @param 要读取的数据的长度
* @retval 成功返回0,失败返回-1
*/
int i2c_read_bytes(uint8_t addr, uint8_t *read_buff, uint8_t buff_size)
{
uint16_t i;
for (i = 0; i < SHT30_INQUIRE_CNT; i++)
{
/* 1. 发起I2C总线启动信号 */
i2c_start();
/* 2. 读的话,先写入从机地址 */
i2c_write_byte(addr << 1 | I2C_WRITE);
/* 3. 发送一个时钟,判断器件是否正确应答 */
if (i2c_wait_ack() == 0)
{
break;
}
}
if (i == SHT30_INQUIRE_CNT)
{
goto cmd_fail; // 写超时
}
for (i = 0; i < SHT30_INQUIRE_CNT; i++)
{
/* 4. 重新启动I2C总线。前面的代码的目的是传送地址,下面开始读取数据 */
i2c_start();
/* 5. 发送读控制 */
i2c_write_byte(addr << 1 | I2C_READ);
/* 6. 发送一个时钟,判断器件是否正确应答 */
if (i2c_wait_ack() == 0)
{
break;
}
}
if (i == SHT30_INQUIRE_CNT)
{
goto cmd_fail; // 写超时
}
/* 7. 循环读取数据 */
for (i = 0; i < buff_size; i++)
{
read_buff[i] = i2c_read_byte(); /* 读1个字节 */
/* 每读完1个字节后,需要发送Ack, 最后一个字节不需要Ack,发Nack */
if (i != buff_size - 1)
{
i2c_ack(); // 中间字节读完后,CPU产生ACK信号(驱动SDA = 0)
}
else
{
i2c_Nack(); // 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1)
}
}
/* 8. 发送I2C总线停止信号 */
i2c_stop();
return 0; /* 执行成功 */
cmd_fail:
i2c_stop(); // 发送写超时
return -1;
}
/**
* @brief I2C初始化
* @param 无
* @retval 无
*/
void i2c_init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = SHT30_SDA_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(SHT30_SDA_PORT, &GPIO_InitStruct);
i2c_stop();
}
/**
* @brief 读取sht30温湿度传感器的值
* @param 指向存储温度值的指针
* @param 指向存储湿度值的指针
* @retval 成功返回0,失败返回-1
*/
int read_sht30_data(float *temp, float *humi)
{
#ifdef SHT40
uint8_t writeData[2] = {0xFD,0x06};//{0x2C, 0x06};//SHT40是发送0xFD,SHT30是发送0x2C 0x06
#endif
#ifdef SHT30
uint8_t writeData[2] = {0x2C, 0x06};//SHT40是发送0xFD,SHT30是发送0x2C 0x06
#endif
uint8_t readData[6] = {0};
uint8_t retryCount =0 ;
do
{
#ifdef SHT40
if (i2c_write_bytes(SHT30_ADDR, writeData, 1) == -1)
{
return -1;
}
#endif
#ifdef SHT30
if (i2c_write_bytes(SHT30_ADDR, writeData, 2) == -1)
{
return -1;
}
#endif
HAL_Delay(20);
if (i2c_read_bytes(SHT30_ADDR, readData, 6) == -1)
{
return -1;
}
retryCount++;
if(retryCount>10)
{
return -1;
}
} while (Crc8(&readData[0], 2) != readData[2] || Crc8(&readData[3], 2) != readData[5]);
*temp = (1.0 * 175 * (readData[0] * 256 + readData[1])) / 65535.0 - 45;
#ifdef SHT40
*humi = (1.0 * 125 * (readData[3] * 256 + readData[4])) / 65535.0 - 6.0;
#endif
#ifdef SHT30
*humi = (1.0 * 100 * (readData[3] * 256 + readData[4])) / 65535.0;
#endif
return 0;
}
4.初始化和调用程序。
void ReadSHT30()
{
i2c_init();
read_sht30_data(&temperature, &humidity);
zhanshiban_Equip_sensor.Humidity = humidity + zhanshibanPar.Humidity_Check - 3.9;
if (zhanshiban_Equip_sensor.Humidity < 0)
{
zhanshiban_Equip_sensor.Humidity = 0.0;
}
zhanshiban_Equip_sensor.Temerature = temperature + zhanshibanPar.Temperature_Check;
}
5.到此结束。
你读到温湿度值了吗?