devices: add SHT40 temp & humidity sensor definitions & code

Signed-off-by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
1 files changed, 225 insertions(+), 0 deletions(-)

A => devices/i2c/sht40.h
A => devices/i2c/sht40.h +225 -0
@@ 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