Teensy 3.6 - Dynamic CPU clock switching

rjeremy

New member
Hi, all.

Board: Teensy 3.6

Basic idea is for Teensy to run at 24 MHz while doing some
non demanding background tasks until receiving request via HW Serial.

After that, clock is restored to 180 MHz and stays in that state until
requested tasks are done and then goes back to 24 MHz.

Teensy boots with 180 MHz clock.

Everything runs fine, but I have some questions.

Here's relevant code:
Code:
const int CPU_CLOCK = 180000000;
const int CPU_UNDERCLOCK = 24000000;

const int mainBusBaudRate = 250000;

const int SERIAL_DIV = (CPU_CLOCK * 2 + (mainBusBaudRate >> 1)) / mainBusBaudRate;
const int SERIAL_DIV_UNDERCLOCK = (CPU_UNDERCLOCK * 2 + (mainBusBaudRate >> 1)) / mainBusBaudRate;

Code:
FASTRUN void UnderClock24()
{
	SMC_PMCTRL = SMC_PMCTRL_RUNM(0);
	while (SMC_PMSTAT == SMC_PMSTAT_HSRUN) { ; }

	MCG_C5 = MCG_C5_PRDIV0(1);
	MCG_C6 = MCG_C6_PLLS | MCG_C6_VDIV0(8);

	while (!(MCG_S & MCG_S_PLLST)) { ; }
	while (!(MCG_S & MCG_S_LOCK0)) { ; }
	
	SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(3, 3, 3, 3);
	SIM_CLKDIV2 = SIM_CLKDIV2_USBDIV(1);

	MCG_C1 = MCG_C1_CLKS(0x00) | MCG_C1_FRDIV(0x04);
	while ((MCG_S & MCG_S_CLKST_MASK) != MCG_S_CLKST(0x03)) { ; }

	SYST_RVR = CPU_UNDERCLOCK / 1000 - 1;

	UART0_BDH = (SERIAL_DIV_UNDERCLOCK >> 13) & 0x1F;
	UART0_BDL = (SERIAL_DIV_UNDERCLOCK >> 5) & 0xFF;
	UART0_C4 = SERIAL_DIV_UNDERCLOCK & 0x1F;

	UART0_C1 = UART_C1_ILT;
	UART0_TWFIFO = 2;
	UART0_RWFIFO = 4;
	UART0_PFIFO = UART_PFIFO_TXFE | UART_PFIFO_RXFE;
}

Code:
FASTRUN void RestoreClock180()
{
	SMC_PMCTRL = SMC_PMCTRL_RUNM(3);
	while (SMC_PMSTAT != SMC_PMSTAT_HSRUN) { ; }

	MCG_C5 = MCG_C5_PRDIV0(1);
	MCG_C6 = MCG_C6_PLLS | MCG_C6_VDIV0(29);

	while (!(MCG_S & MCG_S_PLLST)) { ; }
	while (!(MCG_S & MCG_S_LOCK0)) { ; }

	SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIVS(0, 2, 0, 6);
	SIM_CLKDIV2 = SIM_CLKDIV2_USBDIV(0);

	MCG_C1 = MCG_C1_CLKS(0) | MCG_C1_FRDIV(4);
	while ((MCG_S & MCG_S_CLKST_MASK) != MCG_S_CLKST(3)) { ; }

	SIM_SOPT2 = SIM_SOPT2_USBSRC | SIM_SOPT2_IRC48SEL | SIM_SOPT2_TRACECLKSEL | SIM_SOPT2_CLKOUTSEL(6);
	
	SYST_RVR = CPU_CLOCK / 1000 - 1;	

	UART0_BDH = (SERIAL_DIV >> 13) & 0x1F;
	UART0_BDL = (SERIAL_DIV >> 5) & 0xFF;
	UART0_C4 = SERIAL_DIV & 0x1F;

	UART0_C1 = UART_C1_ILT;
	UART0_TWFIFO = 2; 
	UART0_RWFIFO = 4;
	UART0_PFIFO = UART_PFIFO_TXFE | UART_PFIFO_RXFE;

}

Is PLL lock mandatory?
Code:
while (!(MCG_S & MCG_S_PLLST)) { ; }
while (!(MCG_S & MCG_S_LOCK0)) { ; }
Been testing it for some time now (~10000 requests and responses) and it seems to work fine if I omit it.
I mean, should it run correctly?
Can someone shed some light regarding this, please?


If I understood it correctly, this block of code reset FIFO buffer and status registers.
Code:
UART0_C1 = UART_C1_ILT;
UART0_TWFIFO = 2; 
UART0_RWFIFO = 4;
UART0_PFIFO = UART_PFIFO_TXFE | UART_PFIFO_RXFE;

What is suggested procedure here, to reset it every time clock switch occurs or leave it as is?

