Skip to main content
Version: 6.1.1

Building Custom Firmware

Rationale​

Prebuilt targets may not include the features you wish to use. If a target already exists, it is relatively simple to build your own custom firmware.

This guide provides a high level overview. It is not a detailed development guide.

Prerequisite​

You need a working development environment. There is developer / build environment documentation for the major platforms (Linux, MacOS, Windows).

Target Specific Files​

Overview​

For basic configuration changes, the files are found under src/main/targets/NAME. At the top level this includes a separate directory for each target.

This article considers a prototype flight controller QUARKVISION that was never put into production.

$ cd inav/src/main/target
$ ls QUARKVISION
CMakeFiles CMakeLists.txt README.md target.h
cmake_install.cmake config.c target.c

The CMakefles directory and cmake_install.cmake are generated by the build system; the other files are:

  • CMakeLists.txt : Mandatory, defines the target name and variants
  • README.md : Optional, information about the target
  • target.h : Mandatory, defines capabilities and definitions (e.g. sensors, pin definitions)
  • target.c : Mandatory, defines timers and usage (e.g. for motors and servos)
  • config.c : Mandatory, defines configuration defaults (RX type etc).

CMakeLists.txt​

The QUARKVISION example contains a single line:

target_stm32f405xg(QUARKVISION HSE_MHZ 16 SKIP_RELEASES)

Here is the definition for:

  • The processor class stm32f405xg
  • The target name QUARKVISION
  • Additional parameters HSE_MHZ 16 (an optional parameter defining a non-default high-speed external (HSE) oscillator clock required by this board) and SKIP_RELEASES as this is not an official target.

If we had other variants, for example a V2 variant with different sensors, we could add another line, for example:

target_stm32f405xg(QUARKVISION_V2 HSE_MHZ 16 SKIP_RELEASES)

We can then reference the QUARKVISION_V2 in target.c or target.h to handle the different capabilities of each variation.

See the developer documentation for more information, including a more detailed CMakeLists.txt and a list of processor options.

target.h​

target.h contains hardware definitions, a fragment is below:

#pragma once

#define TARGET_BOARD_IDENTIFIER "QRKV"
#define USBD_PRODUCT_STRING "QuarkVision"

...

#define BEEPER PC15
#define BEEPER_INVERTED
#define USE_UART_INVERTER
#define INVERTER_PIN_UART2 PB2 // PB2 used as inverter select GPIO
#define INVERTER_PIN_UART2_RX PB2 // PB2 used as inverter select GPIO

#define MPU6000_CS_PIN PC1
#define MPU6000_SPI_BUS BUS_SPI2

#define USE_IMU_MPU6000
#define IMU_MPU6000_ALIGN CW270_DEG

// MPU6000 interrupts
#define USE_EXTI
#define GYRO_INT_EXTI PC0
#define USE_MPU_DATA_READY_SIGNAL

//*************** MAG *****************************

#define USE_MAG
#define MAG_I2C_BUS BUS_I2C3
#define USE_MAG_HMC5883
#define USE_MAG_MAG3110
#define USE_MAG_QMC5883
#define USE_MAG_AK8963
#define USE_MAG_AK8975
...

Of note:

  • TARGET_BOARD_IDENTIFIER this should be unique in the first 4 bytes. If the mythical QUARKVISION_V2 existed, it would need a separate ID. For example:
#if defined(QUARKVISION_V2)
# define TARGET_BOARD_IDENTIFIER "QVV2"
#else
# define TARGET_BOARD_IDENTIFIER "QRKV"
#endif

This pattern is required for each variation and the features affected by the variation.

The file fragment then defines hardware options:

  • Pins, Busses
  • Sensors, for example the #define USE_MAG stanza which defines the MAG_I2C_BUS I2C bus for the compass, and the different compass types supported on this board.

There will similar stanzas for all the available sensor components.

target.c​

target.c defines the timer and channel usage. The association between timers and channels is provided by the vendor MCU documentation. This defines an array of timerHardware_t, in turn defined by the DEF_TIM (define timer) macro.

