硬件I2C sht3x溫溼度傳感器 學習過程記錄

硬件I2C sht3x溫溼度傳感器


  • stm32的硬件I2C,非中斷方式
  • 代碼及stm32工程分享
  • 軟件方式實現傳送門
  • sht3x溫溼度傳感器的資料解析

背景

  • 在此之前,對sht3x這款傳感器的開發也是用的軟件I2C模擬驗證的,但項目中要求不能有阻塞行爲,故軟件I2C中的delay等操作就不能用了,那麼stm32有硬件I2C,爲什麼不能用呢。(雖然網上大量搜索表明stm32的I2C的坑很深。)

代碼及stm32工程分享


軟件I2C方式實現傳送門

軟件I2C sht3x溫溼度傳感器 學習過程記錄


sht3x溫溼度傳感器的資料解析


定義硬件I2C底層實現

sensirion_sw_i2c_implementation.c

#include <stm32f4xx.h>
#include <stdint.h>
#include "sensirion_arch_config.h"
#include "sensirion_i2c.h"

static I2C_InitTypeDef hi2c1;
static GPIO_InitTypeDef gpioinit;
static uint32_t I2Cx_TIMEOUT_UserCallback(char value);

#define I2Cx_FLAG_TIMEOUT			((uint32_t) 900)
#define I2Cx_LONG_TIMEOUT 			((uint32_t)(300 * I2Cx_FLAG_TIMEOUT))
#define WAIT_FOR_FLAG(flag, value, timeout, errorcode)	I2CTimeout = timeout;\
														while(I2C_GetFlagStatus(I2C1, flag) != value){\
														if((I2CTimeout--) == 0) return I2Cx_TIMEOUT_UserCallback(errorcode);\
														}\

#define CLEAR_ADDR_BIT			I2C_ReadRegister(I2C1, I2C_Register_SR1);\
								I2C_ReadRegister(I2C1, I2C_Register_SR2);\

static uint32_t I2Cx_TIMEOUT_UserCallback(char value)
{
	I2C_InitTypeDef hi2c1;
	I2C_GenerateSTOP(I2C1, ENABLE);
	I2C_SoftwareResetCmd(I2C1, ENABLE);
	I2C_SoftwareResetCmd(I2C1, DISABLE);
	I2C_DeInit(I2C1);
	hi2c1.I2C_ClockSpeed = 100000;
    hi2c1.I2C_Mode = I2C_Mode_I2C;
    hi2c1.I2C_DutyCycle = I2C_DutyCycle_2;
    hi2c1.I2C_OwnAddress1 = 0;
    hi2c1.I2C_Ack = I2C_Ack_Enable;
    hi2c1.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	I2C_Cmd(I2C1, ENABLE);
	I2C_Init(I2C1, &hi2c1);
	//Console::Instance()->printf("I2C1 Restarted. \n");
	return 1;
}
 
 s8 I2C_WriteBlock(uint8_t w_addr, const uint8_t* p_data, uint16_t len)
{
	uint16_t i = 0;
	__IO uint32_t I2CTimeout = I2Cx_LONG_TIMEOUT;
	WAIT_FOR_FLAG(I2C_FLAG_BUSY,RESET,I2Cx_LONG_TIMEOUT,1);	
	I2C_GenerateSTART(I2C1, ENABLE);
	WAIT_FOR_FLAG(I2C_FLAG_SB, SET, I2Cx_FLAG_TIMEOUT, 2);
	I2C_Send7bitAddress(I2C1,(w_addr << 1), I2C_Direction_Transmitter);
	WAIT_FOR_FLAG(I2C_FLAG_ADDR, SET, I2Cx_FLAG_TIMEOUT, 3);
	CLEAR_ADDR_BIT;
	while (i < len) 
	{
		I2C_SendData(I2C1, *p_data);
		WAIT_FOR_FLAG(I2C_FLAG_TXE, SET, I2Cx_FLAG_TIMEOUT, 4);
		i++; p_data++;
	}
	WAIT_FOR_FLAG(I2C_FLAG_BTF, SET, I2Cx_FLAG_TIMEOUT, 5);
	I2C_GenerateSTOP(I2C1, ENABLE);
	return 0;
}

