Add firmware update from the SD card (tinySA ULTRA)

STORAGE -> UPDATE FIRMWARE installs a firmware .bin from the SD card.
The shell command "fwupdate {file}" checks a file, "fwupdate {file} flash"
installs it. Config, calibration and presets (0x0803C000 and up) are never
touched; ROM DFU (jog held at power-on) stays the recovery.

- Image format: STM32F303xC.ld inlines the code rules to place a header
  (magic, model, size, version) directly after the vector table and a
  trailer (end magic, size, CRC field left unset) at the end of the image.
  Only images with the header are accepted.
- Before anything is erased, the running firmware reads the whole file with
  FatFs (page CRCs, image CRC, card sector map from FIL.clust) and reads it
  again with the raw sector reader used by the update (dry run).
- The flasher runs from CCM RAM with interrupts off and VTOR in CCM. It keeps
  page 0 in RAM, erases page 0 first and programs it last after a full image
  CRC check, skips unchanged pages, retries errors without user input and
  initializes the SD card again after it was removed or keeps failing.
  It draws a progress bar and resets only after jog is released, because
  jog pressed at reset starts the ROM bootloader.
copilot/add-multi-platform-rf-scan-exports
erikkaashoek 2 months ago
parent 650f53855e
commit 609b094be6

@ -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

@ -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.

@ -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")

@ -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 <string.h>
#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__

@ -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 <stdint.h>
#include <stdbool.h>
#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

@ -1226,6 +1226,10 @@ bool SD_Inserted(void){
return !(palReadPort(GPIOB)&(1<<GPIOB_SD_CD));
}
bool SD_BlockAddressing(void){
return (CardType & CT_BLOCK) != 0;
}
static void SD_Select_SPI(void) {
set_SPI_mode(SPI_MODE_SD_CARD);
SD_CS_LOW; // Select SD Card

@ -1165,6 +1165,45 @@ VNA_SHELL_FUNCTION(cmd_usbdisk)
}
#endif
#ifdef __SD_FIRMWARE_UPDATE__
// Firmware update from the SD card (fw_update.c), runs in the sweep thread
// fwupdate version, size and CRC32 of the running firmware
// fwupdate {file} check the file and read it as the update would, nothing is changed
// fwupdate {file} flash install the file and restart
VNA_SHELL_FUNCTION(cmd_fwupdate)
{
if (argc == 0) {
shell_printf("%s size %u crc32 %08x" VNA_SHELL_NEWLINE_STR,
fw_header.version, fw_header.image_size, fwu_running_crc());
return;
}
if (argc > 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;
}

@ -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

96
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 },

@ -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;

Loading…
Cancel
Save

Powered by TurnKey Linux.