/* * This file is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "AP_HAL_ESP32/Scheduler.h" #include "AP_HAL_ESP32/RCInput.h" #include "AP_HAL_ESP32/AnalogIn.h" #include "AP_Math/AP_Math.h" #include "SdCard.h" #include "Profile.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_task_wdt.h" #include #include #include //#define SCHEDULERDEBUG 1 using namespace ESP32; extern const AP_HAL::HAL& hal; bool Scheduler::_initialized = true; Scheduler::Scheduler() { _initialized = false; } Scheduler::~Scheduler() { if (_initialized) { esp_task_wdt_deinit(); } } void Scheduler::wdt_init(uint32_t timeout, uint32_t core_mask) { esp_task_wdt_config_t config = { .timeout_ms = timeout, .idle_core_mask = core_mask, .trigger_panic = true }; if ( ESP_OK != esp_task_wdt_init(&config) ) { printf("esp_task_wdt_init() failed\n"); } if (ESP_OK != esp_task_wdt_add(NULL)) { printf("esp_task_wdt_add(NULL) failed"); } } void Scheduler::init() { #ifdef SCHEDDEBUG printf("%s:%d \n", __PRETTY_FUNCTION__, __LINE__); #endif hal.console->printf("%s:%d running with CONFIG_FREERTOS_HZ=%d\n", __PRETTY_FUNCTION__, __LINE__,CONFIG_FREERTOS_HZ); // keep main tasks that need speed on CPU 0 // pin potentially slow stuff to CPU 1, as we have disabled the WDT on that core. #define FASTCPU 0 #define SLOWCPU 1 // pin main thread to Core 0, and we'll also pin other heavy-tasks to core 1, like wifi-related. if (xTaskCreatePinnedToCore(_main_thread, "APM_MAIN", Scheduler::MAIN_SS, this, Scheduler::MAIN_PRIO, &_main_task_handle,FASTCPU) != pdPASS) { //if (xTaskCreate(_main_thread, "APM_MAIN", Scheduler::MAIN_SS, this, Scheduler::MAIN_PRIO, &_main_task_handle) != pdPASS) { hal.console->printf("FAILED to create task _main_thread on FASTCPU\n"); } else { hal.console->printf("OK created task _main_thread on FASTCPU\n"); } if (xTaskCreatePinnedToCore(_timer_thread, "APM_TIMER", TIMER_SS, this, TIMER_PRIO, &_timer_task_handle,FASTCPU) != pdPASS) { hal.console->printf("FAILED to create task _timer_thread on FASTCPU\n"); } else { hal.console->printf("OK created task _timer_thread on FASTCPU\n"); } if (xTaskCreatePinnedToCore(_rcout_thread, "APM_RCOUT", RCOUT_SS, this, RCOUT_PRIO, &_rcout_task_handle,SLOWCPU) != pdPASS) { hal.console->printf("FAILED to create task _rcout_thread on SLOWCPU\n"); } else { hal.console->printf("OK created task _rcout_thread on SLOWCPU\n"); } if (xTaskCreatePinnedToCore(_rcin_thread, "APM_RCIN", RCIN_SS, this, RCIN_PRIO, &_rcin_task_handle,SLOWCPU) != pdPASS) { hal.console->printf("FAILED to create task _rcin_thread on SLOWCPU\n"); } else { hal.console->printf("OK created task _rcin_thread on SLOWCPU\n"); } // pin this thread to Core 1 as it keeps all teh uart/s feed data, and we need that quick. if (xTaskCreatePinnedToCore(_uart_thread, "APM_UART", UART_SS, this, UART_PRIO, &_uart_task_handle,FASTCPU) != pdPASS) { hal.console->printf("FAILED to create task _uart_thread on FASTCPU\n"); } else { hal.console->printf("OK created task _uart_thread on FASTCPU\n"); } // we put thos on the SLOW core as it mounts the sd card, and that often isn't conencted. if (xTaskCreatePinnedToCore(_io_thread, "SchedulerIO:APM_IO", IO_SS, this, IO_PRIO, &_io_task_handle,SLOWCPU) != pdPASS) { hal.console->printf("FAILED to create task _io_thread on SLOWCPU\n"); } else { hal.console->printf("OK created task _io_thread on SLOWCPU\n"); } if (xTaskCreatePinnedToCore(_storage_thread, "APM_STORAGE", STORAGE_SS, this, STORAGE_PRIO, &_storage_task_handle,SLOWCPU) != pdPASS) { //no actual flash writes without this, storage kinda appears to work, but does an erase on every boot and params don't persist over reset etc. hal.console->printf("FAILED to create task _storage_thread\n"); } else { hal.console->printf("OK created task _storage_thread\n"); } // xTaskCreatePinnedToCore(_print_profile, "APM_PROFILE", IO_SS, this, IO_PRIO, nullptr,SLOWCPU); } template void executor(T oui) { oui(); } void IRAM_ATTR Scheduler::thread_create_trampoline(void *ctx) { AP_HAL::MemberProc *t = (AP_HAL::MemberProc *)ctx; (*t)(); free(t); } /* create a new thread */ bool Scheduler::thread_create(AP_HAL::MemberProc proc, const char *name, uint32_t requested_stack_size, priority_base base, int8_t priority) { #ifdef SCHEDDEBUG printf("%s:%d \n", __PRETTY_FUNCTION__, __LINE__); #endif // take a copy of the MemberProc, it is freed after thread exits AP_HAL::MemberProc *tproc = (AP_HAL::MemberProc *)calloc(1, sizeof(proc)); if (!tproc) { return false; } *tproc = proc; uint8_t thread_priority = IO_PRIO; static const struct { priority_base base; uint8_t p; } priority_map[] = { { PRIORITY_BOOST, IO_PRIO}, { PRIORITY_MAIN, MAIN_PRIO}, { PRIORITY_SPI, SPI_PRIORITY}, { PRIORITY_I2C, I2C_PRIORITY}, { PRIORITY_CAN, IO_PRIO}, { PRIORITY_TIMER, TIMER_PRIO}, { PRIORITY_RCIN, RCIN_PRIO}, { PRIORITY_IO, IO_PRIO}, { PRIORITY_UART, UART_PRIO}, { PRIORITY_NET, WIFI_PRIO1}, { PRIORITY_STORAGE, STORAGE_PRIO}, { PRIORITY_SCRIPTING, UART_PRIO}, }; for (uint8_t i=0; i= ESP32_SCHEDULER_MAX_TIMER_PROCS) { printf("Out of timer processes\n"); return; } _timer_sem.take_blocking(); _timer_proc[_num_timer_procs] = proc; _num_timer_procs++; _timer_sem.give(); } void IRAM_ATTR Scheduler::register_io_process(AP_HAL::MemberProc proc) { #ifdef SCHEDDEBUG printf("%s:%d \n", __PRETTY_FUNCTION__, __LINE__); #endif _io_sem.take_blocking(); for (uint8_t i = 0; i < _num_io_procs; i++) { if (_io_proc[i] == proc) { _io_sem.give(); return; } } if (_num_io_procs < ESP32_SCHEDULER_MAX_IO_PROCS) { _io_proc[_num_io_procs] = proc; _num_io_procs++; } else { printf("Out of IO processes\n"); } _io_sem.give(); } void IRAM_ATTR Scheduler::register_timer_failsafe(AP_HAL::Proc failsafe, uint32_t period_us) { _failsafe = failsafe; } void Scheduler::reboot(bool hold_in_bootloader) { printf("Restarting now...\n"); hal.rcout->force_safety_on(); unmount_sdcard(); esp_restart(); } bool IRAM_ATTR Scheduler::in_main_thread() const { return _main_task_handle == xTaskGetCurrentTaskHandle(); } void Scheduler::set_system_initialized() { #ifdef SCHEDDEBUG printf("%s:%d \n", __PRETTY_FUNCTION__, __LINE__); #endif if (_initialized) { AP_HAL::panic("PANIC: scheduler::system_initialized called more than once"); } _initialized = true; } bool Scheduler::is_system_initialized() { return _initialized; } void IRAM_ATTR Scheduler::_timer_thread(void *arg) { #ifdef SCHEDDEBUG printf("%s:%d start\n", __PRETTY_FUNCTION__, __LINE__); #endif Scheduler *sched = (Scheduler *)arg; #if HAL_INS_DEFAULT != HAL_INS_NONE // wait to ensure INS system inits unless using HAL_INS_NONE while (!_initialized) { sched->delay_microseconds(1000); } #endif #ifdef SCHEDDEBUG printf("%s:%d initialised\n", __PRETTY_FUNCTION__, __LINE__); #endif while (true) { sched->delay_microseconds(1000); sched->_run_timers(); //analog in #ifndef HAL_DISABLE_ADC_DRIVER ((AnalogIn*)hal.analogin)->_timer_tick(); #endif } } void IRAM_ATTR Scheduler::_rcout_thread(void* arg) { Scheduler *sched = (Scheduler *)arg; while (!_initialized) { sched->delay_microseconds(1000); } while (true) { sched->delay_microseconds(4000); // process any pending RC output requests hal.rcout->timer_tick(); } } void IRAM_ATTR Scheduler::_run_timers() { #ifdef SCHEDULERDEBUG printf("%s:%d start \n", __PRETTY_FUNCTION__, __LINE__); #endif if (_in_timer_proc) { return; } #ifdef SCHEDULERDEBUG printf("%s:%d _in_timer_proc \n", __PRETTY_FUNCTION__, __LINE__); #endif _in_timer_proc = true; int num_procs = 0; _timer_sem.take_blocking(); num_procs = _num_timer_procs; _timer_sem.give(); // now call the timer based drivers for (int i = 0; i < num_procs; i++) { if (_timer_proc[i]) { _timer_proc[i](); } } // and the failsafe, if one is setup if (_failsafe != nullptr) { _failsafe(); } _in_timer_proc = false; } void IRAM_ATTR Scheduler::_rcin_thread(void *arg) { Scheduler *sched = (Scheduler *)arg; while (!_initialized) { sched->delay_microseconds(20000); } hal.rcin->init(); while (true) { sched->delay_microseconds(1000); ((RCInput *)hal.rcin)->_timer_tick(); } } void IRAM_ATTR Scheduler::_run_io(void) { #ifdef SCHEDULERDEBUG printf("%s:%d start \n", __PRETTY_FUNCTION__, __LINE__); #endif if (_in_io_proc) { return; } #ifdef SCHEDULERDEBUG printf("%s:%d initialised \n", __PRETTY_FUNCTION__, __LINE__); #endif _in_io_proc = true; int num_procs = 0; _io_sem.take_blocking(); num_procs = _num_io_procs; _io_sem.give(); // now call the IO based drivers for (int i = 0; i < num_procs; i++) { if (_io_proc[i]) { _io_proc[i](); } } _in_io_proc = false; } void IRAM_ATTR Scheduler::_io_thread(void* arg) { #ifdef SCHEDDEBUG printf("%s:%d start \n", __PRETTY_FUNCTION__, __LINE__); #endif mount_sdcard(); Scheduler *sched = (Scheduler *)arg; while (!sched->_initialized) { sched->delay_microseconds(1000); } #ifdef SCHEDDEBUG printf("%s:%d initialised \n", __PRETTY_FUNCTION__, __LINE__); #endif uint32_t last_sd_start_ms = AP_HAL::millis(); while (true) { sched->delay_microseconds(1000); // run registered IO processes sched->_run_io(); if (!hal.util->get_soft_armed()) { // if sdcard hasn't mounted then retry it every 3s in the IO // thread when disarmed uint32_t now = AP_HAL::millis(); if (now - last_sd_start_ms > 3000) { last_sd_start_ms = now; sdcard_retry(); } } } } void Scheduler::_storage_thread(void* arg) { #ifdef SCHEDDEBUG printf("%s:%d start \n", __PRETTY_FUNCTION__, __LINE__); #endif Scheduler *sched = (Scheduler *)arg; while (!sched->_initialized) { sched->delay_microseconds(10000); } #ifdef SCHEDDEBUG printf("%s:%d initialised \n", __PRETTY_FUNCTION__, __LINE__); #endif while (true) { sched->delay_microseconds(1000); // process any pending storage writes hal.storage->_timer_tick(); //print_profile(); } } void Scheduler::_print_profile(void* arg) { Scheduler *sched = (Scheduler *)arg; while (!sched->_initialized) { sched->delay_microseconds(10000); } while (true) { sched->delay(10000); print_profile(); } } void IRAM_ATTR Scheduler::_uart_thread(void *arg) { #ifdef SCHEDDEBUG printf("%s:%d start \n", __PRETTY_FUNCTION__, __LINE__); #endif Scheduler *sched = (Scheduler *)arg; while (!sched->_initialized) { sched->delay_microseconds(2000); } #ifdef SCHEDDEBUG printf("%s:%d initialised\n", __PRETTY_FUNCTION__, __LINE__); #endif while (true) { sched->delay_microseconds(1000); for (uint8_t i=0; i_timer_tick(); } hal.console->_timer_tick(); } } // get the active main loop rate uint16_t IRAM_ATTR Scheduler::get_loop_rate_hz(void) { if (_active_loop_rate_hz == 0) { _active_loop_rate_hz = _loop_rate_hz; } return _active_loop_rate_hz; } // once every 60 seconds, print some stats... void Scheduler::print_stats(void) { static int64_t last_run = 0; if (AP_HAL::millis64() - last_run > 60000) { char buffer[1024]; vTaskGetRunTimeStats(buffer); printf("\n\n%s\n", buffer); heap_caps_print_heap_info(0); last_run = AP_HAL::millis64(); } // printf("loop_rate_hz: %d",get_loop_rate_hz()); } // Run every 10s void Scheduler::print_main_loop_rate(void) { static int64_t last_run = 0; if (AP_HAL::millis64() - last_run > 10000) { last_run = AP_HAL::millis64(); // null pointer in here... const float actual_loop_rate = AP::scheduler().get_filtered_loop_rate_hz(); const uint16_t expected_loop_rate = AP::scheduler().get_loop_rate_hz(); hal.console->printf("loop_rate: actual: %fHz, expected: %uHz\n", actual_loop_rate, expected_loop_rate); } } void IRAM_ATTR Scheduler::_main_thread(void *arg) { #ifdef SCHEDDEBUG printf("%s:%d start\n", __PRETTY_FUNCTION__, __LINE__); #endif Scheduler *sched = (Scheduler *)arg; #ifndef HAL_DISABLE_ADC_DRIVER hal.analogin->init(); #endif hal.rcout->init(); sched->callbacks->setup(); sched->set_system_initialized(); //initialize WTD for current thread on FASTCPU, all cores will be (1 << CONFIG_FREERTOS_NUMBER_OF_CORES) - 1 wdt_init( TWDT_TIMEOUT_MS, 1 << FASTCPU ); // 3 sec #ifdef SCHEDDEBUG printf("%s:%d initialised\n", __PRETTY_FUNCTION__, __LINE__); #endif while (true) { sched->callbacks->loop(); sched->delay_microseconds(250); // run stats periodically #ifdef SCHEDDEBUG sched->print_stats(); #endif sched->print_main_loop_rate(); if (ESP_OK != esp_task_wdt_reset()) { printf("esp_task_wdt_reset() failed\n"); }; } }