Skip to content

Commit 106b28f

Browse files
authored
Add ASAIR AHT30 Temperature and Humidity Sensor (ZigEmbeddedGroup#792)
1 parent b3ee7e8 commit 106b28f

File tree

2 files changed

+128
-0
lines changed

2 files changed

+128
-0
lines changed

drivers/framework.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ pub const sensor = struct {
5151
pub const MPU_6050 = @import("sensor/MPU-6050.zig").MPU_6050;
5252
pub const TLV493D = @import("sensor/TLV493D.zig").TLV493D;
5353
pub const TMP117 = @import("sensor/TMP117.zig").TMP117;
54+
pub const AHT30 = @import("sensor/AHT30.zig").AHT30;
5455
};
5556

5657
pub const stepper = struct {
@@ -227,6 +228,7 @@ test {
227228
_ = sensor.MPU_6050;
228229
_ = sensor.TLV493D;
229230
_ = sensor.TMP117;
231+
_ = sensor.AHT30;
230232

231233
_ = @import("stepper/common.zig");
232234
_ = stepper.A4988;

drivers/sensor/AHT30.zig

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//!
2+
//! Generic driver for the ASAIR AHT30 Temperature and Humidity Sensor.
3+
//!
4+
//! Datasheet:
5+
//! * AHT30: https://eleparts.co.kr/data/goods_attach/202306/good-pdf-12751003-1.pdf
6+
//!
7+
const std = @import("std");
8+
const mdf = @import("../framework.zig");
9+
10+
pub const AHT30 = struct {
11+
dev: mdf.base.I2C_Device,
12+
address: mdf.base.I2C_Device.Address,
13+
14+
const Reading = struct {
15+
relative_humidity: f32,
16+
temperature_c: f32,
17+
};
18+
19+
const Status = packed struct {
20+
reserved: u2,
21+
/// 0 -- The calibrated capacitance data is within the CMP interrupt threshold range
22+
/// 1 -- The calibrated capacitance data is outside the CMP interrupt threshold range
23+
cmp_interrupt: u1,
24+
/// 0 -- The calibration calculation function is disabled, and the output data is the raw data output by the ADC
25+
/// 1 -- The calibration calculation function is enabled, and the output data is the calibrated data
26+
calibration_enabled: bool,
27+
/// 0 -- Indicates that the integrity test failed, indicating that there is an error in the OTP data
28+
/// 1 -- Indicates that the OTP memory data integrity test (CRC) passed,
29+
crc_flag: u1,
30+
mode_status: enum(u2) { nor, cyc, cmd, _ },
31+
/// 0 -- Sensor idle, in sleep state
32+
/// 1 -- Sensor is busy, measuring in progress
33+
busy: bool,
34+
};
35+
36+
const write_measurement_command = [_]u8{ 0xAC, 0x33, 0x00 };
37+
38+
/// init should be followed by a delay of at least 5ms
39+
pub fn init(dev: mdf.base.I2C_Device, address: mdf.base.I2C_Device.Address) !AHT30 {
40+
return AHT30{
41+
.dev = dev,
42+
.address = address,
43+
};
44+
}
45+
46+
/// CRC8 check polynomial is X8+X5+X4+1
47+
fn crc8(data: [7]u8) u8 {
48+
var crc: u8 = 0xFF;
49+
// skip CRC itself
50+
for (data[0 .. data.len - 1]) |b| {
51+
crc ^= b;
52+
for (0..8) |_| {
53+
crc = if (crc & 0x80 != 0) (crc << 1) ^ 0x31 else crc << 1;
54+
}
55+
}
56+
return crc;
57+
}
58+
59+
/// For greater precision the data collection cycle should be greater than 1 second
60+
pub fn read_data(self: AHT30) !Reading {
61+
var data: [7]u8 = undefined;
62+
const read = try self.dev.read(self.address, &data);
63+
if (read != data.len) {
64+
return error.InvalidResponseLength;
65+
}
66+
67+
const status: Status = @bitCast(data[0]);
68+
if (status.busy) return error.SensorBusy;
69+
if (status.cmp_interrupt == 1) return error.DataOutsiteThreshold;
70+
if (status.crc_flag == 0) return error.IntegrityTestFailed;
71+
72+
if (data[6] != crc8(data)) {
73+
return error.InvalidCrc8;
74+
}
75+
76+
const relative_humidity_data = (@as(u32, data[1]) << 12) | (@as(u32, data[2]) << 4) | data[3] >> 4;
77+
const temperature_data = ((@as(u32, data[3]) & 0x0F) << 16) | (@as(u32, data[4]) << 8) | data[5];
78+
79+
return .{
80+
.relative_humidity = to_relative_humidity(relative_humidity_data),
81+
.temperature_c = to_temp(temperature_data),
82+
};
83+
}
84+
85+
/// update_readings should be followed by a delay of at least 80ms
86+
pub fn update_readings(self: AHT30) !void {
87+
try self.dev.write(
88+
self.address,
89+
&write_measurement_command,
90+
);
91+
}
92+
93+
fn to_temp(temp_data: u32) f32 {
94+
return (@as(f32, @floatFromInt(temp_data)) / 1048576) * 200 - 50;
95+
}
96+
97+
fn to_relative_humidity(humidity_data: u32) f32 {
98+
return (@as(f32, @floatFromInt(humidity_data)) / 1048576) * 100;
99+
}
100+
101+
pub fn c_to_f(temp_c: f32) f32 {
102+
return (temp_c * 9 / 5) + 32;
103+
}
104+
};
105+
106+
test "temp conversions C to F" {
107+
try std.testing.expectEqual(-40, AHT30.c_to_f(-40));
108+
try std.testing.expectEqual(32, AHT30.c_to_f(0));
109+
try std.testing.expectEqual(86, AHT30.c_to_f(30));
110+
}
111+
112+
test "to_temp" {
113+
const tolerance = 0.01;
114+
115+
try std.testing.expectApproxEqRel(-50.0, AHT30.to_temp(0), tolerance);
116+
try std.testing.expectApproxEqRel(50.0, AHT30.to_temp(524288), tolerance);
117+
try std.testing.expectApproxEqRel(149.999, AHT30.to_temp(1048575), tolerance);
118+
}
119+
120+
test "to_relative_humidity" {
121+
const tolerance = 0.01;
122+
123+
try std.testing.expectApproxEqRel(0.0, AHT30.to_relative_humidity(0), tolerance);
124+
try std.testing.expectApproxEqRel(50.0, AHT30.to_relative_humidity(524288), tolerance);
125+
try std.testing.expectApproxEqRel(100.0, AHT30.to_relative_humidity(1048575), tolerance);
126+
}

0 commit comments

Comments
 (0)