STM32-TSC: enable discriminating between pins within same TSC group and improve TSC library in general

This commit is contained in:
michel 2024-08-07 21:58:49 +02:00
parent 1a1d5c4689
commit 721c6820d4
27 changed files with 3007 additions and 1438 deletions

3
.gitignore vendored
View File

@ -5,4 +5,7 @@ Cargo.lock
third_party
/Cargo.toml
out/
# editor artifacts
.zed
.neoconf.json
*.vim

View File

@ -0,0 +1,209 @@
#[cfg(any(tsc_v2, tsc_v3))]
use super::pin_groups::G7;
#[cfg(tsc_v3)]
use super::pin_groups::G8;
use super::pin_groups::{tsc_pin_roles, G1, G2, G3, G4, G5, G6};
use super::tsc_io_pin::*;
use super::types::{Group, GroupStatus};
use super::TSC_NUM_GROUPS;
/// Represents a collection of TSC (Touch Sensing Controller) pins for an acquisition bank.
///
/// This struct holds optional `TscIOPin` values for each TSC group, allowing for flexible
/// configuration of TSC acquisition banks. Each field corresponds to a specific TSC group
/// and can be set to `Some(TscIOPin)` if that group is to be included in the acquisition,
/// or `None` if it should be excluded.
#[allow(missing_docs)]
#[derive(Default)]
pub struct TscAcquisitionBankPins {
pub g1_pin: Option<TscIOPinWithRole<G1, tsc_pin_roles::Channel>>,
pub g2_pin: Option<TscIOPinWithRole<G2, tsc_pin_roles::Channel>>,
pub g3_pin: Option<TscIOPinWithRole<G3, tsc_pin_roles::Channel>>,
pub g4_pin: Option<TscIOPinWithRole<G4, tsc_pin_roles::Channel>>,
pub g5_pin: Option<TscIOPinWithRole<G5, tsc_pin_roles::Channel>>,
pub g6_pin: Option<TscIOPinWithRole<G6, tsc_pin_roles::Channel>>,
#[cfg(any(tsc_v2, tsc_v3))]
pub g7_pin: Option<TscIOPinWithRole<G7, tsc_pin_roles::Channel>>,
#[cfg(tsc_v3)]
pub g8_pin: Option<TscIOPinWithRole<G8, tsc_pin_roles::Channel>>,
}
impl TscAcquisitionBankPins {
/// Returns an iterator over the pins in this acquisition bank.
///
/// This method allows for easy traversal of all configured pins in the bank.
pub fn iter(&self) -> TscAcquisitionBankPinsIterator {
TscAcquisitionBankPinsIterator(TscAcquisitionBankIterator::new(self))
}
}
/// Iterator for TSC acquisition banks.
///
/// This iterator allows traversing through the pins of a `TscAcquisitionBankPins` struct,
/// yielding each configured pin in order of the TSC groups.
pub struct TscAcquisitionBankIterator<'a> {
pins: &'a TscAcquisitionBankPins,
current_group: u8,
}
impl<'a> TscAcquisitionBankIterator<'a> {
fn new(pins: &'a TscAcquisitionBankPins) -> Self {
Self { pins, current_group: 0 }
}
fn next_pin(&mut self) -> Option<TscIOPin> {
while self.current_group < TSC_NUM_GROUPS as u8 {
let pin = match self.current_group {
0 => self.pins.g1_pin.map(TscIOPinWithRole::get_pin),
1 => self.pins.g2_pin.map(TscIOPinWithRole::get_pin),
2 => self.pins.g3_pin.map(TscIOPinWithRole::get_pin),
3 => self.pins.g4_pin.map(TscIOPinWithRole::get_pin),
4 => self.pins.g5_pin.map(TscIOPinWithRole::get_pin),
5 => self.pins.g6_pin.map(TscIOPinWithRole::get_pin),
#[cfg(any(tsc_v2, tsc_v3))]
6 => self.pins.g7_pin.map(TscIOPinWithRole::get_pin),
#[cfg(tsc_v3)]
7 => self.pins.g8_pin.map(TscIOPinWithRole::get_pin),
_ => None,
};
self.current_group += 1;
if pin.is_some() {
return pin;
}
}
None
}
}
/// Iterator for TSC acquisition bank pins.
///
/// This iterator yields `TscIOPin` values for each configured pin in the acquisition bank.
pub struct TscAcquisitionBankPinsIterator<'a>(TscAcquisitionBankIterator<'a>);
impl<'a> Iterator for TscAcquisitionBankPinsIterator<'a> {
type Item = TscIOPin;
fn next(&mut self) -> Option<Self::Item> {
self.0.next_pin()
}
}
impl TscAcquisitionBankPins {
/// Returns an iterator over the available pins in the bank
pub fn pins_iterator(&self) -> TscAcquisitionBankPinsIterator {
TscAcquisitionBankPinsIterator(TscAcquisitionBankIterator::new(self))
}
}
/// Represents a collection of TSC pins to be acquired simultaneously.
///
/// This struct contains a set of pins to be used in a TSC acquisition with a pre-computed and
/// verified mask for efficiently setting up the TSC peripheral before performing an acquisition.
/// It ensures that only one channel pin per TSC group is included, adhering to hardware limitations.
pub struct TscAcquisitionBank {
pub(super) pins: TscAcquisitionBankPins,
pub(super) mask: u32,
}
impl TscAcquisitionBank {
/// Returns an iterator over the available pins in the bank.
pub fn pins_iterator(&self) -> TscAcquisitionBankPinsIterator {
self.pins.pins_iterator()
}
/// Returns the mask for this bank.
pub fn mask(&self) -> u32 {
self.mask
}
/// Retrieves the TSC I/O pin for a given group in this acquisition bank.
///
/// # Arguments
/// * `group` - The TSC group to retrieve the pin for.
///
/// # Returns
/// An `Option<TscIOPin>` containing the pin if it exists for the given group, or `None` if not.
pub fn get_pin(&self, group: Group) -> Option<TscIOPin> {
match group {
Group::One => self.pins.g1_pin.map(|p| p.pin),
Group::Two => self.pins.g2_pin.map(|p| p.pin),
Group::Three => self.pins.g3_pin.map(|p| p.pin),
Group::Four => self.pins.g4_pin.map(|p| p.pin),
Group::Five => self.pins.g5_pin.map(|p| p.pin),
Group::Six => self.pins.g6_pin.map(|p| p.pin),
#[cfg(any(tsc_v2, tsc_v3))]
Group::Seven => self.pins.g7_pin.map(|p| p.pin),
#[cfg(tsc_v3)]
Group::Eight => self.pins.g8_pin.map(|p| p.pin),
}
}
}
/// Represents the status of all TSC groups in an acquisition bank
#[derive(Default)]
pub struct TscAcquisitionBankStatus {
pub(super) groups: [Option<GroupStatus>; TSC_NUM_GROUPS],
}
impl TscAcquisitionBankStatus {
/// Check if all groups in the bank are complete
pub fn all_complete(&self) -> bool {
self.groups
.iter()
.all(|&status| status.map_or(true, |s| s == GroupStatus::Complete))
}
/// Check if any group in the bank is ongoing
pub fn any_ongoing(&self) -> bool {
self.groups.iter().any(|&status| status == Some(GroupStatus::Ongoing))
}
/// Get the status of a specific group, if the group is present in the bank
pub fn get_group_status(&self, group: Group) -> Option<GroupStatus> {
let index: usize = group.into();
self.groups[index]
}
/// Iterator for groups present in the bank
pub fn iter(&self) -> impl Iterator<Item = (Group, GroupStatus)> + '_ {
self.groups.iter().enumerate().filter_map(|(group_num, status)| {
status.and_then(|s| Group::try_from(group_num).ok().map(|group| (group, s)))
})
}
}
/// Represents the result of a Touch Sensing Controller (TSC) acquisition for a specific pin.
///
/// This struct contains a reference to the `TscIOPin` from which a value was read,
/// along with the actual sensor reading for that pin. It provides a convenient way
/// to associate TSC readings with their corresponding pins after an acquisition.
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Clone, Copy, Debug)]
pub struct TscChannelReading {
/// The sensor reading value obtained from the TSC acquisition.
/// Lower values typically indicate a detected touch, while higher values indicate no touch.
pub sensor_value: u16,
/// The `TscIOPin` associated with this reading.
/// This allows for easy identification of which pin the reading corresponds to.
pub tsc_pin: TscIOPin,
}
/// Represents the readings from all TSC groups
#[derive(Default)]
pub struct TscAcquisitionBankReadings {
pub(super) groups: [Option<TscChannelReading>; TSC_NUM_GROUPS],
}
impl TscAcquisitionBankReadings {
/// Get the reading for a specific group, if the group is present in the bank
pub fn get_group_reading(&self, group: Group) -> Option<TscChannelReading> {
let index: usize = group.into();
self.groups[index]
}
/// Iterator for readings for groups present in the bank
pub fn iter(&self) -> impl Iterator<Item = TscChannelReading> + '_ {
self.groups.iter().filter_map(|&x| x)
}
}

View File

@ -0,0 +1,175 @@
/// Charge transfer pulse cycles
#[allow(missing_docs)]
#[derive(Copy, Clone, PartialEq)]
pub enum ChargeTransferPulseCycle {
_1,
_2,
_3,
_4,
_5,
_6,
_7,
_8,
_9,
_10,
_11,
_12,
_13,
_14,
_15,
_16,
}
impl Into<u8> for ChargeTransferPulseCycle {
fn into(self) -> u8 {
match self {
ChargeTransferPulseCycle::_1 => 0,
ChargeTransferPulseCycle::_2 => 1,
ChargeTransferPulseCycle::_3 => 2,
ChargeTransferPulseCycle::_4 => 3,
ChargeTransferPulseCycle::_5 => 4,
ChargeTransferPulseCycle::_6 => 5,
ChargeTransferPulseCycle::_7 => 6,
ChargeTransferPulseCycle::_8 => 7,
ChargeTransferPulseCycle::_9 => 8,
ChargeTransferPulseCycle::_10 => 9,
ChargeTransferPulseCycle::_11 => 10,
ChargeTransferPulseCycle::_12 => 11,
ChargeTransferPulseCycle::_13 => 12,
ChargeTransferPulseCycle::_14 => 13,
ChargeTransferPulseCycle::_15 => 14,
ChargeTransferPulseCycle::_16 => 15,
}
}
}
/// Max count
#[allow(missing_docs)]
#[derive(Copy, Clone)]
pub enum MaxCount {
_255,
_511,
_1023,
_2047,
_4095,
_8191,
_16383,
}
impl Into<u8> for MaxCount {
fn into(self) -> u8 {
match self {
MaxCount::_255 => 0,
MaxCount::_511 => 1,
MaxCount::_1023 => 2,
MaxCount::_2047 => 3,
MaxCount::_4095 => 4,
MaxCount::_8191 => 5,
MaxCount::_16383 => 6,
}
}
}
/// Prescaler divider
#[allow(missing_docs)]
#[derive(Copy, Clone, PartialEq)]
pub enum PGPrescalerDivider {
_1,
_2,
_4,
_8,
_16,
_32,
_64,
_128,
}
impl Into<u8> for PGPrescalerDivider {
fn into(self) -> u8 {
match self {
PGPrescalerDivider::_1 => 0,
PGPrescalerDivider::_2 => 1,
PGPrescalerDivider::_4 => 2,
PGPrescalerDivider::_8 => 3,
PGPrescalerDivider::_16 => 4,
PGPrescalerDivider::_32 => 5,
PGPrescalerDivider::_64 => 6,
PGPrescalerDivider::_128 => 7,
}
}
}
/// Error type for SSDeviation
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SSDeviationError {
/// The provided value is too low (0)
ValueTooLow,
/// The provided value is too high (greater than 128)
ValueTooHigh,
}
/// Spread Spectrum Deviation
#[derive(Copy, Clone)]
pub struct SSDeviation(u8);
impl SSDeviation {
/// Create new deviation value, acceptable inputs are 1-128
pub fn new(val: u8) -> Result<Self, SSDeviationError> {
if val == 0 {
return Err(SSDeviationError::ValueTooLow);
} else if val > 128 {
return Err(SSDeviationError::ValueTooHigh);
}
Ok(Self(val - 1))
}
}
impl Into<u8> for SSDeviation {
fn into(self) -> u8 {
self.0
}
}
/// Peripheral configuration
#[derive(Clone, Copy)]
pub struct Config {
/// Duration of high state of the charge transfer pulse
pub ct_pulse_high_length: ChargeTransferPulseCycle,
/// Duration of the low state of the charge transfer pulse
pub ct_pulse_low_length: ChargeTransferPulseCycle,
/// Enable/disable of spread spectrum feature
pub spread_spectrum: bool,
/// Adds variable number of periods of the SS clk to pulse high state
pub spread_spectrum_deviation: SSDeviation,
/// Selects AHB clock divider used to generate SS clk
pub spread_spectrum_prescaler: bool,
/// Selects AHB clock divider used to generate pulse generator clk
pub pulse_generator_prescaler: PGPrescalerDivider,
/// Maximum number of charge transfer pulses that can be generated before error
pub max_count_value: MaxCount,
/// Defines config of all IOs when no ongoing acquisition
pub io_default_mode: bool,
/// Polarity of sync input pin
pub synchro_pin_polarity: bool,
/// Acquisition starts when start bit is set or with sync pin input
pub acquisition_mode: bool,
/// Enable max count interrupt
pub max_count_interrupt: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
ct_pulse_high_length: ChargeTransferPulseCycle::_1,
ct_pulse_low_length: ChargeTransferPulseCycle::_1,
spread_spectrum: false,
spread_spectrum_deviation: SSDeviation::new(1).unwrap(),
spread_spectrum_prescaler: false,
pulse_generator_prescaler: PGPrescalerDivider::_1,
max_count_value: MaxCount::_255,
io_default_mode: false,
synchro_pin_polarity: false,
acquisition_mode: false,
max_count_interrupt: false,
}
}
}

View File

