lpc55_hal/drivers/
touch.rs

1use core::ops::{Deref, DerefMut};
2use embedded_time::duration::Extensions;
3
4use crate::traits::wg::timer::CountDown;
5use crate::{
6    drivers::{pins, timer, timer::Elapsed, Pin},
7    peripherals::{ctimer, dma::Dma},
8    typestates::{
9        init_state,
10        pin::{function, gpio::direction, state, PinId},
11        ClocksSupportTouchToken,
12    },
13};
14
15/// This driver supports 3 touch sensing channels and this enum maps to the physical ADC channel.
16#[derive(Copy, Clone)]
17pub enum TouchSensorChannel {
18    Channel1 = 3,
19    Channel2 = 4,
20    Channel3 = 5,
21}
22
23pub struct ButtonPins<P1: PinId, P2: PinId, P3: PinId>(
24    pub Pin<P1, state::Analog<direction::Input>>,
25    pub Pin<P2, state::Analog<direction::Input>>,
26    pub Pin<P3, state::Analog<direction::Input>>,
27);
28
29type Adc = crate::peripherals::adc::Adc<init_state::Enabled>;
30
31pub struct TouchSensor<
32    P1: PinId,
33    P2: PinId,
34    P3: PinId,
35    // State : init_state::InitState
36> {
37    threshold: [u32; 3],
38    confidence: u32,
39    adc: Adc,
40    adc_timer: ctimer::Ctimer1<init_state::Enabled>,
41    sample_timer: ctimer::Ctimer2<init_state::Enabled>,
42    _buttons: ButtonPins<P1, P2, P3>,
43    // pub _state: State,
44}
45
46// DMA memory
47// Length should be 4-5 samples more than (3 * 2 * running average) to ensure
48// there's always at least (2 * running average) samples from a given ADC source.
49// Running average == 8 samples
50const RESULTS_LEN: usize = 128; // Total buffer size, should be power of 2 to make more efficient
51const RESULTS_LEAD_SIZE: usize = 3; // Number of initial results to skip, improve latency
52const AVERAGES: usize = 16;
53static mut RESULTS: [u32; RESULTS_LEN] = [0u32; RESULTS_LEN];
54
55// ADC sample period in us
56const CHARGE_PERIOD_US: u32 = 400;
57
58impl<P1, P2, P3> Deref for TouchSensor<P1, P2, P3>
59where
60    P1: PinId,
61    P2: PinId,
62    P3: PinId,
63{
64    type Target = Adc;
65    fn deref(&self) -> &Self::Target {
66        &self.adc
67    }
68}
69
70impl<P1, P2, P3> DerefMut for TouchSensor<P1, P2, P3>
71where
72    P1: PinId,
73    P2: PinId,
74    P3: PinId,
75{
76    fn deref_mut(&mut self) -> &mut Self::Target {
77        &mut self.adc
78    }
79}
80
81impl<P1, P2, P3> TouchSensor<P1, P2, P3>
82where
83    P1: PinId,
84    P2: PinId,
85    P3: PinId,
86{
87    /// Threshold is the ADC sample limit where an is considered.
88    /// Confidence is the number of times the threshold needs to be crossed
89    pub fn new(
90        threshold: [u32; 3],
91        confidence: u32,
92        adc: Adc,
93        adc_timer: ctimer::Ctimer1<init_state::Enabled>,
94        sample_timer: ctimer::Ctimer2<init_state::Enabled>,
95        _charge_pin: Pin<
96            pins::Pio1_16,
97            state::Special<function::MATCH_OUTPUT3<ctimer::Ctimer1<init_state::Enabled>>>,
98        >,
99        buttons: ButtonPins<P1, P2, P3>,
100    ) -> Self {
101        // Last match (3) triggers MAT to TOGGLE (start charging or discharging), interrupt ADC trigger.
102        // Use match (2) for general timing info
103        adc_timer.mcr.write(|w| {
104            w.mr3i()
105                .set_bit() // enable interrupt
106                .mr3r()
107                .set_bit() // reset timer
108                .mr3s()
109                .clear_bit() // do not stop.
110        });
111
112        adc_timer.emr.write(|w| {
113            w.emc3().toggle() // match 3 charge
114        });
115
116        // MR3 starts charge or discharge.  should give ample time to either charge or discharge;
117        adc_timer.mr[3].write(|w| unsafe { w.bits(CHARGE_PERIOD_US) });
118
119        // Clear mr3 interrupt.  Setting bit clears it.
120        adc_timer.ir.write(|w| w.mr3int().set_bit());
121
122        // Sample timer is used to correlate which is the latest sample,
123        // and is syncrondized with ADC DMA transactions
124        sample_timer.mcr.write(|w| unsafe { w.bits(0) });
125        sample_timer.emr.write(|w| unsafe { w.bits(0) });
126        sample_timer.ir.modify(|r, w| unsafe { w.bits(r.bits()) });
127
128        // ADC trigger 6 activates from ctimer1 mat3
129        adc.tctrl[6].write(|w| unsafe {
130            w.hten()
131                .set_bit()
132                .fifo_sel_a()
133                .fifo_sel_a_0()
134                .fifo_sel_b()
135                .fifo_sel_b_0()
136                .tcmd()
137                .bits(3) // Target cmd 3
138                .tpri()
139                .bits(2)
140        });
141
142        adc.cmdl3.write(|w| unsafe {
143            w.adch()
144                .bits(buttons.0.state.channel)
145                .ctype()
146                .ctype_0() // A-side single ended
147                .mode()
148                .mode_0() // standard 12-bit resolution
149        });
150
151        adc.cmdh3.write(|w| unsafe {
152            w.avgs()
153                .avgs_6() // 2^6 averages
154                .cmpen()
155                .bits(0b00) // no compare
156                .loop_()
157                .bits(0) // execute once
158                .next()
159                .bits(4) // 3 -> 4
160                .wait_trig()
161                .set_bit() // wait for trigger again
162        });
163
164        adc.cmdl4.write(|w| unsafe {
165            w.adch()
166                .bits(buttons.1.state.channel)
167                .ctype()
168                .ctype_0()
169                .mode()
170                .mode_0()
171        });
172        adc.cmdh4.write(|w| unsafe {
173            w.avgs()
174                .avgs_6()
175                .cmpen()
176                .bits(0b00)
177                .loop_()
178                .bits(0)
179                .next()
180                .bits(5) // 4 -> 5
181                .wait_trig()
182                .set_bit()
183        });
184
185        adc.cmdl5.write(|w| unsafe {
186            w.adch()
187                .bits(buttons.2.state.channel)
188                .ctype()
189                .ctype_0()
190                .mode()
191                .mode_0()
192        });
193        adc.cmdh5.write(|w| unsafe {
194            w.avgs()
195                .avgs_6()
196                .loop_()
197                .bits(0)
198                .next()
199                .bits(3) // 5 -> 3
200                .wait_trig()
201                .set_bit()
202        });
203
204        Self {
205            adc,
206            adc_timer,
207            sample_timer,
208            _buttons: buttons,
209            threshold,
210            confidence,
211            // _state: init_state::Unknown,
212        }
213    }
214}
215
216impl<P1, P2, P3> TouchSensor<P1, P2, P3>
217where
218    P1: PinId,
219    P2: PinId,
220    P3: PinId,
221{
222    /// Starts DMA and internal timers to enable touch detection
223    pub fn enabled(
224        mut self,
225        dma: &mut Dma<init_state::Enabled>,
226        _token: ClocksSupportTouchToken,
227    ) -> Self //<init_state::Enabled>
228    {
229        dma.configure_adc(&mut self.adc, &mut self.sample_timer, unsafe {
230            &mut RESULTS
231        });
232
233        // Start timers
234        self.adc_timer
235            .tcr
236            .write(|w| w.crst().clear_bit().cen().set_bit());
237
238        self.sample_timer
239            .tcr
240            .write(|w| w.crst().clear_bit().cen().set_bit());
241
242        self
243    }
244}
245
246pub struct TouchResult {
247    pub is_active: bool,
248    pub at: usize,
249}
250
251#[derive(Copy, Clone, Debug, PartialEq)]
252pub enum Edge {
253    Rising,
254    Falling,
255}
256
257#[derive(Copy, Clone, Debug, PartialEq)]
258pub enum Compare {
259    AboveThreshold,
260    BelowThreshold,
261}
262
263// for Enabled TouchSensor
264impl<P1, P2, P3> TouchSensor<P1, P2, P3>
265where
266    P1: PinId,
267    P2: PinId,
268    P3: PinId,
269{
270    /// Count how many elements from a source are available
271    /// Used for debugging
272    pub fn count(&self, bufsel: u8) -> u32 {
273        let results = unsafe { &RESULTS };
274        let mut count = 0u32;
275
276        let starting_point = self.get_starting_point();
277
278        for i in 0..(RESULTS_LEN - RESULTS_LEAD_SIZE) {
279            let src = ((results[(starting_point + i) % RESULTS_LEN] & (0xf << 24)) >> 24) as u8;
280
281            if src == bufsel {
282                count += 1;
283            }
284        }
285        count
286    }
287
288    /// For debugging
289    pub(crate) fn get_results<'a>(&self) -> &'a mut [u32] {
290        unsafe { &mut RESULTS }
291    }
292
293    /// Used after an edge is detected to prevent the same
294    /// edge being detected twice
295    pub fn reset_results(&self, channel: TouchSensorChannel, offset: i32) {
296        let results = unsafe { &mut RESULTS };
297        // match button {
298        // buttons::ButtonTop => {
299        for item in results {
300            if (*item & (0xf << 24)) == ((channel as u32) << 24) {
301                *item = (*item & (!0xffff))
302                    | (self.threshold[(channel as usize) - 3] as i32 + offset) as u32;
303            }
304        }
305        // }
306        // buttons::ButtonBot => {
307        // for i in 0 .. RESULTS_LEN {
308        //     if (results[i] & (0xf << 24)) == (4 << 24) {
309        //         results[i] = (results[i] & (!0xffff)) | (self.threshold[1] as i32 + offset) as u32;
310        //     }
311        // }
312        // }
313        // buttons::ButtonMid => {
314        // for i in 0 .. RESULTS_LEN {
315        //     if (results[i] & (0xf << 24)) == (5 << 24) {
316        //         results[i] = (results[i] & (!0xffff)) | (self.threshold[2] as i32 + offset) as u32;
317        //     }
318        // }
319        // }
320        // _ => {
321        //     panic!("Invalid button for buffer selection");
322        // }
323        // }
324    }
325
326    /// Calculates the oldest sample from ADC in the circular buffer.
327    fn get_starting_point(&self) -> usize {
328        let sync_time = self.sample_timer.tc.read().bits();
329
330        // Skip +RESULTS_LEN samples after the last sample written. (iterate through)
331        if sync_time < 1192 {
332            RESULTS_LEAD_SIZE
333        } else {
334            (((sync_time - 1192) / 802) as usize) + RESULTS_LEN + 1
335        }
336    }
337
338    /// Calculate moving average of samples from a specified ADC source/channel
339    fn measure_buffer(&self, bufsel: u8, filtered: &mut [u32; 40 - AVERAGES]) {
340        let results = unsafe { &RESULTS };
341        let mut buf = [0u32; 40];
342        let mut buf_i = 0;
343
344        let starting_point = self.get_starting_point();
345
346        for i in 0..(RESULTS_LEN - RESULTS_LEAD_SIZE) {
347            let res = results[(starting_point + i) % RESULTS_LEN];
348            let src = ((res & (0xf << 24)) >> 24) as u8;
349
350            if src == bufsel {
351                buf[buf_i] = res & 0xffff;
352                buf_i += 1;
353                if buf_i == buf.len() {
354                    break;
355                }
356            }
357        }
358
359        // Running average of AVERAGES samples to produce (40 - AVERAGES) length filtered buffer
360        for i in 0..(40 - AVERAGES) {
361            let mut sum = 0;
362            for j in 0..AVERAGES {
363                let samp = buf[i + j];
364                sum += samp;
365            }
366            filtered[i] = sum / (AVERAGES as u32);
367        }
368    }
369
370    /// Use threshold and confidence value to see if indicated state has occured in current buffer
371    pub fn get_state(&self, channel: TouchSensorChannel, ctype: Compare) -> TouchResult {
372        let mut filtered = [0u32; 40 - AVERAGES];
373        let bufsel = channel as u8;
374        self.measure_buffer(bufsel, &mut filtered);
375
376        if bufsel == 5 {
377            // dbg!(bufsel);
378            // dbg!(filtered);
379        }
380
381        let mut streak = 0u32;
382
383        match ctype {
384            Compare::AboveThreshold =>
385            {
386                #[allow(clippy::needless_range_loop)]
387                for i in 0..(40 - AVERAGES) {
388                    if filtered[i] > self.threshold[(5 - bufsel) as usize] {
389                        streak += 1;
390                        if streak > self.confidence {
391                            return TouchResult {
392                                is_active: true,
393                                at: i,
394                            };
395                        }
396                    }
397                }
398            }
399            Compare::BelowThreshold =>
400            {
401                #[allow(clippy::needless_range_loop)]
402                for i in 0..(40 - AVERAGES) {
403                    if filtered[i] < self.threshold[(5 - bufsel) as usize] {
404                        streak += 1;
405                        if streak > self.confidence {
406                            return TouchResult {
407                                is_active: true,
408                                at: i,
409                            };
410                        }
411                    }
412                }
413            }
414        }
415        TouchResult {
416            is_active: false,
417            at: 0,
418        }
419    }
420
421    /// Indicate if an edge has occured in current buffer.  Does not reset.
422    pub fn has_edge(&self, channel: TouchSensorChannel, edge_type: Edge) -> bool {
423        let low = self.get_state(channel, Compare::BelowThreshold);
424        let high = self.get_state(channel, Compare::AboveThreshold);
425
426        if high.is_active && low.is_active {
427            match edge_type {
428                Edge::Rising => {
429                    return low.at < high.at;
430                }
431                Edge::Falling => {
432                    return high.at < low.at;
433                }
434            }
435        }
436        false
437    }
438}
439
440/// Used when debugging to correlate the sync timer to which sample in the circular buffer is newest
441pub fn profile_touch_sensing(
442    touch_sensor: &mut TouchSensor<impl PinId, impl PinId, impl PinId>,
443    delay_timer: &mut timer::Timer<impl ctimer::Ctimer<init_state::Enabled>>,
444    copy: &mut [u32],
445    times: &mut [u32],
446) {
447    let start = delay_timer.elapsed().0;
448    let results = touch_sensor.get_results();
449
450    delay_timer.start(300_000.microseconds());
451
452    loop {
453        let mut has_zero = false;
454        for i in 0..125 {
455            if results[i] != 0 {
456                if times[i] == 0 {
457                    times[i] = delay_timer.elapsed().0 - start;
458                    copy[i] = results[i];
459                }
460            } else {
461                has_zero = true;
462            }
463        }
464        if !has_zero {
465            break;
466        }
467    }
468}