api-hal-timebase.c 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. #include <api-hal-timebase.h>
  2. #include <api-hal-timebase-timer.h>
  3. #include <api-hal-power.h>
  4. #include <FreeRTOS.h>
  5. #include <cmsis_os.h>
  6. #define API_HAL_TIMEBASE_CLK_FREQUENCY 32768
  7. #define API_HAL_TIMEBASE_TICK_PER_SECOND 1024
  8. #define API_HAL_TIMEBASE_CLK_PER_TICK (API_HAL_TIMEBASE_CLK_FREQUENCY / API_HAL_TIMEBASE_TICK_PER_SECOND)
  9. #define API_HAL_TIMEBASE_TICK_PER_EPOCH (API_HAL_TIMEBASE_TIMER_MAX / API_HAL_TIMEBASE_CLK_PER_TICK)
  10. #define API_HAL_TIMEBASE_MAX_SLEEP (API_HAL_TIMEBASE_TICK_PER_EPOCH - 1)
  11. #ifdef API_HAL_TIMEBASE_DEBUG
  12. #include <stm32wbxx_ll_gpio.h>
  13. #define LED_GREEN_PORT GPIOA
  14. #define LED_GREEN_PIN LL_GPIO_PIN_2
  15. #endif
  16. typedef struct {
  17. // Sleep control
  18. volatile uint16_t insomnia;
  19. // Tick counters
  20. volatile uint32_t in_sleep;
  21. volatile uint32_t in_awake;
  22. // Error counters
  23. volatile uint32_t sleep_error;
  24. volatile uint32_t awake_error;
  25. } ApiHalTimbase;
  26. ApiHalTimbase api_hal_timebase = {
  27. .insomnia = 0,
  28. .in_sleep = 0,
  29. .in_awake = 0,
  30. .sleep_error = 0,
  31. .awake_error = 0,
  32. };
  33. void api_hal_timebase_init() {
  34. api_hal_timebase_timer_init();
  35. LL_DBGMCU_APB1_GRP2_FreezePeriph(LL_DBGMCU_APB1_GRP2_LPTIM2_STOP);
  36. LL_LPTIM_EnableIT_CMPM(API_HAL_TIMEBASE_TIMER);
  37. LL_LPTIM_EnableIT_ARRM(API_HAL_TIMEBASE_TIMER);
  38. LL_LPTIM_SetAutoReload(API_HAL_TIMEBASE_TIMER, API_HAL_TIMEBASE_TIMER_MAX);
  39. LL_LPTIM_SetCompare(API_HAL_TIMEBASE_TIMER, API_HAL_TIMEBASE_CLK_PER_TICK);
  40. LL_LPTIM_StartCounter(API_HAL_TIMEBASE_TIMER, LL_LPTIM_OPERATING_MODE_CONTINUOUS);
  41. }
  42. uint16_t api_hal_timebase_insomnia_level() {
  43. return api_hal_timebase.insomnia;
  44. }
  45. void api_hal_timebase_insomnia_enter() {
  46. api_hal_timebase.insomnia++;
  47. }
  48. void api_hal_timebase_insomnia_exit() {
  49. api_hal_timebase.insomnia--;
  50. }
  51. void LPTIM2_IRQHandler(void) {
  52. // Autoreload
  53. const bool arrm_flag = LL_LPTIM_IsActiveFlag_ARRM(API_HAL_TIMEBASE_TIMER);
  54. if(arrm_flag) {
  55. LL_LPTIM_ClearFLAG_ARRM(API_HAL_TIMEBASE_TIMER);
  56. }
  57. if(LL_LPTIM_IsActiveFlag_CMPM(API_HAL_TIMEBASE_TIMER)) {
  58. LL_LPTIM_ClearFLAG_CMPM(API_HAL_TIMEBASE_TIMER);
  59. // Store important value
  60. uint16_t cnt = api_hal_timebase_timer_get_cnt();
  61. uint16_t cmp = api_hal_timebase_timer_get_cmp();
  62. uint16_t current_tick = cnt / API_HAL_TIMEBASE_CLK_PER_TICK;
  63. uint16_t compare_tick = cmp / API_HAL_TIMEBASE_CLK_PER_TICK;
  64. // Calculate error
  65. // happens when HAL or other high priority IRQ takes our time
  66. int32_t error = (int32_t)compare_tick - current_tick;
  67. api_hal_timebase.awake_error += ((error>0) ? error : -error);
  68. // Calculate and set next tick
  69. uint16_t next_tick = current_tick + 1;
  70. api_hal_timebase_timer_set_cmp(next_tick * API_HAL_TIMEBASE_CLK_PER_TICK);
  71. // Notify OS
  72. api_hal_timebase.in_awake ++;
  73. if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
  74. xPortSysTickHandler();
  75. }
  76. }
  77. }
  78. static inline uint32_t api_hal_timebase_sleep(TickType_t expected_idle_ticks) {
  79. // Store important value before going to sleep
  80. const uint16_t before_cnt = api_hal_timebase_timer_get_cnt();
  81. const uint16_t before_tick = before_cnt / API_HAL_TIMEBASE_CLK_PER_TICK;
  82. // Calculate and set next wakeup compare value
  83. const uint16_t expected_cnt = (before_tick + expected_idle_ticks - 2) * API_HAL_TIMEBASE_CLK_PER_TICK;
  84. api_hal_timebase_timer_set_cmp(expected_cnt);
  85. HAL_SuspendTick();
  86. // Go to stop2 mode
  87. #ifdef API_HAL_TIMEBASE_DEBUG
  88. LL_GPIO_SetOutputPin(LED_GREEN_PORT, LED_GREEN_PIN);
  89. #endif
  90. api_hal_power_deep_sleep();
  91. #ifdef API_HAL_TIMEBASE_DEBUG
  92. LL_GPIO_ResetOutputPin(LED_GREEN_PORT, LED_GREEN_PIN);
  93. #endif
  94. HAL_ResumeTick();
  95. // Spin till we are in timer safe zone
  96. while(!api_hal_timebase_timer_is_safe()) {}
  97. // Store current counter value, calculate current tick
  98. const uint16_t after_cnt = api_hal_timebase_timer_get_cnt();
  99. const uint16_t after_tick = after_cnt / API_HAL_TIMEBASE_CLK_PER_TICK;
  100. // Store and clear interrupt flags
  101. // we don't want handler to be called after renabling IRQ
  102. bool arrm_flag = LL_LPTIM_IsActiveFlag_ARRM(API_HAL_TIMEBASE_TIMER);
  103. // Calculate and set next wakeup compare value
  104. const uint16_t next_cmp = (after_tick + 1) * API_HAL_TIMEBASE_CLK_PER_TICK;
  105. api_hal_timebase_timer_set_cmp(next_cmp);
  106. // Calculate ticks count spent in sleep and perform sanity checks
  107. int32_t completed_ticks = arrm_flag ? (int32_t)before_tick - after_tick : (int32_t)after_tick - before_tick;
  108. return completed_ticks;
  109. }
  110. void vPortSuppressTicksAndSleep(TickType_t expected_idle_ticks) {
  111. if (!api_hal_power_deep_available() || api_hal_timebase.insomnia) {
  112. return;
  113. }
  114. // Limit mount of ticks to maximum that timer can count
  115. if (expected_idle_ticks > API_HAL_TIMEBASE_MAX_SLEEP) {
  116. expected_idle_ticks = API_HAL_TIMEBASE_MAX_SLEEP;
  117. }
  118. // Stop IRQ handling, no one should disturb us till we finish
  119. __disable_irq();
  120. // Confirm OS that sleep is still possible
  121. // And check if timer is in safe zone
  122. // (8 clocks till any IRQ event or ongoing synchronization)
  123. if (eTaskConfirmSleepModeStatus() == eAbortSleep
  124. || !api_hal_timebase_timer_is_safe()) {
  125. __enable_irq();
  126. return;
  127. }
  128. uint32_t completed_ticks = api_hal_timebase_sleep(expected_idle_ticks);
  129. assert(completed_ticks >= 0);
  130. // Reenable IRQ
  131. __enable_irq();
  132. // Notify system about time spent in sleep
  133. if (completed_ticks > 0) {
  134. api_hal_timebase.in_sleep += completed_ticks;
  135. if (completed_ticks > expected_idle_ticks) {
  136. // We are late, count error
  137. api_hal_timebase.sleep_error += (completed_ticks - expected_idle_ticks);
  138. // Freertos is not happy when we overleep
  139. // But we are not going to tell her
  140. vTaskStepTick(expected_idle_ticks);
  141. } else {
  142. vTaskStepTick(completed_ticks);
  143. }
  144. }
  145. }