diff --git a/CMakeLists.txt b/CMakeLists.txt index 8feaf65..a5f9103 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ project(ensaht C CXX ASM) # Add executable. Default name is the project name, version 0.1 -add_executable(ensaht ensaht.c mqtt.c) +add_executable(ensaht main.c mqtt.c bmp.c aht.c ens.c) # Add the standard include files to the build target_include_directories(ensaht PRIVATE diff --git a/aht.c b/aht.c new file mode 100644 index 0000000..8286054 --- /dev/null +++ b/aht.c @@ -0,0 +1,24 @@ +#include "aht.h" + +#include "hardware/i2c.h" +#include "pico/stdlib.h" + +static const uint8_t AHT_MEASURE[3] = {0xAC, 0x33, 0x00}; + +void getAHTData(double *t, double *h) { + uint8_t result[6]; + uint32_t temperature; + uint32_t humidity; + + i2c_write_blocking(i2c_default, AHT_ADDR, AHT_MEASURE, 3, + false); // trigger measurement + // now we must delay ~100ms + sleep_ms(100); + i2c_read_blocking(i2c_default, AHT_ADDR, result, 6, + false); // read 6 bytes back + humidity = (result[1] << 12) | (result[2] << 4) | (result[3] & 0xF0) >> 4; + temperature = (result[3] & 0x0F) << 16 | (result[4] << 8) | result[5]; + *t = (((double)temperature / 1048576.0f) * 200.0f) - 50.0f; + *h = ((double)humidity / 1048576.0f) * 100.0f; + return; +} diff --git a/aht.h b/aht.h new file mode 100644 index 0000000..3e14683 --- /dev/null +++ b/aht.h @@ -0,0 +1,10 @@ +#ifndef AHT_H +#define AHT_H + +#include + +#define AHT_ADDR 0x38 + +void getAHTData(double *t, double *h); + +#endif // AHT_H diff --git a/bmp.c b/bmp.c new file mode 100644 index 0000000..fe41d2f --- /dev/null +++ b/bmp.c @@ -0,0 +1,18 @@ +#include "bmp.h" + +uint8_t BMP_RESET[2] = {0x7E, 0xB6}; +uint8_t BMP_CAL_DATA[21]; +uint8_t BMP_SETUP[6] = {0x1C, 0x0B, 0x1D, 0x08, 0x1F, 0x0A}; +uint8_t BMP_START[2] = {0x1B, 0x33}; + +double bmp_cal_data_fp[15]; + +int getBMPCalData(bmp_cal_data_t *cal) { + // TODO: Implement calibration data retrieval + return 0; +} + +int initBMP(void) { + // TODO: Implement BMP initialization + return 0; +} diff --git a/bmp.h b/bmp.h new file mode 100644 index 0000000..7fc45e4 --- /dev/null +++ b/bmp.h @@ -0,0 +1,57 @@ +#ifndef BMP_H +#define BMP_H + +#include + +#define BMP_ADDR 0x77 +#define BMP_STATUS 0x03 +#define BMP_DATA 0x04 +#define BMP_CAL_ADDR 0x31 +#define BMP_CAL_LEN 21 +extern uint8_t BMP_RESET[2]; +extern uint8_t BMP_CAL_DATA[21]; +extern uint8_t BMP_SETUP[6]; +extern uint8_t BMP_START[2]; + +// instead of a bunch of defines +enum { + PAR_T1, + PAR_T2, + PAR_T3, + PAR_P1, + PAR_P2, + PAR_P3, + PAR_P4, + PAR_P5, + PAR_P6, + PAR_P7, + PAR_P8, + PAR_P9, + PAR_P10, + PAR_P11 +}; +// raw calibration data +typedef struct { + uint16_t NVM_PAR_T1; + uint16_t NVM_PAR_T2; + int8_t NVM_PAR_T3; + int16_t NVM_PAR_P1; + int16_t NVM_PAR_P2; + int8_t NVM_PAR_P3; + int8_t NVM_PAR_P4; + uint16_t NVM_PAR_P5; + uint16_t NVM_PAR_P6; + int8_t NVM_PAR_P7; + int8_t NVM_PAR_P8; + int16_t NVM_PAR_P9; + int8_t NVM_PAR_P10; + int8_t NVM_PAR_P11; +} bmp_cal_data_t; + +// floating point conversions +extern double bmp_cal_data_fp[15]; + +int getBMPCalData(bmp_cal_data_t *cal); +int initBMP(void); + +#endif // BMP_H diff --git a/ens.c b/ens.c new file mode 100644 index 0000000..0a6f460 --- /dev/null +++ b/ens.c @@ -0,0 +1,41 @@ +#include "ens.h" + +#include "hardware/i2c.h" +#include "pico/stdlib.h" + +static const uint8_t ENS_ACTIVATE[2] = {0x10, 0x02}; +static const uint8_t ENS_IDLE[2] = {0x10, 0x01}; + +void activateENS(void) { + i2c_write_blocking(i2c_default, ENS_ADDR, ENS_ACTIVATE, 2, false); + return; +} +void deactivateENS(void) { + i2c_write_blocking(i2c_default, ENS_ADDR, ENS_IDLE, 2, false); + return; +} +uint8_t getENSData(double *t, double *h, uint8_t *AQI, uint16_t *TVOC, + uint16_t *ECO2, uint16_t *ETOH) { + uint16_t K = (uint16_t)((*t + 273.15) * 64.0); // convert to ENS format + uint16_t RH = (uint16_t)*h * 512; + uint8_t inputData[5] = {0x13, (uint8_t)(K & 0x00FF), (uint8_t)(K >> 8), + (uint8_t)(RH & 0x00FF), (uint8_t)(RH >> 8)}; + uint8_t result[7]; + uint8_t dataReg = ENS_AQI; + uint8_t valid; + i2c_write_blocking(i2c_default, ENS_ADDR, inputData, 5, false); + sleep_ms(100); + i2c_write_blocking(i2c_default, ENS_ADDR, &dataReg, 1, true); + i2c_read_blocking(i2c_default, ENS_ADDR, result, 7, false); + *AQI = result[0]; + *TVOC = (result[2] << 8) | result[1]; + *ECO2 = (result[4] << 8) | result[3]; + *ETOH = (result[6] << 8) | result[5]; + dataReg = 0x20; // status register + i2c_write_blocking(i2c_default, ENS_ADDR, &dataReg, 1, true); + i2c_read_blocking(i2c_default, ENS_ADDR, result, 1, false); + valid = (result[0] & ENS_STATUS_MASK) == 0 + ? 1 + : 0; // check if we are in warm-up or normal mode + return valid; +} diff --git a/ens.h b/ens.h new file mode 100644 index 0000000..16221f1 --- /dev/null +++ b/ens.h @@ -0,0 +1,21 @@ +#ifndef ENS_H +#define ENS_H + +#include + +#define ENS_ADDR 0x53 +#define ENS_TEMP_IN 0x13 +#define ENS_HUM_IN 0x15 +#define ENS_STATUS 0x20 +#define ENS_STATUS_MASK 0x0C +#define ENS_AQI 0x21 +#define ENS_TVOC 0x22 +#define ENS_ECO2 0x24 +#define ENS_ETOH 0x26 + +void activateENS(void); +void deactivateENS(void); +uint8_t getENSData(double *t, double *h, uint8_t *AQI, uint16_t *TVOC, + uint16_t *ECO2, uint16_t *ETOH); + +#endif // ENS_H diff --git a/ensaht.c b/main.c similarity index 69% rename from ensaht.c rename to main.c index 93cc3ad..10c0f1f 100644 --- a/ensaht.c +++ b/main.c @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2026 A.M. Rowsell /****************************************************************************** * _ _ * ___ _ __ ___ __ _| |__ | |_ @@ -16,9 +18,11 @@ * the readings to Adafruit IO over MQTT using the Pico W. ******************************************************************************/ +#include "aht.h" +#include "ens.h" +#include "./mqtt.h" #include "hardware/gpio.h" #include "hardware/i2c.h" -#include "mqtt.h" #include "pico/cyw43_arch.h" #include "pico/stdlib.h" // IWYU pragma: keep #include @@ -28,21 +32,6 @@ #include #include -// I2C device defines -#define AHT_ADDR 0x38 -#define ENS_ADDR 0x53 -uint8_t AHT_MEASURE[3] = {0xAC, 0x33, 0x00}; -uint8_t ENS_ACTIVATE[2] = {0x10, 0x02}; -uint8_t ENS_IDLE[2] = {0x10, 0x01}; -#define ENS_TEMP_IN 0x13 -#define ENS_HUM_IN 0x15 -#define ENS_STATUS 0x20 -#define ENS_STATUS_MASK 0x0C -#define ENS_AQI 0x21 -#define ENS_TVOC 0x22 -#define ENS_ECO2 0x24 -#define ENS_ETOH 0x26 - #define AVERAGES 4 // MQTT and WiFi defines @@ -54,12 +43,12 @@ uint8_t ENS_IDLE[2] = {0x10, 0x01}; #define MQTT_KEEP_ALIVE_SECONDS 60 // MQTT Topics -char temp_topic[] = "ADAFRUIT_IO_USERNAME/feeds/ensaht.temperature"; -char humi_topic[] = "ADAFRUIT_IO_USERNAME/feeds/ensaht.humidity"; -char aqi_topic[] = "ADAFRUIT_IO_USERNAME/feeds/ensaht.aqi"; -char tvoc_topic[] = "ADAFRUIT_IO_USERNAME/feeds/ensaht.tvoc"; -char etoh_topic[] = "ADAFRUIT_IO_USERNAME/feeds/ensaht.etoh"; -char eco2_topic[] = "ADAFRUIT_IO_USERNAME/feeds/ensaht.eco2"; +static const char temp_topic[] = "ADAFRUIT_IO_USERNAME/feeds/ensaht.temperature"; +static const char humi_topic[] = "ADAFRUIT_IO_USERNAME/feeds/ensaht.humidity"; +static const char aqi_topic[] = "ADAFRUIT_IO_USERNAME/feeds/ensaht.aqi"; +static const char tvoc_topic[] = "ADAFRUIT_IO_USERNAME/feeds/ensaht.tvoc"; +static const char etoh_topic[] = "ADAFRUIT_IO_USERNAME/feeds/ensaht.etoh"; +static const char eco2_topic[] = "ADAFRUIT_IO_USERNAME/feeds/ensaht.eco2"; static void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection_status_t status) { @@ -82,56 +71,10 @@ static uint16_t average_uint16(const uint16_t *samples, uint8_t count) { for (uint8_t i = 0; i < count; i++) { sum += samples[i]; } - return (uint16_t)((sum + count / 2) / count); + return (uint16_t)((sum + (uint32_t)count / 2) / (uint32_t)count); } -void getAHTData(double *t, double *h) { - uint8_t result[6]; - uint32_t temperature, humidity; - i2c_write_blocking(i2c_default, AHT_ADDR, AHT_MEASURE, 3, - false); // trigger measurement - // now we must delay ~100ms - sleep_ms(100); - i2c_read_blocking(i2c_default, AHT_ADDR, result, 6, - false); // read 6 bytes back - humidity = (result[1] << 12) | (result[2] << 4) | (result[3] & 0xF0) >> 4; - temperature = (result[3] & 0x0F) << 16 | (result[4] << 8) | result[5]; - *t = ((double)temperature / 1048576.0f) * 200.0f - 50.0f; - *h = ((double)humidity / 1048576.0f) * 100.0f; - return; -} -void activateENS(void) { - i2c_write_blocking(i2c_default, ENS_ADDR, ENS_ACTIVATE, 2, false); - return; -} -void deactivateENS(void) { - i2c_write_blocking(i2c_default, ENS_ADDR, ENS_IDLE, 2, false); - return; -} -uint8_t getENSData(double *t, double *h, uint8_t *AQI, uint16_t *TVOC, - uint16_t *ECO2, uint16_t *ETOH) { - uint16_t K = (uint16_t)(*t + 273.15) * 64; // convert to ENS format - uint16_t RH = (uint16_t)*h * 512; - uint8_t inputData[5] = {0x13, (uint8_t)(K & 0x00FF), (uint8_t)(K >> 8), - (uint8_t)(RH & 0x00FF), (uint8_t)(RH >> 8)}; - uint8_t result[7], dataReg = ENS_AQI, valid; - i2c_write_blocking(i2c_default, ENS_ADDR, inputData, 5, false); - sleep_ms(100); - i2c_write_blocking(i2c_default, ENS_ADDR, &dataReg, 1, true); - i2c_read_blocking(i2c_default, ENS_ADDR, result, 7, false); - *AQI = result[0]; - *TVOC = (result[2] << 8) | result[1]; - *ECO2 = (result[4] << 8) | result[3]; - *ETOH = (result[6] << 8) | result[5]; - dataReg = 0x20; // status register - i2c_write_blocking(i2c_default, ENS_ADDR, &dataReg, 1, true); - i2c_read_blocking(i2c_default, ENS_ADDR, result, 1, false); - valid = (result[0] & ENS_STATUS_MASK) == 0 - ? 1 - : 0; // check if we are in warm-up or normal mode - return valid; -} int main() { uint16_t tvoc, eco2, etoh, avg_tvoc, avg_eco2, avg_etoh; uint8_t aqi, validData;