TencentOS-tiny 功耗管理 (二十 二)- tickless(低功耗)

一、功耗管理

  1. tickless

概述

TencentOS tiny的tickless機制提供了一套非週期性時鐘的方案,在系統無需systick驅動調度的情況下,停掉systick。

初級功耗管理方案下,因爲還有系統systick的存在,因此係統進入idle任務後,並不會在睡眠模式下停留太久。要想進入到更極致的低功耗狀態,需要暫停systick。

arm架構提供三級低功耗模式,sleep、stop、standby模式,三種模式運行功耗逐次降低,standby模式最低。TencentOS tiny的內核提供了簡潔清晰的接口來管理各級模式。

API講解

void tos_tickless_wkup_alarm_install(k_cpu_lpwr_mode_t mode, k_tickless_wkup_alarm_t *wkup_alarm);

此接口用以安裝各低功耗模式下的喚醒鬧鐘。當內核進入tickless模式下後,systick以及停止了,因此需要其他計時器來將CPU從低功耗模式下喚醒。

根據arm v7m的芯片規格,三種模式下的喚醒源分別爲:

  • sleep

    CPU進入sleep模式後,可以由systick、硬件timer、RTC時鐘喚醒(wakeup/alarm中斷)。

  • stop

    CPU進入stop模式後,可以由RTC時鐘(wakeup/alarm中斷)喚醒。

  • standby

    CPU進入standby模式後,只可由RTC時鐘的alarm中斷喚醒(還可以通過外部管腳喚醒,但這不屬於TencentOS tiny內核機制設計的範疇)。

k_tickless_wkup_alarm_t定義如下:

typedef struct k_tickless_wakeup_alarm_st {
    int         (*init)(void);
    int         (*setup)(k_time_t millisecond);
    int         (*dismiss)(void);
    k_time_t    (*max_delay)(void); /* in millisecond */
} k_tickless_wkup_alarm_t;

一個喚醒鬧鐘有四個成員方法:

  • init

    鬧鐘初始化函數。

  • setup

    鬧鐘設定函數,入參爲鬧鐘到期時間(單位毫秒)。此鬧鐘在設定完畢後的millisecond毫秒時來中斷。

  • dismiss

    鬧鐘解除函數,執行完後鬧鐘中斷不會再來。

  • max_delay

    此鬧鐘最長的到期時間(單位爲毫秒)。

k_err_t tos_tickless_wkup_alarm_init(k_cpu_lpwr_mode_t mode);

此函數用來初始化特定模式下的喚醒鬧鐘(實際上調用的是tos_tickless_wkup_alarm_install接口中安裝的k_tickless_wkup_alarm_t的init方法)。

k_err_t tos_pm_cpu_lpwr_mode_set(k_cpu_lpwr_mode_t cpu_lpwr_mode);

設置內核在tickless模式下進入的CPU低功耗模式。

編程實例

1、在tos_config.h中,配置低功耗組件開關TOS_CFG_PWR_MGR_EN:

#define TOS_CFG_PWR_MGR_EN 1u

2、在tos_config.h中,配置tickless組件開關TOS_CFG_TICKLESS_EN:

#define TOS_CFG_TICKLESS_EN 1u

3、STM32CubeMX修改libraries配置:

3、編寫main.c示例代碼:

/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "rtc.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "cmsis_os.h" 
#include "stdio.h"

#include "tos_k.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
#define STK_SIZE_TASK_DEMO      512 

#define PRIO_TASK_DEMO          4
 
k_stack_t stack_task_demo[STK_SIZE_TASK_DEMO]; 

k_task_t task_demo;

extern void entry_task_demo(void *arg);

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */


/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
void timer_callback(void *arg)
{
    printf("timer callback: %ld\n", tos_systick_get());
}

