From 48eac0b1462726ab292354b50e299c8ebfde85c6 Mon Sep 17 00:00:00 2001 From: pennae Date: Fri, 21 Jul 2023 21:01:56 +0200 Subject: [PATCH 1/4] rp: add AdcChannel trait this is more general than AdcPin and can also cover the temperature sensor. --- embassy-rp/src/adc.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/embassy-rp/src/adc.rs b/embassy-rp/src/adc.rs index 4fba3116..e0c2910b 100644 --- a/embassy-rp/src/adc.rs +++ b/embassy-rp/src/adc.rs @@ -223,21 +223,23 @@ impl interrupt::typelevel::Handler for Inter } mod sealed { - pub trait AdcPin: crate::gpio::sealed::Pin { + pub trait AdcChannel { fn channel(&mut self) -> u8; } } -pub trait AdcPin: sealed::AdcPin + gpio::Pin {} +pub trait AdcChannel: sealed::AdcChannel {} +pub trait AdcPin: AdcChannel + gpio::Pin {} macro_rules! impl_pin { ($pin:ident, $channel:expr) => { - impl sealed::AdcPin for peripherals::$pin { + impl sealed::AdcChannel for peripherals::$pin { fn channel(&mut self) -> u8 { $channel } } + impl AdcChannel for peripherals::$pin {} impl AdcPin for peripherals::$pin {} }; } From 54d31c98fe44533c955c494ea58dd16810367c4f Mon Sep 17 00:00:00 2001 From: pennae Date: Fri, 21 Jul 2023 21:33:53 +0200 Subject: [PATCH 2/4] rp: remove AdcChannel::channel we're not using it, and actually using it is more trouble than it's worth. remove the false assurance instead. --- embassy-rp/src/adc.rs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/embassy-rp/src/adc.rs b/embassy-rp/src/adc.rs index e0c2910b..38b323ec 100644 --- a/embassy-rp/src/adc.rs +++ b/embassy-rp/src/adc.rs @@ -223,28 +223,21 @@ impl interrupt::typelevel::Handler for Inter } mod sealed { - pub trait AdcChannel { - fn channel(&mut self) -> u8; - } + pub trait AdcChannel {} } pub trait AdcChannel: sealed::AdcChannel {} pub trait AdcPin: AdcChannel + gpio::Pin {} macro_rules! impl_pin { - ($pin:ident, $channel:expr) => { - impl sealed::AdcChannel for peripherals::$pin { - fn channel(&mut self) -> u8 { - $channel - } - } - + ($pin:ident) => { + impl sealed::AdcChannel for peripherals::$pin {} impl AdcChannel for peripherals::$pin {} impl AdcPin for peripherals::$pin {} }; } -impl_pin!(PIN_26, 0); -impl_pin!(PIN_27, 1); -impl_pin!(PIN_28, 2); -impl_pin!(PIN_29, 3); +impl_pin!(PIN_26); +impl_pin!(PIN_27); +impl_pin!(PIN_28); +impl_pin!(PIN_29); From b166ed6b78db0737005a65c1e444ce7563de7da3 Mon Sep 17 00:00:00 2001 From: pennae Date: Fri, 21 Jul 2023 21:31:44 +0200 Subject: [PATCH 3/4] rp: generalize adc inputs from pins to channels this lets us treat pins and the temperature sensor uniformly using the same interface. uniformity in turn lets us add more adc features without combinatorial explosion of methods and types needed to handle them all. --- embassy-rp/src/adc.rs | 102 +++++++++++++++++-------------------- embassy-rp/src/lib.rs | 1 + examples/rp/src/bin/adc.rs | 11 ++-- tests/rp/src/bin/adc.rs | 22 ++++---- 4 files changed, 66 insertions(+), 70 deletions(-) diff --git a/embassy-rp/src/adc.rs b/embassy-rp/src/adc.rs index 38b323ec..2824d893 100644 --- a/embassy-rp/src/adc.rs +++ b/embassy-rp/src/adc.rs @@ -10,8 +10,8 @@ use crate::gpio::sealed::Pin as GpioPin; use crate::gpio::{self, AnyPin, Pull}; use crate::interrupt::typelevel::Binding; use crate::interrupt::InterruptExt; -use crate::peripherals::ADC; -use crate::{interrupt, pac, peripherals, Peripheral}; +use crate::peripherals::{ADC, ADC_TEMP_SENSOR}; +use crate::{interrupt, pac, peripherals, Peripheral, RegExt}; static WAKER: AtomicWaker = AtomicWaker::new(); @@ -24,12 +24,15 @@ impl Default for Config { } } -pub struct Pin<'p> { - pin: PeripheralRef<'p, AnyPin>, +enum Source<'p> { + Pin(PeripheralRef<'p, AnyPin>), + TempSensor(PeripheralRef<'p, ADC_TEMP_SENSOR>), } -impl<'p> Pin<'p> { - pub fn new(pin: impl Peripheral

+ 'p, pull: Pull) -> Self { +pub struct Channel<'p>(Source<'p>); + +impl<'p> Channel<'p> { + pub fn new_pin(pin: impl Peripheral

+ 'p, pull: Pull) -> Self { into_ref!(pin); pin.pad_ctrl().modify(|w| { // manual says: @@ -42,24 +45,40 @@ impl<'p> Pin<'p> { w.set_pue(pull == Pull::Up); w.set_pde(pull == Pull::Down); }); - Self { pin: pin.map_into() } + Self(Source::Pin(pin.map_into())) + } + + pub fn new_sensor(s: impl Peripheral

+ 'p) -> Self { + let r = pac::ADC; + r.cs().write_set(|w| w.set_ts_en(true)); + Self(Source::TempSensor(s.into_ref())) } fn channel(&self) -> u8 { - // this requires adc pins to be sequential and matching the adc channels, - // which is the case for rp2040 - self.pin._pin() - 26 + match &self.0 { + // this requires adc pins to be sequential and matching the adc channels, + // which is the case for rp2040 + Source::Pin(p) => p._pin() - 26, + Source::TempSensor(_) => 4, + } } } -impl<'d> Drop for Pin<'d> { +impl<'p> Drop for Source<'p> { fn drop(&mut self) { - self.pin.pad_ctrl().modify(|w| { - w.set_ie(true); - w.set_od(false); - w.set_pue(false); - w.set_pde(true); - }); + match self { + Source::Pin(p) => { + p.pad_ctrl().modify(|w| { + w.set_ie(true); + w.set_od(false); + w.set_pue(false); + w.set_pde(true); + }); + } + Source::TempSensor(_) => { + pac::ADC.cs().write_clear(|w| w.set_ts_en(true)); + } + } } } @@ -115,10 +134,10 @@ impl<'d, M: Mode> Adc<'d, M> { while !r.cs().read().ready() {} } - fn sample_blocking(channel: u8) -> Result { + pub fn blocking_read(&mut self, ch: &mut Channel) -> Result { let r = Self::regs(); r.cs().modify(|w| { - w.set_ainsel(channel); + w.set_ainsel(ch.channel()); w.set_start_once(true); w.set_err(true); }); @@ -128,19 +147,6 @@ impl<'d, M: Mode> Adc<'d, M> { false => Ok(r.result().read().result().into()), } } - - pub fn blocking_read(&mut self, pin: &mut Pin) -> Result { - Self::sample_blocking(pin.channel()) - } - - pub fn blocking_read_temperature(&mut self) -> Result { - let r = Self::regs(); - r.cs().modify(|w| w.set_ts_en(true)); - while !r.cs().read().ready() {} - let result = Self::sample_blocking(4); - r.cs().modify(|w| w.set_ts_en(false)); - result - } } impl<'d> Adc<'d, Async> { @@ -172,10 +178,10 @@ impl<'d> Adc<'d, Async> { .await; } - async fn sample_async(channel: u8) -> Result { + pub async fn read(&mut self, ch: &mut Channel<'_>) -> Result { let r = Self::regs(); r.cs().modify(|w| { - w.set_ainsel(channel); + w.set_ainsel(ch.channel()); w.set_start_once(true); w.set_err(true); }); @@ -185,21 +191,6 @@ impl<'d> Adc<'d, Async> { false => Ok(r.result().read().result().into()), } } - - pub async fn read(&mut self, pin: &mut Pin<'_>) -> Result { - Self::sample_async(pin.channel()).await - } - - pub async fn read_temperature(&mut self) -> Result { - let r = Self::regs(); - r.cs().modify(|w| w.set_ts_en(true)); - if !r.cs().read().ready() { - Self::wait_for_ready().await; - } - let result = Self::sample_async(4).await; - r.cs().modify(|w| w.set_ts_en(false)); - result - } } impl<'d> Adc<'d, Blocking> { @@ -230,14 +221,17 @@ pub trait AdcChannel: sealed::AdcChannel {} pub trait AdcPin: AdcChannel + gpio::Pin {} macro_rules! impl_pin { - ($pin:ident) => { + ($pin:ident, $channel:expr) => { impl sealed::AdcChannel for peripherals::$pin {} impl AdcChannel for peripherals::$pin {} impl AdcPin for peripherals::$pin {} }; } -impl_pin!(PIN_26); -impl_pin!(PIN_27); -impl_pin!(PIN_28); -impl_pin!(PIN_29); +impl_pin!(PIN_26, 0); +impl_pin!(PIN_27, 1); +impl_pin!(PIN_28, 2); +impl_pin!(PIN_29, 3); + +impl sealed::AdcChannel for peripherals::ADC_TEMP_SENSOR {} +impl AdcChannel for peripherals::ADC_TEMP_SENSOR {} diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index 45156458..49bd3533 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -183,6 +183,7 @@ embassy_hal_internal::peripherals! { FLASH, ADC, + ADC_TEMP_SENSOR, CORE1, diff --git a/examples/rp/src/bin/adc.rs b/examples/rp/src/bin/adc.rs index 81a8b834..c5869551 100644 --- a/examples/rp/src/bin/adc.rs +++ b/examples/rp/src/bin/adc.rs @@ -7,7 +7,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_rp::adc::{Adc, Config, InterruptHandler, Pin}; +use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler}; use embassy_rp::bind_interrupts; use embassy_rp::gpio::Pull; use embassy_time::{Duration, Timer}; @@ -22,9 +22,10 @@ async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); let mut adc = Adc::new(p.ADC, Irqs, Config::default()); - let mut p26 = Pin::new(p.PIN_26, Pull::None); - let mut p27 = Pin::new(p.PIN_27, Pull::None); - let mut p28 = Pin::new(p.PIN_28, Pull::None); + let mut p26 = Channel::new_pin(p.PIN_26, Pull::None); + let mut p27 = Channel::new_pin(p.PIN_27, Pull::None); + let mut p28 = Channel::new_pin(p.PIN_28, Pull::None); + let mut ts = Channel::new_sensor(p.ADC_TEMP_SENSOR); loop { let level = adc.read(&mut p26).await.unwrap(); @@ -33,7 +34,7 @@ async fn main(_spawner: Spawner) { info!("Pin 27 ADC: {}", level); let level = adc.read(&mut p28).await.unwrap(); info!("Pin 28 ADC: {}", level); - let temp = adc.read_temperature().await.unwrap(); + let temp = adc.read(&mut ts).await.unwrap(); info!("Temp: {} degrees", convert_to_celsius(temp)); Timer::after(Duration::from_secs(1)).await; } diff --git a/tests/rp/src/bin/adc.rs b/tests/rp/src/bin/adc.rs index e659844a..9006ce8c 100644 --- a/tests/rp/src/bin/adc.rs +++ b/tests/rp/src/bin/adc.rs @@ -6,7 +6,7 @@ mod common; use defmt::*; use embassy_executor::Spawner; -use embassy_rp::adc::{Adc, Config, InterruptHandler, Pin}; +use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler}; use embassy_rp::bind_interrupts; use embassy_rp::gpio::Pull; use {defmt_rtt as _, panic_probe as _}; @@ -22,12 +22,12 @@ async fn main(_spawner: Spawner) { { { - let mut p = Pin::new(&mut p.PIN_26, Pull::Down); + let mut p = Channel::new_pin(&mut p.PIN_26, Pull::Down); defmt::assert!(adc.blocking_read(&mut p).unwrap() < 0b01_0000_0000); defmt::assert!(adc.read(&mut p).await.unwrap() < 0b01_0000_0000); } { - let mut p = Pin::new(&mut p.PIN_26, Pull::Up); + let mut p = Channel::new_pin(&mut p.PIN_26, Pull::Up); defmt::assert!(adc.blocking_read(&mut p).unwrap() > 0b11_0000_0000); defmt::assert!(adc.read(&mut p).await.unwrap() > 0b11_0000_0000); } @@ -35,21 +35,21 @@ async fn main(_spawner: Spawner) { // not bothering with async reads from now on { { - let mut p = Pin::new(&mut p.PIN_27, Pull::Down); + let mut p = Channel::new_pin(&mut p.PIN_27, Pull::Down); defmt::assert!(adc.blocking_read(&mut p).unwrap() < 0b01_0000_0000); } { - let mut p = Pin::new(&mut p.PIN_27, Pull::Up); + let mut p = Channel::new_pin(&mut p.PIN_27, Pull::Up); defmt::assert!(adc.blocking_read(&mut p).unwrap() > 0b11_0000_0000); } } { { - let mut p = Pin::new(&mut p.PIN_28, Pull::Down); + let mut p = Channel::new_pin(&mut p.PIN_28, Pull::Down); defmt::assert!(adc.blocking_read(&mut p).unwrap() < 0b01_0000_0000); } { - let mut p = Pin::new(&mut p.PIN_28, Pull::Up); + let mut p = Channel::new_pin(&mut p.PIN_28, Pull::Up); defmt::assert!(adc.blocking_read(&mut p).unwrap() > 0b11_0000_0000); } } @@ -57,22 +57,22 @@ async fn main(_spawner: Spawner) { // gp29 is connected to vsys through a 200k/100k divider, // adding pulls should change the value let low = { - let mut p = Pin::new(&mut p.PIN_29, Pull::Down); + let mut p = Channel::new_pin(&mut p.PIN_29, Pull::Down); adc.blocking_read(&mut p).unwrap() }; let none = { - let mut p = Pin::new(&mut p.PIN_29, Pull::None); + let mut p = Channel::new_pin(&mut p.PIN_29, Pull::None); adc.blocking_read(&mut p).unwrap() }; let up = { - let mut p = Pin::new(&mut p.PIN_29, Pull::Up); + let mut p = Channel::new_pin(&mut p.PIN_29, Pull::Up); adc.blocking_read(&mut p).unwrap() }; defmt::assert!(low < none); defmt::assert!(none < up); } - let temp = convert_to_celsius(adc.read_temperature().await.unwrap()); + let temp = convert_to_celsius(adc.read(&mut Channel::new_sensor(p.ADC_TEMP_SENSOR)).await.unwrap()); defmt::assert!(temp > 0.0); defmt::assert!(temp < 60.0); From a6b8f3d99478266b4f110e9c150ce3add5c3ffc6 Mon Sep 17 00:00:00 2001 From: pennae Date: Fri, 21 Jul 2023 23:34:12 +0200 Subject: [PATCH 4/4] rp: add single-channel dma from adc with uniform treatment of adc inputs it's easy enough to add a new sampling method. dma sampling only supports one channel at the moment, though round-robin sampling would be a simple extension (probably a new trait that's implemented for Channel and &[Channel]). continuous dma as proposed in #1608 also isn't done here, we'd expect that to be a compound dma::Channel that internally splits a buffer in half and dispatches callbacks or something like that. --- embassy-rp/src/adc.rs | 115 ++++++++++++++++++++++++++++++++++++- examples/rp/src/bin/adc.rs | 2 +- tests/rp/src/bin/adc.rs | 55 ++++++++++++++++-- 3 files changed, 165 insertions(+), 7 deletions(-) diff --git a/embassy-rp/src/adc.rs b/embassy-rp/src/adc.rs index 2824d893..bac45574 100644 --- a/embassy-rp/src/adc.rs +++ b/embassy-rp/src/adc.rs @@ -1,5 +1,6 @@ use core::future::poll_fn; use core::marker::PhantomData; +use core::mem; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; @@ -11,7 +12,7 @@ use crate::gpio::{self, AnyPin, Pull}; use crate::interrupt::typelevel::Binding; use crate::interrupt::InterruptExt; use crate::peripherals::{ADC, ADC_TEMP_SENSOR}; -use crate::{interrupt, pac, peripherals, Peripheral, RegExt}; +use crate::{dma, interrupt, pac, peripherals, Peripheral, RegExt}; static WAKER: AtomicWaker = AtomicWaker::new(); @@ -48,7 +49,7 @@ impl<'p> Channel<'p> { Self(Source::Pin(pin.map_into())) } - pub fn new_sensor(s: impl Peripheral

+ 'p) -> Self { + pub fn new_temp_sensor(s: impl Peripheral

+ 'p) -> Self { let r = pac::ADC; r.cs().write_set(|w| w.set_ts_en(true)); Self(Source::TempSensor(s.into_ref())) @@ -82,6 +83,21 @@ impl<'p> Drop for Source<'p> { } } +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(transparent)] +pub struct Sample(u16); + +impl Sample { + pub fn good(&self) -> bool { + self.0 < 0x8000 + } + + pub fn value(&self) -> u16 { + self.0 & !0x8000 + } +} + #[derive(Debug, Eq, PartialEq, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { @@ -191,6 +207,91 @@ impl<'d> Adc<'d, Async> { false => Ok(r.result().read().result().into()), } } + + async fn read_many_inner( + &mut self, + ch: &mut Channel<'_>, + buf: &mut [W], + fcs_err: bool, + dma: impl Peripheral

, + ) -> Result<(), Error> { + let r = Self::regs(); + // clear previous errors and set channel + r.cs().modify(|w| { + w.set_ainsel(ch.channel()); + w.set_err_sticky(true); // clear previous errors + w.set_start_many(false); + }); + // wait for previous conversions and drain fifo. an earlier batch read may have + // been cancelled, leaving the adc running. + while !r.cs().read().ready() {} + while !r.fcs().read().empty() { + r.fifo().read(); + } + + // set up fifo for dma + r.fcs().write(|w| { + w.set_thresh(1); + w.set_dreq_en(true); + w.set_shift(mem::size_of::() == 1); + w.set_en(true); + w.set_err(fcs_err); + }); + + // reset dma config on drop, regardless of whether it was a future being cancelled + // or the method returning normally. + struct ResetDmaConfig; + impl Drop for ResetDmaConfig { + fn drop(&mut self) { + pac::ADC.cs().write_clear(|w| w.set_start_many(true)); + while !pac::ADC.cs().read().ready() {} + pac::ADC.fcs().write_clear(|w| { + w.set_dreq_en(true); + w.set_shift(true); + w.set_en(true); + }); + } + } + let auto_reset = ResetDmaConfig; + + let dma = unsafe { dma::read(dma, r.fifo().as_ptr() as *const W, buf as *mut [W], 36) }; + // start conversions and wait for dma to finish. we can't report errors early + // because there's no interrupt to signal them, and inspecting every element + // of the fifo is too costly to do here. + r.cs().write_set(|w| w.set_start_many(true)); + dma.await; + mem::drop(auto_reset); + // we can't report errors before the conversions have ended since no interrupt + // exists to report them early, and since they're exceedingly rare we probably don't + // want to anyway. + match r.cs().read().err_sticky() { + false => Ok(()), + true => Err(Error::ConversionFailed), + } + } + + #[inline] + pub async fn read_many( + &mut self, + ch: &mut Channel<'_>, + buf: &mut [S], + dma: impl Peripheral

, + ) -> Result<(), Error> { + self.read_many_inner(ch, buf, false, dma).await + } + + #[inline] + pub async fn read_many_raw( + &mut self, + ch: &mut Channel<'_>, + buf: &mut [Sample], + dma: impl Peripheral

, + ) { + // errors are reported in individual samples + let _ = self + .read_many_inner(ch, unsafe { mem::transmute::<_, &mut [u16]>(buf) }, true, dma) + .await; + } } impl<'d> Adc<'d, Blocking> { @@ -214,9 +315,19 @@ impl interrupt::typelevel::Handler for Inter } mod sealed { + pub trait AdcSample: crate::dma::Word {} + pub trait AdcChannel {} } +pub trait AdcSample: sealed::AdcSample {} + +impl sealed::AdcSample for u16 {} +impl AdcSample for u16 {} + +impl sealed::AdcSample for u8 {} +impl AdcSample for u8 {} + pub trait AdcChannel: sealed::AdcChannel {} pub trait AdcPin: AdcChannel + gpio::Pin {} diff --git a/examples/rp/src/bin/adc.rs b/examples/rp/src/bin/adc.rs index c5869551..02bc493b 100644 --- a/examples/rp/src/bin/adc.rs +++ b/examples/rp/src/bin/adc.rs @@ -25,7 +25,7 @@ async fn main(_spawner: Spawner) { let mut p26 = Channel::new_pin(p.PIN_26, Pull::None); let mut p27 = Channel::new_pin(p.PIN_27, Pull::None); let mut p28 = Channel::new_pin(p.PIN_28, Pull::None); - let mut ts = Channel::new_sensor(p.ADC_TEMP_SENSOR); + let mut ts = Channel::new_temp_sensor(p.ADC_TEMP_SENSOR); loop { let level = adc.read(&mut p26).await.unwrap(); diff --git a/tests/rp/src/bin/adc.rs b/tests/rp/src/bin/adc.rs index 9006ce8c..d6d58f0c 100644 --- a/tests/rp/src/bin/adc.rs +++ b/tests/rp/src/bin/adc.rs @@ -6,7 +6,7 @@ mod common; use defmt::*; use embassy_executor::Spawner; -use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler}; +use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler, Sample}; use embassy_rp::bind_interrupts; use embassy_rp::gpio::Pull; use {defmt_rtt as _, panic_probe as _}; @@ -71,10 +71,57 @@ async fn main(_spawner: Spawner) { defmt::assert!(low < none); defmt::assert!(none < up); } + { + let temp = convert_to_celsius( + adc.read(&mut Channel::new_temp_sensor(&mut p.ADC_TEMP_SENSOR)) + .await + .unwrap(), + ); + defmt::assert!(temp > 0.0); + defmt::assert!(temp < 60.0); + } - let temp = convert_to_celsius(adc.read(&mut Channel::new_sensor(p.ADC_TEMP_SENSOR)).await.unwrap()); - defmt::assert!(temp > 0.0); - defmt::assert!(temp < 60.0); + // run a bunch of conversions. we'll only check gp29 and the temp + // sensor here for brevity, if those two work the rest will too. + { + // gp29 is connected to vsys through a 200k/100k divider, + // adding pulls should change the value + let mut low = [0u16; 16]; + let mut none = [0u8; 16]; + let mut up = [Sample::default(); 16]; + adc.read_many( + &mut Channel::new_pin(&mut p.PIN_29, Pull::Down), + &mut low, + &mut p.DMA_CH0, + ) + .await + .unwrap(); + adc.read_many( + &mut Channel::new_pin(&mut p.PIN_29, Pull::None), + &mut none, + &mut p.DMA_CH0, + ) + .await + .unwrap(); + adc.read_many_raw(&mut Channel::new_pin(&mut p.PIN_29, Pull::Up), &mut up, &mut p.DMA_CH0) + .await; + defmt::assert!(low.iter().zip(none.iter()).all(|(l, n)| *l >> 4 < *n as u16)); + defmt::assert!(up.iter().all(|s| s.good())); + defmt::assert!(none.iter().zip(up.iter()).all(|(n, u)| (*n as u16) < u.value())); + } + { + let mut temp = [0u16; 16]; + adc.read_many( + &mut Channel::new_temp_sensor(&mut p.ADC_TEMP_SENSOR), + &mut temp, + &mut p.DMA_CH0, + ) + .await + .unwrap(); + let temp = temp.map(convert_to_celsius); + defmt::assert!(temp.iter().all(|t| *t > 0.0)); + defmt::assert!(temp.iter().all(|t| *t < 60.0)); + } info!("Test OK"); cortex_m::asm::bkpt();