Clock, baud, serial backend (hardware USART 0/1 or the software UART on any pins) and the activation window all resolve through one CMake function, pureboot_add_loader() in pureboot/CMakeLists.txt — the unit a downstream project consumes. The default baud is the fastest standard rate within 2.5 % (the same best-divisor search libavr's solver runs), gated on software builds by the polled receiver's 100-cycles-a-bit floor; every explicit pick is re-checked by the compile's static asserts. The size matrix builds each axis that can move the image — backend x clock ladder x USART instance, per chip — against the slot budget, and two nondefault deployments run the whole protocol suite live: the 328P on its shipped 1 MHz fuses over software serial on TX=PB1/RX=PB5 (pureboot.custom), and the 644A over USART1 (pureboot.usart1). The sim runner takes -l to bridge any link, paces a fully quiet bridge toward real time (a free-running 8 M-cycle window loses the reset-race knock), and the fixture application speaks the deployment it is built for. The loader itself shed bytes on the way: the return-address high byte spelled through byteswap (the double swap folds to the one-byte pick), the info-block address composed instead of bit_cast, and libavr's new polled-UART helpers replacing the port's uart::detail reaches. Every combination fits: 458-506 B across the megas' whole matrix, 470-484 B on the tinies, 556-562 B in the 1284s' 1 KiB slot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
107 lines
3.0 KiB
C++
107 lines
3.0 KiB
C++
// Test-fixture application for the pureboot protocol tests: prints "APP" on
|
|
// the chip's serial link (the same link the loader uses) — the proof that
|
|
// the loader's hand-over, and on the tinies the host's reset-vector
|
|
// surgery, actually launched it. Linked normally (crt, vectors at 0); on
|
|
// the tinies its reset vector is the rjmp the host re-homes.
|
|
//
|
|
// On the hardware-USART link it then listens, and an 'L' makes it jump into
|
|
// the resident loader — the application-owned loader entry a
|
|
// BOOTRST-unprogrammed mega relies on (reset always boots the application
|
|
// there), exercised by the self-update tests. The software link idles:
|
|
// reset reaches those loaders through the patched vector (or the runner
|
|
// models BOOTRST), so the application owes them nothing.
|
|
//
|
|
// The fixture speaks the deployment its loader was built for: the same
|
|
// PUREBOOT_* defines configure it, and without them it assumes the stock
|
|
// deployment (the crystal/RC clock table below, the chip's natural link).
|
|
#include <libavr/libavr.hpp>
|
|
|
|
using namespace avr::literals;
|
|
|
|
namespace {
|
|
|
|
consteval avr::hertz_t clock()
|
|
{
|
|
#if defined(PUREBOOT_CLOCK_HZ)
|
|
return avr::hertz_t{PUREBOOT_CLOCK_HZ};
|
|
#else
|
|
auto name = std::string_view{avr::hw::db.name};
|
|
if (name.starts_with("ATtiny13"))
|
|
return 9.6_MHz;
|
|
if (name.starts_with("ATtiny"))
|
|
return 8_MHz;
|
|
return 16_MHz;
|
|
#endif
|
|
}
|
|
|
|
#if !defined(PUREBOOT_TX)
|
|
#define PUREBOOT_TX pb1
|
|
#endif
|
|
#if !defined(PUREBOOT_USART)
|
|
#define PUREBOOT_USART 0
|
|
#endif
|
|
|
|
consteval bool use_hardware()
|
|
{
|
|
#if defined(PUREBOOT_SOFT_SERIAL)
|
|
return false;
|
|
#else
|
|
return avr::hw::db.has_instance("USART0") || avr::hw::db.has_instance("USART");
|
|
#endif
|
|
}
|
|
|
|
using dev = avr::device<{.clock = clock()}>;
|
|
|
|
template <avr::hertz_t C, bool Hardware = use_hardware()>
|
|
struct link {
|
|
#if defined(PUREBOOT_BAUD)
|
|
static constexpr avr::baud_t baud{PUREBOOT_BAUD};
|
|
#else
|
|
static constexpr avr::baud_t baud{115200};
|
|
#endif
|
|
using tx_t = avr::uart::usart<'0' + PUREBOOT_USART, C, {.baud = baud, .max_baud_error = 2.5_pct}>;
|
|
static void tx(char c)
|
|
{
|
|
tx_t::write(static_cast<std::uint8_t>(c));
|
|
}
|
|
[[noreturn]] static void idle()
|
|
{
|
|
// 'L' hands back to the loader at the top slot — 512 bytes, or the
|
|
// 1 KiB the >64 KiB chips use.
|
|
constexpr std::uint32_t slot = avr::hw::db.mem.flash_size > 65536 ? 1024 : 512;
|
|
for (;;)
|
|
if (tx_t::read_blocking() == 'L')
|
|
reinterpret_cast<void (*)()>(static_cast<std::uint16_t>((avr::hw::db.mem.flash_size - slot) / 2))();
|
|
}
|
|
};
|
|
|
|
template <avr::hertz_t C>
|
|
struct link<C, false> {
|
|
#if defined(PUREBOOT_BAUD)
|
|
static constexpr avr::baud_t baud{PUREBOOT_BAUD};
|
|
#else
|
|
static constexpr avr::baud_t baud{57600};
|
|
#endif
|
|
using tx_t = avr::uart::software_tx<C, avr::PUREBOOT_TX, baud>;
|
|
static void tx(char c)
|
|
{
|
|
tx_t::write(static_cast<std::uint8_t>(c));
|
|
}
|
|
[[noreturn]] static void idle()
|
|
{
|
|
while (true) {
|
|
}
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
int main()
|
|
{
|
|
avr::init<typename link<dev::clock>::tx_t>();
|
|
link<dev::clock>::tx('A');
|
|
link<dev::clock>::tx('P');
|
|
link<dev::clock>::tx('P');
|
|
link<dev::clock>::idle();
|
|
}
|