api-hal-timebase.c 6.0 KB

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