Low Power Implementation of stm32f10x Based on freeRTOS

Posted by andreas on Tue, 10 Sep 2019 06:05:16 +0200

Links to the original text: https://www.cnblogs.com/handsoul/p/11496322.html
  1. Write in front

       Without too much time to update, it may be updated occasionally.
    
       Because of the sudden use of the stm32f10x series and battery-driven projects, it is necessary to optimize the power consumption, other CM3 core series should also apply.
    
  2. background

    Stm32 low power mode, reference manual has written a number of modes, the most convenient is Sleep mode (fast recovery), Stop mode (power saving and data can be saved).
    
    The low power design of FreeRtos can be realized by tickless mode and IDLE hook.
    
    Previously, the tickless mode in FreeRtos was generally based on systick + sleep mode, and the power design was not optimal. The tickless scheme proposed in this paper is RTC alarm interrupt + Stop mode, but it needs some calibration (the LSI calibration scheme is built in stm32, but little information is available).
    
  3. Design Thought

    To reduce power consumption, first of all, there is a set target power consumption range and state. Realization of low power consumption:

(1) Hibernation, that is to say, when there is no work task, the system can be reduced to sleep, and when there is work task, the MCU can be waked up to work immediately through the wake-up mechanism. For freeRtos, it can be implemented with IDLE hook and tickless mode.

       It needs to be analyzed clearly when the system can sleep, what is the time range of sleep, and the maximum limit of sleep under the premise of guaranteeing performance.

       To avoid frequent system wake-up, some time-consuming work can be synchronized to complete together.

(2) Reduce power consumption at work.

   (a). MCU power saving: reduce frequency, close unused peripheral ports, and set port status reasonably.

   (b): Peripheral circuit power saving: through MCU control peripheral mode of work or control power supply.
  1. Implementation scheme

    3.1 Analysis

     (1) The sleep mode is adopted in IDLE hook of FreeRtos to save energy during dormancy.
    
       (2) If you do not work for a long time (such as 10ms or more), you can wake up through stop mode, which relies on tickless mode of freeRtos. But stop mode needs external interruption or RTC alarm clock interruption. For the system of this project, it is best to wake up through RTC alarm clock interruption, and it does not need to rely on external wake-up. Wake-up trigger.
    
       (3) Reduce the main frequency of the system. (By default, 72Mhz is sufficient for analysis of 36Mhz).
    

    3.2 Implementation

       (1) Frequency reduction.
    
               Modify the SetSysClock function in system_stm32f10x.c. Simply modify the macro definition directly.
    

Copy code
1 /* #define SYSCLK_FREQ_HSE HSE_VALUE /
2 #define SYSCLK_FREQ_24MHz 24000000
3 #else
4 / #define SYSCLK_FREQ_HSE HSE_VALUE /
5 / #define SYSCLK_FREQ_24MHz 24000000 /
6 #define SYSCLK_FREQ_36MHz 36000000
7 / #define SYSCLK_FREQ_48MHz 48000000 /
8 / #define SYSCLK_FREQ_56MHz 56000000 */
9 // #define SYSCLK_FREQ_72MHz 72000000
10 #endif
Copy code

     (2) Implementing IDLE HOOK.

             IDLE HOOK is mainly used when the system is idle for a short time. Simply speaking, the system has a task IDLE with the lowest priority, in which a user-defined function can be called. Just define the IDLE hook correlation function in FreeRTOSConfig.h.

             For this system, FreeRtos has its own timing interrupt, which can wake up the system at any time, so it can sleep in IDLE and wake up at any time by timing interrupt.

/ Add the following definitions to FreeRTOSConfig/
#define configUSE_IDLE_HOOK 1
Then the vApplicationIdleHook function can be implemented in any file in the project.

Copy code
void EnterSleepMode(void)
{
SCB->SCR &= ~(SCB_SCR_SLEEPDEEP_Msk);
__WFI();
}

void vApplicationIdleHook(void)
{
EnterSleepMode();
}
Copy code
(3) Implement tickless mode.

          In order to facilitate processing, the contents of systick interrupt need to be migrated to the second interrupt of RTC, and tickless mode needs to enter the dormant state through the interrupt of RTC alarm clock.

          By looking at the manual, LSI clock can work in stop mode. It needs LSI clock as the clock input of RTC. But the problem with LSI clocks is that they don't guarantee accuracy (40 Khz, but in fact it's about 30 to 60 K possible). The hardware of this project is equipped with external crystal oscillator, so RTC can be corrected by external crystal oscillator (which will be specified later).

First, we need to open the tickless mode mechanism, or modify configUSE_TICKLESS_IDLE in freeRTOSConfig.h.

