mirror of
https://github.com/embassy-rs/embassy.git
synced 2025-09-28 04:40:39 +00:00
STM32-TSC: enable discriminating between pins within same TSC group and improve TSC library in general
This commit is contained in:
parent
1a1d5c4689
commit
721c6820d4
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,4 +5,7 @@ Cargo.lock
|
||||
third_party
|
||||
/Cargo.toml
|
||||
out/
|
||||
# editor artifacts
|
||||
.zed
|
||||
.neoconf.json
|
||||
*.vim
|
||||
|
209
embassy-stm32/src/tsc/acquisition_banks.rs
Normal file
209
embassy-stm32/src/tsc/acquisition_banks.rs
Normal 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)
|
||||
}
|
||||
}
|
175
embassy-stm32/src/tsc/config.rs
Normal file
175
embassy-stm32/src/tsc/config.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
21
embassy-stm32/src/tsc/errors.rs
Normal file
21
embassy-stm32/src/tsc/errors.rs
Normal 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
675
embassy-stm32/src/tsc/pin_groups.rs
Normal file
675
embassy-stm32/src/tsc/pin_groups.rs
Normal 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);
|
456
embassy-stm32/src/tsc/tsc.rs
Normal file
456
embassy-stm32/src/tsc/tsc.rs
Normal 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 {}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
93
embassy-stm32/src/tsc/types.rs
Normal file
93
embassy-stm32/src/tsc/types.rs
Normal 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)),
|
||||
}
|
||||
}
|
||||
}
|
24
examples/stm32f3/README.md
Normal file
24
examples/stm32f3/README.md
Normal 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
|
@ -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;
|
||||
}
|
||||
}
|
138
examples/stm32f3/src/bin/tsc_blocking.rs
Normal file
138
examples/stm32f3/src/bin/tsc_blocking.rs
Normal 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
|
||||
}
|
@ -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"
|
||||
|
@ -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"] }
|
||||
|
24
examples/stm32l0/README.md
Normal file
24
examples/stm32l0/README.md
Normal 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
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
116
examples/stm32l0/src/bin/tsc_async.rs
Normal file
116
examples/stm32l0/src/bin/tsc_async.rs
Normal 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;
|
||||
}
|
||||
}
|
142
examples/stm32l0/src/bin/tsc_blocking.rs
Normal file
142
examples/stm32l0/src/bin/tsc_blocking.rs
Normal 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
|
||||
}
|
233
examples/stm32l0/src/bin/tsc_multipin.rs
Normal file
233
examples/stm32l0/src/bin/tsc_multipin.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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", ] }
|
||||
|
24
examples/stm32l4/README.md
Normal file
24
examples/stm32l4/README.md
Normal 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
|
108
examples/stm32l4/src/bin/tsc_async.rs
Normal file
108
examples/stm32l4/src/bin/tsc_async.rs
Normal 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;
|
||||
}
|
||||
}
|
147
examples/stm32l4/src/bin/tsc_blocking.rs
Normal file
147
examples/stm32l4/src/bin/tsc_blocking.rs
Normal 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
|
||||
}
|
222
examples/stm32l4/src/bin/tsc_multipin.rs
Normal file
222
examples/stm32l4/src/bin/tsc_multipin.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user