@ -0,0 +1,21 @@
/// Represents errors that can occur when configuring or validating TSC pin groups.
#[derive(Debug)]
pub enum GroupError {
/// Error when a group has no sampling capacitor
NoSamplingCapacitor,
/// Error when a group has neither channel IOs nor a shield IO
NoChannelOrShield,
/// Error when a group has both channel IOs and a shield IO
MixedChannelAndShield,
/// Error when there is more than one shield IO across all groups
MultipleShields,
}
/// Error returned when attempting to set an invalid channel pin as active in the TSC.
#[derive(Debug)]
pub enum AcquisitionBankError {
/// Indicates that one or more of the provided pins is not a valid channel pin.
InvalidChannelPin,
/// Indicates that multiple channels from the same group were provided.
MultipleChannelsPerGroup,
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,675 @@
use core::marker::PhantomData;
use core::ops::BitOr;
use embassy_hal_internal::{into_ref, PeripheralRef};
use super::errors::GroupError;
use super::tsc_io_pin::*;
use super::Instance;
use crate::gpio::{AfType, AnyPin, OutputType, Speed};
use crate::Peripheral;
/// Pin type definition to control IO parameters
#[derive(PartialEq, Clone, Copy)]
pub enum PinType {
/// Sensing channel pin connected to an electrode
Channel,
/// Sampling capacitor pin, one required for every pin group
Sample,
/// Shield pin connected to capacitive sensing shield
Shield,
}
/// Pin struct that maintains usage
#[allow(missing_docs)]
pub struct TscPin<'d, T, Group> {
_pin: PeripheralRef<'d, AnyPin>,
role: PinType,
tsc_io_pin: TscIOPin,
phantom: PhantomData<(T, Group)>,
}
impl<'d, T, Group> TscPin<'d, T, Group> {
/// Returns the role of this TSC pin.
///
/// The role indicates whether this pin is configured as a channel,
/// sampling capacitor, or shield in the TSC group.
///
/// # Returns
/// The `PinType` representing the role of this pin.
pub fn role(&self) -> PinType {
self.role
}
/// Returns the TSC IO pin associated with this pin.
///
/// This method provides access to the specific TSC IO pin configuration,
/// which includes information about the pin's group and position within that group.
///
/// # Returns
/// The `TscIOPin` representing this pin's TSC-specific configuration.
pub fn tsc_io_pin(&self) -> TscIOPin {
self.tsc_io_pin
}
}
/// Represents a group of TSC (Touch Sensing Controller) pins.
///
/// In the TSC peripheral, pins are organized into groups of four IOs. Each group
/// must have exactly one sampling capacitor pin and can have multiple channel pins
/// or a single shield pin. This structure encapsulates these pin configurations
/// for a single TSC group.
///
/// # Pin Roles
/// - Sampling Capacitor: One required per group, used for charge transfer.
/// - Channel: Sensing pins connected to electrodes for touch detection.
/// - Shield: Optional, used for active shielding to improve sensitivity.
///
/// # Constraints
/// - Each group must have exactly one sampling capacitor pin.
/// - A group can have either channel pins or a shield pin, but not both.
/// - No more than one shield pin is allowed across all groups.
#[allow(missing_docs)]
pub struct PinGroup<'d, T, Group> {
pin1: Option<TscPin<'d, T, Group>>,
pin2: Option<TscPin<'d, T, Group>>,
pin3: Option<TscPin<'d, T, Group>>,
pin4: Option<TscPin<'d, T, Group>>,
}
impl<'d, T, G> Default for PinGroup<'d, T, G> {
fn default() -> Self {
Self {
pin1: None,
pin2: None,
pin3: None,
pin4: None,
}
}
}
/// Defines roles and traits for TSC (Touch Sensing Controller) pins.
///
/// This module contains marker types and traits that represent different roles
/// a TSC pin can have, such as channel, sample, or shield.
pub mod tsc_pin_roles {
use super::{OutputType, PinType};
/// Marker type for a TSC channel pin.
#[derive(PartialEq, Clone, Copy, Debug)]
pub struct Channel;
/// Marker type for a TSC sampling pin.
#[derive(PartialEq, Clone, Copy, Debug)]
pub struct Sample;
/// Marker type for a TSC shield pin.
#[derive(PartialEq, Clone, Copy, Debug)]
pub struct Shield;
/// Trait for TSC pin roles.
///
/// This trait defines the behavior and properties of different TSC pin roles.
/// It is implemented by the marker types `Channel`, `Sample`, and `Shield`.
pub trait Role {
/// Returns the `PinType` associated with this role.
fn pin_type() -> PinType;
/// Returns the `OutputType` associated with this role.
fn output_type() -> OutputType;
}
impl Role for Channel {
fn pin_type() -> PinType {
PinType::Channel
}
fn output_type() -> OutputType {
OutputType::PushPull
}
}
impl Role for Sample {
fn pin_type() -> PinType {
PinType::Sample
}
fn output_type() -> OutputType {
OutputType::OpenDrain
}
}
impl Role for Shield {
fn pin_type() -> PinType {
PinType::Shield
}
fn output_type() -> OutputType {
OutputType::PushPull
}
}
}
/// Represents a group of TSC pins with their associated roles.
///
/// This struct allows for type-safe configuration of TSC pin groups,
/// ensuring that pins are assigned appropriate roles within their group.
/// This type is essentially just a wrapper type around a `PinGroup` value.
///
/// # Type Parameters
/// - `'d`: Lifetime of the pin group.
/// - `T`: The TSC instance type.
/// - `G`: The group identifier.
/// - `R1`, `R2`, `R3`, `R4`: Role types for each pin in the group, defaulting to `Channel`.
pub struct PinGroupWithRoles<
'd,
T: Instance,
G,
R1 = tsc_pin_roles::Channel,
R2 = tsc_pin_roles::Channel,
R3 = tsc_pin_roles::Channel,
R4 = tsc_pin_roles::Channel,
> {
/// The underlying pin group without role information.
pub pin_group: PinGroup<'d, T, G>,
_phantom: PhantomData<(R1, R2, R3, R4)>,
}
impl<'d, T: Instance, G, R1, R2, R3, R4> Default for PinGroupWithRoles<'d, T, G, R1, R2, R3, R4> {
fn default() -> Self {
Self {
pin_group: PinGroup::default(),
_phantom: PhantomData,
}
}
}
impl<'d, T: Instance, G> PinGroup<'d, T, G> {
fn contains_exactly_one_shield_pin(&self) -> bool {
let shield_count = self.shield_pins().count();
shield_count == 1
}
fn check_group(&self) -> Result<(), GroupError> {
let mut channel_count = 0;
let mut shield_count = 0;
let mut sample_count = 0;
for pin in self.pins().into_iter().flatten() {
match pin.role {
PinType::Channel => {
channel_count += 1;
}
PinType::Shield => {
shield_count += 1;
}
PinType::Sample => {
sample_count += 1;
}
}
}
// Every group requires exactly one sampling capacitor
if sample_count != 1 {
return Err(GroupError::NoSamplingCapacitor);
}
// Each group must have at least one shield or channel IO
if shield_count == 0 && channel_count == 0 {
return Err(GroupError::NoChannelOrShield);
}
// Any group can either contain channel ios or a shield IO.
// (An active shield requires its own sampling capacitor)
if shield_count != 0 && channel_count != 0 {
return Err(GroupError::MixedChannelAndShield);
}
// No more than one shield IO is allow per group and amongst all groups
if shield_count > 1 {
return Err(GroupError::MultipleShields);
}
Ok(())
}
/// Returns a reference to the first pin in the group, if configured.
pub fn pin1(&self) -> Option<&TscPin<'d, T, G>> {
self.pin1.as_ref()
}
/// Returns a reference to the second pin in the group, if configured.
pub fn pin2(&self) -> Option<&TscPin<'d, T, G>> {
self.pin2.as_ref()
}
/// Returns a reference to the third pin in the group, if configured.
pub fn pin3(&self) -> Option<&TscPin<'d, T, G>> {
self.pin3.as_ref()
}
/// Returns a reference to the fourth pin in the group, if configured.
pub fn pin4(&self) -> Option<&TscPin<'d, T, G>> {
self.pin4.as_ref()
}
fn sample_pins(&self) -> impl Iterator<Item = TscIOPin> + '_ {
self.pins_filtered(PinType::Sample)
}
fn shield_pins(&self) -> impl Iterator<Item = TscIOPin> + '_ {
self.pins_filtered(PinType::Shield)
}
fn channel_pins(&self) -> impl Iterator<Item = TscIOPin> + '_ {
self.pins_filtered(PinType::Channel)
}
fn pins_filtered(&self, pin_type: PinType) -> impl Iterator<Item = TscIOPin> + '_ {
self.pins().into_iter().filter_map(move |pin| {
pin.as_ref()
.and_then(|p| if p.role == pin_type { Some(p.tsc_io_pin) } else { None })
})
}
fn make_channel_ios_mask(&self) -> u32 {
self.channel_pins().fold(0, u32::bitor)
}
fn make_shield_ios_mask(&self) -> u32 {
self.shield_pins().fold(0, u32::bitor)
}
fn make_sample_ios_mask(&self) -> u32 {
self.sample_pins().fold(0, u32::bitor)
}
fn pins(&self) -> [&Option<TscPin<'d, T, G>>; 4] {
[&self.pin1, &self.pin2, &self.pin3, &self.pin4]
}
fn pins_mut(&mut self) -> [&mut Option<TscPin<'d, T, G>>; 4] {
[&mut self.pin1, &mut self.pin2, &mut self.pin3, &mut self.pin4]
}
}
#[cfg(any(tsc_v2, tsc_v3))]
macro_rules! TSC_V2_V3_GUARD {
($e:expr) => {{
#[cfg(any(tsc_v2, tsc_v3))]
{
$e
}
#[cfg(not(any(tsc_v2, tsc_v3)))]
{
compile_error!("Group 7 is not supported in this TSC version")
}
}};
}
#[cfg(tsc_v3)]
macro_rules! TSC_V3_GUARD {
($e:expr) => {{
#[cfg(tsc_v3)]
{
$e
}
#[cfg(not(tsc_v3))]
{
compile_error!("Group 8 is not supported in this TSC version")
}
}};
}
macro_rules! trait_to_tsc_io_pin {
(G1IO1Pin) => {
TscIOPin::Group1Io1
};
(G1IO2Pin) => {
TscIOPin::Group1Io2
};
(G1IO3Pin) => {
TscIOPin::Group1Io3
};
(G1IO4Pin) => {
TscIOPin::Group1Io4
};
(G2IO1Pin) => {
TscIOPin::Group2Io1
};
(G2IO2Pin) => {
TscIOPin::Group2Io2
};
(G2IO3Pin) => {
TscIOPin::Group2Io3
};
(G2IO4Pin) => {
TscIOPin::Group2Io4
};
(G3IO1Pin) => {
TscIOPin::Group3Io1
};
(G3IO2Pin) => {
TscIOPin::Group3Io2
};
(G3IO3Pin) => {
TscIOPin::Group3Io3
};
(G3IO4Pin) => {
TscIOPin::Group3Io4
};
(G4IO1Pin) => {
TscIOPin::Group4Io1
};
(G4IO2Pin) => {
TscIOPin::Group4Io2
};
(G4IO3Pin) => {
TscIOPin::Group4Io3
};
(G4IO4Pin) => {
TscIOPin::Group4Io4
};
(G5IO1Pin) => {
TscIOPin::Group5Io1
};
(G5IO2Pin) => {
TscIOPin::Group5Io2
};
(G5IO3Pin) => {
TscIOPin::Group5Io3
};
(G5IO4Pin) => {
TscIOPin::Group5Io4
};
(G6IO1Pin) => {
TscIOPin::Group6Io1
};
(G6IO2Pin) => {
TscIOPin::Group6Io2
};
(G6IO3Pin) => {
TscIOPin::Group6Io3
};
(G6IO4Pin) => {
TscIOPin::Group6Io4
};
(G7IO1Pin) => {
TSC_V2_V3_GUARD!(TscIOPin::Group7Io1)
};
(G7IO2Pin) => {
TSC_V2_V3_GUARD!(TscIOPin::Group7Io2)
};
(G7IO3Pin) => {
TSC_V2_V3_GUARD!(TscIOPin::Group7Io3)
};
(G7IO4Pin) => {
TSC_V2_V3_GUARD!(TscIOPin::Group7Io4)
};
(G8IO1Pin) => {
TSC_V3_GUARD!(TscIOPin::Group8Io1)
};
(G8IO2Pin) => {
TSC_V3_GUARD!(TscIOPin::Group8Io2)
};
(G8IO3Pin) => {
TSC_V3_GUARD!(TscIOPin::Group8Io3)
};
(G8IO4Pin) => {
TSC_V3_GUARD!(TscIOPin::Group8Io4)
};
}
macro_rules! impl_set_io {
($method:ident, $group:ident, $trait:ident, $index:expr) => {
#[doc = concat!("Create a new pin1 for ", stringify!($group), " TSC group instance.")]
pub fn $method<Role: tsc_pin_roles::Role>(
&mut self,
pin: impl Peripheral<P = impl $trait<T>> + 'd,
) -> TscIOPinWithRole<$group, Role> {
into_ref!(pin);
critical_section::with(|_| {
pin.set_low();
pin.set_as_af(pin.af_num(), AfType::output(Role::output_type(), Speed::VeryHigh));
let tsc_io_pin = trait_to_tsc_io_pin!($trait);
let new_pin = TscPin {
_pin: pin.map_into(),
role: Role::pin_type(),
tsc_io_pin,
phantom: PhantomData,
};
*self.pin_group.pins_mut()[$index] = Some(new_pin);
TscIOPinWithRole {
pin: tsc_io_pin,
phantom: PhantomData,
}
})
}
};
}
macro_rules! group_impl {
($group:ident, $trait1:ident, $trait2:ident, $trait3:ident, $trait4:ident) => {
impl<
'd,
T: Instance,
R1: tsc_pin_roles::Role,
R2: tsc_pin_roles::Role,
R3: tsc_pin_roles::Role,
R4: tsc_pin_roles::Role,
> PinGroupWithRoles<'d, T, $group, R1, R2, R3, R4>
{
impl_set_io!(set_io1, $group, $trait1, 0);
impl_set_io!(set_io2, $group, $trait2, 1);
impl_set_io!(set_io3, $group, $trait3, 2);
impl_set_io!(set_io4, $group, $trait4, 3);
}
};
}
group_impl!(G1, G1IO1Pin, G1IO2Pin, G1IO3Pin, G1IO4Pin);
group_impl!(G2, G2IO1Pin, G2IO2Pin, G2IO3Pin, G2IO4Pin);
group_impl!(G3, G3IO1Pin, G3IO2Pin, G3IO3Pin, G3IO4Pin);
group_impl!(G4, G4IO1Pin, G4IO2Pin, G4IO3Pin, G4IO4Pin);
group_impl!(G5, G5IO1Pin, G5IO2Pin, G5IO3Pin, G5IO4Pin);
group_impl!(G6, G6IO1Pin, G6IO2Pin, G6IO3Pin, G6IO4Pin);
#[cfg(any(tsc_v2, tsc_v3))]
group_impl!(G7, G7IO1Pin, G7IO2Pin, G7IO3Pin, G7IO4Pin);
#[cfg(tsc_v3)]
group_impl!(G8, G8IO1Pin, G8IO2Pin, G8IO3Pin, G8IO4Pin);
/// Group 1 marker type.
#[derive(Clone, Copy, Debug)]
pub enum G1 {}
/// Group 2 marker type.
#[derive(Clone, Copy, Debug)]
pub enum G2 {}
/// Group 3 marker type.
#[derive(Clone, Copy, Debug)]
pub enum G3 {}
/// Group 4 marker type.
#[derive(Clone, Copy, Debug)]
pub enum G4 {}
/// Group 5 marker type.
#[derive(Clone, Copy, Debug)]
pub enum G5 {}
/// Group 6 marker type.
#[derive(Clone, Copy, Debug)]
pub enum G6 {}
/// Group 7 marker type.
#[derive(Clone, Copy, Debug)]
pub enum G7 {}
/// Group 8 marker type.
#[derive(Clone, Copy, Debug)]
pub enum G8 {}
/// Represents the collection of pin groups for the Touch Sensing Controller (TSC).
///
/// Each field corresponds to a specific group of TSC pins:
#[allow(missing_docs)]
pub struct PinGroups<'d, T: Instance> {
pub g1: Option<PinGroup<'d, T, G1>>,
pub g2: Option<PinGroup<'d, T, G2>>,
pub g3: Option<PinGroup<'d, T, G3>>,
pub g4: Option<PinGroup<'d, T, G4>>,
pub g5: Option<PinGroup<'d, T, G5>>,
pub g6: Option<PinGroup<'d, T, G6>>,
#[cfg(any(tsc_v2, tsc_v3))]
pub g7: Option<PinGroup<'d, T, G7>>,
#[cfg(tsc_v3)]
pub g8: Option<PinGroup<'d, T, G8>>,
}
impl<'d, T: Instance> PinGroups<'d, T> {
pub(super) fn check(&self) -> Result<(), GroupError> {
let mut shield_count = 0;
// Helper function to check a single group
fn check_group<C, T: Instance>(
group: &Option<PinGroup<'_, T, C>>,
shield_count: &mut u32,
) -> Result<(), GroupError> {
if let Some(group) = group {
group.check_group()?;
if group.contains_exactly_one_shield_pin() {
*shield_count += 1;
if *shield_count > 1 {
return Err(GroupError::MultipleShields);
}
}
}
Ok(())
}
// Check each group
check_group(&self.g1, &mut shield_count)?;
check_group(&self.g2, &mut shield_count)?;
check_group(&self.g3, &mut shield_count)?;
check_group(&self.g4, &mut shield_count)?;
check_group(&self.g5, &mut shield_count)?;
check_group(&self.g6, &mut shield_count)?;
#[cfg(any(tsc_v2, tsc_v3))]
check_group(&self.g7, &mut shield_count)?;
#[cfg(tsc_v3)]
check_group(&self.g8, &mut shield_count)?;
Ok(())
}
pub(super) fn make_channel_ios_mask(&self) -> u32 {
#[allow(unused_mut)]
let mut mask = self.g1.as_ref().map_or(0, |g| g.make_channel_ios_mask())
| self.g2.as_ref().map_or(0, |g| g.make_channel_ios_mask())
| self.g3.as_ref().map_or(0, |g| g.make_channel_ios_mask())
| self.g4.as_ref().map_or(0, |g| g.make_channel_ios_mask())
| self.g5.as_ref().map_or(0, |g| g.make_channel_ios_mask())
| self.g6.as_ref().map_or(0, |g| g.make_channel_ios_mask());
#[cfg(any(tsc_v2, tsc_v3))]
{
mask |= self.g7.as_ref().map_or(0, |g| g.make_channel_ios_mask());
}
#[cfg(tsc_v3)]
{
mask |= self.g8.as_ref().map_or(0, |g| g.make_channel_ios_mask());
}
mask
}
pub(super) fn make_shield_ios_mask(&self) -> u32 {
#[allow(unused_mut)]
let mut mask = self.g1.as_ref().map_or(0, |g| g.make_shield_ios_mask())
| self.g2.as_ref().map_or(0, |g| g.make_shield_ios_mask())
| self.g3.as_ref().map_or(0, |g| g.make_shield_ios_mask())
| self.g4.as_ref().map_or(0, |g| g.make_shield_ios_mask())
| self.g5.as_ref().map_or(0, |g| g.make_shield_ios_mask())
| self.g6.as_ref().map_or(0, |g| g.make_shield_ios_mask());
#[cfg(any(tsc_v2, tsc_v3))]
{
mask |= self.g7.as_ref().map_or(0, |g| g.make_shield_ios_mask());
}
#[cfg(tsc_v3)]
{
mask |= self.g8.as_ref().map_or(0, |g| g.make_shield_ios_mask());
}
mask
}
pub(super) fn make_sample_ios_mask(&self) -> u32 {
#[allow(unused_mut)]
let mut mask = self.g1.as_ref().map_or(0, |g| g.make_sample_ios_mask())
| self.g2.as_ref().map_or(0, |g| g.make_sample_ios_mask())
| self.g3.as_ref().map_or(0, |g| g.make_sample_ios_mask())
| self.g4.as_ref().map_or(0, |g| g.make_sample_ios_mask())
| self.g5.as_ref().map_or(0, |g| g.make_sample_ios_mask())
| self.g6.as_ref().map_or(0, |g| g.make_sample_ios_mask());
#[cfg(any(tsc_v2, tsc_v3))]
{
mask |= self.g7.as_ref().map_or(0, |g| g.make_sample_ios_mask());
}
#[cfg(tsc_v3)]
{
mask |= self.g8.as_ref().map_or(0, |g| g.make_sample_ios_mask());
}
mask
}
}
impl<'d, T: Instance> Default for PinGroups<'d, T> {
fn default() -> Self {
Self {
g1: None,
g2: None,
g3: None,
g4: None,
g5: None,
g6: None,
#[cfg(any(tsc_v2, tsc_v3))]
g7: None,
#[cfg(tsc_v3)]
g8: None,
}
}
}
pin_trait!(G1IO1Pin, Instance);
pin_trait!(G1IO2Pin, Instance);
pin_trait!(G1IO3Pin, Instance);
pin_trait!(G1IO4Pin, Instance);
pin_trait!(G2IO1Pin, Instance);
pin_trait!(G2IO2Pin, Instance);
pin_trait!(G2IO3Pin, Instance);
pin_trait!(G2IO4Pin, Instance);
pin_trait!(G3IO1Pin, Instance);
pin_trait!(G3IO2Pin, Instance);
pin_trait!(G3IO3Pin, Instance);
pin_trait!(G3IO4Pin, Instance);
pin_trait!(G4IO1Pin, Instance);
pin_trait!(G4IO2Pin, Instance);
pin_trait!(G4IO3Pin, Instance);
pin_trait!(G4IO4Pin, Instance);
pin_trait!(G5IO1Pin, Instance);
pin_trait!(G5IO2Pin, Instance);
pin_trait!(G5IO3Pin, Instance);
pin_trait!(G5IO4Pin, Instance);
pin_trait!(G6IO1Pin, Instance);
pin_trait!(G6IO2Pin, Instance);
pin_trait!(G6IO3Pin, Instance);
pin_trait!(G6IO4Pin, Instance);
pin_trait!(G7IO1Pin, Instance);
pin_trait!(G7IO2Pin, Instance);
pin_trait!(G7IO3Pin, Instance);
pin_trait!(G7IO4Pin, Instance);
pin_trait!(G8IO1Pin, Instance);
pin_trait!(G8IO2Pin, Instance);
pin_trait!(G8IO3Pin, Instance);
pin_trait!(G8IO4Pin, Instance);