...
timerHardware_t timerHardware[] = {
DEF_TIM(TIM1, CH3, PA10, TIM_USE_PPM, 0, 0), // S1_IN_PPM A01
DEF_TIM(TIM8, CH2, PC7, TIM_USE_ANY, 0, 0), // SSERIAL1 RX c07
DEF_TIM(TIM8, CH1, PC6, TIM_USE_ANY, 0, 0), // SSERIAL1 TX
DEF_TIM(TIM2, CH1, PA15, 0, 0, 0), // LED A15
DEF_TIM(TIM3, CH3, PB0, TIM_USE_OUTPUT_AUTO, 0, 0), // S1_OUT
DEF_TIM(TIM3, CH4, PB1, TIM_USE_OUTPUT_AUTO, 0, 0), // S2_OUT
DEF_TIM(TIM12, CH1, PB14, TIM_USE_OUTPUT_AUTO, 0, 0), // S3_OUT
DEF_TIM(TIM12, CH2, PB15, TIM_USE_OUTPUT_AUTO, 0, 0), // S4_OUT
DEF_TIM(TIM11, CH1, PB9, TIM_USE_OUTPUT_AUTO, 0, 0), // S5_OUT
DEF_TIM(TIM10, CH1, PB8, TIM_USE_OUTPUT_AUTO, 0, 0), // S6_OUT
DEF_TIM(TIM3, CH2, PB5, TIM_USE_OUTPUT_AUTO, 0, 0), // S7_OUT
DEF_TIM(TIM3, CH1, PB4, TIM_USE_OUTPUT_AUTO, 0, 0), // S8_OUT
DEF_TIM(TIM8, CH3, PC8, TIM_USE_OUTPUT_AUTO, 0, 0), // S9_OUT
DEF_TIM(TIM2, CH2, PB3, TIM_USE_OUTPUT_AUTO, 0, 0), // S10_OUT
};
...

The parameters are:

  • TIMn: The timer
  • CHn : The channel
  • Pxy : The hardware (MCU) pin
  • The usage function(s) available in this pin. Note that each timer is assigned a rate defined by function, so it is inadvisable to have both MOTOR and SERVO definition on the same timer. TIM_USE_OUTPUT_AUTO will let INAV assign the output to either MOTOR or SERVO automatically.
  • The final two parameters (flags, dmavar are hardware specific / required for DMA (e.g. DSHOT), which is turn is defined by #define USE_DSHOT in target.h. See vendor's technical definitions perhaps compared to comparable targets. The example target here does not define USE_DSHOT and the values are 0. These parameters provide a DMA descriptor table compatible with Betaflight.

Adding a new source file​

If a new source file is added outside of the target/NAME directory, it must be added to the top level src/main/CMakeLists.txt.

Further reading​

Other recommendations​

Use a separate branch​

$ git checkout -b my_super_special_branch

This will isolate your work from the base repo and facilitate making a pull request if you decide to contribute your changes back to the project.

Building​

Build the target.

$ make -j $(nproc) QUARKVISION
## or (using ninja as the build manager)
$ ninja QUARKVISION
...
[365/366] Linking C executable bin/QUARKVISION.elf
Memory region Used Size Region Size %age Used
FLASH: 519160 B 896 KB 56.58%
FLASH_CONFIG: 0 GB 128 KB 0.00%
RAM: 76476 B 128 KB 58.35%
CCM: 13852 B 64 KB 21.14%
BACKUP_SRAM: 0 GB 4 KB 0.00%
MEMORY_B1: 0 GB 0 GB
[366/366] cd /home/jrh/Projects/fc/ina.../inav/build/inav_6.0.0_QUARKVISION.hex

Fix any errors / warnings​

If you changes introduce compiler warnings, please fix them. Submissions (pull requests) with compiler errors / warnings will not be accepted. If your changes overflow the flash size, consider removing unwanted features in target.h. Your target.h can always #undef generic features from src/main/target/common.h (e.g. unwanted RX or telemetry options).

Commit your changes​

You can now commit the changes to your branch, e.g. git commit -a -m "my descriptive commit message" ; otherwise if one wanted to update to the upstream the source tree (e.g.)

git pull

git will complain that there are uncommitted changes and won't perform the update. There are a number of solutions, some beyond the scope of this simple guide, however the easiest are: https://github.com/iNavFlight/inav/blob/mosca-target-converter/src/utils/bf2inav.py

  • Commit to your private branch as above ; or
  • $ git reset --hard before pulling ; or
  • Stash away the original files and restore them after pulling.

The developer documentation has more information on synchronising a custom branch with upstream Github.

Other tools and resources​

There is a script in beta test that can help automate conversion of Betaflight targets to INAV. The developer, @mosca, will be grateful for any reports of success (or failure).

Paweł Spychalski has also made YouTube videos on the subject.