Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
eab79d3
temp stash
DanielProano Oct 10, 2026
6e1f08f
AI scaffolding init (temporary example code)
DanielProano Sep 9, 2026
b429cc3
more clear types
DanielProano Sep 10, 2026
d57e37a
flash type restructuring
DanielProano Sep 11, 2026
81daa5e
ECCS to ECCSE type
DanielProano Sep 11, 2026
e554650
updated flash hash defines
DanielProano Sep 19, 2026
67f34e8
in progress page reads
DanielProano Sep 19, 2026
f7de17e
low level flash logic
DanielProano Sep 20, 2026
8341ad1
bad page table minus begin
DanielProano Sep 24, 2026
8d21162
readable code with macros
DanielProano Sep 25, 2026
0214a9f
format + collapse redundant code
DanielProano Sep 27, 2026
3a7227e
updates to logging
DanielProano Oct 10, 2026
2565b14
readability + functionality
DanielProano Oct 13, 2026
8c0946e
reformatting
DanielProano Oct 14, 2026
d76ab33
cleaned up bugs
DanielProano Oct 14, 2026
02fd278
bad page table updates
DanielProano Oct 14, 2026
fc8faec
more status's checked
DanielProano Oct 15, 2026
25dc3ff
typos fixed
DanielProano Oct 15, 2026
607bfe9
more status checks
DanielProano Oct 15, 2026
315a0b7
bad page table finalized
DanielProano Oct 15, 2026
3bda175
flash finalized
DanielProano Oct 15, 2026
08b51f6
logging high level API init
DanielProano Oct 15, 2026
df61313
privatizing a public function
DanielProano Oct 15, 2026
b8baa94
better logging
DanielProano Oct 16, 2026
80f9081
high level logging
DanielProano Oct 17, 2026
f72f753
logging init
DanielProano Oct 17, 2026
1f96943
high level logging + bad page table finalized
DanielProano Oct 18, 2026
8407cbf
usb code comemnted
DanielProano Oct 18, 2026
730baa1
pin map
DanielProano Oct 18, 2026
d941b78
config in HAL set qspi input explicitly
DanielProano Oct 18, 2026
b92e8b9
prescaler adjustments
DanielProano Oct 19, 2026
cf46ee3
accounting for rounding
DanielProano Oct 19, 2026
7622976
updates
DanielProano Nov 2, 2026
b45a4da
hash defines
DanielProano Nov 2, 2026
d370c1f
removed typedef
DanielProano Nov 2, 2026
30382e0
added Command Router
DanielProano Nov 2, 2026
bf8e2b9
command routes + better serial logic
DanielProano Nov 2, 2026
2d5b9cb
redudant logic
DanielProano Nov 2, 2026
7d19ffa
.ini adjustments
DanielProano Nov 2, 2026
5311500
updated platformio.ini
DanielProano Nov 2, 2026
56e0b75
platformio.ini
DanielProano Nov 2, 2026
3e7b286
accidental additions to router code
DanielProano Nov 2, 2026
3671544
deletions
DanielProano Nov 2, 2026
af45ed7
deleted logging include
DanielProano Nov 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion firmware/boards/TOAD_H7/ldscript.ld
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ _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. */
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
Expand Down Expand Up @@ -133,6 +134,13 @@ SECTIONS
. = ALIGN(4);
} >FLASH

/* Reserved for the NAND flash Bad Page Table */
.bpt_table (NOLOAD) :

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you confirm DFU won't overwrite this section

{
. = ALIGN(4);
_bpt_start = .;
} >BPT_FLASH

/* Used by the startup to initialize data */
_sidata = LOADADDR(.data);

Expand Down
6 changes: 6 additions & 0 deletions firmware/boards/TOAD_H7/variant_TOAD_H7.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,12 @@ void SystemClock_Config(void)
}

__HAL_RCC_FDCAN_CLK_ENABLE();

__HAL_RCC_QSPI_CONFIG(RCC_QSPICLKSOURCE_D1HCLK);
__HAL_RCC_QSPI_CLK_ENABLE();

__HAL_RCC_QSPI_FORCE_RESET();
__HAL_RCC_QSPI_RELEASE_RESET();;
}

// jhillman added this to ensure HSE value is correct for all dynamic clock frequency calculations
Expand Down
262 changes: 262 additions & 0 deletions firmware/lib/logging/bad_page_table.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
/**
* @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)
*/

#include <Arduino.h>

#include "bad_page_table.h"
#include "flash.h"
#include "stm32h7xx_hal_flash.h"
#include "stm32h7xx_hal_flash_ex.h"

#include "CommsSerial.h"
#include <stdlib.h>
#include <string.h>

