LOGO パソコン制御をもっと気軽に  
電子制御をもっと気楽に

低速CPUクロックによる省電力化

Arduino RP2040 2026/01/10

CPUクロックを 2MHz,4MHz,12MHz,40MHz,80MHz,120MHz に設定できます。
PowerSaveHelper.hppに書いてある電流値は Waveshare RP2040Zero でのtypeCの実測値です
download ZIP( 14Kbyte )

Viewer

PowerSaveHelper/src/PowerSaveHelper.cpp
#include "PowerSaveHelper.hpp"


void cPowerSave::WithXOSC()
{
#if 1  
    sleep_run_from_dormant_source(DORMANT_SOURCE_XOSC);
#else
    // 省電力設定
    // 133MHz通常実行時は24mA
    // CLK_SYSのSRCをCLK_REFに変更する
    clocks_hw->clk[clk_sys].ctrl = clocks_hw->clk[clk_ref].ctrl & 0xFFFFFFE | 0x00 ;  
    // CLK_SYS 切り替え完了を待つ
    while( clocks_hw->clk[clk_sys].selected==0 )
    { ; }
    // ここまでの変更で消費電流は約8.8mA
    // PLL_SYS停止
    pll_sys->pwr = 0x2D ;
    ShowClockRegs() ;
    delay(100);
    //
    // これ以降はUSBが停止するのでSerialは使えない
    //
    // PLL_USB停止
    pll_usb->pwr = 0x2D ;
    // ここまでの変更で消費電流は約3.3mA
    // PLL_USBが停止したので clk_peri,clk_usb,clk_adc,clk_rtcも使えない

    rosc_hw->ctrl = 0xD1EFA4;
#endif
    // ここまでの変更で消費電流は約3.1mA

    // clc_periのextsrcをXOSCに切り替える
    // sleep_run_from_dormant_sourceではclk_sysになっている
    #if 1
    cClkPeri::Setup( cClkPeri::XOSC , XOSC_HZ ) ;
    #else
    clocks_hw->clk[clk_peri].ctrl = 4 << 5 | 1<<11 ;
    clock_set_reported_hz(clk_peri,12000000);     // 控え
    #endif

    Serial2.end();
    Serial1.end();
    Serial.end();

    // 2. USBコントローラの割り込みを無効化
    irq_set_enabled(USBCTRL_IRQ, false);
    // 3. USB PHY の電源をオフにする (省電力の核心)
    // USBコントローラをリセット状態に保つことで電力を抑えます
    reset_block(RESETS_RESET_USBCTRL_BITS);
    // ここまでの変更で消費電流は約2.9mA
}

//  
//  RefDiv:1..3  SysDiv 1..2
void cPowerSave::CpuSpeedAdjust( u_int ClkRefDiv , u_int ClkSysDiv ) 
{
  if ( clock_get_hz(clk_sys)> 12*MHZ ){
    // clk_sysのソースをclk_refに変更する
    clocks_hw->clk[clk_sys].ctrl = clocks_hw->clk[clk_sys].ctrl & ~CLOCKS_CLK_SYS_CTRL_SRC_BITS | 
                                                       CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF ;  
    // CLK_SYS 切り替え完了を待つ
    while( clocks_hw->clk[clk_sys].selected==0 )
    { ; }
    // PLL_SYSを停止する
    pll_sys->pwr = PLL_PWR_RESET ;
  }
  // 前提条件 : clk_refのソースはXOSC , clk_sysのソースはclk_ref
  // ClkRefDivは 1~3
  // ClkSysDivは 1~2
  clocks_hw->clk[clk_ref].div =  ClkRefDiv << CLOCKS_CLK_REF_DIV_INT_LSB   ; 
  clock_set_reported_hz(clk_ref, XOSC_HZ / ClkRefDiv );     // 控え 
    // ここまでの変更で消費電流は約1.85mA
    // clockは 6MHz delay(250)が1秒位に遅くなっている
  clocks_hw->clk[clk_sys].div = ClkSysDiv << CLOCKS_CLK_SYS_DIV_INT_LSB ;      // clk_sys 1/3分周 
  clock_set_reported_hz(clk_sys,XOSC_HZ / ClkRefDiv / ClkSysDiv );     // 控え
    // ここまでの変更で消費電流は約1.45mA
    // clockは 2MHz  clk_sysのdevを使っても省電力効果は少ない
#if defined( PICO_RP2040 )
    // delayなどはTIMER( 24bit systick system timer ) を使っていますが
    // sys_refを分周して1MHzを作る必要があります。
    // この為 sys_refを6MHz変更するとtickの分周を6にすることでdelayは正常にります
  watchdog_hw->tick = watchdog_hw->tick & 0xFFFFFFE0 | (XOSC_MHZ/ClkRefDiv) ;
#else
  // RP2350は複数のtick generatorを持っています。今回は関係ありそうな3つを変更します
  {
    tick_gen_num_t Num[] = {tick_gen_num_t::TICK_TIMER0,tick_gen_num_t::TICK_TIMER1,tick_gen_num_t::TICK_WATCHDOG };
    for( int n=0;n<3;n++){
      ticks_slice_hw_t * slice = & ticks_hw->ticks[ Num[n] ] ;
      slice->cycles = slice->cycles & 0xFFFFFFE0 | (XOSC_MHZ/ClkRefDiv)  ;
    }
  }
#endif
}

