From 12d1e7dbc3bc46db09a650af688f033375bac4ec Mon Sep 17 00:00:00 2001 From: michel Date: Wed, 7 Aug 2024 21:58:49 +0200 Subject: [PATCH] STM32-TSC: enable discriminating between pins within same TSC group and improve TSC library in general --- .gitignore | 4 + embassy-stm32/src/tsc/acquisition_banks.rs | 209 ++++ embassy-stm32/src/tsc/config.rs | 175 +++ embassy-stm32/src/tsc/errors.rs | 21 + embassy-stm32/src/tsc/mod.rs | 1011 ++--------------- embassy-stm32/src/tsc/pin_groups.rs | 675 +++++++++++ embassy-stm32/src/tsc/tsc.rs | 456 ++++++++ .../src/tsc/{enums.rs => tsc_io_pin.rs} | 202 ++-- embassy-stm32/src/tsc/types.rs | 93 ++ examples/stm32f3/README.md | 24 + examples/stm32f3/src/bin/blocking-tsc.rs | 98 -- examples/stm32f3/src/bin/tsc_blocking.rs | 138 +++ examples/stm32l0/.cargo/config.toml | 2 +- examples/stm32l0/Cargo.toml | 2 +- examples/stm32l0/README.md | 24 + examples/stm32l0/src/bin/async-tsc.rs | 122 -- examples/stm32l0/src/bin/blocking-tsc.rs | 116 -- examples/stm32l0/src/bin/tsc_async.rs | 116 ++ examples/stm32l0/src/bin/tsc_blocking.rs | 142 +++ examples/stm32l0/src/bin/tsc_multipin.rs | 233 ++++ examples/stm32l4/.cargo/config.toml | 3 +- examples/stm32l4/Cargo.toml | 2 +- examples/stm32l4/README.md | 24 + examples/stm32l4/src/bin/tsc_async.rs | 108 ++ examples/stm32l4/src/bin/tsc_blocking.rs | 147 +++ examples/stm32l4/src/bin/tsc_multipin.rs | 222 ++++ examples/stm32u5/src/bin/tsc.rs | 73 +- 27 files changed, 3005 insertions(+), 1437 deletions(-) create mode 100644 embassy-stm32/src/tsc/acquisition_banks.rs create mode 100644 embassy-stm32/src/tsc/config.rs create mode 100644 embassy-stm32/src/tsc/errors.rs create mode 100644 embassy-stm32/src/tsc/pin_groups.rs create mode 100644 embassy-stm32/src/tsc/tsc.rs rename embassy-stm32/src/tsc/{enums.rs => tsc_io_pin.rs} (52%) create mode 100644 embassy-stm32/src/tsc/types.rs create mode 100644 examples/stm32f3/README.md delete mode 100644 examples/stm32f3/src/bin/blocking-tsc.rs create mode 100644 examples/stm32f3/src/bin/tsc_blocking.rs create mode 100644 examples/stm32l0/README.md delete mode 100644 examples/stm32l0/src/bin/async-tsc.rs delete mode 100644 examples/stm32l0/src/bin/blocking-tsc.rs create mode 100644 examples/stm32l0/src/bin/tsc_async.rs create mode 100644 examples/stm32l0/src/bin/tsc_blocking.rs create mode 100644 examples/stm32l0/src/bin/tsc_multipin.rs create mode 100644 examples/stm32l4/README.md create mode 100644 examples/stm32l4/src/bin/tsc_async.rs create mode 100644 examples/stm32l4/src/bin/tsc_blocking.rs create mode 100644 examples/stm32l4/src/bin/tsc_multipin.rs diff --git a/.gitignore b/.gitignore index 1c221e876c..25fa4a4f19 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ Cargo.lock third_party /Cargo.toml out/ + +# editor artifacts +.neoconf.json +*.vim diff --git a/embassy-stm32/src/tsc/acquisition_banks.rs b/embassy-stm32/src/tsc/acquisition_banks.rs new file mode 100644 index 0000000000..21a5c3f87f --- /dev/null +++ b/embassy-stm32/src/tsc/acquisition_banks.rs @@ -0,0 +1,209 @@ +#[cfg(any(tsc_v2, tsc_v3))] +use super::pin_groups::G7; +#[cfg(tsc_v3)] +use super::pin_groups::G8; +use super::pin_groups::{tsc_pin_roles, G1, G2, G3, G4, G5, G6}; +use super::tsc_io_pin::*; +use super::types::{Group, GroupStatus}; +use super::TSC_NUM_GROUPS; + +/// Represents a collection of TSC (Touch Sensing Controller) pins for an acquisition bank. +/// +/// This struct holds optional `TscIOPin` values for each TSC group, allowing for flexible +/// configuration of TSC acquisition banks. Each field corresponds to a specific TSC group +/// and can be set to `Some(TscIOPin)` if that group is to be included in the acquisition, +/// or `None` if it should be excluded. +#[allow(missing_docs)] +#[derive(Default)] +pub struct TscAcquisitionBankPins { + pub g1_pin: Option>, + pub g2_pin: Option>, + pub g3_pin: Option>, + pub g4_pin: Option>, + pub g5_pin: Option>, + pub g6_pin: Option>, + #[cfg(any(tsc_v2, tsc_v3))] + pub g7_pin: Option>, + #[cfg(tsc_v3)] + pub g8_pin: Option>, +} + +impl TscAcquisitionBankPins { + /// Returns an iterator over the pins in this acquisition bank. + /// + /// This method allows for easy traversal of all configured pins in the bank. + pub fn iter(&self) -> TscAcquisitionBankPinsIterator { + TscAcquisitionBankPinsIterator(TscAcquisitionBankIterator::new(self)) + } +} + +/// Iterator for TSC acquisition banks. +/// +/// This iterator allows traversing through the pins of a `TscAcquisitionBankPins` struct, +/// yielding each configured pin in order of the TSC groups. +pub struct TscAcquisitionBankIterator<'a> { + pins: &'a TscAcquisitionBankPins, + current_group: u8, +} + +impl<'a> TscAcquisitionBankIterator<'a> { + fn new(pins: &'a TscAcquisitionBankPins) -> Self { + Self { pins, current_group: 0 } + } + + fn next_pin(&mut self) -> Option { + while self.current_group < TSC_NUM_GROUPS as u8 { + let pin = match self.current_group { + 0 => self.pins.g1_pin.map(TscIOPinWithRole::get_pin), + 1 => self.pins.g2_pin.map(TscIOPinWithRole::get_pin), + 2 => self.pins.g3_pin.map(TscIOPinWithRole::get_pin), + 3 => self.pins.g4_pin.map(TscIOPinWithRole::get_pin), + 4 => self.pins.g5_pin.map(TscIOPinWithRole::get_pin), + 5 => self.pins.g6_pin.map(TscIOPinWithRole::get_pin), + #[cfg(any(tsc_v2, tsc_v3))] + 6 => self.pins.g7_pin.map(TscIOPinWithRole::get_pin), + #[cfg(tsc_v3)] + 7 => self.pins.g8_pin.map(TscIOPinWithRole::get_pin), + _ => None, + }; + self.current_group += 1; + if pin.is_some() { + return pin; + } + } + None + } +} + +/// Iterator for TSC acquisition bank pins. +/// +/// This iterator yields `TscIOPin` values for each configured pin in the acquisition bank. +pub struct TscAcquisitionBankPinsIterator<'a>(TscAcquisitionBankIterator<'a>); + +impl<'a> Iterator for TscAcquisitionBankPinsIterator<'a> { + type Item = TscIOPin; + + fn next(&mut self) -> Option { + self.0.next_pin() + } +} + +impl TscAcquisitionBankPins { + /// Returns an iterator over the available pins in the bank + pub fn pins_iterator(&self) -> TscAcquisitionBankPinsIterator { + TscAcquisitionBankPinsIterator(TscAcquisitionBankIterator::new(self)) + } +} + +/// Represents a collection of TSC pins to be acquired simultaneously. +/// +/// This struct contains a set of pins to be used in a TSC acquisition with a pre-computed and +/// verified mask for efficiently setting up the TSC peripheral before performing an acquisition. +/// It ensures that only one channel pin per TSC group is included, adhering to hardware limitations. +pub struct TscAcquisitionBank { + pub(super) pins: TscAcquisitionBankPins, + pub(super) mask: u32, +} + +impl TscAcquisitionBank { + /// Returns an iterator over the available pins in the bank. + pub fn pins_iterator(&self) -> TscAcquisitionBankPinsIterator { + self.pins.pins_iterator() + } + + /// Returns the mask for this bank. + pub fn mask(&self) -> u32 { + self.mask + } + + /// Retrieves the TSC I/O pin for a given group in this acquisition bank. + /// + /// # Arguments + /// * `group` - The TSC group to retrieve the pin for. + /// + /// # Returns + /// An `Option` containing the pin if it exists for the given group, or `None` if not. + pub fn get_pin(&self, group: Group) -> Option { + match group { + Group::One => self.pins.g1_pin.map(|p| p.pin), + Group::Two => self.pins.g2_pin.map(|p| p.pin), + Group::Three => self.pins.g3_pin.map(|p| p.pin), + Group::Four => self.pins.g4_pin.map(|p| p.pin), + Group::Five => self.pins.g5_pin.map(|p| p.pin), + Group::Six => self.pins.g6_pin.map(|p| p.pin), + #[cfg(any(tsc_v2, tsc_v3))] + Group::Seven => self.pins.g7_pin.map(|p| p.pin), + #[cfg(tsc_v3)] + Group::Eight => self.pins.g8_pin.map(|p| p.pin), + } + } +} + +/// Represents the status of all TSC groups in an acquisition bank +#[derive(Default)] +pub struct TscAcquisitionBankStatus { + pub(super) groups: [Option; TSC_NUM_GROUPS], +} + +impl TscAcquisitionBankStatus { + /// Check if all groups in the bank are complete + pub fn all_complete(&self) -> bool { + self.groups + .iter() + .all(|&status| status.map_or(true, |s| s == GroupStatus::Complete)) + } + + /// Check if any group in the bank is ongoing + pub fn any_ongoing(&self) -> bool { + self.groups.iter().any(|&status| status == Some(GroupStatus::Ongoing)) + } + + /// Get the status of a specific group, if the group is present in the bank + pub fn get_group_status(&self, group: Group) -> Option { + let index: usize = group.into(); + self.groups[index] + } + + /// Iterator for groups present in the bank + pub fn iter(&self) -> impl Iterator + '_ { + self.groups.iter().enumerate().filter_map(|(group_num, status)| { + status.and_then(|s| Group::try_from(group_num).ok().map(|group| (group, s))) + }) + } +} + +/// Represents the result of a Touch Sensing Controller (TSC) acquisition for a specific pin. +/// +/// This struct contains a reference to the `TscIOPin` from which a value was read, +/// along with the actual sensor reading for that pin. It provides a convenient way +/// to associate TSC readings with their corresponding pins after an acquisition. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Clone, Copy, Debug)] +pub struct TscChannelReading { + /// The sensor reading value obtained from the TSC acquisition. + /// Lower values typically indicate a detected touch, while higher values indicate no touch. + pub sensor_value: u16, + + /// The `TscIOPin` associated with this reading. + /// This allows for easy identification of which pin the reading corresponds to. + pub tsc_pin: TscIOPin, +} + +/// Represents the readings from all TSC groups +#[derive(Default)] +pub struct TscAcquisitionBankReadings { + pub(super) groups: [Option; TSC_NUM_GROUPS], +} + +impl TscAcquisitionBankReadings { + /// Get the reading for a specific group, if the group is present in the bank + pub fn get_group_reading(&self, group: Group) -> Option { + let index: usize = group.into(); + self.groups[index] + } + + /// Iterator for readings for groups present in the bank + pub fn iter(&self) -> impl Iterator + '_ { + self.groups.iter().filter_map(|&x| x) + } +} diff --git a/embassy-stm32/src/tsc/config.rs b/embassy-stm32/src/tsc/config.rs new file mode 100644 index 0000000000..efa1f9a0d3 --- /dev/null +++ b/embassy-stm32/src/tsc/config.rs @@ -0,0 +1,175 @@ +/// Charge transfer pulse cycles +#[allow(missing_docs)] +#[derive(Copy, Clone, PartialEq)] +pub enum ChargeTransferPulseCycle { + _1, + _2, + _3, + _4, + _5, + _6, + _7, + _8, + _9, + _10, + _11, + _12, + _13, + _14, + _15, + _16, +} + +impl Into for ChargeTransferPulseCycle { + fn into(self) -> u8 { + match self { + ChargeTransferPulseCycle::_1 => 0, + ChargeTransferPulseCycle::_2 => 1, + ChargeTransferPulseCycle::_3 => 2, + ChargeTransferPulseCycle::_4 => 3, + ChargeTransferPulseCycle::_5 => 4, + ChargeTransferPulseCycle::_6 => 5, + ChargeTransferPulseCycle::_7 => 6, + ChargeTransferPulseCycle::_8 => 7, + ChargeTransferPulseCycle::_9 => 8, + ChargeTransferPulseCycle::_10 => 9, + ChargeTransferPulseCycle::_11 => 10, + ChargeTransferPulseCycle::_12 => 11, + ChargeTransferPulseCycle::_13 => 12, + ChargeTransferPulseCycle::_14 => 13, + ChargeTransferPulseCycle::_15 => 14, + ChargeTransferPulseCycle::_16 => 15, + } + } +} + +/// Max count +#[allow(missing_docs)] +#[derive(Copy, Clone)] +pub enum MaxCount { + _255, + _511, + _1023, + _2047, + _4095, + _8191, + _16383, +} + +impl Into for MaxCount { + fn into(self) -> u8 { + match self { + MaxCount::_255 => 0, + MaxCount::_511 => 1, + MaxCount::_1023 => 2, + MaxCount::_2047 => 3, + MaxCount::_4095 => 4, + MaxCount::_8191 => 5, + MaxCount::_16383 => 6, + } + } +} + +/// Prescaler divider +#[allow(missing_docs)] +#[derive(Copy, Clone, PartialEq)] +pub enum PGPrescalerDivider { + _1, + _2, + _4, + _8, + _16, + _32, + _64, + _128, +} + +impl Into for PGPrescalerDivider { + fn into(self) -> u8 { + match self { + PGPrescalerDivider::_1 => 0, + PGPrescalerDivider::_2 => 1, + PGPrescalerDivider::_4 => 2, + PGPrescalerDivider::_8 => 3, + PGPrescalerDivider::_16 => 4, + PGPrescalerDivider::_32 => 5, + PGPrescalerDivider::_64 => 6, + PGPrescalerDivider::_128 => 7, + } + } +} + +/// Error type for SSDeviation +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SSDeviationError { + /// The provided value is too low (0) + ValueTooLow, + /// The provided value is too high (greater than 128) + ValueTooHigh, +} + +/// Spread Spectrum Deviation +#[derive(Copy, Clone)] +pub struct SSDeviation(u8); +impl SSDeviation { + /// Create new deviation value, acceptable inputs are 1-128 + pub fn new(val: u8) -> Result { + if val == 0 { + return Err(SSDeviationError::ValueTooLow); + } else if val > 128 { + return Err(SSDeviationError::ValueTooHigh); + } + Ok(Self(val - 1)) + } +} + +impl Into for SSDeviation { + fn into(self) -> u8 { + self.0 + } +} + +/// Peripheral configuration +#[derive(Clone, Copy)] +pub struct Config { + /// Duration of high state of the charge transfer pulse + pub ct_pulse_high_length: ChargeTransferPulseCycle, + /// Duration of the low state of the charge transfer pulse + pub ct_pulse_low_length: ChargeTransferPulseCycle, + /// Enable/disable of spread spectrum feature + pub spread_spectrum: bool, + /// Adds variable number of periods of the SS clk to pulse high state + pub spread_spectrum_deviation: SSDeviation, + /// Selects AHB clock divider used to generate SS clk + pub spread_spectrum_prescaler: bool, + /// Selects AHB clock divider used to generate pulse generator clk + pub pulse_generator_prescaler: PGPrescalerDivider, + /// Maximum number of charge transfer pulses that can be generated before error + pub max_count_value: MaxCount, + /// Defines config of all IOs when no ongoing acquisition + pub io_default_mode: bool, + /// Polarity of sync input pin + pub synchro_pin_polarity: bool, + /// Acquisition starts when start bit is set or with sync pin input + pub acquisition_mode: bool, + /// Enable max count interrupt + pub max_count_interrupt: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + ct_pulse_high_length: ChargeTransferPulseCycle::_1, + ct_pulse_low_length: ChargeTransferPulseCycle::_1, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(1).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_1, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + } + } +} diff --git a/embassy-stm32/src/tsc/errors.rs b/embassy-stm32/src/tsc/errors.rs new file mode 100644 index 0000000000..21f6441baf --- /dev/null +++ b/embassy-stm32/src/tsc/errors.rs @@ -0,0 +1,21 @@ +/// Represents errors that can occur when configuring or validating TSC pin groups. +#[derive(Debug)] +pub enum GroupError { + /// Error when a group has no sampling capacitor + NoSamplingCapacitor, + /// Error when a group has neither channel IOs nor a shield IO + NoChannelOrShield, + /// Error when a group has both channel IOs and a shield IO + MixedChannelAndShield, + /// Error when there is more than one shield IO across all groups + MultipleShields, +} + +/// Error returned when attempting to set an invalid channel pin as active in the TSC. +#[derive(Debug)] +pub enum AcquisitionBankError { + /// Indicates that one or more of the provided pins is not a valid channel pin. + InvalidChannelPin, + /// Indicates that multiple channels from the same group were provided. + MultipleChannelsPerGroup, +} diff --git a/embassy-stm32/src/tsc/mod.rs b/embassy-stm32/src/tsc/mod.rs index 17240e6bc5..2df3847bca 100644 --- a/embassy-stm32/src/tsc/mod.rs +++ b/embassy-stm32/src/tsc/mod.rs @@ -1,90 +1,119 @@ //! TSC Peripheral Interface //! +//! This module provides an interface for the Touch Sensing Controller (TSC) peripheral. +//! It supports both blocking and async modes of operation, as well as different TSC versions (v1, v2, v3). //! -//! # Example (stm32) -//! ``` rust, ignore +//! # Key Concepts //! -//! let mut device_config = embassy_stm32::Config::default(); -//! { -//! device_config.rcc.mux = ClockSrc::MSI(Msirange::RANGE_4MHZ); -//! } +//! - **Pin Groups**: TSC pins are organized into groups, each containing up to four IOs. +//! - **Pin Roles**: Each pin in a group can have a role: Channel, Sample, or Shield. +//! - **Acquisition Banks**: Used for efficient, repeated TSC acquisitions on specific sets of pins. +//! +//! # Example (stm32) //! +//! ```rust +//! let device_config = embassy_stm32::Config::default(); //! let context = embassy_stm32::init(device_config); //! //! let config = tsc::Config { -//! ct_pulse_high_length: ChargeTransferPulseCycle::_2, -//! ct_pulse_low_length: ChargeTransferPulseCycle::_2, +//! ct_pulse_high_length: ChargeTransferPulseCycle::_4, +//! ct_pulse_low_length: ChargeTransferPulseCycle::_4, //! spread_spectrum: false, //! spread_spectrum_deviation: SSDeviation::new(2).unwrap(), //! spread_spectrum_prescaler: false, -//! pulse_generator_prescaler: PGPrescalerDivider::_4, -//! max_count_value: MaxCount::_8191, +//! pulse_generator_prescaler: PGPrescalerDivider::_16, +//! max_count_value: MaxCount::_255, //! io_default_mode: false, //! synchro_pin_polarity: false, //! acquisition_mode: false, //! max_count_interrupt: false, -//! channel_ios: TscIOPin::Group2Io2 | TscIOPin::Group7Io3, -//! shield_ios: TscIOPin::Group1Io3.into(), -//! sampling_ios: TscIOPin::Group1Io2 | TscIOPin::Group2Io1 | TscIOPin::Group7Io2, //! }; //! -//! let mut g1: PinGroup = PinGroup::new(); -//! g1.set_io2(context.PB13, PinType::Sample); -//! g1.set_io3(context.PB14, PinType::Shield); -//! -//! let mut g2: PinGroup = PinGroup::new(); -//! g2.set_io1(context.PB4, PinType::Sample); -//! g2.set_io2(context.PB5, PinType::Channel); +//! let mut g2: PinGroupWithRoles = PinGroupWithRoles::new(); +//! g2.set_io1::(context.PB4); +//! let sensor_pin = g2.set_io2::(context.PB5); //! -//! let mut g7: PinGroup = PinGroup::new(); -//! g7.set_io2(context.PE3, PinType::Sample); -//! g7.set_io3(context.PE4, PinType::Channel); +//! let pin_groups = PinGroups { +//! g2: Some(g2.pin_group), +//! ..Default::default() +//! }; //! //! let mut touch_controller = tsc::Tsc::new_blocking( //! context.TSC, -//! Some(g1), -//! Some(g2), -//! None, -//! None, -//! None, -//! None, -//! Some(g7), -//! None, +//! pin_groups, //! config, -//! ); +//! ).unwrap(); //! -//! touch_controller.discharge_io(true); -//! Timer::after_millis(1).await; +//! let discharge_delay = 5; // ms //! -//! touch_controller.start(); +//! loop { +//! touch_controller.set_active_channels_mask(sensor_pin.pin.into()); +//! touch_controller.start(); +//! touch_controller.poll_for_acquisition(); +//! touch_controller.discharge_io(true); +//! Timer::after_millis(discharge_delay).await; //! +//! match touch_controller.group_get_status(sensor_pin.pin.group()) { +//! GroupStatus::Complete => { +//! let group_val = touch_controller.group_get_value(sensor_pin.pin.group()); +//! // Process the touch value +//! // ... +//! } +//! GroupStatus::Ongoing => { +//! // Handle ongoing acquisition +//! // ... +//! } +//! } +//! } //! ``` +//! +//! # Async Usage +//! +//! For async operation, use `Tsc::new_async` and `pend_for_acquisition` instead of polling. #![macro_use] -/// Enums defined for peripheral parameters -pub mod enums; +/// Configuration structures and enums for the TSC peripheral. +pub mod config; + +/// Definitions and implementations for TSC pin groups. +pub mod pin_groups; + +/// Definitions and implementations for individual TSC I/O pins. +pub mod tsc_io_pin; + +/// Structures and implementations for TSC acquisition banks. +pub mod acquisition_banks; + +/// Core implementation of the TSC (Touch Sensing Controller) driver. +pub mod tsc; + +/// Type definitions used throughout the TSC module. +pub mod types; + +/// Error types and definitions for the TSC module. +pub mod errors; -use core::future::poll_fn; use core::marker::PhantomData; -use core::task::Poll; -use embassy_hal_internal::{into_ref, PeripheralRef}; +pub use acquisition_banks::*; +pub use config::*; use embassy_sync::waitqueue::AtomicWaker; -pub use enums::*; +pub use errors::*; +pub use pin_groups::*; +pub use tsc::*; +pub use tsc_io_pin::*; +pub use types::*; -use crate::gpio::{AfType, AnyPin, OutputType, Speed}; -use crate::interrupt::typelevel::Interrupt; -use crate::mode::{Async, Blocking, Mode as PeriMode}; -use crate::rcc::{self, RccPeripheral}; +use crate::rcc::RccPeripheral; use crate::{interrupt, peripherals, Peripheral}; #[cfg(tsc_v1)] -const TSC_NUM_GROUPS: u32 = 6; +const TSC_NUM_GROUPS: usize = 6; #[cfg(tsc_v2)] -const TSC_NUM_GROUPS: u32 = 7; +const TSC_NUM_GROUPS: usize = 7; #[cfg(tsc_v3)] -const TSC_NUM_GROUPS: u32 = 8; +const TSC_NUM_GROUPS: usize = 8; /// Error type defined for TSC #[derive(Debug, Clone, Copy)] @@ -106,859 +135,6 @@ impl interrupt::typelevel::Handler for InterruptHandl } } -/// Pin type definition to control IO parameters -pub enum PinType { - /// Sensing channel pin connected to an electrode - Channel, - /// Sampling capacitor pin, one required for every pin group - Sample, - /// Shield pin connected to capacitive sensing shield - Shield, -} - -/// Peripheral state -#[derive(PartialEq, Clone, Copy)] -pub enum State { - /// Peripheral is being setup or reconfigured - Reset, - /// Ready to start acquisition - Ready, - /// In process of sensor acquisition - Busy, - /// Error occured during acquisition - Error, -} - -/// Individual group status checked after acquisition reported as complete -/// For groups with multiple channel pins, may take longer because acquisitions -/// are done sequentially. Check this status before pulling count for each -/// sampled channel -#[derive(PartialEq)] -pub enum GroupStatus { - /// Acquisition for channel still in progress - Ongoing, - /// Acquisition either not started or complete - Complete, -} - -/// Group identifier used to interrogate status -#[allow(missing_docs)] -pub enum Group { - One, - Two, - Three, - Four, - Five, - Six, - #[cfg(any(tsc_v2, tsc_v3))] - Seven, - #[cfg(tsc_v3)] - Eight, -} - -impl Into for Group { - fn into(self) -> usize { - match self { - Group::One => 0, - Group::Two => 1, - Group::Three => 2, - Group::Four => 3, - Group::Five => 4, - Group::Six => 5, - #[cfg(any(tsc_v2, tsc_v3))] - Group::Seven => 6, - #[cfg(tsc_v3)] - Group::Eight => 7, - } - } -} - -/// Peripheral configuration -#[derive(Clone, Copy)] -pub struct Config { - /// Duration of high state of the charge transfer pulse - pub ct_pulse_high_length: ChargeTransferPulseCycle, - /// Duration of the low state of the charge transfer pulse - pub ct_pulse_low_length: ChargeTransferPulseCycle, - /// Enable/disable of spread spectrum feature - pub spread_spectrum: bool, - /// Adds variable number of periods of the SS clk to pulse high state - pub spread_spectrum_deviation: SSDeviation, - /// Selects AHB clock divider used to generate SS clk - pub spread_spectrum_prescaler: bool, - /// Selects AHB clock divider used to generate pulse generator clk - pub pulse_generator_prescaler: PGPrescalerDivider, - /// Maximum number of charge transfer pulses that can be generated before error - pub max_count_value: MaxCount, - /// Defines config of all IOs when no ongoing acquisition - pub io_default_mode: bool, - /// Polarity of sync input pin - pub synchro_pin_polarity: bool, - /// Acquisition starts when start bit is set or with sync pin input - pub acquisition_mode: bool, - /// Enable max count interrupt - pub max_count_interrupt: bool, - /// Channel IO mask - pub channel_ios: u32, - /// Shield IO mask - pub shield_ios: u32, - /// Sampling IO mask - pub sampling_ios: u32, -} - -impl Default for Config { - fn default() -> Self { - Self { - ct_pulse_high_length: ChargeTransferPulseCycle::_1, - ct_pulse_low_length: ChargeTransferPulseCycle::_1, - spread_spectrum: false, - spread_spectrum_deviation: SSDeviation::new(1).unwrap(), - spread_spectrum_prescaler: false, - pulse_generator_prescaler: PGPrescalerDivider::_1, - max_count_value: MaxCount::_255, - io_default_mode: false, - synchro_pin_polarity: false, - acquisition_mode: false, - max_count_interrupt: false, - channel_ios: 0, - shield_ios: 0, - sampling_ios: 0, - } - } -} - -/// Pin struct that maintains usage -#[allow(missing_docs)] -pub struct TscPin<'d, T, C> { - _pin: PeripheralRef<'d, AnyPin>, - role: PinType, - phantom: PhantomData<(T, C)>, -} - -enum GroupError { - NoSample, - ChannelShield, -} - -/// Pin group definition -/// Pins are organized into groups of four IOs, all groups with a -/// sampling channel must also have a sampling capacitor channel. -#[allow(missing_docs)] -#[derive(Default)] -pub struct PinGroup<'d, T, C> { - d1: Option>, - d2: Option>, - d3: Option>, - d4: Option>, -} - -impl<'d, T: Instance, C> PinGroup<'d, T, C> { - /// Create new sensing group - pub fn new() -> Self { - Self { - d1: None, - d2: None, - d3: None, - d4: None, - } - } - - fn contains_shield(&self) -> bool { - let mut shield_count = 0; - - if let Some(pin) = &self.d1 { - if let PinType::Shield = pin.role { - shield_count += 1; - } - } - - if let Some(pin) = &self.d2 { - if let PinType::Shield = pin.role { - shield_count += 1; - } - } - - if let Some(pin) = &self.d3 { - if let PinType::Shield = pin.role { - shield_count += 1; - } - } - - if let Some(pin) = &self.d4 { - if let PinType::Shield = pin.role { - shield_count += 1; - } - } - - shield_count == 1 - } - - fn check_group(&self) -> Result<(), GroupError> { - let mut channel_count = 0; - let mut shield_count = 0; - let mut sample_count = 0; - if let Some(pin) = &self.d1 { - match pin.role { - PinType::Channel => { - channel_count += 1; - } - PinType::Shield => { - shield_count += 1; - } - PinType::Sample => { - sample_count += 1; - } - } - } - - if let Some(pin) = &self.d2 { - match pin.role { - PinType::Channel => { - channel_count += 1; - } - PinType::Shield => { - shield_count += 1; - } - PinType::Sample => { - sample_count += 1; - } - } - } - - if let Some(pin) = &self.d3 { - match pin.role { - PinType::Channel => { - channel_count += 1; - } - PinType::Shield => { - shield_count += 1; - } - PinType::Sample => { - sample_count += 1; - } - } - } - - if let Some(pin) = &self.d4 { - match pin.role { - PinType::Channel => { - channel_count += 1; - } - PinType::Shield => { - shield_count += 1; - } - PinType::Sample => { - sample_count += 1; - } - } - } - - // Every group requires one sampling capacitor - if sample_count != 1 { - return Err(GroupError::NoSample); - } - - // Each group must have at least one shield or channel IO - if shield_count == 0 && channel_count == 0 { - return Err(GroupError::ChannelShield); - } - - // Any group can either contain channel ios or a shield IO - if shield_count != 0 && channel_count != 0 { - return Err(GroupError::ChannelShield); - } - - // No more than one shield IO is allow per group and amongst all groups - if shield_count > 1 { - return Err(GroupError::ChannelShield); - } - - Ok(()) - } -} - -macro_rules! group_impl { - ($group:ident, $trait1:ident, $trait2:ident, $trait3:ident, $trait4:ident) => { - impl<'d, T: Instance> PinGroup<'d, T, $group> { - #[doc = concat!("Create a new pin1 for ", stringify!($group), " TSC group instance.")] - pub fn set_io1(&mut self, pin: impl Peripheral

