Read and translate AHT20 sensor readings

This commit is contained in:
2024-09-03 22:42:10 +02:00
parent 83f6fa58a4
commit b9443f8cec
6 changed files with 225 additions and 196 deletions

View File

@@ -33,7 +33,7 @@ RMR := rm -rf
CPPFLAGS := -DF_CPU=$(FREQ) -I$(SRCDIR) CPPFLAGS := -DF_CPU=$(FREQ) -I$(SRCDIR)
CFLAGS := -mmcu=$(MCU) -Os -std=c99 -Wall -Wextra -Werror CFLAGS := -mmcu=$(MCU) -Os -std=c99 -Wall -Wextra -Werror
OCFLAGS := -j .text -j .data -O ihex OCFLAGS := -j .text -j .data -O ihex
LDFLAGS := -mmcu=$(MCU) LDFLAGS := -mmcu=$(MCU) -Wl,-u,vfprintf -lprintf_flt
LDLIBS := LDLIBS :=
# ============================================================================== # ==============================================================================

View File

@@ -1,49 +1,10 @@
## Serial Interface ## Supported Commands
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
- RUN - RUN
- STOP - STOP
- TEMP <num> - TEMP <float>
- DEWP <num> - DEWP <float>
Decimal point for numbers is currently not supported. Decimal point for numbers is currently not supported.
Make sure you have disabled hardware flow control on Make sure you have disabled hardware flow control on
the client side. 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

View File

@@ -16,6 +16,11 @@
// XXX: Handle interrupts in TWI_vect ISR? This may not // XXX: Handle interrupts in TWI_vect ISR? This may not
// actually be much better than the blocking approach // 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) int TWI_Init(void)
{ {
// Set SCL bit rate to 400kHz // Set SCL bit rate to 400kHz
@@ -139,11 +144,6 @@ int TWI_SetChannel(int channel)
{ {
unsigned char crb; unsigned char crb;
// assert(channel >= 0 && channel <= 3);
// PCA9546 I2C Multiplexer
// =======================
Info("Switching I2C channel to %d...", channel); Info("Switching I2C channel to %d...", channel);
// Excerpts taken from the PCA9546A datasheet: // Excerpts taken from the PCA9546A datasheet:
@@ -187,3 +187,171 @@ int TWI_SetChannel(int channel)
return 0; 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();
}

View File

@@ -15,4 +15,7 @@ unsigned char TWI_Read_NACK(void);
int TWI_Wait_ACK(void); int TWI_Wait_ACK(void);
int TWI_Stop(void); int TWI_Stop(void);
int TWI_AHT20_Init(void);
int TWI_AHT20_Read(float *temp, float *rhum);
#endif // MAD_CORE_BUS_TWI_H #endif // MAD_CORE_BUS_TWI_H

View File

@@ -2,6 +2,7 @@
#define MAD_CORE_COMMON_H #define MAD_CORE_COMMON_H
#include <stddef.h> #include <stddef.h>
#include <stdint.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdbool.h> #include <stdbool.h>

View File

@@ -6,137 +6,14 @@
#include <avr/interrupt.h> #include <avr/interrupt.h>
short TWI_ReadAHT20(void) // TODO: Get readings from ADC ADS1115 over TWI.
{ // TODO: Implement optional sensor value check with CRC8.
unsigned char data, crc; // 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 int Init(void)
// 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)
{ {
USART_Init(); USART_Init();
sei(); sei();
@@ -154,22 +31,41 @@ int main(void)
PWM_SetValue(FAN02, 50); PWM_SetValue(FAN02, 50);
PWM_SetValue(FAN03, 20); PWM_SetValue(FAN03, 20);
// TODO: Set FAN03 timer frequency TWI_SetChannel(AHT01);
// TODO: Implement ADS1115 and AHT20 TWI_AHT20_Init();
// TEM01, TEM02, TEM03 TWI_SetChannel(AHT02);
// HUM01, HUM02, HUM03 TWI_AHT20_Init();
// TWI_GetValue(TEM03) TWI_SetChannel(AHT03);
// TWI_GetValue(HUM03) TWI_AHT20_Init();
TWI_SetChannel(AHT01); // I2C Mux return 0;
TWI_ReadAHT20(); // TEMP and RH }
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 (;;) { for (;;) {
// Info("PING"); Update();
Sleep(1000); Sleep(1000);
} }