From 119cdb86422b7da2e8ee136fde08257000d1e709 Mon Sep 17 00:00:00 2001 From: Niklas Dusenlund Date: Mon, 6 Oct 2025 20:08:45 +0200 Subject: [PATCH 1/2] jlink-scripts: reset before load --- scripts/jlink-bootloader.gdb | 8 ++++++-- scripts/jlink.gdb | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/scripts/jlink-bootloader.gdb b/scripts/jlink-bootloader.gdb index a7629aaf46..514c3e4193 100644 --- a/scripts/jlink-bootloader.gdb +++ b/scripts/jlink-bootloader.gdb @@ -1,12 +1,16 @@ # Connect to jlink gdb server target extended-remote :2331 -# load the firmware into ROM -load +# It seems more reliable to reset the chip before loading the new firmware. It +# is also how they do it in the example in the wiki: +# https://kb.segger.com/J-Link_GDB_Server#Console # Reset the CPU monitor reset +# load the firmware into ROM +load + #break Reset_Handler #break HardFault_Handler #break NMI_Handler diff --git a/scripts/jlink.gdb b/scripts/jlink.gdb index 4a9cf9fd0b..899c13530f 100644 --- a/scripts/jlink.gdb +++ b/scripts/jlink.gdb @@ -1,12 +1,16 @@ # Connect to jlink gdb server target extended-remote :2331 -# load the firmware into ROM -load +# It seems more reliable to reset the chip before loading the new firmware. It +# is also how they do it in the example in the wiki: +# https://kb.segger.com/J-Link_GDB_Server#Console # Reset the CPU monitor reset +# load the firmware into ROM +load + # Set VTOR (Vector Table Offset Register) to where the firmware is located set *(uint32_t*)0xE000ED08=0x10000 # Set stack pointer to initial stack pointer according to exception table. From 1de3c84800f2023200f28a06413ec70a48e4a5d3 Mon Sep 17 00:00:00 2001 From: Niklas Dusenlund Date: Thu, 18 Sep 2025 10:43:16 +0200 Subject: [PATCH 2/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 | 7 +- 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 | 77 ++++++++++++++++++++ src/ui/canvas.h | 59 +++++++++++++++ src/ui/graphics/lock_animation.c | 9 ++- src/ui/oled/oled.c | 36 ++++----- 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 - 26 files changed, 251 insertions(+), 127 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 c6344c524e..4fe298c25f 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..5ba9f2f40d 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_blit(); 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_blit(); } #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_blit(); } #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_blit(); } 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_blit(); 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_blit(); while (true) { do { qtouch_process(); diff --git a/src/bootloader/startup.c b/src/bootloader/startup.c index 52959772b0..879bda974b 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_blit(); } } #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..e66fac31b7 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_blit(); 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..b9f2f29eee 100644 --- a/src/rust/bitbox02-rust/src/general/screen.rs +++ b/src/rust/bitbox02-rust/src/general/screen.rs @@ -14,13 +14,14 @@ 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_blit, 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_blit(); delay(duration); } diff --git a/src/rust/bitbox02-sys/build.rs b/src/rust/bitbox02-sys/build.rs index 98af9f82dd..c0e028c99c 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", @@ -114,6 +114,7 @@ const ALLOWLIST_FNS: &[&str] = &[ "memory_get_platform", "memory_get_securechip_type", "memory_spi_get_active_ble_firmware_version", + "oled_blit", "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", @@ -402,6 +404,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 ada97b3899..8959da3264 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..b5769689d5 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_blit() { + unsafe { bitbox02_sys::oled_blit() } } pub fn ug_font_select_9x9() { diff --git a/src/screen.c b/src/screen.c index f0fbecda92..2db1e220c5 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_blit(); #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_blit(); } 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..85b0b81fdc --- /dev/null +++ b/src/ui/canvas.c @@ -0,0 +1,77 @@ +// 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 + +// `canvas_commit` is called from an interrupt in `lock_animation.c` and therefore this must be +// volatile. This is probably not enough to make it completely UB free, but it is something +// ¯\_(ツ)_/¯. Eventually `lock_animation.c` will be refactored so that it doesn't call this +// function. +// TODO: When volatile is removed here, also remove the ignored diagnostics later in this file. +static uint8_t* volatile _canvas_active = NULL; +static uint8_t* volatile _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 << 7 | color << 6 | color << 5 | color << 4 | color << 3 | color << 2 | + color << 1 | color; + memset(canvas_working(), pixels, CANVAS_SIZE); +} + +void canvas_clear(void) +{ + canvas_fill(0); +} + +void canvas_commit(void) +{ +#ifndef TESTING + CRITICAL_SECTION_ENTER() +#endif + uint8_t* volatile _canvas_tmp = _canvas_working; + _canvas_working = _canvas_active; + _canvas_active = _canvas_tmp; +#ifndef TESTING + CRITICAL_SECTION_LEAVE() +#endif + canvas_clear(); +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers" + +uint8_t* canvas_working(void) +{ + ASSERT(_canvas_working); + return _canvas_working; +} + +uint8_t* canvas_active(void) +{ + ASSERT(_canvas_active); + return _canvas_active; +} +#pragma GCC diagnostic pop 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/graphics/lock_animation.c b/src/ui/graphics/lock_animation.c index e3457bcff1..afdfaecce4 100644 --- a/src/ui/graphics/lock_animation.c +++ b/src/ui/graphics/lock_animation.c @@ -20,6 +20,8 @@ #include "graphics.h" #include +#include +#include #include #ifndef TESTING @@ -165,7 +167,7 @@ static void _animation_timer_cb(const struct timer_task* const timer_task) } /* Draw the frame. */ - screen_clear(); + canvas_clear(); position_t pos = { .left = (SCREEN_WIDTH - LOCK_ANIMATION_FRAME_WIDTH) / 2, .top = (SCREEN_HEIGHT - LOCK_ANIMATION_FRAME_HEIGHT) / 2}; @@ -173,7 +175,10 @@ static void _animation_timer_cb(const struct timer_task* const timer_task) in_buffer_t image = { .data = _get_frame(_animation_current_frame), .len = LOCK_ANIMATION_FRAME_SIZE}; graphics_draw_image(&pos, &dim, &image); - UG_SendBuffer(); + // TODO: When this function is refactored away from being called in interrupt context, update + // `_canvas_active` in `canvas.c` to not be volatile. + canvas_commit(); + oled_blit(); _animation_current_frame++; } #endif diff --git a/src/ui/oled/oled.c b/src/ui/oled/oled.c index 713547e457..c1abcee3e9 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 (*blit)(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, + .blit = sh1107_blit, .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.blit = ssd1312_blit; bb02_display.off = ssd1312_off; bb02_display.mirror = ssd1312_mirror; } @@ -120,25 +120,18 @@ void oled_init(void) gpio_set_pin_level(PIN_OLED_RES, 1); delay_us(5); - oled_clear_buffer(); - - bb02_display.configure(_frame_buffer); - - delay_ms(100); - // DC-DC ON gpio_set_pin_level(PIN_OLED_ON, 1); - _enabled = true; -} + delay_ms(100); -void oled_send_buffer(void) -{ - bb02_display.update(); + bb02_display.configure(); + + _enabled = true; } -void oled_clear_buffer(void) +void oled_blit(void) { - memset(_frame_buffer, 0, sizeof(_frame_buffer)); + bb02_display.blit(); } void oled_mirror(bool mirror) @@ -149,7 +142,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..9d7f52a6a7 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_blit(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..c48fac8f6c 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_blit(); 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_blit(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..24edbc4496 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_blit(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..87ae7712f2 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_blit(); 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_blit(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..bc1714a798 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_blit(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 c927574566..1a52c1f8a5 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_blit(); } 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..56d41285e6 --- /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_blit(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) {}