InfiniTime/src/systemtask/SystemTask.cpp
Christoph Honal 977936e8e2 System: Refactor pin and interrupt setup
This should ensure better readability of the pin setup procedure,
as well as allow the configuration of the hardware button enable pin
and the accelerometer interrupt pin via the pin mapping header.
2022-06-05 09:53:22 +03:00

596 lines
21 KiB
C++

#include "systemtask/SystemTask.h"
#include <hal/nrf_rtc.h>
#include <libraries/gpiote/app_gpiote.h>
#include <libraries/log/nrf_log.h>
#include <app_timer.h>
#include "BootloaderVersion.h"
#include "components/battery/BatteryController.h"
#include "components/ble/BleController.h"
#include "displayapp/TouchEvents.h"
#include "drivers/Cst816s.h"
#include "drivers/St7789.h"
#include "drivers/InternalFlash.h"
#include "drivers/SpiMaster.h"
#include "drivers/SpiNorFlash.h"
#include "drivers/TwiMaster.h"
#include "drivers/Hrs3300.h"
#include "drivers/PinMap.h"
#include "main.h"
#include "BootErrors.h"
#include <memory>
using namespace Pinetime::System;
namespace {
static inline bool in_isr(void) {
return (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) != 0;
}
}
void DimTimerCallback(TimerHandle_t xTimer) {
NRF_LOG_INFO("DimTimerCallback");
auto sysTask = static_cast<SystemTask*>(pvTimerGetTimerID(xTimer));
sysTask->OnDim();
}
void IdleTimerCallback(TimerHandle_t xTimer) {
NRF_LOG_INFO("IdleTimerCallback");
auto sysTask = static_cast<SystemTask*>(pvTimerGetTimerID(xTimer));
sysTask->OnIdle();
}
void MeasureBatteryTimerCallback(TimerHandle_t xTimer) {
auto* sysTask = static_cast<SystemTask*>(pvTimerGetTimerID(xTimer));
sysTask->PushMessage(Pinetime::System::Messages::MeasureBatteryTimerExpired);
}
SystemTask::SystemTask(Drivers::SpiMaster& spi,
Drivers::St7789& lcd,
Pinetime::Drivers::SpiNorFlash& spiNorFlash,
Drivers::TwiMaster& twiMaster,
Drivers::Cst816S& touchPanel,
Components::LittleVgl& lvgl,
Controllers::Battery& batteryController,
Controllers::Ble& bleController,
Controllers::DateTime& dateTimeController,
Controllers::TimerController& timerController,
Controllers::AlarmController& alarmController,
Drivers::Watchdog& watchdog,
Pinetime::Controllers::NotificationManager& notificationManager,
Pinetime::Controllers::MotorController& motorController,
Pinetime::Drivers::Hrs3300& heartRateSensor,
Pinetime::Controllers::MotionController& motionController,
Pinetime::Drivers::Bma421& motionSensor,
Controllers::Settings& settingsController,
Pinetime::Controllers::HeartRateController& heartRateController,
Pinetime::Applications::DisplayApp& displayApp,
Pinetime::Applications::HeartRateTask& heartRateApp,
Pinetime::Controllers::FS& fs,
Pinetime::Controllers::TouchHandler& touchHandler,
Pinetime::Controllers::ButtonHandler& buttonHandler)
: spi {spi},
lcd {lcd},
spiNorFlash {spiNorFlash},
twiMaster {twiMaster},
touchPanel {touchPanel},
lvgl {lvgl},
batteryController {batteryController},
bleController {bleController},
dateTimeController {dateTimeController},
timerController {timerController},
alarmController {alarmController},
watchdog {watchdog},
notificationManager {notificationManager},
motorController {motorController},
heartRateSensor {heartRateSensor},
motionSensor {motionSensor},
settingsController {settingsController},
heartRateController {heartRateController},
motionController {motionController},
displayApp {displayApp},
heartRateApp(heartRateApp),
fs {fs},
touchHandler {touchHandler},
buttonHandler {buttonHandler},
nimbleController(*this,
bleController,
dateTimeController,
notificationManager,
batteryController,
spiNorFlash,
heartRateController,
motionController,
fs) {
}
void SystemTask::Start() {
systemTasksMsgQueue = xQueueCreate(10, 1);
if (pdPASS != xTaskCreate(SystemTask::Process, "MAIN", 350, this, 1, &taskHandle)) {
APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
}
}
void SystemTask::Process(void* instance) {
auto* app = static_cast<SystemTask*>(instance);
NRF_LOG_INFO("systemtask task started!");
app->Work();
}
void SystemTask::Work() {
BootErrors bootError = BootErrors::None;
watchdog.Setup(7);
watchdog.Start();
NRF_LOG_INFO("Last reset reason : %s", Pinetime::Drivers::Watchdog::ResetReasonToString(watchdog.ResetReason()));
APP_GPIOTE_INIT(2);
app_timer_init();
spi.Init();
spiNorFlash.Init();
spiNorFlash.Wakeup();
fs.Init();
nimbleController.Init();
lcd.Init();
twiMaster.Init();
/*
* TODO We disable this warning message until we ensure it won't be displayed
* on legitimate PineTime equipped with a compatible touch controller.
* (some users reported false positive). See https://github.com/InfiniTimeOrg/InfiniTime/issues/763
if (!touchPanel.Init()) {
bootError = BootErrors::TouchController;
}
*/
touchPanel.Init();
dateTimeController.Register(this);
batteryController.Register(this);
motorController.Init();
motionSensor.SoftReset();
timerController.Init(this);
alarmController.Init(this);
// Reset the TWI device because the motion sensor chip most probably crashed it...
twiMaster.Sleep();
twiMaster.Init();
motionSensor.Init();
motionController.Init(motionSensor.DeviceType());
settingsController.Init();
displayApp.Register(this);
displayApp.Start(bootError);
heartRateSensor.Init();
heartRateSensor.Disable();
heartRateApp.Start();
buttonHandler.Init(this);
// Setup Interrupts
nrfx_gpiote_in_config_t pinConfig;
pinConfig.skip_gpio_setup = false;
pinConfig.hi_accuracy = false;
pinConfig.is_watcher = false;
// Button
nrf_gpio_cfg_output(PinMap::ButtonEnable);
nrf_gpio_pin_set(PinMap::ButtonEnable);
pinConfig.sense = NRF_GPIOTE_POLARITY_TOGGLE;
pinConfig.pull = NRF_GPIO_PIN_PULLDOWN;
nrfx_gpiote_in_init(PinMap::Button, &pinConfig, nrfx_gpiote_evt_handler);
nrfx_gpiote_in_event_enable(PinMap::Button, true);
// Touchscreen
pinConfig.sense = NRF_GPIOTE_POLARITY_HITOLO;
pinConfig.pull = NRF_GPIO_PIN_PULLUP;
nrfx_gpiote_in_init(PinMap::Cst816sIrq, &pinConfig, nrfx_gpiote_evt_handler);
nrfx_gpiote_in_event_enable(PinMap::Cst816sIrq, true);
// Power present
pinConfig.sense = NRF_GPIOTE_POLARITY_TOGGLE;
pinConfig.pull = NRF_GPIO_PIN_NOPULL;
nrfx_gpiote_in_init(PinMap::PowerPresent, &pinConfig, nrfx_gpiote_evt_handler);
nrfx_gpiote_in_event_enable(PinMap::PowerPresent, true);
batteryController.MeasureVoltage();
idleTimer = xTimerCreate("idleTimer", pdMS_TO_TICKS(2000), pdFALSE, this, IdleTimerCallback);
dimTimer = xTimerCreate("dimTimer", pdMS_TO_TICKS(settingsController.GetScreenTimeOut() - 2000), pdFALSE, this, DimTimerCallback);
measureBatteryTimer = xTimerCreate("measureBattery", batteryMeasurementPeriod, pdTRUE, this, MeasureBatteryTimerCallback);
xTimerStart(dimTimer, 0);
xTimerStart(measureBatteryTimer, portMAX_DELAY);
#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop"
while (true) {
UpdateMotion();
uint8_t msg;
if (xQueueReceive(systemTasksMsgQueue, &msg, 100)) {
Messages message = static_cast<Messages>(msg);
switch (message) {
case Messages::EnableSleeping:
// Make sure that exiting an app doesn't enable sleeping,
// if the exiting was caused by a firmware update
if (!bleController.IsFirmwareUpdating()) {
doNotGoToSleep = false;
}
ReloadIdleTimer();
break;
case Messages::DisableSleeping:
doNotGoToSleep = true;
break;
case Messages::UpdateTimeOut:
xTimerChangePeriod(dimTimer, pdMS_TO_TICKS(settingsController.GetScreenTimeOut() - 2000), 0);
break;
case Messages::GoToRunning:
spi.Wakeup();
// Double Tap needs the touch screen to be in normal mode
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
touchPanel.Wakeup();
}
xTimerStart(dimTimer, 0);
spiNorFlash.Wakeup();
lcd.Wakeup();
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToRunning);
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::WakeUp);
if (bleController.IsRadioEnabled() && !bleController.IsConnected()) {
nimbleController.RestartFastAdv();
}
state = SystemTaskState::Running;
isDimmed = false;
break;
case Messages::TouchWakeUp: {
if (touchHandler.GetNewTouchInfo()) {
auto gesture = touchHandler.GestureGet();
if (gesture != Pinetime::Applications::TouchEvents::None &&
((gesture == Pinetime::Applications::TouchEvents::DoubleTap &&
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) ||
(gesture == Pinetime::Applications::TouchEvents::Tap &&
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::SingleTap)))) {
GoToRunning();
}
}
break;
}
case Messages::GoToSleep:
if (doNotGoToSleep) {
break;
}
state = SystemTaskState::GoingToSleep; // Already set in PushMessage()
NRF_LOG_INFO("[systemtask] Going to sleep");
xTimerStop(idleTimer, 0);
xTimerStop(dimTimer, 0);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToSleep);
heartRateApp.PushMessage(Pinetime::Applications::HeartRateTask::Messages::GoToSleep);
break;
case Messages::OnNewTime:
ReloadIdleTimer();
displayApp.PushMessage(Pinetime::Applications::Display::Messages::UpdateDateTime);
if (alarmController.State() == Controllers::AlarmController::AlarmState::Set) {
alarmController.ScheduleAlarm();
}
break;
case Messages::OnNewNotification:
if (settingsController.GetNotificationStatus() == Pinetime::Controllers::Settings::Notification::ON) {
if (state == SystemTaskState::Sleeping) {
GoToRunning();
} else {
ReloadIdleTimer();
}
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NewNotification);
}
break;
case Messages::OnTimerDone:
if (state == SystemTaskState::Sleeping) {
GoToRunning();
}
motorController.RunForDuration(35);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::TimerDone);
break;
case Messages::SetOffAlarm:
if (state == SystemTaskState::Sleeping) {
GoToRunning();
}
motorController.StartRinging();
displayApp.PushMessage(Pinetime::Applications::Display::Messages::AlarmTriggered);
break;
case Messages::StopRinging:
motorController.StopRinging();
break;
case Messages::BleConnected:
ReloadIdleTimer();
isBleDiscoveryTimerRunning = true;
bleDiscoveryTimer = 5;
break;
case Messages::BleFirmwareUpdateStarted:
doNotGoToSleep = true;
if (state == SystemTaskState::Sleeping) {
GoToRunning();
}
displayApp.PushMessage(Pinetime::Applications::Display::Messages::BleFirmwareUpdateStarted);
break;
case Messages::BleFirmwareUpdateFinished:
if (bleController.State() == Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated) {
NVIC_SystemReset();
}
doNotGoToSleep = false;
xTimerStart(dimTimer, 0);
break;
case Messages::StartFileTransfer:
NRF_LOG_INFO("[systemtask] FS Started");
doNotGoToSleep = true;
if (state == SystemTaskState::Sleeping) {
GoToRunning();
}
// TODO add intent of fs access icon or something
break;
case Messages::StopFileTransfer:
NRF_LOG_INFO("[systemtask] FS Stopped");
doNotGoToSleep = false;
xTimerStart(dimTimer, 0);
// TODO add intent of fs access icon or something
break;
case Messages::OnTouchEvent:
if (touchHandler.GetNewTouchInfo()) {
touchHandler.UpdateLvglTouchPoint();
}
ReloadIdleTimer();
displayApp.PushMessage(Pinetime::Applications::Display::Messages::TouchEvent);
break;
case Messages::HandleButtonEvent: {
Controllers::ButtonActions action;
if (nrf_gpio_pin_read(Pinetime::PinMap::Button) == 0) {
action = buttonHandler.HandleEvent(Controllers::ButtonHandler::Events::Release);
} else {
action = buttonHandler.HandleEvent(Controllers::ButtonHandler::Events::Press);
// This is for faster wakeup, sacrificing special longpress and doubleclick handling while sleeping
if (IsSleeping()) {
fastWakeUpDone = true;
GoToRunning();
break;
}
}
HandleButtonAction(action);
} break;
case Messages::HandleButtonTimerEvent: {
auto action = buttonHandler.HandleEvent(Controllers::ButtonHandler::Events::Timer);
HandleButtonAction(action);
} break;
case Messages::OnDisplayTaskSleeping:
if (BootloaderVersion::IsValid()) {
// First versions of the bootloader do not expose their version and cannot initialize the SPI NOR FLASH
// if it's in sleep mode. Avoid bricked device by disabling sleep mode on these versions.
spiNorFlash.Sleep();
}
lcd.Sleep();
spi.Sleep();
// Double Tap needs the touch screen to be in normal mode
if (!settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
touchPanel.Sleep();
}
state = SystemTaskState::Sleeping;
break;
case Messages::OnNewDay:
// We might be sleeping (with TWI device disabled.
// Remember we'll have to reset the counter next time we're awake
stepCounterMustBeReset = true;
break;
case Messages::OnNewHour:
using Pinetime::Controllers::AlarmController;
if (settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours &&
alarmController.State() != AlarmController::AlarmState::Alerting) {
if (state == SystemTaskState::Sleeping) {
GoToRunning();
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Clock);
}
motorController.RunForDuration(35);
}
break;
case Messages::OnNewHalfHour:
using Pinetime::Controllers::AlarmController;
if (settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours &&
alarmController.State() != AlarmController::AlarmState::Alerting) {
if (state == SystemTaskState::Sleeping) {
GoToRunning();
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Clock);
}
motorController.RunForDuration(35);
}
break;
case Messages::OnChargingEvent:
batteryController.ReadPowerState();
motorController.RunForDuration(15);
ReloadIdleTimer();
if (state == SystemTaskState::Sleeping) {
GoToRunning();
}
break;
case Messages::MeasureBatteryTimerExpired:
batteryController.MeasureVoltage();
break;
case Messages::BatteryPercentageUpdated:
nimbleController.NotifyBatteryLevel(batteryController.PercentRemaining());
break;
case Messages::OnPairing:
if (state == SystemTaskState::Sleeping) {
GoToRunning();
}
motorController.RunForDuration(35);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::ShowPairingKey);
break;
case Messages::BleRadioEnableToggle:
if (settingsController.GetBleRadioEnabled()) {
nimbleController.EnableRadio();
} else {
nimbleController.DisableRadio();
}
break;
default:
break;
}
}
if (isBleDiscoveryTimerRunning) {
if (bleDiscoveryTimer == 0) {
isBleDiscoveryTimerRunning = false;
// Services discovery is deferred from 3 seconds to avoid the conflicts between the host communicating with the
// target and vice-versa. I'm not sure if this is the right way to handle this...
nimbleController.StartDiscovery();
} else {
bleDiscoveryTimer--;
}
}
monitor.Process();
uint32_t systick_counter = nrf_rtc_counter_get(portNRF_RTC_REG);
dateTimeController.UpdateTime(systick_counter);
NoInit_BackUpTime = dateTimeController.CurrentDateTime();
if (!nrf_gpio_pin_read(PinMap::Button)) {
watchdog.Kick();
}
}
#pragma clang diagnostic pop
}
void SystemTask::UpdateMotion() {
if (state == SystemTaskState::GoingToSleep || state == SystemTaskState::WakingUp) {
return;
}
if (state == SystemTaskState::Sleeping && !(settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) ||
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake))) {
return;
}
if (stepCounterMustBeReset) {
motionSensor.ResetStepCounter();
stepCounterMustBeReset = false;
}
auto motionValues = motionSensor.Process();
motionController.IsSensorOk(motionSensor.IsOk());
motionController.Update(motionValues.x, motionValues.y, motionValues.z, motionValues.steps);
if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) &&
motionController.Should_RaiseWake(state == SystemTaskState::Sleeping)) {
GoToRunning();
}
if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake) &&
motionController.Should_ShakeWake(settingsController.GetShakeThreshold())) {
GoToRunning();
}
}
void SystemTask::HandleButtonAction(Controllers::ButtonActions action) {
if (IsSleeping()) {
return;
}
ReloadIdleTimer();
using Actions = Controllers::ButtonActions;
switch (action) {
case Actions::Click:
// If the first action after fast wakeup is a click, it should be ignored.
if (!fastWakeUpDone && state != SystemTaskState::GoingToSleep) {
displayApp.PushMessage(Applications::Display::Messages::ButtonPushed);
}
break;
case Actions::DoubleClick:
displayApp.PushMessage(Applications::Display::Messages::ButtonDoubleClicked);
break;
case Actions::LongPress:
displayApp.PushMessage(Applications::Display::Messages::ButtonLongPressed);
break;
case Actions::LongerPress:
displayApp.PushMessage(Applications::Display::Messages::ButtonLongerPressed);
break;
default:
return;
}
fastWakeUpDone = false;
}
void SystemTask::GoToRunning() {
if (state == SystemTaskState::Sleeping) {
state = SystemTaskState::WakingUp;
PushMessage(Messages::GoToRunning);
}
}
void SystemTask::OnTouchEvent() {
if (state == SystemTaskState::Running) {
PushMessage(Messages::OnTouchEvent);
} else if (state == SystemTaskState::Sleeping) {
if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::SingleTap) or
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::DoubleTap)) {
PushMessage(Messages::TouchWakeUp);
}
}
}
void SystemTask::PushMessage(System::Messages msg) {
if (msg == Messages::GoToSleep && !doNotGoToSleep) {
state = SystemTaskState::GoingToSleep;
}
if (in_isr()) {
BaseType_t xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(systemTasksMsgQueue, &msg, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) {
/* Actual macro used here is port specific. */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
} else {
xQueueSend(systemTasksMsgQueue, &msg, portMAX_DELAY);
}
}
void SystemTask::OnDim() {
if (doNotGoToSleep) {
return;
}
NRF_LOG_INFO("Dim timeout -> Dim screen")
displayApp.PushMessage(Pinetime::Applications::Display::Messages::DimScreen);
xTimerStart(idleTimer, 0);
isDimmed = true;
}
void SystemTask::OnIdle() {
if (doNotGoToSleep) {
return;
}
NRF_LOG_INFO("Idle timeout -> Going to sleep")
PushMessage(Messages::GoToSleep);
}
void SystemTask::ReloadIdleTimer() {
if (state != SystemTaskState::Running) {
return;
}
if (isDimmed) {
displayApp.PushMessage(Pinetime::Applications::Display::Messages::RestoreBrightness);
isDimmed = false;
}
xTimerReset(dimTimer, 0);
xTimerStop(idleTimer, 0);
}