Education

Hermes Flight controller

Fixed-wing and multirotor flight controller

Hermes Flight controller

Overview

Hermes is designed to function as the base flight control system responsible for guidance, navigation and control of a fixed-wing UAV - but general enough for most other vehicles.

Aims

  • Design a complete flight control system from scratch (hardware, software, control systems, navigation algorithms, ground control station, etc..)
  • Goal: Complete system consisting of standardised hardware and basic but robust software framework.
  • Focus on developing and testing novel algorithms - not writing low-level code!

Hardware Requirements

  • Standard set of sensors (e.g. IMU, barometer & airspeed sensor)
  • Expansion and interface capabilities via various IOs.
  • Power distribution for servos.
  • Processing power.
  • Affordability!

Hardware Specification

Bill of Materials

S/NComponentQtyDescriptionUnit Price (CAD)Price (CAD)
1RP20402Flight Control Computer: Main MCU for polling IMU and Radio PPM for control loop
Navigation Computer: Secondary MCU handling onboard guidance & navigation algorithms + extras
$1.45$2.90
2Invensense MPU605016DOF MEMS IMU (Gyroscope + Accelerometer)$3.50$3.50
3NXP MPL3115A21Absolute pressure sensor with an I²C digital interface$3.50$3.50

Datasheets

  • RP2040
    • Dual ARM Cortex-M0+ @ 133MHz
  • MPU6050
    • 6-axis MotionTracking device that combines a 3-axis gyroscope, 3-axis accelerometer, and a Digital Motion Processor™ (DMP)
  • MPL3115A2
    • I²C precision pressure sensor with altimetry

Block Diagram

Hermes Block Diagram

Technical Documentation

This MCU is responsible for polling IMU sensors at a high-rate, listening for incoming radio commands and actively stabilizing the aircraft about a desired setpoint.
The Flight Control Computer (FCC) is based on the RP2040 microcontroller clocked @ 133MHz this is fast enough to run the external PID control loop as well handle all the calculations needed for reliable attitude estimation.

Hardware Design

The Flight Control System consists of:

:::note The datasheets for all the components used can be found here :::

Power

