Skip to content

Commit f1b51c9

Browse files
sched fix (tested on PCU, LV-BMS, HV-BMS and LCU) (#594)
* fix (tested on PCU, LV-BMS and LCU) * get cleanup to compile * format checks * fix tests and scheduler (off by one error) * Add a test for get_at * formatting * Add a test for set_at and fix it * formatting * formatting * try to fix issue in LCU * add a test for front_id and pop_front * add changeset for this pr * formatting for changeset :/ * fix (tested on PCU, LV-BMS and LCU) * get cleanup to compile * format checks * fix tests and scheduler (off by one error) * Add a test for get_at * formatting * Add a test for set_at and fix it * formatting * formatting * try to fix issue in LCU * add a test for front_id and pop_front * revert merge from development * searching for case-insensitive STM32 CLT path * applied formatter * removed unused include * making PacketValue destructor virtual so that CLang doesn't cry * minor fix * commit old changes * fix off by one error in allocating a slot * formatting * fix old compile error which shouldn't be one * Add set_limit_value to set arr in TimerWrapper * fix indentation * indentation, again * formatting * Move errorhandler away from interrupt callback * ErrorHandler -> WARNING, prescaler calc -> TimerDomain * formatting * Add Infowarning to tests, hopefully this compiles * formatting, and try to compile tests * formatting * formatting again * change changeset to minor * remove ErrorHandler in TIM_IC_CaptureCallback since it's an interrupt callback --------- Co-authored-by: Jorge Sáez <jorgeesg82@gmail.com>
1 parent d117fd9 commit f1b51c9

10 files changed

Lines changed: 154 additions & 124 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
release: minor
2+
summary: fix remaining scheduler race conditions and add warning when tasks are not ran in time
3+
4+
The mechanism for checking if tasks are not ran in time is very simple but that also means the scheduler can only know if a task has not been called when the waiting time runs out for the second time.
5+
This means you will know if you're too slow to execute the task in less than 2x its period but not if you're between 1 and 2x its period.

Inc/HALAL/Models/Packets/PacketValue.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ template <> class PacketValue<> {
1111
public:
1212
using value_type = empty_type;
1313
PacketValue() = default;
14-
~PacketValue() = default;
14+
virtual ~PacketValue() = default;
1515
virtual void* get_pointer() = 0;
1616
virtual void set_pointer(void* pointer) = 0;
1717
virtual size_t get_size() = 0;

Inc/HALAL/Models/TimerDomain/TimerDomain.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,8 @@ struct TimerDomain {
576576
ST_LIB::compile_error("This timer is used by the scheduler");
577577
}
578578

579-
if (requests[i].request != TimerRequest::AnyGeneralPurpose &&
579+
if ((requests[i].request != TimerRequest::AnyGeneralPurpose) &&
580+
(requests[i].request != TimerRequest::Any32bit) &&
580581
(requests[i].request < 1 || requests[i].request > 24 ||
581582
(requests[i].request > 17 && requests[i].request < 23))) {
582583
ST_LIB::compile_error("Invalid TimerRequest value for timer");
@@ -817,6 +818,7 @@ struct TimerDomain {
817818
}
818819
}
819820

821+
// NOTE: This is a bit slower than timerwrapper.get_clock_frequency(), so preferrably use that
820822
static inline uint32_t get_timer_frequency(TIM_TypeDef* tim) {
821823
uint32_t result = 0;
822824
if ((tim == TIM2) || (tim == TIM3) || (tim == TIM4) || (tim == TIM5) || (tim == TIM6) ||

Inc/HALAL/Services/Encoder/Encoder.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ template <const TimerDomain::Timer& dev> class Encoder {
6363
}
6464

6565
tim->instance->tim->PSC = 5;
66-
if constexpr (tim->is_32bit_instance) {
66+
if constexpr (TimerWrapper<dev>::is_32bit_instance) {
6767
tim->instance->tim->ARR = UINT32_MAX;
6868
} else {
6969
tim->instance->tim->ARR = UINT16_MAX;

Inc/HALAL/Services/Time/Scheduler.hpp

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,15 @@
99
/* Uso del scheduler, descrito en la wiki:
1010
* https://wiki.hyperloopupv.com/es/firmware/Timing/Scheduler */
1111

12+
/* To allow debugging of inline functions only when testing */
13+
#ifdef SIM_ON
14+
#define HYPER_INLINE inline
15+
#else
16+
#define HYPER_INLINE
17+
#endif
18+
1219
#include "stm32h7xx_ll_tim_wrapper.h"
20+
#include "HALAL/Models/Packets/Packet.hpp"
1321

1422
#include <array>
1523
#include <cstdint>
@@ -33,9 +41,7 @@ struct Scheduler {
3341
// temporary, will be removed
3442
[[deprecated]] static inline void start() {}
3543
static void update();
36-
static inline uint64_t get_global_tick() {
37-
return global_tick_us_ + Scheduler_global_timer->CNT;
38-
}
44+
static uint64_t get_global_tick();
3945

4046
static uint16_t register_task(uint32_t period_us, callback_t func);
4147
static bool unregister_task(uint16_t id);
@@ -44,7 +50,7 @@ struct Scheduler {
4450
static bool cancel_timeout(uint16_t id);
4551

4652
// internal
47-
static void on_timer_update();
53+
static inline void on_timer_update();
4854
static void schedule_next_interval();
4955
static constexpr uint32_t FREQUENCY = 1'000'000u; // 1 MHz -> 1us precision
5056
#ifndef SIM_ON
@@ -92,10 +98,19 @@ struct Scheduler {
9298
static void remove_sorted(uint8_t id);
9399

94100
// helpers
95-
static inline uint8_t get_at(uint8_t idx);
96-
static inline void set_at(uint8_t idx, uint8_t id);
97-
static inline void pop_front();
98-
static inline uint8_t front_id();
101+
static HYPER_INLINE uint8_t get_at(uint8_t idx) {
102+
return (uint8_t)((sorted_task_ids_ >> (idx * 4)) & 0xF);
103+
}
104+
static HYPER_INLINE void set_at(uint8_t idx, uint8_t id) {
105+
uint64_t shift = idx * 4;
106+
uint64_t clearmask = ~(0x0FULL << shift);
107+
Scheduler::sorted_task_ids_ = (sorted_task_ids_ & clearmask) | ((uint64_t)id << shift);
108+
}
109+
static HYPER_INLINE uint8_t front_id() { return *((uint8_t*)&sorted_task_ids_) & 0xF; }
110+
static HYPER_INLINE void pop_front() {
111+
Scheduler::active_task_count_--;
112+
Scheduler::sorted_task_ids_ >>= 4;
113+
}
99114

100115
static inline void global_timer_disable();
101116
static inline void global_timer_enable();

Inc/HALAL/Services/Time/TimerWrapper.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,8 @@ template <const TimerDomain::Timer& dev> struct TimerWrapper {
420420
inline TIM_TypeDef* get_cmsis_handle() { return instance->tim; }
421421

422422
inline void set_prescaler(uint16_t psc) { instance->tim->PSC = psc; }
423+
// TODO: 16 bit and 32 bit version (?)
424+
inline void set_limit_value(uint32_t arr) { instance->tim->ARR = arr; }
423425

424426
inline void configure32bit(void (*callback)(void*), void* callback_data, uint32_t period) {
425427
static_assert(

Src/HALAL/Models/TimerDomain/TimerDomain.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ static void TIM_IC_CaptureCallback(const uint32_t timer_idx, uint32_t channel) {
5757
if (falling_value < info->period)
5858
info->value_falling = falling_value;
5959
} else [[unlikely]] {
60-
ErrorHandler("TimerDomain::input_capture_info was modified");
60+
// TimerDomain::input_capture_info was modified or STM interrupts are all over the place
61+
// either way, this is a no-op
62+
// ErrorHandler("TimerDomain::input_capture_info was modified");
6163
}
6264
}
6365

Src/HALAL/Services/Time/Scheduler.cpp

Lines changed: 44 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
/*
2-
* Scheduler.hpp
2+
* Scheduler.cpp
33
*
44
* Created on: 17 nov. 2025
55
* Author: Victor (coauthor Stephan)
66
*/
77
#include "HALAL/Services/Time/Scheduler.hpp"
88
#include "HALAL/Models/TimerDomain/TimerDomain.hpp"
9-
#include "ErrorHandler/ErrorHandler.hpp"
9+
#include "HALAL/Services/InfoWarning/InfoWarning.hpp"
1010

1111
#include <stdint.h>
1212

@@ -31,22 +31,7 @@ uint64_t Scheduler::global_tick_us_{0};
3131
uint32_t Scheduler::current_interval_us_{0};
3232
uint16_t Scheduler::timeout_idx_{1};
3333

34-
inline uint8_t Scheduler::get_at(uint8_t idx) {
35-
int word_idx = idx > 7;
36-
uint32_t shift = (idx & 7) << 2;
37-
return (((uint32_t*)&sorted_task_ids_)[word_idx] & (0x0F << shift)) >> shift;
38-
}
39-
inline void Scheduler::set_at(uint8_t idx, uint8_t id) {
40-
uint32_t shift = idx * 4;
41-
uint64_t clearmask = ~(0xFF << shift);
42-
Scheduler::sorted_task_ids_ = (sorted_task_ids_ & clearmask) | (id << shift);
43-
}
44-
inline uint8_t Scheduler::front_id() { return *((uint8_t*)&sorted_task_ids_) & 0xF; }
45-
inline void Scheduler::pop_front() {
46-
// O(1) remove of logical index 0
47-
Scheduler::active_task_count_--;
48-
Scheduler::sorted_task_ids_ >>= 4;
49-
}
34+
uint16_t failing_id = Scheduler::INVALID_ID;
5035

5136
// ----------------------------
5237

@@ -73,107 +58,55 @@ void Scheduler_start(void) {
7358
ST_LIB::TimerDomain::callbacks[ST_LIB::timer_idxmap[static_cast<uint8_t>(SCHEDULER_TIMER_DOMAIN
7459
)]] = Scheduler_global_timer_callback;
7560

76-
// TODO: change this to use TimerDomain::get_timer_clock()?
77-
uint32_t prescaler = (SystemCoreClock / Scheduler::FREQUENCY);
78-
// setup prescaler
79-
{
80-
// ref manual: section 8.7.7 RCC domain 1 clock configuration register
81-
uint32_t ahb_prescaler = RCC->D1CFGR & RCC_D1CFGR_HPRE_Msk;
82-
if ((ahb_prescaler & 0b1000) != 0) {
83-
switch (ahb_prescaler) {
84-
case 0b1000:
85-
prescaler /= 2;
86-
break;
87-
case 0b1001:
88-
prescaler /= 4;
89-
break;
90-
case 0b1010:
91-
prescaler /= 8;
92-
break;
93-
case 0b1011:
94-
prescaler /= 16;
95-
break;
96-
case 0b1100:
97-
prescaler /= 64;
98-
break;
99-
case 0b1101:
100-
prescaler /= 128;
101-
break;
102-
case 0b1110:
103-
prescaler /= 256;
104-
break;
105-
case 0b1111:
106-
prescaler /= 512;
107-
break;
108-
}
109-
}
110-
111-
// ref manual: section 8.7.8: RCC domain 2 clock configuration register
112-
uint32_t apb1_prescaler = (RCC->D2CFGR & RCC_D2CFGR_D2PPRE1_Msk) >> RCC_D2CFGR_D2PPRE1_Pos;
113-
if ((apb1_prescaler & 0b100) != 0) {
114-
switch (apb1_prescaler) {
115-
case 0b100:
116-
prescaler /= 2;
117-
break;
118-
case 0b101:
119-
prescaler /= 4;
120-
break;
121-
case 0b110:
122-
prescaler /= 8;
123-
break;
124-
case 0b111:
125-
prescaler /= 16;
126-
break;
127-
}
128-
}
129-
// tim2clk = 2 x pclk1 when apb1_prescaler != 1
130-
if (apb1_prescaler != 1) {
131-
prescaler *= 2;
132-
}
133-
134-
if (prescaler > 1) {
135-
prescaler--;
136-
}
137-
}
138-
139-
if (prescaler == 0 || prescaler > 0xFFFF) {
140-
ErrorHandler("Invalid prescaler value: %u", prescaler);
141-
return;
142-
}
143-
144-
Scheduler_global_timer->PSC = (uint16_t)prescaler;
61+
uint16_t prescaler =
62+
(uint16_t)(ST_LIB::TimerDomain::get_timer_frequency(Scheduler_global_timer) /
63+
Scheduler::FREQUENCY);
64+
Scheduler_global_timer->PSC = prescaler;
14565
Scheduler_global_timer->ARR = 0;
14666
Scheduler_global_timer->DIER |= LL_TIM_DIER_UIE;
14767
Scheduler_global_timer->CR1 =
14868
LL_TIM_CLOCKDIVISION_DIV1 | (Scheduler_global_timer->CR1 & ~TIM_CR1_CKD);
14969

15070
Scheduler_global_timer->CNT = 0; /* Clear counter value */
15171

152-
NVIC_EnableIRQ(SCHEDULER_GLOBAL_TIMER_IRQn);
15372
CLEAR_BIT(Scheduler_global_timer->SR, LL_TIM_SR_UIF); /* clear update interrupt flag */
15473

15574
Scheduler::schedule_next_interval();
15675
}
15776

15877
void Scheduler::update() {
78+
// NOTE: Only _one_ id will be shown per call to update()
79+
if (failing_id != Scheduler::INVALID_ID) [[unlikely]] {
80+
WARNING("Too slow, could not execute task %u in time", failing_id);
81+
failing_id = Scheduler::INVALID_ID;
82+
}
83+
15984
while (ready_bitmap_ != 0u) {
16085
uint32_t bit_index = static_cast<uint32_t>(__builtin_ctz(ready_bitmap_));
16186

162-
CLEAR_BIT(ready_bitmap_, 1u << bit_index);
163-
16487
Task& task = tasks_[bit_index];
88+
16589
task.callback();
90+
91+
SchedLock();
92+
CLEAR_BIT(ready_bitmap_, 1u << bit_index);
16693
if (!task.repeating) [[unlikely]] {
167-
SchedLock();
16894
release_slot(static_cast<uint8_t>(bit_index));
169-
SchedUnlock();
17095
}
96+
SchedUnlock();
17197
}
17298
}
17399

100+
uint64_t Scheduler::get_global_tick() {
101+
SchedLock();
102+
uint64_t val = global_tick_us_ + Scheduler_global_timer->CNT;
103+
SchedUnlock();
104+
return val;
105+
}
106+
174107
inline uint8_t Scheduler::allocate_slot() {
175108
uint32_t idx = __builtin_ffs(Scheduler::free_bitmap_) - 1;
176-
if (idx > static_cast<int>(Scheduler::kMaxTasks)) [[unlikely]]
109+
if (idx >= Scheduler::kMaxTasks) [[unlikely]]
177110
return static_cast<uint8_t>(Scheduler::INVALID_ID);
178111
Scheduler::free_bitmap_ &= ~(1UL << idx);
179112
return static_cast<uint8_t>(idx);
@@ -272,9 +205,12 @@ void Scheduler::schedule_next_interval() {
272205
return;
273206
}
274207

208+
SchedLock();
275209
uint8_t next_id = Scheduler::front_id(); // sorted_task_ids_[0]
276210
Task& next_task = tasks_[next_id];
277211
int32_t diff = (int32_t)(next_task.next_fire_us - static_cast<uint32_t>(global_tick_us_));
212+
SchedUnlock();
213+
278214
if (diff >= -1 && diff <= 1) [[unlikely]] {
279215
current_interval_us_ = 1;
280216
SET_BIT(Scheduler_global_timer->EGR, TIM_EGR_UG); // This should cause an interrupt
@@ -284,18 +220,18 @@ void Scheduler::schedule_next_interval() {
284220
} else {
285221
current_interval_us_ = static_cast<uint32_t>(diff);
286222
}
287-
Scheduler_global_timer->ARR = static_cast<uint32_t>(current_interval_us_ - 1u);
288-
while (Scheduler_global_timer->CNT > Scheduler_global_timer->ARR) [[unlikely]] {
289-
uint32_t offset = Scheduler_global_timer->CNT - Scheduler_global_timer->ARR;
290-
current_interval_us_ = offset;
291-
SET_BIT(Scheduler_global_timer->EGR, TIM_EGR_UG); // This should cause an interrupt
292-
Scheduler_global_timer->CNT = Scheduler_global_timer->CNT + offset;
223+
224+
Scheduler_global_timer->ARR = current_interval_us_ - 1u;
225+
if (Scheduler_global_timer->CNT > Scheduler_global_timer->ARR) [[unlikely]] {
226+
uint32_t cnt_temp = Scheduler_global_timer->CNT;
227+
Scheduler_global_timer->CNT = 0;
228+
global_tick_us_ += cnt_temp;
293229
}
294230
}
295231
Scheduler::global_timer_enable();
296232
}
297233

298-
void Scheduler::on_timer_update() {
234+
inline void Scheduler::on_timer_update() {
299235
global_tick_us_ += current_interval_us_;
300236

301237
while (active_task_count_ > 0) { // Pop all due tasks, several might be due in the same tick
@@ -305,15 +241,20 @@ void Scheduler::on_timer_update() {
305241
if (diff > 0) [[likely]] {
306242
break; // Task is in the future, stop processing
307243
}
308-
pop_front();
244+
uint32_t task_bit = 1u << candidate_id;
309245

246+
SchedLock();
247+
pop_front();
310248
// mark task as ready
311-
SET_BIT(ready_bitmap_, 1u << candidate_id);
312-
249+
if ((ready_bitmap_ & task_bit) != 0) [[unlikely]] {
250+
failing_id = candidate_id;
251+
}
252+
SET_BIT(ready_bitmap_, task_bit);
313253
if (task.repeating) [[likely]] {
314254
task.next_fire_us = static_cast<uint32_t>(global_tick_us_ + task.period_us);
315255
insert_sorted(candidate_id);
316256
}
257+
SchedUnlock();
317258
}
318259

319260
schedule_next_interval();

Tests/Time/common_tests.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <gtest/gtest.h>
22
#include "ErrorHandler/ErrorHandler.hpp"
3+
#include "HALAL/Services/InfoWarning/InfoWarning.hpp"
34

45
std::string ErrorHandlerModel::line;
56
std::string ErrorHandlerModel::func;
@@ -32,3 +33,33 @@ void ErrorHandlerModel::ErrorHandlerTrigger(string format, ...) {
3233
}
3334

3435
void ErrorHandlerModel::ErrorHandlerUpdate() {}
36+
37+
std::string InfoWarning::line;
38+
std::string InfoWarning::func;
39+
std::string InfoWarning::file;
40+
41+
namespace ST_LIB::TestInfoWarning {
42+
bool fail_on_error = false;
43+
int call_count = 0;
44+
45+
void reset() {
46+
fail_on_error = false;
47+
call_count = 0;
48+
}
49+
50+
void set_fail_on_error(bool enabled) { fail_on_error = enabled; }
51+
}; // namespace ST_LIB::TestInfoWarning
52+
53+
void InfoWarning::SetMetaData(int line, const char* func, const char* file) {
54+
InfoWarning::line = to_string(line);
55+
InfoWarning::func = string(func);
56+
InfoWarning::file = string(file);
57+
}
58+
59+
void InfoWarning::InfoWarningTrigger(string format, ...) {
60+
(void)format;
61+
ST_LIB::TestInfoWarning::call_count++;
62+
if (ST_LIB::TestInfoWarning::fail_on_error) {
63+
EXPECT_EQ(1, 0);
64+
}
65+
}

0 commit comments

Comments
 (0)