diff --git a/boards/wio_terminal/.cargo/config.toml b/boards/wio_terminal/.cargo/config.toml index e8f7edb3cf74..d946e04b0ce9 100644 --- a/boards/wio_terminal/.cargo/config.toml +++ b/boards/wio_terminal/.cargo/config.toml @@ -1,5 +1,7 @@ [target.thumbv7em-none-eabihf] -runner = "arm-none-eabi-gdb" +runner = "/home/cooler1989/programs/cgdb/cgdb/cgdb -d gdb-multiarch -x openocd.gdb" +# -ex "mon reset halt" --ex "load" -ex "mon reset halt" +#runner = "arm-none-eabi-gdb" #runner = 'probe-run --chip ATSAMD51P19A' [build] diff --git a/boards/wio_terminal/Cargo.toml b/boards/wio_terminal/Cargo.toml index 3cd890ad02f7..8a1a7b05951a 100644 --- a/boards/wio_terminal/Cargo.toml +++ b/boards/wio_terminal/Cargo.toml @@ -31,6 +31,8 @@ chip = "ATSAMD51P19A" [dependencies] bitfield = "0.13" +modular-bitfield = "0.12" +fugit = "0.3.7" cortex-m-rt = { version = "0.7", optional = true } embedded-hal-bus = "0.3.0" @@ -44,29 +46,51 @@ nb = "1.0" bbqueue = { version = "0.5", optional = true } generic-array = { version = "0.14", optional = true } seeed-erpc = { version = "0.1.3", optional = true } +defmt = "0.3" +defmt-rtt = "0.4" +embassy-sync = { version = "0.6" } +embassy-futures = { version = "0.1.0" } +embassy-executor = { version = "0.7.0", features = [ + "arch-cortex-m", + "executor-thread", + "task-arena-size-12288", +] } +rtic-monotonics = { version = "2.0.1", features = ["cortex-m-systick"] } + +# Opentherm project specific dependnecies: +opentherm_boiler_controller_lib = { version = "0.1.0", git = "ssh://cooler1989@bitbucket.org/learn-rust/boiler_controller_ot_lib.git", branch = "passing_around_slices_instead_of_iterators", features = [ +], optional = true } [dependencies.cortex-m] features = ["critical-section-single-core"] version = "0.7" [dependencies.atsamd-hal] -version = "0.21.0" +path = "../../hal" +version = "0.22.0" default-features = false [dev-dependencies] usbd-serial = "0.2" embedded-graphics = "0.8.1" +panic-probe = "0.3" panic-halt = "0.2" +panic-semihosting = "0.5" +cortex-m-semihosting = "0.3" oorandom = "11.1.3" nom = { version = "8.0", default-features = false } [features] -default = ["atsamd-hal/samd51p", "rt", "usb", "wifi"] +default = ["atsamd-hal/samd51p", "rt", "usb", "wifi", "dma", "async", "use_opentherm"] rt = ["atsamd-hal/samd51p-rt", "cortex-m-rt"] usb = ["atsamd-hal/usb", "usb-device"] # enable feature for RTL8720 firmware older than 2.1.2 wifi-fw-before-212 = [] wifi = ["bbqueue", "generic-array", "seeed-erpc"] +dma = ["atsamd-hal/dma"] +async = ["atsamd-hal/async"] +use_semihosting = [] +use_opentherm = ["opentherm_boiler_controller_lib"] [[example]] name = "blinky" @@ -78,9 +102,9 @@ name = "buttons" name = "clock" required-features = ["usb"] -[[example]] -name = "microphone" - +# [[example]] +# name = "microphone" + [[example]] name = "orientation" @@ -91,6 +115,10 @@ required-features = ["usb"] [[example]] name = "sdcard" +[[example]] +name = "async_opentherm" +required-features = ["async"] + [[example]] name = "qspi" diff --git a/boards/wio_terminal/README.md b/boards/wio_terminal/README.md index c65ae39386ed..882185511714 100644 --- a/boards/wio_terminal/README.md +++ b/boards/wio_terminal/README.md @@ -1,5 +1,10 @@ # `wio-terminal` +High Level TODOs: + +- Implement capture timer driver, capable of timeouts of around 800ms, best to return after 10ms of idle activity but only if some activity has been detected. +- Add confugiration option to use second Grove port to realize Master version of the OpenTherm protocol + [![Crates.io](https://img.shields.io/crates/v/wio-terminal.svg)](https://crates.io/crates/wio-terminal) [![Docs](https://docs.rs/wio-terminal/badge.svg)](https://docs.rs/wio-terminal/) ![License](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-blue) @@ -8,11 +13,11 @@ This project is made possible thanks to the following crates: -* [atsamd-hal](https://github.com/atsamd-rs/atsamd) -* [ili9341-rs](https://github.com/yuri91/ili9341-rs) -* [lis3dh-rs](https://github.com/BenBergman/lis3dh-rs) -* [embedded-sdmmc](https://github.com/rust-embedded-community/embedded-sdmmc-rs) -* [seeed-erpc-rs](https://github.com/twitchyliquid64/seeed-erpc-rs) +- [atsamd-hal](https://github.com/atsamd-rs/atsamd) +- [ili9341-rs](https://github.com/yuri91/ili9341-rs) +- [lis3dh-rs](https://github.com/BenBergman/lis3dh-rs) +- [embedded-sdmmc](https://github.com/rust-embedded-community/embedded-sdmmc-rs) +- [seeed-erpc-rs](https://github.com/twitchyliquid64/seeed-erpc-rs) ## [Documentation] @@ -20,9 +25,9 @@ This project is made possible thanks to the following crates: ## Resources -* [Wio Terminal product page](https://www.seeedstudio.com/Wio-Terminal-p-4509.html) -* [Wio Terminal wiki](https://wiki.seeedstudio.com/Wio-Terminal-Getting-Started/) -* [Wio Terminal user manual](https://files.seeedstudio.com/wiki/Wio-Terminal/res/Wio-Terminal-User-Manual.pdf) +- [Wio Terminal product page](https://www.seeedstudio.com/Wio-Terminal-p-4509.html) +- [Wio Terminal wiki](https://wiki.seeedstudio.com/Wio-Terminal-Getting-Started/) +- [Wio Terminal user manual](https://files.seeedstudio.com/wiki/Wio-Terminal/res/Wio-Terminal-User-Manual.pdf) ## Display @@ -42,9 +47,9 @@ For information on building and flashing the examples to your device, as well as Licensed under either of: -* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) -* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) at your option. @@ -53,3 +58,10 @@ at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + +### Lessons learned + +If you want to expand macro for hal drivers go to hal/ and use: + +cargo install cargo-expand +CARGO_NET_GIT_FETCH_WITH_CLI=true cargo expand --features "samd51p, async, dma" peripherals::timer_capture_waveform > peripherals_timer_capture_waveform.rs diff --git a/boards/wio_terminal/build_async_opentherm.sh b/boards/wio_terminal/build_async_opentherm.sh new file mode 100755 index 000000000000..4c8a47386360 --- /dev/null +++ b/boards/wio_terminal/build_async_opentherm.sh @@ -0,0 +1,4 @@ +CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --example async_opentherm +objcopy -I elf32-little -O binary ../../target/thumbv7em-none-eabihf/debug/examples/async_opentherm async_opentherm.bin +export BINARY_FILE=async_opentherm.bin; gdb-multiarch --batch asyn_opentherm.bin --ex "target remote localhost:3333" --ex "mon reset halt" --ex "mon program $(readlink -f ${BINARY_FILE}) 0x00000 verify" +# export BINARY_FILE=async_opentherm.bin; /home/cooler1989/programs/cgdb/cgdb/cgdb -d gdb-multiarch ../../target/thumbv7em-none-eabihf/debug/examples/async_opentherm --ex "target remote localhost:3333" --ex "mon reset halt" -ex "mon arm semihosting enable" -ex "mon arm semihosting_redirect tcp 2999" diff --git a/boards/wio_terminal/examples/async_opentherm.rs b/boards/wio_terminal/examples/async_opentherm.rs new file mode 100644 index 000000000000..3f4454aa8910 --- /dev/null +++ b/boards/wio_terminal/examples/async_opentherm.rs @@ -0,0 +1,1059 @@ +#![no_std] +#![no_main] + +use bsp::hal::time::Hertz; +use core::time::Duration; +use defmt_rtt as _; +use hal::fugit::Hertz as FugitHertz; +use hal::fugit::MillisDuration; +use heapless::Vec; +use panic_probe as _; + +use embassy_futures::join; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::channel::{Channel, Receiver}; + +use crate::bsp::hal; +use crate::pac::Mclk; +use bsp::pac; +use hal::gpio::{Floating, Input, Output, OutputConfig, PinId, Pins, PushPull, PushPullOutput}; +use hal::gpio::{E, PA16, PA17, PB08, PB09}; +// use wio_terminal::prelude::_embedded_hal_PwmPin; +use wio_terminal::aliases::UserLed; + +use hal::{ + clock::{ClockGenId, ClockSource, GenericClockController, Tc2Tc3Clock, Tc4Tc5Clock}, + delay::Delay, + dmac, + dmac::{Ch1, DmaController, PriorityLevel, ReadyFuture}, + ehal::digital::{OutputPin, StatefulOutputPin}, + eic::{Eic, Sense}, + gpio::{Pin as GpioPin, PullUp, PullUpInterrupt}, + pwm::{PinoutNewTrait, TC2Pinout, TC4Pinout}, + pwm_wg::{PwmWg2, PwmWg4}, + timer_capture_waveform::{ + TimerCapture4, TimerCapture4Future, TimerCaptureBaseTrait, TimerCaptureFutureTrait, + TimerCaptureResultAvailable, + }, +}; +use wio_terminal::prelude::_embedded_hal_blocking_delay_DelayMs; + +// use bsp::pins::UserLed; +use wio_terminal as bsp; + +use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; +use rtic_monotonics::Monotonic; + +use modular_bitfield::prelude::*; + +// use static_cell::StaticCell; + +rtic_monotonics::systick_monotonic!(Mono, 10000); + +#[cfg(feature = "use_opentherm")] +use boiler::opentherm_interface::{ + edge_trigger_capture_interface::{ + CaptureError, CaptureTypeEdges, CapturedEdgePeriod, EdgeCaptureInterface, + EdgeCaptureTransitiveToTriggerCapable, EdgeTriggerInterface, + EdgeTriggerTransitiveToCaptureCapable, InitLevel, TriggerError, + }, + open_therm_message::{CHState, OpenThermMessage, Temperature}, + ListenOpenThermMessage, OpenThermEdgeTriggerBus, SendOpenThermMessage, +}; +#[cfg(feature = "use_opentherm")] +use boiler::{BoilerControl, Instant, TimeBaseRef}; +#[cfg(feature = "use_opentherm")] +use opentherm_boiler_controller_lib as boiler; + +#[cfg(feature = "use_semihosting")] +use panic_semihosting as _; + +use cortex_m_semihosting::hprintln; + +atsamd_hal::bind_interrupts!(struct Irqs { + EIC_EXTINT_5 => atsamd_hal::eic::InterruptHandler; + TC4 => atsamd_hal::timer_capture_waveform::TimerCaptureInterruptHandler; +}); + +atsamd_hal::bind_multiple_interrupts!(struct DmacIrqs { + DMAC: [DMAC_0, DMAC_1, DMAC_2, DMAC_OTHER] => atsamd_hal::dmac::InterruptHandler; +}); + +enum SignalTxSimulation { + Ready_, +} +static CHANNEL: Channel = Channel::new(); + +trait OtMode {} +struct NoneT {} +struct OtTx {} +struct OtRx {} +impl OtMode for OtTx {} +impl OtMode for OtRx {} +impl OtMode for NoneT {} + +#[cfg(feature = "use_opentherm")] +#[embassy_executor::task] +async fn boiler_task() {} + +#[cfg(feature = "use_opentherm")] +mod boiler_implementation { + use crate::dmac::ReadyFuture; + use crate::timer_data_set::PinoutSpecificDataImplTc4; + use atsamd_hal::gpio::G; + use atsamd_hal::gpio::{pin::AnyPin, Alternate, PullUpInput}; + use atsamd_hal::pac::dsu::length; + use atsamd_hal::pac::gclk::genctrl::OeR; + use atsamd_hal::pac::tcc0::per; + use atsamd_hal::pwm_wg::{PinoutCollapse, PwmBaseTrait, PwmWgFutureTrait}; + use atsamd_hal::timer_capture_waveform::{self, TimerCaptureData, TimerCaptureFutureTrait}; + use core::any::Any; + use core::marker::PhantomData; + use fugit::MicrosDuration; + + use super::*; + + trait AtsamdEdgeTriggerCaptureFactory {} + + pub(super) const VEC_SIZE_CAPTURE: usize = 128; + + pub(super) trait CreatePwmPinout { + type PinTxId: PinId; + type PinRxId: PinId; + type PinTx: AnyPin; + type PinRx: AnyPin; + type DmaChannel: dmac::AnyChannel; + type Timer; + type PinoutTx: PinoutCollapse + PinoutNewTrait; + type PinoutRx: PinoutCollapse + PinoutNewTrait; + type PwmBase: PwmBaseTrait< + TC = Self::Timer, + Pinout = Self::PinoutTx, + ConvertibleToFuture = Self::PwmWg, + >; + type PwmWg: PwmWgFutureTrait< + DmaChannel = Self::DmaChannel, + Pinout = Self::PinoutTx, + TC = Self::Timer, + >; + type TimerCaptureBase: TimerCaptureBaseTrait< + TC = Self::Timer, + Pinout = Self::PinoutRx, + ConvertibleToFuture = Self::TimerCaptureFuture, + >; + type TimerCaptureFuture: TimerCaptureFutureTrait< + DmaChannel = Self::DmaChannel, + TC = Self::Timer, + Pinout = Self::PinoutRx, + >; + // fn new_pwm_generator<'a>(pin: Self::PinTx, tc: Self::Timer, dma: Self::DmaChannel, mclk: &mut Mclk) -> Self::PwmWg; + // fn collapse(self) -> Self::PinTx; + } + + pub(super) struct AtsamdEdgeTriggerCapture< + PinoutSpecificData: CreatePwmPinout, + M: OtMode = OtTx, + const N: usize = VEC_SIZE_CAPTURE, + > { + tx_pin: Option>>, + rx_pin: Option>>, + + // Pin>`, found `TC4Pinout`` + tx_init_duty_value: u8, + pwm: Option, // one alternative when TX operation + capture_device: Option, // one alternative when RX operation + periph_clock_freq: Hertz, + mode: PhantomData, + pinout: PhantomData, + } + + impl AtsamdEdgeTriggerCapture + where + PinoutSpecificData: CreatePwmPinout, + OtTx: OtMode, + { + pub fn new_with_default( + pin_tx: GpioPin>, + pin_rx: GpioPin>, + tc_timer: PinoutSpecificData::Timer, /*pac::Tc4*/ + mclk: &mut Mclk, + input_clock_frequency: Hertz, + pinout_factory: PinoutSpecificData, + dma_channel: PinoutSpecificData::DmaChannel, + ) -> AtsamdEdgeTriggerCapture { + let pwm_tx_pin = pin_tx.into_alternate::(); + + // Enable clocks for capture timer + PinoutSpecificData::TimerCaptureBase::enable_mclk_clocks(mclk); + + let pwm_generator_future = PinoutSpecificData::PwmBase::new_waveform_generator( + input_clock_frequency, + Hertz::from_raw(32), + tc_timer, + PinoutSpecificData::PinoutTx::new_pin(pwm_tx_pin), + Some(mclk), + ) + .with_dma_channel(dma_channel); + + Self { + tx_pin: None, + rx_pin: Some(pin_rx), + tx_init_duty_value: 0xff, // This determines idle bus state level. TODO: add configuration + pwm: Some(pwm_generator_future), + capture_device: None, + periph_clock_freq: input_clock_frequency, + mode: PhantomData, + pinout: PhantomData, + } + } + } + + impl AtsamdEdgeTriggerCapture + where + PinoutSpecificData: CreatePwmPinout, + { + // Starting with TX as the boiler controller is more common and uses the TX command first + pub fn new( + pin_tx: GpioPin>, + pin_rx: GpioPin>, + tc_timer: PinoutSpecificData::Timer, + periph_clock_freq: Hertz, + dma_channel: PinoutSpecificData::DmaChannel, + ) -> AtsamdEdgeTriggerCapture { + let pwm_tx_pin = pin_tx.into_alternate::(); + + let pwm_generator_future = PinoutSpecificData::PwmBase::new_waveform_generator( + periph_clock_freq, + Hertz::from_raw(32), + tc_timer, + PinoutSpecificData::PinoutTx::new_pin(pwm_tx_pin), + None, + ) + .with_dma_channel(dma_channel); + + Self { + tx_pin: None, + rx_pin: Some(pin_rx), + tx_init_duty_value: 0xff, // This determines idle bus state level. TODO: add configuration + pwm: Some(pwm_generator_future), + capture_device: None, + periph_clock_freq: periph_clock_freq, + mode: PhantomData, + pinout: PhantomData, + } + } + } + + impl EdgeTriggerTransitiveToCaptureCapable + for AtsamdEdgeTriggerCapture + where + PinoutSpecificData: CreatePwmPinout, + { + type CaptureDevice = AtsamdEdgeTriggerCapture; + fn transition_to_capture_capable_device(self) -> Self::CaptureDevice { + let pwm = self.pwm.unwrap(); + let (dma, tc_timer, pinout) = pwm.decompose(); + let pin_tx = pinout.collapse(); + let pin_tx = pin_tx.into_push_pull_output(); + + AtsamdEdgeTriggerCapture::::new( + pin_tx, + self.rx_pin.unwrap(), + tc_timer, + self.periph_clock_freq, + dma, + ) + } + } + + impl EdgeCaptureTransitiveToTriggerCapable + for AtsamdEdgeTriggerCapture + where + PinoutSpecificData: CreatePwmPinout, + { + type TriggerDevice = AtsamdEdgeTriggerCapture; + fn transition_to_trigger_capable_device( + self, + ) -> AtsamdEdgeTriggerCapture { + let (pin_tx, capture_timer) = (self.tx_pin.unwrap(), self.capture_device.unwrap()); + let (dma, tc_timer, pinout_rx) = capture_timer.decompose(); + // let (dma, tc_timer, pinout) = pwm.decompose(); + let pin_rx = pinout_rx.collapse(); + let pin_rx = pin_rx.into_pull_up_input(); + + AtsamdEdgeTriggerCapture::::new( + pin_tx, + pin_rx, + tc_timer, + self.periph_clock_freq, + dma, + ) + } + } + + impl AtsamdEdgeTriggerCapture + where + PinoutSpecificData: CreatePwmPinout, + { + pub fn new( + pin_tx: GpioPin>, + pin_rx: GpioPin>, + tc_timer: PinoutSpecificData::Timer, + periph_clock_freq: Hertz, + dma_channel: PinoutSpecificData::DmaChannel, + ) -> Self { + let pwm_rx_pin = pin_rx.into_alternate::(); + + let pinout = PinoutSpecificData::PinoutRx::new_pin(pwm_rx_pin); + + let timer_capture = PinoutSpecificData::TimerCaptureBase::new_timer_capture( + periph_clock_freq, + Hertz::from_raw(32), + tc_timer, + pinout, + None, + ) + .with_dma_channel(dma_channel); // TODO: Channel shall be changed to channel0 later on. This is + // just for prototyping + + Self { + tx_pin: Some(pin_tx), + rx_pin: None, + tx_init_duty_value: 0xff, // This determines idle bus state level. TODO: add configuration + pwm: None, + capture_device: Some(timer_capture), // TODO: Implement the capture device + periph_clock_freq: periph_clock_freq, + mode: PhantomData, + pinout: PhantomData, + } + } + } + + impl EdgeTriggerInterface + for AtsamdEdgeTriggerCapture + where + PinoutSpecificData: CreatePwmPinout, + { + type OutputSelf = Self; + async fn trigger( + mut self, + iterator: impl Iterator, + period: core::time::Duration, + ) -> (Self, Result<(), TriggerError>) { + // Invert for hardware adapter compensation: + const INVERT_SIGNAL: bool = true; + // TODO: Implement the period arg usage + let response = match self.pwm.as_mut() { + Some(pwm) => { + // let mut source: [u8; N] = [self.tx_init_duty_value; N]; + // TODO: Actually use the period to set the PWM frequency + pwm.start_regular_pwm(self.tx_init_duty_value); + let dma_future = self + .pwm + .as_mut() + .unwrap() /* TODO: remove runtime panic */ + .start_timer_prepare_dma_transfer::( + self.tx_init_duty_value, + iterator, + ); + dma_future.await.map_err(|_| TriggerError::GenericError) + } + None => Err(TriggerError::GenericError), + }; + (self, response) + } + } + + impl EdgeCaptureInterface + for AtsamdEdgeTriggerCapture + where + PinoutSpecificData: CreatePwmPinout, + { + type OutputSelf = Self; + async fn start_capture>( + mut self, + mut container: OutputType, // fills the container with captured edges or drops it in case of error + timeout_inactive_capture: Duration, + timeout_till_active_capture: Duration, + ) -> (Self, Result<(OutputType, CaptureTypeEdges), CaptureError>) { + /// TODO: + /// 1) Timeout scenario: maybe realized with 32b timer overflow. Will require to set timer overflow event to happen at around 800ms + /// 2) Correct frame finish detection: implemented with reading the timer counter register value in a loop + /// The C++ driver does it by checking number of edges detected to be captured so far by polling in the elements of the DMA buffer array. + /// This element could be improved by reading some internal register of DMA that returns this count, as the array itself is borrowed by DMA and Rust would not allow to read it. + /// In C++ idle bus time is based on the above mentioned edge count by using the independent system timestamp capure mechanism. Maybe that can be improved as well. + // let mut capture_memory: [u32; N] = [0; N]; + let mut data_container: Vec = Vec::new(); + let init_level = self.capture_device.as_mut().unwrap().read_pin_level(); + let result = self + .capture_device + .as_mut() + .unwrap() + .start_timer_execute_dma_transfer::<_, N>(data_container) + .await; + if let Ok(capture_result) = result { + // let mut timestamps = Vec::::new(); + // // The start of the timer is assumed at counter value equal to zero so the lenght can be set to 0ms of relative capture time. + // let _ = timestamps.push(core::time::Duration::from_nanos(0u64)); + // for (idx, value) in capture_memory.iter().enumerate() { + // // TODO: Fix by using the dma transfer coun instead of using non-zero values condition + // // hprintln!("memory captured[{}] = {}", idx, *value).ok(); + // if *value > 0 { + // let ratio_adjusted = (*value as u64 * 4173) / 1000; // TODO: fix this by some ration compund type + // let _ = timestamps.push(core::time::Duration::from_nanos(ratio_adjusted)); + // } + // } + // let differences: Vec = timestamps + // .iter() + // .zip(timestamps.iter().skip(1)) + // .map(|(a, b)| if b > a {*b - *a} else {core::time::Duration::from_micros(0)}).collect(); + // let mut differences_reverse: Vec = Vec::new(); + // for value in differences.iter().rev() { + // let _ = differences_reverse.push(*value); + // hprintln!("timestamps difference: {}ns", value.as_nanos()).ok(); + // } + // let differences_reverse = differences_reverse; + // match capture_result { + // TimerCaptureResultAvailable::DmaPollReady(timer_value_at_termination) => { + // let _ = timestamps.push(core::time::Duration::from_micros(timer_value_at_termination.get_raw_value() as u64)); + // hprintln!("TimerCaptureResultAvailable::DmaPollReady: {}, lvl:{:?}, N={}", timer_value_at_termination.get_raw_value(), init_level, timestamps.len()).ok(); + // } + // TimerCaptureResultAvailable::TimerTimeout(timer_value_at_termination) => { + // let _ = timestamps.push(core::time::Duration::from_micros(timer_value_at_termination.get_raw_value() as u64)); + // hprintln!("TimerCaptureResultAvailable::TimerTimeout: {}, lvl:{:?}, N={}", timer_value_at_termination.get_raw_value(), init_level, timestamps.len()).ok(); + // } + // } + // // First period is skipped so, we start off with the second period and thus oposite level: + // let level = match init_level { + // true => InitLevel::Low, + // false => InitLevel::High, + // }; + // container.extend(differences_reverse.iter().map(|v| CapturedEdgePeriod::FallingToFalling(*v))); + // (self, Ok((container, CaptureTypeEdges::RisingEdge))) + + match capture_result { + TimerCaptureResultAvailable::DmaPollReady(timer_capture_data) + | TimerCaptureResultAvailable::TimerTimeout(timer_capture_data) => { + let timestamps = timer_capture_data.get_data(); + if timestamps.len() < 3 { + hprintln!( + "TimerCaptureResultAvailable::[dma/timeout] timestamps: {:?}", + timestamps + ) + .ok(); + return (self, Err(CaptureError::GenericError)); + } + container.extend( + timestamps + .iter() + .take(1) + .map(|v| CapturedEdgePeriod::StartToFirstRising(*v)), + ); + // The middle part without first and last, is RisignToRising: + container.extend( + timestamps + .iter() + .skip(1) + .take(timestamps.len() - 2) + .map(|v| CapturedEdgePeriod::RisingToRising(*v)), + ); + // ... and the last one is RisingToStop: + container.extend( + timestamps + .iter() + .skip(timestamps.len() - 1) + .map(|v| CapturedEdgePeriod::RisingToStop(*v)), + ); + // hprintln!("TimerCaptureResultAvailable::TimerTimeout: {:?}, N={}", timestamps, timestamps.len()).ok(); + hprintln!( + "TimerCaptureResultAvailable::[dma/timeout] last: {:?}", + timestamps.iter().last() + ) + .ok(); + } + } + + (self, Ok((container, CaptureTypeEdges::RisingEdge))) + } else { + return (self, Err(CaptureError::GenericError)); + } + } + } + + pub struct AtsamdGpioEdgeTriggerDev { + pin_tx: GpioPin, + } + + impl AtsamdGpioEdgeTriggerDev { + pub fn new(mut pin: GpioPin) -> Self { + pin.set_high().unwrap(); // idle state + Self { pin_tx: pin } + } + } + + impl EdgeTriggerInterface for AtsamdGpioEdgeTriggerDev { + type OutputSelf = Self; + async fn trigger( + mut self, + iterator: impl Iterator, + period: core::time::Duration, + ) -> (Self, Result<(), TriggerError>) { + for (_idx, value) in iterator.enumerate() { + if value { + self.pin_tx.set_high().unwrap(); // idle state + } else { + self.pin_tx.set_low().unwrap(); // idle state + } + + Mono::delay( + MicrosDuration::::from_ticks(period.as_micros().try_into().unwrap()) + .convert(), + ) + .await; + } + // Always success: + (self, Ok(())) + } + } + + pub(super) struct AtsamdTimeDriver {} + + impl AtsamdTimeDriver { + pub(super) fn new() -> Self { + Self {} + } + } + + impl TimeBaseRef for AtsamdTimeDriver { + fn now(&self) -> Instant { + // Instant { ticks: 0 } + todo!() + } + } +} + +#[inline] +pub fn check_and_clear_interrupts(flags: InterruptFlags) -> InterruptFlags { + let mut cleared = 0; + let tc4 = unsafe { crate::pac::Peripherals::steal().tc4 }; + tc4.count8().intflag().modify(|r, w| { + cleared = r.bits() & flags.into_bytes()[0]; + unsafe { w.bits(flags.into_bytes()[0]) } + }); + InterruptFlags::from_bytes([cleared]) +} + +#[embassy_executor::task] +async fn toggle_pin_task(mut toggle_pin: GpioPin>) { + loop { + toggle_pin.toggle().unwrap(); + Mono::delay(MillisDuration::::from_ticks(200).convert()).await; + } +} + +mod timer_data_set { + use crate::hal::pwm_wg::{PwmWg2Future, PwmWg4Future}; + // use super::bsp; + use crate::bsp::hal::{ + dmac, + dmac::ReadyFuture, + gpio::{ + Alternate, AnyPin, Floating, Input, Output, OutputConfig, Pin, Pins, PushPull, + PushPullOutput, + }, + gpio::{E, PA16, PA17, PB08, PB09}, + pwm::{PinoutNewTrait, TC2Pinout, TC4Pinout}, + pwm_wg::{PwmBaseTrait, PwmWg2, PwmWg4}, + time::Hertz, + timer_capture_waveform::{ + TimerCapture2, TimerCapture2Future, TimerCapture4, TimerCapture4Future, + TimerCaptureBaseTrait, + }, + }; + use crate::bsp::pac; + use crate::pac::Mclk; + + pub(super) struct PinoutSpecificDataImplTc4 {} + + impl super::boiler_implementation::CreatePwmPinout for PinoutSpecificDataImplTc4 { + type PinTxId = PB09; + type PinRxId = PB08; + type PinTx = Pin>; + type PinRx = Pin>; + type PinoutTx = TC4Pinout; + type PinoutRx = TC4Pinout; + type DmaChannel = dmac::Channel; + type PwmWg = PwmWg4Future; + type TimerCaptureFuture = TimerCapture4Future; + type TimerCaptureBase = TimerCapture4; + type PwmBase = PwmWg4; + type Timer = pac::Tc4; + } + pub(super) struct PinoutSpecificDataImplTc2 {} + + impl super::boiler_implementation::CreatePwmPinout for PinoutSpecificDataImplTc2 { + type PinTxId = PA17; + type PinRxId = PA16; + type PinTx = Pin>; + type PinRx = Pin>; + type PinoutTx = TC2Pinout; + type PinoutRx = TC2Pinout; + type DmaChannel = dmac::Channel; + type PwmWg = PwmWg2Future; + type TimerCaptureFuture = TimerCapture2Future; + type TimerCaptureBase = TimerCapture2; + type PwmBase = PwmWg2; + type Timer = pac::Tc2; + } +} + +#[embassy_executor::main] +async fn main(spawner: embassy_executor::Spawner) { + use boiler_implementation::AtsamdEdgeTriggerCapture; + + let mut peripherals = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + // let core = CorePeripherals::take().unwrap(); + + let pins = Pins::new(peripherals.port); + let dev_dependency_tx_simulation_pin: GpioPin> = + pins.pa17.into_push_pull_output(); + let dev_dependency_rx_simulation_pin: GpioPin> = + pins.pa16.into_pull_up_input(); + let receiver = CHANNEL.receiver(); + + let mut clocks = GenericClockController::with_external_32kosc( + peripherals.gclk, + &mut peripherals.mclk, + &mut peripherals.osc32kctrl, + &mut peripherals.oscctrl, + &mut peripherals.nvmctrl, + ); + let gclk0 = clocks.gclk0(); + // let mut delay = Delay::new(core.SYST, &mut clocks); + let freq: FugitHertz = clocks.gclk0().into(); + Mono::start(core.SYST, freq.to_Hz()); + + // Initialize DMA Controller + let dmac = DmaController::init(peripherals.dmac, &mut peripherals.pm); + // Turn dmac into an async controller + let mut dmac = dmac.into_future(DmacIrqs); + // Get individual handles to DMA channels + let channels = dmac.split(); + // Initialize DMA Channels 0 and 1 + let mut channel0 = channels.0.init(PriorityLevel::Lvl0); + let mut channel1 = channels.1.init(PriorityLevel::Lvl0); + + let tc2_timer = peripherals.tc2; + + // #[cfg(feature = "use_opentherm")] + // spawner.spawn(full_boiler_opentherm_simulation(dev_dependency_tx_simulation_pin, + // dev_dependency_rx_simulation_pin, + // tc2_timer, channel1, + // &mut peripherals.mclk, + // &clocks.tc2_tc3(&gclk0).unwrap(), + // )).unwrap(); + // #[cfg(feature = "use_opentherm")] + // spawner.spawn(simulate_opentherm_tx(dev_dependency_tx_simulation_pin, receiver)).unwrap(); + + let mut pwm_tx_pin = pins.pb09.into_push_pull_output(); + // Set initial level of OpenTherm bus to high: + pwm_tx_pin.set_high().unwrap(); + + let pwm_rx_pin = pins.pb08.into_pull_up_input(); + // let _pwm_tx_pin = pins.pb09.into_alternate::(); + let tc4_timer = peripherals.tc4; + + let async_lambda_delay = || async move { + Mono::delay(MillisDuration::::from_ticks(50).convert()).await; + }; + async_lambda_delay().await; + + // let mut user_led: bsp::UserLed = pin_alias!(pins.user_led).into(); + let mut user_led: UserLed = pins.pa15.into(); + // user_led.toggle().unwrap(); + + // let sets = bsp::Pins::new(peripherals.port).split(); + // let mut user_led = sets.user_led.into_push_pull_output(); + user_led.set_low().unwrap(); + + // spawner.spawn(print_capture_timer_state_task()).unwrap(); + + let pinout_specific_data = timer_data_set::PinoutSpecificDataImplTc4 {}; + + #[cfg(feature = "use_opentherm")] + let mut edge_trigger_capture_dev = boiler_implementation::AtsamdEdgeTriggerCapture::< + _, + _, + { boiler_implementation::VEC_SIZE_CAPTURE }, + >::new_with_default( + pwm_tx_pin, + pwm_rx_pin, + tc4_timer, + &mut peripherals.mclk, + clocks.tc4_tc5(&gclk0).unwrap().freq(), + pinout_specific_data, // determines all the types + channel0, + ); + + let pinout_specific_data_tc2 = timer_data_set::PinoutSpecificDataImplTc2 {}; + + #[cfg(feature = "use_opentherm")] + let mut edge_trigger_capture_simulation_device = + boiler_implementation::AtsamdEdgeTriggerCapture::::new_with_default( + dev_dependency_tx_simulation_pin, + dev_dependency_rx_simulation_pin, + tc2_timer, + &mut peripherals.mclk, + clocks.tc2_tc3(&gclk0).unwrap().freq(), + pinout_specific_data_tc2, + channel1, + ); + + // let _time_driver = boiler_implementation::AtsamdTimeDriver::new(); + // let mut boiler_controller = BoilerControl::new(edge_trigger_capture_dev, time_driver); + // let _ = boiler_controller.set_point(Temperature::Celsius(16)); + + // Driver bringup temporary code: + // Idea is to configure additional PIN and connect it to the TC4 timer to capture the signal + // Pin toggle can be done in independent task + // spawner.spawn(toggle_pin_task(dev_dependency_tx_simulation_pin)).unwrap(); + + hprintln!("main:: loop{} start:").ok(); + + let (mut edge_trigger_capture_dev, _) = edge_trigger_capture_dev + .send_open_therm_message( + OpenThermMessage::try_new_from_u32(0b0_000_0000_00000001_00100101_00000000_u32) + .unwrap(), + ) + .await; + + let mut edge_trigger_capture_dev = + edge_trigger_capture_dev.transition_to_capture_capable_device(); + let sender_trigger_tx_sequence = CHANNEL.sender(); + + Mono::delay(MillisDuration::::from_ticks(5000).convert()).await; + + let mut count_all = 0; + let mut count_success = 0; + loop { + // Test one round of TX(simulation) -> RX(production) + let tx_async_simulation = async { + sender_trigger_tx_sequence + .send(SignalTxSimulation::Ready_) + .await; + let (device, result) = edge_trigger_capture_simulation_device + .send_open_therm_message( + OpenThermMessage::try_new_from_u32(0b0_000_0000_00000001_00100101_00000000_u32) + .unwrap(), + ) + .await; + device + }; + + let rx_async = async |&count_all| { + let dur = Duration::from_millis(100); + let start_time = Mono::now(); + let (tx_device, result) = edge_trigger_capture_dev + .listen_open_therm_message() /* -> (Self, Result)*/ + .await; + let duration = Mono::now() - start_time; + match result { + Ok(message) => { + count_success += 1; + hprintln!( + "Capture finished with opentherm message: {}, took: {}, rate: {}/{}", + message, + duration, + count_success, + count_all + ) + .ok(); + } + Err(e) => { + hprintln!("Capture finished with error on OpenThermMessage: {}, took: {}, rate: {}/{}", e, duration, count_success, count_all).ok(); + } + } + tx_device + }; + + count_all += 1; + hprintln!("start: {}", count_all).ok(); + // TODO: how to join the two futures and return devices as a result after execution? + let (tx_result, rx_result) = + embassy_futures::join::join(tx_async_simulation, rx_async(&count_all)).await; + hprintln!("stop: {}", count_all).ok(); + + // TODO: Count successes and errors. + + edge_trigger_capture_dev = rx_result; + edge_trigger_capture_simulation_device = tx_result; + } + + // let mut edge_trigger_capture_dev = rx_result; + + // let time_d = core::time::Duration::from_millis(100); + // let time_d :u32 = time_d.as_millis().try_into().unwrap(); + // hprintln!("Start Capture: {}", time_d).ok(); + + // let mut count_iterations: u32 = 0; + // loop { + // Mono::delay(MillisDuration::::from_ticks(200).convert()).await; + // let device = edge_trigger_capture_dev; + // // hprintln!("Wait long before starting the capture").ok(); + // // Mono::delay(MillisDuration::::from_ticks(500).convert()).await; + // hprintln!("Start Capture {}", count_iterations).ok(); + // let dur = Duration::from_millis(100); + // // trigger gpio simulation of the OpenTherm TX message + // sender_trigger_tx_sequence.send(SignalTxSimulation::Ready_).await; + // let (rx_device, result) = + // device.start_capture(dur, dur).await; + + // if let Ok((level, vector)) = result { + // hprintln!("Capture finished with: {}", vector.len()).ok(); + // let differences: Vec = vector + // .iter() + // .zip(vector.iter().skip(1)) + // .map(|(a, b)| if b > a {b.as_micros() - a.as_micros()} else {0}).collect(); + + // // for (i, v) in differences.into_iter().enumerate() { + // // hprintln!("{}:{} us", i, v).ok(); + // // } + // } + // hprintln!("Finish Capture {}", count_iterations).ok(); + // Mono::delay(MillisDuration::::from_ticks(50).convert()).await; + + // hprintln!("Start FullOpentherm Capture {}", count_iterations).ok(); + // let dur = Duration::from_millis(100); + // // trigger gpio simulation of the OpenTherm TX message + // sender_trigger_tx_sequence.send(SignalTxSimulation::Ready_).await; + // let (rx_device, result) = + // rx_device.listen_open_therm_message().await; + // if let Ok(message) = result { + // hprintln!("Capture finished with opentherm message: {}", message).ok(); + // } + // else + // { + // // TODO: extend the analysis of what is wrong with this capture here: + // hprintln!("Capture finished with error on OpenThermMessage").ok(); + // } + // hprintln!("Finish Capture {}", count_iterations).ok(); + // Mono::delay(MillisDuration::::from_ticks(50).convert()).await; + + // hprintln!("Start Trigger {}", count_iterations).ok(); + // let tx_device = rx_device.transition_to_trigger_capable_device(); + // let (tx_device, result) = + // tx_device.send_open_therm_message(OpenThermMessage::try_new_from_u32(0b0_000_0000_00000001_00100101_00000000_u32).unwrap()).await; + + // edge_trigger_capture_dev = tx_device.transition_to_capture_capable_device(); + // // let _ = boiler_controller.process().await.unwrap(); + + // user_led.toggle().unwrap(); + // count_iterations += 1; + // } + + // let _ot_rx: GpioPin<_, PullUpInterrupt> = pins.pb08.into(); // D0 + // // let pb_09_ot_tx: GpioPin<_, PushPullOutput> = pins.pb09.into(); // D1 + // // let capture_device = RpEdgeCapture::new(async_input); + // // let mut open_therm_bus = AtsamdEdgeTriggerCapture::new(pb_09_ot_tx); + // let example_vector = heapless::Vec::::from_slice(&[ + // true, true, false, true, false, true, false, false, true, false, false, + // ]) + // .unwrap(); + // // let _ = open_therm_bus.trigger(example_vector.into_iter(), Duration::from_millis(100)); + + // #[cfg(feature = "use_opentherm")] + // let time_driver = AtsamdTimeDriver::new(); + // // let mut boiler_controller = BoilerControl::new(open_therm_bus, time_driver); + // // let _ = boiler_controller.set_point(Temperature::Celsius(16)); + // // let _ = boiler_controller.enable_ch(CHState::Enable(true)); +} + +#[bitfield] +#[repr(u8)] +#[derive(Clone, Copy)] +pub struct InterruptFlags { + /// Overflow + pub ovf: bool, + /// Err + pub err: bool, + #[skip] + _reserved: B6, +} + +impl Default for InterruptFlags { + fn default() -> Self { + Self::new() + } +} + +// Dummy Waker implementation for no_std environment. +static RAW_WAKER_VTABLE: core::task::RawWakerVTable = core::task::RawWakerVTable::new( + |ptr: *const ()| RawWaker::new(ptr, &RAW_WAKER_VTABLE), + |_: *const ()| {}, // Do nothing, for simulation + |_: *const ()| {}, // Do nothing, for simulation + |_: *const ()| {}, +); + +unsafe fn raw_waker(waker_ptr: *const ()) -> Waker { + Waker::from_raw(RawWaker::new(waker_ptr, &RAW_WAKER_VTABLE)) +} + +/// The idea here is to use simpler to implement TX driver using gpio timer to ease on +/// implementation of the OpenTherm RX driver based on timer + DMA +#[embassy_executor::task] +async fn simulate_opentherm_tx( + mut tx_pin: GpioPin>, + receiver: Receiver<'static, ThreadModeRawMutex, SignalTxSimulation, 64>, +) { + let hw_dev = boiler_implementation::AtsamdGpioEdgeTriggerDev::new(tx_pin); + const DURATION_MS: u32 = 500; + + // Give it some time before fire-up the + Mono::delay(MillisDuration::::from_ticks(50).convert()).await; + // TODO: implement heapless queue receiving requests + let mut pass_dev_in_loop = hw_dev; + loop { + // Comment this out to have on demand instead of periodic transfer: + let _received = receiver.receive().await; + + // hprintln!("channel::RX").ok(); + // tx_pin.toggle().unwrap(); + // Wait for the receiver to be ready, give it some time to setup the capture + Mono::delay(MillisDuration::::from_ticks(10).convert()).await; + // The device implements the trigger interface, it shall implement send as well: + let (dev, result) = pass_dev_in_loop + .send_open_therm_message( + OpenThermMessage::try_new_from_u32(0b0_000_0000_00000001_00100101_00000000_u32) + .unwrap(), + ) + .await; + pass_dev_in_loop = dev; + // Mono::delay(MillisDuration::::from_ticks(DURATION_MS).convert()).await; + } +} + +#[embassy_executor::task] +async fn print_capture_timer_state_task(/*mut uart_tx: UartFutureTxDuplexDma, Ch1>*/ +) { + let tc4_readonly = unsafe { crate::pac::Peripherals::steal().tc4 }; + let count32 = tc4_readonly.count32(); + let dmac_readonly = unsafe { crate::pac::Peripherals::steal().dmac }; + // let mut value_cc1 = 0x00u8; + loop { + // Read this value: + // let vcc1 = tc4_readonly.count8().cc(1).read().bits(); + // if vcc1 != value_cc1 { + // hprintln!("tc4.cc1:0x{:08X}", vcc1).ok(); + // value_cc1 = vcc1; + // } + + // uart_tx.write(b"Hello, world!").await.unwrap(); + // defmt::info!("Sent 10 bytes"); + + // let mut delay = Delay::new(core.SYST, &mut clocks); + { + // Read counter one by one to see if it is running: + let _ = count32.ctrlbset().write(|w| w.cmd().readsync()); + let cnt_value = count32.count().read().bits(); + let _ = count32.ctrlbset().write(|w| w.cmd().readsync()); + let cn2_value = count32.count().read().bits(); + // hprintln!("cnt:0x{:08X}, 0x{:08X}", cnt_value, cn2_value).ok(); + } + + hprintln!("tc4int:0x{:08X}", count32.intflag().read().bits()).ok(); + // hprintln!("tc4cc0:0x{:08X}", count32.cc(0).read().bits()).ok(); + //hprintln!("tc4ctrla:0x{:08X}", count32.ctrla().read().bits()).ok(); + //hprintln!("tc4evctrl:0x{:08X}", count32.evctrl().read().bits()).ok(); + // hprintln!("tc4per:0x{:08X}", tc4_readonly.count8().per().read().bits()).ok(); + // hprintln!("dmaact:0x{:08X}", dmac_readonly.active().read().bits()).ok(); + // let btcnt = dmac_readonly.active().read().btcnt() ).ok(); + // hprintln!("dmaact:0x{:08X}", dmac_readonly.active().read().btcnt().bits() ).ok(); + //hprintln!("dmactrl:0x{:08X}", dmac_readonly.ctrl().read().bits()).ok(); + //hprintln!("dmabusy:0x{:08X}", dmac_readonly.busych().read().bits()).ok(); + //hprintln!("dmachint:0x{:08X}", dmac_readonly.intstatus().read().bits()).ok(); + //hprintln!("dmachintpend:0x{:08X}", dmac_readonly.intpend().read().bits()).ok(); + //hprintln!("ch[0]chctrla:0x{:08X}", dmac_readonly.channel(0).chctrla().read().bits()).ok(); + //hprintln!("ch[0]chint:0x{:08X}", dmac_readonly.channel(0).chintflag().read().bits()).ok(); + //hprintln!("ch[0]chstat:0x{:08X}", dmac_readonly.channel(0).chstatus().read().bits()).ok(); + + // let flags_to_check = InterruptFlags::new().with_ovf(true).with_err(true); + // if check_and_clear_interrupts(flags_to_check).ovf() { + // // hprintln!("Overflow detected").ok(); + // } + + // delay.delay_ms(200u16); + Mono::delay(MillisDuration::::from_ticks(2000).convert()).await; + } +} +#[embassy_executor::task] +async fn print_timer_state_task(/*mut uart_tx: UartFutureTxDuplexDma, Ch1>*/) +{ + let tc4_readonly = unsafe { crate::pac::Peripherals::steal().tc4 }; + let dmac_readonly = unsafe { crate::pac::Peripherals::steal().dmac }; + let mut value_cc1 = 0x00u8; + loop { + // Read this value: + let vcc1 = tc4_readonly.count8().cc(1).read().bits(); + if vcc1 != value_cc1 { + hprintln!("tc4.cc1:0x{:08X}", vcc1).ok(); + value_cc1 = vcc1; + } + + // uart_tx.write(b"Hello, world!").await.unwrap(); + // defmt::info!("Sent 10 bytes"); + + // let mut delay = Delay::new(core.SYST, &mut clocks); + let _ = tc4_readonly + .count8() + .ctrlbset() + .write(|w| w.cmd().readsync()); + let cnt_value = tc4_readonly.count8().count().read().bits(); + let _ = tc4_readonly + .count8() + .ctrlbset() + .write(|w| w.cmd().readsync()); + let cn2_value = tc4_readonly.count8().count().read().bits(); + + hprintln!("cnt:0x{:08X}, 0x{:08X}", cnt_value, cn2_value).ok(); + // hprintln!( + // "tc4int:0x{:08X}, cc1:0x{:08X}", + // tc4_readonly.count8().intflag().read().bits(), + // tc4_readonly.count8().cc(1).read().bits() + // ) + // .ok(); + // hprintln!("tc4per:0x{:08X}", tc4_readonly.count8().per().read().bits()).ok(); + // hprintln!("dma:0x{:08X}", dmac_readonly.active().read().bits()).ok(); + + let flags_to_check = InterruptFlags::new().with_ovf(true).with_err(true); + if check_and_clear_interrupts(flags_to_check).ovf() { + // hprintln!("Overflow detected").ok(); + } + + // delay.delay_ms(200u16); + Mono::delay(MillisDuration::::from_ticks(500).convert()).await; + } +} + +#[embassy_executor::task] +async fn full_boiler_opentherm_simulation( + tx_pin: GpioPin>, + mut rx_pin: GpioPin>, + timer: pac::Tc2, + dma_channel: hal::dmac::Channel, + mclk: &'static mut Mclk, + timer_clocks: &'static Tc2Tc3Clock, +) { + // let mut edge_trigger_capture_dev = + // boiler_implementation::AtsamdEdgeTriggerCapture::new_with_default( + // tx_pin, + // rx_pin, + // timer, + // &mclk, + // &timer_clocks, + // dma_channel, + // ); + loop { + Mono::delay(MillisDuration::::from_ticks(500).convert()).await; + } +} diff --git a/boards/wio_terminal/examples/broken/async_sdcard.rs b/boards/wio_terminal/examples/broken/async_sdcard.rs new file mode 100644 index 000000000000..ab122d588f3f --- /dev/null +++ b/boards/wio_terminal/examples/broken/async_sdcard.rs @@ -0,0 +1,160 @@ +#![no_std] +#![no_main] + +/// Makes the wio_terminal read the SD card and print the filenames +/// of the first few entries. +use embedded_graphics as eg; +use panic_halt as _; +use wio_terminal as wio; + +use eg::mono_font::{ascii::FONT_9X15, MonoTextStyle}; +use eg::pixelcolor::Rgb565; +use eg::prelude::*; +use eg::primitives::{PrimitiveStyleBuilder, Rectangle}; +use eg::text::{Baseline, Text}; + +use wio::entry; +use wio::hal::clock::GenericClockController; +use wio::hal::delay::Delay; +use wio::pac::{CorePeripherals, Peripherals}; +use wio::prelude::*; + +use core::fmt::Write; +use heapless::String; + +use embedded_sdmmc::{TimeSource, Timestamp, VolumeIdx}; +use wio::SDCardController; + +#[entry] +fn main() -> ! { + let mut peripherals = Peripherals::take().unwrap(); + let core = CorePeripherals::take().unwrap(); + + let mut clocks = GenericClockController::with_external_32kosc( + peripherals.gclk, + &mut peripherals.mclk, + &mut peripherals.osc32kctrl, + &mut peripherals.oscctrl, + &mut peripherals.nvmctrl, + ); + + let mut delay = Delay::new(core.SYST, &mut clocks); + let sets = wio::Pins::new(peripherals.port).split(); + + let (mut cont, _sd_present) = sets + .sd_card + .init( + &mut clocks, + peripherals.sercom6, + &mut peripherals.mclk, + Clock, + ) + .unwrap(); + + // Initialize the ILI9341-based LCD display. Create a black backdrop the size of + // the screen. + let (mut display, _backlight) = sets + .display + .init( + &mut clocks, + peripherals.sercom7, + &mut peripherals.mclk, + 58.MHz(), + &mut delay, + ) + .unwrap(); + + let style = MonoTextStyle::new(&FONT_9X15, Rgb565::WHITE); + + loop { + match cont.device().init() { + Ok(_) => { + // Now that we have initialized, we can run the SPI bus at + // a reasonable speed. + cont.set_baud(20.MHz()); + + let mut data = String::<128>::new(); + write!(data, "OK! ").unwrap(); + match cont.device().card_size_bytes() { + Ok(size) => writeln!(data, "{}Mb", size / 1024 / 1024).unwrap(), + Err(e) => writeln!(data, "Err: {:?}", e).unwrap(), + } + Text::with_baseline(data.as_str(), Point::new(4, 2), style, Baseline::Top) + .draw(&mut display) + .ok() + .unwrap(); + + if let Err(e) = print_contents(&mut cont, &mut display) { + let mut data = String::<128>::new(); + writeln!(data, "Err: {:?}", e).unwrap(); + Text::with_baseline(data.as_str(), Point::new(4, 20), style, Baseline::Top) + .draw(&mut display) + .ok() + .unwrap(); + } + } + Err(e) => { + let mut data = String::<128>::new(); + writeln!(data, "Error!: {:?}", e).unwrap(); + Text::with_baseline(data.as_str(), Point::new(4, 2), style, Baseline::Top) + .draw(&mut display) + .ok() + .unwrap(); + } + } + + delay.delay_ms(2500_u16); + Rectangle::with_corners(Point::new(0, 0), Point::new(320, 240)) + .into_styled( + PrimitiveStyleBuilder::new() + .fill_color(Rgb565::BLACK) + .build(), + ) + .draw(&mut display) + .ok() + .unwrap(); + } +} + +fn print_contents( + cont: &mut SDCardController, + lcd: &mut wio::LCD, +) -> Result<(), embedded_sdmmc::Error> { + let style = MonoTextStyle::new(&FONT_9X15, Rgb565::WHITE); + + let volume = cont.get_volume(VolumeIdx(0))?; + let dir = cont.open_root_dir(&volume)?; + + let mut count = 0; + let out = cont.iterate_dir(&volume, &dir, |ent| { + let mut data = String::<128>::new(); + writeln!(data, "{} - {:?}", ent.name, ent.attributes).unwrap(); + Text::with_baseline( + data.as_str(), + Point::new(4, 20 + count * 16), + style, + Baseline::Top, + ) + .draw(lcd) + .ok() + .unwrap(); + count += 1; + }); + cont.close_dir(&volume, dir); + out +} + +struct Clock; + +impl TimeSource for Clock { + fn get_timestamp(&self) -> Timestamp { + Timestamp { + year_since_1970: 0, + zero_indexed_month: 0, + zero_indexed_day: 0, + hours: 0, + minutes: 0, + seconds: 0, + } + } +} diff --git a/boards/wio_terminal/examples/microphone.rs b/boards/wio_terminal/examples/broken/microphone.rs similarity index 100% rename from boards/wio_terminal/examples/microphone.rs rename to boards/wio_terminal/examples/broken/microphone.rs diff --git a/boards/wio_terminal/examples/eic.rs b/boards/wio_terminal/examples/eic.rs new file mode 100644 index 000000000000..43153eccf154 --- /dev/null +++ b/boards/wio_terminal/examples/eic.rs @@ -0,0 +1,93 @@ +//! Uses an external interrupt to blink an LED. +//! +//! You need to connect a button between D12 and ground. Each time the button +//! is pressed, the LED will count the total number of button presses so far. +#![no_std] +#![no_main] + +#[cfg(not(feature = "use_semihosting"))] +use panic_halt as _; +#[cfg(feature = "use_semihosting")] +use panic_semihosting as _; + +use bsp::hal; +use bsp::pac; +use wio_terminal as bsp; + +use bsp::entry; +use hal::clock::GenericClockController; +use hal::delay::Delay; +use hal::eic::{Eic, Sense}; +use hal::gpio::{Pin, Pins, PullUpInterrupt}; +use hal::prelude::*; +use pac::{interrupt, CorePeripherals, Peripherals}; +use wio_terminal::aliases::UserLed; + +use core::sync::atomic::{AtomicUsize, Ordering}; + +use cortex_m::peripheral::NVIC; + +static COUNTER: AtomicUsize = AtomicUsize::new(0); + +#[entry] +fn main() -> ! { + let mut peripherals = Peripherals::take().unwrap(); + let mut core = CorePeripherals::take().unwrap(); + let mut clocks = GenericClockController::with_external_32kosc( + peripherals.gclk, + &mut peripherals.mclk, + &mut peripherals.osc32kctrl, + &mut peripherals.oscctrl, + &mut peripherals.nvmctrl, + ); + let gclk0 = clocks.gclk0(); + let pins = Pins::new(peripherals.port); + // let mut red_led: bsp::RedLed = pins.d13.into(); + let mut user_led: UserLed = pins.pa15.into(); + let mut delay = Delay::new(core.SYST, &mut clocks); + + let eic_clock = clocks.eic(&gclk0).unwrap(); + let eic_channels = Eic::new(&mut peripherals.mclk, &eic_clock, peripherals.eic).split(); + + let button: Pin<_, PullUpInterrupt> = pins.pd10.into(); + let mut extint = eic_channels.5.with_pin(button); + extint.sense(Sense::Fall); + extint.enable_interrupt(); + + // Enable EIC interrupt in the NVIC + unsafe { + core.NVIC.set_priority(interrupt::EIC_EXTINT_5, 1); + NVIC::unmask(interrupt::EIC_EXTINT_5); + } + + // Blink the LED once to show that we have started up. + user_led.set_high().unwrap(); + delay.delay_ms(200u8); + user_led.set_low().unwrap(); + delay.delay_ms(200u8); + + let mut last_counter_value = COUNTER.load(Ordering::SeqCst); + loop { + let new_counter_value = COUNTER.load(Ordering::SeqCst); + if last_counter_value != new_counter_value { + last_counter_value = new_counter_value; + for _ in 0..new_counter_value { + user_led.set_high().unwrap(); + delay.delay_ms(200u8); + user_led.set_low().unwrap(); + delay.delay_ms(200u8); + } + } + } +} + +#[interrupt] +fn EIC_EXTINT_5() { + // Increase the counter and clear the interrupt. + unsafe { + // Accessing registers from interrupts context is safe + let eic = &*pac::Eic::ptr(); + eic.intflag().modify(|_, w| w.extint().bits(0x01u16 << 5)); + } + COUNTER.store(COUNTER.load(Ordering::SeqCst) + 1, Ordering::SeqCst); +} diff --git a/boards/wio_terminal/examples/image.png b/boards/wio_terminal/examples/image.png new file mode 100644 index 000000000000..c612afbc2f9b Binary files /dev/null and b/boards/wio_terminal/examples/image.png differ diff --git a/boards/wio_terminal/examples/sdcard.rs b/boards/wio_terminal/examples/sdcard.rs index 40c9b276e3b4..92305d876cf0 100644 --- a/boards/wio_terminal/examples/sdcard.rs +++ b/boards/wio_terminal/examples/sdcard.rs @@ -28,6 +28,7 @@ use heapless::String; use embedded_sdmmc::{sdcard::Error as SdCardError, TimeSource, Timestamp, VolumeIdx}; use wio::SDCardController; +#[allow(clippy::empty_loop)] #[entry] fn main() -> ! { let mut peripherals = Peripherals::take().unwrap(); diff --git a/boards/wio_terminal/memory.x b/boards/wio_terminal/memory.x index 3ff115fb881f..aa2b9deffb71 100644 --- a/boards/wio_terminal/memory.x +++ b/boards/wio_terminal/memory.x @@ -1,7 +1,7 @@ MEMORY { /* Leave 16k for the default bootloader on the Wio Terminal */ - FLASH (rx) : ORIGIN = 0x00000000 + 16K, LENGTH = 512K - 16K + FLASH (rx) : ORIGIN = 0x00000000 + 0K, LENGTH = 512K - 16K RAM (rxw) : ORIGIN = 0x20000000, LENGTH = 192K } _stack_start = ORIGIN(RAM) + LENGTH(RAM); diff --git a/boards/wio_terminal/openocd.gdb b/boards/wio_terminal/openocd.gdb new file mode 100644 index 000000000000..98102b300991 --- /dev/null +++ b/boards/wio_terminal/openocd.gdb @@ -0,0 +1,13 @@ +target remote :3333 + +set print asm-demangle on + +load + +mon reset halt + +break DefaultHandler + +break HardFault + +break rust_begin_unwind diff --git a/boards/wio_terminal/setup_timer.gdb b/boards/wio_terminal/setup_timer.gdb new file mode 100644 index 000000000000..0d94be3b6423 --- /dev/null +++ b/boards/wio_terminal/setup_timer.gdb @@ -0,0 +1,38 @@ +printf "MCLK reg:\n" +x /xw 0x4000081c + +# enable de-assert enable signal: +mon mww 0x42001400 0x604 +shell sleep 1 +# enable assert SWRT: +mon mww 0x42001400 0x605 +shell sleep 1 +# Change mode to 32-bits +mon mww 0x42001400 0x608 +shell sleep 1 +# enable COPEN +mon mww 0x42001400 0x10608 +shell sleep 1 +# enable CAPTEN +mon mww 0x42001400 0x110608 +shell sleep 1 +# enable timer: +mon mww 0x42001400 0x11060A +shell sleep 1 +# enable timer when DBG +mon mwb 0x42001409 0x01 +# Capture data: +mon mwb 0x42001405 0x80 +# read counter: +x /xw 0x42001414 + +set $counter = 0 +while $counter < 5 + printf "iter: %d\n", $counter + # Capture data: + mon mwb 0x42001405 0x80 + # read counter: + x /xw 0x42001414 + shell sleep 0.1 + set $counter = $counter + 1 +end diff --git a/boards/wio_terminal/src/buttons.rs b/boards/wio_terminal/src/buttons.rs index f4ba9bcede55..c8e0311123ed 100644 --- a/boards/wio_terminal/src/buttons.rs +++ b/boards/wio_terminal/src/buttons.rs @@ -37,7 +37,7 @@ impl ButtonPins { ) -> ButtonController { let gclk1 = clocks.gclk1(); let eic_clock = clocks.eic(&gclk1).unwrap(); - let mut eic = eic::Eic::new(mclk, eic_clock, eic); + let mut eic = eic::Eic::new(mclk, &eic_clock, eic); // Unfortunately, the pin assigned to B1 shares the same // ExtInt line as up on the joystick. As such, we don't diff --git a/boards/wio_terminal/src/display.rs b/boards/wio_terminal/src/display.rs index d70f565e20ec..680bd648674b 100644 --- a/boards/wio_terminal/src/display.rs +++ b/boards/wio_terminal/src/display.rs @@ -7,7 +7,7 @@ use atsamd_hal::ehal_nb::serial::Write; use atsamd_hal::pac::Mclk; use atsamd_hal::sercom::spi; use atsamd_hal::sercom::spi::Spi; -use atsamd_hal::sercom::{IoSet4, Sercom7}; +use atsamd_hal::sercom::Sercom7; use atsamd_hal::time::Hertz; use atsamd_hal::typelevel::NoneT; use display_interface_spi::SPIInterface; @@ -40,7 +40,7 @@ pub struct Display { pub backlight: LcdBacklightReset, } -pub type LcdPads = spi::Pads; +pub type LcdPads = spi::Pads; pub type LcdSpi = spi::PanicOnRead, spi::Tx>>; pub type LcdDevice = bspi::ExclusiveDevice; diff --git a/boards/wio_terminal/src/sensors.rs b/boards/wio_terminal/src/sensors.rs index 0533aa294514..66c920869680 100644 --- a/boards/wio_terminal/src/sensors.rs +++ b/boards/wio_terminal/src/sensors.rs @@ -1,9 +1,10 @@ -use atsamd_hal::adc::Adc; +use atsamd_hal::adc::AdcBuilder; +use atsamd_hal::adc::{Accumulation, Adc, Prescaler, Resolution}; use atsamd_hal::clock::GenericClockController; use atsamd_hal::pac::gclk::pchctrl::Genselect::Gclk11; use atsamd_hal::pac::{Adc1, Mclk}; use atsamd_hal::prelude::*; -use atsamd_hal::sercom::{i2c, IoSet3, Sercom4}; +use atsamd_hal::sercom::{i2c, Sercom4}; use lis3dh::{Lis3dh, SlaveAddr}; @@ -21,7 +22,7 @@ pub struct Accelerometer { /// I2C pads for the labelled I2C peripheral /// /// You can use these pads with other, user-defined [`i2c::Config`]urations. -pub type I2cPads = i2c::Pads; +pub type I2cPads = i2c::Pads; impl Accelerometer { /// Initialize the LIS3DH accelerometer using the correct pins and @@ -64,9 +65,20 @@ impl LightSensor { adc: Adc1, clocks: &mut GenericClockController, mclk: &mut Mclk, - ) -> (Adc, LightSensorAdc) { - let adc1 = Adc::adc1(adc, mclk, clocks, Gclk11); + ) -> (Adc, LightSensorAdc) { - (adc1, self.pd1.into()) + todo!() + // let adc1 = Adc::adc1(adc, mclk, clocks, Gclk11); + + // let mut adc = AdcBuilder::new(Accumulation::single(atsamd_hal::adc::AdcResolution::_12)) + // .with_clock_cycles_per_sample(5) + // // Overruns if clock divider < 32 in debug mode + // .with_clock_divider(Prescaler::Div32) + // .with_vref(atsamd_hal::adc::Reference::Arefa) + // .enable(adc, apb_adc0, &pclk_adc0) + // .unwrap(); + // let mut adc_pin = pins.a0.into_alternate(); + + // (adc1, self.pd1.into()) } } diff --git a/boards/wio_terminal/src/serial.rs b/boards/wio_terminal/src/serial.rs index d4ec95339691..29bafb8919fe 100644 --- a/boards/wio_terminal/src/serial.rs +++ b/boards/wio_terminal/src/serial.rs @@ -1,6 +1,6 @@ use atsamd_hal::clock::GenericClockController; use atsamd_hal::pac::{self, Mclk}; -use atsamd_hal::sercom::{uart, IoSet2, Sercom2}; +use atsamd_hal::sercom::{uart, Sercom2}; use atsamd_hal::time::Hertz; #[cfg(feature = "usb")] @@ -20,7 +20,7 @@ pub struct Uart { } /// UART pads for the labelled RX & TX pins -pub type UartPads = uart::Pads; +pub type UartPads = uart::Pads; /// UART device for the labelled RX & TX pins pub type HalUart = uart::Uart, uart::Duplex>; diff --git a/boards/wio_terminal/src/sound.rs b/boards/wio_terminal/src/sound.rs index 0072b6879908..13b522c60ae2 100644 --- a/boards/wio_terminal/src/sound.rs +++ b/boards/wio_terminal/src/sound.rs @@ -51,9 +51,10 @@ impl Microphone { adc: Adc1, clocks: &mut GenericClockController, mclk: &mut Mclk, - ) -> (Adc, MicOutput) { - let adc1 = Adc::adc1(adc, mclk, clocks, Gclk11); + ) -> (Adc, MicOutput) { + todo!() - (adc1, self.mic.into()) + // let adc1 = Adc::adc1(adc, mclk, clocks, Gclk11); + // (adc1, self.mic.into()) } } diff --git a/boards/wio_terminal/src/storage.rs b/boards/wio_terminal/src/storage.rs index 526e58148cd5..6b9f729cf34a 100644 --- a/boards/wio_terminal/src/storage.rs +++ b/boards/wio_terminal/src/storage.rs @@ -1,10 +1,12 @@ +use core::marker::PhantomData; + use atsamd_hal::{ clock::{GenericClockController, Sercom6CoreClock}, delay::Delay, pac::{Mclk, Qspi}, prelude::*, qspi, - sercom::{spi, IoSet1, Sercom6}, + sercom::{spi, Sercom6}, time::Hertz, typelevel::NoneT, }; @@ -60,7 +62,7 @@ pub struct SDCard { pub det: SdDetReset, } -pub type SdPads = spi::Pads; +pub type SdPads = spi::Pads; pub type SdSpi = spi::Spi, spi::Duplex>; type Controller = embedded_sdmmc::VolumeManager< diff --git a/boards/wio_terminal/src/wifi.rs b/boards/wio_terminal/src/wifi.rs index 41c8905dd04c..86b27e155f71 100644 --- a/boards/wio_terminal/src/wifi.rs +++ b/boards/wio_terminal/src/wifi.rs @@ -4,7 +4,7 @@ use atsamd_hal::{ ehal::digital::OutputPin, pac::{interrupt, Mclk}, prelude::*, - sercom::{uart, IoSet2, Sercom0}, + sercom::{uart, Sercom0}, }; use bbqueue::{self, BBBuffer, Consumer, Producer}; @@ -46,7 +46,7 @@ pub struct Wifi { } /// UART pads for the labelled RX & TX pins -pub type WifiUartPads = uart::Pads; +pub type WifiUartPads = uart::Pads; /// UART device for the labelled RX & TX pins pub type WifiUart = uart::Uart, uart::Duplex>; diff --git a/development_documentation/development_atsamd_wio_2503/16 MHz, 304 M Samples [8].logicdata b/development_documentation/development_atsamd_wio_2503/16 MHz, 304 M Samples [8].logicdata new file mode 100644 index 000000000000..42b5f70a63f0 Binary files /dev/null and b/development_documentation/development_atsamd_wio_2503/16 MHz, 304 M Samples [8].logicdata differ diff --git a/development_documentation/development_atsamd_wio_2503/Zrzut ekranu 2025-03-16 221251.jpg b/development_documentation/development_atsamd_wio_2503/Zrzut ekranu 2025-03-16 221251.jpg new file mode 100644 index 000000000000..b27081eb421f Binary files /dev/null and b/development_documentation/development_atsamd_wio_2503/Zrzut ekranu 2025-03-16 221251.jpg differ diff --git a/development_documentation/development_atsamd_wio_2503/Zrzut ekranu 2025-03-16 221318.jpg b/development_documentation/development_atsamd_wio_2503/Zrzut ekranu 2025-03-16 221318.jpg new file mode 100644 index 000000000000..a15e05c3031c Binary files /dev/null and b/development_documentation/development_atsamd_wio_2503/Zrzut ekranu 2025-03-16 221318.jpg differ diff --git a/development_documentation/development_atsamd_wio_2503/Zrzut ekranu 2025-03-16 221338.jpg b/development_documentation/development_atsamd_wio_2503/Zrzut ekranu 2025-03-16 221338.jpg new file mode 100644 index 000000000000..7ef07fc1a7fd Binary files /dev/null and b/development_documentation/development_atsamd_wio_2503/Zrzut ekranu 2025-03-16 221338.jpg differ diff --git a/development_documentation/development_atsamd_wio_2503/Zrzut ekranu 2025-03-16 221354.jpg b/development_documentation/development_atsamd_wio_2503/Zrzut ekranu 2025-03-16 221354.jpg new file mode 100644 index 000000000000..3a310a21641a Binary files /dev/null and b/development_documentation/development_atsamd_wio_2503/Zrzut ekranu 2025-03-16 221354.jpg differ diff --git a/hal/Cargo.toml b/hal/Cargo.toml index 44aec215315b..a2586c77ff49 100644 --- a/hal/Cargo.toml +++ b/hal/Cargo.toml @@ -54,6 +54,7 @@ seq-macro = "0.3" sorted-hlist = "0.2.0" typenum = "1.12.0" void = {version = "1.0", default-features = false} +pin-project = {version = "1.1.9", default-features = false} #=============================================================================== # Optional depdendencies diff --git a/hal/src/peripherals/mod.rs b/hal/src/peripherals/mod.rs index 78eb4048a08c..7982281ca385 100644 --- a/hal/src/peripherals/mod.rs +++ b/hal/src/peripherals/mod.rs @@ -31,6 +31,16 @@ pub mod usb {} )] pub mod pwm {} +#[hal_module( + "clock-d5x" => "pwm_waveform/d5x.rs", +)] +pub mod pwm_wg {} + +#[hal_module( + "clock-d5x" => "timer_capture_waveform/d5x.rs", +)] +pub mod timer_capture_waveform {} + #[hal_module( any("clock-d11", "clock-d21") => "clock/d11.rs", "clock-d5x" => "clock/d5x/mod.rs", diff --git a/hal/src/peripherals/pwm/d5x.rs b/hal/src/peripherals/pwm/d5x.rs index 6b500ea1a6bd..214c8becc51a 100644 --- a/hal/src/peripherals/pwm/d5x.rs +++ b/hal/src/peripherals/pwm/d5x.rs @@ -11,6 +11,19 @@ use crate::timer_params::TimerParams; // Timer/Counter (TCx) +pub trait PinoutCollapse { + type PinId : PinId; + // type Pin = Pin; + + fn collapse(self) -> Pin; + // fn new_pin(pin: impl AnyPin) -> Self; +} +pub trait PinoutNewTrait { + fn new_pin(pin: Pin>) -> Self; +} +pub trait PinoutReadLevel { + fn read_level(&self) -> bool; +} /// This is a major syntax hack. /// /// The previous Pinout types were enums that took specific v1::Pin types. As a @@ -40,15 +53,46 @@ macro_rules! impl_tc_pinout { _pin: Pin, } + impl PinoutReadLevel for $Type { + fn read_level(&self) -> bool { + self._pin._is_high() + } + } + + impl PinoutCollapse for $Type { + type PinId = I; + fn collapse(self) -> Pin { + self._pin + } + } + $( + // $( #[$attr] )? + // impl $Type<$Id> { // those are specializations similar to C++ template specializations + // #[inline] + // pub fn new_pin(pin: impl AnyPin) -> Self { + // let _pin = pin.into().into_alternate(); + // Self { _pin } + // } + // } + + $( #[$attr] )? - impl $Type<$Id> { + impl PinoutNewTrait<$Id> for $Type<$Id> { // those are specializations similar to C++ template specializations #[inline] - pub fn $func(pin: impl AnyPin) -> Self { - let _pin = pin.into().into_alternate(); - Self { _pin } + fn new_pin(pin: Pin<$Id, Alternate>) -> Self { + Self { _pin: pin } } } + + + // Where should this be implemented? TODO: + // $( #[$attr] )? + // impl PinoutCollapse for $Type<$Id> { // those are specializations similar to C++ template specializations + // fn collapse(self) -> Pin<$Id, AlternateE> { + // self._pin + // } + // } )+ }; } @@ -78,7 +122,9 @@ impl_tc_pinout!(TC2Pinout: [ #[hal_cfg("pa13")] (Pa13, PA13), #[hal_cfg("pa17")] - (Pa17, PA17) + (Pa17, PA17), + #[hal_cfg("pa16")] + (Pa16, PA16) ]); #[hal_cfg("tc3")] @@ -93,6 +139,8 @@ impl_tc_pinout!(TC3Pinout: [ impl_tc_pinout!(TC4Pinout: [ #[hal_cfg("pa23")] (Pa23, PA23), + #[hal_cfg("pb08")] + (Pb8, PB08), #[hal_cfg("pb09")] (Pb9, PB09), #[hal_cfg("pb13")] diff --git a/hal/src/peripherals/pwm_waveform/d5x.rs b/hal/src/peripherals/pwm_waveform/d5x.rs new file mode 100644 index 000000000000..f14f9c26465c --- /dev/null +++ b/hal/src/peripherals/pwm_waveform/d5x.rs @@ -0,0 +1,403 @@ +#![allow(non_snake_case)] + +use atsamd_hal_macros::hal_cfg; + +#[cfg(feature = "async")] +use crate::dmac::ReadyFuture; +use crate::dmac::{AnyChannel, Beat, Buffer, Error as DmacError, TriggerAction, TriggerSource}; +// use crate::gpio::*; +use crate::gpio::PinId; +use crate::pac::Mclk; +use crate::time::Hertz; +use crate::timer_params::TimerParams; + +use paste::paste; + +#[derive(Clone)] +pub struct PwmWaveformGeneratorPtr(pub(in super::super) *mut T); + +unsafe impl Buffer for PwmWaveformGeneratorPtr { + type Beat = T; + + #[inline] + fn dma_ptr(&mut self) -> *mut Self::Beat { + self.0 + } + + #[inline] + fn incrementing(&self) -> bool { + false + } + + #[inline] + fn buffer_len(&self) -> usize { + 1 + } +} + +pub use crate::pwm::PinoutCollapse; +pub trait PwmWgFutureTrait { + type DmaChannel: AnyChannel; + type TC; + type Pinout: PinoutCollapse; + + fn decompose(self) -> (Self::DmaChannel, Self::TC, Self::Pinout); + fn start_regular_pwm(&mut self, ccx_value: u8); + async fn start_timer_prepare_dma_transfer( + &mut self, + ccx_value:u8, + generation_pattern_iter: impl Iterator) + -> Result<(), DmacError>; +} +pub trait PwmBaseTrait { + type TC; + type Pinout: PinoutCollapse; + type ConvertibleToFuture: PwmWgFutureTrait + where + D: AnyChannel; + + // type Future: PwmWgFutureTrait; + + /// Create a new PWM Waveform Generator + fn new_waveform_generator( + clock_freq: Hertz, + freq: Hertz, + tc: Self::TC, + pinout: Self::Pinout, + mclk: Option<&mut Mclk>, + ) -> Self; + fn with_dma_channel(self, channel: CH) -> Self::ConvertibleToFuture + where + CH: AnyChannel; +} +// Timer/Counter (TCx) +// +macro_rules! pwm_wg { + ($($TYPE:ident: ($TC:ident, $pinout:ident, $clock:ident, $apmask:ident, $apbits:ident, $wrapper:ident, $event:ident)),+) => { + $( + +use crate::pwm::$pinout; + +pub struct $TYPE { + /// The frequency of the attached clock, not the period of the pwm. + /// Used to calculate the period of the pwm. + clock_freq: Hertz, + requested_freq: Hertz, + tc: crate::pac::$TC, + #[allow(dead_code)] + pinout: $pinout, + // _channel: Option, +} + +paste!{ +pub struct [<$TYPE Future>]>{ + base_pwm: $TYPE, + _channel: DmaCh, + _init_level: u8, +} + +impl> [<$TYPE Future>] { + fn get_init_level(&self) -> u8 { + self._init_level + } + +} + +impl> PwmWgFutureTrait for [<$TYPE Future>] { + type DmaChannel = DmaCh; + type TC = crate::pac::$TC; + type Pinout = $pinout; + + async fn start_timer_prepare_dma_transfer(&mut self, ccx_value:u8, generation_pattern_iter: impl Iterator) + -> Result<(), DmacError> { + + let init_level = self.get_init_level(); + let mut generation_pattern_dma: [u8; N] = [init_level; N]; + for (idx, value) in generation_pattern_iter.enumerate() { + // TODO: move it to the right because for a reason it is not visisble on the wire + // plus resolve the initial driver state. Before the first TX it is low instead of high. + let idx = idx + 2; + if idx >= N { + break; + } + // Implement conditional inversion of the signal: + let value = value != INVERT; + // TODO: Implement configurable idle bus state level + let level = if value { 0xffu8 } else { 0x00u8 }; + generation_pattern_dma[idx] = level; + // hprintln!("trigger::source[{}]: {}", idx, level).ok(); + } + + let count = self.base_pwm.tc.count8(); + + count.cc(0).write(|w| unsafe { w.bits(0x00u8) }); + while count.syncbusy().read().cc0().bit_is_set() {} + count.cc(1).write(|w| unsafe { w.bits(ccx_value) }); + while count.syncbusy().read().cc1().bit_is_set() {} + count.ccbuf(0).write(|w| unsafe { w.bits(0x00u8) }); + count.ccbuf(1).write(|w| unsafe { w.bits(ccx_value) }); + + let pwm_dma_address = self.base_pwm.get_dma_ptr(); + let dma_future = self._channel.as_mut().transfer_future( + &mut generation_pattern_dma, + pwm_dma_address, + TriggerSource::$event, + TriggerAction::Burst, + ); + // Rest of the setup shall go into poll method: i.e. enabling interrupts and the counter + // of the timer. + count.ctrla().modify(|_, w| w.enable().set_bit()); + while count.syncbusy().read().enable().bit_is_set() {} + + // First poll the future starts the DMA transfer. It sets enable bit of the DMA + let value_to_return = dma_future.await; + + count.cc(1).write(|w| unsafe { w.bits(ccx_value) }); + count.ccbuf(1).write(|w| unsafe { w.bits(ccx_value) }); + + value_to_return + } + fn start_regular_pwm(&mut self, ccx_value: u8) { + self._init_level = ccx_value; + let count = self.base_pwm.tc.count8(); + count.cc(0).write(|w| unsafe { w.bits(0x00u8) }); + while count.syncbusy().read().cc0().bit_is_set() {} + count.cc(1).write(|w| unsafe { w.bits(ccx_value) }); + while count.syncbusy().read().cc1().bit_is_set() {} + + count.ccbuf(0).write(|w| unsafe { w.bits(0x00u8) }); + count.ccbuf(1).write(|w| unsafe { w.bits(ccx_value) }); + + count.ctrla().modify(|_, w| w.enable().set_bit()); + while count.syncbusy().read().enable().bit_is_set() {} + } + + fn decompose(self) -> (Self::DmaChannel, Self::TC, Self::Pinout) + { + let $TYPE{clock_freq, requested_freq, tc, pinout} = self.base_pwm; + (self._channel, tc, pinout) + } +} + +/* +/// +/// PRESCALER = 6, in reference project written in C++ +/// cc = 233 +/// per = 233 +/// +/// As the timer is set to produce idle level signal, it can be started before +/// we start the DMA transfer. It will naturally pick and start from the +/// beginning of next cycle of the timer. Timer is configured to prodduce +/// constant period signal by setting PER register to a value that corresponds +/// to requested frequency of the manchester signal. Base of the working +/// principle is that the timer CCx register will be loaded with either 0x00 +/// of 0xFF to produce either full cycle high or low signal. +*/ +impl PwmBaseTrait for $TYPE { + type TC = crate::pac::$TC; + type Pinout = $pinout; + type ConvertibleToFuture = [<$TYPE Future>] where + D: AnyChannel; + + fn new_waveform_generator( + clock_freq: Hertz, + freq: Hertz, + tc: Self::TC, + pinout: Self::Pinout, + mclk: Option<&mut Mclk>, + ) -> Self { + const TIEMR_PERIOD: u8 = 233; // mclk / 256 / 233 = 1000 Hz + let count = tc.count8(); + let tc_ccbuf_dma_data_register_address = tc.count8().ccbuf(1).as_ptr() as *const (); + // let PwmWaveformGeneratorPtr()(pub(in super::super) *mut T); + + // write(|w| w.ccbuf().bits(duty as u8)); + let _params = TimerParams::new(freq.convert(), clock_freq); + // Works as a mask: you can only enable the clock not disable it. TODO: check if it is set in read only/steal method. + match mclk { + Some(mclk) => { + mclk.$apmask().modify(|_, w| w.$apbits().set_bit()); + } + None => {} + } + count.ctrla().write(|w| w.swrst().set_bit()); + while count.ctrla().read().bits() & 1 != 0 {} + count.ctrla().modify(|_, w| w.enable().clear_bit()); + while count.syncbusy().read().enable().bit_is_set() {} + count.ctrla().modify(|_, w| w.mode().count8()); + count.ctrla().modify(|_, w| { + w.prescaler().div256() + // match params.divider { + // 1 => w.prescaler().div1(), + // 2 => w.prescaler().div2(), + // 4 => w.prescaler().div4(), + // 8 => w.prescaler().div8(), + // 16 => w.prescaler().div16(), + // 64 => w.prescaler().div64(), + // 256 => w.prescaler().div256(), + // 1024 => w.prescaler().div1024(), + // _ => unreachable!(), + // } + }); + + count.count().write(|w| unsafe { w.bits(233u8) }); + count.per().write(|w| unsafe { w.bits(233u8) }); + count.perbuf().write(|w| unsafe { w.bits(233u8) }); + // while count.syncbusy().read().per().bit_is_set() {} + + count.wave().write(|w| w.wavegen().npwm()); + // while count.syncbusy().read().wave().bit_is_set() {} + + // TODO: do the test: + // prerequisites: forget DMA configuration + // 1) Set CCx to 0x00 and measure the signal value + // 2) Set CCx to 0x7F and measure the signal value and frequency + // 3) Set CCx to 0xFF and measure the signal value + count.cc(0).write(|w| unsafe { w.bits(0x00u8/*params.cycles as u8*/) }); + while count.syncbusy().read().cc0().bit_is_set() {} + count.cc(1).write(|w| unsafe { w.bits(0xffu8) }); + while count.syncbusy().read().cc1().bit_is_set() {} + + Self { + clock_freq: clock_freq, + requested_freq: freq, + tc, + pinout, + } + } + + // pub fn with_dma_channels(self, rx: R, tx: T) -> Spi + fn with_dma_channel(self, channel: CH) -> Self::ConvertibleToFuture + where + CH: AnyChannel + { + [<$TYPE Future>] { + base_pwm: self, + _channel: channel, + _init_level: 0x00u8, + } + } +} + +impl $TYPE { + + pub fn start(&mut self) { + // Rest of the setup shall go into poll method: i.e. enabling interrupts and the counter + // of the timer. + let count = self.tc.count8(); + count.ctrla().modify(|_, w| w.enable().set_bit()); + while count.syncbusy().read().enable().bit_is_set() {} + } + pub fn GetDmaPtr(tc: crate::pac::$TC) -> PwmWaveformGeneratorPtr { + PwmWaveformGeneratorPtr(tc.count8().ccbuf(1).as_ptr() as *mut _) + } + pub fn get_dma_ptr(&self) -> PwmWaveformGeneratorPtr { + PwmWaveformGeneratorPtr(self.tc.count8().ccbuf(1).as_ptr() as *mut _) + } + + pub fn get_period(&self) -> Hertz { + let count = self.tc.count8(); + let divisor = count.ctrla().read().prescaler().bits(); + let top = count.cc(0).read().cc().bits(); + self.clock_freq / divisor as u32 / (top + 1) as u32 + } + + pub fn set_period(&mut self, period: Hertz) + { + let period = period.into(); + let params = TimerParams::new(period, self.clock_freq); + let count = self.tc.count8(); + count.ctrla().modify(|_, w| w.enable().clear_bit()); + while count.syncbusy().read().enable().bit_is_set() {} + count.ctrla().modify(|_, w| { + match params.divider { + 1 => w.prescaler().div1(), + 2 => w.prescaler().div2(), + 4 => w.prescaler().div4(), + 8 => w.prescaler().div8(), + 16 => w.prescaler().div16(), + 64 => w.prescaler().div64(), + 256 => w.prescaler().div256(), + 1024 => w.prescaler().div1024(), + _ => unreachable!(), + } + }); + count.ctrla().modify(|_, w| w.enable().set_bit()); + while count.syncbusy().read().enable().bit_is_set() {} + count.cc(0).write(|w| unsafe { w.cc().bits(params.cycles as u8) }); + while count.syncbusy().read().cc0().bit_is_set() {} + } +} +} // paste!() + +impl $crate::ehal::pwm::ErrorType for$TYPE { + type Error = ::core::convert::Infallible; +} + +impl $crate::ehal::pwm::SetDutyCycle for $TYPE { + fn max_duty_cycle(&self) -> u16 { + let count = self.tc.count8(); + let top = count.cc(0).read().cc().bits(); + top as u16 + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + let count = self.tc.count8(); + unsafe { count.ccbuf(1).write(|w| w.ccbuf().bits(duty as u8)); } + Ok(()) + } +} + +impl $crate::ehal_02::PwmPin for $TYPE { + type Duty = u16; + + fn disable(&mut self) { + let count = self.tc.count8(); + count.ctrla().modify(|_, w| w.enable().clear_bit()); + while count.syncbusy().read().enable().bit_is_set() {} + } + + fn enable(&mut self) { + let count = self.tc.count8(); + count.ctrla().modify(|_, w| w.enable().set_bit()); + while count.syncbusy().read().enable().bit_is_set() {} + } + + + fn get_duty(&self) -> Self::Duty { + let count = self.tc.count8(); + let duty: u8 = count.ccbuf(1).read().ccbuf().bits(); + duty as Self::Duty + } + + fn get_max_duty(&self) -> Self::Duty { + use $crate::ehal::pwm::SetDutyCycle; + self.max_duty_cycle() + } + + fn set_duty(&mut self, duty: Self::Duty) { + use $crate::ehal::pwm::SetDutyCycle; + let _ignore_infaillible = self.set_duty_cycle(duty); + } +} + +)+}} + +#[hal_cfg("tc0")] +pwm_wg! { PwmWg0: (Tc0, TC0Pinout, Tc0Tc1Clock, apbamask, tc0_, PwmWg0Wrapper, Tc0Ovf) } +#[hal_cfg("tc1")] +pwm_wg! { PwmWg1: (Tc1, TC1Pinout, Tc0Tc1Clock, apbamask, tc1_, PwmWg1Wrapper, Tc1Ovf) } +#[hal_cfg("tc2")] +pwm_wg! { PwmWg2: (Tc2, TC2Pinout, Tc2Tc3Clock, apbbmask, tc2_, PwmWg2Wrapper, Tc2Ovf) } +#[hal_cfg("tc3")] +pwm_wg! { PwmWg3: (Tc3, TC3Pinout, Tc2Tc3Clock, apbbmask, tc3_, PwmWg3Wrapper, Tc3Ovf) } +#[hal_cfg("tc4")] +pwm_wg! { PwmWg4: (Tc4, TC4Pinout, Tc4Tc5Clock, apbcmask, tc4_, PwmWg4Wrapper, Tc4Ovf) } +#[hal_cfg("tc5")] +pwm_wg! { PwmWg5: (Tc5, TC5Pinout, Tc4Tc5Clock, apbcmask, tc5_, PwmWg5Wrapper, Tc5Ovf) } +#[hal_cfg("tc6")] +pwm_wg! { PwmWg6: (Tc6, TC6Pinout, Tc6Tc7Clock, apbdmask, tc6_, PwmWg6Wrapper, Tc6Ovf) } +#[hal_cfg("tc7")] +pwm_wg! { PwmWg7: (Tc7, TC7Pinout, Tc6Tc7Clock, apbdmask, tc7_, PwmWg7Wrapper, Tc7Ovf) } + + // ($($TYPE:ident: ($TC:ident, $pinout:ident, $clock:ident, $apmask:ident, $apbits:ident, $wrapper:ident, $event:ident)),+) => { \ No newline at end of file diff --git a/hal/src/peripherals/timer_capture_waveform/d5x.rs b/hal/src/peripherals/timer_capture_waveform/d5x.rs new file mode 100644 index 000000000000..7f7d57312d36 --- /dev/null +++ b/hal/src/peripherals/timer_capture_waveform/d5x.rs @@ -0,0 +1,822 @@ +#![allow(non_snake_case)] + +use core::future::Future; +use core::sync::atomic; +use core::iter; + +use atsamd_hal_macros::hal_cfg; + +use crate::typelevel; +use crate::pac; +use crate::async_hal::interrupts::{Handler, Interrupt}; +#[cfg(feature = "async")] +use crate::dmac::ReadyFuture; +use crate::dmac::{AnyChannel, Beat, Buffer, Error as DmacError, TriggerAction, TriggerSource}; +use crate::gpio::*; +use crate::pac::Mclk; +use crate::time::Hertz; + +use pin_project::pin_project; + +use paste::paste; + +/// Channel 0 is used to capture the counter value while channel 1 is used to trigger interrupt +/// used for timeout handling + +const TIMER_CHANNEL: usize = 0; +// Second channel may be used for timeout detection. CCx can thus be set to some value +// that will be compared with current value of the counter and trigger interrupt when the value matches. +const TIMER_TIMEOUT_CHANNEL: usize = 1; + +#[hal_cfg("tc1-d11")] +type RegBlock = pac::tc1::RegisterBlock; + +#[hal_cfg("tc3-d21")] +type RegBlock = pac::tc3::RegisterBlock; + +#[hal_cfg("tc1-d5x")] +type RegBlock = pac::tc0::RegisterBlock; + +#[derive(Clone)] +pub struct TimerCaptureWaveformSourcePtr(pub(in super::super) *mut T); + +unsafe impl Buffer for TimerCaptureWaveformSourcePtr { + type Beat = T; + + #[inline] + fn dma_ptr(&mut self) -> *mut Self::Beat { + self.0 + } + + #[inline] + fn incrementing(&self) -> bool { + false + } + + #[inline] + fn buffer_len(&self) -> usize { + 1 + } +} + +#[pin_project] +struct TimerCaptureDmaWrapper<'a, DmaFut, T> { + #[pin] + _dma_future: DmaFut, + timer_started: bool, + tc_waker_index: usize, + _timer: &'a T, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct CounterValueAtTermination(u32); + +impl CounterValueAtTermination { + pub fn get_raw_value(self) -> u32 { + self.0 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct TimerCaptureData> { + data: Container, +} + +impl TimerCaptureData +where + Container: iter::Extend +{ + pub fn get_data(self) -> Container { + self.data + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TimerCaptureResultAvailable> { + DmaPollReady(TimerCaptureData), + TimerTimeout(TimerCaptureData), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct ExtendWithFirstZeroAndTerminationValueIterator<'a>{ + data: &'a [u32], + first_done: bool, + depleted: bool, + last_value: CounterValueAtTermination, +} + +impl<'a> ExtendWithFirstZeroAndTerminationValueIterator<'a> { + fn new(data: &'a [u32], counter_at_termination: CounterValueAtTermination) -> Self { + Self { data, first_done: false, depleted: false, last_value: counter_at_termination } + } +} + +// And only non-zero values are returned. Otherwise the iterator will return `None` +impl<'a> iter::Iterator for ExtendWithFirstZeroAndTerminationValueIterator<'a> { + type Item = u32; + fn next(&mut self) -> Option { + if self.data.len() == 0 { + if self.depleted == false { + self.depleted = true; + if let CounterValueAtTermination(value) = self.last_value { + return Some(value); + } else { + panic!("Invalid value of the counter at termination"); + } + } + return None; + } + if self.first_done == false { + self.first_done = true; + return Some(0u32); + } + if self.depleted == true { + return None; + } + + let value = self.data[0]; + self.data = &self.data[1..]; + if value == 0 { + self.depleted = true; + return None; + } + Some(value) + } +} + +#[derive(Debug, PartialEq, Eq)] +struct TimerCaptureRawData<'a> { + counter_at_termination: CounterValueAtTermination, + data: Option<&'a mut [u32]>, +} +// TODO: DmaPollReady and TimerTimeout should be merged into one trait / type to avoid processing code duplication: +#[derive(Debug, PartialEq, Eq)] +pub enum TimerCaptureRawResultAvailable<'a> { + DmaPollReady(TimerCaptureRawData<'a>), + TimerTimeout(TimerCaptureRawData<'a>), +} + +impl<'a> AsMut> for TimerCaptureRawResultAvailable<'a> { + fn as_mut(&mut self) -> &mut TimerCaptureRawData<'a> { + match self { + TimerCaptureRawResultAvailable::DmaPollReady(data) => data, + TimerCaptureRawResultAvailable::TimerTimeout(data) => data, + } + } +} + +impl<'a> TimerCaptureRawResultAvailable<'a> { + pub fn get_public_data>(self, mut container: Container) -> TimerCaptureResultAvailable { + let adjust_ratio = |x| {(x as u64 * 4173*2) / 1000}; // TODO: fix this by some ratio compund type + + match self { + TimerCaptureRawResultAvailable::DmaPollReady(data) => { + todo!() + } + TimerCaptureRawResultAvailable::TimerTimeout(data) => { + let counter_value_at_termination = data.counter_at_termination; + if let Some(data) = data.data { + // container.extend(data.iter().map(|x| core::time::Duration::from_nanos(adjust_ratio(*x as u64)))); + let data_with_preceeding_zero = ExtendWithFirstZeroAndTerminationValueIterator::new(data, counter_value_at_termination); + container.extend(data_with_preceeding_zero.clone(). + zip(data_with_preceeding_zero.skip(1)). + map(|(x, y)|{ + if y > x { + core::time::Duration::from_nanos(adjust_ratio(y as u64 - x as u64)) + } else { + // TODO: Better handling for errror here: + todo!() + } + } + )); + TimerCaptureResultAvailable::TimerTimeout(TimerCaptureData { + data: container, + }) + } + else {todo!()} + } + } + } + pub fn fill_the_capture_memory<'b>(&mut self, capture_memory: &'b mut [u32]) + where + 'b: 'a, + { + self.as_mut().data = Some(capture_memory); + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TimerCaptureFailure { + DmaFailed(DmacError), + TimerFailed, +} + +impl<'a, DmaFut, T> TimerCaptureDmaWrapper<'a, DmaFut, T> { + fn new(dma_future: DmaFut, timer: &'a T, tc_index: usize) -> Self { + Self { + _dma_future: dma_future, + timer_started: false, + tc_waker_index:tc_index, + _timer: timer, + } + } +} + +impl<'a, DmaFut, T> core::future::Future for TimerCaptureDmaWrapper<'a, DmaFut, T> +where + DmaFut: core::future::Future>, + T: TimerCounterStart, +{ + type Output = Result, TimerCaptureFailure>; + + fn poll( + mut self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + use waker::MC1_INTERRUPT_FIRED; + + // let mut pinned = core::pin::pin!(self); + let this = self.as_mut().project(); + + // First time the future is polled, it sets enable bit of DMA. Only after that the timer shall be started. + let result = this._dma_future.poll(cx); + if result == core::task::Poll::Ready(Ok(())) { + let counter_value = this._timer.counter_value(); + this._timer.stop(); + this._timer.disable_interrupt(); + let raw_result = TimerCaptureRawData { + counter_at_termination: CounterValueAtTermination(counter_value), + data: None, + }; + // TODO: add check on the mc1 flag related to timeout + return core::task::Poll::Ready(Ok(TimerCaptureRawResultAvailable::DmaPollReady(raw_result))); + } + + if *this.timer_started == false { + *this.timer_started = true; + MC1_INTERRUPT_FIRED[*this.tc_waker_index].store(false, atomic::Ordering::Relaxed); + // TODO: Enable interrupts of the timer + this._timer.start(); + // Enable the NVIC interrupt: + this._timer.enable_interrupt(); + } + + // Check if the interrupt was fired: + let result = if + MC1_INTERRUPT_FIRED[*this.tc_waker_index].load(atomic::Ordering::Relaxed) { + // counter_value + let counter_value = this._timer.counter_value(); + this._timer.stop(); + this._timer.disable_interrupt(); + let raw_result = TimerCaptureRawData { + counter_at_termination: CounterValueAtTermination(counter_value), + data: None, + }; + core::task::Poll::Ready(Ok(TimerCaptureRawResultAvailable::TimerTimeout(raw_result))) + } else { + use waker::WAKERS; + // TODO: Do I have to disable interrupt/ put registartion into critical section? + WAKERS[self.tc_waker_index].register(cx.waker()); + // TODO: poll must be called so we need to register Waker for the future. + // The problem is that we have two source of the wake-up events: DMA and Timer. + // We need to combine them into one or as simple alternative we can use the Timer to wake up at the timeout. + // This is not very efficient but it is simpler. + core::task::Poll::Pending + }; + result + } +} + +/// Trait enabling the use of a Timer/Counter in async mode. Specifically, this +/// trait enables us to register a `TC*` interrupt as a waker for timer futures. +/// +/// **⚠️ Warning** This trait should not be implemented outside of this crate! +pub trait TimerSpecificInterruptAndRegisters/* : Sealed*/ { + /// Index of this TC in the `STATE` tracker + const WAKER_ID: usize; + + /// Get a reference to the timer's register block + fn reg_block(peripherals: &pac::Peripherals) -> &RegBlock; + + /// Interrupt type for this timer + type Interrupt: Interrupt; +} + +// Timer/Counter (TCx) +// +trait TimerCounterInterrupt { + type Interrupt: Interrupt; +} + +struct TimerCounterTimeoutInterruptHandler { + _private: (), + _tc: core::marker::PhantomData, +} + +trait TimerTraitToDo { +} + +pub struct InterruptHandlerAsdf { + _private: (), + _tc: core::marker::PhantomData, +} + +// impl typelevel::Sealed for TimerCounterTimeoutInterruptHandler {} + +// impl Handler for TimerCounterTimeoutInterruptHandler { +// unsafe fn on_interrupt() { +// use waker::WAKERS; +// // WAKERS[extract_number!(I)].wake(); +// let peripheral = unsafe{ crate::pac::Peripherals::steal() }; +// } +// } + +const fn extract_number(tc_name: &str) -> usize { + let bytes = tc_name.as_bytes(); + let last_byte = bytes[bytes.len() - 1]; + let tc_number = (last_byte - b'0') as usize; + assert!(tc_number < MAX_TIMER_COUNT); + tc_number +} + +trait TimerCaptureCapable { + type Interrupt: Interrupt; + const WAKER_IDX: usize; + fn reg_block(peripherals: &pac::Peripherals) -> &RegBlock; +} + +pub struct TimerCaptureInterruptHandler { + _private: (), + _tc: core::marker::PhantomData +} + +impl typelevel::Sealed for TimerCaptureInterruptHandler {} +impl Handler for TimerCaptureInterruptHandler { + unsafe fn on_interrupt() { + let periph = unsafe { crate::pac::Peripherals::steal() }; + let tc = T::reg_block(&periph); + let intflag = &tc.count32().intflag(); + + // Wake only on the compare match channel 1, which is used for the timeout detection. + if intflag.read().mc1().bit_is_set() { + // Clear the flag + intflag.modify(|_, w| w.mc1().set_bit()); + use waker::{WAKERS, MC1_INTERRUPT_FIRED}; + MC1_INTERRUPT_FIRED[T::WAKER_IDX].store(true, atomic::Ordering::Relaxed); + WAKERS[T::WAKER_IDX].wake(); + } + } +} + +pub use crate::pwm::{PinoutCollapse, PinoutReadLevel}; + +pub trait TimerCaptureBaseTrait { + type TC; + type Pinout: PinoutCollapse; + type ConvertibleToFuture: TimerCaptureFutureTrait + where + D: AnyChannel; + + // fn get_dma_ptr(&self) -> TimerCaptureWaveformSourcePtr; + fn new_timer_capture( + clock_freq: Hertz, + freq: Hertz, + tc: Self::TC, + pinout: Self::Pinout, + mclk: Option<&mut Mclk>, + // timeout: MillisDurationU32, + ) -> Self; + fn enable_mclk_clocks(mclk: &mut Mclk); + fn with_dma_channel(self, channel: CH) -> Self::ConvertibleToFuture + where + CH: AnyChannel; +} + +pub trait TimerCaptureFutureTrait { + type DmaChannel; + type TC; + type Pinout: PinoutCollapse; + fn decompose(self) -> (Self::DmaChannel, Self::TC, Self::Pinout); + // fn start_regular_pwm(&mut self, ccx_value: u8); + async fn start_timer_execute_dma_transfer, const N: usize>(&mut self, timestamps_capture: Container) + -> Result, TimerCaptureFailure>; + fn read_pin_level(&mut self) -> bool; +} + +macro_rules! create_timer_capture { + ($($TYPE:ident: ($TC:ident, $pinout:ident, $clock:ident, $apmask:ident, $apbits:ident, $wrapper:ident, $event:ident)),+) => { + $( + +macro_rules! extract_number_macro { + ($tc_name:ident) => { + { + extract_number(stringify!{$tc_name}) + } + }; +} + +use crate::pwm::$pinout; + +pub struct $TYPE { + /// The frequency of the attached clock, not the period of the pwm. + /// Used to calculate the period of the pwm. + clock_freq: Hertz, + tc: crate::pac::$TC, + #[allow(dead_code)] + pinout: $pinout, + // _channel: Option, +} + +impl TimerCaptureBaseTrait for $TYPE { + type TC = crate::pac::$TC; + type Pinout = $pinout; + type ConvertibleToFuture = paste!{ [<$TYPE Future>] } + where + D: AnyChannel; + + // fn get_dma_ptr(&self) -> TimerCaptureWaveformSourcePtr { + // TimerCaptureWaveformSourcePtr(self.tc.count32().cc(TIMER_CHANNEL).as_ptr() as *mut _) + // } + + fn enable_mclk_clocks(mclk: &mut Mclk) + { + Self::enable_mclk_clocks(mclk); + } + fn new_timer_capture( + clock_freq: Hertz, + freq: Hertz, + tc: crate::pac::$TC, + pinout: $pinout, + mclk: Option<&mut Mclk>, + // timeout: MillisDurationU32, + ) -> Self { + Self::new_timer_capture(clock_freq, freq, tc, pinout, mclk) + } + + fn with_dma_channel(self, channel: CH) -> Self::ConvertibleToFuture + where + CH: AnyChannel, + { + self.with_dma_channel(channel) + } +} + +impl typelevel::Sealed for $TYPE {} + +paste!{ + pub struct [<$TYPE InterruptData >] { + } + impl TimerCaptureCapable for [< $TYPE InterruptData >] { + const WAKER_IDX: usize = extract_number_macro!($TC); + + type Interrupt = crate::async_hal::interrupts::[< $TC:upper >]; + + fn reg_block(peripherals: &pac::Peripherals) -> &RegBlock { + &*peripherals.[< $TC:lower >] + } + } +} + +// type Interrupt = crate::async_hal::interrupts::[< $TC:upper >]; +// paste!{ +// impl Handler]> for $TYPE { +// unsafe fn on_interrupt() { +// use waker::WAKERS; +// WAKERS[0].wake(); +// } +// } +// } + +impl TimerSpecificInterruptAndRegisters for $TYPE { + const WAKER_ID: usize = extract_number_macro!($TC); + + paste!{ + fn reg_block(peripherals: &pac::Peripherals) -> &RegBlock { + &*peripherals.[< $TC:lower >] + } + + type Interrupt = crate::async_hal::interrupts::[< $TC:upper >]; + } +} + +paste!{ +pub struct [<$TYPE Future>]>{ + base_pwm: $TYPE, + _channel: DmaCh +} + +// Implement Interrupt traits for basic timer struct: +impl TimerCounterInterrupt for $TYPE { + type Interrupt = crate::async_hal::interrupts::[< $TC:upper >]; +} + +impl> [<$TYPE Future>] { + + fn start_capture_timer(&mut self) { + let count = self.base_pwm.tc.count32(); + + count.ctrla().modify(|_, w| w.enable().set_bit()); + while count.syncbusy().read().enable().bit_is_set() {} + } + +} +impl> TimerCaptureFutureTrait for [<$TYPE Future>] { + type DmaChannel = DmaCh; + type TC = crate::pac::$TC; + type Pinout = $pinout; + fn decompose(self) -> (Self::DmaChannel, Self::TC, Self::Pinout){ + let $TYPE{clock_freq, tc, pinout} = self.base_pwm; + (self._channel, tc, pinout) + } + fn read_pin_level(&mut self) -> bool { + // let count = self.base_pwm.tc.count32(); + // let pinout = self.base_pwm.pinout; + // let pin = pinout.get_pin(); + // let level = pin.is_high().unwrap(); + // level + self.base_pwm.read_pin_level() + } + /// The capture_memorys first element will be the value of the counter at the moment of the first event. The timer starts counting from zero, which mean the first period can be assumed as the value of the first element in the memory. + async fn start_timer_execute_dma_transfer, const N: usize> + (&mut self, mut capture_container: ContainerData) + -> Result, TimerCaptureFailure> { + + let count = self.base_pwm.tc.count32(); + + let pwm_dma_address = self.base_pwm.get_dma_ptr(); + + let mut capture_memory: [u32; N] = [0; N]; + // TODO: core::pin::pin_mut!(capture_memory); + // TODO: make transfer_future method to return real number of transferred items + let dma_future = self._channel.as_mut().transfer_future( + pwm_dma_address, + &mut capture_memory, + TriggerSource::$event, + TriggerAction::Burst, + ); + + // dma_future.as_mut().poll() + + let dma_wrapped_future = TimerCaptureDmaWrapper::new(dma_future, &self.base_pwm, extract_number_macro!($TC)); + + // TODO: Change the implementation of the DMA channel so that the timer can be started before the DMA gets enabled + // First poll the future starts the DMA transfer. It sets enable bit of the DMA. + let result = dma_wrapped_future.await; + // Right after the DMA transfer is started, we can start the timer. + + if let Ok(mut result) = result { + result.fill_the_capture_memory(&mut capture_memory); + Ok(result.get_public_data(capture_container)) + }else { + // Err(TimerCaptureFailure::DmaFailed(DmacError::TransferError)) + todo!() + } + // type Output = Result, TimerCaptureFailure>; + // Rest of the setup shall go into poll method: i.e. enabling interrupts and the counter + // of the timer. + // self.start_capture_timer(); + // count.ctrla().modify(|_, w| w.enable().set_bit()); + // while count.syncbusy().read().enable().bit_is_set() {} + + // wait for the settings to be applied + // while count.syncbusy().read().cc0().bit_is_set() {} + // result + } + +} + +} // paste macrto + +/* +/// +/// To resolve the issue of the end condition for DMA transfer we probably need to use second interrupt signal from the timer +/// One possibility is to use the overflow, but considering 32 bits counter it is not very practical. +/// Another possibility is to use the compare match on timer channel 1. The compare match can be set to the maximum value of the counter. +/// +*/ +impl $TYPE { + pub fn new_timer_capture( + clock_freq: Hertz, + _freq: Hertz, + tc: crate::pac::$TC, + pinout: $pinout, + mclk: Option<&mut Mclk>, + // timeout: MillisDurationU32, + ) -> Self { + // TODO: Calculate the valuee of the compare match register: + let capture_comapre_value_timeout = 0x4000_0000_u32; + let count = tc.count32(); + // let tc_ccbuf_dma_data_register_address = count.cc(TIMER_CHANNEL).as_ptr() as *const (); + // let TimerCaptureWaveformSourcePtr()(pub(in super::super) *mut T); + + // write(|w| w.ccbuf().bits(duty as u8)); + // let params = TimerParams::new(freq.convert(), clock_freq); + + match mclk { + Some(mclk) => { + mclk.$apmask().modify(|_, w| w.$apbits().set_bit()); + // TODO: Dirty hack to allow TC4 + TC5 timers work in 32 bits. This is somewhat against + // datasheet declarations so be cerful. + mclk.apbcmask().modify(|_, w| w.tc5_().set_bit()); + // TODO: Dirty hack to allow TC2 + TC3 timers work in 32 bits. This is somewhat against + // datasheet declarations so be cerful.In the future I expect it will be added to + // take over the ownership of the other timer as well so that it is blocked to be + // used for other purposes. + mclk.apbbmask().modify(|_, w| w.tc3_().set_bit()); + } + None => {} + } + + // First disable the timer, only after that we should set SWRST bit. + count.ctrla().modify(|_, w| w.enable().clear_bit()); + while count.syncbusy().read().enable().bit_is_set() {} + + count.ctrla().write(|w| w.swrst().set_bit()); + while count.ctrla().read().bits() & 1 != 0 {} + + // Set the timer to 32-bit mode: + count.ctrla().modify(|_, w| w.mode().count32()); + count.ctrla().modify(|_, w| { + w.prescaler().div1() + // match params.divider { + // 1 => w.prescaler().div1(), + // 2 => w.prescaler().div2(), + // 4 => w.prescaler().div4(), + // 8 => w.prescaler().div8(), + // 16 => w.prescaler().div16(), + // 64 => w.prescaler().div64(), + // 256 => w.prescaler().div256(), + // 1024 => w.prescaler().div1024(), + // _ => unreachable!(), + // } + }); + // TODO: Set capten0 and clear capten1. + count.ctrla().modify(|_, w| w.capten0().set_bit().copen0().set_bit()); + + // clear all interrupt flags: + count.intflag().write(|w| w.mc1().set_bit().mc0().set_bit().ovf().set_bit().err().set_bit()); + + count.evctrl().write(|w| w.evact().stamp().mceo0().set_bit()); + // enable interrupt on the timeout side channel: + count.intenset().modify(|_, w| w.mc1().set_bit() ); + + // It is possible to set capture on falling edges by setting the INVEN bit. + // count.drvctrl().write(|w| w.inven0().set_bit()); + + // count.ccbuf(0).write(|w| unsafe { w.bits(0x00) }); + // count.ccbuf(1).write(|w| unsafe { w.bits(0x00) }); + count.cc(0).write(|w| unsafe { w.bits(0x00) }); + while count.syncbusy().read().cc0().bit_is_set() {} + count.cc(1).write(|w| unsafe { w.bits(capture_comapre_value_timeout) }); + while count.syncbusy().read().cc1().bit_is_set() {} + + Self { + clock_freq: clock_freq, + tc, + pinout, + } + } + + pub fn read_pin_level(&self) -> bool { + // let pinout = self.pinout; + // let pin = pinout.get_pin(); + // let level = pin.is_high().unwrap(); + // level + self.pinout.read_level() + } + + paste!{ + // pub fn with_dma_channels(self, rx: R, tx: T) -> Spi + pub fn with_dma_channel(self, channel: CH ) -> [<$TYPE Future>] + where + CH: AnyChannel + { + [<$TYPE Future>] { + base_pwm: self, + _channel: channel, + } + } + } + + pub fn enable_mclk_clocks(mclk: &mut Mclk) { + mclk.$apmask().modify(|_, w| w.$apbits().set_bit()); + // TODO: Dirty hack to allow TC4 + TC5 timers work in 32 bits. This is somewhat against + // datasheet declarations so be cerful. + mclk.apbcmask().modify(|_, w| w.tc5_().set_bit()); + // TODO: Dirty hack to allow TC2 + TC3 timers work in 32 bits. This is somewhat against + // datasheet declarations so be cerful.In the future I expect it will be added to + // take over the ownership of the other timer as well so that it is blocked to be + // used for other purposes. + mclk.apbbmask().modify(|_, w| w.tc3_().set_bit()); + } + + pub fn start(&self) { + // Rest of the setup shall go into poll method: i.e. enabling interrupts and the counter + // of the timer. + let count = self.tc.count32(); + count.ctrla().modify(|_, w| w.enable().set_bit()); + while count.syncbusy().read().enable().bit_is_set() {} + } + + pub fn stop(&self){ + let count = self.tc.count32(); + count.ctrla().modify(|_, w| w.enable().clear_bit()); + while count.syncbusy().read().enable().bit_is_set() {} + } + + pub fn GetDmaPtr(tc: crate::pac::$TC) -> TimerCaptureWaveformSourcePtr { + TimerCaptureWaveformSourcePtr(tc.count32().cc(TIMER_CHANNEL).as_ptr() as *mut _) + } + pub fn get_dma_ptr(&self) -> TimerCaptureWaveformSourcePtr { + TimerCaptureWaveformSourcePtr(self.tc.count32().cc(TIMER_CHANNEL).as_ptr() as *mut _) + } +} + +impl TimerCounterStart for $TYPE { + paste!{ + type Interrupt = crate::async_hal::interrupts::[< $TC:upper >]; + } + fn start(&self) + { + self.start(); + } + fn stop(&self) + { + self.stop(); + } + fn is_mc1_interrupt_active(&self) -> bool { + let count = self.tc.count32(); + count.intflag().read().mc1().bit_is_set() + // if intflag.read().mc1().bit_is_set() { + } + fn enable_interrupt(&self) + { + unsafe { + Self::Interrupt::enable(); + } + } + fn disable_interrupt(&self) + { + Self::Interrupt::disable(); + } + fn counter_value(&self) -> u32 + { + // trigger counter value read: + let _ = self.tc.count32().ctrlbset().write(|w| w.cmd().readsync()); + // return counter value: + self.tc.count32().count().read().bits() + } +} + +impl $crate::ehal::pwm::ErrorType for $TYPE { + type Error = ::core::convert::Infallible; +} + +)+}} + +#[hal_cfg("tc0")] +create_timer_capture! { TimerCapture0: (Tc0, TC0Pinout, Tc0Tc1Clock, apbamask, tc0_, TimerCapture0Wrapper, Tc0Mc0) } +#[hal_cfg("tc1")] +create_timer_capture! { TimerCapture1: (Tc1, TC1Pinout, Tc0Tc1Clock, apbamask, tc1_, TimerCapture1Wrapper, Tc1Mc0) } +#[hal_cfg("tc2")] +create_timer_capture! { TimerCapture2: (Tc2, TC2Pinout, Tc2Tc3Clock, apbbmask, tc2_, TimerCapture2Wrapper, Tc2Mc0) } +#[hal_cfg("tc3")] +create_timer_capture! { TimerCapture3: (Tc3, TC3Pinout, Tc2Tc3Clock, apbbmask, tc3_, TimerCapture3Wrapper, Tc3Mc0) } +#[hal_cfg("tc4")] +create_timer_capture! { TimerCapture4: (Tc4, TC4Pinout, Tc4Tc5Clock, apbcmask, tc4_, TimerCapture4Wrapper, Tc4Mc0) } +#[hal_cfg("tc5")] +create_timer_capture! { TimerCapture5: (Tc5, TC5Pinout, Tc4Tc5Clock, apbcmask, tc5_, TimerCapture5Wrapper, Tc5Mc0) } +#[hal_cfg("tc6")] +create_timer_capture! { TimerCapture6: (Tc6, TC6Pinout, Tc6Tc7Clock, apbdmask, tc6_, TimerCapture6Wrapper, Tc6Mc0) } +#[hal_cfg("tc7")] +create_timer_capture! { TimerCapture7: (Tc7, TC7Pinout, Tc6Tc7Clock, apbdmask, tc7_, TimerCapture7Wrapper, Tc7Mc0) } + +trait TimerCounterStart { + type Interrupt: Interrupt; + fn start(&self); + fn stop(&self); + fn is_mc1_interrupt_active(&self) -> bool; + fn enable_interrupt(&self); + fn disable_interrupt(&self); + fn counter_value(&self) -> u32; +} + +const MAX_TIMER_COUNT: usize = 8; + +#[cfg(feature = "async")] +mod waker { + use core::sync::atomic::AtomicBool; + + use embassy_sync::waitqueue::AtomicWaker; + + use super::MAX_TIMER_COUNT; + + #[allow(clippy::declare_interior_mutable_const)] + const NEW_WAKER: AtomicWaker = AtomicWaker::new(); + pub(super) static WAKERS: [AtomicWaker; MAX_TIMER_COUNT] = + [NEW_WAKER; MAX_TIMER_COUNT]; + const NEW_INTERRUP_FIRED: AtomicBool = AtomicBool::new(false); + pub(super) static MC1_INTERRUPT_FIRED: [AtomicBool; MAX_TIMER_COUNT] = + [NEW_INTERRUP_FIRED; MAX_TIMER_COUNT]; +} +