View File

@ -0,0 +1,456 @@
use core::future::poll_fn;
use core::marker::PhantomData;
use core::ops::BitOr;
use core::task::Poll;
use embassy_hal_internal::{into_ref, PeripheralRef};
use super::acquisition_banks::*;
use super::config::*;
use super::errors::*;
use super::pin_groups::*;
use super::tsc_io_pin::*;
use super::types::*;
use super::{Instance, InterruptHandler, TSC_NUM_GROUPS};
use crate::interrupt::typelevel::Interrupt;
use crate::mode::{Async, Blocking, Mode as PeriMode};
use crate::{interrupt, rcc, Peripheral};
/// Internal structure holding masks for different types of TSC IOs.
///
/// These masks are used during the initial configuration of the TSC peripheral
/// and for validating pin types during operations like creating acquisition banks.
struct TscIOMasks {
/// Mask representing all configured channel IOs
channel_ios: u32,
/// Mask representing all configured shield IOs
shield_ios: u32,
/// Mask representing all configured sampling IOs
sampling_ios: u32,
}
/// TSC driver
pub struct Tsc<'d, T: Instance, K: PeriMode> {
_peri: PeripheralRef<'d, T>,
_pin_groups: PinGroups<'d, T>,
state: State,
config: Config,
masks: TscIOMasks,
_kind: PhantomData<K>,
}
impl<'d, T: Instance, K: PeriMode> Tsc<'d, T, K> {
// Helper method to check if a pin is a channel pin
fn is_channel_pin(&self, pin: TscIOPin) -> bool {
(self.masks.channel_ios & pin) != 0
}
/// Get the status of all groups involved in a TscAcquisitionBank
pub fn get_acquisition_bank_status(&self, bank: &TscAcquisitionBank) -> TscAcquisitionBankStatus {
let mut bank_status = TscAcquisitionBankStatus::default();
for pin in bank.pins_iterator() {
let group = pin.group();
let group_status = self.group_get_status(group);
let index: usize = group.into();
bank_status.groups[index] = Some(group_status);
}
bank_status
}
/// Get the values for all channels involved in a TscAcquisitionBank
pub fn get_acquisition_bank_values(&self, bank: &TscAcquisitionBank) -> TscAcquisitionBankReadings {
let mut bank_readings = TscAcquisitionBankReadings::default();
for pin in bank.pins_iterator() {
let group = pin.group();
let value = self.group_get_value(group);
let reading = TscChannelReading {
sensor_value: value,
tsc_pin: pin,
};
let index: usize = group.into();
bank_readings.groups[index] = Some(reading);
}
bank_readings
}
/// Creates a new TSC acquisition bank from the provided pin configuration.
///
/// This method creates a `TscAcquisitionBank` that can be used for efficient,
/// repeated TSC acquisitions. It automatically generates the appropriate mask
/// for the provided pins.
///
/// # Note on TSC Hardware Limitation
///
/// The TSC hardware can only read one channel pin from each TSC group per acquisition.
///
/// # Arguments
/// * `acquisition_bank_pins` - The pin configuration for the acquisition bank.
///
/// # Returns
/// A new `TscAcquisitionBank` instance.
///
/// # Example
///
/// ```
/// let tsc = // ... initialize TSC
/// let tsc_sensor1: TscIOPinWithRole<G1, tsc_pin_roles::Channel> = ...;
/// let tsc_sensor2: TscIOPinWithRole<G2, tsc_pin_roles::Channel> = ...;
///
/// let bank = tsc.create_acquisition_bank(TscAcquisitionBankPins {
/// g1_pin: Some(tsc_sensor1),
/// g2_pin: Some(tsc_sensor2),
/// ..Default::default()
/// });
///
/// // Use the bank for acquisitions
/// tsc.set_active_channels_bank(&bank);
/// tsc.start();
/// // ... perform acquisition ...
/// ```
pub fn create_acquisition_bank(&self, acquisition_bank_pins: TscAcquisitionBankPins) -> TscAcquisitionBank {
let bank_mask = acquisition_bank_pins.iter().fold(0u32, BitOr::bitor);
TscAcquisitionBank {
pins: acquisition_bank_pins,
mask: bank_mask,
}
}
fn make_channels_mask<Itt>(&self, channels: Itt) -> Result<u32, AcquisitionBankError>
where
Itt: IntoIterator<Item = TscIOPin>,
{
let mut group_mask = 0u32;
let mut channel_mask = 0u32;
for channel in channels {
if !self.is_channel_pin(channel) {
return Err(AcquisitionBankError::InvalidChannelPin);
}
let group = channel.group();
let group_bit: u32 = 1 << Into::<usize>::into(group);
if group_mask & group_bit != 0 {
return Err(AcquisitionBankError::MultipleChannelsPerGroup);
}
group_mask |= group_bit;
channel_mask |= channel;
}
Ok(channel_mask)
}
/// Sets the active channels for the next TSC acquisition.
///
/// This is a low-level method that directly sets the channel mask. For most use cases,
/// consider using `set_active_channels_bank` with a `TscAcquisitionBank` instead, which
/// provides a higher-level interface and additional safety checks.
///
/// This method configures which sensor channels will be read during the next
/// touch sensing acquisition cycle. It should be called before starting a new
/// acquisition with the start() method.
///
/// # Arguments
/// * `mask` - A 32-bit mask where each bit represents a channel. Set bits indicate
/// active channels.
///
/// # Note
/// Only one pin from each TSC group can be read for each acquisition. This method
/// does not perform checks to ensure this limitation is met. Incorrect masks may
/// lead to unexpected behavior.
///
/// # Safety
/// This method doesn't perform extensive checks on the provided mask. Ensure that
/// the mask is valid and adheres to hardware limitations to avoid undefined behavior.
pub fn set_active_channels_mask(&mut self, mask: u32) {
T::regs().ioccr().write(|w| w.0 = mask | self.masks.shield_ios);
}
/// Convenience method for setting active channels directly from a slice of TscIOPin.
/// This method performs safety checks but is less efficient for repeated use.
pub fn set_active_channels(&mut self, channels: &[TscIOPin]) -> Result<(), AcquisitionBankError> {
let mask = self.make_channels_mask(channels.iter().cloned())?;
self.set_active_channels_mask(mask);
Ok(())
}
/// Sets the active channels for the next TSC acquisition using a pre-configured acquisition bank.
///
/// This method efficiently configures the TSC peripheral to read the channels specified
/// in the provided `TscAcquisitionBank`. It's the recommended way to set up
/// channel configurations for acquisition, especially when using the same set of channels repeatedly.
///
/// # Arguments
///
/// * `bank` - A reference to a `TscAcquisitionBank` containing the pre-configured
/// TSC channel mask.
///
/// # Example
///
/// ```
/// let tsc_sensor1: TscIOPinWithRole<G1, Channel> = ...;
/// let tsc_sensor2: TscIOPinWithRole<G5, Channel> = ...;
/// let mut touch_controller: Tsc<'_, TSC, Async> = ...;
/// let bank = touch_controller.create_acquisition_bank(TscAcquisitionBankPins {
/// g1_pin: Some(tsc_sensor1),
/// g2_pin: Some(tsc_sensor2),
/// ..Default::default()
/// });
///
/// touch_controller.set_active_channels_bank(&bank);
/// touch_controller.start();
/// // ... perform acquisition ...
/// ```
///
/// This method should be called before starting a new acquisition with the `start()` method.
pub fn set_active_channels_bank(&mut self, bank: &TscAcquisitionBank) {
self.set_active_channels_mask(bank.mask)
}
fn extract_groups(io_mask: u32) -> u32 {
let mut groups: u32 = 0;
for idx in 0..TSC_NUM_GROUPS {
if io_mask & (0x0F << (idx * 4)) != 0 {
groups |= 1 << idx
}
}
groups
}
fn new_inner(
peri: impl Peripheral<P = T> + 'd,
pin_groups: PinGroups<'d, T>,
config: Config,
) -> Result<Self, GroupError> {
into_ref!(peri);
pin_groups.check()?;
let masks = TscIOMasks {
channel_ios: pin_groups.make_channel_ios_mask(),
shield_ios: pin_groups.make_shield_ios_mask(),
sampling_ios: pin_groups.make_sample_ios_mask(),
};
rcc::enable_and_reset::<T>();
T::regs().cr().modify(|w| {
w.set_tsce(true);
w.set_ctph(config.ct_pulse_high_length.into());
w.set_ctpl(config.ct_pulse_low_length.into());
w.set_sse(config.spread_spectrum);
// Prevent invalid configuration for pulse generator prescaler
if config.ct_pulse_low_length == ChargeTransferPulseCycle::_1
&& (config.pulse_generator_prescaler == PGPrescalerDivider::_1
|| config.pulse_generator_prescaler == PGPrescalerDivider::_2)
{
w.set_pgpsc(PGPrescalerDivider::_4.into());
} else if config.ct_pulse_low_length == ChargeTransferPulseCycle::_2
&& config.pulse_generator_prescaler == PGPrescalerDivider::_1
{
w.set_pgpsc(PGPrescalerDivider::_2.into());
} else {
w.set_pgpsc(config.pulse_generator_prescaler.into());
}
w.set_ssd(config.spread_spectrum_deviation.into());
w.set_sspsc(config.spread_spectrum_prescaler);
w.set_mcv(config.max_count_value.into());
w.set_syncpol(config.synchro_pin_polarity);
w.set_am(config.acquisition_mode);
});
// Set IO configuration
// Disable Schmitt trigger hysteresis on all used TSC IOs
T::regs()
.iohcr()
.write(|w| w.0 = !(masks.channel_ios | masks.shield_ios | masks.sampling_ios));
// Set channel and shield IOs
T::regs().ioccr().write(|w| w.0 = masks.channel_ios | masks.shield_ios);
// Set sampling IOs
T::regs().ioscr().write(|w| w.0 = masks.sampling_ios);
// Set the groups to be acquired
// Lower bits of `iogcsr` are for enabling groups, while the higher bits are for reading
// status of acquisiton for a group, see method `Tsc::group_get_status`.
T::regs()
.iogcsr()
.write(|w| w.0 = Self::extract_groups(masks.channel_ios));
// Disable interrupts
T::regs().ier().modify(|w| {
w.set_eoaie(false);
w.set_mceie(false);
});
// Clear flags
T::regs().icr().modify(|w| {
w.set_eoaic(true);
w.set_mceic(true);
});
unsafe {
T::Interrupt::enable();
}
Ok(Self {
_peri: peri,
_pin_groups: pin_groups,
state: State::Ready,
config,
masks,
_kind: PhantomData,
})
}
/// Start charge transfer acquisition
pub fn start(&mut self) {
self.state = State::Busy;
// Disable interrupts
T::regs().ier().modify(|w| {
w.set_eoaie(false);
w.set_mceie(false);
});
// Clear flags
T::regs().icr().modify(|w| {
w.set_eoaic(true);
w.set_mceic(true);
});
// Set the touch sensing IOs not acquired to the default mode
T::regs().cr().modify(|w| {
w.set_iodef(self.config.io_default_mode);
});
// Start the acquisition
T::regs().cr().modify(|w| {
w.set_start(true);
});
}
/// Stop charge transfer acquisition
pub fn stop(&mut self) {
T::regs().cr().modify(|w| {
w.set_start(false);
});
// Set the touch sensing IOs in low power mode
T::regs().cr().modify(|w| {
w.set_iodef(false);
});
// Clear flags
T::regs().icr().modify(|w| {
w.set_eoaic(true);
w.set_mceic(true);
});
self.state = State::Ready;
}
/// Get current state of acquisition
pub fn get_state(&mut self) -> State {
if self.state == State::Busy && T::regs().isr().read().eoaf() {
if T::regs().isr().read().mcef() {
self.state = State::Error
} else {
self.state = State::Ready
}
}
self.state
}
/// Get the individual group status to check acquisition complete
pub fn group_get_status(&self, index: Group) -> GroupStatus {
// Status bits are set by hardware when the acquisition on the corresponding
// enabled analog IO group is complete, cleared when new acquisition is started
let status = match index {
Group::One => T::regs().iogcsr().read().g1s(),
Group::Two => T::regs().iogcsr().read().g2s(),
Group::Three => T::regs().iogcsr().read().g3s(),
Group::Four => T::regs().iogcsr().read().g4s(),
Group::Five => T::regs().iogcsr().read().g5s(),
Group::Six => T::regs().iogcsr().read().g6s(),
#[cfg(any(tsc_v2, tsc_v3))]
Group::Seven => T::regs().iogcsr().read().g7s(),
#[cfg(tsc_v3)]
Group::Eight => T::regs().iogcsr().read().g8s(),
};
match status {
true => GroupStatus::Complete,
false => GroupStatus::Ongoing,
}
}
/// Get the count for the acquisiton, valid once group status is set
pub fn group_get_value(&self, index: Group) -> u16 {
T::regs().iogcr(index.into()).read().cnt()
}
/// Discharge the IOs for subsequent acquisition
pub fn discharge_io(&mut self, status: bool) {
// Set the touch sensing IOs in low power mode
T::regs().cr().modify(|w| {
w.set_iodef(!status);
});
}
}
impl<'d, T: Instance, K: PeriMode> Drop for Tsc<'d, T, K> {
fn drop(&mut self) {
rcc::disable::<T>();
}
}
impl<'d, T: Instance> Tsc<'d, T, Async> {
/// Create a Tsc instance that can be awaited for completion
pub fn new_async(
peri: impl Peripheral<P = T> + 'd,
pin_groups: PinGroups<'d, T>,
config: Config,
_irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
) -> Result<Self, GroupError> {
Self::new_inner(peri, pin_groups, config)
}
/// Asyncronously wait for the end of an acquisition
pub async fn pend_for_acquisition(&mut self) {
poll_fn(|cx| match self.get_state() {
State::Busy => {
T::waker().register(cx.waker());
T::regs().ier().write(|w| w.set_eoaie(true));
if self.get_state() != State::Busy {
T::regs().ier().write(|w| w.set_eoaie(false));
return Poll::Ready(());
}
Poll::Pending
}
_ => {
T::regs().ier().write(|w| w.set_eoaie(false));
Poll::Ready(())
}
})
.await;
}
}
impl<'d, T: Instance> Tsc<'d, T, Blocking> {
/// Create a Tsc instance that must be polled for completion
pub fn new_blocking(
peri: impl Peripheral<P = T> + 'd,
pin_groups: PinGroups<'d, T>,
config: Config,
) -> Result<Self, GroupError> {
Self::new_inner(peri, pin_groups, config)
}
/// Wait for end of acquisition
pub fn poll_for_acquisition(&mut self) {
while self.get_state() == State::Busy {}
}
}