Copy code
#define configUSE_TICKLESS_IDLE 1

extern void PreSleepProcessing(uint32_t ulExpectedIdleTime);
extern void PostSleepProcessing(uint32_t ulExpectedIdleTime);

#define configPRE_SLEEP_PROCESSING(x) PreSleepProcessing(x)
#define configPOST_SLEEP_PROCESSING(x) PostSleepProcessing(x)

Copy code
systick interrupt migrates to RTC interrupt code and calibration code network. This article only describes how to realize the important function of tickless mode through alarm interrupt: vPortSuppress Ticks AndSleep (port. c)

Copy code
extern void RTC_Disable_Tick_Int(void); //is self-implemented,
extern void RTC_Enable_Tick_Int(void);
extern void RTC_SetCounter(unsigned int ulValue);
extern unsigned int RTC_GetCounter(void);

__weak void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
{
uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements;
TickType_t xModifiableIdleTime;

    // Maximum number of milliseconds for RTC Alarm. systick freq = 1000hz, period 1ms.
    
    #define RTC_MAX_ALARM_MS    ((uint32_t)(0xFFFFFFFF) / 10)
    
    if( xExpectedIdleTime > RTC_MAX_ALARM_MS )
    {
        xExpectedIdleTime = RTC_MAX_ALARM_MS;
    }
    

    RTC_Disable_Tick_Int(); // No second interruption.
    
    // Note: In the original code, the system tick interrupt is used to schedule the response. It is not needed here.
    
    ulReloadValue = xExpectedIdleTime - 1 ;
    
    if( ulReloadValue > ulStoppedTimerCompensation )
    {
        ulReloadValue -= ulStoppedTimerCompensation;
    }

    /* Enter a critical section but don't use the taskENTER_CRITICAL()
    method as that will mask interrupts that should exit sleep mode. */
    __disable_irq();
    __dsb( portSY_FULL_READ_WRITE );
    __isb( portSY_FULL_READ_WRITE );

    /* If a context switch is pending or a task is waiting for the scheduler
    to be unsuspended then abandon the low power entry. */
    
    if( eTaskConfirmSleepModeStatus() == eAbortSleep )
    {
        /* Restart from whatever is left in the count register to complete
        this tick period. */
        //portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG;

        /* Restart SysTick. */
        //portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;

        RTC_Enable_Tick_Int();
        
        /* Reset the reload register to the value required for normal tick
        periods. */
        // portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;

        /* Re-enable interrupts - see comments above __disable_irq() call
        above. */
        __enable_irq();
    }
    else
    {
        /* Set the new reload value. */
        //portNVIC_SYSTICK_LOAD_REG = ulReloadValue;

        /* Clear the SysTick count flag and set the count value back to
        zero. */
        //portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;

        /* Restart SysTick. */
        // portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;

        
        xModifiableIdleTime = xExpectedIdleTime;
        
        // Processing before hibernation.
        configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
        
        if( xModifiableIdleTime > 0 )
        {
            __dsb( portSY_FULL_READ_WRITE );
            __wfi();
            __isb( portSY_FULL_READ_WRITE );
        }
        
        // Treatment after dormancy.
        configPOST_SLEEP_PROCESSING( xExpectedIdleTime );

        //Start second interrupt.
        RTC_Enable_Tick_Int();
        
        /* Re-enable interrupts to allow the interrupt that brought the MCU
        out of sleep mode to execute immediately.  see comments above
        __disable_interrupt() call above. */
        __enable_irq();
        __dsb( portSY_FULL_READ_WRITE );
        __isb( portSY_FULL_READ_WRITE );

        /* Disable interrupts again because the clock is about to be stopped
        and interrupts that execute while the clock is stopped will increase
        any slippage between the time maintained by the RTOS and calendar
        time. */
        __disable_irq();
        __dsb( portSY_FULL_READ_WRITE );
        __isb( portSY_FULL_READ_WRITE );
        
        
        // Check for early return.
        // The checking method is to check whether the Count of RTC Alarm has reached the set value. In debugging mode, the simulator wakes up the MCU frequently, causing the MCU to wake up early from sleep.
        
        //        If awakened in advance, it is necessary to confirm whether it is necessary to re-execute the dormancy process according to the remaining dormancy time.
        //        Since only RTC wake-up source is designed in software at present, it is no problem to do so. 
        //        !!!!!!!!!!!!!! If there are other external wake-up sources, this method will lead to abnormal scheduling cycle/sleep cycle.
        
        
    /* Restart second interrupt*/
        
        // Clock adjustment of the system (corresponding task execution adjustment).
        ulCompleteTickPeriods = RTC_GetCounter();//xExpectedIdleTime - 1UL;
        // The time of the system moves forward a little (to ensure that the dormancy time of the dormant task is properly handled)
        vTaskStepTick( ulCompleteTickPeriods );
        
        /* Exit with interrpts enabled. */
        __enable_irq();
    }
}

