What peripherals are affected by the undocumented 24 MHz OSC circuit on Teensy 4.0?

Status
Not open for further replies.

ardnew

Member
From Teensyduino's cores/teensy4/startup.c:

Code:
 // ARM SysTick is used for most Ardiuno timing functions, delay(), millis(),
 // micros().  SysTick can run from either the ARM core clock, or from an
 // "external" clock.  NXP documents it as "24 MHz XTALOSC can be the external
 // clock source of SYSTICK" (RT1052 ref manual, rev 1, page 411).  However,
 // NXP actually hid an undocumented divide-by-240 circuit in the hardware, so
 // the external clock is really 100 kHz.  We use this clock rather than the
 // ARM clock, to allow SysTick to maintain correct timing even when we change
 // the ARM clock to run at different speeds.
 #define SYSTICK_EXT_FREQ 100000

I'm curious how this was even discovered, but more to the point I'd like to know why SysTick seems to be the only affected peripheral if this circuit sits in the hardware.

For example, same file, in void ResetHandler(void), the PIT/GPT and UART clock muxes are configured to use this same 24 MHz XTALOSC without divisors:

Code:
     // Configure clocks
     // TODO: make sure all affected peripherals are turned off!
     // PIT & GPT timers to run from 24 MHz clock (independent of CPU speed)
     CCM_CSCMR1 = (CCM_CSCMR1 & ~CCM_CSCMR1_PERCLK_PODF(0x3F)) | CCM_CSCMR1_PERCLK_CLK_SEL;
     // UARTs run from 24 MHz clock (works if PLL3 off or bypassed)
     CCM_CSCDR1 = (CCM_CSCDR1 & ~CCM_CSCDR1_UART_CLK_PODF(0x3F)) | CCM_CSCDR1_UART_CLK_SEL;

And then in cores/teensy4/HardwareSerial.cpp the UART devices assume a bus frequency of 24 MHz (not 100 kHz) to compute an appropriate divisor and over-sample rate:

Code:
 #define UART_CLOCK 24000000