View File

@ -1,7 +1,13 @@
use core::ops::BitOr;
use core::marker::PhantomData;
use core::ops::{BitAnd, BitOr, BitOrAssign};
use super::tsc_pin_roles;
use super::types::Group;
/// Pin defines
#[allow(missing_docs)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum TscIOPin {
Group1Io1,
Group1Io2,
@ -45,6 +51,53 @@ pub enum TscIOPin {
Group8Io4,
}
/// Represents a TSC I/O pin with associated group and role information.
///
/// This type combines a `TscIOPin` with phantom type parameters to statically
/// encode the pin's group and role. This allows for type-safe operations
/// on TSC pins within their specific contexts.
///
/// - `Group`: A type parameter representing the TSC group (e.g., `G1`, `G2`).
/// - `Role`: A type parameter representing the pin's role (e.g., `Channel`, `Sample`).
#[derive(Clone, Copy, Debug)]
pub struct TscIOPinWithRole<Group, Role: tsc_pin_roles::Role> {
/// The underlying TSC I/O pin.
pub pin: TscIOPin,
pub(super) phantom: PhantomData<(Group, Role)>,
}
impl<G, R: tsc_pin_roles::Role> TscIOPinWithRole<G, R> {
pub(super) fn get_pin(wrapped_pin: TscIOPinWithRole<G, R>) -> TscIOPin {
wrapped_pin.pin
}
}
impl TscIOPin {
/// Maps this TscIOPin to the Group it belongs to.
///
/// This method provides a convenient way to determine which Group
/// a specific TSC I/O pin is associated with.
pub const fn group(&self) -> Group {
match self {
TscIOPin::Group1Io1 | TscIOPin::Group1Io2 | TscIOPin::Group1Io3 | TscIOPin::Group1Io4 => Group::One,
TscIOPin::Group2Io1 | TscIOPin::Group2Io2 | TscIOPin::Group2Io3 | TscIOPin::Group2Io4 => Group::Two,
TscIOPin::Group3Io1 | TscIOPin::Group3Io2 | TscIOPin::Group3Io3 | TscIOPin::Group3Io4 => Group::Three,
TscIOPin::Group4Io1 | TscIOPin::Group4Io2 | TscIOPin::Group4Io3 | TscIOPin::Group4Io4 => Group::Four,
TscIOPin::Group5Io1 | TscIOPin::Group5Io2 | TscIOPin::Group5Io3 | TscIOPin::Group5Io4 => Group::Five,
TscIOPin::Group6Io1 | TscIOPin::Group6Io2 | TscIOPin::Group6Io3 | TscIOPin::Group6Io4 => Group::Six,
#[cfg(any(tsc_v2, tsc_v3))]
TscIOPin::Group7Io1 | TscIOPin::Group7Io2 | TscIOPin::Group7Io3 | TscIOPin::Group7Io4 => Group::Seven,
#[cfg(tsc_v3)]
TscIOPin::Group8Io1 | TscIOPin::Group8Io2 | TscIOPin::Group8Io3 | TscIOPin::Group8Io4 => Group::Eight,
}
}
/// Returns the `Group` associated with the given `TscIOPin`.
pub fn get_group(pin: TscIOPin) -> Group {
pin.group()
}
}
impl BitOr<TscIOPin> for u32 {
type Output = u32;
fn bitor(self, rhs: TscIOPin) -> Self::Output {
@ -70,8 +123,31 @@ impl BitOr for TscIOPin {
}
}
impl Into<u32> for TscIOPin {
fn into(self) -> u32 {
impl BitOrAssign<TscIOPin> for u32 {
fn bitor_assign(&mut self, rhs: TscIOPin) {
let rhs: u32 = rhs.into();
*self |= rhs;
}
}
impl BitAnd<TscIOPin> for u32 {
type Output = u32;
fn bitand(self, rhs: TscIOPin) -> Self::Output {
let rhs: u32 = rhs.into();
self & rhs
}
}
impl BitAnd<u32> for TscIOPin {
type Output = u32;
fn bitand(self, rhs: u32) -> Self::Output {
let val: u32 = self.into();
val & rhs
}
}
impl TscIOPin {
const fn to_u32(self) -> u32 {
match self {
TscIOPin::Group1Io1 => 0x00000001,
TscIOPin::Group1Io2 => 0x00000002,
@ -117,122 +193,8 @@ impl Into<u32> for TscIOPin {
}
}
/// Spread Spectrum Deviation
#[derive(Copy, Clone)]
pub struct SSDeviation(u8);
impl SSDeviation {
/// Create new deviation value, acceptable inputs are 1-128
pub fn new(val: u8) -> Result<Self, ()> {
if val == 0 || val > 128 {
return Err(());
}
Ok(Self(val - 1))
}
}
impl Into<u8> for SSDeviation {
fn into(self) -> u8 {
self.0
}
}
/// Charge transfer pulse cycles
#[allow(missing_docs)]
#[derive(Copy, Clone, PartialEq)]
pub enum ChargeTransferPulseCycle {
_1,
_2,
_3,
_4,
_5,
_6,
_7,
_8,
_9,
_10,
_11,
_12,
_13,
_14,
_15,
_16,
}
impl Into<u8> for ChargeTransferPulseCycle {
fn into(self) -> u8 {
match self {
ChargeTransferPulseCycle::_1 => 0,
ChargeTransferPulseCycle::_2 => 1,
ChargeTransferPulseCycle::_3 => 2,
ChargeTransferPulseCycle::_4 => 3,
ChargeTransferPulseCycle::_5 => 4,
ChargeTransferPulseCycle::_6 => 5,
ChargeTransferPulseCycle::_7 => 6,
ChargeTransferPulseCycle::_8 => 7,
ChargeTransferPulseCycle::_9 => 8,
ChargeTransferPulseCycle::_10 => 9,
ChargeTransferPulseCycle::_11 => 10,
ChargeTransferPulseCycle::_12 => 11,
ChargeTransferPulseCycle::_13 => 12,
ChargeTransferPulseCycle::_14 => 13,
ChargeTransferPulseCycle::_15 => 14,
ChargeTransferPulseCycle::_16 => 15,
}
}
}
/// Prescaler divider
#[allow(missing_docs)]
#[derive(Copy, Clone, PartialEq)]
pub enum PGPrescalerDivider {
_1,
_2,
_4,
_8,
_16,
_32,
_64,
_128,
}
impl Into<u8> for PGPrescalerDivider {
fn into(self) -> u8 {
match self {
PGPrescalerDivider::_1 => 0,
PGPrescalerDivider::_2 => 1,
PGPrescalerDivider::_4 => 2,
PGPrescalerDivider::_8 => 3,
PGPrescalerDivider::_16 => 4,
PGPrescalerDivider::_32 => 5,
PGPrescalerDivider::_64 => 6,
PGPrescalerDivider::_128 => 7,
}
}
}
/// Max count
#[allow(missing_docs)]
#[derive(Copy, Clone)]
pub enum MaxCount {
_255,
_511,
_1023,
_2047,
_4095,
_8191,
_16383,
}
impl Into<u8> for MaxCount {
fn into(self) -> u8 {
match self {
MaxCount::_255 => 0,
MaxCount::_511 => 1,
MaxCount::_1023 => 2,
MaxCount::_2047 => 3,
MaxCount::_4095 => 4,
MaxCount::_8191 => 5,
MaxCount::_16383 => 6,
}
impl Into<u32> for TscIOPin {
fn into(self) -> u32 {
self.to_u32()
}
}

View File

@ -0,0 +1,93 @@
/// Peripheral state
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(PartialEq, Clone, Copy)]
pub enum State {
/// Peripheral is being setup or reconfigured
Reset,
/// Ready to start acquisition
Ready,
/// In process of sensor acquisition
Busy,
/// Error occured during acquisition
Error,
}
/// Individual group status checked after acquisition reported as complete
/// For groups with multiple channel pins, may take longer because acquisitions
/// are done sequentially. Check this status before pulling count for each
/// sampled channel
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(PartialEq, Clone, Copy)]
pub enum GroupStatus {
/// Acquisition for channel still in progress
Ongoing,
/// Acquisition either not started or complete
Complete,
}
/// Group identifier used to interrogate status
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[allow(missing_docs)]
#[derive(PartialEq, Clone, Copy)]
pub enum Group {
One,
Two,
Three,
Four,
Five,
Six,
#[cfg(any(tsc_v2, tsc_v3))]
Seven,
#[cfg(tsc_v3)]
Eight,
}
impl Into<usize> for Group {
fn into(self) -> usize {
match self {
Group::One => 0,
Group::Two => 1,
Group::Three => 2,
Group::Four => 3,
Group::Five => 4,
Group::Six => 5,
#[cfg(any(tsc_v2, tsc_v3))]
Group::Seven => 6,
#[cfg(tsc_v3)]
Group::Eight => 7,
}
}
}
/// Error returned when attempting to create a Group from an invalid numeric value.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InvalidGroupError {
invalid_value: usize,
}
impl InvalidGroupError {
#[allow(missing_docs)]
pub fn new(value: usize) -> Self {
Self { invalid_value: value }
}
}
impl TryFrom<usize> for Group {
type Error = InvalidGroupError;
fn try_from(value: usize) -> Result<Self, Self::Error> {
match value {
0 => Ok(Group::One),
1 => Ok(Group::Two),
2 => Ok(Group::Three),
3 => Ok(Group::Four),
4 => Ok(Group::Five),
5 => Ok(Group::Six),
#[cfg(any(tsc_v2, tsc_v3))]
6 => Ok(Group::Two),
#[cfg(tsc_v3)]
7 => Ok(Group::Two),
n => Err(InvalidGroupError::new(n)),
}
}
}

View File

@ -0,0 +1,24 @@
# Examples for STM32F3 family
Run individual examples with
```
cargo run --bin <module-name>
```
for example
```
cargo run --bin blinky
```
## Checklist before running examples
You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using.
* [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for F303ZE it should be `probe-rs run --chip STM32F303ZETx`. (use `probe-rs chip list` to find your chip)
* [ ] Update Cargo.toml to have the correct `embassy-stm32` feature. For example for F303ZE it should be `stm32f303ze`. Look in the `Cargo.toml` file of the `embassy-stm32` project to find the correct feature flag for your chip.
* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately.
* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic
If you are unsure, please drop by the Embassy Matrix chat for support, and let us know:
* Which example you are trying to run
* Which chip and board you are using
Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org

View File

@ -1,98 +0,0 @@
// Example of polling TSC (Touch Sensing Controller) that lights an LED when touch is detected.
//
// Suggested physical setup on STM32F303ZE Nucleo board:
// - Connect a 1000pF capacitor between pin A0 and GND. This is your sampling capacitor.
// - Connect one end of a 1K resistor to pin A1 and leave the other end loose.
// The loose end will act as touch sensor which will register your touch.
//
// Troubleshooting the setup:
// - If no touch seems to be registered, then try to disconnect the sampling capacitor from GND momentarily,
// now the led should light up. Next try using a different value for the sampling capacitor.
// Also experiment with increasing the values for `ct_pulse_high_length`, `ct_pulse_low_length`, `pulse_generator_prescaler`, `max_count_value` and `discharge_delay`.
//
// All configuration values and sampling capacitor value have been determined experimentally.
// Suitable configuration and discharge delay values are highly dependent on the value of the sample capacitor. For example, a shorter discharge delay can be used with smaller capacitor values.
//
#![no_std]
#![no_main]
use defmt::*;
use embassy_stm32::gpio::{Level, Output, Speed};
use embassy_stm32::tsc::{self, *};
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
/// This example is written for the nucleo-stm32f303ze, with a stm32f303ze chip.
///
/// Make sure you check/update the following (whether you use the F303ZE or another board):
///
/// * [ ] Update .cargo/config.toml with the correct `probe-rs run --chip STM32F303ZETx`chip name.
/// * [ ] Update Cargo.toml to have the correct `embassy-stm32` feature, for F303ZE it should be `stm32f303ze`.
/// * [ ] If your board has a special clock or power configuration, make sure that it is
/// set up appropriately.
/// * [ ] If your board has different pin mapping, update any pin numbers or peripherals
/// to match your schematic
///
/// If you are unsure, please drop by the Embassy Matrix chat for support, and let us know:
///
/// * Which example you are trying to run
/// * Which chip and board you are using
///
/// Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org
#[embassy_executor::main]
async fn main(_spawner: embassy_executor::Spawner) {
let device_config = embassy_stm32::Config::default();
let context = embassy_stm32::init(device_config);
let tsc_conf = Config {
ct_pulse_high_length: ChargeTransferPulseCycle::_8,
ct_pulse_low_length: ChargeTransferPulseCycle::_8,
spread_spectrum: false,
spread_spectrum_deviation: SSDeviation::new(2).unwrap(),
spread_spectrum_prescaler: false,
pulse_generator_prescaler: PGPrescalerDivider::_32,
max_count_value: MaxCount::_255,
io_default_mode: false,
synchro_pin_polarity: false,
acquisition_mode: false,
max_count_interrupt: false,
channel_ios: TscIOPin::Group1Io1.into(),
shield_ios: 0, // no shield
sampling_ios: TscIOPin::Group1Io2.into(),
};
let mut g1: PinGroup<embassy_stm32::peripherals::TSC, G1> = PinGroup::new();
g1.set_io1(context.PA0, PinType::Sample);
g1.set_io2(context.PA1, PinType::Channel);
let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, Some(g1), None, None, None, None, None, tsc_conf);
// LED2 on the STM32F303ZE nucleo-board
let mut led = Output::new(context.PB7, Level::High, Speed::Low);
// smaller sample capacitor discharge faster and can be used with shorter delay.
let discharge_delay = 5; // ms
// the interval at which the loop polls for new touch sensor values
let polling_interval = 100; // ms
info!("polling for touch");
loop {
touch_controller.start();
touch_controller.poll_for_acquisition();
touch_controller.discharge_io(true);
Timer::after_millis(discharge_delay).await;
let grp1_status = touch_controller.group_get_status(Group::One);
match grp1_status {
GroupStatus::Complete => {
let group_one_val = touch_controller.group_get_value(Group::One);
info!("{}", group_one_val);
led.set_high();
}
GroupStatus::Ongoing => led.set_low(),
}
Timer::after_millis(polling_interval).await;
}
}

View File

@ -0,0 +1,138 @@
// Example of blocking TSC (Touch Sensing Controller) that lights an LED when touch is detected.
//
// This example demonstrates:
// 1. Configuring a single TSC channel pin
// 2. Using the blocking TSC interface with polling
// 3. Waiting for acquisition completion using `poll_for_acquisition`
// 4. Reading touch values and controlling an LED based on the results
//
// Suggested physical setup on STM32F303ZE Nucleo board:
// - Connect a 1000pF capacitor between pin PA10 and GND. This is your sampling capacitor.
// - Connect one end of a 1K resistor to pin PA9 and leave the other end loose.
// The loose end will act as the touch sensor which will register your touch.
//
// The example uses two pins from Group 4 of the TSC:
// - PA10 as the sampling capacitor, TSC group 4 IO2 (D68 on the STM32F303ZE nucleo-board)
// - PA9 as the channel pin, TSC group 4 IO1 (D69 on the STM32F303ZE nucleo-board)
//
// The program continuously reads the touch sensor value:
// - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value.
// - The LED is turned on when touch is detected (sensor value < 40).
// - Touch values are logged to the console.
//
// Troubleshooting:
// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value.
// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length,
// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity.
//
// Note: Configuration values and sampling capacitor value have been determined experimentally.
// Optimal values may vary based on your specific hardware setup.
// Pins have been chosen for their convenient locations on the STM32F303ZE board. Refer to the
// official relevant STM32 datasheets and user nucleo-board user manuals to find suitable
// alternative pins.
#![no_std]
#![no_main]
use defmt::*;
use embassy_stm32::gpio::{Level, Output, Speed};
use embassy_stm32::tsc::{self, *};
use embassy_stm32::{mode, peripherals};
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup
#[embassy_executor::main]
async fn main(_spawner: embassy_executor::Spawner) {
let device_config = embassy_stm32::Config::default();
let context = embassy_stm32::init(device_config);
let tsc_conf = Config {
ct_pulse_high_length: ChargeTransferPulseCycle::_4,
ct_pulse_low_length: ChargeTransferPulseCycle::_4,
spread_spectrum: false,
spread_spectrum_deviation: SSDeviation::new(2).unwrap(),
spread_spectrum_prescaler: false,
pulse_generator_prescaler: PGPrescalerDivider::_16,
max_count_value: MaxCount::_255,
io_default_mode: false,
synchro_pin_polarity: false,
acquisition_mode: false,
max_count_interrupt: false,
};
let mut g: PinGroupWithRoles<peripherals::TSC, G4> = PinGroupWithRoles::default();
// D68 on the STM32F303ZE nucleo-board
g.set_io2::<tsc_pin_roles::Sample>(context.PA10);
// D69 on the STM32F303ZE nucleo-board
let tsc_sensor = g.set_io1::<tsc_pin_roles::Channel>(context.PA9);
let pin_groups: PinGroups<peripherals::TSC> = PinGroups {
g4: Some(g.pin_group),
..Default::default()
};
let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, pin_groups, tsc_conf).unwrap();
// Check if TSC is ready
if touch_controller.get_state() != State::Ready {
crate::panic!("TSC not ready!");
}
info!("TSC initialized successfully");
// LED2 on the STM32F303ZE nucleo-board
let mut led = Output::new(context.PB7, Level::High, Speed::Low);
// smaller sample capacitor discharge faster and can be used with shorter delay.
let discharge_delay = 5; // ms
// the interval at which the loop polls for new touch sensor values
let polling_interval = 100; // ms
info!("polling for touch");
loop {
touch_controller.set_active_channels_mask(tsc_sensor.pin.into());
touch_controller.start();
touch_controller.poll_for_acquisition();
touch_controller.discharge_io(true);
Timer::after_millis(discharge_delay).await;
match read_touch_value(&mut touch_controller, tsc_sensor.pin).await {
Some(v) => {
info!("sensor value {}", v);
if v < SENSOR_THRESHOLD {
led.set_high();
} else {
led.set_low();
}
}
None => led.set_low(),
}
Timer::after_millis(polling_interval).await;
}
}
const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10;
// attempt to read group status and delay when still ongoing
async fn read_touch_value(
touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Blocking>,
sensor_pin: TscIOPin,
) -> Option<u16> {
for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS {
match touch_controller.group_get_status(sensor_pin.group()) {
GroupStatus::Complete => {
return Some(touch_controller.group_get_value(sensor_pin.group()));
}
GroupStatus::Ongoing => {
// if you end up here a lot, then you prob need to increase discharge_delay
// or consider changing the code to adjust the discharge_delay dynamically
info!("Acquisition still ongoing");
Timer::after_millis(1).await;
}
}
}
None
}

View File

@ -1,6 +1,6 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# replace your chip as listed in `probe-rs chip list`
runner = "probe-rs run --chip STM32L053R8Tx"
runner = "probe-rs run --chip STM32L073RZTx"
[build]
target = "thumbv6m-none-eabi"

View File

@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0"
[dependencies]
# Change stm32l072cz to your chip name, if necessary.
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "unstable-pac", "time-driver-any", "exti", "memory-x"] }
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l073rz", "unstable-pac", "time-driver-any", "exti", "memory-x"] }
embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] }
embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }

View File

@ -0,0 +1,24 @@
# Examples for STM32L0 family
Run individual examples with
```
cargo run --bin <module-name>
```
for example
```
cargo run --bin blinky
```
## Checklist before running examples
You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using.
* [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for L073RZ it should be `probe-rs run --chip STM32L073RZTx`. (use `probe-rs chip list` to find your chip)
* [ ] Update Cargo.toml to have the correct `embassy-stm32` feature. For example for L073RZ it should be `stm32l073rz`. Look in the `Cargo.toml` file of the `embassy-stm32` project to find the correct feature flag for your chip.
* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately.
* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic
If you are unsure, please drop by the Embassy Matrix chat for support, and let us know:
* Which example you are trying to run
* Which chip and board you are using
Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org

View File

@ -1,122 +0,0 @@
// Example of async TSC (Touch Sensing Controller) that lights an LED when touch is detected.
//
// Suggested physical setup on STM32L073RZ Nucleo board:
// - Connect a 1000pF capacitor between pin A0 and GND. This is your sampling capacitor.
// - Connect one end of a 1K resistor to pin A1 and leave the other end loose.
// The loose end will act as touch sensor which will register your touch.
//
// Troubleshooting the setup:
// - If no touch seems to be registered, then try to disconnect the sampling capacitor from GND momentarily,
// now the led should light up. Next try using a different value for the sampling capacitor.
// Also experiment with increasing the values for `ct_pulse_high_length`, `ct_pulse_low_length`, `pulse_generator_prescaler`, `max_count_value` and `discharge_delay`.
//
// All configuration values and sampling capacitor value have been determined experimentally.
// Suitable configuration and discharge delay values are highly dependent on the value of the sample capacitor. For example, a shorter discharge delay can be used with smaller capacitor values.
//
#![no_std]
#![no_main]
use defmt::*;
use embassy_stm32::bind_interrupts;
use embassy_stm32::gpio::{Level, Output, Speed};
use embassy_stm32::tsc::{self, *};
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
bind_interrupts!(struct Irqs {
TSC => InterruptHandler<embassy_stm32::peripherals::TSC>;
});
#[cortex_m_rt::exception]
unsafe fn HardFault(_: &cortex_m_rt::ExceptionFrame) -> ! {
cortex_m::peripheral::SCB::sys_reset();
}
/// This example is written for the nucleo-stm32l073rz, with a stm32l073rz chip.
///
/// Make sure you check/update the following (whether you use the L073RZ or another board):
///
/// * [ ] Update .cargo/config.toml with the correct `probe-rs run --chip STM32L073RZTx`chip name.
/// * [ ] Update Cargo.toml to have the correct `embassy-stm32` feature, for L073RZ it should be `stm32l073rz`.
/// * [ ] If your board has a special clock or power configuration, make sure that it is
/// set up appropriately.
/// * [ ] If your board has different pin mapping, update any pin numbers or peripherals
/// to match your schematic
///
/// If you are unsure, please drop by the Embassy Matrix chat for support, and let us know:
///
/// * Which example you are trying to run
/// * Which chip and board you are using
///
/// Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org
#[embassy_executor::main]
async fn main(_spawner: embassy_executor::Spawner) {
let device_config = embassy_stm32::Config::default();
let context = embassy_stm32::init(device_config);
let config = tsc::Config {
ct_pulse_high_length: ChargeTransferPulseCycle::_4,
ct_pulse_low_length: ChargeTransferPulseCycle::_4,
spread_spectrum: false,
spread_spectrum_deviation: SSDeviation::new(2).unwrap(),
spread_spectrum_prescaler: false,
pulse_generator_prescaler: PGPrescalerDivider::_16,
max_count_value: MaxCount::_255,
io_default_mode: false,
synchro_pin_polarity: false,
acquisition_mode: false,
max_count_interrupt: false,
channel_ios: TscIOPin::Group1Io1.into(),
shield_ios: 0, // no shield
sampling_ios: TscIOPin::Group1Io2.into(),
};
let mut g1: PinGroup<embassy_stm32::peripherals::TSC, G1> = PinGroup::new();
g1.set_io1(context.PA0, PinType::Sample);
g1.set_io2(context.PA1, PinType::Channel);
let mut touch_controller = tsc::Tsc::new_async(
context.TSC,
Some(g1),
None,
None,
None,
None,
None,
None,
None,
config,
Irqs,
);
// Check if TSC is ready
if touch_controller.get_state() != State::Ready {
info!("TSC not ready!");
loop {} // Halt execution
}
info!("TSC initialized successfully");
// LED2 on the STM32L073RZ nucleo-board (PA5)
let mut led = Output::new(context.PA5, Level::High, Speed::Low);
// smaller sample capacitor discharge faster and can be used with shorter delay.
let discharge_delay = 5; // ms
info!("Starting touch_controller interface");
loop {
touch_controller.start();
touch_controller.pend_for_acquisition().await;
touch_controller.discharge_io(true);
Timer::after_millis(discharge_delay).await;
let grp1_status = touch_controller.group_get_status(Group::One);
match grp1_status {
GroupStatus::Complete => {
let group_one_val = touch_controller.group_get_value(Group::One);
info!("{}", group_one_val);
led.set_high();
}
GroupStatus::Ongoing => led.set_low(),
}
}
}

View File

@ -1,116 +0,0 @@
// Example of polling TSC (Touch Sensing Controller) that lights an LED when touch is detected.
//
// Suggested physical setup on STM32L073RZ Nucleo board:
// - Connect a 1000pF capacitor between pin A0 and GND. This is your sampling capacitor.
// - Connect one end of a 1K resistor to pin A1 and leave the other end loose.
// The loose end will act as touch sensor which will register your touch.
//
// Troubleshooting the setup:
// - If no touch seems to be registered, then try to disconnect the sampling capacitor from GND momentarily,
// now the led should light up. Next try using a different value for the sampling capacitor.
// Also experiment with increasing the values for `ct_pulse_high_length`, `ct_pulse_low_length`, `pulse_generator_prescaler`, `max_count_value` and `discharge_delay`.
//
// All configuration values and sampling capacitor value have been determined experimentally.
// Suitable configuration and discharge delay values are highly dependent on the value of the sample capacitor. For example, a shorter discharge delay can be used with smaller capacitor values.
//
#![no_std]
#![no_main]
use defmt::*;
use embassy_stm32::gpio::{Level, Output, Speed};
use embassy_stm32::tsc::{self, *};
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
/// This example is written for the nucleo-stm32l073rz, with a stm32l073rz chip.
///
/// Make sure you check/update the following (whether you use the L073RZ or another board):
///
/// * [ ] Update .cargo/config.toml with the correct `probe-rs run --chip STM32L073RZTx`chip name.
/// * [ ] Update Cargo.toml to have the correct `embassy-stm32` feature, for L073RZ it should be `stm32l073rz`.
/// * [ ] If your board has a special clock or power configuration, make sure that it is
/// set up appropriately.
/// * [ ] If your board has different pin mapping, update any pin numbers or peripherals
/// to match your schematic
///
/// If you are unsure, please drop by the Embassy Matrix chat for support, and let us know:
///
/// * Which example you are trying to run
/// * Which chip and board you are using
///
/// Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org
#[embassy_executor::main]
async fn main(_spawner: embassy_executor::Spawner) {
let device_config = embassy_stm32::Config::default();
let context = embassy_stm32::init(device_config);
let tsc_conf = Config {
ct_pulse_high_length: ChargeTransferPulseCycle::_4,
ct_pulse_low_length: ChargeTransferPulseCycle::_4,
spread_spectrum: false,
spread_spectrum_deviation: SSDeviation::new(2).unwrap(),
spread_spectrum_prescaler: false,
pulse_generator_prescaler: PGPrescalerDivider::_16,
max_count_value: MaxCount::_255,
io_default_mode: false,
synchro_pin_polarity: false,
acquisition_mode: false,
max_count_interrupt: false,
channel_ios: TscIOPin::Group1Io1.into(),
shield_ios: 0, // no shield
sampling_ios: TscIOPin::Group1Io2.into(),
};
let mut g1: PinGroup<embassy_stm32::peripherals::TSC, G1> = PinGroup::new();
g1.set_io1(context.PA0, PinType::Sample);
g1.set_io2(context.PA1, PinType::Channel);
let mut touch_controller = tsc::Tsc::new_blocking(
context.TSC,
Some(g1),
None,
None,
None,
None,
None,
None,
None,
tsc_conf,
);
// Check if TSC is ready
if touch_controller.get_state() != State::Ready {
info!("TSC not ready!");
loop {} // Halt execution
}
info!("TSC initialized successfully");
// LED2 on the STM32L073RZ nucleo-board (PA5)
let mut led = Output::new(context.PA5, Level::High, Speed::Low);
// smaller sample capacitor discharge faster and can be used with shorter delay.
let discharge_delay = 5; // ms
// the interval at which the loop polls for new touch sensor values
let polling_interval = 100; // ms
info!("polling for touch");
loop {
touch_controller.start();
touch_controller.poll_for_acquisition();
touch_controller.discharge_io(true);
Timer::after_millis(discharge_delay).await;
let grp1_status = touch_controller.group_get_status(Group::One);
match grp1_status {
GroupStatus::Complete => {
let group_one_val = touch_controller.group_get_value(Group::One);
info!("{}", group_one_val);
led.set_high();
}
GroupStatus::Ongoing => led.set_low(),
}
Timer::after_millis(polling_interval).await;
}
}

View File

@ -0,0 +1,116 @@
// Example of async TSC (Touch Sensing Controller) that lights an LED when touch is detected.
//
// This example demonstrates:
// 1. Configuring a single TSC channel pin
// 2. Using the blocking TSC interface with polling
// 3. Waiting for acquisition completion using `poll_for_acquisition`
// 4. Reading touch values and controlling an LED based on the results
//
// Suggested physical setup on STM32L073RZ Nucleo board:
// - Connect a 1000pF capacitor between pin PA0 and GND. This is your sampling capacitor.
// - Connect one end of a 1K resistor to pin PA1 and leave the other end loose.
// The loose end will act as the touch sensor which will register your touch.
//
// The example uses two pins from Group 1 of the TSC on the STM32L073RZ Nucleo board:
// - PA0 as the sampling capacitor, TSC group 1 IO1 (label A0)
// - PA1 as the channel pin, TSC group 1 IO2 (label A1)
//
// The program continuously reads the touch sensor value:
// - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value.
// - The LED is turned on when touch is detected (sensor value < 25).
// - Touch values are logged to the console.
//
// Troubleshooting:
// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value.
// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length,
// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity.
//
// Note: Configuration values and sampling capacitor value have been determined experimentally.
// Optimal values may vary based on your specific hardware setup.
// Pins have been chosen for their convenient locations on the STM32L073RZ board. Refer to the
// official relevant STM32 datasheets and nucleo-board user manuals to find suitable
// alternative pins.
//
// Beware for STM32L073RZ nucleo-board, that PA2 and PA3 is used for the uart connection to
// the programmer chip. If you try to use these two pins for TSC, you will get strange
// readings, unless you somehow reconfigure/re-wire your nucleo-board.
// No errors or warnings will be emitted, they will just silently not work as expected.
// (see nucleo user manual UM1724, Rev 14, page 25)
#![no_std]
#![no_main]
use defmt::*;
use embassy_stm32::gpio::{Level, Output, Speed};
use embassy_stm32::tsc::{self, *};
use embassy_stm32::{bind_interrupts, peripherals};
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
bind_interrupts!(struct Irqs {
TSC => InterruptHandler<embassy_stm32::peripherals::TSC>;
});
const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup
#[embassy_executor::main]
async fn main(_spawner: embassy_executor::Spawner) {
let device_config = embassy_stm32::Config::default();
let context = embassy_stm32::init(device_config);
let mut pin_group: PinGroupWithRoles<peripherals::TSC, G1> = PinGroupWithRoles::default();
pin_group.set_io1::<tsc_pin_roles::Sample>(context.PA0);
let sensor = pin_group.set_io2::<tsc_pin_roles::Channel>(context.PA1);
let tsc_conf = Config {
ct_pulse_high_length: ChargeTransferPulseCycle::_4,
ct_pulse_low_length: ChargeTransferPulseCycle::_4,
spread_spectrum: false,
spread_spectrum_deviation: SSDeviation::new(2).unwrap(),
spread_spectrum_prescaler: false,
pulse_generator_prescaler: PGPrescalerDivider::_16,
max_count_value: MaxCount::_255,
io_default_mode: false,
synchro_pin_polarity: false,
acquisition_mode: false,
max_count_interrupt: false,
};
let pin_groups: PinGroups<peripherals::TSC> = PinGroups {
g1: Some(pin_group.pin_group),
..Default::default()
};
let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, tsc_conf, Irqs).unwrap();
// Check if TSC is ready
if touch_controller.get_state() != State::Ready {
info!("TSC not ready!");
return;
}
info!("TSC initialized successfully");
// LED2 on the STM32L073RZ nucleo-board (PA5)
let mut led = Output::new(context.PA5, Level::Low, Speed::Low);
let discharge_delay = 5; // ms
info!("Starting touch_controller interface");
loop {
touch_controller.set_active_channels_mask(sensor.pin.into());
touch_controller.start();
touch_controller.pend_for_acquisition().await;
touch_controller.discharge_io(true);
Timer::after_millis(discharge_delay).await;
let group_val = touch_controller.group_get_value(sensor.pin.group());
info!("Touch value: {}", group_val);
if group_val < SENSOR_THRESHOLD {
led.set_high();
} else {
led.set_low();
}
Timer::after_millis(100).await;
}
}