void entry_task_demo(void *arg)
{
    k_timer_t tmr;

    // 創建一個軟件定時器,每6000個tick觸發一次
    tos_timer_create(&tmr, 0u, 6000u, timer_callback, K_NULL, TOS_OPT_TIMER_PERIODIC);
    tos_timer_start(&tmr);

    // 此任務體內每3000個tick運行一次
    while (K_TRUE) {
        printf("entry task demo: %ld\n", tos_systick_get());
        tos_task_delay(3000);
    }
}

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_TIM6_Init();
  MX_RTC_Init();
  /* USER CODE BEGIN 2 */
	
	tos_knl_init();
	(void)tos_task_create(&task_demo, "demo1", entry_task_demo, NULL,
                        PRIO_TASK_DEMO, stack_task_demo, STK_SIZE_TASK_DEMO, 0); 
  tos_knl_start();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		
  }
  /* USER CODE END 3 */
}

4、實現tos_bsp_tickless_setup回調,直接在源碼中找到tickless文件夾,複製到工程項目根目錄即可,路徑爲:board\TOS_tiny_EVK_STM32L431CBT6\BSP\Src\tickless,同時把board\TOS_tiny_EVK_STM32L431CBT6\BSP\Inc\tickless中.h文件複製到tickless文件夾即可。

修改如下所示:

5、rtc.c文件,新增__weak字段

6、tim.c文件,新增__weak字段

7、bsp_pwr_mgr.c文件

8、bsp_pm_device.c文件

9、bsp_tickless_alarm.c文件

#include "tos_k.h"

#include "stm32l0xx_hal.h"
#include "stm32l0xx_hal_tim.h"
#include "stm32l0xx_hal_rtc.h"

#if TOS_CFG_TICKLESS_EN > 0u

static void tickless_systick_suspend(void)
{
    cpu_systick_suspend();
    cpu_systick_pending_reset();
}

static void tickless_systick_resume(void)
{
    cpu_systick_resume();
}

static void tickless_systick_wkup_alarm_expires_set(k_time_t millisecond)
{
    cpu_systick_expires_set(millisecond);
}

static int tickless_systick_wkup_alarm_setup(k_time_t millisecond)
{
    tickless_systick_suspend();
    tickless_systick_wkup_alarm_expires_set(millisecond);
    tickless_systick_resume();
    return 0;
}

static int tickless_systick_wkup_alarm_dismiss(void)
{
    // TODO:
    // if not wakeup by systick(that's say another interrupt), need to identify this and fix
    return 0;
}

static k_time_t tickless_systick_wkup_alarm_max_delay(void)
{
    return cpu_systick_max_delay_millisecond();
}

k_tickless_wkup_alarm_t tickless_wkup_alarm_systick = {
    .init       = K_NULL,
    .setup      = tickless_systick_wkup_alarm_setup,
    .dismiss    = tickless_systick_wkup_alarm_dismiss,
    .max_delay  = tickless_systick_wkup_alarm_max_delay,
};


/////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////

static TIM_HandleTypeDef tim6;

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *tim_handler)
{
    if (tim_handler->Instance == TIM6) {
        __HAL_RCC_TIM6_CLK_ENABLE();

        /* TIM6 interrupt Init */
        HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 0, 0);
        HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
    }
}

void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef *tim_handler)
{
    if (tim_handler->Instance == TIM6) {
        /* Peripheral clock disable */
        __HAL_RCC_TIM6_CLK_DISABLE();

        /* TIM6 interrupt Deinit */
        HAL_NVIC_DisableIRQ(TIM6_DAC_IRQn);
    }
}

static int tickless_tim6_wkup_alarm_init(void)
{
    tim6.Instance = TIM6;
    tim6.Init.Prescaler = 0;
    tim6.Init.CounterMode = TIM_COUNTERMODE_UP;
    tim6.Init.Period = 0;
    tim6.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    tim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    HAL_TIM_Base_Init(&tim6);
    return 0;
}

static int tickless_tim6_wkup_alarm_setup(k_time_t millisecond)
{
    tim6.Init.Prescaler = 8000 - 1;
    tim6.Init.Period = (millisecond * 10) - 1;

    HAL_TIM_Base_Stop(&tim6);
    __HAL_TIM_CLEAR_IT(&tim6, TIM_IT_UPDATE);

    HAL_TIM_Base_Init(&tim6);
    HAL_TIM_Base_Start_IT(&tim6);
    return 0;
}

