diff --git a/P4422-2/ArtronShop_SHT3x/README.md b/P4422-2/ArtronShop_SHT3x/README.md new file mode 100644 index 0000000..74ce746 --- /dev/null +++ b/P4422-2/ArtronShop_SHT3x/README.md @@ -0,0 +1,38 @@ +# ArtronShop_SHT3x + +Arduino library of SHT30, SHT31, SHT35 Digital humidity and temperature sensor + +## Examples + +```C++ +#include +#include +#include + +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); +} +``` + diff --git a/P4422-2/ArtronShop_SHT3x/examples/SHT3x_test/SHT3x_test.ino b/P4422-2/ArtronShop_SHT3x/examples/SHT3x_test/SHT3x_test.ino new file mode 100644 index 0000000..01dc2af --- /dev/null +++ b/P4422-2/ArtronShop_SHT3x/examples/SHT3x_test/SHT3x_test.ino @@ -0,0 +1,29 @@ +#include +#include +#include + +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); +} diff --git a/P4422-2/ArtronShop_SHT3x/keywords.txt b/P4422-2/ArtronShop_SHT3x/keywords.txt new file mode 100644 index 0000000..80d9c6c --- /dev/null +++ b/P4422-2/ArtronShop_SHT3x/keywords.txt @@ -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) +####################################### diff --git a/P4422-2/ArtronShop_SHT3x/library.properties b/P4422-2/ArtronShop_SHT3x/library.properties new file mode 100644 index 0000000..4fdb067 --- /dev/null +++ b/P4422-2/ArtronShop_SHT3x/library.properties @@ -0,0 +1,9 @@ +name=ArtronShop_SHT3x +version=1.0.0 +author=ArtronShop Co.,Ltd. +maintainer=Sonthaya.NT +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=* diff --git a/P4422-2/ArtronShop_SHT3x/src/ArtronShop_SHT3x.cpp b/P4422-2/ArtronShop_SHT3x/src/ArtronShop_SHT3x.cpp new file mode 100644 index 0000000..8fb95e7 --- /dev/null +++ b/P4422-2/ArtronShop_SHT3x/src/ArtronShop_SHT3x.cpp @@ -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; +} diff --git a/P4422-2/ArtronShop_SHT3x/src/ArtronShop_SHT3x.h b/P4422-2/ArtronShop_SHT3x/src/ArtronShop_SHT3x.h new file mode 100644 index 0000000..ef67c80 --- /dev/null +++ b/P4422-2/ArtronShop_SHT3x/src/ArtronShop_SHT3x.h @@ -0,0 +1,25 @@ +#ifndef __ARTRONSHOP_SHT3X_H__ +#define __ARTRONSHOP_SHT3X_H__ + +#include +#include + +#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 \ No newline at end of file diff --git a/P4422-2/SHT3x_test/SHT3x_test.ino b/P4422-2/SHT3x_test/SHT3x_test.ino new file mode 100644 index 0000000..01dc2af --- /dev/null +++ b/P4422-2/SHT3x_test/SHT3x_test.ino @@ -0,0 +1,29 @@ +#include +#include +#include + +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); +} diff --git a/raspberry_pi4_sht30/sht30_driver.py b/raspberry_pi4_sht30/sht30_driver.py index 385ed51..9aeb039 100644 --- a/raspberry_pi4_sht30/sht30_driver.py +++ b/raspberry_pi4_sht30/sht30_driver.py @@ -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- 가 없으면(예: i2c 미활성) 명확한 오류로 안내한다. + # If /dev/i2c- 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):