View File

@ -0,0 +1,142 @@
// Example of blocking TSC (Touch Sensing Controller) that lights an LED when touch is detected.
//
// This example demonstrates:
// 1. Configuring a single TSC channel pin
// 2. Using the blocking TSC interface with polling
// 3. Waiting for acquisition completion using `poll_for_acquisition`
// 4. Reading touch values and controlling an LED based on the results
//
// Suggested physical setup on STM32L073RZ Nucleo board:
// - Connect a 1000pF capacitor between pin PA0 and GND. This is your sampling capacitor.
// - Connect one end of a 1K resistor to pin PA1 and leave the other end loose.
// The loose end will act as the touch sensor which will register your touch.
//
// The example uses two pins from Group 1 of the TSC on the STM32L073RZ Nucleo board:
// - PA0 as the sampling capacitor, TSC group 1 IO1 (label A0)
// - PA1 as the channel pin, TSC group 1 IO2 (label A1)
//
// The program continuously reads the touch sensor value:
// - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value.
// - The LED is turned on when touch is detected (sensor value < 25).
// - Touch values are logged to the console.
//
// Troubleshooting:
// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value.
// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length,
// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity.
//
// Note: Configuration values and sampling capacitor value have been determined experimentally.
// Optimal values may vary based on your specific hardware setup.
// Pins have been chosen for their convenient locations on the STM32L073RZ board. Refer to the
// official relevant STM32 datasheets and nucleo-board user manuals to find suitable
// alternative pins.
//
// Beware for STM32L073RZ nucleo-board, that PA2 and PA3 is used for the uart connection to
// the programmer chip. If you try to use these two pins for TSC, you will get strange
// readings, unless you somehow reconfigure/re-wire your nucleo-board.
// No errors or warnings will be emitted, they will just silently not work as expected.
// (see nucleo user manual UM1724, Rev 14, page 25)
#![no_std]
#![no_main]
use defmt::*;
use embassy_stm32::gpio::{Level, Output, Speed};
use embassy_stm32::tsc::{self, *};
use embassy_stm32::{mode, peripherals};
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup
#[embassy_executor::main]
async fn main(_spawner: embassy_executor::Spawner) {
let device_config = embassy_stm32::Config::default();
let context = embassy_stm32::init(device_config);
let tsc_conf = Config {
ct_pulse_high_length: ChargeTransferPulseCycle::_4,
ct_pulse_low_length: ChargeTransferPulseCycle::_4,
spread_spectrum: false,
spread_spectrum_deviation: SSDeviation::new(2).unwrap(),
spread_spectrum_prescaler: false,
pulse_generator_prescaler: PGPrescalerDivider::_16,
max_count_value: MaxCount::_255,
io_default_mode: false,
synchro_pin_polarity: false,
acquisition_mode: false,
max_count_interrupt: false,
};
let mut g1: PinGroupWithRoles<peripherals::TSC, G1> = PinGroupWithRoles::default();
g1.set_io1::<tsc_pin_roles::Sample>(context.PA0);
let tsc_sensor = g1.set_io2::<tsc_pin_roles::Channel>(context.PA1);
let pin_groups: PinGroups<peripherals::TSC> = PinGroups {
g1: Some(g1.pin_group),
..Default::default()
};
let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, pin_groups, tsc_conf).unwrap();
// Check if TSC is ready
if touch_controller.get_state() != State::Ready {
crate::panic!("TSC not ready!");
}
info!("TSC initialized successfully");
// LED2 on the STM32L073RZ nucleo-board (PA5)
let mut led = Output::new(context.PA5, Level::High, Speed::Low);
// smaller sample capacitor discharge faster and can be used with shorter delay.
let discharge_delay = 5; // ms
// the interval at which the loop polls for new touch sensor values
let polling_interval = 100; // ms
info!("polling for touch");
loop {
touch_controller.set_active_channels_mask(tsc_sensor.pin.into());
touch_controller.start();
touch_controller.poll_for_acquisition();
touch_controller.discharge_io(true);
Timer::after_millis(discharge_delay).await;
match read_touch_value(&mut touch_controller, tsc_sensor.pin).await {
Some(v) => {
info!("sensor value {}", v);
if v < SENSOR_THRESHOLD {
led.set_high();
} else {
led.set_low();
}
}
None => led.set_low(),
}
Timer::after_millis(polling_interval).await;
}
}
const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10;
// attempt to read group status and delay when still ongoing
async fn read_touch_value(
touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Blocking>,
sensor_pin: TscIOPin,
) -> Option<u16> {
for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS {
match touch_controller.group_get_status(sensor_pin.group()) {
GroupStatus::Complete => {
return Some(touch_controller.group_get_value(sensor_pin.group()));
}
GroupStatus::Ongoing => {
// if you end up here a lot, then you prob need to increase discharge_delay
// or consider changing the code to adjust the discharge_delay dynamically
info!("Acquisition still ongoing");
Timer::after_millis(1).await;
}
}
}
None
}

View File