static int tickless_tim6_wkup_alarm_dismiss(void)
{
    TOS_CPU_CPSR_ALLOC();

    TOS_CPU_INT_DISABLE();

    HAL_TIM_Base_Stop(&tim6);
    HAL_TIM_Base_Stop_IT(&tim6);

    TOS_CPU_INT_ENABLE();
    return 0;
}

static k_time_t tickless_tim6_wkup_alarm_max_delay(void)
{
    k_time_t millisecond;
    uint32_t max_period;

    max_period = ~((uint32_t)0u);
    millisecond = (max_period - 1) / 10;
    return millisecond;
}

void TIM6_DAC_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&tim6);
}

k_tickless_wkup_alarm_t tickless_wkup_alarm_tim = {
    .init       = tickless_tim6_wkup_alarm_init,
    .setup      = tickless_tim6_wkup_alarm_setup,
    .dismiss    = tickless_tim6_wkup_alarm_dismiss,
    .max_delay  = tickless_tim6_wkup_alarm_max_delay,
};


/////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////

static RTC_HandleTypeDef rtc_handler;

static HAL_StatusTypeDef tickless_rtc_time_set(uint8_t hour, uint8_t minu, uint8_t sec, uint8_t format)
{
    RTC_TimeTypeDef rtc_time;

    rtc_time.Hours = hour;
    rtc_time.Minutes = minu;
    rtc_time.Seconds = sec;
    rtc_time.TimeFormat = format;
    rtc_time.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
    rtc_time.StoreOperation = RTC_STOREOPERATION_RESET;
    return HAL_RTC_SetTime(&rtc_handler, &rtc_time, RTC_FORMAT_BIN);
}

static HAL_StatusTypeDef tickless_rtc_date_set(uint8_t year, uint8_t month, uint8_t date, uint8_t week)
{
    RTC_DateTypeDef rtc_date;

    rtc_date.Date = date;
    rtc_date.Month = month;
    rtc_date.WeekDay = week;
    rtc_date.Year = year;
    return HAL_RTC_SetDate(&rtc_handler, &rtc_date, RTC_FORMAT_BIN);
}

static int tickless_rtc_wkup_alarm_init(void)
{
    rtc_handler.Instance = RTC;
    rtc_handler.Init.HourFormat = RTC_HOURFORMAT_24;
    rtc_handler.Init.AsynchPrediv = 0X7F;
    rtc_handler.Init.SynchPrediv = 0XFF;
    rtc_handler.Init.OutPut = RTC_OUTPUT_DISABLE;
    rtc_handler.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
    rtc_handler.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;

    if (HAL_RTC_Init(&rtc_handler) != HAL_OK) {
        return -1;
    }

    if (HAL_RTCEx_BKUPRead(&rtc_handler, RTC_BKP_DR0) != 0X5050) {
        tickless_rtc_time_set(23, 59, 56, RTC_HOURFORMAT12_PM);
        tickless_rtc_date_set(15, 12, 27, 7);
        HAL_RTCEx_BKUPWrite(&rtc_handler, RTC_BKP_DR0,0X5050);
    }

    return 0;
}

static int tickless_rtc_wkupirq_wkup_alarm_setup(k_time_t millisecond)
{
    uint32_t wkup_clock = RTC_WAKEUPCLOCK_CK_SPRE_16BITS;
    if (millisecond < 1000) {
        millisecond = 1000;
    }
    uint32_t wkup_count = (millisecond / 1000) - 1;

    __HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&rtc_handler, RTC_FLAG_WUTF);

    HAL_RTCEx_SetWakeUpTimer_IT(&rtc_handler, wkup_count, wkup_clock);

    HAL_NVIC_SetPriority(RTC_IRQn, 0x02, 0x02);
    HAL_NVIC_EnableIRQ(RTC_IRQn);
    return 0;
}

static int tickless_rtc_wkupirq_wkup_alarm_dismiss(void)
{
#if defined(STM32F0) || defined(STM32L0)
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
#endif

    __HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&rtc_handler, RTC_FLAG_WUTF);

    if (HAL_RTCEx_DeactivateWakeUpTimer(&rtc_handler) != HAL_OK) {
        return -1;
    }

    HAL_NVIC_DisableIRQ(RTC_IRQn);
    return 0;
}

