feat(sht30): 벤더 예제(P4422-2) 기반 단발 측정 명령 0x2400 전환
라즈베리파이 SHT30 드라이버 측정 명령을 0x2C06(clock stretching ENABLED, 주석은 "비활성"이라 모순)에서 0x2400(clock stretching DISABLED)으로 교정. 드라이버의 write→대기→read 접근 패턴 및 Pi BCM I2C 클럭스트레치 버그와 정합하며, 벤더 예제 ArtronShop_SHT3x(P4422-2)의 무-스트레치 방식과 일치. CRC-8 검증은 유지(벤더 예제는 생략). 변환식·주소(0x44)·프레임 동일. STM32 펌웨어는 0x2C06 유지(HAL 스트레치 지원). - P4422-2/: 벤더 SHT3x 아두이노 예제(ArtronShop_SHT3x) 벤더링 - 서명/서버 프로토콜 무영향. 현장 하드웨어 실측 검증 필요(시뮬은 명령 무관)
This commit is contained in:
parent
79af8ef530
commit
9f0e048e49
8 changed files with 254 additions and 38 deletions
38
P4422-2/ArtronShop_SHT3x/README.md
Normal file
38
P4422-2/ArtronShop_SHT3x/README.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# ArtronShop_SHT3x
|
||||
|
||||
Arduino library of SHT30, SHT31, SHT35 Digital humidity and temperature sensor
|
||||
|
||||
## Examples
|
||||
|
||||
```C++
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <ArtronShop_SHT3x.h>
|
||||
|
||||
ArtronShop_SHT3x sht3x(0x44, &Wire); // ADDR: 0 => 0x44, ADDR: 1 => 0x45
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
Wire.begin();
|
||||
while (!sht3x.begin()) {
|
||||
Serial.println("SHT3x not found !");
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (sht3x.measure()) {
|
||||
Serial.print("Temperature: ");
|
||||
Serial.print(sht3x.temperature(), 1);
|
||||
Serial.print(" *C\tHumidity: ");
|
||||
Serial.print(sht3x.humidity(), 1);
|
||||
Serial.print(" %RH");
|
||||
Serial.println();
|
||||
} else {
|
||||
Serial.println("SHT3x read error");
|
||||
}
|
||||
delay(1000);
|
||||
}
|
||||
```
|
||||
|
||||
29
P4422-2/ArtronShop_SHT3x/examples/SHT3x_test/SHT3x_test.ino
Normal file
29
P4422-2/ArtronShop_SHT3x/examples/SHT3x_test/SHT3x_test.ino
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <ArtronShop_SHT3x.h>
|
||||
|
||||
ArtronShop_SHT3x sht3x(0x44, &Wire); // ADDR: 0 => 0x44, ADDR: 1 => 0x45
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
Wire.begin();
|
||||
while (!sht3x.begin()) {
|
||||
Serial.println("SHT3x not found !");
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (sht3x.measure()) {
|
||||
Serial.print("Temperature: ");
|
||||
Serial.print(sht3x.temperature(), 1);
|
||||
Serial.print(" *C\tHumidity: ");
|
||||
Serial.print(sht3x.humidity(), 1);
|
||||
Serial.print(" %RH");
|
||||
Serial.println();
|
||||
} else {
|
||||
Serial.println("SHT3x read error");
|
||||
}
|
||||
delay(1000);
|
||||
}
|
||||
22
P4422-2/ArtronShop_SHT3x/keywords.txt
Normal file
22
P4422-2/ArtronShop_SHT3x/keywords.txt
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#######################################
|
||||
# Syntax Coloring Map For
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
ArtronShop_SHT3x KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
begin KEYWORD2
|
||||
measure KEYWORD2
|
||||
temperature KEYWORD2
|
||||
humidity KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
9
P4422-2/ArtronShop_SHT3x/library.properties
Normal file
9
P4422-2/ArtronShop_SHT3x/library.properties
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
name=ArtronShop_SHT3x
|
||||
version=1.0.0
|
||||
author=ArtronShop Co.,Ltd.
|
||||
maintainer=Sonthaya.NT <sonthaya.nt@artronshop.co.th>
|
||||
sentence=Arduino library of SHT3x Digital humidity and temperature sensor
|
||||
paragraph=Arduino library of SHT3x Digital humidity and temperature sensor
|
||||
category=Sensors
|
||||
url=https://github.com/ArtronShop/ArtronShop_SHT3x
|
||||
architectures=*
|
||||
57
P4422-2/ArtronShop_SHT3x/src/ArtronShop_SHT3x.cpp
Normal file
57
P4422-2/ArtronShop_SHT3x/src/ArtronShop_SHT3x.cpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#include "ArtronShop_SHT3x.h"
|
||||
|
||||
ArtronShop_SHT3x::ArtronShop_SHT3x(uint8_t addr, TwoWire *wire) : _addr(addr), _wire(wire) {
|
||||
// -----
|
||||
}
|
||||
|
||||
bool ArtronShop_SHT3x::begin() {
|
||||
// periodic data acquisition mode
|
||||
this->_wire->beginTransmission(this->_addr);
|
||||
this->_wire->write(0x27); // mps => 10
|
||||
this->_wire->write(0x37); // Repeatability => High
|
||||
return this->_wire->endTransmission() == 0;
|
||||
}
|
||||
|
||||
bool ArtronShop_SHT3x::measure() {
|
||||
this->_wire->beginTransmission(this->_addr);
|
||||
this->_wire->write(0xE0);
|
||||
this->_wire->write(0x00);
|
||||
if (this->_wire->endTransmission(false) != 0) {
|
||||
this->_t = -99;
|
||||
this->_h = -99;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->_wire->requestFrom(this->_addr, (uint8_t)6) != 6) {
|
||||
this->_t = -99;
|
||||
this->_h = -99;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t rx_bytes[6];
|
||||
memset(rx_bytes, 0, 6);
|
||||
this->_wire->readBytes(rx_bytes, 6);
|
||||
|
||||
uint16_t t_ticks = ((uint16_t)rx_bytes[0] << 8) | rx_bytes[1];
|
||||
// checksum_t = rx_bytes[2];
|
||||
uint16_t rh_ticks = ((uint16_t)rx_bytes[3] << 8) | rx_bytes[4];
|
||||
// checksum_rh = rx_bytes[5];
|
||||
this->_t = -45.0f + (175.0f * (t_ticks / 65535.0f));
|
||||
this->_h = 100 * (rh_ticks / 65535.0f);
|
||||
if (this->_h > 100) {
|
||||
this->_h = 100;
|
||||
}
|
||||
if (this->_h < 0) {
|
||||
this->_h = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
float ArtronShop_SHT3x::temperature() {
|
||||
return this->_t;
|
||||
}
|
||||
|
||||
float ArtronShop_SHT3x::humidity() {
|
||||
return this->_h;
|
||||
}
|
||||
25
P4422-2/ArtronShop_SHT3x/src/ArtronShop_SHT3x.h
Normal file
25
P4422-2/ArtronShop_SHT3x/src/ArtronShop_SHT3x.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef __ARTRONSHOP_SHT3X_H__
|
||||
#define __ARTRONSHOP_SHT3X_H__
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
#define SHT3X_ADDR0 (0x44)
|
||||
#define SHT3X_ADDR1 (0x45)
|
||||
|
||||
class ArtronShop_SHT3x {
|
||||
private:
|
||||
uint8_t _addr;
|
||||
TwoWire *_wire = NULL;
|
||||
float _t, _h;
|
||||
|
||||
public:
|
||||
ArtronShop_SHT3x(uint8_t addr = SHT3X_ADDR0, TwoWire *wire = &Wire) ;
|
||||
bool begin() ;
|
||||
bool measure() ;
|
||||
float temperature() ;
|
||||
float humidity() ;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
29
P4422-2/SHT3x_test/SHT3x_test.ino
Normal file
29
P4422-2/SHT3x_test/SHT3x_test.ino
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <ArtronShop_SHT3x.h>
|
||||
|
||||
ArtronShop_SHT3x sht3x(0x44, &Wire); // ADDR: 0 => 0x44, ADDR: 1 => 0x45
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
Wire.begin();
|
||||
while (!sht3x.begin()) {
|
||||
Serial.println("SHT3x not found !");
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (sht3x.measure()) {
|
||||
Serial.print("Temperature: ");
|
||||
Serial.print(sht3x.temperature(), 1);
|
||||
Serial.print(" *C\tHumidity: ");
|
||||
Serial.print(sht3x.humidity(), 1);
|
||||
Serial.print(" %RH");
|
||||
Serial.println();
|
||||
} else {
|
||||
Serial.println("SHT3x read error");
|
||||
}
|
||||
delay(1000);
|
||||
}
|
||||
|
|
@ -1,33 +1,40 @@
|
|||
# =============================================================================
|
||||
# sht30_driver.py - SHT30 I2C 측정 드라이버 (Raspberry Pi 4, I2C 버스 1)
|
||||
# sht30_driver.py - SHT30 I2C measurement driver (Raspberry Pi 4, I2C bus 1)
|
||||
#
|
||||
# [이식 출처]
|
||||
# firmware/common/sht30.c (0x2C06 명령 -> ~20ms 대기 -> 6바이트 read)
|
||||
# firmware/common/sht30_convert.c (CRC-8 검증 + 물리값 변환)
|
||||
# firmware/test/host/reference.py (sht30_crc8 / sht30_parse 파이썬 레퍼런스)
|
||||
# [Ported from]
|
||||
# firmware/common/sht30.c (STM32=0x2C06; Pi uses 0x2400 -> ~20ms wait -> 6-byte read)
|
||||
# * The Pi uses the clock-stretch-disabled command (0x2400) — Pi/Arduino I2C masters
|
||||
# handle clock stretching poorly (Pi BCM I2C stretch bug). The vendor example (P4422-2
|
||||
# ArtronShop_SHT3x) also avoids stretching for the same reason (periodic + fetch). STM32
|
||||
# (HAL supports stretching) keeps 0x2C06. Conversion/CRC are identical.
|
||||
# firmware/common/sht30_convert.c (CRC-8 verification + physical-value conversion)
|
||||
# firmware/test/host/reference.py (sht30_crc8 / sht30_parse Python reference)
|
||||
#
|
||||
# [동작]
|
||||
# 1) 측정 명령 0x2C 0x06 전송 (고반복성, clock stretching 비활성)
|
||||
# 2) ~20ms 대기 (고반복성 측정 최대 15ms + 마진)
|
||||
# 3) 6바이트 read: T_msb,T_lsb,T_crc, RH_msb,RH_lsb,RH_crc
|
||||
# 4) CRC-8(poly 0x31, init 0xFF) 검증 후 변환:
|
||||
# [Operation]
|
||||
# 1) Send measurement command 0x24 0x00 (high-repeatability, clock stretching disabled)
|
||||
# 2) Wait ~20ms (high-repeatability measurement max 15ms + margin)
|
||||
# 3) 6-byte read: T_msb,T_lsb,T_crc, RH_msb,RH_lsb,RH_crc
|
||||
# 4) CRC-8 (poly 0x31, init 0xFF) verification then conversion:
|
||||
# temp_c = -45 + 175 * raw_t / 65535
|
||||
# rh = 100 * raw_rh / 65535
|
||||
#
|
||||
# [I2C 접근] smbus2(순수 파이썬, arch 무관 wheel) 의 i2c_msg 로 write/read 를
|
||||
# 명시적으로 분리 전송한다. read_i2c_block_data 는 레지스터 바이트를 먼저
|
||||
# 쓰므로 SHT30 측정 프레임을 깨뜨린다 → i2c_msg.read 사용.
|
||||
# [I2C access] Uses smbus2's (pure Python, arch-agnostic wheel) i2c_msg to send write/read
|
||||
# as explicitly separate transfers. read_i2c_block_data writes a register byte
|
||||
# first, so it breaks the SHT30 measurement frame -> use i2c_msg.read.
|
||||
#
|
||||
# [시뮬레이션] smbus2 미설치 또는 /dev/i2c-* 부재(개발 PC)면 SIMULATION=True 로
|
||||
# 전환되어 그럴듯한 더미 측정값을 돌려준다(서버 연동/포맷 점검용).
|
||||
# [Simulation] If smbus2 is not installed or /dev/i2c-* is absent (dev PC), it switches to
|
||||
# SIMULATION=True and returns plausible dummy measurements (for server integration/format checks).
|
||||
# =============================================================================
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
|
||||
# ── 상수 (firmware/common/sht30.c, app_config.h 와 동일) ─────────────────────
|
||||
SHT30_CMD = [0x2C, 0x06] # high-repeatability, clock stretching off
|
||||
MEAS_DELAY_SEC = 0.02 # 측정 완료 대기 (15ms + 마진)
|
||||
# ── Constants ────────────────────────────────────────────────────────────────
|
||||
# 0x2400 = single-shot, high-repeatability, clock stretching DISABLED (Sensirion SHT3x datasheet).
|
||||
# (0x2C06 = stretch ENABLED — for STM32 firmware. The Pi uses a write->wait->read pattern, so
|
||||
# the stretch-disabled command is accurate and safe. The vendor example ArtronShop_SHT3x also avoids stretching.)
|
||||
SHT30_CMD = [0x24, 0x00] # high-repeatability, clock stretching disabled (Pi-safe)
|
||||
MEAS_DELAY_SEC = 0.02 # wait for measurement completion (15ms + margin)
|
||||
FRAME_LEN = 6
|
||||
|
||||
try:
|
||||
|
|
@ -37,7 +44,7 @@ except ImportError:
|
|||
SMBUS_AVAILABLE = False
|
||||
|
||||
|
||||
# ── CRC-8 (sht30_convert.c::sht30_crc8 와 동일) ──────────────────────────────
|
||||
# ── CRC-8 (identical to sht30_convert.c::sht30_crc8) ─────────────────────────
|
||||
def crc8(data: bytes) -> int:
|
||||
"""SHT3x CRC-8: poly 0x31, init 0xFF, no reflection, no final xor."""
|
||||
crc = 0xFF
|
||||
|
|
@ -48,18 +55,18 @@ def crc8(data: bytes) -> int:
|
|||
return crc
|
||||
|
||||
|
||||
# ── 프레임 파싱 (sht30_convert.c::sht30_parse 와 동일) ───────────────────────
|
||||
# ── Frame parsing (identical to sht30_convert.c::sht30_parse) ────────────────
|
||||
def parse_frame(frame: bytes):
|
||||
"""6바이트 측정 프레임 검증 + 변환 → (temp_c, rh).
|
||||
"""Verify + convert the 6-byte measurement frame -> (temp_c, rh).
|
||||
|
||||
CRC 불일치 시 ValueError 를 던진다(호출측이 재시도/스킵).
|
||||
Raises ValueError on CRC mismatch (caller retries/skips).
|
||||
"""
|
||||
if len(frame) != FRAME_LEN:
|
||||
raise ValueError(f"프레임 길이 오류: {len(frame)} (기대 {FRAME_LEN})")
|
||||
raise ValueError(f"frame length error: {len(frame)} (expected {FRAME_LEN})")
|
||||
if crc8(frame[0:2]) != frame[2]:
|
||||
raise ValueError("온도 CRC 불일치")
|
||||
raise ValueError("temperature CRC mismatch")
|
||||
if crc8(frame[3:5]) != frame[5]:
|
||||
raise ValueError("습도 CRC 불일치")
|
||||
raise ValueError("humidity CRC mismatch")
|
||||
raw_t = (frame[0] << 8) | frame[1]
|
||||
raw_rh = (frame[3] << 8) | frame[4]
|
||||
temp_c = -45.0 + 175.0 * (raw_t / 65535.0)
|
||||
|
|
@ -68,11 +75,11 @@ def parse_frame(frame: bytes):
|
|||
|
||||
|
||||
class SHT30:
|
||||
"""SHT30 단발 측정 드라이버.
|
||||
"""SHT30 single-shot measurement driver.
|
||||
|
||||
bus_num: I2C 버스 번호 (Pi 4 40핀 헤더 = 1)
|
||||
addr: 7-bit I2C 주소 (ADDR=GND 면 0x44, ADDR=VDD 면 0x45)
|
||||
simulate: True 면 하드웨어 없이 더미값 반환(강제 시뮬레이션)
|
||||
bus_num: I2C bus number (Pi 4 40-pin header = 1)
|
||||
addr: 7-bit I2C address (0x44 if ADDR=GND, 0x45 if ADDR=VDD)
|
||||
simulate: if True, returns dummy values without hardware (forced simulation)
|
||||
"""
|
||||
|
||||
def __init__(self, bus_num: int = 1, addr: int = 0x44, simulate: bool = False):
|
||||
|
|
@ -85,13 +92,13 @@ class SHT30:
|
|||
def open(self):
|
||||
if self.simulate:
|
||||
return
|
||||
# /dev/i2c-<bus_num> 가 없으면(예: i2c 미활성) 명확한 오류로 안내한다.
|
||||
# If /dev/i2c-<bus_num> is missing (e.g. I2C disabled), report a clear error.
|
||||
try:
|
||||
self._bus = SMBus(self.bus_num)
|
||||
except (FileNotFoundError, PermissionError, OSError) as e:
|
||||
raise RuntimeError(
|
||||
f"I2C 버스 {self.bus_num} 열기 실패: {e}. "
|
||||
f"`sudo raspi-config` 로 I2C 활성화 및 사용자 i2c 그룹 확인."
|
||||
f"Failed to open I2C bus {self.bus_num}: {e}. "
|
||||
f"Enable I2C via `sudo raspi-config` and verify the user's i2c group."
|
||||
) from e
|
||||
|
||||
def close(self):
|
||||
|
|
@ -102,9 +109,9 @@ class SHT30:
|
|||
self._bus = None
|
||||
|
||||
def measure(self):
|
||||
"""단발 측정 → (temp_c, rh). 실패 시 예외(통신/CRC).
|
||||
"""Single-shot measurement -> (temp_c, rh). Raises on failure (comms/CRC).
|
||||
|
||||
시뮬레이션 모드에서는 24~26℃, 48~54% 부근의 더미값을 반환한다.
|
||||
In simulation mode, returns dummy values around 24-26 C and 48-54%.
|
||||
"""
|
||||
if self.simulate:
|
||||
self._sim_tick += 1
|
||||
|
|
@ -115,19 +122,19 @@ class SHT30:
|
|||
if self._bus is None:
|
||||
self.open()
|
||||
|
||||
# 1) 측정 명령 전송 (write)
|
||||
# 1) Send measurement command (write)
|
||||
write = i2c_msg.write(self.addr, SHT30_CMD)
|
||||
self._bus.i2c_rdwr(write)
|
||||
|
||||
# 2) 측정 완료 대기 (clock stretching 비활성 → 호스트가 명시적 대기)
|
||||
# 2) Wait for measurement completion (clock stretching disabled -> host waits explicitly)
|
||||
time.sleep(MEAS_DELAY_SEC)
|
||||
|
||||
# 3) 6바이트 read
|
||||
# 3) 6-byte read
|
||||
read = i2c_msg.read(self.addr, FRAME_LEN)
|
||||
self._bus.i2c_rdwr(read)
|
||||
frame = bytes(read)
|
||||
|
||||
# 4) CRC 검증 + 변환
|
||||
# 4) CRC verification + conversion
|
||||
return parse_frame(frame)
|
||||
|
||||
def __enter__(self):
|
||||
|
|
|
|||
Loading…
Reference in a new issue