星允派F103SHT30软件i2c·

星允派(NEBULA PI) [四] 软件I2C驱动SHT30

御坂10032号

御坂10032号

42 0

简介

在最初拿到这块开发板时,我想用它驱动一些 I2C 传感器,但查看原理图后发现板上的硬件 I2C 接口已被占用,因此无法直接使用。这在使用体验上不够友好。在开发板设计阶段,建议将硬件 I2C 的对应引脚预留出来,以便用户扩展 I2C 设备。所以在最开始的时候我便放置起来的。 后来通过查看官方的教程手册进行进行确认。发现驱动OLED的代码也是通过软件I2C实现的。这样便是真的确定了没有I2C Pin的引出。 因此对于I2C的驱动只能使用软件的方式。本文章将会介绍如何使用软件I2C来驱动SHT30 传感器。

软件I2C的驱动库并不是我编写的。我参考了SoftI2C_HAL_Lib 库进行实现。

我主要的修改点是修改了这个库的配置文件,对于延时的控制我使用了DWT (Data Watchpoint and Trace)进行实现的。其核心代码如下所示。

// delay_us.c
#include "delay_us.h"

static uint32_t us_ticks; // 每微秒计数

void Delay_us_Init(void)
{
    // 计算每微秒的计数,系统时钟 HCLK
    us_ticks = HAL_RCC_GetHCLKFreq() / 1000000;
}

// 微秒延时
void Delay_us(uint32_t us)
{
    uint32_t start = DWT->CYCCNT;
    uint32_t delay_ticks = us * us_ticks;
    while ((DWT->CYCCNT - start) < delay_ticks);
}

// DWT 初始化函数(只需调用一次)
void Delay_us_DWT_Init(void)
{
    if (!(CoreDebug->DEMCR & CoreDebug_DEMCR_TRCENA_Msk))
        CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;

    DWT->CYCCNT = 0;
    DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; // 启用计数器
}

然后在它原本软件I2C延时的时候便可以使用DWT进行计数。

/**
 * @file    softi2c_conf.h
 * @author  Myth
 * @version 0.1
 * @date    2021.10.12
 * @brief   STM32 SoftI2C Library Config File
 */

#ifndef __SOFTI2C_CONF_H
#define __SOFTI2C_CONF_H

// Set your HAL Library here.

#include "stm32f1xx_hal.h"
#include "stm32f1xx_hal_tim.h"

// Set your owm Delay_us function here.

#include "delay_us.h"

#define SoftI2C_Delay_us(__time__) Delay_us(__time__)

#endif

所以这样就可以实现高精度的延时控制来避免了I2C通讯失败。 我这里是没有注意,最初的时候使用的HAL的delay进行实现的,导致I2C通讯失败

使用的时候只需要在主程序中对方法进行初始化即可。

Delay_us_DWT_Init();  // 初始化 DWT 计数器
Delay_us_Init();

驱动SHT30

SHT30的驱动起来比较简单,具体的命令可以参考数据手册通讯的部分,只需要注意一点即找到对应的I2C地址即可。不同的SHT30可能有不同的地址,可以使用I2C scanner进行确认。

void I2C_Scan(void)
{
    printf("Scanning I2C bus...\r\n");
    uint8_t ack;
    uint8_t found = 0;

    for (uint8_t addr = 0x03; addr <= 0x77; addr++)
    {
        printf("Trying address 0x%02X ... ", addr);

        SoftI2C_Start(&i2c);
        SoftI2C_WriteByte(&i2c, (addr << 1) | 0); // 写模式
        ack = SoftI2C_WaitAck(&i2c);
        SoftI2C_Stop(&i2c);

        if (ack == 0)
        {
            printf("Device found (ACK=0)\r\n");
            found = 1;
        }
        else
        {
            printf("No device (ACK=1)\r\n");
        }
    }

    if (!found)
    {
        printf("No I2C devices detected!\r\n");
    }
    else
    {
        printf("I2C scan done.\r\n");
    }
}

SHT30.H

/**
 * @file    sht30.h
 * @brief   STM32 SoftI2C Library for SHT30 Sensor
 */

#ifndef __SHT30_H
#define __SHT30_H

#include "softi2c.h"
#include "stdint.h"