void cPowerSave::CpuSpeedAdjustPLL( uint FBDIV , uint PDIV1, uint PDIV2 )
{
  //
/* Datasheet 2.18.2. Calculating PLL parameters
  • Oscillator frequency (FOUTVCO) must be in the range 750MHz → 1600MHz
  • Feedback divider (FBDIV) must be in the range 16 → 320
  • The post dividers POSTDIV1 and POSTDIV2 must be in the range 1 → 7
  750MHz/12MHz = 62.5    1600MHz/12MHz =133.3  FDIV must in 63..133  
*/  
  if (  clock_get_hz(clk_sys) > 12*MHZ ){
    // clk_sys src を clk_refに変更
    clocks_hw->clk[clk_sys].ctrl = clocks_hw->clk[clk_sys].ctrl & ~CLOCKS_CLK_SYS_CTRL_SRC_BITS | 
                                                       CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF ;   
    // CLK_SYS 切り替え完了を待つ
    while( clocks_hw->clk[clk_sys].selected==0 )
    { ; }
    // pll_sys の 停止
    pll_sys->pwr = PLL_PWR_RESET ;
  } else if ( clock_get_hz(clk_sys) != 12*MHZ) {
    // clk_ref,clk_sysの div を1に戻す
    CpuSpeedAdjust( 1 , 1 ) ;
  }
  pll_sys->fbdiv_int = FBDIV            ; 
  pll_sys->prim = ( PDIV2 << PLL_PRIM_POSTDIV2_LSB ) | ( PDIV1 << PLL_PRIM_POSTDIV1_LSB )   ; 
  // pll_sys の 開始
  pll_sys->pwr = PLL_PWR_DSMPD_BITS ;
  // wait PLL locked
  while( ( pll_sys->cs & PLL_CS_LOCK_BITS ) == 0)
  {;}
  // clk_sys src を ext_srcに変更
  clocks_hw->clk[clk_sys].ctrl = clocks_hw->clk[clk_sys].ctrl & ~CLOCKS_CLK_SYS_CTRL_SRC_BITS | 
                                                CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX ;  
  // CLK_SYS 切り替え完了を待つ
  while( clocks_hw->clk[clk_sys].selected==0 )
  { ; }

  clock_set_reported_hz(clk_sys, 12 * MHZ * FBDIV / PDIV1 / PDIV2 );     // 控え 
}


