From b9443f8cec0f006daa1bd8fa2eb697ab7451345a Mon Sep 17 00:00:00 2001 From: Leon Krieg Date: Tue, 3 Sep 2024 22:42:10 +0200 Subject: [PATCH] Read and translate AHT20 sensor readings --- Makefile | 2 +- docs/README | 45 +----------- src/bus/twi.c | 194 ++++++++++++++++++++++++++++++++++++++++++++++---- src/bus/twi.h | 3 + src/common.h | 1 + src/main.c | 176 ++++++++++----------------------------------- 6 files changed, 225 insertions(+), 196 deletions(-) diff --git a/Makefile b/Makefile index 0a0b035..5a62825 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ RMR := rm -rf CPPFLAGS := -DF_CPU=$(FREQ) -I$(SRCDIR) CFLAGS := -mmcu=$(MCU) -Os -std=c99 -Wall -Wextra -Werror OCFLAGS := -j .text -j .data -O ihex -LDFLAGS := -mmcu=$(MCU) +LDFLAGS := -mmcu=$(MCU) -Wl,-u,vfprintf -lprintf_flt LDLIBS := # ============================================================================== diff --git a/docs/README b/docs/README index 5830269..673d723 100644 --- a/docs/README +++ b/docs/README @@ -1,49 +1,10 @@ -## Serial Interface - - Instead of polling for input we use an interrupt vector - to write data to a rx ring buffer when it's available. - - We parse commands using a state machine so we can deal - with small chunks of characters at a time and spit out - the parsed command when the transmission has finished. - - NOTE: System clock frequency should be a multiple of - 1.8432MHz for perfect USART. This means we would need - an external crystal oscillator. - -### Supported Commands +## Supported Commands - RUN - STOP - - TEMP - - DEWP + - TEMP + - DEWP Decimal point for numbers is currently not supported. Make sure you have disabled hardware flow control on the client side. - -### Web Interface Example - - There is an example https and websocket server (written - in Python) which serves a simple Javascript shell that - controls your box. Its main purpose is to give you a - demonstration on how you can write a custom bridge or - interface. - - Check out the directory "/opt/webgui" under the project - root. You may need to run the following command to load - optional submodules if you can't see the webgui folder: - - $ git submodule update --init - - First you need to generate a self signed certificate to - ensure SSL encryption. Run the following command in the - webgui root directory: - - $ ./configure - - To manually start the server you can use the Makefile - (but it's recommended to run the server automatically - as a daemon when the host machine starts): - - $ make run diff --git a/src/bus/twi.c b/src/bus/twi.c index 910b738..b4dafd0 100644 --- a/src/bus/twi.c +++ b/src/bus/twi.c @@ -4,18 +4,23 @@ #include #include -#define TW_START 0x08 -#define TW_REP_START 0x10 -#define TW_MT_SLA_ACK 0x18 -#define TW_MT_SLA_NACK 0x20 -#define TW_MT_DATA_ACK 0x28 -#define TW_MR_SLA_ACK 0x40 -#define TW_MR_SLA_NACK 0x48 -#define TW_MR_DATA_ACK 0x50 +#define TW_START 0x08 +#define TW_REP_START 0x10 +#define TW_MT_SLA_ACK 0x18 +#define TW_MT_SLA_NACK 0x20 +#define TW_MT_DATA_ACK 0x28 +#define TW_MR_SLA_ACK 0x40 +#define TW_MR_SLA_NACK 0x48 +#define TW_MR_DATA_ACK 0x50 // XXX: Handle interrupts in TWI_vect ISR? This may not // actually be much better than the blocking approach +static void TWI_AHT20_Reset(void); +static bool TWI_AHT20_IsCalibrated(void); +static void TWI_AHT20_Calibrate(void); +static void TWI_AHT20_Trigger(void); + int TWI_Init(void) { // Set SCL bit rate to 400kHz @@ -139,11 +144,6 @@ int TWI_SetChannel(int channel) { unsigned char crb; - // assert(channel >= 0 && channel <= 3); - - // PCA9546 I2C Multiplexer - // ======================= - Info("Switching I2C channel to %d...", channel); // Excerpts taken from the PCA9546A datasheet: @@ -187,3 +187,171 @@ int TWI_SetChannel(int channel) return 0; } + +int TWI_AHT20_Init(void) +{ + TWI_AHT20_Reset(); + + // Wait 40ms after power-on + + Sleep(40); + + Info("Initializing AHT20 sensor..."); + + if (!TWI_AHT20_IsCalibrated()) { + TWI_AHT20_Calibrate(); + if (!TWI_AHT20_IsCalibrated()) { + Info("Error: Calibration failed."); + return -1; + } + } + + return 0; +} + +int TWI_AHT20_Read(float *temp, float *rhum) +{ + unsigned char data[6], crc; + unsigned long hraw, traw; + + TWI_AHT20_Trigger(); + + TWI_Start(0x38, 1); + TWI_Wait_ACK(); + + data[0] = TWI_Read_ACK(); + data[1] = TWI_Read_ACK(); + data[2] = TWI_Read_ACK(); + data[3] = TWI_Read_ACK(); + data[4] = TWI_Read_ACK(); + data[5] = TWI_Read_ACK(); + crc = TWI_Read_NACK(); + TWI_Stop(); + + // After receiving six bytes, the next byte is the + // CRC check data, the user can read it as needed, + // if the receiving end needs CRC check, then send + // it after receiving the sixth byte ACK response, + // otherwise NACK is sent out, CRC initial value is + // 0xFF. The CRC8 check polynomial is: + // CRC[7:0]= 1 + (x^4) + (x^5) + (x^8) + + // TODO: Implement CRC8 check + UNUSED(crc); + + hraw = (unsigned long) data[1] << 12; + hraw |= (unsigned short) data[2] << 4; + hraw |= data[3] >> 4; + + traw = (unsigned long) (data[3] & 0x0F) << 16; + traw |= (unsigned short) data[4] << 8; + traw |= data[5]; + + *rhum = (float) hraw / 1048576.0f * 100.0f; + *temp = (float) traw / 1048576.0f * 200.0f - 50.0f; + + return 0; +} + +static void TWI_AHT20_Reset(void) +{ + // The command 0xBA is used to restart the sensor + // system without turning the power off and on + // again. After receiving this command, the sensor + // system begins to re-initialize and restore the + // default setting state. The time required for + // soft reset does not exceed 20 ms. + + TWI_Start(0x38, 0); + TWI_Wait_ACK(); + + TWI_Write(0xBA); + TWI_Wait_ACK(); + TWI_Stop(); + + Sleep(20); +} + +static bool TWI_AHT20_IsCalibrated(void) +{ + unsigned char status; + + // Before reading the temperature and humidity + // values, first check whether the calibration + // enable bit Bit [3] of the status word is 1 (you + // can get a byte of status word by sending 0x71). + + TWI_Start(0x38, 0); + TWI_Wait_ACK(); + + TWI_Write(0x71); + TWI_Wait_ACK(); + TWI_Stop(); + + TWI_Start(0x38, 1); + TWI_Wait_ACK(); + + status = TWI_Read_NACK(); + TWI_Stop(); + + // Note: The calibration status check only needs to + // be checked at power-on. No operation is required + // during the normal acquisition process. + + return status & BIT(3); +} + +static void TWI_AHT20_Calibrate(void) +{ + // To calibrate you need to send 0xBE command (for + // initialization), this command parameter has two + // bytes, the first byte is 0x08, the second byte + // is 0x00, and then wait for 10ms. + + TWI_Start(0x38, 0); + TWI_Wait_ACK(); + + TWI_Write(0xBE); + TWI_Wait_ACK(); + TWI_Write(0x08); + TWI_Wait_ACK(); + TWI_Write(0x00); + TWI_Wait_ACK(); + TWI_Stop(); +} + +static void TWI_AHT20_Trigger(void) +{ + unsigned char status; + + // Send the 0xAC command directly (trigger + // measurement). The parameter of this command has + // two bytes, the first byte is 0x33 and the second + // byte is 0x00. + + TWI_Start(0x38, 0); + TWI_Wait_ACK(); + + TWI_Write(0xAC); + TWI_Wait_ACK(); + TWI_Write(0x33); + TWI_Wait_ACK(); + TWI_Write(0x00); + TWI_Wait_ACK(); + TWI_Stop(); + + // Wait for 80ms to wait for the measurement to be + // completed. If the read status word Bit [7] is 0, + // it indicates that the measurement is completed, + // and six bytes can be read in a row. Otherwise + // continue to wait. + + TWI_Start(0x38, 1); + TWI_Wait_ACK(); + + do { + Sleep(80); + status = TWI_Read_ACK(); + } while ((status & BIT(7)) == 1); + TWI_Stop(); +} diff --git a/src/bus/twi.h b/src/bus/twi.h index c20ca9a..59d53dc 100644 --- a/src/bus/twi.h +++ b/src/bus/twi.h @@ -15,4 +15,7 @@ unsigned char TWI_Read_NACK(void); int TWI_Wait_ACK(void); int TWI_Stop(void); +int TWI_AHT20_Init(void); +int TWI_AHT20_Read(float *temp, float *rhum); + #endif // MAD_CORE_BUS_TWI_H diff --git a/src/common.h b/src/common.h index e2c3f5f..68a790c 100644 --- a/src/common.h +++ b/src/common.h @@ -2,6 +2,7 @@ #define MAD_CORE_COMMON_H #include +#include #include #include diff --git a/src/main.c b/src/main.c index 9689d00..724fb34 100644 --- a/src/main.c +++ b/src/main.c @@ -6,137 +6,14 @@ #include -short TWI_ReadAHT20(void) -{ - unsigned char data, crc; +// TODO: Get readings from ADC ADS1115 over TWI. +// TODO: Implement optional sensor value check with CRC8. +// TODO: Either implement software PWM for the FAN03 timer +// (which will be quite complicated) or pick a chip with +// more than two 16-bit PWM outputs like the ATmega328PB. +// https://www.mikrocontroller.net/articles/Soft-PWM - // After the transmission is initiated, the first - // byte of the subsequent I2C transmission includes - // the 7-bit I2C device address 0x38 and a SDA - // direction bit. - - // Soft reset device. - - TWI_Start(0x38, 0); - TWI_Write(0xBA); - TWI_Wait_ACK(); - TWI_Stop(); - - // Wait 40ms after power-on. - - Sleep(40); - - // Before reading the temperature and humidity - // values, first check whether the calibration - // enable bit Bit [3] of the status word is 1 (you - // can get a byte of status word by sending 0x71). - - TWI_Start(0x38, 0); - TWI_Write(0x71); - TWI_Wait_ACK(); - - data = TWI_Read_NACK(); - TWI_Stop(); - - Info("Received calibration status %02X.", data); - - // If not 1, need to send 0xBE command (for - // initialization), this command parameter has two - // bytes, the first byte is 0x08, the second byte - // is 0x00, and then wait for 10ms. - - if (data & ~BIT(3)) { - Info("Requesting calibration..."); - - TWI_Start(0x38, 0); - TWI_Write(0xBE); - TWI_Wait_ACK(); - TWI_Write(0x08); - TWI_Wait_ACK(); - TWI_Write(0x00); - TWI_Wait_ACK(); - - Sleep(10); - data = TWI_Read_NACK(); - TWI_Stop(); - - if (data & ~BIT(3)) { - Info("Error: Calibration failed."); - return -1; - } - } - - // Send the 0xAC command directly (trigger - // measurement). The parameter of this command has - // two bytes, the first byte is 0x33 and the second - // byte is 0x00. - - Info("Triggering measurement..."); - - TWI_Start(0x38, 0); - TWI_Wait_ACK(); - TWI_Write(0xAC); - TWI_Wait_ACK(); - TWI_Write(0x33); - TWI_Wait_ACK(); - TWI_Write(0x00); - TWI_Wait_ACK(); - TWI_Stop(); - - // Wait for 80ms to wait for the measurement to be - // completed. If the read status word Bit [7] is 0, - // it indicates that the measurement is completed, - // and six bytes can be read in a row. Otherwise - // continue to wait. - - Info("Reading measurement..."); - - TWI_Start(0x38, 1); // Read - TWI_Wait_ACK(); - - do { - Sleep(80); - data = TWI_Read_ACK(); - } while (data & BIT(7)); - - data = TWI_Read_ACK(); - Info("Received data byte %02X.", data); - data = TWI_Read_ACK(); - Info("Received data byte %02X.", data); - data = TWI_Read_ACK(); - Info("Received data byte %02X.", data); - data = TWI_Read_ACK(); - Info("Received data byte %02X.", data); - data = TWI_Read_ACK(); - Info("Received data byte %02X.", data); - data = TWI_Read_ACK(); - Info("Received data byte %02X.", data); - - // After receiving six bytes, the next byte is the - // CRC check data, the user can read it as needed, - // if the receiving end needs CRC check, then send - // it after receiving the sixth byte ACK response, - // otherwise NACK is sent out, CRC initial value is - // 0xFF. The CRC8 check polynomial is: - // CRC[7:0]= 1 + (x^4) + (x^5) + (x^8) - - crc = TWI_Read_NACK(); - Info("Received CRC8 byte %02X.", crc); - - // Calculate the temperature and humidity values. - // Note: The calibration status check in the first - // step only needs to be checked at power-on. No - // operation is required during the normal - // acquisition process. - - // TODO: Calculate values - - TWI_Stop(); - - return 0; -} - -int main(void) +int Init(void) { USART_Init(); sei(); @@ -154,22 +31,41 @@ int main(void) PWM_SetValue(FAN02, 50); PWM_SetValue(FAN03, 20); - // TODO: Set FAN03 timer frequency - // TODO: Implement ADS1115 and AHT20 + TWI_SetChannel(AHT01); + TWI_AHT20_Init(); - // TEM01, TEM02, TEM03 - // HUM01, HUM02, HUM03 + TWI_SetChannel(AHT02); + TWI_AHT20_Init(); - // TWI_GetValue(TEM03) - // TWI_GetValue(HUM03) + TWI_SetChannel(AHT03); + TWI_AHT20_Init(); - TWI_SetChannel(AHT01); // I2C Mux - TWI_ReadAHT20(); // TEMP and RH + return 0; +} - Info("Running idle loop..."); +void Update(void) +{ + float temp, rhum; + + TWI_SetChannel(AHT01); + TWI_AHT20_Read(&temp, &rhum); + Info("TEMP=%.2f, RHUM=%.2f", temp, rhum); + + TWI_SetChannel(AHT02); + TWI_AHT20_Read(&temp, &rhum); + Info("TEMP=%.2f, RHUM=%.2f", temp, rhum); + + TWI_SetChannel(AHT03); + TWI_AHT20_Read(&temp, &rhum); + Info("TEMP=%.2f, RHUM=%.2f", temp, rhum); +} + +int main(void) +{ + Init(); for (;;) { - // Info("PING"); + Update(); Sleep(1000); }