MicroPython Driver for AHT10 / AHT15
Contents
Introduction
This a MicroPython driver written specifically for the BBC micro:bit that will work with the AHT10 and AHT15 relative humidity and ambient temperature sensors. The driver implements all functions described in the product datasheet.
The AHT1X sensor series is discussed in some detail here.
The AHT10 is readily available on an inexpensive breakout board.
Fig 1 - [L to R] AHT10 breakout board and AHT15 module (at different scale)
However the AHT15 sensor is manufactured as a module and is not available on a breakout board. An adapter cable (4-wire DF13 connector to Dupont) can either be purchased or soldered up for using this sensor on a breadboard or for direct connection to the pins of the micro-bit. This is covered in some detail in the reference above.
Connecting the AHT10 and AHT15
The AHT10 and AHT15 communicate via I2C. The micro:bit and these must be connected as follows:
micro:bit | AHT10 /AHT15 |
---|---|
3.3V | VIN / VDD |
GND | GND |
Pin 19 | SCL |
Pin 20 | SDA |
This utilises the standard I2C pins of the micro:bit.
Care must be taken when cabling the AHT15 module. The correct pinout can be found in the datasheet which is reproduced for convenience in the sensor description page linked above. The pinout is different to the AHT10 breakout board.
Driver Overview
The driver code can be:
- Copied from this webpage onto the clipboard then pasted into the MicroPython editor e.g. the Mu Editor. It should be saved as fc_aht1x.py - OR -
- Download as a zip file using the link. Unzip the file and save it as fc_aht1x.py into the default directory where the MicroPython editor e.g. Mu Editor saves python code files.
After saving the fc_aht1x.py file to the computer it should be copied to the small filesystem on the micro:bit. The examples on this page will not work if this step is omitted. Any MicroPython editor that recognises the micro:bit will have an option to do this.
The Mu Editor is recommended for its simplicity. It provides a Files button on the toolbar for just this purpose.
Class constructorThe driver is implemented as a class. The first thing to do is call the constructor. This provides an instance of the class.
Syntax:
instance = AHT1X(addr = 0x38)
Where:
addr : I2C address. See discussion below
with respect to I2C addressing of
these two sensors.
Examples
from fc_aht1x import *
my_sensor = AHT1X()
aht10 = AHT1X(0x39)
aht15 = AHT1X()
The constructor has one argument; the I2C address. The default address for both sensors is 0x38. The address of the AHT15 is fixed and cannot be changed. It is possible to change the address of the AHT10 to 0x39. This is discussed here.
This assumes that the file fc_aht1x.py has been successfully copied to the micro:bit's filesystem as described above.
Class MethodsThe driver provides methods that:
- Initialise() : Initialises the sensor.
- Read(), T(), RH() : Reads the relative humidity and ambient temperature.
- Reset() : Resets the sensor.
Initialising the Sensor
The datasheet states that the sensor should be initialised before the first measurement command is given. The reason why initialisation is necessary and what it does isn't elaborated upon in the official documentation. However initialisation probably causes the sensor to run its internal calibration process.
In normal circumstances there is no need for the user to issue an initialisation command as it is done in the class constructor during object instantiation.
from fc_aht1x import *
my_sensor = AHT1X()
my_sensor.Initialise()
Reading Temperature and Humidity
There are three methods that return the humidity and/or temperature.
- Read()
- RH()
- T()
Read()
Returns a tuple containing two values:
[0] is relative humidity in %,
[1] is temperature in °C
Both values, of type float, at maximum MicroPython resolution.
RH()
Returns relative humidity in °%
The result is returned as an integer.
T()
Returns temperature in °C
The result is rounded to one decimal.
Example:
from fc_aht1x import *
aht10 = AHT1X()
# Read humidity and temperature
data = aht10.Read()
print('RH(%):', data[0], 'Temperature(C):', data[1])
# Get temperature only
print('Temperature(C):', aht10.T())
# Get relative humidity only
print('RH(%):', aht10.RH())
Sample Output:
RH(%): 28.51467 Temperature(C): 27.77214
Temperature(C): 27.9
RH(%): 29
NOTE: The datasheet advises that no more than one reading should be made per two seconds. This is to ensure that the internal temperature of the sensor doesn't increase by more than 0.1°C above the atmospheric ambient temperature.
Resetting the Sensor
The user can send a soft reset command to the sensor chip. This can be done by calling the Reset() method. The reset restores default settings and is completed in less than 20ms.
from fc_aht1x import *
my_sensor = AHT1X()
my_sensor.Reset()
Examples
The AHT10 (and AHT15) product datasheet makes the extraordinary claim that only a single AHT10 can be connected to the I2C bus and no other I2C devices can be connected
. This following example program is designed to test this assertion.
Three different humidity and temperature sensors, AHT10, AHT15 and SHT40, will be used. Each sensor will be read in sequence for humidity and temperature each 2 seconds for 10 seconds i.e. a total of 5 readings from each sensor.
Each 2 second reading will be output. Then the average humidity and temperature readings from each sensor will be calculated and output.
Since the AHT10 and AHT15 sensors have the same I2C address they will be attached to channels on a TCA9548A I2C multiplexer. The AHT10 will use channel 7 and the AHT15 will use channel 2 of the multiplexer.
The following driver module files need to be copied to the micro:bit's filesystem:
- AHT1X : fc_aht1x.py
- TCA9548A : fc_tca9548a.py
- SHT40 : fc_sht4x.py
- Connect TCA9548A, AHT10, AHT15 and SHT40 power and ground pins to the micro:bit's 3.3V and GND pins respectively.
- TCA9548A: Connect SCL and SDA to micro:bit's pin19 and pin20 respectively.
- SHT40: Connect SCL and SDA to micro:bit's pin19 and pin20 respectively.
- AHT10: Connect SCL and SDA pins to TCA9548A's SC7 and SD7 pins respectively.
- AHT15: Connect SCL and SDA pins to TCA9548A's SC2 and SD2 pins respectively.
Example 1
# Simultaneously test AHT10, AHT15
# and SHT40 sensors.
# All three sensors measure humidity
# and temperature.
# AHT10 and AHT15 sensors have the
# same I2C address so it is necessary
# to use a TCA9548A multiplexer.
from fc_aht1x import *
from fc_tca9548a import *
from fc_sht4x import *
from microbit import sleep
# Instantiate multiplexer object.
multi = TCA9548A()
# Instantiate SHT40 sensor object.
sht40 = SHT4X()
# Instantiate AHT10 sensor object.
# Attached to multiplexer channel 7
multi.CloseAllChannels()
multi.OpenChannels(7)
aht10 = AHT1X()
# Instantiate AHT15 sensor object.
# Attached to multiplexer channel 2
multi.CloseAllChannels()
multi.OpenChannels(2)
aht15 = AHT1X()
sum_aht10_rh = sum_aht15_rh = sum_sht40_rh = 0
sum_aht10_t = sum_aht15_t = sum_sht40_t = 0
# Read the sensors
for x in range(5):
# AHT10 sensor
multi.CloseAllChannels()
multi.OpenChannels(7)
data = aht10.Read()
sum_aht10_t += data[1]
sum_aht10_rh += data[0]
print('AHT10 - RH(%):', data[0], 'T(C):', data[1])
# AHT15 sensor
multi.CloseAllChannels()
multi.OpenChannels(2)
data = aht15.Read()
sum_aht15_t += data[1]
sum_aht15_rh += data[0]
print('AHT15 - RH(%):', data[0], 'T(C):', data[1])
# SHT40 sensor
data = sht40.Read()
sum_sht40_t += data[0]
sum_sht40_rh += data[2]
print('SHT40 - RH(%):', data[2], 'T(C):', data[0])
sleep(2000)
# Calculate averages
avg_aht10_t = sum_aht10_t / 5
avg_aht10_rh = sum_aht10_rh / 5
avg_aht15_t = sum_aht15_t / 5
avg_aht15_rh = sum_aht15_rh / 5
avg_sht40_t = sum_sht40_t / 5
avg_sht40_rh = sum_sht40_rh / 5
print('Averages:')
print('AHT10 - RH(%):', avg_aht10_rh, 'T(C):', avg_aht10_t)
print('AHT15 - RH(%):', avg_aht15_rh, 'T(C):', avg_aht15_t)
print('SHT40 - RH(%):', avg_sht40_rh, 'T(C):', avg_sht40_t)
Typical Output:
AHT10 - RH(%): 46.51852 T(C): 19.92226
AHT15 - RH(%): 44.86694 T(C): 19.71779
SHT40 - RH(%): 46.55398 T(C): 20.82361
AHT10 - RH(%): 46.48161 T(C): 19.91501
AHT15 - RH(%): 44.88029 T(C): 19.7361
SHT40 - RH(%): 46.45289 T(C): 20.82361
AHT10 - RH(%): 46.47999 T(C): 19.92226
AHT15 - RH(%): 44.9152 T(C): 19.71588
SHT40 - RH(%): 46.41856 T(C): 20.82895
AHT10 - RH(%): 46.48743 T(C): 19.9213
AHT15 - RH(%): 44.86589 T(C): 19.71912
SHT40 - RH(%): 46.50439 T(C): 20.81293
AHT10 - RH(%): 46.48695 T(C): 19.92321
AHT15 - RH(%): 44.78722 T(C): 19.71321
SHT40 - RH(%): 46.52346 T(C): 20.82628
Averages:
AHT10 - RH(%): 46.4909 T(C): 19.92081
AHT15 - RH(%): 44.86311 T(C): 19.72042
SHT40 - RH(%): 46.49065 T(C): 20.82308
All three sensors (AHT10, AHT15, SHT40) behaved perfectly. However it is mention on some online forums that the AHT10 over time will cause the I2C bus to freeze. So this was tested with the next example.
The first example code was slightly modified to take readings from the three sensors sequentially every 2 seconds for one hour i.e. 1,800 readings were taken by each sensor. The code modifications made were:
- The range in the for loop was changed from 5 to 1800.
- The three print statements inside the loop were commented out.
- The average calculations were changed to reflect that 1,800 samples per sensor were taken.
Typical Output:
Averages:
AHT10 - RH(%): 39.98535 T(C): 24.86712
AHT15 - RH(%): 38.88862 T(C): 23.58581
SHT40 - RH(%): 39.72151 T(C): 25.64433
As it can clearly be seen, the microbit and its I2C bus continued to function and did not freeze. All three sensors showed close results for relative humidity which is the primary function of these sensors.
The SHT40, which would be regarded as the best sensor of the three, displayed a slightly higher temperature reading.
Enjoy!
AHT10, AHT15 Driver Code for micro:bit
Download as zip file
'''
AHT10, AHT15 (humidity and temperature sensors)
MicroPython driver for micro:bit
AUTHOR: fredscave.com
DATE : 2024/11
VERSION : 1.00
'''
from microbit import i2c, sleep
from micropython import const
ADDR = const(0x38)
CMD_INIT = [0xE1, 0x08, 0x00]
CMD_MEASURE = [0xAC, 0x33, 0x00]
CMD_RESET = const(0xBA)
class AHT1X():
def __init__(self, addr=ADDR):
self.addr = addr
self.Initialise()
def Initialise(self):
i2c.write(self.addr, bytearray(CMD_INIT))
def Read(self):
i2c.write(self.addr, bytearray(CMD_MEASURE))
#sleep(50)
busy = True
while busy:
buf = i2c.read(self.addr, 6)
sleep(10)
busy = buf[0] & 0b10000000
measurements = AHT1X.convert(buf)
return measurements
def T(self):
measurements = self.Read()
return round(measurements[1], 1)
def RH(self):
measurements = self.Read()
return int(measurements[0] + 0.5)
def Reset(self):
i2c.write(self.addr, bytes([CMD_RESET]))
sleep(20)
@staticmethod
def convert(buf):
RawRH = ((buf[1] << 16) |( buf[2] << 8) | buf[3]) >> 4
RH = RawRH * 100 / 0x100000
RawT = ((buf[3] & 0x0F) << 16) | (buf[4] << 8) | buf[5]
T = ((RawT * 200) / 0x100000) - 50
return (RH, T)