static k_time_t tickless_rtc_wkupirq_wkup_alarm_max_delay(void)
{
    return 0xFFFF * K_TIME_MILLISEC_PER_SEC;
}

void HAL_RTC_MspInit(RTC_HandleTypeDef *rtc_handler)
{
    RCC_OscInitTypeDef rcc_osc;
    RCC_PeriphCLKInitTypeDef periph_clock;

    __HAL_RCC_PWR_CLK_ENABLE();
    HAL_PWR_EnableBkUpAccess();

    rcc_osc.OscillatorType = RCC_OSCILLATORTYPE_LSE;
    rcc_osc.PLL.PLLState = RCC_PLL_NONE;
    rcc_osc.LSEState = RCC_LSE_ON;
    HAL_RCC_OscConfig(&rcc_osc);

    periph_clock.PeriphClockSelection = RCC_PERIPHCLK_RTC;
    periph_clock.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
    HAL_RCCEx_PeriphCLKConfig(&periph_clock);

    __HAL_RCC_RTC_ENABLE();
}

void RTC_WKUP_IRQHandler(void)
{
    HAL_RTCEx_WakeUpTimerIRQHandler(&rtc_handler);
}

void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *rtc_handler)
{
}

k_tickless_wkup_alarm_t tickless_wkup_alarm_rtc_wkupirq = {
    .init       = tickless_rtc_wkup_alarm_init,
    .setup      = tickless_rtc_wkupirq_wkup_alarm_setup,
    .dismiss    = tickless_rtc_wkupirq_wkup_alarm_dismiss,
    .max_delay  = tickless_rtc_wkupirq_wkup_alarm_max_delay,
};



/////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////
static int tickless_rtc_alarmirq_wkup_alarm_setup(k_time_t millisecond)
{
    uint8_t hour, minute, second, subsecond, date;

    RTC_AlarmTypeDef rtc_alarm;
    RTC_TimeTypeDef rtc_time;
    RTC_DateTypeDef rtc_date;

    HAL_RTC_GetTime(&rtc_handler, &rtc_time, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&rtc_handler, &rtc_date, RTC_FORMAT_BIN);

    hour = rtc_time.Hours;
    minute = rtc_time.Minutes;
    second = rtc_time.Seconds;
#if 0
    date = rtc_date.Date;
#else
    date = rtc_date.WeekDay;
#endif

    printf("before >>>  %d  %d %d %d\n", date, hour, minute, second);

    /* I know it's ugly, I will find a elegant way. Welcome to tell me, 3ks~ */
    second += millisecond / K_TIME_MILLISEC_PER_SEC;
    if (second >= 60) {
        minute += 1;
        second -= 60;
    }
    if (minute >= 60) {
        hour += 1;
        minute -= 60;
    }
    if (hour >= 24) {
        date += 1;
        hour -= 24;
    }

    printf("after >>>  %d  %d %d %d\n", date, hour, minute, second);

    rtc_alarm.AlarmTime.Hours = hour;
    rtc_alarm.AlarmTime.Minutes = minute;
    rtc_alarm.AlarmTime.Seconds = second;
    rtc_alarm.AlarmTime.SubSeconds = 0;
    rtc_alarm.AlarmTime.TimeFormat = RTC_HOURFORMAT12_AM;

    rtc_alarm.AlarmMask = RTC_ALARMMASK_NONE;
    rtc_alarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_NONE;
    rtc_alarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_WEEKDAY; // RTC_ALARMDATEWEEKDAYSEL_DATE; // RTC_ALARMDATEWEEKDAYSEL_WEEKDAY;
    rtc_alarm.AlarmDateWeekDay = date;
    rtc_alarm.Alarm = RTC_ALARM_A;
    HAL_RTC_SetAlarm_IT(&rtc_handler, &rtc_alarm, RTC_FORMAT_BIN);

/*
		//STM32L0系列沒有Alarm中斷線
    HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 0x01, 0x02);
    HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
*/

    // __HAL_PWR_GET_FLAG(PWR_FLAG_WU)


    __HAL_RCC_AHB_FORCE_RESET();        //復位所有IO口
    __HAL_RCC_PWR_CLK_ENABLE();         //使能PWR時鐘

    // __HAL_RCC_BACKUPRESET_FORCE();      //復位備份區域
    HAL_PWR_EnableBkUpAccess();         //後備區域訪問使能

    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
    __HAL_RTC_WRITEPROTECTION_DISABLE(&rtc_handler);//關閉RTC寫保護

    //關閉RTC相關中斷
    __HAL_RTC_WAKEUPTIMER_DISABLE_IT(&rtc_handler,RTC_IT_WUT);
#if 0
    __HAL_RTC_TIMESTAMP_DISABLE_IT(&rtc_handler,RTC_IT_TS);
    __HAL_RTC_ALARM_DISABLE_IT(&rtc_handler,RTC_IT_ALRA|RTC_IT_ALRB);
#endif

    //清除RTC相關中斷標誌位
    __HAL_RTC_ALARM_CLEAR_FLAG(&rtc_handler,RTC_FLAG_ALRAF|RTC_FLAG_ALRBF);
    __HAL_RTC_TIMESTAMP_CLEAR_FLAG(&rtc_handler,RTC_FLAG_TSF);
    __HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&rtc_handler,RTC_FLAG_WUTF);

    // __HAL_RCC_BACKUPRESET_RELEASE();                    //備份區域復位結束
    __HAL_RTC_WRITEPROTECTION_ENABLE(&rtc_handler);     	 //使能RTC寫保護