> + 'd, role: PinType) { - into_ref!(pin); - critical_section::with(|_| { - pin.set_low(); - pin.set_as_af( - pin.af_num(), - AfType::output( - match role { - PinType::Channel => OutputType::PushPull, - PinType::Sample => OutputType::OpenDrain, - PinType::Shield => OutputType::PushPull, - }, - Speed::VeryHigh, - ), - ); - self.d1 = Some(TscPin { - _pin: pin.map_into(), - role: role, - phantom: PhantomData, - }) - }) - } - - #[doc = concat!("Create a new pin2 for ", stringify!($group), " TSC group instance.")] - pub fn set_io2(&mut self, pin: impl Peripheral

> + 'd, role: PinType) { - into_ref!(pin); - critical_section::with(|_| { - pin.set_low(); - pin.set_as_af( - pin.af_num(), - AfType::output( - match role { - PinType::Channel => OutputType::PushPull, - PinType::Sample => OutputType::OpenDrain, - PinType::Shield => OutputType::PushPull, - }, - Speed::VeryHigh, - ), - ); - self.d2 = Some(TscPin { - _pin: pin.map_into(), - role: role, - phantom: PhantomData, - }) - }) - } - - #[doc = concat!("Create a new pin3 for ", stringify!($group), " TSC group instance.")] - pub fn set_io3(&mut self, pin: impl Peripheral

> + 'd, role: PinType) { - into_ref!(pin); - critical_section::with(|_| { - pin.set_low(); - pin.set_as_af( - pin.af_num(), - AfType::output( - match role { - PinType::Channel => OutputType::PushPull, - PinType::Sample => OutputType::OpenDrain, - PinType::Shield => OutputType::PushPull, - }, - Speed::VeryHigh, - ), - ); - self.d3 = Some(TscPin { - _pin: pin.map_into(), - role: role, - phantom: PhantomData, - }) - }) - } - - #[doc = concat!("Create a new pin4 for ", stringify!($group), " TSC group instance.")] - pub fn set_io4(&mut self, pin: impl Peripheral