@ -0,0 +1,233 @@
// Example of TSC (Touch Sensing Controller) using multiple pins from the same tsc-group.
//
// What is special about using multiple TSC pins as sensor channels from the same TSC group,
// is that only one TSC pin for each TSC group can be acquired and read at the time.
// To control which channel pins are acquired and read, we must write a mask before initiating an
// acquisition. To help manage and abstract all this business away, we can organize our channel
// pins into acquisition banks. Each acquisition bank can contain exactly one channel pin per TSC
// group and it will contain the relevant mask.
//
// This example demonstrates how to:
// 1. Configure multiple channel pins within a single TSC group
// 2. Use the set_active_channels method to switch between different channels
// 3. Read and interpret touch values from multiple channels in the same group
//
// Suggested physical setup on STM32L073RZ Nucleo board:
// - Connect a 1000pF capacitor between pin PA0 (label A0) and GND. This is the sampling capacitor for TSC
// group 1.
// - Connect one end of a 1K resistor to pin PA1 (label A1) and leave the other end loose.
// The loose end will act as a touch sensor.
//
// - Connect a 1000pF capacitor between pin PB3 (label D3) and GND. This is the sampling capacitor for TSC
// group 5.
// - Connect one end of another 1K resistor to pin PB4 and leave the other end loose.
// The loose end will act as a touch sensor.
// - Connect one end of another 1K resistor to pin PB6 and leave the other end loose.
// The loose end will act as a touch sensor.
//
// The example uses pins from two TSC groups.
// - PA0 as sampling capacitor, TSC group 1 IO1 (label A0)
// - PA1 as channel, TSC group 1 IO2 (label A1)
// - PB3 as sampling capacitor, TSC group 5 IO1 (label D3)
// - PB4 as channel, TSC group 5 IO2 (label D3)
// - PB6 as channel, TSC group 5 IO3 (label D5)
//
// The pins have been chosen to make it easy to simply add capacitors directly onto the board and
// connect one leg to GND, and to easily add resistors to the board with no special connectors,
// breadboards, special wires or soldering required. All you need is the capacitors and resistors.
//
// The program reads the designated channel pins and adjusts the LED blinking
// pattern based on which sensor(s) are touched:
// - No touch: LED off
// - one sensor touched: Slow blinking
// - two sensors touched: Fast blinking
// - three sensors touched: LED constantly on
//
// Troubleshooting:
// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value.
// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length,
// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity.
//
// Note: Configuration values and sampling capacitor value have been determined experimentally.
// Optimal values may vary based on your specific hardware setup.
// Pins have been chosen for their convenient locations on the STM32L073RZ board. Refer to the
// official relevant STM32 datasheets and nucleo-board user manuals to find suitable
// alternative pins.
//
// Beware for STM32L073RZ nucleo-board, that PA2 and PA3 is used for the uart connection to
// the programmer chip. If you try to use these two pins for TSC, you will get strange
// readings, unless you somehow reconfigure/re-wire your nucleo-board.
// No errors or warnings will be emitted, they will just silently not work as expected.
// (see nucleo user manual UM1724, Rev 14, page 25)
#![no_std]
#![no_main]
use defmt::*;
use embassy_stm32::gpio::{Level, Output, Speed};
use embassy_stm32::tsc::{self, *};
use embassy_stm32::{bind_interrupts, mode, peripherals};
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
bind_interrupts!(struct Irqs {
TSC => InterruptHandler<embassy_stm32::peripherals::TSC>;
});
const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10;
async fn read_touch_values(
touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Async>,
tsc_acquisition_bank: &TscAcquisitionBank,
) -> Option<TscAcquisitionBankReadings> {
for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS {
let status = touch_controller.get_acquisition_bank_status(tsc_acquisition_bank);
if status.all_complete() {
let r = touch_controller.get_acquisition_bank_values(tsc_acquisition_bank);
return Some(r);
} else {
info!("Acquisition still ongoing");
Timer::after_millis(1).await;
}
}
info!("Acquisition failed after {} attempts", MAX_GROUP_STATUS_READ_ATTEMPTS);
None
}
const SENSOR_THRESHOLD: u16 = 35;
async fn acquire_sensors(
touch_controller: &mut Tsc<'static, peripherals::TSC, mode::Async>,
tsc_acquisition_bank: &TscAcquisitionBank,
) {
touch_controller.set_active_channels_mask(tsc_acquisition_bank.mask());
touch_controller.start();
touch_controller.pend_for_acquisition().await;
touch_controller.discharge_io(true);
let discharge_delay = 5; // ms
Timer::after_millis(discharge_delay).await;
}
#[embassy_executor::main]
async fn main(_spawner: embassy_executor::Spawner) {
let device_config = embassy_stm32::Config::default();
let context = embassy_stm32::init(device_config);
// ---------- initial configuration of TSC ----------
let mut pin_group1: PinGroupWithRoles<peripherals::TSC, G1> = PinGroupWithRoles::default();
pin_group1.set_io1::<tsc_pin_roles::Sample>(context.PA0);
let tsc_sensor0 = pin_group1.set_io2(context.PA1);
let mut pin_group5: PinGroupWithRoles<peripherals::TSC, G5> = PinGroupWithRoles::default();
pin_group5.set_io1::<tsc_pin_roles::Sample>(context.PB3);
let tsc_sensor1 = pin_group5.set_io2(context.PB4);
let tsc_sensor2 = pin_group5.set_io3(context.PB6);
let config = tsc::Config {
ct_pulse_high_length: ChargeTransferPulseCycle::_16,
ct_pulse_low_length: ChargeTransferPulseCycle::_16,
spread_spectrum: false,
spread_spectrum_deviation: SSDeviation::new(2).unwrap(),
spread_spectrum_prescaler: false,
pulse_generator_prescaler: PGPrescalerDivider::_16,
max_count_value: MaxCount::_255,
io_default_mode: false,
synchro_pin_polarity: false,
acquisition_mode: false,
max_count_interrupt: false,
};
let pin_groups: PinGroups<peripherals::TSC> = PinGroups {
g1: Some(pin_group1.pin_group),
g5: Some(pin_group5.pin_group),
..Default::default()
};
let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, config, Irqs).unwrap();
// ---------- setting up acquisition banks ----------
// sensor0 and sensor1 in this example belong to different TSC-groups,
// therefore we can acquire and read them both in one go.
let bank1 = touch_controller.create_acquisition_bank(TscAcquisitionBankPins {
g1_pin: Some(tsc_sensor0),
g5_pin: Some(tsc_sensor1),
..Default::default()
});
// `sensor1` and `sensor2` belongs to the same TSC-group, therefore we must make sure to
// acquire them one at the time. Therefore, we organize them into different acquisition banks.
let bank2 = touch_controller.create_acquisition_bank(TscAcquisitionBankPins {
g5_pin: Some(tsc_sensor2),
..Default::default()
});
// Check if TSC is ready
if touch_controller.get_state() != State::Ready {
crate::panic!("TSC not ready!");
}
info!("TSC initialized successfully");
// LED2 on the STM32L073RZ nucleo-board (PA5)
let mut led = Output::new(context.PA5, Level::High, Speed::Low);
let mut led_state = false;
loop {
acquire_sensors(&mut touch_controller, &bank1).await;
let readings1: TscAcquisitionBankReadings = read_touch_values(&mut touch_controller, &bank1)
.await
.expect("should be able to read values for bank 1");
acquire_sensors(&mut touch_controller, &bank2).await;
let readings2: TscAcquisitionBankReadings = read_touch_values(&mut touch_controller, &bank2)
.await
.expect("should be able to read values for bank 2");
let mut touched_sensors_count = 0;
for reading in readings1.iter() {
info!("{}", reading);
if reading.sensor_value < SENSOR_THRESHOLD {
touched_sensors_count += 1;
}
}
for reading in readings2.iter() {
info!("{}", reading);
if reading.sensor_value < SENSOR_THRESHOLD {
touched_sensors_count += 1;
}
}
match touched_sensors_count {
0 => {
// No sensors touched, turn off the LED
led.set_low();
led_state = false;
}
1 => {
// One sensor touched, blink slowly
led_state = !led_state;
if led_state {
led.set_high();
} else {
led.set_low();
}
Timer::after_millis(200).await;
}
2 => {
// Two sensors touched, blink faster
led_state = !led_state;
if led_state {
led.set_high();
} else {
led.set_low();
}
Timer::after_millis(50).await;
}
3 => {
// All three sensors touched, LED constantly on
led.set_high();
led_state = true;
}
_ => crate::unreachable!(), // This case should never occur with 3 sensors
}
}
}

View File

@ -2,7 +2,8 @@
# replace STM32F429ZITx with your chip as listed in `probe-rs chip list`
#runner = "probe-rs run --chip STM32L475VGT6"
#runner = "probe-rs run --chip STM32L475VG"
runner = "probe-rs run --chip STM32L4S5QI"
#runner = "probe-rs run --chip STM32L4S5QI"
runner = "probe-rs run --chip STM32L4R5ZITxP"
[build]
target = "thumbv7em-none-eabi"

View File

@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0"
[dependencies]
# Change stm32l4s5vi to your chip name, if necessary.
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l4s5qi", "memory-x", "time-driver-any", "exti", "chrono"] }
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l4r5zi", "memory-x", "time-driver-any", "exti", "chrono"] }
embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] }
embassy-executor = { version = "0.6.3", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768", ] }

View File

@ -0,0 +1,24 @@
# Examples for STM32L4 family
Run individual examples with
```
cargo run --bin <module-name>
```
for example
```
cargo run --bin blinky
```
## Checklist before running examples
You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using.
* [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for L4R5ZI-P it should be `probe-rs run --chip STM32L4R5ZITxP`. (use `probe-rs chip list` to find your chip)
* [ ] Update Cargo.toml to have the correct `embassy-stm32` feature. For example for L4R5ZI-P it should be `stm32l4r5zi`. Look in the `Cargo.toml` file of the `embassy-stm32` project to find the correct feature flag for your chip.
* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately.
* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic
If you are unsure, please drop by the Embassy Matrix chat for support, and let us know:
* Which example you are trying to run
* Which chip and board you are using
Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org

View File

@ -0,0 +1,108 @@
// Example of async TSC (Touch Sensing Controller) that lights an LED when touch is detected.
//
// This example demonstrates:
// 1. Configuring a single TSC channel pin
// 2. Using the async TSC interface
// 3. Waiting for acquisition completion using `pend_for_acquisition`
// 4. Reading touch values and controlling an LED based on the results
//
// Suggested physical setup on STM32L4R5ZI-P board:
// - Connect a 1000pF capacitor between pin PB4 (D25) and GND. This is your sampling capacitor.
// - Connect one end of a 1K resistor to pin PB5 (D21) and leave the other end loose.
// The loose end will act as the touch sensor which will register your touch.
//
// The example uses two pins from Group 2 of the TSC:
// - PB4 (D25) as the sampling capacitor, TSC group 2 IO1
// - PB5 (D21) as the channel pin, TSC group 2 IO2
//
// The program continuously reads the touch sensor value:
// - It starts acquisition, waits for completion using `pend_for_acquisition`, and reads the value.
// - The LED (connected to PB14) is turned on when touch is detected (sensor value < SENSOR_THRESHOLD).
// - Touch values are logged to the console.
//
// Troubleshooting:
// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value.
// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length,
// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity.
//
// Note: Configuration values and sampling capacitor value have been determined experimentally.
// Optimal values may vary based on your specific hardware setup.
#![no_std]
#![no_main]
use defmt::*;
use embassy_stm32::gpio::{Level, Output, Speed};
use embassy_stm32::tsc::{self, *};
use embassy_stm32::{bind_interrupts, peripherals};
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
bind_interrupts!(struct Irqs {
TSC => InterruptHandler<embassy_stm32::peripherals::TSC>;
});
const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup
#[embassy_executor::main]
async fn main(_spawner: embassy_executor::Spawner) {
let device_config = embassy_stm32::Config::default();
let context = embassy_stm32::init(device_config);
let mut pin_group: PinGroupWithRoles<peripherals::TSC, G2> = PinGroupWithRoles::default();
// D25
pin_group.set_io1::<tsc_pin_roles::Sample>(context.PB4);
// D21
let tsc_sensor = pin_group.set_io2::<tsc_pin_roles::Channel>(context.PB5);
let pin_groups: PinGroups<peripherals::TSC> = PinGroups {
g2: Some(pin_group.pin_group),
..Default::default()
};
let tsc_conf = Config {
ct_pulse_high_length: ChargeTransferPulseCycle::_4,
ct_pulse_low_length: ChargeTransferPulseCycle::_4,
spread_spectrum: false,
spread_spectrum_deviation: SSDeviation::new(2).unwrap(),
spread_spectrum_prescaler: false,
pulse_generator_prescaler: PGPrescalerDivider::_16,
max_count_value: MaxCount::_255,
io_default_mode: false,
synchro_pin_polarity: false,
acquisition_mode: false,
max_count_interrupt: false,
};
let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, tsc_conf, Irqs).unwrap();
// Check if TSC is ready
if touch_controller.get_state() != State::Ready {
info!("TSC not ready!");
return;
}
info!("TSC initialized successfully");
let mut led = Output::new(context.PB14, Level::High, Speed::Low);
let discharge_delay = 1; // ms
info!("Starting touch_controller interface");
loop {
touch_controller.set_active_channels_mask(tsc_sensor.pin.into());
touch_controller.start();
touch_controller.pend_for_acquisition().await;
touch_controller.discharge_io(true);
Timer::after_millis(discharge_delay).await;
let group_val = touch_controller.group_get_value(tsc_sensor.pin.group());
info!("Touch value: {}", group_val);
if group_val < SENSOR_THRESHOLD {
led.set_high();
} else {
led.set_low();
}
Timer::after_millis(100).await;
}
}

View File

@ -0,0 +1,147 @@
// # Example of blocking TSC (Touch Sensing Controller) that lights an LED when touch is detected
//
// This example demonstrates how to use the Touch Sensing Controller (TSC) in blocking mode on an STM32L4R5ZI-P board.
//
// ## This example demonstrates:
//
// 1. Configuring a single TSC channel pin
// 2. Using the blocking TSC interface with polling
// 3. Waiting for acquisition completion using `poll_for_acquisition`
// 4. Reading touch values and controlling an LED based on the results
//
// ## Suggested physical setup on STM32L4R5ZI-P board:
//
// - Connect a 1000pF capacitor between pin PB4 (D25) and GND. This is your sampling capacitor.
// - Connect one end of a 1K resistor to pin PB5 (D21) and leave the other end loose.
// The loose end will act as the touch sensor which will register your touch.
//
// ## Pin Configuration:
//
// The example uses two pins from Group 2 of the TSC:
// - PB4 (D25) as the sampling capacitor, TSC group 2 IO1
// - PB5 (D21) as the channel pin, TSC group 2 IO2
//
// ## Program Behavior:
//
// The program continuously reads the touch sensor value:
// - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value.
// - The LED (connected to PB14) is turned on when touch is detected (sensor value < SENSOR_THRESHOLD).
// - Touch values are logged to the console.
//
// ## Troubleshooting:
//
// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value (currently set to 25).
// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length,
// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity.
// - Be aware that for some boards, there might be overlapping concerns between some pins,
// such as UART connections for the programmer. No errors or warnings will be emitted if you
// try to use such a pin for TSC, but you may get strange sensor readings.
//
// Note: Configuration values and sampling capacitor value have been determined experimentally.
// Optimal values may vary based on your specific hardware setup. Refer to the official
// STM32L4R5ZI-P datasheet and user manuals for more information on pin configurations and TSC functionality.
#![no_std]
#![no_main]
use defmt::*;
use embassy_stm32::gpio::{Level, Output, Speed};
use embassy_stm32::tsc::{self, *};
use embassy_stm32::{mode, peripherals};
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup
#[embassy_executor::main]
async fn main(_spawner: embassy_executor::Spawner) {
let device_config = embassy_stm32::Config::default();
let context = embassy_stm32::init(device_config);
let tsc_conf = Config {
ct_pulse_high_length: ChargeTransferPulseCycle::_4,
ct_pulse_low_length: ChargeTransferPulseCycle::_4,
spread_spectrum: false,
spread_spectrum_deviation: SSDeviation::new(2).unwrap(),
spread_spectrum_prescaler: false,
pulse_generator_prescaler: PGPrescalerDivider::_16,
max_count_value: MaxCount::_255,
io_default_mode: false,
synchro_pin_polarity: false,
acquisition_mode: false,
max_count_interrupt: false,
};
let mut g2: PinGroupWithRoles<peripherals::TSC, G2> = PinGroupWithRoles::default();
// D25
g2.set_io1::<tsc_pin_roles::Sample>(context.PB4);
// D21
let tsc_sensor = g2.set_io2::<tsc_pin_roles::Channel>(context.PB5);
let pin_groups: PinGroups<peripherals::TSC> = PinGroups {
g2: Some(g2.pin_group),
..Default::default()
};
let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, pin_groups, tsc_conf).unwrap();
// Check if TSC is ready
if touch_controller.get_state() != State::Ready {
crate::panic!("TSC not ready!");
}
info!("TSC initialized successfully");
let mut led = Output::new(context.PB14, Level::High, Speed::Low);
// smaller sample capacitor discharge faster and can be used with shorter delay.
let discharge_delay = 5; // ms
// the interval at which the loop polls for new touch sensor values
let polling_interval = 100; // ms
info!("polling for touch");
loop {
touch_controller.set_active_channels_mask(tsc_sensor.pin.into());
touch_controller.start();
touch_controller.poll_for_acquisition();
touch_controller.discharge_io(true);
Timer::after_millis(discharge_delay).await;
match read_touch_value(&mut touch_controller, tsc_sensor.pin).await {
Some(v) => {
info!("sensor value {}", v);
if v < SENSOR_THRESHOLD {
led.set_high();
} else {
led.set_low();
}
}
None => led.set_low(),
}
Timer::after_millis(polling_interval).await;
}
}
const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10;
// attempt to read group status and delay when still ongoing
async fn read_touch_value(
touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Blocking>,
sensor_pin: TscIOPin,
) -> Option<u16> {
for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS {
match touch_controller.group_get_status(sensor_pin.group()) {
GroupStatus::Complete => {
return Some(touch_controller.group_get_value(sensor_pin.group()));
}
GroupStatus::Ongoing => {
// if you end up here a lot, then you prob need to increase discharge_delay
// or consider changing the code to adjust the discharge_delay dynamically
info!("Acquisition still ongoing");
Timer::after_millis(1).await;
}
}
}
None
}