// SHT30 默认 I2C 地址
#define SHT30_ADDR 0x44

// 单次高精度测量命令 (高重复率)
#define SHT30_CMD_MEAS_HIGHREP 0x2400

/**
 * @brief   检测 SHT30 是否存在
 * @retval  1: 存在 0: 不存在
 */
uint8_t SHT30_Check(void);

/**
 * @brief   向 SHT30 发送测量命令
 */
void SHT30_StartMeasure(SoftI2C_TypeDef *i2c);

/**
 * @brief   读取 SHT30 测量值
 * @param   temperature: 温度值 (℃)
 * @param   humidity: 湿度值 (%RH)
 * @retval  0: 成功 1: 读取失败/CRC错误
 */
uint8_t SHT30_Read(SoftI2C_TypeDef *i2c, float *temperature, float *humidity);

#endif

SHT30.C

/**
 * @file    sht30.c
 * @brief   STM32 SoftI2C Library for SHT30 with debug
 */

#include "sht30.h"
#include "stdio.h"

extern SoftI2C_TypeDef i2c;

#define SHT30_ADDR 0x44   // 默认 I2C 地址

// 单次高精度测量命令
#define SHT30_CMD_MEAS_HIGHREP 0x2400

// CRC 校验
static uint8_t SHT30_CalcCRC(uint8_t *data, uint8_t len)
{
    uint8_t crc = 0xFF;
    for (uint8_t i = 0; i < len; i++)
    {
        crc ^= data[i];
        for (uint8_t j = 0; j < 8; j++)
        {
            if (crc & 0x80)
                crc = (crc << 1) ^ 0x31; // 多项式 0x31
            else
                crc <<= 1;
        }
    }
    return crc;
}

// -------------------- 检测 SHT30 --------------------
uint8_t SHT30_Check()
{
    uint8_t ack;

    SoftI2C_Start(&i2c);
    SoftI2C_WriteByte(&i2c, (SHT30_ADDR << 1) | 0);  // 写模式
    ack = SoftI2C_WaitAck(&i2c);
    SoftI2C_Stop(&i2c);

    if (ack == 0) {
        printf("SHT30 ACK detected!\r\n");
        return 1; // 应答
    } else {
        printf("SHT30 not responding!\r\n");
        return 0;
    }
}

// -------------------- 发送测量命令 --------------------
void SHT30_StartMeasure(SoftI2C_TypeDef *i2c)
{
    SoftI2C_Start(i2c);
    SoftI2C_WriteByte(i2c, (SHT30_ADDR << 1) | 0);
    if (SoftI2C_WaitAck(i2c)) {
        SoftI2C_Stop(i2c);
        printf("SHT30 StartMeasure ACK failed!\r\n");
        return;
    }
    SoftI2C_WriteByte(i2c, (SHT30_CMD_MEAS_HIGHREP >> 8) & 0xFF);
    SoftI2C_WaitAck(i2c);
    SoftI2C_WriteByte(i2c, SHT30_CMD_MEAS_HIGHREP & 0xFF);
    SoftI2C_WaitAck(i2c);
    SoftI2C_Stop(i2c);

    printf("SHT30 Measurement command sent: 0x%02X 0x%02X\r\n",
           (SHT30_CMD_MEAS_HIGHREP >> 8) & 0xFF,
           SHT30_CMD_MEAS_HIGHREP & 0xFF);
}

// -------------------- 读取测量数据 --------------------
uint8_t SHT30_Read(SoftI2C_TypeDef *i2c, float *temperature, float *humidity)
{
    uint8_t buf[6] = {0};

    // 读取 6 字节
    SoftI2C_Start(i2c);
    SoftI2C_WriteByte(i2c, (SHT30_ADDR << 1) | 1); // 读模式
    SoftI2C_WaitAck(i2c);
    for (uint8_t i = 0; i < 6; i++) {
        buf[i] = SoftI2C_ReadByte(i2c);
        if (i != 5) SoftI2C_Ack(i2c);
        else SoftI2C_NAck(i2c);
    }
    SoftI2C_Stop(i2c);

    printf("SHT30 ReadBytes: ");
    for (uint8_t i = 0; i < 6; i++) printf("%02X ", buf[i]);
    printf("\r\n");

    // CRC 校验
    if (SHT30_CalcCRC(buf,2) != buf[2] || SHT30_CalcCRC(buf+3,2) != buf[5]) {
        printf("SHT30 CRC error!\r\n");
        return 1;
    }

    // 温度和湿度计算
    uint16_t raw_temp = (buf[0] << 8) | buf[1];
    uint16_t raw_hum  = (buf[3] << 8) | buf[4];

    *temperature = -45 + 175 * ((float)raw_temp / 65535.0f);
    *humidity    = 100 * ((float)raw_hum / 65535.0f);

    printf("SHT30 Converted: Temp %.2f C  Humidity %.2f %%\r\n", *temperature, *humidity);

    return 0;
}

