mirror of
https://github.com/MCKero6423/uv-k5-v3-emulator.git
synced 2026-10-02 03:15:36 +00:00
Document how the machine boots
There is no bootloader, kernel, partition table or filesystem here, and that is not obvious from the code -- someone arriving with hosted-OS assumptions will look for layers that do not exist. Covers the parts that are easy to get wrong rather than restating the source: - The reset handoff is two words. SP from 0x08002800, PC from +0x04. Verified against the image: 00400020 492d0008 is SP 0x20004000 (top of the 16 KB SRAM, matching PY32_SRAM_BASE + PY32_SRAM_SIZE) and PC 0x08002d49, which is also the ELF entry and the Reset_Handler symbol. The odd address is Thumb-state bit 0. - PY32_APP_OFFSET 0x2800 is load-bearing. The first 10 KB of flash is the factory bootloader region, so armv7m_load_kernel gets that offset; without it the vector table lands in the wrong place and the first fetch faults. - Startup is 31 lines of assembly, and the .data copy plus .bss zero-fill are the part worth understanding: on a hosted OS the kernel and loader do that, here nobody does, so a fault in either loop shows up as globals that are silently garbage rather than as a crash. - SETTINGS_InitEEPROM reading fixed SPI offsets is the closest thing to mounting a partition. No metadata and no checksum, just an address both sides must agree on, so a setting that reads back wrong points at the offset before the transport. Also notes that the ~15 s to the main loop is emulation overhead; a real radio is up in about a second.
This commit is contained in:
1 parent
1364d46e97
commit
3e743152c1
1 file changed
+64
@@ -13,6 +13,70 @@ deliberate: the models are small and tightly coupled to each other's wiring, and
|
|||||||
splitting them would spread the board layout out without making any of it
|
splitting them would spread the board layout out without making any of it
|
||||||
clearer.
|
clearer.
|
||||||
|
|
||||||
|
## How it boots
|
||||||
|
|
||||||
|
Worth reading before debugging anything that looks like a startup problem. There is
|
||||||
|
no bootloader, no kernel, no partition table and no filesystem -- the firmware is
|
||||||
|
the only code on the machine and it owns the CPU outright.
|
||||||
|
|
||||||
|
**The hardware knows two numbers.** A Cortex-M0+ coming out of reset does not run
|
||||||
|
any boot logic. It loads SP from the first word of the vector table and PC from the
|
||||||
|
second, and starts executing. That is the whole handoff.
|
||||||
|
|
||||||
|
.isr_vector 0x08002800 (readelf -SW, size 0xc0)
|
||||||
|
+0x00 0x20004000 initial SP, i.e. the top of the 16 KB SRAM
|
||||||
|
+0x04 0x08002d49 Reset_Handler, and the ELF entry point
|
||||||
|
|
||||||
|
Read it straight off the image when in doubt -- the bytes are little-endian, so
|
||||||
|
`00400020 492d0008` is SP 0x20004000 followed by PC 0x08002d49:
|
||||||
|
|
||||||
|
objdump -s -j .isr_vector firmware.elf | head -5
|
||||||
|
|
||||||
|
The odd address is not a typo: bit 0 flags Thumb state and the hardware masks it
|
||||||
|
off when fetching.
|
||||||
|
|
||||||
|
**`PY32_APP_OFFSET` 0x2800 is load-bearing.** Flash starts at `0x08000000` but the
|
||||||
|
first 10 KB is the factory bootloader region, so the application sits after it.
|
||||||
|
`armv7m_load_kernel()` is passed that offset for exactly this reason -- load at
|
||||||
|
`0x08000000` instead and the vector table lands in the wrong place, so the very
|
||||||
|
first fetch faults.
|
||||||
|
|
||||||
|
**Startup is 31 lines of assembly**, in the firmware's
|
||||||
|
`Core/startup_py32f071xx.s`:
|
||||||
|
|
||||||
|
set SP from _estack
|
||||||
|
bl SystemInit
|
||||||
|
copy .data from flash (_sidata) into RAM (_sdata .. _edata)
|
||||||
|
zero .bss (_sbss .. _ebss)
|
||||||
|
bl __libc_init_array
|
||||||
|
bl main
|
||||||
|
LoopForever: b LoopForever @ main never returns
|
||||||
|
|
||||||
|
The copy and the zero-fill are the interesting part. Initialised globals live in
|
||||||
|
flash but have to be writable, so they are copied word by word into RAM;
|
||||||
|
uninitialised globals must read as zero per the C standard, so `.bss` is cleared.
|
||||||
|
On a hosted OS the kernel and the loader do this for you. Here nobody does, so if
|
||||||
|
either loop is wrong you get globals that are silently garbage.
|
||||||
|
|
||||||
|
**Then the application:**
|
||||||
|
|
||||||
|
main() Core/Src/main.c -- clock config only, then Main()
|
||||||
|
Main() App/main.c -- the actual firmware
|
||||||
|
SYSTICK_Init() the 10 ms tick everything is timed against
|
||||||
|
BOARD_Init() GPIO, SPI, LCD, keypad matrix
|
||||||
|
UART_Init() where the SERIAL banner in the log comes from
|
||||||
|
SETTINGS_InitEEPROM() reads settings over SPI from the flash image
|
||||||
|
while (1) { ... } main loop, never exits
|
||||||
|
|
||||||
|
**There is no filesystem.** The nearest thing to "mounting a partition" is
|
||||||
|
`SETTINGS_InitEEPROM()` reading fixed byte offsets over SPI: `0xA008` for the power
|
||||||
|
save byte, `0x0E70` for the VFO indices, and so on. No metadata, no directory, no
|
||||||
|
checksum -- just an address that the code and the data both have to agree on. When
|
||||||
|
a setting reads back wrong, suspect the offset before suspecting the transport.
|
||||||
|
|
||||||
|
The ~15 s to reach the main loop is emulation overhead. A real radio is up in about
|
||||||
|
a second.
|
||||||
|
|
||||||
## Ground rules
|
## Ground rules
|
||||||
|
|
||||||
**Never edit the firmware to make the emulator work.** The firmware is the
|
**Never edit the firmware to make the emulator work.** The firmware is the
|
||||||
|
|||||||
Reference in new issue
Block a user