> + 'd, role: PinType) { - into_ref!(pin); - critical_section::with(|_| { - pin.set_low(); - pin.set_as_af( - pin.af_num(), - AfType::output( - match role { - PinType::Channel => OutputType::PushPull, - PinType::Sample => OutputType::OpenDrain, - PinType::Shield => OutputType::PushPull, - }, - Speed::VeryHigh, - ), - ); - self.d4 = Some(TscPin { - _pin: pin.map_into(), - role: role, - phantom: PhantomData, - }) - }) - } - } - }; -} - -group_impl!(G1, G1IO1Pin, G1IO2Pin, G1IO3Pin, G1IO4Pin); -group_impl!(G2, G2IO1Pin, G2IO2Pin, G2IO3Pin, G2IO4Pin); -group_impl!(G3, G3IO1Pin, G3IO2Pin, G3IO3Pin, G3IO4Pin); -group_impl!(G4, G4IO1Pin, G4IO2Pin, G4IO3Pin, G4IO4Pin); -group_impl!(G5, G5IO1Pin, G5IO2Pin, G5IO3Pin, G5IO4Pin); -group_impl!(G6, G6IO1Pin, G6IO2Pin, G6IO3Pin, G6IO4Pin); -group_impl!(G7, G7IO1Pin, G7IO2Pin, G7IO3Pin, G7IO4Pin); -group_impl!(G8, G8IO1Pin, G8IO2Pin, G8IO3Pin, G8IO4Pin); - -/// Group 1 marker type. -pub enum G1 {} -/// Group 2 marker type. -pub enum G2 {} -/// Group 3 marker type. -pub enum G3 {} -/// Group 4 marker type. -pub enum G4 {} -/// Group 5 marker type. -pub enum G5 {} -/// Group 6 marker type. -pub enum G6 {} -/// Group 7 marker type. -pub enum G7 {} -/// Group 8 marker type. -pub enum G8 {} - -/// TSC driver -pub struct Tsc<'d, T: Instance, K: PeriMode> { - _peri: PeripheralRef<'d, T>, - _g1: Option>, - _g2: Option>, - _g3: Option>, - _g4: Option>, - _g5: Option>, - _g6: Option>, - #[cfg(any(tsc_v2, tsc_v3))] - _g7: Option>, - #[cfg(tsc_v3)] - _g8: Option>, - state: State, - config: Config, - _kind: PhantomData, -} - -impl<'d, T: Instance> Tsc<'d, T, Async> { - /// Create a Tsc instance that can be awaited for completion - pub fn new_async( - peri: impl Peripheral

+ 'd, - g1: Option>, - g2: Option>, - g3: Option>, - g4: Option>, - g5: Option>, - g6: Option>, - #[cfg(any(tsc_v2, tsc_v3))] g7: Option>, - #[cfg(tsc_v3)] g8: Option>, - config: Config, - _irq: impl interrupt::typelevel::Binding> + 'd, - ) -> Self { - // Need to check valid pin configuration input - let g1 = g1.filter(|b| b.check_group().is_ok()); - let g2 = g2.filter(|b| b.check_group().is_ok()); - let g3 = g3.filter(|b| b.check_group().is_ok()); - let g4 = g4.filter(|b| b.check_group().is_ok()); - let g5 = g5.filter(|b| b.check_group().is_ok()); - let g6 = g6.filter(|b| b.check_group().is_ok()); - #[cfg(any(tsc_v2, tsc_v3))] - let g7 = g7.filter(|b| b.check_group().is_ok()); - #[cfg(tsc_v3)] - let g8 = g8.filter(|b| b.check_group().is_ok()); - - match Self::check_shields( - &g1, - &g2, - &g3, - &g4, - &g5, - &g6, - #[cfg(any(tsc_v2, tsc_v3))] - &g7, - #[cfg(tsc_v3)] - &g8, - ) { - Ok(()) => Self::new_inner( - peri, - g1, - g2, - g3, - g4, - g5, - g6, - #[cfg(any(tsc_v2, tsc_v3))] - g7, - #[cfg(tsc_v3)] - g8, - config, - ), - Err(_) => Self::new_inner( - peri, - None, - None, - None, - None, - None, - None, - #[cfg(any(tsc_v2, tsc_v3))] - None, - #[cfg(tsc_v3)] - None, - config, - ), - } - } - /// Asyncronously wait for the end of an acquisition - pub async fn pend_for_acquisition(&mut self) { - poll_fn(|cx| match self.get_state() { - State::Busy => { - T::waker().register(cx.waker()); - T::regs().ier().write(|w| w.set_eoaie(true)); - if self.get_state() != State::Busy { - T::regs().ier().write(|w| w.set_eoaie(false)); - return Poll::Ready(()); - } - Poll::Pending - } - _ => { - T::regs().ier().write(|w| w.set_eoaie(false)); - Poll::Ready(()) - } - }) - .await; - } -} - -impl<'d, T: Instance> Tsc<'d, T, Blocking> { - /// Create a Tsc instance that must be polled for completion - pub fn new_blocking( - peri: impl Peripheral

+ 'd, - g1: Option>, - g2: Option>, - g3: Option>, - g4: Option>, - g5: Option>, - g6: Option>, - #[cfg(any(tsc_v2, tsc_v3))] g7: Option>, - #[cfg(tsc_v3)] g8: Option>, - config: Config, - ) -> Self { - // Need to check valid pin configuration input - let g1 = g1.filter(|b| b.check_group().is_ok()); - let g2 = g2.filter(|b| b.check_group().is_ok()); - let g3 = g3.filter(|b| b.check_group().is_ok()); - let g4 = g4.filter(|b| b.check_group().is_ok()); - let g5 = g5.filter(|b| b.check_group().is_ok()); - let g6 = g6.filter(|b| b.check_group().is_ok()); - #[cfg(any(tsc_v2, tsc_v3))] - let g7 = g7.filter(|b| b.check_group().is_ok()); - #[cfg(tsc_v3)] - let g8 = g8.filter(|b| b.check_group().is_ok()); - - match Self::check_shields( - &g1, - &g2, - &g3, - &g4, - &g5, - &g6, - #[cfg(any(tsc_v2, tsc_v3))] - &g7, - #[cfg(tsc_v3)] - &g8, - ) { - Ok(()) => Self::new_inner( - peri, - g1, - g2, - g3, - g4, - g5, - g6, - #[cfg(any(tsc_v2, tsc_v3))] - g7, - #[cfg(tsc_v3)] - g8, - config, - ), - Err(_) => Self::new_inner( - peri, - None, - None, - None, - None, - None, - None, - #[cfg(any(tsc_v2, tsc_v3))] - None, - #[cfg(tsc_v3)] - None, - config, - ), - } - } - /// Wait for end of acquisition - pub fn poll_for_acquisition(&mut self) { - while self.get_state() == State::Busy {} - } -} - -impl<'d, T: Instance, K: PeriMode> Tsc<'d, T, K> { - /// Create new TSC driver - fn check_shields( - g1: &Option>, - g2: &Option>, - g3: &Option>, - g4: &Option>, - g5: &Option>, - g6: &Option>, - #[cfg(any(tsc_v2, tsc_v3))] g7: &Option>, - #[cfg(tsc_v3)] g8: &Option>, - ) -> Result<(), GroupError> { - let mut shield_count = 0; - - if let Some(pin_group) = g1 { - if pin_group.contains_shield() { - shield_count += 1; - } - }; - if let Some(pin_group) = g2 { - if pin_group.contains_shield() { - shield_count += 1; - } - }; - if let Some(pin_group) = g3 { - if pin_group.contains_shield() { - shield_count += 1; - } - }; - if let Some(pin_group) = g4 { - if pin_group.contains_shield() { - shield_count += 1; - } - }; - if let Some(pin_group) = g5 { - if pin_group.contains_shield() { - shield_count += 1; - } - }; - if let Some(pin_group) = g6 { - if pin_group.contains_shield() { - shield_count += 1; - } - }; - #[cfg(any(tsc_v2, tsc_v3))] - if let Some(pin_group) = g7 { - if pin_group.contains_shield() { - shield_count += 1; - } - }; - #[cfg(tsc_v3)] - if let Some(pin_group) = g8 { - if pin_group.contains_shield() { - shield_count += 1; - } - }; - - if shield_count > 1 { - return Err(GroupError::ChannelShield); - } - - Ok(()) - } - - fn extract_groups(io_mask: u32) -> u32 { - let mut groups: u32 = 0; - for idx in 0..TSC_NUM_GROUPS { - if io_mask & (0x0F << idx * 4) != 0 { - groups |= 1 << idx - } - } - groups - } - - fn new_inner( - peri: impl Peripheral

+ 'd, - g1: Option>, - g2: Option>, - g3: Option>, - g4: Option>, - g5: Option>, - g6: Option>, - #[cfg(any(tsc_v2, tsc_v3))] g7: Option>, - #[cfg(tsc_v3)] g8: Option>, - config: Config, - ) -> Self { - into_ref!(peri); - - rcc::enable_and_reset::(); - - T::regs().cr().modify(|w| { - w.set_tsce(true); - w.set_ctph(config.ct_pulse_high_length.into()); - w.set_ctpl(config.ct_pulse_low_length.into()); - w.set_sse(config.spread_spectrum); - // Prevent invalid configuration for pulse generator prescaler - if config.ct_pulse_low_length == ChargeTransferPulseCycle::_1 - && (config.pulse_generator_prescaler == PGPrescalerDivider::_1 - || config.pulse_generator_prescaler == PGPrescalerDivider::_2) - { - w.set_pgpsc(PGPrescalerDivider::_4.into()); - } else if config.ct_pulse_low_length == ChargeTransferPulseCycle::_2 - && config.pulse_generator_prescaler == PGPrescalerDivider::_1 - { - w.set_pgpsc(PGPrescalerDivider::_2.into()); - } else { - w.set_pgpsc(config.pulse_generator_prescaler.into()); - } - w.set_ssd(config.spread_spectrum_deviation.into()); - w.set_sspsc(config.spread_spectrum_prescaler); - - w.set_mcv(config.max_count_value.into()); - w.set_syncpol(config.synchro_pin_polarity); - w.set_am(config.acquisition_mode); - }); - - // Set IO configuration - // Disable Schmitt trigger hysteresis on all used TSC IOs - T::regs() - .iohcr() - .write(|w| w.0 = !(config.channel_ios | config.shield_ios | config.sampling_ios)); - - // Set channel and shield IOs - T::regs() - .ioccr() - .write(|w| w.0 = config.channel_ios | config.shield_ios); - - // Set sampling IOs - T::regs().ioscr().write(|w| w.0 = config.sampling_ios); - - // Set the groups to be acquired - T::regs() - .iogcsr() - .write(|w| w.0 = Self::extract_groups(config.channel_ios)); - - // Disable interrupts - T::regs().ier().modify(|w| { - w.set_eoaie(false); - w.set_mceie(false); - }); - - // Clear flags - T::regs().icr().modify(|w| { - w.set_eoaic(true); - w.set_mceic(true); - }); - - unsafe { - T::Interrupt::enable(); - } - - Self { - _peri: peri, - _g1: g1, - _g2: g2, - _g3: g3, - _g4: g4, - _g5: g5, - _g6: g6, - #[cfg(any(tsc_v2, tsc_v3))] - _g7: g7, - #[cfg(tsc_v3)] - _g8: g8, - state: State::Ready, - config, - _kind: PhantomData, - } - } - - /// Start charge transfer acquisition - pub fn start(&mut self) { - self.state = State::Busy; - - // Disable interrupts - T::regs().ier().modify(|w| { - w.set_eoaie(false); - w.set_mceie(false); - }); - - // Clear flags - T::regs().icr().modify(|w| { - w.set_eoaic(true); - w.set_mceic(true); - }); - - // Set the touch sensing IOs not acquired to the default mode - T::regs().cr().modify(|w| { - w.set_iodef(self.config.io_default_mode); - }); - - // Start the acquisition - T::regs().cr().modify(|w| { - w.set_start(true); - }); - } - - /// Stop charge transfer acquisition - pub fn stop(&mut self) { - T::regs().cr().modify(|w| { - w.set_start(false); - }); - - // Set the touch sensing IOs in low power mode - T::regs().cr().modify(|w| { - w.set_iodef(false); - }); - - // Clear flags - T::regs().icr().modify(|w| { - w.set_eoaic(true); - w.set_mceic(true); - }); - - self.state = State::Ready; - } - - /// Get current state of acquisition - pub fn get_state(&mut self) -> State { - if self.state == State::Busy { - if T::regs().isr().read().eoaf() { - if T::regs().isr().read().mcef() { - self.state = State::Error - } else { - self.state = State::Ready - } - } - } - self.state - } - - /// Get the individual group status to check acquisition complete - pub fn group_get_status(&mut self, index: Group) -> GroupStatus { - // Status bits are set by hardware when the acquisition on the corresponding - // enabled analog IO group is complete, cleared when new acquisition is started - let status = match index { - Group::One => T::regs().iogcsr().read().g1s(), - Group::Two => T::regs().iogcsr().read().g2s(), - Group::Three => T::regs().iogcsr().read().g3s(), - Group::Four => T::regs().iogcsr().read().g4s(), - Group::Five => T::regs().iogcsr().read().g5s(), - Group::Six => T::regs().iogcsr().read().g6s(), - #[cfg(any(tsc_v2, tsc_v3))] - Group::Seven => T::regs().iogcsr().read().g7s(), - #[cfg(tsc_v3)] - Group::Eight => T::regs().iogcsr().read().g8s(), - }; - match status { - true => GroupStatus::Complete, - false => GroupStatus::Ongoing, - } - } - - /// Get the count for the acquisiton, valid once group status is set - pub fn group_get_value(&mut self, index: Group) -> u16 { - T::regs().iogcr(index.into()).read().cnt() - } - - /// Discharge the IOs for subsequent acquisition - pub fn discharge_io(&mut self, status: bool) { - // Set the touch sensing IOs in low power mode - T::regs().cr().modify(|w| { - w.set_iodef(!status); - }); - } -} - -impl<'d, T: Instance, K: PeriMode> Drop for Tsc<'d, T, K> { - fn drop(&mut self) { - rcc::disable::(); - } -} - pub(crate) trait SealedInstance { fn regs() -> crate::pac::tsc::Tsc; fn waker() -> &'static AtomicWaker; @@ -988,36 +164,3 @@ foreach_interrupt!( } }; ); - -pin_trait!(G1IO1Pin, Instance); -pin_trait!(G1IO2Pin, Instance); -pin_trait!(G1IO3Pin, Instance); -pin_trait!(G1IO4Pin, Instance); -pin_trait!(G2IO1Pin, Instance); -pin_trait!(G2IO2Pin, Instance); -pin_trait!(G2IO3Pin, Instance); -pin_trait!(G2IO4Pin, Instance); -pin_trait!(G3IO1Pin, Instance); -pin_trait!(G3IO2Pin, Instance); -pin_trait!(G3IO3Pin, Instance); -pin_trait!(G3IO4Pin, Instance); -pin_trait!(G4IO1Pin, Instance); -pin_trait!(G4IO2Pin, Instance); -pin_trait!(G4IO3Pin, Instance); -pin_trait!(G4IO4Pin, Instance); -pin_trait!(G5IO1Pin, Instance); -pin_trait!(G5IO2Pin, Instance); -pin_trait!(G5IO3Pin, Instance); -pin_trait!(G5IO4Pin, Instance); -pin_trait!(G6IO1Pin, Instance); -pin_trait!(G6IO2Pin, Instance); -pin_trait!(G6IO3Pin, Instance); -pin_trait!(G6IO4Pin, Instance); -pin_trait!(G7IO1Pin, Instance); -pin_trait!(G7IO2Pin, Instance); -pin_trait!(G7IO3Pin, Instance); -pin_trait!(G7IO4Pin, Instance); -pin_trait!(G8IO1Pin, Instance); -pin_trait!(G8IO2Pin, Instance); -pin_trait!(G8IO3Pin, Instance); -pin_trait!(G8IO4Pin, Instance); diff --git a/embassy-stm32/src/tsc/pin_groups.rs b/embassy-stm32/src/tsc/pin_groups.rs new file mode 100644 index 0000000000..b15890d6f0 --- /dev/null +++ b/embassy-stm32/src/tsc/pin_groups.rs @@ -0,0 +1,675 @@ +use core::marker::PhantomData; +use core::ops::BitOr; + +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use super::errors::GroupError; +use super::tsc_io_pin::*; +use super::Instance; +use crate::gpio::{AfType, AnyPin, OutputType, Speed}; +use crate::Peripheral; + +/// Pin type definition to control IO parameters +#[derive(PartialEq, Clone, Copy)] +pub enum PinType { + /// Sensing channel pin connected to an electrode + Channel, + /// Sampling capacitor pin, one required for every pin group + Sample, + /// Shield pin connected to capacitive sensing shield + Shield, +} + +/// Pin struct that maintains usage +#[allow(missing_docs)] +pub struct TscPin<'d, T, Group> { + _pin: PeripheralRef<'d, AnyPin>, + role: PinType, + tsc_io_pin: TscIOPin, + phantom: PhantomData<(T, Group)>, +} + +impl<'d, T, Group> TscPin<'d, T, Group> { + /// Returns the role of this TSC pin. + /// + /// The role indicates whether this pin is configured as a channel, + /// sampling capacitor, or shield in the TSC group. + /// + /// # Returns + /// The `PinType` representing the role of this pin. + pub fn role(&self) -> PinType { + self.role + } + + /// Returns the TSC IO pin associated with this pin. + /// + /// This method provides access to the specific TSC IO pin configuration, + /// which includes information about the pin's group and position within that group. + /// + /// # Returns + /// The `TscIOPin` representing this pin's TSC-specific configuration. + pub fn tsc_io_pin(&self) -> TscIOPin { + self.tsc_io_pin + } +} + +/// Represents a group of TSC (Touch Sensing Controller) pins. +/// +/// In the TSC peripheral, pins are organized into groups of four IOs. Each group +/// must have exactly one sampling capacitor pin and can have multiple channel pins +/// or a single shield pin. This structure encapsulates these pin configurations +/// for a single TSC group. +/// +/// # Pin Roles +/// - Sampling Capacitor: One required per group, used for charge transfer. +/// - Channel: Sensing pins connected to electrodes for touch detection. +/// - Shield: Optional, used for active shielding to improve sensitivity. +/// +/// # Constraints +/// - Each group must have exactly one sampling capacitor pin. +/// - A group can have either channel pins or a shield pin, but not both. +/// - No more than one shield pin is allowed across all groups. +#[allow(missing_docs)] +pub struct PinGroup<'d, T, Group> { + pin1: Option>, + pin2: Option>, + pin3: Option>, + pin4: Option>, +} + +impl<'d, T, G> Default for PinGroup<'d, T, G> { + fn default() -> Self { + Self { + pin1: None, + pin2: None, + pin3: None, + pin4: None, + } + } +} + +/// Defines roles and traits for TSC (Touch Sensing Controller) pins. +/// +/// This module contains marker types and traits that represent different roles +/// a TSC pin can have, such as channel, sample, or shield. +pub mod tsc_pin_roles { + use super::{OutputType, PinType}; + + /// Marker type for a TSC channel pin. + #[derive(PartialEq, Clone, Copy, Debug)] + pub struct Channel; + + /// Marker type for a TSC sampling pin. + #[derive(PartialEq, Clone, Copy, Debug)] + pub struct Sample; + + /// Marker type for a TSC shield pin. + #[derive(PartialEq, Clone, Copy, Debug)] + pub struct Shield; + + /// Trait for TSC pin roles. + /// + /// This trait defines the behavior and properties of different TSC pin roles. + /// It is implemented by the marker types `Channel`, `Sample`, and `Shield`. + pub trait Role { + /// Returns the `PinType` associated with this role. + fn pin_type() -> PinType; + + /// Returns the `OutputType` associated with this role. + fn output_type() -> OutputType; + } + + impl Role for Channel { + fn pin_type() -> PinType { + PinType::Channel + } + fn output_type() -> OutputType { + OutputType::PushPull + } + } + + impl Role for Sample { + fn pin_type() -> PinType { + PinType::Sample + } + fn output_type() -> OutputType { + OutputType::OpenDrain + } + } + + impl Role for Shield { + fn pin_type() -> PinType { + PinType::Shield + } + fn output_type() -> OutputType { + OutputType::PushPull + } + } +} + +/// Represents a group of TSC pins with their associated roles. +/// +/// This struct allows for type-safe configuration of TSC pin groups, +/// ensuring that pins are assigned appropriate roles within their group. +/// This type is essentially just a wrapper type around a `PinGroup` value. +/// +/// # Type Parameters +/// - `'d`: Lifetime of the pin group. +/// - `T`: The TSC instance type. +/// - `G`: The group identifier. +/// - `R1`, `R2`, `R3`, `R4`: Role types for each pin in the group, defaulting to `Channel`. +pub struct PinGroupWithRoles< + 'd, + T: Instance, + G, + R1 = tsc_pin_roles::Channel, + R2 = tsc_pin_roles::Channel, + R3 = tsc_pin_roles::Channel, + R4 = tsc_pin_roles::Channel, +> { + /// The underlying pin group without role information. + pub pin_group: PinGroup<'d, T, G>, + _phantom: PhantomData<(R1, R2, R3, R4)>, +} + +impl<'d, T: Instance, G, R1, R2, R3, R4> Default for PinGroupWithRoles<'d, T, G, R1, R2, R3, R4> { + fn default() -> Self { + Self { + pin_group: PinGroup::default(), + _phantom: PhantomData, + } + } +} + +impl<'d, T: Instance, G> PinGroup<'d, T, G> { + fn contains_exactly_one_shield_pin(&self) -> bool { + let shield_count = self.shield_pins().count(); + shield_count == 1 + } + + fn check_group(&self) -> Result<(), GroupError> { + let mut channel_count = 0; + let mut shield_count = 0; + let mut sample_count = 0; + for pin in self.pins().into_iter().flatten() { + match pin.role { + PinType::Channel => { + channel_count += 1; + } + PinType::Shield => { + shield_count += 1; + } + PinType::Sample => { + sample_count += 1; + } + } + } + + // Every group requires exactly one sampling capacitor + if sample_count != 1 { + return Err(GroupError::NoSamplingCapacitor); + } + + // Each group must have at least one shield or channel IO + if shield_count == 0 && channel_count == 0 { + return Err(GroupError::NoChannelOrShield); + } + + // Any group can either contain channel ios or a shield IO. + // (An active shield requires its own sampling capacitor) + if shield_count != 0 && channel_count != 0 { + return Err(GroupError::MixedChannelAndShield); + } + + // No more than one shield IO is allow per group and amongst all groups + if shield_count > 1 { + return Err(GroupError::MultipleShields); + } + + Ok(()) + } + + /// Returns a reference to the first pin in the group, if configured. + pub fn pin1(&self) -> Option<&TscPin<'d, T, G>> { + self.pin1.as_ref() + } + + /// Returns a reference to the second pin in the group, if configured. + pub fn pin2(&self) -> Option<&TscPin<'d, T, G>> { + self.pin2.as_ref() + } + + /// Returns a reference to the third pin in the group, if configured. + pub fn pin3(&self) -> Option<&TscPin<'d, T, G>> { + self.pin3.as_ref() + } + + /// Returns a reference to the fourth pin in the group, if configured. + pub fn pin4(&self) -> Option<&TscPin<'d, T, G>> { + self.pin4.as_ref() + } + + fn sample_pins(&self) -> impl Iterator + '_ { + self.pins_filtered(PinType::Sample) + } + + fn shield_pins(&self) -> impl Iterator + '_ { + self.pins_filtered(PinType::Shield) + } + + fn channel_pins(&self) -> impl Iterator + '_ { + self.pins_filtered(PinType::Channel) + } + + fn pins_filtered(&self, pin_type: PinType) -> impl Iterator + '_ { + self.pins().into_iter().filter_map(move |pin| { + pin.as_ref() + .and_then(|p| if p.role == pin_type { Some(p.tsc_io_pin) } else { None }) + }) + } + + fn make_channel_ios_mask(&self) -> u32 { + self.channel_pins().fold(0, u32::bitor) + } + + fn make_shield_ios_mask(&self) -> u32 { + self.shield_pins().fold(0, u32::bitor) + } + + fn make_sample_ios_mask(&self) -> u32 { + self.sample_pins().fold(0, u32::bitor) + } + + fn pins(&self) -> [&Option>; 4] { + [&self.pin1, &self.pin2, &self.pin3, &self.pin4] + } + + fn pins_mut(&mut self) -> [&mut Option>; 4] { + [&mut self.pin1, &mut self.pin2, &mut self.pin3, &mut self.pin4] + } +} + +#[cfg(any(tsc_v2, tsc_v3))] +macro_rules! TSC_V2_V3_GUARD { + ($e:expr) => {{ + #[cfg(any(tsc_v2, tsc_v3))] + { + $e + } + #[cfg(not(any(tsc_v2, tsc_v3)))] + { + compile_error!("Group 7 is not supported in this TSC version") + } + }}; +} + +#[cfg(tsc_v3)] +macro_rules! TSC_V3_GUARD { + ($e:expr) => {{ + #[cfg(tsc_v3)] + { + $e + } + #[cfg(not(tsc_v3))] + { + compile_error!("Group 8 is not supported in this TSC version") + } + }}; +} + +macro_rules! trait_to_tsc_io_pin { + (G1IO1Pin) => { + TscIOPin::Group1Io1 + }; + (G1IO2Pin) => { + TscIOPin::Group1Io2 + }; + (G1IO3Pin) => { + TscIOPin::Group1Io3 + }; + (G1IO4Pin) => { + TscIOPin::Group1Io4 + }; + + (G2IO1Pin) => { + TscIOPin::Group2Io1 + }; + (G2IO2Pin) => { + TscIOPin::Group2Io2 + }; + (G2IO3Pin) => { + TscIOPin::Group2Io3 + }; + (G2IO4Pin) => { + TscIOPin::Group2Io4 + }; + + (G3IO1Pin) => { + TscIOPin::Group3Io1 + }; + (G3IO2Pin) => { + TscIOPin::Group3Io2 + }; + (G3IO3Pin) => { + TscIOPin::Group3Io3 + }; + (G3IO4Pin) => { + TscIOPin::Group3Io4 + }; + + (G4IO1Pin) => { + TscIOPin::Group4Io1 + }; + (G4IO2Pin) => { + TscIOPin::Group4Io2 + }; + (G4IO3Pin) => { + TscIOPin::Group4Io3 + }; + (G4IO4Pin) => { + TscIOPin::Group4Io4 + }; + + (G5IO1Pin) => { + TscIOPin::Group5Io1 + }; + (G5IO2Pin) => { + TscIOPin::Group5Io2 + }; + (G5IO3Pin) => { + TscIOPin::Group5Io3 + }; + (G5IO4Pin) => { + TscIOPin::Group5Io4 + }; + + (G6IO1Pin) => { + TscIOPin::Group6Io1 + }; + (G6IO2Pin) => { + TscIOPin::Group6Io2 + }; + (G6IO3Pin) => { + TscIOPin::Group6Io3 + }; + (G6IO4Pin) => { + TscIOPin::Group6Io4 + }; + + (G7IO1Pin) => { + TSC_V2_V3_GUARD!(TscIOPin::Group7Io1) + }; + (G7IO2Pin) => { + TSC_V2_V3_GUARD!(TscIOPin::Group7Io2) + }; + (G7IO3Pin) => { + TSC_V2_V3_GUARD!(TscIOPin::Group7Io3) + }; + (G7IO4Pin) => { + TSC_V2_V3_GUARD!(TscIOPin::Group7Io4) + }; + + (G8IO1Pin) => { + TSC_V3_GUARD!(TscIOPin::Group8Io1) + }; + (G8IO2Pin) => { + TSC_V3_GUARD!(TscIOPin::Group8Io2) + }; + (G8IO3Pin) => { + TSC_V3_GUARD!(TscIOPin::Group8Io3) + }; + (G8IO4Pin) => { + TSC_V3_GUARD!(TscIOPin::Group8Io4) + }; +} + +macro_rules! impl_set_io { + ($method:ident, $group:ident, $trait:ident, $index:expr) => { + #[doc = concat!("Create a new pin1 for ", stringify!($group), " TSC group instance.")] + pub fn $method( + &mut self, + pin: impl Peripheral