extern uint32_t _bpt_start;

#define STM32_FLASH_WORD_SIZE 32u
#define BPT_EMPTY_ENTRY 0xFFFFFFFFu
#define TYPE_ERASE FLASH_TYPEERASE_SECTORS
#define BANK_NUM FLASH_BANK_2
#define SECTOR_NUM FLASH_SECTOR_7
#define NUM_OF_SECTORS 1
#define VOLTAGE_RANGE FLASH_VOLTAGE_RANGE_3

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;

const uint32_t *bpt_magic = &_bpt_start;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These give the impression that the data pointed to is constant; what I meant is can you make it clear that these point to fixed addresses.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The syntax for this is uint32_t* const bpt_magic = ... which makes it clear that the address pointed to doesn't change, but the data at that address can be modified/

const uint8_t *bpt_table = reinterpret_cast<uint8_t *>(&_bpt_start) + STM32_FLASH_WORD_SIZE;
const uint8_t *bpt_entries = bpt_table + NUM_BYTES_TRACKING_BLOCKS;

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<uint32_t>(bpt_entries + index * STM32_FLASH_WORD_SIZE);
}

uint32_t get_entry(uint32_t index) {
return *reinterpret_cast<uint32_t *>(get_entry_addr(index));
}

uint32_t get_bpt_bad_byte(uint32_t block_num) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider renaming to get_bpt_bad_index or get_bpt_bad_byte_index

return block_num / 8;
}

uint8_t get_bpt_bad_bit(uint32_t block_num) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider renaming to get_bpt_bad_bit_mask

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);
}

// 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) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function looks like it's checking if a block was marked bad at runtime and returns the last good page that was written to? Can you just comment that this is what it does and what the return value means thx

*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) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with this - can you make a note that it stores which specific page went bad.

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<uint32_t>(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() {
static 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<uint32_t>(bpt_table + offset);
uint32_t table_addr = reinterpret_cast<uint32_t>(table_buf + offset);
if (!program_flash_word(flash_addr, table_addr)) {
return false;
}
}

return true;
}

} // namespace

bool begin(void) {
if (*bpt_magic == BPT_MAGIC_NUMBER) {
return true;
}

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

char *res = CommsSerial.readline();
if (atoi(res) != key) {
CommsSerial.println("Incorrect key entered.");
return false;
}

// erase bad page table
FLASH_EraseInitTypeDef erase_init = {
.TypeErase = TYPE_ERASE,
.Banks = BANK_NUM,
.Sector = SECTOR_NUM,
.NbSectors = NUM_OF_SECTORS,
.VoltageRange = VOLTAGE_RANGE
};
uint32_t sector_error;

HAL_FLASH_Unlock();
bool erase_ok = HAL_FLASHEx_Erase(&erase_init, &sector_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;
}

static 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 flash_magic_addr = reinterpret_cast<uint32_t>(bpt_magic);
uint32_t word_buf_addr = reinterpret_cast<uint32_t>(word_buf);

if (!program_flash_word(flash_magic_addr, word_buf_addr)) {
CommsSerial.println("Flash program failed");
return false;
}

CommsSerial.println("Bad Page Table initialized.");
return true;
};

page_status_t get_page_status(uint32_t page_address) {
if (page_address >= NAND_NUM_PAGES) {
return PAGE_BAD;
}

uint32_t block_num = page_address / NAND_PAGES_PER_BLOCK;
if (is_initial_bad_block(block_num)) {
return PAGE_BAD;
}

uint32_t failed_page;
if (!get_runtime_bad_page(block_num, &failed_page)) {
return PAGE_GOOD;
}

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 (get_page_status(page_address) != PAGE_GOOD) {
return true;
}

return add_entry(page_address);
};

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

return add_entry(first_page);
}

} // namespace Bad_Page_Table
33 changes: 33 additions & 0 deletions firmware/lib/logging/bad_page_table.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* @file bad_page_table.h
* @brief Bad Page Table Header
*
* @author Daniel Proano (dproano@purdue.edu)
*/

#pragma once

#include "flash_defs.h"
#include <stdint.h>

#define NUM_BITS (NAND_NUM_BLOCKS)
#define NUM_BYTES_TRACKING_BLOCKS (NUM_BITS / 8)
#define PAGE_MARKED_GOOD (0xFF)

enum page_status_t {
PAGE_GOOD,
PAGE_READ_ONLY,
PAGE_BAD
};

namespace Bad_Page_Table {

bool begin();

page_status_t get_page_status(uint32_t address);

bool mark_page_bad(uint32_t address);

bool retire_block(uint32_t address);

}
Loading
Loading