s8 I2C_ReadBlock(uint8_t r_addr, uint8_t* p_data, uint16_t len)
{
	__IO uint32_t I2CTimeout = I2Cx_LONG_TIMEOUT;
	WAIT_FOR_FLAG(I2C_FLAG_BUSY,RESET,I2Cx_LONG_TIMEOUT,6);
	I2C_GenerateSTART(I2C1, ENABLE);
	WAIT_FOR_FLAG(I2C_FLAG_SB, SET, I2Cx_FLAG_TIMEOUT, 7);
	I2C_Send7bitAddress(I2C1, (r_addr << 1), I2C_Direction_Transmitter);
	WAIT_FOR_FLAG(I2C_FLAG_ADDR, SET, I2Cx_FLAG_TIMEOUT, 8);
	CLEAR_ADDR_BIT;
	WAIT_FOR_FLAG (I2C_FLAG_TXE, SET, I2Cx_FLAG_TIMEOUT, 9);
	I2C_SendData(I2C1,r_addr);
	WAIT_FOR_FLAG (I2C_FLAG_TXE, SET, I2Cx_FLAG_TIMEOUT, 10); 
	I2C_GenerateSTART(I2C1, ENABLE);
	WAIT_FOR_FLAG (I2C_FLAG_SB, SET, I2Cx_FLAG_TIMEOUT, 11);
	I2C_Send7bitAddress(I2C1,(r_addr << 1), I2C_Direction_Receiver);
	WAIT_FOR_FLAG (I2C_FLAG_ADDR, SET, I2Cx_FLAG_TIMEOUT, 12);
	/*規定死就2個長度*/
	while(len) 
	{
		if (len == 1)
		{
			I2C_AcknowledgeConfig(I2C1, DISABLE);
			I2C_GenerateSTOP(I2C1, ENABLE);
		}
		if (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED))
		{
			*p_data = I2C_ReceiveData(I2C1);
			p_data++;
			len--;
		}
	}
	I2C_AcknowledgeConfig(I2C1, ENABLE);
	return 0;
} 

void sensirion_i2c_init()
{
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
	gpioinit.GPIO_Mode = GPIO_Mode_OUT;
	gpioinit.GPIO_OType = GPIO_OType_PP;
	gpioinit.GPIO_PuPd = GPIO_PuPd_UP;
	gpioinit.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_7;
	gpioinit.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &gpioinit);
	GPIO_SetBits(GPIOB, GPIO_Pin_7);
	GPIO_SetBits(GPIOB, GPIO_Pin_8);
	I2C_SoftwareResetCmd(I2C1, ENABLE);

	gpioinit.GPIO_Mode = GPIO_Mode_AF;
	gpioinit.GPIO_OType = GPIO_OType_OD;
	gpioinit.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_7;
	gpioinit.GPIO_PuPd = GPIO_PuPd_NOPULL;
	gpioinit.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource8,GPIO_AF_I2C1);
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_I2C1);
	GPIO_Init(GPIOB, &gpioinit);
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
    hi2c1.I2C_ClockSpeed = 100000;
    hi2c1.I2C_Mode = I2C_Mode_I2C;
    hi2c1.I2C_DutyCycle = I2C_DutyCycle_16_9;
    hi2c1.I2C_OwnAddress1 = 0;
    hi2c1.I2C_Ack = I2C_Ack_Enable;
    hi2c1.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	I2C_Cmd(I2C1, ENABLE);
	I2C_Init(I2C1, &hi2c1);
    /* Enable the remapping of Pins 6/7 to 8/9 and the I2C clock before the initialization of the GPIO Pins*/
}

s8 sensirion_i2c_read(u8 address, u8 *data, u16 count)
{
    return (s8) I2C_ReadBlock(address, data, count);
}

s8 sensirion_i2c_write(u8 address, const u8 *data, u16 count)
{
   	return (s8) I2C_WriteBlock(address,data, count);
}

/**
 * Sleep for a given number of microseconds. The function should delay the
 * execution for at least the given time, but may also sleep longer.
 *
 * @param useconds the sleep time in microseconds
 */
void sensirion_sleep_usec(u32 useconds) {
// 沒用到
}

  • SDA、SCL引腳根據自己的實際情況配置,這裏複用了一個管腳,硬件設計上只剩下這種管腳了。
  • sensirion_sleep_usec()函數不需要用。
  • I2Cx_TIMEOUT_UserCallback()用來應對超時處理的,防止因爲I2C總線出現的各種問題卡死在while中,一出問題就重新初始化I2C進行恢復。
  • WAIT_FOR_FLAG()用宏定義封裝了超時處理及判斷I2C中的EV等事件的發生。見stm32官方手冊。
  • I2C_ReadBlock()讀函數,規定死了2的bytes的長度,有想用的要自己改下。
  • I2C_WriteBlock()寫函數,這個沒有規定長度,可以任意發送,w_addr是寫入設備的地址。

sht3x.c

#include "sensirion_arch_config.h"
#include "sensirion_common.h"
#include "sensirion_i2c.h"
#include "sht.h"
#include "sht_common.h"

