Building custom firmware - iNavFlight/inav GitHub Wiki

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.

  • 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

Migrate Betaflight Targets

There is a script in the repo 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.