mirror of https://github.com/ArduPilot/ardupilot
586 lines
20 KiB
C++
586 lines
20 KiB
C++
#pragma once
|
|
|
|
//#pragma GCC optimize ("O2")
|
|
|
|
#include <AP_HAL/AP_HAL.h>
|
|
|
|
|
|
#include "AP_HAL_F4Light_Namespace.h"
|
|
#include "handler.h"
|
|
#include "Config.h"
|
|
|
|
#include "Semaphores.h"
|
|
#include "GPIO.h"
|
|
|
|
#include <delay.h>
|
|
#include <systick.h>
|
|
#include <boards.h>
|
|
#include <timer.h>
|
|
//#include <setjmp.h>
|
|
|
|
#define F4Light_SCHEDULER_MAX_IO_PROCS 10
|
|
|
|
|
|
#define MAIN_PRIORITY 100 // priority for main task
|
|
#define DRIVER_PRIORITY 98 // priority for drivers, speed of main will be 1/4 of this
|
|
#define IO_PRIORITY 115 // main task has 100 so IO tasks will use 1/16 of CPU
|
|
|
|
#define SHED_FREQ 10000 // timer's freq in Hz
|
|
#define TIMER_PERIOD 100 // task timeslice period in uS
|
|
|
|
|
|
#define MAIN_STACK_SIZE 4096U+1024U // measured use of stack is only 1.5K - but it grows up to 3K when using FatFs, also this includes 1K stack for ISR
|
|
#define IO_STACK_SIZE 4096U // IO_tasks stack size - io_thread can do work with filesystem, stack overflows if 2K
|
|
#define DEFAULT_STACK_SIZE 1024U // Default tasks stack size
|
|
#define SMALL_TASK_STACK 1024U // small stack for sensors
|
|
#define STACK_MAX 65536U
|
|
|
|
#if 1
|
|
#define EnterCriticalSection __set_BASEPRI(SVC_INT_PRIORITY << (8 - __NVIC_PRIO_BITS))
|
|
#define LeaveCriticalSection __set_BASEPRI(0)
|
|
#else
|
|
#define EnterCriticalSection noInterrupts()
|
|
#define LeaveCriticalSection interrupts()
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Task run-time structure (Task control block AKA TCB)
|
|
*/
|
|
struct task_t {
|
|
const uint8_t* sp; // Task stack pointer, should be first to access from context switcher
|
|
task_t* next; // Next task (double linked list)
|
|
task_t* prev; // Previous task
|
|
Handler handle; // loop() in Revo_handler - to allow to change task, called via revo_call_handler
|
|
const uint8_t* stack; // Task stack bottom
|
|
uint8_t id; // id of task
|
|
uint8_t priority; // base priority of task
|
|
uint8_t curr_prio; // current priority of task, usually higher than priority
|
|
bool active; // task still not ended
|
|
bool f_yield; // task gives its quant voluntary (to not call it again)
|
|
uint32_t ttw; // time to wait - for delays and IO
|
|
uint32_t t_yield; // time when task loose control
|
|
uint32_t period; // if set then task will start on time basis
|
|
uint32_t time_start; // start time of task (for periodic tasks)
|
|
F4Light::Semaphore *sem; // task should start after owning this semaphore
|
|
F4Light::Semaphore *sem_wait; // task is waiting this semaphore
|
|
uint32_t sem_time; // max time to wait semaphore
|
|
uint32_t sem_start_wait; // time when waiting starts (can use t_yield but stays for clarity)
|
|
#if defined(MTASK_PROF)
|
|
uint32_t start; // microseconds of timeslice start
|
|
uint32_t in_isr; // time in ISR when task runs
|
|
uint32_t def_ttw; // default TTW - not as hard as period
|
|
uint8_t sw_type; // type of task switch
|
|
uint64_t time; // full time
|
|
uint32_t max_time; // maximal execution time of task - to show
|
|
uint32_t count; // call count to calc mean
|
|
uint32_t work_time; // max time of full task
|
|
uint32_t sem_max_wait; // max time of semaphore waiting
|
|
uint32_t quants; // count of ticks
|
|
uint32_t quants_time; // sum of quatn's times
|
|
uint32_t t_paused; // time task was paused on IO
|
|
uint32_t count_paused; // count task was paused on IO
|
|
uint32_t max_paused; // max time task was paused on IO
|
|
uint32_t max_c_paused; // count task was paused on IO
|
|
uint32_t stack_free; // free stack
|
|
#endif
|
|
uint32_t guard; // stack guard to check TCB corruption
|
|
};
|
|
|
|
extern "C" {
|
|
extern unsigned _estack; // defined by link script
|
|
extern uint32_t us_ticks;
|
|
extern void *_sdata;
|
|
extern void *_edata;
|
|
extern void *_sccm; // start of CCM
|
|
extern void *_eccm; // end of CCM vars
|
|
|
|
void revo_call_handler(Handler hh, uint32_t arg); // universal caller for all type handlers - memberProc and Proc
|
|
|
|
extern voidFuncPtr boardEmergencyHandler; // will be called on any fault or panic() before halt
|
|
void PendSV_Handler();
|
|
void SVC_Handler();
|
|
void getNextTask();
|
|
|
|
void switchContext();
|
|
void __do_context_switch();
|
|
void hal_try_kill_task_or_reboot(uint8_t n);
|
|
void hal_go_next_task();
|
|
void hal_stop_multitask();
|
|
|
|
extern task_t *s_running; // running task
|
|
extern task_t *next_task; // task to run next
|
|
|
|
extern caddr_t stack_bottom; // for SBRK check
|
|
|
|
bool hal_is_armed();
|
|
|
|
// publish to low-level functions
|
|
void hal_yield(uint16_t ttw);
|
|
void hal_delay(uint16_t t);
|
|
void hal_delay_microseconds(uint16_t t);
|
|
uint32_t hal_micros();
|
|
void hal_isr_time(uint32_t t);
|
|
|
|
// task management for USB MSC mode
|
|
void hal_set_task_active(void * handle);
|
|
void hal_context_switch_isr();
|
|
void * hal_register_task(voidFuncPtr task, uint32_t stack);
|
|
void hal_set_task_priority(void * handle, uint8_t prio);
|
|
|
|
void enqueue_flash_erase(uint32_t from, uint32_t to);
|
|
}
|
|
|
|
|
|
#define RAMEND ((size_t)&_estack)
|
|
|
|
|
|
|
|
#ifdef SHED_DEBUG
|
|
typedef struct RevoSchedLog {
|
|
uint32_t start;
|
|
uint32_t end;
|
|
uint32_t ttw;
|
|
uint32_t time_start;
|
|
uint32_t quant;
|
|
uint32_t in_isr;
|
|
task_t *want_tail;
|
|
uint8_t task_id;
|
|
uint8_t prio;
|
|
uint8_t active;
|
|
uint8_t sw_type;
|
|
} revo_sched_log;
|
|
|
|
#define SHED_DEBUG_SIZE 512
|
|
#endif
|
|
|
|
enum Revo_IO_Flags {
|
|
IO_PERIODIC= 0,
|
|
IO_ONCE = 1,
|
|
};
|
|
|
|
typedef struct REVO_IO {
|
|
Handler h;
|
|
Revo_IO_Flags flags;
|
|
} Revo_IO;
|
|
|
|
class F4Light::Scheduler : public AP_HAL::Scheduler {
|
|
public:
|
|
|
|
typedef struct IO_COMPLETION {
|
|
Handler handler;
|
|
bool request;
|
|
#ifdef SHED_PROF
|
|
uint64_t time;
|
|
uint32_t count;
|
|
uint32_t max_time;
|
|
#endif
|
|
} IO_Completion;
|
|
|
|
|
|
|
|
Scheduler();
|
|
void init();
|
|
inline void delay(uint16_t ms) { _delay(ms); } // uses internal static methods
|
|
inline void delay_microseconds(uint16_t us) { _delay_microseconds(us); }
|
|
inline void delay_microseconds_boost(uint16_t us) override { _delay_microseconds_boost(us); }
|
|
|
|
inline uint32_t millis() { return AP_HAL::millis(); }
|
|
inline uint32_t micros() { return _micros(); }
|
|
|
|
inline void register_timer_process(AP_HAL::MemberProc proc) { _register_timer_process(proc, 1000); }
|
|
|
|
void register_delay_callback(AP_HAL::Proc, uint16_t min_time_ms) override;
|
|
static void _register_io_process(Handler h, Revo_IO_Flags flags);
|
|
void register_io_process(AP_HAL::MemberProc proc) { Revo_handler h = { .mp=proc }; _register_io_process(h.h, IO_PERIODIC); }
|
|
|
|
|
|
static inline void _register_timer_process(AP_HAL::MemberProc proc, uint32_t period) {
|
|
Revo_handler r = { .mp=proc };
|
|
|
|
_register_timer_task(period, r.h, NULL);
|
|
}
|
|
|
|
inline bool in_timerprocess() { return false; } // we don't calls anything in ISR
|
|
|
|
void inline register_timer_failsafe(AP_HAL::Proc failsafe, uint32_t period_us) { _failsafe = failsafe; }
|
|
|
|
void system_initialized();
|
|
|
|
static void _reboot(bool hold_in_bootloader);
|
|
void reboot(bool hold_in_bootloader);
|
|
|
|
inline bool in_main_thread() const override { return _in_main_thread(); }
|
|
|
|
// drivers are not the best place for its own sheduler so let do it here
|
|
static inline AP_HAL::Device::PeriodicHandle register_timer_task(uint32_t period_us, AP_HAL::Device::PeriodicCb proc, F4Light::Semaphore *sem) {
|
|
Revo_handler r = { .pcb=proc };
|
|
return _register_timer_task(period_us, r.h, sem);
|
|
}
|
|
|
|
static void _delay(uint16_t ms);
|
|
static void _delay_microseconds(uint16_t us);
|
|
static void _delay_microseconds_boost(uint16_t us);
|
|
|
|
static void _delay_us_ny(uint16_t us); // no yield delay
|
|
|
|
static inline uint32_t _millis() { return systick_uptime(); } //systick_uptime returns 64-bit time
|
|
static inline uint64_t _millis64() { return systick_uptime(); }
|
|
|
|
static inline uint32_t _micros() { return timer_get_count32(TIMER5); }
|
|
static uint64_t _micros64();
|
|
|
|
|
|
static bool adjust_timer_task(AP_HAL::Device::PeriodicHandle h, uint32_t period_us);
|
|
static bool unregister_timer_task(AP_HAL::Device::PeriodicHandle h);
|
|
void loop(); // to add ability to print out scheduler's stats in main thread
|
|
|
|
static inline bool in_interrupt(){ return (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) /* || (__get_BASEPRI()) */; }
|
|
|
|
|
|
//{ this functions do a preemptive multitask and inspired by Arduino-Scheduler (Mikael Patel), and scmrtos
|
|
|
|
/**
|
|
* Initiate scheduler and main task with given stack size. Should
|
|
* be called before start of any tasks if the main task requires a
|
|
* stack size other than the default size. Returns true if
|
|
* successful otherwise false.
|
|
* @param[in] stackSize in bytes.
|
|
* @return bool.
|
|
*/
|
|
static inline bool adjust_stack(size_t stackSize) { s_top = stackSize; return true; }
|
|
|
|
/**
|
|
* Start a task with given function and stack size. Should be
|
|
* called from main task. The functions are executed by the
|
|
* task. The taskLoop function is repeatedly called. Returns
|
|
* not-NULL if successful otherwise NULL (no memory for new task).
|
|
* @param[in] taskLoop function to call.
|
|
* @param[in] stackSize in bytes.
|
|
* @return address of TCB.
|
|
*/
|
|
static void * _start_task(Handler h, size_t stackSize);
|
|
|
|
static inline void * start_task(voidFuncPtr taskLoop, size_t stackSize = DEFAULT_STACK_SIZE){
|
|
Revo_handler r = { .vp=taskLoop };
|
|
return _start_task(r.h, stackSize);
|
|
}
|
|
static inline void * start_task(AP_HAL::MemberProc proc, size_t stackSize = DEFAULT_STACK_SIZE){
|
|
Revo_handler r = { .mp=proc };
|
|
return _start_task(r.h, stackSize);
|
|
}
|
|
// not used - tasks are never stopped
|
|
static void stop_task(void * h);
|
|
|
|
|
|
// functions to alter task's properties
|
|
//[ this functions called only at task start
|
|
static void set_task_period(void *h, uint32_t period); // task will be auto-activated by this period
|
|
|
|
static inline void set_task_priority(void *h, uint8_t prio){ // priority is a relative speed of task
|
|
task_t *task = (task_t *)h;
|
|
|
|
task->curr_prio= prio;
|
|
task->priority = prio;
|
|
}
|
|
|
|
// task wants to run only with this semaphore owned
|
|
static inline void set_task_semaphore(void *h, F4Light::Semaphore *sem){ // taskLoop function will be called owning this semaphore
|
|
task_t *task = (task_t *)h;
|
|
|
|
task->sem = sem;
|
|
}
|
|
//]
|
|
|
|
|
|
// this functions are atomic so don't need to disable interrupts
|
|
static inline void *get_current_task() { // get task handler or 0 if called from ISR
|
|
if(in_interrupt()) return NULL;
|
|
return s_running;
|
|
}
|
|
static inline void *get_current_task_isr() { // get current task handler even if called from ISR
|
|
return s_running;
|
|
}
|
|
static inline void set_task_active(void *h) { // tasks are created in stopped state
|
|
task_t * task = (task_t*)h;
|
|
task->active=true;
|
|
}
|
|
|
|
// do context switch after return from interrupt
|
|
static inline void context_switch_isr(){ timer_generate_update(TIMER14); }
|
|
|
|
|
|
#if defined(MTASK_PROF)
|
|
static void inline task_pause(uint32_t t) { // called from task when it starts transfer
|
|
s_running->ttw=t;
|
|
s_running->sem_start_wait=_micros();
|
|
s_running->count_paused++;
|
|
}
|
|
static void inline task_resume(void *h) { // called from IO_Complete ISR to resume task
|
|
#if defined(USE_MPU)
|
|
mpu_disable(); // we need access to write
|
|
#endif
|
|
task_t * task = (task_t*)h;
|
|
task->ttw=0;
|
|
task->active=true;
|
|
_forced_task = task; // force it. Thus we exclude loop to select task
|
|
context_switch_isr();
|
|
uint32_t dt= _micros() - task->sem_start_wait;
|
|
task->t_paused += dt;
|
|
}
|
|
#else
|
|
static void inline task_pause(uint16_t t) { s_running->ttw=t; } // called from task when it starts IO transfer
|
|
static void inline task_resume(void *h) { // called from IO_Complete ISR to resume task, and will get 1st quant 100%
|
|
#if defined(USE_MPU)
|
|
mpu_disable(); // we need access to write
|
|
#endif
|
|
task_t * task = (task_t*)h; // called from ISR so don't need disabling interrupts when writes to TCB
|
|
task->ttw=0;
|
|
task->active=true;
|
|
_forced_task = task; // force it, to not spent time for search by priority
|
|
context_switch_isr();
|
|
}
|
|
#endif
|
|
//]
|
|
|
|
/*
|
|
task scheduler. Gives task ready to run with highest priority
|
|
*/
|
|
static task_t *get_next_task();
|
|
|
|
/*
|
|
* finish current tick and schedule new task excluding this
|
|
*/
|
|
static void yield(uint16_t ttw=0); // optional time to wait
|
|
|
|
/**
|
|
* Return current task stack size.
|
|
* @return bytes
|
|
*/
|
|
static size_t task_stack();
|
|
|
|
// check from what task it called
|
|
static inline bool _in_main_thread() { return s_running == &s_main; }
|
|
|
|
// resume task that called delay_boost()
|
|
static void resume_boost(){
|
|
if(boost_task) {
|
|
task_t *task = (task_t *) boost_task;
|
|
boost_task=NULL;
|
|
|
|
|
|
if(task->ttw){ // task now in wait
|
|
#if defined(USE_MPU)
|
|
mpu_disable(); // we need access to write
|
|
#endif
|
|
uint32_t now = _micros();
|
|
uint32_t timeFromLast = now - task->t_yield; // time since that moment
|
|
if(task->ttw<=100 || timeFromLast > task->ttw*3/2){ // gone 2/3 of time?
|
|
task->ttw=0; // stop waiting
|
|
task->active=true;
|
|
_forced_task = task; // force it
|
|
}
|
|
} else {
|
|
_forced_task = task; // just force it
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void plan_context_switch(){
|
|
need_switch_task = true; // require context switch
|
|
SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; // PENDSVSET
|
|
}
|
|
|
|
static void SVC_Handler(uint32_t * svc_args); // many functions called via SVC for hardware serialization
|
|
|
|
static void _try_kill_task_or_reboot(uint8_t n); // exception occures in armed state - try to kill current task
|
|
static void _go_next_task();
|
|
static void _stop_multitask();
|
|
|
|
static volatile bool need_switch_task; // should be public for access from C code
|
|
//}
|
|
|
|
|
|
//{ IO completion routines, allows to move out time consuming parts from ISR
|
|
#define MAX_IO_COMPLETION 8
|
|
|
|
typedef voidFuncPtr ioc_proc;
|
|
|
|
static uint8_t register_io_completion(Handler handle);
|
|
|
|
static inline uint8_t register_io_completion(ioc_proc cb) {
|
|
Revo_handler r = { .vp=cb };
|
|
return register_io_completion(r.h);
|
|
}
|
|
static inline uint8_t register_io_completion(AP_HAL::MemberProc proc) {
|
|
Revo_handler r = { .mp=proc };
|
|
return register_io_completion(r.h);
|
|
}
|
|
|
|
static inline void do_io_completion(uint8_t id){ // schedule selected IO completion
|
|
if(id) {
|
|
io_completion[id-1].request = true;
|
|
timer_generate_update(TIMER13);
|
|
}
|
|
}
|
|
|
|
static void exec_io_completion();
|
|
|
|
static volatile bool need_io_completion;
|
|
//}
|
|
|
|
|
|
// helpers
|
|
static inline Handler get_handler(AP_HAL::MemberProc proc){
|
|
Revo_handler h = { .mp = proc };
|
|
return h.h;
|
|
}
|
|
static inline Handler get_handler(AP_HAL::Proc proc){
|
|
Revo_handler h = { .hp = proc };
|
|
return h.h;
|
|
}
|
|
|
|
static inline void setEmergencyHandler(voidFuncPtr handler) { boardEmergencyHandler = handler; }
|
|
|
|
|
|
#ifdef MPU_DEBUG
|
|
static inline void MPU_buffer_overflow(){ MPU_overflow_cnt++; }
|
|
static inline void MPU_restarted() { MPU_restart_cnt++; }
|
|
static inline void MPU_stats(uint16_t count, uint32_t time) {
|
|
if(count>MPU_count) {
|
|
MPU_count=count;
|
|
MPU_Time=time;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static inline void arming_state_changed(bool v){ if(!v && on_disarm_handler) revo_call_handler(on_disarm_handler, 0); }
|
|
static inline void register_on_disarm(Handler h){ on_disarm_handler=h; }
|
|
|
|
static void start_stats_task(); // it interferes with CONNECT_COM and CONNECT_ESC so should be started last
|
|
|
|
protected:
|
|
|
|
//{ multitask
|
|
// executor for task's handler, never called but used when task context formed
|
|
static void do_task(task_t * task);
|
|
|
|
// gves first deleted task or NULL - not used because tasks are never finished
|
|
static task_t* get_empty_task();
|
|
/*
|
|
* Initiate a task with the given functions and stack. When control
|
|
* is yield to the task then the loop function is repeatedly called.
|
|
* @param[in] h task handler (may be NULL)
|
|
* @param[in] stack top reference.
|
|
*/
|
|
static void *init_task(uint64_t h, const uint8_t* stack);
|
|
|
|
static uint32_t fill_task(task_t &tp); // prepares task's TCB
|
|
static void enqueue_task(task_t &tp); // add new task to run queue
|
|
static void dequeue_task(task_t &tp); // remove task from run queue
|
|
|
|
// plan context switch
|
|
static void switch_task();
|
|
static void _switch_task();
|
|
|
|
static task_t s_main; // main task TCB
|
|
static size_t s_top; // Task stack allocation top.
|
|
static uint16_t task_n; // counter of tasks
|
|
|
|
static task_t *_idle_task; // remember TCB of idle task
|
|
static task_t *_forced_task; // task activated from ISR so should be called without prioritization
|
|
static void *boost_task; // task that called delay_boost()
|
|
|
|
static void check_stack(uint32_t sp);
|
|
|
|
#define await(cond) while(!(cond)) yield()
|
|
|
|
//} end of multitask
|
|
|
|
private:
|
|
static AP_HAL::Device::PeriodicHandle _register_timer_task(uint32_t period_us, Handler proc, F4Light::Semaphore *sem);
|
|
|
|
static void * _delay_cb_handle;
|
|
static bool _initialized;
|
|
|
|
// ISR functions
|
|
static void _timer_isr_event(uint32_t v /*TIM_TypeDef *tim */);
|
|
static void _timer5_ovf(uint32_t v /*TIM_TypeDef *tim */ );
|
|
static void _tail_timer_event(uint32_t v /*TIM_TypeDef *tim */);
|
|
static void _ioc_timer_event(uint32_t v /*TIM_TypeDef *tim */);
|
|
static void _delay_timer_event(uint32_t v /*TIM_TypeDef *tim */);
|
|
|
|
static void _run_timer_procs(bool called_from_isr);
|
|
|
|
static uint32_t timer5_ovf_cnt; // high part of 64-bit time
|
|
|
|
static AP_HAL::Proc _failsafe; // periodically called from ISR
|
|
|
|
static Revo_IO _io_proc[F4Light_SCHEDULER_MAX_IO_PROCS]; // low priority tasks for IO thread
|
|
static void _run_io(void);
|
|
static uint8_t _num_io_proc;
|
|
static bool _in_io_proc;
|
|
|
|
static Handler on_disarm_handler;
|
|
|
|
static void _print_stats();
|
|
|
|
static uint32_t lowest_stack;
|
|
|
|
static struct IO_COMPLETION io_completion[MAX_IO_COMPLETION];
|
|
static uint8_t num_io_completion;
|
|
|
|
|
|
#ifdef SHED_PROF
|
|
static uint64_t shed_time;
|
|
static uint64_t task_time;
|
|
static bool flag_10s;
|
|
|
|
static uint64_t delay_time;
|
|
static uint64_t delay_int_time;
|
|
static uint32_t max_loop_time;
|
|
|
|
static void _set_10s_flag();
|
|
static uint64_t ioc_time;
|
|
static uint64_t sleep_time;
|
|
static uint32_t max_delay_err;
|
|
|
|
|
|
static uint32_t tick_micros; // max exec time
|
|
static uint32_t tick_count; // number of calls
|
|
static uint64_t tick_fulltime; // full consumed time to calc mean
|
|
|
|
#endif
|
|
|
|
#ifdef MTASK_PROF
|
|
static uint32_t max_wfe_time;
|
|
static uint32_t tsched_count;
|
|
static uint32_t tsched_sw_count;
|
|
static uint32_t tsched_count_y;
|
|
static uint32_t tsched_sw_count_y;
|
|
static uint32_t tsched_count_t;
|
|
static uint32_t tsched_sw_count_t;
|
|
|
|
|
|
#ifdef SHED_DEBUG
|
|
static revo_sched_log logbuf[SHED_DEBUG_SIZE];
|
|
static uint16_t sched_log_ptr;
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifdef MPU_DEBUG
|
|
static uint32_t MPU_overflow_cnt;
|
|
static uint32_t MPU_restart_cnt;
|
|
static uint32_t MPU_count;
|
|
static uint32_t MPU_Time;
|
|
#endif
|
|
|
|
};
|
|
|
|
void revo_call_handler(Handler h, uint32_t arg);
|
|
|
|
|