/* ============================================================================= * sht30.c - SHT30 I2C 드라이버 구현 (sht30.h) * * RPi sht30_monitor.py read_sht30() 대응: * - 0x2C 0x06 명령 전송(고반복성, clock stretching 비활성) * - 측정 완료까지 대기(고반복성 ~15ms, 마진 포함 20ms) * - 6바이트 read (T_msb,T_lsb,T_crc, RH_msb,RH_lsb,RH_crc) * - CRC 검증/물리값 변환은 이식성 있는 sht30_convert.c(sht30_parse)에 위임 * * 버스/핀: I2C1 (TODO(hw): PB6=SCL, PB7=SDA, AF4, Open-Drain, 100~400kHz) * F407 의 I2C1 SCL/SDA 기본 핀은 PB6/PB7. 보드에 따라 PB8/PB9(AF4)도 가능하니 * 배선에 맞게 핀/AF 만 조정하면 된다(아래 SHT30_I2C_* 매크로). * * 동시성: HAL_I2C 블로킹 API 사용. 호출은 reporter 태스크(SHT30 보드)에서만 * 직렬로 이뤄진다는 전제(단일 소유). 다중 태스크에서 공유한다면 호출측에서 * 뮤텍스로 감싸야 한다. * * 재시도: 단발 read 만 수행한다. 재시도/주기 정책은 호출측(reporter)이 담당. * ===========================================================================*/ #include "sht30.h" #include "app_config.h" /* APP_SHT30_I2C_ADDR */ #include "sht30_convert.h" /* sht30_parse */ #include "applog.h" /* LOGW/LOGE (진단 로그) */ #include "stm32f4xx_hal.h" /* CubeF4 HAL (third_party 에 vendored) */ #include /* ── 버스/핀 구성 (TODO(hw): 배선에 맞게 확인) ───────────────────────────── */ #define SHT30_I2C_INSTANCE I2C1 #define SHT30_I2C_CLK_ENABLE() __HAL_RCC_I2C1_CLK_ENABLE() #define SHT30_I2C_CLK_DISABLE() __HAL_RCC_I2C1_CLK_DISABLE() #define SHT30_I2C_FORCE_RESET() __HAL_RCC_I2C1_FORCE_RESET() #define SHT30_I2C_RELEASE_RESET() __HAL_RCC_I2C1_RELEASE_RESET() /* SCL = PB6, SDA = PB7 (I2C1, AF4). 둘 다 같은 포트(GPIOB)라는 전제. */ #define SHT30_GPIO_PORT GPIOB #define SHT30_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() #define SHT30_SCL_PIN GPIO_PIN_6 #define SHT30_SDA_PIN GPIO_PIN_7 #define SHT30_GPIO_AF GPIO_AF4_I2C1 /* 표준 모드 100kHz (SHT30 은 최대 1MHz 까지 가능하나 배선/풀업 여유 위해 보수적). * 필요 시 400000 으로 올릴 수 있다(Fast Mode). */ #ifndef SHT30_I2C_CLOCK_HZ #define SHT30_I2C_CLOCK_HZ 100000u #endif /* 측정 명령: 고반복성, clock stretching 비활성(0x2C06). * MSB=0x2C, LSB=0x06. */ #define SHT30_CMD_MSB 0x2Cu #define SHT30_CMD_LSB 0x06u /* 고반복성 측정 최대 시간 15ms. 마진 포함 20ms 대기. */ #define SHT30_MEAS_DELAY_MS 20u /* 단일 트랜잭션 타임아웃(블로킹 HAL). 측정 대기는 별도(HAL_Delay)로 처리. */ #define SHT30_I2C_TIMEOUT_MS 50u #define SHT30_FRAME_LEN 6u /* HAL 은 7-bit 주소를 상위 7비트에 정렬(좌측 1비트 시프트)하여 사용한다. */ #define SHT30_I2C_HAL_ADDR ((uint16_t)(APP_SHT30_I2C_ADDR << 1)) /* I2C 핸들. 본 모듈에서 소유/초기화한다(헤더 계약상 sht30_init 책임). */ static I2C_HandleTypeDef s_i2c; static int s_inited = 0; /* ----------------------------------------------------------------------------- * MSP: GPIO/클럭 초기화. HAL_I2C_Init() 내부에서 콜백된다. * 약한 심볼(__weak)을 오버라이드하므로 다른 I2C 인스턴스가 있어도 안전하게 * 인스턴스별로 분기한다. * ---------------------------------------------------------------------------*/ void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c) { if (hi2c->Instance != SHT30_I2C_INSTANCE) { return; /* 다른 I2C 는 해당 모듈이 처리 */ } GPIO_InitTypeDef gpio = {0}; SHT30_GPIO_CLK_ENABLE(); SHT30_I2C_CLK_ENABLE(); /* SCL/SDA: AF, Open-Drain, 풀업은 외부 저항 사용 권장. * TODO(hw): 외부 풀업이 없다면 GPIO_PULLUP 으로 바꾸되, I2C 신뢰성을 위해 * 보드에 4.7kΩ 외부 풀업 장착을 권장한다. */ gpio.Pin = SHT30_SCL_PIN | SHT30_SDA_PIN; gpio.Mode = GPIO_MODE_AF_OD; gpio.Pull = GPIO_NOPULL; gpio.Speed = GPIO_SPEED_FREQ_HIGH; gpio.Alternate = SHT30_GPIO_AF; HAL_GPIO_Init(SHT30_GPIO_PORT, &gpio); } void HAL_I2C_MspDeInit(I2C_HandleTypeDef *hi2c) { if (hi2c->Instance != SHT30_I2C_INSTANCE) { return; } SHT30_I2C_FORCE_RESET(); SHT30_I2C_RELEASE_RESET(); SHT30_I2C_CLK_DISABLE(); HAL_GPIO_DeInit(SHT30_GPIO_PORT, SHT30_SCL_PIN | SHT30_SDA_PIN); } /* ----------------------------------------------------------------------------- * sht30_init: I2C1 주변장치 초기화. 0 성공, 음수 실패. * ---------------------------------------------------------------------------*/ int sht30_init(void) { if (s_inited) { return 0; /* 멱등 */ } s_i2c.Instance = SHT30_I2C_INSTANCE; s_i2c.Init.ClockSpeed = SHT30_I2C_CLOCK_HZ; s_i2c.Init.DutyCycle = I2C_DUTYCYCLE_2; /* 표준/패스트 공통 안전값 */ s_i2c.Init.OwnAddress1 = 0; /* 마스터: 자기 주소 불필요 */ s_i2c.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; s_i2c.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; s_i2c.Init.OwnAddress2 = 0; s_i2c.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; s_i2c.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&s_i2c) != HAL_OK) { LOGE("sht30: HAL_I2C_Init failed"); return -1; } s_inited = 1; return 0; } /* ----------------------------------------------------------------------------- * sht30_measure: 단발 측정. * 반환: 0 성공, -1 I2C 통신 오류, -2 CRC 오류. * ---------------------------------------------------------------------------*/ int sht30_measure(double *temp_c, double *rh) { if (!s_inited) { /* 초기화 안 된 상태를 통신 오류로 취급(호출측이 재시도/복구). */ LOGW("sht30: measure before init"); return -1; } /* 1) 측정 명령 전송: 0x2C 0x06 (high repeatability, no clock stretch) */ uint8_t cmd[2] = { SHT30_CMD_MSB, SHT30_CMD_LSB }; HAL_StatusTypeDef st = HAL_I2C_Master_Transmit( &s_i2c, SHT30_I2C_HAL_ADDR, cmd, sizeof(cmd), SHT30_I2C_TIMEOUT_MS); if (st != HAL_OK) { LOGW("sht30: cmd tx fail (hal=%d)", (int)st); return -1; } /* 2) 측정 완료 대기(~15ms + 마진). clock stretching 을 끈 모드이므로 * 호스트가 명시적으로 대기해야 한다. HAL 타임베이스(TIM)에 의존하므로 * 스케줄러 유무와 무관하게 동작한다. */ HAL_Delay(SHT30_MEAS_DELAY_MS); /* 3) 6바이트 read: T_msb,T_lsb,T_crc, RH_msb,RH_lsb,RH_crc */ uint8_t frame[SHT30_FRAME_LEN] = {0}; st = HAL_I2C_Master_Receive( &s_i2c, SHT30_I2C_HAL_ADDR, frame, SHT30_FRAME_LEN, SHT30_I2C_TIMEOUT_MS); if (st != HAL_OK) { LOGW("sht30: data rx fail (hal=%d)", (int)st); return -1; } /* 4) CRC 검증 + 물리값 변환(이식성 코어). -1/-2 -> CRC 오류(-2)로 매핑. */ int rc = sht30_parse(frame, temp_c, rh); if (rc != 0) { LOGW("sht30: CRC error (parse=%d)", rc); return -2; } return 0; }