From 08348c7233fccf9ac86e7d3fbfd83fdc9d1de856 Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Fri, 9 Oct 2026 22:12:10 -0400 Subject: [PATCH 01/32] temp stash --- firmware/lib/logging/logging.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 firmware/lib/logging/logging.h diff --git a/firmware/lib/logging/logging.h b/firmware/lib/logging/logging.h new file mode 100644 index 0000000..a337afe --- /dev/null +++ b/firmware/lib/logging/logging.h @@ -0,0 +1,21 @@ +/** + * @file logging.h + * @brief NAND flash header for GD5F1GQ5UEYIGR chip + * + * @author Daniel Proano (dproano@purdue.edu) + */ + +#pragma once + +#include "flash.h" +#include "bad_page_table.h" + +namespace Logging { + +bool begin(); + +bool write(uint8_t struct_id, uint8_t *data, size_t len); + +bool + +} \ No newline at end of file From 07558f949bde0d4d2cdb883ad6e902dcc4203c6d Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Wed, 9 Sep 2026 01:36:48 -0400 Subject: [PATCH 02/32] AI scaffolding init (temporary example code) --- firmware/boards/TOAD_H7/ldscript.ld | 17 +- firmware/lib/logging/bad_page_table.cpp | 131 ++++++++++ firmware/lib/logging/bad_page_table.h | 19 ++ firmware/lib/logging/flash.cpp | 313 ++++++++++++++++++++++++ firmware/lib/logging/flash.h | 55 +++++ firmware/lib/logging/flash_defs.h | 43 ++++ firmware/lib/logging/logging.cpp | 215 ++++++++++++++++ 7 files changed, 792 insertions(+), 1 deletion(-) create mode 100644 firmware/lib/logging/bad_page_table.cpp create mode 100644 firmware/lib/logging/bad_page_table.h create mode 100644 firmware/lib/logging/flash.cpp create mode 100644 firmware/lib/logging/flash.h create mode 100644 firmware/lib/logging/flash_defs.h create mode 100644 firmware/lib/logging/logging.cpp diff --git a/firmware/boards/TOAD_H7/ldscript.ld b/firmware/boards/TOAD_H7/ldscript.ld index 94020e6..15a2440 100644 --- a/firmware/boards/TOAD_H7/ldscript.ld +++ b/firmware/boards/TOAD_H7/ldscript.ld @@ -45,7 +45,12 @@ _Min_Stack_Size = 0x400; /* required amount of stack */ MEMORY { RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 512K - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K /* Memory is divided. Actual start is 0x08000000 and actual length is 2048K */ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K /* Bank 1. Actual full flash is 2048K across two 1024K banks. */ + /* Bank 2, Sector 0. Reserved for the NAND flash Bad Page Table (BPT) - see + firmware/lib/logging/bad_page_table.cpp. Bank 2 is otherwise unused by this linker + script, so carving out this sector here guarantees ordinary code/data never gets + linked on top of it even if Bank 2 is mapped for general use later. */ + BPT_FLASH (r) : ORIGIN = 0x08100000, LENGTH = 128K DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 288K RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K @@ -133,6 +138,16 @@ SECTIONS . = ALIGN(4); } >FLASH + /* Reserved for the NAND flash Bad Page Table - see firmware/lib/logging/bad_page_table.cpp. + NOLOAD: this region isn't preloaded from the firmware image, it's read/written live at + runtime via HAL_FLASH_Program()/HAL_FLASHEx_Erase(). Kept empty of any other symbols so + ordinary code/data never gets linked into Bank 2 Sector 0. */ + .bpt_table (NOLOAD) : + { + . = ALIGN(4); + _bpt_table_start = .; + } >BPT_FLASH + /* Used by the startup to initialize data */ _sidata = LOADADDR(.data); diff --git a/firmware/lib/logging/bad_page_table.cpp b/firmware/lib/logging/bad_page_table.cpp new file mode 100644 index 0000000..9c24bff --- /dev/null +++ b/firmware/lib/logging/bad_page_table.cpp @@ -0,0 +1,131 @@ +#include "bad_page_table.h" +#include "flash_defs.h" + +#include "CommsSerial.h" +#include +#include + +#define BPT_MAGIC 0x50425401u // 'PBT' + version 1 - arbitrary, just needs to be unlikely to appear in erased flash + +#define BITMAP_SIZE_BYTES ((NAND_NUM_BLOCKS + 7) / 8) +#define BPT_TABLE_SIZE_BYTES (sizeof(uint32_t) + BITMAP_SIZE_BYTES) +// STM32H7 internal flash program granularity is one 256-bit "flash word" (32 bytes) at a time. +#define FLASH_WORD_SIZE 32u +#define BPT_WRITE_SIZE (((BPT_TABLE_SIZE_BYTES + FLASH_WORD_SIZE - 1) / FLASH_WORD_SIZE) * FLASH_WORD_SIZE) + +// Provided by the linker (ldscript.ld) - base of the reserved Bank 2, Sector 0 region. +extern uint32_t _bpt_table_start; + +namespace BPT { + +uint32_t bpt_base_addr; + +// STM32H7's flash is ECC-protected per flash word: once a word is programmed, it cannot be +// programmed again (even to clear further bits to 0) without erasing its sector first - unlike +// classic STM32 NOR-style flash. So any update to the table means erase-the-sector-and-rewrite- +// the-whole-thing, not a targeted bit flip. +bool erase_bpt_sector() { + HAL_FLASH_Unlock(); + + FLASH_EraseInitTypeDef erase_init{}; + erase_init.TypeErase = FLASH_TYPEERASE_SECTORS; + erase_init.Banks = FLASH_BANK_2; + erase_init.Sector = FLASH_SECTOR_0; + erase_init.NbSectors = 1; + erase_init.VoltageRange = FLASH_VOLTAGE_RANGE_3; // TODO - confirm correct voltage range for this board + + uint32_t sector_error; + HAL_StatusTypeDef status = HAL_FLASHEx_Erase(&erase_init, §or_error); + + HAL_FLASH_Lock(); + return status == HAL_OK; +} + +bool program_flash_word(uint32_t address, const uint8_t *data) { + HAL_FLASH_Unlock(); + HAL_StatusTypeDef status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, address, reinterpret_cast(data)); + HAL_FLASH_Lock(); + return status == HAL_OK; +} + +// Erases the BPT sector and rewrites the magic number + given bitmap in one pass. +bool write_table(const uint8_t *bitmap) { + if (!erase_bpt_sector()) + return false; + + uint8_t buf[BPT_WRITE_SIZE]; + memset(buf, 0xFF, sizeof(buf)); + + uint32_t magic = BPT_MAGIC; + memcpy(buf, &magic, sizeof(magic)); + memcpy(buf + sizeof(magic), bitmap, BITMAP_SIZE_BYTES); + + for (uint32_t offset = 0; offset < sizeof(buf); offset += FLASH_WORD_SIZE) { + if (!program_flash_word(bpt_base_addr + offset, buf + offset)) + return false; + } + + return true; +} + +bool is_page_good(uint32_t addr) { + uint32_t block = addr / NAND_PAGES_PER_BLOCK; + + const uint8_t *bitmap = reinterpret_cast(bpt_base_addr + sizeof(uint32_t)); + bool bad = bitmap[block / 8] & (1u << (block % 8)); + + return !bad; +} + +void mark_page_bad(uint32_t addr) { + uint32_t block = addr / NAND_PAGES_PER_BLOCK; + + if (!is_page_good(addr)) + return; // already marked + + uint8_t bitmap[BITMAP_SIZE_BYTES]; + memcpy(bitmap, reinterpret_cast(bpt_base_addr + sizeof(uint32_t)), sizeof(bitmap)); + bitmap[block / 8] |= (1u << (block % 8)); + + if (!write_table(bitmap)) { + CommsSerial.println("BPT: failed to persist newly-marked bad block!"); + } +} + +void begin() { + bpt_base_addr = reinterpret_cast(&_bpt_table_start); + + uint32_t magic; + memcpy(&magic, reinterpret_cast(bpt_base_addr), sizeof(magic)); + + if (magic == BPT_MAGIC) + return; // table already valid + + CommsSerial.println("Bad Page Table is not initialized."); + CommsSerial.println("This will erase the current BPT and mark every page as good."); + CommsSerial.println("This does NOT scan the NAND for factory-bad blocks - run the factory scan separately before the first write."); + CommsSerial.print("Type 'yes' to initialize the table: "); + + char *response = CommsSerial.readline(); + + if (strcmp(response, "yes") != 0) { + while (1) { + CommsSerial.println("BPT not initialized - halting."); + delay(1000); + } + } + + uint8_t bitmap[BITMAP_SIZE_BYTES]; + memset(bitmap, 0x00, sizeof(bitmap)); // 0 = good + + if (!write_table(bitmap)) { + while (1) { + CommsSerial.println("Failed to initialize BPT - halting."); + delay(1000); + } + } + + CommsSerial.println("BPT initialized."); +} + +} // namespace BPT diff --git a/firmware/lib/logging/bad_page_table.h b/firmware/lib/logging/bad_page_table.h new file mode 100644 index 0000000..95832a4 --- /dev/null +++ b/firmware/lib/logging/bad_page_table.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace BPT { + +// Loads the Bad Page Table from internal flash (Bank 2, Sector 0 - see ldscript.ld). +// Checks a magic number at the table's base; if missing, prompts over serial before +// initializing a fresh (all-good) table. Must be called before is_page_good()/mark_page_bad(). +void begin(); + +// NAND bad-block marking is inherently block-granularity (the factory marker and any +// runtime ECC failure both apply to the whole block containing a page), so addr is +// mapped to its containing block internally even though it's expressed as a page index. +bool is_page_good(uint32_t addr); + +void mark_page_bad(uint32_t addr); + +} // namespace BPT diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp new file mode 100644 index 0000000..6cc8008 --- /dev/null +++ b/firmware/lib/logging/flash.cpp @@ -0,0 +1,313 @@ +#include "flash.h" +#include "flash_defs.h" + +#include "CommsSerial.h" +#include +#include +#include + +#define FREQ_MAX 50'000'000 +#define HAL_TIMEOUT 10000 + +// Pin mapping confirmed against the EC schematic (MCU.SchDoc, STM32H747BIT6) on 2026-09-09: +// PB2 -> QUADSPI_CLK (AF9) +// PD11 -> QUADSPI_BK1_IO0 (AF9) +// PD12 -> QUADSPI_BK1_IO1 (AF9) +// PF7 -> QUADSPI_BK1_IO2 (AF9) +// PD13 -> QUADSPI_BK1_IO3 (AF9) +// PG6 -> QUADSPI_BK1_NCS (AF10) +// This mirrors ASTRA's flash.cpp in hardcoding pins directly in the MSP init rather than +// going through ec_pins.h, since HAL_QSPI_MspInit is a one-time, low-level peripheral +// binding rather than a general-purpose GPIO the rest of the codebase reconfigures. +void HAL_QSPI_MspInit(QSPI_HandleTypeDef *hqspi) { + __HAL_RCC_QSPI_CLK_ENABLE(); + __HAL_RCC_QSPI_FORCE_RESET(); + __HAL_RCC_QSPI_RELEASE_RESET(); + + __HAL_RCC_GPIOB_CLK_ENABLE(); + __HAL_RCC_GPIOD_CLK_ENABLE(); + __HAL_RCC_GPIOF_CLK_ENABLE(); + __HAL_RCC_GPIOG_CLK_ENABLE(); + + GPIO_InitTypeDef gpio_init{}; + gpio_init.Mode = GPIO_MODE_AF_PP; + gpio_init.Pull = GPIO_NOPULL; + gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + + gpio_init.Pin = GPIO_PIN_2; + gpio_init.Alternate = GPIO_AF9_QUADSPI; + HAL_GPIO_Init(GPIOB, &gpio_init); + + gpio_init.Pin = GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13; + gpio_init.Alternate = GPIO_AF9_QUADSPI; + HAL_GPIO_Init(GPIOD, &gpio_init); + + gpio_init.Pin = GPIO_PIN_7; + gpio_init.Alternate = GPIO_AF9_QUADSPI; + HAL_GPIO_Init(GPIOF, &gpio_init); + + gpio_init.Pin = GPIO_PIN_6; + gpio_init.Alternate = GPIO_AF10_QUADSPI; + HAL_GPIO_Init(GPIOG, &gpio_init); +} + +namespace Flash { + +QSPI_HandleTypeDef hqspi; + +// True once PROGRAM LOAD has reset the cache to 0xFF for the page currently being +// assembled. Needed because write_to_cache() may be called several times (id byte, +// then struct bytes) before program() flushes the page - only the first of those +// calls should reset the rest of the cache, or later calls would stomp earlier ones. +bool cache_loaded = false; + +bool exec_command(uint8_t instruction, uint32_t data_len = 0, uint32_t addr = UINT32_MAX, uint8_t addr_size_bits = 24, unsigned int dummy = 0, bool quad_data = false) { + QSPI_CommandTypeDef cmd{}; + + cmd.Instruction = instruction; + cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE; + cmd.DummyCycles = dummy; + cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; + + if (addr != UINT32_MAX) { + cmd.Address = addr; + cmd.AddressMode = QSPI_ADDRESS_1_LINE; + cmd.AddressSize = (addr_size_bits == 8) ? QSPI_ADDRESS_8_BITS : (addr_size_bits == 16) ? QSPI_ADDRESS_16_BITS : QSPI_ADDRESS_24_BITS; + } + + if (data_len) { + cmd.NbData = data_len; + cmd.DataMode = quad_data ? QSPI_DATA_4_LINES : QSPI_DATA_1_LINE; + } + + return HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) == HAL_OK; +} + +bool receive(uint8_t instruction, void *data, uint32_t len, uint32_t addr = UINT32_MAX, uint8_t addr_size_bits = 24, unsigned int dummy = 0, bool quad_data = false) { + if (!exec_command(instruction, len, addr, addr_size_bits, dummy, quad_data)) + return false; + return HAL_QSPI_Receive(&hqspi, static_cast(data), HAL_TIMEOUT) == HAL_OK; +} + +bool transmit(uint8_t instruction, void *data, uint32_t len, uint32_t addr = UINT32_MAX, uint8_t addr_size_bits = 24, bool quad_data = false) { + if (!exec_command(instruction, len, addr, addr_size_bits, 0, quad_data)) + return false; + return HAL_QSPI_Transmit(&hqspi, static_cast(data), HAL_TIMEOUT) == HAL_OK; +} + +// GET FEATURES / SET FEATURES address the target register with a 1-byte address, not a +// memory-array address. +bool get_feature(uint8_t feature_addr, uint8_t *out) { + return receive(CMD_NAND_GET_FEATURES, out, 1, feature_addr, 8); +} + +bool set_feature(uint8_t feature_addr, uint8_t value) { + return transmit(CMD_NAND_SET_FEATURES, &value, 1, feature_addr, 8); +} + +flash_error_t read_status(flash_status_a_t *status_a, flash_status_b_t *status_b) { + uint8_t raw; + + if (status_a) { + if (!get_feature(FEATURE_ADDR_STATUS_A, &raw)) + return FLASH_ERROR_FAIL; + memcpy(status_a, &raw, sizeof(raw)); + } + + if (status_b) { + if (!get_feature(FEATURE_ADDR_STATUS_B, &raw)) + return FLASH_ERROR_FAIL; + memcpy(status_b, &raw, sizeof(raw)); + } + + return FLASH_ERROR_SUCCESS; +} + +// Polls GET FEATURES (status_a) until OIP (operation in progress) clears, or timeout_us elapses. +flash_error_t wait_until_ready(uint32_t timeout_us) { + flash_status_a_t status{}; + uint32_t start = micros(); + + do { + if (!get_feature(FEATURE_ADDR_STATUS_A, reinterpret_cast(&status))) + return FLASH_ERROR_FAIL; + + if (!status.OIP) + return FLASH_ERROR_SUCCESS; + + if (micros() - start > timeout_us) + return FLASH_ERROR_TIMED_OUT; + } while (true); +} + +flash_error_t read_page(uint32_t addr, uint8_t *out) { + // Page Read: array -> cache. + if (!exec_command(CMD_NAND_PAGE_READ, 0, addr, 24)) + return FLASH_ERROR_FAIL; + + flash_error_t err = wait_until_ready(50000); // TODO - confirm max tR from datasheet + if (err != FLASH_ERROR_SUCCESS) + return err; + + // Read From Cache: cache -> host, quad data lines, starting at column 0. + // TODO - confirm dummy cycle count for the quad read opcode against the datasheet. + if (!receive(CMD_NAND_READ_FROM_CACHE_QUAD, out, FLASH_PAGE_SIZE, 0, 16, 8, true)) + return FLASH_ERROR_FAIL; + + flash_status_a_t status_a{}; + if (read_status(&status_a, nullptr) != FLASH_ERROR_SUCCESS) + return FLASH_ERROR_FAIL; + + // TODO - confirm which ECCS value(s) mean "uncorrectable" vs. "corrected" against the + // datasheet (DS p.42/46). Treating any nonzero ECCS as a failure is conservative for now. + if (status_a.ECCS != 0) + return FLASH_ERROR_FAIL; + + return FLASH_ERROR_SUCCESS; +} + +flash_error_t write_to_cache(uint32_t col_addr, uint8_t *data, size_t len) { + if (col_addr + len > FLASH_PAGE_SIZE) + return FLASH_ERROR_FAIL; + + // The first write into a fresh cache uses PROGRAM LOAD, which resets the rest of the + // cache to 0xFF - so unwritten bytes in a partially-filled page read back as erased + // rather than leftover data from whatever was cached before. Later writes in the same + // page cycle use PROGRAM LOAD RANDOM DATA so they don't clobber earlier writes. + uint8_t cmd = cache_loaded ? CMD_NAND_PROGRAM_LOAD_RANDOM : CMD_NAND_PROGRAM_LOAD; + + if (!transmit(cmd, data, len, col_addr, 16)) + return FLASH_ERROR_FAIL; + + cache_loaded = true; + return FLASH_ERROR_SUCCESS; +} + +flash_error_t program(uint32_t addr, uint32_t timeout_us) { + flash_error_t err = wait_until_ready(timeout_us); + if (err != FLASH_ERROR_SUCCESS) + return err; + + if (!exec_command(CMD_NAND_WRITE_ENABLE)) + return FLASH_ERROR_FAIL; + + if (!exec_command(CMD_NAND_PROGRAM_EXECUTE, 0, addr, 24)) + return FLASH_ERROR_FAIL; + + err = wait_until_ready(timeout_us); + cache_loaded = false; // next write_to_cache() call starts a fresh cache, regardless of outcome + + if (err != FLASH_ERROR_SUCCESS) + return err; + + flash_status_a_t status_a{}; + if (read_status(&status_a, nullptr) != FLASH_ERROR_SUCCESS) + return FLASH_ERROR_FAIL; + + if (status_a.P_FAIL) + return FLASH_ERROR_FAIL; // caller (Logging) is expected to mark this page/block bad + + return FLASH_ERROR_SUCCESS; +} + +flash_error_t erase_block(uint32_t addr, uint32_t timeout_us) { + flash_error_t err = wait_until_ready(timeout_us); + if (err != FLASH_ERROR_SUCCESS) + return err; + + if (!exec_command(CMD_NAND_WRITE_ENABLE)) + return FLASH_ERROR_FAIL; + + if (!exec_command(CMD_NAND_BLOCK_ERASE, 0, addr, 24)) + return FLASH_ERROR_FAIL; + + err = wait_until_ready(timeout_us); + if (err != FLASH_ERROR_SUCCESS) + return err; + + flash_status_a_t status_a{}; + if (read_status(&status_a, nullptr) != FLASH_ERROR_SUCCESS) + return FLASH_ERROR_FAIL; + + if (status_a.E_FAIL) + return FLASH_ERROR_FAIL; // caller (BPT) is expected to mark this block bad + + return FLASH_ERROR_SUCCESS; +} + +bool init_qspi_peripheral() { + uint32_t f_hclk = HAL_RCC_GetHCLKFreq(); + + hqspi.Instance = QUADSPI; + hqspi.Init.ClockPrescaler = f_hclk / FREQ_MAX; + if (hqspi.Init.ClockPrescaler > 255) + hqspi.Init.ClockPrescaler = 255; + + hqspi.Init.FifoThreshold = 1; + hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0; + hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_NONE; + hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_8_CYCLE; + hqspi.Init.FlashSize = 23; // TODO - set to log2(chip size in bytes) - 1 once the part is confirmed + hqspi.Init.FlashID = QSPI_FLASH_ID_1; + + return HAL_QSPI_Init(&hqspi) == HAL_OK; +} + +bool reset_and_check_id() { + if (!exec_command(CMD_NAND_RESET)) + return false; + + delay(2); // TODO - confirm reset recovery time against the datasheet + + uint8_t id[2]; // TODO - confirm ID response length/format against the datasheet + if (!receive(CMD_NAND_READ_ID, id, sizeof(id), 0x00, 8, 8)) + return false; + + CommsSerial.print("NAND JEDEC ID: "); + for (size_t i = 0; i < sizeof(id); ++i) { + CommsSerial.print(id[i], HEX); + CommsSerial.print(' '); + } + CommsSerial.println(); + // TODO - compare against the known-good ID once the part is finalized. + + return true; +} + +bool enable_quad_mode() { + uint8_t config; + if (!get_feature(FEATURE_ADDR_CONFIG, &config)) + return false; + + config |= CONFIG_QE_BIT; + return set_feature(FEATURE_ADDR_CONFIG, config); +} + +void begin() { + if (!init_qspi_peripheral()) { + while (1) { + CommsSerial.println("QSPI Init Failed"); + delay(1000); + } + } + + if (!reset_and_check_id()) { + while (1) { + CommsSerial.println("NAND reset/ID check failed"); + delay(1000); + } + } + + if (!enable_quad_mode()) { + while (1) { + CommsSerial.println("NAND enable quad mode failed"); + delay(1000); + } + } + + cache_loaded = false; + + CommsSerial.println("NAND flash driver ready."); +} + +}; // namespace Flash diff --git a/firmware/lib/logging/flash.h b/firmware/lib/logging/flash.h new file mode 100644 index 0000000..0bf8bf5 --- /dev/null +++ b/firmware/lib/logging/flash.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include + +enum flash_error_t { FLASH_ERROR_SUCCESS, FLASH_ERROR_FAIL, FLASH_ERROR_TIMED_OUT }; + +// Usable (ECC-covered) bytes per page. ECC is left enabled (chip default), so this +// does not include the OOB/spare area - the chip's on-die ECC engine handles that +// transparently on program/read. +#define FLASH_PAGE_SIZE (2048U) + +struct __attribute__((packed)) flash_status_a_t { + uint8_t RESERVED1 : 2; + uint8_t ECCS : 2; + uint8_t P_FAIL : 1; + uint8_t E_FAIL : 1; + uint8_t WEL : 1; + uint8_t OIP : 1; +}; + +struct __attribute__((packed)) flash_status_b_t { + uint8_t RESERVED1 : 2; + uint8_t ECCS : 2; + uint8_t BPS : 1; + uint8_t RESERVED2 : 3; +}; + +namespace Flash { + +// Initializes the QSPI peripheral and configures the NAND (reset, read ID, enable quad mode). +void begin(); + +// Reads one full page. Not flight-loop timing critical - internally does a +// Page-Read-To-Cache followed by a Read-From-Cache. +// addr is a page index (row address), not a byte offset. +flash_error_t read_page(uint32_t addr, uint8_t *out); + +// Loads data into the NAND's internal program cache via PROGRAM LOAD. +// col_addr is the byte offset *within the cache* (0..FLASH_PAGE_SIZE-1), NOT a +// memory-array address. +flash_error_t write_to_cache(uint32_t col_addr, uint8_t *data, size_t len); + +// Commits the current cache contents to the array at page addr (WRITE ENABLE + PROGRAM EXECUTE), +// after polling GET FEATURES until the chip is ready for a new write transaction. +flash_error_t program(uint32_t addr, uint32_t timeout_us = 1000); + +// Erases the whole block containing addr. Required before addr's block can be programmed again. +flash_error_t erase_block(uint32_t addr, uint32_t timeout_us = 10000); + +// Reads both status registers. status_a alone is expected to be sufficient to tell whether +// the last read/program failed (see ECCS/E_FAIL/P_FAIL); status_b may be left null. +flash_error_t read_status(flash_status_a_t *status_a, flash_status_b_t *status_b); + +}; // namespace Flash diff --git a/firmware/lib/logging/flash_defs.h b/firmware/lib/logging/flash_defs.h new file mode 100644 index 0000000..d1dd808 --- /dev/null +++ b/firmware/lib/logging/flash_defs.h @@ -0,0 +1,43 @@ +#pragma once + +// SPI NAND command opcodes and GET/SET FEATURES register addresses. +// +// TODO - the exact NAND part is not locked in yet (candidate: GD5F1GQ5RExxG, DS-00889 Rev1.4). +// The opcodes below are the conventional SPI NAND command set shared by most vendors +// (GigaDevice/Winbond/Micron/ISSI all agree on these), but must be re-checked against +// the final datasheet (see DS p.32 for program/erase, p.42 & p.46 for status registers) +// before this is trusted on real hardware. + +#define CMD_NAND_RESET 0xFFu +#define CMD_NAND_READ_ID 0x9Fu +#define CMD_NAND_GET_FEATURES 0x0Fu +#define CMD_NAND_SET_FEATURES 0x1Fu +#define CMD_NAND_WRITE_ENABLE 0x06u +#define CMD_NAND_WRITE_DISABLE 0x04u + +#define CMD_NAND_PAGE_READ 0x13u // page read: array -> cache +#define CMD_NAND_READ_FROM_CACHE 0x03u +#define CMD_NAND_READ_FROM_CACHE_QUAD 0x6Bu // quad output, cache -> host + +#define CMD_NAND_PROGRAM_LOAD 0x02u // load cache, resetting the rest of the cache to 0xFF first +#define CMD_NAND_PROGRAM_LOAD_QUAD 0x32u // same, quad input +#define CMD_NAND_PROGRAM_LOAD_RANDOM 0x84u // load cache without resetting the rest of the cache +#define CMD_NAND_PROGRAM_EXECUTE 0x10u // cache -> array + +#define CMD_NAND_BLOCK_ERASE 0xD8u + +// GET/SET FEATURES register addresses. +// status_a (C0h) and status_b (F0h) are confirmed - see Jacob's spec. +#define FEATURE_ADDR_BLOCK_LOCK 0xA0u +#define FEATURE_ADDR_CONFIG 0xB0u // TODO - confirm QE (quad enable) bit position against final datasheet +#define FEATURE_ADDR_STATUS_A 0xC0u +#define FEATURE_ADDR_STATUS_B 0xF0u + +#define CONFIG_QE_BIT (1u << 0) // TODO - confirm bit position against final datasheet + +// NAND array geometry. +// TODO - confirm against final datasheet. Values below assume a 1Gb x1 part with a +// 2048B (+64B spare) page, 64 pages/block, 1024 blocks - matches the GD5F1GQ5RExxG candidate. +#define NAND_PAGES_PER_BLOCK 64u +#define NAND_NUM_BLOCKS 1024u +#define NAND_NUM_PAGES (NAND_PAGES_PER_BLOCK * NAND_NUM_BLOCKS) diff --git a/firmware/lib/logging/logging.cpp b/firmware/lib/logging/logging.cpp new file mode 100644 index 0000000..8d74208 --- /dev/null +++ b/firmware/lib/logging/logging.cpp @@ -0,0 +1,215 @@ +#include "logging.h" +#include "bad_page_table.h" +#include "flash_defs.h" + +#include "CommandRouter.h" +#include "CommsSerial.h" +#include +#include + +namespace Logging { + +bool log_enabled = false; +uint32_t write_page_addr = 0; // page index currently being assembled in the NAND's cache +uint32_t write_col_addr = 0; // byte offset within that page's cache + +bool is_armed() { + return log_enabled; +} + +// Advances write_page_addr to the next good page, wrapping past any bad ones. +// Returns false if the NAND is full. +bool advance_to_next_good_page() { + write_page_addr++; + while (write_page_addr < NAND_NUM_PAGES && !BPT::is_page_good(write_page_addr)) { + write_page_addr++; + } + write_col_addr = 0; + return write_page_addr < NAND_NUM_PAGES; +} + +void flush_page() { + if (write_col_addr == 0) + return; // nothing written into this page's cache yet + + if (Flash::program(write_page_addr) != FLASH_ERROR_SUCCESS) { + BPT::mark_page_bad(write_page_addr); + } +} + +void write(uint8_t struct_id, uint8_t *data, uint32_t len) { + if (!log_enabled) + return; + + uint32_t entry_len = 1 + len; // id byte + struct data + + if (entry_len > FLASH_PAGE_SIZE) { + CommsSerial.println("Logging::write - struct too large to fit in one page, dropped"); + return; + } + + // Never split an entry across a page boundary, so a reader can always trust that an id + // byte is immediately followed by that struct's full data within the same page. + if (write_col_addr + entry_len > FLASH_PAGE_SIZE) { + flush_page(); + if (!advance_to_next_good_page()) { + CommsSerial.println("Logging::write - out of flash, disarming"); + log_enabled = false; + return; + } + } + + Flash::write_to_cache(write_col_addr, &struct_id, 1); + write_col_addr += 1; + + Flash::write_to_cache(write_col_addr, data, len); + write_col_addr += len; +} + +void complete() { + if (!log_enabled) + return; + + flush_page(); + log_enabled = false; +} + +flash_error_t read_page(uint32_t addr, uint32_t *data) { + if (!BPT::is_page_good(addr)) + return FLASH_ERROR_FAIL; + + flash_error_t err = Flash::read_page(addr, reinterpret_cast(data)); + if (err != FLASH_ERROR_SUCCESS) { + BPT::mark_page_bad(addr); // ECC failure - flag this page/block bad for next time + } + + return err; +} + +// Requires a random confirmation key (same pattern as ASTRA's cmd_log_arm) before erasing +// every currently-good block and enabling writes. This is the gate that's supposed to stop +// anyone from accidentally programming the NAND before a factory bad-block scan has run - +// see the TODO on factory_scan() below. +void cmd_log_arm() { + if (log_enabled) { + CommsSerial.println("Log is already enabled."); + return; + } + + const int key_min = 1000; + const int key_max = 10000; + srand(millis()); + int key = rand() % (key_max - key_min) + key_min; + + CommsSerial.println("WARNING: Proceeding will erase all data currently on the NAND."); + CommsSerial.printf("Enter %d to proceed: \n", key); + + char *res = CommsSerial.readline(); + if (atoi(res) != key) { + CommsSerial.println("Incorrect key entered."); + return; + } + + CommsSerial.println("Erasing all good blocks. This may take a while."); + + for (uint32_t block = 0; block < NAND_NUM_BLOCKS; ++block) { + uint32_t page_addr = block * NAND_PAGES_PER_BLOCK; + if (!BPT::is_page_good(page_addr)) + continue; + + if (Flash::erase_block(page_addr) != FLASH_ERROR_SUCCESS) { + BPT::mark_page_bad(page_addr); + } + } + + CommsSerial.println("Erase complete. Logging enabled."); + + write_page_addr = 0; + while (write_page_addr < NAND_NUM_PAGES && !BPT::is_page_good(write_page_addr)) { + write_page_addr++; + } + write_col_addr = 0; + log_enabled = true; +} + +// Streams the log out page-by-page over USB serial, mirroring ASTRA's send_flash_over_serial() +// protocol (2-byte additive checksum, 'c'/'k' handshake, all-0xFF page = end of log) - see +// firmware/lib/logging/dump.py (TODO - still needs writing, adapted from ASTRA's dump.py, plus +// the struct/type-prefix decoder into CSV that doesn't seem to exist on ASTRA either). +// +// Walking page indices in order and skipping whatever BPT currently marks bad reproduces the +// exact sequence write() used while logging, since write() does the same walk/skip - as long +// as BPT hasn't changed between logging and downloading. +void dump_flash() { + static uint8_t page_buf[FLASH_PAGE_SIZE]; + + while (USB_CommsSerial.available()) { + USB_CommsSerial.read(); + } + + for (uint32_t addr = 0; addr < NAND_NUM_PAGES; ++addr) { + if (!BPT::is_page_good(addr)) + continue; + + if (Flash::read_page(addr, page_buf) != FLASH_ERROR_SUCCESS) { + BPT::mark_page_bad(addr); + continue; + } + + bool all_ff = true; + uint8_t cs_a = 0, cs_b = 0; + for (uint32_t i = 0; i < FLASH_PAGE_SIZE; ++i) { + cs_a += page_buf[i]; + cs_b += cs_a; + if (page_buf[i] != 0xFF) + all_ff = false; + } + + if (all_ff) { + USB_CommsSerial.write('k'); + return; + } + + USB_CommsSerial.write(page_buf, sizeof(page_buf)); + USB_CommsSerial.write(cs_a); + USB_CommsSerial.write(cs_b); + USB_CommsSerial.write('c'); + + int c; + uint32_t wait_start = millis(); + do { + c = USB_CommsSerial.read(); + if (millis() - wait_start > 5000) { + CommsSerial.println("Error: flash dump timed out"); + return; + } + } while (!(c == 'k' || c == 'c')); + + if (c == 'k') + return; + } + + USB_CommsSerial.write('k'); +} + +// TODO: scanning for factory-bad blocks needs to read each block's OOB/spare area (the +// factory bad-block marker lives past the FLASH_PAGE_SIZE=2048 ECC-covered region that +// Flash::read_page() exposes), which isn't part of the Flash interface yet. Needs a new +// low-level primitive (e.g. a raw, non-ECC-checked read at a column offset >= 2048) once +// the OOB layout is confirmed against the final NAND datasheet. +// +// This MUST run (and BPT get built from it) before any write ever touches the NAND - +// that's the whole reason cmd_log_arm() requires a confirmation key first. +void factory_scan() { + CommsSerial.println("factory_scan not yet implemented - see TODO in logging.cpp"); +} + +void begin() { + BPT::begin(); + + CommandRouter::add(cmd_log_arm, "log_arm"); + CommandRouter::add(dump_flash, "dump_flash", "use this with logging/dump.py (not yet written)"); + CommandRouter::add(factory_scan, "factory_scan", "TODO - not yet implemented, see logging.cpp"); +} + +} // namespace Logging From fe4c30cf2b9609cff1a97df5c6f3ecf7ec1a0dfb Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Thu, 10 Sep 2026 19:34:46 -0400 Subject: [PATCH 03/32] more clear types --- firmware/lib/logging/flash.h | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/firmware/lib/logging/flash.h b/firmware/lib/logging/flash.h index 0bf8bf5..c762102 100644 --- a/firmware/lib/logging/flash.h +++ b/firmware/lib/logging/flash.h @@ -3,53 +3,42 @@ #include #include -enum flash_error_t { FLASH_ERROR_SUCCESS, FLASH_ERROR_FAIL, FLASH_ERROR_TIMED_OUT }; - -// Usable (ECC-covered) bytes per page. ECC is left enabled (chip default), so this -// does not include the OOB/spare area - the chip's on-die ECC engine handles that -// transparently on program/read. #define FLASH_PAGE_SIZE (2048U) -struct __attribute__((packed)) flash_status_a_t { +typedef enum { + FLASH_ERROR_SUCCESS, + FLASH_ERROR_FAIL, + FLASH_ERROR_TIMED_OUT +} flash_error_t; + +typedef struct __attribute__((packed)) { uint8_t RESERVED1 : 2; uint8_t ECCS : 2; uint8_t P_FAIL : 1; uint8_t E_FAIL : 1; uint8_t WEL : 1; uint8_t OIP : 1; -}; +} flash_status_a_t; -struct __attribute__((packed)) flash_status_b_t { +typedef struct __attribute__((packed)) { uint8_t RESERVED1 : 2; uint8_t ECCS : 2; uint8_t BPS : 1; uint8_t RESERVED2 : 3; -}; +} flash_status_b_t; namespace Flash { -// Initializes the QSPI peripheral and configures the NAND (reset, read ID, enable quad mode). void begin(); -// Reads one full page. Not flight-loop timing critical - internally does a -// Page-Read-To-Cache followed by a Read-From-Cache. -// addr is a page index (row address), not a byte offset. flash_error_t read_page(uint32_t addr, uint8_t *out); -// Loads data into the NAND's internal program cache via PROGRAM LOAD. -// col_addr is the byte offset *within the cache* (0..FLASH_PAGE_SIZE-1), NOT a -// memory-array address. flash_error_t write_to_cache(uint32_t col_addr, uint8_t *data, size_t len); -// Commits the current cache contents to the array at page addr (WRITE ENABLE + PROGRAM EXECUTE), -// after polling GET FEATURES until the chip is ready for a new write transaction. flash_error_t program(uint32_t addr, uint32_t timeout_us = 1000); -// Erases the whole block containing addr. Required before addr's block can be programmed again. flash_error_t erase_block(uint32_t addr, uint32_t timeout_us = 10000); -// Reads both status registers. status_a alone is expected to be sufficient to tell whether -// the last read/program failed (see ECCS/E_FAIL/P_FAIL); status_b may be left null. flash_error_t read_status(flash_status_a_t *status_a, flash_status_b_t *status_b); }; // namespace Flash From 0630dd52508806c55ed2d8194c0e48f9647dc112 Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Thu, 10 Sep 2026 23:17:10 -0400 Subject: [PATCH 04/32] flash type restructuring --- firmware/lib/logging/flash.cpp | 49 ------------------------------- firmware/lib/logging/flash.h | 16 +++++----- firmware/lib/logging/flash_defs.h | 4 +-- 3 files changed, 9 insertions(+), 60 deletions(-) diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp index 6cc8008..05692e8 100644 --- a/firmware/lib/logging/flash.cpp +++ b/firmware/lib/logging/flash.cpp @@ -9,56 +9,10 @@ #define FREQ_MAX 50'000'000 #define HAL_TIMEOUT 10000 -// Pin mapping confirmed against the EC schematic (MCU.SchDoc, STM32H747BIT6) on 2026-09-09: -// PB2 -> QUADSPI_CLK (AF9) -// PD11 -> QUADSPI_BK1_IO0 (AF9) -// PD12 -> QUADSPI_BK1_IO1 (AF9) -// PF7 -> QUADSPI_BK1_IO2 (AF9) -// PD13 -> QUADSPI_BK1_IO3 (AF9) -// PG6 -> QUADSPI_BK1_NCS (AF10) -// This mirrors ASTRA's flash.cpp in hardcoding pins directly in the MSP init rather than -// going through ec_pins.h, since HAL_QSPI_MspInit is a one-time, low-level peripheral -// binding rather than a general-purpose GPIO the rest of the codebase reconfigures. -void HAL_QSPI_MspInit(QSPI_HandleTypeDef *hqspi) { - __HAL_RCC_QSPI_CLK_ENABLE(); - __HAL_RCC_QSPI_FORCE_RESET(); - __HAL_RCC_QSPI_RELEASE_RESET(); - - __HAL_RCC_GPIOB_CLK_ENABLE(); - __HAL_RCC_GPIOD_CLK_ENABLE(); - __HAL_RCC_GPIOF_CLK_ENABLE(); - __HAL_RCC_GPIOG_CLK_ENABLE(); - - GPIO_InitTypeDef gpio_init{}; - gpio_init.Mode = GPIO_MODE_AF_PP; - gpio_init.Pull = GPIO_NOPULL; - gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - - gpio_init.Pin = GPIO_PIN_2; - gpio_init.Alternate = GPIO_AF9_QUADSPI; - HAL_GPIO_Init(GPIOB, &gpio_init); - - gpio_init.Pin = GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13; - gpio_init.Alternate = GPIO_AF9_QUADSPI; - HAL_GPIO_Init(GPIOD, &gpio_init); - - gpio_init.Pin = GPIO_PIN_7; - gpio_init.Alternate = GPIO_AF9_QUADSPI; - HAL_GPIO_Init(GPIOF, &gpio_init); - - gpio_init.Pin = GPIO_PIN_6; - gpio_init.Alternate = GPIO_AF10_QUADSPI; - HAL_GPIO_Init(GPIOG, &gpio_init); -} - namespace Flash { QSPI_HandleTypeDef hqspi; -// True once PROGRAM LOAD has reset the cache to 0xFF for the page currently being -// assembled. Needed because write_to_cache() may be called several times (id byte, -// then struct bytes) before program() flushes the page - only the first of those -// calls should reset the rest of the cache, or later calls would stomp earlier ones. bool cache_loaded = false; bool exec_command(uint8_t instruction, uint32_t data_len = 0, uint32_t addr = UINT32_MAX, uint8_t addr_size_bits = 24, unsigned int dummy = 0, bool quad_data = false) { @@ -95,8 +49,6 @@ bool transmit(uint8_t instruction, void *data, uint32_t len, uint32_t addr = UIN return HAL_QSPI_Transmit(&hqspi, static_cast(data), HAL_TIMEOUT) == HAL_OK; } -// GET FEATURES / SET FEATURES address the target register with a 1-byte address, not a -// memory-array address. bool get_feature(uint8_t feature_addr, uint8_t *out) { return receive(CMD_NAND_GET_FEATURES, out, 1, feature_addr, 8); } @@ -123,7 +75,6 @@ flash_error_t read_status(flash_status_a_t *status_a, flash_status_b_t *status_b return FLASH_ERROR_SUCCESS; } -// Polls GET FEATURES (status_a) until OIP (operation in progress) clears, or timeout_us elapses. flash_error_t wait_until_ready(uint32_t timeout_us) { flash_status_a_t status{}; uint32_t start = micros(); diff --git a/firmware/lib/logging/flash.h b/firmware/lib/logging/flash.h index c762102..e36079f 100644 --- a/firmware/lib/logging/flash.h +++ b/firmware/lib/logging/flash.h @@ -12,19 +12,19 @@ typedef enum { } flash_error_t; typedef struct __attribute__((packed)) { - uint8_t RESERVED1 : 2; - uint8_t ECCS : 2; - uint8_t P_FAIL : 1; - uint8_t E_FAIL : 1; - uint8_t WEL : 1; uint8_t OIP : 1; + uint8_t WEL : 1; + uint8_t E_FAIL : 1; + uint8_t P_FAIL : 1; + uint8_t ECCS : 2; + uint8_t RESERVED1 : 2; } flash_status_a_t; typedef struct __attribute__((packed)) { - uint8_t RESERVED1 : 2; - uint8_t ECCS : 2; + uint8_t RESERVED1 : 3; uint8_t BPS : 1; - uint8_t RESERVED2 : 3; + uint8_t ECCS : 2; + uint8_t RESERVED2 : 2; } flash_status_b_t; namespace Flash { diff --git a/firmware/lib/logging/flash_defs.h b/firmware/lib/logging/flash_defs.h index d1dd808..22e702e 100644 --- a/firmware/lib/logging/flash_defs.h +++ b/firmware/lib/logging/flash_defs.h @@ -26,10 +26,8 @@ #define CMD_NAND_BLOCK_ERASE 0xD8u -// GET/SET FEATURES register addresses. -// status_a (C0h) and status_b (F0h) are confirmed - see Jacob's spec. #define FEATURE_ADDR_BLOCK_LOCK 0xA0u -#define FEATURE_ADDR_CONFIG 0xB0u // TODO - confirm QE (quad enable) bit position against final datasheet +#define FEATURE_ADDR_CONFIG 0xB0u #define FEATURE_ADDR_STATUS_A 0xC0u #define FEATURE_ADDR_STATUS_B 0xF0u From e23abe6e94a98408c4cb43965732e31f08f89955 Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Thu, 10 Sep 2026 23:24:50 -0400 Subject: [PATCH 05/32] ECCS to ECCSE type --- firmware/lib/logging/flash.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/lib/logging/flash.h b/firmware/lib/logging/flash.h index e36079f..065e273 100644 --- a/firmware/lib/logging/flash.h +++ b/firmware/lib/logging/flash.h @@ -23,7 +23,7 @@ typedef struct __attribute__((packed)) { typedef struct __attribute__((packed)) { uint8_t RESERVED1 : 3; uint8_t BPS : 1; - uint8_t ECCS : 2; + uint8_t ECCSE : 2; uint8_t RESERVED2 : 2; } flash_status_b_t; From 1fcb8f133646f01389854d8e67a6035b6c964715 Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Sat, 19 Sep 2026 15:18:42 -0400 Subject: [PATCH 06/32] updated flash hash defines --- firmware/lib/logging/flash.cpp | 10 ++++-- firmware/lib/logging/flash.h | 2 -- firmware/lib/logging/flash_defs.h | 57 ++++++++++++++++--------------- 3 files changed, 37 insertions(+), 32 deletions(-) diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp index 05692e8..7f7bffb 100644 --- a/firmware/lib/logging/flash.cpp +++ b/firmware/lib/logging/flash.cpp @@ -1,3 +1,10 @@ +/** + * @file flash.cpp + * @brief NAND flash implementation for GD5F1GQ5UEYIGR chip + * + * @author Daniel Proano (dproano@purdue.edu) + */ + #include "flash.h" #include "flash_defs.h" @@ -6,9 +13,6 @@ #include #include -#define FREQ_MAX 50'000'000 -#define HAL_TIMEOUT 10000 - namespace Flash { QSPI_HandleTypeDef hqspi; diff --git a/firmware/lib/logging/flash.h b/firmware/lib/logging/flash.h index 065e273..57672bb 100644 --- a/firmware/lib/logging/flash.h +++ b/firmware/lib/logging/flash.h @@ -3,8 +3,6 @@ #include #include -#define FLASH_PAGE_SIZE (2048U) - typedef enum { FLASH_ERROR_SUCCESS, FLASH_ERROR_FAIL, diff --git a/firmware/lib/logging/flash_defs.h b/firmware/lib/logging/flash_defs.h index 22e702e..c104e08 100644 --- a/firmware/lib/logging/flash_defs.h +++ b/firmware/lib/logging/flash_defs.h @@ -1,41 +1,44 @@ +/** + * @file flash_defs.h + * @brief NAND flash definitions for the GD5F1GQ5xExxG chip + * + * @author Daniel Proano (dproano@purdue.edu) + */ + #pragma once -// SPI NAND command opcodes and GET/SET FEATURES register addresses. -// -// TODO - the exact NAND part is not locked in yet (candidate: GD5F1GQ5RExxG, DS-00889 Rev1.4). -// The opcodes below are the conventional SPI NAND command set shared by most vendors -// (GigaDevice/Winbond/Micron/ISSI all agree on these), but must be re-checked against -// the final datasheet (see DS p.32 for program/erase, p.42 & p.46 for status registers) -// before this is trusted on real hardware. +#define NAND_NUM_BLOCKS 1024u +#define NAND_PAGES_PER_BLOCK 64u +#define NAND_BYTES_PER_PAGE 2176u +#define NAND_PAGE_SIZE 2048u +#define NAND_SPARE_PAGE_SIZE (NAND_BYTES_PER_PAGE - NAND_PAGE_SIZE) +#define NAND_NUM_PAGES (NAND_PAGES_PER_BLOCK * NAND_NUM_BLOCKS) +#define NAND_NUM_BYTES_PER_BLOCK (NAND_PAGES_PER_BLOCK * NAND_BYTES_PER_PAGE) +#define NAND_NUM_BYTES (NAND_NUM_BYTES_PER_BLOCK * NAND_NUM_BLOCKS) -#define CMD_NAND_RESET 0xFFu -#define CMD_NAND_READ_ID 0x9Fu -#define CMD_NAND_GET_FEATURES 0x0Fu -#define CMD_NAND_SET_FEATURES 0x1Fu #define CMD_NAND_WRITE_ENABLE 0x06u #define CMD_NAND_WRITE_DISABLE 0x04u - -#define CMD_NAND_PAGE_READ 0x13u // page read: array -> cache +#define CMD_NAND_GET_FEATURES 0x0Fu +#define CMD_NAND_SET_FEATURES 0x1Fu +#define CMD_NAND_PAGE_READ 0x13u #define CMD_NAND_READ_FROM_CACHE 0x03u -#define CMD_NAND_READ_FROM_CACHE_QUAD 0x6Bu // quad output, cache -> host - -#define CMD_NAND_PROGRAM_LOAD 0x02u // load cache, resetting the rest of the cache to 0xFF first -#define CMD_NAND_PROGRAM_LOAD_QUAD 0x32u // same, quad input -#define CMD_NAND_PROGRAM_LOAD_RANDOM 0x84u // load cache without resetting the rest of the cache -#define CMD_NAND_PROGRAM_EXECUTE 0x10u // cache -> array - +#define CMD_NAND_READ_FROM_CACHE_QUAD 0x6Bu +#define CMD_NAND_READ_FROM_CACHE_QUAD_IO 0xEBu +#define CMD_NAND_READ_ID 0x9Fu +#define CMD_NAND_PROGRAM_LOAD 0x02u +#define CMD_NAND_PROGRAM_LOAD_QUAD 0x32u +#define CMD_NAND_PROGRAM_EXECUTE 0x10u +#define CMD_NAND_PROGRAM_LOAD_RANDOM 0x84u +#define CMD_NAND_PROGRAM_LOAD_RANDOM_QUAD 0xC4u #define CMD_NAND_BLOCK_ERASE 0xD8u +#define CMD_NAND_RESET 0xFFu #define FEATURE_ADDR_BLOCK_LOCK 0xA0u #define FEATURE_ADDR_CONFIG 0xB0u #define FEATURE_ADDR_STATUS_A 0xC0u #define FEATURE_ADDR_STATUS_B 0xF0u -#define CONFIG_QE_BIT (1u << 0) // TODO - confirm bit position against final datasheet +#define CONFIG_QE_BIT (1u << 0) -// NAND array geometry. -// TODO - confirm against final datasheet. Values below assume a 1Gb x1 part with a -// 2048B (+64B spare) page, 64 pages/block, 1024 blocks - matches the GD5F1GQ5RExxG candidate. -#define NAND_PAGES_PER_BLOCK 64u -#define NAND_NUM_BLOCKS 1024u -#define NAND_NUM_PAGES (NAND_PAGES_PER_BLOCK * NAND_NUM_BLOCKS) +#define FREQ_MAX 50'000'000 +#define HAL_TIMEOUT 10000 From e2f439a1074143ce2969611109b29f17bd10b826 Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Sat, 19 Sep 2026 18:53:32 -0400 Subject: [PATCH 07/32] in progress page reads --- firmware/lib/logging/flash.cpp | 119 ++++++++++++++------------------- firmware/lib/logging/flash.h | 6 +- firmware/src/ec_main.cpp | 5 +- 3 files changed, 56 insertions(+), 74 deletions(-) diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp index 7f7bffb..7f40fa0 100644 --- a/firmware/lib/logging/flash.cpp +++ b/firmware/lib/logging/flash.cpp @@ -9,7 +9,9 @@ #include "flash_defs.h" #include "CommsSerial.h" +#include "stm32h7xx_hal_qspi.h" #include +#include #include #include @@ -19,93 +21,70 @@ QSPI_HandleTypeDef hqspi; bool cache_loaded = false; -bool exec_command(uint8_t instruction, uint32_t data_len = 0, uint32_t addr = UINT32_MAX, uint8_t addr_size_bits = 24, unsigned int dummy = 0, bool quad_data = false) { - QSPI_CommandTypeDef cmd{}; - - cmd.Instruction = instruction; - cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE; - cmd.DummyCycles = dummy; - cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; - - if (addr != UINT32_MAX) { - cmd.Address = addr; - cmd.AddressMode = QSPI_ADDRESS_1_LINE; - cmd.AddressSize = (addr_size_bits == 8) ? QSPI_ADDRESS_8_BITS : (addr_size_bits == 16) ? QSPI_ADDRESS_16_BITS : QSPI_ADDRESS_24_BITS; - } - - if (data_len) { - cmd.NbData = data_len; - cmd.DataMode = quad_data ? QSPI_DATA_4_LINES : QSPI_DATA_1_LINE; +void build_cmd(QSPI_CommandTypeDef *cmd, uint32_t nand_cmd, uint32_t addr, uint32_t addr_size, uint32_t data_size = 0) { + memset(cmd, 0, sizeof(*cmd)); + + cmd->Instruction = nand_cmd; + cmd->Address = addr; + cmd->AddressSize = addr_size; + cmd->InstructionMode = QSPI_INSTRUCTION_1_LINE; // during instr phase, use 1 spi line + cmd->AddressMode = QSPI_ADDRESS_1_LINE; // during address phase, use 1 spi line + + if (data_size != 0) { + cmd->DataMode = QSPI_DATA_1_LINE; + cmd->NbData = data_size; } - - return HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) == HAL_OK; -} - -bool receive(uint8_t instruction, void *data, uint32_t len, uint32_t addr = UINT32_MAX, uint8_t addr_size_bits = 24, unsigned int dummy = 0, bool quad_data = false) { - if (!exec_command(instruction, len, addr, addr_size_bits, dummy, quad_data)) - return false; - return HAL_QSPI_Receive(&hqspi, static_cast(data), HAL_TIMEOUT) == HAL_OK; } -bool transmit(uint8_t instruction, void *data, uint32_t len, uint32_t addr = UINT32_MAX, uint8_t addr_size_bits = 24, bool quad_data = false) { - if (!exec_command(instruction, len, addr, addr_size_bits, 0, quad_data)) +bool get_status(uint32_t addr, uint8_t *data_out) { + QSPI_CommandTypeDef cmd; + build_cmd(&cmd, CMD_NAND_GET_FEATURES, addr, QSPI_ADDRESS_8_BITS, 1); + if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { return false; - return HAL_QSPI_Transmit(&hqspi, static_cast(data), HAL_TIMEOUT) == HAL_OK; -} - -bool get_feature(uint8_t feature_addr, uint8_t *out) { - return receive(CMD_NAND_GET_FEATURES, out, 1, feature_addr, 8); -} - -bool set_feature(uint8_t feature_addr, uint8_t value) { - return transmit(CMD_NAND_SET_FEATURES, &value, 1, feature_addr, 8); -} - -flash_error_t read_status(flash_status_a_t *status_a, flash_status_b_t *status_b) { - uint8_t raw; - - if (status_a) { - if (!get_feature(FEATURE_ADDR_STATUS_A, &raw)) - return FLASH_ERROR_FAIL; - memcpy(status_a, &raw, sizeof(raw)); } - if (status_b) { - if (!get_feature(FEATURE_ADDR_STATUS_B, &raw)) - return FLASH_ERROR_FAIL; - memcpy(status_b, &raw, sizeof(raw)); + uint8_t data; + if (HAL_QSPI_Receive(&hqspi, &data, HAL_TIMEOUT) != HAL_OK) { + return false; } - return FLASH_ERROR_SUCCESS; -} + *data_out = data; + return true; +}; -flash_error_t wait_until_ready(uint32_t timeout_us) { - flash_status_a_t status{}; - uint32_t start = micros(); +flash_error_t wait_until_ready(uint32_t timeout) { + uint32_t start_time = micros(); - do { - if (!get_feature(FEATURE_ADDR_STATUS_A, reinterpret_cast(&status))) - return FLASH_ERROR_FAIL; + while (true) { + uint8_t data; + if (!get_status(FEATURE_ADDR_STATUS_A, &data)) { + return FLASH_FAIL; + } - if (!status.OIP) - return FLASH_ERROR_SUCCESS; + if (!(data & (1 << 0))) { + return FLASH_SUCCESS; + } - if (micros() - start > timeout_us) - return FLASH_ERROR_TIMED_OUT; - } while (true); + if (micros() - start_time > timeout) { + return FLASH_TIMED_OUT; + } + } } flash_error_t read_page(uint32_t addr, uint8_t *out) { - // Page Read: array -> cache. - if (!exec_command(CMD_NAND_PAGE_READ, 0, addr, 24)) - return FLASH_ERROR_FAIL; + // load page into cache + QSPI_CommandTypeDef cmd; + build_cmd(&cmd, CMD_NAND_PAGE_READ, addr, QSPI_ADDRESS_24_BITS); + if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { + return FLASH_FAIL; + } - flash_error_t err = wait_until_ready(50000); // TODO - confirm max tR from datasheet - if (err != FLASH_ERROR_SUCCESS) + // wait for page to arrive + flash_error_t err = wait_until_ready(60); + if (err != FLASH_SUCCESS) { return err; + } - // Read From Cache: cache -> host, quad data lines, starting at column 0. - // TODO - confirm dummy cycle count for the quad read opcode against the datasheet. if (!receive(CMD_NAND_READ_FROM_CACHE_QUAD, out, FLASH_PAGE_SIZE, 0, 16, 8, true)) return FLASH_ERROR_FAIL; @@ -150,7 +129,7 @@ flash_error_t program(uint32_t addr, uint32_t timeout_us) { return FLASH_ERROR_FAIL; err = wait_until_ready(timeout_us); - cache_loaded = false; // next write_to_cache() call starts a fresh cache, regardless of outcome + cache_loaded = false; if (err != FLASH_ERROR_SUCCESS) return err; diff --git a/firmware/lib/logging/flash.h b/firmware/lib/logging/flash.h index 57672bb..92fd5e7 100644 --- a/firmware/lib/logging/flash.h +++ b/firmware/lib/logging/flash.h @@ -4,9 +4,9 @@ #include typedef enum { - FLASH_ERROR_SUCCESS, - FLASH_ERROR_FAIL, - FLASH_ERROR_TIMED_OUT + FLASH_SUCCESS, + FLASH_FAIL, + FLASH_TIMED_OUT } flash_error_t; typedef struct __attribute__((packed)) { diff --git a/firmware/src/ec_main.cpp b/firmware/src/ec_main.cpp index babf2e9..4db6660 100644 --- a/firmware/src/ec_main.cpp +++ b/firmware/src/ec_main.cpp @@ -2,7 +2,7 @@ #include "CommandRouter.h" #include "CommsSerial.h" -#include "ErrorCounters.h" +#include "logging.h" #include "PressureSensors.h" #include "RCS.h" #include "SolenoidValves.h" @@ -36,6 +36,9 @@ void setup() { PT_TC_SPI_1.begin(); PT_TC_SPI_3.begin(); + Flash::begin(); + Logging::begin(); + delay(3000); CommsSerial.println("Engine Controller Started!"); From 7e88e322fce2cd2590d58d02227db5605686b4dc Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Sun, 20 Sep 2026 12:32:30 -0400 Subject: [PATCH 08/32] low level flash logic --- firmware/lib/logging/bad_page_table.cpp | 131 --------- firmware/lib/logging/bad_page_table.h | 19 -- firmware/lib/logging/flash.cpp | 345 +++++++++++++++++------- firmware/lib/logging/flash.h | 14 +- firmware/lib/logging/flash_defs.h | 2 + firmware/lib/logging/logging.cpp | 215 --------------- firmware/src/ec_main.cpp | 3 +- 7 files changed, 260 insertions(+), 469 deletions(-) delete mode 100644 firmware/lib/logging/bad_page_table.cpp delete mode 100644 firmware/lib/logging/bad_page_table.h delete mode 100644 firmware/lib/logging/logging.cpp diff --git a/firmware/lib/logging/bad_page_table.cpp b/firmware/lib/logging/bad_page_table.cpp deleted file mode 100644 index 9c24bff..0000000 --- a/firmware/lib/logging/bad_page_table.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#include "bad_page_table.h" -#include "flash_defs.h" - -#include "CommsSerial.h" -#include -#include - -#define BPT_MAGIC 0x50425401u // 'PBT' + version 1 - arbitrary, just needs to be unlikely to appear in erased flash - -#define BITMAP_SIZE_BYTES ((NAND_NUM_BLOCKS + 7) / 8) -#define BPT_TABLE_SIZE_BYTES (sizeof(uint32_t) + BITMAP_SIZE_BYTES) -// STM32H7 internal flash program granularity is one 256-bit "flash word" (32 bytes) at a time. -#define FLASH_WORD_SIZE 32u -#define BPT_WRITE_SIZE (((BPT_TABLE_SIZE_BYTES + FLASH_WORD_SIZE - 1) / FLASH_WORD_SIZE) * FLASH_WORD_SIZE) - -// Provided by the linker (ldscript.ld) - base of the reserved Bank 2, Sector 0 region. -extern uint32_t _bpt_table_start; - -namespace BPT { - -uint32_t bpt_base_addr; - -// STM32H7's flash is ECC-protected per flash word: once a word is programmed, it cannot be -// programmed again (even to clear further bits to 0) without erasing its sector first - unlike -// classic STM32 NOR-style flash. So any update to the table means erase-the-sector-and-rewrite- -// the-whole-thing, not a targeted bit flip. -bool erase_bpt_sector() { - HAL_FLASH_Unlock(); - - FLASH_EraseInitTypeDef erase_init{}; - erase_init.TypeErase = FLASH_TYPEERASE_SECTORS; - erase_init.Banks = FLASH_BANK_2; - erase_init.Sector = FLASH_SECTOR_0; - erase_init.NbSectors = 1; - erase_init.VoltageRange = FLASH_VOLTAGE_RANGE_3; // TODO - confirm correct voltage range for this board - - uint32_t sector_error; - HAL_StatusTypeDef status = HAL_FLASHEx_Erase(&erase_init, §or_error); - - HAL_FLASH_Lock(); - return status == HAL_OK; -} - -bool program_flash_word(uint32_t address, const uint8_t *data) { - HAL_FLASH_Unlock(); - HAL_StatusTypeDef status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, address, reinterpret_cast(data)); - HAL_FLASH_Lock(); - return status == HAL_OK; -} - -// Erases the BPT sector and rewrites the magic number + given bitmap in one pass. -bool write_table(const uint8_t *bitmap) { - if (!erase_bpt_sector()) - return false; - - uint8_t buf[BPT_WRITE_SIZE]; - memset(buf, 0xFF, sizeof(buf)); - - uint32_t magic = BPT_MAGIC; - memcpy(buf, &magic, sizeof(magic)); - memcpy(buf + sizeof(magic), bitmap, BITMAP_SIZE_BYTES); - - for (uint32_t offset = 0; offset < sizeof(buf); offset += FLASH_WORD_SIZE) { - if (!program_flash_word(bpt_base_addr + offset, buf + offset)) - return false; - } - - return true; -} - -bool is_page_good(uint32_t addr) { - uint32_t block = addr / NAND_PAGES_PER_BLOCK; - - const uint8_t *bitmap = reinterpret_cast(bpt_base_addr + sizeof(uint32_t)); - bool bad = bitmap[block / 8] & (1u << (block % 8)); - - return !bad; -} - -void mark_page_bad(uint32_t addr) { - uint32_t block = addr / NAND_PAGES_PER_BLOCK; - - if (!is_page_good(addr)) - return; // already marked - - uint8_t bitmap[BITMAP_SIZE_BYTES]; - memcpy(bitmap, reinterpret_cast(bpt_base_addr + sizeof(uint32_t)), sizeof(bitmap)); - bitmap[block / 8] |= (1u << (block % 8)); - - if (!write_table(bitmap)) { - CommsSerial.println("BPT: failed to persist newly-marked bad block!"); - } -} - -void begin() { - bpt_base_addr = reinterpret_cast(&_bpt_table_start); - - uint32_t magic; - memcpy(&magic, reinterpret_cast(bpt_base_addr), sizeof(magic)); - - if (magic == BPT_MAGIC) - return; // table already valid - - CommsSerial.println("Bad Page Table is not initialized."); - CommsSerial.println("This will erase the current BPT and mark every page as good."); - CommsSerial.println("This does NOT scan the NAND for factory-bad blocks - run the factory scan separately before the first write."); - CommsSerial.print("Type 'yes' to initialize the table: "); - - char *response = CommsSerial.readline(); - - if (strcmp(response, "yes") != 0) { - while (1) { - CommsSerial.println("BPT not initialized - halting."); - delay(1000); - } - } - - uint8_t bitmap[BITMAP_SIZE_BYTES]; - memset(bitmap, 0x00, sizeof(bitmap)); // 0 = good - - if (!write_table(bitmap)) { - while (1) { - CommsSerial.println("Failed to initialize BPT - halting."); - delay(1000); - } - } - - CommsSerial.println("BPT initialized."); -} - -} // namespace BPT diff --git a/firmware/lib/logging/bad_page_table.h b/firmware/lib/logging/bad_page_table.h deleted file mode 100644 index 95832a4..0000000 --- a/firmware/lib/logging/bad_page_table.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include - -namespace BPT { - -// Loads the Bad Page Table from internal flash (Bank 2, Sector 0 - see ldscript.ld). -// Checks a magic number at the table's base; if missing, prompts over serial before -// initializing a fresh (all-good) table. Must be called before is_page_good()/mark_page_bad(). -void begin(); - -// NAND bad-block marking is inherently block-granularity (the factory marker and any -// runtime ECC failure both apply to the whole block containing a page), so addr is -// mapped to its containing block internally even though it's expressed as a page index. -bool is_page_good(uint32_t addr); - -void mark_page_bad(uint32_t addr); - -} // namespace BPT diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp index 7f40fa0..0eabb84 100644 --- a/firmware/lib/logging/flash.cpp +++ b/firmware/lib/logging/flash.cpp @@ -5,11 +5,11 @@ * @author Daniel Proano (dproano@purdue.edu) */ +#include "CommsSerial.h" #include "flash.h" #include "flash_defs.h" - -#include "CommsSerial.h" #include "stm32h7xx_hal_qspi.h" + #include #include #include @@ -21,24 +21,38 @@ QSPI_HandleTypeDef hqspi; bool cache_loaded = false; -void build_cmd(QSPI_CommandTypeDef *cmd, uint32_t nand_cmd, uint32_t addr, uint32_t addr_size, uint32_t data_size = 0) { +typedef struct { + uint32_t opcode; + uint32_t addr = UINT32_MAX; // UINT32_MAX = no address phase + uint32_t addr_size = QSPI_ADDRESS_8_BITS; + uint32_t data_size = 0; // 0 = no data phase + uint32_t dummy_cycles = 0; + uint32_t data_lines = QSPI_DATA_1_LINE; +} cmd_spec; + +void build_cmd(QSPI_CommandTypeDef *cmd, cmd_spec spec) { memset(cmd, 0, sizeof(*cmd)); - cmd->Instruction = nand_cmd; - cmd->Address = addr; - cmd->AddressSize = addr_size; + cmd->Instruction = spec.opcode; cmd->InstructionMode = QSPI_INSTRUCTION_1_LINE; // during instr phase, use 1 spi line - cmd->AddressMode = QSPI_ADDRESS_1_LINE; // during address phase, use 1 spi line - - if (data_size != 0) { - cmd->DataMode = QSPI_DATA_1_LINE; - cmd->NbData = data_size; + cmd->DummyCycles = spec.dummy_cycles; + + // Some commands have no address phase at all + if (spec.addr != UINT32_MAX) { + cmd->Address = spec.addr; + cmd->AddressSize = spec.addr_size; + cmd->AddressMode = QSPI_ADDRESS_1_LINE; // during address phase, use 1 spi line + } + + if (spec.data_size != 0) { + cmd->DataMode = spec.data_lines; + cmd->NbData = spec.data_size; } } -bool get_status(uint32_t addr, uint8_t *data_out) { +bool get_status_a(flash_status_a_t *data_out) { QSPI_CommandTypeDef cmd; - build_cmd(&cmd, CMD_NAND_GET_FEATURES, addr, QSPI_ADDRESS_8_BITS, 1); + build_cmd(&cmd, {.opcode = CMD_NAND_GET_FEATURES, .addr = FEATURE_ADDR_STATUS_A, .data_size = 1}); if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { return false; } @@ -48,7 +62,24 @@ bool get_status(uint32_t addr, uint8_t *data_out) { return false; } - *data_out = data; + memcpy(data_out, &data, sizeof(data)); + return true; +}; + + +bool get_status_b(flash_status_b_t *data_out) { + QSPI_CommandTypeDef cmd; + build_cmd(&cmd, {.opcode = CMD_NAND_GET_FEATURES, .addr = FEATURE_ADDR_STATUS_B, .data_size = 1}); + if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { + return false; + } + + uint8_t data; + if (HAL_QSPI_Receive(&hqspi, &data, HAL_TIMEOUT) != HAL_OK) { + return false; + } + + memcpy(data_out, &data, sizeof(data)); return true; }; @@ -56,12 +87,13 @@ flash_error_t wait_until_ready(uint32_t timeout) { uint32_t start_time = micros(); while (true) { - uint8_t data; - if (!get_status(FEATURE_ADDR_STATUS_A, &data)) { + flash_status_a_t data; + if (!get_status_a(&data)) { return FLASH_FAIL; } - if (!(data & (1 << 0))) { + // if operation no longer in progress + if (data.OIP == 0) { return FLASH_SUCCESS; } @@ -71,105 +103,195 @@ flash_error_t wait_until_ready(uint32_t timeout) { } } -flash_error_t read_page(uint32_t addr, uint8_t *out) { +flash_error_t read_page(uint32_t addr, uint8_t *data_out) { // load page into cache QSPI_CommandTypeDef cmd; - build_cmd(&cmd, CMD_NAND_PAGE_READ, addr, QSPI_ADDRESS_24_BITS); + build_cmd(&cmd, {.opcode = CMD_NAND_PAGE_READ, .addr = addr, .addr_size = QSPI_ADDRESS_24_BITS}); if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { return FLASH_FAIL; } - // wait for page to arrive + // wait for page to arrive in cache flash_error_t err = wait_until_ready(60); if (err != FLASH_SUCCESS) { return err; } - if (!receive(CMD_NAND_READ_FROM_CACHE_QUAD, out, FLASH_PAGE_SIZE, 0, 16, 8, true)) - return FLASH_ERROR_FAIL; + // tell cache to send data + build_cmd(&cmd, {.opcode = CMD_NAND_READ_FROM_CACHE_QUAD, .addr = 0, .addr_size = QSPI_ADDRESS_16_BITS, .data_size = NAND_PAGE_SIZE, .dummy_cycles = 8, .data_lines = QSPI_DATA_4_LINES}); + if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { + return FLASH_FAIL; + } - flash_status_a_t status_a{}; - if (read_status(&status_a, nullptr) != FLASH_ERROR_SUCCESS) - return FLASH_ERROR_FAIL; + // receive data pointer from cache across spi + if (HAL_QSPI_Receive(&hqspi, data_out, HAL_TIMEOUT) != HAL_OK) { + return FLASH_FAIL; + } + + // load status register + flash_status_a_t status_a; + memset(&status_a, 0, sizeof(status_a)); + if (!get_status_a(&status_a)) { + return FLASH_FAIL; + } - // TODO - confirm which ECCS value(s) mean "uncorrectable" vs. "corrected" against the - // datasheet (DS p.42/46). Treating any nonzero ECCS as a failure is conservative for now. - if (status_a.ECCS != 0) - return FLASH_ERROR_FAIL; + // check whether read was ok + if (status_a.ECCS != 0) { + return FLASH_FAIL; + } - return FLASH_ERROR_SUCCESS; + return FLASH_SUCCESS; } flash_error_t write_to_cache(uint32_t col_addr, uint8_t *data, size_t len) { - if (col_addr + len > FLASH_PAGE_SIZE) - return FLASH_ERROR_FAIL; + // does cache page have enough space? + if (col_addr + len > NAND_PAGE_SIZE) { + return FLASH_FAIL; + } - // The first write into a fresh cache uses PROGRAM LOAD, which resets the rest of the - // cache to 0xFF - so unwritten bytes in a partially-filled page read back as erased - // rather than leftover data from whatever was cached before. Later writes in the same - // page cycle use PROGRAM LOAD RANDOM DATA so they don't clobber earlier writes. - uint8_t cmd = cache_loaded ? CMD_NAND_PROGRAM_LOAD_RANDOM : CMD_NAND_PROGRAM_LOAD; + // The first write to cache needs to use Program Load to clear cache + // and afterwards need to use Load Random to not overwrite content + uint32_t nand_cmd = cache_loaded ? CMD_NAND_PROGRAM_LOAD_RANDOM : CMD_NAND_PROGRAM_LOAD; - if (!transmit(cmd, data, len, col_addr, 16)) - return FLASH_ERROR_FAIL; + // load data into cache + QSPI_CommandTypeDef cmd; + build_cmd(&cmd, {.opcode = nand_cmd, .addr = col_addr, .addr_size = QSPI_ADDRESS_16_BITS, .data_size = len}); + if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { + return FLASH_FAIL; + } + + // send data across spi into cache + if (HAL_QSPI_Transmit(&hqspi, data, HAL_TIMEOUT) != HAL_OK) { + return FLASH_FAIL; + } cache_loaded = true; - return FLASH_ERROR_SUCCESS; + return FLASH_SUCCESS; } -flash_error_t program(uint32_t addr, uint32_t timeout_us) { - flash_error_t err = wait_until_ready(timeout_us); - if (err != FLASH_ERROR_SUCCESS) +flash_error_t program(uint32_t addr) { + // chip can lose commands if it is busy when instruction occurs + // ensure worst case timeout is waited until chip free + flash_error_t err = wait_until_ready(WORST_CASE_PROGRAM_TIMEOUT); + if (err != FLASH_SUCCESS) return err; - if (!exec_command(CMD_NAND_WRITE_ENABLE)) - return FLASH_ERROR_FAIL; + // write enable + QSPI_CommandTypeDef cmd; + build_cmd(&cmd, {.opcode = CMD_NAND_WRITE_ENABLE}); + if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { + return FLASH_FAIL; + } - if (!exec_command(CMD_NAND_PROGRAM_EXECUTE, 0, addr, 24)) - return FLASH_ERROR_FAIL; + // commit cache to memory + build_cmd(&cmd, {.opcode = CMD_NAND_PROGRAM_EXECUTE, .addr = addr, .addr_size = QSPI_ADDRESS_24_BITS}); + if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { + return FLASH_FAIL; + } - err = wait_until_ready(timeout_us); + // wait for cache to be written + err = wait_until_ready(WORST_CASE_MEMORY_TIMEOUT); cache_loaded = false; - if (err != FLASH_ERROR_SUCCESS) + if (err != FLASH_SUCCESS) { return err; + } - flash_status_a_t status_a{}; - if (read_status(&status_a, nullptr) != FLASH_ERROR_SUCCESS) - return FLASH_ERROR_FAIL; + // ensure successful write + flash_status_a_t status_a; + if (!get_status_a(&status_a)) { + return FLASH_FAIL; + } - if (status_a.P_FAIL) - return FLASH_ERROR_FAIL; // caller (Logging) is expected to mark this page/block bad + if (status_a.P_FAIL) { + return FLASH_FAIL; + } - return FLASH_ERROR_SUCCESS; + return FLASH_SUCCESS; } -flash_error_t erase_block(uint32_t addr, uint32_t timeout_us) { - flash_error_t err = wait_until_ready(timeout_us); - if (err != FLASH_ERROR_SUCCESS) +flash_error_t erase_block(uint32_t addr) { + // ensure chip is done with any previous erases + flash_error_t err = wait_until_ready(WORST_CASE_MEMORY_TIMEOUT); + if (err != FLASH_SUCCESS) return err; - if (!exec_command(CMD_NAND_WRITE_ENABLE)) - return FLASH_ERROR_FAIL; + // write enable + QSPI_CommandTypeDef cmd; + build_cmd(&cmd, {.opcode = CMD_NAND_WRITE_ENABLE}); + if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { + return FLASH_FAIL; + } - if (!exec_command(CMD_NAND_BLOCK_ERASE, 0, addr, 24)) - return FLASH_ERROR_FAIL; + // erase block + build_cmd(&cmd, {.opcode = CMD_NAND_BLOCK_ERASE, .addr = addr, .addr_size = QSPI_ADDRESS_24_BITS}); + if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { + return FLASH_FAIL; + } - err = wait_until_ready(timeout_us); - if (err != FLASH_ERROR_SUCCESS) + // wait for erase to finish + err = wait_until_ready(WORST_CASE_MEMORY_TIMEOUT); + if (err != FLASH_SUCCESS) { return err; + } - flash_status_a_t status_a{}; - if (read_status(&status_a, nullptr) != FLASH_ERROR_SUCCESS) - return FLASH_ERROR_FAIL; + // ensure erase was successful + flash_status_a_t status_a; + if (!get_status_a(&status_a)) { + return FLASH_FAIL; + } - if (status_a.E_FAIL) - return FLASH_ERROR_FAIL; // caller (BPT) is expected to mark this block bad + if (status_a.E_FAIL) { + return FLASH_FAIL; + } - return FLASH_ERROR_SUCCESS; + return FLASH_SUCCESS; +} + +void init_qspi_gpio() { + __HAL_RCC_QSPI_CLK_ENABLE(); + __HAL_RCC_GPIOB_CLK_ENABLE(); + __HAL_RCC_GPIOD_CLK_ENABLE(); + __HAL_RCC_GPIOF_CLK_ENABLE(); + __HAL_RCC_GPIOG_CLK_ENABLE(); + + GPIO_InitTypeDef gpio_init{}; + gpio_init.Mode = GPIO_MODE_AF_PP; + gpio_init.Pull = GPIO_NOPULL; + gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + + // CLK, IO0, IO1, IO3 - all AF9 + gpio_init.Alternate = GPIO_AF9_QUADSPI; + + gpio_init.Pin = GPIO_PIN_2; + HAL_GPIO_Init(GPIOB, &gpio_init); + + gpio_init.Pin = GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13; + HAL_GPIO_Init(GPIOD, &gpio_init); + + gpio_init.Pin = GPIO_PIN_7; // PF7 - IO2 + HAL_GPIO_Init(GPIOF, &gpio_init); + + // NCS - AF10 + gpio_init.Alternate = GPIO_AF10_QUADSPI; + gpio_init.Pin = GPIO_PIN_6; // PG6 - NCS + HAL_GPIO_Init(GPIOG, &gpio_init); +} + +bool disable_block_protection() { + QSPI_CommandTypeDef cmd; + build_cmd(&cmd, {.opcode = CMD_NAND_SET_FEATURES, .addr = FEATURE_ADDR_BLOCK_LOCK, .data_size = 1}); + if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { + return false; + } + + uint8_t unlock = 0x00; + return HAL_QSPI_Transmit(&hqspi, &unlock, HAL_TIMEOUT) == HAL_OK; } bool init_qspi_peripheral() { + init_qspi_gpio(); + uint32_t f_hclk = HAL_RCC_GetHCLKFreq(); hqspi.Instance = QUADSPI; @@ -181,67 +303,96 @@ bool init_qspi_peripheral() { hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0; hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_NONE; hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_8_CYCLE; - hqspi.Init.FlashSize = 23; // TODO - set to log2(chip size in bytes) - 1 once the part is confirmed + hqspi.Init.FlashSize = 23; hqspi.Init.FlashID = QSPI_FLASH_ID_1; return HAL_QSPI_Init(&hqspi) == HAL_OK; } bool reset_and_check_id() { - if (!exec_command(CMD_NAND_RESET)) + QSPI_CommandTypeDef cmd; + + // reset + build_cmd(&cmd, {.opcode = CMD_NAND_RESET}); + if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { return false; + } - delay(2); // TODO - confirm reset recovery time against the datasheet + // wait for reset to happen + delayMicroseconds(500); - uint8_t id[2]; // TODO - confirm ID response length/format against the datasheet - if (!receive(CMD_NAND_READ_ID, id, sizeof(id), 0x00, 8, 8)) + // read id - opcode, 1 dummy byte, then MID/DID data bytes + build_cmd(&cmd, {.opcode = CMD_NAND_READ_ID, .data_size = 2, .dummy_cycles = 8}); + if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { + return false; + } + + uint8_t id[2]; + if (HAL_QSPI_Receive(&hqspi, id, HAL_TIMEOUT) != HAL_OK) { return false; + } - CommsSerial.print("NAND JEDEC ID: "); - for (size_t i = 0; i < sizeof(id); ++i) { - CommsSerial.print(id[i], HEX); - CommsSerial.print(' '); + // MID & DID ID values for GD5F1GQ5UExxG + if (id[0] != 0xC8 || id[1] != 0x51) { + CommsSerial.print("Bad Chip ID Values"); + return false; } - CommsSerial.println(); - // TODO - compare against the known-good ID once the part is finalized. return true; } bool enable_quad_mode() { + QSPI_CommandTypeDef cmd; + + // tell registers to send config + build_cmd(&cmd, {.opcode = CMD_NAND_GET_FEATURES, .addr = FEATURE_ADDR_CONFIG, .data_size = 1}); + if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { + return false; + } + + // receive config uint8_t config; - if (!get_feature(FEATURE_ADDR_CONFIG, &config)) + if (HAL_QSPI_Receive(&hqspi, &config, HAL_TIMEOUT) != HAL_OK) { return false; + } config |= CONFIG_QE_BIT; - return set_feature(FEATURE_ADDR_CONFIG, config); + + // write updated config back + build_cmd(&cmd, {.opcode = CMD_NAND_SET_FEATURES, .addr = FEATURE_ADDR_CONFIG, .data_size = 1}); + if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { + return false; + } + + return HAL_QSPI_Transmit(&hqspi, &config, HAL_TIMEOUT) == HAL_OK; } -void begin() { +bool begin() { if (!init_qspi_peripheral()) { - while (1) { - CommsSerial.println("QSPI Init Failed"); - delay(1000); - } + CommsSerial.println("QSPI Init Failed"); + return false; } if (!reset_and_check_id()) { - while (1) { - CommsSerial.println("NAND reset/ID check failed"); - delay(1000); - } + CommsSerial.println("NAND reset/ID check failed"); + return false; + } + + if (!disable_block_protection()) { + CommsSerial.println("NAND disable block protection failed"); + return false; } if (!enable_quad_mode()) { - while (1) { - CommsSerial.println("NAND enable quad mode failed"); - delay(1000); - } + CommsSerial.println("NAND enable quad mode failed"); + return false; } cache_loaded = false; - CommsSerial.println("NAND flash driver ready."); + CommsSerial.println("NAND flash driver ready"); + + return true; } }; // namespace Flash diff --git a/firmware/lib/logging/flash.h b/firmware/lib/logging/flash.h index 92fd5e7..89991a2 100644 --- a/firmware/lib/logging/flash.h +++ b/firmware/lib/logging/flash.h @@ -1,3 +1,9 @@ +/** + * @file flash.h + * @brief NAND flash header for GD5F1GQ5UEYIGR chip + * + * @author Daniel Proano (dproano@purdue.edu) + */ #pragma once #include @@ -27,16 +33,14 @@ typedef struct __attribute__((packed)) { namespace Flash { -void begin(); +bool begin(); flash_error_t read_page(uint32_t addr, uint8_t *out); flash_error_t write_to_cache(uint32_t col_addr, uint8_t *data, size_t len); -flash_error_t program(uint32_t addr, uint32_t timeout_us = 1000); +flash_error_t program(uint32_t addr); -flash_error_t erase_block(uint32_t addr, uint32_t timeout_us = 10000); - -flash_error_t read_status(flash_status_a_t *status_a, flash_status_b_t *status_b); +flash_error_t erase_block(uint32_t addr); }; // namespace Flash diff --git a/firmware/lib/logging/flash_defs.h b/firmware/lib/logging/flash_defs.h index c104e08..f915b5c 100644 --- a/firmware/lib/logging/flash_defs.h +++ b/firmware/lib/logging/flash_defs.h @@ -42,3 +42,5 @@ #define FREQ_MAX 50'000'000 #define HAL_TIMEOUT 10000 +#define WORST_CASE_PROGRAM_TIMEOUT 600 +#define WORST_CASE_MEMORY_TIMEOUT 10'000 diff --git a/firmware/lib/logging/logging.cpp b/firmware/lib/logging/logging.cpp deleted file mode 100644 index 8d74208..0000000 --- a/firmware/lib/logging/logging.cpp +++ /dev/null @@ -1,215 +0,0 @@ -#include "logging.h" -#include "bad_page_table.h" -#include "flash_defs.h" - -#include "CommandRouter.h" -#include "CommsSerial.h" -#include -#include - -namespace Logging { - -bool log_enabled = false; -uint32_t write_page_addr = 0; // page index currently being assembled in the NAND's cache -uint32_t write_col_addr = 0; // byte offset within that page's cache - -bool is_armed() { - return log_enabled; -} - -// Advances write_page_addr to the next good page, wrapping past any bad ones. -// Returns false if the NAND is full. -bool advance_to_next_good_page() { - write_page_addr++; - while (write_page_addr < NAND_NUM_PAGES && !BPT::is_page_good(write_page_addr)) { - write_page_addr++; - } - write_col_addr = 0; - return write_page_addr < NAND_NUM_PAGES; -} - -void flush_page() { - if (write_col_addr == 0) - return; // nothing written into this page's cache yet - - if (Flash::program(write_page_addr) != FLASH_ERROR_SUCCESS) { - BPT::mark_page_bad(write_page_addr); - } -} - -void write(uint8_t struct_id, uint8_t *data, uint32_t len) { - if (!log_enabled) - return; - - uint32_t entry_len = 1 + len; // id byte + struct data - - if (entry_len > FLASH_PAGE_SIZE) { - CommsSerial.println("Logging::write - struct too large to fit in one page, dropped"); - return; - } - - // Never split an entry across a page boundary, so a reader can always trust that an id - // byte is immediately followed by that struct's full data within the same page. - if (write_col_addr + entry_len > FLASH_PAGE_SIZE) { - flush_page(); - if (!advance_to_next_good_page()) { - CommsSerial.println("Logging::write - out of flash, disarming"); - log_enabled = false; - return; - } - } - - Flash::write_to_cache(write_col_addr, &struct_id, 1); - write_col_addr += 1; - - Flash::write_to_cache(write_col_addr, data, len); - write_col_addr += len; -} - -void complete() { - if (!log_enabled) - return; - - flush_page(); - log_enabled = false; -} - -flash_error_t read_page(uint32_t addr, uint32_t *data) { - if (!BPT::is_page_good(addr)) - return FLASH_ERROR_FAIL; - - flash_error_t err = Flash::read_page(addr, reinterpret_cast(data)); - if (err != FLASH_ERROR_SUCCESS) { - BPT::mark_page_bad(addr); // ECC failure - flag this page/block bad for next time - } - - return err; -} - -// Requires a random confirmation key (same pattern as ASTRA's cmd_log_arm) before erasing -// every currently-good block and enabling writes. This is the gate that's supposed to stop -// anyone from accidentally programming the NAND before a factory bad-block scan has run - -// see the TODO on factory_scan() below. -void cmd_log_arm() { - if (log_enabled) { - CommsSerial.println("Log is already enabled."); - return; - } - - const int key_min = 1000; - const int key_max = 10000; - srand(millis()); - int key = rand() % (key_max - key_min) + key_min; - - CommsSerial.println("WARNING: Proceeding will erase all data currently on the NAND."); - CommsSerial.printf("Enter %d to proceed: \n", key); - - char *res = CommsSerial.readline(); - if (atoi(res) != key) { - CommsSerial.println("Incorrect key entered."); - return; - } - - CommsSerial.println("Erasing all good blocks. This may take a while."); - - for (uint32_t block = 0; block < NAND_NUM_BLOCKS; ++block) { - uint32_t page_addr = block * NAND_PAGES_PER_BLOCK; - if (!BPT::is_page_good(page_addr)) - continue; - - if (Flash::erase_block(page_addr) != FLASH_ERROR_SUCCESS) { - BPT::mark_page_bad(page_addr); - } - } - - CommsSerial.println("Erase complete. Logging enabled."); - - write_page_addr = 0; - while (write_page_addr < NAND_NUM_PAGES && !BPT::is_page_good(write_page_addr)) { - write_page_addr++; - } - write_col_addr = 0; - log_enabled = true; -} - -// Streams the log out page-by-page over USB serial, mirroring ASTRA's send_flash_over_serial() -// protocol (2-byte additive checksum, 'c'/'k' handshake, all-0xFF page = end of log) - see -// firmware/lib/logging/dump.py (TODO - still needs writing, adapted from ASTRA's dump.py, plus -// the struct/type-prefix decoder into CSV that doesn't seem to exist on ASTRA either). -// -// Walking page indices in order and skipping whatever BPT currently marks bad reproduces the -// exact sequence write() used while logging, since write() does the same walk/skip - as long -// as BPT hasn't changed between logging and downloading. -void dump_flash() { - static uint8_t page_buf[FLASH_PAGE_SIZE]; - - while (USB_CommsSerial.available()) { - USB_CommsSerial.read(); - } - - for (uint32_t addr = 0; addr < NAND_NUM_PAGES; ++addr) { - if (!BPT::is_page_good(addr)) - continue; - - if (Flash::read_page(addr, page_buf) != FLASH_ERROR_SUCCESS) { - BPT::mark_page_bad(addr); - continue; - } - - bool all_ff = true; - uint8_t cs_a = 0, cs_b = 0; - for (uint32_t i = 0; i < FLASH_PAGE_SIZE; ++i) { - cs_a += page_buf[i]; - cs_b += cs_a; - if (page_buf[i] != 0xFF) - all_ff = false; - } - - if (all_ff) { - USB_CommsSerial.write('k'); - return; - } - - USB_CommsSerial.write(page_buf, sizeof(page_buf)); - USB_CommsSerial.write(cs_a); - USB_CommsSerial.write(cs_b); - USB_CommsSerial.write('c'); - - int c; - uint32_t wait_start = millis(); - do { - c = USB_CommsSerial.read(); - if (millis() - wait_start > 5000) { - CommsSerial.println("Error: flash dump timed out"); - return; - } - } while (!(c == 'k' || c == 'c')); - - if (c == 'k') - return; - } - - USB_CommsSerial.write('k'); -} - -// TODO: scanning for factory-bad blocks needs to read each block's OOB/spare area (the -// factory bad-block marker lives past the FLASH_PAGE_SIZE=2048 ECC-covered region that -// Flash::read_page() exposes), which isn't part of the Flash interface yet. Needs a new -// low-level primitive (e.g. a raw, non-ECC-checked read at a column offset >= 2048) once -// the OOB layout is confirmed against the final NAND datasheet. -// -// This MUST run (and BPT get built from it) before any write ever touches the NAND - -// that's the whole reason cmd_log_arm() requires a confirmation key first. -void factory_scan() { - CommsSerial.println("factory_scan not yet implemented - see TODO in logging.cpp"); -} - -void begin() { - BPT::begin(); - - CommandRouter::add(cmd_log_arm, "log_arm"); - CommandRouter::add(dump_flash, "dump_flash", "use this with logging/dump.py (not yet written)"); - CommandRouter::add(factory_scan, "factory_scan", "TODO - not yet implemented, see logging.cpp"); -} - -} // namespace Logging diff --git a/firmware/src/ec_main.cpp b/firmware/src/ec_main.cpp index 4db6660..6900f74 100644 --- a/firmware/src/ec_main.cpp +++ b/firmware/src/ec_main.cpp @@ -2,9 +2,9 @@ #include "CommandRouter.h" #include "CommsSerial.h" -#include "logging.h" #include "PressureSensors.h" #include "RCS.h" +#include "flash.h" #include "SolenoidValves.h" #include "TVC_Actuators.h" #include "TemperatureSensors.h" @@ -37,7 +37,6 @@ void setup() { PT_TC_SPI_3.begin(); Flash::begin(); - Logging::begin(); delay(3000); From 40b5296c0683d1c2fb8f03b44384dfdbb5de378c Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Thu, 24 Sep 2026 19:35:42 -0400 Subject: [PATCH 09/32] bad page table minus begin --- firmware/boards/TOAD_H7/ldscript.ld | 11 +- firmware/lib/logging/bad_page_table.cpp | 150 ++++++++++++++++++++++++ firmware/lib/logging/bad_page_table.h | 27 +++++ firmware/lib/logging/flash.cpp | 32 +++++ firmware/lib/logging/flash.h | 4 +- firmware/lib/logging/flash_defs.h | 2 +- 6 files changed, 215 insertions(+), 11 deletions(-) create mode 100644 firmware/lib/logging/bad_page_table.cpp create mode 100644 firmware/lib/logging/bad_page_table.h diff --git a/firmware/boards/TOAD_H7/ldscript.ld b/firmware/boards/TOAD_H7/ldscript.ld index 15a2440..2825877 100644 --- a/firmware/boards/TOAD_H7/ldscript.ld +++ b/firmware/boards/TOAD_H7/ldscript.ld @@ -46,10 +46,6 @@ MEMORY { RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 512K FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K /* Bank 1. Actual full flash is 2048K across two 1024K banks. */ - /* Bank 2, Sector 0. Reserved for the NAND flash Bad Page Table (BPT) - see - firmware/lib/logging/bad_page_table.cpp. Bank 2 is otherwise unused by this linker - script, so carving out this sector here guarantees ordinary code/data never gets - linked on top of it even if Bank 2 is mapped for general use later. */ BPT_FLASH (r) : ORIGIN = 0x08100000, LENGTH = 128K DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 288K @@ -138,14 +134,11 @@ SECTIONS . = ALIGN(4); } >FLASH - /* Reserved for the NAND flash Bad Page Table - see firmware/lib/logging/bad_page_table.cpp. - NOLOAD: this region isn't preloaded from the firmware image, it's read/written live at - runtime via HAL_FLASH_Program()/HAL_FLASHEx_Erase(). Kept empty of any other symbols so - ordinary code/data never gets linked into Bank 2 Sector 0. */ + /* Reserved for the NAND flash Bad Page Table */ .bpt_table (NOLOAD) : { . = ALIGN(4); - _bpt_table_start = .; + _bpt_start = .; } >BPT_FLASH /* Used by the startup to initialize data */ diff --git a/firmware/lib/logging/bad_page_table.cpp b/firmware/lib/logging/bad_page_table.cpp new file mode 100644 index 0000000..f515d0f --- /dev/null +++ b/firmware/lib/logging/bad_page_table.cpp @@ -0,0 +1,150 @@ +/** + * @file flash_defs.hpp + * @brief Bad Page Table Implementation + * + * @author Daniel Proano (dproano@purdue.edu) + */ + +#include + +#include "bad_page_table.h" +#include "flash.h" +#include "stm32h7xx_hal_flash.h" +#include "stm32h7xx_hal_flash_ex.h" + +#include "CommsSerial.h" +#include +#include + +extern uint32_t _bpt_start; + +#define STM32_FLASH_WORD_SIZE 32u +#define BPT_MAGIC_NUMBER 0x0123ABCDu + +uint32_t *bpt_magic = reinterpret_cast(&_bpt_start); +uint8_t *bpt_table = reinterpret_cast(&_bpt_start) + sizeof(BPT_MAGIC_NUMBER); + +namespace Bad_Page_Table { + +bool begin(void) { + if (*bpt_magic == BPT_MAGIC_NUMBER) { + return true; + } + + const int key_min = 1000; + const int key_max = 10000; + srand(millis()); + int key = rand() % (key_max - key_min) + key_min; + + CommsSerial.println("WARNING: Bad Page Table not initialized - a factory bad-block scan is required."); + CommsSerial.printf("Enter %d to proceed: \n", key); + + char *res = CommsSerial.readline(); + if (atoi(res) != key) { + CommsSerial.println("Incorrect key entered."); + return false; + } + + // Reset the whole region to a known-erased (0xFF = all good) state before scanning, in + // case it was left in an unknown state (e.g. a corrupted magic number from a previous + // partial/failed init). mark_page_bad() only ever clears bits from then on, which never + // needs another erase. + FLASH_EraseInitTypeDef erase_init{}; + erase_init.TypeErase = FLASH_TYPEERASE_SECTORS; + erase_init.Banks = FLASH_BANK_2; + erase_init.Sector = 0; + erase_init.NbSectors = 1; + erase_init.VoltageRange = FLASH_VOLTAGE_RANGE_3; + + uint32_t sector_error; + HAL_FLASH_Unlock(); + bool erase_ok = HAL_FLASHEx_Erase(&erase_init, §or_error) == HAL_OK; + HAL_FLASH_Lock(); + + if (!erase_ok) { + CommsSerial.println("Bad Page Table erase failed."); + return false; + } + + CommsSerial.println("Scanning for factory bad blocks. This may take a while."); + + if (!scan_factory_bad_blocks()) { + CommsSerial.println("Factory bad-block scan failed."); + return false; + } + + // Write the magic number last, so an interrupted/failed scan never leaves a table that + // looks initialized but isn't actually complete. + uint8_t word_buf[STM32_FLASH_WORD_SIZE]; + memcpy(word_buf, bpt_magic, STM32_FLASH_WORD_SIZE); + + uint32_t magic = BPT_MAGIC_NUMBER; + memcpy(word_buf, &magic, sizeof(magic)); + + HAL_FLASH_Unlock(); + HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, reinterpret_cast(bpt_magic), reinterpret_cast(word_buf)); + HAL_FLASH_Lock(); + + CommsSerial.println("Bad Page Table initialized."); + return true; +}; + +bool scan_factory_bad_blocks(void) { + for (uint32_t i = 0; i < NAND_NUM_BLOCKS; i++) { + uint32_t cur_page = i * NAND_PAGES_PER_BLOCK; + + uint8_t bad_block_mark; + if (Flash::read_spare(cur_page, &bad_block_mark, 1) != FLASH_SUCCESS) { + return false; + } + + if (bad_block_mark != 0xFF) { + mark_page_bad(cur_page); + } + } + + return true; +}; + +static uint32_t get_bpt_bad_byte(uint32_t block_num) { + return block_num / 8; +} + +static uint8_t get_bpt_bad_bit(uint32_t block_num) { + return (1 << (block_num % 8)); +} + +bool is_page_good(uint32_t page_address) { + uint32_t block_num = page_address / NAND_PAGES_PER_BLOCK; + uint32_t byte = get_bpt_bad_byte(block_num); + uint8_t bit = get_bpt_bad_bit(block_num); + uint8_t *bad_page_byte = bpt_table + byte; + + if ((*bad_page_byte & bit) == 0) { + return false; + } else { + return true; + } + +}; + +void mark_page_bad(uint32_t page_address) { + uint32_t block_num = page_address / NAND_PAGES_PER_BLOCK; + uint32_t byte = get_bpt_bad_byte(block_num); + uint8_t bit = ~(get_bpt_bad_bit(block_num)); + + // flash can only be programmed in 32 byte aligned chunks + uint32_t byte_addr = reinterpret_cast(bpt_table + byte); + uint32_t word_addr = byte_addr - (byte_addr % STM32_FLASH_WORD_SIZE); + uint32_t byte_offset_in_word = byte_addr - word_addr; + + uint8_t word_buf[STM32_FLASH_WORD_SIZE]; + memcpy(word_buf, reinterpret_cast(word_addr), STM32_FLASH_WORD_SIZE); + word_buf[byte_offset_in_word] &= bit; + + HAL_FLASH_Unlock(); + HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, word_addr, reinterpret_cast(word_buf)); + HAL_FLASH_Lock(); +}; + +} \ No newline at end of file diff --git a/firmware/lib/logging/bad_page_table.h b/firmware/lib/logging/bad_page_table.h new file mode 100644 index 0000000..b6fa896 --- /dev/null +++ b/firmware/lib/logging/bad_page_table.h @@ -0,0 +1,27 @@ +/** + * @file flash_defs.hpp + * @brief Bad Page Table Header + * + * @author Daniel Proano (dproano@purdue.edu) + */ + +#pragma once + +#include +#include "flash_defs.h" + +#define NUM_BITS (NAND_NUM_BLOCKS) +#define NUM_BYTES_TRACKING_BLOCKS (NUM_BITS / 8) + +namespace Bad_Page_Table { + +bool begin(); + +bool scan_factory_bad_blocks(void); + +bool is_page_good(uint32_t address); + +void mark_page_bad(uint32_t address); + +} + diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp index 0eabb84..b073276 100644 --- a/firmware/lib/logging/flash.cpp +++ b/firmware/lib/logging/flash.cpp @@ -143,6 +143,38 @@ flash_error_t read_page(uint32_t addr, uint8_t *data_out) { return FLASH_SUCCESS; } +flash_error_t read_spare(uint32_t addr, uint8_t *data_out, size_t len) { + if (len > NAND_SPARE_PAGE_SIZE) { + return FLASH_FAIL; + } + + // load page into cache + QSPI_CommandTypeDef cmd; + build_cmd(&cmd, {.opcode = CMD_NAND_PAGE_READ, .addr = addr, .addr_size = QSPI_ADDRESS_24_BITS}); + if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { + return FLASH_FAIL; + } + + // wait for page to arrive in cache + flash_error_t err = wait_until_ready(60); + if (err != FLASH_SUCCESS) { + return err; + } + + // tell cache to send data, starting at the spare area's column offset + build_cmd(&cmd, {.opcode = CMD_NAND_READ_FROM_CACHE_QUAD, .addr = NAND_PAGE_SIZE, .addr_size = QSPI_ADDRESS_16_BITS, .data_size = static_cast(len), .dummy_cycles = 8, .data_lines = QSPI_DATA_4_LINES}); + if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { + return FLASH_FAIL; + } + + // receive spare bytes from cache across spi + if (HAL_QSPI_Receive(&hqspi, data_out, HAL_TIMEOUT) != HAL_OK) { + return FLASH_FAIL; + } + + return FLASH_SUCCESS; +} + flash_error_t write_to_cache(uint32_t col_addr, uint8_t *data, size_t len) { // does cache page have enough space? if (col_addr + len > NAND_PAGE_SIZE) { diff --git a/firmware/lib/logging/flash.h b/firmware/lib/logging/flash.h index 89991a2..ebf8058 100644 --- a/firmware/lib/logging/flash.h +++ b/firmware/lib/logging/flash.h @@ -1,5 +1,5 @@ /** - * @file flash.h + * @file flash.hpp * @brief NAND flash header for GD5F1GQ5UEYIGR chip * * @author Daniel Proano (dproano@purdue.edu) @@ -37,6 +37,8 @@ bool begin(); flash_error_t read_page(uint32_t addr, uint8_t *out); +flash_error_t read_spare(uint32_t addr, uint8_t *out, size_t len); + flash_error_t write_to_cache(uint32_t col_addr, uint8_t *data, size_t len); flash_error_t program(uint32_t addr); diff --git a/firmware/lib/logging/flash_defs.h b/firmware/lib/logging/flash_defs.h index f915b5c..9b6f265 100644 --- a/firmware/lib/logging/flash_defs.h +++ b/firmware/lib/logging/flash_defs.h @@ -1,5 +1,5 @@ /** - * @file flash_defs.h + * @file flash_defs.hpp * @brief NAND flash definitions for the GD5F1GQ5xExxG chip * * @author Daniel Proano (dproano@purdue.edu) From e5abe29cf1e7959203947cda738c5d1b4144c2a2 Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Fri, 25 Sep 2026 16:52:28 -0400 Subject: [PATCH 10/32] readable code with macros --- firmware/boards/TOAD_H7/ldscript.ld | 2 +- firmware/lib/logging/flash.cpp | 103 ++++++++++------------------ 2 files changed, 37 insertions(+), 68 deletions(-) diff --git a/firmware/boards/TOAD_H7/ldscript.ld b/firmware/boards/TOAD_H7/ldscript.ld index 2825877..cf95db0 100644 --- a/firmware/boards/TOAD_H7/ldscript.ld +++ b/firmware/boards/TOAD_H7/ldscript.ld @@ -46,7 +46,7 @@ MEMORY { RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 512K FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K /* Bank 1. Actual full flash is 2048K across two 1024K banks. */ - BPT_FLASH (r) : ORIGIN = 0x08100000, LENGTH = 128K + BPT_FLASH (rw) : ORIGIN = 0x08100000, LENGTH = 128K DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 288K RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp index b073276..1aa63b0 100644 --- a/firmware/lib/logging/flash.cpp +++ b/firmware/lib/logging/flash.cpp @@ -21,6 +21,19 @@ QSPI_HandleTypeDef hqspi; bool cache_loaded = false; +#define QSPI_CMD_OR_RETURN(cmd_ptr, fail_ret) \ + do { \ + if (HAL_QSPI_Command(&hqspi, (cmd_ptr), HAL_TIMEOUT) != HAL_OK) \ + return (fail_ret); \ + } while (0) + +// Receives a QSPI data phase; returns fail_ret from the calling function if it fails. +#define QSPI_RECV_OR_RETURN(data_ptr, fail_ret) \ + do { \ + if (HAL_QSPI_Receive(&hqspi, (data_ptr), HAL_TIMEOUT) != HAL_OK) \ + return (fail_ret); \ + } while (0) + typedef struct { uint32_t opcode; uint32_t addr = UINT32_MAX; // UINT32_MAX = no address phase @@ -53,14 +66,10 @@ void build_cmd(QSPI_CommandTypeDef *cmd, cmd_spec spec) { bool get_status_a(flash_status_a_t *data_out) { QSPI_CommandTypeDef cmd; build_cmd(&cmd, {.opcode = CMD_NAND_GET_FEATURES, .addr = FEATURE_ADDR_STATUS_A, .data_size = 1}); - if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { - return false; - } + QSPI_CMD_OR_RETURN(&cmd, false); uint8_t data; - if (HAL_QSPI_Receive(&hqspi, &data, HAL_TIMEOUT) != HAL_OK) { - return false; - } + QSPI_RECV_OR_RETURN(&data, false); memcpy(data_out, &data, sizeof(data)); return true; @@ -70,14 +79,10 @@ bool get_status_a(flash_status_a_t *data_out) { bool get_status_b(flash_status_b_t *data_out) { QSPI_CommandTypeDef cmd; build_cmd(&cmd, {.opcode = CMD_NAND_GET_FEATURES, .addr = FEATURE_ADDR_STATUS_B, .data_size = 1}); - if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { - return false; - } + QSPI_CMD_OR_RETURN(&cmd, false); uint8_t data; - if (HAL_QSPI_Receive(&hqspi, &data, HAL_TIMEOUT) != HAL_OK) { - return false; - } + QSPI_RECV_OR_RETURN(&data, false); memcpy(data_out, &data, sizeof(data)); return true; @@ -107,9 +112,7 @@ flash_error_t read_page(uint32_t addr, uint8_t *data_out) { // load page into cache QSPI_CommandTypeDef cmd; build_cmd(&cmd, {.opcode = CMD_NAND_PAGE_READ, .addr = addr, .addr_size = QSPI_ADDRESS_24_BITS}); - if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { - return FLASH_FAIL; - } + QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); // wait for page to arrive in cache flash_error_t err = wait_until_ready(60); @@ -119,14 +122,10 @@ flash_error_t read_page(uint32_t addr, uint8_t *data_out) { // tell cache to send data build_cmd(&cmd, {.opcode = CMD_NAND_READ_FROM_CACHE_QUAD, .addr = 0, .addr_size = QSPI_ADDRESS_16_BITS, .data_size = NAND_PAGE_SIZE, .dummy_cycles = 8, .data_lines = QSPI_DATA_4_LINES}); - if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { - return FLASH_FAIL; - } + QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); // receive data pointer from cache across spi - if (HAL_QSPI_Receive(&hqspi, data_out, HAL_TIMEOUT) != HAL_OK) { - return FLASH_FAIL; - } + QSPI_RECV_OR_RETURN(data_out, FLASH_FAIL); // load status register flash_status_a_t status_a; @@ -151,9 +150,7 @@ flash_error_t read_spare(uint32_t addr, uint8_t *data_out, size_t len) { // load page into cache QSPI_CommandTypeDef cmd; build_cmd(&cmd, {.opcode = CMD_NAND_PAGE_READ, .addr = addr, .addr_size = QSPI_ADDRESS_24_BITS}); - if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { - return FLASH_FAIL; - } + QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); // wait for page to arrive in cache flash_error_t err = wait_until_ready(60); @@ -163,14 +160,10 @@ flash_error_t read_spare(uint32_t addr, uint8_t *data_out, size_t len) { // tell cache to send data, starting at the spare area's column offset build_cmd(&cmd, {.opcode = CMD_NAND_READ_FROM_CACHE_QUAD, .addr = NAND_PAGE_SIZE, .addr_size = QSPI_ADDRESS_16_BITS, .data_size = static_cast(len), .dummy_cycles = 8, .data_lines = QSPI_DATA_4_LINES}); - if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { - return FLASH_FAIL; - } + QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); // receive spare bytes from cache across spi - if (HAL_QSPI_Receive(&hqspi, data_out, HAL_TIMEOUT) != HAL_OK) { - return FLASH_FAIL; - } + QSPI_RECV_OR_RETURN(data_out, FLASH_FAIL); return FLASH_SUCCESS; } @@ -188,9 +181,7 @@ flash_error_t write_to_cache(uint32_t col_addr, uint8_t *data, size_t len) { // load data into cache QSPI_CommandTypeDef cmd; build_cmd(&cmd, {.opcode = nand_cmd, .addr = col_addr, .addr_size = QSPI_ADDRESS_16_BITS, .data_size = len}); - if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { - return FLASH_FAIL; - } + QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); // send data across spi into cache if (HAL_QSPI_Transmit(&hqspi, data, HAL_TIMEOUT) != HAL_OK) { @@ -211,15 +202,11 @@ flash_error_t program(uint32_t addr) { // write enable QSPI_CommandTypeDef cmd; build_cmd(&cmd, {.opcode = CMD_NAND_WRITE_ENABLE}); - if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { - return FLASH_FAIL; - } + QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); // commit cache to memory build_cmd(&cmd, {.opcode = CMD_NAND_PROGRAM_EXECUTE, .addr = addr, .addr_size = QSPI_ADDRESS_24_BITS}); - if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { - return FLASH_FAIL; - } + QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); // wait for cache to be written err = wait_until_ready(WORST_CASE_MEMORY_TIMEOUT); @@ -251,15 +238,11 @@ flash_error_t erase_block(uint32_t addr) { // write enable QSPI_CommandTypeDef cmd; build_cmd(&cmd, {.opcode = CMD_NAND_WRITE_ENABLE}); - if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { - return FLASH_FAIL; - } + QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); // erase block build_cmd(&cmd, {.opcode = CMD_NAND_BLOCK_ERASE, .addr = addr, .addr_size = QSPI_ADDRESS_24_BITS}); - if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { - return FLASH_FAIL; - } + QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); // wait for erase to finish err = wait_until_ready(WORST_CASE_MEMORY_TIMEOUT); @@ -313,9 +296,7 @@ void init_qspi_gpio() { bool disable_block_protection() { QSPI_CommandTypeDef cmd; build_cmd(&cmd, {.opcode = CMD_NAND_SET_FEATURES, .addr = FEATURE_ADDR_BLOCK_LOCK, .data_size = 1}); - if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { - return false; - } + QSPI_CMD_OR_RETURN(&cmd, false); uint8_t unlock = 0x00; return HAL_QSPI_Transmit(&hqspi, &unlock, HAL_TIMEOUT) == HAL_OK; @@ -335,7 +316,7 @@ bool init_qspi_peripheral() { hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0; hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_NONE; hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_8_CYCLE; - hqspi.Init.FlashSize = 23; + hqspi.Init.FlashSize = 26; hqspi.Init.FlashID = QSPI_FLASH_ID_1; return HAL_QSPI_Init(&hqspi) == HAL_OK; @@ -346,23 +327,17 @@ bool reset_and_check_id() { // reset build_cmd(&cmd, {.opcode = CMD_NAND_RESET}); - if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { - return false; - } + QSPI_CMD_OR_RETURN(&cmd, false); // wait for reset to happen delayMicroseconds(500); // read id - opcode, 1 dummy byte, then MID/DID data bytes build_cmd(&cmd, {.opcode = CMD_NAND_READ_ID, .data_size = 2, .dummy_cycles = 8}); - if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { - return false; - } + QSPI_CMD_OR_RETURN(&cmd, false); uint8_t id[2]; - if (HAL_QSPI_Receive(&hqspi, id, HAL_TIMEOUT) != HAL_OK) { - return false; - } + QSPI_RECV_OR_RETURN(id, false); // MID & DID ID values for GD5F1GQ5UExxG if (id[0] != 0xC8 || id[1] != 0x51) { @@ -378,23 +353,17 @@ bool enable_quad_mode() { // tell registers to send config build_cmd(&cmd, {.opcode = CMD_NAND_GET_FEATURES, .addr = FEATURE_ADDR_CONFIG, .data_size = 1}); - if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { - return false; - } + QSPI_CMD_OR_RETURN(&cmd, false); // receive config uint8_t config; - if (HAL_QSPI_Receive(&hqspi, &config, HAL_TIMEOUT) != HAL_OK) { - return false; - } + QSPI_RECV_OR_RETURN(&config, false); config |= CONFIG_QE_BIT; // write updated config back build_cmd(&cmd, {.opcode = CMD_NAND_SET_FEATURES, .addr = FEATURE_ADDR_CONFIG, .data_size = 1}); - if (HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT) != HAL_OK) { - return false; - } + QSPI_CMD_OR_RETURN(&cmd, false); return HAL_QSPI_Transmit(&hqspi, &config, HAL_TIMEOUT) == HAL_OK; } From bdab5f7ac19b871cfde076b790489c93300ed9c3 Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Sun, 27 Sep 2026 01:50:19 -0400 Subject: [PATCH 11/32] format + collapse redundant code --- firmware/lib/logging/bad_page_table.cpp | 202 ++++++++++++------------ firmware/lib/logging/bad_page_table.h | 5 +- firmware/lib/logging/flash.cpp | 121 +++++++------- firmware/lib/logging/flash.h | 11 +- firmware/lib/logging/flash_defs.h | 4 +- 5 files changed, 168 insertions(+), 175 deletions(-) diff --git a/firmware/lib/logging/bad_page_table.cpp b/firmware/lib/logging/bad_page_table.cpp index f515d0f..35919ad 100644 --- a/firmware/lib/logging/bad_page_table.cpp +++ b/firmware/lib/logging/bad_page_table.cpp @@ -18,8 +18,8 @@ extern uint32_t _bpt_start; -#define STM32_FLASH_WORD_SIZE 32u -#define BPT_MAGIC_NUMBER 0x0123ABCDu +#define STM32_FLASH_WORD_SIZE 32u +#define BPT_MAGIC_NUMBER 0x0123ABCDu uint32_t *bpt_magic = reinterpret_cast(&_bpt_start); uint8_t *bpt_table = reinterpret_cast(&_bpt_start) + sizeof(BPT_MAGIC_NUMBER); @@ -27,124 +27,124 @@ uint8_t *bpt_table = reinterpret_cast(&_bpt_start) + sizeof(BPT_MAGIC namespace Bad_Page_Table { bool begin(void) { - if (*bpt_magic == BPT_MAGIC_NUMBER) { - return true; - } - - const int key_min = 1000; - const int key_max = 10000; - srand(millis()); - int key = rand() % (key_max - key_min) + key_min; - - CommsSerial.println("WARNING: Bad Page Table not initialized - a factory bad-block scan is required."); - CommsSerial.printf("Enter %d to proceed: \n", key); - - char *res = CommsSerial.readline(); - if (atoi(res) != key) { - CommsSerial.println("Incorrect key entered."); - return false; - } - - // Reset the whole region to a known-erased (0xFF = all good) state before scanning, in - // case it was left in an unknown state (e.g. a corrupted magic number from a previous - // partial/failed init). mark_page_bad() only ever clears bits from then on, which never - // needs another erase. - FLASH_EraseInitTypeDef erase_init{}; - erase_init.TypeErase = FLASH_TYPEERASE_SECTORS; - erase_init.Banks = FLASH_BANK_2; - erase_init.Sector = 0; - erase_init.NbSectors = 1; - erase_init.VoltageRange = FLASH_VOLTAGE_RANGE_3; - - uint32_t sector_error; - HAL_FLASH_Unlock(); - bool erase_ok = HAL_FLASHEx_Erase(&erase_init, §or_error) == HAL_OK; - HAL_FLASH_Lock(); - - if (!erase_ok) { - CommsSerial.println("Bad Page Table erase failed."); - return false; - } - - CommsSerial.println("Scanning for factory bad blocks. This may take a while."); - - if (!scan_factory_bad_blocks()) { - CommsSerial.println("Factory bad-block scan failed."); - return false; - } - - // Write the magic number last, so an interrupted/failed scan never leaves a table that - // looks initialized but isn't actually complete. - uint8_t word_buf[STM32_FLASH_WORD_SIZE]; - memcpy(word_buf, bpt_magic, STM32_FLASH_WORD_SIZE); - - uint32_t magic = BPT_MAGIC_NUMBER; - memcpy(word_buf, &magic, sizeof(magic)); - - HAL_FLASH_Unlock(); - HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, reinterpret_cast(bpt_magic), reinterpret_cast(word_buf)); - HAL_FLASH_Lock(); - - CommsSerial.println("Bad Page Table initialized."); + if (*bpt_magic == BPT_MAGIC_NUMBER) { return true; + } + + const int key_min = 1000; + const int key_max = 10000; + srand(millis()); + int key = rand() % (key_max - key_min) + key_min; + + CommsSerial.println("WARNING: Bad Page Table not initialized - a factory bad-block scan is required."); + CommsSerial.printf("Enter %d to proceed: \n", key); + + char *res = CommsSerial.readline(); + if (atoi(res) != key) { + CommsSerial.println("Incorrect key entered."); + return false; + } + + // Reset the whole region to a known-erased (0xFF = all good) state before scanning, in + // case it was left in an unknown state (e.g. a corrupted magic number from a previous + // partial/failed init). mark_page_bad() only ever clears bits from then on, which never + // needs another erase. + FLASH_EraseInitTypeDef erase_init{}; + erase_init.TypeErase = FLASH_TYPEERASE_SECTORS; + erase_init.Banks = FLASH_BANK_2; + erase_init.Sector = 0; + erase_init.NbSectors = 1; + erase_init.VoltageRange = FLASH_VOLTAGE_RANGE_3; + + uint32_t sector_error; + HAL_FLASH_Unlock(); + bool erase_ok = HAL_FLASHEx_Erase(&erase_init, §or_error) == HAL_OK; + HAL_FLASH_Lock(); + + if (!erase_ok) { + CommsSerial.println("Bad Page Table erase failed."); + return false; + } + + CommsSerial.println("Scanning for factory bad blocks. This may take a while."); + + if (!scan_factory_bad_blocks()) { + CommsSerial.println("Factory bad-block scan failed."); + return false; + } + + // Write the magic number last, so an interrupted/failed scan never leaves a table that + // looks initialized but isn't actually complete. + uint8_t word_buf[STM32_FLASH_WORD_SIZE]; + memcpy(word_buf, bpt_magic, STM32_FLASH_WORD_SIZE); + + uint32_t magic = BPT_MAGIC_NUMBER; + memcpy(word_buf, &magic, sizeof(magic)); + + HAL_FLASH_Unlock(); + HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, reinterpret_cast(bpt_magic), + reinterpret_cast(word_buf)); + HAL_FLASH_Lock(); + + CommsSerial.println("Bad Page Table initialized."); + return true; }; bool scan_factory_bad_blocks(void) { - for (uint32_t i = 0; i < NAND_NUM_BLOCKS; i++) { - uint32_t cur_page = i * NAND_PAGES_PER_BLOCK; + for (uint32_t i = 0; i < NAND_NUM_BLOCKS; i++) { + uint32_t cur_page = i * NAND_PAGES_PER_BLOCK; - uint8_t bad_block_mark; - if (Flash::read_spare(cur_page, &bad_block_mark, 1) != FLASH_SUCCESS) { - return false; - } + uint8_t bad_block_mark; + if (Flash::read(cur_page, NAND_PAGE_SIZE, &bad_block_mark, 1, false) != FLASH_SUCCESS) { + return false; + } - if (bad_block_mark != 0xFF) { - mark_page_bad(cur_page); - } + if (bad_block_mark != 0xFF) { + mark_page_bad(cur_page); } + } - return true; + return true; }; static uint32_t get_bpt_bad_byte(uint32_t block_num) { - return block_num / 8; + return block_num / 8; } static uint8_t get_bpt_bad_bit(uint32_t block_num) { - return (1 << (block_num % 8)); + return (1 << (block_num % 8)); } bool is_page_good(uint32_t page_address) { - uint32_t block_num = page_address / NAND_PAGES_PER_BLOCK; - uint32_t byte = get_bpt_bad_byte(block_num); - uint8_t bit = get_bpt_bad_bit(block_num); - uint8_t *bad_page_byte = bpt_table + byte; - - if ((*bad_page_byte & bit) == 0) { - return false; - } else { - return true; - } - + uint32_t block_num = page_address / NAND_PAGES_PER_BLOCK; + uint32_t byte = get_bpt_bad_byte(block_num); + uint8_t bit = get_bpt_bad_bit(block_num); + uint8_t *bad_page_byte = bpt_table + byte; + + if ((*bad_page_byte & bit) == 0) { + return false; + } else { + return true; + } }; void mark_page_bad(uint32_t page_address) { - uint32_t block_num = page_address / NAND_PAGES_PER_BLOCK; - uint32_t byte = get_bpt_bad_byte(block_num); - uint8_t bit = ~(get_bpt_bad_bit(block_num)); - - // flash can only be programmed in 32 byte aligned chunks - uint32_t byte_addr = reinterpret_cast(bpt_table + byte); - uint32_t word_addr = byte_addr - (byte_addr % STM32_FLASH_WORD_SIZE); - uint32_t byte_offset_in_word = byte_addr - word_addr; - - uint8_t word_buf[STM32_FLASH_WORD_SIZE]; - memcpy(word_buf, reinterpret_cast(word_addr), STM32_FLASH_WORD_SIZE); - word_buf[byte_offset_in_word] &= bit; - - HAL_FLASH_Unlock(); - HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, word_addr, reinterpret_cast(word_buf)); - HAL_FLASH_Lock(); + uint32_t block_num = page_address / NAND_PAGES_PER_BLOCK; + uint32_t byte = get_bpt_bad_byte(block_num); + uint8_t bit = ~(get_bpt_bad_bit(block_num)); + + // flash can only be programmed in 32 byte aligned chunks + uint32_t byte_addr = reinterpret_cast(bpt_table + byte); + uint32_t word_addr = byte_addr - (byte_addr % STM32_FLASH_WORD_SIZE); + uint32_t byte_offset_in_word = byte_addr - word_addr; + + uint8_t word_buf[STM32_FLASH_WORD_SIZE]; + memcpy(word_buf, reinterpret_cast(word_addr), STM32_FLASH_WORD_SIZE); + word_buf[byte_offset_in_word] &= bit; + + HAL_FLASH_Unlock(); + HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, word_addr, reinterpret_cast(word_buf)); + HAL_FLASH_Lock(); }; -} \ No newline at end of file +} // namespace Bad_Page_Table \ No newline at end of file diff --git a/firmware/lib/logging/bad_page_table.h b/firmware/lib/logging/bad_page_table.h index b6fa896..7d7b934 100644 --- a/firmware/lib/logging/bad_page_table.h +++ b/firmware/lib/logging/bad_page_table.h @@ -7,8 +7,8 @@ #pragma once -#include #include "flash_defs.h" +#include #define NUM_BITS (NAND_NUM_BLOCKS) #define NUM_BYTES_TRACKING_BLOCKS (NUM_BITS / 8) @@ -23,5 +23,4 @@ bool is_page_good(uint32_t address); void mark_page_bad(uint32_t address); -} - +} // namespace Bad_Page_Table diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp index 1aa63b0..e6104d2 100644 --- a/firmware/lib/logging/flash.cpp +++ b/firmware/lib/logging/flash.cpp @@ -5,8 +5,8 @@ * @author Daniel Proano (dproano@purdue.edu) */ -#include "CommsSerial.h" #include "flash.h" +#include "CommsSerial.h" #include "flash_defs.h" #include "stm32h7xx_hal_qspi.h" @@ -21,24 +21,32 @@ QSPI_HandleTypeDef hqspi; bool cache_loaded = false; -#define QSPI_CMD_OR_RETURN(cmd_ptr, fail_ret) \ - do { \ +// Header phase command +#define QSPI_CMD_OR_RETURN(cmd_ptr, fail_ret) \ + do { \ if (HAL_QSPI_Command(&hqspi, (cmd_ptr), HAL_TIMEOUT) != HAL_OK) \ - return (fail_ret); \ + return (fail_ret); \ } while (0) -// Receives a QSPI data phase; returns fail_ret from the calling function if it fails. -#define QSPI_RECV_OR_RETURN(data_ptr, fail_ret) \ - do { \ +// Data phase command +#define QSPI_TRANSMIT_OR_RETURN(data_ptr, fail_ret) \ + do { \ + if (HAL_QSPI_Transmit(&hqspi, (data_ptr), HAL_TIMEOUT) != HAL_OK) \ + return (fail_ret); \ + } while (0) + +// Receives a QSPI data phase +#define QSPI_RECV_OR_RETURN(data_ptr, fail_ret) \ + do { \ if (HAL_QSPI_Receive(&hqspi, (data_ptr), HAL_TIMEOUT) != HAL_OK) \ - return (fail_ret); \ + return (fail_ret); \ } while (0) typedef struct { uint32_t opcode; - uint32_t addr = UINT32_MAX; // UINT32_MAX = no address phase + uint32_t addr = UINT32_MAX; // UINT32_MAX = no address phase uint32_t addr_size = QSPI_ADDRESS_8_BITS; - uint32_t data_size = 0; // 0 = no data phase + uint32_t data_size = 0; // 0 = no data phase uint32_t dummy_cycles = 0; uint32_t data_lines = QSPI_DATA_1_LINE; } cmd_spec; @@ -46,43 +54,48 @@ typedef struct { void build_cmd(QSPI_CommandTypeDef *cmd, cmd_spec spec) { memset(cmd, 0, sizeof(*cmd)); - cmd->Instruction = spec.opcode; - cmd->InstructionMode = QSPI_INSTRUCTION_1_LINE; // during instr phase, use 1 spi line - cmd->DummyCycles = spec.dummy_cycles; + cmd->Instruction = spec.opcode; + cmd->InstructionMode = QSPI_INSTRUCTION_1_LINE; // during instr phase, use 1 spi line + cmd->DummyCycles = spec.dummy_cycles; // Some commands have no address phase at all if (spec.addr != UINT32_MAX) { - cmd->Address = spec.addr; - cmd->AddressSize = spec.addr_size; - cmd->AddressMode = QSPI_ADDRESS_1_LINE; // during address phase, use 1 spi line + cmd->Address = spec.addr; + cmd->AddressSize = spec.addr_size; + cmd->AddressMode = QSPI_ADDRESS_1_LINE; // during address phase, use 1 spi line } if (spec.data_size != 0) { - cmd->DataMode = spec.data_lines; - cmd->NbData = spec.data_size; + cmd->DataMode = spec.data_lines; + cmd->NbData = spec.data_size; } } -bool get_status_a(flash_status_a_t *data_out) { +// load directly from register, only need 1 Instr. CMD + 1 Receive +static bool get_feature(uint8_t feature_addr, uint8_t *data_out) { QSPI_CommandTypeDef cmd; - build_cmd(&cmd, {.opcode = CMD_NAND_GET_FEATURES, .addr = FEATURE_ADDR_STATUS_A, .data_size = 1}); + build_cmd(&cmd, {.opcode = CMD_NAND_GET_FEATURES, .addr = feature_addr, .data_size = 1}); QSPI_CMD_OR_RETURN(&cmd, false); + QSPI_RECV_OR_RETURN(data_out, false); + return true; +} + +bool get_status_a(flash_status_a_t *data_out) { uint8_t data; - QSPI_RECV_OR_RETURN(&data, false); + if (!get_feature(FEATURE_ADDR_STATUS_A, &data)) { + return false; + } memcpy(data_out, &data, sizeof(data)); return true; }; - bool get_status_b(flash_status_b_t *data_out) { - QSPI_CommandTypeDef cmd; - build_cmd(&cmd, {.opcode = CMD_NAND_GET_FEATURES, .addr = FEATURE_ADDR_STATUS_B, .data_size = 1}); - QSPI_CMD_OR_RETURN(&cmd, false); - uint8_t data; - QSPI_RECV_OR_RETURN(&data, false); + if (!get_feature(FEATURE_ADDR_STATUS_B, &data)) { + return false; + } memcpy(data_out, &data, sizeof(data)); return true; @@ -108,7 +121,12 @@ flash_error_t wait_until_ready(uint32_t timeout) { } } -flash_error_t read_page(uint32_t addr, uint8_t *data_out) { +flash_error_t read(uint32_t addr, uint32_t col, uint8_t *data_out, size_t len, bool check_ecc) { + // main + spare area are addressable as one column range + if (col + len > NAND_PAGE_SIZE + NAND_SPARE_PAGE_SIZE) { + return FLASH_FAIL; + } + // load page into cache QSPI_CommandTypeDef cmd; build_cmd(&cmd, {.opcode = CMD_NAND_PAGE_READ, .addr = addr, .addr_size = QSPI_ADDRESS_24_BITS}); @@ -120,13 +138,22 @@ flash_error_t read_page(uint32_t addr, uint8_t *data_out) { return err; } - // tell cache to send data - build_cmd(&cmd, {.opcode = CMD_NAND_READ_FROM_CACHE_QUAD, .addr = 0, .addr_size = QSPI_ADDRESS_16_BITS, .data_size = NAND_PAGE_SIZE, .dummy_cycles = 8, .data_lines = QSPI_DATA_4_LINES}); + // tell cache to send data, starting at the given column offset + build_cmd(&cmd, {.opcode = CMD_NAND_READ_FROM_CACHE_QUAD, + .addr = col, + .addr_size = QSPI_ADDRESS_16_BITS, + .data_size = static_cast(len), + .dummy_cycles = 8, + .data_lines = QSPI_DATA_4_LINES}); QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); - // receive data pointer from cache across spi + // receive data from cache across spi QSPI_RECV_OR_RETURN(data_out, FLASH_FAIL); + if (!check_ecc) { + return FLASH_SUCCESS; + } + // load status register flash_status_a_t status_a; memset(&status_a, 0, sizeof(status_a)); @@ -142,32 +169,6 @@ flash_error_t read_page(uint32_t addr, uint8_t *data_out) { return FLASH_SUCCESS; } -flash_error_t read_spare(uint32_t addr, uint8_t *data_out, size_t len) { - if (len > NAND_SPARE_PAGE_SIZE) { - return FLASH_FAIL; - } - - // load page into cache - QSPI_CommandTypeDef cmd; - build_cmd(&cmd, {.opcode = CMD_NAND_PAGE_READ, .addr = addr, .addr_size = QSPI_ADDRESS_24_BITS}); - QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); - - // wait for page to arrive in cache - flash_error_t err = wait_until_ready(60); - if (err != FLASH_SUCCESS) { - return err; - } - - // tell cache to send data, starting at the spare area's column offset - build_cmd(&cmd, {.opcode = CMD_NAND_READ_FROM_CACHE_QUAD, .addr = NAND_PAGE_SIZE, .addr_size = QSPI_ADDRESS_16_BITS, .data_size = static_cast(len), .dummy_cycles = 8, .data_lines = QSPI_DATA_4_LINES}); - QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); - - // receive spare bytes from cache across spi - QSPI_RECV_OR_RETURN(data_out, FLASH_FAIL); - - return FLASH_SUCCESS; -} - flash_error_t write_to_cache(uint32_t col_addr, uint8_t *data, size_t len) { // does cache page have enough space? if (col_addr + len > NAND_PAGE_SIZE) { @@ -184,9 +185,7 @@ flash_error_t write_to_cache(uint32_t col_addr, uint8_t *data, size_t len) { QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); // send data across spi into cache - if (HAL_QSPI_Transmit(&hqspi, data, HAL_TIMEOUT) != HAL_OK) { - return FLASH_FAIL; - } + QSPI_TRANSMIT_OR_RETURN(data, FLASH_FAIL); cache_loaded = true; return FLASH_SUCCESS; @@ -330,7 +329,7 @@ bool reset_and_check_id() { QSPI_CMD_OR_RETURN(&cmd, false); // wait for reset to happen - delayMicroseconds(500); + delayMicroseconds(500); // read id - opcode, 1 dummy byte, then MID/DID data bytes build_cmd(&cmd, {.opcode = CMD_NAND_READ_ID, .data_size = 2, .dummy_cycles = 8}); diff --git a/firmware/lib/logging/flash.h b/firmware/lib/logging/flash.h index ebf8058..0ee6be0 100644 --- a/firmware/lib/logging/flash.h +++ b/firmware/lib/logging/flash.h @@ -9,11 +9,7 @@ #include #include -typedef enum { - FLASH_SUCCESS, - FLASH_FAIL, - FLASH_TIMED_OUT -} flash_error_t; +typedef enum { FLASH_SUCCESS, FLASH_FAIL, FLASH_TIMED_OUT } flash_error_t; typedef struct __attribute__((packed)) { uint8_t OIP : 1; @@ -35,9 +31,8 @@ namespace Flash { bool begin(); -flash_error_t read_page(uint32_t addr, uint8_t *out); - -flash_error_t read_spare(uint32_t addr, uint8_t *out, size_t len); +// Reads len bytes from page addr starting at column col (spare area begins at NAND_PAGE_SIZE) +flash_error_t read(uint32_t addr, uint32_t col, uint8_t *out, size_t len, bool check_ecc = true); flash_error_t write_to_cache(uint32_t col_addr, uint8_t *data, size_t len); diff --git a/firmware/lib/logging/flash_defs.h b/firmware/lib/logging/flash_defs.h index 9b6f265..203b141 100644 --- a/firmware/lib/logging/flash_defs.h +++ b/firmware/lib/logging/flash_defs.h @@ -25,7 +25,7 @@ #define CMD_NAND_READ_FROM_CACHE_QUAD 0x6Bu #define CMD_NAND_READ_FROM_CACHE_QUAD_IO 0xEBu #define CMD_NAND_READ_ID 0x9Fu -#define CMD_NAND_PROGRAM_LOAD 0x02u +#define CMD_NAND_PROGRAM_LOAD 0x02u #define CMD_NAND_PROGRAM_LOAD_QUAD 0x32u #define CMD_NAND_PROGRAM_EXECUTE 0x10u #define CMD_NAND_PROGRAM_LOAD_RANDOM 0x84u @@ -38,7 +38,7 @@ #define FEATURE_ADDR_STATUS_A 0xC0u #define FEATURE_ADDR_STATUS_B 0xF0u -#define CONFIG_QE_BIT (1u << 0) +#define CONFIG_QE_BIT (1u << 0) #define FREQ_MAX 50'000'000 #define HAL_TIMEOUT 10000 From 6b4d7f05813735272ffa3520e3c5888d6d47896b Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Fri, 9 Oct 2026 22:15:54 -0400 Subject: [PATCH 12/32] updates to logging --- firmware/lib/logging/bad_page_table.h | 2 +- firmware/lib/logging/flash.cpp | 245 ++++++++++++++------------ firmware/lib/logging/flash.h | 6 +- firmware/lib/logging/flash_defs.h | 2 +- 4 files changed, 136 insertions(+), 119 deletions(-) diff --git a/firmware/lib/logging/bad_page_table.h b/firmware/lib/logging/bad_page_table.h index 7d7b934..02a5a8c 100644 --- a/firmware/lib/logging/bad_page_table.h +++ b/firmware/lib/logging/bad_page_table.h @@ -1,5 +1,5 @@ /** - * @file flash_defs.hpp + * @file flash_defs.h * @brief Bad Page Table Header * * @author Daniel Proano (dproano@purdue.edu) diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp index e6104d2..7028af6 100644 --- a/firmware/lib/logging/flash.cpp +++ b/firmware/lib/logging/flash.cpp @@ -17,9 +17,12 @@ namespace Flash { +namespace { + QSPI_HandleTypeDef hqspi; bool cache_loaded = false; +uint32_t cache_offset = 0; // Header phase command #define QSPI_CMD_OR_RETURN(cmd_ptr, fail_ret) \ @@ -42,6 +45,15 @@ bool cache_loaded = false; return (fail_ret); \ } while (0) +// Call a funtion and fail if bad +#define ACTION_OR_FAIL(function, msg) \ + do { \ + if (!function()) { \ + CommsSerial.println(msg); \ + return false; \ + } \ + } while (0) + typedef struct { uint32_t opcode; uint32_t addr = UINT32_MAX; // UINT32_MAX = no address phase @@ -112,7 +124,7 @@ flash_error_t wait_until_ready(uint32_t timeout) { // if operation no longer in progress if (data.OIP == 0) { - return FLASH_SUCCESS; + return FLASH_READY; } if (micros() - start_time > timeout) { @@ -121,6 +133,113 @@ flash_error_t wait_until_ready(uint32_t timeout) { } } +void init_qspi_gpio() { + __HAL_RCC_QSPI_CLK_ENABLE(); + __HAL_RCC_GPIOB_CLK_ENABLE(); + __HAL_RCC_GPIOD_CLK_ENABLE(); + __HAL_RCC_GPIOF_CLK_ENABLE(); + __HAL_RCC_GPIOG_CLK_ENABLE(); + + GPIO_InitTypeDef gpio_init{}; + gpio_init.Mode = GPIO_MODE_AF_PP; + gpio_init.Pull = GPIO_NOPULL; + gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + + // CLK, IO0, IO1, IO3 - all AF9 + gpio_init.Alternate = GPIO_AF9_QUADSPI; + + gpio_init.Pin = GPIO_PIN_2; + HAL_GPIO_Init(GPIOB, &gpio_init); + + gpio_init.Pin = GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13; + HAL_GPIO_Init(GPIOD, &gpio_init); + + gpio_init.Pin = GPIO_PIN_7; // PF7 - IO2 + HAL_GPIO_Init(GPIOF, &gpio_init); + + // NCS - AF10 + gpio_init.Alternate = GPIO_AF10_QUADSPI; + gpio_init.Pin = GPIO_PIN_6; // PG6 - NCS + HAL_GPIO_Init(GPIOG, &gpio_init); +} + +bool disable_block_protection() { + QSPI_CommandTypeDef cmd; + build_cmd(&cmd, {.opcode = CMD_NAND_SET_FEATURES, .addr = FEATURE_ADDR_BLOCK_LOCK, .data_size = 1}); + QSPI_CMD_OR_RETURN(&cmd, false); + + uint8_t unlock = 0x00; + return HAL_QSPI_Transmit(&hqspi, &unlock, HAL_TIMEOUT) == HAL_OK; +} + +bool init_qspi_peripheral() { + init_qspi_gpio(); + + uint32_t f_hclk = HAL_RCC_GetHCLKFreq(); + + hqspi.Instance = QUADSPI; + hqspi.Init.ClockPrescaler = f_hclk / FREQ_MAX; + if (hqspi.Init.ClockPrescaler > 255) + hqspi.Init.ClockPrescaler = 255; + + hqspi.Init.FifoThreshold = 1; + hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0; + hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_NONE; + hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_8_CYCLE; + hqspi.Init.FlashSize = 26; + hqspi.Init.FlashID = QSPI_FLASH_ID_1; + + return HAL_QSPI_Init(&hqspi) == HAL_OK; +} + +bool reset_and_check_id() { + QSPI_CommandTypeDef cmd; + + // reset + build_cmd(&cmd, {.opcode = CMD_NAND_RESET}); + QSPI_CMD_OR_RETURN(&cmd, false); + + // wait for reset to happen + delayMicroseconds(500); + + // read id - opcode, 1 dummy byte, then MID/DID data bytes + build_cmd(&cmd, {.opcode = CMD_NAND_READ_ID, .data_size = 2, .dummy_cycles = 8}); + QSPI_CMD_OR_RETURN(&cmd, false); + + uint8_t id[2]; + QSPI_RECV_OR_RETURN(id, false); + + // MID & DID ID values for GD5F1GQ5UExxG + if (id[0] != 0xC8 || id[1] != 0x51) { + CommsSerial.print("Bad Chip ID Values"); + return false; + } + + return true; +} + +bool enable_quad_mode() { + QSPI_CommandTypeDef cmd; + + // tell registers to send config + build_cmd(&cmd, {.opcode = CMD_NAND_GET_FEATURES, .addr = FEATURE_ADDR_CONFIG, .data_size = 1}); + QSPI_CMD_OR_RETURN(&cmd, false); + + // receive config + uint8_t config; + QSPI_RECV_OR_RETURN(&config, false); + + config |= CONFIG_QE_BIT; + + // write updated config back + build_cmd(&cmd, {.opcode = CMD_NAND_SET_FEATURES, .addr = FEATURE_ADDR_CONFIG, .data_size = 1}); + QSPI_CMD_OR_RETURN(&cmd, false); + + return HAL_QSPI_Transmit(&hqspi, &config, HAL_TIMEOUT) == HAL_OK; +} + +} // namespace + flash_error_t read(uint32_t addr, uint32_t col, uint8_t *data_out, size_t len, bool check_ecc) { // main + spare area are addressable as one column range if (col + len > NAND_PAGE_SIZE + NAND_SPARE_PAGE_SIZE) { @@ -134,7 +253,7 @@ flash_error_t read(uint32_t addr, uint32_t col, uint8_t *data_out, size_t len, b // wait for page to arrive in cache flash_error_t err = wait_until_ready(60); - if (err != FLASH_SUCCESS) { + if (err != FLASH_READY) { return err; } @@ -169,9 +288,9 @@ flash_error_t read(uint32_t addr, uint32_t col, uint8_t *data_out, size_t len, b return FLASH_SUCCESS; } -flash_error_t write_to_cache(uint32_t col_addr, uint8_t *data, size_t len) { +flash_error_t write_to_cache(uint8_t *data, size_t len) { // does cache page have enough space? - if (col_addr + len > NAND_PAGE_SIZE) { + if (cache_offset + len > NAND_PAGE_SIZE) { return FLASH_FAIL; } @@ -181,13 +300,14 @@ flash_error_t write_to_cache(uint32_t col_addr, uint8_t *data, size_t len) { // load data into cache QSPI_CommandTypeDef cmd; - build_cmd(&cmd, {.opcode = nand_cmd, .addr = col_addr, .addr_size = QSPI_ADDRESS_16_BITS, .data_size = len}); + build_cmd(&cmd, {.opcode = nand_cmd, .addr = cache_offset, .addr_size = QSPI_ADDRESS_16_BITS, .data_size = len}); QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); // send data across spi into cache QSPI_TRANSMIT_OR_RETURN(data, FLASH_FAIL); cache_loaded = true; + cache_offset += len; return FLASH_SUCCESS; } @@ -195,7 +315,7 @@ flash_error_t program(uint32_t addr) { // chip can lose commands if it is busy when instruction occurs // ensure worst case timeout is waited until chip free flash_error_t err = wait_until_ready(WORST_CASE_PROGRAM_TIMEOUT); - if (err != FLASH_SUCCESS) + if (err != FLASH_READY) return err; // write enable @@ -210,8 +330,9 @@ flash_error_t program(uint32_t addr) { // wait for cache to be written err = wait_until_ready(WORST_CASE_MEMORY_TIMEOUT); cache_loaded = false; + cache_offset = 0; - if (err != FLASH_SUCCESS) { + if (err != FLASH_READY) { return err; } @@ -231,7 +352,7 @@ flash_error_t program(uint32_t addr) { flash_error_t erase_block(uint32_t addr) { // ensure chip is done with any previous erases flash_error_t err = wait_until_ready(WORST_CASE_MEMORY_TIMEOUT); - if (err != FLASH_SUCCESS) + if (err != FLASH_READY) return err; // write enable @@ -245,7 +366,7 @@ flash_error_t erase_block(uint32_t addr) { // wait for erase to finish err = wait_until_ready(WORST_CASE_MEMORY_TIMEOUT); - if (err != FLASH_SUCCESS) { + if (err != FLASH_READY) { return err; } @@ -262,111 +383,6 @@ flash_error_t erase_block(uint32_t addr) { return FLASH_SUCCESS; } -void init_qspi_gpio() { - __HAL_RCC_QSPI_CLK_ENABLE(); - __HAL_RCC_GPIOB_CLK_ENABLE(); - __HAL_RCC_GPIOD_CLK_ENABLE(); - __HAL_RCC_GPIOF_CLK_ENABLE(); - __HAL_RCC_GPIOG_CLK_ENABLE(); - - GPIO_InitTypeDef gpio_init{}; - gpio_init.Mode = GPIO_MODE_AF_PP; - gpio_init.Pull = GPIO_NOPULL; - gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - - // CLK, IO0, IO1, IO3 - all AF9 - gpio_init.Alternate = GPIO_AF9_QUADSPI; - - gpio_init.Pin = GPIO_PIN_2; - HAL_GPIO_Init(GPIOB, &gpio_init); - - gpio_init.Pin = GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13; - HAL_GPIO_Init(GPIOD, &gpio_init); - - gpio_init.Pin = GPIO_PIN_7; // PF7 - IO2 - HAL_GPIO_Init(GPIOF, &gpio_init); - - // NCS - AF10 - gpio_init.Alternate = GPIO_AF10_QUADSPI; - gpio_init.Pin = GPIO_PIN_6; // PG6 - NCS - HAL_GPIO_Init(GPIOG, &gpio_init); -} - -bool disable_block_protection() { - QSPI_CommandTypeDef cmd; - build_cmd(&cmd, {.opcode = CMD_NAND_SET_FEATURES, .addr = FEATURE_ADDR_BLOCK_LOCK, .data_size = 1}); - QSPI_CMD_OR_RETURN(&cmd, false); - - uint8_t unlock = 0x00; - return HAL_QSPI_Transmit(&hqspi, &unlock, HAL_TIMEOUT) == HAL_OK; -} - -bool init_qspi_peripheral() { - init_qspi_gpio(); - - uint32_t f_hclk = HAL_RCC_GetHCLKFreq(); - - hqspi.Instance = QUADSPI; - hqspi.Init.ClockPrescaler = f_hclk / FREQ_MAX; - if (hqspi.Init.ClockPrescaler > 255) - hqspi.Init.ClockPrescaler = 255; - - hqspi.Init.FifoThreshold = 1; - hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0; - hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_NONE; - hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_8_CYCLE; - hqspi.Init.FlashSize = 26; - hqspi.Init.FlashID = QSPI_FLASH_ID_1; - - return HAL_QSPI_Init(&hqspi) == HAL_OK; -} - -bool reset_and_check_id() { - QSPI_CommandTypeDef cmd; - - // reset - build_cmd(&cmd, {.opcode = CMD_NAND_RESET}); - QSPI_CMD_OR_RETURN(&cmd, false); - - // wait for reset to happen - delayMicroseconds(500); - - // read id - opcode, 1 dummy byte, then MID/DID data bytes - build_cmd(&cmd, {.opcode = CMD_NAND_READ_ID, .data_size = 2, .dummy_cycles = 8}); - QSPI_CMD_OR_RETURN(&cmd, false); - - uint8_t id[2]; - QSPI_RECV_OR_RETURN(id, false); - - // MID & DID ID values for GD5F1GQ5UExxG - if (id[0] != 0xC8 || id[1] != 0x51) { - CommsSerial.print("Bad Chip ID Values"); - return false; - } - - return true; -} - -bool enable_quad_mode() { - QSPI_CommandTypeDef cmd; - - // tell registers to send config - build_cmd(&cmd, {.opcode = CMD_NAND_GET_FEATURES, .addr = FEATURE_ADDR_CONFIG, .data_size = 1}); - QSPI_CMD_OR_RETURN(&cmd, false); - - // receive config - uint8_t config; - QSPI_RECV_OR_RETURN(&config, false); - - config |= CONFIG_QE_BIT; - - // write updated config back - build_cmd(&cmd, {.opcode = CMD_NAND_SET_FEATURES, .addr = FEATURE_ADDR_CONFIG, .data_size = 1}); - QSPI_CMD_OR_RETURN(&cmd, false); - - return HAL_QSPI_Transmit(&hqspi, &config, HAL_TIMEOUT) == HAL_OK; -} - bool begin() { if (!init_qspi_peripheral()) { CommsSerial.println("QSPI Init Failed"); @@ -389,6 +405,7 @@ bool begin() { } cache_loaded = false; + cache_offset = 0; CommsSerial.println("NAND flash driver ready"); diff --git a/firmware/lib/logging/flash.h b/firmware/lib/logging/flash.h index 0ee6be0..07263a8 100644 --- a/firmware/lib/logging/flash.h +++ b/firmware/lib/logging/flash.h @@ -1,5 +1,5 @@ /** - * @file flash.hpp + * @file flash.h * @brief NAND flash header for GD5F1GQ5UEYIGR chip * * @author Daniel Proano (dproano@purdue.edu) @@ -9,7 +9,7 @@ #include #include -typedef enum { FLASH_SUCCESS, FLASH_FAIL, FLASH_TIMED_OUT } flash_error_t; +typedef enum { FLASH_SUCCESS, FLASH_READY, FLASH_FAIL, FLASH_TIMED_OUT } flash_error_t; typedef struct __attribute__((packed)) { uint8_t OIP : 1; @@ -34,7 +34,7 @@ bool begin(); // Reads len bytes from page addr starting at column col (spare area begins at NAND_PAGE_SIZE) flash_error_t read(uint32_t addr, uint32_t col, uint8_t *out, size_t len, bool check_ecc = true); -flash_error_t write_to_cache(uint32_t col_addr, uint8_t *data, size_t len); +flash_error_t write_to_cache(uint8_t *data, size_t len); flash_error_t program(uint32_t addr); diff --git a/firmware/lib/logging/flash_defs.h b/firmware/lib/logging/flash_defs.h index 203b141..cd079d5 100644 --- a/firmware/lib/logging/flash_defs.h +++ b/firmware/lib/logging/flash_defs.h @@ -1,5 +1,5 @@ /** - * @file flash_defs.hpp + * @file flash_defs.h * @brief NAND flash definitions for the GD5F1GQ5xExxG chip * * @author Daniel Proano (dproano@purdue.edu) From ac9a6cb416075106aee9c0a91e885c4aa0638c7b Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Tue, 13 Oct 2026 19:03:49 -0400 Subject: [PATCH 13/32] readability + functionality --- firmware/lib/logging/bad_page_table.cpp | 2 +- firmware/lib/logging/flash.cpp | 154 ++++++++++++++++-------- firmware/lib/logging/flash.h | 5 +- 3 files changed, 109 insertions(+), 52 deletions(-) diff --git a/firmware/lib/logging/bad_page_table.cpp b/firmware/lib/logging/bad_page_table.cpp index 35919ad..b10016a 100644 --- a/firmware/lib/logging/bad_page_table.cpp +++ b/firmware/lib/logging/bad_page_table.cpp @@ -95,7 +95,7 @@ bool scan_factory_bad_blocks(void) { uint32_t cur_page = i * NAND_PAGES_PER_BLOCK; uint8_t bad_block_mark; - if (Flash::read(cur_page, NAND_PAGE_SIZE, &bad_block_mark, 1, false) != FLASH_SUCCESS) { + if (Flash::read_spare(cur_page, 0, &bad_block_mark, 1, false) != FLASH_SUCCESS) { return false; } diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp index 7028af6..39a55e2 100644 --- a/firmware/lib/logging/flash.cpp +++ b/firmware/lib/logging/flash.cpp @@ -56,40 +56,49 @@ uint32_t cache_offset = 0; typedef struct { uint32_t opcode; - uint32_t addr = UINT32_MAX; // UINT32_MAX = no address phase - uint32_t addr_size = QSPI_ADDRESS_8_BITS; - uint32_t data_size = 0; // 0 = no data phase - uint32_t dummy_cycles = 0; - uint32_t data_lines = QSPI_DATA_1_LINE; + uint32_t addr = UINT32_MAX; // no address if max address + uint32_t addr_size = QSPI_ADDRESS_8_BITS; + uint32_t data_size = 0; // 0 = no data phase + uint32_t dummy_cycles = 0; + uint32_t data_lines = QSPI_DATA_1_LINE; } cmd_spec; -void build_cmd(QSPI_CommandTypeDef *cmd, cmd_spec spec) { - memset(cmd, 0, sizeof(*cmd)); +QSPI_CommandTypeDef build_cmd(cmd_spec spec) { + QSPI_CommandTypeDef out_cmd; + memset(&out_cmd, 0, sizeof(out_cmd)); - cmd->Instruction = spec.opcode; - cmd->InstructionMode = QSPI_INSTRUCTION_1_LINE; // during instr phase, use 1 spi line - cmd->DummyCycles = spec.dummy_cycles; + out_cmd.Instruction = spec.opcode; + // The NAND chip only recognizes instructions + // on one line, even though the QSPI supports + // using more for instructions + out_cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE; + out_cmd.DummyCycles = spec.dummy_cycles; - // Some commands have no address phase at all if (spec.addr != UINT32_MAX) { - cmd->Address = spec.addr; - cmd->AddressSize = spec.addr_size; - cmd->AddressMode = QSPI_ADDRESS_1_LINE; // during address phase, use 1 spi line + out_cmd.Address = spec.addr; + out_cmd.AddressSize = spec.addr_size; + out_cmd.AddressMode = QSPI_ADDRESS_1_LINE; } if (spec.data_size != 0) { - cmd->DataMode = spec.data_lines; - cmd->NbData = spec.data_size; + out_cmd.DataMode = spec.data_lines; + out_cmd.NbData = spec.data_size; } + + return out_cmd; } // load directly from register, only need 1 Instr. CMD + 1 Receive static bool get_feature(uint8_t feature_addr, uint8_t *data_out) { - QSPI_CommandTypeDef cmd; - build_cmd(&cmd, {.opcode = CMD_NAND_GET_FEATURES, .addr = feature_addr, .data_size = 1}); + cmd_spec new_cmd_spec = { + .opcode = CMD_NAND_GET_FEATURES, + .addr = feature_addr, + .data_size = 1, + }; + QSPI_CommandTypeDef cmd = build_cmd(new_cmd_spec); QSPI_CMD_OR_RETURN(&cmd, false); - QSPI_RECV_OR_RETURN(data_out, false); + return true; } @@ -123,7 +132,7 @@ flash_error_t wait_until_ready(uint32_t timeout) { } // if operation no longer in progress - if (data.OIP == 0) { + if (!data.OIP) { return FLASH_READY; } @@ -133,8 +142,42 @@ flash_error_t wait_until_ready(uint32_t timeout) { } } -void init_qspi_gpio() { +bool disable_block_protection() { + /* page 49 of GD5F1 datasheet */ + + cmd_spec disable_spec = { + .opcode = CMD_NAND_SET_FEATURES, + .addr = FEATURE_ADDR_BLOCK_LOCK, + .data_size = 1 + }; + + QSPI_CommandTypeDef cmd = build_cmd(disable_spec); + QSPI_CMD_OR_RETURN(&cmd, false); + + uint8_t unlock = 0x00; + if (HAL_QSPI_Transmit(&hqspi, &unlock, HAL_TIMEOUT) != HAL_OK) { + return false; + } + + // ensure write unlock succeedded + uint8_t protection_reg; + if (!get_feature(FEATURE_ADDR_BLOCK_LOCK, &protection_reg)) { + return false; + } + + if (protection_reg != unlock) { + return false; + } + + return true; +} + +void HAL_QSPI_MspInit(QSPI_HandleTypeDef *) { __HAL_RCC_QSPI_CLK_ENABLE(); + + __HAL_RCC_QSPI_FORCE_RESET(); + __HAL_RCC_QSPI_RELEASE_RESET(); + __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_GPIOF_CLK_ENABLE(); @@ -143,38 +186,30 @@ void init_qspi_gpio() { GPIO_InitTypeDef gpio_init{}; gpio_init.Mode = GPIO_MODE_AF_PP; gpio_init.Pull = GPIO_NOPULL; - gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - // CLK, IO0, IO1, IO3 - all AF9 - gpio_init.Alternate = GPIO_AF9_QUADSPI; + // Should speed be very high or high? + gpio_init.Speed = GPIO_SPEED_FREQ_HIGH; + // PB2, PD11, PD12, PD13, PF7 all under AF9 + gpio_init.Alternate = GPIO_AF9_QUADSPI; + gpio_init.Pin = GPIO_PIN_2; HAL_GPIO_Init(GPIOB, &gpio_init); gpio_init.Pin = GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13; HAL_GPIO_Init(GPIOD, &gpio_init); - gpio_init.Pin = GPIO_PIN_7; // PF7 - IO2 + gpio_init.Pin = GPIO_PIN_7; HAL_GPIO_Init(GPIOF, &gpio_init); - // NCS - AF10 + // PG6 is under AF10 gpio_init.Alternate = GPIO_AF10_QUADSPI; - gpio_init.Pin = GPIO_PIN_6; // PG6 - NCS + gpio_init.Pin = GPIO_PIN_6; HAL_GPIO_Init(GPIOG, &gpio_init); } -bool disable_block_protection() { - QSPI_CommandTypeDef cmd; - build_cmd(&cmd, {.opcode = CMD_NAND_SET_FEATURES, .addr = FEATURE_ADDR_BLOCK_LOCK, .data_size = 1}); - QSPI_CMD_OR_RETURN(&cmd, false); - - uint8_t unlock = 0x00; - return HAL_QSPI_Transmit(&hqspi, &unlock, HAL_TIMEOUT) == HAL_OK; -} - bool init_qspi_peripheral() { - init_qspi_gpio(); - + // See page 1261 of STM32H7 dm00392525 user manual uint32_t f_hclk = HAL_RCC_GetHCLKFreq(); hqspi.Instance = QUADSPI; @@ -189,21 +224,24 @@ bool init_qspi_peripheral() { hqspi.Init.FlashSize = 26; hqspi.Init.FlashID = QSPI_FLASH_ID_1; + // Internally calls MspInit return HAL_QSPI_Init(&hqspi) == HAL_OK; } bool reset_and_check_id() { QSPI_CommandTypeDef cmd; - // reset - build_cmd(&cmd, {.opcode = CMD_NAND_RESET}); + cmd_spec reset_cmd = { + .opcode = CMD_NAND_RESET, + }; + build_cmd(reset_cmd); QSPI_CMD_OR_RETURN(&cmd, false); // wait for reset to happen delayMicroseconds(500); // read id - opcode, 1 dummy byte, then MID/DID data bytes - build_cmd(&cmd, {.opcode = CMD_NAND_READ_ID, .data_size = 2, .dummy_cycles = 8}); + build_cmd({.opcode = CMD_NAND_READ_ID, .data_size = 2, .dummy_cycles = 8}); QSPI_CMD_OR_RETURN(&cmd, false); uint8_t id[2]; @@ -222,7 +260,7 @@ bool enable_quad_mode() { QSPI_CommandTypeDef cmd; // tell registers to send config - build_cmd(&cmd, {.opcode = CMD_NAND_GET_FEATURES, .addr = FEATURE_ADDR_CONFIG, .data_size = 1}); + build_cmd({.opcode = CMD_NAND_GET_FEATURES, .addr = FEATURE_ADDR_CONFIG, .data_size = 1}); QSPI_CMD_OR_RETURN(&cmd, false); // receive config @@ -232,15 +270,15 @@ bool enable_quad_mode() { config |= CONFIG_QE_BIT; // write updated config back - build_cmd(&cmd, {.opcode = CMD_NAND_SET_FEATURES, .addr = FEATURE_ADDR_CONFIG, .data_size = 1}); + build_cmd({.opcode = CMD_NAND_SET_FEATURES, .addr = FEATURE_ADDR_CONFIG, .data_size = 1}); QSPI_CMD_OR_RETURN(&cmd, false); return HAL_QSPI_Transmit(&hqspi, &config, HAL_TIMEOUT) == HAL_OK; } -} // namespace - -flash_error_t read(uint32_t addr, uint32_t col, uint8_t *data_out, size_t len, bool check_ecc) { +// Reads len bytes from page addr, starting at column col (spare area begins at NAND_PAGE_SIZE). +// Callers outside this file go through read_main()/read_spare(), which keep col scoped to one region. +flash_error_t read_at_col(uint32_t addr, uint32_t col, uint8_t *data_out, size_t len, bool check_ecc) { // main + spare area are addressable as one column range if (col + len > NAND_PAGE_SIZE + NAND_SPARE_PAGE_SIZE) { return FLASH_FAIL; @@ -248,7 +286,7 @@ flash_error_t read(uint32_t addr, uint32_t col, uint8_t *data_out, size_t len, b // load page into cache QSPI_CommandTypeDef cmd; - build_cmd(&cmd, {.opcode = CMD_NAND_PAGE_READ, .addr = addr, .addr_size = QSPI_ADDRESS_24_BITS}); + build_cmd({.opcode = CMD_NAND_PAGE_READ, .addr = addr, .addr_size = QSPI_ADDRESS_24_BITS}); QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); // wait for page to arrive in cache @@ -258,7 +296,7 @@ flash_error_t read(uint32_t addr, uint32_t col, uint8_t *data_out, size_t len, b } // tell cache to send data, starting at the given column offset - build_cmd(&cmd, {.opcode = CMD_NAND_READ_FROM_CACHE_QUAD, + build_cmd({.opcode = CMD_NAND_READ_FROM_CACHE_QUAD, .addr = col, .addr_size = QSPI_ADDRESS_16_BITS, .data_size = static_cast(len), @@ -288,6 +326,24 @@ flash_error_t read(uint32_t addr, uint32_t col, uint8_t *data_out, size_t len, b return FLASH_SUCCESS; } +} // namespace + +flash_error_t read_main(uint32_t addr, uint32_t offset, uint8_t *out, size_t len, bool check_ecc) { + if (offset + len > NAND_PAGE_SIZE) { + return FLASH_FAIL; + } + + return read_at_col(addr, offset, out, len, check_ecc); +} + +flash_error_t read_spare(uint32_t addr, uint32_t offset, uint8_t *out, size_t len, bool check_ecc) { + if (offset + len > NAND_SPARE_PAGE_SIZE) { + return FLASH_FAIL; + } + + return read_at_col(addr, NAND_PAGE_SIZE + offset, out, len, check_ecc); +} + flash_error_t write_to_cache(uint8_t *data, size_t len) { // does cache page have enough space? if (cache_offset + len > NAND_PAGE_SIZE) { @@ -300,7 +356,7 @@ flash_error_t write_to_cache(uint8_t *data, size_t len) { // load data into cache QSPI_CommandTypeDef cmd; - build_cmd(&cmd, {.opcode = nand_cmd, .addr = cache_offset, .addr_size = QSPI_ADDRESS_16_BITS, .data_size = len}); + build_cmd({.opcode = nand_cmd, .addr = cache_offset, .addr_size = QSPI_ADDRESS_16_BITS, .data_size = len}); QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); // send data across spi into cache diff --git a/firmware/lib/logging/flash.h b/firmware/lib/logging/flash.h index 07263a8..f58270f 100644 --- a/firmware/lib/logging/flash.h +++ b/firmware/lib/logging/flash.h @@ -31,8 +31,9 @@ namespace Flash { bool begin(); -// Reads len bytes from page addr starting at column col (spare area begins at NAND_PAGE_SIZE) -flash_error_t read(uint32_t addr, uint32_t col, uint8_t *out, size_t len, bool check_ecc = true); +flash_error_t read_main(uint32_t addr, uint32_t offset, uint8_t *out, size_t len, bool check_ecc = true); + +flash_error_t read_spare(uint32_t addr, uint32_t offset, uint8_t *out, size_t len, bool check_ecc = true); flash_error_t write_to_cache(uint8_t *data, size_t len); From 281c90725bc62632cba37e660ae6e1de9365f491 Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Tue, 13 Oct 2026 20:36:09 -0400 Subject: [PATCH 14/32] reformatting --- firmware/lib/logging/flash.cpp | 120 +++++++++++++++++++++--------- firmware/lib/logging/flash_defs.h | 15 ++++ firmware/lib/logging/logging.h | 2 +- firmware/src/ec_main.cpp | 1 + 4 files changed, 100 insertions(+), 38 deletions(-) diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp index 39a55e2..95dc03e 100644 --- a/firmware/lib/logging/flash.cpp +++ b/firmware/lib/logging/flash.cpp @@ -187,8 +187,7 @@ void HAL_QSPI_MspInit(QSPI_HandleTypeDef *) { gpio_init.Mode = GPIO_MODE_AF_PP; gpio_init.Pull = GPIO_NOPULL; - // Should speed be very high or high? - gpio_init.Speed = GPIO_SPEED_FREQ_HIGH; + gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; // PB2, PD11, PD12, PD13, PF7 all under AF9 gpio_init.Alternate = GPIO_AF9_QUADSPI; @@ -213,6 +212,10 @@ bool init_qspi_peripheral() { uint32_t f_hclk = HAL_RCC_GetHCLKFreq(); hqspi.Instance = QUADSPI; + + // Clock Prescaler here is 32 bits but + // must be a value between 0 and 255 per + // stm32h7xx_hal_qspi.h:53 hqspi.Init.ClockPrescaler = f_hclk / FREQ_MAX; if (hqspi.Init.ClockPrescaler > 255) hqspi.Init.ClockPrescaler = 255; @@ -221,8 +224,14 @@ bool init_qspi_peripheral() { hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0; hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_NONE; hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_8_CYCLE; + + // 128 MB = 2^27 bytes + // Flash size + 1 = # of bits to address whole array + // Flash size = 27 - 1 = 26 hqspi.Init.FlashSize = 26; hqspi.Init.FlashID = QSPI_FLASH_ID_1; + + // Dual flash defaults to disabled // Internally calls MspInit return HAL_QSPI_Init(&hqspi) == HAL_OK; @@ -238,17 +247,22 @@ bool reset_and_check_id() { QSPI_CMD_OR_RETURN(&cmd, false); // wait for reset to happen - delayMicroseconds(500); - - // read id - opcode, 1 dummy byte, then MID/DID data bytes - build_cmd({.opcode = CMD_NAND_READ_ID, .data_size = 2, .dummy_cycles = 8}); + delayMicroseconds(RESET_MAX_DELAY_US); + + // read ID bytes + // read page 14 of DS-00889 + cmd_spec read_cmd = { + .opcode = CMD_NAND_READ_ID, + .data_size = 2, + .dummy_cycles = 8, + }; + build_cmd(read_cmd); QSPI_CMD_OR_RETURN(&cmd, false); uint8_t id[2]; QSPI_RECV_OR_RETURN(id, false); - // MID & DID ID values for GD5F1GQ5UExxG - if (id[0] != 0xC8 || id[1] != 0x51) { + if (id[0] != MANUFACTURER_ID || id[1] != DEVICE_ID) { CommsSerial.print("Bad Chip ID Values"); return false; } @@ -257,10 +271,13 @@ bool reset_and_check_id() { } bool enable_quad_mode() { - QSPI_CommandTypeDef cmd; - // tell registers to send config - build_cmd({.opcode = CMD_NAND_GET_FEATURES, .addr = FEATURE_ADDR_CONFIG, .data_size = 1}); + cmd_spec get_config_cmd = { + .opcode = CMD_NAND_GET_FEATURES, + .addr = FEATURE_ADDR_CONFIG, + .data_size = 1, + }; + QSPI_CommandTypeDef cmd = build_cmd(get_config_cmd); QSPI_CMD_OR_RETURN(&cmd, false); // receive config @@ -270,43 +287,54 @@ bool enable_quad_mode() { config |= CONFIG_QE_BIT; // write updated config back - build_cmd({.opcode = CMD_NAND_SET_FEATURES, .addr = FEATURE_ADDR_CONFIG, .data_size = 1}); + cmd_spec set_config_cmd = { + .opcode = CMD_NAND_SET_FEATURES, + .addr = FEATURE_ADDR_CONFIG, + .data_size = 1, + }; + build_cmd(set_config_cmd); QSPI_CMD_OR_RETURN(&cmd, false); return HAL_QSPI_Transmit(&hqspi, &config, HAL_TIMEOUT) == HAL_OK; } -// Reads len bytes from page addr, starting at column col (spare area begins at NAND_PAGE_SIZE). -// Callers outside this file go through read_main()/read_spare(), which keep col scoped to one region. flash_error_t read_at_col(uint32_t addr, uint32_t col, uint8_t *data_out, size_t len, bool check_ecc) { - // main + spare area are addressable as one column range + // Don't read past the chip's main page and spare page bounds if (col + len > NAND_PAGE_SIZE + NAND_SPARE_PAGE_SIZE) { return FLASH_FAIL; } // load page into cache - QSPI_CommandTypeDef cmd; - build_cmd({.opcode = CMD_NAND_PAGE_READ, .addr = addr, .addr_size = QSPI_ADDRESS_24_BITS}); + cmd_spec page_load_cmd = { + .opcode = CMD_NAND_PAGE_READ, + .addr = addr, + .addr_size = QSPI_ADDRESS_24_BITS, + }; + QSPI_CommandTypeDef cmd = build_cmd(page_load_cmd); QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); // wait for page to arrive in cache - flash_error_t err = wait_until_ready(60); + flash_error_t err = wait_until_ready(READ_ARRAY_WITH_ECC_MAX_DELAY_US); if (err != FLASH_READY) { return err; } // tell cache to send data, starting at the given column offset - build_cmd({.opcode = CMD_NAND_READ_FROM_CACHE_QUAD, - .addr = col, - .addr_size = QSPI_ADDRESS_16_BITS, - .data_size = static_cast(len), - .dummy_cycles = 8, - .data_lines = QSPI_DATA_4_LINES}); + cmd_spec cache_send_cmd = { + .opcode = CMD_NAND_READ_FROM_CACHE_QUAD, + .addr = col, + .addr_size = QSPI_ADDRESS_16_BITS, + .data_size = static_cast(len), + .dummy_cycles = 8, + .data_lines = QSPI_DATA_4_LINES + }; + build_cmd(cache_send_cmd); QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); // receive data from cache across spi QSPI_RECV_OR_RETURN(data_out, FLASH_FAIL); + // if we dont have to check ecc, end early if (!check_ecc) { return FLASH_SUCCESS; } @@ -319,7 +347,7 @@ flash_error_t read_at_col(uint32_t addr, uint32_t col, uint8_t *data_out, size_t } // check whether read was ok - if (status_a.ECCS != 0) { + if (status_a.ECCS == UNCORRECTED_ERROR) { return FLASH_FAIL; } @@ -328,6 +356,7 @@ flash_error_t read_at_col(uint32_t addr, uint32_t col, uint8_t *data_out, size_t } // namespace + flash_error_t read_main(uint32_t addr, uint32_t offset, uint8_t *out, size_t len, bool check_ecc) { if (offset + len > NAND_PAGE_SIZE) { return FLASH_FAIL; @@ -355,8 +384,13 @@ flash_error_t write_to_cache(uint8_t *data, size_t len) { uint32_t nand_cmd = cache_loaded ? CMD_NAND_PROGRAM_LOAD_RANDOM : CMD_NAND_PROGRAM_LOAD; // load data into cache - QSPI_CommandTypeDef cmd; - build_cmd({.opcode = nand_cmd, .addr = cache_offset, .addr_size = QSPI_ADDRESS_16_BITS, .data_size = len}); + cmd_spec load_cmd = { + .opcode = nand_cmd, + .addr = cache_offset, + .addr_size = QSPI_ADDRESS_16_BITS, + .data_size = len + }; + QSPI_CommandTypeDef cmd = build_cmd(load_cmd); QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); // send data across spi into cache @@ -374,13 +408,18 @@ flash_error_t program(uint32_t addr) { if (err != FLASH_READY) return err; - // write enable - QSPI_CommandTypeDef cmd; - build_cmd(&cmd, {.opcode = CMD_NAND_WRITE_ENABLE}); + cmd_spec write_enable_cmd = { + .opcode = CMD_NAND_WRITE_ENABLE + }; + QSPI_CommandTypeDef cmd = build_cmd(write_enable_cmd); QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); - // commit cache to memory - build_cmd(&cmd, {.opcode = CMD_NAND_PROGRAM_EXECUTE, .addr = addr, .addr_size = QSPI_ADDRESS_24_BITS}); + cmd_spec commit_cache_cmd = { + .opcode = CMD_NAND_PROGRAM_EXECUTE, + .addr = addr, + .addr_size = QSPI_ADDRESS_24_BITS + }; + cmd = build_cmd(commit_cache_cmd); QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); // wait for cache to be written @@ -394,6 +433,7 @@ flash_error_t program(uint32_t addr) { // ensure successful write flash_status_a_t status_a; + memset(&status_a, 0, sizeof(status_a)); if (!get_status_a(&status_a)) { return FLASH_FAIL; } @@ -411,13 +451,18 @@ flash_error_t erase_block(uint32_t addr) { if (err != FLASH_READY) return err; - // write enable - QSPI_CommandTypeDef cmd; - build_cmd(&cmd, {.opcode = CMD_NAND_WRITE_ENABLE}); + cmd_spec enable_write_cmd = { + .opcode = CMD_NAND_WRITE_ENABLE, + }; + QSPI_CommandTypeDef cmd = build_cmd(enable_write_cmd); QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); - // erase block - build_cmd(&cmd, {.opcode = CMD_NAND_BLOCK_ERASE, .addr = addr, .addr_size = QSPI_ADDRESS_24_BITS}); + cmd_spec erase_cmd = { + .opcode = CMD_NAND_BLOCK_ERASE, + .addr = addr, + .addr_size = QSPI_ADDRESS_24_BITS + }; + cmd = build_cmd(erase_cmd); QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); // wait for erase to finish @@ -428,6 +473,7 @@ flash_error_t erase_block(uint32_t addr) { // ensure erase was successful flash_status_a_t status_a; + memset(&status_a, 0, sizeof(status_a)); if (!get_status_a(&status_a)) { return FLASH_FAIL; } diff --git a/firmware/lib/logging/flash_defs.h b/firmware/lib/logging/flash_defs.h index cd079d5..aaa89e5 100644 --- a/firmware/lib/logging/flash_defs.h +++ b/firmware/lib/logging/flash_defs.h @@ -40,7 +40,22 @@ #define CONFIG_QE_BIT (1u << 0) +// chips limit is 104 MHz for 1.8V or 133 MHz for 3.3 V #define FREQ_MAX 50'000'000 #define HAL_TIMEOUT 10000 #define WORST_CASE_PROGRAM_TIMEOUT 600 #define WORST_CASE_MEMORY_TIMEOUT 10'000 + +// MID & DID ID values for GD5F1GQ5UExxG +#define MANUFACTURER_ID 0xC8u +#define DEVICE_ID 0x51u + +// Cmd worst case timing +#define RESET_MAX_DELAY_US 500 +#define READ_ARRAY_MAX_DELAY_US 25 +#define READ_ARRAY_WITH_ECC_MAX_DELAY_US 60 +#define PAGE_PROGRAMMING_MAX_DELAY_US 600 +#define PAGE_PROGRAMMING_WITH_ECC_MAX_DELAY_US 600 +#define BLOCK_ERASE_MAX_DELAY_US 10 + +#define UNCORRECTED_ERROR 2 \ No newline at end of file diff --git a/firmware/lib/logging/logging.h b/firmware/lib/logging/logging.h index a337afe..8093b81 100644 --- a/firmware/lib/logging/logging.h +++ b/firmware/lib/logging/logging.h @@ -16,6 +16,6 @@ bool begin(); bool write(uint8_t struct_id, uint8_t *data, size_t len); -bool +//bool } \ No newline at end of file diff --git a/firmware/src/ec_main.cpp b/firmware/src/ec_main.cpp index 6900f74..f210a9e 100644 --- a/firmware/src/ec_main.cpp +++ b/firmware/src/ec_main.cpp @@ -12,6 +12,7 @@ #include "ValveController.h" #include "fdcan_toad.h" #include "RS485.h" +#include "ErrorCounters.h" // shared interfaces // CommsSerial_t USB_CommsSerial; From 1fc10ba8754c8d13988638a7380cde3e2684a0c0 Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Tue, 13 Oct 2026 21:17:39 -0400 Subject: [PATCH 15/32] cleaned up bugs --- firmware/lib/logging/flash.cpp | 19 ++++--------------- firmware/lib/logging/flash_defs.h | 4 ---- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp index 95dc03e..b1e468a 100644 --- a/firmware/lib/logging/flash.cpp +++ b/firmware/lib/logging/flash.cpp @@ -45,15 +45,6 @@ uint32_t cache_offset = 0; return (fail_ret); \ } while (0) -// Call a funtion and fail if bad -#define ACTION_OR_FAIL(function, msg) \ - do { \ - if (!function()) { \ - CommsSerial.println(msg); \ - return false; \ - } \ - } while (0) - typedef struct { uint32_t opcode; uint32_t addr = UINT32_MAX; // no address if max address @@ -238,12 +229,10 @@ bool init_qspi_peripheral() { } bool reset_and_check_id() { - QSPI_CommandTypeDef cmd; - cmd_spec reset_cmd = { .opcode = CMD_NAND_RESET, }; - build_cmd(reset_cmd); + QSPI_CommandTypeDef cmd = build_cmd(reset_cmd); QSPI_CMD_OR_RETURN(&cmd, false); // wait for reset to happen @@ -256,7 +245,7 @@ bool reset_and_check_id() { .data_size = 2, .dummy_cycles = 8, }; - build_cmd(read_cmd); + cmd = build_cmd(read_cmd); QSPI_CMD_OR_RETURN(&cmd, false); uint8_t id[2]; @@ -292,7 +281,7 @@ bool enable_quad_mode() { .addr = FEATURE_ADDR_CONFIG, .data_size = 1, }; - build_cmd(set_config_cmd); + cmd = build_cmd(set_config_cmd); QSPI_CMD_OR_RETURN(&cmd, false); return HAL_QSPI_Transmit(&hqspi, &config, HAL_TIMEOUT) == HAL_OK; @@ -328,7 +317,7 @@ flash_error_t read_at_col(uint32_t addr, uint32_t col, uint8_t *data_out, size_t .dummy_cycles = 8, .data_lines = QSPI_DATA_4_LINES }; - build_cmd(cache_send_cmd); + cmd = build_cmd(cache_send_cmd); QSPI_CMD_OR_RETURN(&cmd, FLASH_FAIL); // receive data from cache across spi diff --git a/firmware/lib/logging/flash_defs.h b/firmware/lib/logging/flash_defs.h index aaa89e5..b0728a0 100644 --- a/firmware/lib/logging/flash_defs.h +++ b/firmware/lib/logging/flash_defs.h @@ -52,10 +52,6 @@ // Cmd worst case timing #define RESET_MAX_DELAY_US 500 -#define READ_ARRAY_MAX_DELAY_US 25 #define READ_ARRAY_WITH_ECC_MAX_DELAY_US 60 -#define PAGE_PROGRAMMING_MAX_DELAY_US 600 -#define PAGE_PROGRAMMING_WITH_ECC_MAX_DELAY_US 600 -#define BLOCK_ERASE_MAX_DELAY_US 10 #define UNCORRECTED_ERROR 2 \ No newline at end of file From 8b22f2cd29b378a8c71929e54a22cbfacf72d17b Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Wed, 14 Oct 2026 02:28:59 -0400 Subject: [PATCH 16/32] bad page table updates --- firmware/lib/logging/bad_page_table.cpp | 71 ++++++++++++------------- firmware/lib/logging/bad_page_table.h | 3 +- 2 files changed, 36 insertions(+), 38 deletions(-) diff --git a/firmware/lib/logging/bad_page_table.cpp b/firmware/lib/logging/bad_page_table.cpp index b10016a..6e8421b 100644 --- a/firmware/lib/logging/bad_page_table.cpp +++ b/firmware/lib/logging/bad_page_table.cpp @@ -1,6 +1,6 @@ /** - * @file flash_defs.hpp - * @brief Bad Page Table Implementation + * @file flash_defs.cpp + * @brief Bad Page Table Implementation for STM32H7 internal flash * * @author Daniel Proano (dproano@purdue.edu) */ @@ -19,22 +19,38 @@ extern uint32_t _bpt_start; #define STM32_FLASH_WORD_SIZE 32u -#define BPT_MAGIC_NUMBER 0x0123ABCDu +constexpr uint32_t BPT_MAGIC_NUMBER = 0x0123ABCDu; uint32_t *bpt_magic = reinterpret_cast(&_bpt_start); -uint8_t *bpt_table = reinterpret_cast(&_bpt_start) + sizeof(BPT_MAGIC_NUMBER); +uint8_t *bpt_table = reinterpret_cast(&_bpt_start) + STM32_FLASH_WORD_SIZE; namespace Bad_Page_Table { +namespace { + +uint32_t get_bpt_bad_byte(uint32_t block_num) { + return block_num / 8; +} + +uint8_t get_bpt_bad_bit(uint32_t block_num) { + return (1 << (block_num % 8)); +} + +int generate_key() { + const int key_min = 1000; + const int key_max = 10000; + srand(millis()); + return (rand() % (key_max - key_min) + key_min); +} + +} // namespace + bool begin(void) { if (*bpt_magic == BPT_MAGIC_NUMBER) { return true; } - const int key_min = 1000; - const int key_max = 10000; - srand(millis()); - int key = rand() % (key_max - key_min) + key_min; + int key = generate_key(); CommsSerial.println("WARNING: Bad Page Table not initialized - a factory bad-block scan is required."); CommsSerial.printf("Enter %d to proceed: \n", key); @@ -45,16 +61,14 @@ bool begin(void) { return false; } - // Reset the whole region to a known-erased (0xFF = all good) state before scanning, in - // case it was left in an unknown state (e.g. a corrupted magic number from a previous - // partial/failed init). mark_page_bad() only ever clears bits from then on, which never - // needs another erase. - FLASH_EraseInitTypeDef erase_init{}; - erase_init.TypeErase = FLASH_TYPEERASE_SECTORS; - erase_init.Banks = FLASH_BANK_2; - erase_init.Sector = 0; - erase_init.NbSectors = 1; - erase_init.VoltageRange = FLASH_VOLTAGE_RANGE_3; + // erase bad page table + FLASH_EraseInitTypeDef erase_init = { + .TypeErase = FLASH_TYPEERASE_SECTORS, + .Banks = FLASH_BANK_2, + .Sector = 0, + .NbSectors = 1, + .VoltageRange = FLASH_VOLTAGE_RANGE_3 + }; uint32_t sector_error; HAL_FLASH_Unlock(); @@ -73,13 +87,8 @@ bool begin(void) { return false; } - // Write the magic number last, so an interrupted/failed scan never leaves a table that - // looks initialized but isn't actually complete. uint8_t word_buf[STM32_FLASH_WORD_SIZE]; - memcpy(word_buf, bpt_magic, STM32_FLASH_WORD_SIZE); - - uint32_t magic = BPT_MAGIC_NUMBER; - memcpy(word_buf, &magic, sizeof(magic)); + memcpy(word_buf, &BPT_MAGIC_NUMBER, sizeof(BPT_MAGIC_NUMBER)); HAL_FLASH_Unlock(); HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, reinterpret_cast(bpt_magic), @@ -107,25 +116,13 @@ bool scan_factory_bad_blocks(void) { return true; }; -static uint32_t get_bpt_bad_byte(uint32_t block_num) { - return block_num / 8; -} - -static uint8_t get_bpt_bad_bit(uint32_t block_num) { - return (1 << (block_num % 8)); -} - bool is_page_good(uint32_t page_address) { uint32_t block_num = page_address / NAND_PAGES_PER_BLOCK; uint32_t byte = get_bpt_bad_byte(block_num); uint8_t bit = get_bpt_bad_bit(block_num); uint8_t *bad_page_byte = bpt_table + byte; - if ((*bad_page_byte & bit) == 0) { - return false; - } else { - return true; - } + return ((*bad_page_byte & bit) != 0); }; void mark_page_bad(uint32_t page_address) { diff --git a/firmware/lib/logging/bad_page_table.h b/firmware/lib/logging/bad_page_table.h index 02a5a8c..f6f0d12 100644 --- a/firmware/lib/logging/bad_page_table.h +++ b/firmware/lib/logging/bad_page_table.h @@ -12,6 +12,7 @@ #define NUM_BITS (NAND_NUM_BLOCKS) #define NUM_BYTES_TRACKING_BLOCKS (NUM_BITS / 8) +#define PAGE_MARKED_GOOD (0xFF) namespace Bad_Page_Table { @@ -23,4 +24,4 @@ bool is_page_good(uint32_t address); void mark_page_bad(uint32_t address); -} // namespace Bad_Page_Table +} From 7bd81a169bdfd97c654e54987e4c0bfe8519c7b0 Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Thu, 15 Oct 2026 09:26:02 -0400 Subject: [PATCH 17/32] more status's checked --- firmware/lib/logging/bad_page_table.cpp | 13 +++- firmware/lib/logging/flash.cpp | 84 ++++++++++++++----------- 2 files changed, 59 insertions(+), 38 deletions(-) diff --git a/firmware/lib/logging/bad_page_table.cpp b/firmware/lib/logging/bad_page_table.cpp index 6e8421b..f2b8e95 100644 --- a/firmware/lib/logging/bad_page_table.cpp +++ b/firmware/lib/logging/bad_page_table.cpp @@ -69,8 +69,8 @@ bool begin(void) { .NbSectors = 1, .VoltageRange = FLASH_VOLTAGE_RANGE_3 }; - uint32_t sector_error; + HAL_FLASH_Unlock(); bool erase_ok = HAL_FLASHEx_Erase(&erase_init, §or_error) == HAL_OK; HAL_FLASH_Lock(); @@ -90,11 +90,18 @@ bool begin(void) { uint8_t word_buf[STM32_FLASH_WORD_SIZE]; memcpy(word_buf, &BPT_MAGIC_NUMBER, sizeof(BPT_MAGIC_NUMBER)); + uint32_t bpt_magic_addr = reinterpret_cast(bpt_magic); + uint32_t word_buf_addr = reinterpret_cast(word_buf); + HAL_StatusTypeDef status; + HAL_FLASH_Unlock(); - HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, reinterpret_cast(bpt_magic), - reinterpret_cast(word_buf)); + status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, bpt_magic_addr, word_buf_addr != HAL_OK); HAL_FLASH_Lock(); + if (status != HAL_OK) + CommsSerial.println("Flash program failed"); + return false; + CommsSerial.println("Bad Page Table initialized."); return true; }; diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp index b1e468a..25df377 100644 --- a/firmware/lib/logging/flash.cpp +++ b/firmware/lib/logging/flash.cpp @@ -163,41 +163,6 @@ bool disable_block_protection() { return true; } -void HAL_QSPI_MspInit(QSPI_HandleTypeDef *) { - __HAL_RCC_QSPI_CLK_ENABLE(); - - __HAL_RCC_QSPI_FORCE_RESET(); - __HAL_RCC_QSPI_RELEASE_RESET(); - - __HAL_RCC_GPIOB_CLK_ENABLE(); - __HAL_RCC_GPIOD_CLK_ENABLE(); - __HAL_RCC_GPIOF_CLK_ENABLE(); - __HAL_RCC_GPIOG_CLK_ENABLE(); - - GPIO_InitTypeDef gpio_init{}; - gpio_init.Mode = GPIO_MODE_AF_PP; - gpio_init.Pull = GPIO_NOPULL; - - gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - - // PB2, PD11, PD12, PD13, PF7 all under AF9 - gpio_init.Alternate = GPIO_AF9_QUADSPI; - - gpio_init.Pin = GPIO_PIN_2; - HAL_GPIO_Init(GPIOB, &gpio_init); - - gpio_init.Pin = GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13; - HAL_GPIO_Init(GPIOD, &gpio_init); - - gpio_init.Pin = GPIO_PIN_7; - HAL_GPIO_Init(GPIOF, &gpio_init); - - // PG6 is under AF10 - gpio_init.Alternate = GPIO_AF10_QUADSPI; - gpio_init.Pin = GPIO_PIN_6; - HAL_GPIO_Init(GPIOG, &gpio_init); -} - bool init_qspi_peripheral() { // See page 1261 of STM32H7 dm00392525 user manual uint32_t f_hclk = HAL_RCC_GetHCLKFreq(); @@ -284,6 +249,21 @@ bool enable_quad_mode() { cmd = build_cmd(set_config_cmd); QSPI_CMD_OR_RETURN(&cmd, false); + // confirm QE bit was set + cmd_spec read_status_cmd = { + .opcode = CMD_NAND_GET_FEATURES, + .addr = FEATURE_ADDR_STATUS_A, + .data_size = 1, + }; + cmd = build_cmd(read_status_cmd); + QSPI_CMD_OR_RETURN(&cmd, false); + + uint8_t status_a; + QSPI_RECV_OR_RETURN(&status_a, false); + + if ((status_a & CONFIG_QE_BIT) != 1) + return false; + return HAL_QSPI_Transmit(&hqspi, &config, HAL_TIMEOUT) == HAL_OK; } @@ -345,6 +325,40 @@ flash_error_t read_at_col(uint32_t addr, uint32_t col, uint8_t *data_out, size_t } // namespace +void HAL_QSPI_MspInit(QSPI_HandleTypeDef *) { + __HAL_RCC_QSPI_CLK_ENABLE(); + + __HAL_RCC_QSPI_FORCE_RESET(); + __HAL_RCC_QSPI_RELEASE_RESET(); + + __HAL_RCC_GPIOB_CLK_ENABLE(); + __HAL_RCC_GPIOD_CLK_ENABLE(); + __HAL_RCC_GPIOF_CLK_ENABLE(); + __HAL_RCC_GPIOG_CLK_ENABLE(); + + GPIO_InitTypeDef gpio_init{}; + gpio_init.Mode = GPIO_MODE_AF_PP; + gpio_init.Pull = GPIO_NOPULL; + + gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + + // PB2, PD11, PD12, PD13, PF7 all under AF9 + gpio_init.Alternate = GPIO_AF9_QUADSPI; + + gpio_init.Pin = GPIO_PIN_2; + HAL_GPIO_Init(GPIOB, &gpio_init); + + gpio_init.Pin = GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13; + HAL_GPIO_Init(GPIOD, &gpio_init); + + gpio_init.Pin = GPIO_PIN_7; + HAL_GPIO_Init(GPIOF, &gpio_init); + + // PG6 is under AF10 + gpio_init.Alternate = GPIO_AF10_QUADSPI; + gpio_init.Pin = GPIO_PIN_6; + HAL_GPIO_Init(GPIOG, &gpio_init); +} flash_error_t read_main(uint32_t addr, uint32_t offset, uint8_t *out, size_t len, bool check_ecc) { if (offset + len > NAND_PAGE_SIZE) { From 0f1fd5402829d7ef6bffaae78187c9617f32e287 Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Thu, 15 Oct 2026 09:27:00 -0400 Subject: [PATCH 18/32] typos fixed --- firmware/lib/logging/bad_page_table.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/firmware/lib/logging/bad_page_table.cpp b/firmware/lib/logging/bad_page_table.cpp index f2b8e95..a3bdc94 100644 --- a/firmware/lib/logging/bad_page_table.cpp +++ b/firmware/lib/logging/bad_page_table.cpp @@ -95,12 +95,13 @@ bool begin(void) { HAL_StatusTypeDef status; HAL_FLASH_Unlock(); - status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, bpt_magic_addr, word_buf_addr != HAL_OK); + status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, bpt_magic_addr, word_buf_addr); HAL_FLASH_Lock(); - if (status != HAL_OK) + if (status != HAL_OK) { CommsSerial.println("Flash program failed"); return false; + } CommsSerial.println("Bad Page Table initialized."); return true; From a506e371edda38f826f0e19c94947a6d70fc1ed8 Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Thu, 15 Oct 2026 09:41:16 -0400 Subject: [PATCH 19/32] more status checks --- firmware/lib/logging/bad_page_table.cpp | 11 +++- firmware/lib/logging/bad_page_table.h | 2 +- firmware/lib/logging/flash.cpp | 83 +++++++++++++------------ 3 files changed, 52 insertions(+), 44 deletions(-) diff --git a/firmware/lib/logging/bad_page_table.cpp b/firmware/lib/logging/bad_page_table.cpp index a3bdc94..a7bd6e7 100644 --- a/firmware/lib/logging/bad_page_table.cpp +++ b/firmware/lib/logging/bad_page_table.cpp @@ -88,6 +88,7 @@ bool begin(void) { } uint8_t word_buf[STM32_FLASH_WORD_SIZE]; + memset(word_buf, 0, sizeof(word_buf)); memcpy(word_buf, &BPT_MAGIC_NUMBER, sizeof(BPT_MAGIC_NUMBER)); uint32_t bpt_magic_addr = reinterpret_cast(bpt_magic); @@ -117,7 +118,8 @@ bool scan_factory_bad_blocks(void) { } if (bad_block_mark != 0xFF) { - mark_page_bad(cur_page); + if (mark_page_bad(cur_page) != HAL_OK) + return false; } } @@ -133,7 +135,7 @@ bool is_page_good(uint32_t page_address) { return ((*bad_page_byte & bit) != 0); }; -void mark_page_bad(uint32_t page_address) { +bool mark_page_bad(uint32_t page_address) { uint32_t block_num = page_address / NAND_PAGES_PER_BLOCK; uint32_t byte = get_bpt_bad_byte(block_num); uint8_t bit = ~(get_bpt_bad_bit(block_num)); @@ -146,10 +148,13 @@ void mark_page_bad(uint32_t page_address) { uint8_t word_buf[STM32_FLASH_WORD_SIZE]; memcpy(word_buf, reinterpret_cast(word_addr), STM32_FLASH_WORD_SIZE); word_buf[byte_offset_in_word] &= bit; + HAL_StatusTypeDef status; HAL_FLASH_Unlock(); - HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, word_addr, reinterpret_cast(word_buf)); + status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, word_addr, reinterpret_cast(word_buf)); HAL_FLASH_Lock(); + + return status == HAL_OK; }; } // namespace Bad_Page_Table \ No newline at end of file diff --git a/firmware/lib/logging/bad_page_table.h b/firmware/lib/logging/bad_page_table.h index f6f0d12..a273565 100644 --- a/firmware/lib/logging/bad_page_table.h +++ b/firmware/lib/logging/bad_page_table.h @@ -22,6 +22,6 @@ bool scan_factory_bad_blocks(void); bool is_page_good(uint32_t address); -void mark_page_bad(uint32_t address); +bool mark_page_bad(uint32_t address); } diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp index 25df377..d0cac01 100644 --- a/firmware/lib/logging/flash.cpp +++ b/firmware/lib/logging/flash.cpp @@ -15,6 +15,41 @@ #include #include +void HAL_QSPI_MspInit(QSPI_HandleTypeDef *) { + __HAL_RCC_QSPI_CLK_ENABLE(); + + __HAL_RCC_QSPI_FORCE_RESET(); + __HAL_RCC_QSPI_RELEASE_RESET(); + + __HAL_RCC_GPIOB_CLK_ENABLE(); + __HAL_RCC_GPIOD_CLK_ENABLE(); + __HAL_RCC_GPIOF_CLK_ENABLE(); + __HAL_RCC_GPIOG_CLK_ENABLE(); + + GPIO_InitTypeDef gpio_init{}; + gpio_init.Mode = GPIO_MODE_AF_PP; + gpio_init.Pull = GPIO_NOPULL; + + gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + + // PB2, PD11, PD12, PD13, PF7 all under AF9 + gpio_init.Alternate = GPIO_AF9_QUADSPI; + + gpio_init.Pin = GPIO_PIN_2; + HAL_GPIO_Init(GPIOB, &gpio_init); + + gpio_init.Pin = GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13; + HAL_GPIO_Init(GPIOD, &gpio_init); + + gpio_init.Pin = GPIO_PIN_7; + HAL_GPIO_Init(GPIOF, &gpio_init); + + // PG6 is under AF10 + gpio_init.Alternate = GPIO_AF10_QUADSPI; + gpio_init.Pin = GPIO_PIN_6; + HAL_GPIO_Init(GPIOG, &gpio_init); +} + namespace Flash { namespace { @@ -248,23 +283,26 @@ bool enable_quad_mode() { }; cmd = build_cmd(set_config_cmd); QSPI_CMD_OR_RETURN(&cmd, false); + + if (HAL_QSPI_Transmit(&hqspi, &config, HAL_TIMEOUT) != HAL_OK) + return false; // confirm QE bit was set cmd_spec read_status_cmd = { .opcode = CMD_NAND_GET_FEATURES, - .addr = FEATURE_ADDR_STATUS_A, + .addr = FEATURE_ADDR_CONFIG, .data_size = 1, }; cmd = build_cmd(read_status_cmd); QSPI_CMD_OR_RETURN(&cmd, false); - uint8_t status_a; - QSPI_RECV_OR_RETURN(&status_a, false); + uint8_t config; + QSPI_RECV_OR_RETURN(&config, false); - if ((status_a & CONFIG_QE_BIT) != 1) + if (!(config & CONFIG_QE_BIT)) return false; - return HAL_QSPI_Transmit(&hqspi, &config, HAL_TIMEOUT) == HAL_OK; + return true; } flash_error_t read_at_col(uint32_t addr, uint32_t col, uint8_t *data_out, size_t len, bool check_ecc) { @@ -325,41 +363,6 @@ flash_error_t read_at_col(uint32_t addr, uint32_t col, uint8_t *data_out, size_t } // namespace -void HAL_QSPI_MspInit(QSPI_HandleTypeDef *) { - __HAL_RCC_QSPI_CLK_ENABLE(); - - __HAL_RCC_QSPI_FORCE_RESET(); - __HAL_RCC_QSPI_RELEASE_RESET(); - - __HAL_RCC_GPIOB_CLK_ENABLE(); - __HAL_RCC_GPIOD_CLK_ENABLE(); - __HAL_RCC_GPIOF_CLK_ENABLE(); - __HAL_RCC_GPIOG_CLK_ENABLE(); - - GPIO_InitTypeDef gpio_init{}; - gpio_init.Mode = GPIO_MODE_AF_PP; - gpio_init.Pull = GPIO_NOPULL; - - gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - - // PB2, PD11, PD12, PD13, PF7 all under AF9 - gpio_init.Alternate = GPIO_AF9_QUADSPI; - - gpio_init.Pin = GPIO_PIN_2; - HAL_GPIO_Init(GPIOB, &gpio_init); - - gpio_init.Pin = GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13; - HAL_GPIO_Init(GPIOD, &gpio_init); - - gpio_init.Pin = GPIO_PIN_7; - HAL_GPIO_Init(GPIOF, &gpio_init); - - // PG6 is under AF10 - gpio_init.Alternate = GPIO_AF10_QUADSPI; - gpio_init.Pin = GPIO_PIN_6; - HAL_GPIO_Init(GPIOG, &gpio_init); -} - flash_error_t read_main(uint32_t addr, uint32_t offset, uint8_t *out, size_t len, bool check_ecc) { if (offset + len > NAND_PAGE_SIZE) { return FLASH_FAIL; From 6397ffabfe3125c1d1f07a51e2056a0447374558 Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Thu, 15 Oct 2026 13:43:23 -0400 Subject: [PATCH 20/32] bad page table finalized --- firmware/boards/TOAD_H7/ldscript.ld | 2 +- firmware/lib/logging/bad_page_table.cpp | 115 ++++++++++++++++++------ firmware/lib/logging/flash_defs.h | 2 +- 3 files changed, 89 insertions(+), 30 deletions(-) diff --git a/firmware/boards/TOAD_H7/ldscript.ld b/firmware/boards/TOAD_H7/ldscript.ld index cf95db0..4b7b5ab 100644 --- a/firmware/boards/TOAD_H7/ldscript.ld +++ b/firmware/boards/TOAD_H7/ldscript.ld @@ -46,7 +46,7 @@ MEMORY { RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 512K FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K /* Bank 1. Actual full flash is 2048K across two 1024K banks. */ - BPT_FLASH (rw) : ORIGIN = 0x08100000, LENGTH = 128K + BPT_FLASH (rw) : ORIGIN = 0x081E0000, LENGTH = 128K DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 288K RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K diff --git a/firmware/lib/logging/bad_page_table.cpp b/firmware/lib/logging/bad_page_table.cpp index a7bd6e7..30ff90b 100644 --- a/firmware/lib/logging/bad_page_table.cpp +++ b/firmware/lib/logging/bad_page_table.cpp @@ -1,6 +1,9 @@ /** - * @file flash_defs.cpp + * @file bad_page_table.cpp * @brief Bad Page Table Implementation for STM32H7 internal flash + * @note There are effectively two bad page tables, one is the initial + * table filled only at init and the other is the entries table, filled + * dynamically over time. * * @author Daniel Proano (dproano@purdue.edu) */ @@ -19,15 +22,39 @@ extern uint32_t _bpt_start; #define STM32_FLASH_WORD_SIZE 32u +#define BPT_EMPTY_ENTRY 0xFFFFFFFFu constexpr uint32_t BPT_MAGIC_NUMBER = 0x0123ABCDu; -uint32_t *bpt_magic = reinterpret_cast(&_bpt_start); +constexpr uint32_t BPT_MAX_ENTRIES = + (FLASH_SECTOR_SIZE - STM32_FLASH_WORD_SIZE - NUM_BYTES_TRACKING_BLOCKS) / STM32_FLASH_WORD_SIZE; + +uint32_t *bpt_magic = &_bpt_start; uint8_t *bpt_table = reinterpret_cast(&_bpt_start) + STM32_FLASH_WORD_SIZE; +uint8_t *bpt_entries = bpt_table + NUM_BYTES_TRACKING_BLOCKS; namespace Bad_Page_Table { namespace { +bool program_flash_word(uint32_t flash_addr, uint32_t word_addr) { + HAL_StatusTypeDef status; + + HAL_FLASH_Unlock(); + // Flash Typeprogram flashword means program 1 flash word, 32 bytes on the H7 + status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, flash_addr, word_addr); + HAL_FLASH_Lock(); + + return status == HAL_OK; +} + +uint32_t get_entry_addr(uint32_t index) { + return reinterpret_cast(bpt_entries + index * STM32_FLASH_WORD_SIZE); +} + +uint32_t get_entry(uint32_t index) { + return *reinterpret_cast(get_entry_addr(index)); +} + uint32_t get_bpt_bad_byte(uint32_t block_num) { return block_num / 8; } @@ -65,7 +92,7 @@ bool begin(void) { FLASH_EraseInitTypeDef erase_init = { .TypeErase = FLASH_TYPEERASE_SECTORS, .Banks = FLASH_BANK_2, - .Sector = 0, + .Sector = FLASH_SECTOR_7, .NbSectors = 1, .VoltageRange = FLASH_VOLTAGE_RANGE_3 }; @@ -91,15 +118,10 @@ bool begin(void) { memset(word_buf, 0, sizeof(word_buf)); memcpy(word_buf, &BPT_MAGIC_NUMBER, sizeof(BPT_MAGIC_NUMBER)); - uint32_t bpt_magic_addr = reinterpret_cast(bpt_magic); + uint32_t flash_magic_addr = reinterpret_cast(bpt_magic); uint32_t word_buf_addr = reinterpret_cast(word_buf); - HAL_StatusTypeDef status; - - HAL_FLASH_Unlock(); - status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, bpt_magic_addr, word_buf_addr); - HAL_FLASH_Lock(); - if (status != HAL_OK) { + if (!program_flash_word(flash_magic_addr, word_buf_addr)) { CommsSerial.println("Flash program failed"); return false; } @@ -109,6 +131,9 @@ bool begin(void) { }; bool scan_factory_bad_blocks(void) { + uint8_t table_buf[NUM_BYTES_TRACKING_BLOCKS]; + memset(table_buf, PAGE_MARKED_GOOD, sizeof(table_buf)); + for (uint32_t i = 0; i < NAND_NUM_BLOCKS; i++) { uint32_t cur_page = i * NAND_PAGES_PER_BLOCK; @@ -117,9 +142,17 @@ bool scan_factory_bad_blocks(void) { return false; } - if (bad_block_mark != 0xFF) { - if (mark_page_bad(cur_page) != HAL_OK) - return false; + if (bad_block_mark != PAGE_MARKED_GOOD) { + table_buf[get_bpt_bad_byte(i)] &= ~get_bpt_bad_bit(i); + } + } + + // flash can only be programmed once per erase, in 32 byte aligned chunks + for (uint32_t offset = 0; offset < NUM_BYTES_TRACKING_BLOCKS; offset += STM32_FLASH_WORD_SIZE) { + uint32_t flash_addr = reinterpret_cast(bpt_table + offset); + uint32_t table_addr = reinterpret_cast(table_buf + offset); + if (!program_flash_word(flash_addr, table_addr)) { + return false; } } @@ -127,34 +160,60 @@ bool scan_factory_bad_blocks(void) { }; bool is_page_good(uint32_t page_address) { + if (page_address >= NAND_NUM_PAGES) { + return false; + } + uint32_t block_num = page_address / NAND_PAGES_PER_BLOCK; uint32_t byte = get_bpt_bad_byte(block_num); uint8_t bit = get_bpt_bad_bit(block_num); uint8_t *bad_page_byte = bpt_table + byte; - return ((*bad_page_byte & bit) != 0); + if (!(*bad_page_byte & bit)) { + return false; + } + + for (uint32_t i = 0; i < BPT_MAX_ENTRIES; i++) { + uint32_t entry = get_entry(i); + if (entry == BPT_EMPTY_ENTRY) { + break; + } + + if (entry == block_num) { + return false; + } + } + + return true; }; bool mark_page_bad(uint32_t page_address) { + if (page_address >= NAND_NUM_PAGES) { + return false; + } + + if (!is_page_good(page_address)) { + return true; + } + uint32_t block_num = page_address / NAND_PAGES_PER_BLOCK; - uint32_t byte = get_bpt_bad_byte(block_num); - uint8_t bit = ~(get_bpt_bad_bit(block_num)); - // flash can only be programmed in 32 byte aligned chunks - uint32_t byte_addr = reinterpret_cast(bpt_table + byte); - uint32_t word_addr = byte_addr - (byte_addr % STM32_FLASH_WORD_SIZE); - uint32_t byte_offset_in_word = byte_addr - word_addr; + for (uint32_t i = 0; i < BPT_MAX_ENTRIES; i++) { + if (get_entry(i) != BPT_EMPTY_ENTRY) { + continue; + } - uint8_t word_buf[STM32_FLASH_WORD_SIZE]; - memcpy(word_buf, reinterpret_cast(word_addr), STM32_FLASH_WORD_SIZE); - word_buf[byte_offset_in_word] &= bit; - HAL_StatusTypeDef status; + uint8_t word_buf[STM32_FLASH_WORD_SIZE]; + memset(word_buf, PAGE_MARKED_GOOD, sizeof(word_buf)); + memcpy(word_buf, &block_num, sizeof(block_num)); - HAL_FLASH_Unlock(); - status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, word_addr, reinterpret_cast(word_buf)); - HAL_FLASH_Lock(); + uint32_t word_buf_addr = reinterpret_cast(word_buf); + uint32_t entry_addr = get_entry_addr(i); - return status == HAL_OK; + return program_flash_word(entry_addr, word_buf_addr); + } + + return false; }; } // namespace Bad_Page_Table \ No newline at end of file diff --git a/firmware/lib/logging/flash_defs.h b/firmware/lib/logging/flash_defs.h index b0728a0..86d63ea 100644 --- a/firmware/lib/logging/flash_defs.h +++ b/firmware/lib/logging/flash_defs.h @@ -42,7 +42,7 @@ // chips limit is 104 MHz for 1.8V or 133 MHz for 3.3 V #define FREQ_MAX 50'000'000 -#define HAL_TIMEOUT 10000 +#define QSPI_TIMEOUT_MS 10000 #define WORST_CASE_PROGRAM_TIMEOUT 600 #define WORST_CASE_MEMORY_TIMEOUT 10'000 From 8063b97cb52dd73a95c09dab3f08dd42b362aedc Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Thu, 15 Oct 2026 13:51:24 -0400 Subject: [PATCH 21/32] flash finalized --- firmware/lib/logging/flash.cpp | 39 ++++++++++++---------------------- firmware/lib/logging/flash.h | 4 ++-- 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp index d0cac01..ee40942 100644 --- a/firmware/lib/logging/flash.cpp +++ b/firmware/lib/logging/flash.cpp @@ -56,27 +56,24 @@ namespace { QSPI_HandleTypeDef hqspi; -bool cache_loaded = false; -uint32_t cache_offset = 0; - // Header phase command #define QSPI_CMD_OR_RETURN(cmd_ptr, fail_ret) \ do { \ - if (HAL_QSPI_Command(&hqspi, (cmd_ptr), HAL_TIMEOUT) != HAL_OK) \ + if (HAL_QSPI_Command(&hqspi, (cmd_ptr), QSPI_TIMEOUT_MS) != HAL_OK) \ return (fail_ret); \ } while (0) // Data phase command #define QSPI_TRANSMIT_OR_RETURN(data_ptr, fail_ret) \ do { \ - if (HAL_QSPI_Transmit(&hqspi, (data_ptr), HAL_TIMEOUT) != HAL_OK) \ + if (HAL_QSPI_Transmit(&hqspi, (data_ptr), QSPI_TIMEOUT_MS) != HAL_OK) \ return (fail_ret); \ } while (0) // Receives a QSPI data phase #define QSPI_RECV_OR_RETURN(data_ptr, fail_ret) \ do { \ - if (HAL_QSPI_Receive(&hqspi, (data_ptr), HAL_TIMEOUT) != HAL_OK) \ + if (HAL_QSPI_Receive(&hqspi, (data_ptr), QSPI_TIMEOUT_MS) != HAL_OK) \ return (fail_ret); \ } while (0) @@ -181,7 +178,7 @@ bool disable_block_protection() { QSPI_CMD_OR_RETURN(&cmd, false); uint8_t unlock = 0x00; - if (HAL_QSPI_Transmit(&hqspi, &unlock, HAL_TIMEOUT) != HAL_OK) { + if (HAL_QSPI_Transmit(&hqspi, &unlock, QSPI_TIMEOUT_MS) != HAL_OK) { return false; } @@ -284,7 +281,7 @@ bool enable_quad_mode() { cmd = build_cmd(set_config_cmd); QSPI_CMD_OR_RETURN(&cmd, false); - if (HAL_QSPI_Transmit(&hqspi, &config, HAL_TIMEOUT) != HAL_OK) + if (HAL_QSPI_Transmit(&hqspi, &config, QSPI_TIMEOUT_MS) != HAL_OK) return false; // confirm QE bit was set @@ -296,10 +293,10 @@ bool enable_quad_mode() { cmd = build_cmd(read_status_cmd); QSPI_CMD_OR_RETURN(&cmd, false); - uint8_t config; - QSPI_RECV_OR_RETURN(&config, false); + uint8_t config_check; + QSPI_RECV_OR_RETURN(&config_check, false); - if (!(config & CONFIG_QE_BIT)) + if (!(config_check & CONFIG_QE_BIT)) return false; return true; @@ -355,7 +352,7 @@ flash_error_t read_at_col(uint32_t addr, uint32_t col, uint8_t *data_out, size_t // check whether read was ok if (status_a.ECCS == UNCORRECTED_ERROR) { - return FLASH_FAIL; + return FLASH_ECC_FAIL; } return FLASH_SUCCESS; @@ -379,20 +376,19 @@ flash_error_t read_spare(uint32_t addr, uint32_t offset, uint8_t *out, size_t le return read_at_col(addr, NAND_PAGE_SIZE + offset, out, len, check_ecc); } -flash_error_t write_to_cache(uint8_t *data, size_t len) { +flash_error_t write_to_cache(uint32_t col, uint8_t *data, size_t len) { // does cache page have enough space? - if (cache_offset + len > NAND_PAGE_SIZE) { + if (col + len > NAND_PAGE_SIZE) { return FLASH_FAIL; } - // The first write to cache needs to use Program Load to clear cache - // and afterwards need to use Load Random to not overwrite content - uint32_t nand_cmd = cache_loaded ? CMD_NAND_PROGRAM_LOAD_RANDOM : CMD_NAND_PROGRAM_LOAD; + // Program Load at col 0 clears the cache, Load Random keeps existing content + uint32_t nand_cmd = col == 0 ? CMD_NAND_PROGRAM_LOAD : CMD_NAND_PROGRAM_LOAD_RANDOM; // load data into cache cmd_spec load_cmd = { .opcode = nand_cmd, - .addr = cache_offset, + .addr = col, .addr_size = QSPI_ADDRESS_16_BITS, .data_size = len }; @@ -402,8 +398,6 @@ flash_error_t write_to_cache(uint8_t *data, size_t len) { // send data across spi into cache QSPI_TRANSMIT_OR_RETURN(data, FLASH_FAIL); - cache_loaded = true; - cache_offset += len; return FLASH_SUCCESS; } @@ -430,8 +424,6 @@ flash_error_t program(uint32_t addr) { // wait for cache to be written err = wait_until_ready(WORST_CASE_MEMORY_TIMEOUT); - cache_loaded = false; - cache_offset = 0; if (err != FLASH_READY) { return err; @@ -512,9 +504,6 @@ bool begin() { return false; } - cache_loaded = false; - cache_offset = 0; - CommsSerial.println("NAND flash driver ready"); return true; diff --git a/firmware/lib/logging/flash.h b/firmware/lib/logging/flash.h index f58270f..20f8965 100644 --- a/firmware/lib/logging/flash.h +++ b/firmware/lib/logging/flash.h @@ -9,7 +9,7 @@ #include #include -typedef enum { FLASH_SUCCESS, FLASH_READY, FLASH_FAIL, FLASH_TIMED_OUT } flash_error_t; +typedef enum { FLASH_SUCCESS, FLASH_READY, FLASH_FAIL, FLASH_TIMED_OUT, FLASH_ECC_FAIL } flash_error_t; typedef struct __attribute__((packed)) { uint8_t OIP : 1; @@ -35,7 +35,7 @@ flash_error_t read_main(uint32_t addr, uint32_t offset, uint8_t *out, size_t len flash_error_t read_spare(uint32_t addr, uint32_t offset, uint8_t *out, size_t len, bool check_ecc = true); -flash_error_t write_to_cache(uint8_t *data, size_t len); +flash_error_t write_to_cache(uint32_t col, uint8_t *data, size_t len); flash_error_t program(uint32_t addr); From 7971276f2f4bc9a40b209c5e8cb7ef4c628c83ec Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Thu, 15 Oct 2026 14:08:31 -0400 Subject: [PATCH 22/32] logging high level API init --- firmware/lib/logging/logging.cpp | 228 +++++++++++++++++++++++++++++++ firmware/lib/logging/logging.h | 19 ++- 2 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 firmware/lib/logging/logging.cpp diff --git a/firmware/lib/logging/logging.cpp b/firmware/lib/logging/logging.cpp new file mode 100644 index 0000000..b50f176 --- /dev/null +++ b/firmware/lib/logging/logging.cpp @@ -0,0 +1,228 @@ +/** + * @file logging.cpp + * @brief Packed struct logging to NAND flash for GD5F1GQ5UEYIGR chip + * + * @author Daniel Proano (dproano@purdue.edu) + */ + +#include + +#include "logging.h" +#include "CommsSerial.h" + +#include + +#define DUMP_CONTINUE_CHAR 'c' +#define DUMP_TIMEOUT_MS 5000u + +namespace Logging { + +namespace { + +uint8_t page_buf[NAND_PAGE_SIZE]; +uint8_t read_buf[NAND_PAGE_SIZE]; +uint32_t page_offset = 0; +uint32_t cur_page = NAND_NUM_PAGES; + +uint32_t next_good_page(uint32_t page) { + while (page < NAND_NUM_PAGES && !Bad_Page_Table::is_page_good(page)) { + page = (page / NAND_PAGES_PER_BLOCK + 1) * NAND_PAGES_PER_BLOCK; + } + + return page; +} + +void reset_page_buf() { + memset(page_buf, LOG_EMPTY_BYTE, sizeof(page_buf)); + page_offset = 0; +} + +bool page_is_empty(uint32_t page) { + uint8_t first_byte; + if (Flash::read_main(page, 0, &first_byte, 1) != FLASH_SUCCESS) { + return false; + } + + return first_byte == LOG_EMPTY_BYTE; +} + +uint32_t find_write_page() { + uint32_t last_used_block = UINT32_MAX; + + for (uint32_t block = 0; block < NAND_NUM_BLOCKS; block++) { + uint32_t first_page = block * NAND_PAGES_PER_BLOCK; + if (!Bad_Page_Table::is_page_good(first_page)) { + continue; + } + + if (page_is_empty(first_page)) { + break; + } + + last_used_block = block; + } + + if (last_used_block == UINT32_MAX) { + return next_good_page(0); + } + + uint32_t page = last_used_block * NAND_PAGES_PER_BLOCK; + uint32_t block_end = page + NAND_PAGES_PER_BLOCK; + while (page < block_end && !page_is_empty(page)) { + page++; + } + + return next_good_page(page); +} + +bool commit_page() { + bool ok = true; + + if (Flash::write_to_cache(0, page_buf, NAND_PAGE_SIZE) != FLASH_SUCCESS) { + ok = false; + } else if (Flash::program(cur_page) != FLASH_SUCCESS) { + Bad_Page_Table::mark_page_bad(cur_page); + ok = false; + } + + reset_page_buf(); + cur_page = next_good_page(cur_page + 1); + return ok; +} + +bool wait_for_continue() { + uint32_t start = millis(); + + while (!CommsSerial.available()) { + if (millis() - start > DUMP_TIMEOUT_MS) { + return false; + } + } + + return CommsSerial.read() == DUMP_CONTINUE_CHAR; +} + +bool send_page(uint8_t *data) { + if (!wait_for_continue()) { + return false; + } + + uint8_t cs_a = 0; + uint8_t cs_b = 0; + for (uint32_t i = 0; i < NAND_PAGE_SIZE; i++) { + cs_a += data[i]; + cs_b += cs_a; + } + + CommsSerial.write(data, NAND_PAGE_SIZE); + CommsSerial.write(cs_a); + CommsSerial.write(cs_b); + return true; +} + +} // namespace + +bool begin() { + if (!Bad_Page_Table::begin()) { + return false; + } + + reset_page_buf(); + cur_page = find_write_page(); + + if (cur_page >= NAND_NUM_PAGES) { + CommsSerial.println("NAND log is full."); + return false; + } + + return true; +} + +bool erase() { + bool ok = true; + + CommsSerial.println("Erasing NAND log. This may take a while."); + + for (uint32_t block = 0; block < NAND_NUM_BLOCKS; block++) { + uint32_t first_page = block * NAND_PAGES_PER_BLOCK; + if (!Bad_Page_Table::is_page_good(first_page)) { + continue; + } + + if (Flash::erase_block(first_page) != FLASH_SUCCESS) { + ok &= Bad_Page_Table::mark_page_bad(first_page); + } + } + + reset_page_buf(); + cur_page = next_good_page(0); + + CommsSerial.println("NAND log erased."); + return ok; +} + +bool write(uint8_t struct_id, uint8_t *data, size_t len) { + size_t entry_len = sizeof(struct_id) + len; + if (struct_id == LOG_EMPTY_BYTE || entry_len > NAND_PAGE_SIZE) { + return false; + } + + bool ok = true; + if (page_offset + entry_len > NAND_PAGE_SIZE) { + ok = commit_page(); + } + + if (cur_page >= NAND_NUM_PAGES) { + return false; + } + + page_buf[page_offset] = struct_id; + memcpy(page_buf + page_offset + sizeof(struct_id), data, len); + page_offset += entry_len; + + return ok; +} + +bool flush() { + if (page_offset == 0 || cur_page >= NAND_NUM_PAGES) { + return true; + } + + return commit_page(); +} + +flash_error_t read_page(uint32_t page_addr, uint8_t *out) { + if (!Bad_Page_Table::is_page_good(page_addr)) { + return FLASH_FAIL; + } + + flash_error_t err = Flash::read_main(page_addr, 0, out, NAND_PAGE_SIZE); + if (err == FLASH_ECC_FAIL) { + Bad_Page_Table::mark_page_bad(page_addr); + } + + return err; +} + +bool dump() { + flush(); + + for (uint32_t page = next_good_page(0); page < NAND_NUM_PAGES; page = next_good_page(page + 1)) { + if (read_page(page, read_buf) != FLASH_SUCCESS) { + continue; + } + + if (read_buf[0] == LOG_EMPTY_BYTE) { + break; + } + + if (!send_page(read_buf)) { + return false; + } + } + + memset(read_buf, LOG_EMPTY_BYTE, sizeof(read_buf)); + return send_page(read_buf); +} + +} // namespace Logging diff --git a/firmware/lib/logging/logging.h b/firmware/lib/logging/logging.h index 8093b81..65f1c24 100644 --- a/firmware/lib/logging/logging.h +++ b/firmware/lib/logging/logging.h @@ -1,21 +1,32 @@ /** * @file logging.h - * @brief NAND flash header for GD5F1GQ5UEYIGR chip + * @brief Packed struct logging to NAND flash for GD5F1GQ5UEYIGR chip * * @author Daniel Proano (dproano@purdue.edu) */ #pragma once -#include "flash.h" #include "bad_page_table.h" +#include "flash.h" + +#include +#include + +#define LOG_EMPTY_BYTE 0xFFu namespace Logging { bool begin(); +bool erase(); + bool write(uint8_t struct_id, uint8_t *data, size_t len); -//bool +bool flush(); + +flash_error_t read_page(uint32_t page_addr, uint8_t *out); + +bool dump(); -} \ No newline at end of file +} // namespace Logging From 57d2ec788a69d816664ce570cad4f1597d9b8e47 Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Thu, 15 Oct 2026 14:10:06 -0400 Subject: [PATCH 23/32] privatizing a public function --- firmware/lib/logging/logging.cpp | 26 +++++++++++++------------- firmware/lib/logging/logging.h | 2 -- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/firmware/lib/logging/logging.cpp b/firmware/lib/logging/logging.cpp index b50f176..4b70f85 100644 --- a/firmware/lib/logging/logging.cpp +++ b/firmware/lib/logging/logging.cpp @@ -102,6 +102,19 @@ bool wait_for_continue() { return CommsSerial.read() == DUMP_CONTINUE_CHAR; } +flash_error_t read_page(uint32_t page_addr, uint8_t *out) { + if (!Bad_Page_Table::is_page_good(page_addr)) { + return FLASH_FAIL; + } + + flash_error_t err = Flash::read_main(page_addr, 0, out, NAND_PAGE_SIZE); + if (err == FLASH_ECC_FAIL) { + Bad_Page_Table::mark_page_bad(page_addr); + } + + return err; +} + bool send_page(uint8_t *data) { if (!wait_for_continue()) { return false; @@ -191,19 +204,6 @@ bool flush() { return commit_page(); } -flash_error_t read_page(uint32_t page_addr, uint8_t *out) { - if (!Bad_Page_Table::is_page_good(page_addr)) { - return FLASH_FAIL; - } - - flash_error_t err = Flash::read_main(page_addr, 0, out, NAND_PAGE_SIZE); - if (err == FLASH_ECC_FAIL) { - Bad_Page_Table::mark_page_bad(page_addr); - } - - return err; -} - bool dump() { flush(); diff --git a/firmware/lib/logging/logging.h b/firmware/lib/logging/logging.h index 65f1c24..1d08fd3 100644 --- a/firmware/lib/logging/logging.h +++ b/firmware/lib/logging/logging.h @@ -25,8 +25,6 @@ bool write(uint8_t struct_id, uint8_t *data, size_t len); bool flush(); -flash_error_t read_page(uint32_t page_addr, uint8_t *out); - bool dump(); } // namespace Logging From 0f0345be0cf541d61afbaae072e01008cd999c75 Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Thu, 15 Oct 2026 21:44:44 -0400 Subject: [PATCH 24/32] better logging --- firmware/lib/logging/bad_page_table.cpp | 8 +++- firmware/lib/logging/flash.cpp | 6 +-- firmware/lib/logging/flash.h | 8 +++- firmware/lib/logging/logging.cpp | 50 ++++++++++++++++--------- 4 files changed, 50 insertions(+), 22 deletions(-) diff --git a/firmware/lib/logging/bad_page_table.cpp b/firmware/lib/logging/bad_page_table.cpp index 30ff90b..dc914fd 100644 --- a/firmware/lib/logging/bad_page_table.cpp +++ b/firmware/lib/logging/bad_page_table.cpp @@ -210,9 +210,15 @@ bool mark_page_bad(uint32_t page_address) { uint32_t word_buf_addr = reinterpret_cast(word_buf); uint32_t entry_addr = get_entry_addr(i); - return program_flash_word(entry_addr, word_buf_addr); + if (!program_flash_word(entry_addr, word_buf_addr)) { + CommsSerial.printf("WARNING: failed to mark block %lu bad.\n", block_num); + return false; + } + + return true; } + CommsSerial.printf("WARNING: Bad Page Table full, block %lu not marked bad.\n", block_num); return false; }; diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp index ee40942..78ea385 100644 --- a/firmware/lib/logging/flash.cpp +++ b/firmware/lib/logging/flash.cpp @@ -352,7 +352,7 @@ flash_error_t read_at_col(uint32_t addr, uint32_t col, uint8_t *data_out, size_t // check whether read was ok if (status_a.ECCS == UNCORRECTED_ERROR) { - return FLASH_ECC_FAIL; + return FLASH_BAD_BLOCK; } return FLASH_SUCCESS; @@ -437,7 +437,7 @@ flash_error_t program(uint32_t addr) { } if (status_a.P_FAIL) { - return FLASH_FAIL; + return FLASH_BAD_BLOCK; } return FLASH_SUCCESS; @@ -477,7 +477,7 @@ flash_error_t erase_block(uint32_t addr) { } if (status_a.E_FAIL) { - return FLASH_FAIL; + return FLASH_BAD_BLOCK; } return FLASH_SUCCESS; diff --git a/firmware/lib/logging/flash.h b/firmware/lib/logging/flash.h index 20f8965..04086e4 100644 --- a/firmware/lib/logging/flash.h +++ b/firmware/lib/logging/flash.h @@ -9,7 +9,13 @@ #include #include -typedef enum { FLASH_SUCCESS, FLASH_READY, FLASH_FAIL, FLASH_TIMED_OUT, FLASH_ECC_FAIL } flash_error_t; +typedef enum { + FLASH_SUCCESS, + FLASH_READY, + FLASH_FAIL, + FLASH_TIMED_OUT, + FLASH_BAD_BLOCK +} flash_error_t; typedef struct __attribute__((packed)) { uint8_t OIP : 1; diff --git a/firmware/lib/logging/logging.cpp b/firmware/lib/logging/logging.cpp index 4b70f85..1101070 100644 --- a/firmware/lib/logging/logging.cpp +++ b/firmware/lib/logging/logging.cpp @@ -22,10 +22,12 @@ namespace { uint8_t page_buf[NAND_PAGE_SIZE]; uint8_t read_buf[NAND_PAGE_SIZE]; uint32_t page_offset = 0; -uint32_t cur_page = NAND_NUM_PAGES; +uint32_t cur_page = NAND_NUM_PAGES; // init to one past the last valid page -uint32_t next_good_page(uint32_t page) { +uint32_t first_good_page_from(uint32_t page) { while (page < NAND_NUM_PAGES && !Bad_Page_Table::is_page_good(page)) { + // if one page in a block is bad, then all 64 pages are marked bad + // and we can just skip to the nex block page = (page / NAND_PAGES_PER_BLOCK + 1) * NAND_PAGES_PER_BLOCK; } @@ -46,6 +48,7 @@ bool page_is_empty(uint32_t page) { return first_byte == LOG_EMPTY_BYTE; } +// Find the first empty good page to resume writing uint32_t find_write_page() { uint32_t last_used_block = UINT32_MAX; @@ -63,7 +66,7 @@ uint32_t find_write_page() { } if (last_used_block == UINT32_MAX) { - return next_good_page(0); + return first_good_page_from(0); } uint32_t page = last_used_block * NAND_PAGES_PER_BLOCK; @@ -72,21 +75,27 @@ uint32_t find_write_page() { page++; } - return next_good_page(page); + return first_good_page_from(page); } bool commit_page() { bool ok = true; - if (Flash::write_to_cache(0, page_buf, NAND_PAGE_SIZE) != FLASH_SUCCESS) { + if (Flash::write_to_cache(0, page_buf, NAND_PAGE_SIZE) != FLASH_SUCCESS) ok = false; - } else if (Flash::program(cur_page) != FLASH_SUCCESS) { + + flash_error_t err = Flash::program(cur_page); + + if (err == FLASH_BAD_BLOCK) { Bad_Page_Table::mark_page_bad(cur_page); + } + + if (err != FLASH_SUCCESS) { ok = false; } reset_page_buf(); - cur_page = next_good_page(cur_page + 1); + cur_page = first_good_page_from(cur_page + 1); return ok; } @@ -108,7 +117,7 @@ flash_error_t read_page(uint32_t page_addr, uint8_t *out) { } flash_error_t err = Flash::read_main(page_addr, 0, out, NAND_PAGE_SIZE); - if (err == FLASH_ECC_FAIL) { + if (err == FLASH_BAD_BLOCK) { Bad_Page_Table::mark_page_bad(page_addr); } @@ -120,16 +129,16 @@ bool send_page(uint8_t *data) { return false; } - uint8_t cs_a = 0; - uint8_t cs_b = 0; + uint8_t checksum_a = 0; + uint8_t checksum_b = 0; for (uint32_t i = 0; i < NAND_PAGE_SIZE; i++) { - cs_a += data[i]; - cs_b += cs_a; + checksum_a += data[i]; + checksum_b += checksum_a; } CommsSerial.write(data, NAND_PAGE_SIZE); - CommsSerial.write(cs_a); - CommsSerial.write(cs_b); + CommsSerial.write(checksum_a); + CommsSerial.write(checksum_b); return true; } @@ -156,19 +165,26 @@ bool erase() { CommsSerial.println("Erasing NAND log. This may take a while."); + // loop through all blocks and erase filled blocks for (uint32_t block = 0; block < NAND_NUM_BLOCKS; block++) { uint32_t first_page = block * NAND_PAGES_PER_BLOCK; if (!Bad_Page_Table::is_page_good(first_page)) { continue; } - if (Flash::erase_block(first_page) != FLASH_SUCCESS) { + flash_error_t err = Flash::erase_block(first_page); + if (err == FLASH_BAD_BLOCK) { + // only takes one bad page marking to fail ok &= Bad_Page_Table::mark_page_bad(first_page); + } + + if (err != FLASH_SUCCESS) { + ok = false; } } reset_page_buf(); - cur_page = next_good_page(0); + cur_page = first_good_page_from(0); CommsSerial.println("NAND log erased."); return ok; @@ -207,7 +223,7 @@ bool flush() { bool dump() { flush(); - for (uint32_t page = next_good_page(0); page < NAND_NUM_PAGES; page = next_good_page(page + 1)) { + for (uint32_t page = first_good_page_from(0); page < NAND_NUM_PAGES; page = first_good_page_from(page + 1)) { if (read_page(page, read_buf) != FLASH_SUCCESS) { continue; } From 485e9cf65c6954c29a6bbeff507e21a93ec1e06d Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Sat, 17 Oct 2026 13:42:42 -0400 Subject: [PATCH 25/32] high level logging --- firmware/lib/logging/logging.cpp | 79 ++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/firmware/lib/logging/logging.cpp b/firmware/lib/logging/logging.cpp index 1101070..91ec723 100644 --- a/firmware/lib/logging/logging.cpp +++ b/firmware/lib/logging/logging.cpp @@ -24,11 +24,15 @@ uint8_t read_buf[NAND_PAGE_SIZE]; uint32_t page_offset = 0; uint32_t cur_page = NAND_NUM_PAGES; // init to one past the last valid page +uint32_t skip_to_next_block(uint32_t page) { + return (page / NAND_PAGES_PER_BLOCK + 1) * NAND_PAGES_PER_BLOCK; +} + uint32_t first_good_page_from(uint32_t page) { while (page < NAND_NUM_PAGES && !Bad_Page_Table::is_page_good(page)) { // if one page in a block is bad, then all 64 pages are marked bad - // and we can just skip to the nex block - page = (page / NAND_PAGES_PER_BLOCK + 1) * NAND_PAGES_PER_BLOCK; + // and we can just skip to the next block + page = skip_to_next_block(page); } return page; @@ -79,24 +83,35 @@ uint32_t find_write_page() { } bool commit_page() { - bool ok = true; - - if (Flash::write_to_cache(0, page_buf, NAND_PAGE_SIZE) != FLASH_SUCCESS) - ok = false; + if (Flash::write_to_cache(0, page_buf, NAND_PAGE_SIZE) != FLASH_SUCCESS) { + CommsSerial.println("Write to cache failed"); + return false; + } flash_error_t err = Flash::program(cur_page); if (err == FLASH_BAD_BLOCK) { - Bad_Page_Table::mark_page_bad(cur_page); + if (!Bad_Page_Table::mark_page_bad(cur_page)) { + CommsSerial.printf("While committing page %lu, it was found to be bad but marking it as such failed\n", cur_page); + + // skip to the next block manually + cur_page = skip_to_next_block(cur_page); + } + cur_page = first_good_page_from(cur_page); + } + + if (cur_page >= NAND_NUM_PAGES) { + return false; } if (err != FLASH_SUCCESS) { - ok = false; + return false; } reset_page_buf(); cur_page = first_good_page_from(cur_page + 1); - return ok; + + return true; } bool wait_for_continue() { @@ -111,12 +126,13 @@ bool wait_for_continue() { return CommsSerial.read() == DUMP_CONTINUE_CHAR; } -flash_error_t read_page(uint32_t page_addr, uint8_t *out) { +flash_error_t read_page(uint32_t page_addr) { if (!Bad_Page_Table::is_page_good(page_addr)) { return FLASH_FAIL; } - flash_error_t err = Flash::read_main(page_addr, 0, out, NAND_PAGE_SIZE); + flash_error_t err = Flash::read_main(page_addr, 0, read_buf, NAND_PAGE_SIZE); + if (err == FLASH_BAD_BLOCK) { Bad_Page_Table::mark_page_bad(page_addr); } @@ -124,7 +140,7 @@ flash_error_t read_page(uint32_t page_addr, uint8_t *out) { return err; } -bool send_page(uint8_t *data) { +bool send_page() { if (!wait_for_continue()) { return false; } @@ -132,11 +148,11 @@ bool send_page(uint8_t *data) { uint8_t checksum_a = 0; uint8_t checksum_b = 0; for (uint32_t i = 0; i < NAND_PAGE_SIZE; i++) { - checksum_a += data[i]; + checksum_a += read_buf[i]; checksum_b += checksum_a; } - CommsSerial.write(data, NAND_PAGE_SIZE); + CommsSerial.write(read_buf, NAND_PAGE_SIZE); CommsSerial.write(checksum_a); CommsSerial.write(checksum_b); return true; @@ -145,11 +161,18 @@ bool send_page(uint8_t *data) { } // namespace bool begin() { + if (!Flash::begin()) { + CommsSerial.println("Flash init failed"); + return false; + } + if (!Bad_Page_Table::begin()) { + CommsSerial.println("Bad Page Table Init failed"); return false; } reset_page_buf(); + cur_page = find_write_page(); if (cur_page >= NAND_NUM_PAGES) { @@ -176,8 +199,8 @@ bool erase() { if (err == FLASH_BAD_BLOCK) { // only takes one bad page marking to fail ok &= Bad_Page_Table::mark_page_bad(first_page); - } - + } + if (err != FLASH_SUCCESS) { ok = false; } @@ -186,6 +209,11 @@ bool erase() { reset_page_buf(); cur_page = first_good_page_from(0); + if (cur_page >= NAND_NUM_PAGES) { + CommsSerial.println("Something is very wrong, first good page is outside flash"); + return false; + } + CommsSerial.println("NAND log erased."); return ok; } @@ -196,9 +224,10 @@ bool write(uint8_t struct_id, uint8_t *data, size_t len) { return false; } - bool ok = true; if (page_offset + entry_len > NAND_PAGE_SIZE) { - ok = commit_page(); + if (!commit_page()) { + return false; + } } if (cur_page >= NAND_NUM_PAGES) { @@ -209,10 +238,12 @@ bool write(uint8_t struct_id, uint8_t *data, size_t len) { memcpy(page_buf + page_offset + sizeof(struct_id), data, len); page_offset += entry_len; - return ok; + return true; } bool flush() { + // Either buffer is empty and nothing to flush or + // flash is full and cannot commit another page if (page_offset == 0 || cur_page >= NAND_NUM_PAGES) { return true; } @@ -221,10 +252,12 @@ bool flush() { } bool dump() { - flush(); + if (!flush()) { + return false; + } for (uint32_t page = first_good_page_from(0); page < NAND_NUM_PAGES; page = first_good_page_from(page + 1)) { - if (read_page(page, read_buf) != FLASH_SUCCESS) { + if (read_page(page) != FLASH_SUCCESS) { continue; } @@ -232,13 +265,13 @@ bool dump() { break; } - if (!send_page(read_buf)) { + if (!send_page()) { return false; } } memset(read_buf, LOG_EMPTY_BYTE, sizeof(read_buf)); - return send_page(read_buf); + return send_page(); } } // namespace Logging From 71a329e7244e2da80a9dd00bbabc66d67dab009f Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Sat, 17 Oct 2026 15:04:56 -0400 Subject: [PATCH 26/32] logging init --- firmware/platformio.ini | 2 ++ firmware/src/ec_main.cpp | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/firmware/platformio.ini b/firmware/platformio.ini index d4bb272..299357d 100644 --- a/firmware/platformio.ini +++ b/firmware/platformio.ini @@ -56,6 +56,8 @@ board = TOAD_H7 build_flags = ${env.build_flags} -D TOAD_ENGINE_CONTROLLER_ONLY + -D USBCON + -D USBD_USE_CDC build_src_filter = -<**/*.cpp> diff --git a/firmware/src/ec_main.cpp b/firmware/src/ec_main.cpp index f210a9e..3777cee 100644 --- a/firmware/src/ec_main.cpp +++ b/firmware/src/ec_main.cpp @@ -4,7 +4,7 @@ #include "CommsSerial.h" #include "PressureSensors.h" #include "RCS.h" -#include "flash.h" +#include "logging.h" #include "SolenoidValves.h" #include "TVC_Actuators.h" #include "TemperatureSensors.h" @@ -37,8 +37,6 @@ void setup() { PT_TC_SPI_1.begin(); PT_TC_SPI_3.begin(); - Flash::begin(); - delay(3000); CommsSerial.println("Engine Controller Started!"); @@ -57,6 +55,7 @@ void setup() { all_modules_ok &= SolenoidValves::begin(); all_modules_ok &= TVC_Actuators::begin(); all_modules_ok &= ValveController::begin(); + all_modules_ok &= Logging::begin(); if (!all_modules_ok) { while (true) { From c399dde1eac2a8bdffe24198723257d58a666aa8 Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Sun, 18 Oct 2026 01:14:41 -0400 Subject: [PATCH 27/32] high level logging + bad page table finalized --- firmware/lib/logging/bad_page_table.cpp | 180 ++++++++++++++---------- firmware/lib/logging/bad_page_table.h | 16 ++- firmware/lib/logging/logging.cpp | 99 ++++++++----- 3 files changed, 179 insertions(+), 116 deletions(-) diff --git a/firmware/lib/logging/bad_page_table.cpp b/firmware/lib/logging/bad_page_table.cpp index dc914fd..6869d0b 100644 --- a/firmware/lib/logging/bad_page_table.cpp +++ b/firmware/lib/logging/bad_page_table.cpp @@ -24,6 +24,10 @@ extern uint32_t _bpt_start; #define STM32_FLASH_WORD_SIZE 32u #define BPT_EMPTY_ENTRY 0xFFFFFFFFu +namespace Bad_Page_Table { + +namespace { + constexpr uint32_t BPT_MAGIC_NUMBER = 0x0123ABCDu; constexpr uint32_t BPT_MAX_ENTRIES = (FLASH_SECTOR_SIZE - STM32_FLASH_WORD_SIZE - NUM_BYTES_TRACKING_BLOCKS) / STM32_FLASH_WORD_SIZE; @@ -32,10 +36,6 @@ uint32_t *bpt_magic = &_bpt_start; uint8_t *bpt_table = reinterpret_cast(&_bpt_start) + STM32_FLASH_WORD_SIZE; uint8_t *bpt_entries = bpt_table + NUM_BYTES_TRACKING_BLOCKS; -namespace Bad_Page_Table { - -namespace { - bool program_flash_word(uint32_t flash_addr, uint32_t word_addr) { HAL_StatusTypeDef status; @@ -67,7 +67,88 @@ int generate_key() { const int key_min = 1000; const int key_max = 10000; srand(millis()); - return (rand() % (key_max - key_min) + key_min); + return (rand() % (key_max - key_min) + key_min); +} + +// bad block from factory +bool is_initial_bad_block(uint32_t block_num) { + return !(bpt_table[get_bpt_bad_byte(block_num)] & get_bpt_bad_bit(block_num)); +} + +bool get_runtime_bad_page(uint32_t block_num, uint32_t *out_page) { + *out_page = NAND_NUM_PAGES; + + // find the bad page in the block + for (uint32_t i = 0; i < BPT_MAX_ENTRIES; i++) { + uint32_t entry = get_entry(i); + + // last entry already seen, no more entries + if (entry == BPT_EMPTY_ENTRY) { + break; + } + + if (entry / NAND_PAGES_PER_BLOCK == block_num && entry < *out_page) { + *out_page = entry; + } + } + + return *out_page != NAND_NUM_PAGES; +} + +bool add_entry(uint32_t page_address) { + uint32_t block_num = page_address / NAND_PAGES_PER_BLOCK; + + for (uint32_t i = 0; i < BPT_MAX_ENTRIES; i++) { + if (get_entry(i) != BPT_EMPTY_ENTRY) { + continue; + } + + uint8_t word_buf[STM32_FLASH_WORD_SIZE]; + memset(word_buf, PAGE_MARKED_GOOD, sizeof(word_buf)); + memcpy(word_buf, &page_address, sizeof(page_address)); + + uint32_t word_buf_addr = reinterpret_cast(word_buf); + uint32_t entry_addr = get_entry_addr(i); + + if (!program_flash_word(entry_addr, word_buf_addr)) { + CommsSerial.printf("WARNING: failed to mark page %lu (block %lu) bad.\n", page_address, block_num); + return false; + } + + return true; + } + + CommsSerial.printf("WARNING: Bad Page Table full, block %lu not marked bad.\n", block_num); + return false; +} + +bool scan_factory_bad_blocks() { + uint8_t table_buf[NUM_BYTES_TRACKING_BLOCKS]; + memset(table_buf, PAGE_MARKED_GOOD, sizeof(table_buf)); + + for (uint32_t i = 0; i < NAND_NUM_BLOCKS; i++) { + uint32_t cur_page = i * NAND_PAGES_PER_BLOCK; + + uint8_t bad_block_mark; + if (Flash::read_spare(cur_page, 0, &bad_block_mark, 1, false) != FLASH_SUCCESS) { + return false; + } + + if (bad_block_mark != PAGE_MARKED_GOOD) { + table_buf[get_bpt_bad_byte(i)] &= ~get_bpt_bad_bit(i); + } + } + + // flash can only be programmed once per erase, in 32 byte aligned chunks + for (uint32_t offset = 0; offset < NUM_BYTES_TRACKING_BLOCKS; offset += STM32_FLASH_WORD_SIZE) { + uint32_t flash_addr = reinterpret_cast(bpt_table + offset); + uint32_t table_addr = reinterpret_cast(table_buf + offset); + if (!program_flash_word(flash_addr, table_addr)) { + return false; + } + } + + return true; } } // namespace @@ -130,96 +211,47 @@ bool begin(void) { return true; }; -bool scan_factory_bad_blocks(void) { - uint8_t table_buf[NUM_BYTES_TRACKING_BLOCKS]; - memset(table_buf, PAGE_MARKED_GOOD, sizeof(table_buf)); - - for (uint32_t i = 0; i < NAND_NUM_BLOCKS; i++) { - uint32_t cur_page = i * NAND_PAGES_PER_BLOCK; - - uint8_t bad_block_mark; - if (Flash::read_spare(cur_page, 0, &bad_block_mark, 1, false) != FLASH_SUCCESS) { - return false; - } - - if (bad_block_mark != PAGE_MARKED_GOOD) { - table_buf[get_bpt_bad_byte(i)] &= ~get_bpt_bad_bit(i); - } - } - - // flash can only be programmed once per erase, in 32 byte aligned chunks - for (uint32_t offset = 0; offset < NUM_BYTES_TRACKING_BLOCKS; offset += STM32_FLASH_WORD_SIZE) { - uint32_t flash_addr = reinterpret_cast(bpt_table + offset); - uint32_t table_addr = reinterpret_cast(table_buf + offset); - if (!program_flash_word(flash_addr, table_addr)) { - return false; - } - } - - return true; -}; - -bool is_page_good(uint32_t page_address) { +page_status_t get_page_status(uint32_t page_address) { if (page_address >= NAND_NUM_PAGES) { - return false; + return PAGE_BAD; } uint32_t block_num = page_address / NAND_PAGES_PER_BLOCK; - uint32_t byte = get_bpt_bad_byte(block_num); - uint8_t bit = get_bpt_bad_bit(block_num); - uint8_t *bad_page_byte = bpt_table + byte; - - if (!(*bad_page_byte & bit)) { - return false; + if (is_initial_bad_block(block_num)) { + return PAGE_BAD; } - for (uint32_t i = 0; i < BPT_MAX_ENTRIES; i++) { - uint32_t entry = get_entry(i); - if (entry == BPT_EMPTY_ENTRY) { - break; - } - - if (entry == block_num) { - return false; - } + uint32_t failed_page; + if (!get_runtime_bad_page(block_num, &failed_page)) { + return PAGE_GOOD; } - return true; -}; + return page_address < failed_page ? PAGE_READ_ONLY : PAGE_BAD; +} bool mark_page_bad(uint32_t page_address) { if (page_address >= NAND_NUM_PAGES) { return false; } - if (!is_page_good(page_address)) { + if (get_page_status(page_address) != PAGE_GOOD) { return true; } - uint32_t block_num = page_address / NAND_PAGES_PER_BLOCK; - - for (uint32_t i = 0; i < BPT_MAX_ENTRIES; i++) { - if (get_entry(i) != BPT_EMPTY_ENTRY) { - continue; - } - - uint8_t word_buf[STM32_FLASH_WORD_SIZE]; - memset(word_buf, PAGE_MARKED_GOOD, sizeof(word_buf)); - memcpy(word_buf, &block_num, sizeof(block_num)); - - uint32_t word_buf_addr = reinterpret_cast(word_buf); - uint32_t entry_addr = get_entry_addr(i); + return add_entry(page_address); +}; - if (!program_flash_word(entry_addr, word_buf_addr)) { - CommsSerial.printf("WARNING: failed to mark block %lu bad.\n", block_num); - return false; - } +bool retire_block(uint32_t page_address) { + if (page_address >= NAND_NUM_PAGES) { + return false; + } + uint32_t first_page = page_address / NAND_PAGES_PER_BLOCK * NAND_PAGES_PER_BLOCK; + if (get_page_status(first_page) == PAGE_BAD) { return true; } - CommsSerial.printf("WARNING: Bad Page Table full, block %lu not marked bad.\n", block_num); - return false; -}; + return add_entry(first_page); +} } // namespace Bad_Page_Table \ No newline at end of file diff --git a/firmware/lib/logging/bad_page_table.h b/firmware/lib/logging/bad_page_table.h index a273565..df7a64a 100644 --- a/firmware/lib/logging/bad_page_table.h +++ b/firmware/lib/logging/bad_page_table.h @@ -1,5 +1,5 @@ /** - * @file flash_defs.h + * @file bad_page_table.h * @brief Bad Page Table Header * * @author Daniel Proano (dproano@purdue.edu) @@ -14,14 +14,20 @@ #define NUM_BYTES_TRACKING_BLOCKS (NUM_BITS / 8) #define PAGE_MARKED_GOOD (0xFF) +typedef enum { + PAGE_GOOD, + PAGE_READ_ONLY, + PAGE_BAD +} page_status_t; + namespace Bad_Page_Table { bool begin(); -bool scan_factory_bad_blocks(void); - -bool is_page_good(uint32_t address); +page_status_t get_page_status(uint32_t address); bool mark_page_bad(uint32_t address); -} +bool retire_block(uint32_t address); + +} diff --git a/firmware/lib/logging/logging.cpp b/firmware/lib/logging/logging.cpp index 91ec723..cdf7094 100644 --- a/firmware/lib/logging/logging.cpp +++ b/firmware/lib/logging/logging.cpp @@ -14,6 +14,7 @@ #define DUMP_CONTINUE_CHAR 'c' #define DUMP_TIMEOUT_MS 5000u +#define DumpSerial SerialUSB namespace Logging { @@ -29,7 +30,7 @@ uint32_t skip_to_next_block(uint32_t page) { } uint32_t first_good_page_from(uint32_t page) { - while (page < NAND_NUM_PAGES && !Bad_Page_Table::is_page_good(page)) { + while (page < NAND_NUM_PAGES && Bad_Page_Table::get_page_status(page) != PAGE_GOOD) { // if one page in a block is bad, then all 64 pages are marked bad // and we can just skip to the next block page = skip_to_next_block(page); @@ -58,7 +59,7 @@ uint32_t find_write_page() { for (uint32_t block = 0; block < NAND_NUM_BLOCKS; block++) { uint32_t first_page = block * NAND_PAGES_PER_BLOCK; - if (!Bad_Page_Table::is_page_good(first_page)) { + if (Bad_Page_Table::get_page_status(first_page) != PAGE_GOOD) { continue; } @@ -117,27 +118,17 @@ bool commit_page() { bool wait_for_continue() { uint32_t start = millis(); - while (!CommsSerial.available()) { + while (!DumpSerial.available()) { if (millis() - start > DUMP_TIMEOUT_MS) { return false; } } - return CommsSerial.read() == DUMP_CONTINUE_CHAR; + return DumpSerial.read() == DUMP_CONTINUE_CHAR; } flash_error_t read_page(uint32_t page_addr) { - if (!Bad_Page_Table::is_page_good(page_addr)) { - return FLASH_FAIL; - } - - flash_error_t err = Flash::read_main(page_addr, 0, read_buf, NAND_PAGE_SIZE); - - if (err == FLASH_BAD_BLOCK) { - Bad_Page_Table::mark_page_bad(page_addr); - } - - return err; + return Flash::read_main(page_addr, 0, read_buf, NAND_PAGE_SIZE); } bool send_page() { @@ -152,12 +143,54 @@ bool send_page() { checksum_b += checksum_a; } - CommsSerial.write(read_buf, NAND_PAGE_SIZE); - CommsSerial.write(checksum_a); - CommsSerial.write(checksum_b); + DumpSerial.write(read_buf, NAND_PAGE_SIZE); + DumpSerial.write(checksum_a); + DumpSerial.write(checksum_b); return true; } +bool send_end_page() { + memset(read_buf, LOG_EMPTY_BYTE, sizeof(read_buf)); + return send_page(); +} + +bool send_log() { + uint32_t page = 0; + while (page < NAND_NUM_PAGES) { + page_status_t status = Bad_Page_Table::get_page_status(page); + + if (status == PAGE_BAD) { + page = skip_to_next_block(page); + continue; + } + + if (read_page(page) != FLASH_SUCCESS) { + page++; + continue; + } + + if (read_buf[0] == LOG_EMPTY_BYTE) { + // end of logs found + if (status == PAGE_GOOD) { + break; + } + + // otherwise, page is bad, go to next block + page = skip_to_next_block(page); + continue; + } + + if (!send_page()) { + return false; + } + + page++; + } + + // send an empty page to indicate no more data + return send_end_page(); +} + } // namespace bool begin() { @@ -176,8 +209,7 @@ bool begin() { cur_page = find_write_page(); if (cur_page >= NAND_NUM_PAGES) { - CommsSerial.println("NAND log is full."); - return false; + CommsSerial.println("WARNING: NAND log is full, erase it before logging."); } return true; @@ -191,11 +223,19 @@ bool erase() { // loop through all blocks and erase filled blocks for (uint32_t block = 0; block < NAND_NUM_BLOCKS; block++) { uint32_t first_page = block * NAND_PAGES_PER_BLOCK; - if (!Bad_Page_Table::is_page_good(first_page)) { + page_status_t status = Bad_Page_Table::get_page_status(first_page); + if (status == PAGE_BAD) { + continue; + } + + if (status == PAGE_READ_ONLY) { + Flash::erase_block(first_page); + ok &= Bad_Page_Table::retire_block(first_page); continue; } flash_error_t err = Flash::erase_block(first_page); + if (err == FLASH_BAD_BLOCK) { // only takes one bad page marking to fail ok &= Bad_Page_Table::mark_page_bad(first_page); @@ -256,22 +296,7 @@ bool dump() { return false; } - for (uint32_t page = first_good_page_from(0); page < NAND_NUM_PAGES; page = first_good_page_from(page + 1)) { - if (read_page(page) != FLASH_SUCCESS) { - continue; - } - - if (read_buf[0] == LOG_EMPTY_BYTE) { - break; - } - - if (!send_page()) { - return false; - } - } - - memset(read_buf, LOG_EMPTY_BYTE, sizeof(read_buf)); - return send_page(); + return send_log(); } } // namespace Logging From 87ba69b17c642527d6ab1c791cf2e6557ec21458 Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Sun, 18 Oct 2026 01:20:46 -0400 Subject: [PATCH 28/32] usb code comemnted --- firmware/src/prog_main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firmware/src/prog_main.cpp b/firmware/src/prog_main.cpp index a3e479e..ea57832 100644 --- a/firmware/src/prog_main.cpp +++ b/firmware/src/prog_main.cpp @@ -8,7 +8,7 @@ // https://www.st.com/resource/en/application_note/an4286-how-to-use-spi-protocol-in-bootloader-on-stm32-mcus-stmicroelectronics.pdf // Citations commented as [pg#] -CommsSerial_t USB_CommsSerial; +// CommsSerial_t USB_CommsSerial; CommsSerial_t HW_CommsSerial(PIN_HW_COMM_SERIAL_RX, PIN_HW_COMM_SERIAL_TX); CommsSerial_t HW_FallbackSerial(PIN_HW_FALLBACK_SERIAL_RX, PIN_HW_FALLBACK_SERIAL_TX); @@ -51,7 +51,7 @@ void setup() { // All shared interfaces are begun here. // Use same baud rate on all Comm Serials for consistency. - USB_CommsSerial.begin(RADIO_BAUD); + // USB_CommsSerial.begin(RADIO_BAUD); HW_CommsSerial.begin(RADIO_BAUD); HW_FallbackSerial.begin(RADIO_BAUD); From 383901df050f1fd97c9deadf037a02ec6cd6f6db Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Sun, 18 Oct 2026 01:33:36 -0400 Subject: [PATCH 29/32] pin map --- firmware/lib/logging/flash.cpp | 34 +++++++--------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp index 78ea385..a94f62a 100644 --- a/firmware/lib/logging/flash.cpp +++ b/firmware/lib/logging/flash.cpp @@ -7,6 +7,7 @@ #include "flash.h" #include "CommsSerial.h" +#include "ec_pins.h" #include "flash_defs.h" #include "stm32h7xx_hal_qspi.h" @@ -21,33 +22,12 @@ void HAL_QSPI_MspInit(QSPI_HandleTypeDef *) { __HAL_RCC_QSPI_FORCE_RESET(); __HAL_RCC_QSPI_RELEASE_RESET(); - __HAL_RCC_GPIOB_CLK_ENABLE(); - __HAL_RCC_GPIOD_CLK_ENABLE(); - __HAL_RCC_GPIOF_CLK_ENABLE(); - __HAL_RCC_GPIOG_CLK_ENABLE(); - - GPIO_InitTypeDef gpio_init{}; - gpio_init.Mode = GPIO_MODE_AF_PP; - gpio_init.Pull = GPIO_NOPULL; - - gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - - // PB2, PD11, PD12, PD13, PF7 all under AF9 - gpio_init.Alternate = GPIO_AF9_QUADSPI; - - gpio_init.Pin = GPIO_PIN_2; - HAL_GPIO_Init(GPIOB, &gpio_init); - - gpio_init.Pin = GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13; - HAL_GPIO_Init(GPIOD, &gpio_init); - - gpio_init.Pin = GPIO_PIN_7; - HAL_GPIO_Init(GPIOF, &gpio_init); - - // PG6 is under AF10 - gpio_init.Alternate = GPIO_AF10_QUADSPI; - gpio_init.Pin = GPIO_PIN_6; - HAL_GPIO_Init(GPIOG, &gpio_init); + pinmap_pinout(digitalPinToPinName(PIN_QSPI_CLK), PinMap_QUADSPI_SCLK); + pinmap_pinout(digitalPinToPinName(PIN_QSPI_IO0), PinMap_QUADSPI_DATA0); + pinmap_pinout(digitalPinToPinName(PIN_QSPI_IO1), PinMap_QUADSPI_DATA1); + pinmap_pinout(digitalPinToPinName(PIN_QSPI_IO2), PinMap_QUADSPI_DATA2); + pinmap_pinout(digitalPinToPinName(PIN_QSPI_IO3), PinMap_QUADSPI_DATA3); + pinmap_pinout(digitalPinToPinName(PIN_QSPI_CS), PinMap_QUADSPI_SSEL); } namespace Flash { From f7ad12b54bd4618d96d58b40c1467a5c15ce6a4e Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Sun, 18 Oct 2026 01:42:10 -0400 Subject: [PATCH 30/32] config in HAL set qspi input explicitly --- firmware/lib/logging/flash.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp index a94f62a..d150e4b 100644 --- a/firmware/lib/logging/flash.cpp +++ b/firmware/lib/logging/flash.cpp @@ -17,6 +17,7 @@ #include void HAL_QSPI_MspInit(QSPI_HandleTypeDef *) { + __HAL_RCC_QSPI_CONFIG(RCC_QSPICLKSOURCE_D1HCLK); __HAL_RCC_QSPI_CLK_ENABLE(); __HAL_RCC_QSPI_FORCE_RESET(); From 36968674eae374f8aa982bd22824edba7c4f36a1 Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Sun, 18 Oct 2026 20:37:37 -0400 Subject: [PATCH 31/32] prescaler adjustments --- firmware/lib/logging/flash.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp index d150e4b..676c987 100644 --- a/firmware/lib/logging/flash.cpp +++ b/firmware/lib/logging/flash.cpp @@ -185,7 +185,7 @@ bool init_qspi_peripheral() { // Clock Prescaler here is 32 bits but // must be a value between 0 and 255 per // stm32h7xx_hal_qspi.h:53 - hqspi.Init.ClockPrescaler = f_hclk / FREQ_MAX; + hqspi.Init.ClockPrescaler = (f_hclk / FREQ_MAX) - 1; if (hqspi.Init.ClockPrescaler > 255) hqspi.Init.ClockPrescaler = 255; From bbe0d92a380da75364e6778ce66cbd07a1166558 Mon Sep 17 00:00:00 2001 From: Danny <179722984+DanielProano@users.noreply.github.com> Date: Sun, 18 Oct 2026 20:44:44 -0400 Subject: [PATCH 32/32] accounting for rounding --- firmware/lib/logging/flash.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/lib/logging/flash.cpp b/firmware/lib/logging/flash.cpp index 676c987..79e383f 100644 --- a/firmware/lib/logging/flash.cpp +++ b/firmware/lib/logging/flash.cpp @@ -185,7 +185,7 @@ bool init_qspi_peripheral() { // Clock Prescaler here is 32 bits but // must be a value between 0 and 255 per // stm32h7xx_hal_qspi.h:53 - hqspi.Init.ClockPrescaler = (f_hclk / FREQ_MAX) - 1; + hqspi.Init.ClockPrescaler = (f_hclk + FREQ_MAX - 1) / FREQ_MAX - 1; if (hqspi.Init.ClockPrescaler > 255) hqspi.Init.ClockPrescaler = 255;