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. 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) {}