diff --git a/changelog.md b/changelog.md index 96e80d2e0..4487071db 100644 --- a/changelog.md +++ b/changelog.md @@ -8,6 +8,7 @@ it and is upstream Adafruit history, kept for provenance. - DFU screens on I2C mono OLEDs (SSD1306 and SH1106), with a four-line layout: title, mode, version, and banner. A board opts in by defining `DISPLAY_PIN_SDA` in its `board.h`; `boards.h` rejects a board that also defines `DISPLAY_PIN_SCK`, since TWIM0 and SPIM0 share a peripheral. The bootloader probes 0x3C and 0x3D and reads the controller type off the panel, and a board with no panel shows nothing and otherwise behaves as before. Enabled on `wiscore_rak4631_board`, `wiscore_rak3401`, `wio_tracker_l1`, `promicro_nrf52840`, `xiao_nrf52840_ble`, and `xiao_nrf52840_ble_sense`, for about 2 KB of bootloader flash each. Verified on the RAK4631 and the Wio Tracker L1; the other four follow their firmware variants' pins. On the RAK4631 and RAK3401, DFU raises the switched 3V3_S rail (WB_IO2) that feeds the WisBlock slots, and lowers it again when no panel answers. - Fixed an out-of-bounds write in `printicon()` on the 160x80 `heltec_t096` and `heltec_t1`, where the third drag icon ran past the end of `frame_buf`. Those boards place it inside the panel, and `printicon()` skips any icon that would land outside. - Fixed BLE OTA started from an application's buttonless DFU falling back to the application after about 10 s. The 0xB1 entry now restarts through the 0xA8 path, so the bootloader initializes the SoftDevice itself; the peer data for reconnection survives the reset. +- The DFU screen shows upload progress during UF2, serial, and BLE transfers, as a bar in place of the banner. The title reads BOOTLOADER during a bootloader update, ERASE during a factory erase, WRONG FILE for a UF2 this board cannot take, and NO FIRMWARE when DFU starts without a valid application. ## OTAFIX 2.5 diff --git a/lib/sdk11/components/libraries/bootloader_dfu/dfu_single_bank.c b/lib/sdk11/components/libraries/bootloader_dfu/dfu_single_bank.c index f8c90ef8f..76487480f 100644 --- a/lib/sdk11/components/libraries/bootloader_dfu/dfu_single_bank.c +++ b/lib/sdk11/components/libraries/bootloader_dfu/dfu_single_bank.c @@ -332,6 +332,7 @@ uint32_t dfu_start_pkt_handle(dfu_update_packet_t * p_packet) if ( DFU_STATE_IDLE != m_dfu_state ) return NRF_ERROR_INVALID_STATE; + if (IS_UPDATING_BL(m_start_packet)) screen_set_title("BOOTLOADER"); m_functions.prepare(m_image_size); return NRF_SUCCESS; @@ -388,6 +389,7 @@ uint32_t dfu_data_pkt_handle(dfu_update_packet_t * p_packet) } m_data_received += data_length; + screen_set_progress(m_data_received, m_image_size); if (m_data_received != m_image_size) { diff --git a/src/boards/boards.h b/src/boards/boards.h index 74b8d8b8a..21c57bbea 100644 --- a/src/boards/boards.h +++ b/src/boards/boards.h @@ -138,6 +138,10 @@ bool board_display_init(void); void board_display_teardown(void); void screen_draw_drag(void); void screen_draw_ble(void); +// Both redraw only once a screen is up. A title replaces the board name; +// NULL restores it. +void screen_set_title(char const* title); +void screen_set_progress(uint32_t done, uint32_t total); #ifdef DISPLAY_MONO // One 8-row page, DISPLAY_WIDTH bytes, LSB = topmost row of the page. @@ -145,6 +149,9 @@ void board_display_draw_page(uint8_t page, uint8_t const* buf, size_t nbytes); #else void board_display_draw_line(uint16_t y, uint8_t const* buf, size_t nbytes); #endif +#else +static inline void screen_set_title(char const* title) { (void) title; } +static inline void screen_set_progress(uint32_t done, uint32_t total) { (void) done; (void) total; } #endif //--------------------------------------------------------------------+ diff --git a/src/main.c b/src/main.c index ed436ecdc..e9fe140db 100644 --- a/src/main.c +++ b/src/main.c @@ -327,6 +327,7 @@ static void check_dfu_mode(void) { // Enter DFU mode accordingly to input if (dfu_start || !valid_app) { + if (!valid_app) screen_set_title("NO FIRMWARE"); if (_ota_dfu) { #ifdef BOARD_HAS_DISPLAY if (board_display_init()) { diff --git a/src/screen.c b/src/screen.c index b8c3d0618..563fc4aab 100644 --- a/src/screen.c +++ b/src/screen.c @@ -287,8 +287,10 @@ static void print(int x, int y, int color, const char* text, int size) { } } -static inline void print_centered(int y, int color, const char* text, int size) { +// Steps the size down until the text fits the panel width. +static void print_centered(int y, int color, const char* text, int size) { int len = (int) strlen(text); + while (size > 1 && TEXT_WIDTH(size, len) > DISPLAY_WIDTH) size--; int text_x = (DISPLAY_WIDTH - TEXT_WIDTH(size, len)) / 2; print(text_x >= 0 ? text_x : 0, y, color, text, size); } @@ -303,11 +305,12 @@ static inline void print_centered(int y, int color, const char* text, int size) // colors stay dark. #define MONO_LIT_MASK ((1u << COLOR_WHITE) | (1u << COLOR_PURPLE)) -static void draw_screen(uint8_t const* fb) { +// Sends only the pages covering rows y0..y1. +static void draw_screen(uint8_t const* fb, int y0, int y1) { // SSD1306/SH1106 GDDRAM is paged: one byte spans 8 rows of a single // column, LSB topmost. frame_buf is already column-major, so each output // byte gathers 8 contiguous bytes of it. - for (int page = 0; page < DISPLAY_HEIGHT / 8; ++page) { + for (int page = y0 / 8; page <= y1 / 8; ++page) { uint8_t line[DISPLAY_WIDTH]; for (int x = 0; x < DISPLAY_WIDTH; ++x) { @@ -327,7 +330,10 @@ static void draw_screen(uint8_t const* fb) { #else -static void draw_screen(uint8_t const* fb) { +// Always sends the whole frame; lines are columns, so rows cannot be cropped. +static void draw_screen(uint8_t const* fb, int y0, int y1) { + (void) y0; + (void) y1; uint8_t const* p = fb; for (int y = 0; y < DISPLAY_WIDTH; ++y) { uint8_t cc[DISPLAY_HEIGHT * 2]; @@ -345,7 +351,7 @@ static void draw_screen(uint8_t const* fb) { #endif // draw color bar -static void drawBar(int y, int h, int color) { +static void drawBar(int y, int h, int color, int w) { // The default bar geometry assumes a 240x135 panel. if (y < 0) { h += y; @@ -358,22 +364,38 @@ static void drawBar(int y, int h, int color) { h = DISPLAY_HEIGHT - y; } - for (int x = 0; x < DISPLAY_WIDTH; ++x) { + for (int x = 0; x < w; ++x) { memset(frame_buf + x * DISPLAY_HEIGHT + y, color, h); } } -// draw drag & drop screen -void screen_draw_drag(void) { - drawBar(SCREEN_BAR1_Y, SCREEN_BAR1_H, COLOR_GREEN); - drawBar(SCREEN_BAR2_Y, SCREEN_BAR2_H, COLOR_BLUE); - drawBar(SCREEN_BAR3_Y, SCREEN_BAR3_H, COLOR_ORANGE); +static char const* _title; // NULL shows DISPLAY_TITLE +static int _pct = -1; +static bool _ble; +static bool _shown; + +#define ALL_ROWS 0, DISPLAY_HEIGHT - 1 + +// Renders the whole frame and sends rows y0..y1 of it. +static void draw(int y0, int y1) { + _shown = true; + drawBar(SCREEN_BAR1_Y, SCREEN_BAR1_H, COLOR_GREEN, DISPLAY_WIDTH); + drawBar(SCREEN_BAR2_Y, SCREEN_BAR2_H, COLOR_BLUE, DISPLAY_WIDTH); + drawBar(SCREEN_BAR3_Y, SCREEN_BAR3_H, COLOR_ORANGE, DISPLAY_WIDTH); - // Print title, version, banner. - print_centered(DISPLAY_TITLE_Y, COLOR_WHITE, DISPLAY_TITLE, FONT_SIZE_LARGE); + print_centered(DISPLAY_TITLE_Y, COLOR_WHITE, _title ? _title : DISPLAY_TITLE, FONT_SIZE_LARGE); print_centered(UF2_VERSION_BASE_Y, COLOR_PURPLE, UF2_VERSION_BASE, 1); - print_centered(BANNER_TEXT_Y, COLOR_WHITE, BANNER_TEXT, 1); + if (_pct < 0) { + print_centered(BANNER_TEXT_Y, COLOR_WHITE, BANNER_TEXT, 1); + } else { + // The banner line becomes the progress bar, over a full-width baseline. + drawBar(BANNER_TEXT_Y + 7, 1, COLOR_WHITE, DISPLAY_WIDTH); + drawBar(BANNER_TEXT_Y, 8, COLOR_WHITE, DISPLAY_WIDTH * _pct / 100); + } + if (_ble) { + print_centered(BLE_OTA_Y, COLOR_WHITE, "BLE OTA", FONT_SIZE_LARGE); + } else { #ifdef DISPLAY_NO_DRAG_ICONS print_centered(DRAG_TEXT_Y, COLOR_WHITE, DRAG_TEXT, FONT_SIZE_LARGE); #else @@ -391,22 +413,34 @@ void screen_draw_drag(void) { print(160, DRAG - 12, COLOR_WHITE, UF2_VOLUME_LABEL, 1); #endif // NOLABELS #endif // DISPLAY_NO_DRAG_ICONS + } - draw_screen(frame_buf); + draw_screen(frame_buf, y0, y1); +} + +void screen_draw_drag(void) { + _ble = false; + draw(ALL_ROWS); } void screen_draw_ble(void) { - drawBar(SCREEN_BAR1_Y, SCREEN_BAR1_H, COLOR_GREEN); - drawBar(SCREEN_BAR2_Y, SCREEN_BAR2_H, COLOR_BLUE); - drawBar(SCREEN_BAR3_Y, SCREEN_BAR3_H, COLOR_ORANGE); + _ble = true; + draw(ALL_ROWS); +} - // Print title, version, OTA, banner - print_centered(DISPLAY_TITLE_Y, COLOR_WHITE, DISPLAY_TITLE, FONT_SIZE_LARGE); - print_centered(UF2_VERSION_BASE_Y, COLOR_PURPLE, UF2_VERSION_BASE, 1); - print_centered(BLE_OTA_Y, COLOR_WHITE, "BLE OTA", FONT_SIZE_LARGE); - print_centered(BANNER_TEXT_Y, COLOR_WHITE, BANNER_TEXT, 1); +void screen_set_title(char const* title) { + if (title == _title) return; + _title = title; + if (_shown) draw(ALL_ROWS); +} - draw_screen(frame_buf); +// Redraws in 5% steps. +void screen_set_progress(uint32_t done, uint32_t total) { + if (!_shown || total == 0 || total == 0xffffffff || done > total) return; + int pct = (int) (done * 100 / total); + if (_pct >= 0 && pct / 5 == _pct / 5) return; + _pct = pct; + draw(BANNER_TEXT_Y, BANNER_TEXT_Y + 7); } #endif diff --git a/src/usb/uf2/ghostfat.c b/src/usb/uf2/ghostfat.c index 8d20d5069..5fe97fc16 100644 --- a/src/usb/uf2/ghostfat.c +++ b/src/usb/uf2/ghostfat.c @@ -35,6 +35,7 @@ #include "bootloader_settings.h" #include "bootloader.h" +#include "boards.h" //--------------------------------------------------------------------+ // @@ -495,6 +496,7 @@ int write_block (uint32_t block_no, uint8_t *data, WriteState *state) */ if ( in_app_space(bl->targetAddr) ) { + screen_set_title(NULL); // a good block after a rejected one clears WRONG FILE PRINTF("Write addr = 0x%08lX, block = %ld (%ld of %ld)\r\n", bl->targetAddr, bl->blockNo, state->numWritten, bl->numBlocks); flash_nrf5x_write(bl->targetAddr, bl->data, bl->payloadSize, true); if ( bl->targetAddr + bl->payloadSize > state->appEnd ) state->appEnd = bl->targetAddr + bl->payloadSize; @@ -505,7 +507,7 @@ int write_block (uint32_t block_no, uint8_t *data, WriteState *state) PRINTF("skip writing to MBR\r\n"); }else { - return -1; + goto reject; } break; @@ -543,6 +545,7 @@ int write_block (uint32_t block_no, uint8_t *data, WriteState *state) PRINTF("addr = 0x%08lX, block = %ld (%ld of %ld)\r\n", bl->targetAddr, bl->blockNo, state->numWritten, bl->numBlocks); state->update_bootloader = true; + screen_set_title("BOOTLOADER"); if ( in_uicr_space(bl->targetAddr) ) { /* UCIR contains bootloader & MBR address as follow: @@ -567,7 +570,7 @@ int write_block (uint32_t block_no, uint8_t *data, WriteState *state) PRINT_HEX(uicr_mbr_param); state->aborted = true; - return -1; + goto reject; } state->has_uicr = true; @@ -599,7 +602,7 @@ int write_block (uint32_t block_no, uint8_t *data, WriteState *state) { PRINTF("DOES NOT match our VID/PID\r\n"); state->aborted = true; - return -1; + goto reject; } } } @@ -623,7 +626,7 @@ int write_block (uint32_t block_no, uint8_t *data, WriteState *state) else { state->aborted = true; - return -1; + goto reject; } break; @@ -632,10 +635,11 @@ int write_block (uint32_t block_no, uint8_t *data, WriteState *state) // msc_uf2.c once the host has acknowledged the write. PRINTF("Factory erase requested\r\n"); state->factory_erase = true; + screen_set_title("ERASE"); break; // unknown family ID - default: return -1; + default: goto reject; } //------------- Update written blocks -------------// @@ -660,6 +664,7 @@ int write_block (uint32_t block_no, uint8_t *data, WriteState *state) { state->writtenMask[pos] |= mask; state->numWritten++; + screen_set_progress(state->numWritten, state->numBlocks); } // flush last blocks @@ -678,4 +683,9 @@ int write_block (uint32_t block_no, uint8_t *data, WriteState *state) } return BPB_SECTOR_SIZE; + + // A UF2 block this board cannot take: another family, board or address. +reject: + screen_set_title("WRONG FILE"); + return -1; }