From 6d1fe77fc9ebf3c620cd68f65774ab5a47030077 Mon Sep 17 00:00:00 2001 From: Niklas Dusenlund Date: Thu, 18 Sep 2025 10:43:16 +0200 Subject: [PATCH 1/2] ui: introduce double buffering Create a new module called "canvas" which is responsible for double buffering. Double buffering is required to enable asynchronous transfer of the frame buffer. While the "active" frame buffer is being transferred to the oled in the background, the ui will render to a "working" frame buffer. When the rendering is complete the buffers are flipped with "canvas_commit". --- src/CMakeLists.txt | 1 + src/bootloader/bootloader.c | 25 ++++---- src/bootloader/startup.c | 7 ++- src/factorysetup.c | 2 +- src/firmware.c | 2 +- src/reset.c | 7 ++- src/rust/bitbox02-rust/src/general/screen.rs | 9 ++- src/rust/bitbox02-sys/build.rs | 7 ++- src/rust/bitbox02-sys/wrapper.h | 1 + src/rust/bitbox02/src/lib.rs | 12 ++-- src/screen.c | 25 +++----- src/screen.h | 5 +- src/ui/canvas.c | 61 ++++++++++++++++++++ src/ui/canvas.h | 59 +++++++++++++++++++ src/ui/oled/oled.c | 30 ++++------ src/ui/oled/oled.h | 17 ++---- src/ui/oled/sh1107.c | 16 +++-- src/ui/oled/sh1107.h | 4 +- src/ui/oled/ssd1312.c | 16 +++-- src/ui/oled/ssd1312.h | 4 +- src/ui/screen_process.c | 6 +- src/ui/ugui/ugui.c | 12 ---- src/ui/ugui/ugui.h | 4 -- test/hardware-fakes/src/fake_oled.c | 15 +++++ test/hardware-fakes/src/fake_screen.c | 2 - 25 files changed, 228 insertions(+), 121 deletions(-) create mode 100644 src/ui/canvas.c create mode 100644 src/ui/canvas.h create mode 100644 test/hardware-fakes/src/fake_oled.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 254529ccfa..b010e31fbc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -128,6 +128,7 @@ set(DRIVER-SOURCES ${CMAKE_SOURCE_DIR}/src/platform/driver_init.c ${CMAKE_SOURCE_DIR}/src/ui/oled/oled.c ${CMAKE_SOURCE_DIR}/src/ui/oled/oled_writer.c + ${CMAKE_SOURCE_DIR}/src/ui/canvas.c ) set(DRIVER-SOURCES ${DRIVER-SOURCES} PARENT_SCOPE) diff --git a/src/bootloader/bootloader.c b/src/bootloader/bootloader.c index 3791fe4e72..85da6ab6a4 100644 --- a/src/bootloader/bootloader.c +++ b/src/bootloader/bootloader.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -328,15 +329,14 @@ static void _render_message(const char* message, int duration) { char print[100]; snprintf(print, sizeof(print), "%s", message); - UG_ClearBuffer(); UG_PutString(0, 0, print, false); - UG_SendBuffer(); + canvas_commit(); + oled_present(); delay_ms(duration); } void bootloader_render_default_screen(void) { - UG_ClearBuffer(); _load_logo(); #if PLATFORM_BITBOX02PLUS == 1 UG_PutString(0, SCREEN_HEIGHT - 9 * 2 - 5, "See the BitBoxApp", false); @@ -354,7 +354,8 @@ void bootloader_render_default_screen(void) } UG_PutString(0, SCREEN_HEIGHT - 9, "See the BitBoxApp", false); #endif - UG_SendBuffer(); + canvas_commit(); + oled_present(); } #if PLATFORM_BITBOX02PLUS @@ -368,7 +369,6 @@ void bootloader_render_ble_confirm_screen(bool confirmed) uint32_t pairing_code_int = (*(uint32_t*)&bootloader_pairing_code_bytes[0]) % 1000000; char code_str[10] = {0}; snprintf(code_str, sizeof(code_str), "%06u", (unsigned)pairing_code_int); - UG_ClearBuffer(); uint16_t check_width = IMAGE_DEFAULT_CHECKMARK_HEIGHT + IMAGE_DEFAULT_CHECKMARK_HEIGHT / 2 - 1; if (confirmed) { UG_PutString(15, 0, "Confirm on app", false); @@ -380,13 +380,13 @@ void bootloader_render_ble_confirm_screen(bool confirmed) UG_FontSelect(&font_monogram_5X9); UG_PutString(45, SCREEN_HEIGHT / 2 - 9, code_str, false); UG_FontSelect(&font_font_a_9X9); - UG_SendBuffer(); + canvas_commit(); + oled_present(); } #endif static void _render_progress(float progress) { - UG_ClearBuffer(); _load_logo(); if (progress > 0) { char label[5] = {0}; @@ -401,7 +401,8 @@ static void _render_progress(float progress) msg = "INSTALLING"; } UG_PutString(SCREEN_WIDTH / 2 - 3, SCREEN_HEIGHT - 9 * 2, msg, false); - UG_SendBuffer(); + canvas_commit(); + oled_present(); } static void _render_hash(const char* title, const uint8_t* hash) @@ -433,7 +434,6 @@ static void _render_hash(const char* title, const uint8_t* hash) &hash_hex[48]); for (uint8_t i = 1; i <= seconds; i++) { - UG_ClearBuffer(); UG_PutString(0, 0, title, false); snprintf(timer_buf, sizeof(timer_buf), "%ds", seconds - i); @@ -449,7 +449,8 @@ static void _render_hash(const char* title, const uint8_t* hash) UG_FontSelect(f_regular); - UG_SendBuffer(); + canvas_commit(); + oled_present(); delay_ms(1000); } bootloader_render_default_screen(); @@ -1013,7 +1014,6 @@ static void _check_init(boot_data_t* data) #ifdef BOOTLOADER_DEVDEVICE static bool _devdevice_enter(secbool_u32 firmware_verified) { - UG_ClearBuffer(); UG_PutString(0, 0, " ", false); UG_PutString(0, SCREEN_HEIGHT / 2 - 11, "DEV DEVICE", false); UG_PutString(0, SCREEN_HEIGHT / 2 + 2, "NOT FOR VALUE", false); @@ -1043,7 +1043,8 @@ static bool _devdevice_enter(secbool_u32 firmware_verified) UG_DrawLine(xpos + 5, ypos, xpos, ypos + 5, C_WHITE); UG_DrawLine(xpos - 2, ypos + 3, xpos, ypos + 5, C_WHITE); } - UG_SendBuffer(); + canvas_commit(); + oled_present(); while (true) { do { qtouch_process(); diff --git a/src/bootloader/startup.c b/src/bootloader/startup.c index 52959772b0..75d935c130 100644 --- a/src/bootloader/startup.c +++ b/src/bootloader/startup.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -83,7 +84,7 @@ int main(void) bootloader_init(); platform_init(); __stack_chk_guard = rand_sync_read32(&RAND_0); - screen_init(oled_set_pixel, oled_mirror, oled_clear_buffer); + screen_init(oled_set_pixel, oled_mirror); #if defined(BOOTLOADER_DEVDEVICE) || PLATFORM_BITBOX02PLUS == 1 qtouch_init(); #endif @@ -189,7 +190,6 @@ int main(void) if (qtouch_is_scroller_active(top_slider)) { bool ok; - UG_ClearBuffer(); if (qtouch_get_scroller_position(top_slider) < 127) { bootloader_render_default_screen(); ok = false; @@ -218,7 +218,8 @@ int main(void) ringbuffer_put(&uart_write_queue, tmp[i]); } bootloader_pairing_request = false; - UG_SendBuffer(); + canvas_commit(); + oled_present(); } } #endif diff --git a/src/factorysetup.c b/src/factorysetup.c index 24185c255e..ec10afc443 100644 --- a/src/factorysetup.c +++ b/src/factorysetup.c @@ -577,7 +577,7 @@ int main(void) system_init(); platform_init(); __stack_chk_guard = common_stack_chk_guard(); - screen_init(oled_set_pixel, oled_mirror, oled_clear_buffer); + screen_init(oled_set_pixel, oled_mirror); screen_splash(); common_main(); diff --git a/src/firmware.c b/src/firmware.c index 6af86f8747..bdc7d291c6 100644 --- a/src/firmware.c +++ b/src/firmware.c @@ -41,7 +41,7 @@ int main(void) system_init(); platform_init(); __stack_chk_guard = common_stack_chk_guard(); - screen_init(oled_set_pixel, oled_mirror, oled_clear_buffer); + screen_init(oled_set_pixel, oled_mirror); screen_splash(); qtouch_init(); common_main(); diff --git a/src/reset.c b/src/reset.c index f860fd4ea8..71d71f803e 100644 --- a/src/reset.c +++ b/src/reset.c @@ -23,12 +23,14 @@ #include "system.h" #include "uart.h" #include +#include #ifndef TESTING #include "securechip/securechip.h" #include #include #include +#include #include #endif @@ -41,9 +43,10 @@ static void _show_reset_label(bool status) { const char* msg = "Device reset"; component_t* comp = status_create(msg, status, NULL, NULL); - screen_clear(); + canvas_clear(); comp->f->render(comp); - UG_SendBuffer(); + canvas_commit(); + oled_present(); comp->f->cleanup(comp); delay_ms(3000); } diff --git a/src/rust/bitbox02-rust/src/general/screen.rs b/src/rust/bitbox02-rust/src/general/screen.rs index 1aedafb50c..b1e216ec90 100644 --- a/src/rust/bitbox02-rust/src/general/screen.rs +++ b/src/rust/bitbox02-rust/src/general/screen.rs @@ -14,13 +14,16 @@ use core::time::Duration; -use bitbox02::{delay, ug_clear_buffer, ug_font_select_9x9, ug_put_string, ug_send_buffer}; +use bitbox02::{ + canvas_clear, canvas_commit, delay, oled_present, ug_font_select_9x9, ug_put_string, +}; pub fn print_debug_internal(duration: Duration, msg: &str) { - ug_clear_buffer(); + canvas_clear(); ug_font_select_9x9(); ug_put_string(0, 0, msg, false); - ug_send_buffer(); + canvas_commit(); + oled_present(); delay(duration); } diff --git a/src/rust/bitbox02-sys/build.rs b/src/rust/bitbox02-sys/build.rs index 78c7597d9d..f77b3f90bb 100644 --- a/src/rust/bitbox02-sys/build.rs +++ b/src/rust/bitbox02-sys/build.rs @@ -53,14 +53,14 @@ const ALLOWLIST_TYPES: &[&str] = &[ ]; const ALLOWLIST_FNS: &[&str] = &[ - "UG_ClearBuffer", "UG_FontSelect", "UG_PutString", - "UG_SendBuffer", "bip32_derive_xpub", "bitbox02_smarteeprom_init", "bitbox_secp256k1_dleq_prove", "bitbox_secp256k1_dleq_verify", + "canvas_clear", + "canvas_commit", "confirm_create", "confirm_transaction_address_create", "confirm_transaction_fee_create", @@ -113,6 +113,7 @@ const ALLOWLIST_FNS: &[&str] = &[ "memory_get_platform", "memory_get_securechip_type", "memory_spi_get_active_ble_firmware_version", + "oled_present", "spi_mem_protected_area_write", "menu_create", "fake_memory_factoryreset", @@ -199,6 +200,7 @@ const BITBOX02_SOURCES: &[&str] = &[ "src/u2f.c", "src/u2f/u2f_app.c", "src/u2f/u2f_packet.c", + "src/ui/canvas.c", "src/ui/components/button.c", "src/ui/components/confirm_gesture.c", "src/ui/components/confirm_transaction.c", @@ -411,6 +413,7 @@ pub fn main() -> Result<(), &'static str> { "test/hardware-fakes/src/fake_component.c", "test/hardware-fakes/src/fake_diskio.c", "test/hardware-fakes/src/fake_memory.c", + "test/hardware-fakes/src/fake_oled.c", "test/hardware-fakes/src/fake_qtouch.c", "test/hardware-fakes/src/fake_screen.c", "test/hardware-fakes/src/fake_securechip.c", diff --git a/src/rust/bitbox02-sys/wrapper.h b/src/rust/bitbox02-sys/wrapper.h index af56304aa2..fe0803b1c3 100644 --- a/src/rust/bitbox02-sys/wrapper.h +++ b/src/rust/bitbox02-sys/wrapper.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rust/bitbox02/src/lib.rs b/src/rust/bitbox02/src/lib.rs index 4e93f4732b..e8e4b09e5b 100644 --- a/src/rust/bitbox02/src/lib.rs +++ b/src/rust/bitbox02/src/lib.rs @@ -62,12 +62,16 @@ pub fn ug_put_string(x: i16, y: i16, input: &str, inverted: bool) { } } -pub fn ug_clear_buffer() { - unsafe { bitbox02_sys::UG_ClearBuffer() } +pub fn canvas_clear() { + unsafe { bitbox02_sys::canvas_clear() } } -pub fn ug_send_buffer() { - unsafe { bitbox02_sys::UG_SendBuffer() } +pub fn canvas_commit() { + unsafe { bitbox02_sys::canvas_commit() } +} + +pub fn oled_present() { + unsafe { bitbox02_sys::oled_present() } } pub fn ug_font_select_9x9() { diff --git a/src/screen.c b/src/screen.c index f0fbecda92..ebfe7e07dc 100644 --- a/src/screen.c +++ b/src/screen.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -32,7 +33,6 @@ static UG_GUI guioled; // Global GUI structure for OLED screen static bool screen_upside_down = false; static void (*_mirror_fn)(bool); -static void (*_clear_fn)(void); UG_COLOR screen_front_color = C_WHITE; UG_COLOR screen_back_color = C_BLACK; @@ -45,10 +45,11 @@ void screen_print_debug(const char* message, int duration) { char print[100]; snprintf(print, sizeof(print), "%s", message); - screen_clear(); + canvas_clear(); UG_FontSelect(&font_font_a_9X9); UG_PutString(0, 0, print, false); - UG_SendBuffer(); + canvas_commit(); + oled_present(); #ifndef TESTING if (duration > 0) delay_ms(duration); #endif @@ -79,16 +80,14 @@ void screen_print_debug_hex(const uint8_t* bytes, size_t len, int duration) // Careful, this function is used in both the bootloader and the firmware. void screen_splash(void) { - screen_clear(); - int height = IMAGE_DEFAULT_ARROW_HEIGHT; int x = 0; int y = SCREEN_HEIGHT / 2 - height; image_arrow(x - height + 2, y, height, ARROW_RIGHT); image_arrow(SCREEN_WIDTH - x - 2, y, height, ARROW_LEFT); - UG_SendBuffer(); - screen_clear(); + canvas_commit(); + oled_present(); } void screen_rotate(void) @@ -105,18 +104,8 @@ bool screen_is_upside_down(void) return screen_upside_down; } -void screen_init( - void (*pixel_fn)(UG_S16, UG_S16, UG_COLOR), - void (*mirror_fn)(bool), - void (*clear_fn)(void)) +void screen_init(void (*pixel_fn)(UG_S16, UG_S16, UG_COLOR), void (*mirror_fn)(bool)) { _mirror_fn = mirror_fn; - _clear_fn = clear_fn; UG_Init(&guioled, pixel_fn, &font_font_a_11X10, SCREEN_WIDTH, SCREEN_HEIGHT); } - -void screen_clear(void) -{ - ASSERT(_clear_fn); - _clear_fn(); -} diff --git a/src/screen.h b/src/screen.h index a2f43883ff..4be6271d8e 100644 --- a/src/screen.h +++ b/src/screen.h @@ -34,10 +34,7 @@ extern slider_location_t bottom_slider; #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 -void screen_init( - void (*pixel_fn)(UG_S16, UG_S16, UG_COLOR), - void (*mirror_fn)(bool), - void (*clear_fn)(void)); +void screen_init(void (*pixel_fn)(UG_S16, UG_S16, UG_COLOR), void (*mirror_fn)(bool)); void screen_print_debug(const char* message, int duration); void screen_sprintf_debug(int duration, const char* fmt, ...) __attribute__((format(printf, 2, 0))); void screen_print_debug_hex(const uint8_t* bytes, size_t len, int duration); diff --git a/src/ui/canvas.c b/src/ui/canvas.c new file mode 100644 index 0000000000..597a32dd4f --- /dev/null +++ b/src/ui/canvas.c @@ -0,0 +1,61 @@ +// Copyright 2025 Shift Crypto AG +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +static uint8_t* _canvas_active = NULL; +static uint8_t* _canvas_working = NULL; + +// One working buffer and one active buffer. The buffer must be 4 byte aligned for DMA transfers. +static uint8_t _canvas_0[CANVAS_SIZE] __attribute__((aligned(4))) = {0}; +static uint8_t _canvas_1[CANVAS_SIZE] __attribute__((aligned(4))) = {0}; + +void canvas_init(void) +{ + _canvas_working = _canvas_0; + _canvas_active = _canvas_1; +} + +void canvas_fill(uint8_t color) +{ + uint8_t pixels = color ? 0xff : 0; + memset(canvas_working(), pixels, CANVAS_SIZE); +} + +void canvas_clear(void) +{ + canvas_fill(0); +} + +void canvas_commit(void) +{ + uint8_t* _canvas_tmp = _canvas_working; + _canvas_working = _canvas_active; + _canvas_active = _canvas_tmp; + canvas_clear(); +} + +uint8_t* canvas_working(void) +{ + ASSERT(_canvas_working); + return _canvas_working; +} + +uint8_t* canvas_active(void) +{ + ASSERT(_canvas_active); + return _canvas_active; +} diff --git a/src/ui/canvas.h b/src/ui/canvas.h new file mode 100644 index 0000000000..3a05181848 --- /dev/null +++ b/src/ui/canvas.h @@ -0,0 +1,59 @@ +// Copyright 2025 Shift Crypto AG +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef CANVAS_H +#define CANVAS_H + +#include + +// 8 pixels per byte in the canvas. +#define CANVAS_SIZE ((SCREEN_WIDTH * SCREEN_HEIGHT) / 8) + +#include + +/* + * Initialize canvas + */ + +void canvas_init(void); + +/* + * Fill the whole working canvas with one color + */ +void canvas_fill(uint8_t color); + +/* + * Clear working canvas (fill with 0) + */ +void canvas_clear(void); + +/* + * Commit the current "working" buffer to become "active" and clear the working buffer. + * + * Invalidates pointer returned from `canvas_working()`. `canvas_working()` must be called again to + * get the current working frame buffer. + */ +void canvas_commit(void); + +/* + * Get a pointer to current working canvas. This is the canvas that can be updated and isn't + * currently being displayed. + */ +uint8_t* canvas_working(void); + +/* + * Get a pointer ot the current active canvas, being sent to the display. (Should only be used by + * the screen driver.) + */ +uint8_t* canvas_active(void); +#endif diff --git a/src/ui/oled/oled.c b/src/ui/oled/oled.c index 713547e457..b25bb7073d 100644 --- a/src/ui/oled/oled.c +++ b/src/ui/oled/oled.c @@ -65,7 +65,6 @@ #include "oled.h" -#include "oled_writer.h" #include #include #include @@ -73,19 +72,18 @@ #include #include #include +#include +#include #include #include #include -static bool _frame_buffer_updated = false; -static uint8_t _frame_buffer[128 * 8]; - static volatile bool _enabled = false; struct bb02_display { - void (*configure)(uint8_t*); + void (*configure)(void); void (*set_pixel)(int16_t x, int16_t y, uint8_t c); - void (*update)(void); + void (*present)(void); void (*off)(void); void (*mirror)(bool); }; @@ -93,17 +91,19 @@ struct bb02_display { static struct bb02_display bb02_display = { .configure = sh1107_configure, .set_pixel = sh1107_set_pixel, - .update = sh1107_update, + .present = sh1107_present, .off = sh1107_off, .mirror = sh1107_mirror, }; void oled_init(void) { + canvas_init(); + if (memory_get_screen_type() == MEMORY_SCREEN_TYPE_SSD1312) { bb02_display.configure = ssd1312_configure; bb02_display.set_pixel = ssd1312_set_pixel; - bb02_display.update = ssd1312_update; + bb02_display.present = ssd1312_present; bb02_display.off = ssd1312_off; bb02_display.mirror = ssd1312_mirror; } @@ -120,9 +120,9 @@ void oled_init(void) gpio_set_pin_level(PIN_OLED_RES, 1); delay_us(5); - oled_clear_buffer(); + oled_present(); - bb02_display.configure(_frame_buffer); + bb02_display.configure(); delay_ms(100); @@ -131,14 +131,9 @@ void oled_init(void) _enabled = true; } -void oled_send_buffer(void) -{ - bb02_display.update(); -} - -void oled_clear_buffer(void) +void oled_present(void) { - memset(_frame_buffer, 0, sizeof(_frame_buffer)); + bb02_display.present(); } void oled_mirror(bool mirror) @@ -149,7 +144,6 @@ void oled_mirror(bool mirror) void oled_set_pixel(int16_t x, int16_t y, uint8_t c) { bb02_display.set_pixel(x, y, c); - _frame_buffer_updated = true; } void oled_off(void) diff --git a/src/ui/oled/oled.h b/src/ui/oled/oled.h index c73faedab8..614ef8d045 100644 --- a/src/ui/oled/oled.h +++ b/src/ui/oled/oled.h @@ -71,19 +71,14 @@ void oled_init(void); /** - * Prints the frame buffer to the screen. - */ -void oled_send_buffer(void); - -/** - * Clears the frame buffer. + * Sets displayed frames rotated by 180 degrees. */ -void oled_clear_buffer(void); +void oled_mirror(bool mirror); /** - * Sets displayed frames rotated by 180 degrees. + * Transfer active canvas to the screen */ -void oled_mirror(bool mirror); +void oled_present(void); /** * Turn off oled @@ -91,8 +86,8 @@ void oled_mirror(bool mirror); void oled_off(void); /** - * Set a screen pixel. This fills the frame buffer - * prior to it being sent to the screen by oled_send_buffer(). + * Set a pixel on the "working" canvas arcoding to the screen requirements. The working and active + * canvases are flipped with canvas_commit(); */ void oled_set_pixel(int16_t x, int16_t y, uint8_t c); diff --git a/src/ui/oled/sh1107.c b/src/ui/oled/sh1107.c index 544e672cfe..fa4ed3e48d 100644 --- a/src/ui/oled/sh1107.c +++ b/src/ui/oled/sh1107.c @@ -14,6 +14,7 @@ #include "sh1107.h" #include "oled_writer.h" +#include // Specify the column address of display RAM 0-127 #define SH1107_CMD_SET_LOW_COL(column) (0x00 | ((column) & 0x0F)) @@ -73,11 +74,8 @@ // Double byte command (0x00 to 0x7F) #define SH1107_CMD_SET_DISPLAY_START_LINE 0xDC -static uint8_t* _frame_buffer; - -void sh1107_configure(uint8_t* buf) +void sh1107_configure(void) { - _frame_buffer = buf; oled_writer_write_cmd(SH1107_CMD_SET_DISPLAY_OFF); oled_writer_write_cmd_with_param(SH1107_CMD_SET_CONTRAST_CONTROL, 0xff); oled_writer_write_cmd(SH1107_CMD_SET_VERTICAL_ADDRESSING_MODE); @@ -92,7 +90,7 @@ void sh1107_configure(uint8_t* buf) oled_writer_write_cmd_with_param(SH1107_CMD_SET_VCOMH_DESELECT_LEVEL, 0x35); oled_writer_write_cmd_with_param(0xad, 0x8a); oled_writer_write_cmd(SH1107_CMD_ENTIRE_DISPLAY_AND_GDDRAM_ON); - sh1107_update(); + sh1107_present(); oled_writer_write_cmd(SH1107_CMD_SET_DISPLAY_ON); } @@ -105,21 +103,21 @@ void sh1107_set_pixel(int16_t x, int16_t y, uint8_t c) p = y * 16; p += x / 8; if (c) { - _frame_buffer[p] |= 1 << (x % 8); + canvas_working()[p] |= 1 << (x % 8); } else { - _frame_buffer[p] &= ~(1 << (x % 8)); + canvas_working()[p] &= ~(1 << (x % 8)); } } /* The SH1107 Segment/Common driver specifies that there are 16 pages per column * In total we should be writing 64*128 pixels. 8 bits per page, 16 pages per column and 64 * columns */ -void sh1107_update(void) +void sh1107_present(void) { for (size_t i = 0; i < 64; i++) { oled_writer_write_cmd(SH1107_CMD_SET_LOW_COL(i)); oled_writer_write_cmd(SH1107_CMD_SET_HIGH_COL(i)); - oled_writer_write_data(&_frame_buffer[i * 16], 16); + oled_writer_write_data(&canvas_active()[i * 16], 16); } } diff --git a/src/ui/oled/sh1107.h b/src/ui/oled/sh1107.h index f6f0c42ea7..a6768e971d 100644 --- a/src/ui/oled/sh1107.h +++ b/src/ui/oled/sh1107.h @@ -22,10 +22,10 @@ /* * The sh1107 driver will store this pointer and later use it for "set_pixel" and "update". */ -void sh1107_configure(uint8_t* buf); +void sh1107_configure(void); void sh1107_set_pixel(int16_t x, int16_t y, uint8_t c); -void sh1107_update(void); +void sh1107_present(void); void sh1107_mirror(bool mirror); void sh1107_off(void); diff --git a/src/ui/oled/ssd1312.c b/src/ui/oled/ssd1312.c index 3d11f3ef7a..6b6a21e872 100644 --- a/src/ui/oled/ssd1312.c +++ b/src/ui/oled/ssd1312.c @@ -15,6 +15,7 @@ #include "ssd1312.h" #include "oled_writer.h" #include +#include #define SSD1312_CMD_SET_LOW_COL(column) (0x00 | ((column) & 0x0F)) #define SSD1312_CMD_SET_HIGH_COL(column) (0x10 | (((column) >> 4) & 0x07)) @@ -78,11 +79,8 @@ // Double byte command #define SSD1312_CMD_SET_CHARGE_PUMP_SETTING 0x8D -static uint8_t* _frame_buffer; - -void ssd1312_configure(uint8_t* buf) +void ssd1312_configure(void) { - _frame_buffer = buf; oled_writer_write_cmd(SSD1312_CMD_SET_LOW_COL(0)); oled_writer_write_cmd(SSD1312_CMD_SET_HIGH_COL(0)); oled_writer_write_cmd(SSD1312_CMD_SET_DISPLAY_OFF); @@ -98,7 +96,7 @@ void ssd1312_configure(uint8_t* buf) oled_writer_write_cmd_with_param(SSD1312_CMD_SET_VCOMH_SELECT_LEVEL, 0x35); oled_writer_write_cmd_with_param(SSD1312_CMD_SET_IREF, 0x40); oled_writer_write_cmd(SSD1312_CMD_ENTIRE_DISPLAY_AND_GDDRAM_ON); - ssd1312_update(); + ssd1312_present(); oled_writer_write_cmd(SSD1312_CMD_SET_DISPLAY_ON); } @@ -110,12 +108,12 @@ void ssd1312_set_pixel(int16_t x, int16_t y, uint8_t c) p = (y / 8) * 128; p += x; if (c) { - _frame_buffer[p] |= 1 << (y % 8); + canvas_working()[p] |= 1 << (y % 8); } else { - _frame_buffer[p] &= ~(1 << (y % 8)); + canvas_working()[p] &= ~(1 << (y % 8)); } } -void ssd1312_update(void) +void ssd1312_present(void) { /* The SSD1312 has one page per 8 rows. One page is 128 bytes. Every byte is 8 rows */ for (size_t i = 0; i < 64 / 8; i++) { @@ -126,7 +124,7 @@ void ssd1312_update(void) // address to be correct if all bytes arrive at the screen. oled_writer_write_cmd(SSD1312_CMD_SET_LOW_COL(0)); oled_writer_write_cmd(SSD1312_CMD_SET_HIGH_COL(0)); - oled_writer_write_data(&_frame_buffer[i * 128], 128); + oled_writer_write_data(&canvas_active()[i * 128], 128); } } diff --git a/src/ui/oled/ssd1312.h b/src/ui/oled/ssd1312.h index 2a0318ef52..b73a1ea59c 100644 --- a/src/ui/oled/ssd1312.h +++ b/src/ui/oled/ssd1312.h @@ -21,10 +21,10 @@ /* * The ssd1312 driver will store this pointer and later use it for "set_pixel" and "update". */ -void ssd1312_configure(uint8_t* buf); +void ssd1312_configure(void); void ssd1312_set_pixel(int16_t x, int16_t y, uint8_t c); -void ssd1312_update(void); +void ssd1312_present(void); void ssd1312_mirror(bool mirror); void ssd1312_off(void); diff --git a/src/ui/screen_process.c b/src/ui/screen_process.c index 9e84a09443..cb90fa7b47 100644 --- a/src/ui/screen_process.c +++ b/src/ui/screen_process.c @@ -16,7 +16,9 @@ #include "screen_stack.h" #include #include +#include #include +#include #include #include #include @@ -25,11 +27,11 @@ static uint8_t screen_frame_cnt = 0; void ui_screen_render_component(component_t* component) { - screen_clear(); component->position.left = 0; component->position.top = 0; component->f->render(component); - UG_SendBuffer(); + canvas_commit(); + oled_present(); } static component_t* _get_waiting_screen(void) diff --git a/src/ui/ugui/ugui.c b/src/ui/ugui/ugui.c index a8f3ed140a..5b6e40c827 100644 --- a/src/ui/ugui/ugui.c +++ b/src/ui/ugui/ugui.c @@ -852,15 +852,3 @@ void UG_FontSetVSpace( UG_U16 s ) gui->char_v_space = s; } } - -void UG_SendBuffer(void) { -#ifndef TESTING - oled_send_buffer(); -#endif -} - -void UG_ClearBuffer(void) { -#ifndef TESTING - oled_clear_buffer(); -#endif -} diff --git a/src/ui/ugui/ugui.h b/src/ui/ugui/ugui.h index e64074c58d..24ef847ac2 100644 --- a/src/ui/ugui/ugui.h +++ b/src/ui/ugui/ugui.h @@ -135,8 +135,4 @@ UG_S16 UG_GetYDim( void ); void UG_FontSetHSpace( UG_U16 s ); void UG_FontSetVSpace( UG_U16 s ); -/* ssd1306.h wrapper */ -void UG_SendBuffer(void); -void UG_ClearBuffer(void); - #endif diff --git a/test/hardware-fakes/src/fake_oled.c b/test/hardware-fakes/src/fake_oled.c new file mode 100644 index 0000000000..dbbc1bfb55 --- /dev/null +++ b/test/hardware-fakes/src/fake_oled.c @@ -0,0 +1,15 @@ +// Copyright 2025 Shift Crypto AG +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +void oled_present(void) {} diff --git a/test/hardware-fakes/src/fake_screen.c b/test/hardware-fakes/src/fake_screen.c index 60e9f409cc..db368def72 100644 --- a/test/hardware-fakes/src/fake_screen.c +++ b/test/hardware-fakes/src/fake_screen.c @@ -43,5 +43,3 @@ bool screen_is_upside_down(void) { return false; } - -void screen_clear(void) {} From cb71d3d2ca9c9d02731ffb7731dbb6ad062dbbe7 Mon Sep 17 00:00:00 2001 From: Niklas Dusenlund Date: Thu, 18 Sep 2025 10:43:16 +0200 Subject: [PATCH 2/2] oled: Use DMA to update screen To run the UI at 100Hz triple buffering is needed. --- external/CMakeLists.txt | 2 + .../asf4-drivers/Config/hpl_dmac_config.h | 12 +- .../asf4-drivers/Config/hpl_sercom_config.h | 179 ++++++++ .../asf4-drivers/hal/include/hal_spi_m_dma.h | 257 +++++++++++ .../asf4-drivers/hal/include/hpl_spi_dma.h | 88 ++++ external/asf4-drivers/hal/src/hal_spi_m_dma.c | 183 ++++++++ external/asf4-drivers/hpl/dmac/hpl_dmac.c | 3 - external/asf4-drivers/hpl/sercom/hpl_sercom.c | 426 ++++++++++++++++++ external/asf4-drivers/hpl/spi/spi_lite.c | 124 ----- external/asf4-drivers/hpl/spi/spi_lite.h | 56 +-- src/bootloader/bootloader.c | 12 +- src/bootloader/startup.c | 2 +- src/firmware_main_loop.c | 5 +- src/platform/driver_init.c | 13 +- src/platform/driver_init.h | 5 + src/reset.c | 2 +- src/rust/bitbox02/src/lib.rs | 2 +- src/screen.c | 6 +- src/ui/canvas.c | 27 +- src/ui/components/confirm_gesture.c | 2 +- src/ui/components/status.c | 2 +- src/ui/oled/oled.c | 50 +- src/ui/oled/oled.h | 5 +- src/ui/oled/oled_writer.c | 60 ++- src/ui/oled/oled_writer.h | 14 +- src/ui/oled/sh1107.c | 70 +-- src/ui/oled/ssd1312.c | 75 ++- src/ui/screen_process.c | 45 +- src/ui/screen_process.h | 6 +- 29 files changed, 1393 insertions(+), 340 deletions(-) create mode 100644 external/asf4-drivers/hal/include/hal_spi_m_dma.h create mode 100644 external/asf4-drivers/hal/include/hpl_spi_dma.h create mode 100644 external/asf4-drivers/hal/src/hal_spi_m_dma.c diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 6caaa3d4ad..6d6624c2a1 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -66,6 +66,7 @@ if(CMAKE_CROSSCOMPILING) asf4-drivers/hal/src/hal_delay.c asf4-drivers/hal/src/hal_timer.c asf4-drivers/hal/src/hal_usb_device.c + asf4-drivers/hal/src/hal_spi_m_dma.c asf4-drivers/hal/src/hal_rand_sync.c asf4-drivers/hal/src/hal_flash.c asf4-drivers/hal/src/hal_pac.c @@ -75,6 +76,7 @@ if(CMAKE_CROSSCOMPILING) asf4-drivers/hal/src/hal_usart_async.c asf4-drivers/hal/utils/src/utils_ringbuffer.c asf4-drivers/hpl/gclk/hpl_gclk.c + asf4-drivers/hpl/dmac/hpl_dmac.c asf4-drivers/hpl/oscctrl/hpl_oscctrl.c asf4-drivers/hpl/mclk/hpl_mclk.c asf4-drivers/hpl/osc32kctrl/hpl_osc32kctrl.c diff --git a/external/asf4-drivers/Config/hpl_dmac_config.h b/external/asf4-drivers/Config/hpl_dmac_config.h index 90499fc27f..076626acd3 100644 --- a/external/asf4-drivers/Config/hpl_dmac_config.h +++ b/external/asf4-drivers/Config/hpl_dmac_config.h @@ -8,7 +8,7 @@ // Indicates whether dmac is enabled or not // dmac_enable #ifndef CONF_DMAC_ENABLE -#define CONF_DMAC_ENABLE 0 +#define CONF_DMAC_ENABLE 1 #endif // Priority Level 0 @@ -105,7 +105,7 @@ // Channel 0 settings // dmac_channel_0_settings #ifndef CONF_DMAC_CHANNEL_0_SETTINGS -#define CONF_DMAC_CHANNEL_0_SETTINGS 0 +#define CONF_DMAC_CHANNEL_0_SETTINGS 1 #endif // Channel Run in Standby @@ -122,7 +122,7 @@ // Defines the trigger action used for a transfer // dmac_trigact_0 #ifndef CONF_DMAC_TRIGACT_0 -#define CONF_DMAC_TRIGACT_0 0 +#define CONF_DMAC_TRIGACT_0 2 #endif // Trigger source @@ -214,7 +214,7 @@ // Defines the peripheral trigger which is source of the transfer // dmac_trifsrc_0 #ifndef CONF_DMAC_TRIGSRC_0 -#define CONF_DMAC_TRIGSRC_0 0 +#define CONF_DMAC_TRIGSRC_0 11 #endif // Channel Arbitration Level @@ -277,14 +277,14 @@ // Defines whether source or destination addresses are using the step size settings // dmac_stepsel_0 #ifndef CONF_DMAC_STEPSEL_0 -#define CONF_DMAC_STEPSEL_0 0 +#define CONF_DMAC_STEPSEL_0 1 #endif // Source Address Increment // Indicates whether the source address incrementation is enabled or not // dmac_srcinc_0 #ifndef CONF_DMAC_SRCINC_0 -#define CONF_DMAC_SRCINC_0 0 +#define CONF_DMAC_SRCINC_0 1 #endif // Destination Address Increment diff --git a/external/asf4-drivers/Config/hpl_sercom_config.h b/external/asf4-drivers/Config/hpl_sercom_config.h index 06cf5e641a..7f74bcb010 100644 --- a/external/asf4-drivers/Config/hpl_sercom_config.h +++ b/external/asf4-drivers/Config/hpl_sercom_config.h @@ -235,6 +235,185 @@ #define CONF_SERCOM_0_USART_RECEIVE_PULSE_LENGTH 0 #endif #endif +// +// Enable configuration of module +#ifndef CONF_SERCOM_3_SPI_ENABLE +#define CONF_SERCOM_3_SPI_ENABLE 1 +#endif + +// SPI DMA TX Channel <0-32> +// This defines DMA channel to be used +// spi_master_dma_tx_channel +#ifndef CONF_SERCOM_3_SPI_M_DMA_TX_CHANNEL +#define CONF_SERCOM_3_SPI_M_DMA_TX_CHANNEL 0 +#endif + +// SPI RX Channel Enable +// spi_master_rx_channel +#ifndef CONF_SERCOM_3_SPI_RX_CHANNEL +#define CONF_SERCOM_3_SPI_RX_CHANNEL 0 +#endif + +// DMA Channel <0-32> +// This defines DMA channel to be used +// spi_master_dma_rx_channel +#ifndef CONF_SERCOM_3_SPI_M_DMA_RX_CHANNEL +#define CONF_SERCOM_3_SPI_M_DMA_RX_CHANNEL 1 +#endif + +// + +// Set module in SPI Master mode +#ifndef CONF_SERCOM_3_SPI_MODE +#define CONF_SERCOM_3_SPI_MODE 0x03 +#endif + +// Basic Configuration + +// Receive buffer enable +// Enable receive buffer to receive data from slave (RXEN) +// spi_master_rx_enable +#ifndef CONF_SERCOM_3_SPI_RXEN +#define CONF_SERCOM_3_SPI_RXEN 0x0 +#endif + +// Character Size +// Bit size for all characters sent over the SPI bus (CHSIZE) +// <0x0=>8 bits +// <0x1=>9 bits +// spi_master_character_size +#ifndef CONF_SERCOM_3_SPI_CHSIZE +#define CONF_SERCOM_3_SPI_CHSIZE 0x0 +#endif +// Baud rate <1-18000000> +// The SPI data transfer rate +// spi_master_baud_rate +#ifndef CONF_SERCOM_3_SPI_BAUD +#define CONF_SERCOM_3_SPI_BAUD 3000000 +#endif + +// + +// Advanced Configuration +// spi_master_advanced +#ifndef CONF_SERCOM_3_SPI_ADVANCED +#define CONF_SERCOM_3_SPI_ADVANCED 1 +#endif + +// Dummy byte <0x00-0x1ff> +// spi_master_dummybyte +// Dummy byte used when reading data from the slave without sending any data +#ifndef CONF_SERCOM_3_SPI_DUMMYBYTE +#define CONF_SERCOM_3_SPI_DUMMYBYTE 0x1ff +#endif + +// Data Order +// <0=>MSB first +// <1=>LSB first +// I least significant or most significant bit is shifted out first (DORD) +// spi_master_arch_dord +#ifndef CONF_SERCOM_3_SPI_DORD +#define CONF_SERCOM_3_SPI_DORD 0x0 +#endif + +// Clock Polarity +// <0=>SCK is low when idle +// <1=>SCK is high when idle +// Determines if the leading edge is rising or falling with a corresponding opposite edge at the trailing edge. (CPOL) +// spi_master_arch_cpol +#ifndef CONF_SERCOM_3_SPI_CPOL +#define CONF_SERCOM_3_SPI_CPOL 0x0 +#endif + +// Clock Phase +// <0x0=>Sample input on leading edge +// <0x1=>Sample input on trailing edge +// Determines if input data is sampled on leading or trailing SCK edge. (CPHA) +// spi_master_arch_cpha +#ifndef CONF_SERCOM_3_SPI_CPHA +#define CONF_SERCOM_3_SPI_CPHA 0x0 +#endif + +// Immediate Buffer Overflow Notification +// Controls when OVF is asserted (IBON) +// <0x0=>In data stream +// <0x1=>On buffer overflow +// spi_master_arch_ibon +#ifndef CONF_SERCOM_3_SPI_IBON +#define CONF_SERCOM_3_SPI_IBON 0x0 +#endif + +// Run in stand-by +// Module stays active in stand-by sleep mode. (RUNSTDBY) +// spi_master_arch_runstdby +#ifndef CONF_SERCOM_3_SPI_RUNSTDBY +#define CONF_SERCOM_3_SPI_RUNSTDBY 0x0 +#endif + +// Debug Stop Mode +// Behavior of the baud-rate generator when CPU is halted by external debugger. (DBGSTOP) +// <0=>Keep running +// <1=>Halt +// spi_master_arch_dbgstop +#ifndef CONF_SERCOM_3_SPI_DBGSTOP +#define CONF_SERCOM_3_SPI_DBGSTOP 0 +#endif + +// + +// Address mode disabled in master mode +#ifndef CONF_SERCOM_3_SPI_AMODE_EN +#define CONF_SERCOM_3_SPI_AMODE_EN 0 +#endif + +#ifndef CONF_SERCOM_3_SPI_AMODE +#define CONF_SERCOM_3_SPI_AMODE 0 +#endif + +#ifndef CONF_SERCOM_3_SPI_ADDR +#define CONF_SERCOM_3_SPI_ADDR 0 +#endif + +#ifndef CONF_SERCOM_3_SPI_ADDRMASK +#define CONF_SERCOM_3_SPI_ADDRMASK 0 +#endif + +#ifndef CONF_SERCOM_3_SPI_SSDE +#define CONF_SERCOM_3_SPI_SSDE 0 +#endif + +#ifndef CONF_SERCOM_3_SPI_MSSEN +#define CONF_SERCOM_3_SPI_MSSEN 0x0 +#endif + +#ifndef CONF_SERCOM_3_SPI_PLOADEN +#define CONF_SERCOM_3_SPI_PLOADEN 0 +#endif + +// Receive Data Pinout +// <0x0=>PAD[0] +// <0x1=>PAD[1] +// <0x2=>PAD[2] +// <0x3=>PAD[3] +// spi_master_rxpo +#ifndef CONF_SERCOM_3_SPI_RXPO +#define CONF_SERCOM_3_SPI_RXPO 2 +#endif + +// Transmit Data Pinout +// <0x0=>PAD[0,1]_DO_SCK +// <0x1=>PAD[2,3]_DO_SCK +// <0x2=>PAD[3,1]_DO_SCK +// <0x3=>PAD[0,3]_DO_SCK +// spi_master_txpo +#ifndef CONF_SERCOM_3_SPI_TXPO +#define CONF_SERCOM_3_SPI_TXPO 0 +#endif + +// Calculate baud register value from requested baudrate value +#ifndef CONF_SERCOM_3_SPI_BAUD_RATE +#define CONF_SERCOM_3_SPI_BAUD_RATE ((float)CONF_GCLK_SERCOM3_CORE_FREQUENCY / (float)(2 * CONF_SERCOM_3_SPI_BAUD)) - 1 +#endif #include diff --git a/external/asf4-drivers/hal/include/hal_spi_m_dma.h b/external/asf4-drivers/hal/include/hal_spi_m_dma.h new file mode 100644 index 0000000000..24e7b11e5a --- /dev/null +++ b/external/asf4-drivers/hal/include/hal_spi_m_dma.h @@ -0,0 +1,257 @@ +/** + * \file + * + * \brief SPI DMA related functionality declaration. + * + * Copyright (c) 2016-2018 Microchip Technology Inc. and its subsidiaries. + * + * \asf_license_start + * + * \page License + * + * Subject to your compliance with these terms, you may use Microchip + * software and any derivatives exclusively with Microchip products. + * It is your responsibility to comply with third party license terms applicable + * to your use of third party software (including open source software) that + * may accompany Microchip software. + * + * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, + * WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, + * INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, + * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE + * LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL + * LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE + * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE + * POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE FULLEST EXTENT + * ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY + * RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY, + * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE. + * + * \asf_license_stop + * + */ + +#ifndef _HAL_SPI_M_DMA_H_INCLUDED +#define _HAL_SPI_M_DMA_H_INCLUDED + +#include +#include + +/** + * \addtogroup doc_driver_hal_spi_master_dma + * + * @{ + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Forward declaration of spi_descriptor. */ +struct spi_m_dma_descriptor; + +/** The callback types */ +enum spi_m_dma_cb_type { + /** Callback type for DMA transfer buffer done */ + SPI_M_DMA_CB_TX_DONE, + /** Callback type for DMA receive buffer done */ + SPI_M_DMA_CB_RX_DONE, + /** Callback type for DMA errors */ + SPI_M_DMA_CB_ERROR, + SPI_M_DMA_CB_N +}; + +/** + * \brief SPI Master DMA callback type + */ +typedef void (*spi_m_dma_cb_t)(struct _dma_resource *resource); + +/** \brief SPI HAL driver struct for DMA access + */ +struct spi_m_dma_descriptor { + struct _spi_m_dma_hpl_interface *func; + /** Pointer to SPI device instance */ + struct _spi_m_dma_dev dev; + /** I/O read/write */ + struct io_descriptor io; + /** DMA resource */ + struct _dma_resource *resource; +}; + +/** \brief Set the SPI HAL instance function pointer for HPL APIs. + * + * Set SPI HAL instance function pointer for HPL APIs. + * + * \param[in] spi Pointer to the HAL SPI instance. + * \param[in] func Pointer to the HPL api structure. + * + */ +void spi_m_dma_set_func_ptr(struct spi_m_dma_descriptor *spi, void *const func); + +/** \brief Initialize the SPI HAL instance and hardware for DMA mode + * + * Initialize SPI HAL with dma mode. + * + * \param[in] spi Pointer to the HAL SPI instance. + * \param[in] hw Pointer to the hardware base. + * + * \return Operation status. + * \retval ERR_NONE Success. + * \retval ERR_INVALID_DATA Error, initialized. + */ +int32_t spi_m_dma_init(struct spi_m_dma_descriptor *spi, void *const hw); + +/** \brief Deinitialize the SPI HAL instance + * + * Abort transfer, disable and reset SPI, de-init software. + * + * \param[in] spi Pointer to the HAL SPI instance. + * + * \return Operation status. + * \retval ERR_NONE Success. + * \retval <0 Error code. + */ +void spi_m_dma_deinit(struct spi_m_dma_descriptor *spi); + +/** \brief Enable SPI + * + * \param[in] spi Pointer to the HAL SPI instance. + * + * \return Operation status. + * \retval ERR_NONE Success. + * \retval <0 Error code. + */ +void spi_m_dma_enable(struct spi_m_dma_descriptor *spi); + +/** \brief Disable SPI + * + * \param[in] spi Pointer to the HAL SPI instance. + * + * \return Operation status. + * \retval ERR_NONE Success. + * \retval <0 Error code. + */ +void spi_m_dma_disable(struct spi_m_dma_descriptor *spi); + +/** \brief Set SPI baudrate + * + * Works if SPI is initialized as master. + * In the function a sanity check is used to confirm it's called in the correct mode. + * + * \param[in] spi Pointer to the HAL SPI instance. + * \param[in] baud_val The target baudrate value + * (See "baudrate calculation" for calculating the value). + * + * \return Operation status. + * \retval ERR_NONE Success. + * \retval ERR_BUSY Busy. + * + * note: This api should be used to write the baudrate register with the given baud_val + * paramter, the user has to calculate the baud_val for required baud rate and pass it as + * argument(baud_val) to this api + */ +int32_t spi_m_dma_set_baudrate(struct spi_m_dma_descriptor *spi, const uint32_t baud_val); + +/** \brief Set SPI mode + * + * Set SPI transfer mode (\ref spi_transfer_mode), + * which controls clock polarity and clock phase: + * - Mode 0: leading edge is rising edge, data sample on leading edge. + * - Mode 1: leading edge is rising edge, data sample on trailing edge. + * - Mode 2: leading edge is falling edge, data sample on leading edge. + * - Mode 3: leading edge is falling edge, data sample on trailing edge. + * + * \param[in] spi Pointer to the HAL SPI instance. + * \param[in] mode The mode (\ref spi_transfer_mode). + * + * \return Operation status. + * \retval ERR_NONE Success. + * \retval ERR_BUSY Busy, CS activated. + */ +int32_t spi_m_dma_set_mode(struct spi_m_dma_descriptor *spi, const enum spi_transfer_mode mode); + +/** \brief Set the SPI transfer character size in number of bits + * + * The character size (\ref spi_char_size) influence the way the data is + * sent/received. + * For char size <= 8-bit, data is stored byte by byte. + * For char size between 9-bit ~ 16-bit, data is stored in 2-byte length. + * Note that the default and recommended char size is 8-bit since it's + * supported by all system. + * + * \param[in] spi Pointer to the HAL SPI instance. + * \param[in] char_size The char size (\ref spi_char_size). + * + * \return Operation status. + * \retval ERR_NONE Success. + * \retval ERR_BUSY Busy, CS activated. + * \retval ERR_INVALID_ARG The char size is not supported. + */ +int32_t spi_m_dma_set_char_size(struct spi_m_dma_descriptor *spi, const enum spi_char_size char_size); + +/** \brief Set SPI transfer data order + * + * \param[in] spi Pointer to the HAL SPI instance. + * \param[in] dord The data order: send LSB/MSB first. + * + * \return Operation status. + * \retval ERR_NONE Success. + * \retval ERR_BUSY Busy, CS activated. + * \retval ERR_INVALID The data order is not supported. + */ +int32_t spi_m_dma_set_data_order(struct spi_m_dma_descriptor *spi, const enum spi_data_order dord); + +/** \brief Perform the SPI data transfer (TX and RX) with the DMA + * + * \param[in] spi Pointer to the HAL SPI instance. + * \param[in] txbuf Pointer to the transfer information. + * \param[out] rxbuf Pointer to the receiver information. + * \param[in] length SPI transfer data length. + * + * \return Operation status. + * \retval ERR_NONE Success. + * \retval ERR_BUSY Busy. + */ +int32_t spi_m_dma_transfer(struct spi_m_dma_descriptor *spi, uint8_t const *txbuf, uint8_t *const rxbuf, + const uint16_t length); + +/** \brief Register a function as an SPI transfer completion callback + * + * Register a callback function specified by its \c type. + * - SPI_CB_COMPLETE: set the function that will be called on the SPI transfer + * completion including deactivating the CS. + * - SPI_CB_XFER: set the function that will be called on the SPI buffer transfer + * completion. + * Register a NULL function to not use the callback. + * + * \param[in] spi Pointer to the HAL SPI instance. + * \param[in] type Callback type (\ref spi_m_dma_cb_type). + * \param[in] func Pointer to callback function. + */ +void spi_m_dma_register_callback(struct spi_m_dma_descriptor *spi, const enum spi_m_dma_cb_type type, + spi_m_dma_cb_t func); + +/** + * \brief Return I/O descriptor for this SPI instance + * + * This function will return an I/O instance for this SPI driver instance + * + * \param[in] spi An SPI master descriptor, which is used to communicate through + * SPI + * \param[in, out] io A pointer to an I/O descriptor pointer type + * + * \retval ERR_NONE + */ +int32_t spi_m_dma_get_io_descriptor(struct spi_m_dma_descriptor *const spi, struct io_descriptor **io); + +/** \brief Retrieve the current driver version + * + * \return Current driver version. + */ +uint32_t spi_m_dma_get_version(void); + +#ifdef __cplusplus +} +#endif +/**@}*/ +#endif /* ifndef _HAL_SPI_M_DMA_H_INCLUDED */ diff --git a/external/asf4-drivers/hal/include/hpl_spi_dma.h b/external/asf4-drivers/hal/include/hpl_spi_dma.h new file mode 100644 index 0000000000..04a3015807 --- /dev/null +++ b/external/asf4-drivers/hal/include/hpl_spi_dma.h @@ -0,0 +1,88 @@ +/** + * \file + * + * \brief Common SPI DMA related functionality declaration. + * + * Copyright (c) 2016-2018 Microchip Technology Inc. and its subsidiaries. + * + * \asf_license_start + * + * \page License + * + * Subject to your compliance with these terms, you may use Microchip + * software and any derivatives exclusively with Microchip products. + * It is your responsibility to comply with third party license terms applicable + * to your use of third party software (including open source software) that + * may accompany Microchip software. + * + * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, + * WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, + * INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, + * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE + * LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL + * LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE + * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE + * POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE FULLEST EXTENT + * ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY + * RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY, + * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE. + * + * \asf_license_stop + * + */ + +#ifndef _HPL_SPI_DMA_H_INCLUDED +#define _HPL_SPI_DMA_H_INCLUDED + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** The callback types */ +enum _spi_dma_dev_cb_type { + /** Callback type for DMA transmit. */ + SPI_DEV_CB_DMA_TX, + /** Callback type for DMA receive. */ + SPI_DEV_CB_DMA_RX, + /** Callback type for DMA error. */ + SPI_DEV_CB_DMA_ERROR, + /** Number of callbacks. */ + SPI_DEV_CB_DMA_N +}; + +struct _spi_dma_dev; + +/** + * \brief The prototype for callback on SPI DMA. + */ +typedef void (*_spi_dma_cb_t)(struct _dma_resource *resource); + +/** + * \brief The callbacks offered by SPI driver + */ +struct _spi_dma_dev_callbacks { + _spi_dma_cb_t tx; + _spi_dma_cb_t rx; + _spi_dma_cb_t error; +}; + +/** SPI driver to support DMA HAL */ +struct _spi_dma_dev { + /** Pointer to the hardware base or private data for special device. */ + void *prvt; + /** Pointer to callback functions */ + struct _spi_dma_dev_callbacks callbacks; + /** IRQ instance for SPI device. */ + struct _irq_descriptor irq; + /** DMA resource */ + struct _dma_resource *resource; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* ifndef _HPL_SPI_DMA_H_INCLUDED */ diff --git a/external/asf4-drivers/hal/src/hal_spi_m_dma.c b/external/asf4-drivers/hal/src/hal_spi_m_dma.c new file mode 100644 index 0000000000..976d0f886c --- /dev/null +++ b/external/asf4-drivers/hal/src/hal_spi_m_dma.c @@ -0,0 +1,183 @@ +/** + * \file + * + * \brief I/O SPI DMA related functionality implementation. + * + * Copyright (c) 2016-2018 Microchip Technology Inc. and its subsidiaries. + * + * \asf_license_start + * + * \page License + * + * Subject to your compliance with these terms, you may use Microchip + * software and any derivatives exclusively with Microchip products. + * It is your responsibility to comply with third party license terms applicable + * to your use of third party software (including open source software) that + * may accompany Microchip software. + * + * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, + * WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, + * INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, + * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE + * LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL + * LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE + * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE + * POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE FULLEST EXTENT + * ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY + * RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY, + * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE. + * + * \asf_license_stop + * + */ + +#include "hal_atomic.h" +#include "hal_spi_m_dma.h" +#include +#include + +/** + * \brief Driver version + */ +#define SPI_DRIVER_VERSION 0x00000001u + +static int32_t _spi_m_dma_io_write(struct io_descriptor *const io, const uint8_t *const buf, const uint16_t length); +static int32_t _spi_m_dma_io_read(struct io_descriptor *const io, uint8_t *const buf, const uint16_t length); + +/** + * \brief Initialize the SPI HAL instance function pointer for HPL APIs. + */ +void spi_m_dma_set_func_ptr(struct spi_m_dma_descriptor *spi, void *const func) +{ + ASSERT(spi); + spi->func = (struct _spi_m_dma_hpl_interface *)func; +} + +int32_t spi_m_dma_init(struct spi_m_dma_descriptor *spi, void *const hw) +{ + int32_t rc = 0; + ASSERT(spi && hw); + spi->dev.prvt = (void *)hw; + rc = _spi_m_dma_init(&spi->dev, hw); + + if (rc) { + return rc; + } + + spi->io.read = _spi_m_dma_io_read; + spi->io.write = _spi_m_dma_io_write; + + return ERR_NONE; +} + +void spi_m_dma_deinit(struct spi_m_dma_descriptor *spi) +{ + ASSERT(spi); + _spi_m_dma_deinit(&spi->dev); +} + +void spi_m_dma_enable(struct spi_m_dma_descriptor *spi) +{ + ASSERT(spi); + _spi_m_dma_enable(&spi->dev); +} + +void spi_m_dma_disable(struct spi_m_dma_descriptor *spi) +{ + ASSERT(spi); + _spi_m_dma_disable(&spi->dev); +} + +int32_t spi_m_dma_set_baudrate(struct spi_m_dma_descriptor *spi, const uint32_t baud_val) +{ + ASSERT(spi); + return _spi_m_dma_set_baudrate(&spi->dev, baud_val); +} + +int32_t spi_m_dma_set_mode(struct spi_m_dma_descriptor *spi, const enum spi_transfer_mode mode) +{ + ASSERT(spi); + return _spi_m_dma_set_mode(&spi->dev, mode); +} + +int32_t spi_m_dma_set_char_size(struct spi_m_dma_descriptor *spi, const enum spi_char_size char_size) +{ + ASSERT(spi); + return _spi_m_dma_set_char_size(&spi->dev, char_size); +} + +int32_t spi_m_dma_set_data_order(struct spi_m_dma_descriptor *spi, const enum spi_data_order dord) +{ + ASSERT(spi); + return _spi_m_dma_set_data_order(&spi->dev, dord); +} + +/** \brief Do SPI read in background + * + * It never blocks and return quickly, user check status or set callback to + * know when data is ready to process. + * + * \param[in, out] spi Pointer to the HAL SPI instance. + * \param[out] p_buf Pointer to the buffer to store read data. + * \param[in] size Size of the data in number of characters. + * \return ERR_NONE on success, or an error code on failure. + * \retval ERR_NONE Success, transfer started. + * \retval ERR_BUSY Busy. + */ +static int32_t _spi_m_dma_io_read(struct io_descriptor *io, uint8_t *const buf, const uint16_t length) +{ + ASSERT(io); + + struct spi_m_dma_descriptor *spi = CONTAINER_OF(io, struct spi_m_dma_descriptor, io); + return _spi_m_dma_transfer(&spi->dev, NULL, buf, length); +} + +/** \brief Do SPI data write in background + * + * The data read back is discarded. + * + * It never blocks and return quickly, user check status or set callback to + * know when data is sent. + * + * \param[in, out] spi Pointer to the HAL SPI instance. + * \param[in] p_buf Pointer to the buffer to store data to write. + * \param[in] size Size of the data in number of characters. + * + * \return ERR_NONE on success, or an error code on failure. + * \retval ERR_NONE Success, transfer started. + * \retval ERR_BUSY Busy. + */ +static int32_t _spi_m_dma_io_write(struct io_descriptor *io, const uint8_t *const buf, const uint16_t length) +{ + ASSERT(io); + + struct spi_m_dma_descriptor *spi = CONTAINER_OF(io, struct spi_m_dma_descriptor, io); + return _spi_m_dma_transfer(&spi->dev, buf, NULL, length); +} + +int32_t spi_m_dma_transfer(struct spi_m_dma_descriptor *spi, uint8_t const *txbuf, uint8_t *const rxbuf, + const uint16_t length) +{ + ASSERT(spi); + return _spi_m_dma_transfer(&spi->dev, txbuf, rxbuf, length); +} + +void spi_m_dma_register_callback(struct spi_m_dma_descriptor *spi, const enum spi_m_dma_cb_type type, + spi_m_dma_cb_t func) +{ + ASSERT(spi); + _spi_m_dma_register_callback(&spi->dev, (enum _spi_dma_dev_cb_type)type, func); +} + +int32_t spi_m_dma_get_io_descriptor(struct spi_m_dma_descriptor *const spi, struct io_descriptor **io) +{ + ASSERT(spi && io); + *io = &spi->io; + + return 0; +} + +uint32_t spi_m_dma_get_version(void) +{ + return SPI_DRIVER_VERSION; +} diff --git a/external/asf4-drivers/hpl/dmac/hpl_dmac.c b/external/asf4-drivers/hpl/dmac/hpl_dmac.c index c7b03b0095..7dddc9d1e3 100644 --- a/external/asf4-drivers/hpl/dmac/hpl_dmac.c +++ b/external/asf4-drivers/hpl/dmac/hpl_dmac.c @@ -37,7 +37,6 @@ #include #include -#if CONF_DMAC_ENABLE /* Section containing first descriptors for all DMAC channels */ COMPILER_ALIGNED(16) DmacDescriptor _descriptor_section[DMAC_CH_NUM]; @@ -259,5 +258,3 @@ void DMAC_4_Handler(void) { _dmac_handler(); } - -#endif /* CONF_DMAC_ENABLE */ diff --git a/external/asf4-drivers/hpl/sercom/hpl_sercom.c b/external/asf4-drivers/hpl/sercom/hpl_sercom.c index 47f3d37849..754a432866 100644 --- a/external/asf4-drivers/hpl/sercom/hpl_sercom.c +++ b/external/asf4-drivers/hpl/sercom/hpl_sercom.c @@ -31,6 +31,7 @@ * \asf_license_stop * */ +#include #include #include #include @@ -3026,3 +3027,428 @@ void _spi_s_async_set_irq_state(struct _spi_async_dev *const device, const enum { _spi_m_async_set_irq_state(device, type, state); } +#ifndef CONF_SERCOM_0_SPI_M_DMA_TX_CHANNEL +#define CONF_SERCOM_0_SPI_M_DMA_TX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_0_SPI_M_DMA_RX_CHANNEL +#define CONF_SERCOM_0_SPI_M_DMA_RX_CHANNEL 1 +#endif +#ifndef CONF_SERCOM_1_SPI_M_DMA_TX_CHANNEL +#define CONF_SERCOM_1_SPI_M_DMA_TX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_1_SPI_M_DMA_RX_CHANNEL +#define CONF_SERCOM_1_SPI_M_DMA_RX_CHANNEL 1 +#endif +#ifndef CONF_SERCOM_2_SPI_M_DMA_TX_CHANNEL +#define CONF_SERCOM_2_SPI_M_DMA_TX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_2_SPI_M_DMA_RX_CHANNEL +#define CONF_SERCOM_2_SPI_M_DMA_RX_CHANNEL 1 +#endif +#ifndef CONF_SERCOM_3_SPI_M_DMA_TX_CHANNEL +#define CONF_SERCOM_3_SPI_M_DMA_TX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_3_SPI_M_DMA_RX_CHANNEL +#define CONF_SERCOM_3_SPI_M_DMA_RX_CHANNEL 1 +#endif +#ifndef CONF_SERCOM_4_SPI_M_DMA_TX_CHANNEL +#define CONF_SERCOM_4_SPI_M_DMA_TX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_4_SPI_M_DMA_RX_CHANNEL +#define CONF_SERCOM_4_SPI_M_DMA_RX_CHANNEL 1 +#endif +#ifndef CONF_SERCOM_5_SPI_M_DMA_TX_CHANNEL +#define CONF_SERCOM_5_SPI_M_DMA_TX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_5_SPI_M_DMA_RX_CHANNEL +#define CONF_SERCOM_5_SPI_M_DMA_RX_CHANNEL 1 +#endif +#ifndef CONF_SERCOM_6_SPI_M_DMA_TX_CHANNEL +#define CONF_SERCOM_6_SPI_M_DMA_TX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_6_SPI_M_DMA_RX_CHANNEL +#define CONF_SERCOM_6_SPI_M_DMA_RX_CHANNEL 1 +#endif +#ifndef CONF_SERCOM_7_SPI_M_DMA_TX_CHANNEL +#define CONF_SERCOM_7_SPI_M_DMA_TX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_7_SPI_M_DMA_RX_CHANNEL +#define CONF_SERCOM_7_SPI_M_DMA_RX_CHANNEL 1 +#endif + +#ifndef CONF_SERCOM_0_SPI_RX_CHANNEL +#define CONF_SERCOM_0_SPI_RX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_1_SPI_RX_CHANNEL +#define CONF_SERCOM_1_SPI_RX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_2_SPI_RX_CHANNEL +#define CONF_SERCOM_2_SPI_RX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_3_SPI_RX_CHANNEL +#define CONF_SERCOM_3_SPI_RX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_4_SPI_RX_CHANNEL +#define CONF_SERCOM_4_SPI_RX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_5_SPI_RX_CHANNEL +#define CONF_SERCOM_5_SPI_RX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_6_SPI_RX_CHANNEL +#define CONF_SERCOM_6_SPI_RX_CHANNEL 0 +#endif +#ifndef CONF_SERCOM_7_SPI_RX_CHANNEL +#define CONF_SERCOM_7_SPI_RX_CHANNEL 0 +#endif + +/** \internal Enable SERCOM SPI RX + * + * \param[in] hw Pointer to the hardware register base. + * + * \return Enabling status + */ +static int32_t _spi_sync_rx_enable(void *const hw) +{ + if (hri_sercomspi_is_syncing(hw, SERCOM_SPI_SYNCBUSY_CTRLB)) { + return ERR_BUSY; + } + + hri_sercomspi_set_CTRLB_RXEN_bit(hw); + + return ERR_NONE; +} + +/** \internal Disable SERCOM SPI RX + * + * \param[in] hw Pointer to the hardware register base. + * + * \return Disabling status + */ +static int32_t _spi_sync_rx_disable(void *const hw) +{ + if (hri_sercomspi_is_syncing(hw, SERCOM_SPI_SYNCBUSY_CTRLB)) { + return ERR_BUSY; + } + hri_sercomspi_clear_CTRLB_RXEN_bit(hw); + + return ERR_NONE; +} + +static int32_t _spi_m_dma_rx_enable(struct _spi_m_dma_dev *dev) +{ + ASSERT(dev && dev->prvt); + + return _spi_sync_rx_enable(dev->prvt); +} + +static int32_t _spi_m_dma_rx_disable(struct _spi_m_dma_dev *dev) +{ + ASSERT(dev && dev->prvt); + + return _spi_sync_rx_disable(dev->prvt); +} + +/** + * \brief Get the spi source address for DMA + * \param[in] dev Pointer to the SPI device instance + * + * \return The spi source address + */ +static uint32_t _spi_m_get_source_for_dma(void *const hw) +{ + return (uint32_t) & (((Sercom *)hw)->SPI.DATA); +} + +/** + * \brief Get the spi destination address for DMA + * \param[in] dev Pointer to the SPI device instance + * + * \return The spi destination address + */ +static uint32_t _spi_m_get_destination_for_dma(void *const hw) +{ + return (uint32_t) & (((Sercom *)hw)->SPI.DATA); +} + +/** + * \brief Return the SPI TX DMA channel index + * \param[in] hw_addr The hardware register base address + * + * \return SPI TX DMA channel index. + */ +static uint8_t _spi_get_tx_dma_channel(const void *const hw) +{ + uint8_t index = _sercom_get_hardware_index(hw); + + switch (index) { + case 0: + return CONF_SERCOM_0_SPI_M_DMA_TX_CHANNEL; + case 1: + return CONF_SERCOM_1_SPI_M_DMA_TX_CHANNEL; + case 2: + return CONF_SERCOM_2_SPI_M_DMA_TX_CHANNEL; + case 3: + return CONF_SERCOM_3_SPI_M_DMA_TX_CHANNEL; + case 4: + return CONF_SERCOM_4_SPI_M_DMA_TX_CHANNEL; + case 5: + return CONF_SERCOM_5_SPI_M_DMA_TX_CHANNEL; + case 6: + return CONF_SERCOM_6_SPI_M_DMA_TX_CHANNEL; + case 7: + return CONF_SERCOM_7_SPI_M_DMA_TX_CHANNEL; + default: + return CONF_SERCOM_0_SPI_M_DMA_TX_CHANNEL; + } +} + +/** + * \brief Return whether SPI RX DMA channel is enabled or not + * \param[in] hw_addr The hardware register base address + * + * \return one if enabled. + */ +static uint8_t _spi_is_rx_dma_channel_enabled(const void *const hw) +{ + uint8_t index = _sercom_get_hardware_index(hw); + + switch (index) { + case 0: + return CONF_SERCOM_0_SPI_RX_CHANNEL; + case 1: + return CONF_SERCOM_1_SPI_RX_CHANNEL; + case 2: + return CONF_SERCOM_2_SPI_RX_CHANNEL; + case 3: + return CONF_SERCOM_3_SPI_RX_CHANNEL; + case 4: + return CONF_SERCOM_4_SPI_RX_CHANNEL; + case 5: + return CONF_SERCOM_5_SPI_RX_CHANNEL; + case 6: + return CONF_SERCOM_6_SPI_RX_CHANNEL; + case 7: + return CONF_SERCOM_7_SPI_RX_CHANNEL; + default: + return false; + } +} + +/** + * \brief Return the SPI RX DMA channel index + * \param[in] hw_addr The hardware register base address + * + * \return SPI RX DMA channel index. + */ +static uint8_t _spi_get_rx_dma_channel(const void *const hw) +{ + uint8_t index = _sercom_get_hardware_index(hw); + + switch (index) { + case 0: + return CONF_SERCOM_0_SPI_M_DMA_RX_CHANNEL; + case 1: + return CONF_SERCOM_1_SPI_M_DMA_RX_CHANNEL; + case 2: + return CONF_SERCOM_2_SPI_M_DMA_RX_CHANNEL; + case 3: + return CONF_SERCOM_3_SPI_M_DMA_RX_CHANNEL; + case 4: + return CONF_SERCOM_4_SPI_M_DMA_RX_CHANNEL; + case 5: + return CONF_SERCOM_5_SPI_M_DMA_RX_CHANNEL; + case 6: + return CONF_SERCOM_6_SPI_M_DMA_RX_CHANNEL; + case 7: + return CONF_SERCOM_7_SPI_M_DMA_RX_CHANNEL; + default: + return CONF_SERCOM_0_SPI_M_DMA_TX_CHANNEL; + } +} + +/** + * \brief Callback for RX + * \param[in, out] dev Pointer to the DMA resource. + */ +static void _spi_dma_rx_complete(struct _dma_resource *resource) +{ + struct _spi_m_dma_dev *dev = (struct _spi_m_dma_dev *)resource->back; + + if (dev->callbacks.rx) { + dev->callbacks.rx(resource); + } +} + +/** + * \brief Callback for TX + * \param[in, out] dev Pointer to the DMA resource. + */ +static void _spi_dma_tx_complete(struct _dma_resource *resource) +{ + struct _spi_m_dma_dev *dev = (struct _spi_m_dma_dev *)resource->back; + + if (dev->callbacks.tx) { + dev->callbacks.tx(resource); + } +} + +/** + * \brief Callback for ERROR + * \param[in, out] dev Pointer to the DMA resource. + */ +static void _spi_dma_error_occured(struct _dma_resource *resource) +{ + struct _spi_m_dma_dev *dev = (struct _spi_m_dma_dev *)resource->back; + + if (dev->callbacks.error) { + dev->callbacks.error(resource); + } +} + +int32_t _spi_m_dma_init(struct _spi_m_dma_dev *dev, void *const hw) +{ + const struct sercomspi_regs_cfg *regs = _spi_get_regs((uint32_t)hw); + + ASSERT(dev && hw); + + if (regs == NULL) { + return ERR_INVALID_ARG; + } + + if (!hri_sercomspi_is_syncing(hw, SERCOM_SPI_SYNCBUSY_SWRST)) { + uint32_t mode = regs->ctrla & SERCOM_SPI_CTRLA_MODE_Msk; + if (hri_sercomspi_get_CTRLA_reg(hw, SERCOM_SPI_CTRLA_ENABLE)) { + hri_sercomspi_clear_CTRLA_ENABLE_bit(hw); + hri_sercomspi_wait_for_sync(hw, SERCOM_SPI_SYNCBUSY_ENABLE); + } + hri_sercomspi_write_CTRLA_reg(hw, SERCOM_SPI_CTRLA_SWRST | mode); + } + hri_sercomspi_wait_for_sync(hw, SERCOM_SPI_SYNCBUSY_SWRST); + + dev->prvt = hw; + + _spi_load_regs_master(hw, regs); + + /* If enabled, initialize DMA rx channel */ + if (_spi_is_rx_dma_channel_enabled(hw)) { + _dma_get_channel_resource(&dev->resource, _spi_get_rx_dma_channel(hw)); + dev->resource->back = dev; + dev->resource->dma_cb.transfer_done = _spi_dma_rx_complete; + dev->resource->dma_cb.error = _spi_dma_error_occured; + } + /* Initialize DMA tx channel */ + _dma_get_channel_resource(&dev->resource, _spi_get_tx_dma_channel(hw)); + dev->resource->back = dev; + dev->resource->dma_cb.transfer_done = _spi_dma_tx_complete; + dev->resource->dma_cb.error = _spi_dma_error_occured; + + return ERR_NONE; +} + +int32_t _spi_m_dma_deinit(struct _spi_m_dma_dev *dev) +{ + return _spi_deinit(dev->prvt); +} + +int32_t _spi_m_dma_enable(struct _spi_m_dma_dev *dev) +{ + ASSERT(dev && dev->prvt); + + return _spi_sync_enable(dev->prvt); +} + +int32_t _spi_m_dma_disable(struct _spi_m_dma_dev *dev) +{ + ASSERT(dev && dev->prvt); + + return _spi_sync_disable(dev->prvt); +} + +int32_t _spi_m_dma_set_mode(struct _spi_m_dma_dev *dev, const enum spi_transfer_mode mode) +{ + ASSERT(dev && dev->prvt); + + return _spi_set_mode(dev->prvt, mode); +} + +int32_t _spi_m_dma_set_baudrate(struct _spi_m_dma_dev *dev, const uint32_t baud_val) +{ + ASSERT(dev && dev->prvt); + + return _spi_set_baudrate(dev->prvt, baud_val); +} + +int32_t _spi_m_dma_set_data_order(struct _spi_m_dma_dev *dev, const enum spi_data_order dord) +{ + ASSERT(dev && dev->prvt); + + return _spi_set_data_order(dev->prvt, dord); +} + +int32_t _spi_m_dma_set_char_size(struct _spi_m_dma_dev *dev, const enum spi_char_size char_size) +{ + uint8_t size; + + ASSERT(dev && dev->prvt); + + _spi_set_char_size(dev->prvt, char_size, &size); + + return size; +} + +void _spi_m_dma_register_callback(struct _spi_m_dma_dev *dev, enum _spi_dma_dev_cb_type type, _spi_dma_cb_t func) +{ + switch (type) { + case SPI_DEV_CB_DMA_TX: + dev->callbacks.tx = func; + _dma_set_irq_state(_spi_get_tx_dma_channel(dev->prvt), DMA_TRANSFER_COMPLETE_CB, func != NULL); + break; + case SPI_DEV_CB_DMA_RX: + dev->callbacks.rx = func; + _dma_set_irq_state(_spi_get_rx_dma_channel(dev->prvt), DMA_TRANSFER_COMPLETE_CB, func != NULL); + break; + case SPI_DEV_CB_DMA_ERROR: + dev->callbacks.error = func; + _dma_set_irq_state(_spi_get_rx_dma_channel(dev->prvt), DMA_TRANSFER_ERROR_CB, func != NULL); + _dma_set_irq_state(_spi_get_tx_dma_channel(dev->prvt), DMA_TRANSFER_ERROR_CB, func != NULL); + break; + case SPI_DEV_CB_DMA_N: + break; + } +} + +int32_t _spi_m_dma_transfer(struct _spi_m_dma_dev *dev, uint8_t const *txbuf, uint8_t *const rxbuf, + const uint16_t length) +{ + const struct sercomspi_regs_cfg *regs = _spi_get_regs((uint32_t)dev->prvt); + uint8_t rx_ch = _spi_get_rx_dma_channel(dev->prvt); + uint8_t tx_ch = _spi_get_tx_dma_channel(dev->prvt); + + if (rxbuf) { + /* Enable spi rx */ + _spi_m_dma_rx_enable(dev); + _dma_set_source_address(rx_ch, (void *)_spi_m_get_source_for_dma(dev->prvt)); + _dma_set_destination_address(rx_ch, rxbuf); + _dma_set_data_amount(rx_ch, length); + _dma_enable_transaction(rx_ch, false); + } else { + /* Disable spi rx */ + _spi_m_dma_rx_disable(dev); + } + + if (txbuf) { + /* Enable spi tx */ + _dma_set_source_address(tx_ch, txbuf); + _dma_set_destination_address(tx_ch, (void *)_spi_m_get_destination_for_dma(dev->prvt)); + _dma_srcinc_enable(tx_ch, true); + _dma_set_data_amount(tx_ch, length); + } else { + _dma_set_source_address(tx_ch, ®s->dummy_byte); + _dma_set_destination_address(tx_ch, (void *)_spi_m_get_destination_for_dma(dev->prvt)); + _dma_srcinc_enable(tx_ch, false); + _dma_set_data_amount(tx_ch, length); + } + _dma_enable_transaction(tx_ch, false); + + return ERR_NONE; +} diff --git a/external/asf4-drivers/hpl/spi/spi_lite.c b/external/asf4-drivers/hpl/spi/spi_lite.c index 4ce85472c0..8885638a68 100644 --- a/external/asf4-drivers/hpl/spi/spi_lite.c +++ b/external/asf4-drivers/hpl/spi/spi_lite.c @@ -158,127 +158,3 @@ void SPI_MEM_read_block(void *block, size_t size) b++; } } - -/** - * \brief Initialize SPI interface - */ -int8_t SPI_OLED_init() -{ - - if (!hri_sercomspi_is_syncing(SERCOM3, SERCOM_SPI_SYNCBUSY_SWRST)) { - uint32_t mode = SERCOM_SPI_CTRLA_MODE(3); - if (hri_sercomspi_get_CTRLA_reg(SERCOM3, SERCOM_SPI_CTRLA_ENABLE)) { - hri_sercomspi_clear_CTRLA_ENABLE_bit(SERCOM3); - hri_sercomspi_wait_for_sync(SERCOM3, SERCOM_SPI_SYNCBUSY_ENABLE); - } - hri_sercomspi_write_CTRLA_reg(SERCOM3, SERCOM_SPI_CTRLA_SWRST | mode); - } - hri_sercomspi_wait_for_sync(SERCOM3, SERCOM_SPI_SYNCBUSY_SWRST); - - hri_sercomspi_write_CTRLA_reg( - SERCOM3, - 0 << SERCOM_SPI_CTRLA_DORD_Pos /* Data Order: disabled */ - | 0 << SERCOM_SPI_CTRLA_CPOL_Pos /* Clock Polarity: disabled */ - | 0 << SERCOM_SPI_CTRLA_CPHA_Pos /* Clock Phase: disabled */ - | 0 << SERCOM_SPI_CTRLA_FORM_Pos /* Frame Format: 0 */ - | 0 << SERCOM_SPI_CTRLA_IBON_Pos /* Immediate Buffer Overflow Notification: disabled */ - | 0 << SERCOM_SPI_CTRLA_RUNSTDBY_Pos /* Run In Standby: disabled */ - | 3 << SERCOM_SPI_CTRLA_MODE_Pos); /* Operating Mode: 3 */ - - hri_sercomspi_write_CTRLA_DOPO_bf(SERCOM3, SERCOM3_TXPO); - hri_sercomspi_write_CTRLA_DIPO_bf(SERCOM3, SERCOM3_RXPO); - - hri_sercomspi_write_CTRLB_reg(SERCOM3, - 1 << SERCOM_SPI_CTRLB_RXEN_Pos /* Receiver Enable: enabled */ - | 0 << SERCOM_SPI_CTRLB_MSSEN_Pos /* Master Slave Select Enabl: disabled */ - | 0 << SERCOM_SPI_CTRLB_AMODE_Pos /* Address Mode: 0 */ - | 0 << SERCOM_SPI_CTRLB_SSDE_Pos /* Slave Select Low Detect Enable: disabled */ - | 0 << SERCOM_SPI_CTRLB_PLOADEN_Pos /* Slave Data Preload Enable: disabled */ - | 0); /* Character Size: 0 */ - - hri_sercomspi_write_BAUD_reg(SERCOM3, SERCOM3_BAUD_RATE); - - // hri_sercomspi_write_DBGCTRL_reg(SERCOM3,0 << SERCOM_SPI_DBGCTRL_DBGSTOP_Pos); /* Debug Stop Mode: disabled */ - - // hri_sercomspi_write_INTEN_reg(SERCOM3,0 << SERCOM_SPI_INTENSET_ERROR_Pos /* Error Interrupt Enable: disabled */ - // | 0 << SERCOM_SPI_INTENSET_SSL_Pos /* Slave Select Low Interrupt Enable: disabled */ - // | 0 << SERCOM_SPI_INTENSET_RXC_Pos /* Receive Complete Interrupt Enable: disabled */ - // | 0 << SERCOM_SPI_INTENSET_TXC_Pos /* Transmit Complete Interrupt Enable: disabled */ - // | 0 << SERCOM_SPI_INTENSET_DRE_Pos); /* Data Register Empty Interrupt Enable: disabled */ - - hri_sercomspi_write_CTRLA_ENABLE_bit(SERCOM3, 1 << SERCOM_SPI_CTRLA_ENABLE_Pos); /* Enable: enabled */ - - return 0; -} - -/** - * \brief Enable SPI module - */ -void SPI_OLED_enable() -{ - hri_sercomspi_set_CTRLA_ENABLE_bit(SERCOM3); -} - -/** - * \brief Disable SPI module - */ -void SPI_OLED_disable() -{ - hri_sercomspi_clear_CTRLA_ENABLE_bit(SERCOM3); -} - -/** - * \brief Exchange_byte in SPI module - */ -uint32_t SPI_OLED_exchange_data(uint32_t data) -{ - /* If settings are not applied (pending), we can not go on */ - if (hri_sercomspi_is_syncing( - SERCOM3, (SERCOM_SPI_SYNCBUSY_SWRST | SERCOM_SPI_SYNCBUSY_ENABLE | SERCOM_SPI_SYNCBUSY_CTRLB))) { - return ERR_BUSY; - } - - hri_sercomspi_write_DATA_reg(SERCOM3, data); - while (!(hri_sercomspi_read_INTFLAG_reg(SERCOM3) & SERCOM_SPI_INTFLAG_RXC)) - ; - return hri_sercomspi_read_DATA_reg(SERCOM3); -} - -void SPI_OLED_exchange_block(void *block, uint8_t size) -{ - - uint8_t *b = (uint8_t *)block; - - while (size--) { - hri_sercomspi_write_DATA_reg(SERCOM3, *b); - while (!(hri_sercomspi_read_INTFLAG_reg(SERCOM3) & SERCOM_SPI_INTFLAG_RXC)) - ; - *b = hri_sercomspi_read_DATA_reg(SERCOM3); - b++; - } -} - -void SPI_OLED_write_block(void *block, uint8_t size) -{ - - uint8_t *b = (uint8_t *)block; - while (size--) { - hri_sercomspi_write_DATA_reg(SERCOM3, *b); - while (!(hri_sercomspi_read_INTFLAG_reg(SERCOM3) & SERCOM_SPI_INTFLAG_TXC)) - ; - b++; - } -} - -void SPI_OLED_read_block(void *block, uint8_t size) -{ - - uint8_t *b = (uint8_t *)block; - while (size--) { - hri_sercomspi_write_DATA_reg(SERCOM3, 0); - while (!(hri_sercomspi_read_INTFLAG_reg(SERCOM3) & SERCOM_SPI_INTFLAG_RXC)) - ; - *b = hri_sercomspi_read_DATA_reg(SERCOM3); - b++; - } -} diff --git a/external/asf4-drivers/hpl/spi/spi_lite.h b/external/asf4-drivers/hpl/spi/spi_lite.h index 2b77d46c34..151d32d9d6 100644 --- a/external/asf4-drivers/hpl/spi/spi_lite.h +++ b/external/asf4-drivers/hpl/spi/spi_lite.h @@ -89,67 +89,17 @@ uint32_t SPI_MEM_exchange_data(uint32_t data); /** * \brief Exchange block in SPI module */ -void SPI_MEM_exchange_block(void *block, size_t size); +void SPI_MEM_exchange_block(void* block, size_t size); /** * \brief Write block in SPI module */ -void SPI_MEM_write_block(void *block, size_t size); +void SPI_MEM_write_block(void* block, size_t size); /** * \brief Read block in SPI module */ -void SPI_MEM_read_block(void *block, size_t size); - -// Calculate baud register value from requested baudrate value -#ifndef SERCOM3_BAUD_RATE -#define SERCOM3_BAUD_RATE (((float)CONF_GCLK_SERCOM3_CORE_FREQUENCY / (float)(2 * 3000000)) - 1) -#endif - -#ifndef SERCOM3_RXPO -#define SERCOM3_RXPO 2 -#endif - -#ifndef SERCOM3_TXPO -#define SERCOM3_TXPO 0 -#endif - -/** - * \brief Initialize usart interface - * - * \return Initialization status. - */ -int8_t SPI_OLED_init(); - -/** - * \brief Enable SPI module - */ -void SPI_OLED_enable(); - -/** - * \brief Disable SPI module - */ -void SPI_OLED_disable(); - -/** - * \brief Exchange byte in SPI module - */ -uint32_t SPI_OLED_exchange_data(uint32_t data); - -/** - * \brief Exchange block in SPI module - */ -void SPI_OLED_exchange_block(void *block, uint8_t size); - -/** - * \brief Write block in SPI module - */ -void SPI_OLED_write_block(void *block, uint8_t size); - -/** - * \brief Read block in SPI module - */ -void SPI_OLED_read_block(void *block, uint8_t size); +void SPI_MEM_read_block(void* block, size_t size); #ifdef __cplusplus } diff --git a/src/bootloader/bootloader.c b/src/bootloader/bootloader.c index 85da6ab6a4..138a6a588a 100644 --- a/src/bootloader/bootloader.c +++ b/src/bootloader/bootloader.c @@ -331,7 +331,7 @@ static void _render_message(const char* message, int duration) snprintf(print, sizeof(print), "%s", message); UG_PutString(0, 0, print, false); canvas_commit(); - oled_present(); + oled_present(true); delay_ms(duration); } @@ -355,7 +355,7 @@ void bootloader_render_default_screen(void) UG_PutString(0, SCREEN_HEIGHT - 9, "See the BitBoxApp", false); #endif canvas_commit(); - oled_present(); + oled_present(true); } #if PLATFORM_BITBOX02PLUS @@ -381,7 +381,7 @@ void bootloader_render_ble_confirm_screen(bool confirmed) UG_PutString(45, SCREEN_HEIGHT / 2 - 9, code_str, false); UG_FontSelect(&font_font_a_9X9); canvas_commit(); - oled_present(); + oled_present(true); } #endif @@ -402,7 +402,7 @@ static void _render_progress(float progress) } UG_PutString(SCREEN_WIDTH / 2 - 3, SCREEN_HEIGHT - 9 * 2, msg, false); canvas_commit(); - oled_present(); + oled_present(true); } static void _render_hash(const char* title, const uint8_t* hash) @@ -450,7 +450,7 @@ static void _render_hash(const char* title, const uint8_t* hash) UG_FontSelect(f_regular); canvas_commit(); - oled_present(); + oled_present(true); delay_ms(1000); } bootloader_render_default_screen(); @@ -1044,7 +1044,7 @@ static bool _devdevice_enter(secbool_u32 firmware_verified) UG_DrawLine(xpos - 2, ypos + 3, xpos, ypos + 5, C_WHITE); } canvas_commit(); - oled_present(); + oled_present(true); while (true) { do { qtouch_process(); diff --git a/src/bootloader/startup.c b/src/bootloader/startup.c index 75d935c130..132d100ef3 100644 --- a/src/bootloader/startup.c +++ b/src/bootloader/startup.c @@ -219,9 +219,9 @@ int main(void) } bootloader_pairing_request = false; canvas_commit(); - oled_present(); } } + oled_present(true); #endif } return 0; diff --git a/src/firmware_main_loop.c b/src/firmware_main_loop.c index 8282b8d5db..20e22aea3d 100644 --- a/src/firmware_main_loop.c +++ b/src/firmware_main_loop.c @@ -35,6 +35,7 @@ #include "workflow/orientation_screen.h" #include #include +#include #include #if APP_U2F == 1 #include "u2f.h" @@ -47,6 +48,7 @@ void firmware_main_loop(void) { + screen_process_init(); // Set the size of uart_read_buf to the size of the ringbuffer in the UART driver so we can read // out all bytes uint8_t uart_read_buf[USART_0_BUFFER_SIZE] = {0}; @@ -177,8 +179,9 @@ void firmware_main_loop(void) #if APP_U2F == 1 u2f_process(); #endif - screen_process(); + oled_present(false); + /* And finally, run the high-level event processing. */ rust_workflow_spin(); diff --git a/src/platform/driver_init.c b/src/platform/driver_init.c index 69dbf0eaa2..d60c12c2da 100644 --- a/src/platform/driver_init.c +++ b/src/platform/driver_init.c @@ -23,9 +23,7 @@ #include #include -#define PIN_HIGH 1 -#define PIN_LOW 0 - +struct spi_m_dma_descriptor SPI_0; struct sha_sync_descriptor HASH_ALGORITHM_0; struct timer_descriptor TIMER_0; struct flash_descriptor FLASH_0; @@ -116,10 +114,11 @@ static void _spi_init(void) GCLK, SERCOM3_GCLK_ID_CORE, CONF_GCLK_SERCOM3_CORE_SRC | (1 << GCLK_PCHCTRL_CHEN_Pos)); hri_gclk_write_PCHCTRL_reg( GCLK, SERCOM3_GCLK_ID_SLOW, CONF_GCLK_SERCOM3_SLOW_SRC | (1 << GCLK_PCHCTRL_CHEN_Pos)); + hri_mclk_set_APBBMASK_SERCOM3_bit(MCLK); - SPI_OLED_init(); + spi_m_dma_init(&SPI_0, SERCOM3); + _spi_set_pins(); - SPI_OLED_enable(); } static void _spi_mem_clock_init(void) @@ -434,7 +433,7 @@ void system_close_interfaces(void) i2c_m_sync_deinit(&I2C_0); // OLED interface bus // Display remains on last screen - SPI_OLED_disable(); + spi_m_dma_deinit(&SPI_0); // Flash flash_deinit(&FLASH_0); // USB @@ -451,7 +450,7 @@ void bootloader_close_interfaces(void) } // OLED interface bus // Display remains on last screen - SPI_OLED_disable(); + spi_m_dma_deinit(&SPI_0); // Flash flash_deinit(&FLASH_0); // USB diff --git a/src/platform/driver_init.h b/src/platform/driver_init.h index d656433f7e..ec08d36594 100644 --- a/src/platform/driver_init.h +++ b/src/platform/driver_init.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,9 @@ #include "platform_config.h" +#define PIN_HIGH 1 +#define PIN_LOW 0 + #define SHA256_DIGEST_LENGTH 32 // 64 is our typical packet size and it gets a little bit longer over UART due to framing. Set the // buffer size so we can handle at least one whole frame. Must be a power of 2. @@ -59,6 +63,7 @@ extern struct rand_sync_desc RAND_0; extern PPUKCL_PARAM pvPUKCLParam; extern PUKCL_PARAM PUKCLParam; extern struct usart_async_descriptor USART_0; +extern struct spi_m_dma_descriptor SPI_0; #endif /** diff --git a/src/reset.c b/src/reset.c index 71d71f803e..0c3499c8d5 100644 --- a/src/reset.c +++ b/src/reset.c @@ -46,7 +46,7 @@ static void _show_reset_label(bool status) canvas_clear(); comp->f->render(comp); canvas_commit(); - oled_present(); + oled_present(true); comp->f->cleanup(comp); delay_ms(3000); } diff --git a/src/rust/bitbox02/src/lib.rs b/src/rust/bitbox02/src/lib.rs index e8e4b09e5b..df60c75720 100644 --- a/src/rust/bitbox02/src/lib.rs +++ b/src/rust/bitbox02/src/lib.rs @@ -71,7 +71,7 @@ pub fn canvas_commit() { } pub fn oled_present() { - unsafe { bitbox02_sys::oled_present() } + unsafe { bitbox02_sys::oled_present(true) } } pub fn ug_font_select_9x9() { diff --git a/src/screen.c b/src/screen.c index ebfe7e07dc..9ca0b2309c 100644 --- a/src/screen.c +++ b/src/screen.c @@ -49,9 +49,11 @@ void screen_print_debug(const char* message, int duration) UG_FontSelect(&font_font_a_9X9); UG_PutString(0, 0, print, false); canvas_commit(); - oled_present(); + oled_present(true); #ifndef TESTING if (duration > 0) delay_ms(duration); +#else + (void)duration; #endif } @@ -87,7 +89,7 @@ void screen_splash(void) image_arrow(SCREEN_WIDTH - x - 2, y, height, ARROW_LEFT); canvas_commit(); - oled_present(); + oled_present(true); } void screen_rotate(void) diff --git a/src/ui/canvas.c b/src/ui/canvas.c index 597a32dd4f..62c086bf15 100644 --- a/src/ui/canvas.c +++ b/src/ui/canvas.c @@ -16,17 +16,21 @@ #include #include -static uint8_t* _canvas_active = NULL; -static uint8_t* _canvas_working = NULL; +#define NUM_CANVASES 3 -// One working buffer and one active buffer. The buffer must be 4 byte aligned for DMA transfers. +// The buffers must be 4 byte aligned for DMA transfers. static uint8_t _canvas_0[CANVAS_SIZE] __attribute__((aligned(4))) = {0}; static uint8_t _canvas_1[CANVAS_SIZE] __attribute__((aligned(4))) = {0}; +static uint8_t _canvas_2[CANVAS_SIZE] __attribute__((aligned(4))) = {0}; + +static uint8_t* _canvases[NUM_CANVASES] = {0}; +static uint8_t _canvas_active = 0; void canvas_init(void) { - _canvas_working = _canvas_0; - _canvas_active = _canvas_1; + _canvases[0] = _canvas_0; + _canvases[1] = _canvas_1; + _canvases[2] = _canvas_2; } void canvas_fill(uint8_t color) @@ -42,20 +46,19 @@ void canvas_clear(void) void canvas_commit(void) { - uint8_t* _canvas_tmp = _canvas_working; - _canvas_working = _canvas_active; - _canvas_active = _canvas_tmp; + _canvas_active = (_canvas_active + 1) % NUM_CANVASES; canvas_clear(); } uint8_t* canvas_working(void) { - ASSERT(_canvas_working); - return _canvas_working; + uint8_t canvas_working = (_canvas_active + 1) % NUM_CANVASES; + ASSERT(_canvases[canvas_working]); + return _canvases[canvas_working]; } uint8_t* canvas_active(void) { - ASSERT(_canvas_active); - return _canvas_active; + ASSERT(_canvases[_canvas_active]); + return _canvases[_canvas_active]; } diff --git a/src/ui/components/confirm_gesture.c b/src/ui/components/confirm_gesture.c index 8ad36034ff..a278976682 100644 --- a/src/ui/components/confirm_gesture.c +++ b/src/ui/components/confirm_gesture.c @@ -28,7 +28,7 @@ #include #include -#define SCALE 6 // Divide active_count by scale to slow down motion +#define SCALE 4 // Divide active_count by scale to slow down motion typedef struct { bool active_top; // Marker is 'active', i.e., touched diff --git a/src/ui/components/status.c b/src/ui/components/status.c index d7ce208844..cf4bf76267 100644 --- a/src/ui/components/status.c +++ b/src/ui/components/status.c @@ -21,7 +21,7 @@ #include #include -#define STATUS_DEFAULT_DELAY 500 // counts +#define STATUS_DEFAULT_DELAY 100 // counts typedef struct { bool status; diff --git a/src/ui/oled/oled.c b/src/ui/oled/oled.c index b25bb7073d..aa18572e4f 100644 --- a/src/ui/oled/oled.c +++ b/src/ui/oled/oled.c @@ -96,9 +96,25 @@ static struct bb02_display bb02_display = { .mirror = sh1107_mirror, }; +static struct timer_task _task_present_screen; +static volatile bool _present_screen = true; + +static void present_screen_cb(const struct timer_task* const timer_task) +{ + (void)timer_task; + _present_screen = true; +} + void oled_init(void) { canvas_init(); + oled_writer_init(); + + // Limit screen presentting to 30Hz + _task_present_screen.interval = 33; + _task_present_screen.cb = present_screen_cb; + _task_present_screen.mode = TIMER_TASK_REPEAT; + timer_add_task(&TIMER_0, &_task_present_screen); if (memory_get_screen_type() == MEMORY_SCREEN_TYPE_SSD1312) { bb02_display.configure = ssd1312_configure; @@ -110,30 +126,38 @@ void oled_init(void) if (_enabled) { return; } - // DC-DC OFF - gpio_set_pin_level(PIN_OLED_ON, 0); - delay_us(5); - // Hard reset OLED display controller - gpio_set_pin_level(PIN_OLED_RES, 0); - delay_us(5); - gpio_set_pin_level(PIN_OLED_RES, 1); - delay_us(5); + // * VDD is powered on at the same time as the MCU. + // * PIN_OLED_RES starts high (see driver_init.c) + // * PIN_OLED_ON starts low (see driver_init.c) + + // Wait at least 20ms from VDD ON + delay_ms(25); - oled_present(); + gpio_set_pin_level(PIN_OLED_RES, PIN_LOW); + // Wait at least 3us + delay_us(6); + gpio_set_pin_level(PIN_OLED_RES, PIN_HIGH); + delay_us(5); bb02_display.configure(); + // VCC ON + gpio_set_pin_level(PIN_OLED_ON, PIN_HIGH); + + // Wait at least 100ms from DISPLAY_ON before starting to use delay_ms(100); - // DC-DC ON - gpio_set_pin_level(PIN_OLED_ON, 1); _enabled = true; } -void oled_present(void) +void oled_present(bool force) { bb02_display.present(); + if (_present_screen || force) { + _present_screen = false; + bb02_display.present(); + } } void oled_mirror(bool mirror) @@ -160,5 +184,5 @@ void oled_off(void) void oled_set_brightness(uint8_t value) { // brightness uses the same command on all displays 0x81. - oled_writer_write_cmd_with_param(0x81, value); + oled_writer_write_cmd_with_param_blocking(0x81, value); } diff --git a/src/ui/oled/oled.h b/src/ui/oled/oled.h index 614ef8d045..93db645c72 100644 --- a/src/ui/oled/oled.h +++ b/src/ui/oled/oled.h @@ -78,7 +78,7 @@ void oled_mirror(bool mirror); /** * Transfer active canvas to the screen */ -void oled_present(void); +void oled_present(bool force); /** * Turn off oled @@ -88,12 +88,13 @@ void oled_off(void); /** * Set a pixel on the "working" canvas arcoding to the screen requirements. The working and active * canvases are flipped with canvas_commit(); + * @param c Must be 0 for black and 1 for white */ void oled_set_pixel(int16_t x, int16_t y, uint8_t c); /** * Set brightness (0x00..0xff). - * 0x00 does not mean black, just loweset brightness. + * 0x00 does not mean black, just lowest brightness. */ void oled_set_brightness(uint8_t value); diff --git a/src/ui/oled/oled_writer.c b/src/ui/oled/oled_writer.c index e71cfe3cdd..af2994b6f5 100644 --- a/src/ui/oled/oled_writer.c +++ b/src/ui/oled/oled_writer.c @@ -14,6 +14,14 @@ #include "oled_writer.h" #include "driver_init.h" +#include + +// microseconds to wait after buffer has been sent out over SPI +// (works with 3, use 6 to have some margin) +#define WAIT_AFTER_TX_READY 6 + +static struct io_descriptor* _io; +volatile bool _tx_ready = true; enum _interface_t { INTERFACE_COMMAND, @@ -30,27 +38,59 @@ static inline void _write(enum _interface_t interface, const uint8_t* buf, size_ uint8_t cmd = interface == INTERFACE_COMMAND ? 0 : 1; gpio_set_pin_level(PIN_OLED_CMD, cmd); gpio_set_pin_level(PIN_OLED_CS, 0); - // It is safe to cast from const here because "write_block" only reads from buf -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-qual" - SPI_OLED_write_block((void*)buf, buf_len); -#pragma GCC diagnostic pop - gpio_set_pin_level(PIN_OLED_CS, 1); + io_write(_io, buf, buf_len); } +// This function intentionally does not block until buffer is transferred void oled_writer_write_data(const uint8_t* buf, size_t buf_len) { _write(INTERFACE_DATA, buf, buf_len); } -void oled_writer_write_cmd(uint8_t command) +void oled_writer_write_data_blocking(const uint8_t* buf, size_t buf_len) +{ + _tx_ready = false; + oled_writer_write_data(buf, buf_len); + while (!_tx_ready); + // Wait a moment so that CMD/CS doesn't change until device has processed all data + delay_us(WAIT_AFTER_TX_READY); + gpio_set_pin_level(PIN_OLED_CS, 1); +} + +void oled_writer_write_cmd_blocking(uint8_t command) { - const uint8_t buf[] = {command}; + _tx_ready = false; + const uint8_t buf[] __attribute((aligned(4))) = {command}; _write(INTERFACE_COMMAND, buf, sizeof(buf)); + // Buffer is stack allocated, need to wait until done + while (!_tx_ready); + // Wait a moment so that CMD/CS doesn't change until device has processed all data + delay_us(WAIT_AFTER_TX_READY); + gpio_set_pin_level(PIN_OLED_CS, 1); } -void oled_writer_write_cmd_with_param(uint8_t command, uint8_t value) +void oled_writer_write_cmd_with_param_blocking(uint8_t command, uint8_t value) { - const uint8_t buf[] = {command, value}; + _tx_ready = false; + const uint8_t buf[] __attribute((aligned(4))) = {command, value}; _write(INTERFACE_COMMAND, buf, sizeof(buf)); + // Buffer is stack allocated, need to wait until done + while (!_tx_ready); + // Wait a moment so that CMD/CS doesn't change until device has processed all data + delay_us(WAIT_AFTER_TX_READY); + gpio_set_pin_level(PIN_OLED_CS, 1); +} + +static void _dma_tx_complete(struct _dma_resource* resource) +{ + (void)resource; + _tx_ready = true; +} + +void oled_writer_init(void) +{ + spi_m_dma_get_io_descriptor(&SPI_0, &_io); + + spi_m_dma_register_callback(&SPI_0, SPI_M_DMA_CB_TX_DONE, _dma_tx_complete); + spi_m_dma_enable(&SPI_0); } diff --git a/src/ui/oled/oled_writer.h b/src/ui/oled/oled_writer.h index c982583644..34d26a5699 100644 --- a/src/ui/oled/oled_writer.h +++ b/src/ui/oled/oled_writer.h @@ -23,14 +23,24 @@ */ void oled_writer_write_data(const uint8_t* buf, size_t buf_len); +/* + * Write display data to graphics RAM + */ +void oled_writer_write_data_blocking(const uint8_t* buf, size_t buf_len); + /* * Write single byte command */ -void oled_writer_write_cmd(uint8_t command); +void oled_writer_write_cmd_blocking(uint8_t command); /* * Write double byte command */ -void oled_writer_write_cmd_with_param(uint8_t command, uint8_t value); +void oled_writer_write_cmd_with_param_blocking(uint8_t command, uint8_t value); + +/* + * Initialize oled writer + */ +void oled_writer_init(void); #endif diff --git a/src/ui/oled/sh1107.c b/src/ui/oled/sh1107.c index fa4ed3e48d..d841b1353e 100644 --- a/src/ui/oled/sh1107.c +++ b/src/ui/oled/sh1107.c @@ -76,66 +76,68 @@ void sh1107_configure(void) { - oled_writer_write_cmd(SH1107_CMD_SET_DISPLAY_OFF); - oled_writer_write_cmd_with_param(SH1107_CMD_SET_CONTRAST_CONTROL, 0xff); - oled_writer_write_cmd(SH1107_CMD_SET_VERTICAL_ADDRESSING_MODE); - oled_writer_write_cmd(SH1107_CMD_SET_SEGMENT_RE_MAP_REVERSE); - oled_writer_write_cmd(SH1107_CMD_SET_COM_OUTPUT_SCAN_UP); - oled_writer_write_cmd(SH1107_CMD_SET_NORMAL_DISPLAY); - oled_writer_write_cmd_with_param(SH1107_CMD_SET_MULTIPLEX_RATIO, 0x3f); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_DISPLAY_OFF); + oled_writer_write_cmd_with_param_blocking(SH1107_CMD_SET_CONTRAST_CONTROL, 0xff); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_VERTICAL_ADDRESSING_MODE); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_SEGMENT_RE_MAP_REVERSE); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_COM_OUTPUT_SCAN_UP); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_NORMAL_DISPLAY); + oled_writer_write_cmd_with_param_blocking(SH1107_CMD_SET_MULTIPLEX_RATIO, 0x3f); // Shift the columns by 96 when display is in non-mirrored orientation - oled_writer_write_cmd_with_param(SH1107_CMD_SET_DISPLAY_OFFSET, 0x60); - oled_writer_write_cmd_with_param(SH1107_CMD_SET_DISPLAY_CLOCK_DIVIDE_RATIO, 0xf0); - oled_writer_write_cmd_with_param(SH1107_CMD_SET_PRE_CHARGE_PERIOD, 0x22); - oled_writer_write_cmd_with_param(SH1107_CMD_SET_VCOMH_DESELECT_LEVEL, 0x35); - oled_writer_write_cmd_with_param(0xad, 0x8a); - oled_writer_write_cmd(SH1107_CMD_ENTIRE_DISPLAY_AND_GDDRAM_ON); - sh1107_present(); - oled_writer_write_cmd(SH1107_CMD_SET_DISPLAY_ON); + oled_writer_write_cmd_with_param_blocking(SH1107_CMD_SET_DISPLAY_OFFSET, 0x60); + oled_writer_write_cmd_with_param_blocking(SH1107_CMD_SET_DISPLAY_CLOCK_DIVIDE_RATIO, 0xf0); + oled_writer_write_cmd_with_param_blocking(SH1107_CMD_SET_PRE_CHARGE_PERIOD, 0x22); + oled_writer_write_cmd_with_param_blocking(SH1107_CMD_SET_VCOMH_DESELECT_LEVEL, 0x35); + oled_writer_write_cmd_with_param_blocking(0xad, 0x8a); + oled_writer_write_cmd_blocking(SH1107_CMD_ENTIRE_DISPLAY_AND_GDDRAM_ON); + oled_writer_write_data_blocking(canvas_active(), CANVAS_SIZE); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_DISPLAY_ON); } /* pixels can be accessed via buf[y*16+x/8] >> x%8 */ void sh1107_set_pixel(int16_t x, int16_t y, uint8_t c) { - uint32_t p; if (x < 0 || x > 127) return; if (y < 0 || y > 63) return; - p = y * 16; - p += x / 8; - if (c) { - canvas_working()[p] |= 1 << (x % 8); - } else { - canvas_working()[p] &= ~(1 << (x % 8)); - } + uint_fast8_t shift = x % 8; + uint8_t* p = &canvas_working()[y * 16 + x / 8]; + // Clear pixel + *p &= ~(1 << shift); + // Set pixel + *p |= (c & 0x1) << shift; } /* The SH1107 Segment/Common driver specifies that there are 16 pages per column * In total we should be writing 64*128 pixels. 8 bits per page, 16 pages per column and 64 - * columns */ + * columns + * + * Since the display requires us to execute commands between each row we need to use the blocking + * interface. + * */ void sh1107_present(void) { for (size_t i = 0; i < 64; i++) { - oled_writer_write_cmd(SH1107_CMD_SET_LOW_COL(i)); - oled_writer_write_cmd(SH1107_CMD_SET_HIGH_COL(i)); - oled_writer_write_data(&canvas_active()[i * 16], 16); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_LOW_COL(i)); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_HIGH_COL(i)); + oled_writer_write_data_blocking(&canvas_active()[i * 16], 16); } } void sh1107_mirror(bool mirror) { if (mirror) { - oled_writer_write_cmd(SH1107_CMD_SET_SEGMENT_RE_MAP_NORMAL); - oled_writer_write_cmd(SH1107_CMD_SET_COM_OUTPUT_SCAN_DOWN); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_SEGMENT_RE_MAP_NORMAL); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_COM_OUTPUT_SCAN_DOWN); // Shift the columns by 32 when display is in mirrored orientation - oled_writer_write_cmd_with_param(SH1107_CMD_SET_DISPLAY_OFFSET, 0x20); + oled_writer_write_cmd_with_param_blocking(SH1107_CMD_SET_DISPLAY_OFFSET, 0x20); } else { - oled_writer_write_cmd(SH1107_CMD_SET_SEGMENT_RE_MAP_REVERSE); - oled_writer_write_cmd(SH1107_CMD_SET_COM_OUTPUT_SCAN_UP); - oled_writer_write_cmd_with_param(SH1107_CMD_SET_DISPLAY_OFFSET, 0x60); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_SEGMENT_RE_MAP_REVERSE); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_COM_OUTPUT_SCAN_UP); + oled_writer_write_cmd_with_param_blocking(SH1107_CMD_SET_DISPLAY_OFFSET, 0x60); } } void sh1107_off(void) { - oled_writer_write_cmd(SH1107_CMD_SET_DISPLAY_OFF); + oled_writer_write_cmd_blocking(SH1107_CMD_SET_DISPLAY_OFF); } diff --git a/src/ui/oled/ssd1312.c b/src/ui/oled/ssd1312.c index 6b6a21e872..bf4fdf7fc0 100644 --- a/src/ui/oled/ssd1312.c +++ b/src/ui/oled/ssd1312.c @@ -81,65 +81,58 @@ void ssd1312_configure(void) { - oled_writer_write_cmd(SSD1312_CMD_SET_LOW_COL(0)); - oled_writer_write_cmd(SSD1312_CMD_SET_HIGH_COL(0)); - oled_writer_write_cmd(SSD1312_CMD_SET_DISPLAY_OFF); - oled_writer_write_cmd_with_param(SSD1312_CMD_SET_CONTRAST_CONTROL, 0xff); - oled_writer_write_cmd_with_param( - SSD1312_CMD_SET_MEMORY_ADDRESSING_MODE, SSD1312_CMD_SET_PAGE_ADDRESSING_MODE); - oled_writer_write_cmd(SSD1312_CMD_SET_SEGMENT_RE_MAP_SEG0_0); - oled_writer_write_cmd(SSD1312_CMD_SET_COM_OUTPUT_SCAN_DOWN); - oled_writer_write_cmd(SSD1312_CMD_SET_NORMAL_DISPLAY); - oled_writer_write_cmd_with_param(SSD1312_CMD_SET_MULTIPLEX_RATIO, 0x3f); - oled_writer_write_cmd_with_param(SSD1312_CMD_SET_DISPLAY_CLOCK_DIVIDE_RATIO, 0xf0); - oled_writer_write_cmd_with_param(SSD1312_CMD_SET_PRE_CHARGE_PERIOD, 0x22); - oled_writer_write_cmd_with_param(SSD1312_CMD_SET_VCOMH_SELECT_LEVEL, 0x35); - oled_writer_write_cmd_with_param(SSD1312_CMD_SET_IREF, 0x40); - oled_writer_write_cmd(SSD1312_CMD_ENTIRE_DISPLAY_AND_GDDRAM_ON); - ssd1312_present(); - oled_writer_write_cmd(SSD1312_CMD_SET_DISPLAY_ON); + // Initialize in the same order as the product docs example + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_DISPLAY_OFF); + oled_writer_write_cmd_with_param_blocking(SSD1312_CMD_SET_MULTIPLEX_RATIO, 0x3f); + oled_writer_write_cmd_with_param_blocking(SSD1312_CMD_SET_IREF, 0x40); + oled_writer_write_cmd_with_param_blocking(SSD1312_CMD_SET_DISPLAY_OFFSET, 0); + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_COM_OUTPUT_SCAN_DOWN); + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_SEGMENT_RE_MAP_SEG0_0); + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_NORMAL_DISPLAY); + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_DISPLAY_START_LINE(0)); + oled_writer_write_cmd_blocking(SSD1312_CMD_ENTIRE_DISPLAY_AND_GDDRAM_ON); + oled_writer_write_cmd_with_param_blocking(SSD1312_CMD_SET_CONTRAST_CONTROL, 0xff); + oled_writer_write_cmd_with_param_blocking(SSD1312_CMD_SET_PRE_CHARGE_PERIOD, 0x22); + oled_writer_write_cmd_with_param_blocking(SSD1312_CMD_SET_DISPLAY_CLOCK_DIVIDE_RATIO, 0xf0); + oled_writer_write_cmd_with_param_blocking(SSD1312_CMD_SET_SEG_PINS, 0x10); + oled_writer_write_cmd_with_param_blocking( + SSD1312_CMD_SET_MEMORY_ADDRESSING_MODE, SSD1312_CMD_SET_SEG_PAGE_H_MODE); + oled_writer_write_cmd_with_param_blocking(SSD1312_CMD_SET_VCOMH_SELECT_LEVEL, 0x35); + + oled_writer_write_data_blocking(canvas_active(), CANVAS_SIZE); + + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_DISPLAY_ON); } void ssd1312_set_pixel(int16_t x, int16_t y, uint8_t c) { - uint32_t p; if (x < 0 || x > 127) return; if (y < 0 || y > 63) return; - p = (y / 8) * 128; - p += x; - if (c) { - canvas_working()[p] |= 1 << (y % 8); - } else { - canvas_working()[p] &= ~(1 << (y % 8)); - } + uint_fast8_t shift = x % 8; + uint_fast8_t num_cols = x / 8; + uint8_t* p = &canvas_working()[num_cols * 64 + y]; + // Clear pixel + *p &= ~(1 << shift); + // Set pixel + *p |= (c & 0x1) << shift; } void ssd1312_present(void) { - /* The SSD1312 has one page per 8 rows. One page is 128 bytes. Every byte is 8 rows */ - for (size_t i = 0; i < 64 / 8; i++) { - oled_writer_write_cmd(SSD1312_CMD_SET_PAGE_START_ADDRESS(i)); - // Explicitly set column address to 0 during initialization and screen updates to resolve - // intermittent ~20px horizontal offset and wrapping, which was experienced on one Nova - // device so far. This fixes the symptom, not the underlying issue, as we expect the column - // address to be correct if all bytes arrive at the screen. - oled_writer_write_cmd(SSD1312_CMD_SET_LOW_COL(0)); - oled_writer_write_cmd(SSD1312_CMD_SET_HIGH_COL(0)); - oled_writer_write_data(&canvas_active()[i * 128], 128); - } + oled_writer_write_data(canvas_active(), CANVAS_SIZE); } void ssd1312_mirror(bool mirror) { if (mirror) { - oled_writer_write_cmd(SSD1312_CMD_SET_SEGMENT_RE_MAP_SEG0_128); - oled_writer_write_cmd(SSD1312_CMD_SET_COM_OUTPUT_SCAN_UP); + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_SEGMENT_RE_MAP_SEG0_128); + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_COM_OUTPUT_SCAN_UP); } else { - oled_writer_write_cmd(SSD1312_CMD_SET_SEGMENT_RE_MAP_SEG0_0); - oled_writer_write_cmd(SSD1312_CMD_SET_COM_OUTPUT_SCAN_DOWN); + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_SEGMENT_RE_MAP_SEG0_0); + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_COM_OUTPUT_SCAN_DOWN); } } void ssd1312_off(void) { - oled_writer_write_cmd(SSD1312_CMD_SET_DISPLAY_OFF); + oled_writer_write_cmd_blocking(SSD1312_CMD_SET_DISPLAY_OFF); } diff --git a/src/ui/screen_process.c b/src/ui/screen_process.c index cb90fa7b47..3df3516d3c 100644 --- a/src/ui/screen_process.c +++ b/src/ui/screen_process.c @@ -15,6 +15,7 @@ #include "screen_process.h" #include "screen_stack.h" #include +#include #include #include #include @@ -23,7 +24,29 @@ #include #include -static uint8_t screen_frame_cnt = 0; +volatile bool update_ui = true; + +#if !defined(TESTING) +static struct timer_task task_update_ui; + +static void update_ui_cb(const struct timer_task* const timer_task) +{ + (void)timer_task; + update_ui = true; +} +#endif + +void screen_process_init(void) +{ +#if !defined(TESTING) + // Limit UI updateing to 100Hz + task_update_ui.interval = 10; + task_update_ui.cb = update_ui_cb; + task_update_ui.mode = TIMER_TASK_REPEAT; + + timer_add_task(&TIMER_0, &task_update_ui); +#endif +} void ui_screen_render_component(component_t* component) { @@ -31,7 +54,6 @@ void ui_screen_render_component(component_t* component) component->position.top = 0; component->f->render(component); canvas_commit(); - oled_present(); } static component_t* _get_waiting_screen(void) @@ -65,15 +87,6 @@ component_t* screen_process_get_top_component(void) return result; } -static void _screen_draw(component_t* component) -{ - if (screen_frame_cnt == SCREEN_FRAME_RATE) { - screen_frame_cnt = 0; - ui_screen_render_component(component); - } - screen_frame_cnt++; -} - #ifndef TESTING /** * Detects if the screen component being displayed has changed @@ -95,10 +108,14 @@ static bool _screen_has_changed(const component_t* current_component) void screen_process(void) { - screen_saver_process(); - component_t* component = screen_process_get_top_component(); - _screen_draw(component); + + if (update_ui) { + update_ui = false; + screen_saver_process(); + + ui_screen_render_component(component); + } #ifndef TESTING /* diff --git a/src/ui/screen_process.h b/src/ui/screen_process.h index 699754bdaf..624ed07e47 100644 --- a/src/ui/screen_process.h +++ b/src/ui/screen_process.h @@ -40,10 +40,6 @@ void screen_process_waiting_switch_to_logo(void); */ void screen_process(void); -/** - * Period of screen updates. - * The screen is refreshed every SCREEN_FRAME_RATE event loops cycles. - */ -#define SCREEN_FRAME_RATE 30 +void screen_process_init(void); #endif