@@ 0,0 1,225 @@
+/*
+ * Copyright (c) 2024 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef AVR_COMMON_DEVICES_I2C_SHT40_H
+#define AVR_COMMON_DEVICES_I2C_SHT40_H
+
+#define SHT40_ADDR_W (0x44 << 1 | 0)
+#define SHT40_ADDR_R (0x44 << 1 | 1)
+
+#define SHT40_CMD_MEAS_HIGH 0xfd /* high precision/repeatablity */
+#define SHT40_CMD_MEAS_MED 0xf6 /* medium prec./rep. */
+#define SHT40_CMD_MEAS_LOW 0xe0 /* low prec./rep. */
+#define SHT40_CMD_MEAS_HEAT_200_1000 0x39 /* heat 1s @ 200mW & measure */
+#define SHT40_CMD_MEAS_HEAT_200_100 0x32 /* heat 0.1s @ 200mW & measure */
+#define SHT40_CMD_MEAS_HEAT_110_1000 0x2f /* heat 1s @ 110mW & measure */
+#define SHT40_CMD_MEAS_HEAT_110_100 0x24 /* heat 0.1s @ 110mW & measure */
+#define SHT40_CMD_MEAS_HEAT_20_1000 0x1e /* heat 1s @ 20mW & measure */
+#define SHT40_CMD_MEAS_HEAT_20_100 0x15 /* heat 0.1 @ 20mW & measure */
+#define SHT40_CMD_SOFT_RESET 0x94 /* only ack / no data */
+#define SHT40_CMD_READ_SERIAL 0x89 /* read serial number */
+
+#define SHT40_READ_RETRIES 16
+
+#ifndef _ASM
+
+/* helper to execute a command with no data */
+static bool __sht40_exec_cmd(uint8_t dev_w, uint8_t addr)
+{
+ i2c_start();
+ if (!i2c_sync(I2C_STATUS_START))
+ goto err;
+
+ i2c_tx_byte(dev_w);
+ if (!i2c_sync(I2C_STATUS_MT_SLA_ACK))
+ goto err;
+
+ i2c_tx_byte(addr);
+ if (!i2c_sync(I2C_STATUS_MT_DATA_ACK))
+ goto err;
+
+ i2c_stop();
+
+ return true;
+
+err:
+ i2c_stop();
+
+ return false;
+}
+
+#define sht40_exec_cmd(cmd) __sht40_exec_cmd(SHT40_ADDR_W, (cmd))
+#define sht40_reset() __sht40_exec_cmd(SHT40_ADDR_W, SHT40_CMD_SOFT_RESET)
+
+/* helper to read a measurement - success (+1), not-ready (0), error (-1) */
+static inline int __sht40_read(uint8_t dev_r, void *_buf, size_t len)
+{
+ uint8_t *buf = _buf;
+ uint8_t status;
+ size_t i;
+
+ i2c_start();
+ if (!i2c_sync(I2C_STATUS_START))
+ goto err;
+
+ i2c_tx_byte(dev_r);
+ status = i2c_sync_raw();
+ switch (status) {
+ case I2C_STATUS_MR_SLA_ACK:
+ break;
+ case I2C_STATUS_MR_SLA_NACK:
+ i2c_stop();
+ return 0;
+ default:
+ goto err;
+ }
+
+ for (i = 0; i < len; i++) {
+ bool sync;
+
+ buf[i] = i2c_rx_byte((i + 1) == len, &sync);
+ if (!sync)
+ goto err;
+ }
+
+ i2c_stop();
+
+ return +1;
+
+err:
+ i2c_stop();
+
+ return -1;
+}
+
+/*
+ * Returns: (off * 256) + (scale * ticks) / 256
+ *
+ * This is a 8.8 fixed point version of:
+ *
+ * off + scale * ticks / 65536
+ *
+ * This fixed point representation yields about 0.004 resolution which is
+ * plenty. It limits the integer part to -128..+127, which is fine for both
+ * the temperature (larger than the device operating temperature range) and
+ * humidity (trivially correct).
+ *
+ * To save cycles and .text, this code calculates a 24-bit temporary result,
+ * and then divides (by truncation) by 256 to yield a 16-bit signed value.
+ */
+static inline int16_t __sht40_mult(int8_t off, uint8_t scale, uint16_t ticks)
+{
+#ifdef MCU_ATMEGA88A
+ int16_t out;
+
+ asm(
+ "mul %2,%A3\n" /* scale * ticks_l */
+ "mov %A0,r1\n" /* out_l = mult result high */
+ "eor %B0,%B0\n" /* out_h = 0 */
+
+ "mul %2,%B3\n" /* scale * ticks_h */
+ "add %A0,r0\n" /* out_l += mult result low */
+ "adc %B0,r1\n" /* out_h += mult result high */
+
+ "add %B0,%1\n" /* out_h += off */
+
+ "eor r1,r1\n" /* restore ABI-mandated value */
+ : /* output */
+ "=&r" (out)
+ : /* input */
+ "r" (off),
+ "r" (scale),
+ "r" (ticks)
+ : /* clobber */
+ "r0", "r1"
+ );
+
+ return out;
+#else
+#warning "Unknown ISA"
+ uint16_t a, b;
+
+ a = scale * (ticks & 0xff);
+ b = scale * (ticks >> 8);
+
+ return b + (a >> 8) + (((int16_t) off) << 8);
+#endif
+}
+
+/* cmd is one of SHT40_CMD_MEAS_* */
+static inline bool sht40_read_measurement(uint8_t cmd, int16_t *t, int16_t *rh)
+{
+ uint8_t retry;
+ uint8_t buf[6];
+ uint16_t t_ticks;
+ uint16_t rh_ticks;
+
+ for (retry = 0; retry < SHT40_READ_RETRIES; retry++) {
+ int status;
+
+ status = __sht40_read(SHT40_ADDR_R, buf, sizeof(buf));
+ if (status < 0)
+ return false; /* error */
+ if (status > 0)
+ break; /* success */
+
+ /* delay 1 ms */
+ volatile uint16_t dummy;
+ for (dummy = 0; dummy < MCU_RAW_FREQ / 1000; dummy++)
+ ;
+ }
+
+ if (retry == SHT40_READ_RETRIES)
+ return false; /* all retries failed (error or not-ready) */
+
+ t_ticks = (((uint16_t) buf[0]) << 8) + buf[1];
+ rh_ticks = (((uint16_t) buf[3]) << 8) + buf[4];
+
+ /*
+ * The datasheet points out that division by 2^16 has negiligible
+ * error compared to the more correct 2^16-1. This difference in
+ * the equation greatly simplifies the computation.
+ *
+ * We use a hand-crafted assembly function that returns a
+ * fixed-point version of the approximated result. This gives us a
+ * 8 bits for the (signed) integer part, and 8 bits for the
+ * fraction.
+ *
+ * For completeness, here are the exact expressions:
+ *
+ * t = -45 + 175 * t_ticks / 65535
+ * rh = -6 + 125 * rh_ticks / 65535
+ */
+ *t = __sht40_mult(-45, 175, t_ticks);
+ *rh = __sht40_mult(-6, 125, rh_ticks);
+
+ if (*rh > (100 * 256))
+ *rh = 100 * 256;
+ else if (*rh < 0)
+ *rh = 0;
+
+ return true; /* success */
+}
+
+#endif
+
+#endif