Better integration of SPI with DMA and IRQ. Using only 'End' IRQ. Perf could be improved by using 'Started' IRQ to prepare the next buffer while the current one is beeing sent.

This commit is contained in:
JF 2020-01-26 13:37:10 +01:00
parent eb7a1b3ac9
commit 5fa4f5abe0
10 changed files with 223 additions and 164 deletions

View file

@ -7,23 +7,33 @@ Gfx::Gfx(Pinetime::Drivers::St7789 &lcd) : lcd{lcd} {
}
void Gfx::Init() {
lcd.Init();
}
void Gfx::ClearScreen() {
SetBackgroundColor(0x0000);
state.remainingIterations = 240 + 1;
state.currentIteration = 0;
state.busy = true;
state.action = Action::FillRectangle;
lcd.BeginDrawBuffer(0, 0, width, height);
for(int i = 0; i < height; i++) {
lcd.NextDrawBuffer(reinterpret_cast<const uint8_t *>(buffer), width * 2);
}
lcd.EndDrawBuffer();
while(state.busy) {} // TODO wait on an event/queue/... instead of polling
}
void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint16_t color) {
void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint16_t color) {
SetBackgroundColor(color);
lcd.BeginDrawBuffer(0, 0, width, height);
lcd.NextDrawBuffer(reinterpret_cast<const uint8_t *>(buffer), width * 2, 240);
lcd.EndDrawBuffer();
state.remainingIterations = 240 + 1;
state.currentIteration = 0;
state.busy = true;
state.action = Action::FillRectangle;
lcd.BeginDrawBuffer(x, y, w, h);
lcd.NextDrawBuffer(reinterpret_cast<const uint8_t *>(buffer), width * 2);
while(state.busy) {} // TODO wait on an event/queue/... instead of polling
}
void Gfx::DrawString(uint8_t x, uint8_t y, uint16_t color, const char *text, const FONT_INFO *p_font, bool wrap) {
@ -64,21 +74,17 @@ void Gfx::DrawString(uint8_t x, uint8_t y, uint16_t color, const char *text, con
void Gfx::DrawChar(const FONT_INFO *font, uint8_t c, uint8_t *x, uint8_t y, uint16_t color) {
uint8_t char_idx = c - font->startChar;
uint16_t bytes_in_line = CEIL_DIV(font->charInfo[char_idx].widthBits, 8);
uint16_t bg = 0x0000;
if (c == ' ') {
*x += font->height / 2;
return;
}
// TODO For now, LCD and SPI driver start a new transfer (cs pin + set address windows + write byte) FOR EACH PIXEL!
// This could be improved by setting CS pin, DC pin and address window ONLY ONCE for the whole character
lcd.BeginDrawBuffer(*x, y, bytes_in_line*8, font->height);
uint16_t bg = 0x0000;
for (uint16_t i = 0; i < font->height; i++) {
// Build first line
for (uint16_t j = 0; j < bytes_in_line; j++) {
for (uint8_t k = 0; k < 8; k++) {
if ((1 << (7 - k)) & font->data[font->charInfo[char_idx].offset + i * bytes_in_line + j]) {
if ((1 << (7 - k)) & font->data[font->charInfo[char_idx].offset + j]) {
buffer[(j*8)+k] = color;
}
else {
@ -86,9 +92,19 @@ void Gfx::DrawChar(const FONT_INFO *font, uint8_t c, uint8_t *x, uint8_t y, uint
}
}
}
lcd.NextDrawBuffer(reinterpret_cast<uint8_t *>(&buffer), bytes_in_line*8*2);
}
lcd.EndDrawBuffer();
state.remainingIterations = font->height + 0;
state.currentIteration = 0;
state.busy = true;
state.action = Action::DrawChar;
state.font = const_cast<FONT_INFO *>(font);
state.character = c;
state.color = color;
lcd.BeginDrawBuffer(*x, y, bytes_in_line*8, font->height);
lcd.NextDrawBuffer(reinterpret_cast<const uint8_t *>(&buffer), bytes_in_line*8*2);
while(state.busy) {} // TODO wait on an event/queue/... instead of polling
*x += font->charInfo[char_idx].widthBits + font->spacePixels;
}
@ -110,4 +126,40 @@ void Gfx::SetBackgroundColor(uint16_t color) {
}
}
bool Gfx::GetNextBuffer(uint8_t **data, size_t &size) {
if(!state.busy) return false;
state.remainingIterations--;
if (state.remainingIterations == 0) {
state.busy = false;
return false;
}
if(state.action == Action::FillRectangle) {
*data = reinterpret_cast<uint8_t *>(buffer);
size = width * 2;
} else if(state.action == Action::DrawChar) {
uint16_t bg = 0x0000;
uint8_t char_idx = state.character - state.font->startChar;
uint16_t bytes_in_line = CEIL_DIV(state.font->charInfo[char_idx].widthBits, 8);
for (uint16_t j = 0; j < bytes_in_line; j++) {
for (uint8_t k = 0; k < 8; k++) {
if ((1 << (7 - k)) & state.font->data[state.font->charInfo[char_idx].offset + ((state.currentIteration+1) * bytes_in_line) + j]) {
buffer[(j*8)+k] = state.color;
}
else {
buffer[(j*8)+k] = bg;
}
}
}
*data = reinterpret_cast<uint8_t *>(buffer);
size = bytes_in_line*8*2;
}
state.currentIteration++;
return true;
}

View file

@ -1,6 +1,7 @@
#pragma once
#include <cstdint>
#include <nrf_font.h>
#include <drivers/BufferProvider.h>
namespace Pinetime {
@ -8,7 +9,7 @@ namespace Pinetime {
class St7789;
}
namespace Components {
class Gfx {
class Gfx : public Pinetime::Drivers::BufferProvider {
public:
explicit Gfx(Drivers::St7789& lcd);
void Init();
@ -19,11 +20,26 @@ namespace Pinetime {
void Sleep();
void Wakeup();
bool GetNextBuffer(uint8_t **buffer, size_t &size) override;
private:
static constexpr uint8_t width = 240;
static constexpr uint8_t height = 240;
enum class Action { None, FillRectangle, DrawChar};
struct State {
State() : busy{false}, action{Action::None}, remainingIterations{0}, currentIteration{0} {}
volatile bool busy;
volatile Action action;
volatile uint16_t remainingIterations;
volatile uint16_t currentIteration;
volatile FONT_INFO *font;
volatile uint16_t color;
volatile uint8_t character;
};
volatile State state;
uint16_t buffer[width]; // 1 line buffer
Drivers::St7789& lcd;

View file

@ -14,25 +14,20 @@
using namespace Pinetime::Applications;
DisplayApp::DisplayApp(Controllers::Battery &batteryController,
DisplayApp::DisplayApp(Pinetime::Drivers::St7789& lcd,
Pinetime::Components::Gfx& gfx,
Pinetime::Drivers::Cst816S& touchPanel,
Controllers::Battery &batteryController,
Controllers::Ble &bleController,
Controllers::DateTime &dateTimeController) :
spi{Drivers::SpiMaster::SpiModule::SPI0, {
Drivers::SpiMaster::BitOrder::Msb_Lsb,
Drivers::SpiMaster::Modes::Mode3,
Drivers::SpiMaster::Frequencies::Freq8Mhz,
pinSpiSck,
pinSpiMosi,
pinSpiMiso,
pinSpiCsn
}},
lcd{new Drivers::St7789(spi, pinLcdDataCommand)},
gfx{new Components::Gfx(*lcd.get()) },
lcd{lcd},
gfx{gfx},
touchPanel{touchPanel},
batteryController{batteryController},
bleController{bleController},
dateTimeController{dateTimeController},
clockScreen{*(gfx.get())},
messageScreen{*(gfx.get())} {
clockScreen{gfx},
messageScreen{gfx} {
msgQueue = xQueueCreate(queueSize, itemSize);
currentScreen = &clockScreen;
}
@ -59,22 +54,19 @@ void DisplayApp::InitHw() {
nrf_gpio_pin_clear(pinLcdBacklight2);
nrf_gpio_pin_clear(pinLcdBacklight3);
spi.Init();
gfx->Init();
currentScreen->Refresh(true);
touchPanel.Init();
}
uint32_t acc = 0;
uint32_t count = 0;
bool toggle = true;
void DisplayApp::Refresh() {
#if 0
uint32_t before = nrf_rtc_counter_get(portNRF_RTC_REG);
if(toggle) {
gfx->FillRectangle(0,0,240,240,0x0000);
gfx.FillRectangle(0,0,240,240,0x0000);
} else {
gfx->FillRectangle(0,0,240,240,0xffff);
gfx.FillRectangle(0,0,240,240,0xffff);
}
uint32_t after = nrf_rtc_counter_get(portNRF_RTC_REG);
@ -85,8 +77,9 @@ void DisplayApp::Refresh() {
}
count++;
toggle = !toggle;
#endif
#if 0
#if 1
TickType_t queueTimeout;
switch (state) {
case States::Idle:
@ -108,16 +101,16 @@ void DisplayApp::Refresh() {
nrf_gpio_pin_set(pinLcdBacklight2);
vTaskDelay(100);
nrf_gpio_pin_set(pinLcdBacklight1);
lcd->DisplayOff();
lcd->Sleep();
lcd.DisplayOff();
lcd.Sleep();
touchPanel.Sleep();
state = States::Idle;
break;
case Messages::GoToRunning:
lcd->Wakeup();
lcd.Wakeup();
touchPanel.Wakeup();
lcd->DisplayOn();
lcd.DisplayOn();
nrf_gpio_pin_clear(pinLcdBacklight3);
nrf_gpio_pin_clear(pinLcdBacklight2);
nrf_gpio_pin_clear(pinLcdBacklight1);
@ -179,7 +172,7 @@ void DisplayApp::OnTouchEvent() {
auto info = touchPanel.GetTouchInfo();
if(info.isTouch) {
gfx->FillRectangle(info.x-10, info.y-10, 20,20, pointColor);
gfx.FillRectangle(info.x-10, info.y-10, 20,20, pointColor);
pointColor+=10;
}
}

View file

@ -23,7 +23,10 @@ namespace Pinetime {
public:
enum class States {Idle, Running};
enum class Messages : uint8_t {GoToSleep, GoToRunning, UpdateDateTime, UpdateBleConnection, UpdateBatteryLevel, TouchEvent} ;
DisplayApp(Controllers::Battery &batteryController,
DisplayApp(Pinetime::Drivers::St7789& lcd,
Pinetime::Components::Gfx& gfx,
Pinetime::Drivers::Cst816S&,
Controllers::Battery &batteryController,
Controllers::Ble &bleController,
Controllers::DateTime& dateTimeController);
void Start();
@ -33,9 +36,8 @@ namespace Pinetime {
TaskHandle_t taskHandle;
static void Process(void* instance);
void InitHw();
Pinetime::Drivers::SpiMaster spi;
std::unique_ptr<Drivers::St7789> lcd;
std::unique_ptr<Components::Gfx> gfx;
Pinetime::Drivers::St7789& lcd;
Pinetime::Components::Gfx& gfx;
const FONT_INFO largeFont {lCD_70ptFontInfo.height, lCD_70ptFontInfo.startChar, lCD_70ptFontInfo.endChar, lCD_70ptFontInfo.spacePixels, lCD_70ptFontInfo.charInfo, lCD_70ptFontInfo.data};
const FONT_INFO smallFont {lCD_14ptFontInfo.height, lCD_14ptFontInfo.startChar, lCD_14ptFontInfo.endChar, lCD_14ptFontInfo.spacePixels, lCD_14ptFontInfo.charInfo, lCD_14ptFontInfo.data};
void Refresh();
@ -52,18 +54,13 @@ namespace Pinetime {
Pinetime::Controllers::Ble &bleController;
Pinetime::Controllers::DateTime& dateTimeController;
Pinetime::Drivers::Cst816S touchPanel;
Pinetime::Drivers::Cst816S& touchPanel;
void OnTouchEvent();
Screens::Clock clockScreen;
Screens::Screen* currentScreen = nullptr;
Screens::Message messageScreen;
bool screenState = false;
static constexpr uint8_t pinSpiSck = 2;
static constexpr uint8_t pinSpiMosi = 3;
static constexpr uint8_t pinSpiMiso = 4;
static constexpr uint8_t pinSpiCsn = 25;
static constexpr uint8_t pinLcdDataCommand = 18;
static constexpr uint8_t pinLcdBacklight1 = 14;
static constexpr uint8_t pinLcdBacklight2 = 22;
static constexpr uint8_t pinLcdBacklight3 = 23;

View file

@ -0,0 +1,11 @@
#pragma once
#include <cstddef>
namespace Pinetime {
namespace Drivers {
class BufferProvider {
public:
virtual bool GetNextBuffer(uint8_t** buffer, size_t& size) = 0;
};
}
}

View file

@ -4,10 +4,9 @@
#include <algorithm>
using namespace Pinetime::Drivers;
SpiMaster* spiInstance;
SpiMaster::SpiMaster(const SpiMaster::SpiModule spi, const SpiMaster::Parameters &params) :
spi{spi}, params{params} {
spiInstance = this;
}
bool SpiMaster::Init() {
@ -84,11 +83,12 @@ void SpiMaster::setup_workaround_for_ftpan_58(NRF_SPIM_Type *spim, uint32_t ppi_
NRF_PPI->CHENSET = 1U << ppi_channel;
}
void SpiMaster::irqStarted() {
if(busy) {
void SpiMaster::OnEndEvent(BufferProvider& provider) {
if(!busy) return;
auto s = currentBufferSize;
if(s > 0) {
auto currentSize = std::min((size_t)255, s);
auto currentSize = std::min((size_t) 255, s);
NRF_SPIM0->TXD.PTR = (uint32_t) currentBufferAddr;
NRF_SPIM0->TXD.MAXCNT = currentSize;
@ -101,17 +101,14 @@ void SpiMaster::irqStarted() {
NRF_SPIM0->RXD.MAXCNT = 0;
NRF_SPIM0->RXD.LIST = 0;
if(repeat == 0)
NRF_SPIM0->SHORTS = 0;
return;
}else {
if(repeat > 0) {
repeat = repeat -1;
currentBufferAddr = bufferAddr;
currentBufferSize = bufferSize;
s = currentBufferSize;
NRF_SPIM0->TASKS_START = 1;
} else {
uint8_t* buffer = nullptr;
size_t size = 0;
if(provider.GetNextBuffer(&buffer, size)) {
currentBufferAddr = (uint32_t) buffer;
currentBufferSize = size;
auto s = currentBufferSize;
auto currentSize = std::min((size_t)255, s);
NRF_SPIM0->TXD.PTR = (uint32_t) currentBufferAddr;
NRF_SPIM0->TXD.MAXCNT = currentSize;
@ -123,22 +120,20 @@ void SpiMaster::irqStarted() {
NRF_SPIM0->RXD.PTR = (uint32_t) 0;
NRF_SPIM0->RXD.MAXCNT = 0;
NRF_SPIM0->RXD.LIST = 0;
}
}
}
}
void SpiMaster::irqEnd() {
if(busy) {
if(repeat == 0 && currentBufferSize == 0) {
nrf_gpio_pin_set(pinCsn);
NRF_SPIM0->TASKS_START = 1;
} else {
busy = false;
nrf_gpio_pin_set(pinCsn);
}
}
}
void SpiMaster::OnStartedEvent(BufferProvider& provider) {
if(!busy) return;
}
bool SpiMaster::Write(const uint8_t *data, size_t size, size_t r) {
bool SpiMaster::Write(const uint8_t *data, size_t size) {
if(data == nullptr) return false;
while(busy) {
@ -162,16 +157,12 @@ bool SpiMaster::Write(const uint8_t *data, size_t size, size_t r) {
nrf_gpio_pin_clear(pinCsn);
currentBufferAddr = bufferAddr = (uint32_t)data;
currentBufferSize = bufferSize = size;
repeat = r;
currentBufferAddr = (uint32_t)data;
currentBufferSize = size;
busy = true;
if(repeat > 0)
NRF_SPIM0->SHORTS = (1<<17);
auto currentSize = std::min((size_t)255, bufferSize);
NRF_SPIM0->TXD.PTR = bufferAddr;
auto currentSize = std::min((size_t)255, (size_t)currentBufferSize);
NRF_SPIM0->TXD.PTR = currentBufferAddr;
NRF_SPIM0->TXD.MAXCNT = currentSize;
NRF_SPIM0->TXD.LIST = 0;
@ -187,21 +178,11 @@ bool SpiMaster::Write(const uint8_t *data, size_t size, size_t r) {
if(size == 1) {
while (NRF_SPIM0->EVENTS_END == 0);
busy = false;
nrf_gpio_pin_set(pinCsn);
}
return true;
}
bool SpiMaster::GetStatusEnd() {
return (bool)*(volatile uint32_t *)((uint8_t *)spiBaseAddress + (uint32_t)NRF_SPIM_EVENT_END);
}
bool SpiMaster::GetStatusStarted() {
return (bool)*(volatile uint32_t *)((uint8_t *)spiBaseAddress + (uint32_t)NRF_SPIM_EVENT_STARTED);
}
void SpiMaster::Sleep() {
while(NRF_SPIM0->ENABLE != 0) {
NRF_SPIM0->ENABLE = (SPIM_ENABLE_ENABLE_Disabled << SPIM_ENABLE_ENABLE_Pos);
@ -216,8 +197,4 @@ void SpiMaster::Wakeup() {
Init();
}
void SpiMaster::Wait() {
while(busy) {
asm("nop");
}
}

View file

@ -3,6 +3,8 @@
#include <cstddef>
#include <array>
#include <atomic>
#include "BufferProvider.h"
namespace Pinetime {
namespace Drivers {
class SpiMaster {
@ -23,20 +25,17 @@ namespace Pinetime {
SpiMaster(const SpiModule spi, const Parameters& params);
bool Init();
bool Write(const uint8_t* data, size_t size, size_t r = 0);
void setup_workaround_for_ftpan_58(NRF_SPIM_Type *spim, uint32_t ppi_channel, uint32_t gpiote_channel);
void Wait();
bool Write(const uint8_t* data, size_t size);
void OnStartedEvent(BufferProvider& provider);
void OnEndEvent(BufferProvider& provider);
void Sleep();
void Wakeup();
bool GetStatusEnd();
bool GetStatusStarted();
void irqEnd();
void irqStarted();
private:
void setup_workaround_for_ftpan_58(NRF_SPIM_Type *spim, uint32_t ppi_channel, uint32_t gpiote_channel);
NRF_SPIM_Type * spiBaseAddress;
uint8_t pinCsn;
@ -44,12 +43,8 @@ namespace Pinetime {
SpiMaster::Parameters params;
volatile bool busy = false;
uint32_t bufferAddr = 0;
volatile uint32_t currentBufferAddr = 0;
size_t bufferSize = 0;
volatile size_t currentBufferSize = 0;
volatile uint32_t repeat = 0;
};
}
}

View file

@ -37,8 +37,8 @@ void St7789::WriteData(uint8_t data) {
}
void St7789::WriteSpi(const uint8_t* data, size_t size, size_t repeat) {
spi.Write(data, size, repeat);
void St7789::WriteSpi(const uint8_t* data, size_t size) {
spi.Write(data, size);
}
void St7789::SoftwareReset() {
@ -142,12 +142,8 @@ void St7789::BeginDrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t he
nrf_gpio_pin_set(pinDataCommand);
}
void St7789::EndDrawBuffer() {
spi.Wait();
}
void St7789::NextDrawBuffer(const uint8_t *data, size_t size, size_t repeat) {
WriteSpi(data, size, repeat);
void St7789::NextDrawBuffer(const uint8_t *data, size_t size) {
WriteSpi(data, size);
}
void St7789::HardwareReset() {
@ -178,5 +174,3 @@ void St7789::Wakeup() {
NormalModeOn();
DisplayOn();
}

View file

@ -12,16 +12,13 @@ namespace Pinetime {
void DrawPixel(uint16_t x, uint16_t y, uint32_t color);
void BeginDrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height);
void NextDrawBuffer(const uint8_t* data, size_t size, size_t repeat = 0);
void EndDrawBuffer();
void NextDrawBuffer(const uint8_t* data, size_t size);
void DisplayOn();
void DisplayOff();
void Sleep();
void Wakeup();
private:
SpiMaster& spi;
uint8_t pinDataCommand;
@ -35,12 +32,9 @@ namespace Pinetime {
void DisplayInversionOn();
void NormalModeOn();
void WriteToRam();
void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void WriteCommand(uint8_t cmd);
void WriteSpi(const uint8_t* data, size_t size, size_t repeat = 0);
void WriteSpi(const uint8_t* data, size_t size);
enum class Commands : uint8_t {
SoftwareReset = 0x01,
@ -62,7 +56,6 @@ namespace Pinetime {
static constexpr uint16_t Width = 240;
static constexpr uint16_t Height = 240;
void RowAddressSet();
};
}
}

View file

@ -15,6 +15,10 @@
#include "BLE/BleManager.h"
#include "Components/Battery/BatteryController.h"
#include "Components/Ble/BleController.h"
#include "../drivers/Cst816s.h"
#include <drivers/St7789.h>
#include <drivers/SpiMaster.h>
#include <Components/Gfx/Gfx.h>
#if NRF_LOG_ENABLED
#include "Logging/NrfLogger.h"
@ -24,6 +28,18 @@ Pinetime::Logging::NrfLogger logger;
Pinetime::Logging::DummyLogger logger;
#endif
std::unique_ptr<Pinetime::Drivers::SpiMaster> spi;
std::unique_ptr<Pinetime::Drivers::St7789> lcd;
std::unique_ptr<Pinetime::Components::Gfx> gfx;
std::unique_ptr<Pinetime::Drivers::Cst816S> touchPanel;
static constexpr uint8_t pinSpiSck = 2;
static constexpr uint8_t pinSpiMosi = 3;
static constexpr uint8_t pinSpiMiso = 4;
static constexpr uint8_t pinSpiCsn = 25;
static constexpr uint8_t pinLcdDataCommand = 18;
std::unique_ptr<Pinetime::Applications::DisplayApp> displayApp;
TaskHandle_t systemThread;
bool isSleeping = false;
@ -85,9 +101,29 @@ void SystemTask(void *) {
APP_GPIOTE_INIT(2);
bool erase_bonds=false;
nrf_sdh_freertos_init(ble_manager_start_advertising, &erase_bonds);
spi.reset(new Pinetime::Drivers::SpiMaster {Pinetime::Drivers::SpiMaster::SpiModule::SPI0, {
Pinetime::Drivers::SpiMaster::BitOrder::Msb_Lsb,
Pinetime::Drivers::SpiMaster::Modes::Mode3,
Pinetime::Drivers::SpiMaster::Frequencies::Freq8Mhz,
pinSpiSck,
pinSpiMosi,
pinSpiMiso,
pinSpiCsn
}});
lcd.reset(new Pinetime::Drivers::St7789(*spi, pinLcdDataCommand));
gfx.reset(new Pinetime::Components::Gfx(*lcd));
touchPanel.reset(new Pinetime::Drivers::Cst816S());
spi->Init();
lcd->Init();
touchPanel->Init();
batteryController.Init();
displayApp.reset(new Pinetime::Applications::DisplayApp(*lcd, *gfx, *touchPanel, batteryController, bleController, dateTimeController));
displayApp->Start();
batteryController.Init();
batteryController.Update();
displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::UpdateBatteryLevel);
@ -158,27 +194,22 @@ void OnNewTime(current_time_char_t* currentTime) {
dayOfWeek, hour, minute, second, nrf_rtc_counter_get(portNRF_RTC_REG));
}
extern Pinetime::Drivers::SpiMaster* spiInstance;
void SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler(void) {
if(((NRF_SPIM0->INTENSET & (1<<6)) != 0) && NRF_SPIM0->EVENTS_END == 1) {
NRF_SPIM0->EVENTS_END = 0;
spiInstance->irqEnd();
spi->OnEndEvent(*gfx);
}
if(((NRF_SPIM0->INTENSET & (1<<19)) != 0) && NRF_SPIM0->EVENTS_STARTED == 1) {
NRF_SPIM0->EVENTS_STARTED = 0;
spiInstance->irqStarted();
spi->OnStartedEvent(*gfx);
}
if(((NRF_SPIM0->INTENSET & (1<<1)) != 0) && NRF_SPIM0->EVENTS_STOPPED == 1) {
NRF_SPIM0->EVENTS_STOPPED = 0;
}
return;
}
int main(void) {
displayApp.reset(new Pinetime::Applications::DisplayApp(batteryController, bleController, dateTimeController));
logger.Init();
nrf_drv_clock_init();