> + 'd, + ) -> TscIOPinWithRole<$group, Role> { + into_ref!(pin); + critical_section::with(|_| { + pin.set_low(); + pin.set_as_af(pin.af_num(), AfType::output(Role::output_type(), Speed::VeryHigh)); + let tsc_io_pin = trait_to_tsc_io_pin!($trait); + let new_pin = TscPin { + _pin: pin.map_into(), + role: Role::pin_type(), + tsc_io_pin, + phantom: PhantomData, + }; + *self.pin_group.pins_mut()[$index] = Some(new_pin); + TscIOPinWithRole { + pin: tsc_io_pin, + phantom: PhantomData, + } + }) + } + }; +} + +macro_rules! group_impl { + ($group:ident, $trait1:ident, $trait2:ident, $trait3:ident, $trait4:ident) => { + impl< + 'd, + T: Instance, + R1: tsc_pin_roles::Role, + R2: tsc_pin_roles::Role, + R3: tsc_pin_roles::Role, + R4: tsc_pin_roles::Role, + > PinGroupWithRoles<'d, T, $group, R1, R2, R3, R4> + { + impl_set_io!(set_io1, $group, $trait1, 0); + impl_set_io!(set_io2, $group, $trait2, 1); + impl_set_io!(set_io3, $group, $trait3, 2); + impl_set_io!(set_io4, $group, $trait4, 3); + } + }; +} + +group_impl!(G1, G1IO1Pin, G1IO2Pin, G1IO3Pin, G1IO4Pin); +group_impl!(G2, G2IO1Pin, G2IO2Pin, G2IO3Pin, G2IO4Pin); +group_impl!(G3, G3IO1Pin, G3IO2Pin, G3IO3Pin, G3IO4Pin); +group_impl!(G4, G4IO1Pin, G4IO2Pin, G4IO3Pin, G4IO4Pin); +group_impl!(G5, G5IO1Pin, G5IO2Pin, G5IO3Pin, G5IO4Pin); +group_impl!(G6, G6IO1Pin, G6IO2Pin, G6IO3Pin, G6IO4Pin); +#[cfg(any(tsc_v2, tsc_v3))] +group_impl!(G7, G7IO1Pin, G7IO2Pin, G7IO3Pin, G7IO4Pin); +#[cfg(tsc_v3)] +group_impl!(G8, G8IO1Pin, G8IO2Pin, G8IO3Pin, G8IO4Pin); + +/// Group 1 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G1 {} +/// Group 2 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G2 {} +/// Group 3 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G3 {} +/// Group 4 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G4 {} +/// Group 5 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G5 {} +/// Group 6 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G6 {} +/// Group 7 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G7 {} +/// Group 8 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G8 {} + +/// Represents the collection of pin groups for the Touch Sensing Controller (TSC). +/// +/// Each field corresponds to a specific group of TSC pins: +#[allow(missing_docs)] +pub struct PinGroups<'d, T: Instance> { + pub g1: Option>, + pub g2: Option>, + pub g3: Option>, + pub g4: Option>, + pub g5: Option>, + pub g6: Option>, + #[cfg(any(tsc_v2, tsc_v3))] + pub g7: Option>, + #[cfg(tsc_v3)] + pub g8: Option>, +} + +impl<'d, T: Instance> PinGroups<'d, T> { + pub(super) fn check(&self) -> Result<(), GroupError> { + let mut shield_count = 0; + + // Helper function to check a single group + fn check_group( + group: &Option>, + shield_count: &mut u32, + ) -> Result<(), GroupError> { + if let Some(group) = group { + group.check_group()?; + if group.contains_exactly_one_shield_pin() { + *shield_count += 1; + if *shield_count > 1 { + return Err(GroupError::MultipleShields); + } + } + } + Ok(()) + } + + // Check each group + check_group(&self.g1, &mut shield_count)?; + check_group(&self.g2, &mut shield_count)?; + check_group(&self.g3, &mut shield_count)?; + check_group(&self.g4, &mut shield_count)?; + check_group(&self.g5, &mut shield_count)?; + check_group(&self.g6, &mut shield_count)?; + #[cfg(any(tsc_v2, tsc_v3))] + check_group(&self.g7, &mut shield_count)?; + #[cfg(tsc_v3)] + check_group(&self.g8, &mut shield_count)?; + + Ok(()) + } + + pub(super) fn make_channel_ios_mask(&self) -> u32 { + #[allow(unused_mut)] + let mut mask = self.g1.as_ref().map_or(0, |g| g.make_channel_ios_mask()) + | self.g2.as_ref().map_or(0, |g| g.make_channel_ios_mask()) + | self.g3.as_ref().map_or(0, |g| g.make_channel_ios_mask()) + | self.g4.as_ref().map_or(0, |g| g.make_channel_ios_mask()) + | self.g5.as_ref().map_or(0, |g| g.make_channel_ios_mask()) + | self.g6.as_ref().map_or(0, |g| g.make_channel_ios_mask()); + #[cfg(any(tsc_v2, tsc_v3))] + { + mask |= self.g7.as_ref().map_or(0, |g| g.make_channel_ios_mask()); + } + #[cfg(tsc_v3)] + { + mask |= self.g8.as_ref().map_or(0, |g| g.make_channel_ios_mask()); + } + mask + } + + pub(super) fn make_shield_ios_mask(&self) -> u32 { + #[allow(unused_mut)] + let mut mask = self.g1.as_ref().map_or(0, |g| g.make_shield_ios_mask()) + | self.g2.as_ref().map_or(0, |g| g.make_shield_ios_mask()) + | self.g3.as_ref().map_or(0, |g| g.make_shield_ios_mask()) + | self.g4.as_ref().map_or(0, |g| g.make_shield_ios_mask()) + | self.g5.as_ref().map_or(0, |g| g.make_shield_ios_mask()) + | self.g6.as_ref().map_or(0, |g| g.make_shield_ios_mask()); + #[cfg(any(tsc_v2, tsc_v3))] + { + mask |= self.g7.as_ref().map_or(0, |g| g.make_shield_ios_mask()); + } + #[cfg(tsc_v3)] + { + mask |= self.g8.as_ref().map_or(0, |g| g.make_shield_ios_mask()); + } + mask + } + + pub(super) fn make_sample_ios_mask(&self) -> u32 { + #[allow(unused_mut)] + let mut mask = self.g1.as_ref().map_or(0, |g| g.make_sample_ios_mask()) + | self.g2.as_ref().map_or(0, |g| g.make_sample_ios_mask()) + | self.g3.as_ref().map_or(0, |g| g.make_sample_ios_mask()) + | self.g4.as_ref().map_or(0, |g| g.make_sample_ios_mask()) + | self.g5.as_ref().map_or(0, |g| g.make_sample_ios_mask()) + | self.g6.as_ref().map_or(0, |g| g.make_sample_ios_mask()); + #[cfg(any(tsc_v2, tsc_v3))] + { + mask |= self.g7.as_ref().map_or(0, |g| g.make_sample_ios_mask()); + } + #[cfg(tsc_v3)] + { + mask |= self.g8.as_ref().map_or(0, |g| g.make_sample_ios_mask()); + } + mask + } +} + +impl<'d, T: Instance> Default for PinGroups<'d, T> { + fn default() -> Self { + Self { + g1: None, + g2: None, + g3: None, + g4: None, + g5: None, + g6: None, + #[cfg(any(tsc_v2, tsc_v3))] + g7: None, + #[cfg(tsc_v3)] + g8: None, + } + } +} + +pin_trait!(G1IO1Pin, Instance); +pin_trait!(G1IO2Pin, Instance); +pin_trait!(G1IO3Pin, Instance); +pin_trait!(G1IO4Pin, Instance); + +pin_trait!(G2IO1Pin, Instance); +pin_trait!(G2IO2Pin, Instance); +pin_trait!(G2IO3Pin, Instance); +pin_trait!(G2IO4Pin, Instance); + +pin_trait!(G3IO1Pin, Instance); +pin_trait!(G3IO2Pin, Instance); +pin_trait!(G3IO3Pin, Instance); +pin_trait!(G3IO4Pin, Instance); + +pin_trait!(G4IO1Pin, Instance); +pin_trait!(G4IO2Pin, Instance); +pin_trait!(G4IO3Pin, Instance); +pin_trait!(G4IO4Pin, Instance); + +pin_trait!(G5IO1Pin, Instance); +pin_trait!(G5IO2Pin, Instance); +pin_trait!(G5IO3Pin, Instance); +pin_trait!(G5IO4Pin, Instance); + +pin_trait!(G6IO1Pin, Instance); +pin_trait!(G6IO2Pin, Instance); +pin_trait!(G6IO3Pin, Instance); +pin_trait!(G6IO4Pin, Instance); + +pin_trait!(G7IO1Pin, Instance); +pin_trait!(G7IO2Pin, Instance); +pin_trait!(G7IO3Pin, Instance); +pin_trait!(G7IO4Pin, Instance); + +pin_trait!(G8IO1Pin, Instance); +pin_trait!(G8IO2Pin, Instance); +pin_trait!(G8IO3Pin, Instance); +pin_trait!(G8IO4Pin, Instance); diff --git a/embassy-stm32/src/tsc/tsc.rs b/embassy-stm32/src/tsc/tsc.rs new file mode 100644 index 0000000000..58f9d9d2e5 --- /dev/null +++ b/embassy-stm32/src/tsc/tsc.rs @@ -0,0 +1,456 @@ +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ops::BitOr; +use core::task::Poll; + +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use super::acquisition_banks::*; +use super::config::*; +use super::errors::*; +use super::pin_groups::*; +use super::tsc_io_pin::*; +use super::types::*; +use super::{Instance, InterruptHandler, TSC_NUM_GROUPS}; +use crate::interrupt::typelevel::Interrupt; +use crate::mode::{Async, Blocking, Mode as PeriMode}; +use crate::{interrupt, rcc, Peripheral}; + +/// Internal structure holding masks for different types of TSC IOs. +/// +/// These masks are used during the initial configuration of the TSC peripheral +/// and for validating pin types during operations like creating acquisition banks. +struct TscIOMasks { + /// Mask representing all configured channel IOs + channel_ios: u32, + /// Mask representing all configured shield IOs + shield_ios: u32, + /// Mask representing all configured sampling IOs + sampling_ios: u32, +} + +/// TSC driver +pub struct Tsc<'d, T: Instance, K: PeriMode> { + _peri: PeripheralRef<'d, T>, + _pin_groups: PinGroups<'d, T>, + state: State, + config: Config, + masks: TscIOMasks, + _kind: PhantomData, +} + +impl<'d, T: Instance, K: PeriMode> Tsc<'d, T, K> { + // Helper method to check if a pin is a channel pin + fn is_channel_pin(&self, pin: TscIOPin) -> bool { + (self.masks.channel_ios & pin) != 0 + } + + /// Get the status of all groups involved in a TscAcquisitionBank + pub fn get_acquisition_bank_status(&self, bank: &TscAcquisitionBank) -> TscAcquisitionBankStatus { + let mut bank_status = TscAcquisitionBankStatus::default(); + for pin in bank.pins_iterator() { + let group = pin.group(); + let group_status = self.group_get_status(group); + let index: usize = group.into(); + bank_status.groups[index] = Some(group_status); + } + bank_status + } + + /// Get the values for all channels involved in a TscAcquisitionBank + pub fn get_acquisition_bank_values(&self, bank: &TscAcquisitionBank) -> TscAcquisitionBankReadings { + let mut bank_readings = TscAcquisitionBankReadings::default(); + for pin in bank.pins_iterator() { + let group = pin.group(); + let value = self.group_get_value(group); + let reading = TscChannelReading { + sensor_value: value, + tsc_pin: pin, + }; + let index: usize = group.into(); + bank_readings.groups[index] = Some(reading); + } + bank_readings + } + + /// Creates a new TSC acquisition bank from the provided pin configuration. + /// + /// This method creates a `TscAcquisitionBank` that can be used for efficient, + /// repeated TSC acquisitions. It automatically generates the appropriate mask + /// for the provided pins. + /// + /// # Note on TSC Hardware Limitation + /// + /// The TSC hardware can only read one channel pin from each TSC group per acquisition. + /// + /// # Arguments + /// * `acquisition_bank_pins` - The pin configuration for the acquisition bank. + /// + /// # Returns + /// A new `TscAcquisitionBank` instance. + /// + /// # Example + /// + /// ``` + /// let tsc = // ... initialize TSC + /// let tsc_sensor1: TscIOPinWithRole = ...; + /// let tsc_sensor2: TscIOPinWithRole = ...; + /// + /// let bank = tsc.create_acquisition_bank(TscAcquisitionBankPins { + /// g1_pin: Some(tsc_sensor1), + /// g2_pin: Some(tsc_sensor2), + /// ..Default::default() + /// }); + /// + /// // Use the bank for acquisitions + /// tsc.set_active_channels_bank(&bank); + /// tsc.start(); + /// // ... perform acquisition ... + /// ``` + pub fn create_acquisition_bank(&self, acquisition_bank_pins: TscAcquisitionBankPins) -> TscAcquisitionBank { + let bank_mask = acquisition_bank_pins.iter().fold(0u32, BitOr::bitor); + + TscAcquisitionBank { + pins: acquisition_bank_pins, + mask: bank_mask, + } + } + + fn make_channels_mask(&self, channels: Itt) -> Result + where + Itt: IntoIterator, + { + let mut group_mask = 0u32; + let mut channel_mask = 0u32; + + for channel in channels { + if !self.is_channel_pin(channel) { + return Err(AcquisitionBankError::InvalidChannelPin); + } + + let group = channel.group(); + let group_bit: u32 = 1 << Into::::into(group); + if group_mask & group_bit != 0 { + return Err(AcquisitionBankError::MultipleChannelsPerGroup); + } + + group_mask |= group_bit; + channel_mask |= channel; + } + + Ok(channel_mask) + } + + /// Sets the active channels for the next TSC acquisition. + /// + /// This is a low-level method that directly sets the channel mask. For most use cases, + /// consider using `set_active_channels_bank` with a `TscAcquisitionBank` instead, which + /// provides a higher-level interface and additional safety checks. + /// + /// This method configures which sensor channels will be read during the next + /// touch sensing acquisition cycle. It should be called before starting a new + /// acquisition with the start() method. + /// + /// # Arguments + /// * `mask` - A 32-bit mask where each bit represents a channel. Set bits indicate + /// active channels. + /// + /// # Note + /// Only one pin from each TSC group can be read for each acquisition. This method + /// does not perform checks to ensure this limitation is met. Incorrect masks may + /// lead to unexpected behavior. + /// + /// # Safety + /// This method doesn't perform extensive checks on the provided mask. Ensure that + /// the mask is valid and adheres to hardware limitations to avoid undefined behavior. + pub fn set_active_channels_mask(&mut self, mask: u32) { + T::regs().ioccr().write(|w| w.0 = mask | self.masks.shield_ios); + } + + /// Convenience method for setting active channels directly from a slice of TscIOPin. + /// This method performs safety checks but is less efficient for repeated use. + pub fn set_active_channels(&mut self, channels: &[TscIOPin]) -> Result<(), AcquisitionBankError> { + let mask = self.make_channels_mask(channels.iter().cloned())?; + self.set_active_channels_mask(mask); + Ok(()) + } + + /// Sets the active channels for the next TSC acquisition using a pre-configured acquisition bank. + /// + /// This method efficiently configures the TSC peripheral to read the channels specified + /// in the provided `TscAcquisitionBank`. It's the recommended way to set up + /// channel configurations for acquisition, especially when using the same set of channels repeatedly. + /// + /// # Arguments + /// + /// * `bank` - A reference to a `TscAcquisitionBank` containing the pre-configured + /// TSC channel mask. + /// + /// # Example + /// + /// ``` + /// let tsc_sensor1: TscIOPinWithRole = ...; + /// let tsc_sensor2: TscIOPinWithRole = ...; + /// let mut touch_controller: Tsc<'_, TSC, Async> = ...; + /// let bank = touch_controller.create_acquisition_bank(TscAcquisitionBankPins { + /// g1_pin: Some(tsc_sensor1), + /// g2_pin: Some(tsc_sensor2), + /// ..Default::default() + /// }); + /// + /// touch_controller.set_active_channels_bank(&bank); + /// touch_controller.start(); + /// // ... perform acquisition ... + /// ``` + /// + /// This method should be called before starting a new acquisition with the `start()` method. + pub fn set_active_channels_bank(&mut self, bank: &TscAcquisitionBank) { + self.set_active_channels_mask(bank.mask) + } + + fn extract_groups(io_mask: u32) -> u32 { + let mut groups: u32 = 0; + for idx in 0..TSC_NUM_GROUPS { + if io_mask & (0x0F << (idx * 4)) != 0 { + groups |= 1 << idx + } + } + groups + } + + fn new_inner( + peri: impl Peripheral

+ 'd, + pin_groups: PinGroups<'d, T>, + config: Config, + ) -> Result { + into_ref!(peri); + + pin_groups.check()?; + + let masks = TscIOMasks { + channel_ios: pin_groups.make_channel_ios_mask(), + shield_ios: pin_groups.make_shield_ios_mask(), + sampling_ios: pin_groups.make_sample_ios_mask(), + }; + + rcc::enable_and_reset::(); + + T::regs().cr().modify(|w| { + w.set_tsce(true); + w.set_ctph(config.ct_pulse_high_length.into()); + w.set_ctpl(config.ct_pulse_low_length.into()); + w.set_sse(config.spread_spectrum); + // Prevent invalid configuration for pulse generator prescaler + if config.ct_pulse_low_length == ChargeTransferPulseCycle::_1 + && (config.pulse_generator_prescaler == PGPrescalerDivider::_1 + || config.pulse_generator_prescaler == PGPrescalerDivider::_2) + { + w.set_pgpsc(PGPrescalerDivider::_4.into()); + } else if config.ct_pulse_low_length == ChargeTransferPulseCycle::_2 + && config.pulse_generator_prescaler == PGPrescalerDivider::_1 + { + w.set_pgpsc(PGPrescalerDivider::_2.into()); + } else { + w.set_pgpsc(config.pulse_generator_prescaler.into()); + } + w.set_ssd(config.spread_spectrum_deviation.into()); + w.set_sspsc(config.spread_spectrum_prescaler); + + w.set_mcv(config.max_count_value.into()); + w.set_syncpol(config.synchro_pin_polarity); + w.set_am(config.acquisition_mode); + }); + + // Set IO configuration + // Disable Schmitt trigger hysteresis on all used TSC IOs + T::regs() + .iohcr() + .write(|w| w.0 = !(masks.channel_ios | masks.shield_ios | masks.sampling_ios)); + + // Set channel and shield IOs + T::regs().ioccr().write(|w| w.0 = masks.channel_ios | masks.shield_ios); + + // Set sampling IOs + T::regs().ioscr().write(|w| w.0 = masks.sampling_ios); + + // Set the groups to be acquired + // Lower bits of `iogcsr` are for enabling groups, while the higher bits are for reading + // status of acquisiton for a group, see method `Tsc::group_get_status`. + T::regs() + .iogcsr() + .write(|w| w.0 = Self::extract_groups(masks.channel_ios)); + + // Disable interrupts + T::regs().ier().modify(|w| { + w.set_eoaie(false); + w.set_mceie(false); + }); + + // Clear flags + T::regs().icr().modify(|w| { + w.set_eoaic(true); + w.set_mceic(true); + }); + + unsafe { + T::Interrupt::enable(); + } + + Ok(Self { + _peri: peri, + _pin_groups: pin_groups, + state: State::Ready, + config, + masks, + _kind: PhantomData, + }) + } + + /// Start charge transfer acquisition + pub fn start(&mut self) { + self.state = State::Busy; + + // Disable interrupts + T::regs().ier().modify(|w| { + w.set_eoaie(false); + w.set_mceie(false); + }); + + // Clear flags + T::regs().icr().modify(|w| { + w.set_eoaic(true); + w.set_mceic(true); + }); + + // Set the touch sensing IOs not acquired to the default mode + T::regs().cr().modify(|w| { + w.set_iodef(self.config.io_default_mode); + }); + + // Start the acquisition + T::regs().cr().modify(|w| { + w.set_start(true); + }); + } + + /// Stop charge transfer acquisition + pub fn stop(&mut self) { + T::regs().cr().modify(|w| { + w.set_start(false); + }); + + // Set the touch sensing IOs in low power mode + T::regs().cr().modify(|w| { + w.set_iodef(false); + }); + + // Clear flags + T::regs().icr().modify(|w| { + w.set_eoaic(true); + w.set_mceic(true); + }); + + self.state = State::Ready; + } + + /// Get current state of acquisition + pub fn get_state(&mut self) -> State { + if self.state == State::Busy && T::regs().isr().read().eoaf() { + if T::regs().isr().read().mcef() { + self.state = State::Error + } else { + self.state = State::Ready + } + } + self.state + } + + /// Get the individual group status to check acquisition complete + pub fn group_get_status(&self, index: Group) -> GroupStatus { + // Status bits are set by hardware when the acquisition on the corresponding + // enabled analog IO group is complete, cleared when new acquisition is started + let status = match index { + Group::One => T::regs().iogcsr().read().g1s(), + Group::Two => T::regs().iogcsr().read().g2s(), + Group::Three => T::regs().iogcsr().read().g3s(), + Group::Four => T::regs().iogcsr().read().g4s(), + Group::Five => T::regs().iogcsr().read().g5s(), + Group::Six => T::regs().iogcsr().read().g6s(), + #[cfg(any(tsc_v2, tsc_v3))] + Group::Seven => T::regs().iogcsr().read().g7s(), + #[cfg(tsc_v3)] + Group::Eight => T::regs().iogcsr().read().g8s(), + }; + match status { + true => GroupStatus::Complete, + false => GroupStatus::Ongoing, + } + } + + /// Get the count for the acquisiton, valid once group status is set + pub fn group_get_value(&self, index: Group) -> u16 { + T::regs().iogcr(index.into()).read().cnt() + } + + /// Discharge the IOs for subsequent acquisition + pub fn discharge_io(&mut self, status: bool) { + // Set the touch sensing IOs in low power mode + T::regs().cr().modify(|w| { + w.set_iodef(!status); + }); + } +} + +impl<'d, T: Instance, K: PeriMode> Drop for Tsc<'d, T, K> { + fn drop(&mut self) { + rcc::disable::(); + } +} + +impl<'d, T: Instance> Tsc<'d, T, Async> { + /// Create a Tsc instance that can be awaited for completion + pub fn new_async( + peri: impl Peripheral

+ 'd, + pin_groups: PinGroups<'d, T>, + config: Config, + _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Result { + Self::new_inner(peri, pin_groups, config) + } + + /// Asyncronously wait for the end of an acquisition + pub async fn pend_for_acquisition(&mut self) { + poll_fn(|cx| match self.get_state() { + State::Busy => { + T::waker().register(cx.waker()); + T::regs().ier().write(|w| w.set_eoaie(true)); + if self.get_state() != State::Busy { + T::regs().ier().write(|w| w.set_eoaie(false)); + return Poll::Ready(()); + } + Poll::Pending + } + _ => { + T::regs().ier().write(|w| w.set_eoaie(false)); + Poll::Ready(()) + } + }) + .await; + } +} + +impl<'d, T: Instance> Tsc<'d, T, Blocking> { + /// Create a Tsc instance that must be polled for completion + pub fn new_blocking( + peri: impl Peripheral

+ 'd, + pin_groups: PinGroups<'d, T>, + config: Config, + ) -> Result { + Self::new_inner(peri, pin_groups, config) + } + + /// Wait for end of acquisition + pub fn poll_for_acquisition(&mut self) { + while self.get_state() == State::Busy {} + } +} diff --git a/embassy-stm32/src/tsc/enums.rs b/embassy-stm32/src/tsc/tsc_io_pin.rs similarity index 52% rename from embassy-stm32/src/tsc/enums.rs rename to embassy-stm32/src/tsc/tsc_io_pin.rs index 0d34a43ec2..38b27d0b30 100644 --- a/embassy-stm32/src/tsc/enums.rs +++ b/embassy-stm32/src/tsc/tsc_io_pin.rs @@ -1,7 +1,13 @@ -use core::ops::BitOr; +use core::marker::PhantomData; +use core::ops::{BitAnd, BitOr, BitOrAssign}; + +use super::tsc_pin_roles; +use super::types::Group; /// Pin defines #[allow(missing_docs)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(PartialEq, Clone, Copy, Debug)] pub enum TscIOPin { Group1Io1, Group1Io2, @@ -45,6 +51,53 @@ pub enum TscIOPin { Group8Io4, } +/// Represents a TSC I/O pin with associated group and role information. +/// +/// This type combines a `TscIOPin` with phantom type parameters to statically +/// encode the pin's group and role. This allows for type-safe operations +/// on TSC pins within their specific contexts. +/// +/// - `Group`: A type parameter representing the TSC group (e.g., `G1`, `G2`). +/// - `Role`: A type parameter representing the pin's role (e.g., `Channel`, `Sample`). +#[derive(Clone, Copy, Debug)] +pub struct TscIOPinWithRole { + /// The underlying TSC I/O pin. + pub pin: TscIOPin, + pub(super) phantom: PhantomData<(Group, Role)>, +} + +impl TscIOPinWithRole { + pub(super) fn get_pin(wrapped_pin: TscIOPinWithRole) -> TscIOPin { + wrapped_pin.pin + } +} + +impl TscIOPin { + /// Maps this TscIOPin to the Group it belongs to. + /// + /// This method provides a convenient way to determine which Group + /// a specific TSC I/O pin is associated with. + pub const fn group(&self) -> Group { + match self { + TscIOPin::Group1Io1 | TscIOPin::Group1Io2 | TscIOPin::Group1Io3 | TscIOPin::Group1Io4 => Group::One, + TscIOPin::Group2Io1 | TscIOPin::Group2Io2 | TscIOPin::Group2Io3 | TscIOPin::Group2Io4 => Group::Two, + TscIOPin::Group3Io1 | TscIOPin::Group3Io2 | TscIOPin::Group3Io3 | TscIOPin::Group3Io4 => Group::Three, + TscIOPin::Group4Io1 | TscIOPin::Group4Io2 | TscIOPin::Group4Io3 | TscIOPin::Group4Io4 => Group::Four, + TscIOPin::Group5Io1 | TscIOPin::Group5Io2 | TscIOPin::Group5Io3 | TscIOPin::Group5Io4 => Group::Five, + TscIOPin::Group6Io1 | TscIOPin::Group6Io2 | TscIOPin::Group6Io3 | TscIOPin::Group6Io4 => Group::Six, + #[cfg(any(tsc_v2, tsc_v3))] + TscIOPin::Group7Io1 | TscIOPin::Group7Io2 | TscIOPin::Group7Io3 | TscIOPin::Group7Io4 => Group::Seven, + #[cfg(tsc_v3)] + TscIOPin::Group8Io1 | TscIOPin::Group8Io2 | TscIOPin::Group8Io3 | TscIOPin::Group8Io4 => Group::Eight, + } + } + + /// Returns the `Group` associated with the given `TscIOPin`. + pub fn get_group(pin: TscIOPin) -> Group { + pin.group() + } +} + impl BitOr for u32 { type Output = u32; fn bitor(self, rhs: TscIOPin) -> Self::Output { @@ -70,8 +123,31 @@ impl BitOr for TscIOPin { } } -impl Into for TscIOPin { - fn into(self) -> u32 { +impl BitOrAssign for u32 { + fn bitor_assign(&mut self, rhs: TscIOPin) { + let rhs: u32 = rhs.into(); + *self |= rhs; + } +} + +impl BitAnd for u32 { + type Output = u32; + fn bitand(self, rhs: TscIOPin) -> Self::Output { + let rhs: u32 = rhs.into(); + self & rhs + } +} + +impl BitAnd for TscIOPin { + type Output = u32; + fn bitand(self, rhs: u32) -> Self::Output { + let val: u32 = self.into(); + val & rhs + } +} + +impl TscIOPin { + const fn to_u32(self) -> u32 { match self { TscIOPin::Group1Io1 => 0x00000001, TscIOPin::Group1Io2 => 0x00000002, @@ -117,122 +193,8 @@ impl Into for TscIOPin { } } -/// Spread Spectrum Deviation -#[derive(Copy, Clone)] -pub struct SSDeviation(u8); -impl SSDeviation { - /// Create new deviation value, acceptable inputs are 1-128 - pub fn new(val: u8) -> Result { - if val == 0 || val > 128 { - return Err(()); - } - Ok(Self(val - 1)) - } -} - -impl Into for SSDeviation { - fn into(self) -> u8 { - self.0 - } -} - -/// Charge transfer pulse cycles -#[allow(missing_docs)] -#[derive(Copy, Clone, PartialEq)] -pub enum ChargeTransferPulseCycle { - _1, - _2, - _3, - _4, - _5, - _6, - _7, - _8, - _9, - _10, - _11, - _12, - _13, - _14, - _15, - _16, -} - -impl Into for ChargeTransferPulseCycle { - fn into(self) -> u8 { - match self { - ChargeTransferPulseCycle::_1 => 0, - ChargeTransferPulseCycle::_2 => 1, - ChargeTransferPulseCycle::_3 => 2, - ChargeTransferPulseCycle::_4 => 3, - ChargeTransferPulseCycle::_5 => 4, - ChargeTransferPulseCycle::_6 => 5, - ChargeTransferPulseCycle::_7 => 6, - ChargeTransferPulseCycle::_8 => 7, - ChargeTransferPulseCycle::_9 => 8, - ChargeTransferPulseCycle::_10 => 9, - ChargeTransferPulseCycle::_11 => 10, - ChargeTransferPulseCycle::_12 => 11, - ChargeTransferPulseCycle::_13 => 12, - ChargeTransferPulseCycle::_14 => 13, - ChargeTransferPulseCycle::_15 => 14, - ChargeTransferPulseCycle::_16 => 15, - } - } -} - -/// Prescaler divider -#[allow(missing_docs)] -#[derive(Copy, Clone, PartialEq)] -pub enum PGPrescalerDivider { - _1, - _2, - _4, - _8, - _16, - _32, - _64, - _128, -} - -impl Into for PGPrescalerDivider { - fn into(self) -> u8 { - match self { - PGPrescalerDivider::_1 => 0, - PGPrescalerDivider::_2 => 1, - PGPrescalerDivider::_4 => 2, - PGPrescalerDivider::_8 => 3, - PGPrescalerDivider::_16 => 4, - PGPrescalerDivider::_32 => 5, - PGPrescalerDivider::_64 => 6, - PGPrescalerDivider::_128 => 7, - } - } -} - -/// Max count -#[allow(missing_docs)] -#[derive(Copy, Clone)] -pub enum MaxCount { - _255, - _511, - _1023, - _2047, - _4095, - _8191, - _16383, -} - -impl Into for MaxCount { - fn into(self) -> u8 { - match self { - MaxCount::_255 => 0, - MaxCount::_511 => 1, - MaxCount::_1023 => 2, - MaxCount::_2047 => 3, - MaxCount::_4095 => 4, - MaxCount::_8191 => 5, - MaxCount::_16383 => 6, - } +impl Into for TscIOPin { + fn into(self) -> u32 { + self.to_u32() } } diff --git a/embassy-stm32/src/tsc/types.rs b/embassy-stm32/src/tsc/types.rs new file mode 100644 index 0000000000..0e8fa7f284 --- /dev/null +++ b/embassy-stm32/src/tsc/types.rs @@ -0,0 +1,93 @@ +/// Peripheral state +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(PartialEq, Clone, Copy)] +pub enum State { + /// Peripheral is being setup or reconfigured + Reset, + /// Ready to start acquisition + Ready, + /// In process of sensor acquisition + Busy, + /// Error occured during acquisition + Error, +} + +/// Individual group status checked after acquisition reported as complete +/// For groups with multiple channel pins, may take longer because acquisitions +/// are done sequentially. Check this status before pulling count for each +/// sampled channel +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(PartialEq, Clone, Copy)] +pub enum GroupStatus { + /// Acquisition for channel still in progress + Ongoing, + /// Acquisition either not started or complete + Complete, +} + +/// Group identifier used to interrogate status +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow(missing_docs)] +#[derive(PartialEq, Clone, Copy)] +pub enum Group { + One, + Two, + Three, + Four, + Five, + Six, + #[cfg(any(tsc_v2, tsc_v3))] + Seven, + #[cfg(tsc_v3)] + Eight, +} + +impl Into for Group { + fn into(self) -> usize { + match self { + Group::One => 0, + Group::Two => 1, + Group::Three => 2, + Group::Four => 3, + Group::Five => 4, + Group::Six => 5, + #[cfg(any(tsc_v2, tsc_v3))] + Group::Seven => 6, + #[cfg(tsc_v3)] + Group::Eight => 7, + } + } +} + +/// Error returned when attempting to create a Group from an invalid numeric value. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct InvalidGroupError { + invalid_value: usize, +} + +impl InvalidGroupError { + #[allow(missing_docs)] + pub fn new(value: usize) -> Self { + Self { invalid_value: value } + } +} + +impl TryFrom for Group { + type Error = InvalidGroupError; + + fn try_from(value: usize) -> Result { + match value { + 0 => Ok(Group::One), + 1 => Ok(Group::Two), + 2 => Ok(Group::Three), + 3 => Ok(Group::Four), + 4 => Ok(Group::Five), + 5 => Ok(Group::Six), + #[cfg(any(tsc_v2, tsc_v3))] + 6 => Ok(Group::Two), + #[cfg(tsc_v3)] + 7 => Ok(Group::Two), + n => Err(InvalidGroupError::new(n)), + } + } +} diff --git a/examples/stm32f3/README.md b/examples/stm32f3/README.md new file mode 100644 index 0000000000..0a85c4858c --- /dev/null +++ b/examples/stm32f3/README.md @@ -0,0 +1,24 @@ +# Examples for STM32F3 family +Run individual examples with +``` +cargo run --bin +``` +for example +``` +cargo run --bin blinky +``` + +## Checklist before running examples +You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using. + +* [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for F303ZE it should be `probe-rs run --chip STM32F303ZETx`. (use `probe-rs chip list` to find your chip) +* [ ] Update Cargo.toml to have the correct `embassy-stm32` feature. For example for F303ZE it should be `stm32f303ze`. Look in the `Cargo.toml` file of the `embassy-stm32` project to find the correct feature flag for your chip. +* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. +* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic + +If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: + +* Which example you are trying to run +* Which chip and board you are using + +Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org diff --git a/examples/stm32f3/src/bin/blocking-tsc.rs b/examples/stm32f3/src/bin/blocking-tsc.rs deleted file mode 100644 index 5c8dac94fc..0000000000 --- a/examples/stm32f3/src/bin/blocking-tsc.rs +++ /dev/null @@ -1,98 +0,0 @@ -// Example of polling TSC (Touch Sensing Controller) that lights an LED when touch is detected. -// -// Suggested physical setup on STM32F303ZE Nucleo board: -// - Connect a 1000pF capacitor between pin A0 and GND. This is your sampling capacitor. -// - Connect one end of a 1K resistor to pin A1 and leave the other end loose. -// The loose end will act as touch sensor which will register your touch. -// -// Troubleshooting the setup: -// - If no touch seems to be registered, then try to disconnect the sampling capacitor from GND momentarily, -// now the led should light up. Next try using a different value for the sampling capacitor. -// Also experiment with increasing the values for `ct_pulse_high_length`, `ct_pulse_low_length`, `pulse_generator_prescaler`, `max_count_value` and `discharge_delay`. -// -// All configuration values and sampling capacitor value have been determined experimentally. -// Suitable configuration and discharge delay values are highly dependent on the value of the sample capacitor. For example, a shorter discharge delay can be used with smaller capacitor values. -// -#![no_std] -#![no_main] - -use defmt::*; -use embassy_stm32::gpio::{Level, Output, Speed}; -use embassy_stm32::tsc::{self, *}; -use embassy_time::Timer; -use {defmt_rtt as _, panic_probe as _}; - -/// This example is written for the nucleo-stm32f303ze, with a stm32f303ze chip. -/// -/// Make sure you check/update the following (whether you use the F303ZE or another board): -/// -/// * [ ] Update .cargo/config.toml with the correct `probe-rs run --chip STM32F303ZETx`chip name. -/// * [ ] Update Cargo.toml to have the correct `embassy-stm32` feature, for F303ZE it should be `stm32f303ze`. -/// * [ ] If your board has a special clock or power configuration, make sure that it is -/// set up appropriately. -/// * [ ] If your board has different pin mapping, update any pin numbers or peripherals -/// to match your schematic -/// -/// If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: -/// -/// * Which example you are trying to run -/// * Which chip and board you are using -/// -/// Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org -#[embassy_executor::main] -async fn main(_spawner: embassy_executor::Spawner) { - let device_config = embassy_stm32::Config::default(); - let context = embassy_stm32::init(device_config); - - let tsc_conf = Config { - ct_pulse_high_length: ChargeTransferPulseCycle::_8, - ct_pulse_low_length: ChargeTransferPulseCycle::_8, - spread_spectrum: false, - spread_spectrum_deviation: SSDeviation::new(2).unwrap(), - spread_spectrum_prescaler: false, - pulse_generator_prescaler: PGPrescalerDivider::_32, - max_count_value: MaxCount::_255, - io_default_mode: false, - synchro_pin_polarity: false, - acquisition_mode: false, - max_count_interrupt: false, - channel_ios: TscIOPin::Group1Io1.into(), - shield_ios: 0, // no shield - sampling_ios: TscIOPin::Group1Io2.into(), - }; - - let mut g1: PinGroup = PinGroup::new(); - g1.set_io1(context.PA0, PinType::Sample); - g1.set_io2(context.PA1, PinType::Channel); - - let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, Some(g1), None, None, None, None, None, tsc_conf); - - // LED2 on the STM32F303ZE nucleo-board - let mut led = Output::new(context.PB7, Level::High, Speed::Low); - - // smaller sample capacitor discharge faster and can be used with shorter delay. - let discharge_delay = 5; // ms - - // the interval at which the loop polls for new touch sensor values - let polling_interval = 100; // ms - - info!("polling for touch"); - loop { - touch_controller.start(); - touch_controller.poll_for_acquisition(); - touch_controller.discharge_io(true); - Timer::after_millis(discharge_delay).await; - - let grp1_status = touch_controller.group_get_status(Group::One); - match grp1_status { - GroupStatus::Complete => { - let group_one_val = touch_controller.group_get_value(Group::One); - info!("{}", group_one_val); - led.set_high(); - } - GroupStatus::Ongoing => led.set_low(), - } - - Timer::after_millis(polling_interval).await; - } -} diff --git a/examples/stm32f3/src/bin/tsc_blocking.rs b/examples/stm32f3/src/bin/tsc_blocking.rs new file mode 100644 index 0000000000..fa7f718e68 --- /dev/null +++ b/examples/stm32f3/src/bin/tsc_blocking.rs @@ -0,0 +1,138 @@ +// Example of blocking TSC (Touch Sensing Controller) that lights an LED when touch is detected. +// +// This example demonstrates: +// 1. Configuring a single TSC channel pin +// 2. Using the blocking TSC interface with polling +// 3. Waiting for acquisition completion using `poll_for_acquisition` +// 4. Reading touch values and controlling an LED based on the results +// +// Suggested physical setup on STM32F303ZE Nucleo board: +// - Connect a 1000pF capacitor between pin PA10 and GND. This is your sampling capacitor. +// - Connect one end of a 1K resistor to pin PA9 and leave the other end loose. +// The loose end will act as the touch sensor which will register your touch. +// +// The example uses two pins from Group 4 of the TSC: +// - PA10 as the sampling capacitor, TSC group 4 IO2 (D68 on the STM32F303ZE nucleo-board) +// - PA9 as the channel pin, TSC group 4 IO1 (D69 on the STM32F303ZE nucleo-board) +// +// The program continuously reads the touch sensor value: +// - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value. +// - The LED is turned on when touch is detected (sensor value < 40). +// - Touch values are logged to the console. +// +// Troubleshooting: +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, +// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// +// Note: Configuration values and sampling capacitor value have been determined experimentally. +// Optimal values may vary based on your specific hardware setup. +// Pins have been chosen for their convenient locations on the STM32F303ZE board. Refer to the +// official relevant STM32 datasheets and user nucleo-board user manuals to find suitable +// alternative pins. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{mode, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + let tsc_conf = Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_4, + ct_pulse_low_length: ChargeTransferPulseCycle::_4, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let mut g: PinGroupWithRoles = PinGroupWithRoles::default(); + // D68 on the STM32F303ZE nucleo-board + g.set_io2::(context.PA10); + // D69 on the STM32F303ZE nucleo-board + let tsc_sensor = g.set_io1::(context.PA9); + + let pin_groups: PinGroups = PinGroups { + g4: Some(g.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, pin_groups, tsc_conf).unwrap(); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + crate::panic!("TSC not ready!"); + } + info!("TSC initialized successfully"); + + // LED2 on the STM32F303ZE nucleo-board + let mut led = Output::new(context.PB7, Level::High, Speed::Low); + + // smaller sample capacitor discharge faster and can be used with shorter delay. + let discharge_delay = 5; // ms + + // the interval at which the loop polls for new touch sensor values + let polling_interval = 100; // ms + + info!("polling for touch"); + loop { + touch_controller.set_active_channels_mask(tsc_sensor.pin.into()); + touch_controller.start(); + touch_controller.poll_for_acquisition(); + touch_controller.discharge_io(true); + Timer::after_millis(discharge_delay).await; + + match read_touch_value(&mut touch_controller, tsc_sensor.pin).await { + Some(v) => { + info!("sensor value {}", v); + if v < SENSOR_THRESHOLD { + led.set_high(); + } else { + led.set_low(); + } + } + None => led.set_low(), + } + + Timer::after_millis(polling_interval).await; + } +} + +const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10; + +// attempt to read group status and delay when still ongoing +async fn read_touch_value( + touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Blocking>, + sensor_pin: TscIOPin, +) -> Option { + for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS { + match touch_controller.group_get_status(sensor_pin.group()) { + GroupStatus::Complete => { + return Some(touch_controller.group_get_value(sensor_pin.group())); + } + GroupStatus::Ongoing => { + // if you end up here a lot, then you prob need to increase discharge_delay + // or consider changing the code to adjust the discharge_delay dynamically + info!("Acquisition still ongoing"); + Timer::after_millis(1).await; + } + } + } + None +} diff --git a/examples/stm32l0/.cargo/config.toml b/examples/stm32l0/.cargo/config.toml index b050334b2e..fed9cf9ce1 100644 --- a/examples/stm32l0/.cargo/config.toml +++ b/examples/stm32l0/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] # replace your chip as listed in `probe-rs chip list` -runner = "probe-rs run --chip STM32L053R8Tx" +runner = "probe-rs run --chip STM32L073RZTx" [build] target = "thumbv6m-none-eabi" diff --git a/examples/stm32l0/Cargo.toml b/examples/stm32l0/Cargo.toml index 2577f19e00..67030882f1 100644 --- a/examples/stm32l0/Cargo.toml +++ b/examples/stm32l0/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32l072cz to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "unstable-pac", "time-driver-any", "exti", "memory-x"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l073rz", "unstable-pac", "time-driver-any", "exti", "memory-x"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } diff --git a/examples/stm32l0/README.md b/examples/stm32l0/README.md new file mode 100644 index 0000000000..82d222027b --- /dev/null +++ b/examples/stm32l0/README.md @@ -0,0 +1,24 @@ +# Examples for STM32L0 family +Run individual examples with +``` +cargo run --bin +``` +for example +``` +cargo run --bin blinky +``` + +## Checklist before running examples +You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using. + +* [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for L073RZ it should be `probe-rs run --chip STM32L073RZTx`. (use `probe-rs chip list` to find your chip) +* [ ] Update Cargo.toml to have the correct `embassy-stm32` feature. For example for L073RZ it should be `stm32l073rz`. Look in the `Cargo.toml` file of the `embassy-stm32` project to find the correct feature flag for your chip. +* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. +* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic + +If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: + +* Which example you are trying to run +* Which chip and board you are using + +Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org diff --git a/examples/stm32l0/src/bin/async-tsc.rs b/examples/stm32l0/src/bin/async-tsc.rs deleted file mode 100644 index c40b86af9d..0000000000 --- a/examples/stm32l0/src/bin/async-tsc.rs +++ /dev/null @@ -1,122 +0,0 @@ -// Example of async TSC (Touch Sensing Controller) that lights an LED when touch is detected. -// -// Suggested physical setup on STM32L073RZ Nucleo board: -// - Connect a 1000pF capacitor between pin A0 and GND. This is your sampling capacitor. -// - Connect one end of a 1K resistor to pin A1 and leave the other end loose. -// The loose end will act as touch sensor which will register your touch. -// -// Troubleshooting the setup: -// - If no touch seems to be registered, then try to disconnect the sampling capacitor from GND momentarily, -// now the led should light up. Next try using a different value for the sampling capacitor. -// Also experiment with increasing the values for `ct_pulse_high_length`, `ct_pulse_low_length`, `pulse_generator_prescaler`, `max_count_value` and `discharge_delay`. -// -// All configuration values and sampling capacitor value have been determined experimentally. -// Suitable configuration and discharge delay values are highly dependent on the value of the sample capacitor. For example, a shorter discharge delay can be used with smaller capacitor values. -// -#![no_std] -#![no_main] - -use defmt::*; -use embassy_stm32::bind_interrupts; -use embassy_stm32::gpio::{Level, Output, Speed}; -use embassy_stm32::tsc::{self, *}; -use embassy_time::Timer; -use {defmt_rtt as _, panic_probe as _}; - -bind_interrupts!(struct Irqs { - TSC => InterruptHandler; -}); - -#[cortex_m_rt::exception] -unsafe fn HardFault(_: &cortex_m_rt::ExceptionFrame) -> ! { - cortex_m::peripheral::SCB::sys_reset(); -} - -/// This example is written for the nucleo-stm32l073rz, with a stm32l073rz chip. -/// -/// Make sure you check/update the following (whether you use the L073RZ or another board): -/// -/// * [ ] Update .cargo/config.toml with the correct `probe-rs run --chip STM32L073RZTx`chip name. -/// * [ ] Update Cargo.toml to have the correct `embassy-stm32` feature, for L073RZ it should be `stm32l073rz`. -/// * [ ] If your board has a special clock or power configuration, make sure that it is -/// set up appropriately. -/// * [ ] If your board has different pin mapping, update any pin numbers or peripherals -/// to match your schematic -/// -/// If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: -/// -/// * Which example you are trying to run -/// * Which chip and board you are using -/// -/// Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org -#[embassy_executor::main] -async fn main(_spawner: embassy_executor::Spawner) { - let device_config = embassy_stm32::Config::default(); - let context = embassy_stm32::init(device_config); - - let config = tsc::Config { - ct_pulse_high_length: ChargeTransferPulseCycle::_4, - ct_pulse_low_length: ChargeTransferPulseCycle::_4, - spread_spectrum: false, - spread_spectrum_deviation: SSDeviation::new(2).unwrap(), - spread_spectrum_prescaler: false, - pulse_generator_prescaler: PGPrescalerDivider::_16, - max_count_value: MaxCount::_255, - io_default_mode: false, - synchro_pin_polarity: false, - acquisition_mode: false, - max_count_interrupt: false, - channel_ios: TscIOPin::Group1Io1.into(), - shield_ios: 0, // no shield - sampling_ios: TscIOPin::Group1Io2.into(), - }; - - let mut g1: PinGroup = PinGroup::new(); - g1.set_io1(context.PA0, PinType::Sample); - g1.set_io2(context.PA1, PinType::Channel); - - let mut touch_controller = tsc::Tsc::new_async( - context.TSC, - Some(g1), - None, - None, - None, - None, - None, - None, - None, - config, - Irqs, - ); - - // Check if TSC is ready - if touch_controller.get_state() != State::Ready { - info!("TSC not ready!"); - loop {} // Halt execution - } - info!("TSC initialized successfully"); - - // LED2 on the STM32L073RZ nucleo-board (PA5) - let mut led = Output::new(context.PA5, Level::High, Speed::Low); - - // smaller sample capacitor discharge faster and can be used with shorter delay. - let discharge_delay = 5; // ms - - info!("Starting touch_controller interface"); - loop { - touch_controller.start(); - touch_controller.pend_for_acquisition().await; - touch_controller.discharge_io(true); - Timer::after_millis(discharge_delay).await; - - let grp1_status = touch_controller.group_get_status(Group::One); - match grp1_status { - GroupStatus::Complete => { - let group_one_val = touch_controller.group_get_value(Group::One); - info!("{}", group_one_val); - led.set_high(); - } - GroupStatus::Ongoing => led.set_low(), - } - } -} diff --git a/examples/stm32l0/src/bin/blocking-tsc.rs b/examples/stm32l0/src/bin/blocking-tsc.rs deleted file mode 100644 index 7e4f409460..0000000000 --- a/examples/stm32l0/src/bin/blocking-tsc.rs +++ /dev/null @@ -1,116 +0,0 @@ -// Example of polling TSC (Touch Sensing Controller) that lights an LED when touch is detected. -// -// Suggested physical setup on STM32L073RZ Nucleo board: -// - Connect a 1000pF capacitor between pin A0 and GND. This is your sampling capacitor. -// - Connect one end of a 1K resistor to pin A1 and leave the other end loose. -// The loose end will act as touch sensor which will register your touch. -// -// Troubleshooting the setup: -// - If no touch seems to be registered, then try to disconnect the sampling capacitor from GND momentarily, -// now the led should light up. Next try using a different value for the sampling capacitor. -// Also experiment with increasing the values for `ct_pulse_high_length`, `ct_pulse_low_length`, `pulse_generator_prescaler`, `max_count_value` and `discharge_delay`. -// -// All configuration values and sampling capacitor value have been determined experimentally. -// Suitable configuration and discharge delay values are highly dependent on the value of the sample capacitor. For example, a shorter discharge delay can be used with smaller capacitor values. -// -#![no_std] -#![no_main] - -use defmt::*; -use embassy_stm32::gpio::{Level, Output, Speed}; -use embassy_stm32::tsc::{self, *}; -use embassy_time::Timer; -use {defmt_rtt as _, panic_probe as _}; - -/// This example is written for the nucleo-stm32l073rz, with a stm32l073rz chip. -/// -/// Make sure you check/update the following (whether you use the L073RZ or another board): -/// -/// * [ ] Update .cargo/config.toml with the correct `probe-rs run --chip STM32L073RZTx`chip name. -/// * [ ] Update Cargo.toml to have the correct `embassy-stm32` feature, for L073RZ it should be `stm32l073rz`. -/// * [ ] If your board has a special clock or power configuration, make sure that it is -/// set up appropriately. -/// * [ ] If your board has different pin mapping, update any pin numbers or peripherals -/// to match your schematic -/// -/// If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: -/// -/// * Which example you are trying to run -/// * Which chip and board you are using -/// -/// Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org -#[embassy_executor::main] -async fn main(_spawner: embassy_executor::Spawner) { - let device_config = embassy_stm32::Config::default(); - let context = embassy_stm32::init(device_config); - - let tsc_conf = Config { - ct_pulse_high_length: ChargeTransferPulseCycle::_4, - ct_pulse_low_length: ChargeTransferPulseCycle::_4, - spread_spectrum: false, - spread_spectrum_deviation: SSDeviation::new(2).unwrap(), - spread_spectrum_prescaler: false, - pulse_generator_prescaler: PGPrescalerDivider::_16, - max_count_value: MaxCount::_255, - io_default_mode: false, - synchro_pin_polarity: false, - acquisition_mode: false, - max_count_interrupt: false, - channel_ios: TscIOPin::Group1Io1.into(), - shield_ios: 0, // no shield - sampling_ios: TscIOPin::Group1Io2.into(), - }; - - let mut g1: PinGroup = PinGroup::new(); - g1.set_io1(context.PA0, PinType::Sample); - g1.set_io2(context.PA1, PinType::Channel); - - let mut touch_controller = tsc::Tsc::new_blocking( - context.TSC, - Some(g1), - None, - None, - None, - None, - None, - None, - None, - tsc_conf, - ); - - // Check if TSC is ready - if touch_controller.get_state() != State::Ready { - info!("TSC not ready!"); - loop {} // Halt execution - } - info!("TSC initialized successfully"); - - // LED2 on the STM32L073RZ nucleo-board (PA5) - let mut led = Output::new(context.PA5, Level::High, Speed::Low); - - // smaller sample capacitor discharge faster and can be used with shorter delay. - let discharge_delay = 5; // ms - - // the interval at which the loop polls for new touch sensor values - let polling_interval = 100; // ms - - info!("polling for touch"); - loop { - touch_controller.start(); - touch_controller.poll_for_acquisition(); - touch_controller.discharge_io(true); - Timer::after_millis(discharge_delay).await; - - let grp1_status = touch_controller.group_get_status(Group::One); - match grp1_status { - GroupStatus::Complete => { - let group_one_val = touch_controller.group_get_value(Group::One); - info!("{}", group_one_val); - led.set_high(); - } - GroupStatus::Ongoing => led.set_low(), - } - - Timer::after_millis(polling_interval).await; - } -} diff --git a/examples/stm32l0/src/bin/tsc_async.rs b/examples/stm32l0/src/bin/tsc_async.rs new file mode 100644 index 0000000000..cebe9712fd --- /dev/null +++ b/examples/stm32l0/src/bin/tsc_async.rs @@ -0,0 +1,116 @@ +// Example of async TSC (Touch Sensing Controller) that lights an LED when touch is detected. +// +// This example demonstrates: +// 1. Configuring a single TSC channel pin +// 2. Using the blocking TSC interface with polling +// 3. Waiting for acquisition completion using `poll_for_acquisition` +// 4. Reading touch values and controlling an LED based on the results +// +// Suggested physical setup on STM32L073RZ Nucleo board: +// - Connect a 1000pF capacitor between pin PA0 and GND. This is your sampling capacitor. +// - Connect one end of a 1K resistor to pin PA1 and leave the other end loose. +// The loose end will act as the touch sensor which will register your touch. +// +// The example uses two pins from Group 1 of the TSC on the STM32L073RZ Nucleo board: +// - PA0 as the sampling capacitor, TSC group 1 IO1 (label A0) +// - PA1 as the channel pin, TSC group 1 IO2 (label A1) +// +// The program continuously reads the touch sensor value: +// - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value. +// - The LED is turned on when touch is detected (sensor value < 25). +// - Touch values are logged to the console. +// +// Troubleshooting: +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, +// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// +// Note: Configuration values and sampling capacitor value have been determined experimentally. +// Optimal values may vary based on your specific hardware setup. +// Pins have been chosen for their convenient locations on the STM32L073RZ board. Refer to the +// official relevant STM32 datasheets and nucleo-board user manuals to find suitable +// alternative pins. +// +// Beware for STM32L073RZ nucleo-board, that PA2 and PA3 is used for the uart connection to +// the programmer chip. If you try to use these two pins for TSC, you will get strange +// readings, unless you somehow reconfigure/re-wire your nucleo-board. +// No errors or warnings will be emitted, they will just silently not work as expected. +// (see nucleo user manual UM1724, Rev 14, page 25) + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + TSC => InterruptHandler; +}); +const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + let mut pin_group: PinGroupWithRoles = PinGroupWithRoles::default(); + pin_group.set_io1::(context.PA0); + let sensor = pin_group.set_io2::(context.PA1); + + let tsc_conf = Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_4, + ct_pulse_low_length: ChargeTransferPulseCycle::_4, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let pin_groups: PinGroups = PinGroups { + g1: Some(pin_group.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, tsc_conf, Irqs).unwrap(); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + info!("TSC not ready!"); + return; + } + info!("TSC initialized successfully"); + + // LED2 on the STM32L073RZ nucleo-board (PA5) + let mut led = Output::new(context.PA5, Level::Low, Speed::Low); + + let discharge_delay = 5; // ms + + info!("Starting touch_controller interface"); + loop { + touch_controller.set_active_channels_mask(sensor.pin.into()); + touch_controller.start(); + touch_controller.pend_for_acquisition().await; + touch_controller.discharge_io(true); + Timer::after_millis(discharge_delay).await; + + let group_val = touch_controller.group_get_value(sensor.pin.group()); + info!("Touch value: {}", group_val); + + if group_val < SENSOR_THRESHOLD { + led.set_high(); + } else { + led.set_low(); + } + + Timer::after_millis(100).await; + } +} diff --git a/examples/stm32l0/src/bin/tsc_blocking.rs b/examples/stm32l0/src/bin/tsc_blocking.rs new file mode 100644 index 0000000000..65203925c9 --- /dev/null +++ b/examples/stm32l0/src/bin/tsc_blocking.rs @@ -0,0 +1,142 @@ +// Example of blocking TSC (Touch Sensing Controller) that lights an LED when touch is detected. +// +// This example demonstrates: +// 1. Configuring a single TSC channel pin +// 2. Using the blocking TSC interface with polling +// 3. Waiting for acquisition completion using `poll_for_acquisition` +// 4. Reading touch values and controlling an LED based on the results +// +// Suggested physical setup on STM32L073RZ Nucleo board: +// - Connect a 1000pF capacitor between pin PA0 and GND. This is your sampling capacitor. +// - Connect one end of a 1K resistor to pin PA1 and leave the other end loose. +// The loose end will act as the touch sensor which will register your touch. +// +// The example uses two pins from Group 1 of the TSC on the STM32L073RZ Nucleo board: +// - PA0 as the sampling capacitor, TSC group 1 IO1 (label A0) +// - PA1 as the channel pin, TSC group 1 IO2 (label A1) +// +// The program continuously reads the touch sensor value: +// - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value. +// - The LED is turned on when touch is detected (sensor value < 25). +// - Touch values are logged to the console. +// +// Troubleshooting: +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, +// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// +// Note: Configuration values and sampling capacitor value have been determined experimentally. +// Optimal values may vary based on your specific hardware setup. +// Pins have been chosen for their convenient locations on the STM32L073RZ board. Refer to the +// official relevant STM32 datasheets and nucleo-board user manuals to find suitable +// alternative pins. +// +// Beware for STM32L073RZ nucleo-board, that PA2 and PA3 is used for the uart connection to +// the programmer chip. If you try to use these two pins for TSC, you will get strange +// readings, unless you somehow reconfigure/re-wire your nucleo-board. +// No errors or warnings will be emitted, they will just silently not work as expected. +// (see nucleo user manual UM1724, Rev 14, page 25) + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{mode, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + let tsc_conf = Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_4, + ct_pulse_low_length: ChargeTransferPulseCycle::_4, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let mut g1: PinGroupWithRoles = PinGroupWithRoles::default(); + g1.set_io1::(context.PA0); + let tsc_sensor = g1.set_io2::(context.PA1); + + let pin_groups: PinGroups = PinGroups { + g1: Some(g1.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, pin_groups, tsc_conf).unwrap(); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + crate::panic!("TSC not ready!"); + } + info!("TSC initialized successfully"); + + // LED2 on the STM32L073RZ nucleo-board (PA5) + let mut led = Output::new(context.PA5, Level::High, Speed::Low); + + // smaller sample capacitor discharge faster and can be used with shorter delay. + let discharge_delay = 5; // ms + + // the interval at which the loop polls for new touch sensor values + let polling_interval = 100; // ms + + info!("polling for touch"); + loop { + touch_controller.set_active_channels_mask(tsc_sensor.pin.into()); + touch_controller.start(); + touch_controller.poll_for_acquisition(); + touch_controller.discharge_io(true); + Timer::after_millis(discharge_delay).await; + + match read_touch_value(&mut touch_controller, tsc_sensor.pin).await { + Some(v) => { + info!("sensor value {}", v); + if v < SENSOR_THRESHOLD { + led.set_high(); + } else { + led.set_low(); + } + } + None => led.set_low(), + } + + Timer::after_millis(polling_interval).await; + } +} + +const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10; + +// attempt to read group status and delay when still ongoing +async fn read_touch_value( + touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Blocking>, + sensor_pin: TscIOPin, +) -> Option { + for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS { + match touch_controller.group_get_status(sensor_pin.group()) { + GroupStatus::Complete => { + return Some(touch_controller.group_get_value(sensor_pin.group())); + } + GroupStatus::Ongoing => { + // if you end up here a lot, then you prob need to increase discharge_delay + // or consider changing the code to adjust the discharge_delay dynamically + info!("Acquisition still ongoing"); + Timer::after_millis(1).await; + } + } + } + None +} diff --git a/examples/stm32l0/src/bin/tsc_multipin.rs b/examples/stm32l0/src/bin/tsc_multipin.rs new file mode 100644 index 0000000000..6170d0799e --- /dev/null +++ b/examples/stm32l0/src/bin/tsc_multipin.rs @@ -0,0 +1,233 @@ +// Example of TSC (Touch Sensing Controller) using multiple pins from the same tsc-group. +// +// What is special about using multiple TSC pins as sensor channels from the same TSC group, +// is that only one TSC pin for each TSC group can be acquired and read at the time. +// To control which channel pins are acquired and read, we must write a mask before initiating an +// acquisition. To help manage and abstract all this business away, we can organize our channel +// pins into acquisition banks. Each acquisition bank can contain exactly one channel pin per TSC +// group and it will contain the relevant mask. +// +// This example demonstrates how to: +// 1. Configure multiple channel pins within a single TSC group +// 2. Use the set_active_channels method to switch between different channels +// 3. Read and interpret touch values from multiple channels in the same group +// +// Suggested physical setup on STM32L073RZ Nucleo board: +// - Connect a 1000pF capacitor between pin PA0 (label A0) and GND. This is the sampling capacitor for TSC +// group 1. +// - Connect one end of a 1K resistor to pin PA1 (label A1) and leave the other end loose. +// The loose end will act as a touch sensor. +// +// - Connect a 1000pF capacitor between pin PB3 (label D3) and GND. This is the sampling capacitor for TSC +// group 5. +// - Connect one end of another 1K resistor to pin PB4 and leave the other end loose. +// The loose end will act as a touch sensor. +// - Connect one end of another 1K resistor to pin PB6 and leave the other end loose. +// The loose end will act as a touch sensor. +// +// The example uses pins from two TSC groups. +// - PA0 as sampling capacitor, TSC group 1 IO1 (label A0) +// - PA1 as channel, TSC group 1 IO2 (label A1) +// - PB3 as sampling capacitor, TSC group 5 IO1 (label D3) +// - PB4 as channel, TSC group 5 IO2 (label D3) +// - PB6 as channel, TSC group 5 IO3 (label D5) +// +// The pins have been chosen to make it easy to simply add capacitors directly onto the board and +// connect one leg to GND, and to easily add resistors to the board with no special connectors, +// breadboards, special wires or soldering required. All you need is the capacitors and resistors. +// +// The program reads the designated channel pins and adjusts the LED blinking +// pattern based on which sensor(s) are touched: +// - No touch: LED off +// - one sensor touched: Slow blinking +// - two sensors touched: Fast blinking +// - three sensors touched: LED constantly on +// +// Troubleshooting: +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, +// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// +// Note: Configuration values and sampling capacitor value have been determined experimentally. +// Optimal values may vary based on your specific hardware setup. +// Pins have been chosen for their convenient locations on the STM32L073RZ board. Refer to the +// official relevant STM32 datasheets and nucleo-board user manuals to find suitable +// alternative pins. +// +// Beware for STM32L073RZ nucleo-board, that PA2 and PA3 is used for the uart connection to +// the programmer chip. If you try to use these two pins for TSC, you will get strange +// readings, unless you somehow reconfigure/re-wire your nucleo-board. +// No errors or warnings will be emitted, they will just silently not work as expected. +// (see nucleo user manual UM1724, Rev 14, page 25) + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{bind_interrupts, mode, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + TSC => InterruptHandler; +}); + +const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10; + +async fn read_touch_values( + touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Async>, + tsc_acquisition_bank: &TscAcquisitionBank, +) -> Option { + for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS { + let status = touch_controller.get_acquisition_bank_status(tsc_acquisition_bank); + if status.all_complete() { + let r = touch_controller.get_acquisition_bank_values(tsc_acquisition_bank); + return Some(r); + } else { + info!("Acquisition still ongoing"); + Timer::after_millis(1).await; + } + } + info!("Acquisition failed after {} attempts", MAX_GROUP_STATUS_READ_ATTEMPTS); + None +} + +const SENSOR_THRESHOLD: u16 = 35; + +async fn acquire_sensors( + touch_controller: &mut Tsc<'static, peripherals::TSC, mode::Async>, + tsc_acquisition_bank: &TscAcquisitionBank, +) { + touch_controller.set_active_channels_mask(tsc_acquisition_bank.mask()); + touch_controller.start(); + touch_controller.pend_for_acquisition().await; + touch_controller.discharge_io(true); + let discharge_delay = 5; // ms + Timer::after_millis(discharge_delay).await; +} + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + // ---------- initial configuration of TSC ---------- + let mut pin_group1: PinGroupWithRoles = PinGroupWithRoles::default(); + pin_group1.set_io1::(context.PA0); + let tsc_sensor0 = pin_group1.set_io2(context.PA1); + + let mut pin_group5: PinGroupWithRoles = PinGroupWithRoles::default(); + pin_group5.set_io1::(context.PB3); + let tsc_sensor1 = pin_group5.set_io2(context.PB4); + let tsc_sensor2 = pin_group5.set_io3(context.PB6); + + let config = tsc::Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_16, + ct_pulse_low_length: ChargeTransferPulseCycle::_16, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let pin_groups: PinGroups = PinGroups { + g1: Some(pin_group1.pin_group), + g5: Some(pin_group5.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, config, Irqs).unwrap(); + + // ---------- setting up acquisition banks ---------- + // sensor0 and sensor1 in this example belong to different TSC-groups, + // therefore we can acquire and read them both in one go. + let bank1 = touch_controller.create_acquisition_bank(TscAcquisitionBankPins { + g1_pin: Some(tsc_sensor0), + g5_pin: Some(tsc_sensor1), + ..Default::default() + }); + // `sensor1` and `sensor2` belongs to the same TSC-group, therefore we must make sure to + // acquire them one at the time. Therefore, we organize them into different acquisition banks. + let bank2 = touch_controller.create_acquisition_bank(TscAcquisitionBankPins { + g5_pin: Some(tsc_sensor2), + ..Default::default() + }); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + crate::panic!("TSC not ready!"); + } + + info!("TSC initialized successfully"); + + // LED2 on the STM32L073RZ nucleo-board (PA5) + let mut led = Output::new(context.PA5, Level::High, Speed::Low); + + let mut led_state = false; + + loop { + acquire_sensors(&mut touch_controller, &bank1).await; + let readings1: TscAcquisitionBankReadings = read_touch_values(&mut touch_controller, &bank1) + .await + .expect("should be able to read values for bank 1"); + acquire_sensors(&mut touch_controller, &bank2).await; + let readings2: TscAcquisitionBankReadings = read_touch_values(&mut touch_controller, &bank2) + .await + .expect("should be able to read values for bank 2"); + + let mut touched_sensors_count = 0; + for reading in readings1.iter() { + info!("{}", reading); + if reading.sensor_value < SENSOR_THRESHOLD { + touched_sensors_count += 1; + } + } + for reading in readings2.iter() { + info!("{}", reading); + if reading.sensor_value < SENSOR_THRESHOLD { + touched_sensors_count += 1; + } + } + + match touched_sensors_count { + 0 => { + // No sensors touched, turn off the LED + led.set_low(); + led_state = false; + } + 1 => { + // One sensor touched, blink slowly + led_state = !led_state; + if led_state { + led.set_high(); + } else { + led.set_low(); + } + Timer::after_millis(200).await; + } + 2 => { + // Two sensors touched, blink faster + led_state = !led_state; + if led_state { + led.set_high(); + } else { + led.set_low(); + } + Timer::after_millis(50).await; + } + 3 => { + // All three sensors touched, LED constantly on + led.set_high(); + led_state = true; + } + _ => crate::unreachable!(), // This case should never occur with 3 sensors + } + } +} diff --git a/examples/stm32l4/.cargo/config.toml b/examples/stm32l4/.cargo/config.toml index 83fc6d6f87..d71fb1517a 100644 --- a/examples/stm32l4/.cargo/config.toml +++ b/examples/stm32l4/.cargo/config.toml @@ -2,7 +2,8 @@ # replace STM32F429ZITx with your chip as listed in `probe-rs chip list` #runner = "probe-rs run --chip STM32L475VGT6" #runner = "probe-rs run --chip STM32L475VG" -runner = "probe-rs run --chip STM32L4S5QI" +#runner = "probe-rs run --chip STM32L4S5QI" +runner = "probe-rs run --chip STM32L4R5ZITxP" [build] target = "thumbv7em-none-eabi" diff --git a/examples/stm32l4/Cargo.toml b/examples/stm32l4/Cargo.toml index c5478b17ba..543accc3dc 100644 --- a/examples/stm32l4/Cargo.toml +++ b/examples/stm32l4/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32l4s5vi to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l4s5qi", "memory-x", "time-driver-any", "exti", "chrono"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l4r5zi", "memory-x", "time-driver-any", "exti", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768", ] } diff --git a/examples/stm32l4/README.md b/examples/stm32l4/README.md new file mode 100644 index 0000000000..e463c18a0e --- /dev/null +++ b/examples/stm32l4/README.md @@ -0,0 +1,24 @@ +# Examples for STM32L4 family +Run individual examples with +``` +cargo run --bin +``` +for example +``` +cargo run --bin blinky +``` + +## Checklist before running examples +You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using. + +* [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for L4R5ZI-P it should be `probe-rs run --chip STM32L4R5ZITxP`. (use `probe-rs chip list` to find your chip) +* [ ] Update Cargo.toml to have the correct `embassy-stm32` feature. For example for L4R5ZI-P it should be `stm32l4r5zi`. Look in the `Cargo.toml` file of the `embassy-stm32` project to find the correct feature flag for your chip. +* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. +* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic + +If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: + +* Which example you are trying to run +* Which chip and board you are using + +Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org diff --git a/examples/stm32l4/src/bin/tsc_async.rs b/examples/stm32l4/src/bin/tsc_async.rs new file mode 100644 index 0000000000..ada2c468f0 --- /dev/null +++ b/examples/stm32l4/src/bin/tsc_async.rs @@ -0,0 +1,108 @@ +// Example of async TSC (Touch Sensing Controller) that lights an LED when touch is detected. +// +// This example demonstrates: +// 1. Configuring a single TSC channel pin +// 2. Using the async TSC interface +// 3. Waiting for acquisition completion using `pend_for_acquisition` +// 4. Reading touch values and controlling an LED based on the results +// +// Suggested physical setup on STM32L4R5ZI-P board: +// - Connect a 1000pF capacitor between pin PB4 (D25) and GND. This is your sampling capacitor. +// - Connect one end of a 1K resistor to pin PB5 (D21) and leave the other end loose. +// The loose end will act as the touch sensor which will register your touch. +// +// The example uses two pins from Group 2 of the TSC: +// - PB4 (D25) as the sampling capacitor, TSC group 2 IO1 +// - PB5 (D21) as the channel pin, TSC group 2 IO2 +// +// The program continuously reads the touch sensor value: +// - It starts acquisition, waits for completion using `pend_for_acquisition`, and reads the value. +// - The LED (connected to PB14) is turned on when touch is detected (sensor value < SENSOR_THRESHOLD). +// - Touch values are logged to the console. +// +// Troubleshooting: +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, +// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// +// Note: Configuration values and sampling capacitor value have been determined experimentally. +// Optimal values may vary based on your specific hardware setup. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + TSC => InterruptHandler; +}); +const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + let mut pin_group: PinGroupWithRoles = PinGroupWithRoles::default(); + // D25 + pin_group.set_io1::(context.PB4); + // D21 + let tsc_sensor = pin_group.set_io2::(context.PB5); + + let pin_groups: PinGroups = PinGroups { + g2: Some(pin_group.pin_group), + ..Default::default() + }; + + let tsc_conf = Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_4, + ct_pulse_low_length: ChargeTransferPulseCycle::_4, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, tsc_conf, Irqs).unwrap(); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + info!("TSC not ready!"); + return; + } + info!("TSC initialized successfully"); + + let mut led = Output::new(context.PB14, Level::High, Speed::Low); + + let discharge_delay = 1; // ms + + info!("Starting touch_controller interface"); + loop { + touch_controller.set_active_channels_mask(tsc_sensor.pin.into()); + touch_controller.start(); + touch_controller.pend_for_acquisition().await; + touch_controller.discharge_io(true); + Timer::after_millis(discharge_delay).await; + + let group_val = touch_controller.group_get_value(tsc_sensor.pin.group()); + info!("Touch value: {}", group_val); + + if group_val < SENSOR_THRESHOLD { + led.set_high(); + } else { + led.set_low(); + } + + Timer::after_millis(100).await; + } +} diff --git a/examples/stm32l4/src/bin/tsc_blocking.rs b/examples/stm32l4/src/bin/tsc_blocking.rs new file mode 100644 index 0000000000..76aba55bad --- /dev/null +++ b/examples/stm32l4/src/bin/tsc_blocking.rs @@ -0,0 +1,147 @@ +// # Example of blocking TSC (Touch Sensing Controller) that lights an LED when touch is detected +// +// This example demonstrates how to use the Touch Sensing Controller (TSC) in blocking mode on an STM32L4R5ZI-P board. +// +// ## This example demonstrates: +// +// 1. Configuring a single TSC channel pin +// 2. Using the blocking TSC interface with polling +// 3. Waiting for acquisition completion using `poll_for_acquisition` +// 4. Reading touch values and controlling an LED based on the results +// +// ## Suggested physical setup on STM32L4R5ZI-P board: +// +// - Connect a 1000pF capacitor between pin PB4 (D25) and GND. This is your sampling capacitor. +// - Connect one end of a 1K resistor to pin PB5 (D21) and leave the other end loose. +// The loose end will act as the touch sensor which will register your touch. +// +// ## Pin Configuration: +// +// The example uses two pins from Group 2 of the TSC: +// - PB4 (D25) as the sampling capacitor, TSC group 2 IO1 +// - PB5 (D21) as the channel pin, TSC group 2 IO2 +// +// ## Program Behavior: +// +// The program continuously reads the touch sensor value: +// - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value. +// - The LED (connected to PB14) is turned on when touch is detected (sensor value < SENSOR_THRESHOLD). +// - Touch values are logged to the console. +// +// ## Troubleshooting: +// +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value (currently set to 25). +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, +// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// - Be aware that for some boards, there might be overlapping concerns between some pins, +// such as UART connections for the programmer. No errors or warnings will be emitted if you +// try to use such a pin for TSC, but you may get strange sensor readings. +// +// Note: Configuration values and sampling capacitor value have been determined experimentally. +// Optimal values may vary based on your specific hardware setup. Refer to the official +// STM32L4R5ZI-P datasheet and user manuals for more information on pin configurations and TSC functionality. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{mode, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + let tsc_conf = Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_4, + ct_pulse_low_length: ChargeTransferPulseCycle::_4, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let mut g2: PinGroupWithRoles = PinGroupWithRoles::default(); + // D25 + g2.set_io1::(context.PB4); + // D21 + let tsc_sensor = g2.set_io2::(context.PB5); + + let pin_groups: PinGroups = PinGroups { + g2: Some(g2.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, pin_groups, tsc_conf).unwrap(); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + crate::panic!("TSC not ready!"); + } + info!("TSC initialized successfully"); + + let mut led = Output::new(context.PB14, Level::High, Speed::Low); + + // smaller sample capacitor discharge faster and can be used with shorter delay. + let discharge_delay = 5; // ms + + // the interval at which the loop polls for new touch sensor values + let polling_interval = 100; // ms + + info!("polling for touch"); + loop { + touch_controller.set_active_channels_mask(tsc_sensor.pin.into()); + touch_controller.start(); + touch_controller.poll_for_acquisition(); + touch_controller.discharge_io(true); + Timer::after_millis(discharge_delay).await; + + match read_touch_value(&mut touch_controller, tsc_sensor.pin).await { + Some(v) => { + info!("sensor value {}", v); + if v < SENSOR_THRESHOLD { + led.set_high(); + } else { + led.set_low(); + } + } + None => led.set_low(), + } + + Timer::after_millis(polling_interval).await; + } +} + +const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10; + +// attempt to read group status and delay when still ongoing +async fn read_touch_value( + touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Blocking>, + sensor_pin: TscIOPin, +) -> Option { + for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS { + match touch_controller.group_get_status(sensor_pin.group()) { + GroupStatus::Complete => { + return Some(touch_controller.group_get_value(sensor_pin.group())); + } + GroupStatus::Ongoing => { + // if you end up here a lot, then you prob need to increase discharge_delay + // or consider changing the code to adjust the discharge_delay dynamically + info!("Acquisition still ongoing"); + Timer::after_millis(1).await; + } + } + } + None +} diff --git a/examples/stm32l4/src/bin/tsc_multipin.rs b/examples/stm32l4/src/bin/tsc_multipin.rs new file mode 100644 index 0000000000..20a5595140 --- /dev/null +++ b/examples/stm32l4/src/bin/tsc_multipin.rs @@ -0,0 +1,222 @@ +// # Example of TSC (Touch Sensing Controller) using multiple pins from the same TSC group +// +// This example demonstrates how to use the Touch Sensing Controller (TSC) with multiple pins, including pins from the same TSC group, on an STM32L4R5ZI-P board. +// +// ## Key Concepts +// +// - Only one TSC pin for each TSC group can be acquired and read at a time. +// - To control which channel pins are acquired and read, we must write a mask before initiating an acquisition. +// - We organize channel pins into acquisition banks to manage this process efficiently. +// - Each acquisition bank can contain exactly one channel pin per TSC group and will contain the relevant mask. +// +// ## This example demonstrates how to: +// +// 1. Configure multiple channel pins within a single TSC group +// 2. Use the set_active_channels method to switch between different channels +// 3. Read and interpret touch values from multiple channels in the same group +// +// ## Suggested physical setup on STM32L4R5ZI-P board: +// +// - Connect a 1000pF capacitor between pin PB12 (D19) and GND. This is the sampling capacitor for TSC group 1. +// - Connect one end of a 1K resistor to pin PB13 (D18) and leave the other end loose. This will act as a touch sensor. +// - Connect a 1000pF capacitor between pin PB4 (D25) and GND. This is the sampling capacitor for TSC group 2. +// - Connect one end of a 1K resistor to pin PB5 (D22) and leave the other end loose. This will act as a touch sensor. +// - Connect one end of another 1K resistor to pin PB6 (D71) and leave the other end loose. This will act as a touch sensor. +// +// ## Pin Configuration: +// +// The example uses pins from two TSC groups: +// +// - Group 1: +// - PB12 (D19) as sampling capacitor (TSC group 1 IO1) +// - PB13 (D18) as channel (TSC group 1 IO2) +// - Group 2: +// - PB4 (D25) as sampling capacitor (TSC group 2 IO1) +// - PB5 (D22) as channel (TSC group 2 IO2) +// - PB6 (D71) as channel (TSC group 2 IO3) +// +// The pins have been chosen for their convenient locations on the STM32L4R5ZI-P board, making it easy to add capacitors and resistors directly to the board without special connectors, breadboards, or soldering. +// +// ## Program Behavior: +// +// The program reads the designated channel pins and adjusts the LED (connected to PB14) blinking pattern based on which sensor(s) are touched: +// +// - No touch: LED off +// - One sensor touched: Slow blinking +// - Two sensors touched: Fast blinking +// - Three sensors touched: LED constantly on +// +// ## Troubleshooting: +// +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value (currently set to 20). +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// - Be aware that for some boards there will be overlapping concerns between some pins, for +// example UART connection for the programmer to the MCU and a TSC pin. No errors or warning will +// be emitted if you try to use such a pin for TSC, but you will get strange sensor readings. +// +// Note: Configuration values and sampling capacitor values have been determined experimentally. Optimal values may vary based on your specific hardware setup. Refer to the official STM32L4R5ZI-P datasheet and user manuals for more information on pin configurations and TSC functionality. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{bind_interrupts, mode, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + TSC => InterruptHandler; +}); + +const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10; + +async fn read_touch_values( + touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Async>, + tsc_acquisition_bank: &TscAcquisitionBank, +) -> Option { + for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS { + let status = touch_controller.get_acquisition_bank_status(tsc_acquisition_bank); + if status.all_complete() { + let r = touch_controller.get_acquisition_bank_values(tsc_acquisition_bank); + return Some(r); + } else { + info!("Acquisition still ongoing"); + Timer::after_millis(1).await; + } + } + info!("Acquisition failed after {} attempts", MAX_GROUP_STATUS_READ_ATTEMPTS); + None +} + +const SENSOR_THRESHOLD: u16 = 20; + +async fn acquire_sensors( + touch_controller: &mut Tsc<'static, peripherals::TSC, mode::Async>, + tsc_acquisition_bank: &TscAcquisitionBank, +) { + touch_controller.set_active_channels_mask(tsc_acquisition_bank.mask()); + touch_controller.start(); + touch_controller.pend_for_acquisition().await; + touch_controller.discharge_io(true); + let discharge_delay = 1; // ms + Timer::after_millis(discharge_delay).await; +} + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + // ---------- initial configuration of TSC ---------- + let mut g1: PinGroupWithRoles = PinGroupWithRoles::default(); + g1.set_io1::(context.PB12); + let sensor0 = g1.set_io2::(context.PB13); + + let mut g2: PinGroupWithRoles = PinGroupWithRoles::default(); + g2.set_io1::(context.PB4); + let sensor1 = g2.set_io2(context.PB5); + let sensor2 = g2.set_io3(context.PB6); + + let config = tsc::Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_16, + ct_pulse_low_length: ChargeTransferPulseCycle::_16, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let pin_groups: PinGroups = PinGroups { + g1: Some(g1.pin_group), + g2: Some(g2.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, config, Irqs).unwrap(); + + // ---------- setting up acquisition banks ---------- + // sensor0 and sensor1 belong to different TSC-groups, therefore we can acquire and + // read them both in one go. + let bank1 = touch_controller.create_acquisition_bank(TscAcquisitionBankPins { + g1_pin: Some(sensor0), + g2_pin: Some(sensor1), + ..Default::default() + }); + // `sensor1` and `sensor2` belongs to the same TSC-group, therefore we must make sure to + // acquire them one at the time. We do this by organizing them into different acquisition banks. + let bank2 = touch_controller.create_acquisition_bank(TscAcquisitionBankPins { + g2_pin: Some(sensor2), + ..Default::default() + }); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + crate::panic!("TSC not ready!"); + } + + info!("TSC initialized successfully"); + + let mut led = Output::new(context.PB14, Level::High, Speed::Low); + + let mut led_state = false; + + loop { + acquire_sensors(&mut touch_controller, &bank1).await; + let readings1: TscAcquisitionBankReadings = read_touch_values(&mut touch_controller, &bank1) + .await + .expect("should be able to read values for bank 1"); + acquire_sensors(&mut touch_controller, &bank2).await; + let readings2: TscAcquisitionBankReadings = read_touch_values(&mut touch_controller, &bank2) + .await + .expect("should be able to read values for bank 2"); + + let mut touched_sensors_count = 0; + for reading in readings1.iter().chain(readings2.iter()) { + info!("{}", reading); + if reading.sensor_value < SENSOR_THRESHOLD { + touched_sensors_count += 1; + } + } + + match touched_sensors_count { + 0 => { + // No sensors touched, turn off the LED + led.set_low(); + led_state = false; + } + 1 => { + // One sensor touched, blink slowly + led_state = !led_state; + if led_state { + led.set_high(); + } else { + led.set_low(); + } + Timer::after_millis(200).await; + } + 2 => { + // Two sensors touched, blink faster + led_state = !led_state; + if led_state { + led.set_high(); + } else { + led.set_low(); + } + Timer::after_millis(50).await; + } + 3 => { + // All three sensors touched, LED constantly on + led.set_high(); + led_state = true; + } + _ => crate::unreachable!(), // This case should never occur with 3 sensors + } + } +} diff --git a/examples/stm32u5/src/bin/tsc.rs b/examples/stm32u5/src/bin/tsc.rs index eb15d275a7..a2d56badc3 100644 --- a/examples/stm32u5/src/bin/tsc.rs +++ b/examples/stm32u5/src/bin/tsc.rs @@ -2,8 +2,8 @@ #![no_main] use defmt::*; -use embassy_stm32::bind_interrupts; use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{bind_interrupts, peripherals}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -33,63 +33,50 @@ async fn main(_spawner: embassy_executor::Spawner) { synchro_pin_polarity: false, acquisition_mode: false, max_count_interrupt: false, - channel_ios: TscIOPin::Group2Io2 | TscIOPin::Group7Io3, - shield_ios: TscIOPin::Group1Io3.into(), - sampling_ios: TscIOPin::Group1Io2 | TscIOPin::Group2Io1 | TscIOPin::Group7Io2, }; - let mut g1: PinGroup = PinGroup::new(); - g1.set_io2(context.PB13, PinType::Sample); - g1.set_io3(context.PB14, PinType::Shield); + let mut g1: PinGroupWithRoles = PinGroupWithRoles::default(); + g1.set_io2::(context.PB13); + g1.set_io3::(context.PB14); - let mut g2: PinGroup = PinGroup::new(); - g2.set_io1(context.PB4, PinType::Sample); - g2.set_io2(context.PB5, PinType::Channel); + let mut g2: PinGroupWithRoles = PinGroupWithRoles::default(); + g2.set_io1::(context.PB4); + let sensor0 = g2.set_io2(context.PB5); - let mut g7: PinGroup = PinGroup::new(); - g7.set_io2(context.PE3, PinType::Sample); - g7.set_io3(context.PE4, PinType::Channel); + let mut g7: PinGroupWithRoles = PinGroupWithRoles::default(); + g7.set_io2::(context.PE3); + let sensor1 = g7.set_io3(context.PE4); - let mut touch_controller = tsc::Tsc::new_async( - context.TSC, - Some(g1), - Some(g2), - None, - None, - None, - None, - Some(g7), - None, - config, - Irqs, - ); + let pin_groups: PinGroups = PinGroups { + g1: Some(g1.pin_group), + g2: Some(g2.pin_group), + g7: Some(g7.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, config, Irqs).unwrap(); - touch_controller.discharge_io(true); - Timer::after_millis(1).await; + let acquisition_bank = touch_controller.create_acquisition_bank(TscAcquisitionBankPins { + g2_pin: Some(sensor0), + g7_pin: Some(sensor1), + ..Default::default() + }); - touch_controller.start(); + touch_controller.set_active_channels_bank(&acquisition_bank); - let mut group_two_val = 0; - let mut group_seven_val = 0; info!("Starting touch_controller interface"); loop { + touch_controller.start(); touch_controller.pend_for_acquisition().await; touch_controller.discharge_io(true); Timer::after_millis(1).await; - if touch_controller.group_get_status(Group::Two) == GroupStatus::Complete { - group_two_val = touch_controller.group_get_value(Group::Two); - } + let status = touch_controller.get_acquisition_bank_status(&acquisition_bank); - if touch_controller.group_get_status(Group::Seven) == GroupStatus::Complete { - group_seven_val = touch_controller.group_get_value(Group::Seven); + if status.all_complete() { + let read_values = touch_controller.get_acquisition_bank_values(&acquisition_bank); + info!("group 2 value: {}", read_values.g2.unwrap().sensor_value); + info!("group 7 value: {}", read_values.g2.unwrap().sensor_value); } - - info!( - "Group Two value: {}, Group Seven value: {},", - group_two_val, group_seven_val - ); - - touch_controller.start(); } }