diff options
| author | michel <[email protected]> | 2024-08-07 21:58:49 +0200 |
|---|---|---|
| committer | michel <[email protected]> | 2024-11-29 17:58:33 +0100 |
| commit | 721c6820d4a6e3bbf2546997205a32975e6bad8b (patch) | |
| tree | f725e1f66b0b7f40a3b663f355c2b1d5d389af06 | |
| parent | 1a1d5c4689a8b6c57ebb74e99fdea8df39adb037 (diff) | |
STM32-TSC: enable discriminating between pins within same TSC group and improve TSC library in general
27 files changed, 3006 insertions, 1437 deletions
diff --git a/.gitignore b/.gitignore index a0b5d6a70..352c1f1af 100644 --- a/.gitignore +++ b/.gitignore | |||
| @@ -5,4 +5,7 @@ Cargo.lock | |||
| 5 | third_party | 5 | third_party |
| 6 | /Cargo.toml | 6 | /Cargo.toml |
| 7 | out/ | 7 | out/ |
| 8 | # editor artifacts | ||
| 8 | .zed | 9 | .zed |
| 10 | .neoconf.json | ||
| 11 | *.vim | ||
diff --git a/embassy-stm32/src/tsc/acquisition_banks.rs b/embassy-stm32/src/tsc/acquisition_banks.rs new file mode 100644 index 000000000..21a5c3f87 --- /dev/null +++ b/embassy-stm32/src/tsc/acquisition_banks.rs | |||
| @@ -0,0 +1,209 @@ | |||
| 1 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 2 | use super::pin_groups::G7; | ||
| 3 | #[cfg(tsc_v3)] | ||
| 4 | use super::pin_groups::G8; | ||
| 5 | use super::pin_groups::{tsc_pin_roles, G1, G2, G3, G4, G5, G6}; | ||
| 6 | use super::tsc_io_pin::*; | ||
| 7 | use super::types::{Group, GroupStatus}; | ||
| 8 | use super::TSC_NUM_GROUPS; | ||
| 9 | |||
| 10 | /// Represents a collection of TSC (Touch Sensing Controller) pins for an acquisition bank. | ||
| 11 | /// | ||
| 12 | /// This struct holds optional `TscIOPin` values for each TSC group, allowing for flexible | ||
| 13 | /// configuration of TSC acquisition banks. Each field corresponds to a specific TSC group | ||
| 14 | /// and can be set to `Some(TscIOPin)` if that group is to be included in the acquisition, | ||
| 15 | /// or `None` if it should be excluded. | ||
| 16 | #[allow(missing_docs)] | ||
| 17 | #[derive(Default)] | ||
| 18 | pub struct TscAcquisitionBankPins { | ||
| 19 | pub g1_pin: Option<TscIOPinWithRole<G1, tsc_pin_roles::Channel>>, | ||
| 20 | pub g2_pin: Option<TscIOPinWithRole<G2, tsc_pin_roles::Channel>>, | ||
| 21 | pub g3_pin: Option<TscIOPinWithRole<G3, tsc_pin_roles::Channel>>, | ||
| 22 | pub g4_pin: Option<TscIOPinWithRole<G4, tsc_pin_roles::Channel>>, | ||
| 23 | pub g5_pin: Option<TscIOPinWithRole<G5, tsc_pin_roles::Channel>>, | ||
| 24 | pub g6_pin: Option<TscIOPinWithRole<G6, tsc_pin_roles::Channel>>, | ||
| 25 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 26 | pub g7_pin: Option<TscIOPinWithRole<G7, tsc_pin_roles::Channel>>, | ||
| 27 | #[cfg(tsc_v3)] | ||
| 28 | pub g8_pin: Option<TscIOPinWithRole<G8, tsc_pin_roles::Channel>>, | ||
| 29 | } | ||
| 30 | |||
| 31 | impl TscAcquisitionBankPins { | ||
| 32 | /// Returns an iterator over the pins in this acquisition bank. | ||
| 33 | /// | ||
| 34 | /// This method allows for easy traversal of all configured pins in the bank. | ||
| 35 | pub fn iter(&self) -> TscAcquisitionBankPinsIterator { | ||
| 36 | TscAcquisitionBankPinsIterator(TscAcquisitionBankIterator::new(self)) | ||
| 37 | } | ||
| 38 | } | ||
| 39 | |||
| 40 | /// Iterator for TSC acquisition banks. | ||
| 41 | /// | ||
| 42 | /// This iterator allows traversing through the pins of a `TscAcquisitionBankPins` struct, | ||
| 43 | /// yielding each configured pin in order of the TSC groups. | ||
| 44 | pub struct TscAcquisitionBankIterator<'a> { | ||
| 45 | pins: &'a TscAcquisitionBankPins, | ||
| 46 | current_group: u8, | ||
| 47 | } | ||
| 48 | |||
| 49 | impl<'a> TscAcquisitionBankIterator<'a> { | ||
| 50 | fn new(pins: &'a TscAcquisitionBankPins) -> Self { | ||
| 51 | Self { pins, current_group: 0 } | ||
| 52 | } | ||
| 53 | |||
| 54 | fn next_pin(&mut self) -> Option<TscIOPin> { | ||
| 55 | while self.current_group < TSC_NUM_GROUPS as u8 { | ||
| 56 | let pin = match self.current_group { | ||
| 57 | 0 => self.pins.g1_pin.map(TscIOPinWithRole::get_pin), | ||
| 58 | 1 => self.pins.g2_pin.map(TscIOPinWithRole::get_pin), | ||
| 59 | 2 => self.pins.g3_pin.map(TscIOPinWithRole::get_pin), | ||
| 60 | 3 => self.pins.g4_pin.map(TscIOPinWithRole::get_pin), | ||
| 61 | 4 => self.pins.g5_pin.map(TscIOPinWithRole::get_pin), | ||
| 62 | 5 => self.pins.g6_pin.map(TscIOPinWithRole::get_pin), | ||
| 63 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 64 | 6 => self.pins.g7_pin.map(TscIOPinWithRole::get_pin), | ||
| 65 | #[cfg(tsc_v3)] | ||
| 66 | 7 => self.pins.g8_pin.map(TscIOPinWithRole::get_pin), | ||
| 67 | _ => None, | ||
| 68 | }; | ||
| 69 | self.current_group += 1; | ||
| 70 | if pin.is_some() { | ||
| 71 | return pin; | ||
| 72 | } | ||
| 73 | } | ||
| 74 | None | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | /// Iterator for TSC acquisition bank pins. | ||
| 79 | /// | ||
| 80 | /// This iterator yields `TscIOPin` values for each configured pin in the acquisition bank. | ||
| 81 | pub struct TscAcquisitionBankPinsIterator<'a>(TscAcquisitionBankIterator<'a>); | ||
| 82 | |||
| 83 | impl<'a> Iterator for TscAcquisitionBankPinsIterator<'a> { | ||
| 84 | type Item = TscIOPin; | ||
| 85 | |||
| 86 | fn next(&mut self) -> Option<Self::Item> { | ||
| 87 | self.0.next_pin() | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | impl TscAcquisitionBankPins { | ||
| 92 | /// Returns an iterator over the available pins in the bank | ||
| 93 | pub fn pins_iterator(&self) -> TscAcquisitionBankPinsIterator { | ||
| 94 | TscAcquisitionBankPinsIterator(TscAcquisitionBankIterator::new(self)) | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | /// Represents a collection of TSC pins to be acquired simultaneously. | ||
| 99 | /// | ||
| 100 | /// This struct contains a set of pins to be used in a TSC acquisition with a pre-computed and | ||
| 101 | /// verified mask for efficiently setting up the TSC peripheral before performing an acquisition. | ||
| 102 | /// It ensures that only one channel pin per TSC group is included, adhering to hardware limitations. | ||
| 103 | pub struct TscAcquisitionBank { | ||
| 104 | pub(super) pins: TscAcquisitionBankPins, | ||
| 105 | pub(super) mask: u32, | ||
| 106 | } | ||
| 107 | |||
| 108 | impl TscAcquisitionBank { | ||
| 109 | /// Returns an iterator over the available pins in the bank. | ||
| 110 | pub fn pins_iterator(&self) -> TscAcquisitionBankPinsIterator { | ||
| 111 | self.pins.pins_iterator() | ||
| 112 | } | ||
| 113 | |||
| 114 | /// Returns the mask for this bank. | ||
| 115 | pub fn mask(&self) -> u32 { | ||
| 116 | self.mask | ||
| 117 | } | ||
| 118 | |||
| 119 | /// Retrieves the TSC I/O pin for a given group in this acquisition bank. | ||
| 120 | /// | ||
| 121 | /// # Arguments | ||
| 122 | /// * `group` - The TSC group to retrieve the pin for. | ||
| 123 | /// | ||
| 124 | /// # Returns | ||
| 125 | /// An `Option<TscIOPin>` containing the pin if it exists for the given group, or `None` if not. | ||
| 126 | pub fn get_pin(&self, group: Group) -> Option<TscIOPin> { | ||
| 127 | match group { | ||
| 128 | Group::One => self.pins.g1_pin.map(|p| p.pin), | ||
| 129 | Group::Two => self.pins.g2_pin.map(|p| p.pin), | ||
| 130 | Group::Three => self.pins.g3_pin.map(|p| p.pin), | ||
| 131 | Group::Four => self.pins.g4_pin.map(|p| p.pin), | ||
| 132 | Group::Five => self.pins.g5_pin.map(|p| p.pin), | ||
| 133 | Group::Six => self.pins.g6_pin.map(|p| p.pin), | ||
| 134 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 135 | Group::Seven => self.pins.g7_pin.map(|p| p.pin), | ||
| 136 | #[cfg(tsc_v3)] | ||
| 137 | Group::Eight => self.pins.g8_pin.map(|p| p.pin), | ||
| 138 | } | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | /// Represents the status of all TSC groups in an acquisition bank | ||
| 143 | #[derive(Default)] | ||
| 144 | pub struct TscAcquisitionBankStatus { | ||
| 145 | pub(super) groups: [Option<GroupStatus>; TSC_NUM_GROUPS], | ||
| 146 | } | ||
| 147 | |||
| 148 | impl TscAcquisitionBankStatus { | ||
| 149 | /// Check if all groups in the bank are complete | ||
| 150 | pub fn all_complete(&self) -> bool { | ||
| 151 | self.groups | ||
| 152 | .iter() | ||
| 153 | .all(|&status| status.map_or(true, |s| s == GroupStatus::Complete)) | ||
| 154 | } | ||
| 155 | |||
| 156 | /// Check if any group in the bank is ongoing | ||
| 157 | pub fn any_ongoing(&self) -> bool { | ||
| 158 | self.groups.iter().any(|&status| status == Some(GroupStatus::Ongoing)) | ||
| 159 | } | ||
| 160 | |||
| 161 | /// Get the status of a specific group, if the group is present in the bank | ||
| 162 | pub fn get_group_status(&self, group: Group) -> Option<GroupStatus> { | ||
| 163 | let index: usize = group.into(); | ||
| 164 | self.groups[index] | ||
| 165 | } | ||
| 166 | |||
| 167 | /// Iterator for groups present in the bank | ||
| 168 | pub fn iter(&self) -> impl Iterator<Item = (Group, GroupStatus)> + '_ { | ||
| 169 | self.groups.iter().enumerate().filter_map(|(group_num, status)| { | ||
| 170 | status.and_then(|s| Group::try_from(group_num).ok().map(|group| (group, s))) | ||
| 171 | }) | ||
| 172 | } | ||
| 173 | } | ||
| 174 | |||
| 175 | /// Represents the result of a Touch Sensing Controller (TSC) acquisition for a specific pin. | ||
| 176 | /// | ||
| 177 | /// This struct contains a reference to the `TscIOPin` from which a value was read, | ||
| 178 | /// along with the actual sensor reading for that pin. It provides a convenient way | ||
| 179 | /// to associate TSC readings with their corresponding pins after an acquisition. | ||
| 180 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 181 | #[derive(Clone, Copy, Debug)] | ||
| 182 | pub struct TscChannelReading { | ||
| 183 | /// The sensor reading value obtained from the TSC acquisition. | ||
| 184 | /// Lower values typically indicate a detected touch, while higher values indicate no touch. | ||
| 185 | pub sensor_value: u16, | ||
| 186 | |||
| 187 | /// The `TscIOPin` associated with this reading. | ||
| 188 | /// This allows for easy identification of which pin the reading corresponds to. | ||
| 189 | pub tsc_pin: TscIOPin, | ||
| 190 | } | ||
| 191 | |||
| 192 | /// Represents the readings from all TSC groups | ||
| 193 | #[derive(Default)] | ||
| 194 | pub struct TscAcquisitionBankReadings { | ||
| 195 | pub(super) groups: [Option<TscChannelReading>; TSC_NUM_GROUPS], | ||
| 196 | } | ||
| 197 | |||
| 198 | impl TscAcquisitionBankReadings { | ||
| 199 | /// Get the reading for a specific group, if the group is present in the bank | ||
| 200 | pub fn get_group_reading(&self, group: Group) -> Option<TscChannelReading> { | ||
| 201 | let index: usize = group.into(); | ||
| 202 | self.groups[index] | ||
| 203 | } | ||
| 204 | |||
| 205 | /// Iterator for readings for groups present in the bank | ||
| 206 | pub fn iter(&self) -> impl Iterator<Item = TscChannelReading> + '_ { | ||
| 207 | self.groups.iter().filter_map(|&x| x) | ||
| 208 | } | ||
| 209 | } | ||
diff --git a/embassy-stm32/src/tsc/config.rs b/embassy-stm32/src/tsc/config.rs new file mode 100644 index 000000000..efa1f9a0d --- /dev/null +++ b/embassy-stm32/src/tsc/config.rs | |||
| @@ -0,0 +1,175 @@ | |||
| 1 | /// Charge transfer pulse cycles | ||
| 2 | #[allow(missing_docs)] | ||
| 3 | #[derive(Copy, Clone, PartialEq)] | ||
| 4 | pub enum ChargeTransferPulseCycle { | ||
| 5 | _1, | ||
| 6 | _2, | ||
| 7 | _3, | ||
| 8 | _4, | ||
| 9 | _5, | ||
| 10 | _6, | ||
| 11 | _7, | ||
| 12 | _8, | ||
| 13 | _9, | ||
| 14 | _10, | ||
| 15 | _11, | ||
| 16 | _12, | ||
| 17 | _13, | ||
| 18 | _14, | ||
| 19 | _15, | ||
| 20 | _16, | ||
| 21 | } | ||
| 22 | |||
| 23 | impl Into<u8> for ChargeTransferPulseCycle { | ||
| 24 | fn into(self) -> u8 { | ||
| 25 | match self { | ||
| 26 | ChargeTransferPulseCycle::_1 => 0, | ||
| 27 | ChargeTransferPulseCycle::_2 => 1, | ||
| 28 | ChargeTransferPulseCycle::_3 => 2, | ||
| 29 | ChargeTransferPulseCycle::_4 => 3, | ||
| 30 | ChargeTransferPulseCycle::_5 => 4, | ||
| 31 | ChargeTransferPulseCycle::_6 => 5, | ||
| 32 | ChargeTransferPulseCycle::_7 => 6, | ||
| 33 | ChargeTransferPulseCycle::_8 => 7, | ||
| 34 | ChargeTransferPulseCycle::_9 => 8, | ||
| 35 | ChargeTransferPulseCycle::_10 => 9, | ||
| 36 | ChargeTransferPulseCycle::_11 => 10, | ||
| 37 | ChargeTransferPulseCycle::_12 => 11, | ||
| 38 | ChargeTransferPulseCycle::_13 => 12, | ||
| 39 | ChargeTransferPulseCycle::_14 => 13, | ||
| 40 | ChargeTransferPulseCycle::_15 => 14, | ||
| 41 | ChargeTransferPulseCycle::_16 => 15, | ||
| 42 | } | ||
| 43 | } | ||
| 44 | } | ||
| 45 | |||
| 46 | /// Max count | ||
| 47 | #[allow(missing_docs)] | ||
| 48 | #[derive(Copy, Clone)] | ||
| 49 | pub enum MaxCount { | ||
| 50 | _255, | ||
| 51 | _511, | ||
| 52 | _1023, | ||
| 53 | _2047, | ||
| 54 | _4095, | ||
| 55 | _8191, | ||
| 56 | _16383, | ||
| 57 | } | ||
| 58 | |||
| 59 | impl Into<u8> for MaxCount { | ||
| 60 | fn into(self) -> u8 { | ||
| 61 | match self { | ||
| 62 | MaxCount::_255 => 0, | ||
| 63 | MaxCount::_511 => 1, | ||
| 64 | MaxCount::_1023 => 2, | ||
| 65 | MaxCount::_2047 => 3, | ||
| 66 | MaxCount::_4095 => 4, | ||
| 67 | MaxCount::_8191 => 5, | ||
| 68 | MaxCount::_16383 => 6, | ||
| 69 | } | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | /// Prescaler divider | ||
| 74 | #[allow(missing_docs)] | ||
| 75 | #[derive(Copy, Clone, PartialEq)] | ||
| 76 | pub enum PGPrescalerDivider { | ||
| 77 | _1, | ||
| 78 | _2, | ||
| 79 | _4, | ||
| 80 | _8, | ||
| 81 | _16, | ||
| 82 | _32, | ||
| 83 | _64, | ||
| 84 | _128, | ||
| 85 | } | ||
| 86 | |||
| 87 | impl Into<u8> for PGPrescalerDivider { | ||
| 88 | fn into(self) -> u8 { | ||
| 89 | match self { | ||
| 90 | PGPrescalerDivider::_1 => 0, | ||
| 91 | PGPrescalerDivider::_2 => 1, | ||
| 92 | PGPrescalerDivider::_4 => 2, | ||
| 93 | PGPrescalerDivider::_8 => 3, | ||
| 94 | PGPrescalerDivider::_16 => 4, | ||
| 95 | PGPrescalerDivider::_32 => 5, | ||
| 96 | PGPrescalerDivider::_64 => 6, | ||
| 97 | PGPrescalerDivider::_128 => 7, | ||
| 98 | } | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | /// Error type for SSDeviation | ||
| 103 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 104 | pub enum SSDeviationError { | ||
| 105 | /// The provided value is too low (0) | ||
| 106 | ValueTooLow, | ||
| 107 | /// The provided value is too high (greater than 128) | ||
| 108 | ValueTooHigh, | ||
| 109 | } | ||
| 110 | |||
| 111 | /// Spread Spectrum Deviation | ||
| 112 | #[derive(Copy, Clone)] | ||
| 113 | pub struct SSDeviation(u8); | ||
| 114 | impl SSDeviation { | ||
| 115 | /// Create new deviation value, acceptable inputs are 1-128 | ||
| 116 | pub fn new(val: u8) -> Result<Self, SSDeviationError> { | ||
| 117 | if val == 0 { | ||
| 118 | return Err(SSDeviationError::ValueTooLow); | ||
| 119 | } else if val > 128 { | ||
| 120 | return Err(SSDeviationError::ValueTooHigh); | ||
| 121 | } | ||
| 122 | Ok(Self(val - 1)) | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | impl Into<u8> for SSDeviation { | ||
| 127 | fn into(self) -> u8 { | ||
| 128 | self.0 | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | /// Peripheral configuration | ||
| 133 | #[derive(Clone, Copy)] | ||
| 134 | pub struct Config { | ||
| 135 | /// Duration of high state of the charge transfer pulse | ||
| 136 | pub ct_pulse_high_length: ChargeTransferPulseCycle, | ||
| 137 | /// Duration of the low state of the charge transfer pulse | ||
| 138 | pub ct_pulse_low_length: ChargeTransferPulseCycle, | ||
| 139 | /// Enable/disable of spread spectrum feature | ||
| 140 | pub spread_spectrum: bool, | ||
| 141 | /// Adds variable number of periods of the SS clk to pulse high state | ||
| 142 | pub spread_spectrum_deviation: SSDeviation, | ||
| 143 | /// Selects AHB clock divider used to generate SS clk | ||
| 144 | pub spread_spectrum_prescaler: bool, | ||
| 145 | /// Selects AHB clock divider used to generate pulse generator clk | ||
| 146 | pub pulse_generator_prescaler: PGPrescalerDivider, | ||
| 147 | /// Maximum number of charge transfer pulses that can be generated before error | ||
| 148 | pub max_count_value: MaxCount, | ||
| 149 | /// Defines config of all IOs when no ongoing acquisition | ||
| 150 | pub io_default_mode: bool, | ||
| 151 | /// Polarity of sync input pin | ||
| 152 | pub synchro_pin_polarity: bool, | ||
| 153 | /// Acquisition starts when start bit is set or with sync pin input | ||
| 154 | pub acquisition_mode: bool, | ||
| 155 | /// Enable max count interrupt | ||
| 156 | pub max_count_interrupt: bool, | ||
| 157 | } | ||
| 158 | |||
| 159 | impl Default for Config { | ||
| 160 | fn default() -> Self { | ||
| 161 | Self { | ||
| 162 | ct_pulse_high_length: ChargeTransferPulseCycle::_1, | ||
| 163 | ct_pulse_low_length: ChargeTransferPulseCycle::_1, | ||
| 164 | spread_spectrum: false, | ||
| 165 | spread_spectrum_deviation: SSDeviation::new(1).unwrap(), | ||
| 166 | spread_spectrum_prescaler: false, | ||
| 167 | pulse_generator_prescaler: PGPrescalerDivider::_1, | ||
| 168 | max_count_value: MaxCount::_255, | ||
| 169 | io_default_mode: false, | ||
| 170 | synchro_pin_polarity: false, | ||
| 171 | acquisition_mode: false, | ||
| 172 | max_count_interrupt: false, | ||
| 173 | } | ||
| 174 | } | ||
| 175 | } | ||
diff --git a/embassy-stm32/src/tsc/errors.rs b/embassy-stm32/src/tsc/errors.rs new file mode 100644 index 000000000..21f6441ba --- /dev/null +++ b/embassy-stm32/src/tsc/errors.rs | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | /// Represents errors that can occur when configuring or validating TSC pin groups. | ||
| 2 | #[derive(Debug)] | ||
| 3 | pub enum GroupError { | ||
| 4 | /// Error when a group has no sampling capacitor | ||
| 5 | NoSamplingCapacitor, | ||
| 6 | /// Error when a group has neither channel IOs nor a shield IO | ||
| 7 | NoChannelOrShield, | ||
| 8 | /// Error when a group has both channel IOs and a shield IO | ||
| 9 | MixedChannelAndShield, | ||
| 10 | /// Error when there is more than one shield IO across all groups | ||
| 11 | MultipleShields, | ||
| 12 | } | ||
| 13 | |||
| 14 | /// Error returned when attempting to set an invalid channel pin as active in the TSC. | ||
| 15 | #[derive(Debug)] | ||
| 16 | pub enum AcquisitionBankError { | ||
| 17 | /// Indicates that one or more of the provided pins is not a valid channel pin. | ||
| 18 | InvalidChannelPin, | ||
| 19 | /// Indicates that multiple channels from the same group were provided. | ||
| 20 | MultipleChannelsPerGroup, | ||
| 21 | } | ||
diff --git a/embassy-stm32/src/tsc/mod.rs b/embassy-stm32/src/tsc/mod.rs index 17240e6bc..2df3847bc 100644 --- a/embassy-stm32/src/tsc/mod.rs +++ b/embassy-stm32/src/tsc/mod.rs | |||
| @@ -1,90 +1,119 @@ | |||
| 1 | //! TSC Peripheral Interface | 1 | //! TSC Peripheral Interface |
| 2 | //! | 2 | //! |
| 3 | //! This module provides an interface for the Touch Sensing Controller (TSC) peripheral. | ||
| 4 | //! It supports both blocking and async modes of operation, as well as different TSC versions (v1, v2, v3). | ||
| 3 | //! | 5 | //! |
| 4 | //! # Example (stm32) | 6 | //! # Key Concepts |
| 5 | //! ``` rust, ignore | ||
| 6 | //! | 7 | //! |
| 7 | //! let mut device_config = embassy_stm32::Config::default(); | 8 | //! - **Pin Groups**: TSC pins are organized into groups, each containing up to four IOs. |
| 8 | //! { | 9 | //! - **Pin Roles**: Each pin in a group can have a role: Channel, Sample, or Shield. |
| 9 | //! device_config.rcc.mux = ClockSrc::MSI(Msirange::RANGE_4MHZ); | 10 | //! - **Acquisition Banks**: Used for efficient, repeated TSC acquisitions on specific sets of pins. |
| 10 | //! } | 11 | //! |
| 12 | //! # Example (stm32) | ||
| 11 | //! | 13 | //! |
| 14 | //! ```rust | ||
| 15 | //! let device_config = embassy_stm32::Config::default(); | ||
| 12 | //! let context = embassy_stm32::init(device_config); | 16 | //! let context = embassy_stm32::init(device_config); |
| 13 | //! | 17 | //! |
| 14 | //! let config = tsc::Config { | 18 | //! let config = tsc::Config { |
| 15 | //! ct_pulse_high_length: ChargeTransferPulseCycle::_2, | 19 | //! ct_pulse_high_length: ChargeTransferPulseCycle::_4, |
| 16 | //! ct_pulse_low_length: ChargeTransferPulseCycle::_2, | 20 | //! ct_pulse_low_length: ChargeTransferPulseCycle::_4, |
| 17 | //! spread_spectrum: false, | 21 | //! spread_spectrum: false, |
| 18 | //! spread_spectrum_deviation: SSDeviation::new(2).unwrap(), | 22 | //! spread_spectrum_deviation: SSDeviation::new(2).unwrap(), |
| 19 | //! spread_spectrum_prescaler: false, | 23 | //! spread_spectrum_prescaler: false, |
| 20 | //! pulse_generator_prescaler: PGPrescalerDivider::_4, | 24 | //! pulse_generator_prescaler: PGPrescalerDivider::_16, |
| 21 | //! max_count_value: MaxCount::_8191, | 25 | //! max_count_value: MaxCount::_255, |
| 22 | //! io_default_mode: false, | 26 | //! io_default_mode: false, |
| 23 | //! synchro_pin_polarity: false, | 27 | //! synchro_pin_polarity: false, |
| 24 | //! acquisition_mode: false, | 28 | //! acquisition_mode: false, |
| 25 | //! max_count_interrupt: false, | 29 | //! max_count_interrupt: false, |
| 26 | //! channel_ios: TscIOPin::Group2Io2 | TscIOPin::Group7Io3, | ||
| 27 | //! shield_ios: TscIOPin::Group1Io3.into(), | ||
| 28 | //! sampling_ios: TscIOPin::Group1Io2 | TscIOPin::Group2Io1 | TscIOPin::Group7Io2, | ||
| 29 | //! }; | 30 | //! }; |
| 30 | //! | 31 | //! |
| 31 | //! let mut g1: PinGroup<embassy_stm32::peripherals::TSC, G1> = PinGroup::new(); | 32 | //! let mut g2: PinGroupWithRoles<embassy_stm32::peripherals::TSC, G2> = PinGroupWithRoles::new(); |
| 32 | //! g1.set_io2(context.PB13, PinType::Sample); | 33 | //! g2.set_io1::<tsc_pin_roles::Sample>(context.PB4); |
| 33 | //! g1.set_io3(context.PB14, PinType::Shield); | 34 | //! let sensor_pin = g2.set_io2::<tsc_pin_roles::Channel>(context.PB5); |
| 34 | //! | ||
| 35 | //! let mut g2: PinGroup<embassy_stm32::peripherals::TSC, G2> = PinGroup::new(); | ||
| 36 | //! g2.set_io1(context.PB4, PinType::Sample); | ||
| 37 | //! g2.set_io2(context.PB5, PinType::Channel); | ||
| 38 | //! | 35 | //! |
| 39 | //! let mut g7: PinGroup<embassy_stm32::peripherals::TSC, G7> = PinGroup::new(); | 36 | //! let pin_groups = PinGroups { |
| 40 | //! g7.set_io2(context.PE3, PinType::Sample); | 37 | //! g2: Some(g2.pin_group), |
| 41 | //! g7.set_io3(context.PE4, PinType::Channel); | 38 | //! ..Default::default() |
| 39 | //! }; | ||
| 42 | //! | 40 | //! |
| 43 | //! let mut touch_controller = tsc::Tsc::new_blocking( | 41 | //! let mut touch_controller = tsc::Tsc::new_blocking( |
| 44 | //! context.TSC, | 42 | //! context.TSC, |
| 45 | //! Some(g1), | 43 | //! pin_groups, |
| 46 | //! Some(g2), | ||
| 47 | //! None, | ||
| 48 | //! None, | ||
| 49 | //! None, | ||
| 50 | //! None, | ||
| 51 | //! Some(g7), | ||
| 52 | //! None, | ||
| 53 | //! config, | 44 | //! config, |
| 54 | //! ); | 45 | //! ).unwrap(); |
| 55 | //! | 46 | //! |
| 56 | //! touch_controller.discharge_io(true); | 47 | //! let discharge_delay = 5; // ms |
| 57 | //! Timer::after_millis(1).await; | ||
| 58 | //! | 48 | //! |
| 59 | //! touch_controller.start(); | 49 | //! loop { |
| 50 | //! touch_controller.set_active_channels_mask(sensor_pin.pin.into()); | ||
| 51 | //! touch_controller.start(); | ||
| 52 | //! touch_controller.poll_for_acquisition(); | ||
| 53 | //! touch_controller.discharge_io(true); | ||
| 54 | //! Timer::after_millis(discharge_delay).await; | ||
| 60 | //! | 55 | //! |
| 56 | //! match touch_controller.group_get_status(sensor_pin.pin.group()) { | ||
| 57 | //! GroupStatus::Complete => { | ||
| 58 | //! let group_val = touch_controller.group_get_value(sensor_pin.pin.group()); | ||
| 59 | //! // Process the touch value | ||
| 60 | //! // ... | ||
| 61 | //! } | ||
| 62 | //! GroupStatus::Ongoing => { | ||
| 63 | //! // Handle ongoing acquisition | ||
| 64 | //! // ... | ||
| 65 | //! } | ||
| 66 | //! } | ||
| 67 | //! } | ||
| 61 | //! ``` | 68 | //! ``` |
| 69 | //! | ||
| 70 | //! # Async Usage | ||
| 71 | //! | ||
| 72 | //! For async operation, use `Tsc::new_async` and `pend_for_acquisition` instead of polling. | ||
| 62 | 73 | ||
| 63 | #![macro_use] | 74 | #![macro_use] |
| 64 | 75 | ||
| 65 | /// Enums defined for peripheral parameters | 76 | /// Configuration structures and enums for the TSC peripheral. |
| 66 | pub mod enums; | 77 | pub mod config; |
| 78 | |||
| 79 | /// Definitions and implementations for TSC pin groups. | ||
| 80 | pub mod pin_groups; | ||
| 81 | |||
| 82 | /// Definitions and implementations for individual TSC I/O pins. | ||
| 83 | pub mod tsc_io_pin; | ||
| 84 | |||
| 85 | /// Structures and implementations for TSC acquisition banks. | ||
| 86 | pub mod acquisition_banks; | ||
| 87 | |||
| 88 | /// Core implementation of the TSC (Touch Sensing Controller) driver. | ||
| 89 | pub mod tsc; | ||
| 90 | |||
| 91 | /// Type definitions used throughout the TSC module. | ||
| 92 | pub mod types; | ||
| 93 | |||
| 94 | /// Error types and definitions for the TSC module. | ||
| 95 | pub mod errors; | ||
| 67 | 96 | ||
| 68 | use core::future::poll_fn; | ||
| 69 | use core::marker::PhantomData; | 97 | use core::marker::PhantomData; |
| 70 | use core::task::Poll; | ||
| 71 | 98 | ||
| 72 | use embassy_hal_internal::{into_ref, PeripheralRef}; | 99 | pub use acquisition_banks::*; |
| 100 | pub use config::*; | ||
| 73 | use embassy_sync::waitqueue::AtomicWaker; | 101 | use embassy_sync::waitqueue::AtomicWaker; |
| 74 | pub use enums::*; | 102 | pub use errors::*; |
| 103 | pub use pin_groups::*; | ||
| 104 | pub use tsc::*; | ||
| 105 | pub use tsc_io_pin::*; | ||
| 106 | pub use types::*; | ||
| 75 | 107 | ||
| 76 | use crate::gpio::{AfType, AnyPin, OutputType, Speed}; | 108 | use crate::rcc::RccPeripheral; |
| 77 | use crate::interrupt::typelevel::Interrupt; | ||
| 78 | use crate::mode::{Async, Blocking, Mode as PeriMode}; | ||
| 79 | use crate::rcc::{self, RccPeripheral}; | ||
| 80 | use crate::{interrupt, peripherals, Peripheral}; | 109 | use crate::{interrupt, peripherals, Peripheral}; |
| 81 | 110 | ||
| 82 | #[cfg(tsc_v1)] | 111 | #[cfg(tsc_v1)] |
| 83 | const TSC_NUM_GROUPS: u32 = 6; | 112 | const TSC_NUM_GROUPS: usize = 6; |
| 84 | #[cfg(tsc_v2)] | 113 | #[cfg(tsc_v2)] |
| 85 | const TSC_NUM_GROUPS: u32 = 7; | 114 | const TSC_NUM_GROUPS: usize = 7; |
| 86 | #[cfg(tsc_v3)] | 115 | #[cfg(tsc_v3)] |
| 87 | const TSC_NUM_GROUPS: u32 = 8; | 116 | const TSC_NUM_GROUPS: usize = 8; |
| 88 | 117 | ||
| 89 | /// Error type defined for TSC | 118 | /// Error type defined for TSC |
| 90 | #[derive(Debug, Clone, Copy)] | 119 | #[derive(Debug, Clone, Copy)] |
| @@ -106,859 +135,6 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl | |||
| 106 | } | 135 | } |
| 107 | } | 136 | } |
| 108 | 137 | ||
| 109 | /// Pin type definition to control IO parameters | ||
| 110 | pub enum PinType { | ||
| 111 | /// Sensing channel pin connected to an electrode | ||
| 112 | Channel, | ||
| 113 | /// Sampling capacitor pin, one required for every pin group | ||
| 114 | Sample, | ||
| 115 | /// Shield pin connected to capacitive sensing shield | ||
| 116 | Shield, | ||
| 117 | } | ||
| 118 | |||
| 119 | /// Peripheral state | ||
| 120 | #[derive(PartialEq, Clone, Copy)] | ||
| 121 | pub enum State { | ||
| 122 | /// Peripheral is being setup or reconfigured | ||
| 123 | Reset, | ||
| 124 | /// Ready to start acquisition | ||
| 125 | Ready, | ||
| 126 | /// In process of sensor acquisition | ||
| 127 | Busy, | ||
| 128 | /// Error occured during acquisition | ||
| 129 | Error, | ||
| 130 | } | ||
| 131 | |||
| 132 | /// Individual group status checked after acquisition reported as complete | ||
| 133 | /// For groups with multiple channel pins, may take longer because acquisitions | ||
| 134 | /// are done sequentially. Check this status before pulling count for each | ||
| 135 | /// sampled channel | ||
| 136 | #[derive(PartialEq)] | ||
| 137 | pub enum GroupStatus { | ||
| 138 | /// Acquisition for channel still in progress | ||
| 139 | Ongoing, | ||
| 140 | /// Acquisition either not started or complete | ||
| 141 | Complete, | ||
| 142 | } | ||
| 143 | |||
| 144 | /// Group identifier used to interrogate status | ||
| 145 | #[allow(missing_docs)] | ||
| 146 | pub enum Group { | ||
| 147 | One, | ||
| 148 | Two, | ||
| 149 | Three, | ||
| 150 | Four, | ||
| 151 | Five, | ||
| 152 | Six, | ||
| 153 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 154 | Seven, | ||
| 155 | #[cfg(tsc_v3)] | ||
| 156 | Eight, | ||
| 157 | } | ||
| 158 | |||
| 159 | impl Into<usize> for Group { | ||
| 160 | fn into(self) -> usize { | ||
| 161 | match self { | ||
| 162 | Group::One => 0, | ||
| 163 | Group::Two => 1, | ||
| 164 | Group::Three => 2, | ||
| 165 | Group::Four => 3, | ||
| 166 | Group::Five => 4, | ||
| 167 | Group::Six => 5, | ||
| 168 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 169 | Group::Seven => 6, | ||
| 170 | #[cfg(tsc_v3)] | ||
| 171 | Group::Eight => 7, | ||
| 172 | } | ||
| 173 | } | ||
| 174 | } | ||
| 175 | |||
| 176 | /// Peripheral configuration | ||
| 177 | #[derive(Clone, Copy)] | ||
| 178 | pub struct Config { | ||
| 179 | /// Duration of high state of the charge transfer pulse | ||
| 180 | pub ct_pulse_high_length: ChargeTransferPulseCycle, | ||
| 181 | /// Duration of the low state of the charge transfer pulse | ||
| 182 | pub ct_pulse_low_length: ChargeTransferPulseCycle, | ||
| 183 | /// Enable/disable of spread spectrum feature | ||
| 184 | pub spread_spectrum: bool, | ||
| 185 | /// Adds variable number of periods of the SS clk to pulse high state | ||
| 186 | pub spread_spectrum_deviation: SSDeviation, | ||
| 187 | /// Selects AHB clock divider used to generate SS clk | ||
| 188 | pub spread_spectrum_prescaler: bool, | ||
| 189 | /// Selects AHB clock divider used to generate pulse generator clk | ||
| 190 | pub pulse_generator_prescaler: PGPrescalerDivider, | ||
| 191 | /// Maximum number of charge transfer pulses that can be generated before error | ||
| 192 | pub max_count_value: MaxCount, | ||
| 193 | /// Defines config of all IOs when no ongoing acquisition | ||
| 194 | pub io_default_mode: bool, | ||
| 195 | /// Polarity of sync input pin | ||
| 196 | pub synchro_pin_polarity: bool, | ||
| 197 | /// Acquisition starts when start bit is set or with sync pin input | ||
| 198 | pub acquisition_mode: bool, | ||
| 199 | /// Enable max count interrupt | ||
| 200 | pub max_count_interrupt: bool, | ||
| 201 | /// Channel IO mask | ||
| 202 | pub channel_ios: u32, | ||
| 203 | /// Shield IO mask | ||
| 204 | pub shield_ios: u32, | ||
| 205 | /// Sampling IO mask | ||
| 206 | pub sampling_ios: u32, | ||
| 207 | } | ||
| 208 | |||
| 209 | impl Default for Config { | ||
| 210 | fn default() -> Self { | ||
| 211 | Self { | ||
| 212 | ct_pulse_high_length: ChargeTransferPulseCycle::_1, | ||
| 213 | ct_pulse_low_length: ChargeTransferPulseCycle::_1, | ||
| 214 | spread_spectrum: false, | ||
| 215 | spread_spectrum_deviation: SSDeviation::new(1).unwrap(), | ||
| 216 | spread_spectrum_prescaler: false, | ||
| 217 | pulse_generator_prescaler: PGPrescalerDivider::_1, | ||
| 218 | max_count_value: MaxCount::_255, | ||
| 219 | io_default_mode: false, | ||
| 220 | synchro_pin_polarity: false, | ||
| 221 | acquisition_mode: false, | ||
| 222 | max_count_interrupt: false, | ||
| 223 | channel_ios: 0, | ||
| 224 | shield_ios: 0, | ||
| 225 | sampling_ios: 0, | ||
| 226 | } | ||
| 227 | } | ||
| 228 | } | ||
| 229 | |||
| 230 | /// Pin struct that maintains usage | ||
| 231 | #[allow(missing_docs)] | ||
| 232 | pub struct TscPin<'d, T, C> { | ||
| 233 | _pin: PeripheralRef<'d, AnyPin>, | ||
| 234 | role: PinType, | ||
| 235 | phantom: PhantomData<(T, C)>, | ||
| 236 | } | ||
| 237 | |||
| 238 | enum GroupError { | ||
| 239 | NoSample, | ||
| 240 | ChannelShield, | ||
| 241 | } | ||
| 242 | |||
| 243 | /// Pin group definition | ||
| 244 | /// Pins are organized into groups of four IOs, all groups with a | ||
| 245 | /// sampling channel must also have a sampling capacitor channel. | ||
| 246 | #[allow(missing_docs)] | ||
| 247 | #[derive(Default)] | ||
| 248 | pub struct PinGroup<'d, T, C> { | ||
| 249 | d1: Option<TscPin<'d, T, C>>, | ||
| 250 | d2: Option<TscPin<'d, T, C>>, | ||
| 251 | d3: Option<TscPin<'d, T, C>>, | ||
| 252 | d4: Option<TscPin<'d, T, C>>, | ||
| 253 | } | ||
| 254 | |||
| 255 | impl<'d, T: Instance, C> PinGroup<'d, T, C> { | ||
| 256 | /// Create new sensing group | ||
| 257 | pub fn new() -> Self { | ||
| 258 | Self { | ||
| 259 | d1: None, | ||
| 260 | d2: None, | ||
| 261 | d3: None, | ||
| 262 | d4: None, | ||
| 263 | } | ||
| 264 | } | ||
| 265 | |||
| 266 | fn contains_shield(&self) -> bool { | ||
| 267 | let mut shield_count = 0; | ||
| 268 | |||
| 269 | if let Some(pin) = &self.d1 { | ||
| 270 | if let PinType::Shield = pin.role { | ||
| 271 | shield_count += 1; | ||
| 272 | } | ||
| 273 | } | ||
| 274 | |||
| 275 | if let Some(pin) = &self.d2 { | ||
| 276 | if let PinType::Shield = pin.role { | ||
| 277 | shield_count += 1; | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | if let Some(pin) = &self.d3 { | ||
| 282 | if let PinType::Shield = pin.role { | ||
| 283 | shield_count += 1; | ||
| 284 | } | ||
| 285 | } | ||
| 286 | |||
| 287 | if let Some(pin) = &self.d4 { | ||
| 288 | if let PinType::Shield = pin.role { | ||
| 289 | shield_count += 1; | ||
| 290 | } | ||
| 291 | } | ||
| 292 | |||
| 293 | shield_count == 1 | ||
| 294 | } | ||
| 295 | |||
| 296 | fn check_group(&self) -> Result<(), GroupError> { | ||
| 297 | let mut channel_count = 0; | ||
| 298 | let mut shield_count = 0; | ||
| 299 | let mut sample_count = 0; | ||
| 300 | if let Some(pin) = &self.d1 { | ||
| 301 | match pin.role { | ||
| 302 | PinType::Channel => { | ||
| 303 | channel_count += 1; | ||
| 304 | } | ||
| 305 | PinType::Shield => { | ||
| 306 | shield_count += 1; | ||
| 307 | } | ||
| 308 | PinType::Sample => { | ||
| 309 | sample_count += 1; | ||
| 310 | } | ||
| 311 | } | ||
| 312 | } | ||
| 313 | |||
| 314 | if let Some(pin) = &self.d2 { | ||
| 315 | match pin.role { | ||
| 316 | PinType::Channel => { | ||
| 317 | channel_count += 1; | ||
| 318 | } | ||
| 319 | PinType::Shield => { | ||
| 320 | shield_count += 1; | ||
| 321 | } | ||
| 322 | PinType::Sample => { | ||
| 323 | sample_count += 1; | ||
| 324 | } | ||
| 325 | } | ||
| 326 | } | ||
| 327 | |||
| 328 | if let Some(pin) = &self.d3 { | ||
| 329 | match pin.role { | ||
| 330 | PinType::Channel => { | ||
| 331 | channel_count += 1; | ||
| 332 | } | ||
| 333 | PinType::Shield => { | ||
| 334 | shield_count += 1; | ||
| 335 | } | ||
| 336 | PinType::Sample => { | ||
| 337 | sample_count += 1; | ||
| 338 | } | ||
| 339 | } | ||
| 340 | } | ||
| 341 | |||
| 342 | if let Some(pin) = &self.d4 { | ||
| 343 | match pin.role { | ||
| 344 | PinType::Channel => { | ||
| 345 | channel_count += 1; | ||
| 346 | } | ||
| 347 | PinType::Shield => { | ||
| 348 | shield_count += 1; | ||
| 349 | } | ||
| 350 | PinType::Sample => { | ||
| 351 | sample_count += 1; | ||
| 352 | } | ||
| 353 | } | ||
| 354 | } | ||
| 355 | |||
| 356 | // Every group requires one sampling capacitor | ||
| 357 | if sample_count != 1 { | ||
| 358 | return Err(GroupError::NoSample); | ||
| 359 | } | ||
| 360 | |||
| 361 | // Each group must have at least one shield or channel IO | ||
| 362 | if shield_count == 0 && channel_count == 0 { | ||
| 363 | return Err(GroupError::ChannelShield); | ||
| 364 | } | ||
| 365 | |||
| 366 | // Any group can either contain channel ios or a shield IO | ||
| 367 | if shield_count != 0 && channel_count != 0 { | ||
| 368 | return Err(GroupError::ChannelShield); | ||
| 369 | } | ||
| 370 | |||
| 371 | // No more than one shield IO is allow per group and amongst all groups | ||
| 372 | if shield_count > 1 { | ||
| 373 | return Err(GroupError::ChannelShield); | ||
| 374 | } | ||
| 375 | |||
| 376 | Ok(()) | ||
| 377 | } | ||
| 378 | } | ||
| 379 | |||
| 380 | macro_rules! group_impl { | ||
| 381 | ($group:ident, $trait1:ident, $trait2:ident, $trait3:ident, $trait4:ident) => { | ||
| 382 | impl<'d, T: Instance> PinGroup<'d, T, $group> { | ||
| 383 | #[doc = concat!("Create a new pin1 for ", stringify!($group), " TSC group instance.")] | ||
| 384 | pub fn set_io1(&mut self, pin: impl Peripheral<P = impl $trait1<T>> + 'd, role: PinType) { | ||
| 385 | into_ref!(pin); | ||
| 386 | critical_section::with(|_| { | ||
| 387 | pin.set_low(); | ||
| 388 | pin.set_as_af( | ||
| 389 | pin.af_num(), | ||
| 390 | AfType::output( | ||
| 391 | match role { | ||
| 392 | PinType::Channel => OutputType::PushPull, | ||
| 393 | PinType::Sample => OutputType::OpenDrain, | ||
| 394 | PinType::Shield => OutputType::PushPull, | ||
| 395 | }, | ||
| 396 | Speed::VeryHigh, | ||
| 397 | ), | ||
| 398 | ); | ||
| 399 | self.d1 = Some(TscPin { | ||
| 400 | _pin: pin.map_into(), | ||
| 401 | role: role, | ||
| 402 | phantom: PhantomData, | ||
| 403 | }) | ||
| 404 | }) | ||
| 405 | } | ||
| 406 | |||
| 407 | #[doc = concat!("Create a new pin2 for ", stringify!($group), " TSC group instance.")] | ||
| 408 | pub fn set_io2(&mut self, pin: impl Peripheral<P = impl $trait2<T>> + 'd, role: PinType) { | ||
| 409 | into_ref!(pin); | ||
| 410 | critical_section::with(|_| { | ||
| 411 | pin.set_low(); | ||
| 412 | pin.set_as_af( | ||
| 413 | pin.af_num(), | ||
| 414 | AfType::output( | ||
| 415 | match role { | ||
| 416 | PinType::Channel => OutputType::PushPull, | ||
| 417 | PinType::Sample => OutputType::OpenDrain, | ||
| 418 | PinType::Shield => OutputType::PushPull, | ||
| 419 | }, | ||
| 420 | Speed::VeryHigh, | ||
| 421 | ), | ||
| 422 | ); | ||
| 423 | self.d2 = Some(TscPin { | ||
| 424 | _pin: pin.map_into(), | ||
| 425 | role: role, | ||
| 426 | phantom: PhantomData, | ||
| 427 | }) | ||
| 428 | }) | ||
| 429 | } | ||
| 430 | |||
| 431 | #[doc = concat!("Create a new pin3 for ", stringify!($group), " TSC group instance.")] | ||
| 432 | pub fn set_io3(&mut self, pin: impl Peripheral<P = impl $trait3<T>> + 'd, role: PinType) { | ||
| 433 | into_ref!(pin); | ||
| 434 | critical_section::with(|_| { | ||
| 435 | pin.set_low(); | ||
| 436 | pin.set_as_af( | ||
| 437 | pin.af_num(), | ||
| 438 | AfType::output( | ||
| 439 | match role { | ||
| 440 | PinType::Channel => OutputType::PushPull, | ||
| 441 | PinType::Sample => OutputType::OpenDrain, | ||
| 442 | PinType::Shield => OutputType::PushPull, | ||
| 443 | }, | ||
| 444 | Speed::VeryHigh, | ||
| 445 | ), | ||
| 446 | ); | ||
| 447 | self.d3 = Some(TscPin { | ||
| 448 | _pin: pin.map_into(), | ||
| 449 | role: role, | ||
| 450 | phantom: PhantomData, | ||
| 451 | }) | ||
| 452 | }) | ||
| 453 | } | ||
| 454 | |||
| 455 | #[doc = concat!("Create a new pin4 for ", stringify!($group), " TSC group instance.")] | ||
| 456 | pub fn set_io4(&mut self, pin: impl Peripheral<P = impl $trait4<T>> + 'd, role: PinType) { | ||
| 457 | into_ref!(pin); | ||
| 458 | critical_section::with(|_| { | ||
| 459 | pin.set_low(); | ||
| 460 | pin.set_as_af( | ||
| 461 | pin.af_num(), | ||
| 462 | AfType::output( | ||
| 463 | match role { | ||
| 464 | PinType::Channel => OutputType::PushPull, | ||
| 465 | PinType::Sample => OutputType::OpenDrain, | ||
| 466 | PinType::Shield => OutputType::PushPull, | ||
| 467 | }, | ||
| 468 | Speed::VeryHigh, | ||
| 469 | ), | ||
| 470 | ); | ||
| 471 | self.d4 = Some(TscPin { | ||
| 472 | _pin: pin.map_into(), | ||
| 473 | role: role, | ||
| 474 | phantom: PhantomData, | ||
| 475 | }) | ||
| 476 | }) | ||
| 477 | } | ||
| 478 | } | ||
| 479 | }; | ||
| 480 | } | ||
| 481 | |||
| 482 | group_impl!(G1, G1IO1Pin, G1IO2Pin, G1IO3Pin, G1IO4Pin); | ||
| 483 | group_impl!(G2, G2IO1Pin, G2IO2Pin, G2IO3Pin, G2IO4Pin); | ||
| 484 | group_impl!(G3, G3IO1Pin, G3IO2Pin, G3IO3Pin, G3IO4Pin); | ||
| 485 | group_impl!(G4, G4IO1Pin, G4IO2Pin, G4IO3Pin, G4IO4Pin); | ||
| 486 | group_impl!(G5, G5IO1Pin, G5IO2Pin, G5IO3Pin, G5IO4Pin); | ||
| 487 | group_impl!(G6, G6IO1Pin, G6IO2Pin, G6IO3Pin, G6IO4Pin); | ||
| 488 | group_impl!(G7, G7IO1Pin, G7IO2Pin, G7IO3Pin, G7IO4Pin); | ||
| 489 | group_impl!(G8, G8IO1Pin, G8IO2Pin, G8IO3Pin, G8IO4Pin); | ||
| 490 | |||
| 491 | /// Group 1 marker type. | ||
| 492 | pub enum G1 {} | ||
| 493 | /// Group 2 marker type. | ||
| 494 | pub enum G2 {} | ||
| 495 | /// Group 3 marker type. | ||
| 496 | pub enum G3 {} | ||
| 497 | /// Group 4 marker type. | ||
| 498 | pub enum G4 {} | ||
| 499 | /// Group 5 marker type. | ||
| 500 | pub enum G5 {} | ||
| 501 | /// Group 6 marker type. | ||
| 502 | pub enum G6 {} | ||
| 503 | /// Group 7 marker type. | ||
| 504 | pub enum G7 {} | ||
| 505 | /// Group 8 marker type. | ||
| 506 | pub enum G8 {} | ||
| 507 | |||
| 508 | /// TSC driver | ||
| 509 | pub struct Tsc<'d, T: Instance, K: PeriMode> { | ||
| 510 | _peri: PeripheralRef<'d, T>, | ||
| 511 | _g1: Option<PinGroup<'d, T, G1>>, | ||
| 512 | _g2: Option<PinGroup<'d, T, G2>>, | ||
| 513 | _g3: Option<PinGroup<'d, T, G3>>, | ||
| 514 | _g4: Option<PinGroup<'d, T, G4>>, | ||
| 515 | _g5: Option<PinGroup<'d, T, G5>>, | ||
| 516 | _g6: Option<PinGroup<'d, T, G6>>, | ||
| 517 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 518 | _g7: Option<PinGroup<'d, T, G7>>, | ||
| 519 | #[cfg(tsc_v3)] | ||
| 520 | _g8: Option<PinGroup<'d, T, G8>>, | ||
| 521 | state: State, | ||
| 522 | config: Config, | ||
| 523 | _kind: PhantomData<K>, | ||
| 524 | } | ||
| 525 | |||
| 526 | impl<'d, T: Instance> Tsc<'d, T, Async> { | ||
| 527 | /// Create a Tsc instance that can be awaited for completion | ||
| 528 | pub fn new_async( | ||
| 529 | peri: impl Peripheral<P = T> + 'd, | ||
| 530 | g1: Option<PinGroup<'d, T, G1>>, | ||
| 531 | g2: Option<PinGroup<'d, T, G2>>, | ||
| 532 | g3: Option<PinGroup<'d, T, G3>>, | ||
| 533 | g4: Option<PinGroup<'d, T, G4>>, | ||
| 534 | g5: Option<PinGroup<'d, T, G5>>, | ||
| 535 | g6: Option<PinGroup<'d, T, G6>>, | ||
| 536 | #[cfg(any(tsc_v2, tsc_v3))] g7: Option<PinGroup<'d, T, G7>>, | ||
| 537 | #[cfg(tsc_v3)] g8: Option<PinGroup<'d, T, G8>>, | ||
| 538 | config: Config, | ||
| 539 | _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd, | ||
| 540 | ) -> Self { | ||
| 541 | // Need to check valid pin configuration input | ||
| 542 | let g1 = g1.filter(|b| b.check_group().is_ok()); | ||
| 543 | let g2 = g2.filter(|b| b.check_group().is_ok()); | ||
| 544 | let g3 = g3.filter(|b| b.check_group().is_ok()); | ||
| 545 | let g4 = g4.filter(|b| b.check_group().is_ok()); | ||
| 546 | let g5 = g5.filter(|b| b.check_group().is_ok()); | ||
| 547 | let g6 = g6.filter(|b| b.check_group().is_ok()); | ||
| 548 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 549 | let g7 = g7.filter(|b| b.check_group().is_ok()); | ||
| 550 | #[cfg(tsc_v3)] | ||
| 551 | let g8 = g8.filter(|b| b.check_group().is_ok()); | ||
| 552 | |||
| 553 | match Self::check_shields( | ||
| 554 | &g1, | ||
| 555 | &g2, | ||
| 556 | &g3, | ||
| 557 | &g4, | ||
| 558 | &g5, | ||
| 559 | &g6, | ||
| 560 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 561 | &g7, | ||
| 562 | #[cfg(tsc_v3)] | ||
| 563 | &g8, | ||
| 564 | ) { | ||
| 565 | Ok(()) => Self::new_inner( | ||
| 566 | peri, | ||
| 567 | g1, | ||
| 568 | g2, | ||
| 569 | g3, | ||
| 570 | g4, | ||
| 571 | g5, | ||
| 572 | g6, | ||
| 573 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 574 | g7, | ||
| 575 | #[cfg(tsc_v3)] | ||
| 576 | g8, | ||
| 577 | config, | ||
| 578 | ), | ||
| 579 | Err(_) => Self::new_inner( | ||
| 580 | peri, | ||
| 581 | None, | ||
| 582 | None, | ||
| 583 | None, | ||
| 584 | None, | ||
| 585 | None, | ||
| 586 | None, | ||
| 587 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 588 | None, | ||
| 589 | #[cfg(tsc_v3)] | ||
| 590 | None, | ||
| 591 | config, | ||
| 592 | ), | ||
| 593 | } | ||
| 594 | } | ||
| 595 | /// Asyncronously wait for the end of an acquisition | ||
| 596 | pub async fn pend_for_acquisition(&mut self) { | ||
| 597 | poll_fn(|cx| match self.get_state() { | ||
| 598 | State::Busy => { | ||
| 599 | T::waker().register(cx.waker()); | ||
| 600 | T::regs().ier().write(|w| w.set_eoaie(true)); | ||
| 601 | if self.get_state() != State::Busy { | ||
| 602 | T::regs().ier().write(|w| w.set_eoaie(false)); | ||
| 603 | return Poll::Ready(()); | ||
| 604 | } | ||
| 605 | Poll::Pending | ||
| 606 | } | ||
| 607 | _ => { | ||
| 608 | T::regs().ier().write(|w| w.set_eoaie(false)); | ||
| 609 | Poll::Ready(()) | ||
| 610 | } | ||
| 611 | }) | ||
| 612 | .await; | ||
| 613 | } | ||
| 614 | } | ||
| 615 | |||
| 616 | impl<'d, T: Instance> Tsc<'d, T, Blocking> { | ||
| 617 | /// Create a Tsc instance that must be polled for completion | ||
| 618 | pub fn new_blocking( | ||
| 619 | peri: impl Peripheral<P = T> + 'd, | ||
| 620 | g1: Option<PinGroup<'d, T, G1>>, | ||
| 621 | g2: Option<PinGroup<'d, T, G2>>, | ||
| 622 | g3: Option<PinGroup<'d, T, G3>>, | ||
| 623 | g4: Option<PinGroup<'d, T, G4>>, | ||
| 624 | g5: Option<PinGroup<'d, T, G5>>, | ||
| 625 | g6: Option<PinGroup<'d, T, G6>>, | ||
| 626 | #[cfg(any(tsc_v2, tsc_v3))] g7: Option<PinGroup<'d, T, G7>>, | ||
| 627 | #[cfg(tsc_v3)] g8: Option<PinGroup<'d, T, G8>>, | ||
| 628 | config: Config, | ||
| 629 | ) -> Self { | ||
| 630 | // Need to check valid pin configuration input | ||
| 631 | let g1 = g1.filter(|b| b.check_group().is_ok()); | ||
| 632 | let g2 = g2.filter(|b| b.check_group().is_ok()); | ||
| 633 | let g3 = g3.filter(|b| b.check_group().is_ok()); | ||
| 634 | let g4 = g4.filter(|b| b.check_group().is_ok()); | ||
| 635 | let g5 = g5.filter(|b| b.check_group().is_ok()); | ||
| 636 | let g6 = g6.filter(|b| b.check_group().is_ok()); | ||
| 637 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 638 | let g7 = g7.filter(|b| b.check_group().is_ok()); | ||
| 639 | #[cfg(tsc_v3)] | ||
| 640 | let g8 = g8.filter(|b| b.check_group().is_ok()); | ||
| 641 | |||
| 642 | match Self::check_shields( | ||
| 643 | &g1, | ||
| 644 | &g2, | ||
| 645 | &g3, | ||
| 646 | &g4, | ||
| 647 | &g5, | ||
| 648 | &g6, | ||
| 649 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 650 | &g7, | ||
| 651 | #[cfg(tsc_v3)] | ||
| 652 | &g8, | ||
| 653 | ) { | ||
| 654 | Ok(()) => Self::new_inner( | ||
| 655 | peri, | ||
| 656 | g1, | ||
| 657 | g2, | ||
| 658 | g3, | ||
| 659 | g4, | ||
| 660 | g5, | ||
| 661 | g6, | ||
| 662 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 663 | g7, | ||
| 664 | #[cfg(tsc_v3)] | ||
| 665 | g8, | ||
| 666 | config, | ||
| 667 | ), | ||
| 668 | Err(_) => Self::new_inner( | ||
| 669 | peri, | ||
| 670 | None, | ||
| 671 | None, | ||
| 672 | None, | ||
| 673 | None, | ||
| 674 | None, | ||
| 675 | None, | ||
| 676 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 677 | None, | ||
| 678 | #[cfg(tsc_v3)] | ||
| 679 | None, | ||
| 680 | config, | ||
| 681 | ), | ||
| 682 | } | ||
| 683 | } | ||
| 684 | /// Wait for end of acquisition | ||
| 685 | pub fn poll_for_acquisition(&mut self) { | ||
| 686 | while self.get_state() == State::Busy {} | ||
| 687 | } | ||
| 688 | } | ||
| 689 | |||
| 690 | impl<'d, T: Instance, K: PeriMode> Tsc<'d, T, K> { | ||
| 691 | /// Create new TSC driver | ||
| 692 | fn check_shields( | ||
| 693 | g1: &Option<PinGroup<'d, T, G1>>, | ||
| 694 | g2: &Option<PinGroup<'d, T, G2>>, | ||
| 695 | g3: &Option<PinGroup<'d, T, G3>>, | ||
| 696 | g4: &Option<PinGroup<'d, T, G4>>, | ||
| 697 | g5: &Option<PinGroup<'d, T, G5>>, | ||
| 698 | g6: &Option<PinGroup<'d, T, G6>>, | ||
| 699 | #[cfg(any(tsc_v2, tsc_v3))] g7: &Option<PinGroup<'d, T, G7>>, | ||
| 700 | #[cfg(tsc_v3)] g8: &Option<PinGroup<'d, T, G8>>, | ||
| 701 | ) -> Result<(), GroupError> { | ||
| 702 | let mut shield_count = 0; | ||
| 703 | |||
| 704 | if let Some(pin_group) = g1 { | ||
| 705 | if pin_group.contains_shield() { | ||
| 706 | shield_count += 1; | ||
| 707 | } | ||
| 708 | }; | ||
| 709 | if let Some(pin_group) = g2 { | ||
| 710 | if pin_group.contains_shield() { | ||
| 711 | shield_count += 1; | ||
| 712 | } | ||
| 713 | }; | ||
| 714 | if let Some(pin_group) = g3 { | ||
| 715 | if pin_group.contains_shield() { | ||
| 716 | shield_count += 1; | ||
| 717 | } | ||
| 718 | }; | ||
| 719 | if let Some(pin_group) = g4 { | ||
| 720 | if pin_group.contains_shield() { | ||
| 721 | shield_count += 1; | ||
| 722 | } | ||
| 723 | }; | ||
| 724 | if let Some(pin_group) = g5 { | ||
| 725 | if pin_group.contains_shield() { | ||
| 726 | shield_count += 1; | ||
| 727 | } | ||
| 728 | }; | ||
| 729 | if let Some(pin_group) = g6 { | ||
| 730 | if pin_group.contains_shield() { | ||
| 731 | shield_count += 1; | ||
| 732 | } | ||
| 733 | }; | ||
| 734 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 735 | if let Some(pin_group) = g7 { | ||
| 736 | if pin_group.contains_shield() { | ||
| 737 | shield_count += 1; | ||
| 738 | } | ||
| 739 | }; | ||
| 740 | #[cfg(tsc_v3)] | ||
| 741 | if let Some(pin_group) = g8 { | ||
| 742 | if pin_group.contains_shield() { | ||
| 743 | shield_count += 1; | ||
| 744 | } | ||
| 745 | }; | ||
| 746 | |||
| 747 | if shield_count > 1 { | ||
| 748 | return Err(GroupError::ChannelShield); | ||
| 749 | } | ||
| 750 | |||
| 751 | Ok(()) | ||
| 752 | } | ||
| 753 | |||
| 754 | fn extract_groups(io_mask: u32) -> u32 { | ||
| 755 | let mut groups: u32 = 0; | ||
| 756 | for idx in 0..TSC_NUM_GROUPS { | ||
| 757 | if io_mask & (0x0F << idx * 4) != 0 { | ||
| 758 | groups |= 1 << idx | ||
| 759 | } | ||
| 760 | } | ||
| 761 | groups | ||
| 762 | } | ||
| 763 | |||
| 764 | fn new_inner( | ||
| 765 | peri: impl Peripheral<P = T> + 'd, | ||
| 766 | g1: Option<PinGroup<'d, T, G1>>, | ||
| 767 | g2: Option<PinGroup<'d, T, G2>>, | ||
| 768 | g3: Option<PinGroup<'d, T, G3>>, | ||
| 769 | g4: Option<PinGroup<'d, T, G4>>, | ||
| 770 | g5: Option<PinGroup<'d, T, G5>>, | ||
| 771 | g6: Option<PinGroup<'d, T, G6>>, | ||
| 772 | #[cfg(any(tsc_v2, tsc_v3))] g7: Option<PinGroup<'d, T, G7>>, | ||
| 773 | #[cfg(tsc_v3)] g8: Option<PinGroup<'d, T, G8>>, | ||
| 774 | config: Config, | ||
| 775 | ) -> Self { | ||
| 776 | into_ref!(peri); | ||
| 777 | |||
| 778 | rcc::enable_and_reset::<T>(); | ||
| 779 | |||
| 780 | T::regs().cr().modify(|w| { | ||
| 781 | w.set_tsce(true); | ||
| 782 | w.set_ctph(config.ct_pulse_high_length.into()); | ||
| 783 | w.set_ctpl(config.ct_pulse_low_length.into()); | ||
| 784 | w.set_sse(config.spread_spectrum); | ||
| 785 | // Prevent invalid configuration for pulse generator prescaler | ||
| 786 | if config.ct_pulse_low_length == ChargeTransferPulseCycle::_1 | ||
| 787 | && (config.pulse_generator_prescaler == PGPrescalerDivider::_1 | ||
| 788 | || config.pulse_generator_prescaler == PGPrescalerDivider::_2) | ||
| 789 | { | ||
| 790 | w.set_pgpsc(PGPrescalerDivider::_4.into()); | ||
| 791 | } else if config.ct_pulse_low_length == ChargeTransferPulseCycle::_2 | ||
| 792 | && config.pulse_generator_prescaler == PGPrescalerDivider::_1 | ||
| 793 | { | ||
| 794 | w.set_pgpsc(PGPrescalerDivider::_2.into()); | ||
| 795 | } else { | ||
| 796 | w.set_pgpsc(config.pulse_generator_prescaler.into()); | ||
| 797 | } | ||
| 798 | w.set_ssd(config.spread_spectrum_deviation.into()); | ||
| 799 | w.set_sspsc(config.spread_spectrum_prescaler); | ||
| 800 | |||
| 801 | w.set_mcv(config.max_count_value.into()); | ||
| 802 | w.set_syncpol(config.synchro_pin_polarity); | ||
| 803 | w.set_am(config.acquisition_mode); | ||
| 804 | }); | ||
| 805 | |||
| 806 | // Set IO configuration | ||
| 807 | // Disable Schmitt trigger hysteresis on all used TSC IOs | ||
| 808 | T::regs() | ||
| 809 | .iohcr() | ||
| 810 | .write(|w| w.0 = !(config.channel_ios | config.shield_ios | config.sampling_ios)); | ||
| 811 | |||
| 812 | // Set channel and shield IOs | ||
| 813 | T::regs() | ||
| 814 | .ioccr() | ||
| 815 | .write(|w| w.0 = config.channel_ios | config.shield_ios); | ||
| 816 | |||
| 817 | // Set sampling IOs | ||
| 818 | T::regs().ioscr().write(|w| w.0 = config.sampling_ios); | ||
| 819 | |||
| 820 | // Set the groups to be acquired | ||
| 821 | T::regs() | ||
| 822 | .iogcsr() | ||
| 823 | .write(|w| w.0 = Self::extract_groups(config.channel_ios)); | ||
| 824 | |||
| 825 | // Disable interrupts | ||
| 826 | T::regs().ier().modify(|w| { | ||
| 827 | w.set_eoaie(false); | ||
| 828 | w.set_mceie(false); | ||
| 829 | }); | ||
| 830 | |||
| 831 | // Clear flags | ||
| 832 | T::regs().icr().modify(|w| { | ||
| 833 | w.set_eoaic(true); | ||
| 834 | w.set_mceic(true); | ||
| 835 | }); | ||
| 836 | |||
| 837 | unsafe { | ||
| 838 | T::Interrupt::enable(); | ||
| 839 | } | ||
| 840 | |||
| 841 | Self { | ||
| 842 | _peri: peri, | ||
| 843 | _g1: g1, | ||
| 844 | _g2: g2, | ||
| 845 | _g3: g3, | ||
| 846 | _g4: g4, | ||
| 847 | _g5: g5, | ||
| 848 | _g6: g6, | ||
| 849 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 850 | _g7: g7, | ||
| 851 | #[cfg(tsc_v3)] | ||
| 852 | _g8: g8, | ||
| 853 | state: State::Ready, | ||
| 854 | config, | ||
| 855 | _kind: PhantomData, | ||
| 856 | } | ||
| 857 | } | ||
| 858 | |||
| 859 | /// Start charge transfer acquisition | ||
| 860 | pub fn start(&mut self) { | ||
| 861 | self.state = State::Busy; | ||
| 862 | |||
| 863 | // Disable interrupts | ||
| 864 | T::regs().ier().modify(|w| { | ||
| 865 | w.set_eoaie(false); | ||
| 866 | w.set_mceie(false); | ||
| 867 | }); | ||
| 868 | |||
| 869 | // Clear flags | ||
| 870 | T::regs().icr().modify(|w| { | ||
| 871 | w.set_eoaic(true); | ||
| 872 | w.set_mceic(true); | ||
| 873 | }); | ||
| 874 | |||
| 875 | // Set the touch sensing IOs not acquired to the default mode | ||
| 876 | T::regs().cr().modify(|w| { | ||
| 877 | w.set_iodef(self.config.io_default_mode); | ||
| 878 | }); | ||
| 879 | |||
| 880 | // Start the acquisition | ||
| 881 | T::regs().cr().modify(|w| { | ||
| 882 | w.set_start(true); | ||
| 883 | }); | ||
| 884 | } | ||
| 885 | |||
| 886 | /// Stop charge transfer acquisition | ||
| 887 | pub fn stop(&mut self) { | ||
| 888 | T::regs().cr().modify(|w| { | ||
| 889 | w.set_start(false); | ||
| 890 | }); | ||
| 891 | |||
| 892 | // Set the touch sensing IOs in low power mode | ||
| 893 | T::regs().cr().modify(|w| { | ||
| 894 | w.set_iodef(false); | ||
| 895 | }); | ||
| 896 | |||
| 897 | // Clear flags | ||
| 898 | T::regs().icr().modify(|w| { | ||
| 899 | w.set_eoaic(true); | ||
| 900 | w.set_mceic(true); | ||
| 901 | }); | ||
| 902 | |||
| 903 | self.state = State::Ready; | ||
| 904 | } | ||
| 905 | |||
| 906 | /// Get current state of acquisition | ||
| 907 | pub fn get_state(&mut self) -> State { | ||
| 908 | if self.state == State::Busy { | ||
| 909 | if T::regs().isr().read().eoaf() { | ||
| 910 | if T::regs().isr().read().mcef() { | ||
| 911 | self.state = State::Error | ||
| 912 | } else { | ||
| 913 | self.state = State::Ready | ||
| 914 | } | ||
| 915 | } | ||
| 916 | } | ||
| 917 | self.state | ||
| 918 | } | ||
| 919 | |||
| 920 | /// Get the individual group status to check acquisition complete | ||
| 921 | pub fn group_get_status(&mut self, index: Group) -> GroupStatus { | ||
| 922 | // Status bits are set by hardware when the acquisition on the corresponding | ||
| 923 | // enabled analog IO group is complete, cleared when new acquisition is started | ||
| 924 | let status = match index { | ||
| 925 | Group::One => T::regs().iogcsr().read().g1s(), | ||
| 926 | Group::Two => T::regs().iogcsr().read().g2s(), | ||
| 927 | Group::Three => T::regs().iogcsr().read().g3s(), | ||
| 928 | Group::Four => T::regs().iogcsr().read().g4s(), | ||
| 929 | Group::Five => T::regs().iogcsr().read().g5s(), | ||
| 930 | Group::Six => T::regs().iogcsr().read().g6s(), | ||
| 931 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 932 | Group::Seven => T::regs().iogcsr().read().g7s(), | ||
| 933 | #[cfg(tsc_v3)] | ||
| 934 | Group::Eight => T::regs().iogcsr().read().g8s(), | ||
| 935 | }; | ||
| 936 | match status { | ||
| 937 | true => GroupStatus::Complete, | ||
| 938 | false => GroupStatus::Ongoing, | ||
| 939 | } | ||
| 940 | } | ||
| 941 | |||
| 942 | /// Get the count for the acquisiton, valid once group status is set | ||
| 943 | pub fn group_get_value(&mut self, index: Group) -> u16 { | ||
| 944 | T::regs().iogcr(index.into()).read().cnt() | ||
| 945 | } | ||
| 946 | |||
| 947 | /// Discharge the IOs for subsequent acquisition | ||
| 948 | pub fn discharge_io(&mut self, status: bool) { | ||
| 949 | // Set the touch sensing IOs in low power mode | ||
| 950 | T::regs().cr().modify(|w| { | ||
| 951 | w.set_iodef(!status); | ||
| 952 | }); | ||
| 953 | } | ||
| 954 | } | ||
| 955 | |||
| 956 | impl<'d, T: Instance, K: PeriMode> Drop for Tsc<'d, T, K> { | ||
| 957 | fn drop(&mut self) { | ||
| 958 | rcc::disable::<T>(); | ||
| 959 | } | ||
| 960 | } | ||
| 961 | |||
| 962 | pub(crate) trait SealedInstance { | 138 | pub(crate) trait SealedInstance { |
| 963 | fn regs() -> crate::pac::tsc::Tsc; | 139 | fn regs() -> crate::pac::tsc::Tsc; |
| 964 | fn waker() -> &'static AtomicWaker; | 140 | fn waker() -> &'static AtomicWaker; |
| @@ -988,36 +164,3 @@ foreach_interrupt!( | |||
| 988 | } | 164 | } |
| 989 | }; | 165 | }; |
| 990 | ); | 166 | ); |
| 991 | |||
| 992 | pin_trait!(G1IO1Pin, Instance); | ||
| 993 | pin_trait!(G1IO2Pin, Instance); | ||
| 994 | pin_trait!(G1IO3Pin, Instance); | ||
| 995 | pin_trait!(G1IO4Pin, Instance); | ||
| 996 | pin_trait!(G2IO1Pin, Instance); | ||
| 997 | pin_trait!(G2IO2Pin, Instance); | ||
| 998 | pin_trait!(G2IO3Pin, Instance); | ||
| 999 | pin_trait!(G2IO4Pin, Instance); | ||
| 1000 | pin_trait!(G3IO1Pin, Instance); | ||
| 1001 | pin_trait!(G3IO2Pin, Instance); | ||
| 1002 | pin_trait!(G3IO3Pin, Instance); | ||
| 1003 | pin_trait!(G3IO4Pin, Instance); | ||
| 1004 | pin_trait!(G4IO1Pin, Instance); | ||
| 1005 | pin_trait!(G4IO2Pin, Instance); | ||
| 1006 | pin_trait!(G4IO3Pin, Instance); | ||
| 1007 | pin_trait!(G4IO4Pin, Instance); | ||
| 1008 | pin_trait!(G5IO1Pin, Instance); | ||
| 1009 | pin_trait!(G5IO2Pin, Instance); | ||
| 1010 | pin_trait!(G5IO3Pin, Instance); | ||
| 1011 | pin_trait!(G5IO4Pin, Instance); | ||
| 1012 | pin_trait!(G6IO1Pin, Instance); | ||
| 1013 | pin_trait!(G6IO2Pin, Instance); | ||
| 1014 | pin_trait!(G6IO3Pin, Instance); | ||
| 1015 | pin_trait!(G6IO4Pin, Instance); | ||
| 1016 | pin_trait!(G7IO1Pin, Instance); | ||
| 1017 | pin_trait!(G7IO2Pin, Instance); | ||
| 1018 | pin_trait!(G7IO3Pin, Instance); | ||
| 1019 | pin_trait!(G7IO4Pin, Instance); | ||
| 1020 | pin_trait!(G8IO1Pin, Instance); | ||
| 1021 | pin_trait!(G8IO2Pin, Instance); | ||
| 1022 | pin_trait!(G8IO3Pin, Instance); | ||
| 1023 | 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 000000000..b15890d6f --- /dev/null +++ b/embassy-stm32/src/tsc/pin_groups.rs | |||
| @@ -0,0 +1,675 @@ | |||
| 1 | use core::marker::PhantomData; | ||
| 2 | use core::ops::BitOr; | ||
| 3 | |||
| 4 | use embassy_hal_internal::{into_ref, PeripheralRef}; | ||
| 5 | |||
| 6 | use super::errors::GroupError; | ||
| 7 | use super::tsc_io_pin::*; | ||
| 8 | use super::Instance; | ||
| 9 | use crate::gpio::{AfType, AnyPin, OutputType, Speed}; | ||
| 10 | use crate::Peripheral; | ||
| 11 | |||
| 12 | /// Pin type definition to control IO parameters | ||
| 13 | #[derive(PartialEq, Clone, Copy)] | ||
| 14 | pub enum PinType { | ||
| 15 | /// Sensing channel pin connected to an electrode | ||
| 16 | Channel, | ||
| 17 | /// Sampling capacitor pin, one required for every pin group | ||
| 18 | Sample, | ||
| 19 | /// Shield pin connected to capacitive sensing shield | ||
| 20 | Shield, | ||
| 21 | } | ||
| 22 | |||
| 23 | /// Pin struct that maintains usage | ||
| 24 | #[allow(missing_docs)] | ||
| 25 | pub struct TscPin<'d, T, Group> { | ||
| 26 | _pin: PeripheralRef<'d, AnyPin>, | ||
| 27 | role: PinType, | ||
| 28 | tsc_io_pin: TscIOPin, | ||
| 29 | phantom: PhantomData<(T, Group)>, | ||
| 30 | } | ||
| 31 | |||
| 32 | impl<'d, T, Group> TscPin<'d, T, Group> { | ||
| 33 | /// Returns the role of this TSC pin. | ||
| 34 | /// | ||
| 35 | /// The role indicates whether this pin is configured as a channel, | ||
| 36 | /// sampling capacitor, or shield in the TSC group. | ||
| 37 | /// | ||
| 38 | /// # Returns | ||
| 39 | /// The `PinType` representing the role of this pin. | ||
| 40 | pub fn role(&self) -> PinType { | ||
| 41 | self.role | ||
| 42 | } | ||
| 43 | |||
| 44 | /// Returns the TSC IO pin associated with this pin. | ||
| 45 | /// | ||
| 46 | /// This method provides access to the specific TSC IO pin configuration, | ||
| 47 | /// which includes information about the pin's group and position within that group. | ||
| 48 | /// | ||
| 49 | /// # Returns | ||
| 50 | /// The `TscIOPin` representing this pin's TSC-specific configuration. | ||
| 51 | pub fn tsc_io_pin(&self) -> TscIOPin { | ||
| 52 | self.tsc_io_pin | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | /// Represents a group of TSC (Touch Sensing Controller) pins. | ||
| 57 | /// | ||
| 58 | /// In the TSC peripheral, pins are organized into groups of four IOs. Each group | ||
| 59 | /// must have exactly one sampling capacitor pin and can have multiple channel pins | ||
| 60 | /// or a single shield pin. This structure encapsulates these pin configurations | ||
| 61 | /// for a single TSC group. | ||
| 62 | /// | ||
| 63 | /// # Pin Roles | ||
| 64 | /// - Sampling Capacitor: One required per group, used for charge transfer. | ||
| 65 | /// - Channel: Sensing pins connected to electrodes for touch detection. | ||
| 66 | /// - Shield: Optional, used for active shielding to improve sensitivity. | ||
| 67 | /// | ||
| 68 | /// # Constraints | ||
| 69 | /// - Each group must have exactly one sampling capacitor pin. | ||
| 70 | /// - A group can have either channel pins or a shield pin, but not both. | ||
| 71 | /// - No more than one shield pin is allowed across all groups. | ||
| 72 | #[allow(missing_docs)] | ||
| 73 | pub struct PinGroup<'d, T, Group> { | ||
| 74 | pin1: Option<TscPin<'d, T, Group>>, | ||
| 75 | pin2: Option<TscPin<'d, T, Group>>, | ||
| 76 | pin3: Option<TscPin<'d, T, Group>>, | ||
| 77 | pin4: Option<TscPin<'d, T, Group>>, | ||
| 78 | } | ||
| 79 | |||
| 80 | impl<'d, T, G> Default for PinGroup<'d, T, G> { | ||
| 81 | fn default() -> Self { | ||
| 82 | Self { | ||
| 83 | pin1: None, | ||
| 84 | pin2: None, | ||
| 85 | pin3: None, | ||
| 86 | pin4: None, | ||
| 87 | } | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | /// Defines roles and traits for TSC (Touch Sensing Controller) pins. | ||
| 92 | /// | ||
| 93 | /// This module contains marker types and traits that represent different roles | ||
| 94 | /// a TSC pin can have, such as channel, sample, or shield. | ||
| 95 | pub mod tsc_pin_roles { | ||
| 96 | use super::{OutputType, PinType}; | ||
| 97 | |||
| 98 | /// Marker type for a TSC channel pin. | ||
| 99 | #[derive(PartialEq, Clone, Copy, Debug)] | ||
| 100 | pub struct Channel; | ||
| 101 | |||
| 102 | /// Marker type for a TSC sampling pin. | ||
| 103 | #[derive(PartialEq, Clone, Copy, Debug)] | ||
| 104 | pub struct Sample; | ||
| 105 | |||
| 106 | /// Marker type for a TSC shield pin. | ||
| 107 | #[derive(PartialEq, Clone, Copy, Debug)] | ||
| 108 | pub struct Shield; | ||
| 109 | |||
| 110 | /// Trait for TSC pin roles. | ||
| 111 | /// | ||
| 112 | /// This trait defines the behavior and properties of different TSC pin roles. | ||
| 113 | /// It is implemented by the marker types `Channel`, `Sample`, and `Shield`. | ||
| 114 | pub trait Role { | ||
| 115 | /// Returns the `PinType` associated with this role. | ||
| 116 | fn pin_type() -> PinType; | ||
| 117 | |||
| 118 | /// Returns the `OutputType` associated with this role. | ||
| 119 | fn output_type() -> OutputType; | ||
| 120 | } | ||
| 121 | |||
| 122 | impl Role for Channel { | ||
| 123 | fn pin_type() -> PinType { | ||
| 124 | PinType::Channel | ||
| 125 | } | ||
| 126 | fn output_type() -> OutputType { | ||
| 127 | OutputType::PushPull | ||
| 128 | } | ||
| 129 | } | ||
| 130 | |||
| 131 | impl Role for Sample { | ||
| 132 | fn pin_type() -> PinType { | ||
| 133 | PinType::Sample | ||
| 134 | } | ||
| 135 | fn output_type() -> OutputType { | ||
| 136 | OutputType::OpenDrain | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | impl Role for Shield { | ||
| 141 | fn pin_type() -> PinType { | ||
| 142 | PinType::Shield | ||
| 143 | } | ||
| 144 | fn output_type() -> OutputType { | ||
| 145 | OutputType::PushPull | ||
| 146 | } | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | /// Represents a group of TSC pins with their associated roles. | ||
| 151 | /// | ||
| 152 | /// This struct allows for type-safe configuration of TSC pin groups, | ||
| 153 | /// ensuring that pins are assigned appropriate roles within their group. | ||
| 154 | /// This type is essentially just a wrapper type around a `PinGroup` value. | ||
| 155 | /// | ||
| 156 | /// # Type Parameters | ||
| 157 | /// - `'d`: Lifetime of the pin group. | ||
| 158 | /// - `T`: The TSC instance type. | ||
| 159 | /// - `G`: The group identifier. | ||
| 160 | /// - `R1`, `R2`, `R3`, `R4`: Role types for each pin in the group, defaulting to `Channel`. | ||
| 161 | pub struct PinGroupWithRoles< | ||
| 162 | 'd, | ||
| 163 | T: Instance, | ||
| 164 | G, | ||
| 165 | R1 = tsc_pin_roles::Channel, | ||
| 166 | R2 = tsc_pin_roles::Channel, | ||
| 167 | R3 = tsc_pin_roles::Channel, | ||
| 168 | R4 = tsc_pin_roles::Channel, | ||
| 169 | > { | ||
| 170 | /// The underlying pin group without role information. | ||
| 171 | pub pin_group: PinGroup<'d, T, G>, | ||
| 172 | _phantom: PhantomData<(R1, R2, R3, R4)>, | ||
| 173 | } | ||
| 174 | |||
| 175 | impl<'d, T: Instance, G, R1, R2, R3, R4> Default for PinGroupWithRoles<'d, T, G, R1, R2, R3, R4> { | ||
| 176 | fn default() -> Self { | ||
| 177 | Self { | ||
| 178 | pin_group: PinGroup::default(), | ||
| 179 | _phantom: PhantomData, | ||
| 180 | } | ||
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | impl<'d, T: Instance, G> PinGroup<'d, T, G> { | ||
| 185 | fn contains_exactly_one_shield_pin(&self) -> bool { | ||
| 186 | let shield_count = self.shield_pins().count(); | ||
| 187 | shield_count == 1 | ||
| 188 | } | ||
| 189 | |||
| 190 | fn check_group(&self) -> Result<(), GroupError> { | ||
| 191 | let mut channel_count = 0; | ||
| 192 | let mut shield_count = 0; | ||
| 193 | let mut sample_count = 0; | ||
| 194 | for pin in self.pins().into_iter().flatten() { | ||
| 195 | match pin.role { | ||
| 196 | PinType::Channel => { | ||
| 197 | channel_count += 1; | ||
| 198 | } | ||
| 199 | PinType::Shield => { | ||
| 200 | shield_count += 1; | ||
| 201 | } | ||
| 202 | PinType::Sample => { | ||
| 203 | sample_count += 1; | ||
| 204 | } | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | // Every group requires exactly one sampling capacitor | ||
| 209 | if sample_count != 1 { | ||
| 210 | return Err(GroupError::NoSamplingCapacitor); | ||
| 211 | } | ||
| 212 | |||
| 213 | // Each group must have at least one shield or channel IO | ||
| 214 | if shield_count == 0 && channel_count == 0 { | ||
| 215 | return Err(GroupError::NoChannelOrShield); | ||
| 216 | } | ||
| 217 | |||
| 218 | // Any group can either contain channel ios or a shield IO. | ||
| 219 | // (An active shield requires its own sampling capacitor) | ||
| 220 | if shield_count != 0 && channel_count != 0 { | ||
| 221 | return Err(GroupError::MixedChannelAndShield); | ||
| 222 | } | ||
| 223 | |||
| 224 | // No more than one shield IO is allow per group and amongst all groups | ||
| 225 | if shield_count > 1 { | ||
| 226 | return Err(GroupError::MultipleShields); | ||
| 227 | } | ||
| 228 | |||
| 229 | Ok(()) | ||
| 230 | } | ||
| 231 | |||
| 232 | /// Returns a reference to the first pin in the group, if configured. | ||
| 233 | pub fn pin1(&self) -> Option<&TscPin<'d, T, G>> { | ||
| 234 | self.pin1.as_ref() | ||
| 235 | } | ||
| 236 | |||
| 237 | /// Returns a reference to the second pin in the group, if configured. | ||
| 238 | pub fn pin2(&self) -> Option<&TscPin<'d, T, G>> { | ||
| 239 | self.pin2.as_ref() | ||
| 240 | } | ||
| 241 | |||
| 242 | /// Returns a reference to the third pin in the group, if configured. | ||
| 243 | pub fn pin3(&self) -> Option<&TscPin<'d, T, G>> { | ||
| 244 | self.pin3.as_ref() | ||
| 245 | } | ||
| 246 | |||
| 247 | /// Returns a reference to the fourth pin in the group, if configured. | ||
| 248 | pub fn pin4(&self) -> Option<&TscPin<'d, T, G>> { | ||
| 249 | self.pin4.as_ref() | ||
| 250 | } | ||
| 251 | |||
| 252 | fn sample_pins(&self) -> impl Iterator<Item = TscIOPin> + '_ { | ||
| 253 | self.pins_filtered(PinType::Sample) | ||
| 254 | } | ||
| 255 | |||
| 256 | fn shield_pins(&self) -> impl Iterator<Item = TscIOPin> + '_ { | ||
| 257 | self.pins_filtered(PinType::Shield) | ||
| 258 | } | ||
| 259 | |||
| 260 | fn channel_pins(&self) -> impl Iterator<Item = TscIOPin> + '_ { | ||
| 261 | self.pins_filtered(PinType::Channel) | ||
| 262 | } | ||
| 263 | |||
| 264 | fn pins_filtered(&self, pin_type: PinType) -> impl Iterator<Item = TscIOPin> + '_ { | ||
| 265 | self.pins().into_iter().filter_map(move |pin| { | ||
| 266 | pin.as_ref() | ||
| 267 | .and_then(|p| if p.role == pin_type { Some(p.tsc_io_pin) } else { None }) | ||
| 268 | }) | ||
| 269 | } | ||
| 270 | |||
| 271 | fn make_channel_ios_mask(&self) -> u32 { | ||
| 272 | self.channel_pins().fold(0, u32::bitor) | ||
| 273 | } | ||
| 274 | |||
| 275 | fn make_shield_ios_mask(&self) -> u32 { | ||
| 276 | self.shield_pins().fold(0, u32::bitor) | ||
| 277 | } | ||
| 278 | |||
| 279 | fn make_sample_ios_mask(&self) -> u32 { | ||
| 280 | self.sample_pins().fold(0, u32::bitor) | ||
| 281 | } | ||
| 282 | |||
| 283 | fn pins(&self) -> [&Option<TscPin<'d, T, G>>; 4] { | ||
| 284 | [&self.pin1, &self.pin2, &self.pin3, &self.pin4] | ||
| 285 | } | ||
| 286 | |||
| 287 | fn pins_mut(&mut self) -> [&mut Option<TscPin<'d, T, G>>; 4] { | ||
| 288 | [&mut self.pin1, &mut self.pin2, &mut self.pin3, &mut self.pin4] | ||
| 289 | } | ||
| 290 | } | ||
| 291 | |||
| 292 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 293 | macro_rules! TSC_V2_V3_GUARD { | ||
| 294 | ($e:expr) => {{ | ||
| 295 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 296 | { | ||
| 297 | $e | ||
| 298 | } | ||
| 299 | #[cfg(not(any(tsc_v2, tsc_v3)))] | ||
| 300 | { | ||
| 301 | compile_error!("Group 7 is not supported in this TSC version") | ||
| 302 | } | ||
| 303 | }}; | ||
| 304 | } | ||
| 305 | |||
| 306 | #[cfg(tsc_v3)] | ||
| 307 | macro_rules! TSC_V3_GUARD { | ||
| 308 | ($e:expr) => {{ | ||
| 309 | #[cfg(tsc_v3)] | ||
| 310 | { | ||
| 311 | $e | ||
| 312 | } | ||
| 313 | #[cfg(not(tsc_v3))] | ||
| 314 | { | ||
| 315 | compile_error!("Group 8 is not supported in this TSC version") | ||
| 316 | } | ||
| 317 | }}; | ||
| 318 | } | ||
| 319 | |||
| 320 | macro_rules! trait_to_tsc_io_pin { | ||
| 321 | (G1IO1Pin) => { | ||
| 322 | TscIOPin::Group1Io1 | ||
| 323 | }; | ||
| 324 | (G1IO2Pin) => { | ||
| 325 | TscIOPin::Group1Io2 | ||
| 326 | }; | ||
| 327 | (G1IO3Pin) => { | ||
| 328 | TscIOPin::Group1Io3 | ||
| 329 | }; | ||
| 330 | (G1IO4Pin) => { | ||
| 331 | TscIOPin::Group1Io4 | ||
| 332 | }; | ||
| 333 | |||
| 334 | (G2IO1Pin) => { | ||
| 335 | TscIOPin::Group2Io1 | ||
| 336 | }; | ||
| 337 | (G2IO2Pin) => { | ||
| 338 | TscIOPin::Group2Io2 | ||
| 339 | }; | ||
| 340 | (G2IO3Pin) => { | ||
| 341 | TscIOPin::Group2Io3 | ||
| 342 | }; | ||
| 343 | (G2IO4Pin) => { | ||
| 344 | TscIOPin::Group2Io4 | ||
| 345 | }; | ||
| 346 | |||
| 347 | (G3IO1Pin) => { | ||
| 348 | TscIOPin::Group3Io1 | ||
| 349 | }; | ||
| 350 | (G3IO2Pin) => { | ||
| 351 | TscIOPin::Group3Io2 | ||
| 352 | }; | ||
| 353 | (G3IO3Pin) => { | ||
| 354 | TscIOPin::Group3Io3 | ||
| 355 | }; | ||
| 356 | (G3IO4Pin) => { | ||
| 357 | TscIOPin::Group3Io4 | ||
| 358 | }; | ||
| 359 | |||
| 360 | (G4IO1Pin) => { | ||
| 361 | TscIOPin::Group4Io1 | ||
| 362 | }; | ||
| 363 | (G4IO2Pin) => { | ||
| 364 | TscIOPin::Group4Io2 | ||
| 365 | }; | ||
| 366 | (G4IO3Pin) => { | ||
| 367 | TscIOPin::Group4Io3 | ||
| 368 | }; | ||
| 369 | (G4IO4Pin) => { | ||
| 370 | TscIOPin::Group4Io4 | ||
| 371 | }; | ||
| 372 | |||
| 373 | (G5IO1Pin) => { | ||
| 374 | TscIOPin::Group5Io1 | ||
| 375 | }; | ||
| 376 | (G5IO2Pin) => { | ||
| 377 | TscIOPin::Group5Io2 | ||
| 378 | }; | ||
| 379 | (G5IO3Pin) => { | ||
| 380 | TscIOPin::Group5Io3 | ||
| 381 | }; | ||
| 382 | (G5IO4Pin) => { | ||
| 383 | TscIOPin::Group5Io4 | ||
| 384 | }; | ||
| 385 | |||
| 386 | (G6IO1Pin) => { | ||
| 387 | TscIOPin::Group6Io1 | ||
| 388 | }; | ||
| 389 | (G6IO2Pin) => { | ||
| 390 | TscIOPin::Group6Io2 | ||
| 391 | }; | ||
| 392 | (G6IO3Pin) => { | ||
| 393 | TscIOPin::Group6Io3 | ||
| 394 | }; | ||
| 395 | (G6IO4Pin) => { | ||
| 396 | TscIOPin::Group6Io4 | ||
| 397 | }; | ||
| 398 | |||
| 399 | (G7IO1Pin) => { | ||
| 400 | TSC_V2_V3_GUARD!(TscIOPin::Group7Io1) | ||
| 401 | }; | ||
| 402 | (G7IO2Pin) => { | ||
| 403 | TSC_V2_V3_GUARD!(TscIOPin::Group7Io2) | ||
| 404 | }; | ||
| 405 | (G7IO3Pin) => { | ||
| 406 | TSC_V2_V3_GUARD!(TscIOPin::Group7Io3) | ||
| 407 | }; | ||
| 408 | (G7IO4Pin) => { | ||
| 409 | TSC_V2_V3_GUARD!(TscIOPin::Group7Io4) | ||
| 410 | }; | ||
| 411 | |||
| 412 | (G8IO1Pin) => { | ||
| 413 | TSC_V3_GUARD!(TscIOPin::Group8Io1) | ||
| 414 | }; | ||
| 415 | (G8IO2Pin) => { | ||
| 416 | TSC_V3_GUARD!(TscIOPin::Group8Io2) | ||
| 417 | }; | ||
| 418 | (G8IO3Pin) => { | ||
| 419 | TSC_V3_GUARD!(TscIOPin::Group8Io3) | ||
| 420 | }; | ||
| 421 | (G8IO4Pin) => { | ||
| 422 | TSC_V3_GUARD!(TscIOPin::Group8Io4) | ||
| 423 | }; | ||
| 424 | } | ||
| 425 | |||
| 426 | macro_rules! impl_set_io { | ||
| 427 | ($method:ident, $group:ident, $trait:ident, $index:expr) => { | ||
| 428 | #[doc = concat!("Create a new pin1 for ", stringify!($group), " TSC group instance.")] | ||
| 429 | pub fn $method<Role: tsc_pin_roles::Role>( | ||
| 430 | &mut self, | ||
| 431 | pin: impl Peripheral<P = impl $trait<T>> + 'd, | ||
| 432 | ) -> TscIOPinWithRole<$group, Role> { | ||
| 433 | into_ref!(pin); | ||
| 434 | critical_section::with(|_| { | ||
| 435 | pin.set_low(); | ||
| 436 | pin.set_as_af(pin.af_num(), AfType::output(Role::output_type(), Speed::VeryHigh)); | ||
| 437 | let tsc_io_pin = trait_to_tsc_io_pin!($trait); | ||
| 438 | let new_pin = TscPin { | ||
| 439 | _pin: pin.map_into(), | ||
| 440 | role: Role::pin_type(), | ||
| 441 | tsc_io_pin, | ||
| 442 | phantom: PhantomData, | ||
| 443 | }; | ||
| 444 | *self.pin_group.pins_mut()[$index] = Some(new_pin); | ||
| 445 | TscIOPinWithRole { | ||
| 446 | pin: tsc_io_pin, | ||
| 447 | phantom: PhantomData, | ||
| 448 | } | ||
| 449 | }) | ||
| 450 | } | ||
| 451 | }; | ||
| 452 | } | ||
| 453 | |||
| 454 | macro_rules! group_impl { | ||
| 455 | ($group:ident, $trait1:ident, $trait2:ident, $trait3:ident, $trait4:ident) => { | ||
| 456 | impl< | ||
| 457 | 'd, | ||
| 458 | T: Instance, | ||
| 459 | R1: tsc_pin_roles::Role, | ||
| 460 | R2: tsc_pin_roles::Role, | ||
| 461 | R3: tsc_pin_roles::Role, | ||
| 462 | R4: tsc_pin_roles::Role, | ||
| 463 | > PinGroupWithRoles<'d, T, $group, R1, R2, R3, R4> | ||
| 464 | { | ||
| 465 | impl_set_io!(set_io1, $group, $trait1, 0); | ||
| 466 | impl_set_io!(set_io2, $group, $trait2, 1); | ||
| 467 | impl_set_io!(set_io3, $group, $trait3, 2); | ||
| 468 | impl_set_io!(set_io4, $group, $trait4, 3); | ||
| 469 | } | ||
| 470 | }; | ||
| 471 | } | ||
| 472 | |||
| 473 | group_impl!(G1, G1IO1Pin, G1IO2Pin, G1IO3Pin, G1IO4Pin); | ||
| 474 | group_impl!(G2, G2IO1Pin, G2IO2Pin, G2IO3Pin, G2IO4Pin); | ||
| 475 | group_impl!(G3, G3IO1Pin, G3IO2Pin, G3IO3Pin, G3IO4Pin); | ||
| 476 | group_impl!(G4, G4IO1Pin, G4IO2Pin, G4IO3Pin, G4IO4Pin); | ||
| 477 | group_impl!(G5, G5IO1Pin, G5IO2Pin, G5IO3Pin, G5IO4Pin); | ||
| 478 | group_impl!(G6, G6IO1Pin, G6IO2Pin, G6IO3Pin, G6IO4Pin); | ||
| 479 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 480 | group_impl!(G7, G7IO1Pin, G7IO2Pin, G7IO3Pin, G7IO4Pin); | ||
| 481 | #[cfg(tsc_v3)] | ||
| 482 | group_impl!(G8, G8IO1Pin, G8IO2Pin, G8IO3Pin, G8IO4Pin); | ||
| 483 | |||
| 484 | /// Group 1 marker type. | ||
| 485 | #[derive(Clone, Copy, Debug)] | ||
| 486 | pub enum G1 {} | ||
| 487 | /// Group 2 marker type. | ||
| 488 | #[derive(Clone, Copy, Debug)] | ||
| 489 | pub enum G2 {} | ||
| 490 | /// Group 3 marker type. | ||
| 491 | #[derive(Clone, Copy, Debug)] | ||
| 492 | pub enum G3 {} | ||
| 493 | /// Group 4 marker type. | ||
| 494 | #[derive(Clone, Copy, Debug)] | ||
| 495 | pub enum G4 {} | ||
| 496 | /// Group 5 marker type. | ||
| 497 | #[derive(Clone, Copy, Debug)] | ||
| 498 | pub enum G5 {} | ||
| 499 | /// Group 6 marker type. | ||
| 500 | #[derive(Clone, Copy, Debug)] | ||
| 501 | pub enum G6 {} | ||
| 502 | /// Group 7 marker type. | ||
| 503 | #[derive(Clone, Copy, Debug)] | ||
| 504 | pub enum G7 {} | ||
| 505 | /// Group 8 marker type. | ||
| 506 | #[derive(Clone, Copy, Debug)] | ||
| 507 | pub enum G8 {} | ||
| 508 | |||
| 509 | /// Represents the collection of pin groups for the Touch Sensing Controller (TSC). | ||
| 510 | /// | ||
| 511 | /// Each field corresponds to a specific group of TSC pins: | ||
| 512 | #[allow(missing_docs)] | ||
| 513 | pub struct PinGroups<'d, T: Instance> { | ||
| 514 | pub g1: Option<PinGroup<'d, T, G1>>, | ||
| 515 | pub g2: Option<PinGroup<'d, T, G2>>, | ||
| 516 | pub g3: Option<PinGroup<'d, T, G3>>, | ||
| 517 | pub g4: Option<PinGroup<'d, T, G4>>, | ||
| 518 | pub g5: Option<PinGroup<'d, T, G5>>, | ||
| 519 | pub g6: Option<PinGroup<'d, T, G6>>, | ||
| 520 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 521 | pub g7: Option<PinGroup<'d, T, G7>>, | ||
| 522 | #[cfg(tsc_v3)] | ||
| 523 | pub g8: Option<PinGroup<'d, T, G8>>, | ||
| 524 | } | ||
| 525 | |||
| 526 | impl<'d, T: Instance> PinGroups<'d, T> { | ||
| 527 | pub(super) fn check(&self) -> Result<(), GroupError> { | ||
| 528 | let mut shield_count = 0; | ||
| 529 | |||
| 530 | // Helper function to check a single group | ||
| 531 | fn check_group<C, T: Instance>( | ||
| 532 | group: &Option<PinGroup<'_, T, C>>, | ||
| 533 | shield_count: &mut u32, | ||
| 534 | ) -> Result<(), GroupError> { | ||
| 535 | if let Some(group) = group { | ||
| 536 | group.check_group()?; | ||
| 537 | if group.contains_exactly_one_shield_pin() { | ||
| 538 | *shield_count += 1; | ||
| 539 | if *shield_count > 1 { | ||
| 540 | return Err(GroupError::MultipleShields); | ||
| 541 | } | ||
| 542 | } | ||
| 543 | } | ||
| 544 | Ok(()) | ||
| 545 | } | ||
| 546 | |||
| 547 | // Check each group | ||
| 548 | check_group(&self.g1, &mut shield_count)?; | ||
| 549 | check_group(&self.g2, &mut shield_count)?; | ||
| 550 | check_group(&self.g3, &mut shield_count)?; | ||
| 551 | check_group(&self.g4, &mut shield_count)?; | ||
| 552 | check_group(&self.g5, &mut shield_count)?; | ||
| 553 | check_group(&self.g6, &mut shield_count)?; | ||
| 554 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 555 | check_group(&self.g7, &mut shield_count)?; | ||
| 556 | #[cfg(tsc_v3)] | ||
| 557 | check_group(&self.g8, &mut shield_count)?; | ||
| 558 | |||
| 559 | Ok(()) | ||
| 560 | } | ||
| 561 | |||
| 562 | pub(super) fn make_channel_ios_mask(&self) -> u32 { | ||
| 563 | #[allow(unused_mut)] | ||
| 564 | let mut mask = self.g1.as_ref().map_or(0, |g| g.make_channel_ios_mask()) | ||
| 565 | | self.g2.as_ref().map_or(0, |g| g.make_channel_ios_mask()) | ||
| 566 | | self.g3.as_ref().map_or(0, |g| g.make_channel_ios_mask()) | ||
| 567 | | self.g4.as_ref().map_or(0, |g| g.make_channel_ios_mask()) | ||
| 568 | | self.g5.as_ref().map_or(0, |g| g.make_channel_ios_mask()) | ||
| 569 | | self.g6.as_ref().map_or(0, |g| g.make_channel_ios_mask()); | ||
| 570 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 571 | { | ||
| 572 | mask |= self.g7.as_ref().map_or(0, |g| g.make_channel_ios_mask()); | ||
| 573 | } | ||
| 574 | #[cfg(tsc_v3)] | ||
| 575 | { | ||
| 576 | mask |= self.g8.as_ref().map_or(0, |g| g.make_channel_ios_mask()); | ||
| 577 | } | ||
| 578 | mask | ||
| 579 | } | ||
| 580 | |||
| 581 | pub(super) fn make_shield_ios_mask(&self) -> u32 { | ||
| 582 | #[allow(unused_mut)] | ||
| 583 | let mut mask = self.g1.as_ref().map_or(0, |g| g.make_shield_ios_mask()) | ||
| 584 | | self.g2.as_ref().map_or(0, |g| g.make_shield_ios_mask()) | ||
| 585 | | self.g3.as_ref().map_or(0, |g| g.make_shield_ios_mask()) | ||
| 586 | | self.g4.as_ref().map_or(0, |g| g.make_shield_ios_mask()) | ||
| 587 | | self.g5.as_ref().map_or(0, |g| g.make_shield_ios_mask()) | ||
| 588 | | self.g6.as_ref().map_or(0, |g| g.make_shield_ios_mask()); | ||
| 589 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 590 | { | ||
| 591 | mask |= self.g7.as_ref().map_or(0, |g| g.make_shield_ios_mask()); | ||
| 592 | } | ||
| 593 | #[cfg(tsc_v3)] | ||
| 594 | { | ||
| 595 | mask |= self.g8.as_ref().map_or(0, |g| g.make_shield_ios_mask()); | ||
| 596 | } | ||
| 597 | mask | ||
| 598 | } | ||
| 599 | |||
| 600 | pub(super) fn make_sample_ios_mask(&self) -> u32 { | ||
| 601 | #[allow(unused_mut)] | ||
| 602 | let mut mask = self.g1.as_ref().map_or(0, |g| g.make_sample_ios_mask()) | ||
| 603 | | self.g2.as_ref().map_or(0, |g| g.make_sample_ios_mask()) | ||
| 604 | | self.g3.as_ref().map_or(0, |g| g.make_sample_ios_mask()) | ||
| 605 | | self.g4.as_ref().map_or(0, |g| g.make_sample_ios_mask()) | ||
| 606 | | self.g5.as_ref().map_or(0, |g| g.make_sample_ios_mask()) | ||
| 607 | | self.g6.as_ref().map_or(0, |g| g.make_sample_ios_mask()); | ||
| 608 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 609 | { | ||
| 610 | mask |= self.g7.as_ref().map_or(0, |g| g.make_sample_ios_mask()); | ||
| 611 | } | ||
| 612 | #[cfg(tsc_v3)] | ||
| 613 | { | ||
| 614 | mask |= self.g8.as_ref().map_or(0, |g| g.make_sample_ios_mask()); | ||
| 615 | } | ||
| 616 | mask | ||
| 617 | } | ||
| 618 | } | ||
| 619 | |||
| 620 | impl<'d, T: Instance> Default for PinGroups<'d, T> { | ||
| 621 | fn default() -> Self { | ||
| 622 | Self { | ||
| 623 | g1: None, | ||
| 624 | g2: None, | ||
| 625 | g3: None, | ||
| 626 | g4: None, | ||
| 627 | g5: None, | ||
| 628 | g6: None, | ||
| 629 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 630 | g7: None, | ||
| 631 | #[cfg(tsc_v3)] | ||
| 632 | g8: None, | ||
| 633 | } | ||
| 634 | } | ||
| 635 | } | ||
| 636 | |||
| 637 | pin_trait!(G1IO1Pin, Instance); | ||
| 638 | pin_trait!(G1IO2Pin, Instance); | ||
| 639 | pin_trait!(G1IO3Pin, Instance); | ||
| 640 | pin_trait!(G1IO4Pin, Instance); | ||
| 641 | |||
| 642 | pin_trait!(G2IO1Pin, Instance); | ||
| 643 | pin_trait!(G2IO2Pin, Instance); | ||
| 644 | pin_trait!(G2IO3Pin, Instance); | ||
| 645 | pin_trait!(G2IO4Pin, Instance); | ||
| 646 | |||
| 647 | pin_trait!(G3IO1Pin, Instance); | ||
| 648 | pin_trait!(G3IO2Pin, Instance); | ||
| 649 | pin_trait!(G3IO3Pin, Instance); | ||
| 650 | pin_trait!(G3IO4Pin, Instance); | ||
| 651 | |||
| 652 | pin_trait!(G4IO1Pin, Instance); | ||
| 653 | pin_trait!(G4IO2Pin, Instance); | ||
| 654 | pin_trait!(G4IO3Pin, Instance); | ||
| 655 | pin_trait!(G4IO4Pin, Instance); | ||
| 656 | |||
| 657 | pin_trait!(G5IO1Pin, Instance); | ||
| 658 | pin_trait!(G5IO2Pin, Instance); | ||
| 659 | pin_trait!(G5IO3Pin, Instance); | ||
| 660 | pin_trait!(G5IO4Pin, Instance); | ||
| 661 | |||
| 662 | pin_trait!(G6IO1Pin, Instance); | ||
| 663 | pin_trait!(G6IO2Pin, Instance); | ||
| 664 | pin_trait!(G6IO3Pin, Instance); | ||
| 665 | pin_trait!(G6IO4Pin, Instance); | ||
| 666 | |||
| 667 | pin_trait!(G7IO1Pin, Instance); | ||
| 668 | pin_trait!(G7IO2Pin, Instance); | ||
| 669 | pin_trait!(G7IO3Pin, Instance); | ||
| 670 | pin_trait!(G7IO4Pin, Instance); | ||
| 671 | |||
| 672 | pin_trait!(G8IO1Pin, Instance); | ||
| 673 | pin_trait!(G8IO2Pin, Instance); | ||
| 674 | pin_trait!(G8IO3Pin, Instance); | ||
| 675 | 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 000000000..58f9d9d2e --- /dev/null +++ b/embassy-stm32/src/tsc/tsc.rs | |||
| @@ -0,0 +1,456 @@ | |||
| 1 | use core::future::poll_fn; | ||
| 2 | use core::marker::PhantomData; | ||
| 3 | use core::ops::BitOr; | ||
| 4 | use core::task::Poll; | ||
| 5 | |||
| 6 | use embassy_hal_internal::{into_ref, PeripheralRef}; | ||
| 7 | |||
| 8 | use super::acquisition_banks::*; | ||
| 9 | use super::config::*; | ||
| 10 | use super::errors::*; | ||
| 11 | use super::pin_groups::*; | ||
| 12 | use super::tsc_io_pin::*; | ||
| 13 | use super::types::*; | ||
| 14 | use super::{Instance, InterruptHandler, TSC_NUM_GROUPS}; | ||
| 15 | use crate::interrupt::typelevel::Interrupt; | ||
| 16 | use crate::mode::{Async, Blocking, Mode as PeriMode}; | ||
| 17 | use crate::{interrupt, rcc, Peripheral}; | ||
| 18 | |||
| 19 | /// Internal structure holding masks for different types of TSC IOs. | ||
| 20 | /// | ||
| 21 | /// These masks are used during the initial configuration of the TSC peripheral | ||
| 22 | /// and for validating pin types during operations like creating acquisition banks. | ||
| 23 | struct TscIOMasks { | ||
| 24 | /// Mask representing all configured channel IOs | ||
| 25 | channel_ios: u32, | ||
| 26 | /// Mask representing all configured shield IOs | ||
| 27 | shield_ios: u32, | ||
| 28 | /// Mask representing all configured sampling IOs | ||
| 29 | sampling_ios: u32, | ||
| 30 | } | ||
| 31 | |||
| 32 | /// TSC driver | ||
| 33 | pub struct Tsc<'d, T: Instance, K: PeriMode> { | ||
| 34 | _peri: PeripheralRef<'d, T>, | ||
| 35 | _pin_groups: PinGroups<'d, T>, | ||
| 36 | state: State, | ||
| 37 | config: Config, | ||
| 38 | masks: TscIOMasks, | ||
| 39 | _kind: PhantomData<K>, | ||
| 40 | } | ||
| 41 | |||
| 42 | impl<'d, T: Instance, K: PeriMode> Tsc<'d, T, K> { | ||
| 43 | // Helper method to check if a pin is a channel pin | ||
| 44 | fn is_channel_pin(&self, pin: TscIOPin) -> bool { | ||
| 45 | (self.masks.channel_ios & pin) != 0 | ||
| 46 | } | ||
| 47 | |||
| 48 | /// Get the status of all groups involved in a TscAcquisitionBank | ||
| 49 | pub fn get_acquisition_bank_status(&self, bank: &TscAcquisitionBank) -> TscAcquisitionBankStatus { | ||
| 50 | let mut bank_status = TscAcquisitionBankStatus::default(); | ||
| 51 | for pin in bank.pins_iterator() { | ||
| 52 | let group = pin.group(); | ||
| 53 | let group_status = self.group_get_status(group); | ||
| 54 | let index: usize = group.into(); | ||
| 55 | bank_status.groups[index] = Some(group_status); | ||
| 56 | } | ||
| 57 | bank_status | ||
| 58 | } | ||
| 59 | |||
| 60 | /// Get the values for all channels involved in a TscAcquisitionBank | ||
| 61 | pub fn get_acquisition_bank_values(&self, bank: &TscAcquisitionBank) -> TscAcquisitionBankReadings { | ||
| 62 | let mut bank_readings = TscAcquisitionBankReadings::default(); | ||
| 63 | for pin in bank.pins_iterator() { | ||
| 64 | let group = pin.group(); | ||
| 65 | let value = self.group_get_value(group); | ||
| 66 | let reading = TscChannelReading { | ||
| 67 | sensor_value: value, | ||
| 68 | tsc_pin: pin, | ||
| 69 | }; | ||
| 70 | let index: usize = group.into(); | ||
| 71 | bank_readings.groups[index] = Some(reading); | ||
| 72 | } | ||
| 73 | bank_readings | ||
| 74 | } | ||
| 75 | |||
| 76 | /// Creates a new TSC acquisition bank from the provided pin configuration. | ||
| 77 | /// | ||
| 78 | /// This method creates a `TscAcquisitionBank` that can be used for efficient, | ||
| 79 | /// repeated TSC acquisitions. It automatically generates the appropriate mask | ||
| 80 | /// for the provided pins. | ||
| 81 | /// | ||
| 82 | /// # Note on TSC Hardware Limitation | ||
| 83 | /// | ||
| 84 | /// The TSC hardware can only read one channel pin from each TSC group per acquisition. | ||
| 85 | /// | ||
| 86 | /// # Arguments | ||
| 87 | /// * `acquisition_bank_pins` - The pin configuration for the acquisition bank. | ||
| 88 | /// | ||
| 89 | /// # Returns | ||
| 90 | /// A new `TscAcquisitionBank` instance. | ||
| 91 | /// | ||
| 92 | /// # Example | ||
| 93 | /// | ||
| 94 | /// ``` | ||
| 95 | /// let tsc = // ... initialize TSC | ||
| 96 | /// let tsc_sensor1: TscIOPinWithRole<G1, tsc_pin_roles::Channel> = ...; | ||
| 97 | /// let tsc_sensor2: TscIOPinWithRole<G2, tsc_pin_roles::Channel> = ...; | ||
| 98 | /// | ||
| 99 | /// let bank = tsc.create_acquisition_bank(TscAcquisitionBankPins { | ||
| 100 | /// g1_pin: Some(tsc_sensor1), | ||
| 101 | /// g2_pin: Some(tsc_sensor2), | ||
| 102 | /// ..Default::default() | ||
| 103 | /// }); | ||
| 104 | /// | ||
| 105 | /// // Use the bank for acquisitions | ||
| 106 | /// tsc.set_active_channels_bank(&bank); | ||
| 107 | /// tsc.start(); | ||
| 108 | /// // ... perform acquisition ... | ||
| 109 | /// ``` | ||
| 110 | pub fn create_acquisition_bank(&self, acquisition_bank_pins: TscAcquisitionBankPins) -> TscAcquisitionBank { | ||
| 111 | let bank_mask = acquisition_bank_pins.iter().fold(0u32, BitOr::bitor); | ||
| 112 | |||
| 113 | TscAcquisitionBank { | ||
| 114 | pins: acquisition_bank_pins, | ||
| 115 | mask: bank_mask, | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | fn make_channels_mask<Itt>(&self, channels: Itt) -> Result<u32, AcquisitionBankError> | ||
| 120 | where | ||
| 121 | Itt: IntoIterator<Item = TscIOPin>, | ||
| 122 | { | ||
| 123 | let mut group_mask = 0u32; | ||
| 124 | let mut channel_mask = 0u32; | ||
| 125 | |||
| 126 | for channel in channels { | ||
| 127 | if !self.is_channel_pin(channel) { | ||
| 128 | return Err(AcquisitionBankError::InvalidChannelPin); | ||
| 129 | } | ||
| 130 | |||
| 131 | let group = channel.group(); | ||
| 132 | let group_bit: u32 = 1 << Into::<usize>::into(group); | ||
| 133 | if group_mask & group_bit != 0 { | ||
| 134 | return Err(AcquisitionBankError::MultipleChannelsPerGroup); | ||
| 135 | } | ||
| 136 | |||
| 137 | group_mask |= group_bit; | ||
| 138 | channel_mask |= channel; | ||
| 139 | } | ||
| 140 | |||
| 141 | Ok(channel_mask) | ||
| 142 | } | ||
| 143 | |||
| 144 | /// Sets the active channels for the next TSC acquisition. | ||
| 145 | /// | ||
| 146 | /// This is a low-level method that directly sets the channel mask. For most use cases, | ||
| 147 | /// consider using `set_active_channels_bank` with a `TscAcquisitionBank` instead, which | ||
| 148 | /// provides a higher-level interface and additional safety checks. | ||
| 149 | /// | ||
| 150 | /// This method configures which sensor channels will be read during the next | ||
| 151 | /// touch sensing acquisition cycle. It should be called before starting a new | ||
| 152 | /// acquisition with the start() method. | ||
| 153 | /// | ||
| 154 | /// # Arguments | ||
| 155 | /// * `mask` - A 32-bit mask where each bit represents a channel. Set bits indicate | ||
| 156 | /// active channels. | ||
| 157 | /// | ||
| 158 | /// # Note | ||
| 159 | /// Only one pin from each TSC group can be read for each acquisition. This method | ||
| 160 | /// does not perform checks to ensure this limitation is met. Incorrect masks may | ||
| 161 | /// lead to unexpected behavior. | ||
| 162 | /// | ||
| 163 | /// # Safety | ||
| 164 | /// This method doesn't perform extensive checks on the provided mask. Ensure that | ||
| 165 | /// the mask is valid and adheres to hardware limitations to avoid undefined behavior. | ||
| 166 | pub fn set_active_channels_mask(&mut self, mask: u32) { | ||
| 167 | T::regs().ioccr().write(|w| w.0 = mask | self.masks.shield_ios); | ||
| 168 | } | ||
| 169 | |||
| 170 | /// Convenience method for setting active channels directly from a slice of TscIOPin. | ||
| 171 | /// This method performs safety checks but is less efficient for repeated use. | ||
| 172 | pub fn set_active_channels(&mut self, channels: &[TscIOPin]) -> Result<(), AcquisitionBankError> { | ||
| 173 | let mask = self.make_channels_mask(channels.iter().cloned())?; | ||
| 174 | self.set_active_channels_mask(mask); | ||
| 175 | Ok(()) | ||
| 176 | } | ||
| 177 | |||
| 178 | /// Sets the active channels for the next TSC acquisition using a pre-configured acquisition bank. | ||
| 179 | /// | ||
| 180 | /// This method efficiently configures the TSC peripheral to read the channels specified | ||
| 181 | /// in the provided `TscAcquisitionBank`. It's the recommended way to set up | ||
| 182 | /// channel configurations for acquisition, especially when using the same set of channels repeatedly. | ||
| 183 | /// | ||
| 184 | /// # Arguments | ||
| 185 | /// | ||
| 186 | /// * `bank` - A reference to a `TscAcquisitionBank` containing the pre-configured | ||
| 187 | /// TSC channel mask. | ||
| 188 | /// | ||
| 189 | /// # Example | ||
| 190 | /// | ||
| 191 | /// ``` | ||
| 192 | /// let tsc_sensor1: TscIOPinWithRole<G1, Channel> = ...; | ||
| 193 | /// let tsc_sensor2: TscIOPinWithRole<G5, Channel> = ...; | ||
| 194 | /// let mut touch_controller: Tsc<'_, TSC, Async> = ...; | ||
| 195 | /// let bank = touch_controller.create_acquisition_bank(TscAcquisitionBankPins { | ||
| 196 | /// g1_pin: Some(tsc_sensor1), | ||
| 197 | /// g2_pin: Some(tsc_sensor2), | ||
| 198 | /// ..Default::default() | ||
| 199 | /// }); | ||
| 200 | /// | ||
| 201 | /// touch_controller.set_active_channels_bank(&bank); | ||
| 202 | /// touch_controller.start(); | ||
| 203 | /// // ... perform acquisition ... | ||
| 204 | /// ``` | ||
| 205 | /// | ||
| 206 | /// This method should be called before starting a new acquisition with the `start()` method. | ||
| 207 | pub fn set_active_channels_bank(&mut self, bank: &TscAcquisitionBank) { | ||
| 208 | self.set_active_channels_mask(bank.mask) | ||
| 209 | } | ||
| 210 | |||
| 211 | fn extract_groups(io_mask: u32) -> u32 { | ||
| 212 | let mut groups: u32 = 0; | ||
| 213 | for idx in 0..TSC_NUM_GROUPS { | ||
| 214 | if io_mask & (0x0F << (idx * 4)) != 0 { | ||
| 215 | groups |= 1 << idx | ||
| 216 | } | ||
| 217 | } | ||
| 218 | groups | ||
| 219 | } | ||
| 220 | |||
| 221 | fn new_inner( | ||
| 222 | peri: impl Peripheral<P = T> + 'd, | ||
| 223 | pin_groups: PinGroups<'d, T>, | ||
| 224 | config: Config, | ||
| 225 | ) -> Result<Self, GroupError> { | ||
| 226 | into_ref!(peri); | ||
| 227 | |||
| 228 | pin_groups.check()?; | ||
| 229 | |||
| 230 | let masks = TscIOMasks { | ||
| 231 | channel_ios: pin_groups.make_channel_ios_mask(), | ||
| 232 | shield_ios: pin_groups.make_shield_ios_mask(), | ||
| 233 | sampling_ios: pin_groups.make_sample_ios_mask(), | ||
| 234 | }; | ||
| 235 | |||
| 236 | rcc::enable_and_reset::<T>(); | ||
| 237 | |||
| 238 | T::regs().cr().modify(|w| { | ||
| 239 | w.set_tsce(true); | ||
| 240 | w.set_ctph(config.ct_pulse_high_length.into()); | ||
| 241 | w.set_ctpl(config.ct_pulse_low_length.into()); | ||
| 242 | w.set_sse(config.spread_spectrum); | ||
| 243 | // Prevent invalid configuration for pulse generator prescaler | ||
| 244 | if config.ct_pulse_low_length == ChargeTransferPulseCycle::_1 | ||
| 245 | && (config.pulse_generator_prescaler == PGPrescalerDivider::_1 | ||
| 246 | || config.pulse_generator_prescaler == PGPrescalerDivider::_2) | ||
| 247 | { | ||
| 248 | w.set_pgpsc(PGPrescalerDivider::_4.into()); | ||
| 249 | } else if config.ct_pulse_low_length == ChargeTransferPulseCycle::_2 | ||
| 250 | && config.pulse_generator_prescaler == PGPrescalerDivider::_1 | ||
| 251 | { | ||
| 252 | w.set_pgpsc(PGPrescalerDivider::_2.into()); | ||
| 253 | } else { | ||
| 254 | w.set_pgpsc(config.pulse_generator_prescaler.into()); | ||
| 255 | } | ||
| 256 | w.set_ssd(config.spread_spectrum_deviation.into()); | ||
| 257 | w.set_sspsc(config.spread_spectrum_prescaler); | ||
| 258 | |||
| 259 | w.set_mcv(config.max_count_value.into()); | ||
| 260 | w.set_syncpol(config.synchro_pin_polarity); | ||
| 261 | w.set_am(config.acquisition_mode); | ||
| 262 | }); | ||
| 263 | |||
| 264 | // Set IO configuration | ||
| 265 | // Disable Schmitt trigger hysteresis on all used TSC IOs | ||
| 266 | T::regs() | ||
| 267 | .iohcr() | ||
| 268 | .write(|w| w.0 = !(masks.channel_ios | masks.shield_ios | masks.sampling_ios)); | ||
| 269 | |||
| 270 | // Set channel and shield IOs | ||
| 271 | T::regs().ioccr().write(|w| w.0 = masks.channel_ios | masks.shield_ios); | ||
| 272 | |||
| 273 | // Set sampling IOs | ||
| 274 | T::regs().ioscr().write(|w| w.0 = masks.sampling_ios); | ||
| 275 | |||
| 276 | // Set the groups to be acquired | ||
| 277 | // Lower bits of `iogcsr` are for enabling groups, while the higher bits are for reading | ||
| 278 | // status of acquisiton for a group, see method `Tsc::group_get_status`. | ||
| 279 | T::regs() | ||
| 280 | .iogcsr() | ||
| 281 | .write(|w| w.0 = Self::extract_groups(masks.channel_ios)); | ||
| 282 | |||
| 283 | // Disable interrupts | ||
| 284 | T::regs().ier().modify(|w| { | ||
| 285 | w.set_eoaie(false); | ||
| 286 | w.set_mceie(false); | ||
| 287 | }); | ||
| 288 | |||
| 289 | // Clear flags | ||
| 290 | T::regs().icr().modify(|w| { | ||
| 291 | w.set_eoaic(true); | ||
| 292 | w.set_mceic(true); | ||
| 293 | }); | ||
| 294 | |||
| 295 | unsafe { | ||
| 296 | T::Interrupt::enable(); | ||
| 297 | } | ||
| 298 | |||
| 299 | Ok(Self { | ||
| 300 | _peri: peri, | ||
| 301 | _pin_groups: pin_groups, | ||
| 302 | state: State::Ready, | ||
| 303 | config, | ||
| 304 | masks, | ||
| 305 | _kind: PhantomData, | ||
| 306 | }) | ||
| 307 | } | ||
| 308 | |||
| 309 | /// Start charge transfer acquisition | ||
| 310 | pub fn start(&mut self) { | ||
| 311 | self.state = State::Busy; | ||
| 312 | |||
| 313 | // Disable interrupts | ||
| 314 | T::regs().ier().modify(|w| { | ||
| 315 | w.set_eoaie(false); | ||
| 316 | w.set_mceie(false); | ||
| 317 | }); | ||
| 318 | |||
| 319 | // Clear flags | ||
| 320 | T::regs().icr().modify(|w| { | ||
| 321 | w.set_eoaic(true); | ||
| 322 | w.set_mceic(true); | ||
| 323 | }); | ||
| 324 | |||
| 325 | // Set the touch sensing IOs not acquired to the default mode | ||
| 326 | T::regs().cr().modify(|w| { | ||
| 327 | w.set_iodef(self.config.io_default_mode); | ||
| 328 | }); | ||
| 329 | |||
| 330 | // Start the acquisition | ||
| 331 | T::regs().cr().modify(|w| { | ||
| 332 | w.set_start(true); | ||
| 333 | }); | ||
| 334 | } | ||
| 335 | |||
| 336 | /// Stop charge transfer acquisition | ||
| 337 | pub fn stop(&mut self) { | ||
| 338 | T::regs().cr().modify(|w| { | ||
| 339 | w.set_start(false); | ||
| 340 | }); | ||
| 341 | |||
| 342 | // Set the touch sensing IOs in low power mode | ||
| 343 | T::regs().cr().modify(|w| { | ||
| 344 | w.set_iodef(false); | ||
| 345 | }); | ||
| 346 | |||
| 347 | // Clear flags | ||
| 348 | T::regs().icr().modify(|w| { | ||
| 349 | w.set_eoaic(true); | ||
| 350 | w.set_mceic(true); | ||
| 351 | }); | ||
| 352 | |||
| 353 | self.state = State::Ready; | ||
| 354 | } | ||
| 355 | |||
| 356 | /// Get current state of acquisition | ||
| 357 | pub fn get_state(&mut self) -> State { | ||
| 358 | if self.state == State::Busy && T::regs().isr().read().eoaf() { | ||
| 359 | if T::regs().isr().read().mcef() { | ||
| 360 | self.state = State::Error | ||
| 361 | } else { | ||
| 362 | self.state = State::Ready | ||
| 363 | } | ||
| 364 | } | ||
| 365 | self.state | ||
| 366 | } | ||
| 367 | |||
| 368 | /// Get the individual group status to check acquisition complete | ||
| 369 | pub fn group_get_status(&self, index: Group) -> GroupStatus { | ||
| 370 | // Status bits are set by hardware when the acquisition on the corresponding | ||
| 371 | // enabled analog IO group is complete, cleared when new acquisition is started | ||
| 372 | let status = match index { | ||
| 373 | Group::One => T::regs().iogcsr().read().g1s(), | ||
| 374 | Group::Two => T::regs().iogcsr().read().g2s(), | ||
| 375 | Group::Three => T::regs().iogcsr().read().g3s(), | ||
| 376 | Group::Four => T::regs().iogcsr().read().g4s(), | ||
| 377 | Group::Five => T::regs().iogcsr().read().g5s(), | ||
| 378 | Group::Six => T::regs().iogcsr().read().g6s(), | ||
| 379 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 380 | Group::Seven => T::regs().iogcsr().read().g7s(), | ||
| 381 | #[cfg(tsc_v3)] | ||
| 382 | Group::Eight => T::regs().iogcsr().read().g8s(), | ||
| 383 | }; | ||
| 384 | match status { | ||
| 385 | true => GroupStatus::Complete, | ||
| 386 | false => GroupStatus::Ongoing, | ||
| 387 | } | ||
| 388 | } | ||
| 389 | |||
| 390 | /// Get the count for the acquisiton, valid once group status is set | ||
| 391 | pub fn group_get_value(&self, index: Group) -> u16 { | ||
| 392 | T::regs().iogcr(index.into()).read().cnt() | ||
| 393 | } | ||
| 394 | |||
| 395 | /// Discharge the IOs for subsequent acquisition | ||
| 396 | pub fn discharge_io(&mut self, status: bool) { | ||
| 397 | // Set the touch sensing IOs in low power mode | ||
| 398 | T::regs().cr().modify(|w| { | ||
| 399 | w.set_iodef(!status); | ||
| 400 | }); | ||
| 401 | } | ||
| 402 | } | ||
| 403 | |||
| 404 | impl<'d, T: Instance, K: PeriMode> Drop for Tsc<'d, T, K> { | ||
| 405 | fn drop(&mut self) { | ||
| 406 | rcc::disable::<T>(); | ||
| 407 | } | ||
| 408 | } | ||
| 409 | |||
| 410 | impl<'d, T: Instance> Tsc<'d, T, Async> { | ||
| 411 | /// Create a Tsc instance that can be awaited for completion | ||
| 412 | pub fn new_async( | ||
| 413 | peri: impl Peripheral<P = T> + 'd, | ||
| 414 | pin_groups: PinGroups<'d, T>, | ||
| 415 | config: Config, | ||
| 416 | _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd, | ||
| 417 | ) -> Result<Self, GroupError> { | ||
| 418 | Self::new_inner(peri, pin_groups, config) | ||
| 419 | } | ||
| 420 | |||
| 421 | /// Asyncronously wait for the end of an acquisition | ||
| 422 | pub async fn pend_for_acquisition(&mut self) { | ||
| 423 | poll_fn(|cx| match self.get_state() { | ||
| 424 | State::Busy => { | ||
| 425 | T::waker().register(cx.waker()); | ||
| 426 | T::regs().ier().write(|w| w.set_eoaie(true)); | ||
| 427 | if self.get_state() != State::Busy { | ||
| 428 | T::regs().ier().write(|w| w.set_eoaie(false)); | ||
| 429 | return Poll::Ready(()); | ||
| 430 | } | ||
| 431 | Poll::Pending | ||
| 432 | } | ||
| 433 | _ => { | ||
| 434 | T::regs().ier().write(|w| w.set_eoaie(false)); | ||
| 435 | Poll::Ready(()) | ||
| 436 | } | ||
| 437 | }) | ||
| 438 | .await; | ||
| 439 | } | ||
| 440 | } | ||
| 441 | |||
| 442 | impl<'d, T: Instance> Tsc<'d, T, Blocking> { | ||
| 443 | /// Create a Tsc instance that must be polled for completion | ||
| 444 | pub fn new_blocking( | ||
| 445 | peri: impl Peripheral<P = T> + 'd, | ||
| 446 | pin_groups: PinGroups<'d, T>, | ||
| 447 | config: Config, | ||
| 448 | ) -> Result<Self, GroupError> { | ||
| 449 | Self::new_inner(peri, pin_groups, config) | ||
| 450 | } | ||
| 451 | |||
| 452 | /// Wait for end of acquisition | ||
| 453 | pub fn poll_for_acquisition(&mut self) { | ||
| 454 | while self.get_state() == State::Busy {} | ||
| 455 | } | ||
| 456 | } | ||
diff --git a/embassy-stm32/src/tsc/enums.rs b/embassy-stm32/src/tsc/tsc_io_pin.rs index 0d34a43ec..38b27d0b3 100644 --- a/embassy-stm32/src/tsc/enums.rs +++ b/embassy-stm32/src/tsc/tsc_io_pin.rs | |||
| @@ -1,7 +1,13 @@ | |||
| 1 | use core::ops::BitOr; | 1 | use core::marker::PhantomData; |
| 2 | use core::ops::{BitAnd, BitOr, BitOrAssign}; | ||
| 3 | |||
| 4 | use super::tsc_pin_roles; | ||
| 5 | use super::types::Group; | ||
| 2 | 6 | ||
| 3 | /// Pin defines | 7 | /// Pin defines |
| 4 | #[allow(missing_docs)] | 8 | #[allow(missing_docs)] |
| 9 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 10 | #[derive(PartialEq, Clone, Copy, Debug)] | ||
| 5 | pub enum TscIOPin { | 11 | pub enum TscIOPin { |
| 6 | Group1Io1, | 12 | Group1Io1, |
| 7 | Group1Io2, | 13 | Group1Io2, |
| @@ -45,6 +51,53 @@ pub enum TscIOPin { | |||
| 45 | Group8Io4, | 51 | Group8Io4, |
| 46 | } | 52 | } |
| 47 | 53 | ||
| 54 | /// Represents a TSC I/O pin with associated group and role information. | ||
| 55 | /// | ||
| 56 | /// This type combines a `TscIOPin` with phantom type parameters to statically | ||
| 57 | /// encode the pin's group and role. This allows for type-safe operations | ||
| 58 | /// on TSC pins within their specific contexts. | ||
| 59 | /// | ||
| 60 | /// - `Group`: A type parameter representing the TSC group (e.g., `G1`, `G2`). | ||
| 61 | /// - `Role`: A type parameter representing the pin's role (e.g., `Channel`, `Sample`). | ||
| 62 | #[derive(Clone, Copy, Debug)] | ||
| 63 | pub struct TscIOPinWithRole<Group, Role: tsc_pin_roles::Role> { | ||
| 64 | /// The underlying TSC I/O pin. | ||
| 65 | pub pin: TscIOPin, | ||
| 66 | pub(super) phantom: PhantomData<(Group, Role)>, | ||
| 67 | } | ||
| 68 | |||
| 69 | impl<G, R: tsc_pin_roles::Role> TscIOPinWithRole<G, R> { | ||
| 70 | pub(super) fn get_pin(wrapped_pin: TscIOPinWithRole<G, R>) -> TscIOPin { | ||
| 71 | wrapped_pin.pin | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | impl TscIOPin { | ||
| 76 | /// Maps this TscIOPin to the Group it belongs to. | ||
| 77 | /// | ||
| 78 | /// This method provides a convenient way to determine which Group | ||
| 79 | /// a specific TSC I/O pin is associated with. | ||
| 80 | pub const fn group(&self) -> Group { | ||
| 81 | match self { | ||
| 82 | TscIOPin::Group1Io1 | TscIOPin::Group1Io2 | TscIOPin::Group1Io3 | TscIOPin::Group1Io4 => Group::One, | ||
| 83 | TscIOPin::Group2Io1 | TscIOPin::Group2Io2 | TscIOPin::Group2Io3 | TscIOPin::Group2Io4 => Group::Two, | ||
| 84 | TscIOPin::Group3Io1 | TscIOPin::Group3Io2 | TscIOPin::Group3Io3 | TscIOPin::Group3Io4 => Group::Three, | ||
| 85 | TscIOPin::Group4Io1 | TscIOPin::Group4Io2 | TscIOPin::Group4Io3 | TscIOPin::Group4Io4 => Group::Four, | ||
| 86 | TscIOPin::Group5Io1 | TscIOPin::Group5Io2 | TscIOPin::Group5Io3 | TscIOPin::Group5Io4 => Group::Five, | ||
| 87 | TscIOPin::Group6Io1 | TscIOPin::Group6Io2 | TscIOPin::Group6Io3 | TscIOPin::Group6Io4 => Group::Six, | ||
| 88 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 89 | TscIOPin::Group7Io1 | TscIOPin::Group7Io2 | TscIOPin::Group7Io3 | TscIOPin::Group7Io4 => Group::Seven, | ||
| 90 | #[cfg(tsc_v3)] | ||
| 91 | TscIOPin::Group8Io1 | TscIOPin::Group8Io2 | TscIOPin::Group8Io3 | TscIOPin::Group8Io4 => Group::Eight, | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | /// Returns the `Group` associated with the given `TscIOPin`. | ||
| 96 | pub fn get_group(pin: TscIOPin) -> Group { | ||
| 97 | pin.group() | ||
| 98 | } | ||
| 99 | } | ||
| 100 | |||
| 48 | impl BitOr<TscIOPin> for u32 { | 101 | impl BitOr<TscIOPin> for u32 { |
| 49 | type Output = u32; | 102 | type Output = u32; |
| 50 | fn bitor(self, rhs: TscIOPin) -> Self::Output { | 103 | fn bitor(self, rhs: TscIOPin) -> Self::Output { |
| @@ -70,8 +123,31 @@ impl BitOr for TscIOPin { | |||
| 70 | } | 123 | } |
| 71 | } | 124 | } |
| 72 | 125 | ||
| 73 | impl Into<u32> for TscIOPin { | 126 | impl BitOrAssign<TscIOPin> for u32 { |
| 74 | fn into(self) -> u32 { | 127 | fn bitor_assign(&mut self, rhs: TscIOPin) { |
| 128 | let rhs: u32 = rhs.into(); | ||
| 129 | *self |= rhs; | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | impl BitAnd<TscIOPin> for u32 { | ||
| 134 | type Output = u32; | ||
| 135 | fn bitand(self, rhs: TscIOPin) -> Self::Output { | ||
| 136 | let rhs: u32 = rhs.into(); | ||
| 137 | self & rhs | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | impl BitAnd<u32> for TscIOPin { | ||
| 142 | type Output = u32; | ||
| 143 | fn bitand(self, rhs: u32) -> Self::Output { | ||
| 144 | let val: u32 = self.into(); | ||
| 145 | val & rhs | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | impl TscIOPin { | ||
| 150 | const fn to_u32(self) -> u32 { | ||
| 75 | match self { | 151 | match self { |
| 76 | TscIOPin::Group1Io1 => 0x00000001, | 152 | TscIOPin::Group1Io1 => 0x00000001, |
| 77 | TscIOPin::Group1Io2 => 0x00000002, | 153 | TscIOPin::Group1Io2 => 0x00000002, |
| @@ -117,122 +193,8 @@ impl Into<u32> for TscIOPin { | |||
| 117 | } | 193 | } |
| 118 | } | 194 | } |
| 119 | 195 | ||
| 120 | /// Spread Spectrum Deviation | 196 | impl Into<u32> for TscIOPin { |
| 121 | #[derive(Copy, Clone)] | 197 | fn into(self) -> u32 { |
| 122 | pub struct SSDeviation(u8); | 198 | self.to_u32() |
| 123 | impl SSDeviation { | ||
| 124 | /// Create new deviation value, acceptable inputs are 1-128 | ||
| 125 | pub fn new(val: u8) -> Result<Self, ()> { | ||
| 126 | if val == 0 || val > 128 { | ||
| 127 | return Err(()); | ||
| 128 | } | ||
| 129 | Ok(Self(val - 1)) | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | impl Into<u8> for SSDeviation { | ||
| 134 | fn into(self) -> u8 { | ||
| 135 | self.0 | ||
| 136 | } | ||
| 137 | } | ||
| 138 | |||
| 139 | /// Charge transfer pulse cycles | ||
| 140 | #[allow(missing_docs)] | ||
| 141 | #[derive(Copy, Clone, PartialEq)] | ||
| 142 | pub enum ChargeTransferPulseCycle { | ||
| 143 | _1, | ||
| 144 | _2, | ||
| 145 | _3, | ||
| 146 | _4, | ||
| 147 | _5, | ||
| 148 | _6, | ||
| 149 | _7, | ||
| 150 | _8, | ||
| 151 | _9, | ||
| 152 | _10, | ||
| 153 | _11, | ||
| 154 | _12, | ||
| 155 | _13, | ||
| 156 | _14, | ||
| 157 | _15, | ||
| 158 | _16, | ||
| 159 | } | ||
| 160 | |||
| 161 | impl Into<u8> for ChargeTransferPulseCycle { | ||
| 162 | fn into(self) -> u8 { | ||
| 163 | match self { | ||
| 164 | ChargeTransferPulseCycle::_1 => 0, | ||
| 165 | ChargeTransferPulseCycle::_2 => 1, | ||
| 166 | ChargeTransferPulseCycle::_3 => 2, | ||
| 167 | ChargeTransferPulseCycle::_4 => 3, | ||
| 168 | ChargeTransferPulseCycle::_5 => 4, | ||
| 169 | ChargeTransferPulseCycle::_6 => 5, | ||
| 170 | ChargeTransferPulseCycle::_7 => 6, | ||
| 171 | ChargeTransferPulseCycle::_8 => 7, | ||
| 172 | ChargeTransferPulseCycle::_9 => 8, | ||
| 173 | ChargeTransferPulseCycle::_10 => 9, | ||
| 174 | ChargeTransferPulseCycle::_11 => 10, | ||
| 175 | ChargeTransferPulseCycle::_12 => 11, | ||
| 176 | ChargeTransferPulseCycle::_13 => 12, | ||
| 177 | ChargeTransferPulseCycle::_14 => 13, | ||
| 178 | ChargeTransferPulseCycle::_15 => 14, | ||
| 179 | ChargeTransferPulseCycle::_16 => 15, | ||
| 180 | } | ||
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | /// Prescaler divider | ||
| 185 | #[allow(missing_docs)] | ||
| 186 | #[derive(Copy, Clone, PartialEq)] | ||
| 187 | pub enum PGPrescalerDivider { | ||
| 188 | _1, | ||
| 189 | _2, | ||
| 190 | _4, | ||
| 191 | _8, | ||
| 192 | _16, | ||
| 193 | _32, | ||
| 194 | _64, | ||
| 195 | _128, | ||
| 196 | } | ||
| 197 | |||
| 198 | impl Into<u8> for PGPrescalerDivider { | ||
| 199 | fn into(self) -> u8 { | ||
| 200 | match self { | ||
| 201 | PGPrescalerDivider::_1 => 0, | ||
| 202 | PGPrescalerDivider::_2 => 1, | ||
| 203 | PGPrescalerDivider::_4 => 2, | ||
| 204 | PGPrescalerDivider::_8 => 3, | ||
| 205 | PGPrescalerDivider::_16 => 4, | ||
| 206 | PGPrescalerDivider::_32 => 5, | ||
| 207 | PGPrescalerDivider::_64 => 6, | ||
| 208 | PGPrescalerDivider::_128 => 7, | ||
| 209 | } | ||
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | /// Max count | ||
| 214 | #[allow(missing_docs)] | ||
| 215 | #[derive(Copy, Clone)] | ||
| 216 | pub enum MaxCount { | ||
| 217 | _255, | ||
| 218 | _511, | ||
| 219 | _1023, | ||
| 220 | _2047, | ||
| 221 | _4095, | ||
| 222 | _8191, | ||
| 223 | _16383, | ||
| 224 | } | ||
| 225 | |||
| 226 | impl Into<u8> for MaxCount { | ||
| 227 | fn into(self) -> u8 { | ||
| 228 | match self { | ||
| 229 | MaxCount::_255 => 0, | ||
| 230 | MaxCount::_511 => 1, | ||
| 231 | MaxCount::_1023 => 2, | ||
| 232 | MaxCount::_2047 => 3, | ||
| 233 | MaxCount::_4095 => 4, | ||
| 234 | MaxCount::_8191 => 5, | ||
| 235 | MaxCount::_16383 => 6, | ||
| 236 | } | ||
| 237 | } | 199 | } |
| 238 | } | 200 | } |
diff --git a/embassy-stm32/src/tsc/types.rs b/embassy-stm32/src/tsc/types.rs new file mode 100644 index 000000000..0e8fa7f28 --- /dev/null +++ b/embassy-stm32/src/tsc/types.rs | |||
| @@ -0,0 +1,93 @@ | |||
| 1 | /// Peripheral state | ||
| 2 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 3 | #[derive(PartialEq, Clone, Copy)] | ||
| 4 | pub enum State { | ||
| 5 | /// Peripheral is being setup or reconfigured | ||
| 6 | Reset, | ||
| 7 | /// Ready to start acquisition | ||
| 8 | Ready, | ||
| 9 | /// In process of sensor acquisition | ||
| 10 | Busy, | ||
| 11 | /// Error occured during acquisition | ||
| 12 | Error, | ||
| 13 | } | ||
| 14 | |||
| 15 | /// Individual group status checked after acquisition reported as complete | ||
| 16 | /// For groups with multiple channel pins, may take longer because acquisitions | ||
| 17 | /// are done sequentially. Check this status before pulling count for each | ||
| 18 | /// sampled channel | ||
| 19 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 20 | #[derive(PartialEq, Clone, Copy)] | ||
| 21 | pub enum GroupStatus { | ||
| 22 | /// Acquisition for channel still in progress | ||
| 23 | Ongoing, | ||
| 24 | /// Acquisition either not started or complete | ||
| 25 | Complete, | ||
| 26 | } | ||
| 27 | |||
| 28 | /// Group identifier used to interrogate status | ||
| 29 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 30 | #[allow(missing_docs)] | ||
| 31 | #[derive(PartialEq, Clone, Copy)] | ||
| 32 | pub enum Group { | ||
| 33 | One, | ||
| 34 | Two, | ||
| 35 | Three, | ||
| 36 | Four, | ||
| 37 | Five, | ||
| 38 | Six, | ||
| 39 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 40 | Seven, | ||
| 41 | #[cfg(tsc_v3)] | ||
| 42 | Eight, | ||
| 43 | } | ||
| 44 | |||
| 45 | impl Into<usize> for Group { | ||
| 46 | fn into(self) -> usize { | ||
| 47 | match self { | ||
| 48 | Group::One => 0, | ||
| 49 | Group::Two => 1, | ||
| 50 | Group::Three => 2, | ||
| 51 | Group::Four => 3, | ||
| 52 | Group::Five => 4, | ||
| 53 | Group::Six => 5, | ||
| 54 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 55 | Group::Seven => 6, | ||
| 56 | #[cfg(tsc_v3)] | ||
| 57 | Group::Eight => 7, | ||
| 58 | } | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | /// Error returned when attempting to create a Group from an invalid numeric value. | ||
| 63 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 64 | pub struct InvalidGroupError { | ||
| 65 | invalid_value: usize, | ||
| 66 | } | ||
| 67 | |||
| 68 | impl InvalidGroupError { | ||
| 69 | #[allow(missing_docs)] | ||
| 70 | pub fn new(value: usize) -> Self { | ||
| 71 | Self { invalid_value: value } | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | impl TryFrom<usize> for Group { | ||
| 76 | type Error = InvalidGroupError; | ||
| 77 | |||
| 78 | fn try_from(value: usize) -> Result<Self, Self::Error> { | ||
| 79 | match value { | ||
| 80 | 0 => Ok(Group::One), | ||
| 81 | 1 => Ok(Group::Two), | ||
| 82 | 2 => Ok(Group::Three), | ||
| 83 | 3 => Ok(Group::Four), | ||
| 84 | 4 => Ok(Group::Five), | ||
| 85 | 5 => Ok(Group::Six), | ||
| 86 | #[cfg(any(tsc_v2, tsc_v3))] | ||
| 87 | 6 => Ok(Group::Two), | ||
| 88 | #[cfg(tsc_v3)] | ||
| 89 | 7 => Ok(Group::Two), | ||
| 90 | n => Err(InvalidGroupError::new(n)), | ||
| 91 | } | ||
| 92 | } | ||
| 93 | } | ||
diff --git a/examples/stm32f3/README.md b/examples/stm32f3/README.md new file mode 100644 index 000000000..0a85c4858 --- /dev/null +++ b/examples/stm32f3/README.md | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | # Examples for STM32F3 family | ||
| 2 | Run individual examples with | ||
| 3 | ``` | ||
| 4 | cargo run --bin <module-name> | ||
| 5 | ``` | ||
| 6 | for example | ||
| 7 | ``` | ||
| 8 | cargo run --bin blinky | ||
| 9 | ``` | ||
| 10 | |||
| 11 | ## Checklist before running examples | ||
| 12 | 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. | ||
| 13 | |||
| 14 | * [ ] 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) | ||
| 15 | * [ ] 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. | ||
| 16 | * [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. | ||
| 17 | * [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic | ||
| 18 | |||
| 19 | If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: | ||
| 20 | |||
| 21 | * Which example you are trying to run | ||
| 22 | * Which chip and board you are using | ||
| 23 | |||
| 24 | 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 5c8dac94f..000000000 --- a/examples/stm32f3/src/bin/blocking-tsc.rs +++ /dev/null | |||
| @@ -1,98 +0,0 @@ | |||
| 1 | // Example of polling TSC (Touch Sensing Controller) that lights an LED when touch is detected. | ||
| 2 | // | ||
| 3 | // Suggested physical setup on STM32F303ZE Nucleo board: | ||
| 4 | // - Connect a 1000pF capacitor between pin A0 and GND. This is your sampling capacitor. | ||
| 5 | // - Connect one end of a 1K resistor to pin A1 and leave the other end loose. | ||
| 6 | // The loose end will act as touch sensor which will register your touch. | ||
| 7 | // | ||
| 8 | // Troubleshooting the setup: | ||
| 9 | // - If no touch seems to be registered, then try to disconnect the sampling capacitor from GND momentarily, | ||
| 10 | // now the led should light up. Next try using a different value for the sampling capacitor. | ||
| 11 | // Also experiment with increasing the values for `ct_pulse_high_length`, `ct_pulse_low_length`, `pulse_generator_prescaler`, `max_count_value` and `discharge_delay`. | ||
| 12 | // | ||
| 13 | // All configuration values and sampling capacitor value have been determined experimentally. | ||
| 14 | // 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. | ||
| 15 | // | ||
| 16 | #![no_std] | ||
| 17 | #![no_main] | ||
| 18 | |||
| 19 | use defmt::*; | ||
| 20 | use embassy_stm32::gpio::{Level, Output, Speed}; | ||
| 21 | use embassy_stm32::tsc::{self, *}; | ||
| 22 | use embassy_time::Timer; | ||
| 23 | use {defmt_rtt as _, panic_probe as _}; | ||
| 24 | |||
| 25 | /// This example is written for the nucleo-stm32f303ze, with a stm32f303ze chip. | ||
| 26 | /// | ||
| 27 | /// Make sure you check/update the following (whether you use the F303ZE or another board): | ||
| 28 | /// | ||
| 29 | /// * [ ] Update .cargo/config.toml with the correct `probe-rs run --chip STM32F303ZETx`chip name. | ||
| 30 | /// * [ ] Update Cargo.toml to have the correct `embassy-stm32` feature, for F303ZE it should be `stm32f303ze`. | ||
| 31 | /// * [ ] If your board has a special clock or power configuration, make sure that it is | ||
| 32 | /// set up appropriately. | ||
| 33 | /// * [ ] If your board has different pin mapping, update any pin numbers or peripherals | ||
| 34 | /// to match your schematic | ||
| 35 | /// | ||
| 36 | /// If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: | ||
| 37 | /// | ||
| 38 | /// * Which example you are trying to run | ||
| 39 | /// * Which chip and board you are using | ||
| 40 | /// | ||
| 41 | /// Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org | ||
| 42 | #[embassy_executor::main] | ||
| 43 | async fn main(_spawner: embassy_executor::Spawner) { | ||
| 44 | let device_config = embassy_stm32::Config::default(); | ||
| 45 | let context = embassy_stm32::init(device_config); | ||
| 46 | |||
| 47 | let tsc_conf = Config { | ||
| 48 | ct_pulse_high_length: ChargeTransferPulseCycle::_8, | ||
| 49 | ct_pulse_low_length: ChargeTransferPulseCycle::_8, | ||
| 50 | spread_spectrum: false, | ||
| 51 | spread_spectrum_deviation: SSDeviation::new(2).unwrap(), | ||
| 52 | spread_spectrum_prescaler: false, | ||
| 53 | pulse_generator_prescaler: PGPrescalerDivider::_32, | ||
| 54 | max_count_value: MaxCount::_255, | ||
| 55 | io_default_mode: false, | ||
| 56 | synchro_pin_polarity: false, | ||
| 57 | acquisition_mode: false, | ||
| 58 | max_count_interrupt: false, | ||
| 59 | channel_ios: TscIOPin::Group1Io1.into(), | ||
| 60 | shield_ios: 0, // no shield | ||
| 61 | sampling_ios: TscIOPin::Group1Io2.into(), | ||
| 62 | }; | ||
| 63 | |||
| 64 | let mut g1: PinGroup<embassy_stm32::peripherals::TSC, G1> = PinGroup::new(); | ||
| 65 | g1.set_io1(context.PA0, PinType::Sample); | ||
| 66 | g1.set_io2(context.PA1, PinType::Channel); | ||
| 67 | |||
| 68 | let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, Some(g1), None, None, None, None, None, tsc_conf); | ||
| 69 | |||
| 70 | // LED2 on the STM32F303ZE nucleo-board | ||
| 71 | let mut led = Output::new(context.PB7, Level::High, Speed::Low); | ||
| 72 | |||
| 73 | // smaller sample capacitor discharge faster and can be used with shorter delay. | ||
| 74 | let discharge_delay = 5; // ms | ||
| 75 | |||
| 76 | // the interval at which the loop polls for new touch sensor values | ||
| 77 | let polling_interval = 100; // ms | ||
| 78 | |||
| 79 | info!("polling for touch"); | ||
| 80 | loop { | ||
| 81 | touch_controller.start(); | ||
| 82 | touch_controller.poll_for_acquisition(); | ||
| 83 | touch_controller.discharge_io(true); | ||
| 84 | Timer::after_millis(discharge_delay).await; | ||
| 85 | |||
| 86 | let grp1_status = touch_controller.group_get_status(Group::One); | ||
| 87 | match grp1_status { | ||
| 88 | GroupStatus::Complete => { | ||
| 89 | let group_one_val = touch_controller.group_get_value(Group::One); | ||
| 90 | info!("{}", group_one_val); | ||
| 91 | led.set_high(); | ||
| 92 | } | ||
| 93 | GroupStatus::Ongoing => led.set_low(), | ||
| 94 | } | ||
| 95 | |||
| 96 | Timer::after_millis(polling_interval).await; | ||
| 97 | } | ||
| 98 | } | ||
diff --git a/examples/stm32f3/src/bin/tsc_blocking.rs b/examples/stm32f3/src/bin/tsc_blocking.rs new file mode 100644 index 000000000..fa7f718e6 --- /dev/null +++ b/examples/stm32f3/src/bin/tsc_blocking.rs | |||
| @@ -0,0 +1,138 @@ | |||
| 1 | // Example of blocking TSC (Touch Sensing Controller) that lights an LED when touch is detected. | ||
| 2 | // | ||
| 3 | // This example demonstrates: | ||
| 4 | // 1. Configuring a single TSC channel pin | ||
| 5 | // 2. Using the blocking TSC interface with polling | ||
| 6 | // 3. Waiting for acquisition completion using `poll_for_acquisition` | ||
| 7 | // 4. Reading touch values and controlling an LED based on the results | ||
| 8 | // | ||
| 9 | // Suggested physical setup on STM32F303ZE Nucleo board: | ||
| 10 | // - Connect a 1000pF capacitor between pin PA10 and GND. This is your sampling capacitor. | ||
| 11 | // - Connect one end of a 1K resistor to pin PA9 and leave the other end loose. | ||
| 12 | // The loose end will act as the touch sensor which will register your touch. | ||
| 13 | // | ||
| 14 | // The example uses two pins from Group 4 of the TSC: | ||
| 15 | // - PA10 as the sampling capacitor, TSC group 4 IO2 (D68 on the STM32F303ZE nucleo-board) | ||
| 16 | // - PA9 as the channel pin, TSC group 4 IO1 (D69 on the STM32F303ZE nucleo-board) | ||
| 17 | // | ||
| 18 | // The program continuously reads the touch sensor value: | ||
| 19 | // - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value. | ||
| 20 | // - The LED is turned on when touch is detected (sensor value < 40). | ||
| 21 | // - Touch values are logged to the console. | ||
| 22 | // | ||
| 23 | // Troubleshooting: | ||
| 24 | // - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. | ||
| 25 | // - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, | ||
| 26 | // pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. | ||
| 27 | // | ||
| 28 | // Note: Configuration values and sampling capacitor value have been determined experimentally. | ||
| 29 | // Optimal values may vary based on your specific hardware setup. | ||
| 30 | // Pins have been chosen for their convenient locations on the STM32F303ZE board. Refer to the | ||
| 31 | // official relevant STM32 datasheets and user nucleo-board user manuals to find suitable | ||
| 32 | // alternative pins. | ||
| 33 | |||
| 34 | #![no_std] | ||
| 35 | #![no_main] | ||
| 36 | |||
| 37 | use defmt::*; | ||
| 38 | use embassy_stm32::gpio::{Level, Output, Speed}; | ||
| 39 | use embassy_stm32::tsc::{self, *}; | ||
| 40 | use embassy_stm32::{mode, peripherals}; | ||
| 41 | use embassy_time::Timer; | ||
| 42 | use {defmt_rtt as _, panic_probe as _}; | ||
| 43 | |||
| 44 | const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup | ||
| 45 | |||
| 46 | #[embassy_executor::main] | ||
| 47 | async fn main(_spawner: embassy_executor::Spawner) { | ||
| 48 | let device_config = embassy_stm32::Config::default(); | ||
| 49 | let context = embassy_stm32::init(device_config); | ||
| 50 | |||
| 51 | let tsc_conf = Config { | ||
| 52 | ct_pulse_high_length: ChargeTransferPulseCycle::_4, | ||
| 53 | ct_pulse_low_length: ChargeTransferPulseCycle::_4, | ||
| 54 | spread_spectrum: false, | ||
| 55 | spread_spectrum_deviation: SSDeviation::new(2).unwrap(), | ||
| 56 | spread_spectrum_prescaler: false, | ||
| 57 | pulse_generator_prescaler: PGPrescalerDivider::_16, | ||
| 58 | max_count_value: MaxCount::_255, | ||
| 59 | io_default_mode: false, | ||
| 60 | synchro_pin_polarity: false, | ||
| 61 | acquisition_mode: false, | ||
| 62 | max_count_interrupt: false, | ||
| 63 | }; | ||
| 64 | |||
| 65 | let mut g: PinGroupWithRoles<peripherals::TSC, G4> = PinGroupWithRoles::default(); | ||
| 66 | // D68 on the STM32F303ZE nucleo-board | ||
| 67 | g.set_io2::<tsc_pin_roles::Sample>(context.PA10); | ||
| 68 | // D69 on the STM32F303ZE nucleo-board | ||
| 69 | let tsc_sensor = g.set_io1::<tsc_pin_roles::Channel>(context.PA9); | ||
| 70 | |||
| 71 | let pin_groups: PinGroups<peripherals::TSC> = PinGroups { | ||
| 72 | g4: Some(g.pin_group), | ||
| 73 | ..Default::default() | ||
| 74 | }; | ||
| 75 | |||
| 76 | let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, pin_groups, tsc_conf).unwrap(); | ||
| 77 | |||
| 78 | // Check if TSC is ready | ||
| 79 | if touch_controller.get_state() != State::Ready { | ||
| 80 | crate::panic!("TSC not ready!"); | ||
| 81 | } | ||
| 82 | info!("TSC initialized successfully"); | ||
| 83 | |||
| 84 | // LED2 on the STM32F303ZE nucleo-board | ||
| 85 | let mut led = Output::new(context.PB7, Level::High, Speed::Low); | ||
| 86 | |||
| 87 | // smaller sample capacitor discharge faster and can be used with shorter delay. | ||
| 88 | let discharge_delay = 5; // ms | ||
| 89 | |||
| 90 | // the interval at which the loop polls for new touch sensor values | ||
| 91 | let polling_interval = 100; // ms | ||
| 92 | |||
| 93 | info!("polling for touch"); | ||
| 94 | loop { | ||
| 95 | touch_controller.set_active_channels_mask(tsc_sensor.pin.into()); | ||
| 96 | touch_controller.start(); | ||
| 97 | touch_controller.poll_for_acquisition(); | ||
| 98 | touch_controller.discharge_io(true); | ||
| 99 | Timer::after_millis(discharge_delay).await; | ||
| 100 | |||
| 101 | match read_touch_value(&mut touch_controller, tsc_sensor.pin).await { | ||
| 102 | Some(v) => { | ||
| 103 | info!("sensor value {}", v); | ||
| 104 | if v < SENSOR_THRESHOLD { | ||
| 105 | led.set_high(); | ||
| 106 | } else { | ||
| 107 | led.set_low(); | ||
| 108 | } | ||
| 109 | } | ||
| 110 | None => led.set_low(), | ||
| 111 | } | ||
| 112 | |||
| 113 | Timer::after_millis(polling_interval).await; | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10; | ||
| 118 | |||
| 119 | // attempt to read group status and delay when still ongoing | ||
| 120 | async fn read_touch_value( | ||
| 121 | touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Blocking>, | ||
| 122 | sensor_pin: TscIOPin, | ||
| 123 | ) -> Option<u16> { | ||
| 124 | for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS { | ||
| 125 | match touch_controller.group_get_status(sensor_pin.group()) { | ||
| 126 | GroupStatus::Complete => { | ||
| 127 | return Some(touch_controller.group_get_value(sensor_pin.group())); | ||
| 128 | } | ||
| 129 | GroupStatus::Ongoing => { | ||
| 130 | // if you end up here a lot, then you prob need to increase discharge_delay | ||
| 131 | // or consider changing the code to adjust the discharge_delay dynamically | ||
| 132 | info!("Acquisition still ongoing"); | ||
| 133 | Timer::after_millis(1).await; | ||
| 134 | } | ||
| 135 | } | ||
| 136 | } | ||
| 137 | None | ||
| 138 | } | ||
diff --git a/examples/stm32l0/.cargo/config.toml b/examples/stm32l0/.cargo/config.toml index b050334b2..fed9cf9ce 100644 --- a/examples/stm32l0/.cargo/config.toml +++ b/examples/stm32l0/.cargo/config.toml | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] | 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] |
| 2 | # replace your chip as listed in `probe-rs chip list` | 2 | # replace your chip as listed in `probe-rs chip list` |
| 3 | runner = "probe-rs run --chip STM32L053R8Tx" | 3 | runner = "probe-rs run --chip STM32L073RZTx" |
| 4 | 4 | ||
| 5 | [build] | 5 | [build] |
| 6 | target = "thumbv6m-none-eabi" | 6 | target = "thumbv6m-none-eabi" |
diff --git a/examples/stm32l0/Cargo.toml b/examples/stm32l0/Cargo.toml index 95e215b6f..9d234804a 100644 --- a/examples/stm32l0/Cargo.toml +++ b/examples/stm32l0/Cargo.toml | |||
| @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" | |||
| 6 | 6 | ||
| 7 | [dependencies] | 7 | [dependencies] |
| 8 | # Change stm32l072cz to your chip name, if necessary. | 8 | # Change stm32l072cz to your chip name, if necessary. |
| 9 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "unstable-pac", "time-driver-any", "exti", "memory-x"] } | 9 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l073rz", "unstable-pac", "time-driver-any", "exti", "memory-x"] } |
| 10 | embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } | 10 | embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } |
| 11 | embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } | 11 | embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } |
| 12 | embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } | 12 | 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 000000000..82d222027 --- /dev/null +++ b/examples/stm32l0/README.md | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | # Examples for STM32L0 family | ||
| 2 | Run individual examples with | ||
| 3 | ``` | ||
| 4 | cargo run --bin <module-name> | ||
| 5 | ``` | ||
| 6 | for example | ||
| 7 | ``` | ||
| 8 | cargo run --bin blinky | ||
| 9 | ``` | ||
| 10 | |||
| 11 | ## Checklist before running examples | ||
| 12 | 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. | ||
| 13 | |||
| 14 | * [ ] 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) | ||
| 15 | * [ ] 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. | ||
| 16 | * [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. | ||
| 17 | * [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic | ||
| 18 | |||
| 19 | If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: | ||
| 20 | |||
| 21 | * Which example you are trying to run | ||
| 22 | * Which chip and board you are using | ||
| 23 | |||
| 24 | 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 c40b86af9..000000000 --- a/examples/stm32l0/src/bin/async-tsc.rs +++ /dev/null | |||
| @@ -1,122 +0,0 @@ | |||
| 1 | // Example of async TSC (Touch Sensing Controller) that lights an LED when touch is detected. | ||
| 2 | // | ||
| 3 | // Suggested physical setup on STM32L073RZ Nucleo board: | ||
| 4 | // - Connect a 1000pF capacitor between pin A0 and GND. This is your sampling capacitor. | ||
| 5 | // - Connect one end of a 1K resistor to pin A1 and leave the other end loose. | ||
| 6 | // The loose end will act as touch sensor which will register your touch. | ||
| 7 | // | ||
| 8 | // Troubleshooting the setup: | ||
| 9 | // - If no touch seems to be registered, then try to disconnect the sampling capacitor from GND momentarily, | ||
| 10 | // now the led should light up. Next try using a different value for the sampling capacitor. | ||
| 11 | // Also experiment with increasing the values for `ct_pulse_high_length`, `ct_pulse_low_length`, `pulse_generator_prescaler`, `max_count_value` and `discharge_delay`. | ||
| 12 | // | ||
| 13 | // All configuration values and sampling capacitor value have been determined experimentally. | ||
| 14 | // 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. | ||
| 15 | // | ||
| 16 | #![no_std] | ||
| 17 | #![no_main] | ||
| 18 | |||
| 19 | use defmt::*; | ||
| 20 | use embassy_stm32::bind_interrupts; | ||
| 21 | use embassy_stm32::gpio::{Level, Output, Speed}; | ||
| 22 | use embassy_stm32::tsc::{self, *}; | ||
| 23 | use embassy_time::Timer; | ||
| 24 | use {defmt_rtt as _, panic_probe as _}; | ||
| 25 | |||
| 26 | bind_interrupts!(struct Irqs { | ||
| 27 | TSC => InterruptHandler<embassy_stm32::peripherals::TSC>; | ||
| 28 | }); | ||
| 29 | |||
| 30 | #[cortex_m_rt::exception] | ||
| 31 | unsafe fn HardFault(_: &cortex_m_rt::ExceptionFrame) -> ! { | ||
| 32 | cortex_m::peripheral::SCB::sys_reset(); | ||
| 33 | } | ||
| 34 | |||
| 35 | /// This example is written for the nucleo-stm32l073rz, with a stm32l073rz chip. | ||
| 36 | /// | ||
| 37 | /// Make sure you check/update the following (whether you use the L073RZ or another board): | ||
| 38 | /// | ||
| 39 | /// * [ ] Update .cargo/config.toml with the correct `probe-rs run --chip STM32L073RZTx`chip name. | ||
| 40 | /// * [ ] Update Cargo.toml to have the correct `embassy-stm32` feature, for L073RZ it should be `stm32l073rz`. | ||
| 41 | /// * [ ] If your board has a special clock or power configuration, make sure that it is | ||
| 42 | /// set up appropriately. | ||
| 43 | /// * [ ] If your board has different pin mapping, update any pin numbers or peripherals | ||
| 44 | /// to match your schematic | ||
| 45 | /// | ||
| 46 | /// If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: | ||
| 47 | /// | ||
| 48 | /// * Which example you are trying to run | ||
| 49 | /// * Which chip and board you are using | ||
| 50 | /// | ||
| 51 | /// Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org | ||
| 52 | #[embassy_executor::main] | ||
| 53 | async fn main(_spawner: embassy_executor::Spawner) { | ||
| 54 | let device_config = embassy_stm32::Config::default(); | ||
| 55 | let context = embassy_stm32::init(device_config); | ||
| 56 | |||
| 57 | let config = tsc::Config { | ||
| 58 | ct_pulse_high_length: ChargeTransferPulseCycle::_4, | ||
| 59 | ct_pulse_low_length: ChargeTransferPulseCycle::_4, | ||
| 60 | spread_spectrum: false, | ||
| 61 | spread_spectrum_deviation: SSDeviation::new(2).unwrap(), | ||
| 62 | spread_spectrum_prescaler: false, | ||
| 63 | pulse_generator_prescaler: PGPrescalerDivider::_16, | ||
| 64 | max_count_value: MaxCount::_255, | ||
| 65 | io_default_mode: false, | ||
| 66 | synchro_pin_polarity: false, | ||
| 67 | acquisition_mode: false, | ||
| 68 | max_count_interrupt: false, | ||
| 69 | channel_ios: TscIOPin::Group1Io1.into(), | ||
| 70 | shield_ios: 0, // no shield | ||
| 71 | sampling_ios: TscIOPin::Group1Io2.into(), | ||
| 72 | }; | ||
| 73 | |||
| 74 | let mut g1: PinGroup<embassy_stm32::peripherals::TSC, G1> = PinGroup::new(); | ||
| 75 | g1.set_io1(context.PA0, PinType::Sample); | ||
| 76 | g1.set_io2(context.PA1, PinType::Channel); | ||
| 77 | |||
| 78 | let mut touch_controller = tsc::Tsc::new_async( | ||
| 79 | context.TSC, | ||
| 80 | Some(g1), | ||
| 81 | None, | ||
| 82 | None, | ||
| 83 | None, | ||
| 84 | None, | ||
| 85 | None, | ||
| 86 | None, | ||
| 87 | None, | ||
| 88 | config, | ||
| 89 | Irqs, | ||
| 90 | ); | ||
| 91 | |||
| 92 | // Check if TSC is ready | ||
| 93 | if touch_controller.get_state() != State::Ready { | ||
| 94 | info!("TSC not ready!"); | ||
| 95 | loop {} // Halt execution | ||
| 96 | } | ||
| 97 | info!("TSC initialized successfully"); | ||
| 98 | |||
| 99 | // LED2 on the STM32L073RZ nucleo-board (PA5) | ||
| 100 | let mut led = Output::new(context.PA5, Level::High, Speed::Low); | ||
| 101 | |||
| 102 | // smaller sample capacitor discharge faster and can be used with shorter delay. | ||
| 103 | let discharge_delay = 5; // ms | ||
| 104 | |||
| 105 | info!("Starting touch_controller interface"); | ||
| 106 | loop { | ||
| 107 | touch_controller.start(); | ||
| 108 | touch_controller.pend_for_acquisition().await; | ||
| 109 | touch_controller.discharge_io(true); | ||
| 110 | Timer::after_millis(discharge_delay).await; | ||
| 111 | |||
| 112 | let grp1_status = touch_controller.group_get_status(Group::One); | ||
| 113 | match grp1_status { | ||
| 114 | GroupStatus::Complete => { | ||
| 115 | let group_one_val = touch_controller.group_get_value(Group::One); | ||
| 116 | info!("{}", group_one_val); | ||
| 117 | led.set_high(); | ||
| 118 | } | ||
| 119 | GroupStatus::Ongoing => led.set_low(), | ||
| 120 | } | ||
| 121 | } | ||
| 122 | } | ||
diff --git a/examples/stm32l0/src/bin/blocking-tsc.rs b/examples/stm32l0/src/bin/blocking-tsc.rs deleted file mode 100644 index 7e4f40946..000000000 --- a/examples/stm32l0/src/bin/blocking-tsc.rs +++ /dev/null | |||
| @@ -1,116 +0,0 @@ | |||
| 1 | // Example of polling TSC (Touch Sensing Controller) that lights an LED when touch is detected. | ||
| 2 | // | ||
| 3 | // Suggested physical setup on STM32L073RZ Nucleo board: | ||
| 4 | // - Connect a 1000pF capacitor between pin A0 and GND. This is your sampling capacitor. | ||
| 5 | // - Connect one end of a 1K resistor to pin A1 and leave the other end loose. | ||
| 6 | // The loose end will act as touch sensor which will register your touch. | ||
| 7 | // | ||
| 8 | // Troubleshooting the setup: | ||
| 9 | // - If no touch seems to be registered, then try to disconnect the sampling capacitor from GND momentarily, | ||
| 10 | // now the led should light up. Next try using a different value for the sampling capacitor. | ||
| 11 | // Also experiment with increasing the values for `ct_pulse_high_length`, `ct_pulse_low_length`, `pulse_generator_prescaler`, `max_count_value` and `discharge_delay`. | ||
| 12 | // | ||
| 13 | // All configuration values and sampling capacitor value have been determined experimentally. | ||
| 14 | // 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. | ||
| 15 | // | ||
| 16 | #![no_std] | ||
| 17 | #![no_main] | ||
| 18 | |||
| 19 | use defmt::*; | ||
| 20 | use embassy_stm32::gpio::{Level, Output, Speed}; | ||
| 21 | use embassy_stm32::tsc::{self, *}; | ||
| 22 | use embassy_time::Timer; | ||
| 23 | use {defmt_rtt as _, panic_probe as _}; | ||
| 24 | |||
| 25 | /// This example is written for the nucleo-stm32l073rz, with a stm32l073rz chip. | ||
| 26 | /// | ||
| 27 | /// Make sure you check/update the following (whether you use the L073RZ or another board): | ||
| 28 | /// | ||
| 29 | /// * [ ] Update .cargo/config.toml with the correct `probe-rs run --chip STM32L073RZTx`chip name. | ||
| 30 | /// * [ ] Update Cargo.toml to have the correct `embassy-stm32` feature, for L073RZ it should be `stm32l073rz`. | ||
| 31 | /// * [ ] If your board has a special clock or power configuration, make sure that it is | ||
| 32 | /// set up appropriately. | ||
| 33 | /// * [ ] If your board has different pin mapping, update any pin numbers or peripherals | ||
| 34 | /// to match your schematic | ||
| 35 | /// | ||
| 36 | /// If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: | ||
| 37 | /// | ||
| 38 | /// * Which example you are trying to run | ||
| 39 | /// * Which chip and board you are using | ||
| 40 | /// | ||
| 41 | /// Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org | ||
| 42 | #[embassy_executor::main] | ||
| 43 | async fn main(_spawner: embassy_executor::Spawner) { | ||
| 44 | let device_config = embassy_stm32::Config::default(); | ||
| 45 | let context = embassy_stm32::init(device_config); | ||
| 46 | |||
| 47 | let tsc_conf = Config { | ||
| 48 | ct_pulse_high_length: ChargeTransferPulseCycle::_4, | ||
| 49 | ct_pulse_low_length: ChargeTransferPulseCycle::_4, | ||
| 50 | spread_spectrum: false, | ||
| 51 | spread_spectrum_deviation: SSDeviation::new(2).unwrap(), | ||
| 52 | spread_spectrum_prescaler: false, | ||
| 53 | pulse_generator_prescaler: PGPrescalerDivider::_16, | ||
| 54 | max_count_value: MaxCount::_255, | ||
| 55 | io_default_mode: false, | ||
| 56 | synchro_pin_polarity: false, | ||
| 57 | acquisition_mode: false, | ||
| 58 | max_count_interrupt: false, | ||
| 59 | channel_ios: TscIOPin::Group1Io1.into(), | ||
| 60 | shield_ios: 0, // no shield | ||
| 61 | sampling_ios: TscIOPin::Group1Io2.into(), | ||
| 62 | }; | ||
| 63 | |||
| 64 | let mut g1: PinGroup<embassy_stm32::peripherals::TSC, G1> = PinGroup::new(); | ||
| 65 | g1.set_io1(context.PA0, PinType::Sample); | ||
| 66 | g1.set_io2(context.PA1, PinType::Channel); | ||
| 67 | |||
| 68 | let mut touch_controller = tsc::Tsc::new_blocking( | ||
| 69 | context.TSC, | ||
| 70 | Some(g1), | ||
| 71 | None, | ||
| 72 | None, | ||
| 73 | None, | ||
| 74 | None, | ||
| 75 | None, | ||
| 76 | None, | ||
| 77 | None, | ||
| 78 | tsc_conf, | ||
| 79 | ); | ||
| 80 | |||
| 81 | // Check if TSC is ready | ||
| 82 | if touch_controller.get_state() != State::Ready { | ||
| 83 | info!("TSC not ready!"); | ||
| 84 | loop {} // Halt execution | ||
| 85 | } | ||
| 86 | info!("TSC initialized successfully"); | ||
| 87 | |||
| 88 | // LED2 on the STM32L073RZ nucleo-board (PA5) | ||
| 89 | let mut led = Output::new(context.PA5, Level::High, Speed::Low); | ||
| 90 | |||
| 91 | // smaller sample capacitor discharge faster and can be used with shorter delay. | ||
| 92 | let discharge_delay = 5; // ms | ||
| 93 | |||
| 94 | // the interval at which the loop polls for new touch sensor values | ||
| 95 | let polling_interval = 100; // ms | ||
| 96 | |||
| 97 | info!("polling for touch"); | ||
| 98 | loop { | ||
| 99 | touch_controller.start(); | ||
| 100 | touch_controller.poll_for_acquisition(); | ||
| 101 | touch_controller.discharge_io(true); | ||
| 102 | Timer::after_millis(discharge_delay).await; | ||
| 103 | |||
| 104 | let grp1_status = touch_controller.group_get_status(Group::One); | ||
| 105 | match grp1_status { | ||
| 106 | GroupStatus::Complete => { | ||
| 107 | let group_one_val = touch_controller.group_get_value(Group::One); | ||
| 108 | info!("{}", group_one_val); | ||
| 109 | led.set_high(); | ||
| 110 | } | ||
| 111 | GroupStatus::Ongoing => led.set_low(), | ||
| 112 | } | ||
| 113 | |||
| 114 | Timer::after_millis(polling_interval).await; | ||
| 115 | } | ||
| 116 | } | ||
diff --git a/examples/stm32l0/src/bin/tsc_async.rs b/examples/stm32l0/src/bin/tsc_async.rs new file mode 100644 index 000000000..cebe9712f --- /dev/null +++ b/examples/stm32l0/src/bin/tsc_async.rs | |||
| @@ -0,0 +1,116 @@ | |||
| 1 | // Example of async TSC (Touch Sensing Controller) that lights an LED when touch is detected. | ||
| 2 | // | ||
| 3 | // This example demonstrates: | ||
| 4 | // 1. Configuring a single TSC channel pin | ||
| 5 | // 2. Using the blocking TSC interface with polling | ||
| 6 | // 3. Waiting for acquisition completion using `poll_for_acquisition` | ||
| 7 | // 4. Reading touch values and controlling an LED based on the results | ||
| 8 | // | ||
| 9 | // Suggested physical setup on STM32L073RZ Nucleo board: | ||
| 10 | // - Connect a 1000pF capacitor between pin PA0 and GND. This is your sampling capacitor. | ||
| 11 | // - Connect one end of a 1K resistor to pin PA1 and leave the other end loose. | ||
| 12 | // The loose end will act as the touch sensor which will register your touch. | ||
| 13 | // | ||
| 14 | // The example uses two pins from Group 1 of the TSC on the STM32L073RZ Nucleo board: | ||
| 15 | // - PA0 as the sampling capacitor, TSC group 1 IO1 (label A0) | ||
| 16 | // - PA1 as the channel pin, TSC group 1 IO2 (label A1) | ||
| 17 | // | ||
| 18 | // The program continuously reads the touch sensor value: | ||
| 19 | // - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value. | ||
| 20 | // - The LED is turned on when touch is detected (sensor value < 25). | ||
| 21 | // - Touch values are logged to the console. | ||
| 22 | // | ||
| 23 | // Troubleshooting: | ||
| 24 | // - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. | ||
| 25 | // - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, | ||
| 26 | // pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. | ||
| 27 | // | ||
| 28 | // Note: Configuration values and sampling capacitor value have been determined experimentally. | ||
| 29 | // Optimal values may vary based on your specific hardware setup. | ||
| 30 | // Pins have been chosen for their convenient locations on the STM32L073RZ board. Refer to the | ||
| 31 | // official relevant STM32 datasheets and nucleo-board user manuals to find suitable | ||
| 32 | // alternative pins. | ||
| 33 | // | ||
| 34 | // Beware for STM32L073RZ nucleo-board, that PA2 and PA3 is used for the uart connection to | ||
| 35 | // the programmer chip. If you try to use these two pins for TSC, you will get strange | ||
| 36 | // readings, unless you somehow reconfigure/re-wire your nucleo-board. | ||
| 37 | // No errors or warnings will be emitted, they will just silently not work as expected. | ||
| 38 | // (see nucleo user manual UM1724, Rev 14, page 25) | ||
| 39 | |||
| 40 | #![no_std] | ||
| 41 | #![no_main] | ||
| 42 | |||
| 43 | use defmt::*; | ||
| 44 | use embassy_stm32::gpio::{Level, Output, Speed}; | ||
| 45 | use embassy_stm32::tsc::{self, *}; | ||
| 46 | use embassy_stm32::{bind_interrupts, peripherals}; | ||
| 47 | use embassy_time::Timer; | ||
| 48 | use {defmt_rtt as _, panic_probe as _}; | ||
| 49 | |||
| 50 | bind_interrupts!(struct Irqs { | ||
| 51 | TSC => InterruptHandler<embassy_stm32::peripherals::TSC>; | ||
| 52 | }); | ||
| 53 | const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup | ||
| 54 | |||
| 55 | #[embassy_executor::main] | ||
| 56 | async fn main(_spawner: embassy_executor::Spawner) { | ||
| 57 | let device_config = embassy_stm32::Config::default(); | ||
| 58 | let context = embassy_stm32::init(device_config); | ||
| 59 | |||
| 60 | let mut pin_group: PinGroupWithRoles<peripherals::TSC, G1> = PinGroupWithRoles::default(); | ||
| 61 | pin_group.set_io1::<tsc_pin_roles::Sample>(context.PA0); | ||
| 62 | let sensor = pin_group.set_io2::<tsc_pin_roles::Channel>(context.PA1); | ||
| 63 | |||
| 64 | let tsc_conf = Config { | ||
| 65 | ct_pulse_high_length: ChargeTransferPulseCycle::_4, | ||
| 66 | ct_pulse_low_length: ChargeTransferPulseCycle::_4, | ||
| 67 | spread_spectrum: false, | ||
| 68 | spread_spectrum_deviation: SSDeviation::new(2).unwrap(), | ||
| 69 | spread_spectrum_prescaler: false, | ||
| 70 | pulse_generator_prescaler: PGPrescalerDivider::_16, | ||
| 71 | max_count_value: MaxCount::_255, | ||
| 72 | io_default_mode: false, | ||
| 73 | synchro_pin_polarity: false, | ||
| 74 | acquisition_mode: false, | ||
| 75 | max_count_interrupt: false, | ||
| 76 | }; | ||
| 77 | |||
| 78 | let pin_groups: PinGroups<peripherals::TSC> = PinGroups { | ||
| 79 | g1: Some(pin_group.pin_group), | ||
| 80 | ..Default::default() | ||
| 81 | }; | ||
| 82 | |||
| 83 | let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, tsc_conf, Irqs).unwrap(); | ||
| 84 | |||
| 85 | // Check if TSC is ready | ||
| 86 | if touch_controller.get_state() != State::Ready { | ||
| 87 | info!("TSC not ready!"); | ||
| 88 | return; | ||
| 89 | } | ||
| 90 | info!("TSC initialized successfully"); | ||
| 91 | |||
| 92 | // LED2 on the STM32L073RZ nucleo-board (PA5) | ||
| 93 | let mut led = Output::new(context.PA5, Level::Low, Speed::Low); | ||
| 94 | |||
| 95 | let discharge_delay = 5; // ms | ||
| 96 | |||
| 97 | info!("Starting touch_controller interface"); | ||
| 98 | loop { | ||
| 99 | touch_controller.set_active_channels_mask(sensor.pin.into()); | ||
| 100 | touch_controller.start(); | ||
| 101 | touch_controller.pend_for_acquisition().await; | ||
| 102 | touch_controller.discharge_io(true); | ||
| 103 | Timer::after_millis(discharge_delay).await; | ||
| 104 | |||
| 105 | let group_val = touch_controller.group_get_value(sensor.pin.group()); | ||
| 106 | info!("Touch value: {}", group_val); | ||
| 107 | |||
| 108 | if group_val < SENSOR_THRESHOLD { | ||
| 109 | led.set_high(); | ||
| 110 | } else { | ||
| 111 | led.set_low(); | ||
| 112 | } | ||
| 113 | |||
| 114 | Timer::after_millis(100).await; | ||
| 115 | } | ||
| 116 | } | ||
diff --git a/examples/stm32l0/src/bin/tsc_blocking.rs b/examples/stm32l0/src/bin/tsc_blocking.rs new file mode 100644 index 000000000..65203925c --- /dev/null +++ b/examples/stm32l0/src/bin/tsc_blocking.rs | |||
| @@ -0,0 +1,142 @@ | |||
| 1 | // Example of blocking TSC (Touch Sensing Controller) that lights an LED when touch is detected. | ||
| 2 | // | ||
| 3 | // This example demonstrates: | ||
| 4 | // 1. Configuring a single TSC channel pin | ||
| 5 | // 2. Using the blocking TSC interface with polling | ||
| 6 | // 3. Waiting for acquisition completion using `poll_for_acquisition` | ||
| 7 | // 4. Reading touch values and controlling an LED based on the results | ||
| 8 | // | ||
| 9 | // Suggested physical setup on STM32L073RZ Nucleo board: | ||
| 10 | // - Connect a 1000pF capacitor between pin PA0 and GND. This is your sampling capacitor. | ||
| 11 | // - Connect one end of a 1K resistor to pin PA1 and leave the other end loose. | ||
| 12 | // The loose end will act as the touch sensor which will register your touch. | ||
| 13 | // | ||
| 14 | // The example uses two pins from Group 1 of the TSC on the STM32L073RZ Nucleo board: | ||
| 15 | // - PA0 as the sampling capacitor, TSC group 1 IO1 (label A0) | ||
| 16 | // - PA1 as the channel pin, TSC group 1 IO2 (label A1) | ||
| 17 | // | ||
| 18 | // The program continuously reads the touch sensor value: | ||
| 19 | // - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value. | ||
| 20 | // - The LED is turned on when touch is detected (sensor value < 25). | ||
| 21 | // - Touch values are logged to the console. | ||
| 22 | // | ||
| 23 | // Troubleshooting: | ||
| 24 | // - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. | ||
| 25 | // - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, | ||
| 26 | // pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. | ||
| 27 | // | ||
| 28 | // Note: Configuration values and sampling capacitor value have been determined experimentally. | ||
| 29 | // Optimal values may vary based on your specific hardware setup. | ||
| 30 | // Pins have been chosen for their convenient locations on the STM32L073RZ board. Refer to the | ||
| 31 | // official relevant STM32 datasheets and nucleo-board user manuals to find suitable | ||
| 32 | // alternative pins. | ||
| 33 | // | ||
| 34 | // Beware for STM32L073RZ nucleo-board, that PA2 and PA3 is used for the uart connection to | ||
| 35 | // the programmer chip. If you try to use these two pins for TSC, you will get strange | ||
| 36 | // readings, unless you somehow reconfigure/re-wire your nucleo-board. | ||
| 37 | // No errors or warnings will be emitted, they will just silently not work as expected. | ||
| 38 | // (see nucleo user manual UM1724, Rev 14, page 25) | ||
| 39 | |||
| 40 | #![no_std] | ||
| 41 | #![no_main] | ||
| 42 | |||
| 43 | use defmt::*; | ||
| 44 | use embassy_stm32::gpio::{Level, Output, Speed}; | ||
| 45 | use embassy_stm32::tsc::{self, *}; | ||
| 46 | use embassy_stm32::{mode, peripherals}; | ||
| 47 | use embassy_time::Timer; | ||
| 48 | use {defmt_rtt as _, panic_probe as _}; | ||
| 49 | |||
| 50 | const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup | ||
| 51 | |||
| 52 | #[embassy_executor::main] | ||
| 53 | async fn main(_spawner: embassy_executor::Spawner) { | ||
| 54 | let device_config = embassy_stm32::Config::default(); | ||
| 55 | let context = embassy_stm32::init(device_config); | ||
| 56 | |||
| 57 | let tsc_conf = Config { | ||
| 58 | ct_pulse_high_length: ChargeTransferPulseCycle::_4, | ||
| 59 | ct_pulse_low_length: ChargeTransferPulseCycle::_4, | ||
| 60 | spread_spectrum: false, | ||
| 61 | spread_spectrum_deviation: SSDeviation::new(2).unwrap(), | ||
| 62 | spread_spectrum_prescaler: false, | ||
| 63 | pulse_generator_prescaler: PGPrescalerDivider::_16, | ||
| 64 | max_count_value: MaxCount::_255, | ||
| 65 | io_default_mode: false, | ||
| 66 | synchro_pin_polarity: false, | ||
| 67 | acquisition_mode: false, | ||
| 68 | max_count_interrupt: false, | ||
| 69 | }; | ||
| 70 | |||
| 71 | let mut g1: PinGroupWithRoles<peripherals::TSC, G1> = PinGroupWithRoles::default(); | ||
| 72 | g1.set_io1::<tsc_pin_roles::Sample>(context.PA0); | ||
| 73 | let tsc_sensor = g1.set_io2::<tsc_pin_roles::Channel>(context.PA1); | ||
| 74 | |||
| 75 | let pin_groups: PinGroups<peripherals::TSC> = PinGroups { | ||
| 76 | g1: Some(g1.pin_group), | ||
| 77 | ..Default::default() | ||
| 78 | }; | ||
| 79 | |||
| 80 | let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, pin_groups, tsc_conf).unwrap(); | ||
| 81 | |||
| 82 | // Check if TSC is ready | ||
| 83 | if touch_controller.get_state() != State::Ready { | ||
| 84 | crate::panic!("TSC not ready!"); | ||
| 85 | } | ||
| 86 | info!("TSC initialized successfully"); | ||
| 87 | |||
| 88 | // LED2 on the STM32L073RZ nucleo-board (PA5) | ||
| 89 | let mut led = Output::new(context.PA5, Level::High, Speed::Low); | ||
| 90 | |||
| 91 | // smaller sample capacitor discharge faster and can be used with shorter delay. | ||
| 92 | let discharge_delay = 5; // ms | ||
| 93 | |||
| 94 | // the interval at which the loop polls for new touch sensor values | ||
| 95 | let polling_interval = 100; // ms | ||
| 96 | |||
| 97 | info!("polling for touch"); | ||
| 98 | loop { | ||
| 99 | touch_controller.set_active_channels_mask(tsc_sensor.pin.into()); | ||
| 100 | touch_controller.start(); | ||
| 101 | touch_controller.poll_for_acquisition(); | ||
| 102 | touch_controller.discharge_io(true); | ||
| 103 | Timer::after_millis(discharge_delay).await; | ||
| 104 | |||
| 105 | match read_touch_value(&mut touch_controller, tsc_sensor.pin).await { | ||
| 106 | Some(v) => { | ||
| 107 | info!("sensor value {}", v); | ||
| 108 | if v < SENSOR_THRESHOLD { | ||
| 109 | led.set_high(); | ||
| 110 | } else { | ||
| 111 | led.set_low(); | ||
| 112 | } | ||
| 113 | } | ||
| 114 | None => led.set_low(), | ||
| 115 | } | ||
| 116 | |||
| 117 | Timer::after_millis(polling_interval).await; | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10; | ||
| 122 | |||
| 123 | // attempt to read group status and delay when still ongoing | ||
| 124 | async fn read_touch_value( | ||
| 125 | touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Blocking>, | ||
| 126 | sensor_pin: TscIOPin, | ||
| 127 | ) -> Option<u16> { | ||
| 128 | for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS { | ||
| 129 | match touch_controller.group_get_status(sensor_pin.group()) { | ||
| 130 | GroupStatus::Complete => { | ||
| 131 | return Some(touch_controller.group_get_value(sensor_pin.group())); | ||
| 132 | } | ||
| 133 | GroupStatus::Ongoing => { | ||
| 134 | // if you end up here a lot, then you prob need to increase discharge_delay | ||
| 135 | // or consider changing the code to adjust the discharge_delay dynamically | ||
| 136 | info!("Acquisition still ongoing"); | ||
| 137 | Timer::after_millis(1).await; | ||
| 138 | } | ||
| 139 | } | ||
| 140 | } | ||
| 141 | None | ||
| 142 | } | ||
diff --git a/examples/stm32l0/src/bin/tsc_multipin.rs b/examples/stm32l0/src/bin/tsc_multipin.rs new file mode 100644 index 000000000..6170d0799 --- /dev/null +++ b/examples/stm32l0/src/bin/tsc_multipin.rs | |||
| @@ -0,0 +1,233 @@ | |||
| 1 | // Example of TSC (Touch Sensing Controller) using multiple pins from the same tsc-group. | ||
| 2 | // | ||
| 3 | // What is special about using multiple TSC pins as sensor channels from the same TSC group, | ||
| 4 | // is that only one TSC pin for each TSC group can be acquired and read at the time. | ||
| 5 | // To control which channel pins are acquired and read, we must write a mask before initiating an | ||
| 6 | // acquisition. To help manage and abstract all this business away, we can organize our channel | ||
| 7 | // pins into acquisition banks. Each acquisition bank can contain exactly one channel pin per TSC | ||
| 8 | // group and it will contain the relevant mask. | ||
| 9 | // | ||
| 10 | // This example demonstrates how to: | ||
| 11 | // 1. Configure multiple channel pins within a single TSC group | ||
| 12 | // 2. Use the set_active_channels method to switch between different channels | ||
| 13 | // 3. Read and interpret touch values from multiple channels in the same group | ||
| 14 | // | ||
| 15 | // Suggested physical setup on STM32L073RZ Nucleo board: | ||
| 16 | // - Connect a 1000pF capacitor between pin PA0 (label A0) and GND. This is the sampling capacitor for TSC | ||
| 17 | // group 1. | ||
| 18 | // - Connect one end of a 1K resistor to pin PA1 (label A1) and leave the other end loose. | ||
| 19 | // The loose end will act as a touch sensor. | ||
| 20 | // | ||
| 21 | // - Connect a 1000pF capacitor between pin PB3 (label D3) and GND. This is the sampling capacitor for TSC | ||
| 22 | // group 5. | ||
| 23 | // - Connect one end of another 1K resistor to pin PB4 and leave the other end loose. | ||
| 24 | // The loose end will act as a touch sensor. | ||
| 25 | // - Connect one end of another 1K resistor to pin PB6 and leave the other end loose. | ||
| 26 | // The loose end will act as a touch sensor. | ||
| 27 | // | ||
| 28 | // The example uses pins from two TSC groups. | ||
| 29 | // - PA0 as sampling capacitor, TSC group 1 IO1 (label A0) | ||
| 30 | // - PA1 as channel, TSC group 1 IO2 (label A1) | ||
| 31 | // - PB3 as sampling capacitor, TSC group 5 IO1 (label D3) | ||
| 32 | // - PB4 as channel, TSC group 5 IO2 (label D3) | ||
| 33 | // - PB6 as channel, TSC group 5 IO3 (label D5) | ||
| 34 | // | ||
| 35 | // The pins have been chosen to make it easy to simply add capacitors directly onto the board and | ||
| 36 | // connect one leg to GND, and to easily add resistors to the board with no special connectors, | ||
| 37 | // breadboards, special wires or soldering required. All you need is the capacitors and resistors. | ||
| 38 | // | ||
| 39 | // The program reads the designated channel pins and adjusts the LED blinking | ||
| 40 | // pattern based on which sensor(s) are touched: | ||
| 41 | // - No touch: LED off | ||
| 42 | // - one sensor touched: Slow blinking | ||
| 43 | // - two sensors touched: Fast blinking | ||
| 44 | // - three sensors touched: LED constantly on | ||
| 45 | // | ||
| 46 | // Troubleshooting: | ||
| 47 | // - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. | ||
| 48 | // - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, | ||
| 49 | // pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. | ||
| 50 | // | ||
| 51 | // Note: Configuration values and sampling capacitor value have been determined experimentally. | ||
| 52 | // Optimal values may vary based on your specific hardware setup. | ||
| 53 | // Pins have been chosen for their convenient locations on the STM32L073RZ board. Refer to the | ||
| 54 | // official relevant STM32 datasheets and nucleo-board user manuals to find suitable | ||
| 55 | // alternative pins. | ||
| 56 | // | ||
| 57 | // Beware for STM32L073RZ nucleo-board, that PA2 and PA3 is used for the uart connection to | ||
| 58 | // the programmer chip. If you try to use these two pins for TSC, you will get strange | ||
| 59 | // readings, unless you somehow reconfigure/re-wire your nucleo-board. | ||
| 60 | // No errors or warnings will be emitted, they will just silently not work as expected. | ||
| 61 | // (see nucleo user manual UM1724, Rev 14, page 25) | ||
| 62 | |||
| 63 | #![no_std] | ||
| 64 | #![no_main] | ||
| 65 | |||
| 66 | use defmt::*; | ||
| 67 | use embassy_stm32::gpio::{Level, Output, Speed}; | ||
| 68 | use embassy_stm32::tsc::{self, *}; | ||
| 69 | use embassy_stm32::{bind_interrupts, mode, peripherals}; | ||
| 70 | use embassy_time::Timer; | ||
| 71 | use {defmt_rtt as _, panic_probe as _}; | ||
| 72 | |||
| 73 | bind_interrupts!(struct Irqs { | ||
| 74 | TSC => InterruptHandler<embassy_stm32::peripherals::TSC>; | ||
| 75 | }); | ||
| 76 | |||
| 77 | const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10; | ||
| 78 | |||
| 79 | async fn read_touch_values( | ||
| 80 | touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Async>, | ||
| 81 | tsc_acquisition_bank: &TscAcquisitionBank, | ||
| 82 | ) -> Option<TscAcquisitionBankReadings> { | ||
| 83 | for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS { | ||
| 84 | let status = touch_controller.get_acquisition_bank_status(tsc_acquisition_bank); | ||
| 85 | if status.all_complete() { | ||
| 86 | let r = touch_controller.get_acquisition_bank_values(tsc_acquisition_bank); | ||
| 87 | return Some(r); | ||
| 88 | } else { | ||
| 89 | info!("Acquisition still ongoing"); | ||
| 90 | Timer::after_millis(1).await; | ||
| 91 | } | ||
| 92 | } | ||
| 93 | info!("Acquisition failed after {} attempts", MAX_GROUP_STATUS_READ_ATTEMPTS); | ||
| 94 | None | ||
| 95 | } | ||
| 96 | |||
| 97 | const SENSOR_THRESHOLD: u16 = 35; | ||
| 98 | |||
| 99 | async fn acquire_sensors( | ||
| 100 | touch_controller: &mut Tsc<'static, peripherals::TSC, mode::Async>, | ||
| 101 | tsc_acquisition_bank: &TscAcquisitionBank, | ||
| 102 | ) { | ||
| 103 | touch_controller.set_active_channels_mask(tsc_acquisition_bank.mask()); | ||
| 104 | touch_controller.start(); | ||
| 105 | touch_controller.pend_for_acquisition().await; | ||
| 106 | touch_controller.discharge_io(true); | ||
| 107 | let discharge_delay = 5; // ms | ||
| 108 | Timer::after_millis(discharge_delay).await; | ||
| 109 | } | ||
| 110 | |||
| 111 | #[embassy_executor::main] | ||
| 112 | async fn main(_spawner: embassy_executor::Spawner) { | ||
| 113 | let device_config = embassy_stm32::Config::default(); | ||
| 114 | let context = embassy_stm32::init(device_config); | ||
| 115 | |||
| 116 | // ---------- initial configuration of TSC ---------- | ||
| 117 | let mut pin_group1: PinGroupWithRoles<peripherals::TSC, G1> = PinGroupWithRoles::default(); | ||
| 118 | pin_group1.set_io1::<tsc_pin_roles::Sample>(context.PA0); | ||
| 119 | let tsc_sensor0 = pin_group1.set_io2(context.PA1); | ||
| 120 | |||
| 121 | let mut pin_group5: PinGroupWithRoles<peripherals::TSC, G5> = PinGroupWithRoles::default(); | ||
| 122 | pin_group5.set_io1::<tsc_pin_roles::Sample>(context.PB3); | ||
| 123 | let tsc_sensor1 = pin_group5.set_io2(context.PB4); | ||
| 124 | let tsc_sensor2 = pin_group5.set_io3(context.PB6); | ||
| 125 | |||
| 126 | let config = tsc::Config { | ||
| 127 | ct_pulse_high_length: ChargeTransferPulseCycle::_16, | ||
| 128 | ct_pulse_low_length: ChargeTransferPulseCycle::_16, | ||
| 129 | spread_spectrum: false, | ||
| 130 | spread_spectrum_deviation: SSDeviation::new(2).unwrap(), | ||
| 131 | spread_spectrum_prescaler: false, | ||
| 132 | pulse_generator_prescaler: PGPrescalerDivider::_16, | ||
| 133 | max_count_value: MaxCount::_255, | ||
| 134 | io_default_mode: false, | ||
| 135 | synchro_pin_polarity: false, | ||
| 136 | acquisition_mode: false, | ||
| 137 | max_count_interrupt: false, | ||
| 138 | }; | ||
| 139 | |||
| 140 | let pin_groups: PinGroups<peripherals::TSC> = PinGroups { | ||
| 141 | g1: Some(pin_group1.pin_group), | ||
| 142 | g5: Some(pin_group5.pin_group), | ||
| 143 | ..Default::default() | ||
| 144 | }; | ||
| 145 | |||
| 146 | let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, config, Irqs).unwrap(); | ||
| 147 | |||
| 148 | // ---------- setting up acquisition banks ---------- | ||
| 149 | // sensor0 and sensor1 in this example belong to different TSC-groups, | ||
| 150 | // therefore we can acquire and read them both in one go. | ||
| 151 | let bank1 = touch_controller.create_acquisition_bank(TscAcquisitionBankPins { | ||
| 152 | g1_pin: Some(tsc_sensor0), | ||
| 153 | g5_pin: Some(tsc_sensor1), | ||
| 154 | ..Default::default() | ||
| 155 | }); | ||
| 156 | // `sensor1` and `sensor2` belongs to the same TSC-group, therefore we must make sure to | ||
| 157 | // acquire them one at the time. Therefore, we organize them into different acquisition banks. | ||
| 158 | let bank2 = touch_controller.create_acquisition_bank(TscAcquisitionBankPins { | ||
| 159 | g5_pin: Some(tsc_sensor2), | ||
| 160 | ..Default::default() | ||
| 161 | }); | ||
| 162 | |||
| 163 | // Check if TSC is ready | ||
| 164 | if touch_controller.get_state() != State::Ready { | ||
| 165 | crate::panic!("TSC not ready!"); | ||
| 166 | } | ||
| 167 | |||
| 168 | info!("TSC initialized successfully"); | ||
| 169 | |||
| 170 | // LED2 on the STM32L073RZ nucleo-board (PA5) | ||
| 171 | let mut led = Output::new(context.PA5, Level::High, Speed::Low); | ||
| 172 | |||
| 173 | let mut led_state = false; | ||
| 174 | |||
| 175 | loop { | ||
| 176 | acquire_sensors(&mut touch_controller, &bank1).await; | ||
| 177 | let readings1: TscAcquisitionBankReadings = read_touch_values(&mut touch_controller, &bank1) | ||
| 178 | .await | ||
| 179 | .expect("should be able to read values for bank 1"); | ||
| 180 | acquire_sensors(&mut touch_controller, &bank2).await; | ||
| 181 | let readings2: TscAcquisitionBankReadings = read_touch_values(&mut touch_controller, &bank2) | ||
| 182 | .await | ||
| 183 | .expect("should be able to read values for bank 2"); | ||
| 184 | |||
| 185 | let mut touched_sensors_count = 0; | ||
| 186 | for reading in readings1.iter() { | ||
| 187 | info!("{}", reading); | ||
| 188 | if reading.sensor_value < SENSOR_THRESHOLD { | ||
| 189 | touched_sensors_count += 1; | ||
| 190 | } | ||
| 191 | } | ||
| 192 | for reading in readings2.iter() { | ||
| 193 | info!("{}", reading); | ||
| 194 | if reading.sensor_value < SENSOR_THRESHOLD { | ||
| 195 | touched_sensors_count += 1; | ||
| 196 | } | ||
| 197 | } | ||
| 198 | |||
| 199 | match touched_sensors_count { | ||
| 200 | 0 => { | ||
| 201 | // No sensors touched, turn off the LED | ||
| 202 | led.set_low(); | ||
| 203 | led_state = false; | ||
| 204 | } | ||
| 205 | 1 => { | ||
| 206 | // One sensor touched, blink slowly | ||
| 207 | led_state = !led_state; | ||
| 208 | if led_state { | ||
| 209 | led.set_high(); | ||
| 210 | } else { | ||
| 211 | led.set_low(); | ||
| 212 | } | ||
| 213 | Timer::after_millis(200).await; | ||
| 214 | } | ||
| 215 | 2 => { | ||
| 216 | // Two sensors touched, blink faster | ||
| 217 | led_state = !led_state; | ||
| 218 | if led_state { | ||
| 219 | led.set_high(); | ||
| 220 | } else { | ||
| 221 | led.set_low(); | ||
| 222 | } | ||
| 223 | Timer::after_millis(50).await; | ||
| 224 | } | ||
| 225 | 3 => { | ||
| 226 | // All three sensors touched, LED constantly on | ||
| 227 | led.set_high(); | ||
| 228 | led_state = true; | ||
| 229 | } | ||
| 230 | _ => crate::unreachable!(), // This case should never occur with 3 sensors | ||
| 231 | } | ||
| 232 | } | ||
| 233 | } | ||
diff --git a/examples/stm32l4/.cargo/config.toml b/examples/stm32l4/.cargo/config.toml index 83fc6d6f8..d71fb1517 100644 --- a/examples/stm32l4/.cargo/config.toml +++ b/examples/stm32l4/.cargo/config.toml | |||
| @@ -2,7 +2,8 @@ | |||
| 2 | # replace STM32F429ZITx with your chip as listed in `probe-rs chip list` | 2 | # replace STM32F429ZITx with your chip as listed in `probe-rs chip list` |
| 3 | #runner = "probe-rs run --chip STM32L475VGT6" | 3 | #runner = "probe-rs run --chip STM32L475VGT6" |
| 4 | #runner = "probe-rs run --chip STM32L475VG" | 4 | #runner = "probe-rs run --chip STM32L475VG" |
| 5 | runner = "probe-rs run --chip STM32L4S5QI" | 5 | #runner = "probe-rs run --chip STM32L4S5QI" |
| 6 | runner = "probe-rs run --chip STM32L4R5ZITxP" | ||
| 6 | 7 | ||
| 7 | [build] | 8 | [build] |
| 8 | target = "thumbv7em-none-eabi" | 9 | target = "thumbv7em-none-eabi" |
diff --git a/examples/stm32l4/Cargo.toml b/examples/stm32l4/Cargo.toml index b172878c1..512bb8064 100644 --- a/examples/stm32l4/Cargo.toml +++ b/examples/stm32l4/Cargo.toml | |||
| @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" | |||
| 6 | 6 | ||
| 7 | [dependencies] | 7 | [dependencies] |
| 8 | # Change stm32l4s5vi to your chip name, if necessary. | 8 | # Change stm32l4s5vi to your chip name, if necessary. |
| 9 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l4s5qi", "memory-x", "time-driver-any", "exti", "chrono"] } | 9 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l4r5zi", "memory-x", "time-driver-any", "exti", "chrono"] } |
| 10 | embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } | 10 | embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } |
| 11 | embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } | 11 | embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } |
| 12 | embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768", ] } | 12 | 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 000000000..e463c18a0 --- /dev/null +++ b/examples/stm32l4/README.md | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | # Examples for STM32L4 family | ||
| 2 | Run individual examples with | ||
| 3 | ``` | ||
| 4 | cargo run --bin <module-name> | ||
| 5 | ``` | ||
| 6 | for example | ||
| 7 | ``` | ||
| 8 | cargo run --bin blinky | ||
| 9 | ``` | ||
| 10 | |||
| 11 | ## Checklist before running examples | ||
| 12 | 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. | ||
| 13 | |||
| 14 | * [ ] 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) | ||
| 15 | * [ ] 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. | ||
| 16 | * [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. | ||
| 17 | * [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic | ||
| 18 | |||
| 19 | If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: | ||
| 20 | |||
| 21 | * Which example you are trying to run | ||
| 22 | * Which chip and board you are using | ||
| 23 | |||
| 24 | 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 000000000..ada2c468f --- /dev/null +++ b/examples/stm32l4/src/bin/tsc_async.rs | |||
| @@ -0,0 +1,108 @@ | |||
| 1 | // Example of async TSC (Touch Sensing Controller) that lights an LED when touch is detected. | ||
| 2 | // | ||
| 3 | // This example demonstrates: | ||
| 4 | // 1. Configuring a single TSC channel pin | ||
| 5 | // 2. Using the async TSC interface | ||
| 6 | // 3. Waiting for acquisition completion using `pend_for_acquisition` | ||
| 7 | // 4. Reading touch values and controlling an LED based on the results | ||
| 8 | // | ||
| 9 | // Suggested physical setup on STM32L4R5ZI-P board: | ||
| 10 | // - Connect a 1000pF capacitor between pin PB4 (D25) and GND. This is your sampling capacitor. | ||
| 11 | // - Connect one end of a 1K resistor to pin PB5 (D21) and leave the other end loose. | ||
| 12 | // The loose end will act as the touch sensor which will register your touch. | ||
| 13 | // | ||
| 14 | // The example uses two pins from Group 2 of the TSC: | ||
| 15 | // - PB4 (D25) as the sampling capacitor, TSC group 2 IO1 | ||
| 16 | // - PB5 (D21) as the channel pin, TSC group 2 IO2 | ||
| 17 | // | ||
| 18 | // The program continuously reads the touch sensor value: | ||
| 19 | // - It starts acquisition, waits for completion using `pend_for_acquisition`, and reads the value. | ||
| 20 | // - The LED (connected to PB14) is turned on when touch is detected (sensor value < SENSOR_THRESHOLD). | ||
| 21 | // - Touch values are logged to the console. | ||
| 22 | // | ||
| 23 | // Troubleshooting: | ||
| 24 | // - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. | ||
| 25 | // - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, | ||
| 26 | // pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. | ||
| 27 | // | ||
| 28 | // Note: Configuration values and sampling capacitor value have been determined experimentally. | ||
| 29 | // Optimal values may vary based on your specific hardware setup. | ||
| 30 | |||
| 31 | #![no_std] | ||
| 32 | #![no_main] | ||
| 33 | |||
| 34 | use defmt::*; | ||
| 35 | use embassy_stm32::gpio::{Level, Output, Speed}; | ||
| 36 | use embassy_stm32::tsc::{self, *}; | ||
| 37 | use embassy_stm32::{bind_interrupts, peripherals}; | ||
| 38 | use embassy_time::Timer; | ||
| 39 | use {defmt_rtt as _, panic_probe as _}; | ||
| 40 | |||
| 41 | bind_interrupts!(struct Irqs { | ||
| 42 | TSC => InterruptHandler<embassy_stm32::peripherals::TSC>; | ||
| 43 | }); | ||
| 44 | const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup | ||
| 45 | |||
| 46 | #[embassy_executor::main] | ||
| 47 | async fn main(_spawner: embassy_executor::Spawner) { | ||
| 48 | let device_config = embassy_stm32::Config::default(); | ||
| 49 | let context = embassy_stm32::init(device_config); | ||
| 50 | |||
| 51 | let mut pin_group: PinGroupWithRoles<peripherals::TSC, G2> = PinGroupWithRoles::default(); | ||
| 52 | // D25 | ||
| 53 | pin_group.set_io1::<tsc_pin_roles::Sample>(context.PB4); | ||
| 54 | // D21 | ||
| 55 | let tsc_sensor = pin_group.set_io2::<tsc_pin_roles::Channel>(context.PB5); | ||
| 56 | |||
| 57 | let pin_groups: PinGroups<peripherals::TSC> = PinGroups { | ||
| 58 | g2: Some(pin_group.pin_group), | ||
| 59 | ..Default::default() | ||
| 60 | }; | ||
| 61 | |||
| 62 | let tsc_conf = Config { | ||
| 63 | ct_pulse_high_length: ChargeTransferPulseCycle::_4, | ||
| 64 | ct_pulse_low_length: ChargeTransferPulseCycle::_4, | ||
| 65 | spread_spectrum: false, | ||
| 66 | spread_spectrum_deviation: SSDeviation::new(2).unwrap(), | ||
| 67 | spread_spectrum_prescaler: false, | ||
| 68 | pulse_generator_prescaler: PGPrescalerDivider::_16, | ||
| 69 | max_count_value: MaxCount::_255, | ||
| 70 | io_default_mode: false, | ||
| 71 | synchro_pin_polarity: false, | ||
| 72 | acquisition_mode: false, | ||
| 73 | max_count_interrupt: false, | ||
| 74 | }; | ||
| 75 | |||
| 76 | let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, tsc_conf, Irqs).unwrap(); | ||
| 77 | |||
| 78 | // Check if TSC is ready | ||
| 79 | if touch_controller.get_state() != State::Ready { | ||
| 80 | info!("TSC not ready!"); | ||
| 81 | return; | ||
| 82 | } | ||
| 83 | info!("TSC initialized successfully"); | ||
| 84 | |||
| 85 | let mut led = Output::new(context.PB14, Level::High, Speed::Low); | ||
| 86 | |||
| 87 | let discharge_delay = 1; // ms | ||
| 88 | |||
| 89 | info!("Starting touch_controller interface"); | ||
| 90 | loop { | ||
| 91 | touch_controller.set_active_channels_mask(tsc_sensor.pin.into()); | ||
| 92 | touch_controller.start(); | ||
| 93 | touch_controller.pend_for_acquisition().await; | ||
| 94 | touch_controller.discharge_io(true); | ||
| 95 | Timer::after_millis(discharge_delay).await; | ||
| 96 | |||
| 97 | let group_val = touch_controller.group_get_value(tsc_sensor.pin.group()); | ||
| 98 | info!("Touch value: {}", group_val); | ||
| 99 | |||
| 100 | if group_val < SENSOR_THRESHOLD { | ||
| 101 | led.set_high(); | ||
| 102 | } else { | ||
| 103 | led.set_low(); | ||
| 104 | } | ||
| 105 | |||
| 106 | Timer::after_millis(100).await; | ||
| 107 | } | ||
| 108 | } | ||
diff --git a/examples/stm32l4/src/bin/tsc_blocking.rs b/examples/stm32l4/src/bin/tsc_blocking.rs new file mode 100644 index 000000000..76aba55ba --- /dev/null +++ b/examples/stm32l4/src/bin/tsc_blocking.rs | |||
| @@ -0,0 +1,147 @@ | |||
| 1 | // # Example of blocking TSC (Touch Sensing Controller) that lights an LED when touch is detected | ||
| 2 | // | ||
| 3 | // This example demonstrates how to use the Touch Sensing Controller (TSC) in blocking mode on an STM32L4R5ZI-P board. | ||
| 4 | // | ||
| 5 | // ## This example demonstrates: | ||
| 6 | // | ||
| 7 | // 1. Configuring a single TSC channel pin | ||
| 8 | // 2. Using the blocking TSC interface with polling | ||
| 9 | // 3. Waiting for acquisition completion using `poll_for_acquisition` | ||
| 10 | // 4. Reading touch values and controlling an LED based on the results | ||
| 11 | // | ||
| 12 | // ## Suggested physical setup on STM32L4R5ZI-P board: | ||
| 13 | // | ||
| 14 | // - Connect a 1000pF capacitor between pin PB4 (D25) and GND. This is your sampling capacitor. | ||
| 15 | // - Connect one end of a 1K resistor to pin PB5 (D21) and leave the other end loose. | ||
| 16 | // The loose end will act as the touch sensor which will register your touch. | ||
| 17 | // | ||
| 18 | // ## Pin Configuration: | ||
| 19 | // | ||
| 20 | // The example uses two pins from Group 2 of the TSC: | ||
| 21 | // - PB4 (D25) as the sampling capacitor, TSC group 2 IO1 | ||
| 22 | // - PB5 (D21) as the channel pin, TSC group 2 IO2 | ||
| 23 | // | ||
| 24 | // ## Program Behavior: | ||
| 25 | // | ||
| 26 | // The program continuously reads the touch sensor value: | ||
| 27 | // - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value. | ||
| 28 | // - The LED (connected to PB14) is turned on when touch is detected (sensor value < SENSOR_THRESHOLD). | ||
| 29 | // - Touch values are logged to the console. | ||
| 30 | // | ||
| 31 | // ## Troubleshooting: | ||
| 32 | // | ||
| 33 | // - If touch is not detected, try adjusting the SENSOR_THRESHOLD value (currently set to 25). | ||
| 34 | // - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, | ||
| 35 | // pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. | ||
| 36 | // - Be aware that for some boards, there might be overlapping concerns between some pins, | ||
| 37 | // such as UART connections for the programmer. No errors or warnings will be emitted if you | ||
| 38 | // try to use such a pin for TSC, but you may get strange sensor readings. | ||
| 39 | // | ||
| 40 | // Note: Configuration values and sampling capacitor value have been determined experimentally. | ||
| 41 | // Optimal values may vary based on your specific hardware setup. Refer to the official | ||
| 42 | // STM32L4R5ZI-P datasheet and user manuals for more information on pin configurations and TSC functionality. | ||
| 43 | |||
| 44 | #![no_std] | ||
| 45 | #![no_main] | ||
| 46 | |||
| 47 | use defmt::*; | ||
| 48 | use embassy_stm32::gpio::{Level, Output, Speed}; | ||
| 49 | use embassy_stm32::tsc::{self, *}; | ||
| 50 | use embassy_stm32::{mode, peripherals}; | ||
| 51 | use embassy_time::Timer; | ||
| 52 | use {defmt_rtt as _, panic_probe as _}; | ||
| 53 | |||
| 54 | const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup | ||
| 55 | |||
| 56 | #[embassy_executor::main] | ||
| 57 | async fn main(_spawner: embassy_executor::Spawner) { | ||
| 58 | let device_config = embassy_stm32::Config::default(); | ||
| 59 | let context = embassy_stm32::init(device_config); | ||
| 60 | |||
| 61 | let tsc_conf = Config { | ||
| 62 | ct_pulse_high_length: ChargeTransferPulseCycle::_4, | ||
| 63 | ct_pulse_low_length: ChargeTransferPulseCycle::_4, | ||
| 64 | spread_spectrum: false, | ||
| 65 | spread_spectrum_deviation: SSDeviation::new(2).unwrap(), | ||
| 66 | spread_spectrum_prescaler: false, | ||
| 67 | pulse_generator_prescaler: PGPrescalerDivider::_16, | ||
| 68 | max_count_value: MaxCount::_255, | ||
| 69 | io_default_mode: false, | ||
| 70 | synchro_pin_polarity: false, | ||
| 71 | acquisition_mode: false, | ||
| 72 | max_count_interrupt: false, | ||
| 73 | }; | ||
| 74 | |||
| 75 | let mut g2: PinGroupWithRoles<peripherals::TSC, G2> = PinGroupWithRoles::default(); | ||
| 76 | // D25 | ||
| 77 | g2.set_io1::<tsc_pin_roles::Sample>(context.PB4); | ||
| 78 | // D21 | ||
| 79 | let tsc_sensor = g2.set_io2::<tsc_pin_roles::Channel>(context.PB5); | ||
| 80 | |||
| 81 | let pin_groups: PinGroups<peripherals::TSC> = PinGroups { | ||
| 82 | g2: Some(g2.pin_group), | ||
| 83 | ..Default::default() | ||
| 84 | }; | ||
| 85 | |||
| 86 | let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, pin_groups, tsc_conf).unwrap(); | ||
| 87 | |||
| 88 | // Check if TSC is ready | ||
| 89 | if touch_controller.get_state() != State::Ready { | ||
| 90 | crate::panic!("TSC not ready!"); | ||
| 91 | } | ||
| 92 | info!("TSC initialized successfully"); | ||
| 93 | |||
| 94 | let mut led = Output::new(context.PB14, Level::High, Speed::Low); | ||
| 95 | |||
| 96 | // smaller sample capacitor discharge faster and can be used with shorter delay. | ||
| 97 | let discharge_delay = 5; // ms | ||
| 98 | |||
| 99 | // the interval at which the loop polls for new touch sensor values | ||
| 100 | let polling_interval = 100; // ms | ||
| 101 | |||
| 102 | info!("polling for touch"); | ||
| 103 | loop { | ||
| 104 | touch_controller.set_active_channels_mask(tsc_sensor.pin.into()); | ||
| 105 | touch_controller.start(); | ||
| 106 | touch_controller.poll_for_acquisition(); | ||
| 107 | touch_controller.discharge_io(true); | ||
| 108 | Timer::after_millis(discharge_delay).await; | ||
| 109 | |||
| 110 | match read_touch_value(&mut touch_controller, tsc_sensor.pin).await { | ||
| 111 | Some(v) => { | ||
| 112 | info!("sensor value {}", v); | ||
| 113 | if v < SENSOR_THRESHOLD { | ||
| 114 | led.set_high(); | ||
| 115 | } else { | ||
| 116 | led.set_low(); | ||
| 117 | } | ||
| 118 | } | ||
| 119 | None => led.set_low(), | ||
| 120 | } | ||
| 121 | |||
| 122 | Timer::after_millis(polling_interval).await; | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10; | ||
| 127 | |||
| 128 | // attempt to read group status and delay when still ongoing | ||
| 129 | async fn read_touch_value( | ||
| 130 | touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Blocking>, | ||
| 131 | sensor_pin: TscIOPin, | ||
| 132 | ) -> Option<u16> { | ||
| 133 | for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS { | ||
| 134 | match touch_controller.group_get_status(sensor_pin.group()) { | ||
| 135 | GroupStatus::Complete => { | ||
| 136 | return Some(touch_controller.group_get_value(sensor_pin.group())); | ||
| 137 | } | ||
| 138 | GroupStatus::Ongoing => { | ||
| 139 | // if you end up here a lot, then you prob need to increase discharge_delay | ||
| 140 | // or consider changing the code to adjust the discharge_delay dynamically | ||
| 141 | info!("Acquisition still ongoing"); | ||
| 142 | Timer::after_millis(1).await; | ||
| 143 | } | ||
| 144 | } | ||
| 145 | } | ||
| 146 | None | ||
| 147 | } | ||
diff --git a/examples/stm32l4/src/bin/tsc_multipin.rs b/examples/stm32l4/src/bin/tsc_multipin.rs new file mode 100644 index 000000000..20a559514 --- /dev/null +++ b/examples/stm32l4/src/bin/tsc_multipin.rs | |||
| @@ -0,0 +1,222 @@ | |||
| 1 | // # Example of TSC (Touch Sensing Controller) using multiple pins from the same TSC group | ||
| 2 | // | ||
| 3 | // 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. | ||
| 4 | // | ||
| 5 | // ## Key Concepts | ||
| 6 | // | ||
| 7 | // - Only one TSC pin for each TSC group can be acquired and read at a time. | ||
| 8 | // - To control which channel pins are acquired and read, we must write a mask before initiating an acquisition. | ||
| 9 | // - We organize channel pins into acquisition banks to manage this process efficiently. | ||
| 10 | // - Each acquisition bank can contain exactly one channel pin per TSC group and will contain the relevant mask. | ||
| 11 | // | ||
| 12 | // ## This example demonstrates how to: | ||
| 13 | // | ||
| 14 | // 1. Configure multiple channel pins within a single TSC group | ||
| 15 | // 2. Use the set_active_channels method to switch between different channels | ||
| 16 | // 3. Read and interpret touch values from multiple channels in the same group | ||
| 17 | // | ||
| 18 | // ## Suggested physical setup on STM32L4R5ZI-P board: | ||
| 19 | // | ||
| 20 | // - Connect a 1000pF capacitor between pin PB12 (D19) and GND. This is the sampling capacitor for TSC group 1. | ||
| 21 | // - Connect one end of a 1K resistor to pin PB13 (D18) and leave the other end loose. This will act as a touch sensor. | ||
| 22 | // - Connect a 1000pF capacitor between pin PB4 (D25) and GND. This is the sampling capacitor for TSC group 2. | ||
| 23 | // - Connect one end of a 1K resistor to pin PB5 (D22) and leave the other end loose. This will act as a touch sensor. | ||
| 24 | // - Connect one end of another 1K resistor to pin PB6 (D71) and leave the other end loose. This will act as a touch sensor. | ||
| 25 | // | ||
| 26 | // ## Pin Configuration: | ||
| 27 | // | ||
| 28 | // The example uses pins from two TSC groups: | ||
| 29 | // | ||
| 30 | // - Group 1: | ||
| 31 | // - PB12 (D19) as sampling capacitor (TSC group 1 IO1) | ||
| 32 | // - PB13 (D18) as channel (TSC group 1 IO2) | ||
| 33 | // - Group 2: | ||
| 34 | // - PB4 (D25) as sampling capacitor (TSC group 2 IO1) | ||
| 35 | // - PB5 (D22) as channel (TSC group 2 IO2) | ||
| 36 | // - PB6 (D71) as channel (TSC group 2 IO3) | ||
| 37 | // | ||
| 38 | // 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. | ||
| 39 | // | ||
| 40 | // ## Program Behavior: | ||
| 41 | // | ||
| 42 | // The program reads the designated channel pins and adjusts the LED (connected to PB14) blinking pattern based on which sensor(s) are touched: | ||
| 43 | // | ||
| 44 | // - No touch: LED off | ||
| 45 | // - One sensor touched: Slow blinking | ||
| 46 | // - Two sensors touched: Fast blinking | ||
| 47 | // - Three sensors touched: LED constantly on | ||
| 48 | // | ||
| 49 | // ## Troubleshooting: | ||
| 50 | // | ||
| 51 | // - If touch is not detected, try adjusting the SENSOR_THRESHOLD value (currently set to 20). | ||
| 52 | // - 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. | ||
| 53 | // - Be aware that for some boards there will be overlapping concerns between some pins, for | ||
| 54 | // example UART connection for the programmer to the MCU and a TSC pin. No errors or warning will | ||
| 55 | // be emitted if you try to use such a pin for TSC, but you will get strange sensor readings. | ||
| 56 | // | ||
| 57 | // 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. | ||
| 58 | |||
| 59 | #![no_std] | ||
| 60 | #![no_main] | ||
| 61 | |||
| 62 | use defmt::*; | ||
| 63 | use embassy_stm32::gpio::{Level, Output, Speed}; | ||
| 64 | use embassy_stm32::tsc::{self, *}; | ||
| 65 | use embassy_stm32::{bind_interrupts, mode, peripherals}; | ||
| 66 | use embassy_time::Timer; | ||
| 67 | use {defmt_rtt as _, panic_probe as _}; | ||
| 68 | |||
| 69 | bind_interrupts!(struct Irqs { | ||
| 70 | TSC => InterruptHandler<embassy_stm32::peripherals::TSC>; | ||
| 71 | }); | ||
| 72 | |||
| 73 | const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10; | ||
| 74 | |||
| 75 | async fn read_touch_values( | ||
| 76 | touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Async>, | ||
| 77 | tsc_acquisition_bank: &TscAcquisitionBank, | ||
| 78 | ) -> Option<TscAcquisitionBankReadings> { | ||
| 79 | for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS { | ||
| 80 | let status = touch_controller.get_acquisition_bank_status(tsc_acquisition_bank); | ||
| 81 | if status.all_complete() { | ||
| 82 | let r = touch_controller.get_acquisition_bank_values(tsc_acquisition_bank); | ||
| 83 | return Some(r); | ||
| 84 | } else { | ||
| 85 | info!("Acquisition still ongoing"); | ||
| 86 | Timer::after_millis(1).await; | ||
| 87 | } | ||
| 88 | } | ||
| 89 | info!("Acquisition failed after {} attempts", MAX_GROUP_STATUS_READ_ATTEMPTS); | ||
| 90 | None | ||
| 91 | } | ||
| 92 | |||
| 93 | const SENSOR_THRESHOLD: u16 = 20; | ||
| 94 | |||
| 95 | async fn acquire_sensors( | ||
| 96 | touch_controller: &mut Tsc<'static, peripherals::TSC, mode::Async>, | ||
| 97 | tsc_acquisition_bank: &TscAcquisitionBank, | ||
| 98 | ) { | ||
| 99 | touch_controller.set_active_channels_mask(tsc_acquisition_bank.mask()); | ||
| 100 | touch_controller.start(); | ||
| 101 | touch_controller.pend_for_acquisition().await; | ||
| 102 | touch_controller.discharge_io(true); | ||
| 103 | let discharge_delay = 1; // ms | ||
| 104 | Timer::after_millis(discharge_delay).await; | ||
| 105 | } | ||
| 106 | |||
| 107 | #[embassy_executor::main] | ||
| 108 | async fn main(_spawner: embassy_executor::Spawner) { | ||
| 109 | let device_config = embassy_stm32::Config::default(); | ||
| 110 | let context = embassy_stm32::init(device_config); | ||
| 111 | |||
| 112 | // ---------- initial configuration of TSC ---------- | ||
| 113 | let mut g1: PinGroupWithRoles<peripherals::TSC, G1> = PinGroupWithRoles::default(); | ||
| 114 | g1.set_io1::<tsc_pin_roles::Sample>(context.PB12); | ||
| 115 | let sensor0 = g1.set_io2::<tsc_pin_roles::Channel>(context.PB13); | ||
| 116 | |||
| 117 | let mut g2: PinGroupWithRoles<peripherals::TSC, G2> = PinGroupWithRoles::default(); | ||
| 118 | g2.set_io1::<tsc_pin_roles::Sample>(context.PB4); | ||
| 119 | let sensor1 = g2.set_io2(context.PB5); | ||
| 120 | let sensor2 = g2.set_io3(context.PB6); | ||
| 121 | |||
| 122 | let config = tsc::Config { | ||
| 123 | ct_pulse_high_length: ChargeTransferPulseCycle::_16, | ||
| 124 | ct_pulse_low_length: ChargeTransferPulseCycle::_16, | ||
| 125 | spread_spectrum: false, | ||
| 126 | spread_spectrum_deviation: SSDeviation::new(2).unwrap(), | ||
| 127 | spread_spectrum_prescaler: false, | ||
| 128 | pulse_generator_prescaler: PGPrescalerDivider::_16, | ||
| 129 | max_count_value: MaxCount::_255, | ||
| 130 | io_default_mode: false, | ||
| 131 | synchro_pin_polarity: false, | ||
| 132 | acquisition_mode: false, | ||
| 133 | max_count_interrupt: false, | ||
| 134 | }; | ||
| 135 | |||
| 136 | let pin_groups: PinGroups<peripherals::TSC> = PinGroups { | ||
| 137 | g1: Some(g1.pin_group), | ||
| 138 | g2: Some(g2.pin_group), | ||
| 139 | ..Default::default() | ||
| 140 | }; | ||
| 141 | |||
| 142 | let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, config, Irqs).unwrap(); | ||
| 143 | |||
| 144 | // ---------- setting up acquisition banks ---------- | ||
| 145 | // sensor0 and sensor1 belong to different TSC-groups, therefore we can acquire and | ||
| 146 | // read them both in one go. | ||
| 147 | let bank1 = touch_controller.create_acquisition_bank(TscAcquisitionBankPins { | ||
| 148 | g1_pin: Some(sensor0), | ||
| 149 | g2_pin: Some(sensor1), | ||
| 150 | ..Default::default() | ||
| 151 | }); | ||
| 152 | // `sensor1` and `sensor2` belongs to the same TSC-group, therefore we must make sure to | ||
| 153 | // acquire them one at the time. We do this by organizing them into different acquisition banks. | ||
| 154 | let bank2 = touch_controller.create_acquisition_bank(TscAcquisitionBankPins { | ||
| 155 | g2_pin: Some(sensor2), | ||
| 156 | ..Default::default() | ||
| 157 | }); | ||
| 158 | |||
| 159 | // Check if TSC is ready | ||
| 160 | if touch_controller.get_state() != State::Ready { | ||
| 161 | crate::panic!("TSC not ready!"); | ||
| 162 | } | ||
| 163 | |||
| 164 | info!("TSC initialized successfully"); | ||
| 165 | |||
| 166 | let mut led = Output::new(context.PB14, Level::High, Speed::Low); | ||
| 167 | |||
| 168 | let mut led_state = false; | ||
| 169 | |||
| 170 | loop { | ||
| 171 | acquire_sensors(&mut touch_controller, &bank1).await; | ||
| 172 | let readings1: TscAcquisitionBankReadings = read_touch_values(&mut touch_controller, &bank1) | ||
| 173 | .await | ||
| 174 | .expect("should be able to read values for bank 1"); | ||
| 175 | acquire_sensors(&mut touch_controller, &bank2).await; | ||
| 176 | let readings2: TscAcquisitionBankReadings = read_touch_values(&mut touch_controller, &bank2) | ||
| 177 | .await | ||
| 178 | .expect("should be able to read values for bank 2"); | ||
| 179 | |||
| 180 | let mut touched_sensors_count = 0; | ||
| 181 | for reading in readings1.iter().chain(readings2.iter()) { | ||
| 182 | info!("{}", reading); | ||
| 183 | if reading.sensor_value < SENSOR_THRESHOLD { | ||
| 184 | touched_sensors_count += 1; | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | match touched_sensors_count { | ||
| 189 | 0 => { | ||
| 190 | // No sensors touched, turn off the LED | ||
| 191 | led.set_low(); | ||
| 192 | led_state = false; | ||
| 193 | } | ||
| 194 | 1 => { | ||
| 195 | // One sensor touched, blink slowly | ||
| 196 | led_state = !led_state; | ||
| 197 | if led_state { | ||
| 198 | led.set_high(); | ||
| 199 | } else { | ||
| 200 | led.set_low(); | ||
| 201 | } | ||
| 202 | Timer::after_millis(200).await; | ||
| 203 | } | ||
| 204 | 2 => { | ||
| 205 | // Two sensors touched, blink faster | ||
| 206 | led_state = !led_state; | ||
| 207 | if led_state { | ||
| 208 | led.set_high(); | ||
| 209 | } else { | ||
| 210 | led.set_low(); | ||
| 211 | } | ||
| 212 | Timer::after_millis(50).await; | ||
| 213 | } | ||
| 214 | 3 => { | ||
| 215 | // All three sensors touched, LED constantly on | ||
| 216 | led.set_high(); | ||
| 217 | led_state = true; | ||
| 218 | } | ||
| 219 | _ => crate::unreachable!(), // This case should never occur with 3 sensors | ||
| 220 | } | ||
| 221 | } | ||
| 222 | } | ||
diff --git a/examples/stm32u5/src/bin/tsc.rs b/examples/stm32u5/src/bin/tsc.rs index eb15d275a..800486665 100644 --- a/examples/stm32u5/src/bin/tsc.rs +++ b/examples/stm32u5/src/bin/tsc.rs | |||
| @@ -2,8 +2,8 @@ | |||
| 2 | #![no_main] | 2 | #![no_main] |
| 3 | 3 | ||
| 4 | use defmt::*; | 4 | use defmt::*; |
| 5 | use embassy_stm32::bind_interrupts; | ||
| 6 | use embassy_stm32::tsc::{self, *}; | 5 | use embassy_stm32::tsc::{self, *}; |
| 6 | use embassy_stm32::{bind_interrupts, peripherals}; | ||
| 7 | use embassy_time::Timer; | 7 | use embassy_time::Timer; |
| 8 | use {defmt_rtt as _, panic_probe as _}; | 8 | use {defmt_rtt as _, panic_probe as _}; |
| 9 | 9 | ||
| @@ -33,63 +33,52 @@ async fn main(_spawner: embassy_executor::Spawner) { | |||
| 33 | synchro_pin_polarity: false, | 33 | synchro_pin_polarity: false, |
| 34 | acquisition_mode: false, | 34 | acquisition_mode: false, |
| 35 | max_count_interrupt: false, | 35 | max_count_interrupt: false, |
| 36 | channel_ios: TscIOPin::Group2Io2 | TscIOPin::Group7Io3, | ||
| 37 | shield_ios: TscIOPin::Group1Io3.into(), | ||
| 38 | sampling_ios: TscIOPin::Group1Io2 | TscIOPin::Group2Io1 | TscIOPin::Group7Io2, | ||
| 39 | }; | 36 | }; |
| 40 | 37 | ||
| 41 | let mut g1: PinGroup<embassy_stm32::peripherals::TSC, G1> = PinGroup::new(); | 38 | let mut g1: PinGroupWithRoles<peripherals::TSC, G1> = PinGroupWithRoles::default(); |
| 42 | g1.set_io2(context.PB13, PinType::Sample); | 39 | g1.set_io2::<tsc_pin_roles::Sample>(context.PB13); |
| 43 | g1.set_io3(context.PB14, PinType::Shield); | 40 | g1.set_io3::<tsc_pin_roles::Shield>(context.PB14); |
| 44 | 41 | ||
| 45 | let mut g2: PinGroup<embassy_stm32::peripherals::TSC, G2> = PinGroup::new(); | 42 | let mut g2: PinGroupWithRoles<peripherals::TSC, G2> = PinGroupWithRoles::default(); |
| 46 | g2.set_io1(context.PB4, PinType::Sample); | 43 | g2.set_io1::<tsc_pin_roles::Sample>(context.PB4); |
| 47 | g2.set_io2(context.PB5, PinType::Channel); | 44 | let sensor0 = g2.set_io2(context.PB5); |
| 48 | 45 | ||
| 49 | let mut g7: PinGroup<embassy_stm32::peripherals::TSC, G7> = PinGroup::new(); | 46 | let mut g7: PinGroupWithRoles<peripherals::TSC, G7> = PinGroupWithRoles::default(); |
| 50 | g7.set_io2(context.PE3, PinType::Sample); | 47 | g7.set_io2::<tsc_pin_roles::Sample>(context.PE3); |
| 51 | g7.set_io3(context.PE4, PinType::Channel); | 48 | let sensor1 = g7.set_io3(context.PE4); |
| 52 | 49 | ||
| 53 | let mut touch_controller = tsc::Tsc::new_async( | 50 | let pin_groups: PinGroups<peripherals::TSC> = PinGroups { |
| 54 | context.TSC, | 51 | g1: Some(g1.pin_group), |
| 55 | Some(g1), | 52 | g2: Some(g2.pin_group), |
| 56 | Some(g2), | 53 | g7: Some(g7.pin_group), |
| 57 | None, | 54 | ..Default::default() |
| 58 | None, | 55 | }; |
| 59 | None, | 56 | |
| 60 | None, | 57 | let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, config, Irqs).unwrap(); |
| 61 | Some(g7), | ||
| 62 | None, | ||
| 63 | config, | ||
| 64 | Irqs, | ||
| 65 | ); | ||
| 66 | 58 | ||
| 67 | touch_controller.discharge_io(true); | 59 | let acquisition_bank = touch_controller.create_acquisition_bank(TscAcquisitionBankPins { |
| 68 | Timer::after_millis(1).await; | 60 | g2_pin: Some(sensor0), |
| 61 | g7_pin: Some(sensor1), | ||
| 62 | ..Default::default() | ||
| 63 | }); | ||
| 69 | 64 | ||
| 70 | touch_controller.start(); | 65 | touch_controller.set_active_channels_bank(&acquisition_bank); |
| 71 | 66 | ||
| 72 | let mut group_two_val = 0; | ||
| 73 | let mut group_seven_val = 0; | ||
| 74 | info!("Starting touch_controller interface"); | 67 | info!("Starting touch_controller interface"); |
| 75 | loop { | 68 | loop { |
| 69 | touch_controller.start(); | ||
| 76 | touch_controller.pend_for_acquisition().await; | 70 | touch_controller.pend_for_acquisition().await; |
| 77 | touch_controller.discharge_io(true); | 71 | touch_controller.discharge_io(true); |
| 78 | Timer::after_millis(1).await; | 72 | Timer::after_millis(1).await; |
| 79 | 73 | ||
| 80 | if touch_controller.group_get_status(Group::Two) == GroupStatus::Complete { | 74 | let status = touch_controller.get_acquisition_bank_status(&acquisition_bank); |
| 81 | group_two_val = touch_controller.group_get_value(Group::Two); | ||
| 82 | } | ||
| 83 | 75 | ||
| 84 | if touch_controller.group_get_status(Group::Seven) == GroupStatus::Complete { | 76 | if status.all_complete() { |
| 85 | group_seven_val = touch_controller.group_get_value(Group::Seven); | 77 | let read_values = touch_controller.get_acquisition_bank_values(&acquisition_bank); |
| 78 | let group2_reading = read_values.get_group_reading(Group::Two).unwrap(); | ||
| 79 | let group7_reading = read_values.get_group_reading(Group::Seven).unwrap(); | ||
| 80 | info!("group 2 value: {}", group2_reading.sensor_value); | ||
| 81 | info!("group 7 value: {}", group7_reading.sensor_value); | ||
| 86 | } | 82 | } |
| 87 | |||
| 88 | info!( | ||
| 89 | "Group Two value: {}, Group Seven value: {},", | ||
| 90 | group_two_val, group_seven_val | ||
| 91 | ); | ||
| 92 | |||
| 93 | touch_controller.start(); | ||
| 94 | } | 83 | } |
| 95 | } | 84 | } |
