Flight controller firmware
Status: pre-hardware. th-eda’s pin map is frozen and the board is in layout (six layers, being routed). No board is built or flown yet, so this hwdef is not tested on hardware. It compiles; that is all it has proved.
The hwdef is generated, not hand-edited: make_hwdef.py reads ArduPilot’s Pixhawk6C/hwdef.dat
and hwdef-bl.dat and applies th-eda’s pin map (docs/flight-controller-pinmap.md in th-eda’s terrahawk-eda repository, which is not published),
so the board stays a short, reviewable diff of a design ArduPilot already flies. Every rule must
match exactly once or the script stops.
make firmware ARDUPILOT=<private ArduPilot checkout with submodules> # never the SITL treeOutputs in build/: TerraHawkFC_bl.bin, arduplane.apj, arduplane_with_bl.hex.
| Item | Value |
|---|---|
| Board id | 20410, not registered with ArduPilot (the charge node, 20401, is not registered either). The bootloader accepts firmware by this id, so if someone else registers the same number later, their firmware can flash onto this board. Registration is a pull request to ArduPilot and Ryan’s decision |
| USB ids | ArduPilot’s defaults, not Holybro’s |
| CAN | CAN1 PD0/PD1, CAN2 PB5/PB13, two DroneCAN drivers (defaults.parm) |
| Outputs | 1-2 TIM5 (pusher, spare motor; provisional, see below), 3-6 TIM1 (the 4-in-1’s lift motors with bidirectional DShot, or servos), 7-8 TIM4 (elevons alone), 9-10 TIM8 (camera, NODMA); with DroneCAN pod ESCs, the lift motors are virtual 11-14 on CAN bus 1 |
| IMUs | ICM-42688-P on SPI1 (with DMA), ST LSM6DSV16X on SPI6 (a second vendor; the BMI088 is out of stock; no DMA stream is available for SPI6, so it is polled) |
| Compass | none on the board; the Q43’s on the GPS1 port |
| Peripheral 5 V | Enables active-high (VDD_5V_PERIPH_EN, VDD_5V_HIPOWER_EN), for the TPS2553 family |
| Checked | ArduPlane at 4c98c92 builds with -Werror, Lua scripting compiled in, 1,544,184 bytes, 421,892 free (2026-09-30, with the 2026-09-29 change: BIDIR on TIM1 and the DMA priority). The build’s hwdef.h gives SPI1 streams 2,7 and 2,3 and TIM1 stream 2,6, none shared |
Known sharing: TIM5_UP shares a DMA stream with SPI1_TX. It is used only for DShot, and the
pusher runs PWM. If the pusher moves to DShot, review the DMA allocation first.
DMA for the lift motors on outputs 3-6 (2026-09-29): DMA_PRIORITY puts SPI1 (the primary IMU) first and
TIM1 second. chibios_hwdef.py at 4c98c92 then gives SPI1 streams 2,7 and 2,3 and TIM1 (update and both
capture channels) stream 2,6, none shared. The sharing falls on the FRAM (SPI2), UART7 and USART3/USART2
TX, and the PWM-only TIM4 and TIM5. With the 6C’s own order, TIM1 shared the FRAM’s stream; with TIM1
first, SPI1 shared with the FRAM. No pin moves. For that reason,
outputs 1 and 2 (PA0/PA1) are provisional: if Ryan chooses a bidirectional-DShot pusher ESC, th-eda
moves them to another timer.
Rotations of both IMUs are the 6C’s until th-eda’s layout fixes the parts’ orientation. A third CAN
bus (H723 in the same footprint, FDCAN3 on PF6/PF7 AF2 per ST DS13313) would need those two pins
added to ArduPilot’s STM32H723xx.py and a 1 MB build with Lua forced on.
Bring-up check for the IMU heater: the IMU island joins the board through a thermal break of about
30 K/W (th-eda’s estimate, against about 8 K/W with the planes crossing; terrahawk-eda 0ad59bc), so
some processor heat still reaches the IMUs. On the bench, log both IMUs’ temperatures against CPU load
with the heater at its 45 °C target. If a temperature rises above 45 °C and follows the load, set
BRD_HEAT_TARG 50 in defaults.parm and log again.
The LUX F765 with QAUTOTUNE
Section titled “The LUX F765 with QAUTOTUNE”ArduPlane compiles QAUTOTUNE only for SITL (ArduPlane/qautotune.h), so the stock LUX F765 firmware has no
QAUTOTUNE mode. The lift testbed (params/testbed-quad.parm) and the first build need it for the VTOL rate tune.
lux-qautotune.hwdef adds define QAUTOTUNE_ENABLED 1 to ArduPilot’s own LumenierLUXF765-NDAA hwdef through waf’s
--extra-hwdef; nothing else in the board changes.
make lux-firmware ARDUPILOT=<private ArduPilot checkout with submodules> # never the SITL treeOutput in build/lux/: arduplane.apj (load with Mission Planner’s Load custom firmware) and
arduplane_with_bl.hex. Checked at 4c98c92 (2026-10-10): 1,515,676 bytes, 483,164 free; the define is in the
build’s hwdef.h, and the binary carries the QAutoTune code (16 symbols). Not flown yet.
The source files this page describes, as they are in the repo.
hw/flight-controller/hwdef/TerraHawkFC/hwdef.dat · 259 lines · open · download
# TerraHawk flight controller, generated by hw/flight-controller/make_hwdef.py# as a diff of Pixhawk6C/hwdef.dat at ArduPilot 4c98c92. Edit the script, not this file.
# default to all pins low to avoid ESD issues#DEFAULTGPIO OUTPUT LOW PULLDOWN
# MCU class and specific typeMCU STM32H7xx STM32H743xx
# crystal frequencyOSCILLATOR_HZ 16000000
# ChibiOS system timerSTM32_ST_USE_TIMER 2
# USB setupUSB_STRING_MANUFACTURER "TerraHawk"
# board ID. See Tools/AP_Bootloader/board_types.txtAPJ_BOARD_ID 20410
FLASH_RESERVE_START_KB 128
# flash sizeFLASH_SIZE_KB 2048
# order of UARTs (and USB)SERIAL_ORDER OTG1 UART7 UART5 USART1 UART8 USART2 USART3 OTG2
# USBPA11 OTG_FS_DM OTG1PA12 OTG_FS_DP OTG1PA9 VBUS INPUT OPENDRAIN
# pins for SWD debuggingPA13 JTMS-SWDIO SWDPA14 JTCK-SWCLK SWD
# telem1PE8 UART7_TX UART7PE7 UART7_RX UART7PE9 UART7_RTS UART7 LOW PULLDOWNPE10 UART7_CTS UART7
# telem2PC12 UART5_TX UART5PD2 UART5_RX UART5PC8 UART5_RTS UART5 LOW PULLDOWNPC9 UART5_CTS UART5
# GPS1PB6 USART1_TX USART1PA10 USART1_RX USART1
# GPS2PE1 UART8_TX UART8PE0 UART8_RX UART8
# uart2 telem3PD5 USART2_TX USART2PA3 USART2_RX USART2
# debug uartPD8 USART3_TX USART3PD9 USART3_RX USART3
# outputs 9-10 (TIM8): camera trigger, exposure feedback (was USART6 to the IO coprocessor).# NODMA: PWM needs none, and TIM8_UP would otherwise share a stream with SPI1_TX, the primary IMUPC6 TIM8_CH1 TIM8 PWM(9) GPIO(58) NODMAPC7 TIM8_CH2 TIM8 PWM(10) GPIO(59) NODMA
# ADC
PC5 BATT_VOLTAGE_SENS ADC1 SCALE(1)PC4 BATT_CURRENT_SENS ADC1 SCALE(1)
PB1 BATT2_VOLTAGE_SENS ADC1 SCALE(1)PA2 BATT2_CURRENT_SENS ADC1 SCALE(1)
define HAL_BATT_VOLT_PIN 8define HAL_BATT_CURR_PIN 4define HAL_BATT_VOLT_SCALE 18.18define HAL_BATT_CURR_SCALE 36.36
PA4 VDD_5V_SENS ADC1 SCALE(2)
# SPI1 - IMUsPA5 SPI1_SCK SPI1PA6 SPI1_MISO SPI1PA7 SPI1_MOSI SPI1PC13 ICM42688_CS CSPE6 ICM42688_DRDY INPUT
# SPI6 - second IMU on its own busPG13 SPI6_SCK SPI6PG12 SPI6_MISO SPI6PG14 SPI6_MOSI SPI6PC15 LSM6DSV_CS CSPE4 LSM6DSV_DRDY INPUT
# SPI2 - FRAMPD3 SPI2_SCK SPI2PC2 SPI2_MISO SPI2PC3 SPI2_MOSI SPI2PD4 FRAM_CS CS
# PWM output pinsPA0 TIM5_CH1 TIM5 PWM(1) GPIO(50)PA1 TIM5_CH2 TIM5 PWM(2) GPIO(51)PA8 TIM1_CH1 TIM1 PWM(3) GPIO(52) BIDIRPE11 TIM1_CH2 TIM1 PWM(4) GPIO(53)PE13 TIM1_CH3 TIM1 PWM(5) GPIO(54) BIDIRPE14 TIM1_CH4 TIM1 PWM(6) GPIO(55)PD14 TIM4_CH3 TIM4 PWM(7) GPIO(56)PD15 TIM4_CH4 TIM4 PWM(8) GPIO(57)
# CAN busPD0 CAN1_RX CAN1PD1 CAN1_TX CAN1
PB5 CAN2_RX CAN2PB13 CAN2_TX CAN2
# I2C buses
# I2C1, GPS+MAGPB7 I2C1_SDA I2C1PB8 I2C1_SCL I2C1
# I2C2, GPS2+MAGPB10 I2C2_SCL I2C2PB11 I2C2_SDA I2C2
# I2C4 internalPD12 I2C4_SCL I2C4PD13 I2C4_SDA I2C4
# order of I2C busesI2C_ORDER I2C4 I2C1 I2C2define HAL_I2C_INTERNAL_MASK 1
# this board is tight on DMA channels. To allow for more UART DMA we# disable DMA on I2C. This also prevents a problem with DMA on I2C# interfering with IMUs
# heaterPB9 HEATER_EN OUTPUT LOW GPIO(80)define HAL_HEATER_GPIO_PIN 80
# Setup the IMU heaterdefine HAL_HAVE_IMU_HEATER 1define HAL_IMU_TEMP_DEFAULT 45define HAL_IMUHEAT_P_DEFAULT 50define HAL_IMUHEAT_I_DEFAULT 0.07
# power enable pinsPB2 VDD_3V3_SENSORS1_EN OUTPUT HIGH
# start peripheral power onPC10 VDD_5V_HIPOWER_EN OUTPUT HIGHPE2 VDD_5V_PERIPH_EN OUTPUT HIGH
# safety switch and LED on the FMU (were on the IO coprocessor)PE15 SAFETY_IN INPUT PULLDOWNPG0 LED_SAFETY OUTPUT
# enable pin of the external switched 12 V video BEC (relay 2)PG1 VTX_EN OUTPUT LOW GPIO(81)
# RSSI padPC0 RSSI_IN ADC1 SCALE(1)
# TPS2121 status: low means POWER2 (IN2) is supplying the board; high means POWER1 or neither.# Logged by the flight script. The VDD_BRICK*_nVALID inputs mean a supply is present, not in range:# a brick cut off by the ~5.6 V overvoltage limit still reads valid (th-eda, SCHEMATIC-NOTES.md).PF8 MUX_ST INPUT PULLUP GPIO(82)
# power sensingPE3 VDD_5V_PERIPH_nOC INPUT PULLUPPC11 VDD_5V_HIPOWER_nOC INPUT PULLUP
PA15 VDD_BRICK_nVALID INPUT PULLUPPB12 VDD_BRICK2_nVALID INPUT PULLUP
# microSD supportPD6 SDMMC2_CK SDMMC2PD7 SDMMC2_CMD SDMMC2PB14 SDMMC2_D0 SDMMC2PB15 SDMMC2_D1 SDMMC2PB3 SDMMC2_D2 SDMMC2PB4 SDMMC2_D3 SDMMC2define FATFS_HAL_DEVICE SDCD2
# LEDsPD10 LED_RED OUTPUT OPENDRAIN GPIO(90) HIGHPD11 LED_BLUE OUTPUT OPENDRAIN GPIO(92) HIGH
# setup for BoardLED2define AP_NOTIFY_GPIO_LED_2_ENABLED 1define HAL_GPIO_A_LED_PIN 90define HAL_GPIO_B_LED_PIN 92
# ID pinsPE12 HW_VER_REV_DRIVE OUTPUT LOW# PC0 HW_REV_SENS ADC1 SCALE(1)# PC1 HW_VER_SENS ADC1 SCALE(1)
# PWM output for buzzerPB0 TIM3_CH3 TIM3 GPIO(77) ALARM
# barometersBARO MS5611 I2C:0:0x76BARO MS5611 I2C:0:0x77BARO BMP388 I2C:0:0x77
# prototypes may have no barodefine HAL_BARO_ALLOW_INIT_NO_BARO 1
# compassdefine AP_COMPASS_PROBING_ENABLED 1
# we need to stop the probe of an IST8310 as an internal compass with PITCH_180define AP_COMPASS_IST8310_INTERNAL_BUS_PROBING_ENABLED 0# Disable generic internal RM3100 probing so the explicit hwdef rotation is honoureddefine AP_COMPASS_RM3100_INTERNAL_BUS_PROBING_ENABLED 0
# SPI devicesSPIDEV lsm6dsv SPI6 DEVID1 LSM6DSV_CS MODE3 2*MHZ 8*MHZSPIDEV icm42688 SPI1 DEVID3 ICM42688_CS MODE3 2*MHZ 8*MHZSPIDEV ramtron SPI2 DEVID1 FRAM_CS MODE3 8*MHZ 8*MHZ
# 2 IMUs# rotations are the 6C's until the layout fixes the IMU orientation (th-eda)IMU Invensensev3 SPI:icm42688 ROTATION_PITCH_180_YAW_90IMU LSM6DSV SPI:lsm6dsv ROTATION_PITCH_180_YAW_90
define HAL_DEFAULT_INS_FAST_SAMPLE 3
# enable RAMTRON parameter storagedefine HAL_STORAGE_SIZE 32768define HAL_WITH_RAMTRON 1
# allow to have have a dedicated safety switch pindefine HAL_HAVE_SAFETY_SWITCH 1
DMA_PRIORITY SPI1* TIM1* SDMMC* ADC* UART* USART* SPI* TIM*
# enable FAT filesystem support (needs a microSD defined via SDMMC)define HAL_OS_FATFS_IO 1hw/flight-controller/hwdef/TerraHawkFC/hwdef-bl.dat · 93 lines · open · download
# TerraHawk flight controller, generated by hw/flight-controller/make_hwdef.py# as a diff of Pixhawk6C/hwdef-bl.dat at ArduPilot 4c98c92. Edit the script, not this file.
# MCU class and specific typeMCU STM32H7xx STM32H743xx
# crystal frequencyOSCILLATOR_HZ 16000000
# USB setupUSB_STRING_MANUFACTURER "TerraHawk"
# board ID. See Tools/AP_Bootloader/board_types.txtAPJ_BOARD_ID 20410
# bootloader is installed at zero offsetFLASH_RESERVE_START_KB 0
# the location where the bootloader will put the firmwareFLASH_BOOTLOADER_LOAD_KB 128
# flash sizeFLASH_SIZE_KB 2048
# order of UARTs (and USB)SERIAL_ORDER OTG1 UART7 UART5 USART3
# USBPA11 OTG_FS_DM OTG1PA12 OTG_FS_DP OTG1PA9 VBUS INPUT OPENDRAIN
# pins for SWD debuggingPA13 JTMS-SWDIO SWDPA14 JTCK-SWCLK SWD
# CS pinsPC13 ICM42688_CS CSPC15 LSM6DSV_CS CSPD4 FRAM_CS CS
# telem1PE8 UART7_TX UART7PE7 UART7_RX UART7
# telem2PC12 UART5_TX UART5PD2 UART5_RX UART5
# debug uartPD8 USART3_TX USART3PD9 USART3_RX USART3
# start peripheral power onPC10 VDD_5V_HIPOWER_EN OUTPUT HIGHPE2 VDD_5V_PERIPH_EN OUTPUT HIGH
# LEDsPD10 LED_ACTIVITY OUTPUT OPENDRAIN GPIO(90) HIGH # redPD11 LED_BOOTLOADER OUTPUT OPENDRAIN GPIO(92) HIGH # bluedefine HAL_LED_ON 0
define HAL_USE_EMPTY_STORAGE 1define HAL_STORAGE_SIZE 16384
# support flashing from SD card:
# FATFS support:define CH_CFG_USE_MEMCORE 1define CH_CFG_USE_HEAP 1define CH_CFG_USE_SEMAPHORES 0define CH_CFG_USE_MUTEXES 1define CH_CFG_USE_DYNAMIC 1define CH_CFG_USE_WAITEXIT 1define CH_CFG_USE_REGISTRY 1
# microSD supportPD6 SDMMC2_CK SDMMC2PD7 SDMMC2_CMD SDMMC2PB14 SDMMC2_D0 SDMMC2PB15 SDMMC2_D1 SDMMC2PB3 SDMMC2_D2 SDMMC2PB4 SDMMC2_D3 SDMMC2define FATFS_HAL_DEVICE SDCD2
DMA_PRIORITY SDMMC* ADC* UART* USART* SPI* TIM*
# enable FAT filesystem support (needs a microSD defined via SDMMC)define HAL_OS_FATFS_IO 1
define AP_BOOTLOADER_FLASH_FROM_SD_ENABLED 1hw/flight-controller/hwdef/TerraHawkFC/defaults.parm · 11 lines · open · download
# TerraHawk flight controller board defaults (th-avionics, 2026-09-28)# Two independent DroneCAN networks, never one driver on both buses (docs/electronics-notes.md)CAN_P1_DRIVER 1CAN_P2_DRIVER 2CAN_D1_PROTOCOL 1CAN_D2_PROTOCOL 1# lift motors: virtual outputs 11-14 sent over bus 1CAN_D1_UC_ESC_BM 15360CAN_D1_UC_ESC_OF 10# video BEC enable on the VTX_EN padRELAY2_PIN 81hw/flight-controller/make_hwdef.py · 99 lines · open · download
"""Write TerraHawkFC's hwdef as a diff of ArduPilot's Pixhawk6C hwdef.
Usage: uv run --no-project python make_hwdef.py <ardupilot tree>Pin map: th-eda, terrahawk-eda docs/flight-controller-pinmap.md (2026-09-28)."""import reimport sysfrom pathlib import Path
ap = Path(sys.argv[1])src = ap / "libraries/AP_HAL_ChibiOS/hwdef/Pixhawk6C"out = Path(__file__).parent / "hwdef/TerraHawkFC"
HEADER = ("# TerraHawk flight controller, generated by hw/flight-controller/make_hwdef.py\n" "# as a diff of Pixhawk6C/hwdef{bl}.dat at ArduPilot 4c98c92. Edit the script, not this file.\n")
IDENT = [ # Holybro's USB ids and board id are theirs; ours uses ArduPilot's defaults and a private id (r"^USB_VENDOR.*\n", ""), (r"^USB_PRODUCT.*\n", ""), (r'^USB_STRING_MANUFACTURER.*$', 'USB_STRING_MANUFACTURER "TerraHawk"'), (r"^APJ_BOARD_ID .*$", "APJ_BOARD_ID 20410"),]
MAIN = IDENT + [ # the PX4-bootloader RAM map is not needed with ArduPilot's bootloader (r"^# to be compatible.*\n# to use a different RAM_MAP\nenv USE_ALT_RAM_MAP 1\n", ""), # no IO coprocessor: USART6 pins become outputs 9 and 10 on TIM8 (r"^# USART6 is for IOMCU\nPC6 USART6_TX USART6\nPC7 USART6_RX USART6\n\nIOMCU_UART USART6\n", "# outputs 9-10 (TIM8): camera trigger, exposure feedback (was USART6 to the IO coprocessor).\n# NODMA: PWM needs none, and TIM8_UP would otherwise share a stream with SPI1_TX, the primary IMU\n" "PC6 TIM8_CH1 TIM8 PWM(9) GPIO(58) NODMA\nPC7 TIM8_CH2 TIM8 PWM(10) GPIO(59) NODMA\n"), # second IMU on its own bus, SPI6: ST LSM6DSV16X (the BMI088 is out of stock, th-eda 2026-09-28); # one chip select (PC15) and one data-ready (PE4); PC14 and PE5 are free (r"^PC14 BMI055_G_CS CS\nPC15 BMI055_A_CS CS\nPE4 BMI055_DRDY_A INPUT\nPE5 BMI055_DRDY_G INPUT\n", "PE6 ICM42688_DRDY_PLACEHOLDER\n"), (r"^PE6 ICM42688_DRDY INPUT\n", ""), (r"^PE6 ICM42688_DRDY_PLACEHOLDER\n", "PE6 ICM42688_DRDY INPUT\n\n# SPI6 - second IMU on its own bus\n" "PG13 SPI6_SCK SPI6\nPG12 SPI6_MISO SPI6\nPG14 SPI6_MOSI SPI6\n" "PC15 LSM6DSV_CS CS\nPE4 LSM6DSV_DRDY INPUT\n"), # outputs in four timer groups (th-avionics): 1-2 TIM5 pusher and spare, 3-6 TIM1 (the 4-in-1's lift # motors with DShot, or servos), 7-8 TIM4 elevons alone. BIDIR on TIM1 CH1 and CH3 gives bidirectional # DShot rpm on all four, as in ArduPilot's Pixhawk6C-bdshot. (r"^PA8 TIM1_CH1 TIM1 PWM\(1\) GPIO\(50\)\n.*?PA1 TIM5_CH2 TIM5 PWM\(8\) GPIO\(57\)\n", "PA0 TIM5_CH1 TIM5 PWM(1) GPIO(50)\nPA1 TIM5_CH2 TIM5 PWM(2) GPIO(51)\n" "PA8 TIM1_CH1 TIM1 PWM(3) GPIO(52) BIDIR\nPE11 TIM1_CH2 TIM1 PWM(4) GPIO(53)\n" "PE13 TIM1_CH3 TIM1 PWM(5) GPIO(54) BIDIR\nPE14 TIM1_CH4 TIM1 PWM(6) GPIO(55)\n" "PD14 TIM4_CH3 TIM4 PWM(7) GPIO(56)\nPD15 TIM4_CH4 TIM4 PWM(8) GPIO(57)\n"), # Spektrum power pin is not on LQFP-144 (r"^# Control of Spektrum power pin\nPH2 .*\ndefine HAL_GPIO_SPEKTRUM_PWR 73\n\n# Spektrum Power is Active High\ndefine HAL_SPEKTRUM_PWR_ENABLED 1\n", "# safety switch and LED on the FMU (were on the IO coprocessor)\nPE15 SAFETY_IN INPUT PULLDOWN\nPG0 LED_SAFETY OUTPUT\n\n" "# enable pin of the external switched 12 V video BEC (relay 2)\nPG1 VTX_EN OUTPUT LOW GPIO(81)\n\n" "# RSSI pad\nPC0 RSSI_IN ADC1 SCALE(1)\n\n" "# TPS2121 status: low means POWER2 (IN2) is supplying the board; high means POWER1 or neither.\n" "# Logged by the flight script. The VDD_BRICK*_nVALID inputs mean a supply is present, not in range:\n" "# a brick cut off by the ~5.6 V overvoltage limit still reads valid (th-eda, SCHEMATIC-NOTES.md).\n" "PF8 MUX_ST INPUT PULLUP GPIO(82)\n"), # no internal compass: the GPS1 port's external compass is used (r"^COMPASS IST8310 .*\nCOMPASS RM3100 .*\n", ""), (r"^# compensate for magnetic field.*\ndefine HAL_HEATER_MAG_OFFSET .*\n", ""), # IMU devices (r"^SPIDEV bmi055_g SPI1 .*\nSPIDEV bmi055_a SPI1 .*\n", "SPIDEV lsm6dsv SPI6 DEVID1 LSM6DSV_CS MODE3 2*MHZ 8*MHZ\n"), (r"^# alternative to icm42688.*\nSPIDEV lsm6dsv .*\n", ""), # peripheral 5 V switches active-high, so the better-stocked TPS2553 family fits (th-eda) (r"^PC10 nVDD_5V_HIPOWER_EN OUTPUT LOW\nPE2 nVDD_5V_PERIPH_EN OUTPUT LOW\n", "PC10 VDD_5V_HIPOWER_EN OUTPUT HIGH\nPE2 VDD_5V_PERIPH_EN OUTPUT HIGH\n"), (r"^IMU Invensensev3 .*\nIMU LSM6DSV .*\nIMU BMI055 .*\n# BMI055 is replaced.*\n# They use the same.*\nIMU BMI088 .*\n", "# rotations are the 6C's until the layout fixes the IMU orientation (th-eda)\n" "IMU Invensensev3 SPI:icm42688 ROTATION_PITCH_180_YAW_90\nIMU LSM6DSV SPI:lsm6dsv ROTATION_PITCH_180_YAW_90\n"), # DMA: the primary IMU first, then the lift DShot timer, so both get streams of their own; the sharing # falls on the FRAM, a UART TX and the PWM-only TIM4/TIM5 (checked with chibios_hwdef.py, 4c98c92) (r"^DMA_PRIORITY SDMMC\* USART6\* ADC\* UART\* USART\* SPI\* TIM\*\n", "DMA_PRIORITY SPI1* TIM1* SDMMC* ADC* UART* USART* SPI* TIM*\n"), # IO coprocessor firmware images (r"^ROMFS io_firmware\.bin .*\n", ""), (r"^# build ABIN.*\nenv BUILD_ABIN True\n", ""), (r"^# enable support for dshot on iomcu\nROMFS .*\ndefine HAL_WITH_IO_MCU_BIDIR_DSHOT 1\n", ""),]
BL = IDENT + [ (r"^PC14 BMI055_G_CS CS\nPC15 BMI055_A_CS CS\n", "PC15 LSM6DSV_CS CS\n"), (r"^PC10 nVDD_5V_HIPOWER_EN OUTPUT LOW\nPE2 nVDD_5V_PERIPH_EN OUTPUT LOW\n", "PC10 VDD_5V_HIPOWER_EN OUTPUT HIGH\nPE2 VDD_5V_PERIPH_EN OUTPUT HIGH\n"), (r"USART6\* ", ""),]
def apply(text, rules, name): for pat, rep in rules: new, n = re.subn(pat, rep, text, flags=re.M | re.S if ".*?" in pat else re.M) if n != 1: sys.exit(f"{name}: rule matched {n} times: {pat[:60]!r}") text = new return text
out.mkdir(parents=True, exist_ok=True)for fname, rules, bl in (("hwdef.dat", MAIN, ""), ("hwdef-bl.dat", BL, "-bl")): text = (src / fname).read_text() text = re.sub(r"\A(#[^\n]*\n)+", "", text) (out / fname).write_text(HEADER.format(bl=bl) + apply(text, rules, fname)) print("wrote", out / fname)hw/flight-controller/Makefile · 36 lines · open · download
# Build the TerraHawk flight controller firmware in ArduPilot's Docker image.# ARDUPILOT must be a checkout with submodules that nothing else is using (never the SITL tree).
ARDUPILOT ?=# Pinned by digest (Ryan, 2026-10-03: tracked builds). This is the image :latest named on 2026-06-25; a tag# can move to a new compiler without notice. SITL uses the same digest through mcardupilot.IMAGE ?= ardupilot/ardupilot-dev-chibios@sha256:8bb0f850fb3fe1c170cb12dd577ab64f3708be8a828f9eafd28d73559766e81fBOARD := TerraHawkFCHERE := $(abspath .)
.PHONY: hwdef firmware lux-firmware clean
hwdef: ## Regenerate the hwdef from the Pixhawk6C hwdef in $(ARDUPILOT) @test -n "$(ARDUPILOT)" || { echo "set ARDUPILOT=<private ArduPilot checkout>"; exit 1; } uv run --no-project python make_hwdef.py "$(ARDUPILOT)"
firmware: hwdef ## Build bootloader and ArduPlane, outputs in build/ rm -rf "$(ARDUPILOT)/libraries/AP_HAL_ChibiOS/hwdef/$(BOARD)" cp -r hwdef/$(BOARD) "$(ARDUPILOT)/libraries/AP_HAL_ChibiOS/hwdef/" docker run --rm -v "$(ARDUPILOT)":"$(ARDUPILOT)" -w "$(ARDUPILOT)" -u "$$(id -u):$$(id -g)" -e HOME=/tmp $(IMAGE) \ bash -c 'git config --global --add safe.directory "*" && Tools/scripts/build_bootloaders.py $(BOARD) && ./waf configure --board $(BOARD) && ./waf plane' mkdir -p build cp "$(ARDUPILOT)/build/$(BOARD)/bin/arduplane.apj" "$(ARDUPILOT)/build/$(BOARD)/bin/arduplane_with_bl.hex" build/ cp "$(ARDUPILOT)/Tools/bootloaders/$(BOARD)_bl.bin" build/ @ls -la build/
lux-firmware: ## ArduPlane for the stock LUX F765 NDAA with QAUTOTUNE on (lux-qautotune.hwdef); outputs in build/lux/ @test -n "$(ARDUPILOT)" || { echo "set ARDUPILOT=<private ArduPilot checkout>"; exit 1; } docker run --rm -v "$(ARDUPILOT)":"$(ARDUPILOT)" -v "$(HERE)":"$(HERE)":ro -w "$(ARDUPILOT)" -u "$$(id -u):$$(id -g)" -e HOME=/tmp $(IMAGE) \ bash -c 'git config --global --add safe.directory "*" && ./waf configure --board LumenierLUXF765-NDAA --extra-hwdef="$(HERE)/lux-qautotune.hwdef" && ./waf plane' mkdir -p build/lux cp "$(ARDUPILOT)/build/LumenierLUXF765-NDAA/bin/arduplane.apj" "$(ARDUPILOT)/build/LumenierLUXF765-NDAA/bin/arduplane_with_bl.hex" build/lux/ @ls -la build/lux/
clean: rm -rf buildSource: hw/flight-controller/README.md in the repo. Edit it there; this page is generated.
Comments
Comment on the whole page here, or on one section or step with the button beside it.