Four cuts, none touching what the loader can do. The entry stub stops re-doing the reset logic's own SP write where the datasheet guarantees RAMEND (stack::hardware — the classic megas keep theirs). The autobaud unit moves into GPIOR2:GPIOR1 wherever the chip has the pair: one-word accesses, no RAM object, and the host's measured-clock peek follows it by version and geometry. 'J' rides the unified decode, carrying a selector it ignores so its address is the same two reads as every other command — the tool sends the bare form to older residents. run_app stops insisting on a body of its own. The fleet lands at 358–410 B stock and 438–474 B autobaud; the tightest image in the space — the 1284s' autobaud on a USART's own pins with the OSCCAL trim — drops from 510 to 484 of its 512. Every chip's suite is green on the wire that changed, and the README's table is machine-checked against the built images. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
396 lines
15 KiB
CMake
396 lines
15 KiB
CMake
# pureboot as a consumable CMake unit: the per-chip geometry, the default baud
|
||
# ladder, and pureboot_add_loader() — the one way a loader target is created.
|
||
# A downstream project brings its usual libavr setup (the `libavr` target and
|
||
# the LIBAVR_MCU toolchain preset), adds this directory, and states its
|
||
# deployment; every argument is optional (README.md):
|
||
#
|
||
# add_subdirectory(bootloader/pureboot)
|
||
# pureboot_add_loader(myboot CLOCK 1000000 SERIAL software TX pb1 RX pb5)
|
||
|
||
# Per-family geometry, deployment defaults, and the linker wrap the PC modulo
|
||
# needs. The slot is 512 bytes on every chip. The USART flags mirror the
|
||
# hardware inventory the loader's own static asserts check — the plain 644 is
|
||
# the x4 family's one single-USART die (Atmel-2593).
|
||
set(_pb_has_usart 1)
|
||
set(_pb_has_usart1 0)
|
||
if(LIBAVR_MCU MATCHES "^attiny13a?$")
|
||
set(_pb_flash 1024)
|
||
set(_pb_wrap "")
|
||
set(_pb_page 32)
|
||
set(_pb_hz 9600000)
|
||
set(_pb_eeprom 64)
|
||
set(_pb_has_usart 0)
|
||
elseif(LIBAVR_MCU STREQUAL "attiny25")
|
||
set(_pb_flash 2048)
|
||
set(_pb_wrap "")
|
||
set(_pb_page 32)
|
||
set(_pb_hz 8000000)
|
||
set(_pb_eeprom 128)
|
||
set(_pb_has_usart 0)
|
||
elseif(LIBAVR_MCU STREQUAL "attiny45")
|
||
set(_pb_flash 4096)
|
||
set(_pb_wrap "")
|
||
set(_pb_page 64)
|
||
set(_pb_hz 8000000)
|
||
set(_pb_eeprom 256)
|
||
set(_pb_has_usart 0)
|
||
elseif(LIBAVR_MCU STREQUAL "attiny85")
|
||
set(_pb_flash 8192)
|
||
set(_pb_wrap -Wl,--pmem-wrap-around=8k)
|
||
set(_pb_page 64)
|
||
set(_pb_hz 8000000)
|
||
set(_pb_eeprom 512)
|
||
set(_pb_has_usart 0)
|
||
elseif(LIBAVR_MCU MATCHES "^atmega48(a|p|pa)?$")
|
||
set(_pb_flash 4096)
|
||
set(_pb_wrap "")
|
||
set(_pb_page 64)
|
||
set(_pb_hz 16000000)
|
||
set(_pb_eeprom 256)
|
||
elseif(LIBAVR_MCU MATCHES "^atmega8a?$" OR LIBAVR_MCU MATCHES "^atmega88(a|p|pa)?$")
|
||
set(_pb_flash 8192)
|
||
set(_pb_wrap -Wl,--pmem-wrap-around=8k)
|
||
set(_pb_page 64)
|
||
set(_pb_hz 16000000)
|
||
set(_pb_eeprom 512)
|
||
elseif(LIBAVR_MCU MATCHES "^atmega16a?$" OR LIBAVR_MCU MATCHES "^atmega168(a|p|pa)?$")
|
||
set(_pb_flash 16384)
|
||
set(_pb_wrap -Wl,--pmem-wrap-around=16k)
|
||
set(_pb_page 128)
|
||
set(_pb_hz 16000000)
|
||
set(_pb_eeprom 512)
|
||
elseif(LIBAVR_MCU MATCHES "^atmega164(a|p|pa)$")
|
||
set(_pb_flash 16384)
|
||
set(_pb_wrap -Wl,--pmem-wrap-around=16k)
|
||
set(_pb_page 128)
|
||
set(_pb_hz 16000000)
|
||
set(_pb_eeprom 512)
|
||
set(_pb_has_usart1 1)
|
||
elseif(LIBAVR_MCU MATCHES "^atmega32a?$" OR LIBAVR_MCU MATCHES "^atmega328p?$")
|
||
set(_pb_flash 32768)
|
||
set(_pb_wrap -Wl,--pmem-wrap-around=32k)
|
||
set(_pb_page 128)
|
||
set(_pb_hz 16000000)
|
||
set(_pb_eeprom 1024)
|
||
elseif(LIBAVR_MCU MATCHES "^atmega324(a|p|pa)$")
|
||
set(_pb_flash 32768)
|
||
set(_pb_wrap -Wl,--pmem-wrap-around=32k)
|
||
set(_pb_page 128)
|
||
set(_pb_hz 16000000)
|
||
set(_pb_eeprom 1024)
|
||
set(_pb_has_usart1 1)
|
||
elseif(LIBAVR_MCU MATCHES "^atmega644(a|p|pa)?$")
|
||
# 64 KiB is exactly the 16-bit byte space, so plain LPM still reaches
|
||
# everything and the wire stays byte-addressed. The plain 644 is the
|
||
# family's one single-USART die.
|
||
set(_pb_flash 65536)
|
||
set(_pb_wrap -Wl,--pmem-wrap-around=64k)
|
||
set(_pb_page 256)
|
||
set(_pb_hz 16000000)
|
||
set(_pb_eeprom 2048)
|
||
if(NOT LIBAVR_MCU STREQUAL "atmega644")
|
||
set(_pb_has_usart1 1)
|
||
endif()
|
||
elseif(LIBAVR_MCU MATCHES "^atmega1284p?$")
|
||
# 128 KiB: wire addresses are words, reads go through ELPM, and the PC's
|
||
# modulo wrap exceeds what --pmem-wrap-around models.
|
||
set(_pb_flash 131072)
|
||
set(_pb_wrap "")
|
||
set(_pb_page 256)
|
||
set(_pb_hz 16000000)
|
||
set(_pb_eeprom 4096)
|
||
set(_pb_has_usart1 1)
|
||
else()
|
||
message(FATAL_ERROR "pureboot: no geometry for ${LIBAVR_MCU}")
|
||
endif()
|
||
set(_pb_slot 512)
|
||
math(EXPR _pb_base "${_pb_flash} - ${_pb_slot}")
|
||
math(EXPR _pb_base_hex "${_pb_base}" OUTPUT_FORMAT HEXADECIMAL)
|
||
# Patched-vector chips hand over through the trampoline word below the slot,
|
||
# which is also the slot's own last word — their budget is slot − 2.
|
||
if(LIBAVR_MCU MATCHES "^atmega" AND NOT LIBAVR_MCU MATCHES "^atmega48")
|
||
set(_pb_app 0)
|
||
set(_pb_limit ${_pb_slot})
|
||
else()
|
||
math(EXPR _pb_app "${_pb_base} - 2")
|
||
math(EXPR _pb_limit "${_pb_slot} - 2")
|
||
endif()
|
||
|
||
# The pins each USART owns. A bit-banged link deployed on them has to release
|
||
# that USART before it can drive the line, and those instructions are the one
|
||
# way the choice of pins moves the image — so a size matrix needs them as an
|
||
# axis even though pins are otherwise immediate operands. Uniform across every
|
||
# mega libavr covers: USART0 (the classics' un-numbered USART included) on
|
||
# PD0/PD1, USART1 on PD2/PD3.
|
||
set(_pb_usart0_rx pd0)
|
||
set(_pb_usart0_tx pd1)
|
||
set(_pb_usart1_rx pd2)
|
||
set(_pb_usart1_tx pd3)
|
||
|
||
# simavr names its cores after the base dies; the A revisions run on them
|
||
# (the 644PA on the 644P core).
|
||
set(_pb_sim_mcu ${LIBAVR_MCU})
|
||
if(LIBAVR_MCU MATCHES "^atmega(8|16|32|48|88|164|168|644)a$")
|
||
string(REGEX REPLACE "a$" "" _pb_sim_mcu ${LIBAVR_MCU})
|
||
elseif(LIBAVR_MCU STREQUAL "atmega644pa")
|
||
set(_pb_sim_mcu atmega644p)
|
||
endif()
|
||
|
||
# Where SRAM begins: the classic megas keep it right after the plain I/O
|
||
# registers, the x8/x4 generations push it past their extended I/O file, and
|
||
# the tinies match the classics. An autobaud loader keeps its measured unit
|
||
# in GPIOR2:GPIOR1 wherever the chip has the pair (data 0x32 on the
|
||
# t25/45/85, 0x4A from the x8 generation on) and as the first RAM object at
|
||
# SRAM start where it does not (the t13s and classic megas). The host reads
|
||
# whichever home applies (pureboot.py's geometry), and the unit-position
|
||
# test holds the image to the same split.
|
||
if(LIBAVR_MCU MATCHES "^atmega(8|16|32)a?$")
|
||
set(_pb_ram 0x60)
|
||
set(_pb_unit_gpior "")
|
||
elseif(LIBAVR_MCU MATCHES "^atmega")
|
||
set(_pb_ram 0x100)
|
||
set(_pb_unit_gpior 0x4A)
|
||
elseif(LIBAVR_MCU MATCHES "^attiny13")
|
||
set(_pb_ram 0x60)
|
||
set(_pb_unit_gpior "")
|
||
else()
|
||
set(_pb_ram 0x60)
|
||
set(_pb_unit_gpior 0x32)
|
||
endif()
|
||
|
||
# The function runs in its caller's scope, so everything it needs crosses
|
||
# scopes as global properties.
|
||
set_property(GLOBAL PROPERTY PUREBOOT_BASE_HEX ${_pb_base_hex})
|
||
set_property(GLOBAL PROPERTY PUREBOOT_APP ${_pb_app})
|
||
set_property(GLOBAL PROPERTY PUREBOOT_WRAP "${_pb_wrap}")
|
||
set_property(GLOBAL PROPERTY PUREBOOT_DEFAULT_HZ ${_pb_hz})
|
||
set_property(GLOBAL PROPERTY PUREBOOT_HAS_USART ${_pb_has_usart})
|
||
set_property(GLOBAL PROPERTY PUREBOOT_HAS_USART1 ${_pb_has_usart1})
|
||
set_property(GLOBAL PROPERTY PUREBOOT_USART0_TX ${_pb_usart0_tx})
|
||
set_property(GLOBAL PROPERTY PUREBOOT_USART1_TX ${_pb_usart1_tx})
|
||
|
||
# The port's own build (tests, the size matrix) reads the geometry from the
|
||
# parent scope; a downstream consumer gets the same variables for free.
|
||
set(PUREBOOT_BASE_HEX ${_pb_base_hex} PARENT_SCOPE)
|
||
set(PUREBOOT_PAGE ${_pb_page} PARENT_SCOPE)
|
||
set(PUREBOOT_SLOT ${_pb_slot} PARENT_SCOPE)
|
||
set(PUREBOOT_LIMIT ${_pb_limit} PARENT_SCOPE)
|
||
set(PUREBOOT_EEPROM ${_pb_eeprom} PARENT_SCOPE)
|
||
set(PUREBOOT_DEFAULT_HZ ${_pb_hz} PARENT_SCOPE)
|
||
set(PUREBOOT_RAM_START ${_pb_ram} PARENT_SCOPE)
|
||
set(PUREBOOT_UNIT_GPIOR "${_pb_unit_gpior}" PARENT_SCOPE)
|
||
set(PUREBOOT_HAS_USART ${_pb_has_usart} PARENT_SCOPE)
|
||
set(PUREBOOT_HAS_USART1 ${_pb_has_usart1} PARENT_SCOPE)
|
||
set(PUREBOOT_SIM_MCU ${_pb_sim_mcu} PARENT_SCOPE)
|
||
set(PUREBOOT_USART0_RX ${_pb_usart0_rx} PARENT_SCOPE)
|
||
set(PUREBOOT_USART0_TX ${_pb_usart0_tx} PARENT_SCOPE)
|
||
set(PUREBOOT_USART1_RX ${_pb_usart1_rx} PARENT_SCOPE)
|
||
set(PUREBOOT_USART1_TX ${_pb_usart1_tx} PARENT_SCOPE)
|
||
|
||
# The rates a default may pick, fastest first.
|
||
set_property(GLOBAL PROPERTY PUREBOOT_BAUD_LADDER 115200 57600 38400 19200 9600)
|
||
|
||
# Whether <baud> is reachable from <clock> within 2.5 %, by the same
|
||
# best-of-U2X-and-plain divisor search libavr's solve_baud runs, so a build
|
||
# never trips the compile-time error it is checked against. A software build
|
||
# also needs the polled receiver's 100-cycles-a-bit floor: at low clocks the
|
||
# U2X divisor reaches rates the bit-banged sampler cannot.
|
||
function(pureboot_baud_feasible clock baud software outvar)
|
||
set(${outvar} 0 PARENT_SCOPE)
|
||
math(EXPR _cycles "${clock} / ${baud}")
|
||
if(software AND _cycles LESS 100)
|
||
return()
|
||
endif()
|
||
foreach(divisor 8 16)
|
||
math(EXPR _step "${divisor} * ${baud}")
|
||
math(EXPR _n "(${clock} + ${_step} / 2) / ${_step}")
|
||
if(_n LESS 1 OR _n GREATER 4096)
|
||
continue()
|
||
endif()
|
||
math(EXPR _actual "${clock} / (${divisor} * ${_n})")
|
||
math(EXPR _delta "${_actual} - ${baud}")
|
||
if(_delta LESS 0)
|
||
math(EXPR _delta "-(${_delta})")
|
||
endif()
|
||
math(EXPR _error_bp "${_delta} * 10000 / ${baud}")
|
||
if(_error_bp LESS_EQUAL 250)
|
||
set(${outvar} 1 PARENT_SCOPE)
|
||
return()
|
||
endif()
|
||
endforeach()
|
||
endfunction()
|
||
|
||
# The fastest ladder rate the clock reaches.
|
||
function(pureboot_default_baud clock software outvar)
|
||
get_property(_ladder GLOBAL PROPERTY PUREBOOT_BAUD_LADDER)
|
||
foreach(baud ${_ladder})
|
||
pureboot_baud_feasible(${clock} ${baud} ${software} _ok)
|
||
if(_ok)
|
||
set(${outvar} ${baud} PARENT_SCOPE)
|
||
return()
|
||
endif()
|
||
endforeach()
|
||
message(FATAL_ERROR "pureboot: no standard baud rate fits a ${clock} Hz clock within 2.5 % "
|
||
"— pass BAUD <rate> to deploy a non-standard one")
|
||
endfunction()
|
||
|
||
# pureboot_add_loader(<name> [CLOCK <hz>] [BAUD <bd>]
|
||
# [SERIAL auto|hardware|software|autobaud] [USART <n>]
|
||
# [RX <pin>] [TX <pin>] [TIMEOUT <s>] [OSCCAL <byte>])
|
||
#
|
||
# The loader target plus its flashable images (<name>.hex for a programmer,
|
||
# <name>.bin for --update-loader). The resolved deployment is stamped on the
|
||
# target as PUREBOOT_HZ / PUREBOOT_BAUD / PUREBOOT_LINK (the link spelled
|
||
# usart0, usart1, or sw:<RX>,<TX> with a trailing @<n> where those pins are a
|
||
# USART's own) — what a test harness speaks to it with.
|
||
#
|
||
# SERIAL autobaud measures the host's bit timing at run time, so the image
|
||
# carries no clock and no baud: CLOCK and BAUD are not build parameters there,
|
||
# and one binary per chip serves every F_CPU and every rate. The stamped
|
||
# PUREBOOT_HZ/PUREBOOT_BAUD then record what a harness should *drive* it at,
|
||
# not what it was built for.
|
||
#
|
||
# OSCCAL bakes a measured oscillator trim into the loader (README.md: the
|
||
# RC-oscillator deployment answer): the byte is written at the top of run(),
|
||
# so every reset path — the watchdog hand-over included — runs on the
|
||
# corrected clock. Orthogonal to the backend: an autobaud build may carry it
|
||
# purely for the application's benefit, its own link being clock-free. No
|
||
# value, no code.
|
||
function(pureboot_add_loader name)
|
||
cmake_parse_arguments(PB "" "CLOCK;BAUD;SERIAL;USART;RX;TX;TIMEOUT;OSCCAL" "" ${ARGN})
|
||
if(PB_UNPARSED_ARGUMENTS)
|
||
message(FATAL_ERROR "pureboot_add_loader(${name}): unknown arguments ${PB_UNPARSED_ARGUMENTS}")
|
||
endif()
|
||
get_property(_hz GLOBAL PROPERTY PUREBOOT_DEFAULT_HZ)
|
||
get_property(_base_hex GLOBAL PROPERTY PUREBOOT_BASE_HEX)
|
||
get_property(_app GLOBAL PROPERTY PUREBOOT_APP)
|
||
get_property(_wrap GLOBAL PROPERTY PUREBOOT_WRAP)
|
||
get_property(_usart GLOBAL PROPERTY PUREBOOT_HAS_USART)
|
||
get_property(_usart1 GLOBAL PROPERTY PUREBOOT_HAS_USART1)
|
||
|
||
if(NOT PB_CLOCK)
|
||
set(PB_CLOCK ${_hz})
|
||
endif()
|
||
if(NOT PB_TIMEOUT)
|
||
set(PB_TIMEOUT 8)
|
||
endif()
|
||
if(NOT PB_SERIAL)
|
||
set(PB_SERIAL auto)
|
||
endif()
|
||
if(DEFINED PB_USART AND NOT PB_SERIAL MATCHES "^(auto|hardware)$")
|
||
message(FATAL_ERROR "pureboot_add_loader(${name}): USART ${PB_USART} contradicts SERIAL ${PB_SERIAL}")
|
||
endif()
|
||
if(DEFINED PB_USART)
|
||
set(PB_SERIAL hardware)
|
||
elseif(PB_SERIAL STREQUAL "hardware")
|
||
set(PB_USART 0)
|
||
endif()
|
||
|
||
set(_serial_defines "")
|
||
if(PB_SERIAL STREQUAL "hardware")
|
||
if(PB_USART EQUAL 1 AND NOT _usart1)
|
||
message(FATAL_ERROR "pureboot_add_loader(${name}): ${LIBAVR_MCU} has no USART1")
|
||
elseif(NOT _usart)
|
||
message(FATAL_ERROR "pureboot_add_loader(${name}): ${LIBAVR_MCU} has no hardware USART")
|
||
endif()
|
||
set(_serial_defines PUREBOOT_USART=${PB_USART})
|
||
set(_link usart${PB_USART})
|
||
else()
|
||
if(PB_SERIAL STREQUAL "auto")
|
||
if(_usart AND (PB_RX OR PB_TX))
|
||
message(WARNING "pureboot_add_loader(${name}): RX/TX apply to the software UART, "
|
||
"which auto does not pick on ${LIBAVR_MCU} — SERIAL software to force it")
|
||
endif()
|
||
if(_usart)
|
||
set(_link usart0)
|
||
else()
|
||
set(PB_SERIAL software)
|
||
endif()
|
||
endif()
|
||
if(PB_SERIAL MATCHES "^(software|autobaud)$")
|
||
if(NOT PB_RX)
|
||
set(PB_RX pb0)
|
||
endif()
|
||
if(NOT PB_TX)
|
||
set(PB_TX pb1)
|
||
endif()
|
||
foreach(_pin ${PB_RX} ${PB_TX})
|
||
if(NOT _pin MATCHES "^p[a-h][0-7]$")
|
||
message(FATAL_ERROR "pureboot_add_loader(${name}): pin '${_pin}' is not of the form pb1")
|
||
endif()
|
||
endforeach()
|
||
if(PB_SERIAL STREQUAL "autobaud")
|
||
set(_serial_defines PUREBOOT_AUTOBAUD PUREBOOT_RX=${PB_RX} PUREBOOT_TX=${PB_TX})
|
||
else()
|
||
set(_serial_defines PUREBOOT_SOFT_SERIAL PUREBOOT_RX=${PB_RX} PUREBOOT_TX=${PB_TX})
|
||
endif()
|
||
# sw:<RX>,<TX> as port letter and bit, upcased — with @<n> where
|
||
# the TX pin is a USART's own TXD, since a harness driving that
|
||
# link has to know the USART owns the pin until the loader
|
||
# releases it.
|
||
string(SUBSTRING ${PB_RX} 1 2 _rx_pin)
|
||
string(SUBSTRING ${PB_TX} 1 2 _tx_pin)
|
||
string(TOUPPER "sw:${_rx_pin},${_tx_pin}" _link)
|
||
string(REPLACE "SW" "sw" _link ${_link})
|
||
get_property(_tx0 GLOBAL PROPERTY PUREBOOT_USART0_TX)
|
||
get_property(_tx1 GLOBAL PROPERTY PUREBOOT_USART1_TX)
|
||
if(_usart AND PB_TX STREQUAL _tx0)
|
||
set(_link "${_link}@0")
|
||
elseif(_usart1 AND PB_TX STREQUAL _tx1)
|
||
set(_link "${_link}@1")
|
||
endif()
|
||
endif()
|
||
endif()
|
||
if(NOT PB_BAUD)
|
||
if(PB_SERIAL STREQUAL "software")
|
||
pureboot_default_baud(${PB_CLOCK} 1 PB_BAUD)
|
||
else()
|
||
pureboot_default_baud(${PB_CLOCK} 0 PB_BAUD)
|
||
endif()
|
||
endif()
|
||
|
||
if(PB_SERIAL STREQUAL "autobaud")
|
||
# No clock and no baud reach the image; the window is a poll budget.
|
||
set(_defines ${_serial_defines})
|
||
else()
|
||
set(_defines PUREBOOT_CLOCK_HZ=${PB_CLOCK} PUREBOOT_BAUD=${PB_BAUD} PUREBOOT_TIMEOUT=${PB_TIMEOUT}
|
||
${_serial_defines})
|
||
endif()
|
||
if(DEFINED PB_OSCCAL)
|
||
math(EXPR _osccal "${PB_OSCCAL}" OUTPUT_FORMAT DECIMAL)
|
||
if(_osccal LESS 0 OR _osccal GREATER 255)
|
||
message(FATAL_ERROR "pureboot_add_loader(${name}): OSCCAL ${PB_OSCCAL} is not one byte")
|
||
endif()
|
||
list(APPEND _defines PUREBOOT_OSCCAL=${_osccal})
|
||
endif()
|
||
|
||
add_executable(${name} ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/pureboot.cpp)
|
||
target_link_libraries(${name} PRIVATE libavr)
|
||
target_compile_definitions(${name} PRIVATE ${_defines})
|
||
# Codegen shaping for the loader TU only. At -Os GCC otherwise rewrites the
|
||
# byte-stream loops' counters into end-pointer forms that cost registers
|
||
# (-fno-ivopts, -fno-split-wide-types), leaves register pressure on the
|
||
# table with the default allocator (-fira-algorithm=priority), and keeps
|
||
# expression temporaries in registers (-fno-tree-ter) — but every loop body
|
||
# here contains a call, so a register held across it costs more than the
|
||
# load-immediate it saves. The set is fitted to the loader's body and has to
|
||
# be re-measured when that body changes: -fno-move-loop-invariants belonged
|
||
# here while the command loop carried four transfer bodies and costs bytes
|
||
# now that it carries one.
|
||
target_compile_options(${name} PRIVATE
|
||
-fno-ivopts -fira-algorithm=priority -fno-tree-ter -fno-split-wide-types)
|
||
target_link_options(${name} PRIVATE -nostartfiles -Wl,--section-start=.text=${_base_hex}
|
||
-Wl,--defsym=pureboot_app=${_app} ${_wrap})
|
||
add_custom_command(TARGET ${name} POST_BUILD COMMAND ${CMAKE_SIZE} $<TARGET_FILE:${name}>)
|
||
# The ELF is a container, never flashed: .hex for a programmer, .bin (the
|
||
# slot's bare bytes) for --update-loader.
|
||
add_custom_command(TARGET ${name} POST_BUILD
|
||
COMMAND ${CMAKE_OBJCOPY} -O ihex -R .eeprom
|
||
$<TARGET_FILE:${name}> $<TARGET_FILE:${name}>.hex
|
||
COMMAND ${CMAKE_OBJCOPY} -O binary -R .eeprom
|
||
$<TARGET_FILE:${name}> $<TARGET_FILE:${name}>.bin)
|
||
set_target_properties(${name} PROPERTIES PUREBOOT_HZ ${PB_CLOCK} PUREBOOT_BAUD ${PB_BAUD}
|
||
PUREBOOT_LINK ${_link})
|
||
endfunction()
|
||
|