View File

@ -0,0 +1,222 @@
// # Example of TSC (Touch Sensing Controller) using multiple pins from the same TSC group
//
// This example demonstrates how to use the Touch Sensing Controller (TSC) with multiple pins, including pins from the same TSC group, on an STM32L4R5ZI-P board.
//
// ## Key Concepts
//
// - Only one TSC pin for each TSC group can be acquired and read at a time.
// - To control which channel pins are acquired and read, we must write a mask before initiating an acquisition.
// - We organize channel pins into acquisition banks to manage this process efficiently.
// - Each acquisition bank can contain exactly one channel pin per TSC group and will contain the relevant mask.
//
// ## This example demonstrates how to:
//
// 1. Configure multiple channel pins within a single TSC group
// 2. Use the set_active_channels method to switch between different channels
// 3. Read and interpret touch values from multiple channels in the same group
//
// ## Suggested physical setup on STM32L4R5ZI-P board:
//
// - Connect a 1000pF capacitor between pin PB12 (D19) and GND. This is the sampling capacitor for TSC group 1.
// - Connect one end of a 1K resistor to pin PB13 (D18) and leave the other end loose. This will act as a touch sensor.
// - Connect a 1000pF capacitor between pin PB4 (D25) and GND. This is the sampling capacitor for TSC group 2.
// - Connect one end of a 1K resistor to pin PB5 (D22) and leave the other end loose. This will act as a touch sensor.
// - Connect one end of another 1K resistor to pin PB6 (D71) and leave the other end loose. This will act as a touch sensor.
//
// ## Pin Configuration:
//
// The example uses pins from two TSC groups:
//
// - Group 1:
// - PB12 (D19) as sampling capacitor (TSC group 1 IO1)
// - PB13 (D18) as channel (TSC group 1 IO2)
// - Group 2:
// - PB4 (D25) as sampling capacitor (TSC group 2 IO1)
// - PB5 (D22) as channel (TSC group 2 IO2)
// - PB6 (D71) as channel (TSC group 2 IO3)
//
// The pins have been chosen for their convenient locations on the STM32L4R5ZI-P board, making it easy to add capacitors and resistors directly to the board without special connectors, breadboards, or soldering.
//
// ## Program Behavior:
//
// The program reads the designated channel pins and adjusts the LED (connected to PB14) blinking pattern based on which sensor(s) are touched:
//
// - No touch: LED off
// - One sensor touched: Slow blinking
// - Two sensors touched: Fast blinking
// - Three sensors touched: LED constantly on
//
// ## Troubleshooting:
//
// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value (currently set to 20).
// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity.
// - Be aware that for some boards there will be overlapping concerns between some pins, for
// example UART connection for the programmer to the MCU and a TSC pin. No errors or warning will
// be emitted if you try to use such a pin for TSC, but you will get strange sensor readings.
//
// Note: Configuration values and sampling capacitor values have been determined experimentally. Optimal values may vary based on your specific hardware setup. Refer to the official STM32L4R5ZI-P datasheet and user manuals for more information on pin configurations and TSC functionality.
#![no_std]
#![no_main]
use defmt::*;
use embassy_stm32::gpio::{Level, Output, Speed};
use embassy_stm32::tsc::{self, *};
use embassy_stm32::{bind_interrupts, mode, peripherals};
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
bind_interrupts!(struct Irqs {
TSC => InterruptHandler<embassy_stm32::peripherals::TSC>;
});
const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10;
async fn read_touch_values(
touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Async>,
tsc_acquisition_bank: &TscAcquisitionBank,
) -> Option<TscAcquisitionBankReadings> {
for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS {
let status = touch_controller.get_acquisition_bank_status(tsc_acquisition_bank);
if status.all_complete() {
let r = touch_controller.get_acquisition_bank_values(tsc_acquisition_bank);
return Some(r);
} else {
info!("Acquisition still ongoing");
Timer::after_millis(1).await;
}
}
info!("Acquisition failed after {} attempts", MAX_GROUP_STATUS_READ_ATTEMPTS);
None
}
const SENSOR_THRESHOLD: u16 = 20;
async fn acquire_sensors(
touch_controller: &mut Tsc<'static, peripherals::TSC, mode::Async>,
tsc_acquisition_bank: &TscAcquisitionBank,
) {
touch_controller.set_active_channels_mask(tsc_acquisition_bank.mask());
touch_controller.start();
touch_controller.pend_for_acquisition().await;
touch_controller.discharge_io(true);
let discharge_delay = 1; // ms
Timer::after_millis(discharge_delay).await;
}
#[embassy_executor::main]
async fn main(_spawner: embassy_executor::Spawner) {
let device_config = embassy_stm32::Config::default();
let context = embassy_stm32::init(device_config);
// ---------- initial configuration of TSC ----------
let mut g1: PinGroupWithRoles<peripherals::TSC, G1> = PinGroupWithRoles::default();
g1.set_io1::<tsc_pin_roles::Sample>(context.PB12);
let sensor0 = g1.set_io2::<tsc_pin_roles::Channel>(context.PB13);
let mut g2: PinGroupWithRoles<peripherals::TSC, G2> = PinGroupWithRoles::default();
g2.set_io1::<tsc_pin_roles::Sample>(context.PB4);
let sensor1 = g2.set_io2(context.PB5);
let sensor2 = g2.set_io3(context.PB6);
let config = tsc::Config {
ct_pulse_high_length: ChargeTransferPulseCycle::_16,
ct_pulse_low_length: ChargeTransferPulseCycle::_16,
spread_spectrum: false,
spread_spectrum_deviation: SSDeviation::new(2).unwrap(),
spread_spectrum_prescaler: false,
pulse_generator_prescaler: PGPrescalerDivider::_16,
max_count_value: MaxCount::_255,
io_default_mode: false,
synchro_pin_polarity: false,
acquisition_mode: false,
max_count_interrupt: false,
};
let pin_groups: PinGroups<peripherals::TSC> = PinGroups {
g1: Some(g1.pin_group),
g2: Some(g2.pin_group),
..Default::default()
};
let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, config, Irqs).unwrap();
// ---------- setting up acquisition banks ----------
// sensor0 and sensor1 belong to different TSC-groups, therefore we can acquire and
// read them both in one go.
let bank1 = touch_controller.create_acquisition_bank(TscAcquisitionBankPins {
g1_pin: Some(sensor0),
g2_pin: Some(sensor1),
..Default::default()
});
// `sensor1` and `sensor2` belongs to the same TSC-group, therefore we must make sure to
// acquire them one at the time. We do this by organizing them into different acquisition banks.
let bank2 = touch_controller.create_acquisition_bank(TscAcquisitionBankPins {
g2_pin: Some(sensor2),
..Default::default()
});
// Check if TSC is ready
if touch_controller.get_state() != State::Ready {
crate::panic!("TSC not ready!");
}
info!("TSC initialized successfully");
let mut led = Output::new(context.PB14, Level::High, Speed::Low);
let mut led_state = false;
loop {
acquire_sensors(&mut touch_controller, &bank1).await;
let readings1: TscAcquisitionBankReadings = read_touch_values(&mut touch_controller, &bank1)
.await
.expect("should be able to read values for bank 1");
acquire_sensors(&mut touch_controller, &bank2).await;
let readings2: TscAcquisitionBankReadings = read_touch_values(&mut touch_controller, &bank2)
.await
.expect("should be able to read values for bank 2");
let mut touched_sensors_count = 0;
for reading in readings1.iter().chain(readings2.iter()) {
info!("{}", reading);
if reading.sensor_value < SENSOR_THRESHOLD {
touched_sensors_count += 1;
}
}
match touched_sensors_count {
0 => {
// No sensors touched, turn off the LED
led.set_low();
led_state = false;
}
1 => {
// One sensor touched, blink slowly
led_state = !led_state;
if led_state {
led.set_high();
} else {
led.set_low();
}
Timer::after_millis(200).await;
}
2 => {
// Two sensors touched, blink faster
led_state = !led_state;
if led_state {
led.set_high();
} else {
led.set_low();
}
Timer::after_millis(50).await;
}
3 => {
// All three sensors touched, LED constantly on
led.set_high();
led_state = true;
}
_ => crate::unreachable!(), // This case should never occur with 3 sensors
}
}
}

View File

@ -2,8 +2,8 @@
#![no_main]
use defmt::*;
use embassy_stm32::bind_interrupts;
use embassy_stm32::tsc::{self, *};
use embassy_stm32::{bind_interrupts, peripherals};
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
@ -33,63 +33,52 @@ async fn main(_spawner: embassy_executor::Spawner) {
synchro_pin_polarity: false,
acquisition_mode: false,
max_count_interrupt: false,
channel_ios: TscIOPin::Group2Io2 | TscIOPin::Group7Io3,
shield_ios: TscIOPin::Group1Io3.into(),
sampling_ios: TscIOPin::Group1Io2 | TscIOPin::Group2Io1 | TscIOPin::Group7Io2,
};
let mut g1: PinGroup<embassy_stm32::peripherals::TSC, G1> = PinGroup::new();
g1.set_io2(context.PB13, PinType::Sample);
g1.set_io3(context.PB14, PinType::Shield);
let mut g1: PinGroupWithRoles<peripherals::TSC, G1> = PinGroupWithRoles::default();
g1.set_io2::<tsc_pin_roles::Sample>(context.PB13);
g1.set_io3::<tsc_pin_roles::Shield>(context.PB14);
let mut g2: PinGroup<embassy_stm32::peripherals::TSC, G2> = PinGroup::new();
g2.set_io1(context.PB4, PinType::Sample);
g2.set_io2(context.PB5, PinType::Channel);
let mut g2: PinGroupWithRoles<peripherals::TSC, G2> = PinGroupWithRoles::default();
g2.set_io1::<tsc_pin_roles::Sample>(context.PB4);
let sensor0 = g2.set_io2(context.PB5);
let mut g7: PinGroup<embassy_stm32::peripherals::TSC, G7> = PinGroup::new();
g7.set_io2(context.PE3, PinType::Sample);
g7.set_io3(context.PE4, PinType::Channel);
let mut g7: PinGroupWithRoles<peripherals::TSC, G7> = PinGroupWithRoles::default();
g7.set_io2::<tsc_pin_roles::Sample>(context.PE3);
let sensor1 = g7.set_io3(context.PE4);
let mut touch_controller = tsc::Tsc::new_async(
context.TSC,
Some(g1),
Some(g2),
None,
None,
None,
None,
Some(g7),
None,
config,
Irqs,
);
let pin_groups: PinGroups<peripherals::TSC> = PinGroups {
g1: Some(g1.pin_group),
g2: Some(g2.pin_group),
g7: Some(g7.pin_group),
..Default::default()
};
touch_controller.discharge_io(true);
Timer::after_millis(1).await;
let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, config, Irqs).unwrap();
touch_controller.start();
let acquisition_bank = touch_controller.create_acquisition_bank(TscAcquisitionBankPins {
g2_pin: Some(sensor0),
g7_pin: Some(sensor1),
..Default::default()
});
touch_controller.set_active_channels_bank(&acquisition_bank);
let mut group_two_val = 0;
let mut group_seven_val = 0;
info!("Starting touch_controller interface");
loop {
touch_controller.start();
touch_controller.pend_for_acquisition().await;
touch_controller.discharge_io(true);
Timer::after_millis(1).await;
if touch_controller.group_get_status(Group::Two) == GroupStatus::Complete {
group_two_val = touch_controller.group_get_value(Group::Two);
let status = touch_controller.get_acquisition_bank_status(&acquisition_bank);
if status.all_complete() {
let read_values = touch_controller.get_acquisition_bank_values(&acquisition_bank);
let group2_reading = read_values.get_group_reading(Group::Two).unwrap();
let group7_reading = read_values.get_group_reading(Group::Seven).unwrap();
info!("group 2 value: {}", group2_reading.sensor_value);
info!("group 7 value: {}", group7_reading.sensor_value);
}
if touch_controller.group_get_status(Group::Seven) == GroupStatus::Complete {
group_seven_val = touch_controller.group_get_value(Group::Seven);
}
info!(
"Group Two value: {}, Group Seven value: {},",
group_two_val, group_seven_val
);
touch_controller.start();
}
}