main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
#include "softi2c.h"
#include "softi2c_conf.h"
#include "sht30.h"
#include "delay_us.h"

SoftI2C_TypeDef i2c;

void SystemClock_Config(void);

int __io_putchar(int ch) {
    HAL_UART_Transmit(&huart1, (uint8_t *) &ch, 1, HAL_MAX_DELAY);
    return ch;
}

void I2C_Scan(void)
{
    printf("Scanning I2C bus...\r\n");
    uint8_t ack;
    uint8_t found = 0;

    for (uint8_t addr = 0x03; addr <= 0x77; addr++)
    {
        printf("Trying address 0x%02X ... ", addr);

        SoftI2C_Start(&i2c);
        SoftI2C_WriteByte(&i2c, (addr << 1) | 0); // 写模式
        ack = SoftI2C_WaitAck(&i2c);
        SoftI2C_Stop(&i2c);

        if (ack == 0)
        {
            printf("Device found (ACK=0)\r\n");
            found = 1;
        }
        else
        {
            printf("No device (ACK=1)\r\n");
        }
    }

    if (!found)
    {
        printf("No I2C devices detected!\r\n");
    }
    else
    {
        printf("I2C scan done.\r\n");
    }
}



int main(void)
{
    float temperature, humidity;

    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    Delay_us_DWT_Init();  // 初始化 DWT 计数器
    Delay_us_Init();
    // 初始化 SoftI2C
    i2c.SCL_GPIO = GPIOB;
    i2c.SCL_Pin  = GPIO_PIN_4;
    i2c.SDA_GPIO = GPIOD;
    i2c.SDA_Pin  = GPIO_PIN_3;
    i2c.Delay_Time = 5; // 微秒

    if(SoftI2C_Init(&i2c) != HAL_OK)
    {
        printf("SoftI2C Init Failed!\r\n");
        while(1);
    }

    HAL_Delay(40); // 上电稳定

    // 检测 SHT30
    if(!SHT30_Check())
    {
        printf("SHT30 not detected!\r\n");
        while(1);
    }

    while(1)
    {
        // 启动测量
        SHT30_StartMeasure(&i2c);
        HAL_Delay(15); // 高精度测量等待

        // 读取数据
        if(SHT30_Read(&i2c, &temperature, &humidity) == 0)
        {
            printf("Temperature: %.2f C, Humidity: %.2f %%\r\n", temperature, humidity);
        }
        else
        {
            printf("SHT30 Read Failed!\r\n");
        }

        HAL_Delay(1000); // 每 1 秒读取一次
    }
}


void SystemClock_Config(void) {
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

    /** Initializes the RCC Oscillators according to the specified parameters
    * in the RCC_OscInitTypeDef structure.
    */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
        Error_Handler();
    }

    /** Initializes the CPU, AHB and APB buses clocks
    */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
                                  | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) {
        Error_Handler();
    }
    PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
    PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) {
        Error_Handler();
    }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void) {
    /* USER CODE BEGIN Error_Handler_Debug */
    /* User can add his own implementation to report the HAL error return state */
    __disable_irq();
    while (1) {
    }
    /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

然后将程序烧录到单片机中,通过串口助手既可以看到完整的SHT30数据。

所属系列

从当前文章继续阅读它所在合集中的前后内容。

星允派公测报告合集 第 4 / 4 篇
查看合集

星允派公测报告合集

上一篇
星允派(NEBULA PI) [三] ADC NTC测温
当前已是该系列最后一篇

相关文章

优先推荐同专题、同标签和同作者内容,补足热门文章。

评论 0

登录 后参与评论

评论

成为第一个评论的人