-
Notifications
You must be signed in to change notification settings - Fork 16
Description
FreeTouch as used in libraries
- Adafruit_Circuit_Playground 1.11.4
- Adafruit_FreeTouch 1.1.1
- Arduino board: Circuit Playground Express, or any SAM D21 board
- Arduino IDE version: 1.8.13
Summary
During a measurement, FreeTouch makes many reads to some Read-Synchronized registers in the PTC, and does so without the synchronizing read-request sequence. Because the PTC is clocked slower than the system clock, these reads cause a significant stall (~185µs each) of the Peripheral Bridge C, stalling all access to the other peripherals on the same bridge.
If DMA is trying access any of those peripherals (like the DAC or ADC) - then it will be stalled. At ~185µs stall, this is enough to lose several samples at audio rates, causing significant distortion.
Discussion
This bridge stall can be induced with any peripheral on the C bridge that has a Read Synchronized register. To induce the stall:
a) the peripheral needs to be clocked with a clock slower than GCLK0
b) make the register read without first performing the READREQ sequence
Note that if the peripheral is clocked via GLCK0 - as most are by default - then a read to a Read Synchronized register made without the READREQ sequence will still stall the bridge, but it will be for a very short time (docs. imply <8 clocks). This won't materially affect DMA audio.
However, the PTC must be clocked at 4MHz, and this is achieved by setting GCLK1 to use the 8MHz clock source and dividing down. FreeTouch sets this up. And then FreeTouch makes reads that stall the bridge.
Demonstration
This sketch demonstrates the issue on a Circuit Playground Express: https://gist.github.com/mzero/89955e14d41d7e37a439ba806746f632
The sketch uses DMA drive audio out to the DAC "in the background", only using CPU during DMA interrupts to compute the next buffer of samples.
In the foreground (in loop()
) the sketch can call FreeTouch ever 50ms to demonstrate the issue. It can also demonstrate the issue by directly accessing the PTC, or reading TC3 configured to cause the issue. The distortion is plainly audible.
FreeTouch and PTC
The main issue is in the code:
bool adafruit_ptc_is_conversion_finished(Ptc *module_inst) {
return module_inst->CONVCTRL.bit.CONVERT == 0;
}
uint16_t adafruit_ptc_get_conversion_result(Ptc *module_inst) {
sync_config(module_inst);
return module_inst->RESULT.reg;
}
Both CONVERT
and RESULT
appear to be Read Synchronized registers. (Note: the sync_config
call does not implement the read request sequence and does not sync reads.)
In the the Circuit Playground version, this code is:
uint16_t Adafruit_CPlay_FreeTouch::startPtcAcquire(void) {
ptcConfigIOpin();
ptcAcquire();
while (QTOUCH_PTC->CONVCONTROL.bit.CONVERT) {
yield();
}
sync_config();
uint16_t result = QTOUCH_PTC->RESULT.reg;
return result;
}
This makes the issue plainly bad: The CONVERT
read is done in a spin loop... causing stalls lasting over 8ms during which only a fraction of DMA operations get in.
It is clear from the code that the READREQ
register for the PTC hasn't been sleuthed out yet. There are two other SAM D21 modules with READREQ
functionality (RTC & TC timers) and one imagines that PTC's is similarly constructed in layout and operation. Some more reverse engineering of the PTC is in order.