The RP2040 chip requires two different voltage supplies 3.3V (for I/O's) and 1.1V (for the chips digital core). The main power source for this embedded system is a 3-6S LiPo Battery. Both MCU's are powered via a single 5V USB connection despite needing seperate USB connectors to program, this done to aid debugging.

Requirements

  • Step-down LiPo 11.2-26V DC Input to 5V for actuators
  • Supply both MCU's with 3.3V from either USB_VBUS or VCC.

Battery & 5V Power Supply

To step down 11.2V from the LiPo Battery to 5V a DC/DC Buck Regulator rated to a max. of 28V is needed.
The Micrel MIC26903 is used to supply 5V @ 8A to all the actuators and 3.3V regulators.

Battery
Figure 1: Battery
Buck Regulator input filtering
Figure 2: Buck Regulator input filtering
Buck Regulator
Figure 3: Buck Regulator
Buck Regulator output filtering
Figure 4: Buck Regulator output filtering

3.3V Power

Both USB ports are connected to a LDO providing 3.3V @2A to both MCU's and their peripherals.

Solderjumper for LiPo 5V
Figure 5: Solder-jumper for LiPo 5V input
3.3V Low Dropout Voltage Regulator (LDO)
Figure 6: 3.3V Low Dropout Voltage Regulator (LDO)

Crystal Oscillator

Load capacitance calculation

Where C_s is the stray capacitance in the oscillator circuit, and can be estimated to 5pF.

12MHz 16pF Crystal Oscillator
Figure 7: 12MHz 16pF Crystal Oscillator

Flash Storage

In order to be able to store program code which RP2040 can boot and run from, we need to use a flash memory, specifically, a quad SPI flash memory. The device chosen here is an W25Q128JVS device, which is a 128Mbit chip (16Mbyte). This is the largest memory size that RP2040 can support.

W25Q128JVS 128Mbit flash
Figure 8: W25Q128JVS 128Mbit flash (16Mbyte)

MCU

RP2040 is a 56 pin, 7x7mm QFN (Quad Flat No-leads) package with a small pitch (0.4mm pin-to-pin spacing).

RP2040
Figure 9: RP2040

Sensors

Invensense MPU6050 is the accelerometer/gyroscope sensor.
NXP MPL3115A2 is the barometric pressure sensor chosen to estimate altitude.

Invensense MPU6050
Figure 10: Invensense MPU6050
NXP MPL3115A2
Figure 11: NXP MPL3115A2

Radio

The Flysky FS-iA10B receiver is used to receive RC control input signals via PPM.
Get the Flysky FS-iA10B here

radio.h
#include <stdint.h>
#include <stdbool.h>
#include "config.h"  // Ensure this contains NUM_PPM_CHANNELS and PPM_PIN
 
// PPM Timing Constants (all times in microseconds)
#define MIN_PPM_DELAY_BETWEEN_PACKETS 5000  // Minimum delay between packets
#define MAX_PPM_DELAY_BETWEEN_CHANNELS 500  // Maximum delay between channel pulses
 
// PPM Decoding State Variables
static uint32_t ppm_delta = 0;                  // Time difference between edges
static uint32_t ppm_prev_time = 0;             // Timestamp of the previous edge
static uint8_t ppm_channel_index = 0;          // Current channel being decoded
static bool ppm_packet_start = false;          // Indicates the start of a new packet
 
// PPM Packet Storage
static uint32_t ppm_packet[NUM_PPM_CHANNELS] = {0};  // Array to store channel values
 
/**
 * @brief Interrupt Service Routine (ISR) to decode PPM signals.
 *        Called on GPIO pin state change.
 * @param gpio GPIO pin number where the interrupt occurred.
 * @param events Type of interrupt (rising or falling edge).
 */
void ppm_callback(uint gpio, uint32_t events) {
    // Record the current time and calculate the time delta
    uint32_t current_time = time_us_32();       // Current timestamp in microseconds
    ppm_delta = current_time - ppm_prev_time;   // Time since the last edge
    ppm_prev_time = current_time;               // Update the previous timestamp
 
    // Check if the delta indicates the start of a new PPM packet
    if (ppm_delta > MIN_PPM_DELAY_BETWEEN_PACKETS) {
        ppm_packet_start = true;                // Mark the start of a new packet
        ppm_channel_index = 0;                  // Reset channel index
    } 
    else if (ppm_delta < MAX_PPM_DELAY_BETWEEN_CHANNELS) {
        // Ignore glitches or invalid small pulses
        return;
    } 
    else {
        // Process valid channel data within a packet
        if (ppm_packet_start) {
            if (ppm_channel_index < NUM_PPM_CHANNELS) {
                ppm_packet[ppm_channel_index] = ppm_delta; // Store the channel pulse width
                ppm_channel_index++;                       // Move to the next channel
            } else {
                // If channels exceed the expected count, reset decoding
                ppm_channel_index = 0;
                ppm_packet_start = false;
            }
        }
    }
}
 
/**
 * @brief Initializes the radio input by setting up the GPIO interrupt for PPM decoding.
 */
void init_radio() {
    gpio_set_irq_enabled_with_callback(
        PPM_PIN,                               // GPIO pin for PPM input
        GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, // Trigger on both edges
        true,                                  // Enable the interrupt
        &ppm_callback                          // Callback function
    );
}
 
Radio
Figure 12: Connectors

Before First Boot

After assembling all the components. Solder jumper JP2 & JP3 only after ensuring 3.3V is the nominal voltage.
This supplies power to the MCU and peripheral components (eg. Sensors and Flash)

Radio
Figure 13: Bridge 3V_MCU_EN & 3V_IO_EN pads

After soldering JP2 & JP3, it is safe to power the board and begin flashing firmware.
Below is a brief video showing the FC booting and completing it's LED boot sequence.

Resources

All Design files and resources can be found here