Copy code
Among them:

              PreSleep Processing and PostSleep Processing are implemented as follows:

Copy code
void PreSleepProcessing(uint32_t ulExpectedIdleTime)
{
// Turn off power-consuming peripherals.
// ADC1_Disable();
// ADC2_Disable();

// Clear the relevant RTC flags.
RTC->CRL &= ~(RTC_CRL_ALRF);
RTC->CRL &= ~(RTC_CRL_SECF);
PWR->CR |= PWR_CR_CWUF;


SCB->SCR |= (SCB_SCR_SLEEPDEEP_Msk);
PWR->CR  &= ~PWR_CR_PDDS;
PWR->CR  &= ~PWR_CR_LPDS;
// SCB->SCR |= (SCB_SCR_SLEEPONEXIT_Msk);

SetRTCAlarm(ulExpectedIdleTime);    // tick = 1ms.

}

void PostSleepProcessing(uint32_t ulExpectedIdleTime)
{
// Clear up.
SCB->SCR &= ~(SCB_SCR_SLEEPDEEP_Msk);
StopRtcAlarm(); // Stop RTC alarm clock.

//SystemInit(); // Using SystemInit is safer. The system clock configuration will be reset. (Crystal oscillator/PLL/bus clock).
SetSysClock();  // Restore the system clock. stop mode after wake-up default HSI.

// Turn on the closed peripheral.
// ADC1_Enable();
// ADC2_Enable();

}
Copy code

         (4) Reasonable design of the working time of the system.

                    Make sure that the task design of the system has a chance to go to sleep, otherwise tickless mode will be meaningless. This is related to specific business, not to mention too much.

                    To sum up, try to call vTaskDelay as many as possible.
  1. Calibration and Compensation

        LSI is inaccurate and needs to be calibrated by external crystal oscillator/PLL when the system is powered on. The specific calibration method can be referred to the official manual (TIM5_CH4 + AFIO).
    
       In addition to the proportional error introduced by LSI, there is an additional time spent switching clocks and operating registers after each wake-up. This part is a fixed error, which can compensate the linear error with the idea of K B calibration.
    
    
    
        Relevant functions of RTC are rather messy and uncluttered, so stick a calibrated function to see the idea roughly, other auxiliary functions will not stick.
    

Copy code
# Define the stability of LSI_INTERVAL_VALID_CNT 10// interval. (In practice, the preceding periods of LSI clocks are unstable.)
# Define MAX_CALIB_TIME_CNT 50// Total Period.

u8 g_ucIntCnt = 0;

u16 g_ausTcnt[MAX_CALIB_TIME_CNT];

// The calibration uses. 36M clock to collect 40k clock, and the value should be around 900.
u32 g_ulLsiTicksHse = 0;

void StartLsiCalib(void)
{
// (1) Enabling LSI clock.
RCC - > CSR |= RCC_CSR_LSION; //Start LSI.(LSION,bit: 0). Open LSI.
While ((RCC-> CSR & RCC_CSR_LSIRDY) === 0); //Waiting for LSI clock stability.

// (2) Start TIM5-CH4. Set LSI clock input capture by CH4.

// Turn on the power supply of TIM5.
RCC->APB1ENR |= RCC_APB1ENR_TIM5EN;

// Open the AFIO clock.
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;

// Mapping LSI clock to TIM5_CH4. to complete capture.
AFIO->MAPR |= AFIO_MAPR_TIM5CH4_IREMAP;

// TIM5_CH4 is configured for input capture.
TIM5->ARR = 0xFFFF; // 
TIM5->PSC = 0;      // The more accurate the system is, the better. The system clock is 36MHz. (Refer to startup - > system_stm32f10x.c.)
TIM5->CR1 = 0x00;   // Set the clock.
TIM5->CCMR2 = (0x01 << 8);  //CC4 maps to TI4. It is configured for input capture.
TIM5->CCER |= (0x01 << 12); // Enabling input capture.

// Interrupt enabling TIM5 - Code is an example of copy, not collated.
u8 tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))>> 0x08;
u8 tmppre = (0x4 - tmppriority);
u8 tmpsub = tmpsub >> tmppriority;

tmppriority = (uint32_t)0 << tmppre;
tmppriority |=  0 & tmpsub;
tmppriority = tmppriority << 0x04;
    
NVIC->IP[TIM5_IRQn] = tmppriority;