/* all measurement commands return T (CRC) RH (CRC) */
#if USE_SENSIRION_CLOCK_STRETCHING
static const u8 CMD_MEASURE_HPM[]     = { 0x2C, 0x06 };
static const u8 CMD_MEASURE_LPM[]     = { 0x2C, 0x10 };
#else
static const u8 CMD_MEASURE_HPM[]     = { 0x24, 0x00 };
static const u8 CMD_MEASURE_LPM[]     = { 0x24, 0x16 };
#endif /* USE_SENSIRION_CLOCK_STRETCHING */
static const u8 CMD_READ_STATUS_REG[] = { 0xF3, 0x2D };
static const u8 COMMAND_SIZE = sizeof(CMD_MEASURE_HPM);
#ifdef SHT_ADDRESS
static const u8 SHT3X_ADDRESS = SHT_ADDRESS;
#else
static const u8 SHT3X_ADDRESS = 0x44;
#endif

static const u16 MEASUREMENT_DURATION_USEC = 15000;

static const u8 *cmd_measure = CMD_MEASURE_HPM;

s8 sht_measure_blocking_read(s32 *temperature, s32 *humidity)
{
    s8 ret = sht_measure();
    if (ret == STATUS_OK) {
        sensirion_sleep_usec(MEASUREMENT_DURATION_USEC);
        ret = sht_read(temperature, humidity);
    }
    return ret;
}

s8 sht_measure()
{
    return sensirion_i2c_write(SHT3X_ADDRESS, CMD_MEASURE_HPM, COMMAND_SIZE);
}

s8 sht_read(s32 *temperature, s32 *humidity)
{
    return sht_common_read_measurement(SHT3X_ADDRESS, temperature, humidity);
}

s8 sht_probe()
{
    u8 data[3];
    sensirion_i2c_init();
    s8 ret = sensirion_i2c_write(SHT3X_ADDRESS, CMD_READ_STATUS_REG, COMMAND_SIZE);
    if (ret)
        return ret;

    ret = sensirion_i2c_read(SHT3X_ADDRESS, data, sizeof(data));
    if (ret)
        return ret;

    ret = sensirion_common_check_crc(data, 2, data[2]);
    if (ret)
        return ret;
    return STATUS_OK;
}

s8 sht_disable_sleep(u8 disable_sleep)
{
    return STATUS_FAIL; /* sleep mode not supported */
}

void sht_enable_low_power_mode(u8 enable_low_power_mode)
{
    cmd_measure = enable_low_power_mode ? CMD_MEASURE_LPM : CMD_MEASURE_HPM;
}

//const char *sht_get_driver_version()
//{
//    return SHT_DRV_VERSION_STR;
//}

u8 sht_get_configured_sht_address()
{
    return SHT3X_ADDRESS;
}

  • const char *sht_get_driver_version()需要注掉,並沒有定義驅動版本,你也可以自己定義,否則會報錯。
  • 有很多封裝好的函數可以調用,睡眠,低功耗模式等等(其實鳥用也沒用,反正我沒用)
  • sht_probe()探針,用來檢測是不是正常的,主程序中會用到。
  • 還有一些開頭定義的命令數組及地址

mian.c

while (sht_probe() != STATUS_OK) {
	  LED_D2_D3(GPIO_Pin_6);
        /* printf("SHT sensor probing failed\n"); */
    }
	GPIO_SetBits(GPIOA, GPIO_Pin_6);
	LED_D2_D3(GPIO_Pin_7);
    /*printf("SHT sensor probing successful\n"); */

    while (1) {
        s32 temperature, humidity;
        /* Measure temperature and relative humidity and store into variables
         * temperature, humidity (each output multiplied by 1000).
         */
        s8 ret = sht_measure_blocking_read(&temperature, &humidity);
        if (ret == STATUS_OK) {
            /* printf("measured temperature: %0.2f degreeCelsius, "
                      "measured humidity: %0.2f percentRH\n",
                      temperature / 1000.0f,
                      humidity / 1000.0f); */
			LED_D2_D3(GPIO_Pin_6);
        } else {
            //printf("error reading measurement\n");
			LED_D2_D3(GPIO_Pin_7);
        }

        delay_ms(1000);
    }
}

void LED_D2_D3(uint32_t GPIO_Pin_x)
{
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
	GPIO_InitTypeDef GPIOInit;
	GPIOInit.GPIO_Mode = GPIO_Mode_OUT;
	GPIOInit.GPIO_OType = GPIO_OType_PP;
	GPIOInit.GPIO_Pin = GPIO_Pin_x;
	GPIOInit.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIOInit);
}

完整讀取的時間:16ms(傳感器自身測量佔了15ms見手冊)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章