#ifdef STM32F4
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);                  	 //清除Wake_UP標誌
#endif

#ifdef STM32F7
    // __HAL_PWR_CLEAR_WAKEUP_FLAG(PWR_WAKEUP_PIN_FLAG1);  //清除Wake_UP標誌
#endif

    // HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);           //設置WKUP用於喚醒

    return 0;
}

static int tickless_rtc_alarmirq_wkup_alarm_dismiss(void)
{
#if 1
    // __HAL_PWR_GET_FLAG(PWR_FLAG_WU);

    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);

    // __HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&rtc_handler, RTC_FLAG_ALRAF);

    __HAL_RTC_ALARM_CLEAR_FLAG(&rtc_handler, RTC_FLAG_ALRAF);

#if 0
    if (HAL_RTCEx_DeactivateWakeUpTimer(&rtc_handler) != HAL_OK) {
        return -1;
    }
#endif

//    HAL_NVIC_DisableIRQ(RTC_Alarm_IRQn);		//STM32L0系列沒有Alarm中斷線
    return 0;
#endif
}

static k_time_t tickless_rtc_alarmirq_wkup_alarm_max_delay(void)
{
    return 0xFFFF; // just kidding, I will fix it out. Welcome to tell me, 3ks~ */
}

void RTC_Alarm_IRQHandler(void)
{
    HAL_RTC_AlarmIRQHandler(&rtc_handler);
}

void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *rtc_handler)
{
}

k_tickless_wkup_alarm_t tickless_wkup_alarm_rtc_alarmirq = {
    .init       = tickless_rtc_wkup_alarm_init,
    .setup      = tickless_rtc_alarmirq_wkup_alarm_setup,
    .dismiss    = tickless_rtc_alarmirq_wkup_alarm_dismiss,
    .max_delay  = tickless_rtc_alarmirq_wkup_alarm_max_delay,
};

#endif

10、爲了觀察在tickless時是否確實沒有systick中斷,在tos_sys.c的idle任務體內加一句調試代碼:

__STATIC__ void knl_idle_entry(void *arg)
{
    arg = arg; // make compiler happy

    while (K_TRUE) {
#if TOS_CFG_TASK_DYNAMIC_CREATE_EN > 0u
			// 這裏在idle任務體內加上一句打印,如果systick正常開啓,在沒有用戶任務運行時,此調試信息會不斷打印;如果是tickless狀態,此調試信息應該只會第一次進入idle任務時,或在用戶任務等待到期,或用戶的軟件定時器到期時,纔打印一次。
			  printf("idle entry: %ld\n", tos_systick_get());
        task_free_all();
#endif

#if TOS_CFG_PWR_MGR_EN > 0u
        pm_power_manager();
#endif
    }
}

11、運行效果:

源碼鏈接:Git

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章