STM32CUBEMX 模拟IIC总线读SHT40温湿度传感器

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.到此结束。

你读到温湿度值了吗?

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值