Ich muss gestehen, es ist echt eine Ewigkeit her, dass ich hier etwas geschrieben habe. Aber aus dem aktuellen Anlass denke ich mir, dass es sich lohnt den Lösungsweg zu skizzieren, weil dieser Ablauf für jede Art von Firmware-Hacking, sei es nun für Cleanflight oder Betaflight oder auch die passenden Konfiguratoren, der Gleiche ist.
TL;DR
- Cleanflight Code holen,
- Passende Stellen finden und modifizieren,
- Neue Firmware übersetzen und fertig.
1. Cleanflight Code holen
Als erstes holt man sich den Code-Basis von Cleanflight. Ich arbeite hier auf einem Mac, daher sind die relevanten Shell-Befehle alle schon vorhanden, wie auch bei Linux. Unter Windows ist es etwas komplizierter, aber diese Details erspare ich mir hier für die Demonstration.
Bash:
michael@MacBookAir foo % git clone https://github.com/cleanflight/cleanflight.git
Cloning into 'cleanflight'...
remote: Enumerating objects: 158780, done.
remote: Counting objects: 100% (319/319), done.
remote: Compressing objects: 100% (212/212), done.
remote: Total 158780 (delta 140), reused 219 (delta 97), pack-reused 158461
Receiving objects: 100% (158780/158780), 354.01 MiB | 5.16 MiB/s, done.
Resolving deltas: 100% (110341/110341), done.
Updating files: 100% (4487/4487), done.
michael@MacBookAir foo %
Danach gehen wir in der Code-Basis auf die Version 2.0.5 zurück, das ist die Version, die Jörn haben wollte. Der Code bekommt alle Jubeljahre ein Tag, mit dem die passender Version definiert wurde. Man kann auf die Github-Seite von Cleanflight gehen, um herauszufinden, wie diese Tags heißen. Es gibt auch separate
git
-Befehle dazu. In diesem Fall heißt das Tag
CLFL_v2.0.5
. Also:
Bash:
michael@MacBookAir foo % cd cleanflight
michael@MacBookAir cleanflight % git checkout CLFL_v2.0.5
Updating files: 100% (6158/6158), done.
Note: switching to 'CLFL_v2.0.5'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at 060a9e4d5 CF/BF - SPRACINGF4NEO stabilise ADC readings.
michael@MacBookAir cleanflight %
Die Warnungen, dass wir uns im
detached HEAD state befinden, können wir ignorieren. Es geht hier nur um einen schnellen Fix des Problems.
2. Passende Stellen finden
Jetzt wird es kniffelig. Wonach soll man suchen? Der Hinweis von Jörn war, dass er seine Y6-Konfiguration nicht zum Laufen bekommen hat. Also durchsuchen wir die gesamte Code-Basis nach diesem Suchbegriff.
Bash:
michael@MacBookAir cleanflight % cd src/main
michael@MacBookAir main % search.sh Y6
Diese Dateien enthalten den Ausdruck: Y6
./flight/mixer.h
./fc/cli.c
./flight/mixer.c
./flight/servos.c
./telemetry/mavlink.c
Ende der Liste.
michael@MacBookAir main %
Der Begriff
Y6
taucht also an nur wenigen Stellen in der Code-Basis auf. Das ist gut. Zu dem verwendeten Befehl
search.sh
schreibe ich später separat. In
flight/mixer.h
finden wir unter anderem:
C:
typedef enum mixerMode
{
MIXER_TRI = 1,
MIXER_QUADP = 2,
MIXER_QUADX = 3,
MIXER_BICOPTER = 4,
MIXER_GIMBAL = 5,
MIXER_Y6 = 6,
MIXER_HEX6 = 7,
Das ist nur ein Enumerator für die gesamte Liste an Mixern in der Firmware, also nicht wirklich etwas überraschendes. Ähnlich verhält es sich mit
fc/cli.c
:
C:
#ifndef USE_QUAD_MIXER_ONLY
// sync this with mixerMode_e
static const char * const mixerNames[] = {
"TRI", "QUADP", "QUADX", "BI",
"GIMBAL", "Y6", "HEX6",
"FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
"AIRPLANE", "HE
LI_120_CCPM", "HELI_90_DEG", "VTAIL4",
"HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
"ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
};
#endif
Auch kein Treffer. Anders verhält es sich mit
flight/mixer.c
:
C:
#if defined(USE_UNCOMMON_MIXERS)
static const motorMixer_t mixerHex6H[] = {
{ 1.0f, -1.0f, 1.0f, -1.0f }, // REAR_R
{ 1.0f, -1.0f, -1.0f, 1.0f }, // FRONT_R
{ 1.0f, 1.0f, 1.0f, 1.0f }, // REAR_L
{ 1.0f, 1.0f, -1.0f, -1.0f }, // FRONT_L
{ 1.0f, 0.0f, 0.0f, 0.0f }, // RIGHT
{ 1.0f, 0.0f, 0.0f, 0.0f }, // LEFT
};
static const motorMixer_t mixerHex6P[] = {
{ 1.0f, -0.866025f, 0.5f, 1.0f }, // REAR_R
{ 1.0f, -0.866025f, -0.5f, -1.0f }, // FRONT_R
{ 1.0f, 0.866025f, 0.5f, 1.0f }, // REAR_L
{ 1.0f, 0.866025f, -0.5f, -1.0f }, // FRONT_L
{ 1.0f, 0.0f, -1.0f, 1.0f }, // FRONT
{ 1.0f, 0.0f, 1.0f, -1.0f }, // REAR
};
static const motorMixer_t mixerY6[] = {
{ 1.0f, 0.0f, 1.333333f, 1.0f }, // REAR
{ 1.0f, -1.0f, -0.666667f, -1.0f }, // RIGHT
{ 1.0f, 1.0f, -0.666667f, -1.0f }, // LEFT
{ 1.0f, 0.0f, 1.333333f, -1.0f }, // UNDER_REAR
{ 1.0f, -1.0f, -0.666667f, 1.0f }, // UNDER_RIGHT
{ 1.0f, 1.0f, -0.666667f, 1.0f }, // UNDER_LEFT
};
#else
#define mixerHex6H NULL
#define mixerHex6P NULL
#define mixerY6 NULL
#endif // USE_UNCOMMON_MIXERS
Hier werden irgendwelche Mixer-Variablen gesetzt, die ich jetzt in der Tiefe auch nicht verstehen muss, aber der ganze Block ist in einer
#if defined(USE_UNCOMMON_MIXERS)
-Umgebung. Das bedeutet, wenn dieser Bezeichner nicht definiert/gesetzt ist, wird
static const motorMixer_t mixerY6[
nicht gesetzt, beinhaltet Null und wird demzufolge in der Motor-Steuerung ignoriert. Jetzt muss man also prüfen, ob für das Target Flip32/NAZE der Bezeichner
USE_UNCOMMON_MIXERS
tatsächlich gesetzt ist. Diese Information finden wir in
target/common_fc_pre.h
, und zwar ganz am Ende der Datei:
C:
...
#define USE_SERIALRX_SUMH // Graupner legacy protocol
#define USE_SERIALRX_XBUS // JR
#if (FLASH_SIZE > 64)
#define MAX_PROFILE_COUNT 3
#else
#define MAX_PROFILE_COUNT 2
#endif
#if (FLASH_SIZE > 64)
#define BLACKBOX
#define LED_STRIP
#define TELEMETRY
#define TELEMETRY_FRSKY
#define TELEMETRY_HOTT
#define TELEMETRY_SMARTPORT
#define USE_RESOURCE_MGMT
#define USE_SERVOS
#endif
#if (FLASH_SIZE > 128)
#define GPS
#define CMS
#define TELEMETRY_LTM
#define TELEMETRY_CRSF
#define TELEMETRY_IBUS
#define TELEMETRY_JETIEXBUS
#define TELEMETRY_MAVLINK
#define TELEMETRY_SRXL
#define USE_DASHBOARD
#define USE_MSP_DISPLAYPORT
#define USE_RX_MSP
#define USE_SERIALRX_JETIEXBUS
#define USE_SENSOR_NAMES
#define USE_VIRTUAL_CURRENT_METER
#define VTX_COMMON
#define VTX_CONTROL
#define VTX_SMARTAUDIO
#define VTX_TRAMP
#endif
#if (FLASH_SIZE > 256)
#define USE_UNCOMMON_MIXERS
#endif
Also nur für Targets mit Flash-Größe von mehr als 256 werden die UNCOMMON_MIXERS überhaupt mit einkompiliert. An der Stelle wurde mir schlagartig klar, dass hier der Grund für das Problem von Jörn liegen könnte, weil für seinen kleinen Flip32 mit F3 CPU der Flash zu klein sein würde. Man muss also aus der Standard-Konfiguration für kleine F3 etwas hergeben, um die UNCOMMON_MIXERS haben zu können. Dazu habe ich die Datei
target/common_fc_pre.h
etwas umgebaut:
C:
...
#define USE_SERIALRX_XBUS // JR
#define USE_UNCOMMON_MIXERS
#if (FLASH_SIZE > 64)
#define MAX_PROFILE_COUNT 3
#else
#define MAX_PROFILE_COUNT 2
#endif
#if (FLASH_SIZE > 64)
#define BLACKBOX
#define LED_STRIP
#define TELEMETRY
#define TELEMETRY_FRSKY
#define TELEMETRY_HOTT
#define TELEMETRY_SMARTPORT
#define USE_RESOURCE_MGMT
//#define USE_SERVOS
#endif
#define USE_UNCOMMON_MIXERS
wurde nach oben kopiert, so dass es in jedem Fall gesetzt wird und für die Targets mit FLASH_SIZE > 64 (trifft für Flip32 zu) habe ich den Support für die Servos geopfert.
3. Neue Firmware übersetzen
Zum Übersetzen der Firmware dann einfach die folgenden Befehle:
Bash:
michael@MacBookAir main % cd ..
michael@MacBookAir src % cd ..
michael@MacBookAir cleanflight % make arm_sdk_install
michael@MacBookAir cleanflight % make TARGET=NAZE
Also zwei Verzeichnisebenen zurückgehen, dann den passenden Compiler automatisch und lokal in die Code-Basis installieren lassen und schließlich das gewünschte Target übersetzen. Wenn alles passt, bekommt man am Ende die folgende Meldung:
Bash:
...
%% stm32f10x_wwdg.c
Linking NAZE
./tools/gcc-arm-none-eabi-6-2017-q1-update/bin/arm-none-eabi-size ./obj/main/cleanflight_NAZE.elf
text data bss dec hex filename
123004 1616 17232 141852 22a1c ./obj/main/cleanflight_NAZE.elf
./tools/gcc-arm-none-eabi-6-2017-q1-update/bin/arm-none-eabi-objcopy -O ihex --set-start 0x8000000 obj/main/cleanflight_NAZE.elf obj/cleanflight_2.0.5_NAZE.hex
michael@MacBookAir cleanflight %
Der Linker schmeißt Fehlermeldungen, wenn man zu viele Features versucht einzukompilieren. Hier kann man experimentieren mit
target/common_fc_pre.h
, was man haben möchte und auf was man verzichten kann.
Das war es dann auch schon. Die neue Firmware liegt im Unterverzeichnis
obj
und heißt in unserem Fall
cleanflight_2.0.5_NAZE.hex
. Diese Firmware hat Jörn dann auf seinen FC geladen und BOOM - es hat sofort funktioniert.
Nachtrag zu search.sh
Ich muss gestehen, dieses kleine Shell-Skript ist für mich eines der wichtigsten Werkzeuge überhaupt, um mich in der Cleanflight/Betaflight Codebasis zurechtzufinden. Wie bei vielen OpenSource-Projekten ist die Dokumentation meist sehr dürftig und man muss sich leider selbst durch die vielen Zeilen wühlen. Dazu ist es enorm hilfreich ein Tool zu haben, welches selektiv nur gewisse Quelltext-Dateien nach einem Suchbegriff durchsucht und sagt, wo es Treffer gibt. In meinem Fall liegt dieses Shell-Skript in Verzeichnis
$HOME/bin
. Die Rechte des Skripts sind so gesetzt, dass es ausführbar ist:
Bash:
michael@MacBookAir obj % cd $HOME/bin
michael@MacBookAir bin % ll search.sh
-rwxr-xr-x 1 michael staff 546 5 Aug 12:50 search.sh
michael@MacBookAir bin %
Damit kann man es von jedem Ort aus direkt aufrufen, vorausgesetzt dass
$HOME/bin
sich im Suchpfad befindet. Der Inhalt des Skripts ist der folgende:
Bash:
#! /bin/bash
#datafile
datafile=data.asc
if [ $# -ne 1 ]; then
echo
echo "Falscher Parameter!"
echo
exit 1
fi
# suche aller dateien mit *.inc oder *.f oder *.f90
find . -name "*.html" > $datafile
find . -name "*.js" >> $datafile
find . -name "*.h" >> $datafile
find . -name "*.c" >> $datafile
find . -name "*.cpp" >> $datafile
echo "Diese Dateien enthalten den Ausdruck: $1"
echo
cat $datafile | xargs grep -i --count "$1" | grep -iv ":0" | sed -e "s/:.*//"
echo
echo "Ende der Liste."
rm -f $datafile
exit 0
Es werden also vom aktuellen Verzeichnis-Startpunkt aus alle relevanten Quelltext-Dateien gesucht und deren relative Pfade in einer temporären Datei gespeichert. Der Inhalt dieser Datei wird dann mit einer Kette von Befehlen verarbeitet, so dass am Ende herauskommt, welche Datei den gewünschten Suchbegriff beinhaltet. Am Ende wird die temporäre Datei gelöscht.