// Setup for clk_gpout0..3
void cClkGpout::Setup(eOutPin PinNo, eSrc Src, double div)
{
    // 2040 datasheet の 2.15.6.3. Configuring a GPIO output clock を
    // 参考に作りました
    uint gpclk = 0;
    switch ( PinNo)
    {
#if defined( PICO_RP2350 )  
      case eOutPin::GPOUT13 :
#endif
      case eOutPin::GPOUT21 :
        gpclk = clk_gpout0; 
        break;
#if defined( PICO_RP2350 )    
      case eOutPin::GPOUT15 :
#endif
      case eOutPin::GPOUT23 :
        gpclk = clk_gpout1; 
        break;
      case eOutPin::GPOUT24 :
        gpclk = clk_gpout2; 
        break;
      case eOutPin::GPOUT25 :
        gpclk = clk_gpout3; 
        break;
    }
    div = div + 0.5 / ( 1<< (CLOCKS_CLK_GPOUT0_DIV_FRAC_MSB+1) ) ;
    u_int32_t DivInt  = (u_int32_t) div ;
    u_int32_t DivFrac = (u_int32_t)
          ( div * ( 1<< (CLOCKS_CLK_GPOUT0_DIV_FRAC_MSB+1) ) ) & CLOCKS_CLK_GPOUT0_DIV_FRAC_BITS ;

    // Serial2.printf( "cClkGpout::Setup pin=%d src=%d DInt=%d DFrac=%d \n",PinNo,Src,DivInt,DivFrac ) ;

    // Set up the gpclk generator
    clocks_hw->clk[gpclk].ctrl = ( Src << CLOCKS_CLK_GPOUT0_CTRL_AUXSRC_LSB) |
                                 CLOCKS_CLK_GPOUT0_CTRL_ENABLE_BITS;
    clocks_hw->clk[gpclk].div = ( DivInt << CLOCKS_CLK_GPOUT0_DIV_INT_LSB) | DivFrac ;

    // Set gpio pin to gpclock function
    gpio_set_function( PinNo , GPIO_FUNC_GPCK );
}

// Setup for clk_peri
void cClkPeri::Setup( eSrc Src ,u_int32_t Hz )
{
	if ( Hz != 0 ){
	    // Set up the gpclk generator
	    clocks_hw->clk[clk_peri].ctrl = ( Src << CLOCKS_CLK_PERI_CTRL_AUXSRC_LSB) |
	                                 CLOCKS_CLK_PERI_CTRL_ENABLE_BITS;
	} else {
		clocks_hw->clk[clk_peri].ctrl &= ~CLOCKS_CLK_PERI_CTRL_ENABLE_BITS;
	}
    clock_set_reported_hz(clk_peri , Hz ); 
}

// Setup for clk_adc
// Div[2040:1..3],[2350:1..15]
void cClkAdc::Setup( eSrc Src ,u_int32_t Div,u_int32_t Hz )
{
	if ( Hz != 0 ){
	    // Set up the gpclk generator
	    clocks_hw->clk[clk_adc].ctrl = ( Src << CLOCKS_CLK_ADC_CTRL_AUXSRC_LSB) |
	                                 CLOCKS_CLK_ADC_CTRL_ENABLE_BITS;
      clocks_hw->clk[clk_adc].div = (Div << CLOCKS_CLK_ADC_DIV_INT_LSB ) & CLOCKS_CLK_ADC_DIV_INT_BITS ;
    } else {
	    clocks_hw->clk[clk_adc].ctrl &= ~CLOCKS_CLK_ADC_CTRL_ENABLE_BITS;
    }
    clock_set_reported_hz(clk_adc , Hz ); 
}

CPU is RP2040 group
CPU is RP2040
Setup done

CPU 12MHz Cu=  1
PLL_SYS :     0KHz
PLL_USB :     0KHz
ROSC    :     0KHz
XOSC    : 12000KHz
CLK_REF : 12002KHz
CLK_SYS : 12000KHz  <-- 12MHz
CLK_PERI: 12002KHz
CLK_USB :     0KHz
CLK_ADC :     0KHz
CLK_RTC :     0Hz

CPU 40MHz Cu=  2
PLL_SYS : 40000KHz  <-- PLL_SYSが動作している
PLL_USB :     0KHz
ROSC    :     0KHz
XOSC    : 12000KHz
CLK_REF : 12002KHz
CLK_SYS : 40000KHz  <-- 40MHz
CLK_PERI: 12002KHz
CLK_USB :     0KHz
CLK_ADC :     0KHz
CLK_RTC :     0Hz

CPU 2MHz Cu=  3
PLL_SYS :     0KHz
PLL_USB :     0KHz
ROSC    :     0KHz
XOSC    : 12000KHz
CLK_REF :  4002KHz
CLK_SYS :  2000KHz  <-- 2MHz
CLK_PERI: 12000KHz
CLK_USB :     0KHz
CLK_ADC :     0KHz
CLK_RTC :     0Hz

シーブイデブ e-mail:mnakatani@cvdev-jp.com