diff --git a/Makefile b/Makefile index b6e5c9a..9f2f8d9 100644 --- a/Makefile +++ b/Makefile @@ -179,6 +179,7 @@ CSRC = $(STARTUPSRC) \ FatFs/ffunicode.c \ usbcfg.c \ usb_msc.c \ + fw_update.c \ NANOVNA_STM32_F303/adc.c \ main.c plot.c ui.c ili9341.c tlv320aic3204.c si5351.c numfont20x22.c Font5x7.c Font10x14.c flash.c si4468.c Font7x13b.c rtc.c else diff --git a/README.md b/README.md index 6d14c70..5159ee4 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,18 @@ Or simply use make. $ make flash +### Update from the SD card (tinySA Ultra) + +The tinySA Ultra can install a new firmware from its SD card, without DFU: + +1. Copy the firmware `.bin` file to the root folder of the SD card, for example with STORAGE→USB DISK. Eject the drive when the copy is done. +2. Select STORAGE→UPDATE FIRMWARE, pick the file and confirm with UPDATE. +3. The tinySA checks the file, programs it in about 10 seconds and restarts. Do not switch it off or remove the SD card during the update. If the card is removed anyway, the update waits and continues when it is inserted again. + +Settings, calibration and presets are kept. The shell command `fwupdate {file}` checks a file without changing anything, `fwupdate {file} flash` installs it. + +Only firmware that contains the update header (from this version on) can be installed from the SD card; older versions and the tinySA basic firmware are refused. If an update is interrupted, the tinySA does not start: hold the jog button while switching on to enter DFU mode and flash the firmware with a PC as above. + ## Companion Tools There are several numbers of great companion PC tools from third-party. diff --git a/STM32F303xC.ld b/STM32F303xC.ld index 76c838b..edd6c34 100644 --- a/STM32F303xC.ld +++ b/STM32F303xC.ld @@ -84,13 +84,101 @@ REGION_ALIAS("BSS_RAM", ram0); /* RAM region to be used for the default heap.*/ REGION_ALIAS("HEAP_RAM", ram0); -/* Generic rules inclusion.*/ -INCLUDE rules.ld +/* Generic rules inclusion. The code rules of rules.ld (rules_code.ld) are inlined + below so that the firmware image header (.fwinfo) directly follows the vectors.*/ +INCLUDE rules_stacks.ld + +ENTRY(Reset_Handler) SECTIONS { + vectors : ALIGN(16) SUBALIGN(16) + { + KEEP(*(.vectors)) + } > VECTORS_FLASH AT > VECTORS_FLASH_LMA + + /* Firmware image header (fw_update.c), used by the firmware update from the SD card */ + .fwinfo : ALIGN(4) + { + KEEP(*(.fwinfo)) + } > VECTORS_FLASH AT > VECTORS_FLASH_LMA + + xtors : ALIGN(4) SUBALIGN(4) + { + __init_array_start = .; + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array)) + __init_array_end = .; + __fini_array_start = .; + KEEP(*(.fini_array)) + KEEP(*(SORT(.fini_array.*))) + __fini_array_end = .; + } > XTORS_FLASH AT > XTORS_FLASH_LMA + + .text : ALIGN(4) SUBALIGN(4) + { + *(.text) + *(.text.*) + *(.glue_7t) + *(.glue_7) + *(.gcc*) + } > TEXT_FLASH AT > TEXT_FLASH_LMA + + .rodata : ALIGN(4) + { + . = ALIGN(4); + __rodata_base__ = .; + *(.rodata) + *(.rodata.*) + . = ALIGN(4); + __rodata_end__ = .; + } > RODATA_FLASH AT > RODATA_FLASH_LMA + + .ARM.extab : + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > VARIOUS_FLASH AT > VARIOUS_FLASH_LMA + + .ARM.exidx : { + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + } > VARIOUS_FLASH AT > VARIOUS_FLASH_LMA + + .eh_frame_hdr : + { + *(.eh_frame_hdr) + } > VARIOUS_FLASH AT > VARIOUS_FLASH_LMA + + .eh_frame : ONLY_IF_RO + { + *(.eh_frame) + } > VARIOUS_FLASH AT > VARIOUS_FLASH_LMA +} + +INCLUDE rules_data.ld + +SECTIONS +{ + /* Firmware image trailer (fw_update.h fw_trailer_t), must be the last data in flash: + end magic "FEND", image size, CRC32 (0xFFFFFFFF = not set) */ + .fwtrailer : ALIGN(4) + { + LONG(0x444E4546) + LONG(__fw_image_size__) + LONG(0xFFFFFFFF) + __fw_image_end__ = .; + } > flash0 + .calsave (NOLOAD) : ALIGN(4) { *(.calsave) } > CALDATA_FLASH } + +/* Size of the firmware image (.bin file) including the trailer */ +__fw_image_size__ = __fw_image_end__ - ORIGIN(flash0); + +ASSERT(ADDR(.fwinfo) == ORIGIN(flash0) + SIZEOF(vectors), "firmware header must follow the vectors") +ASSERT(LOADADDR(.fwtrailer) >= LOADADDR(.data) + SIZEOF(.data), "firmware trailer must be the last data in flash") +ASSERT(LOADADDR(.fwtrailer) >= LOADADDR(.ram4_init) + SIZEOF(.ram4_init), "firmware trailer must be the last data in flash") diff --git a/fw_update.c b/fw_update.c new file mode 100644 index 0000000..0d51c52 --- /dev/null +++ b/fw_update.c @@ -0,0 +1,818 @@ +/* + * Firmware update from the SD card (tinySA ULTRA), see fw_update.h + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * The software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include "ch.h" +#include "hal.h" +#include "nanovna.h" +#include "spi.h" +#include "fw_update.h" +#include + +#ifdef __SD_FIRMWARE_UPDATE__ + +#ifdef __WATCHDOG__ +#error "The firmware update runs about 10 s with interrupts off and does not refresh the IWDG" +#endif + +//***************************************************************************** +// Image header of this firmware, placed after the vector table by the linker script +//***************************************************************************** +extern const char __fw_image_size__[]; // linker script symbol, its address is the image size + +_Static_assert(sizeof(VERSION) <= sizeof(((fw_header_t *)0)->version), "VERSION too long for fw_header_t"); + +__attribute__((used, section(".fwinfo"))) +const fw_header_t fw_header = { + .magic = {FW_HEADER_MAGIC0, FW_HEADER_MAGIC1}, + .hdr_version = FW_HEADER_VERSION, + .hdr_size = sizeof(fw_header_t), + .model = FW_MODEL_TINYSA4, + .load_addr = FLASH_START_ADDRESS, + .image_size = (uint32_t)__fw_image_size__, + .flags = 0, + .version = VERSION, +}; + +//***************************************************************************** +// Limits and layout +//***************************************************************************** +#define FWU_APP_START FLASH_START_ADDRESS +#define FWU_APP_END SAVE_CONFIG_ADDR // never erase or program at or above this address +#define FWU_MAX_PAGES ((FWU_APP_END - FWU_APP_START) / FLASH_PAGESIZE) +#define FWU_MAX_EXTENTS 64 // contiguous sector runs of the image file +#define FWU_SECTOR_SIZE 512 +#define FWU_RAM_START 0x20000000U +#define FWU_RAM_END (0x20000000U + 40*1024) +#define FWU_MIN_BATTERY_MV 3500 +#define FWU_PAGE_BUF ((uint8_t *)spi_buffer) // 2 KB page buffer in SRAM (4 KB spi_buffer) +#define FWU_PAGE0_BUF ((uint8_t *)spi_buffer + FLASH_PAGESIZE) // page 0, read before the erase starts + +// Progress bar on the update screen, drawn by the CCM code +#define FWU_BAR_X 40 +#define FWU_BAR_Y 130 +#define FWU_BAR_W 400 +#define FWU_BAR_H 24 +#define FWU_COLOR_BAR_BG RGB565( 64, 64, 64) +#define FWU_COLOR_WRITE RGB565( 0,128,255) +#define FWU_COLOR_RETRY RGB565(255,255, 0) +#define FWU_COLOR_NOCARD RGB565(255, 0,255) +#define FWU_COLOR_ERROR RGB565(255, 0, 0) +#define FWU_COLOR_DONE RGB565( 0,255, 0) + +//***************************************************************************** +// CCM RAM code and data +// The flasher runs from CCM RAM with interrupts off while the application flash is erased. +// Rules for FWU_TEXT functions: no calls into flash (library functions, memcpy/memset, struct +// copies, string literals, switch tables, OS or HAL functions), only register access. +// .ram4_init.* is copied from flash to CCM at boot by crt1 (__init_ram_areas). +//***************************************************************************** +#define FWU_TEXT __attribute__((section(".ram4_init.fwu_text"), noinline)) +#define FWU_BSS __attribute__((section(".ram4.fwu_bss"))) +#define FWU_INLINE static inline __attribute__((always_inline)) + +typedef struct { + uint32_t lba; // first card sector + uint32_t count; // number of sectors +} fwu_extent_t; + +typedef struct { + uint32_t image_size; // bytes to program + uint32_t n_pages; // flash pages to program + uint32_t image_crc; // CRC32 of the whole image + uint32_t n_ext; // used entries in ext[] + uint32_t byte_addr; // SDSC card: command argument is a byte address + uint32_t bar_w; // width of the progress bar already drawn in bar_color + uint32_t bar_color; + uint32_t page_crc[FWU_MAX_PAGES]; // CRC32 of the image bytes in each flash page + fwu_extent_t ext[FWU_MAX_EXTENTS]; // file sectors -> card sectors +} fwu_job_t; + +FWU_BSS static fwu_job_t fwu_job; +FWU_BSS static uint32_t fwu_vtor[16] __attribute__((aligned(128))); // exception vectors in CCM + +// CCM functions called from flash code +__attribute__((long_call)) uint32_t fwu_crc32(uint32_t crc, const void *data, uint32_t len); +__attribute__((long_call)) static int fwu_dry_run(void); +__attribute__((long_call, noreturn)) static void fwu_run(void); + +// Internal results of the CCM code +#define FWU_R_OK 0 +#define FWU_R_NOCARD 1 +#define FWU_R_READ 2 +#define FWU_R_CRC 3 +#define FWU_R_FLASH 4 +#define FWU_R_VERIFY 5 + +// TIM2 is the free running 32 bit ChibiOS system timer (10 kHz), it runs with interrupts off +#define FWU_NOW() (STM32_ST_TIM->CNT) +#define FWU_TICKS(ms) ((uint32_t)(ms) * (CH_CFG_ST_FREQUENCY / 1000)) +#define FWU_DSB() __asm volatile ("dsb 0xF" ::: "memory") +#define FWU_ISB() __asm volatile ("isb 0xF" ::: "memory") + +#define FWU_SD_CS_LOW() palClearLine(LINE_SD_CS) +#define FWU_SD_CS_HIGH() palSetLine(LINE_SD_CS) +#define FWU_LCD_CS_LOW() palClearPad(GPIO_LCD_CS_PORT, GPIO_LCD_CS) +#define FWU_LCD_CS_HIGH() palSetPad(GPIO_LCD_CS_PORT, GPIO_LCD_CS) +#define FWU_LCD_DC_CMD() palClearPad(GPIO_LCD_CD_PORT, GPIO_LCD_CD) +#define FWU_LCD_DC_DATA() palSetPad(GPIO_LCD_CD_PORT, GPIO_LCD_CD) +#define FWU_CARD_PRESENT() ((palReadPort(GPIOB) & (1U << GPIOB_SD_CD)) == 0) +#define FWU_JOG_PUSHED() ((palReadPort(GPIOA) & (1U << GPIOA_PUSH)) != 0) + +// CRC32 as zlib, chainable: crc = fwu_crc32(crc, next data, length), start with 0 +FWU_TEXT uint32_t fwu_crc32(uint32_t crc, const void *data, uint32_t len) { + const uint8_t *p = data; + crc = ~crc; + while (len--) { + crc ^= *p++; + for (int i = 0; i < 8; i++) + crc = (crc >> 1) ^ (0xEDB88320U & -(crc & 1)); + } + return ~crc; +} + +FWU_TEXT static void fwu_delay(uint32_t ms) { + uint32_t t0 = FWU_NOW(); + while (FWU_NOW() - t0 < FWU_TICKS(ms)) + ; +} + +// SPI1 byte exchange, returns after the byte is fully transferred (RX FIFO stays empty) +FWU_INLINE uint8_t fwu_spi_xfer(uint8_t b) { + while ((SPI1->SR & SPI_SR_TXE) == 0) + ; + *(volatile uint8_t *)&SPI1->DR = b; + while ((SPI1->SR & SPI_SR_RXNE) == 0) + ; + return *(volatile uint8_t *)&SPI1->DR; +} + +// Wait until SPI1 is idle, drop received data and clear an overrun +FWU_INLINE void fwu_spi_flush(void) { + while (SPI1->SR & (SPI_SR_FTLVL | SPI_SR_BSY)) + ; + while (SPI1->SR & SPI_SR_RXNE) + (void)*(volatile uint8_t *)&SPI1->DR; + (void)SPI1->SR; +} + +//***************************************************************************** +// LCD progress bar (same SPI speed as the SD card, only the chip selects change) +//***************************************************************************** +FWU_INLINE void fwu_lcd_command(uint8_t cmd) { + FWU_LCD_DC_CMD(); + fwu_spi_xfer(cmd); + FWU_LCD_DC_DATA(); +} + +FWU_INLINE void fwu_lcd_data16(uint32_t v) { + fwu_spi_xfer(v >> 8); + fwu_spi_xfer(v); +} + +// color is a pixel_t value as in the palette (sent low byte first) +FWU_TEXT static void fwu_lcd_fill(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t color) { + fwu_spi_flush(); + FWU_SD_CS_HIGH(); + FWU_LCD_CS_LOW(); + fwu_lcd_command(0x2A); // column address set + fwu_lcd_data16(x); + fwu_lcd_data16(x + w - 1); + fwu_lcd_command(0x2B); // page address set + fwu_lcd_data16(y); + fwu_lcd_data16(y + h - 1); + fwu_lcd_command(0x2C); // memory write + for (uint32_t n = w * h; n; n--) { + fwu_spi_xfer(color); + fwu_spi_xfer(color >> 8); + } + FWU_LCD_CS_HIGH(); +} + +// Show done pages of all pages in color +FWU_TEXT static void fwu_bar(uint32_t done, uint32_t color) { + uint32_t w = FWU_BAR_W * done / fwu_job.n_pages; + if (color != fwu_job.bar_color || w < fwu_job.bar_w) { + fwu_job.bar_w = 0; // redraw all + fwu_job.bar_color = color; + fwu_lcd_fill(FWU_BAR_X, FWU_BAR_Y, FWU_BAR_W, FWU_BAR_H, FWU_COLOR_BAR_BG); + } + if (w > fwu_job.bar_w) + fwu_lcd_fill(FWU_BAR_X + fwu_job.bar_w, FWU_BAR_Y, w - fwu_job.bar_w, FWU_BAR_H, color); + fwu_job.bar_w = w; +} + +//***************************************************************************** +// SD card sector read, polled SPI. The firmware initializes the card before the update, +// fwu_sd_init() does it again for a card that was removed or disturbed during the update. +//***************************************************************************** +#define FWU_CMD0 (0x40 + 0) // GO_IDLE_STATE +#define FWU_CMD1 (0x40 + 1) // SEND_OP_COND (MMC) +#define FWU_CMD8 (0x40 + 8) // SEND_IF_COND +#define FWU_CMD16 (0x40 + 16) // SET_BLOCKLEN +#define FWU_CMD17 (0x40 + 17) // READ_SINGLE_BLOCK +#define FWU_CMD55 (0x40 + 55) // APP_CMD +#define FWU_CMD58 (0x40 + 58) // READ_OCR +#define FWU_ACMD41 (0x40 + 41) // SD_SEND_OP_COND +#define FWU_SD_SPEED SPI_BR_DIV2 // as SD_SPI_SPEED and LCD_SPI_SPEED in ili9341.c +#define FWU_SD_INIT_SPEED SPI_BR_DIV256 // as SD_INIT_SPI_SPEED in ili9341.c + +FWU_TEXT static bool fwu_sd_wait_ready(uint32_t ms) { + uint32_t t0 = FWU_NOW(); + do { + if (fwu_spi_xfer(0xFF) == 0xFF) + return true; + } while (FWU_NOW() - t0 < FWU_TICKS(ms)); + return false; +} + +FWU_TEXT static uint8_t fwu_sd_cmd(uint8_t cmd, uint32_t arg) { + if (!fwu_sd_wait_ready(500)) + return 0xFF; + fwu_spi_xfer(cmd); + fwu_spi_xfer(arg >> 24); + fwu_spi_xfer(arg >> 16); + fwu_spi_xfer(arg >> 8); + fwu_spi_xfer(arg); + fwu_spi_xfer(cmd == FWU_CMD0 ? 0x95 : cmd == FWU_CMD8 ? 0x87 : 0x01); // CRC is checked for CMD0 and CMD8 only + uint8_t r1; + int n = 100; + while (((r1 = fwu_spi_xfer(0xFF)) & 0x80) && --n) + ; + return r1; +} + +// 32 bit response after R1 (R7 of CMD8, OCR of CMD58), most significant byte first +FWU_TEXT static uint32_t fwu_sd_rx32(void) { + uint32_t v = 0; + for (int i = 0; i < 4; i++) + v = (v << 8) | fwu_spi_xfer(0xFF); + return v; +} + +// Card initialization, same sequence as disk_initialize() in ili9341.c. Sets fwu_job.byte_addr. +FWU_TEXT static bool fwu_sd_init(void) { + bool ok = false, byte_addr = true; + fwu_spi_flush(); + FWU_LCD_CS_HIGH(); + FWU_SD_CS_HIGH(); + SPI_BR_SET(SPI1, FWU_SD_INIT_SPEED); + for (int i = 0; i < 10; i++) // 80 clocks with the card not selected + fwu_spi_xfer(0xFF); + FWU_SD_CS_LOW(); + if (fwu_sd_cmd(FWU_CMD0, 0) == 0x01) { + uint32_t t0 = FWU_NOW(); + bool ready = false; + if (fwu_sd_cmd(FWU_CMD8, 0x1AA) == 0x01) { + // SDv2: accepts 2.7-3.6 V, wait up to 1 s until it leaves the idle state, then read the CCS bit + if (fwu_sd_rx32() == 0x1AA) { + do { + if (fwu_sd_cmd(FWU_CMD55, 0) <= 1 && fwu_sd_cmd(FWU_ACMD41, 0x40000000) == 0) { + ready = true; + break; + } + fwu_delay(10); + } while (FWU_NOW() - t0 < FWU_TICKS(1000)); + if (ready && fwu_sd_cmd(FWU_CMD58, 0) == 0) { + byte_addr = (fwu_sd_rx32() & 0x40000000) == 0; + ok = true; + } + } + } else { + // SDv1 or MMC, byte addresses with a 512 byte block length + bool sd1 = fwu_sd_cmd(FWU_CMD55, 0) <= 1 && fwu_sd_cmd(FWU_ACMD41, 0) <= 1; + do { + if (sd1 ? (fwu_sd_cmd(FWU_CMD55, 0) <= 1 && fwu_sd_cmd(FWU_ACMD41, 0) == 0) : fwu_sd_cmd(FWU_CMD1, 0) == 0) { + ready = true; + break; + } + fwu_delay(10); + } while (FWU_NOW() - t0 < FWU_TICKS(1000)); + ok = ready && fwu_sd_cmd(FWU_CMD16, FWU_SECTOR_SIZE) == 0; + } + } + FWU_SD_CS_HIGH(); + fwu_spi_xfer(0xFF); + SPI_BR_SET(SPI1, FWU_SD_SPEED); + if (ok) + fwu_job.byte_addr = byte_addr; + return ok; +} + +FWU_TEXT static bool fwu_sd_read_sector(uint32_t lba, uint8_t *dst) { + bool ok = false; + fwu_spi_flush(); + FWU_LCD_CS_HIGH(); + FWU_SD_CS_LOW(); + if (fwu_sd_cmd(FWU_CMD17, fwu_job.byte_addr ? lba * FWU_SECTOR_SIZE : lba) == 0) { + uint32_t t0 = FWU_NOW(); + uint8_t token; + do { + token = fwu_spi_xfer(0xFF); + } while (token == 0xFF && FWU_NOW() - t0 < FWU_TICKS(200)); + if (token == 0xFE) { // start block token + for (uint32_t i = 0; i < FWU_SECTOR_SIZE; i++) + dst[i] = fwu_spi_xfer(0xFF); + fwu_spi_xfer(0xFF); // CRC, not checked + fwu_spi_xfer(0xFF); + ok = true; + } + } + FWU_SD_CS_HIGH(); + fwu_spi_xfer(0xFF); // one byte after CS up + return ok; +} + +// Card sector of file sector s +FWU_TEXT static uint32_t fwu_lba(uint32_t s) { + for (uint32_t i = 0; i < fwu_job.n_ext; i++) { + if (s < fwu_job.ext[i].count) + return fwu_job.ext[i].lba + s; + s -= fwu_job.ext[i].count; + } + return 0xFFFFFFFFU; +} + +// The card counts as missing only when the detect switch reads "no card" for 250 ms without +// interruption, a short glitch on the detect line is ignored +FWU_TEXT static bool fwu_card_missing(void) { + uint32_t t0 = FWU_NOW(); + while (!FWU_CARD_PRESENT()) + if (FWU_NOW() - t0 >= FWU_TICKS(250)) + return true; + return false; +} + +// Read image page p from the card into buf, pad with 0xFF, check the page CRC +FWU_TEXT static int fwu_read_page(uint32_t p, uint8_t *buf) { + uint32_t off = p * FLASH_PAGESIZE; + uint32_t len = fwu_job.image_size - off; + if (len > FLASH_PAGESIZE) + len = FLASH_PAGESIZE; + for (uint32_t s = 0; s * FWU_SECTOR_SIZE < len; s++) { + uint32_t lba = fwu_lba(off / FWU_SECTOR_SIZE + s); + int tries = 3; + while (!fwu_sd_read_sector(lba, buf + s * FWU_SECTOR_SIZE)) { + if (fwu_card_missing()) + return FWU_R_NOCARD; + if (--tries == 0) + return FWU_R_READ; + } + } + for (uint32_t i = len; i < FLASH_PAGESIZE; i++) + buf[i] = 0xFF; + if (fwu_crc32(0, buf, len) != fwu_job.page_crc[p]) + return FWU_R_CRC; + return FWU_R_OK; +} + +// Read all pages with the raw sector reader while the firmware still runs +FWU_TEXT static int fwu_dry_run(void) { + for (uint32_t p = 0; p < fwu_job.n_pages; p++) { + int r = fwu_read_page(p, FWU_PAGE_BUF); + if (r != FWU_R_OK) + return r; + } + return FWU_R_OK; +} + +//***************************************************************************** +// Flash erase / program, never outside FWU_APP_START..FWU_APP_END +//***************************************************************************** +FWU_TEXT static int fwu_flash_wait(uint32_t ms) { + uint32_t t0 = FWU_NOW(); + while (FLASH->SR & FLASH_SR_BSY) + if (FWU_NOW() - t0 > FWU_TICKS(ms)) + return FWU_R_FLASH; + uint32_t sr = FLASH->SR; + FLASH->SR = FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPERR; // write 1 to clear + return (sr & (FLASH_SR_PGERR | FLASH_SR_WRPERR)) ? FWU_R_FLASH : FWU_R_OK; +} + +FWU_TEXT static int fwu_flash_erase(uint32_t addr) { + if (addr < FWU_APP_START || addr >= FWU_APP_END) + return FWU_R_FLASH; + FLASH->CR |= FLASH_CR_PER; + FLASH->AR = addr; + FLASH->CR |= FLASH_CR_STRT; + int r = fwu_flash_wait(100); + FLASH->CR &= ~FLASH_CR_PER; + return r; +} + +FWU_TEXT static int fwu_flash_program(uint32_t addr, const uint16_t *src) { + if (addr < FWU_APP_START || addr + FLASH_PAGESIZE > FWU_APP_END) + return FWU_R_FLASH; + int r = FWU_R_OK; + volatile uint16_t *dst = (volatile uint16_t *)addr; + FLASH->CR |= FLASH_CR_PG; + for (uint32_t i = 0; i < FLASH_PAGESIZE / 2 && r == FWU_R_OK; i++) { + if (src[i] == 0xFFFF) // erased value + continue; + dst[i] = src[i]; + r = fwu_flash_wait(10); + } + FLASH->CR &= ~FLASH_CR_PG; + return r; +} + +FWU_TEXT static bool fwu_flash_equal(uint32_t addr, const uint32_t *src) { + const volatile uint32_t *f = (const volatile uint32_t *)addr; + for (uint32_t i = 0; i < FLASH_PAGESIZE / 4; i++) + if (f[i] != src[i]) + return false; + return true; +} + +//***************************************************************************** +// Update sequence +//***************************************************************************** +// Reset into the firmware. With the jog button pressed the reset would start the ROM DFU bootloader (BOOT0). +FWU_TEXT __attribute__((noreturn)) static void fwu_reset(void) { + while (FWU_JOG_PUSHED()) + ; + fwu_delay(50); + FWU_DSB(); + SCB->AIRCR = (0x5FAUL << SCB_AIRCR_VECTKEY_Pos) | (SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) | SCB_AIRCR_SYSRESETREQ_Msk; + FWU_DSB(); + while (true) + ; +} + +// Any exception after the flash erase started ends here: red bar, the unit needs DFU +FWU_TEXT static void fwu_fault(void) { + fwu_job.bar_color = 0; + fwu_bar(fwu_job.n_pages, FWU_COLOR_ERROR); + while (true) + ; +} + +// Pause before the next attempt, no input needed: a missing card (magenta) is waited for, +// other errors are retried after a pause, yellow at first, red when they keep failing. +// A card that was removed, or keeps failing, is initialized again. +FWU_TEXT static void fwu_retry_wait(int r, uint32_t done, uint32_t attempt) { + if (r == FWU_R_NOCARD) { + fwu_bar(fwu_job.n_pages, FWU_COLOR_NOCARD); + uint32_t t0 = FWU_NOW(); + while (FWU_NOW() - t0 < FWU_TICKS(1000)) // card back for 1 s without interruption + if (!FWU_CARD_PRESENT()) + t0 = FWU_NOW(); + } else if (attempt < 3) { + fwu_bar(done, FWU_COLOR_RETRY); + fwu_delay(100); + } else { + fwu_bar(fwu_job.n_pages, FWU_COLOR_ERROR); + fwu_delay(1000); + } + if (r == FWU_R_NOCARD || ((r == FWU_R_READ || r == FWU_R_CRC) && attempt >= 1)) + fwu_sd_init(); +} + +// Erase, program and verify one page, retry until it succeeds. Pages 1.. are read from the +// card, page 0 is in FWU_PAGE0_BUF since the start of the update. +FWU_TEXT static void fwu_update_page(uint32_t p) { + uint32_t addr = FWU_APP_START + p * FLASH_PAGESIZE; + uint8_t *buf = p == 0 ? FWU_PAGE0_BUF : FWU_PAGE_BUF; + for (uint32_t attempt = 0; ; attempt++) { + int r = p == 0 ? FWU_R_OK : fwu_read_page(p, buf); + if (r == FWU_R_OK) { + if (p != 0 && fwu_flash_equal(addr, (const uint32_t *)buf)) + return; // already programmed + r = fwu_flash_erase(addr); + if (r == FWU_R_OK) + r = fwu_flash_program(addr, (const uint16_t *)buf); + if (r == FWU_R_OK && !fwu_flash_equal(addr, (const uint32_t *)buf)) + r = FWU_R_VERIFY; + if (r == FWU_R_OK) + return; + } + fwu_retry_wait(r, p, attempt); + } +} + +// CRC of the image as it will be after page 0 is programmed: page 0 from FWU_PAGE0_BUF, the rest from flash +FWU_TEXT static uint32_t fwu_final_crc(void) { + uint32_t len0 = fwu_job.image_size < FLASH_PAGESIZE ? fwu_job.image_size : FLASH_PAGESIZE; + uint32_t crc = fwu_crc32(0, FWU_PAGE0_BUF, len0); + return fwu_crc32(crc, (const void *)(FWU_APP_START + len0), fwu_job.image_size - len0); +} + +// Entered with interrupts off, never returns +FWU_TEXT static void fwu_run(void) { + // Exceptions go to CCM, the vector table in flash page 0 is erased first + for (int i = 0; i < 16; i++) + fwu_vtor[i] = (uint32_t)fwu_fault; + SCB->VTOR = (uint32_t)fwu_vtor; + FWU_DSB(); + FWU_ISB(); + fwu_spi_flush(); + fwu_job.bar_w = 0; + fwu_job.bar_color = 0; + fwu_bar(0, FWU_COLOR_WRITE); + // Page 0 goes to RAM first, so the end of the update does not need the card. Nothing is + // erased yet: when the card cannot be read the old firmware is started again. + for (uint32_t attempt = 0; fwu_read_page(0, FWU_PAGE0_BUF) != FWU_R_OK; attempt++) { + if (attempt >= 10) { + fwu_bar(fwu_job.n_pages, FWU_COLOR_ERROR); + fwu_delay(2000); + fwu_reset(); + } + fwu_delay(200); + if (attempt >= 1) + fwu_sd_init(); + } + // Unlock the flash, the firmware does not lock it after saving the config + if (FLASH->CR & FLASH_CR_LOCK) { + FLASH->KEYR = FLASH_KEY1; + FLASH->KEYR = FLASH_KEY2; + } + if (FLASH->CR & FLASH_CR_LOCK) + fwu_reset(); // nothing erased yet, restart the old firmware + FLASH->SR = FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPERR; + FLASH->CR &= ~(FLASH_CR_PG | FLASH_CR_PER); + // Erase the vector table first: an interrupted update then leaves a firmware that does not + // start (DFU needed) instead of a mix of old and new code + for (uint32_t attempt = 0; fwu_flash_erase(FWU_APP_START) != FWU_R_OK; attempt++) + fwu_retry_wait(FWU_R_FLASH, 0, attempt); + for (uint32_t pass = 0; ; pass++) { + for (uint32_t p = 1; p < fwu_job.n_pages; p++) { + fwu_update_page(p); + fwu_bar(p, FWU_COLOR_WRITE); + } + // Check the complete image before the vector table is written + if (fwu_final_crc() == fwu_job.image_crc) + break; + fwu_retry_wait(FWU_R_VERIFY, 0, pass); // check all pages again, unchanged pages are skipped + } + fwu_update_page(0); + FLASH->CR |= FLASH_CR_LOCK; + fwu_bar(fwu_job.n_pages, FWU_COLOR_DONE); + fwu_delay(500); + fwu_reset(); +} + +//***************************************************************************** +// Flash resident part, runs in the sweep thread +//***************************************************************************** +extern bool dirty; +volatile bool fwu_active = false; +static uint8_t fwu_saved_auto_capture; + +static const char * const fwu_errors[FWU_E_COUNT] = { + [FWU_OK] = "OK", + [FWU_E_BUSY] = "busy, try again", + [FWU_E_BATTERY] = "battery too low, charge first", + [FWU_E_NOCARD] = "no SD card or card error", + [FWU_E_OPEN] = "file not found", + [FWU_E_READ] = "file read error", + [FWU_E_FORMAT] = "not a firmware file for SD update", + [FWU_E_MODEL] = "firmware for another model", + [FWU_E_SIZE] = "image size error", + [FWU_E_VECTOR] = "vector table error", + [FWU_E_TRAILER] = "file incomplete", + [FWU_E_CRC] = "CRC error", + [FWU_E_FRAG] = "file too fragmented, copy it again", + [FWU_E_DRYRUN] = "SD card sector read error", +}; + +const char *fwu_error_text(int err) { + return (err >= 0 && err < FWU_E_COUNT) ? fwu_errors[err] : "error"; +} + +// Take the SD card and SPI1 for the update. Call fwu_end() afterwards, also on error. +int fwu_begin(void) { + chSysLock(); + fwu_active = true; // the shell refuses all commands from now on + chSysUnlock(); + fwu_saved_auto_capture = auto_capture; + auto_capture = false; // no screen copies over USB + systime_t start = chVTGetSystemTimeX(); + while (shell_direct_cmd && chVTGetSystemTimeX() - start < MS2ST(2000)) + chThdSleepMilliseconds(5); + if (shell_direct_cmd) + return FWU_E_BUSY; + int16_t vbat = adc_vbat_read(); + if (vbat > 0 && vbat < FWU_MIN_BATTERY_MV) + return FWU_E_BATTERY; + if (!SD_Inserted()) + return FWU_E_NOCARD; +#ifdef __MCU_CLOCK_SHIFT__ + clock_at_48MHz(); // flash timing and USB need the nominal clock +#endif + SD_PowerOff(); // full card initialization on the next mount + return FWU_OK; +} + +void fwu_end(void) { + SD_PowerOff(); + auto_capture = fwu_saved_auto_capture; + dirty = true; // restore MCU clock and hardware settings on the next sweep + fwu_active = false; +} + +// Read and check the header and vector table of an image file +int fwu_read_header(const char *path, fw_header_t *hdr) { + uint32_t *w = (uint32_t *)spi_buffer; + UINT br; + if (f_mount(fs_volume, "", 1) != FR_OK) + return FWU_E_NOCARD; + if (f_open(fs_file, path, FA_READ) != FR_OK) + return FWU_E_OPEN; + FSIZE_t fsize = f_size(fs_file); + FRESULT res = f_read(fs_file, w, FWU_SECTOR_SIZE, &br); + f_close(fs_file); + if (res != FR_OK) + return FWU_E_READ; + // The header follows the vector table (first 16 system vectors at least) + uint32_t hofs = 0; + for (uint32_t o = 16 * 4; o + sizeof(fw_header_t) <= br; o += 4) + if (w[o / 4] == FW_HEADER_MAGIC0 && w[o / 4 + 1] == FW_HEADER_MAGIC1) { + hofs = o; + break; + } + if (hofs == 0) + return FWU_E_FORMAT; + memcpy(hdr, &w[hofs / 4], sizeof(fw_header_t)); + hdr->version[sizeof(hdr->version) - 1] = 0; + if (hdr->hdr_version < 1 || hdr->hdr_size < sizeof(fw_header_t) || hdr->load_addr != FWU_APP_START) + return FWU_E_FORMAT; + if (hdr->model != FW_MODEL_TINYSA4) + return FWU_E_MODEL; + uint32_t size = hdr->image_size; + if ((size & 3) || size < hofs + hdr->hdr_size + sizeof(fw_trailer_t) || + size > FWU_APP_END - FWU_APP_START || size > fsize) + return FWU_E_SIZE; + uint32_t sp = w[0], reset = w[1]; + if (sp <= FWU_RAM_START || sp > FWU_RAM_END || !(reset & 1) || + reset < FWU_APP_START + hofs + hdr->hdr_size || reset >= FWU_APP_START + size) + return FWU_E_VECTOR; + return FWU_OK; +} + +// Read the whole image: page CRCs, image CRC, trailer check and the file sector map. +// Then read it once more with the raw sector reader used during the update. +int fwu_prepare(const char *path, const fw_header_t *hdr) { + fwu_job_t *j = &fwu_job; + uint8_t *buf = (uint8_t *)spi_buffer; // SRAM, FatFs reads with DMA + const uint32_t size = hdr->image_size; + const uint32_t trailer_ofs = size - sizeof(fw_trailer_t); + fw_trailer_t trailer; + uint32_t crc = 0, body_crc = 0; + UINT br; + int err = FWU_OK; + + if (f_mount(fs_volume, "", 1) != FR_OK) + return FWU_E_NOCARD; + FIL *fp = fs_file; + if (f_open(fp, path, FA_READ) != FR_OK) + return FWU_E_OPEN; + FATFS *fs = fp->obj.fs; + j->image_size = size; + j->n_pages = (size + FLASH_PAGESIZE - 1) / FLASH_PAGESIZE; + j->n_ext = 0; + j->byte_addr = !SD_BlockAddressing(); + memset(&trailer, 0, sizeof(trailer)); + // Read 512 bytes at sector aligned offsets: f_read then updates fp->clust for every sector + for (uint32_t off = 0; off < size; off += FWU_SECTOR_SIZE) { + uint32_t n = size - off; + if (n > FWU_SECTOR_SIZE) + n = FWU_SECTOR_SIZE; + if (f_read(fp, buf, FWU_SECTOR_SIZE, &br) != FR_OK || br < n) { + err = FWU_E_READ; + break; + } + // Card sector of this file sector + DWORD clst = fp->clust; + if (clst < 2 || clst >= fs->n_fatent) { + err = FWU_E_READ; + break; + } + uint32_t lba = (uint32_t)fs->database + (uint32_t)fs->csize * (clst - 2) + ((off / FWU_SECTOR_SIZE) & (fs->csize - 1)); + if (j->n_ext && j->ext[j->n_ext - 1].lba + j->ext[j->n_ext - 1].count == lba) + j->ext[j->n_ext - 1].count++; + else if (j->n_ext < FWU_MAX_EXTENTS) { + j->ext[j->n_ext].lba = lba; + j->ext[j->n_ext].count = 1; + j->n_ext++; + } else { + err = FWU_E_FRAG; + break; + } + // CRCs + uint32_t page = off / FLASH_PAGESIZE; + if (off % FLASH_PAGESIZE == 0) + j->page_crc[page] = 0; + j->page_crc[page] = fwu_crc32(j->page_crc[page], buf, n); + crc = fwu_crc32(crc, buf, n); + if (off < size - 4) + body_crc = fwu_crc32(body_crc, buf, off + n <= size - 4 ? n : size - 4 - off); + // Trailer bytes (can be split over two sectors) + for (uint32_t i = off < trailer_ofs ? trailer_ofs - off : 0; i < n; i++) + ((uint8_t *)&trailer)[off + i - trailer_ofs] = buf[i]; + } + f_close(fp); + if (err != FWU_OK) + return err; + if (trailer.magic != FW_TRAILER_MAGIC || trailer.image_size != size) + return FWU_E_TRAILER; + if (trailer.crc != FW_CRC_UNSET && trailer.crc != body_crc) + return FWU_E_CRC; + j->image_crc = crc; + // Dry run with the reader of the update + ili9341_bulk_finish(); + set_SPI_mode(SPI_MODE_SD_CARD); + if (fwu_dry_run() != FWU_R_OK) + return FWU_E_DRYRUN; + return FWU_OK; +} + +uint32_t fwu_image_crc(void) { + return fwu_job.image_crc; +} + +uint32_t fwu_extent_count(void) { + return fwu_job.n_ext; +} + +bool fwu_card_byte_addressing(void) { + return fwu_job.byte_addr != 0; +} + +uint32_t fwu_running_crc(void) { + return fwu_crc32(0, (const void *)FWU_APP_START, fw_header.image_size); +} + +static void fwu_legend(int y, uint16_t color, const char *text) { + background_color = color; + ili9341_fill(FWU_BAR_X, y, 12, 12); + ili9341_set_background(LCD_BG_COLOR); + lcd_printf(FWU_BAR_X + 20, y + 1, "%s", text); +} + +// Update screen: versions, warning, progress bar frame and color legend +void fwu_draw_screen(const fw_header_t *hdr) { + int x = 10, y = 10; + ili9341_set_foreground(LCD_FG_COLOR); + ili9341_set_background(LCD_BG_COLOR); + ili9341_clear_screen(); + lcd_set_font(FONT_NORMAL); + lcd_printf(x, y, "FIRMWARE UPDATE"); + y += 2 * bFONT_STR_HEIGHT; + lcd_printf(x, y, "Installed: %s", fw_header.version); + y += bFONT_STR_HEIGHT + 4; + lcd_printf(x, y, "New: %s", hdr->version); + y += 2 * bFONT_STR_HEIGHT; + lcd_printf(x, y, "Do not switch off or remove the SD card."); + y += bFONT_STR_HEIGHT + 4; + lcd_printf(x, y, "The tinySA restarts when the update is done."); + // Bar frame + ili9341_set_background(LCD_FG_COLOR); + ili9341_fill(FWU_BAR_X - 2, FWU_BAR_Y - 2, FWU_BAR_W + 4, FWU_BAR_H + 4); + background_color = FWU_COLOR_BAR_BG; + ili9341_fill(FWU_BAR_X, FWU_BAR_Y, FWU_BAR_W, FWU_BAR_H); + ili9341_set_background(LCD_BG_COLOR); + y = FWU_BAR_Y + FWU_BAR_H + 16; + fwu_legend(y, FWU_COLOR_WRITE, "writing"); + y += bFONT_STR_HEIGHT + 6; + fwu_legend(y, FWU_COLOR_RETRY, "retrying"); + y += bFONT_STR_HEIGHT + 6; + fwu_legend(y, FWU_COLOR_NOCARD, "no SD card: insert it, the update continues"); + y += bFONT_STR_HEIGHT + 6; + fwu_legend(y, FWU_COLOR_ERROR, "error, still retrying"); + y += bFONT_STR_HEIGHT + 6; + fwu_legend(y, FWU_COLOR_DONE, "done, restarting"); + y = LCD_HEIGHT - 3 * bFONT_STR_HEIGHT - 10; + lcd_printf(x, y, "If the tinySA does not start after an interrupted update:"); + y += bFONT_STR_HEIGHT + 4; + lcd_printf(x, y, "hold the jog button while switching on (DFU) and use a PC."); + lcd_set_font(FONT_SMALL); + ili9341_bulk_finish(); +} + +// Program the image prepared by fwu_prepare() and reset, never returns +void fwu_start(void) { + extern uint32_t __ram4_init_text__[], __ram4_init__[], __ram4_clear__[]; + ili9341_bulk_finish(); + set_SPI_mode(SPI_MODE_SD_CARD); + usbDisconnectBus(&USBD1); // the PC sees the tinySA leave instead of a hanging port + chThdSleepMilliseconds(100); + adc_stop_analog_watchdog(); // stop the touch ADC + __disable_irq(); + // Fresh copy of the CCM code from flash + for (uint32_t *d = __ram4_init__, *s = __ram4_init_text__; d < __ram4_clear__; ) + *d++ = *s++; + FWU_DSB(); + FWU_ISB(); + fwu_run(); +} + +#endif // __SD_FIRMWARE_UPDATE__ diff --git a/fw_update.h b/fw_update.h new file mode 100644 index 0000000..92b4d06 --- /dev/null +++ b/fw_update.h @@ -0,0 +1,86 @@ +/* + * Firmware update from the SD card (tinySA ULTRA) + * + * The update runs in two parts: + * - fw_update.c, flash resident: check the image file, map it to SD card sectors and + * verify that the sectors can be read (dry run), all while the firmware runs normally. + * - a small self-contained routine in CCM RAM that, with interrupts off, reads the image + * sector by sector from the SD card, erases and programs the application flash, verifies + * it and resets. It never touches the config/preset area at SAVE_CONFIG_ADDR and above. + * + * Image format: the linker script (STM32F303xC.ld) places fw_header_t directly after the + * vector table and a fw_trailer_t at the end of the image. Only images with this header can + * be installed from the SD card; older images and other models need DFU. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + */ +#ifndef __FW_UPDATE_H__ +#define __FW_UPDATE_H__ + +#include +#include + +#define FW_HEADER_MAGIC0 0x796E6974U // "tiny" +#define FW_HEADER_MAGIC1 0x57464153U // "SAFW" +#define FW_HEADER_VERSION 1 +#define FW_MODEL_TINYSA4 4 // tinySA ULTRA / ULTRA+ (STM32F303) +#define FW_TRAILER_MAGIC 0x444E4546U // "FEND" +#define FW_CRC_UNSET 0xFFFFFFFFU // trailer CRC not set by the build + +// Image header, placed after the vector table. Newer versions only append fields. +typedef struct { + uint32_t magic[2]; // FW_HEADER_MAGIC0, FW_HEADER_MAGIC1 + uint16_t hdr_version; // FW_HEADER_VERSION + uint16_t hdr_size; // sizeof(fw_header_t) + uint32_t model; // FW_MODEL_TINYSA4 + uint32_t load_addr; // FLASH_START_ADDRESS + uint32_t image_size; // bytes from load_addr up to and including the trailer + uint32_t flags; // reserved, 0 + char version[48]; // VERSION string, zero terminated +} fw_header_t; + +// Image trailer, the last 12 bytes of the image +typedef struct { + uint32_t magic; // FW_TRAILER_MAGIC + uint32_t image_size; // same as fw_header_t.image_size + uint32_t crc; // CRC32 (zlib) of the image without this word, or FW_CRC_UNSET +} fw_trailer_t; + +// Error codes +enum { + FWU_OK = 0, + FWU_E_BUSY, // a shell command is still running + FWU_E_BATTERY, // battery too low + FWU_E_NOCARD, // no SD card or card error + FWU_E_OPEN, // file not found + FWU_E_READ, // file read error + FWU_E_FORMAT, // no firmware header: not a firmware file or an older version + FWU_E_MODEL, // firmware for another model + FWU_E_SIZE, // image size not valid or larger than the file + FWU_E_VECTOR, // vector table not valid + FWU_E_TRAILER, // end of image not found: incomplete file + FWU_E_CRC, // CRC mismatch + FWU_E_FRAG, // file too fragmented + FWU_E_DRYRUN, // raw sector read check failed + FWU_E_COUNT +}; + +extern const fw_header_t fw_header; // header of the running firmware +extern volatile bool fwu_active; // firmware update in progress, the shell refuses commands + +const char *fwu_error_text(int err); +int fwu_begin(void); // take SPI1/SD card for the update (sweep thread) +void fwu_end(void); // give them back after a check or a cancel +int fwu_read_header(const char *path, fw_header_t *hdr); +int fwu_prepare(const char *path, const fw_header_t *hdr); // full file check, sector map and dry run +uint32_t fwu_image_crc(void); // CRC32 of the prepared image +uint32_t fwu_extent_count(void); // number of contiguous sector runs of the prepared image +bool fwu_card_byte_addressing(void); // prepared card uses byte addresses (SDSC) +uint32_t fwu_running_crc(void); // CRC32 of the running firmware image +void fwu_draw_screen(const fw_header_t *hdr); // update screen, before fwu_start() +void fwu_start(void) __attribute__((noreturn)); // program the prepared image and reset + +#endif diff --git a/ili9341.c b/ili9341.c index 2fab13d..7517e87 100644 --- a/ili9341.c +++ b/ili9341.c @@ -1226,6 +1226,10 @@ bool SD_Inserted(void){ return !(palReadPort(GPIOB)&(1< 2 || argv[0][0] == '?' || (argc == 2 && strcmp(argv[1], "flash") != 0)) { + usage_printf("fwupdate [{file} [flash]]" VNA_SHELL_NEWLINE_STR); + return; + } + fw_header_t hdr; + int err = fwu_begin(); + if (err == FWU_OK) + err = fwu_read_header(argv[0], &hdr); + if (err == FWU_OK) { + shell_printf("file %s size %u" VNA_SHELL_NEWLINE_STR, hdr.version, hdr.image_size); + err = fwu_prepare(argv[0], &hdr); + } + if (err == FWU_OK) { + shell_printf("crc32 %08x, %u sector run(s), %s card: OK" VNA_SHELL_NEWLINE_STR, + fwu_image_crc(), fwu_extent_count(), fwu_card_byte_addressing() ? "SDSC" : "SDHC/SDXC"); + if (argc == 2) { + shell_printf("updating, the tinySA restarts when done" VNA_SHELL_NEWLINE_STR); + chThdSleepMilliseconds(100); // let USB send the text + fwu_draw_screen(&hdr); + fwu_start(); + } + } else + shell_printf("error: %s" VNA_SHELL_NEWLINE_STR, fwu_error_text(err)); + fwu_end(); +} +#endif + config_t config = { .magic = CONFIG_MAGIC, .dac_value = 1922, @@ -2635,6 +2674,9 @@ static const VNAShellCommand commands[] = #ifdef __USE_USB_MSC__ { "usbdisk", cmd_usbdisk, CMD_DISK_OK }, #endif +#ifdef __SD_FIRMWARE_UPDATE__ + { "fwupdate", cmd_fwupdate, CMD_WAIT_MUTEX }, +#endif #ifdef ENABLE_THREADS_COMMAND {"threads" , cmd_threads , CMD_DISK_OK}, #endif @@ -2950,10 +2992,19 @@ static void VNAShell_executeLine(char *line) // shell_direct_cmd tells usb_disk_mode() to wait for a command already running in this thread. chSysLock(); bool refuse = msc_disk_mode && !(cmd_flag & CMD_DISK_OK); +#ifdef __SD_FIRMWARE_UPDATE__ + bool fw_update = fwu_active; // firmware update owns SPI1 and the SD card, refuse all commands + refuse |= fw_update; +#endif if (!refuse && !(cmd_flag & CMD_WAIT_MUTEX)) shell_direct_cmd = true; chSysUnlock(); if (refuse) { +#ifdef __SD_FIRMWARE_UPDATE__ + if (fw_update) + shell_printf("firmware update active" VNA_SHELL_NEWLINE_STR VNA_SHELL_PROMPT_STR); + else +#endif shell_printf("usb disk mode active" VNA_SHELL_NEWLINE_STR VNA_SHELL_PROMPT_STR); return; } diff --git a/nanovna.h b/nanovna.h index 36f589b..068b7fb 100644 --- a/nanovna.h +++ b/nanovna.h @@ -110,6 +110,7 @@ #define __SD_CARD_DUMP_FIRMWARE__ // Allow dump firmware to SD card #define __SD_FILE_BROWSER__ #define __USE_USB_MSC__ // USB DISK mode: SD card as USB mass storage next to the USB serial port +#define __SD_FIRMWARE_UPDATE__ // Install a firmware .bin from the SD card (fw_update.c) #define __LCD_BRIGHTNESS__ // LCD or hardware allow change brightness, add menu item for this #define __HARMONIC__ #define __NOISE_FIGURE__ @@ -1751,6 +1752,7 @@ void rtc_set_time(uint32_t dr, uint32_t tr); extern uint16_t sd_card_inserted_at_boot; bool SD_Inserted(void); void SD_PowerOff(void); +bool SD_BlockAddressing(void); // initialized card uses block (sector) addresses (SDHC/SDXC) // Buffers for SD card use spi_buffer #if SPI_BUFFER_SIZE < 2048 #error "SPI_BUFFER_SIZE for SD card support need size >= 2048" @@ -1786,6 +1788,12 @@ void clock_at_48MHz(void); #if defined(__USE_USB_MSC__) && !defined(__USE_SD_CARD__) #error "__USE_USB_MSC__ needs __USE_SD_CARD__" #endif +#if defined(__SD_FIRMWARE_UPDATE__) && (!defined(TINYSA4) || !defined(__USE_USB_MSC__) || !defined(__SD_FILE_BROWSER__)) +#error "__SD_FIRMWARE_UPDATE__ needs TINYSA4, __USE_USB_MSC__ and __SD_FILE_BROWSER__" +#endif +#ifdef __SD_FIRMWARE_UPDATE__ +#include "fw_update.h" +#endif /* * Backup diff --git a/ui.c b/ui.c index 63744a0..fe25476 100644 --- a/ui.c +++ b/ui.c @@ -4347,7 +4347,7 @@ static uint16_t file_mask; // Save format enum enum { FMT_BMP_FILE, FMT_CSV_FILE, -#ifdef __SD_CARD_DUMP_FIRMWARE__ +#if defined(__SD_CARD_DUMP_FIRMWARE__) || defined(__SD_FIRMWARE_UPDATE__) FMT_BIN_FILE, #endif FMT_CMD_FILE, @@ -4363,7 +4363,7 @@ enum { static const char *file_ext[] = { [FMT_BMP_FILE] = "bmp", [FMT_CSV_FILE] = "csv", -#ifdef __SD_CARD_DUMP_FIRMWARE__ +#if defined(__SD_CARD_DUMP_FIRMWARE__) || defined(__SD_FIRMWARE_UPDATE__) [FMT_BIN_FILE] = "bin", #endif [FMT_CMD_FILE] = "cmd", @@ -4477,6 +4477,95 @@ static UI_FUNCTION_CALLBACK(menu_usb_disk_cb) { } #endif +#ifdef __SD_FIRMWARE_UPDATE__ +// Firmware update from the SD card (fw_update.c), started from the file browser on a .bin file +#define FWU_DLG_W 420 +#define FWU_DLG_H 160 +#define FWU_DLG_X ((LCD_WIDTH - FWU_DLG_W) / 2) +#define FWU_DLG_Y ((LCD_HEIGHT - FWU_DLG_H) / 2) +#define FWU_BTN_W 120 +#define FWU_BTN_H 36 +#define FWU_BTN_Y (FWU_DLG_Y + FWU_DLG_H - FWU_BTN_H - 10) +#define FWU_BTN_X(i) ((i) == 0 ? FWU_DLG_X + 30 : FWU_DLG_X + FWU_DLG_W - 30 - FWU_BTN_W) + +static void fw_update_draw_button(int i, bool selected) { + ui_button_t b; + b.bg = selected ? LCD_MENU_ACTIVE_COLOR : LCD_MENU_COLOR; + b.fg = LCD_MENU_TEXT_COLOR; + b.border = (selected ? BUTTON_BORDER_FALLING : BUTTON_BORDER_RISE) | 2; + draw_button(FWU_BTN_X(i), FWU_BTN_Y, FWU_BTN_W, FWU_BTN_H, &b); + ili9341_drawstring_7x13(i == 0 ? "UPDATE" : "CANCEL", FWU_BTN_X(i) + (FWU_BTN_W - 6 * bFONT_WIDTH) / 2, + FWU_BTN_Y + (FWU_BTN_H - bFONT_STR_HEIGHT) / 2); +} + +// Show both versions and ask. Jog up/down selects, jog push or touch confirms. CANCEL is the default. +static bool fw_update_confirm(const fw_header_t *hdr) { + char line[64]; + ui_button_t b; + b.bg = LCD_MENU_COLOR; + b.fg = LCD_MENU_TEXT_COLOR; + b.border = BUTTON_BORDER_FLAT | 1; + draw_button(FWU_DLG_X, FWU_DLG_Y, FWU_DLG_W, FWU_DLG_H, &b); + int x = FWU_DLG_X + 10, y = FWU_DLG_Y + 8; + ili9341_drawstring_7x13("UPDATE FIRMWARE?", x, y); + y += bFONT_STR_HEIGHT + 10; + plot_printf(line, sizeof line, "Installed: %s", fw_header.version); + ili9341_drawstring_7x13(line, x, y); + y += bFONT_STR_HEIGHT + 4; + plot_printf(line, sizeof line, "New: %s", hdr->version); + ili9341_drawstring_7x13(line, x, y); + y += bFONT_STR_HEIGHT + 4; + ili9341_drawstring_7x13("Settings, calibration and presets are kept.", x, y); + int sel = 1; + fw_update_draw_button(0, false); + fw_update_draw_button(1, true); + touch_wait_release(); + systime_t start = chVTGetSystemTimeX(); + while (chVTGetSystemTimeX() - start < MS2ST(30000)) { + int status = btn_check(); + if (status & EVT_BUTTON_SINGLE_CLICK) + return sel == 0; + if (status & (EVT_UP | EVT_DOWN)) { + sel ^= 1; + fw_update_draw_button(0, sel == 0); + fw_update_draw_button(1, sel == 1); + } + if (touch_check() == EVT_TOUCH_PRESSED) { + int tx, ty; + touch_position(&tx, &ty); + touch_wait_release(); + for (int i = 0; i < 2; i++) + if (tx >= FWU_BTN_X(i) && tx < FWU_BTN_X(i) + FWU_BTN_W && ty >= FWU_BTN_Y && ty < FWU_BTN_Y + FWU_BTN_H) + return i == 0; + } + chThdSleepMilliseconds(40); + } + return false; +} + +// Check the file, ask, check the image and update. Returns only on cancel or error. +static void fw_update_ui(const char *fname) { + fw_header_t hdr; + int err = fwu_begin(); + if (err == FWU_OK) + err = fwu_read_header(fname, &hdr); + if (err == FWU_OK) { + if (!fw_update_confirm(&hdr)) { + fwu_end(); + return; + } + drawMessageBox("FIRMWARE UPDATE", "Checking file...", 0); + err = fwu_prepare(fname, &hdr); + } + if (err == FWU_OK) { + fwu_draw_screen(&hdr); + fwu_start(); + } + fwu_end(); + drawMessageBox("FIRMWARE UPDATE", (char *)fwu_error_text(err), 3000); +} +#endif + static UI_FUNCTION_ADV_CALLBACK(menu_autoname_acb) @@ -5520,6 +5609,9 @@ static const menuitem_t menu_storage[] = { { MT_CALLBACK, FMT_CSV_FILE, "SAVE\nTRACES", menu_save_traces_cb}, #ifdef __USE_USB_MSC__ { MT_CALLBACK, 0, "USB\nDISK", menu_usb_disk_cb}, +#endif +#ifdef __SD_FIRMWARE_UPDATE__ + { MT_CALLBACK, FMT_BIN_FILE, "UPDATE\nFIRMWARE", menu_sdcard_browse_cb}, #endif { MT_SUBMENU, 0, "CONFIG", menu_storage_config }, // { MT_KEYPAD, KM_INTERVAL, "INTERVAL\n\b%s", NULL }, diff --git a/vna_browser.c b/vna_browser.c index 0773ea8..e804ec4 100644 --- a/vna_browser.c +++ b/vna_browser.c @@ -114,6 +114,15 @@ run_file: ili9341_set_foreground(LCD_FG_COLOR); ili9341_set_background(LCD_BG_COLOR); switch (keypad_mode) { +#ifdef __SD_FIRMWARE_UPDATE__ + /* + * Firmware update, fw_update_ui() returns only on cancel or error + */ + case FMT_BIN_FILE: + f_close(fs_file); + fw_update_ui(fno.fname); + return; +#endif case FMT_TBL_FILE: { const int buffer_size = 256;