Precise measurement/adjustment of SAI1_CLK_ROOT.

hatchjaw

Member
(First, if this isn't the appropriate category in which to post, please feel free to relocate this thread!)

I've posted previously about my networked/distributed audio experiments, and I'm looking again at synchronising audio across a network of Teensy 4.1s as best I can.

I know I can start and stop PLL4 while Teensy is running, and adjust the rate of the SAI1 clock. What I would like, is a way to derive a high-resolution clock from the audio subsystem.

Imagine for a moment that my Teensy is receiving a really accurate 1 PPS signal*. I would like to measure, the number of cycles of the SAI1 clock between two pulses. I know that, ideally, in one second, that clock should cycle 11,289,600 (44100 * 256) times per second; if I detect more cycles than that, then I should adjust PLL4 to generate a lower frequency; fewer cycles, higher frequency. The aim is to condition SAI1 to run at the rate indicated by the 1 PPS source.

My suspicion is there's no equivalent to the cycle count register (ARM_DWT_CYCCNT, 0xE0001004) for PLL4. I tried duplicating and modifying AudioOutputI2S and counting calls to MyOutputI2S::isr(), but that's only called twice per buffer, when DMA requests one half or the other of it, so, at AUDIO_BLOCK_SAMPLES=32, resolution is only ~2.756 kHz. Can anyone suggest a more accurate/higher-resolution approach? Is such a thing possible?

Any advice or suggestions very gratefully received.

* In practice I want to attempt a software PTP implementation, which jitter on the network may render completely unfeasible. In any case, I have to at least give it a try.
 
You might want to look at Dan Drown's Teensy NTP Server. I'm not sure if by "network" you mean Ethernet network, but Dan's project shows how to derive an accurate timestamp using a 1 PPS signal from a GPS module. The basic idea is to use input capture on the 1 PPS and keep track of a "correction factor" to go from timer clock pulses to an accurate time. He uses the 1588 capabilities of the T4 Ethernet peripheral, so I think that might give you some ideas.
 
Hello, and thanks for the reply! I looked up teensy-ntp (and the thread in which Dan introduces that project); I'm certain that I'll be able to learn a lot from it, particularly the PID implementation (to date I've just been using a buffer-fill-level/delay-locked-loop algorithm for low-accuracy sync).

I've been working with unmanaged switches with the aim of keeping things low-cost/accessible, and had ruled out hardware PTP, so (to my discredit) I didn't realise that the iMX RT1060 provides IEEE 1588 support. I guess an unmanaged switch wouldn't remove physical layer timestamp information (it just wouldn't be able to add any of its own); seems to be corroborated by this blog post.

I'm wondering whether anyone can help me better understand the ENET Adjustable Timer Control Register and Timer Value Register (ENET_ATCR/ENET_ATVR). Are these related to/derived from PLL6? Does the ATCR (or ENET_TCCRn) actually adjust the clock from which 1588 timestamps are derived?

Since the thing I'm interested in is synchronising audio between Teensies, how could I use incoming timestamp info in the ENET part of the system to set the SAI1 clock? I guess they, and PLL1, are all derived from the 24 MHz crystal oscillator, so if I were to compare ARM_DWT_CYCCNT (internally consistent) with ENET_ATVR (conditioned to be consistent with external clock) I could calculate a ratio by which to adjust SAI1...

There's a good chance that the above is nonsense, so if anyone can point me in the right direction I'd be really grateful.
 
By way of an update, should it interest anyone, teensy-ntp led me to an NXP application note on IEEE 1588, which in turn led me to a conference paper by a team from Hannover, who forked qnethernet, implementing the ENET timer registers, and a github repository with a PTP implementation. I forked that repository, exposing the nsps adjust value, and applied it as a proportion to the audio sampling rate. I also wrote a sort of hardware abstraction layer so I could better understand how to work with the audio subsystem at a low level; sampling rate conditioning is one thing, syncing DMA interrupts is another, and took some trial and error. It's a messy, undocumented work in progress, but I have something that supports sample-synchronous output across a network of Teensy 4.1s, give or take a couple of ppb remaining drift that I'd like to fix at some point. Here are some plots of before and after PTP-based audio clock conditioning; I played back a 1 Hz pulse on one Teensy, and also sent it to a UDP multicast group, read and played it back on three other Teensies, and measured the rising-edge offsets.

Before:

noptp.png
After:
ptp.png
(Audio client .48.191 falls far enough behind that its ring buffer wraps around in the unconditioned case.)

Supporting the system is a reasonably-priced managed switch. For the measurements above I was using linuxptp and a PTP ethernet dongle, but since it doesn't matter to me whether all the PTP devices think it's 1970, and since I found that using phc2sys to condition the system clock to follow the PTP clock didn't achieve a good sync (probably because the former is a software clock) more recently I've been running one Teensy as a USB audio device, governing audio time for the general purpose machine, and also as a PTP authority for the rest of the Teensies. I wanted to run at 48 kHz (not least because a sampling rate that's a multiple of the buffer size makes syncing audio interrupts a lot easier) so I created a modified teensy4 core, with changes to usb_desc.h, etc. setting a different USB feedback accumulator and increasing AUDIO_TX_SIZE and AUDIO_RX_SIZE.

Plenty more to do, but it's a start. The aim is to support distributed WFS, ambisonics (if it'll parallelise), and ultimately low-cost virtual acoustics. The latter depends on seeing whether I can run real-time impulse response convolutions, which I suspect is contingent on the memory expansion chips being fast enough to cope.
 
Back
Top