aht20_driver/lib.rs
1#![cfg_attr(not(test), no_std)]
2//! AHT20 driver.
3//!
4//! Example:
5//!
6//! # use embedded_hal_mock::eh1::delay::NoopDelay as MockDelay;
7//! # use embedded_hal_mock::eh1::i2c::Mock as I2cMock;
8//! # use embedded_hal_mock::eh1::i2c::Transaction;
9//! # use aht20_driver::{AHT20, AHT20Initialized, Command, SENSOR_ADDRESS};
10//! # let expectations = vec![
11//! # // check_status immediately succeeds, we don't need to send Initialize.
12//! # Transaction::read(SENSOR_ADDRESS, vec![0b0000_1000]),
13//! # // send_trigger_measurement
14//! # Transaction::write(
15//! # SENSOR_ADDRESS,
16//! # vec![
17//! # Command::TriggerMeasurement as u8,
18//! # 0b0011_0011, // 0x33
19//! # 0b0000_0000, // 0x00
20//! # ],
21//! # ),
22//! # // check_status - with ready bit set to 'ready' (off)
23//! # Transaction::read(SENSOR_ADDRESS, vec![0b0000_1000]),
24//! # // We can now read 7 bytes. status byte, 5 data bytes, crc byte.
25//! # // These are taken from a run of the sensor.
26//! # Transaction::read(
27//! # SENSOR_ADDRESS,
28//! # vec![
29//! # 0b0001_1100, // 28, 0x1c - ready, calibrated, and some mystery flags.
30//! # // bit 8 set to 0 is ready. bit 4 set is calibrated. bit 5
31//! # // and 3 are described as 'reserved'.
32//! # 0b0110_0101, // 101, 0x65 - first byte of humidity value
33//! # 0b1011_0100, // 180, 0xb4 - second byte of humidity vaue
34//! # 0b0010_0101, // 37, 0x25 - split byte. 4 bits humidity, 4 bits temperature.
35//! # 0b1100_1101, // 205, 0xcd - first full byte of temperature.
36//! # 0b0010_0110, // 38, 0x26 - second full byte of temperature.
37//! # 0b1100_0110, // 198, 0xc6 - CRC
38//! # ],
39//! # ),
40//! # ];
41//! # let mock_i2c = I2cMock::new(&expectations);
42//! # let mut mock_delay = MockDelay::new();
43//! let mut aht20_uninit = AHT20::new(mock_i2c, SENSOR_ADDRESS);
44//! let mut aht20 = aht20_uninit.init(&mut mock_delay).unwrap();
45//! let measurement = aht20.measure(&mut mock_delay).unwrap();
46//!
47//! println!("temperature (aht20): {:.2}C", measurement.temperature);
48//! println!("humidity (aht20): {:.2}%", measurement.humidity);
49//!
50//! aht20_uninit.destroy().done();
51//!
52//! [AHT20 Datasheet](https://cdn-learn.adafruit.com/assets/assets/000/091/676/original/AHT20-datasheet-2020-4-16.pdf?1591047915)
53//!
54//! Note that the datasheet linked directly from the manufacturer's website
55//! [Aogong AHT20](http://www.aosong.com/en/products-32.html) is an older datasheet (version
56//! 1.0, rather than version 1.1 as linked above) and is significantly more
57//! difficult to understand. I recommend reading version 1.1. All section
58//! references in this file are to the 1.1 version.
59//!
60//! The below is a flowchart of how the sensor gets initialized and measurements taken.
61//! Note that the flowchart does not include the parameters that you need to give to
62//! some commands, and it also doesn't include the SoftReset command flow.
63//!
64//! ```text
65//! Start (Power on)
66//! │
67//! ▼
68//! Wait 40 ms
69//! │
70//! ▼
71//! Read status byte ◄─── Wait 10 ms
72//! │ ▲
73//! ▼ │
74//! Status::Calibrated ──► No ──► Command::Initialize (0xBE)
75//! │
76//! ▼
77//! Yes
78//! │
79//! ▼
80//! Command::TriggerMeasurement (0xAC) ◄─┐
81//! │ │
82//! ▼ │
83//! Wait 80 ms │
84//! │ │
85//! ▼ │
86//! Read status byte ◄──┐ │
87//! │ │ │
88//! ▼ │ │
89//! Status::Busy ───► Yes │
90//! │ │
91//! ▼ │
92//! No │
93//! │ │
94//! ▼ │
95//! Read 7 bytes │
96//! │ │
97//! ▼ │
98//! Calculate CRC │
99//! │ │
100//! ▼ │
101//! CRC good ─► No ─────────┘
102//! │ ▲
103//! ▼ │
104//! Yes │
105//! │ │
106//! ▼ │
107//! CRC-checked Ready ─► No ─────┘
108//! │
109//! ▼
110//! Yes
111//! │
112//! ▼
113//! Calc Humidity and Temp
114//! ```
115
116use crc_any::CRCu8;
117use embedded_hal::delay::DelayNs;
118use embedded_hal::i2c::I2c;
119
120/// AHT20 sensor's I2C address.
121pub const SENSOR_ADDRESS: u8 = 0b0011_1000; // This is I2C address 0x38;
122
123/// Commands that can be sent to the AHT20 sensor.
124///
125/// Note that a few of these take parameters but that there are no explanations provided about what
126/// those parameters actually are. You should consider the command and specified parameters to be
127/// just one three-byte command. These can be found in the datasheet, Section 5.3, page 8, Table 9.
128pub enum Command {
129 Initialize = 0b1011_1110, // 0xBE, Initialize and calibrate the sensor.
130 // This command takes two bytes of parameter: 0b0000_1000 (0x08), then 0b0000_0000 (0x00).
131 Calibrate = 0b1110_0001, // 0xE1, Calibrate - or return calibration status.
132 // Status will be Status::Calibrated, where bit4 indicates calibrated. If it's 0, it's not.
133 TriggerMeasurement = 0b1010_1100, // 0xAC
134 // This command takes two bytes of parameter: 0b00110011 (0x33), then 0b0000_0000 (0x00).
135 // Wait 80ms for the measurement. You'll get a status byte back. Check the status for
136 // Status::Busy to be 0. If it is, then read 7 bytes. A status byte, 5 data plus a byte of CRC.
137 SoftReset = 0b1011_1010, // 0xBA
138 // Also see Section 5.5. This takes 20ms or less to complete.
139}
140
141/// Status byte meanings.
142///
143/// Table 10, page 8 of the datasheet.
144pub enum Status {
145 Busy = 0b1000_0000, // Status bit for busy - 8th bit enabled. 1<<7, 0x80
146 // 1 is Busy measuring. 0 is "Free in dormant state" or "ready".
147 Calibrated = 0b0000_1000, // Status bit for calibrated - 4th bit enabled. 1<<3, 0x08.
148 // 1 is Calibrated, 0 is uncalibrated. If 0, send Command::Initialize.
149}
150
151/// SensorStatus is the response from the sensor indicating if it is ready to read from, and if it
152/// is calibrated.
153///
154/// This is returned from the `check_status` method. It is used both
155/// during initialization, which is when the sensor caibrates itself, and during
156/// measure. During measure the sensor will report itself as busy (not ready)
157/// for a period of 80ms.
158#[derive(Debug, Clone, Copy)]
159pub struct SensorStatus(pub u8);
160
161impl SensorStatus {
162 /// Create a new SensorStatus from an AHT20 status byte.
163 ///
164 /// That byte comes from the `check_status` method.
165 pub fn new(status: u8) -> Self {
166 SensorStatus(status)
167 }
168
169 /// Check if the sensor is ready to have data read from it. After issuing a sensor read, you
170 /// must check is_ready before reading the result. The measure function takes care of this wait
171 /// and check.
172 pub fn is_ready(self) -> bool {
173 // The busy bit should be 0 (not busy) for the sensor to report ready.
174 (self.0 & Status::Busy as u8) == 0
175 }
176
177 /// Check if the sensor is calibrated. If it is not, you must call `init` to initialize the
178 /// sensor.
179 pub fn is_calibrated(self) -> bool {
180 // The calibrated bit should be set.
181 (self.0 & Status::Calibrated as u8) != 0
182 }
183}
184
185/// SensorReading is a single reading from the AHT20 sensor.
186///
187/// This is returned from the `measure` method. You get:
188/// * humidity in % Relative Humidity
189/// * temperature in degrees Celsius.
190#[derive(Debug, Clone, Copy)]
191pub struct SensorReading {
192 pub humidity: f32,
193 pub temperature: f32,
194}
195
196impl SensorReading {
197 /// Create a SensorReading from the data returned by the sensor.
198 ///
199 /// This is done by the `measure` method.
200 fn from_bytes(sensor_data: [u8; 5]) -> Self {
201 let (humidity_val, temperature_val) = SensorReading::raw_from_bytes(sensor_data);
202
203 // From section 6.1 "Relative humidity transformation" here is how we turn this into
204 // a relative humidity percantage value.
205 let humidity_percent = (humidity_val as f32) / ((1 << 20) as f32) * 100.0;
206
207 // From section 6.2 "Temperature transformation" here is how we turn this into
208 // a temprature in °C.
209 let temperature_celcius = (temperature_val as f32) / ((1 << 20) as f32) * 200.0 - 50.0;
210
211 SensorReading {
212 humidity: humidity_percent,
213 temperature: temperature_celcius,
214 }
215 }
216
217 /// Identical to `from_bytes`, but doesn't use floating point math.
218 ///
219 /// This limits the precision to just integer values, but doesn't bring in floating point
220 /// libraries on microcontrollers with no FP support, saving space and being faster.
221 fn from_bytes_no_fp(sensor_data: [u8; 5]) -> Self {
222 let (humidity_val, temperature_val) = SensorReading::raw_from_bytes(sensor_data);
223
224 // From section 6.1 "Relative humidity transformation" here is how we turn this into
225 // a relative humidity percantage value.
226 let humidity_percent = (100 * humidity_val) >> 20;
227
228 // From section 6.2 "Temperature transformation" here is how we turn this into
229 // a temprature in °C.
230 let temperature_celcius = (((200 * temperature_val) >> 20) - 50) as f32;
231
232 SensorReading {
233 humidity: humidity_percent as f32,
234 temperature: temperature_celcius,
235 }
236 }
237
238 /// Turn the raw data from a sensor reding into two u32 raw values.
239 ///
240 /// These values still need to be converted into %age humidity and degrees C, this is done with
241 /// `from_bytes` and `from_bytes_no_fp`. This is just a helper method for the aforementioned.
242 fn raw_from_bytes(sensor_data: [u8; 5]) -> (u32, u32) {
243 // Our five bytes of sensor data is split into 20 bits (two and a half bytes) humidity and
244 // 20 bits temperature. We'll have to spend a bit of time splitting the middle byte up.
245 let humidity_bytes: &[u8] = &sensor_data[..2];
246 let split_byte: u8 = sensor_data[2];
247 let temperature_bytes: &[u8] = &sensor_data[3..5];
248
249 // We have a byte that might look like 0x0101_1010, we want only the first four bits, (the
250 // 0101) to be at the end of the byte. So we shift them four right and end up with
251 // 0x0000_0101. These 4 bits go at the very end of our 20-bit humidity value.
252 // In the final 32-bit value they're these ones: 0x0000_0000_0000_0000_0000_0000_0000_1111
253 let right_bits_humidity: u32 = (split_byte >> 4).into();
254 // In the final 32-bit value they're these ones: 0x0000_0000_0000_1111_1111_0000_0000_0000
255 let left_bits_humidity: u32 = (humidity_bytes[0] as u32) << 12;
256 // In the final 32-bit value they're these ones: 0x0000_0000_0000_0000_0000_1111_1111_0000
257 let middle_bits_humidity: u32 = (humidity_bytes[1] as u32) << 4;
258 // We combine them to form the complete 20 bits: 0x0000_0000_0000_1111_1111_1111_1111_1111
259 let humidity_val: u32 = left_bits_humidity | middle_bits_humidity | right_bits_humidity;
260
261 // With that same example byte - we want to keep only the last four bits this time, so we
262 // mask the first four and end up with 0x0000_1010. These bits end up at the very start of
263 // our 20-bit temperature value. In the final 32-bit value they're these ones:
264 // 0x0000_0000_0000_1111_0000_0000_0000_0000 To get them into their final position - we'll
265 // left-shift them by 16 positions.
266 let split_byte_temperature: u32 = (split_byte & 0b0000_1111).into();
267 // We need to fill the rightmost 20 bits, starting with our split byte
268 // In the final 32-bit value they're these ones: 0x0000_0000_0000_1111_0000_0000_0000_0000
269 let left_bits_temp: u32 = split_byte_temperature << 16;
270 // In the final 32-bit value they're these ones: 0x0000_0000_0000_0000_1111_1111_0000_0000
271 let middle_bits_temp: u32 = (temperature_bytes[0] as u32) << 8;
272 // And just for symmetry...
273 // In the final 32-bit value they're these ones: 0x0000_0000_0000_0000_0000_0000_1111_1111
274 let right_bits_temp: u32 = temperature_bytes[1] as u32;
275 // We combine them to form the complete 20 bits: 0x0000_0000_0000_1111_1111_1111_1111_1111
276 let temperature_val: u32 = left_bits_temp | middle_bits_temp | right_bits_temp;
277
278 (humidity_val, temperature_val)
279 }
280}
281
282/// Driver errors.
283#[derive(Debug, PartialEq)]
284pub enum Error<E> {
285 /// I2C bus error
286 I2c(E),
287 /// CRC validation failed
288 InvalidCrc,
289 /// Unexpectedly not ready - this can happen when the sensor sends back "busy" but the
290 /// I2C data gets corrupted and we receive "ready", then later the
291 /// CRC-checked status byte correctly reports "busy" and we have to abort the measurement.
292 UnexpectedBusy,
293 /// Errors such as overflowing the stack.
294 Internal,
295}
296
297impl<E> core::fmt::Display for Error<E> {
298 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
299 match self {
300 Error::I2c(_e) => write!(f, "I2C error"),
301 Error::InvalidCrc => write!(f, "invalid CRC error"),
302 Error::UnexpectedBusy => write!(f, "unexpected busy error"),
303 Error::Internal => write!(f, "internal ATH20 driver error"),
304 }
305 }
306}
307
308impl<E> core::error::Error for Error<E> where E: core::fmt::Debug {}
309
310/// An AHT20 sensor on the I2C bus `I`.
311///
312/// The address of the sensor will be `SENSOR_ADDRESS` from this package, unless there is some kind
313/// of special address translating hardware in use.
314pub struct AHT20<I>
315where
316 I: I2c,
317{
318 i2c: I,
319 address: u8,
320}
321
322impl<I> AHT20<I>
323where
324 I: I2c,
325{
326 /// Initializes the AHT20 driver.
327 ///
328 /// This consumes the I2C bus `I`. Before you can get temperature and humidity measurements,
329 /// you must call the `init` method which calibrates the sensor. The address will almost always
330 /// be `SENSOR_ADDRESS` from this crate.
331 pub fn new(i2c: I, address: u8) -> Self {
332 AHT20 { i2c, address }
333 }
334
335 /// Run the AHT20 init and calibration routines.
336 ///
337 /// This must be called before any other methods except `check_status`. This method will take
338 /// *at least* 40ms to return.
339 ///
340 /// ```text
341 /// Start (Power on)
342 /// │
343 /// ▼
344 /// Wait 40 ms
345 /// │
346 /// ▼
347 /// Read status byte ◄─── Wait 10 ms
348 /// │ ▲
349 /// ▼ │
350 /// Status::Calibrated ──► No ──► Command::Initialize (0xBE)
351 /// │
352 /// ▼
353 /// Yes
354 /// ```
355 pub fn init(
356 &mut self,
357 delay: &mut impl DelayNs,
358 ) -> Result<AHT20Initialized<I>, Error<I::Error>> {
359 delay.delay_ms(40);
360
361 while !self.check_status()?.is_calibrated() {
362 self.send_initialize()?;
363 #[cfg(feature = "use-defmt")]
364 defmt::debug!("init: waiting for sensor to report being calibrated, 10ms.");
365 delay.delay_ms(10);
366 }
367
368 #[cfg(feature = "use-defmt")]
369 defmt::debug!("init: sensor reporting being calibrated, init done.");
370 Ok(AHT20Initialized { aht20: self })
371 }
372
373 /// check_Status reads a status byte from the AHT20 sensor to check its status.
374 ///
375 /// The sensor can be calibrated or not, also busy generating a sensor measurement or ready.
376 /// This method returns the SensorStatus struct, which you can use to determine what the state
377 /// of the sensor is.
378 ///
379 /// NOTE: The documentation suggests that we send a CheckStatus (0x71) command, followed by a
380 /// read. Experience (https://github.com/anglerud/aht20-driver/pull/10) indicates that we
381 /// can create a hang writing that command, and that just reading a status byte works.
382 ///
383 /// This is used by both measure_once and init.
384 fn check_status(&mut self) -> Result<SensorStatus, Error<I::Error>> {
385 #[cfg(feature = "use-defmt")]
386 defmt::debug!("check_status: requesting a status check from sensor.");
387 let mut read_buffer = [0u8; 1];
388
389 self.i2c
390 .read(self.address, &mut read_buffer)
391 .map_err(Error::I2c)?;
392
393 let status_byte = read_buffer[0];
394 Ok(SensorStatus::new(status_byte))
395 }
396
397 /// send_initialize sends the Initialize command to the sensor which make it calibrate.
398 ///
399 /// After sending initialize, there is a required 40ms wait period and verification
400 /// that the sensor reports itself calibrated. See the `init` method.
401 fn send_initialize(&mut self) -> Result<(), Error<I::Error>> {
402 #[cfg(feature = "use-defmt")]
403 defmt::debug!("send_initialize: requesting sensor to initialize itself.");
404 let command: [u8; 3] = [
405 // Initialize = 0b1011_1110. Equivalent to 0xBE, Section 5.3, page 8, Table 9
406 Command::Initialize as u8,
407 // Two parameters as described in the datasheet. There is no indication what these
408 // parameters mean, just that they should be provided. There is also no returned
409 // value.
410 0b0000_1000, // 0x08
411 0b0000_0000, // 0x00
412 ];
413
414 self.i2c.write(self.address, &command).map_err(Error::I2c)?;
415
416 Ok(())
417 }
418
419 /// Destroys this driver and releases the I2C bus `I`
420 pub fn destroy(self) -> I {
421 self.i2c
422 }
423}
424
425/// AHT20Initialized is returned by AHT20::init() and the sensor is ready to read from.
426///
427/// In this state you can trigger a measurement with `.measure(&mut delay)`.
428pub struct AHT20Initialized<'a, I>
429where
430 I: I2c,
431{
432 aht20: &'a mut AHT20<I>,
433}
434
435impl<'a, I> AHT20Initialized<'a, I>
436where
437 I: I2c,
438{
439 /// Measure temperature and humidity.
440 ///
441 /// This masurement takes at least 80ms to complete. Together with the `measure_once` method,
442 /// this is the work being carried out:
443 ///
444 /// ```text
445 /// Command::TriggerMeasurement (0xAC) ◄─┐
446 /// │ │
447 /// ▼ │
448 /// Wait 80 ms │
449 /// │ │
450 /// ▼ │
451 /// Read status byte ◄──┐ │
452 /// │ │ │
453 /// ▼ │ │
454 /// Status::Busy ───► Yes │
455 /// │ │
456 /// ▼ │
457 /// No │
458 /// │ │
459 /// ▼ │
460 /// Read 7 bytes │
461 /// │ │
462 /// ▼ │
463 /// Calculate CRC │
464 /// │ │
465 /// ▼ │
466 /// CRC good ─► No ─────────┘
467 /// │ ▲
468 /// ▼ │
469 /// Yes │
470 /// │ │
471 /// ▼ │
472 /// CRC-checked Ready ─► No ─────┘
473 /// │
474 /// ▼
475 /// Yes
476 /// │
477 /// ▼
478 /// Calc Humidity and Temp
479 /// ```
480 pub fn measure(&mut self, delay: &mut impl DelayNs) -> Result<SensorReading, Error<I::Error>> {
481 loop {
482 let measurement_result = self.measure_once(delay);
483 match measurement_result {
484 Ok(sb) => {
485 return Ok(SensorReading::from_bytes(sb));
486 }
487 Err(Error::InvalidCrc) => {
488 // CRC failed to validate, we'll go back and issue another read request.
489 #[cfg(feature = "use-defmt")]
490 defmt::error!("Invalid CRC, retrying.");
491 }
492 Err(Error::UnexpectedBusy) => {
493 // Possibly indicates the previously seen 'ready' was due to uncorrected noise.
494 #[cfg(feature = "use-defmt")]
495 defmt::error!("Sensor contradicted a ready status with a crc-checked busy.");
496 }
497 Err(other) => return Err(other),
498 }
499 }
500 }
501
502 /// This is identical to `measure`, except it doesn't use floating point math.
503 ///
504 /// This means it can be used on microcontrollers with no FP support, without having
505 /// to bring in floating point math functions, which can take up a lot of space, and
506 /// might be slow.
507 ///
508 /// The drawback is that precision is limited to only integer values.
509 pub fn measure_no_fp(
510 &mut self,
511 delay: &mut impl DelayNs,
512 ) -> Result<SensorReading, Error<I::Error>> {
513 // TODO: See if we can refactor here, the only difference is from_bytes and
514 // from_bytes_no_fp.
515 loop {
516 let measurement_result = self.measure_once(delay);
517 match measurement_result {
518 Ok(sb) => {
519 return Ok(SensorReading::from_bytes_no_fp(sb));
520 }
521 Err(Error::InvalidCrc) => {
522 // CRC failed to validate, we'll go back and issue another read request.
523 #[cfg(feature = "use-defmt")]
524 defmt::error!("Invalid CRC, retrying.");
525 }
526 Err(Error::UnexpectedBusy) => {
527 // Possibly indicates the previously seen 'ready' was due to uncorrected noise.
528 #[cfg(feature = "use-defmt")]
529 defmt::error!("Sensor contradicted a ready status with a crc-checked busy.");
530 }
531 Err(other) => return Err(other),
532 }
533 }
534 }
535
536 /// Perform one measurement and return the sensor's 5 raw data bytes.
537 ///
538 /// This takes at least 80ms to complete, and only returns 2x20 bits in 5 bytes.
539 /// This data is interpreted by the `measure` function.
540 fn measure_once(&mut self, delay: &mut impl DelayNs) -> Result<[u8; 5], Error<I::Error>> {
541 self.send_trigger_measurement()?;
542 delay.delay_ms(80);
543
544 // Wait for measurement to be ready
545 while !self.aht20.check_status()?.is_ready() {
546 #[cfg(feature = "use-defmt")]
547 defmt::debug!("measure_once: waiting for ready, 1ms.");
548 delay.delay_ms(1);
549 }
550
551 // 1 byte status, 20 bits humidity + 20 bits temperature, 1 byte CRC
552 let mut read_buffer = [0u8; 7];
553 self.aht20
554 .i2c
555 .read(self.aht20.address, &mut read_buffer)
556 .map_err(Error::I2c)?;
557
558 let data: &[u8] = &read_buffer[..6];
559 let crc_byte: u8 = read_buffer[6];
560
561 let crc = compute_crc(data);
562 if crc_byte != crc {
563 return Err(Error::InvalidCrc);
564 }
565
566 // The first byte of the sensor's response is a repeat of the status byte.
567 // There is a minescule chance that the previous ready message was caused
568 // by noise on the i2c bus. This byte has been CRC-checked.
569 let status = SensorStatus::new(read_buffer[0]);
570 if !status.is_ready() {
571 return Err(Error::UnexpectedBusy);
572 }
573
574 // Arrays implement TryFrom for slices. In case the length of the slice does not match
575 // the requested array - it will return a TryFromSliceError, but we are selecting the
576 // right number of bytes so there is no risk. Mapping to a generic error.
577 data[1..6].try_into().map_err(|_| Error::Internal)
578 }
579
580 /// Send the "Trigger Measurement" command to the sensor.
581 ///
582 /// This does not return anything, it only instructs the sensor to get the data ready. After
583 /// sending this command, you need to wait 80ms before attempting to read data back. See the
584 /// `measure_once` function and the flowchart at the top of this file.
585 fn send_trigger_measurement(&mut self) -> Result<(), Error<I::Error>> {
586 // TriggerMeasurement is 0b1010_1100. Equivalent to 0xAC: Section 5.3, page 8, Table 9
587 // This command takes two bytes of parameter: 0b00110011 (0x33), then 0b0000_0000 (0x00).
588 let command: [u8; 3] = [
589 Command::TriggerMeasurement as u8,
590 // Two parameters as described in the datasheet. There is no indication what these
591 // parameters mean, just that they should be provided. There is no returned value.
592 // To get the measurement, see [measure](measure).
593 0b0011_0011, // 0x33
594 0b0000_0000, // 0x00
595 ];
596
597 self.aht20
598 .i2c
599 .write(self.aht20.address, &command)
600 .map_err(Error::I2c)?;
601
602 Ok(())
603 }
604
605 /// Send the Soft Reset command to the sensor.
606 ///
607 /// This performs a soft reset, it's unclear when this might be needed. It takes 20ms to
608 /// complete and returns nothing.
609 pub fn soft_reset(&mut self, delay: &mut impl DelayNs) -> Result<(), Error<I::Error>> {
610 // SoftReset is 0b1011_1010. Equivalent to 0xBA, Section 5.3, page 8, Table 9.
611 let command: [u8; 1] = [Command::SoftReset as u8];
612
613 self.aht20
614 .i2c
615 .write(self.aht20.address, &command)
616 .map_err(Error::I2c)?;
617 // The datasheet in section 5.5 says there is a guarantee that the reset time does
618 // not exceed 20ms. We wait the full 20ms to ensure you can trigger a measurement
619 // immediately after this function.
620 delay.delay_ms(20);
621
622 Ok(())
623 }
624
625 /// Destroys this initialized driver and lets you release the I2C bus `I`
626 pub fn destroy(self) -> &'a mut AHT20<I> {
627 self.aht20
628 }
629}
630
631/// compute_crc uses the CRCu8 algoritm from crc-any. The parameter choice makes this a
632/// "CRC-8-Dallas/Maxim".
633///
634/// The CRC invocation takes some parameters, which we get from the datasheet:
635/// https://cdn-learn.adafruit.com/assets/assets/000/091/676/original/AHT20-datasheet-2020-4-16.pdf?1591047915
636/// Section 5.4.4:
637///
638/// > CRC initial vaue is 0xFF, crc8 check polynomial CRC[7:0]=1+x**4 + x**5 + x**8
639///
640/// https://en.wikipedia.org/wiki/Cyclic_redundancy_check#Polynomial_representations_of_cyclic_redundancy_checks
641/// You can find it in the table on wikipedia, under "CRC-8-Dallas/Maxim", 1-Wire bus.
642///
643/// This article explains how we get from `CRC[7:0]=1 + x**4 + x**5 + x**8` to `0x31` as the hex
644/// representation: http://www.sunshine2k.de/articles/coding/crc/understanding_crc.html#ch72
645///
646/// The **N is the Nth bit (zero indexed).
647/// > The most significant bit [(x**8)] is left out in the hexadecimal representation
648///
649/// That the leaves bit 0 (the +1 we do), 4, 5. So that gives us:
650///
651/// ```python
652/// >>> hex(0x00110001)
653/// '0x31'
654/// ```
655///
656/// This is also what Knurling's test driver crate uses.
657/// https://github.com/knurling-rs/test-driver-crate-example/blob/main/src/lib.rs#L59
658/// which indicates this is either an I2C thing, or a common driver default as CRC parameters.
659fn compute_crc(bytes: &[u8]) -> u8 {
660 // Poly (0x31), bits (8), initial (0xff), final_xor (0x00), reflect (false).
661 let mut crc = CRCu8::create_crc(0x31, 8, 0xff, 0x00, false);
662 crc.digest(bytes);
663 crc.get_crc()
664}
665
666#[cfg(test)]
667mod tests {
668 use super::{AHT20Initialized, Error, AHT20, SENSOR_ADDRESS};
669 use embedded_hal_mock::eh1::delay::NoopDelay as MockDelay;
670 use embedded_hal_mock::eh1::i2c::Mock as I2cMock;
671 use embedded_hal_mock::eh1::i2c::Transaction;
672
673 /// Test SensorStatus reporting being ready.
674 #[test]
675 fn sensorstatus_is_ready() {
676 let status = super::SensorStatus::new(0x00);
677 assert!(status.is_ready());
678 }
679
680 /// Test SensorStatus reporting being busy.
681 #[test]
682 fn sensorstatus_is_not_ready() {
683 // 8th bit being 1 signifies "busy"
684 // Equiv to 0x01 << 7, or 128 (dec) or 0x80 (hex)
685 let status = super::SensorStatus::new(0b1000_0000);
686 assert!(!status.is_ready());
687 }
688
689 /// Test SensorStatus reporting being calibrated.
690 #[test]
691 fn sensorstatus_is_calibrated() {
692 // 4th bit being 1 signifies the sensor being calibrated.
693 // Equiv to 0x01 << 3, or 8 (dec) or 0x08
694 let status = super::SensorStatus::new(0b0000_1000);
695 assert!(status.is_calibrated());
696 }
697
698 /// Test SensorStatus reporting being uncalibrated.
699 #[test]
700 fn sensorstatus_is_not_calibrated() {
701 let status = super::SensorStatus::new(0b0000_0000);
702 assert!(!status.is_calibrated());
703 }
704
705 /// Test creating new AHT20 sensors.
706 ///
707 /// Test that we can create multiple AHT20 devices. We test this because it's one of the
708 /// measures of success for this driver.
709 #[test]
710 fn aht20_new() {
711 // In the real app we'd used shared-bus to share the i2c bus between the two drivers, but
712 // I think this is fine for a test.
713 let mock_i2c_1 = I2cMock::new(&[]);
714 let mock_i2c_2 = I2cMock::new(&[]);
715
716 let aht20_1 = AHT20::new(mock_i2c_1, SENSOR_ADDRESS);
717 let aht20_2 = AHT20::new(mock_i2c_2, SENSOR_ADDRESS);
718
719 aht20_1.destroy().done();
720 aht20_2.destroy().done();
721 }
722
723 /// Test reading a status byte.
724 #[test]
725 fn check_status() {
726 let expectations = vec![Transaction::read(SENSOR_ADDRESS, vec![0b0000_1000])];
727 let mock_i2c = I2cMock::new(&expectations);
728
729 let mut aht20 = AHT20::new(mock_i2c, SENSOR_ADDRESS);
730 let status = aht20.check_status().unwrap();
731 assert!(status.is_calibrated());
732
733 let mut mock = aht20.destroy();
734 mock.done(); // verify expectations
735 }
736
737 /// Test sending the i2c Initialize command.
738 #[test]
739 fn send_initialize() {
740 let expectations = vec![Transaction::write(
741 SENSOR_ADDRESS,
742 vec![
743 super::Command::Initialize as u8,
744 0b0000_1000, // 0x08
745 0b0000_0000, // 0x00
746 ],
747 )];
748 let mock_i2c = I2cMock::new(&expectations);
749
750 let mut aht20 = AHT20::new(mock_i2c, SENSOR_ADDRESS);
751 aht20.send_initialize().unwrap();
752
753 let mut mock = aht20.destroy();
754 mock.done(); // verify expectations
755 }
756
757 /// Initialize sensor, with the sensor reporting calibrated immediately.
758 ///
759 /// No call to send_initialize will be required.
760 #[test]
761 fn init_with_calibrated_sensor() {
762 // This test has check_status return an already calibrated sensor. This means
763 // that send_initialize is not called.
764 let expectations = vec![
765 // 4th bit being 1 signifies the sensor being calibrated.
766 // Equiv to 0x01 << 3, or 8 (dec) or 0x08
767 Transaction::read(SENSOR_ADDRESS, vec![0b0000_1000]),
768 ];
769 let mock_i2c = I2cMock::new(&expectations);
770 let mut mock_delay = MockDelay::new();
771
772 let mut aht20 = AHT20::new(mock_i2c, SENSOR_ADDRESS);
773 aht20.init(&mut mock_delay).unwrap();
774
775 let mut mock = aht20.destroy();
776 mock.done(); // verify expectations
777 }
778
779 /// Initialize sensor, with a report of an uncalibrated sensor.
780 ///
781 /// The sensor will report being uncalibrated once, then after initialization the sensor will
782 /// report being calibrated.
783 #[test]
784 fn init_with_uncalibrated_sensor() {
785 // This test has check_status return an uncalibrated sensor. With that, a call
786 // to send_initialize is done to initialize and calibrate the sensor. A second
787 // call to check_status verifies the new calibrated status.
788 let expectations = vec![
789 // 4th bit being 0 signifies the sensor not being calibrated.
790 Transaction::read(SENSOR_ADDRESS, vec![0b0000_0000]),
791 // This is send_initialize
792 Transaction::write(
793 SENSOR_ADDRESS,
794 vec![
795 super::Command::Initialize as u8,
796 0b0000_1000, // 0x08
797 0b0000_0000, // 0x00
798 ],
799 ),
800 // One more check_status will be called, this time with the 4th bit set
801 // to 1 - signifying the sensor is now calibrated and we can finish the init.
802 Transaction::read(SENSOR_ADDRESS, vec![0b0000_1000]),
803 ];
804 let mock_i2c = I2cMock::new(&expectations);
805 let mut mock_delay = MockDelay::new();
806
807 let mut aht20 = AHT20::new(mock_i2c, SENSOR_ADDRESS);
808 aht20.init(&mut mock_delay).unwrap();
809
810 let mut mock = aht20.destroy();
811 mock.done(); // verify expectations
812 }
813
814 /// Test sending the i2c SoftReset command.
815 #[test]
816 fn soft_reset() {
817 let expectations = vec![Transaction::write(
818 SENSOR_ADDRESS,
819 vec![super::Command::SoftReset as u8],
820 )];
821 let mock_i2c = I2cMock::new(&expectations);
822 let mut mock_delay = MockDelay::new();
823
824 let mut aht20 = AHT20::new(mock_i2c, SENSOR_ADDRESS);
825 let mut aht20_init = AHT20Initialized { aht20: &mut aht20 };
826 aht20_init.soft_reset(&mut mock_delay).unwrap();
827
828 let mut mock = aht20.destroy();
829 mock.done(); // verify expectations
830 }
831
832 /// Test sending the i2c TriggerMeasurement command.
833 #[test]
834 fn send_trigger_measurement() {
835 let expectations = vec![Transaction::write(
836 SENSOR_ADDRESS,
837 vec![
838 super::Command::TriggerMeasurement as u8,
839 0b0011_0011, // 0x33
840 0b0000_0000, // 0x00
841 ],
842 )];
843 let mock_i2c = I2cMock::new(&expectations);
844
845 let mut aht20 = AHT20::new(mock_i2c, SENSOR_ADDRESS);
846 let mut aht20_init = AHT20Initialized { aht20: &mut aht20 };
847 aht20_init.send_trigger_measurement().unwrap();
848
849 let mut mock = aht20.destroy();
850 mock.done(); // verify expectations
851 }
852
853 /// Measure once, sensor reports ready at once.
854 ///
855 /// No wait is needed in this scenario.
856 #[test]
857 fn measure_once_immediately_ready() {
858 let expectations = vec![
859 // send_trigger_measurement
860 Transaction::write(
861 SENSOR_ADDRESS,
862 vec![
863 super::Command::TriggerMeasurement as u8,
864 0b0011_0011, // 0x33
865 0b0000_0000, // 0x00
866 ],
867 ),
868 // check_status called. 4th bit set to to 1 - signifying the sensor is calibrated 8th
869 // bit set to 0 (not busy), signalling that a measurement is ready for us to read.
870 Transaction::read(SENSOR_ADDRESS, vec![0b0000_1000]),
871 // We can now read 7 bytes. status byte, 5 data bytes, crc byte.
872 // These are taken from a run of the sensor.
873 Transaction::read(
874 SENSOR_ADDRESS,
875 vec![
876 0b0001_1100, // 28, 0x1c - ready, calibrated, and some mystery flags.
877 // bit 8 set to 0 is ready. bit 4 set is calibrated. bit 5
878 // and 3 are described as 'reserved'.
879 0b0110_0101, // 101, 0x65 - first byte of humidity value
880 0b1011_0100, // 180, 0xb4 - second byte of humidity vaue
881 0b0010_0101, // 37, 0x25 - split byte. 4 bits humidity, 4 bits temperature.
882 0b1100_1101, // 205, 0xcd - first full byte of temperature.
883 0b0010_0110, // 38, 0x26 - second full byte of temperature.
884 0b1100_0110, // 198, 0xc6 - CRC
885 ],
886 ),
887 ];
888 let mock_i2c = I2cMock::new(&expectations);
889 let mut mock_delay = MockDelay::new();
890
891 let mut aht20 = AHT20::new(mock_i2c, SENSOR_ADDRESS);
892 let mut aht20_init = AHT20Initialized { aht20: &mut aht20 };
893 aht20_init.measure_once(&mut mock_delay).unwrap();
894
895 let mut mock = aht20.destroy();
896 mock.done(); // verify expectations
897 }
898
899 /// Measure once, sensor erroniously reports ready at once, then correctly reports
900 /// busy in the CRC-checked status byte causing an error.
901 ///
902 /// No wait is needed in this scenario.
903 #[test]
904 fn measure_once_ready_misreported() {
905 let expectations = vec![
906 // send_trigger_measurement
907 Transaction::write(
908 SENSOR_ADDRESS,
909 vec![
910 super::Command::TriggerMeasurement as u8,
911 0b0011_0011, // 0x33
912 0b0000_0000, // 0x00
913 ],
914 ),
915 // check_status called. 4th bit set to to 1 - signifying the sensor is calibrated 8th
916 // bit set to 0 (not busy), signalling that a measurement is ready for us to read.
917 // NOTE: This read says we're not busy, that is "ready".
918 Transaction::read(SENSOR_ADDRESS, vec![0b0000_1000]),
919 // We can now read 7 bytes. status byte, 5 data bytes, crc byte.
920 // These are taken from a run of the sensor.
921 Transaction::read(
922 SENSOR_ADDRESS,
923 vec![
924 0b1001_1100, // 156, 0x9c - busy, calibrated, and some mystery flags.
925 // bit 8 set to 1 is busy. bit 4 set is calibrated. bit 5
926 // and 3 are described as 'reserved'.
927 // NOTE: this says busy, contradicting the ready above.
928 0b0110_0101, // 101, 0x65 - first byte of humidity value
929 0b1011_0100, // 180, 0xb4 - second byte of humidity vaue
930 0b0010_0101, // 37, 0x25 - split byte. 4 bits humidity, 4 bits temperature.
931 0b1100_1101, // 205, 0xcd - first full byte of temperature.
932 0b0010_0110, // 38, 0x26 - second full byte of temperature.
933 0b0010_1010, // 424, 0x2a - CRC
934 ],
935 ),
936 ];
937 let mock_i2c = I2cMock::new(&expectations);
938 let mut mock_delay = MockDelay::new();
939
940 let mut aht20 = AHT20::new(mock_i2c, SENSOR_ADDRESS);
941 let mut aht20_init = AHT20Initialized { aht20: &mut aht20 };
942 // We received a ready from the check_status method, then a busy in the CRC-checked
943 // status byte - and therefore we got the UnexpectedBusy.
944 assert_eq!(
945 aht20_init.measure_once(&mut mock_delay),
946 Err(Error::UnexpectedBusy)
947 );
948
949 let mut mock = aht20.destroy();
950 mock.done(); // verify expectations
951 }
952
953 /// Measure once, with a wait inserted.
954 ///
955 /// We signal via check_status that a wait should be inserted before another attempt to read
956 /// data from the sensor is made.
957 #[test]
958 fn measure_once_wait_once() {
959 let expectations = vec![
960 // send_trigger_measurement
961 Transaction::write(
962 SENSOR_ADDRESS,
963 vec![
964 super::Command::TriggerMeasurement as u8,
965 0b0011_0011, // 0x33
966 0b0000_0000, // 0x00
967 ],
968 ),
969 // check_status called. 4th bit set to to 1 - signifying the sensor is calibrated 8th
970 // bit set to 1 (busy), signalling that we should wait for the sensor.
971 Transaction::read(SENSOR_ADDRESS, vec![0b1000_1000]),
972 // Next time round, we say that the sensor is good to go.
973 Transaction::read(SENSOR_ADDRESS, vec![0b0000_1000]),
974 // We can now read 7 bytes. status byte, 5 data bytes, crc byte.
975 // These are taken from a run of the sensor.
976 Transaction::read(
977 SENSOR_ADDRESS,
978 vec![
979 0b0001_1100, // 28, 0x1c - ready, calibrated, and some mystery flags.
980 // bit 8 set to 0 is ready. bit 4 set is calibrated. bit 5
981 // and 3 are described as 'reserved'.
982 0b0110_0101, // 101, 0x65 - first byte of humidity value
983 0b1011_0100, // 180, 0xb4 - second byte of humidity vaue
984 0b0010_0101, // 37, 0x25 - split byte. 4 bits humidity, 4 bits temperature.
985 0b1100_1101, // 205, 0xcd - first full byte of temperature.
986 0b0010_0110, // 38, 0x26 - second full byte of temperature.
987 0b1100_0110, // 198, 0xc6 - CRC
988 ],
989 ),
990 ];
991 let mock_i2c = I2cMock::new(&expectations);
992 let mut mock_delay = MockDelay::new();
993
994 let mut aht20 = AHT20::new(mock_i2c, SENSOR_ADDRESS);
995 let mut aht20_init = AHT20Initialized { aht20: &mut aht20 };
996 aht20_init.measure_once(&mut mock_delay).unwrap();
997
998 let mut mock = aht20.destroy();
999 mock.done(); // verify expectations
1000 }
1001
1002 /// Single measurement pass with bad CRC.
1003 ///
1004 /// Intentionally corrupt the read data to make sure we get a CRC error.
1005 #[test]
1006 fn measure_once_bad_crc() {
1007 let expectations = vec![
1008 // send_trigger_measurement
1009 Transaction::write(
1010 SENSOR_ADDRESS,
1011 vec![
1012 super::Command::TriggerMeasurement as u8,
1013 0b0011_0011, // 0x33
1014 0b0000_0000, // 0x00
1015 ],
1016 ),
1017 // Check status, and we say that the sensor is good to go.
1018 Transaction::read(SENSOR_ADDRESS, vec![0b0000_1000]),
1019 // We can now read 7 bytes. status byte, 5 data bytes, crc byte.
1020 // These are taken from a run of the sensor.
1021 Transaction::read(
1022 SENSOR_ADDRESS,
1023 vec![
1024 0b0001_1100, // 28, 0x1c - ready, calibrated, and some mystery flags.
1025 // bit 8 set to 0 is ready. bit 4 set is calibrated. bit 5
1026 // and 3 are described as 'reserved'.
1027 0b0110_0101, // 101, 0x65 - first byte of humidity value
1028 0b1011_0100, // 180, 0xb4 - second byte of humidity vaue
1029 0b0010_0101, // 37, 0x25 - split byte. 4 bits humidity, 4 bits temperature.
1030 0b1100_1101, // 205, 0xcd - first full byte of temperature.
1031 0b0010_0111, // 39, 0x27 - second full byte of temperature.
1032 // NOTE: This should be 38, 0x26, but is intentionally corrupted
1033 // so that the CRC won't match. Last bit flipped from 0 to 1.
1034 0b1100_0110, // 198, 0xc6 - CRC
1035 ],
1036 ),
1037 ];
1038 let mock_i2c = I2cMock::new(&expectations);
1039 let mut mock_delay = MockDelay::new();
1040
1041 // test and verify
1042 let mut aht20 = AHT20::new(mock_i2c, SENSOR_ADDRESS);
1043 let mut aht20_init = AHT20Initialized { aht20: &mut aht20 };
1044 match aht20_init.measure_once(&mut mock_delay) {
1045 Ok(_) => panic!("CRC is wrong and measure_once should not pass."),
1046 Err(err_type) => assert_eq!(err_type, Error::InvalidCrc),
1047 }
1048
1049 let mut mock = aht20.destroy();
1050 mock.done(); // verify expectations
1051 }
1052
1053 /// Test a measurement.
1054 ///
1055 /// This uses data from an actual sensor run.
1056 #[test]
1057 fn measure() {
1058 // setup
1059 let expectations = vec![
1060 // send_trigger_measurement
1061 Transaction::write(
1062 SENSOR_ADDRESS,
1063 vec![
1064 super::Command::TriggerMeasurement as u8,
1065 0b0011_0011, // 0x33
1066 0b0000_0000, // 0x00
1067 ],
1068 ),
1069 // check_status - with ready bit set to 'ready' (off)
1070 Transaction::read(SENSOR_ADDRESS, vec![0b0000_1000]),
1071 // We can now read 7 bytes. status byte, 5 data bytes, crc byte.
1072 // These are taken from a run of the sensor.
1073 Transaction::read(
1074 SENSOR_ADDRESS,
1075 vec![
1076 0b0001_1100, // 28, 0x1c - ready, calibrated, and some mystery flags.
1077 // bit 8 set to 0 is ready. bit 4 set is calibrated. bit 5
1078 // and 3 are described as 'reserved'.
1079 0b0110_0101, // 101, 0x65 - first byte of humidity value
1080 0b1011_0100, // 180, 0xb4 - second byte of humidity vaue
1081 0b0010_0101, // 37, 0x25 - split byte. 4 bits humidity, 4 bits temperature.
1082 0b1100_1101, // 205, 0xcd - first full byte of temperature.
1083 0b0010_0110, // 38, 0x26 - second full byte of temperature.
1084 0b1100_0110, // 198, 0xc6 - CRC
1085 ],
1086 ),
1087 ];
1088 let mock_i2c = I2cMock::new(&expectations);
1089 let mut mock_delay = MockDelay::new();
1090
1091 // test
1092 let mut aht20 = AHT20::new(mock_i2c, SENSOR_ADDRESS);
1093 let mut aht20_init = AHT20Initialized { aht20: &mut aht20 };
1094 let measurement = aht20_init.measure(&mut mock_delay).unwrap();
1095
1096 // verification
1097 let mut mock = aht20.destroy();
1098 mock.done(); // verify expectations
1099
1100 // Temp was 22.52C and humidity 39.73% when above data taken.
1101 assert!(measurement.temperature > 22.5);
1102 assert!(measurement.temperature < 22.6);
1103 assert!(measurement.humidity > 39.7 && measurement.humidity < 39.8);
1104 }
1105
1106 /// Test a measurement, without using floating point math.
1107 ///
1108 /// This uses data from an actual sensor run.
1109 #[test]
1110 fn measure_no_fp() {
1111 // setup
1112 let expectations = vec![
1113 // send_trigger_measurement
1114 Transaction::write(
1115 SENSOR_ADDRESS,
1116 vec![
1117 super::Command::TriggerMeasurement as u8,
1118 0b0011_0011, // 0x33
1119 0b0000_0000, // 0x00
1120 ],
1121 ),
1122 // check_status - with ready bit set to 'ready' (off)
1123 Transaction::read(SENSOR_ADDRESS, vec![0b0000_1000]),
1124 // We can now read 7 bytes. status byte, 5 data bytes, crc byte.
1125 // These are taken from a run of the sensor.
1126 Transaction::read(
1127 SENSOR_ADDRESS,
1128 vec![
1129 0b0001_1100, // 28, 0x1c - ready, calibrated, and some mystery flags.
1130 // bit 8 set to 0 is ready. bit 4 set is calibrated. bit 5
1131 // and 3 are described as 'reserved'.
1132 0b0110_0101, // 101, 0x65 - first byte of humidity value
1133 0b1011_0100, // 180, 0xb4 - second byte of humidity vaue
1134 0b0010_0101, // 37, 0x25 - split byte. 4 bits humidity, 4 bits temperature.
1135 0b1100_1101, // 205, 0xcd - first full byte of temperature.
1136 0b0010_0110, // 38, 0x26 - second full byte of temperature.
1137 0b1100_0110, // 198, 0xc6 - CRC
1138 ],
1139 ),
1140 ];
1141 let mock_i2c = I2cMock::new(&expectations);
1142 let mut mock_delay = MockDelay::new();
1143
1144 // test
1145 let mut aht20 = AHT20::new(mock_i2c, SENSOR_ADDRESS);
1146 let mut aht20_init = AHT20Initialized { aht20: &mut aht20 };
1147 let measurement = aht20_init.measure_no_fp(&mut mock_delay).unwrap();
1148
1149 // verification
1150 let mut mock = aht20.destroy();
1151 mock.done(); // verify expectations
1152
1153 // Temp was 22.52C and humidity 39.73% when above data taken.
1154 // No fp mode will found that to 22.0C and 39.0%.
1155 println!("temp: {:.2}", measurement.temperature);
1156 println!("humidity: {:.2}", measurement.humidity);
1157 assert!(measurement.temperature == 22.0);
1158 assert!(measurement.humidity == 39.0);
1159 }
1160
1161 /// Test a valid CRC invocation.
1162 /// Test a valid CRC invocation.
1163 #[test]
1164 fn crc_correct() {
1165 // Example from the Interface Specification document.
1166 assert_eq!(super::compute_crc(&[0xBE, 0xEF]), 0x92);
1167 }
1168
1169 /// Test a CRC call that does not match.
1170 #[test]
1171 fn crc_wrong() {
1172 // Changed example from the Interface Specification document. This should not match - the
1173 // bytes going in are changed from the known good values, but the expected result is the
1174 // same.
1175 assert_ne!(super::compute_crc(&[0xFF, 0xFF]), 0x92);
1176 }
1177}