NVIC->ISER[TIM5_IRQn >> 0x05] =
  (uint32_t)0x01 << (TIM5_IRQn & (uint8_t)0x1F);
  

TIM5->CR1 |= 0x01; // Start TIM5.

TIM5->SR = 0;

TIM5->DIER = 0x10;

while(g_ucIntCnt < MAX_CALIB_TIME_CNT); // Wait for a certain LSI clock cycle.

//Close TIM5.
TIM5->CCER &= ~(0x01 << 12); // Input capture is prohibited.
TIM5->DIER = 0; // Capturing interrupts is prohibited.
TIM5->CR1 = 0;  // TIM5 is prohibited.


//Correct crystal oscillator accuracy data.
u32 ulTotalSum = 0;
for(int i = LSI_INTERVAL_VALID_CNT ;i < MAX_CALIB_TIME_CNT;++i)
{
    ulTotalSum += g_ausTcnt[i];
}

g_ulLsiTicksHse = (ulTotalSum+((MAX_CALIB_TIME_CNT-LSI_INTERVAL_VALID_CNT)/2)) / (MAX_CALIB_TIME_CNT-LSI_INTERVAL_VALID_CNT);


// Unmap.
AFIO->MAPR &= ~AFIO_MAPR_TIM5CH4_IREMAP;

// Turn off the power supply of TIM5.
RCC->APB1ENR &= ~RCC_APB1ENR_TIM5EN;

// Turn off the power of AFIO.
RCC->APB2ENR &= ~RCC_APB2ENR_AFIOEN;

}

// TIME5CH4 interruption.
void TIM5_IRQHandler(void)
{
static u8 s_ucFirstEnter = 0;
static u16 s_usLastTick = 0;

u16 usCurTicks = 0;

if (s_ucFirstEnter == 0)
{
    s_ucFirstEnter = 1;
    s_usLastTick = TIM5->CCR4;
}
else if (g_ucIntCnt < MAX_CALIB_TIME_CNT)
{
    usCurTicks = TIM5->CCR4;
    g_ausTcnt[g_ucIntCnt++] = (usCurTicks > s_usLastTick) ? (usCurTicks - s_usLastTick) : (usCurTicks + 65535 - s_usLastTick);
    s_usLastTick = usCurTicks;
}

// Clear interrupt mark.
TIM5->SR = ~(0x10);

}
Copy code

      The value of g_ulLsiTicksHse obtained should be around 900 (the main frequency is 36M, LSI is 40K). The tick number of actual dormancy time should be multiplied by (900/g_ulLsiTicksHse) for correction.



     The tasks of time error testing are as follows:

Copy code
void Task_LedCtl(void * pData)
{
if(pData){}

for(;;)
{
    TestLed();     // Reverse GPIO.
    vTaskDelay(25);// Delay can be adjusted to obtain processing errors at different times. For example: 25/50/100/250, etc.
}

}
Copy code

      By collecting a fixed task processing GPIO time through the oscilloscope, the actual error data of the system are obtained as follows:

Copy code
/*

  • Actual measurements of test points (ms)

  • 500 503.6

  • 250 253.6

  • 100 103

  • 50 52.8

  • After analysis, the linear error K = 0.9982. The error is about 0.18%. It should be introduced by integer calculation. It is solved by modifying tick calculation method.

  •                B = 2.85/2 = 1.425 ms, which belongs to the error introduced by system wake-up and recovery settings. It needs to be compensated.
    
  • After verification, the relative error of timing after calibration is less than 0.1%. It satisfies the requirement of use.
    */
    Copy code

       After the above calibration, the clock of the system is basically accurate and meets the needs of use.
    
       The actual dormancy time Tx = t0 * K + B, where T0 is the expected dormancy time, K/B is calculated by the above method.
    
    
    
       The value of B here also affects another important parameter. This time must be much larger than the value of B mentioned above before it is meaningful.
    

# Definition configEXPECTED_IDLE_TIME_BEFORE_SLEEP 8// / Allow dormancy when the system is out of idle.

  1. Summary

     The realization of power design requires:
    

Reasonable design of task dormancy time, if there are tasks running at all times, the system can not enter the low-power mode.
Understand IDLE Hook and Tickless mechanisms.
Design wake-up source in low power mode, ensure timing accuracy, pay attention to the processing before and after wake-up.
Appropriate reduction of the main frequency of the system is conducive to reducing the power consumption of the system in normal operation.
6. References

    (1) freertos version for stm32 can be downloaded from the official website.

    (2) The Chinese version of STM32 data manual is available on ST official website.

Construction of Shenzhen Website:https://www.sz886.com

Topics: network simulator less