Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 4 of 4

Thread: Teensy 3.6 - Dynamic CPU clock switching

  1. #1
    Junior Member
    Join Date
    Apr 2018
    Posts
    3

    Teensy 3.6 - Dynamic CPU clock switching

    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.

  2. #2
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    23,046
    Quote Originally Posted by rjeremy View Post
    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.

  3. #3
    Junior Member
    Join Date
    Apr 2018
    Posts
    3
    Thanks, Paul. Much appreciated.

    Quote Originally Posted by PaulStoffregen View Post
    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)?

  4. #4
    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.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •