diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 303e73c..4043a31 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -22,6 +22,12 @@ jobs:
run: make BOARD=esp32:esp32:esp32c3
- name: Build firmware for ESP32-S3
run: make BOARD=esp32:esp32:esp32s3:CDCOnBoot=cdc
+ - name: Build firmware for ESP32-S3 with QSPI PSRAM
+ run: make BOARD=esp32:esp32:esp32s3:CDCOnBoot=cdc,PSRAM=enabled EXTRA=--output-dir=flix/build/esp32.esp32.esp32s3.qspi
+ - name: Build firmware for ESP32-S3 with OPI PSRAM
+ run: make BOARD=esp32:esp32:esp32s3:CDCOnBoot=cdc,PSRAM=opi EXTRA=--output-dir=flix/build/esp32.esp32.esp32s3.opi
+ - name: Build firmware for Flix2
+ run: make BOARD=esp32:esp32:esp32s3:FlashSize=4M,CDCOnBoot=cdc,PSRAM=opi EXTRA='--build-property "compiler.cpp.extra_flags=-DFLIX2" --output-dir=flix/build/esp32.esp32.flix2'
- name: Upload binaries
uses: actions/upload-artifact@v4
with:
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 038e4ef..5d7fa59 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -8,6 +8,7 @@ on:
permissions:
contents: read
+ actions: read
pages: write
id-token: write
@@ -30,6 +31,38 @@ jobs:
run: cargo install mdbook --vers 0.4.43 --locked
- name: Build book
run: cd docs && mdbook build
+ - name: Wait for Build to complete
+ uses: lewagon/wait-on-check-action@v1.9.1
+ with:
+ ref: ${{ github.sha }}
+ check-name: build_linux
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ wait-interval: 30
+ - name: Find firmware binaries
+ id: build_run
+ run: |
+ RUN_ID=$(gh api "repos/${{ github.repository }}/actions/workflows/build.yml/runs?head_sha=${{ github.sha }}&per_page=1" --jq '.workflow_runs[0].id')
+ echo "id=$RUN_ID" >> $GITHUB_OUTPUT
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Download firmware binaries
+ uses: actions/download-artifact@v4
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ repository: ${{ github.repository }}
+ run-id: ${{ steps.build_run.outputs.id }}
+ name: firmware-binary
+ path: docs/build
+ - name: Create shortcuts for firmware binaries
+ working-directory: docs/build
+ run: |
+ for FQBN in esp32.esp32.*; do
+ zip -r $FQBN.zip $FQBN
+ BOARD="${FQBN#esp32.esp32.}"
+ ln -s "$FQBN/flix.ino.merged.bin" "flix.$BOARD.merged.bin"
+ ln -s "$FQBN/flix.ino.bin" "flix.$BOARD.bin"
+ ln -s "$FQBN/flix.ino.bootloader.bin" "flix.$BOARD.bootloader.bin"
+ done
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
diff --git a/Makefile b/Makefile
index 105d2f1..448ba1f 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@ PORT := $(strip $(wildcard /dev/serial/by-id/usb-Silicon_Labs_CP21* /dev/serial/
export ARDUINO_NETWORK_CONNECTION_TIMEOUT := 1h
build: .core .libs
- arduino-cli compile --fqbn $(BOARD) --build-property "build.core_debug_level=1" flix
+ arduino-cli compile --fqbn $(BOARD) --build-property "build.core_debug_level=1" flix $(EXTRA)
upload: build
arduino-cli upload --fqbn $(BOARD) -p "$(PORT)" flix
diff --git a/docs/img/web-flasher.png b/docs/img/web-flasher.png
new file mode 100644
index 0000000..56dbaca
Binary files /dev/null and b/docs/img/web-flasher.png differ
diff --git a/docs/usage.md b/docs/usage.md
index c6914c3..1b8b13f 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -1,20 +1,48 @@
# Usage: build, setup and flight
-To fly Flix quadcopter, you need to build the firmware, upload it to the ESP32 board, and set up the drone for flight.
+To fly Flix quadcopter, you need to upload the firmware to the ESP32 board, and set up the drone for flight.
-To get the firmware sources, clone the repository using git:
+## Uploading the firmware
+
+You can either use the **prebuilt binaries** or **build the firmware** from sources — this will let you modify the firmware and add new features.
+
+### Prebuilt binaries (the easiest way)
+
+1. Download the latest firmware file using the following links:
+
+
+ |Type|Boards|Link|
+ |-|-|-|
+ |ESP32|DevKit, D1 Mini|[`quadcopter.dev/flix.esp32.merged.bin`](https://quadcopter.dev/flix.esp32.merged.bin)|
+ |ESP32-S3|Most S3 based|[`quadcopter.dev/flix.esp32s3.merged.bin`](https://quadcopter.dev/flix.esp32s3.merged.bin)|
+ |ESP32-S3 (2MB PSRAM)|S3 Super Mini, S3 Zero (2MB PSRAM)|[`quadcopter.dev/flix.esp32s3.qspi.merged.bin`](https://quadcopter.dev/flix.esp32s3.qspi.merged.bin)|
+ |ESP32-S3 (8/16MB PSRAM)|S3 Zero (8MB PSRAM)|[`quadcopter.dev/flix.esp32s3.opi.merged.bin`](https://quadcopter.dev/flix.esp32s3.opi.merged.bin)|
+ |ESP32-C3|C3 Super Mini|[`quadcopter.dev/flix.esp32c3.merged.bin`](https://quadcopter.dev/flix.esp32c3.merged.bin)|
+ |Flix2|Flix2 board|[`quadcopter.dev/flix.flix2.merged.bin`](https://quadcopter.dev/flix.flix2.merged.bin)|
+
+
+2. Flash your ESP32 board using [ESP32 Web Flasher](https://www.espboards.dev/tools/program/):
+
+
+
+ * Connect the board to your computer, press *Connect to ESP*, choose the serial port.
+ * Go to the *Flash* tab.
+ * Choose the downloaded firmware file, set *Flash address* to *0* (important).
+ * Click *Program* button and wait until the process is finished.
+
+### Building from sources (flexible)
+
+You can build and upload the firmware using either **Arduino IDE** (easier for beginners) or **command line**.
+
+Get the sources using git:
```bash
git clone https://github.com/okalachev/flix.git && cd flix
```
-Beginners can [download the source code as a ZIP archive](https://github.com/okalachev/flix/archive/refs/heads/master.zip).
+Beginners can [download the sources as a ZIP archive](https://github.com/okalachev/flix/archive/refs/heads/master.zip).
-## Building the firmware
-
-You can build and upload the firmware using either **Arduino IDE** (easier for beginners) or **command line**.
-
-### Arduino IDE (Windows, Linux, macOS)
+#### Arduino IDE (Windows, Linux, macOS)
@@ -29,7 +57,7 @@ You can build and upload the firmware using either **Arduino IDE** (easier for b
7. Set *Tools* ⇒ *Core Debug Level* to *Error* to see the errors in the serial console. Set *Tools* ⇒ *USB CDC on Boot* to *Enabled* for ESP32-S3/ESP32-C3 boards.
8. [Build and upload](https://docs.arduino.cc/software/ide-v2/tutorials/getting-started/ide-v2-uploading-a-sketch) the firmware using Arduino IDE.
-### Command line (Windows, Linux, macOS)
+#### Command line (Windows, Linux, macOS)
1. [Install Arduino CLI](https://arduino.github.io/arduino-cli/installation/).
@@ -150,8 +178,6 @@ Before flight you need to calibrate the accelerometer:
If using non-default motor pins, set the pin numbers using the parameters: `MOTOR_PIN_FL`, `MOTOR_PIN_FR`, `MOTOR_PIN_RL`, `MOTOR_PIN_RR` (front-left, front-right, rear-left, rear-right respectively).
-Certain ESP32 models (such as ESP32-S3 and ESP32-C3) support a lower maximum PWM frequency; on these boards the parameter `MOT_PWM_FREQ` should be set to 38000 Hz.
-
#### Brushless motors
If using brushless motors with ESCs:
diff --git a/flix/config.h b/flix/config.h
new file mode 100644
index 0000000..badea19
--- /dev/null
+++ b/flix/config.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2026 Oleg Kalachev
+// Repository: https://github.com/okalachev/flix
+
+// Parameter defaults
+
+#pragma once
+
+void setDefaults() {
+ // Set defaults here
+
+ #if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)
+ pwmFrequency = 38000;
+ #endif
+
+ #ifdef FLIX2
+ imuModel = 4; // ICM-40609-D
+ imuIntPin = 10;
+ imuCsPin = 14;
+
+ motorPins[MOTOR_REAR_LEFT] = 41;
+ motorPins[MOTOR_REAR_RIGHT] = 7;
+ motorPins[MOTOR_FRONT_RIGHT] = 18;
+ motorPins[MOTOR_FRONT_LEFT] = 38;
+
+ voltagePin = 3;
+ #endif
+}
diff --git a/flix/control.ino b/flix/control.ino
index 2b7e3ca..dfa3661 100644
--- a/flix/control.ino
+++ b/flix/control.ino
@@ -9,31 +9,6 @@
#include "lpf.h"
#include "util.h"
-#define PITCHRATE_P 0.05
-#define PITCHRATE_I 0.2
-#define PITCHRATE_D 0.001
-#define PITCHRATE_I_LIM 0.3
-#define ROLLRATE_P PITCHRATE_P
-#define ROLLRATE_I PITCHRATE_I
-#define ROLLRATE_D PITCHRATE_D
-#define ROLLRATE_I_LIM PITCHRATE_I_LIM
-#define YAWRATE_P 0.3
-#define YAWRATE_I 0.0
-#define YAWRATE_D 0.0
-#define YAWRATE_I_LIM 0.3
-#define ROLL_P 6
-#define ROLL_I 0
-#define ROLL_D 0
-#define PITCH_P ROLL_P
-#define PITCH_I ROLL_I
-#define PITCH_D ROLL_D
-#define YAW_P 3
-#define PITCHRATE_MAX radians(360)
-#define ROLLRATE_MAX radians(360)
-#define YAWRATE_MAX radians(300)
-#define TILT_MAX radians(30)
-#define RATES_D_LPF_ALPHA 0.2 // cutoff frequency ~ 40 Hz
-
const int RAW = 0, ACRO = 1, STAB = 2, AUTO = 3; // flight modes
int mode = STAB;
bool armed = false;
@@ -44,14 +19,14 @@ Vector ratesExtra; // feedforward rates
Vector torqueTarget;
float thrustTarget;
-PID rollRatePID(ROLLRATE_P, ROLLRATE_I, ROLLRATE_D, ROLLRATE_I_LIM, RATES_D_LPF_ALPHA);
-PID pitchRatePID(PITCHRATE_P, PITCHRATE_I, PITCHRATE_D, PITCHRATE_I_LIM, RATES_D_LPF_ALPHA);
-PID yawRatePID(YAWRATE_P, YAWRATE_I, YAWRATE_D);
-PID rollPID(ROLL_P, ROLL_I, ROLL_D);
-PID pitchPID(PITCH_P, PITCH_I, PITCH_D);
-PID yawPID(YAW_P, 0, 0);
-Vector maxRate(ROLLRATE_MAX, PITCHRATE_MAX, YAWRATE_MAX);
-float tiltMax = TILT_MAX;
+PID rollRatePID(0.05, 0.2, 0.001, 0.3, 0.2);
+PID pitchRatePID(0.05, 0.2, 0.001, 0.3, 0.2);
+PID yawRatePID(0.3, 0, 0, 0.3);
+PID rollPID(6);
+PID pitchPID(6);
+PID yawPID(3);
+Vector maxRate(radians(360), radians(360), radians(360));
+float tiltMax = radians(30);
int flightModes[] = {STAB, STAB, STAB}; // map for rc mode switch
extern const int MOTOR_REAR_LEFT, MOTOR_REAR_RIGHT, MOTOR_FRONT_RIGHT, MOTOR_FRONT_LEFT;
diff --git a/flix/parameters.ino b/flix/parameters.ino
index daed501..4594c3e 100644
--- a/flix/parameters.ino
+++ b/flix/parameters.ino
@@ -14,6 +14,8 @@ extern float rcLossTimeout, descendTime, disarmTilt;
extern float voltageScale;
extern LowPassFilter voltageFilter;
+#include "config.h"
+
Preferences storage;
struct Parameter {
@@ -144,6 +146,7 @@ Parameter parameters[] = {
void setupParameters() {
print("Setup parameters\n");
+ setDefaults();
storage.begin("flix");
// Read parameters from storage
for (auto ¶meter : parameters) {
diff --git a/flix/pid.h b/flix/pid.h
index ac7a66b..c82e009 100644
--- a/flix/pid.h
+++ b/flix/pid.h
@@ -18,7 +18,7 @@ public:
LowPassFilter lpf; // low pass filter for derivative term
- PID(float p, float i, float d, float windup = 0, float dAlpha = 1, float dtMax = 0.1) :
+ PID(float p, float i = 0, float d = 0, float windup = 0, float dAlpha = 1, float dtMax = 0.1) :
p(p), i(i), d(d), windup(windup), lpf(dAlpha), dtMax(dtMax) {}
float update(float error) {