Merge branch 'master' into stm

This commit is contained in:
Oleg Kalachev
2026-08-11 23:17:04 +03:00
parent c3b818c2ae
commit b8c687f3ed
117 changed files with 2264 additions and 810 deletions
+6 -1
View File
@@ -21,6 +21,8 @@
#define degrees(rad) ((rad)*RAD_TO_DEG)
#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))
template<typename T> T max(T a, T b) { return a > b ? a : b; }
template<typename T> T min(T a, T b) { return a < b ? a : b; }
long map(long x, long in_min, long in_max, long out_min, long out_max) {
const long run = in_max - in_min;
@@ -149,7 +151,7 @@ public:
void setRxInvert(bool invert) {};
};
HardwareSerial Serial, Serial2;
HardwareSerial Serial, Serial1, Serial2;
class EspClass {
public:
@@ -165,6 +167,9 @@ void delay(uint32_t ms) {
bool ledcAttach(uint8_t pin, uint32_t freq, uint8_t resolution) { return true; }
bool ledcWrite(uint8_t pin, uint32_t duty) { return true; }
uint32_t ledcChangeFrequency(uint8_t pin, uint32_t freq, uint8_t resolution) { return freq; }
int8_t digitalPinToAnalogChannel(uint8_t pin) { return -1; }
uint32_t analogReadMilliVolts(uint8_t pin) { return 0; }
unsigned long __micros;
unsigned long __resetTime = 0;
+12
View File
@@ -0,0 +1,12 @@
// Dummy file for the simulator
class ESP_NOW_Peer {
protected:
size_t send(const uint8_t *data, int len) { return 0; }
};
class ESP_NOW_Serial_Class : public ESP_NOW_Peer {
public:
virtual void onSent(bool success) {};
virtual size_t write(const uint8_t *data, size_t len) { return 0; };
};
+91 -7
View File
@@ -1,15 +1,99 @@
# Gazebo Simulation
# Simulation
<img src="../docs/img/simulator.png" width=500 alt="Flix simulator">
The Flix drone simulator is based on Gazebo 11 and runs the firmware code in virtual physical environment.
## Building and running
Gazebo 11 works on **Ubuntu 20.04** and used to work on macOS. However, on the recent macOS versions it seems to be broken, so Ubuntu 20.04 is recommended.
See [building and running instructions](../docs/usage.md#simulation).
<img src="../docs/img/simulator1.png" width=600 alt="Flix simulator running on macOS">
## Installation
1. Clone the Flix repository using it:
```bash
git clone https://github.com/okalachev/flix.git && cd flix
```
2. Install Arduino CLI:
```bash
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=~/.local/bin sh
```
3. Install Gazebo 11:
```bash
sudo sh -c 'echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable `lsb_release -cs` main" > /etc/apt/sources.list.d/gazebo-stable.list'
wget https://packages.osrfoundation.org/gazebo.key -O - | sudo apt-key add -
sudo apt-get update
sudo apt-get install -y gazebo11 libgazebo11-dev
```
Set up your Gazebo environment variables:
```bash
echo "source /usr/share/gazebo/setup.sh" >> ~/.bashrc
source ~/.bashrc
```
4. Install SDL2 and other dependencies:
```bash
sudo apt-get update && sudo apt-get install build-essential libsdl2-dev
```
5. Add your user to the `input` group to enable joystick support (you need to re-login after this command):
```bash
sudo usermod -a -G input $USER
```
6. Run the simulation:
```bash
make simulator
```
## Usage
Just like the real drone, the simulator can be controlled using a USB remote control or a smartphone.
### Control with smartphone
1. Install [QGroundControl mobile app](https://docs.qgroundcontrol.com/master/en/qgc-user-guide/getting_started/download_and_install.html#android) on your smartphone. For **iOS**, use [QGroundControl build from TAJISOFT](https://apps.apple.com/ru/app/qgc-from-tajisoft/id1618653051).
2. Connect your smartphone to the same Wi-Fi network as the machine running the simulator.
3. If you're using a virtual machine, make sure that its network is set to the **bridged** mode with Wi-Fi adapter selected.
4. Run the simulation.
5. Open QGroundControl app. It should connect and begin showing the virtual drone's telemetry automatically.
6. Go to the settings and enable *Virtual Joystick*. *Auto-Center Throttle* setting **should be disabled**.
7. Use the virtual joystick to fly the drone!
> [!TIP]
> Decrease `CTL_TILT_MAX` parameter when flying using the smartphone to make the controls less sensitive.
### Control with USB remote control
1. Connect your USB remote control to the machine running the simulator.
2. Run the simulation.
3. Calibrate the RC using `cr` command in the command line interface.
4. Use the USB remote control to fly the drone!
### Piloting
To start the flight, arm the drone moving the throttle stick to the bottom right position:
<img src="../docs/img/arming.svg" width="150">
To disarm, move the throttle stick to the bottom left position:
<img src="../docs/img/disarming.svg" width="150">
See other piloting and usage details in general [usage article](../docs/usage.md).
## Code structure
Flix simulator is based on [Gazebo Classic](https://classic.gazebosim.org) and consists of the following components:
Flix simulator consists of the following components:
* Physical model of the drone: [`models/flix/flix.sdf`](models/flix/flix.sdf).
* Physical model of the drone in Gazebo format: [`models/flix/flix.sdf`](models/flix/flix.sdf).
* Plugin for Gazebo: [`simulator.cpp`](simulator.cpp). The plugin is attached to the physical model. It receives stick positions from the controller, gets the data from the virtual sensors, and then passes this data to the Arduino code.
* Arduino imitation: [`Arduino.h`](Arduino.h). This file contains partial implementation of the Arduino API, that is working within Gazebo plugin environment.
* Arduino emulation: [`Arduino.h`](Arduino.h). This file contains partial implementation of the Arduino API, that is working within Gazebo plugin environment.
+5 -6
View File
@@ -13,14 +13,13 @@ class SBUS {
public:
SBUS(HardwareSerial& bus, const bool inv = true) {};
SBUS(HardwareSerial& bus, const int8_t rxpin, const int8_t txpin, const bool inv = true) {};
void begin() {};
void begin(int rxpin = -1, int txpin = -1, bool inv = true, bool fast = false) {};
bool read() { return joystickInit(); };
SBUSData data() {
SBUSData data;
joystickGet(data.ch);
void getChannels(uint16_t (&channels)[16]) const {
int16_t ch[16];
joystickGet(ch);
for (int i = 0; i < 16; i++) {
data.ch[i] = map(data.ch[i], -32768, 32767, 1000, 2000); // convert to pulse width style
channels[i] = map(ch[i], -32768, 32767, 1000, 2000); // convert to pulse width style
}
return data;
};
};
+27 -17
View File
@@ -9,43 +9,50 @@
#include "quaternion.h"
#include "Arduino.h"
#include "wifi.h"
#include "lpf.h"
#define WIFI_ENABLED 1
extern float t, dt;
extern float controlRoll, controlPitch, controlYaw, controlThrottle, controlMode;
extern Vector rates;
extern Quaternion attitude;
extern bool landed;
extern float motors[4];
float t = NAN;
float dt;
float motors[4];
float controlRoll, controlPitch, controlYaw, controlThrottle = NAN;
float controlMode = NAN;
Vector acc;
Vector gyro;
Vector rates;
Quaternion attitude;
bool landed;
Vector gyro, acc, imuRotation;
Vector accBias, gyroBias, accScale(1, 1, 1);
LowPassFilter<Vector> gyroBiasFilter(0);
int imuModel = 1, imuBus = 0;
int imuSckPin = 0, imuMisoPin = 0, imuMosiPin = 0, imuCsPin = -1, imuIntPin = -1;
int imuSdaPin = 0, imuSclPin = 0;
// declarations
void step();
void computeLoopRate();
void applyGyro();
void applyAcc();
void applyLevel();
void control();
void interpretControls();
void controlAttitude();
void controlRates();
void controlTorque();
void desaturate(float& a, float& b, float& c, float& d);
const char* getModeName();
void sendMotors();
int getDutyCycle(float value);
bool motorsActive();
void testMotor(int n);
void testMotor(int, float);
void print(const char* format, ...);
void pause(float duration);
void doCommand(String str, bool echo);
void handleInput();
void setupRC();
void normalizeRC();
void calibrateRC();
void calibrateRCChannel(float *channel, uint16_t zero[16], uint16_t max[16], const char *str);
void calibrateRCChannel(int*, uint16_t[16], uint16_t[16], const char*);
void printRCCalibration();
void dumpLog();
void printLogHeader();
void printLogData();
void processMavlink();
void sendMavlink();
void sendMessage(const void *msg);
@@ -54,22 +61,25 @@ void handleMavlink(const void *_msg);
void mavlinkPrint(const char* str);
void sendMavlinkPrint();
inline Quaternion fluToFrd(const Quaternion &q);
void setupPower();
void failsafe();
void rcLossFailsafe();
void descend();
void autoFailsafe();
void tiltFailsafe();
int parametersCount();
const char *getParameterName(int index);
float getParameter(int index);
float getParameter(const char *name);
bool setParameter(const char *name, const float value);
void printParameters();
void printParameters(const char *filter);
void resetParameters();
// mocks
void setLED(bool on) {};
void calibrateGyro() { print("Skip gyro calibrating\n"); };
void calibrateAccel() { print("Skip accel calibrating\n"); };
void printIMUCalibration() { print("cal: N/A\n"); };
void printIMUInfo() {};
Vector accBias, gyroBias, accScale(1, 1, 1);
void printWiFiInfo() {};
void configWiFi(bool, const char*, const char*) { print("Skip WiFi config\n"); };
void setWiFiMode(const String& mode) { print("Skip WiFi mode set\n"); };
+4
View File
@@ -27,6 +27,7 @@
#include "mavlink.ino"
#include "motors.ino"
#include "parameters.ino"
#include "power.ino"
#include "rc.ino"
#include "time.ino"
@@ -54,6 +55,7 @@ public:
initNode();
Serial.begin(0);
setupParameters();
rcRxPin = 1; // set rc pin to enable rc reading
gzmsg << "Flix plugin loaded" << endl;
}
@@ -72,6 +74,8 @@ public:
gyro = Vector(imu->AngularVelocity().X(), imu->AngularVelocity().Y(), imu->AngularVelocity().Z());
acc = this->accFilter.update(Vector(imu->LinearAcceleration().X(), imu->LinearAcceleration().Y(), imu->LinearAcceleration().Z()));
voltage = 4.2f; // dummy voltage value
readRC();
estimate();
-1
View File
@@ -1,4 +1,3 @@
// Dummy file to make it possible to compile simulator with Flix' util.h
#define WRITE_PERI_REG(addr, val) {}
#define REG_CLR_BIT(_r, _b) {}
+14 -8
View File
@@ -11,9 +11,15 @@
#include <sys/poll.h>
#include <gazebo/gazebo.hh>
#define WIFI_UDP_PORT 14580
#define WIFI_UDP_REMOTE_PORT 14550
#define WIFI_UDP_REMOTE_ADDR "255.255.255.255"
// Mocks
int wifiMode = 1;
int wifiLongRange = 0;
int espnowChannel = 6;
const int W_DISABLED = 0, W_AP = 1, W_STA = 2, W_ESPNOW = 3;
int udpLocalPort = 14580;
int udpRemotePort = 14550;
const char *udpRemoteIP = "255.255.255.255";
int wifiSocket;
@@ -22,22 +28,22 @@ void setupWiFi() {
sockaddr_in addr; // local address
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(WIFI_UDP_PORT);
addr.sin_port = htons(udpLocalPort);
if (bind(wifiSocket, (sockaddr *)&addr, sizeof(addr))) {
gzerr << "Failed to bind WiFi UDP socket on port " << WIFI_UDP_PORT << std::endl;
gzerr << "Failed to bind WiFi UDP socket on port " << udpLocalPort << std::endl;
return;
}
int broadcast = 1;
setsockopt(wifiSocket, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast)); // enable broadcast
gzmsg << "WiFi UDP socket initialized on port " << WIFI_UDP_PORT << " (remote port " << WIFI_UDP_REMOTE_PORT << ")" << std::endl;
gzmsg << "WiFi UDP socket initialized on port " << udpLocalPort << " (remote port " << udpRemotePort << ")" << std::endl;
}
void sendWiFi(const uint8_t *buf, int len) {
if (wifiSocket == 0) setupWiFi();
sockaddr_in addr; // remote address
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(WIFI_UDP_REMOTE_ADDR);
addr.sin_port = htons(WIFI_UDP_REMOTE_PORT);
addr.sin_addr.s_addr = inet_addr(udpRemoteIP);
addr.sin_port = htons(udpRemotePort);
sendto(wifiSocket, buf, len, 0, (sockaddr *)&addr, sizeof(addr));
}