And finally, should I be concerned about CPU longevity
and overall stability with all this frequent clock switching?

Many thanks.
 
Is PLL lock mandatory?

No, and you probably should not mess with the PLL at all. Or if you do, read the MGC chapter of the reference manual and pay very careful attention to the many clocking modes. You're not supposed to change the PLL while running in the mode which actually uses it.

But you can write to SIM_CLKDIV1 at any time. That is easy way to switch speeds, at least within the ranges SIM_CLKDIV1 allows.

What is suggested procedure here, to reset it every time clock switch occurs or leave it as is?

Serail1 and Serial2 run from F_CPU, so you'll need to re-init them if you want the baud rates to work.

Almost all other peripherals run from F_BUS. If you change the F_BUS speed, pretty much everything will change.

Be careful not to overclock the flash memory. It's the one part of the chip which doesn't work well with overclocking. Well, also the I2S MCLK generator is a known issue above 192 MHz.

And finally, should I be concerned about CPU longevity
and overall stability with all this frequent clock switching?

SIM_CLKDIV1 switching should be perfectly fine. Even PLL switching should be ok, if you go to the trouble of managing the run modes.

Quartz crystals do age. It's a factor even if left running normally, but usually the only consequence is a slight increase in error from the ideal frequency. I do not know what effect regularly starting and stopping the crystal would have. Probably not an issue anyway for your case. But if anything were to be affected, I'd look at the crystal.
 
Thanks, Paul. Much appreciated.

Serail1 and Serial2 run from F_CPU, so you'll need to re-init them if you want the baud rates to work.

Yes, understandable. But what about FIFO buffer?
Would it be a good practice to redefine it every time after Serial re-init?
Code:
UART0_C1 = UART_C1_ILT;
UART0_TWFIFO = 2; 
UART0_RWFIFO = 4;
UART0_PFIFO = UART_PFIFO_TXFE | UART_PFIFO_RXFE;

One more thing I forgot to ask in OP...
If I'm about to use Ethernet (W5500) at both clock speeds (24 MHz and 180 MHz), would this
Code:
#define SPI_ETHERNET_SETTINGS SPISettings(-1, MSBFIRST, SPI_MODE0)
suffice for Ethernet library to take advantage of maximum supported SPI bus speed (12 MHz and 30 MHz, respectively)?
 
I do exactly that on T3.5, but in my case I compile for 24MHz and dynamically switch to 48MHz depending on the load. At minimum it requires a change to F_CPU via SIM_CLKDIV1 and millis() counter via SYST_RVR. But you have to tweak few files in the the system libraries so that micros() work properly and all reports are correct, which is a bit of a pain as it requires patching every release.

When compiled for 24MHz Teensy consumes less power since the F_BUS and peripherals run at low speed. I've also tried switching from 24MHz to 96MHz but I2C module complains, perhaps the F_CPU/F_BUS ratio is too high for it to work properly. I can compile for 48MHz and dynamically switch to 96MHz, but so far I have enough CPU power at 24/48.
 
Hi All,

I want to do the opposite but I can't. Iwant to program my teensy 3.6 with "board_build.f_cpu = 24000000L" (24Mhz) and modify on fly CPU speed with clock180. In this case my teensy start with low consumation.
It's possible to do this?

Many thanks.
 
Not sure if this adds anything not known? PJRC includes code for T_3.6 going over 120 MHz down to 120 MHz and exiting hsrun to program the EEPROM that cannot be done in hsrun mode, then returning the HSRUN and prior F_CPU clock speed

Look at: int kinetis_hsrun_disable(void) and int kinetis_hsrun_enable(void)
in: {ID1.8.x Local Install}\hardware\teensy\avr\cores\teensy3\mk20dx128.c or IDE 2 C:\Users\<< YOU >>\AppData\Local\Arduino15\packages\teensy\hardware\avr\0.58.3\cores\teensy3\mk20dx128.c

There the note is:
// First, reduce the CPU clock speed, but do not change
// the peripheral speed (F_BUS). Serial1 & Serial2 baud
// rates will be impacted, but most other peripherals
// will continue functioning at the same speed.

T_3.5: Ignore the SMC_PMSTAT_HSRUN test {the goal here is not to cross that boundary} and set, but it shows detection of the various F_CPU && F_BUS speed settings to be expected on entry, or set on exit. The speeds tested are all on the HIGH side.
> the T_3.5 doesn't have HSRUN, and here assuming the T_3.5 uses the same settings for the F_CPU speed.


On the T_3.6 the speed set here is DOWN to 120 MHz, so going between 180 and 24 MHz would require finding the settings that would go to 24 MHz, and setting the hsrun mode as done in this code.
On the T_3.5 case the correct values for 24 and 48 or 96 MHz would be needed. And above is seems the Serial UARTS are in use so that would need attention.
 
Back
Top