...
 void HardwareSerial::begin(uint32_t baud, uint16_t format)
 {
     //printf("HardwareSerial begin\n");
     float base = (float)UART_CLOCK / (float)baud;
     float besterr = 1e20;
     int bestdiv = 1;
     int bestosr = 4;
     for (int osr=4; osr <= 32; osr++) {
         float div = base / (float)osr;
         int divint = (int)(div + 0.5f);
         if (divint < 1) divint = 1;
         else if (divint > 8191) divint = 8191;
         float err = ((float)divint - div) / div;
         if (err < 0.0f) err = -err;
         if (err <= besterr) {
             besterr = err;
             bestdiv = divint;
             bestosr = osr;
         }
     }

Obviously the UARTs work quite well on Teensy 4.0, so what gives? Why can SysTick and UART both use the same external 24 MHz OSC, yet only one of them has to account for this divide-by-240 circuit?
 
Sorry I am not really sure what you are asking and what information you are trying to understand.


Note: I have not done much of anything with the SYSTICK code other than use results so not much help there, maybe others who have played with it will chime in.

As for the timers used by sub-systems.

They are controlled by the CCM module. See the CCM Clock Tree, in my version of manual about page 1016 and and which clock is used and which dividers are by settings in a few registers.
As you mentioned the PIT/GPT timers run off of 24mhz clock as we currently set the CCM_CSCMR1_PERCLK_CLK_SEL,

I have a Pull Request from awhile ago that for example allowed us to change that and have the IntervalTimer run at a faster speed.
That is if a sketch did: CCM_CSCMR1 &= ~CCM_CSCMR1_PERCLK_CLK_SEL;
Those clocks would run at the same speed as ADC/XBAR or ARM Core Clock / value in CBCDR(IPG_PODF) setting (default to 4). So 600mhz the timers would run at 150mhz.
Again not sure if this will be incorporated or not. But @luni has timer library that does handle the different settings.

Again Hardware Serial, currently is assuming the CCM_CSCDR1_UART_CLK_SEL,
It started out that way, when Paul first started the core. When I did the Hardware Serial class code I just used it. It could be enhanced to make it more flexible, by having it also read
the appropriate CCM settings and compute the the appropriate BAUD rate conversions. I believe the other option was to use the pll3_sw_clk which unless in bypass (CCM_PLL3_BYP) will be 480mhz / 6 = 80mhz (and of course we could divide this again by the CSCDR1(UART_CLK_PODR) which defaults to 1...

As for what other things can run off of the 24mhz clock, again look the the Clock Tree and you will see lots of gates showing as one option OSC...

Not sure if this helps or not.
 
I'm curious how this was even discovered

When I first tried using Systick (about 2 years ago, on the 1052 chip, months before we started the Teensy 4 beta test) is was pretty obviously running much slower than expected when configured for the "external" clock. At the time very little of the core library code was written, so it took quite a bit of testing to figure out the ratio was a fixed divide by 240.

Since then, NXP has updated their documentation. This now appears in the reference manual.

sc.png

I wanted to run Systick from the external clock so we could (maybe) someday have consistent Arduino timing functions even if the CPU clock speed is dynamically changed. Sadly, NXP didn't give us a glitchless mux for the peripheral clock, so work on seamless dynamic clock scaling hasn't happened. But that's still a dream of mine... to someday have really nice support for dynamically changing the clock speed at runtime. That's why I put so much effort into using the Systick external clock.


Obviously the UARTs work quite well on Teensy 4.0, so what gives? Why can SysTick and UART both use the same external 24 MHz OSC, yet only one of them has to account for this divide-by-240 circuit?

This clock divider is only for Systick. At the time, NXP didn't document anything about Systick. They still have very little about it, since it's technically part of the M7 core they license from ARM. It's fully documented in ARM's architecture reference manual, except that the source of the "external" clock is vendor defined and it not even strictly required to be implemented.

All the other peripherals which NXP documents as getting the 24 MHz oscillator clock (when configured to do so) do indeed get 24 MHz.
 
Since then, NXP has updated their documentation. This now appears in the reference manual.
This must come from the 1052 documentation you mention in the comment.

At the time, NXP didn't document anything about Systick.
The current 1062 reference manual (IMXRT1060RM Rev.2 12/2019) still contains precisely 0 instances of the term "SysTick" across 3437 pages!

That's why I put so much effort into using the Systick external clock.
I can see a lot of effort has also been put into scaling the core clock to a given frequency with uint32_t set_arm_clock(uint32_t frequency) -- even if we can't yet take advantage of it. NXP's own 1062 SDK has no such generalized functionality as this routine, very nice!

All the other peripherals which NXP documents as getting the 24 MHz oscillator clock (when configured to do so) do indeed get 24 MHz.

Perfect, exactly what I was asking. Thanks a bunch for all the insight.
 
The current 1062 reference manual (IMXRT1060RM Rev.2 12/2019) still contains precisely 0 instances of the term "SysTick" across 3437 pages!
Mine does, in the exact section that Paul mentioned: 13.3.2.1 (Page 986)
 
The current 1062 reference manual (IMXRT1060RM Rev.2 12/2019) still contains precisely 0 instances of the term "SysTick" across 3437 pages!

Maybe your search was case sensitive? It appears on page 986 in all caps. You can see "SYSTICK" appears twice in the 13.3.2.1 screen grab I posted on msg #3 above.

But as I mentioned earlier, SysTick is part of the ARM Cortex-M7 which NXP licenses from ARM. If you want the details, ARM's document number is "DDI0403E" (Google with that number to get the PDF). Turn to page 676 for the detailed SysTick documentation.
 
Ah, indeed my mistake, I was searching case-sensitive (and assumed the screenshot was from the 1052 manual referenced in the source code).

Thanks again guys
 
Status
Not open for further replies.
Back
Top