224 Commits

Author SHA1 Message Date
Oleg Kalachev
3104410bb9 Group control parameters
Also add IMU group to accelerometer calibration parameters.
2025-11-19 01:30:02 +03:00
Oleg Kalachev
1551d096fc Merge changes from master 2025-11-14 20:27:02 +03:00
Oleg Kalachev
80f23ab016 Update log analysis documentation 2025-11-14 20:17:34 +03:00
Oleg Kalachev
e6fb264499 Remove unneeded SERIAL_BAUDRATE define 2025-11-14 13:46:02 +03:00
Oleg Kalachev
4d0871b00b Updates in documentation
Fixes, updates, new illustrations.
2025-11-10 20:13:39 +03:00
Oleg Kalachev
f1b993d719 Many updates to documentation
Updates to main readme.
Add much more info to usage article.
Move simulator building to simulation's readme.
Improve assembly article.
Many fixes.
Updates in diagrams.
2025-11-06 13:46:25 +03:00
Oleg Kalachev
2e7330d2f5 Refactor Wi-Fi log download
Use MAVLink LOG_REQUEST_DATA and LOG_DATA for download log instead of console.
Make Wi-Fi download default way of downloading the log.
Make `log` command only print the header and `log dump` dump the log.
2025-11-02 00:24:38 +03:00
Oleg Kalachev
e22df3ab01 Simplify rate limiter code 2025-11-02 00:03:37 +03:00
Oleg Kalachev
8484854576 Keep only one floating point version of map function
Two variants are redundant
2025-11-01 23:55:55 +03:00
Oleg Kalachev
b9d5624f30 Add some excludes to sloc 2025-10-29 03:35:31 +03:00
Oleg Kalachev
205270b8ec Add Rate class for running the code at fixed rate 2025-10-29 03:25:05 +03:00
Oleg Kalachev
f1bedb2b10 Count sloc in Actions 2025-10-29 02:20:50 +03:00
Oleg Kalachev
46d1749a8c Minor code fixes 2025-10-21 19:33:57 +03:00
Oleg Kalachev
66fb7a13c3 Simplify command for command handling 2025-10-21 19:33:57 +03:00
Oleg Kalachev
cfef3b9c36 Fixes to troubleshooting 2025-10-21 19:33:57 +03:00
KiraFlux
1338a9ea79 Quaternion::fromBetweenVectors: pass u and v as const references (#21) 2025-10-19 10:17:38 +03:00
Oleg Kalachev
b60757ec1d Minor code style change 2025-10-18 12:36:20 +03:00
Oleg Kalachev
491e054534 Revert t variable type to float instead of double
For the sake of simplicity and consistency.
2025-10-18 12:28:01 +03:00
Oleg Kalachev
3eaf24f89d Minor change 2025-10-17 19:22:48 +03:00
Oleg Kalachev
dc09459613 Add generic Delay filter 2025-10-17 19:19:27 +03:00
Oleg Kalachev
59c9ea8cb3 Lowercase imu and rc variables
To make it more obvious these are variables, not classes.
2025-10-17 19:02:46 +03:00
Oleg Kalachev
5bdd46c6ad Increase thrust to ARMED_THRUST if it's less 2025-10-17 18:54:01 +03:00
Oleg Kalachev
5b37c87166 Refactor PID controllers
Use t variable instead of passing dt argument.
Reset PID automatically on large dts.
2025-10-17 18:53:15 +03:00
Oleg Kalachev
48ba55aa7e Rename failsafe.ino to safety.ino
To aggregate all the safety related functionality.
2025-10-17 01:09:23 +03:00
Oleg Kalachev
2708c1eafd Add ESP32-S3 build to Actions 2025-10-14 16:56:48 +03:00
Oleg Kalachev
b2c9dfe6ef Fix Gazebo installation
Installation script is deprecated, install using package on Ubuntu 20.04
2025-10-14 11:44:27 +03:00
Oleg Kalachev
0579118dd5 Update VSCode settings
Disable error squiggles as they often work incorrectly.
Decrease number of include libraries to index.
2025-10-14 11:31:47 +03:00
Oleg Kalachev
05818349d8 Improve rc failsafe logic
Don't trigger failsafe if there's no RC at all
Use AUTO mode for descending, instead of STAB
Increase RC loss timeout and descend time
2025-10-12 21:20:46 +03:00
Oleg Kalachev
6c1d651caa Disarm the drone on simulator plugin reset
In order to reset yaw target.
2025-10-07 15:45:30 +03:00
Oleg Kalachev
49a0aa7036 Reset yaw target when drone disarmed
Prevent unexpected behavior when the drone tries to restore its old yaw on takeoff.
2025-10-07 15:42:52 +03:00
Oleg Kalachev
bf9eeb90a4 Include FlixPeriph header instead of MPU9250
This simplifies choosing IMU model
2025-10-07 08:41:56 +03:00
Oleg Kalachev
96836b2e3e Ensure showing correct raw data in imu command
Some IMUs will reset acc and gyro buffer on whoAmI() call
2025-10-07 08:41:56 +03:00
Oleg Kalachev
82d9d3570d Send only mavlink heartbeats until connected 2025-10-03 07:08:17 +03:00
Oleg Kalachev
d7f8c8d934 Add Wi-Fi client mode
WIFI_AP_MODE define
2025-10-03 06:56:03 +03:00
Oleg Kalachev
c08c8ad91c pyflix@0.9 2025-10-03 06:49:44 +03:00
Oleg Kalachev
e44f32fca7 pyflix: don't quit on any sendto error 2025-10-03 06:47:56 +03:00
Oleg Kalachev
ca03bdb260 pyflix: partially fix wireless downloading logs 2025-10-03 06:46:56 +03:00
Oleg Kalachev
b3dffe99fb pyflix: add passing event name to off method 2025-10-03 06:46:29 +03:00
Oleg Kalachev
6e6a71fa69 Remove unneeded advice from troubleshooting 2025-10-03 06:45:16 +03:00
Oleg Kalachev
838fe11f6b Simplify mode index check in set_mode 2025-09-26 05:03:36 +03:00
Oleg Kalachev
8b36509932 pyflix@0.8 2025-09-25 16:55:06 +03:00
Oleg Kalachev
0268c8ebcf Some fixes and updates in pyflix
Fix set_controls
Add set_armed method
2025-09-25 16:53:49 +03:00
Oleg Kalachev
09bf09e520 Update schematics diagram 2025-09-25 06:16:02 +03:00
Oleg Kalachev
4c89b10767 Fix fields order in psq command 2025-09-20 22:35:36 +03:00
Oleg Kalachev
a79df52959 Don't trigger rc failsafe in AUTO mode or if disamed 2025-09-20 20:36:36 +03:00
Oleg Kalachev
e88888baeb Fix rc calibration steps enumeration again 2025-09-11 11:47:28 +03:00
Oleg Kalachev
de69b228ff Fix rc calibration steps enumeration 2025-09-02 11:03:44 +03:00
Oleg Kalachev
f9739dcd7e Don't arm by mavlink command if throttle is not low 2025-08-29 03:47:51 +03:00
Oleg Kalachev
708c8f04dc Minor docs fix 2025-08-28 05:17:46 +03:00
Oleg Kalachev
2128201440 Fix simulation build 2025-08-28 01:13:38 +03:00
Oleg Kalachev
8e3c86f5ee pyflix@0.7 2025-08-28 00:52:27 +03:00
Oleg Kalachev
40fc4b96b5 Implement AUTO mode for automatic flights
Support SET_ATTITUDE_TARGET, SET_ACTUATOR_CONTROL_TARGET in mavlink.
ACTUATOR_OUTPUT_STATUS is changed ACTUATOR_CONTROL_TARGET to match used message for setting motor outputs.
Add support for changing mode from mavlink.
Support automatic flights in pyflix.
2025-08-28 00:49:24 +03:00
Oleg Kalachev
10fafbc4a0 Send udp packets in unicast after connection is established
This makes qgc connection faster.
Add WIFI_UDP_REMOTE_ADDR macro for default remote address for both the firmware and simulation.
2025-08-27 05:01:07 +03:00
Oleg Kalachev
d47d7b8bd4 Support arm/disarm mavlink commands
Refactor commands handling to remove repeating ack message packing.
2025-08-27 04:45:25 +03:00
Oleg Kalachev
a7fdc2a88f Minor change in cli help message 2025-08-27 04:43:24 +03:00
Oleg Kalachev
c1788e2c75 Refactor arming logic
Arm and disarm with gestures only: left stick right/down for arming, left/down for disarming.
Remove arming switch as it complicates arming gestures logic.
Remove MAV_CTRL_SCALE parameter as it complicates arming gestures logic, advise to decrease TILT_MAX when controlling with a smartphone.
Put some minimal thrust to motors to indicate armed state.
Rename build article to usage article, add flight instructions.
2025-08-27 03:19:26 +03:00
Oleg Kalachev
beb655fdcb Add illustration for qgc proxy for pyflix 2025-08-27 03:13:28 +03:00
Oleg Kalachev
bf0cdac111 Major update of the articles
Reflect control subsystem refactoring.
Update dataflow diagram.
Add control subsystem diagram.
Minor updates.
2025-08-27 00:09:42 +03:00
Oleg Kalachev
b21e81a68b Add cli commands for switching mode
Make mode variable int instead of enum, which is more convinient.
2025-08-26 21:55:27 +03:00
Oleg Kalachev
8418723ccc Refactor control subsystem
Add interpretControls function to convert pilot commands and mode into control targets and make control functions independent from the mode.
Add ratesExtra target for rates feed-forward; remove yawMode.
Rename controlRate to controlRates to reflect rates variable name.
Remove USER mode.
2025-08-26 01:00:56 +03:00
Oleg Kalachev
a1539157b8 Show raw values in imu command 2025-08-22 17:20:33 +03:00
Oleg Kalachev
80922dc68a Some updates to readme and build article
Add info on using USB gamepad
Replace KINGKONG transmitter with BetaFPV LiteRadio
Add RoboCamp video
2025-08-20 22:06:17 +03:00
Oleg Kalachev
fcd2738763 Add link to stls from robocamp 2025-08-19 15:20:24 +03:00
Oleg Kalachev
fa07ed3a4e Minor docs change 2025-08-15 00:51:08 +03:00
Oleg Kalachev
dee4d97ab3 Add getRoll, getPitch, setRoll, setPitch methods
Add methods to Quaternion for consistency with getYaw and setYaw
2025-08-09 18:10:11 +03:00
Oleg Kalachev
ea35db37da Minor code simplification 2025-08-09 17:53:06 +03:00
Oleg Kalachev
cd953f24ad Add RoboCamp to built drones article 2025-08-07 14:29:39 +03:00
Oleg Kalachev
3f80712641 Some updates to articles 2025-08-06 23:52:35 +03:00
Oleg Kalachev
18bacb64f3 Make rc loss timeout longer 2025-07-31 12:35:28 +03:00
Oleg Kalachev
7e8bd3e834 Minor updates 2025-07-28 22:07:33 +03:00
Oleg Kalachev
bb0643e8c6 Add missing set_velocity method stub to pyflix 2025-07-28 22:06:30 +03:00
Oleg Kalachev
32f417efae Updates in pyflix
Rename mav_to to system_id to match firmware naming.
Readme updates.
2025-07-24 09:15:44 +03:00
Oleg Kalachev
018a6d4fce Add repository field to python library 2025-07-22 17:21:51 +03:00
Oleg Kalachev
1f47aa6d62 Add Python library (#20) 2025-07-22 14:17:08 +03:00
Oleg Kalachev
779fa13e80 Increase connection timeout for arduino-cli as it prevents some users from downloading the core 2025-07-21 11:12:47 +03:00
Oleg Kalachev
5eccb3f0c4 Fix rates, acc and gyro coordinate frame in mavlink
All of them should be in frd.
Get rid of fluToFrd function - there is no big need for it.
2025-07-19 05:32:49 +03:00
Oleg Kalachev
29f1a2b22b Minor fixes to builds list 2025-07-18 14:19:43 +03:00
Oleg Kalachev
1d4ce810a9 Add chkroko's bldc build 2025-07-18 12:14:42 +03:00
Oleg Kalachev
32874b92fd Minor fixes 2025-07-14 12:05:16 +03:00
Oleg Kalachev
6b38070e43 Rename printIMUCal to printIMUCalibration for consistency with rc 2025-07-14 12:04:02 +03:00
Oleg Kalachev
52819e403b Major rework of rc subsystem
Implement channels mapping calibration.
Store mapping in parameters.
Get rid of `controls` array and store control inputs in `controlRoll`, `controlPitch`, ... variables.
Move `channels` variable to rc.ino, channels are not involved when controled using mavlink.
'Neutral' values are renamed to 'zero' - more precise naming.
`controlsTime` is renamed to `controlTime`.
Use unsigned values for channels.
Make channel values in simulation more alike to real world: unsigned values in range [1000, 2000].
Send RC_CHANNELS_RAW instead of RC_CHANNELS_SCALED via mavlink
Don't send channels data via mavlink if rc is not used
2025-07-14 12:01:29 +03:00
Oleg Kalachev
449dd44741 Fix storing nans and infs in preferences in simulator
Turns out file streams cannot parse nans and infs on some platforms, so use std::stof to parse.
2025-07-14 09:52:49 +03:00
Oleg Kalachev
e389d717d6 Show unspecified core as * in sys command 2025-07-13 11:12:54 +03:00
Oleg Kalachev
ea8463ed70 Fixes in firmware variables description 2025-07-12 10:07:52 +03:00
Oleg Kalachev
85afe405cb Improve pause function work
Fix disconnecting from qgc while pausing in the simulation. 
Consider total delay time in micros() in simulation to increase t while delaying.
Simplify and get rid of ARDUINO macro check.
2025-07-12 09:29:47 +03:00
Oleg Kalachev
fd4bcbeb89 Minor changes 2025-07-10 07:27:53 +03:00
Oleg Kalachev
121b50d896 Increase motors pwm frequency to 78Khz
1000 Hz is too low frequency considering the update loop for motors signal is also 1000 Hz.
Decrease resolution as it's required to set larger pwm frequencies.
This change should vastly improve control jitter and remove audible motors noise.
2025-07-03 03:46:11 +03:00
Oleg Kalachev
48c7135efb Return zero rotation vector when converting neutral quaternion
Previously it would return nans
2025-07-01 02:48:49 +03:00
Oleg Kalachev
9229b743eb Add missing equals and non-equals operators for quaternion lib 2025-07-01 02:47:01 +03:00
Oleg Kalachev
52d31ba7a5 Add missing includes to Arduino.h to make build more portable 2025-07-01 02:38:47 +03:00
Oleg Kalachev
f11ab2dc16 Add info on mpu-6050 2025-06-30 12:29:07 +03:00
Oleg Kalachev
93383cc7f9 Add chkroko's build 2025-06-19 13:25:01 +03:00
Oleg Kalachev
389cfb94ab Add missing newlines to initialization prints 2025-06-19 13:19:00 +03:00
Oleg Kalachev
045f2c5ed5 Minor docs changes 2025-06-19 13:19:00 +03:00
Oleg Kalachev
31f5e1efbb Upload built firmware binaries as artifact 2025-06-02 02:32:27 +03:00
Oleg Kalachev
2d77317abc Minor fixes in book 2025-05-31 16:56:05 +03:00
Oleg Kalachev
963cbe09dd Minor fix in book 2025-05-31 13:15:25 +03:00
Oleg Kalachev
98fc0cf5b4 Add quaternion and vector chapter to book 2025-05-31 12:46:33 +03:00
Oleg Kalachev
6b7601c0bd Improve vector and quaternion libraries
Make the order or basic methods consistent between Vector and Quaternion.
Remove `ZYX` from Euler method names as this is standard for robotics.
Rename angular rates to rotation vector, which is more correct.
Make rotation methods static, to keep the arguments order consistent.
Make `Quaternion::fromAxisAngle` accept Vector for axis.
Minor fixes.
2025-05-31 04:17:00 +03:00
Oleg Kalachev
929bdd1f35 Minor fixes in book 2025-05-31 03:29:44 +03:00
Oleg Kalachev
660913f8bb Remove version 0 section from the readme 2025-05-23 17:17:27 +03:00
Oleg Kalachev
25e3056891 Add disclaimer to readme 2025-05-23 16:47:04 +03:00
Oleg Kalachev
be7b6ec0c9 Fix simulator build 2025-05-16 05:02:27 +03:00
Oleg Kalachev
9c8c0e2578 Minor code updates 2025-05-15 09:22:17 +03:00
Oleg Kalachev
7e5a75a01f Revert sending mavlink udp packets in unicast
This requires more complex approach as client ip may change between reconnections
2025-05-10 05:45:57 +03:00
Oleg Kalachev
2bcab6edb3 Make cli command case insensitive
iOS QGC capitalizes the command by default, so it's more convinient
2025-05-10 05:15:54 +03:00
Oleg Kalachev
df2b10acd4 Make wi-fi code more consistent between the firmware and simulation 2025-05-10 05:13:57 +03:00
Oleg Kalachev
31d6636754 Send mavlink udp packets in unicast after connected
Tests and research show this is more efficient way of sending telemetry
2025-05-10 05:08:04 +03:00
Oleg Kalachev
b143c2f1b3 Add recommended 3D printing settings to readme 2025-05-09 06:55:28 +03:00
Oleg Kalachev
a491b28201 Make sending udp packets much faster
Turns out parsing IP address string is very slow
2025-05-06 04:32:36 +03:00
Oleg Kalachev
4a4642bcf6 Update ESP32-Core to 3.2.0 2025-05-06 03:52:46 +03:00
Oleg Kalachev
81037d94ec Some cli improvements
Improve loop rate formatting
Show cpu temperature in sys command
2025-05-06 03:16:45 +03:00
Oleg Kalachev
965813e8f0 Use interrupts instead of polling for main loop 2025-05-05 13:58:23 +03:00
Oleg Kalachev
94c2d399b3 Add sys command
Show ESP32 model and free heap
Show tasks table with stack and cpu usage
2025-05-05 04:32:41 +03:00
Oleg Kalachev
21dc47c472 Make mavlink print buffered
Combine all output of each step into one SERIAL_CONTROL message
2025-05-05 00:44:06 +03:00
Oleg Kalachev
4b938e8d89 Make accelerometer calibration more verbose
Print the number of each calibration step
2025-05-05 00:38:08 +03:00
Oleg Kalachev
67efcdd08a Remove unused macro
MAVLINK_CONTROL_SCALE is now parameter
2025-05-04 00:03:38 +03:00
Oleg Kalachev
d1d10c4c6c Updates to readme and documentation 2025-04-30 00:05:52 +03:00
Oleg Kalachev
4e0a1fcdab Update simulator illustration 2025-04-29 23:37:34 +03:00
Oleg Kalachev
5165355abc Make low pass filter formula more straightforward 2025-04-29 23:28:56 +03:00
Oleg Kalachev
a268475f7a Add notice about firewall and vpn to troubleshooting 2025-04-29 23:22:40 +03:00
Oleg Kalachev
c14fe7c48b Add some missing operator for vector library 2025-04-29 23:21:12 +03:00
Oleg Kalachev
b2736e6a5b Fix simulation build in Actions
Switched runner to Ubuntu 22.04 since Gazebo 11 now has binaries for 22.04 (amd64 only).
Changed the building tutorial to reflect that.
2025-04-24 19:38:47 +03:00
Oleg Kalachev
962757f46e Update user builds illustration in readme 2025-04-23 20:10:29 +03:00
Oleg Kalachev
f03dec4fae Update demo video 2025-04-22 11:27:29 +03:00
Oleg Kalachev
fe98a5bf97 Minor code simplifications 2025-04-13 01:42:47 +03:00
Oleg Kalachev
253f2fe3dd Update MAVLink-Arduino to 2.0.16 2025-04-11 07:01:54 +03:00
Oleg Kalachev
94dc566643 Show landed state in imu command output 2025-03-29 16:19:23 +03:00
Oleg Kalachev
547f5087ef Pass landed state to mavlink
Using EXTENDED_SYS_STATE message
2025-03-29 16:14:37 +03:00
Oleg Kalachev
66a43ab246 Continuous gyro bias estimation (#17)
Estimate gyro bias continuously instead of calibrating the gyro at startup.
2025-03-29 12:21:40 +03:00
Oleg Kalachev
117ae42d1b Add Wi-Fi password to build tutorial 2025-03-29 12:02:59 +03:00
Oleg Kalachev
3a61dca102 Simplify and improve acc calibration command output 2025-03-29 01:05:55 +03:00
Oleg Kalachev
a8fe1324c3 Minor readme update 2025-03-28 20:50:23 +03:00
Oleg Kalachev
fc0b805cc2 Add cryptokobans's build to user projects 2025-03-28 18:23:09 +03:00
Oleg Kalachev
d68222953d Simplify user builds article layout: remove tables
Tables make photos squeezed in phones
2025-03-27 18:56:35 +03:00
Oleg Kalachev
bca1312b46 Remove twxs.cmake from the list of recommended extensions 2025-03-14 03:24:30 +03:00
Oleg Kalachev
d5148d12a1 Minor code style fix 2025-03-14 03:03:27 +03:00
Oleg Kalachev
208e50aa15 Encode if the mode in stabilized in heartbeat message 2025-03-14 03:02:43 +03:00
Oleg Kalachev
0a87ccf435 Some minor readme updates 2025-03-01 00:27:55 +03:00
Oleg Kalachev
3fdebf39d8 Fix mavlink disconnection in pauses in cli commands
Implement pause function that proceeds processing mavlink.
Use temporal workaround for simulation, as micros function gives the same result on the same simulation step.
2025-02-28 19:25:41 +03:00
Oleg Kalachev
5bf2e06c5a Use natural order of ino files includes in simulation
In Arduino, ino files are included in alphabetical order.
Cleanup unused function declarations in simulation, add missing.
Rename flu to frd function to match the code style.
2025-02-28 19:06:58 +03:00
Oleg Kalachev
4e3e8c70b0 Update main book illustration 2025-02-28 03:17:09 +03:00
Oleg Kalachev
bda44fca02 Remove dt multiplier from acc correction and increase acc weight
More classical complementary filter implementation
Increase effective accelerometer weight for faster convergence
2025-02-28 00:51:16 +03:00
Oleg Kalachev
e66f6563a5 Add custom Wi-Fi control description by @pavelkabakov #11
Co-authored-by: Pavel Kabakov <110939392+pavelkabakov@users.noreply.github.com>
2025-02-26 01:08:36 +03:00
Oleg Kalachev
95084c167c Add user project by jeka_chex 2025-02-26 00:55:14 +03:00
Oleg Kalachev
931b2066bb Minor book fix 2025-02-26 00:20:50 +03:00
Oleg Kalachev
a2cf318189 Check target system id in mavlink messages
Skip messages addressed to other systems
2025-02-26 00:08:23 +03:00
Oleg Kalachev
83a8dcd63e Cleanup mavlink subsystem code 2025-02-24 13:06:38 +03:00
Oleg Kalachev
c62e536b50 Put last control time in RC control mavlink message instead of send time 2025-02-22 20:07:26 +03:00
Oleg Kalachev
287a4b5a71 Fix accel calibration via mavlink console
Add 5 s timeout as waiting for enter is not implemented for mavlink console yet
2025-02-18 13:01:44 +03:00
Oleg Kalachev
d60628e14d Support MAVLink console
Implement receiving and sending SERIAL_CONTROL message
Use global defined print function instead of Serial.printf
2025-02-18 10:33:01 +03:00
Oleg Kalachev
bfef7bd26a Remove non-nessesary printArray function 2025-02-18 10:26:59 +03:00
Oleg Kalachev
e3c6a0d4df Make some clarifications regarding imu check in troubleshooting 2025-02-18 10:18:27 +03:00
Oleg Kalachev
9566a4a503 Add parameters access method to build article 2025-02-18 10:14:39 +03:00
Oleg Kalachev
e54e0e8c48 Make all the basic functionality work without the imu 2025-02-17 19:44:18 +03:00
Oleg Kalachev
149c62568f Refactor CLI submodule
Move command parsing to doCommand
Parse command with splitString instead of stringToken
Trim commands
Move cliTestMotor to the bottom
Rename parseInput to handleInput, which is more clear
Move motor test function to motors.ino
2025-02-17 15:51:58 +03:00
Oleg Kalachev
641e711e67 Minor fix in joystick support for simulation
Don't use channels variable as it breaks code isolation
2025-02-15 03:29:09 +03:00
Oleg Kalachev
f2171f2db4 Minor clarification in RC receiver connection table 2025-02-12 11:10:04 +03:00
Oleg Kalachev
6ed6ef3e8c Assume armed is true if armed channel is not defined 2025-02-12 10:15:42 +03:00
Oleg Kalachev
083db659c6 Improve RC reading in calibration process 2025-02-12 10:15:13 +03:00
Oleg Kalachev
ce1223e82d Allow CI simulator build under macOS if manually triggered 2025-02-12 06:24:51 +03:00
Peter A. Ukhov
437ce81a68 Add video for flix2 by Peter Ukhov (#10)
Co-authored-by: Oleg Kalachev <okalachev@gmail.com>
2025-02-12 05:01:56 +03:00
Oleg Kalachev
42f318c6df Another update of hall of fame page 2025-02-11 14:02:15 +03:00
Oleg Kalachev
1450c793b7 Update hall of fame page 2025-02-11 12:11:22 +03:00
Oleg Kalachev
3ed4143ba0 Simplify WIFI_ENABLED macro test 2025-02-08 02:41:09 +03:00
Oleg Kalachev
33adf33f0e Add proper command to install arduino-cli on Linux in instructions 2025-02-01 22:56:09 +03:00
Oleg Kalachev
373c0f117a Update user builds page 2025-01-31 11:15:57 +03:00
Oleg Kalachev
0cb2eb5fac Update upload-artifact action to fix build 2025-01-31 03:32:09 +03:00
Oleg Kalachev
70f63bfbe9 Add hall of fame page 2025-01-31 03:19:49 +03:00
Oleg Kalachev
15fbe34d19 Add failsafe to prevent arming without prior zero throttle 2025-01-24 16:23:59 +03:00
Zatupitel
7d2d54a94d Fix working on ESP32-S3 (#8)
Disable brown-out detector in a more correct way: clear only enable bit instead of clearing the whole register.

---------

Co-authored-by: Oleg Kalachev <okalachev@gmail.com>
2025-01-24 14:35:44 +03:00
Oleg Kalachev
60fbe1c450 Fix firmware build with Wi-Fi disabled 2025-01-24 13:50:07 +03:00
Oleg Kalachev
40043768fe Add test on building the firmware without Wi-Fi to Actions 2025-01-24 13:40:27 +03:00
Oleg Kalachev
dcfe39f8c9 Move SBUS RC declaration to the top 2025-01-24 12:10:48 +03:00
Oleg Kalachev
b2100d10da Add motors voltage notice in troubleshooting article 2025-01-23 15:03:19 +03:00
Oleg Kalachev
fd6bc42e9e Fix critical typo in RC loss fail-safe 2025-01-23 00:34:59 +03:00
Oleg Kalachev
c01bac0d0a Update Flix image for frame version 1.1 2025-01-22 22:11:59 +03:00
Oleg Kalachev
f65c668ca1 Add brief assembly guide article 2025-01-22 01:44:31 +03:00
Oleg Kalachev
64cf5929e2 Add new frame models
Version 1.1
2025-01-22 01:42:42 +03:00
Oleg Kalachev
a9e5b2d5ca Add board pin names for motors to readme 2025-01-21 23:42:28 +03:00
Oleg Kalachev
6028b8a617 Catch port bind error in simulation 2025-01-17 17:38:47 +03:00
Oleg Kalachev
b19270f14e Minor cleanups and fixes 2025-01-17 12:30:12 +03:00
Oleg Kalachev
740121a88e Check if requested parameters indexes are correct
In case if gcs gets crazy and requests incorrect parameter index
2025-01-14 21:14:04 +03:00
Oleg Kalachev
b915e47f33 Add instructions on using USB remote control via QGroundControl app 2025-01-14 15:07:02 +03:00
Oleg Kalachev
7effd92043 Make MAVLink control scale a parameter 2025-01-14 14:51:34 +03:00
Oleg Kalachev
26bb4d2b3f Add link to working iOS QGroundControl build 2025-01-13 04:23:19 +03:00
Oleg Kalachev
70f5186c1b Use double for storing time instead of float
Float precision may be not enough after some time of operating
2025-01-12 19:58:36 +03:00
Oleg Kalachev
d4e04c46cd Add time command to cli 2025-01-12 19:50:00 +03:00
Oleg Kalachev
48d21a911f Add missing const qualifiers 2025-01-12 19:46:50 +03:00
Oleg Kalachev
f456e10177 Increase motors PWM frequency to 1000
To match the main loop frequency
2025-01-12 15:35:05 +03:00
Oleg Kalachev
ac54c954aa Cleanup 2025-01-11 04:31:53 +03:00
Oleg Kalachev
9e4a2c5ffc Move controlsTime variable to rc.ino 2025-01-11 00:28:31 +03:00
Oleg Kalachev
7bf5ee330b Add link to contributed circuit diagram to readme 2025-01-10 10:52:31 +03:00
Oleg Kalachev
b9e30be98c Better support for ESCs, add PWM_STOP parameter 2025-01-10 10:49:40 +03:00
Oleg Kalachev
821e6b105e Make channels definition to rc.ino
It's also planned to parametrize them later
2025-01-10 09:37:48 +03:00
Oleg Kalachev
568f9dd5b1 Minor code improvements 2025-01-10 08:59:09 +03:00
Oleg Kalachev
698cc3d9b8 Global variables cleanups
Remove unused PID objects for cli
Move loopRate to time.ino
2025-01-10 07:10:30 +03:00
Oleg Kalachev
85172cdcc8 Make util module header instead of .ino-file 2025-01-10 06:51:14 +03:00
Oleg Kalachev
08b14d1d76 Minor cleanup 2025-01-10 06:04:32 +03:00
Oleg Kalachev
95824e3b75 Make max tilt and max angle rates MAVLink parameters
Also decrease default max yaw rate to 300 degrees
2025-01-10 06:00:06 +03:00
Oleg Kalachev
0a45614751 Move motorsActive function to motors.ino 2025-01-09 11:30:04 +03:00
Oleg Kalachev
c8109af04f Make ONE_G definition const and move to utils.ino 2025-01-09 11:24:40 +03:00
Oleg Kalachev
404ceed851 Make motor indexes definition const int and move them to motors.ino
Remove motor indexes definitions from flix.ino
Add motors.ino to simulation code and implement required mocks
2025-01-09 11:14:18 +03:00
Oleg Kalachev
72033cdd75 Increase motors PWM resolution to 12 bits 2025-01-09 11:02:38 +03:00
Oleg Kalachev
3088ade743 Fix getDutyCycle return type to make it possible to increase resolution 2025-01-09 11:02:21 +03:00
Oleg Kalachev
c2a9d36d4e Add small delay before gyro calibration 2025-01-09 10:06:15 +03:00
Oleg Kalachev
ca409396c7 Add missing const qualifiers to some quaternion methods 2025-01-09 10:02:53 +03:00
Oleg Kalachev
ca032abc03 Implement rotate method for quaternions as replace for multiplication
Vector rotating method is renamed from `rotate` to `rotateVector` to avoid inconsistent object and argument order in different `rotate` methods
2025-01-09 09:56:49 +03:00
Oleg Kalachev
5d10446aaf Bring back possibility to use ESCs for motors 2025-01-09 07:43:49 +03:00
Oleg Kalachev
87cf44371b Some fixes and updates to readme and other articles 2025-01-09 03:46:54 +03:00
Oleg Kalachev
5ee407af8d Update ESP32-core to 3.1.0 2025-01-06 21:01:39 +03:00
Oleg Kalachev
59cb55cf94 Use ubuntu-20.04 runner to build simulator in CI
The latest Ubuntu Gazebo 11 officially supports is Ubuntu 20.04
2025-01-06 00:56:25 +03:00
Oleg Kalachev
5db1258f78 Add battery connector cable to components list 2025-01-06 00:21:59 +03:00
Oleg Kalachev
732de2a5d6 Remove redundant inline specifiers
In-class defined methods are specified as inline by default
2025-01-04 04:09:51 +03:00
Oleg Kalachev
e10475a5e0 Some minor cleanups and fixes 2024-12-28 23:57:44 +03:00
Oleg Kalachev
7ae5457bb4 Improve logging code
Make it easer to add and remove log entries
2024-12-28 22:10:43 +03:00
Oleg Kalachev
299c8a6a02 Various minor fixes 2024-12-27 21:52:21 +03:00
Oleg Kalachev
43be27c43d Fix joystick work in simulation
Logic was broken as joystickGet never got called
2024-12-27 15:34:33 +03:00
Oleg Kalachev
2440c65c46 Remove unused include 2024-12-26 16:07:01 +03:00
Oleg Kalachev
8d7a4595f5 Rename flushParameters to more clear syncParameters 2024-12-26 01:14:26 +03:00
Oleg Kalachev
acc0274175 Minor fix 2024-12-25 02:21:17 +03:00
Oleg Kalachev
edd249566e Increase motors output frequency 2024-12-25 02:13:57 +03:00
Oleg Kalachev
ca355e0162 Simplify motors duty cycle computation 2024-12-25 02:13:33 +03:00
Oleg Kalachev
2efae82177 Minor fixes 2024-12-25 01:41:45 +03:00
149 changed files with 8725 additions and 1630 deletions

View File

@@ -5,6 +5,7 @@ on:
branches: [ '*' ]
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
build_linux:
@@ -14,7 +15,18 @@ jobs:
- name: Install Arduino CLI
run: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh
- name: Build firmware
env:
ARDUINO_SKETCH_ALWAYS_EXPORT_BINARIES: 1
run: make
- name: Upload binaries
uses: actions/upload-artifact@v4
with:
name: firmware-binary
path: flix/build
- name: Build firmware for ESP32-S3
run: make BOARD=esp32:esp32:esp32s3
- name: Build firmware with WiFi disabled
run: sed -i 's/^#define WIFI_ENABLED 1$/#define WIFI_ENABLED 0/' flix/flix.ino && make
- name: Check c_cpp_properties.json
run: tools/check_c_cpp_properties.py
@@ -44,38 +56,49 @@ jobs:
build_simulator:
runs-on: ubuntu-latest
container:
image: ubuntu:20.04
steps:
- name: Install dependencies
run: |
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget build-essential cmake g++ pkg-config gnupg2 lsb-release sudo
- name: Install Arduino CLI
uses: arduino/setup-arduino-cli@v1.1.1
- uses: actions/checkout@v4
- name: Install Gazebo
run: curl -sSL http://get.gazebosim.org | sh
run: |
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
- name: Install SDL2
run: sudo apt-get install libsdl2-dev
run: sudo apt-get install -y libsdl2-dev
- name: Build simulator
run: make build_simulator
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: gazebo-plugin-binary
path: gazebo/build/*.so
retention-days: 1
# build_simulator_macos:
# runs-on: macos-latest
# steps:
# - name: Install Arduino CLI
# run: brew install arduino-cli
# - uses: actions/checkout@v4
# - name: Clean up python binaries # Workaround for https://github.com/actions/setup-python/issues/577
# run: |
# rm -f /usr/local/bin/2to3*
# rm -f /usr/local/bin/idle3*
# rm -f /usr/local/bin/pydoc3*
# rm -f /usr/local/bin/python3*
# rm -f /usr/local/bin/python3*-config
# - name: Install Gazebo
# run: brew update && brew tap osrf/simulation && brew install gazebo11
# - name: Install SDL2
# run: brew install sdl2
# - name: Build simulator
# run: make build_simulator
build_simulator_macos:
runs-on: macos-latest
if: github.event_name == 'workflow_dispatch'
steps:
- name: Install Arduino CLI
run: brew install arduino-cli
- uses: actions/checkout@v4
- name: Clean up python binaries # Workaround for https://github.com/actions/setup-python/issues/577
run: |
rm -f /usr/local/bin/2to3*
rm -f /usr/local/bin/idle3*
rm -f /usr/local/bin/pydoc3*
rm -f /usr/local/bin/python3*
rm -f /usr/local/bin/python3*-config
- name: Install Gazebo
run: brew update && brew tap osrf/simulation && brew install gazebo11
- name: Install SDL2
run: brew install sdl2
- name: Build simulator
run: make build_simulator

View File

@@ -19,6 +19,21 @@ jobs:
echo -e "t,x,y,z\n0,1,2,3\n1,4,5,6" > log.csv
./csv_to_ulog log.csv
test $(stat -c %s log.ulg) -eq 196
pyflix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Python build tools
run: pip install build
- name: Build pyflix
run: python3 -m build tools
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: pyflix
path: |
tools/dist/pyflix-*.tar.gz
tools/dist/pyflix-*.whl
python_tools:
runs-on: ubuntu-latest
steps:
@@ -31,3 +46,14 @@ jobs:
echo -e "t,x,y,z\n0,1,2,3\n1,4,5,6" > log.csv
./csv_to_mcap.py log.csv
test $(stat -c %s log.mcap) -eq 883
sloc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install cloc
run: sudo apt-get install -y cloc
- name: Firmware source lines count
run: cloc --by-file-by-lang flix
- name: Overall source lines count
run: cloc --by-file-by-lang --exclude-ext=svg,dae,css,hbs,md,json,yaml,yml,toml .

2
.gitignore vendored
View File

@@ -2,6 +2,8 @@
*.elf
build/
tools/log/
tools/dist/
*.egg-info/
.dependencies
.vscode/*
!.vscode/settings.json

View File

@@ -34,6 +34,7 @@
"MPU-6050",
"MPU-9250",
"GY-91",
"GY-521",
"ICM-20948",
"Linux",
"Windows",

View File

@@ -5,18 +5,20 @@
"includePath": [
"${workspaceFolder}/flix",
"${workspaceFolder}/gazebo",
"~/.arduino15/packages/esp32/hardware/esp32/3.0.7/cores/esp32",
"~/.arduino15/packages/esp32/hardware/esp32/3.0.7/libraries/**",
"~/.arduino15/packages/esp32/hardware/esp32/3.0.7/variants/d1_mini32",
"~/.arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.1-632e0c2a/esp32/**",
"~/.arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.1-632e0c2a/esp32/dio_qspi/include",
"${workspaceFolder}/tools/**",
"~/.arduino15/packages/esp32/hardware/esp32/3.2.0/cores/esp32",
"~/.arduino15/packages/esp32/hardware/esp32/3.2.0/libraries/**",
"~/.arduino15/packages/esp32/hardware/esp32/3.2.0/variants/d1_mini32",
"~/.arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.4-2f7dcd86-v1/esp32/**",
"~/.arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.4-2f7dcd86-v1/esp32/dio_qspi/include",
"~/Arduino/libraries/**",
"/usr/include/**"
"/usr/include/gazebo-11/",
"/usr/include/ignition/math6/"
],
"forcedInclude": [
"${workspaceFolder}/.vscode/intellisense.h",
"~/.arduino15/packages/esp32/hardware/esp32/3.0.7/cores/esp32/Arduino.h",
"~/.arduino15/packages/esp32/hardware/esp32/3.0.7/variants/d1_mini32/pins_arduino.h",
"~/.arduino15/packages/esp32/hardware/esp32/3.2.0/cores/esp32/Arduino.h",
"~/.arduino15/packages/esp32/hardware/esp32/3.2.0/variants/d1_mini32/pins_arduino.h",
"${workspaceFolder}/flix/cli.ino",
"${workspaceFolder}/flix/control.ino",
"${workspaceFolder}/flix/estimate.ino",
@@ -28,11 +30,10 @@
"${workspaceFolder}/flix/motors.ino",
"${workspaceFolder}/flix/rc.ino",
"${workspaceFolder}/flix/time.ino",
"${workspaceFolder}/flix/util.ino",
"${workspaceFolder}/flix/wifi.ino",
"${workspaceFolder}/flix/parameters.ino"
],
"compilerPath": "~/.arduino15/packages/esp32/tools/esp-x32/2302/bin/xtensa-esp32-elf-g++",
"compilerPath": "~/.arduino15/packages/esp32/tools/esp-x32/2411/bin/xtensa-esp32-elf-g++",
"cStandard": "c11",
"cppStandard": "c++17",
"defines": [
@@ -52,19 +53,19 @@
"name": "Mac",
"includePath": [
"${workspaceFolder}/flix",
"${workspaceFolder}/gazebo",
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.0.7/cores/esp32",
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.0.7/libraries/**",
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.0.7/variants/d1_mini32",
"~/Library/Arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.1-632e0c2a/esp32/include/**",
"~/Library/Arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.1-632e0c2a/esp32/dio_qspi/include",
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.2.0/cores/esp32",
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.2.0/libraries/**",
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.2.0/variants/d1_mini32",
"~/Library/Arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.4-2f7dcd86-v1/esp32/include/**",
"~/Library/Arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.4-2f7dcd86-v1/esp32/dio_qspi/include",
"~/Documents/Arduino/libraries/**",
"/opt/homebrew/include/**"
"/opt/homebrew/include/gazebo-11/",
"/opt/homebrew/include/ignition/math6/"
],
"forcedInclude": [
"${workspaceFolder}/.vscode/intellisense.h",
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.0.7/cores/esp32/Arduino.h",
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.0.7/variants/d1_mini32/pins_arduino.h",
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.2.0/cores/esp32/Arduino.h",
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.2.0/variants/d1_mini32/pins_arduino.h",
"${workspaceFolder}/flix/flix.ino",
"${workspaceFolder}/flix/cli.ino",
"${workspaceFolder}/flix/control.ino",
@@ -76,11 +77,10 @@
"${workspaceFolder}/flix/motors.ino",
"${workspaceFolder}/flix/rc.ino",
"${workspaceFolder}/flix/time.ino",
"${workspaceFolder}/flix/util.ino",
"${workspaceFolder}/flix/wifi.ino",
"${workspaceFolder}/flix/parameters.ino"
],
"compilerPath": "~/Library/Arduino15/packages/esp32/tools/esp-x32/2302/bin/xtensa-esp32-elf-g++",
"compilerPath": "~/Library/Arduino15/packages/esp32/tools/esp-x32/2411/bin/xtensa-esp32-elf-g++",
"cStandard": "c11",
"cppStandard": "c++17",
"defines": [
@@ -102,17 +102,18 @@
"includePath": [
"${workspaceFolder}/flix",
"${workspaceFolder}/gazebo",
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.0.7/cores/esp32",
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.0.7/libraries/**",
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.0.7/variants/d1_mini32",
"~/AppData/Local/Arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.1-632e0c2a/esp32/**",
"~/AppData/Local/Arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.1-632e0c2a/esp32/dio_qspi/include",
"${workspaceFolder}/tools/**",
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.2.0/cores/esp32",
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.2.0/libraries/**",
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.2.0/variants/d1_mini32",
"~/AppData/Local/Arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.4-2f7dcd86-v1/esp32/**",
"~/AppData/Local/Arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.4-2f7dcd86-v1/esp32/dio_qspi/include",
"~/Documents/Arduino/libraries/**"
],
"forcedInclude": [
"${workspaceFolder}/.vscode/intellisense.h",
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.0.7/cores/esp32/Arduino.h",
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.0.7/variants/d1_mini32/pins_arduino.h",
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.2.0/cores/esp32/Arduino.h",
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.2.0/variants/d1_mini32/pins_arduino.h",
"${workspaceFolder}/flix/cli.ino",
"${workspaceFolder}/flix/control.ino",
"${workspaceFolder}/flix/estimate.ino",
@@ -124,11 +125,10 @@
"${workspaceFolder}/flix/motors.ino",
"${workspaceFolder}/flix/rc.ino",
"${workspaceFolder}/flix/time.ino",
"${workspaceFolder}/flix/util.ino",
"${workspaceFolder}/flix/wifi.ino",
"${workspaceFolder}/flix/parameters.ino"
],
"compilerPath": "~/AppData/Local/Arduino15/packages/esp32/tools/esp-x32/2302/bin/xtensa-esp32-elf-g++.exe",
"compilerPath": "~/AppData/Local/Arduino15/packages/esp32/tools/esp-x32/2411/bin/xtensa-esp32-elf-g++.exe",
"cStandard": "c11",
"cppStandard": "c++17",
"defines": [

View File

@@ -2,7 +2,6 @@
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
"recommendations": [
"ms-vscode.cpptools",
"twxs.cmake",
"ms-vscode.cmake-tools",
"ms-python.python"
],

View File

@@ -1,5 +1,6 @@
{
"C_Cpp.intelliSenseEngineFallback": "enabled",
"C_Cpp.errorSquiggles": "disabled",
"files.associations": {
"*.sdf": "xml",
"*.ino": "cpp",

View File

@@ -13,10 +13,10 @@ monitor:
dependencies .dependencies:
arduino-cli core update-index --config-file arduino-cli.yaml
arduino-cli core install esp32:esp32@3.0.7 --config-file arduino-cli.yaml
arduino-cli core install esp32:esp32@3.2.0 --config-file arduino-cli.yaml
arduino-cli lib update-index
arduino-cli lib install "FlixPeriph"
arduino-cli lib install "MAVLink"@2.0.12
arduino-cli lib install "MAVLink"@2.0.16
touch .dependencies
gazebo/build cmake: gazebo/CMakeLists.txt
@@ -32,7 +32,7 @@ simulator: build_simulator
gazebo --verbose ${CURDIR}/gazebo/flix.world
log:
PORT=$(PORT) tools/grab_log.py
tools/log.py
plot:
plotjuggler -d $(shell ls -t tools/log/*.csv | head -n1)

140
README.md
View File

@@ -1,79 +1,98 @@
# Flix
**Flix** (*flight + X*) — making an open source ESP32-based quadcopter from scratch.
**Flix** (*flight + X*) — open source ESP32-based quadcopter made from scratch.
<table>
<tr>
<td align=center><strong>Version 1</strong> (3D-printed frame)</td>
<td align=center><strong>Version 1.1</strong> (3D-printed frame)</td>
<td align=center><strong>Version 0</strong></td>
</tr>
<tr>
<td><img src="docs/img/flix1.jpg" width=500 alt="Flix quadcopter"></td>
<td><img src="docs/img/flix1.1.jpg" width=500 alt="Flix quadcopter"></td>
<td><img src="docs/img/flix.jpg" width=500 alt="Flix quadcopter"></td>
</tr>
</table>
## Features
* Simple and clean Arduino based source code.
* Acro and Stabilized flight using remote control.
* Precise simulation using Gazebo.
* [In-RAM logging](docs/log.md).
* Command line interface through USB port.
* Wi-Fi support.
* MAVLink support.
* Control using mobile phone (with QGroundControl app).
* Completely 3D-printed frame.
* Textbook for students on writing a flight controller ([in development](https://quadcopter.dev)).
* *Position control and autonomous flights using external camera¹*.
* [Building and running instructions](docs/build.md).
* Dedicated for education and research.
* Made from general-purpose components.
* Simple and clean source code in Arduino (<2k lines firmware).
* Control using USB gamepad, remote control or smartphone.
* Wi-Fi and MAVLink support.
* Wireless command line interface and analyzing.
* Precise simulation with Gazebo.
* Python library.
* Textbook on flight control theory and practice ([in development](https://quadcopter.dev)).
* *Position control (using external camera) and autonomous flights¹*.
*¹ — planned.*
## It actually flies
See detailed demo video (for version 0): https://youtu.be/8GzzIQ3C6DQ.
See detailed demo video: https://youtu.be/hT46CZ1CgC4.
<a href="https://youtu.be/hT46CZ1CgC4"><img width=500 src="https://i3.ytimg.com/vi/hT46CZ1CgC4/maxresdefault.jpg"></a>
Version 0 demo video: https://youtu.be/8GzzIQ3C6DQ.
<a href="https://youtu.be/8GzzIQ3C6DQ"><img width=500 src="https://i3.ytimg.com/vi/8GzzIQ3C6DQ/maxresdefault.jpg"></a>
Version 1 test flight: https://t.me/opensourcequadcopter/42.
Usage in education (RoboCamp): https://youtu.be/Wd3yaorjTx0.
<a href="https://t.me/opensourcequadcopter/42"><img width=500 src="docs/img/flight-video.jpg"></a>
<a href="https://youtu.be/Wd3yaorjTx0"><img width=500 src="https://i3.ytimg.com/vi/Wd3yaorjTx0/sddefault.jpg"></a>
See the [user builds gallery](docs/user.md):
<a href="docs/user.md"><img src="docs/img/user/user.jpg" width=500></a>
## Simulation
The simulator is implemented using Gazebo and runs the original Arduino code:
<img src="docs/img/simulator.png" width=500 alt="Flix simulator">
<img src="docs/img/simulator1.png" width=500 alt="Flix simulator">
See [instructions on running the simulation](docs/build.md).
## Documentation
## Components (version 1)
1. [Assembly instructions](docs/assembly.md).
2. [Usage: build, setup and flight](docs/usage.md).
3. [Simulation](gazebo/README.md).
4. [Python library](tools/pyflix/README.md).
Additional articles:
* [User builds gallery](docs/user.md).
* [Firmware architectural overview](docs/firmware.md).
* [Troubleshooting](docs/troubleshooting.md).
* [Log analysis](docs/log.md).
## Components
|Type|Part|Image|Quantity|
|-|-|:-:|:-:|
|Microcontroller board|ESP32 Mini|<img src="docs/img/esp32.jpg" width=100>|1|
|IMU (and barometer²) board|GY91 (or other MPU9250/MPU6500 board), ICM20948³|<img src="docs/img/gy-91.jpg" width=90 align=center><img src="docs/img/icm-20948.jpg" width=100>|1|
|Motor|8520 3.7V brushed motor (**shaft 0.8mm!**)|<img src="docs/img/motor.jpeg" width=100>|4|
|Propeller|Hubsan 55 mm|<img src="docs/img/prop.jpg" width=100>|4|
|IMU (and barometer¹) board|GY91, MPU-9265 (or other MPU9250/MPU6500 board)<br>ICM20948V2 (ICM20948)³<br>GY-521 (MPU-6050)³⁻¹|<img src="docs/img/gy-91.jpg" width=90 align=center><br><img src="docs/img/icm-20948.jpg" width=100><br><img src="docs/img/gy-521.jpg" width=100>|1|
|Boost converter (optional, for more stable power supply)|5V output|<img src="docs/img/buck-boost.jpg" width=100>|1|
|Motor|8520 3.7V brushed motor.<br>Motor with exact 3.7V voltage is needed, not ranged working voltage (3.7V — 6V).<br>Make sure the motor shaft diameter and propeller hole diameter match!|<img src="docs/img/motor.jpeg" width=100>|4|
|Propeller|55 mm (alternatively 65 mm)|<img src="docs/img/prop.jpg" width=100>|4|
|MOSFET (transistor)|100N03A or [analog](https://t.me/opensourcequadcopter/33)|<img src="docs/img/100n03a.jpg" width=100>|4|
|Pull-down resistor|10 kΩ|<img src="docs/img/resistor10k.jpg" width=100>|4|
|3.7V Li-Po battery|LW 952540 (or any compatible by the size)|<img src="docs/img/battery.jpg" width=100>|1|
|Battery connector cable|MX2.0 2P female|<img src="docs/img/mx.png" width=100>|1|
|Li-Po Battery charger|Any|<img src="docs/img/charger.jpg" width=100>|1|
|Screws for IMU board mounting|M3x5|<img src="docs/img/screw-m3.jpg" width=100>|2|
|Screws for frame assembly|M1.4x5|<img src="docs/img/screw-m1.4.jpg" height=30 align=center>|4|
|Frame bottom part|3D printed⁴:<br>[`flix-frame.stl`](docs/assets/flix-frame.stl) [`flix-frame.step`](docs/assets/flix-frame.step)|<img src="docs/img/frame1.jpg" width=100>|1|
|Frame top part|3D printed:<br>[`esp32-holder.stl`](docs/assets/esp32-holder.stl) [`esp32-holder.step`](docs/assets/esp32-holder.step)|<img src="docs/img/esp32-holder.jpg" width=100>|1|
|Washer for IMU board mounting|3D printed:<br>[`washer-m3.stl`](docs/assets/washer-m3.stl) [`washer-m3.step`](docs/assets/washer-m3.step)|<img src="docs/img/washer-m3.jpg" width=100>|1|
|*RC transmitter (optional)*|*KINGKONG TINY X8 or other⁵*|<img src="docs/img/tx.jpg" width=100>|1|
|*RC receiver (optional)*|*DF500 or other*|<img src="docs/img/rx.jpg" width=100>|1|
|Frame main part|3D printed²: [`stl`](docs/assets/flix-frame-1.1.stl) [`step`](docs/assets/flix-frame-1.1.step)<br>Recommended settings: layer 0.2 mm, line 0.4 mm, infill 100%.|<img src="docs/img/frame1.jpg" width=100>|1|
|Frame top part|3D printed: [`stl`](docs/assets/esp32-holder.stl) [`step`](docs/assets/esp32-holder.step)|<img src="docs/img/esp32-holder.jpg" width=100>|1|
|Washer for IMU board mounting|3D printed: [`stl`](docs/assets/washer-m3.stl) [`step`](docs/assets/washer-m3.step)|<img src="docs/img/washer-m3.jpg" width=100>|2|
|Controller (recommended)|CC2500 transmitter, like BetaFPV LiteRadio CC2500 (RC receiver/Wi-Fi).<br>Two-sticks gamepad (Wi-Fi only) — see [recommended gamepads](https://docs.qgroundcontrol.com/master/en/qgc-user-guide/setup_view/joystick.html#supported-joysticks).<br>Other⁵|<img src="docs/img/betafpv.jpg" width=100><img src="docs/img/logitech.jpg" width=80>|1|
|*RC receiver (optional)*|*DF500 or other³*|<img src="docs/img/rx.jpg" width=100>|1|
|Wires|28 AWG recommended|<img src="docs/img/wire-28awg.jpg" width=100>||
|Tape, double-sided tape||||
*² barometer is not used for now.*<br>
*³ — change `MPU9250` to `ICM20948` in `imu.ino` file if using ICM-20948 board.*<br>
* — this frame is optimized for GY-91 board, if using other, the board mount holes positions should be modified.*<br>
*⁵ — you may use any transmitter-receiver pair with SBUS interface.*
*¹ barometer is not used for now.*<br>
*² — this frame is optimized for GY-91 board, if using other, the board mount holes positions should be modified.*<br>
*³ — you also may use any transmitter-receiver pair with SBUS interface.*
Tools required for assembly:
@@ -83,19 +102,21 @@ Tools required for assembly:
* Screwdrivers.
* Multimeter.
Feel free to modify the design and or code, and create your own improved versions of Flix! Send your results to the [official Telegram chat](https://t.me/opensourcequadcopterchat), or directly to the author ([E-mail](mailto:okalachev@gmail.com), [Telegram](https://t.me/okalachev)).
Feel free to modify the design and or code, and create your own improved versions. Send your results to the [official Telegram chat](https://t.me/opensourcequadcopterchat), or directly to the author ([E-mail](mailto:okalachev@gmail.com), [Telegram](https://t.me/okalachev)).
## Schematics (version 1)
## Schematics
### Simplified connection diagram
<img src="docs/img/schematics1.svg" width=800 alt="Flix version 1 schematics">
<img src="docs/img/schematics1.svg" width=700 alt="Flix version 1 schematics">
*(Dashed elements are optional).*
Motor connection scheme:
<img src="docs/img/mosfet-connection.png" height=400 alt="MOSFET connection scheme">
Complete diagram is Work-in-Progress.
You can see a user-contributed [variant of complete circuit diagram](https://miro.com/app/board/uXjVN-dTjoo=/?moveToWidget=3458764612338222067&cot=14) of the drone.
### Notes
@@ -114,43 +135,34 @@ Complete diagram is Work-in-Progress.
* Solder pull-down resistors to the MOSFETs.
* Connect the motors to the ESP32 Mini using MOSFETs, by following scheme:
|Motor|Position|Direction|Wires|GPIO|
|-|-|-|-|-|
|Motor 0|Rear left|Counter-clockwise|Black & White|GPIO12|
|Motor 1|Rear right|Clockwise|Blue & Red|GPIO13|
|Motor 2|Front right|Counter-clockwise|Black & White|GPIO14|
|Motor 3|Front left|Clockwise|Blue & Red|GPIO15|
|Motor|Position|Direction|Prop type|Motor wires|GPIO|
|-|-|-|-|-|-|
|Motor 0|Rear left|Counter-clockwise|B|Black & White|GPIO12 (*TDI*)|
|Motor 1|Rear right|Clockwise|A|Blue & Red|GPIO13 (*TCK*)|
|Motor 2|Front right|Counter-clockwise|B|Black & White|GPIO14 (*TMS*)|
|Motor 3|Front left|Clockwise|A|Blue & Red|GPIO15 (*TD0*)|
Counter-clockwise motors have black and white wires and clockwise motors have blue and red wires.
Clockwise motors have blue & red wires and correspond to propeller type A (marked on the propeller).
Counter-clockwise motors have black & white wires correspond to propeller type B.
* Optionally connect the RC receiver to the ESP32's UART2:
|Receiver pin|ESP32 pin|
|-|-|
|GND|GND|
|VIN|VC (or 3.3V depending on the receiver)|
|Signal|GPIO4|
|VIN|VCC (or 3.3V depending on the receiver)|
|Signal (TX)|GPIO4¹|
* — UART2 RX pin was [changed](https://docs.espressif.com/projects/arduino-esp32/en/latest/migration_guides/2.x_to_3.0.html#id14) to GPIO4 in Arduino ESP32 core 3.0.*
*¹ — UART2 RX pin was [changed](https://docs.espressif.com/projects/arduino-esp32/en/latest/migration_guides/2.x_to_3.0.html#id14) to GPIO4 in Arduino ESP32 core 3.0.*
### IMU placement
## Resources
Default IMU orientation in the code is **LFD** (Left-Forward-Down):
* Telegram channel on developing the drone and the flight controller (in Russian): https://t.me/opensourcequadcopter.
* Official Telegram chat: https://t.me/opensourcequadcopterchat.
* Detailed article on Habr.com about the development of the drone (in Russian): https://habr.com/ru/articles/814127/.
<img src="docs/img/gy91-lfd.svg" width=400 alt="GY-91 axes">
## Disclaimer
In case of using other IMU orientation, modify the `rotateIMU` function in the `imu.ino` file.
This is a DIY project, and I hope you find it interesting and useful. However, it's not easy to assemble and set up, and it's provided "as is" without any warranties. There's no guarantee that it will work perfectly, or even work at all.
See [FlixPeriph documentation](https://github.com/okalachev/flixperiph?tab=readme-ov-file#imu-axes-orientation) to learn axis orientation of other IMU boards.
## Version 0
See the information on the obsolete version 0 in the [corresponding article](docs/version0.md).
## Materials
Subscribe to the Telegram channel on developing the drone and the flight controller (in Russian): https://t.me/opensourcequadcopter.
Join the official Telegram chat: https://t.me/opensourcequadcopterchat.
Detailed article on Habr.com about the development of the drone (in Russian): https://habr.com/ru/articles/814127/.
⚠️ The author is not responsible for any damage, injury, or loss resulting from the use of this project. Use at your own risk!

View File

@@ -1,3 +1,5 @@
board_manager:
additional_urls:
- https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
network:
connection_timeout: 1h

53
docs/assembly.md Normal file
View File

@@ -0,0 +1,53 @@
# Brief assembly guide
Soldered components ([schematics variant](https://miro.com/app/board/uXjVN-dTjoo=/?moveToWidget=3458764612338222067&cot=14)):
<img src="img/assembly/1.jpg" width=600>
<br>Use double-sided tape to attach ESP32 to the top frame part (ESP32 holder):
<img src="img/assembly/2.jpg" width=600>
<br>Use two washers to screw the IMU board to the frame:
<img src="img/assembly/3.jpg" width=600>
<br>Screw the IMU with M3x5 screws as shown:
<img src="img/assembly/4.jpg" width=600>
<br>Install the motors, attach MOSFETs to the frame using tape:
<img src="img/assembly/5.jpg" width=600>
<br>Screw the ESP32 holder with M1.4x5 screws to the frame:
<img src="img/assembly/6.jpg" width=600>
<br>Assembled drone:
<img src="img/assembly/7.jpg" width=600>
## Motor directions
> [!WARNING]
> The drone above is an early build, and it has **inversed** motor directions scheme. The photos only illustrate the assembly process in general.
Use standard motor directions scheme:
<img src="img/motors.svg" width=200>
Motors connection table:
|Motor|Position|Direction|Prop type|Motor wires|GPIO|
|-|-|-|-|-|-|
|Motor 0|Rear left|Counter-clockwise|B|Black & White|GPIO12 (*TDI*)|
|Motor 1|Rear right|Clockwise|A|Blue & Red|GPIO13 (*TCK*)|
|Motor 2|Front right|Counter-clockwise|B|Black & White|GPIO14 (*TMS*)|
|Motor 3|Front left|Clockwise|A|Blue & Red|GPIO15 (*TD0*)|
## Motors tightening
Motors should be installed very tightly — any vibration may lead to bad attitude estimation and unstable flight. If motors are loose, use tiny tape pieces to fix them tightly as shown below:
<img src="img/motor-tape.jpg" width=600>

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -53,6 +53,12 @@ footer a.telegram, footer a.github {
border: 1px solid #c9c9c9;
}
@media (max-width: 600px) {
.MathJax_Display {
overflow-x: auto;
}
}
.firmware {
position: relative;
margin: 20px 0;

View File

@@ -10,7 +10,7 @@ description = "Учебник по разработке полетного ко
build-dir = "build"
[output.html]
additional-css = ["book.css", "zoom.css"]
additional-css = ["book.css", "zoom.css", "rotation.css"]
additional-js = ["zoom.js", "js.js"]
edit-url-template = "https://github.com/okalachev/flix/blob/master/docs/{path}?plain=1"
mathjax-support = true

View File

@@ -3,7 +3,7 @@
> [!IMPORTANT]
> Flix — это проект по созданию открытого квадрокоптера на базе ESP32 с нуля и учебника по разработке полетных контроллеров.
<img src="img/flix1.jpg" class="border" width=500 alt="Flix quadcopter">
<img src="img/flix1.1.jpg" class="border" width=500 alt="Flix quadcopter">
<p class="github">GitHub:&nbsp;<a href="https://github.com/okalachev/flix">github.com/okalachev/flix</a>.</p>

View File

@@ -11,8 +11,9 @@
* [Светодиод]()
* [Моторы]()
* [Радиоуправление]()
* [Вектор, кватернион](geometry.md)
* [Гироскоп](gyro.md)
* [Акселерометр]()s
* [Акселерометр]()
* [Оценка состояния]()
* [PID-регулятор]()
* [Режим ACRO]()

View File

@@ -1,8 +1,10 @@
# Архитектура прошивки
<img src="img/dataflow.svg" width=800 alt="Firmware dataflow diagram">
Прошивка Flix это обычный скетч Arduino, реализованный в однопоточном стиле. Код инициализации находится в функции `setup()`, а главный цикл — в функции `loop()`. Скетч состоит из нескольких файлов, каждый из которых отвечает за определенную подсистему.
Главный цикл работает на частоте 1000 Гц. Передача данных между подсистемами происходит через глобальные переменные:
<img src="img/dataflow.svg" width=600 alt="Firmware dataflow diagram">
Главный цикл `loop()` работает на частоте 1000 Гц. Передача данных между подсистемами происходит через глобальные переменные:
* `t` *(float)* — текущее время шага, *с*.
* `dt` *(float)* — дельта времени между текущим и предыдущим шагами, *с*.
@@ -10,23 +12,39 @@
* `acc` *(Vector)* — данные с акселерометра, *м/с<sup>2</sup>*.
* `rates` *(Vector)* — отфильтрованные угловые скорости, *рад/с*.
* `attitude` *(Quaternion)* — оценка ориентации (положения) дрона.
* `controls` *(float[])*пользовательские управляющие сигналы с пульта, нормализованные в диапазоне [-1, 1].
* `motors` *(float[])* — выходные сигналы на моторы, нормализованные в диапазоне [-1, 1] (возможно вращение в обратную сторону).
* `controlRoll`, `controlPitch`, `controlYaw`, `controlThrottle`, `controlMode` *(float)*команды управления от пилота, в диапазоне [-1, 1].
* `motors` *(float[4])* — выходные сигналы на моторы, в диапазоне [0, 1].
## Исходные файлы
Исходные файлы прошивки находятся в директории `flix`. Ключевые файлы:
Исходные файлы прошивки находятся в директории `flix`. Основные файлы:
* [`flix.ino`](https://github.com/okalachev/flix/blob/canonical/flix/flix.ino) — основной входной файл, скетч Arduino. Включает определение глобальных переменных и главный цикл.
* [`imu.ino`](https://github.com/okalachev/flix/blob/canonical/flix/imu.ino) — чтение данных с датчика IMU (гироскоп и акселерометр), калибровка IMU.
* [`rc.ino`](https://github.com/okalachev/flix/blob/canonical/flix/rc.ino) — чтение данных с RC-приемника, калибровка RC.
* [`mavlink.ino`](https://github.com/okalachev/flix/blob/canonical/flix/mavlink.ino) — взаимодействие с QGroundControl через MAVLink.
* [`estimate.ino`](https://github.com/okalachev/flix/blob/canonical/flix/estimate.ino) — оценка ориентации дрона, комплементарный фильтр.
* [`control.ino`](https://github.com/okalachev/flix/blob/canonical/flix/control.ino) — управление ориентацией и угловыми скоростями дрона, трехмерный двухуровневый каскадный PID-регулятор.
* [`motors.ino`](https://github.com/okalachev/flix/blob/canonical/flix/motors.ino) — управление выходными сигналами на моторы через ШИМ.
* [`flix.ino`](https://github.com/okalachev/flix/blob/master/flix/flix.ino) — основной файл Arduino-скетча. Определяет некоторые глобальные переменные и главный цикл.
* [`imu.ino`](https://github.com/okalachev/flix/blob/master/flix/imu.ino) — чтение данных с датчика IMU (гироскоп и акселерометр), калибровка IMU.
* [`rc.ino`](https://github.com/okalachev/flix/blob/master/flix/rc.ino) — чтение данных с RC-приемника, калибровка RC.
* [`estimate.ino`](https://github.com/okalachev/flix/blob/master/flix/estimate.ino) — оценка ориентации дрона, комплементарный фильтр.
* [`control.ino`](https://github.com/okalachev/flix/blob/master/flix/control.ino) — подсистема управления, трехмерный двухуровневый каскадный ПИД-регулятор.
* [`motors.ino`](https://github.com/okalachev/flix/blob/master/flix/motors.ino) — выход PWM на моторы.
* [`mavlink.ino`](https://github.com/okalachev/flix/blob/master/flix/mavlink.ino) — взаимодействие с QGroundControl или [pyflix](https://github.com/okalachev/flix/tree/master/tools/pyflix) через протокол MAVLink.
Вспомогательные файлы включают:
Вспомогательные файлы:
* [`vector.h`](https://github.com/okalachev/flix/blob/canonical/flix/vector.h), [`quaternion.h`](https://github.com/okalachev/flix/blob/canonical/flix/quaternion.h) — реализация библиотек векторов и кватернионов проекта.
* [`pid.h`](https://github.com/okalachev/flix/blob/canonical/flix/pid.h) — реализация общего ПИД-регулятора.
* [`lpf.h`](https://github.com/okalachev/flix/blob/canonical/flix/lpf.h) — реализация общего фильтра нижних частот.
* [`vector.h`](https://github.com/okalachev/flix/blob/master/flix/vector.h), [`quaternion.h`](https://github.com/okalachev/flix/blob/master/flix/quaternion.h) — библиотеки векторов и кватернионов.
* [`pid.h`](https://github.com/okalachev/flix/blob/master/flix/pid.h) — ПИД-регулятор.
* [`lpf.h`](https://github.com/okalachev/flix/blob/master/flix/lpf.h) — фильтр нижних частот.
### Подсистема управления
Состояние органов управления обрабатывается в функции `interpretControls()` и преобразуется в *команду управления*, которая включает следующее:
* `attitudeTarget` *(Quaternion)* — целевая ориентация дрона.
* `ratesTarget` *(Vector)* — целевые угловые скорости, *рад/с*.
* `ratesExtra` *(Vector)* — дополнительные (feed-forward) угловые скорости, для управления рысканием в режиме STAB, *рад/с*.
* `torqueTarget` *(Vector)* — целевой крутящий момент, диапазон [-1, 1].
* `thrustTarget` *(float)* — целевая общая тяга, диапазон [0, 1].
Команда управления обрабатывается в функциях `controlAttitude()`, `controlRates()`, `controlTorque()`. Если значение одной из переменных установлено в `NAN`, то соответствующая функция пропускается.
<img src="img/control.svg" width=300 alt="Control subsystem diagram">
Состояние *armed* хранится в переменной `armed`, а текущий режим — в переменной `mode`.

309
docs/book/geometry.md Normal file
View File

@@ -0,0 +1,309 @@
# Вектор, кватернион
В алгоритме управления квадрокоптером широко применяются геометрические (и алгебраические) объекты, такие как **векторы** и **кватернионы**. Они позволяют упростить математические вычисления и улучшить читаемость кода. В этой главе мы рассмотрим именно те геометрические объекты, которые используются в алгоритме управления квадрокоптером Flix, причем акцент будет сделан на практических аспектах их использования.
## Система координат
### Оси координат
Для работы с объектами в трехмерном пространстве необходимо определить *систему координат*. Как известно, система координат задается тремя взаимно перпендикулярными осями, которые обозначаются как *X*, *Y* и *Z*. Порядок обозначения этих осей зависит от того, какую систему координат мы выбрали — *левую* или *правую*:
|Левая система координат|Правая система координат|
|-----------------------|------------------------|
|<img src="img/left-axes.svg" alt="Левая система координат" width="200">|<img src="img/right-axes.svg" alt="Правая система координат" width="200">|
В Flix для всех математических расчетов используется **правая система координат**, что является стандартом в робототехнике и авиации.
Также необходимо выбрать направление осей — в Flix они выбраны в соответствии со стандартом [REP-103](https://www.ros.org/reps/rep-0103.html). Для величин, заданных в подвижной системе координат, связанной с корпусом дрона, применяется порядок <abbr title="Forward Left Up">FLU</abbr>:
* ось X — направлена **вперед**;
* ось Y — направлена **влево**;
* ось Z — направлена **вверх**.
Для величин, заданных в *мировой* системе координат (относительно фиксированной точки в пространстве) — <abbr title="East North Up">ENU</abbr>:
* ось X — направлена на **восток** (условный);
* ось Y — направлена на **север** (условный);
* ось Z — направлена **вверх**.
> [!NOTE]
> Для системы ENU важно только взаимное направление осей. Если доступен магнитометр, то используются реальные восток и север, но если нет — то произвольно выбранные.
Углы и угловые скорости определяются в соответствии с правилами математики: значения увеличиваются против часовой стрелки, если смотреть в сторону начала координат. Общий вид системы координат:
<img src="img/axes-rotation.svg" alt="Система координат" width="200">
> [!TIP]
> Оси координат <i>X</i>, <i>Y</i> и <i>Z</i> часто обозначаются красными, зелеными и синими цветами соответственно. Запомнить это можно с помощью сокращения <abbr title="Red Green Blue">RGB</abbr>.
## Вектор
<div class="firmware">
<strong>Файл прошивки:</strong>
<a href="https://github.com/okalachev/flix/blob/master/flix/vector.h"><code>vector.h</code></a>.<br>
</div>
**Вектор** — простой геометрический объект, который содержит три значения, соответствующие координатам *X*, *Y* и *Z*. Эти значения называются *компонентами вектора*. Вектор может описывать точку в пространстве, направление или ось вращения, скорость, ускорение, угловые скорости и другие физические величины. В Flix векторы задаются объектами `Vector` из библиотеки `vector.h`:
```cpp
Vector v(1, 2, 3);
v.x = 5;
v.y = 10;
v.z = 15;
```
> [!TIP]
> Не следует путать геометрический вектор — <code>vector</code> и динамический массив в стандартной библиотеке C++ — <code>std::vector</code>.
В прошивке в виде векторов представлены, например:
* `acc` собственное ускорение с акселерометра.
* `gyro` — угловые скорости с гироскопа.
* `rates` — рассчитанная угловая скорость дрона.
* `accBias`, `accScale`, `gyroBias` — параметры калибровки IMU.
### Операции с векторами
**Длина вектора** рассчитывается при помощи теоремы Пифагора; в прошивке используется метод `norm()`:
```cpp
Vector v(3, 4, 5);
float length = v.norm(); // 7.071
```
Любой вектор можно привести к **единичному вектору** (сохранить направление, но сделать длину равной 1) при помощи метода `normalize()`:
```cpp
Vector v(3, 4, 5);
v.normalize(); // 0.424, 0.566, 0.707
```
**Сложение и вычитание** векторов реализуется через простое покомпонентное сложение и вычитание. Геометрически сумма векторов представляет собой вектор, который соединяет начало первого вектора с концом второго. Разность векторов представляет собой вектор, который соединяет конец первого вектора с концом второго. Это удобно для расчета относительных позиций, суммарных скоростей и решения других задач. В коде эти операции интуитивно понятны:
```cpp
Vector a(1, 2, 3);
Vector b(4, 5, 6);
Vector sum = a + b; // 5, 7, 9
Vector diff = a - b; // -3, -3, -3
```
Операция **умножения на число** `n` увеличивает (или уменьшает) длину вектора в `n` раз (сохраняя направление):
```cpp
Vector a(1, 2, 3);
Vector b = a * 2; // 2, 4, 6
```
В некоторых случаях полезна операция **покомпонентного умножения** (или деления) векторов. Например, для применения коэффициентов калибровки к данным с IMU. В разных библиотеках эта операция обозначается по разному, но в библиотеке `vector.h` используется простые знаки `*` и `/`:
```cpp
acc = acc / accScale;
```
**Угол между векторами** можно найти при помощи статического метода `Vector::angleBetween()`:
```cpp
Vector a(1, 0, 0);
Vector b(0, 1, 0);
float angle = Vector::angleBetween(a, b); // 1.57 (90 градусов)
```
#### Скалярное произведение
Скалярное произведение векторов (*dot product*) — это произведение длин двух векторов на косинус угла между ними. В математике оно обозначается знаком `·` или слитным написанием векторов. Интуитивно, результат скалярного произведения показывает, насколько два вектора *сонаправлены*.
В Flix используется статический метод `Vector::dot()`:
```cpp
Vector a(1, 2, 3);
Vector b(4, 5, 6);
float dotProduct = Vector::dot(a, b); // 32
```
Операция скалярного произведения может помочь, например, при расчете проекции одного вектора на другой.
#### Векторное произведение
Векторное произведение (*cross product*) позволяет найти вектор, перпендикулярный двум другим векторам. В математике оно обозначается знаком `×`, а в прошивке используется статический метод `Vector::cross()`:
```cpp
Vector a(1, 2, 3);
Vector b(4, 5, 6);
Vector crossProduct = Vector::cross(a, b); // -3, 6, -3
```
## Кватернион
### Ориентация в трехмерном пространстве
В отличие от позиции и скорости, у ориентации в трехмерном пространстве нет универсального для всех случаев способа представления. В зависимости от задачи ориентация может быть представлена в виде *углов Эйлера*, *матрицы поворота*, *вектора вращения* или *кватерниона*. Рассмотрим используемые в полетной прошивке способы представления ориентации.
### Углы Эйлера
**Углы Эйлера***крен*, *тангаж* и *рыскание* — это наиболее «естественный» для человека способ представления ориентации. Они описывают последовательные вращения объекта вокруг трех осей координат.
В прошивке углы Эйлера сохраняются в обычный объект `Vector` (хоть и, строго говоря, не являются вектором):
* Угол по крену (*roll*) — `vector.x`.
* Угол по тангажу (*pitch*) — `vector.y`.
* Угол по рысканию (*yaw*) — `vector.z`.
Особенности углов Эйлера:
1. Углы Эйлера зависят от порядка применения вращений, то есть существует 6 типов углов Эйлера. Порядок вращений, принятый в Flix (и в роботехнике в целом) — рыскание, тангаж, крен (ZYX).
2. Для некоторых ориентаций углы Эйлера «вырождаются». Так, если объект «смотрит» строго вниз, то угол по рысканию и угол по крену становятся неразличимыми. Эта ситуация называется *gimbal lock* — потеря одной степени свободы.
Ввиду этих особенности для углов Эйлера не существует общих формул для самых базовых задач с ориентациями, таких как применение одного вращения (ориентации) к другому, расчет разницы между ориентациями и подобных. Поэтому в основном углы Эйлера применяются в пользовательском интерфейсе, но редко используются в математических расчетах.
> [!IMPORTANT]
> Для углов Эйлера не существует общих формул для самых базовых операций с ориентациями.
### Axis-angle
Помимо углов Эйлера, любую ориентацию в трехмерном пространстве можно представить в виде вращения вокруг некоторой оси на некоторый угол. В геометрии это доказывается, как **теорема вращения Эйлера**. В таком представлении ориентация задается двумя величинами:
* **Ось вращения** (*axis*) — единичный вектор, определяющий ось вращения.
* **Угол поворота** (*angle* или *θ*) — угол, на который нужно повернуть объект вокруг этой оси.
В Flix ось вращения задается объектом `Vector`, а угол поворота — числом типа `float` в радианах:
```cpp
// Вращение на 45 градусов вокруг оси (1, 2, 3)
Vector axis(1, 2, 3);
float angle = radians(45);
```
Этот способ более удобен для расчетов, чем углы Эйлера, но все еще не является оптимальным.
### Вектор вращения
Если умножить вектор *axis* на угол поворота *θ*, то получится **вектор вращения** (*rotation vector*). Этот вектор играет важную роль в алгоритмах управления ориентацией летательного аппарата.
Вектор вращения обладает замечательным свойством: если угловые скорости объекта (в собственной системе координат) в каждый момент времени совпадают с компонентами этого вектора, то за единичное время объект придет к заданной этим вектором ориентации. Это свойство позволяет использовать вектор вращения для управления ориентацией объекта посредством управления угловыми скоростями.
> [!IMPORTANT]
> Чтобы за единичное время прийти к заданной ориентации, собственные угловые скорости объекта должны быть равны компонентам вектора вращения.
Вектора вращения в Flix представляются в виде объектов `Vector`:
```cpp
// Вращение на 45 градусов вокруг оси (1, 2, 3)
Vector rotation = radians(45) * Vector(1, 2, 3);
```
### Кватернион
<div class="firmware">
<strong>Файл прошивки:</strong>
<a href="https://github.com/okalachev/flix/blob/master/flix/quaternion.h"><code>quaternion.h</code></a>.<br>
</div>
Вектор вращения удобен, но еще удобнее использовать **кватернион**. В Flix кватернионы задаются объектами `Quaternion` из библиотеки `quaternion.h`. Кватернион состоит из четырех значений: *w*, *x*, *y*, *z* и рассчитывается из вектора оси вращения (*axis*) и угла поворота (*θ*) по формуле:
\\[ q = \left( \begin{array}{c} w \\\\ x \\\\ y \\\\ z \end{array} \right) = \left( \begin{array}{c} \cos\left(\frac{\theta}{2}\right) \\\\ axis\_x \cdot \sin\left(\frac{\theta}{2}\right) \\\\ axis\_y \cdot \sin\left(\frac{\theta}{2}\right) \\\\ axis\_z \cdot \sin\left(\frac{\theta}{2}\right) \end{array} \right) \\]
На практике оказывается, что **именно такое представление наиболее удобно для математических расчетов**.
Проиллюстрируем кватернион и описанные выше способы представления ориентации на интерактивной визуализации. Изменяйте угол поворота *θ* с помощью ползунка (ось вращения константна) и изучите, как меняется ориентация объекта, вектор вращения и кватернион:
<div id="rotation-diagram" class="diagram">
<p>
<label class="angle" for="angle-range"></label>
<input type="range" name="angle" id="angle-range" min="0" max="360" value="0" step="1">
</p>
<p class="axis"></p>
<p class="rotation-vector"></p>
<p class="quaternion"></p>
<p class="euler"></p>
</div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.176.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.176.0/examples/jsm/"
}
}
</script>
<script type="module" src="js/rotation.js"></script>
> [!IMPORTANT]
> В контексте управляющих алгоритмов кватернион — это оптимизированный для расчетов аналог вектора вращения.
Кватернион это наиболее часто используемый способ представления ориентации в алгоритмах. Кроме этого, у кватерниона есть большое значение в теории чисел и алгебре, как у расширения понятия комплексного числа, но рассмотрение этого аспекта выходит за рамки описания работы с вращениями с практической точки зрения.
В прошивке в виде кватернионов представлены, например:
* `attitude` — текущая ориентация квадрокоптера.
* `attitudeTarget` — целевая ориентация квадрокоптера.
### Операции с кватернионами
Кватернион создается напрямую из четырех его компонент:
```cpp
// Кватернион, представляющий нулевую (исходную) ориентацию
Quaternion q(1, 0, 0, 0);
```
Кватернион можно создать из оси вращения и угла поворота, вектора вращения или углов Эйлера:
```cpp
Quaternion q1 = Quaternion::fromAxisAngle(axis, angle);
Quaternion q2 = Quaternion::fromRotationVector(rotation);
Quaternion q3 = Quaternion::fromEuler(Vector(roll, pitch, yaw));
```
И наоборот:
```cpp
q1.toAxisAngle(axis, angle);
Vector rotation = q2.toRotationVector();
Vector euler = q3.toEuler();
```
Возможно рассчитать вращение между двумя обычными векторами:
```cpp
Quaternion q = Quaternion::fromBetweenVectors(v1, v2); // в виде кватерниона
Vector rotation = Vector::rotationVectorBetween(v1, v2); // в виде вектора вращения
```
Шорткаты для работы с углом Эйлера по рысканью (удобно для алгоритмов управления полетом):
```cpp
float yaw = q.getYaw();
q.setYaw(yaw);
```
#### Применения вращений
Чтобы применить вращение, выраженное в кватернионе, к другому кватерниону, в математике используется операция **умножения кватернионов**. При использовании этой операции, необходимо учитывать, что она не является коммутативной, то есть порядок операндов имеет значение. Формула умножения кватернионов выглядит так:
\\[ q_1 \times q_2 = \left( \begin{array}{c} w_1 \\\\ x_1 \\\\ y_1 \\\\ z_1 \end{array} \right) \times \left( \begin{array}{c} w_2 \\\\ x_2 \\\\ y_2 \\\\ z_2 \end{array} \right) = \left( \begin{array}{c} w_1 w_2 - x_1 x_2 - y_1 y_2 - z_1 z_2 \\\\ w_1 x_2 + x_1 w_2 + y_1 z_2 - z_1 y_2 \\\\ w_1 y_2 - x_1 z_2 + y_1 w_2 + z_1 x_2 \\\\ w_1 z_2 + x_1 y_2 - y_1 x_2 + z_1 w_2 \end{array} \right) \\]
В библиотеке `quaternion.h` для этой операции используется статический метод `Quaternion::rotate()`:
```cpp
// Композиция вращений q1 и q2
Quaternion result = Quaternion::rotate(q1, q2);
```
Также полезной является операция применения вращения к вектору, которая делается похожим образом:
```cpp
// Вращение вектора v кватернионом q
Vector result = Quaternion::rotateVector(v, q);
```
Для расчета разницы между двумя ориентациями используется метод `Quaternion::between()`:
```cpp
// Расчет вращения от q1 к q2
Quaternion q = Quaternion::between(q1, q2);
```
## Дополнительные материалы
* [Интерактивный учебник по кватернионам](https://eater.net/quaternions).
* [Визуализация вращения вектора с помощью кватернионов](https://quaternions.online).

View File

@@ -1,7 +1,7 @@
# Гироскоп
<div class="firmware">
<strong>Файл прошивки Flix:</strong>
<strong>Файл прошивки:</strong>
<a href="https://github.com/okalachev/flix/blob/canonical/flix/imu.ino"><code>imu.ino</code></a> <small>(каноничная версия)</small>.<br>
Текущая версия: <a href="https://github.com/okalachev/flix/blob/master/flix/imu.ino"><code>imu.ino</code></a>.
</div>
@@ -100,7 +100,7 @@ void setup() {
Для однократного считывания данных используется метод `read()`. Затем данные с гироскопа получаются при помощи метода `getGyro(x, y, z)`. Этот метод записывает в переменные `x`, `y` и `z` угловые скорости вокруг соответствующих осей в радианах в секунду.
Если нужно гарантировать, что будут считаны новые данные, можно использовать метод `waitForData()`. Этот метод блокирует выполнение программы до тех пор, пока в IMU не появятся новые данные. Метод `waitForData()` позволяет привязать частоту главного цикла `loop` к частоте обновления данных IMU. Это удобно для организации главного цикла управления квадрокоптером.
Если нужно гарантировать, что будут считаны новые данные, можно использовать метод `waitForData()`. Этот метод блокирует выполнение программы до тех пор, пока в IMU не появятся новые данные. Метод `waitForData()` позволяет привязать частоту главного цикла `loop` к частоте обновления данных IMU. Это удобно для организации главного цикла управления квадрокоптером.
Программа для чтения данных с гироскопа и вывода их в консоль для построения графиков в Serial Plotter выглядит так:
@@ -139,7 +139,7 @@ void loop() {
### Частота сэмплов
Большинство IMU могут обновлять данные с разной частотой. В полетных контроллерах обычно используется частота обновления от 500 Гц до 8 кГц. Чем выше частота сэмплов, тем выше точность управления полетом, но и больше нагрузка на микроконтроллер. В Flix используется частота сэмплов 1 кГц.
Большинство IMU могут обновлять данные с разной частотой. В полетных контроллерах обычно используется частота обновления от 500 Гц до 8 кГц. Чем выше частота сэмплов, тем выше точность управления полетом, но и больше нагрузка на микроконтроллер.
Частота сэмплов устанавливается методом `setSampleRate()`. В Flix используется частота 1 кГц:
@@ -153,7 +153,7 @@ IMU.setRate(IMU.RATE_1KHZ_APPROX);
* `RATE_MIN` — минимальная частота сэмплов для конкретного IMU.
* `RATE_50HZ_APPROX` — значение, близкое к 50 Гц.
* `RATE_1KHZ_APPROX`  — значение, близкое к 1 кГц.
* `RATE_1KHZ_APPROX` — значение, близкое к 1 кГц.
* `RATE_8KHZ_APPROX` — значение, близкое к 8 кГц.
* `RATE_MAX` — максимальная частота сэмплов для конкретного IMU.

262
docs/book/js/rotation.js Normal file
View File

@@ -0,0 +1,262 @@
import * as THREE from 'three';
import { SVGRenderer, SVGObject } from 'three/addons/renderers/SVGRenderer.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const diagramEl = document.getElementById('rotation-diagram');
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
const camera = new THREE.OrthographicCamera();
camera.position.set(9, 26, 20);
camera.up.set(0, 0, 1);
camera.lookAt(0, 0, 0);
const renderer = new SVGRenderer();
diagramEl.prepend(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableZoom = false;
const LINE_WIDTH = 4;
function createLabel(text, x, y, z, min = false) {
const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
label.setAttribute('class', 'label' + (min ? ' min' : ''));
label.textContent = text;
label.setAttribute('y', -15);
const object = new SVGObject(label);
object.position.x = x;
object.position.y = y;
object.position.z = z;
return object;
}
function createLine(x1, y1, z1, x2, y2, z2, color) {
const geometry = new THREE.BufferGeometry().setFromPoints([
new THREE.Vector3(x1, y1, z1),
new THREE.Vector3(x2, y2, z2)
]);
const material = new THREE.LineBasicMaterial({ color: color, linewidth: LINE_WIDTH, transparent: true, opacity: 0.8 });
const line = new THREE.Line(geometry, material);
scene.add(line);
return line;
}
function changeLine(line, x1, y1, z1, x2, y2, z2) {
line.geometry.setFromPoints([new THREE.Vector3(x1, y1, z1), new THREE.Vector3(x2, y2, z2)]);
return line;
}
function createVector(x1, y1, z1, x2, y2, z2, color, label = '') {
const HEAD_LENGTH = 1;
const HEAD_WIDTH = 0.2;
const group = new THREE.Group();
const direction = new THREE.Vector3(x2 - x1, y2 - y1, z2 - z1).normalize();
const norm = new THREE.Vector3(x2 - x1, y2 - y1, z2 - z1).length();
let end = new THREE.Vector3(x2, y2, z2);
if (norm > HEAD_LENGTH) {
end = new THREE.Vector3(x2 - direction.x * HEAD_LENGTH / 2, y2 - direction.y * HEAD_LENGTH / 2, z2 - direction.z * HEAD_LENGTH / 2);
}
// create line
const geometry = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(x1, y1, z1), end]);
const material = new THREE.LineBasicMaterial({ color: color, linewidth: LINE_WIDTH, transparent: true, opacity: 0.8 });
const line = new THREE.Line(geometry, material);
group.add(line);
if (norm > HEAD_LENGTH) {
// Create arrow
const arrowGeometry = new THREE.ConeGeometry(HEAD_WIDTH, HEAD_LENGTH, 16);
const arrowMaterial = new THREE.MeshBasicMaterial({ color: color });
const arrow = new THREE.Mesh(arrowGeometry, arrowMaterial);
arrow.position.set(x2 - direction.x * HEAD_LENGTH / 2, y2 - direction.y * HEAD_LENGTH / 2, z2 - direction.z * HEAD_LENGTH / 2);
arrow.lookAt(new THREE.Vector3(x1, y1, z1));
arrow.rotateX(-Math.PI / 2);
group.add(arrow);
}
// create label
if (label) group.add(createLabel(label, x2, y2, z2));
scene.add(group);
return group;
}
function changeVector(vector, x1, y1, z1, x2, y2, z2, color, label = '') {
vector.removeFromParent();
return createVector(x1, y1, z1, x2, y2, z2, color, label);
}
function createDrone(x, y, z) {
const group = new THREE.Group();
// Fuselage and wing triangle (main body)
const fuselageGeometry = new THREE.BufferGeometry();
const fuselageVertices = new Float32Array([
1, 0, 0,
-1, 0.6, 0,
-1, -0.6, 0
]);
fuselageGeometry.setAttribute('position', new THREE.BufferAttribute(fuselageVertices, 3));
const fuselageMaterial = new THREE.MeshBasicMaterial({ color: 0xb3b3b3, side: THREE.DoubleSide, transparent: true, opacity: 0.8 });
const fuselage = new THREE.Mesh(fuselageGeometry, fuselageMaterial);
group.add(fuselage);
// Tail triangle
const tailGeometry = new THREE.BufferGeometry();
const tailVertices = new Float32Array([
-0.2, 0, 0,
-1, 0, 0,
-1, 0, 0.5,
]);
tailGeometry.setAttribute('position', new THREE.BufferAttribute(tailVertices, 3));
const tailMaterial = new THREE.MeshBasicMaterial({ color: 0xd80100, side: THREE.DoubleSide, transparent: true, opacity: 0.9 });
const tail = new THREE.Mesh(tailGeometry, tailMaterial);
group.add(tail);
group.position.set(x, y, z);
group.scale.set(2, 2, 2);
scene.add(group);
return group;
}
// Create axes
const AXES_LENGTH = 10;
createVector(0, 0, 0, AXES_LENGTH, 0, 0, 0xd80100, 'x');
createVector(0, 0, 0, 0, AXES_LENGTH, 0, 0x0076ba, 'y');
createVector(0, 0, 0, 0, 0, AXES_LENGTH, 0x57ed00, 'z');
// Rotation values
const rotationAxisSrc = new THREE.Vector3(2, 1, 3);
let rotationAngle = 0;
let rotationAxis = rotationAxisSrc.clone().normalize();
let rotationVector = new THREE.Vector3(rotationAxis.x * rotationAngle, rotationAxis.y * rotationAngle, rotationAxis.z * rotationAngle);
let rotationVectorObj = createVector(0, 0, 0, rotationVector.x, rotationVector.y, rotationVector.z, 0xff9900);
let axisObj = createLine(0, 0, 0, rotationAxis.x * AXES_LENGTH, rotationAxis.y * AXES_LENGTH, rotationAxis.z * AXES_LENGTH, 0xe8e8e8);
const drone = createDrone(0, 0, 0);
// UI
const angleInput = diagramEl.querySelector('input[name=angle]');
const rotationVectorEl = diagramEl.querySelector('.rotation-vector');
const angleEl = diagramEl.querySelector('.angle');
const quaternionEl = diagramEl.querySelector('.quaternion');
const eulerEl = diagramEl.querySelector('.euler');
diagramEl.querySelector('.axis').innerHTML = `<b style='color:#b6b6b6'>Ось вращения:</b> (${rotationAxisSrc.x}, ${rotationAxisSrc.y}, ${rotationAxisSrc.z}) ∥ (${rotationAxis.x.toFixed(1)}, ${rotationAxis.y.toFixed(1)}, ${rotationAxis.z.toFixed(1)})`;
function updateScene() {
rotationAngle = parseFloat(angleInput.value) * Math.PI / 180;
rotationVector.set(rotationAxis.x * rotationAngle, rotationAxis.y * rotationAngle, rotationAxis.z * rotationAngle);
rotationVectorObj = changeVector(rotationVectorObj, 0, 0, 0, rotationVector.x, rotationVector.y, rotationVector.z, 0xff9900);
// rotate drone
drone.rotation.set(0, 0, 0);
drone.rotateOnAxis(rotationAxis, rotationAngle);
// update labels
angleEl.innerHTML = `<b>Угол вращения:</b> ${parseFloat(angleInput.value).toFixed(0)}° = ${(rotationAngle).toFixed(2)} рад`;
rotationVectorEl.innerHTML = `<b style='color:#e49a44'>Вектор вращения:</b> (${rotationVector.x.toFixed(1)}, ${rotationVector.y.toFixed(1)}, ${rotationVector.z.toFixed(1)}) рад`;
let quaternion = new THREE.Quaternion();
quaternion.setFromAxisAngle(rotationAxis, rotationAngle);
quaternionEl.innerHTML = `<b>Кватернион:</b>
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mrow>
<mo>(</mo>
<mrow>
<mi>cos</mi>
<mo>(</mo>
<mfrac>
<mi>${rotationAngle.toFixed(2)}</mi>
<mn>2</mn>
</mfrac>
<mo>)</mo>
</mrow>
<mo>, </mo>
<mrow>
<mi>${rotationAxis.x.toFixed(1)}</mi>
<mo>·</mo>
<mi>sin</mi>
<mo>(</mo>
<mfrac>
<mi>${rotationAngle.toFixed(2)}</mi>
<mn>2</mn>
</mfrac>
<mo>)</mo>
</mrow>
<mo>, </mo>
<mrow>
<mi>${rotationAxis.y.toFixed(1)}</mi>
<mo>·</mo>
<mi>sin</mi>
<mo>(</mo>
<mfrac>
<mi>${rotationAngle.toFixed(2)}</mi>
<mn>2</mn>
</mfrac>
<mo>)</mo>
</mrow>
<mo>,</mo>
<mrow>
<mi>${rotationAxis.z.toFixed(1)}</mi>
<mo>·</mo>
<mi>sin</mi>
<mo>(</mo>
<mfrac>
<mi>${rotationAngle.toFixed(2)}</mi>
<mn>2</mn>
</mfrac>
<mo>)</mo>
</mrow>
<mo>)</mo>
</mrow>
</math>
= (${quaternion.w.toFixed(1)}, ${(quaternion.x).toFixed(1)}, ${(quaternion.y).toFixed(1)}, ${(quaternion.z).toFixed(1)})`;
eulerEl.innerHTML = `<b>Углы Эйлера:</b> крен ${(drone.rotation.x * 180 / Math.PI).toFixed(0)}°,
тангаж ${(drone.rotation.y * 180 / Math.PI).toFixed(0)}°, рыскание ${(drone.rotation.z * 180 / Math.PI).toFixed(0)}°`;
}
function updateCamera() {
const RANGE = 8;
const VERT_SHIFT = 2;
const HOR_SHIFT = -2;
const width = renderer.domElement.clientWidth;
const height = renderer.domElement.clientHeight;
const ratio = width / height;
if (ratio > 1) {
camera.left = -RANGE * ratio;
camera.right = RANGE * ratio;
camera.top = RANGE + VERT_SHIFT;
camera.bottom = -RANGE + VERT_SHIFT;
} else {
camera.left = -RANGE + HOR_SHIFT;
camera.right = RANGE + HOR_SHIFT;
camera.top = RANGE / ratio + VERT_SHIFT;
camera.bottom = -RANGE / ratio + VERT_SHIFT;
}
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}
function update() {
// requestAnimationFrame(update);
updateCamera();
updateScene();
controls.update();
renderer.render(scene, camera);
}
update();
window.addEventListener('resize', update);
angleInput.addEventListener('input', update);
angleInput.addEventListener('change', update);
diagramEl.addEventListener('mousemove', update);
diagramEl.addEventListener('touchmove', update);
diagramEl.addEventListener('scroll', update);
diagramEl.addEventListener('wheel', update);

View File

@@ -1,170 +1,2 @@
# Building and running
To build the firmware or the simulator, you need to clone the repository using git:
```bash
git clone https://github.com/okalachev/flix.git
cd flix
```
## Simulation
### Ubuntu 20.04
The latest version of Ubuntu supported by Gazebo 11 simulator is 20.04. If you have a newer version, consider using a virtual machine.
1. Install Arduino CLI:
```bash
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=~/.local/bin sh
```
2. Install Gazebo 11:
```bash
curl -sSL http://get.gazebosim.org | sh
```
Set up your Gazebo environment variables:
```bash
echo "source /usr/share/gazebo/setup.sh" >> ~/.bashrc
source ~/.bashrc
```
3. Install SDL2 and other dependencies:
```bash
sudo apt-get update && sudo apt-get install build-essential libsdl2-dev
```
4. 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
```
5. Run the simulation:
```bash
make simulator
```
### macOS
1. Install Homebrew package manager, if you don't have it installed:
```bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```
2. Install Arduino CLI, Gazebo 11 and SDL2:
```bash
brew tap osrf/simulation
brew install arduino-cli
brew install gazebo11
brew install sdl2
```
Set up your Gazebo environment variables:
```bash
echo "source /opt/homebrew/share/gazebo/setup.sh" >> ~/.zshrc
source ~/.zshrc
```
3. Run the simulation:
```bash
make simulator
```
### Setup and flight
#### 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.
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!
#### 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. Run the simulation again.
5. Use the USB remote control to fly the drone!
## Firmware
### Arduino IDE (Windows, Linux, macOS)
1. Install [Arduino IDE](https://www.arduino.cc/en/software) (version 2 is recommended).
2. Install ESP32 core, version 3.0.7 (version 2.x is not supported). See the [official Espressif's instructions](https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html#installing-using-arduino-ide) on installing ESP32 Core in Arduino IDE.
3. Install the following libraries using [Library Manager](https://docs.arduino.cc/software/ide-v2/tutorials/ide-v2-installing-a-library):
* `FlixPeriph`, the latest version.
* `MAVLink`, version 2.0.12.
4. Clone the project using git or [download the source code as a ZIP archive](https://codeload.github.com/okalachev/flix/zip/refs/heads/master).
5. Open the downloaded Arduino sketch `flix/flix.ino` in Arduino IDE.
6. [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)
1. [Install Arduino CLI](https://arduino.github.io/arduino-cli/installation/).
2. Windows users might need to install [USB to UART bridge driver from Silicon Labs](https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers).
3. Compile the firmware using `make`. Arduino dependencies will be installed automatically:
```bash
make
```
You can flash the firmware to the board using command:
```bash
make upload
```
You can also compile the firmware, upload it and start serial port monitoring using command:
```bash
make upload monitor
```
See other available Make commands in the [Makefile](../Makefile).
### Setup and flight
Before flight you need to calibrate the accelerometer:
1. Open Serial Monitor in Arduino IDE (use use `make monitor` command in the command line).
2. Type `ca` command there and follow the instructions.
#### 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.
2. Power the drone using the battery.
3. Connect your smartphone to the appeared `flix` Wi-Fi network.
4. Open QGroundControl app. It should connect and begin showing the drone's telemetry automatically.
5. Go to the settings and enable *Virtual Joystick*. *Auto-Center Throttle* setting **should be disabled**.
6. Use the virtual joystick to fly the drone!
#### Control with remote control
Before flight using remote control, you need to calibrate it:
1. Open Serial Monitor in Arduino IDE (use use `make monitor` command in the command line).
2. Type `cr` command there and follow the instructions.
Then you can use your remote control to fly the drone!
> [!NOTE]
> If something goes wrong, go to the [Troubleshooting](troubleshooting.md) article.
### Firmware code structure
See [firmware overview](firmware.md) for more details.
<!-- markdownlint-disable MD041 -->
Build instructions are moved to [usage article](usage.md).

View File

@@ -1,37 +1,67 @@
# Firmware overview
The firmware is a regular Arduino sketch, and it follows the classic Arduino one-threaded design. The initialization code is in the `setup()` function, and the main loop is in the `loop()` function. The sketch includes several files, each responsible for a specific subsystem.
## Dataflow
<img src="img/dataflow.svg" width=800 alt="Firmware dataflow diagram">
<img src="img/dataflow.svg" width=600 alt="Firmware dataflow diagram">
The main loop is running at 1000 Hz. All the dataflow is happening through global variables (for simplicity):
The main loop is running at 1000 Hz. The dataflow goes through global variables, including:
* `t` *(float)* current step time, *s*.
* `t` *(float)* current step time, *s*.
* `dt` *(float)* — time delta between the current and previous steps, *s*.
* `gyro` *(Vector)* — data from the gyroscope, *rad/s*.
* `acc` *(Vector)* — acceleration data from the accelerometer, *m/s<sup>2</sup>*.
* `rates` *(Vector)* — filtered angular rates, *rad/s*.
* `attitude` *(Quaternion)* — estimated attitude (orientation) of drone.
* `controls` *(float[])* user control inputs from the RC, normalized to [-1, 1] range.
* `motors` *(float[])* motor outputs, normalized to [-1, 1] range; reverse rotation is possible.
* `controlRoll`, `controlPitch`, `controlYaw`, `controlThrottle`, `controlMode` *(float)* pilot control inputs, range [-1, 1].
* `motors` *(float[4])* motor outputs, range [0, 1].
## Source files
Firmware source files are located in `flix` directory. The key files are:
Firmware source files are located in `flix` directory.
* [`flix.ino`](../flix/flix.ino) — main entry point, Arduino sketch. Includes global variables definition and the main loop.
* [`flix.ino`](../flix/flix.ino) — Arduino sketch main file, entry point.Includes some global variable definitions and the main loop.
* [`imu.ino`](../flix/imu.ino) — reading data from the IMU sensor (gyroscope and accelerometer), IMU calibration.
* [`rc.ino`](../flix/rc.ino) — reading data from the RC receiver, RC calibration.
* [`estimate.ino`](../flix/estimate.ino) — drone's attitude estimation, complementary filter.
* [`control.ino`](../flix/control.ino) — drone's attitude and rates control, three-dimensional two-level cascade PID controller.
* [`motors.ino`](../flix/motors.ino) — PWM motor outputs control.
* [`estimate.ino`](../flix/estimate.ino) — attitude estimation, complementary filter.
* [`control.ino`](../flix/control.ino) — control subsystem, three-dimensional two-level cascade PID controller.
* [`motors.ino`](../flix/motors.ino) — PWM motor output control.
* [`mavlink.ino`](../flix/mavlink.ino) — interaction with QGroundControl or [pyflix](../tools/pyflix) via MAVLink protocol.
* [`cli.ino`](../flix/cli.ino) — serial and MAVLink console.
Utility files include:
Utility files:
* [`vector.h`](../flix/vector.h), [`quaternion.h`](../flix/quaternion.h) — project's vector and quaternion libraries implementation.
* [`pid.h`](../flix/pid.h) — generic PID controller implementation.
* [`lpf.h`](../flix/lpf.h) — generic low-pass filter implementation.
* [`vector.h`](../flix/vector.h), [`quaternion.h`](../flix/quaternion.h) — vector and quaternion libraries.
* [`pid.h`](../flix/pid.h) — generic PID controller.
* [`lpf.h`](../flix/lpf.h) — generic low-pass filter.
## Building
### Control subsystem
See build instructions in [build.md](build.md).
Pilot inputs are interpreted in `interpretControls()`, and then converted to the *control command*, which consists of the following:
* `attitudeTarget` *(Quaternion)* — target attitude of the drone.
* `ratesTarget` *(Vector)* — target angular rates, *rad/s*.
* `ratesExtra` *(Vector)* — additional (feed-forward) angular rates , used for yaw rate control in STAB mode, *rad/s*.
* `torqueTarget` *(Vector)* — target torque, range [-1, 1].
* `thrustTarget` *(float)* — collective thrust target, range [0, 1].
Control command is handled in `controlAttitude()`, `controlRates()`, `controlTorque()` functions. Each function may be skipped if the corresponding control target is set to `NAN`.
<img src="img/control.svg" width=300 alt="Control subsystem diagram">
Armed state is stored in `armed` variable, and current mode is stored in `mode` variable.
### Console
To write into the console, `print()` function is used. This function sends data both to the Serial console and to the MAVLink console (which can be accessed wirelessly in QGroundControl). The function supports formatting:
```cpp
print("Test value: %.2f\n", testValue);
```
In order to add a console command, modify the `doCommand()` function in `cli.ino` file.
## Building the firmware
See build instructions in [usage.md](usage.md).

BIN
docs/img/arduino-ide.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

22
docs/img/arming.svg Normal file
View File

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 340.21 211.28">
<defs>
<style>
.a {
fill: #d5d5d5;
}
.b {
fill: #fff;
}
.c {
fill: #636363;
}
</style>
</defs>
<path class="a" d="M340,159.31c-4.74-86-35.9-128.7-35.9-128.7C288.3,9.53,269.17,2.91,251.87.39s-22.31,7.87-22.31,7.87C201.7,4,170.11,4.19,170.11,4.19S138.51,4,110.65,8.26c0,0-5-10.38-22.3-7.87S51.91,9.53,36.14,30.61c0,0-31.16,42.67-35.9,128.7-2.82,51.08,19.68,55.36,38.43,50.4a60.08,60.08,0,0,0,30.55-19.66c7.51-9,19.64-25.32,34-28,17.28-3.26,33.14-4.77,45.09-4.78l21.82,0,21.81,0c12,0,27.82,1.52,45.09,4.78,14.34,2.71,26.47,19,34,28a60.06,60.06,0,0,0,30.56,19.66C320.29,214.67,342.79,210.39,340,159.31Z"/>
<circle class="b" cx="88.54" cy="85.75" r="45.22"/>
<circle class="b" cx="251.67" cy="85.75" r="45.22"/>
<circle class="c" cx="251.67" cy="85.75" r="13.8"/>
<circle class="c" cx="103.8" cy="112.12" r="13.8"/>
</svg>

After

Width:  |  Height:  |  Size: 971 B

BIN
docs/img/assembly/1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

BIN
docs/img/assembly/2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
docs/img/assembly/3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

BIN
docs/img/assembly/4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

BIN
docs/img/assembly/5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

BIN
docs/img/assembly/6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
docs/img/assembly/7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

View File

@@ -0,0 +1,94 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 533 646.91">
<defs>
<style>
.a {
font-size: 50px;
font-family: Tahoma;
}
.b {
opacity: 0.8;
}
.c, .e, .g, .i {
fill: none;
}
.c {
stroke: #0076ba;
}
.c, .e, .g {
stroke-linejoin: bevel;
stroke-width: 13px;
}
.d {
fill: #0076ba;
}
.e {
stroke: #d80100;
}
.f {
fill: #d80100;
}
.g {
stroke: #57ed00;
}
.h {
fill: #57ed00;
}
.i {
stroke: #000;
stroke-miterlimit: 10;
stroke-width: 10px;
}
</style>
</defs>
<g>
<text class="a" transform="translate(58.62 636.12)">x</text>
<text class="a" transform="translate(505.06 562.18)">y</text>
<text class="a" transform="translate(370.06 43.18)">z</text>
<g class="b">
<g>
<line class="c" x1="347" y1="420.2" x2="347" y2="61.78"/>
<polygon class="d" points="370.34 68.61 347 28.2 323.66 68.61 370.34 68.61"/>
</g>
</g>
<g class="b">
<g>
<line class="e" x1="347" y1="420.2" x2="29.31" y2="597.81"/>
<polygon class="f" points="23.89 574.11 0 614.2 46.66 614.84 23.89 574.11"/>
</g>
</g>
<g class="b">
<g>
<line class="g" x1="347" y1="420.2" x2="503.22" y2="501.67"/>
<polygon class="h" points="486.38 519.2 533 517.2 507.96 477.82 486.38 519.2"/>
</g>
</g>
<g class="b">
<g>
<path class="i" d="M103.19,617.68a52.66,52.66,0,1,0-55.51-89.19"/>
<polygon points="41.63 516.97 34.76 541.97 59.85 535.42 41.63 516.97"/>
</g>
</g>
<g class="b">
<g>
<path class="i" d="M295.58,87.51a52.66,52.66,0,1,0,103.78,16.31"/>
<polygon points="412.03 106.78 397.6 85.24 386.16 108.51 412.03 106.78"/>
</g>
</g>
<g class="b">
<g>
<path class="i" d="M505,452.58a52.66,52.66,0,1,0-76,72.53"/>
<polygon points="418.96 533.38 444.84 535 433.31 511.78 418.96 533.38"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
docs/img/betafpv.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
docs/img/buck-boost.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
docs/img/cli.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

3
docs/img/control.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

123
docs/img/controls.svg Normal file
View File

@@ -0,0 +1,123 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 464.2 249.05">
<defs>
<style>
.a {
fill: #d5d5d5;
}
.b {
fill: #fff;
}
.c {
fill: #636363;
}
.d {
opacity: 0.7;
}
.e {
fill: none;
stroke: #0076ba;
stroke-linecap: round;
stroke-miterlimit: 10;
stroke-width: 13px;
}
.f {
fill: #0076ba;
}
.g {
font-size: 30px;
font-family: Tahoma;
}
.h {
letter-spacing: 0em;
}
.i {
letter-spacing: -0.01em;
}
.j {
letter-spacing: -0.06em;
}
.k {
letter-spacing: 0em;
}
.l {
letter-spacing: -0.02em;
}
.m {
letter-spacing: 0em;
}
.n {
opacity: 0.6;
}
</style>
</defs>
<path class="a" d="M408.84,197.08c-4.74-86-35.9-128.7-35.9-128.7C357.17,47.3,338,40.68,320.73,38.17S298.43,46,298.43,46C270.57,41.81,239,42,239,42s-31.59-.15-59.45,4.07c0,0-5-10.38-22.31-7.86S120.78,47.3,105,68.38c0,0-31.16,42.68-35.9,128.7-2.82,51.08,19.68,55.36,38.42,50.4a60.06,60.06,0,0,0,30.56-19.66c7.51-9,19.64-25.32,34-28,17.27-3.26,33.14-4.77,45.09-4.78L239,195l21.82,0c11.95,0,27.81,1.52,45.09,4.78,14.34,2.71,26.47,19,34,28a60.08,60.08,0,0,0,30.55,19.66C389.16,252.44,411.66,248.16,408.84,197.08Z"/>
<circle class="b" cx="157.41" cy="123.52" r="45.22"/>
<circle class="b" cx="320.54" cy="123.52" r="45.22"/>
<circle class="c" cx="320.54" cy="123.52" r="13.8"/>
<circle class="c" cx="157.41" cy="149.89" r="13.8"/>
<g class="d">
<g>
<line class="e" x1="157.41" y1="149.89" x2="157.41" y2="49.87"/>
<polygon class="f" points="180.74 56.7 157.41 16.29 134.07 56.7 180.74 56.7"/>
</g>
</g>
<text class="g" transform="translate(38.38 25.91)">Th<tspan class="h" x="34.25" y="0">r</tspan><tspan x="44.91" y="0">o</tspan><tspan class="i" x="61.2" y="0">t</tspan><tspan x="71" y="0">tle</tspan></text>
<g class="d">
<g>
<line class="e" x1="157.41" y1="149.89" x2="82.41" y2="149.89"/>
<polygon class="f" points="89.24 126.56 48.82 149.89 89.24 173.23 89.24 126.56"/>
</g>
</g>
<text class="g" transform="translate(0.18 176.36)"><tspan class="j">Y</tspan><tspan class="h" x="15.37" y="0">a</tspan><tspan x="30.97" y="0">w</tspan></text>
<g class="d">
<g>
<line class="e" x1="320.54" y1="123.52" x2="320.54" y2="50.32"/>
<polygon class="f" points="343.88 57.15 320.54 16.74 297.2 57.15 343.88 57.15"/>
</g>
</g>
<text class="g" transform="translate(336.56 26.36)">P<tspan class="k" x="16.54" y="0">i</tspan><tspan x="23.45" y="0">tch</tspan></text>
<g class="d">
<g>
<line class="e" x1="320.54" y1="123.52" x2="395.54" y2="123.52"/>
<polygon class="f" points="388.71 146.86 429.12 123.52 388.71 100.19 388.71 146.86"/>
</g>
</g>
<text class="g" transform="translate(416.03 160.01)"><tspan class="l">R</tspan><tspan x="18.08" y="0">o</tspan><tspan class="m" x="34.37" y="0">l</tspan><tspan x="41.31" y="0">l</tspan></text>
<g class="d">
<g>
<line class="e" x1="157.41" y1="149.89" x2="213.73" y2="149.89"/>
<polygon class="f" points="206.9 173.23 247.32 149.89 206.9 126.56 206.9 173.23"/>
</g>
</g>
<g class="d">
<g>
<line class="e" x1="320.54" y1="124.52" x2="320.54" y2="197.73"/>
<polygon class="f" points="297.2 190.9 320.54 231.31 343.88 190.9 297.2 190.9"/>
</g>
</g>
<g class="n">
<g>
<line class="e" x1="318.03" y1="123.52" x2="262.32" y2="123.52"/>
<polygon class="f" points="269.14 100.19 228.73 123.52 269.14 146.86 269.14 100.19"/>
</g>
</g>
<g class="d">
<g>
<line class="e" x1="157.41" y1="151.66" x2="157.41" y2="197.73"/>
<polygon class="f" points="134.07 190.9 157.41 231.31 180.74 190.9 134.07 190.9"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 13 KiB

22
docs/img/disarming.svg Normal file
View File

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 340.21 211.28">
<defs>
<style>
.a {
fill: #d5d5d5;
}
.b {
fill: #fff;
}
.c {
fill: #636363;
}
</style>
</defs>
<path class="a" d="M340,159.31c-4.74-86-35.9-128.7-35.9-128.7C288.3,9.53,269.17,2.91,251.87.39s-22.31,7.87-22.31,7.87C201.7,4,170.11,4.19,170.11,4.19S138.51,4,110.65,8.26c0,0-5-10.38-22.3-7.87S51.91,9.53,36.14,30.61c0,0-31.16,42.67-35.9,128.7-2.82,51.08,19.68,55.36,38.43,50.4a60.08,60.08,0,0,0,30.55-19.66c7.51-9,19.64-25.32,34-28,17.28-3.26,33.14-4.77,45.09-4.78l21.82,0,21.81,0c12,0,27.82,1.52,45.09,4.78,14.34,2.71,26.47,19,34,28a60.06,60.06,0,0,0,30.56,19.66C320.29,214.67,342.79,210.39,340,159.31Z"/>
<circle class="b" cx="88.54" cy="85.75" r="45.22"/>
<circle class="b" cx="251.67" cy="85.75" r="45.22"/>
<circle class="c" cx="251.67" cy="85.75" r="13.8"/>
<circle class="c" cx="73.28" cy="112.12" r="13.8"/>
</svg>

After

Width:  |  Height:  |  Size: 971 B

View File

@@ -0,0 +1,136 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 813.79 508.65">
<defs>
<style>
.a, .d, .f, .j, .p {
fill: none;
}
.a {
stroke: #d5d5d5;
stroke-width: 31px;
}
.a, .p {
stroke-miterlimit: 10;
}
.b {
fill: #ff9400;
}
.c {
opacity: 0.8;
}
.d {
stroke: #d80100;
}
.d, .f, .j {
stroke-linejoin: bevel;
stroke-width: 13px;
}
.e {
fill: #d80100;
}
.f {
stroke: #57ed00;
}
.g {
fill: #57ed00;
}
.h {
fill: #c1c1c1;
}
.i {
opacity: 0.12;
}
.j {
stroke: #0076ba;
}
.k {
fill: #0076ba;
}
.l {
font-size: 50px;
font-family: Tahoma;
}
.m {
letter-spacing: -0.01em;
}
.n {
letter-spacing: -0.01em;
}
.o {
letter-spacing: 0em;
}
.p {
stroke: #000;
stroke-width: 10px;
}
</style>
</defs>
<g>
<g>
<line class="a" x1="259.14" y1="432.84" x2="532.46" y2="340.23"/>
<line class="a" x1="311.69" y1="313.16" x2="481.62" y2="461.39"/>
<ellipse class="b" cx="311.35" cy="312.8" rx="88.68" ry="47.94"/>
<ellipse class="b" cx="532.53" cy="340.42" rx="88.68" ry="47.94"/>
<ellipse class="b" cx="479.72" cy="460.7" rx="88.68" ry="47.94"/>
<ellipse class="b" cx="259.21" cy="433.03" rx="88.68" ry="47.94"/>
</g>
<g class="c">
<g>
<line class="d" x1="396.65" y1="386.83" x2="564.66" y2="35.72"/>
<polygon class="e" points="582.76 51.95 579.15 5.42 540.66 31.8 582.76 51.95"/>
</g>
</g>
<g class="c">
<g>
<line class="f" x1="396.77" y1="387.06" x2="69.41" y2="341.09"/>
<polygon class="g" points="79.42 318.93 36.15 336.42 72.93 365.15 79.42 318.93"/>
</g>
</g>
<ellipse class="h" cx="396.36" cy="386.61" rx="47.21" ry="25.52"/>
<path class="i" d="M398,375.67l-14.42,12.95a4.32,4.32,0,0,0,2.35,7.5l16.81,2.11a4.33,4.33,0,0,0,4.8-5l-2.39-15.06A4.32,4.32,0,0,0,398,375.67Z"/>
<g class="c">
<g>
<line class="j" x1="396.77" y1="385.56" x2="396.77" y2="92.64"/>
<polygon class="k" points="420.1 99.47 396.77 59.06 373.43 99.47 420.1 99.47"/>
</g>
</g>
<text class="l" transform="translate(0 292.27)">y/left</text>
<text class="l" transform="translate(268.4 81.58)">z/up</text>
<text class="l" transform="translate(600.99 43.18)">x/<tspan class="m" x="43.87" y="0">f</tspan><tspan x="59.3" y="0">or</tspan><tspan class="n" x="104.47" y="0">w</tspan><tspan x="141.14" y="0">a</tspan><tspan class="o" x="167.38" y="0">r</tspan><tspan x="185.16" y="0">d</tspan></text>
<g class="c">
<g>
<path class="p" d="M470.35,114a52.71,52.71,0,0,1,103.57-2.31,51.67,51.67,0,0,1-1.33,25.92"/>
<polygon points="562.15 128.29 563.93 154.16 585.44 139.69 562.15 128.29"/>
</g>
</g>
<g class="c">
<g>
<path class="p" d="M344.16,164.77a52.66,52.66,0,1,0,103.78,16.31"/>
<polygon points="460.61 184.04 446.18 162.5 434.74 185.76 460.61 184.04"/>
</g>
</g>
<g class="c">
<g>
<path class="p" d="M138.4,411.11a52.71,52.71,0,0,1,18.43-101.94A51.68,51.68,0,0,1,182,315.65"/>
<polygon points="170.73 324.01 196.43 327.44 186.55 303.47 170.73 324.01"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

110
docs/img/drone-axes.svg Normal file
View File

@@ -0,0 +1,110 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 664.49 478.47">
<defs>
<style>
.a, .d, .f, .j {
fill: none;
}
.a {
stroke: #d5d5d5;
stroke-miterlimit: 10;
stroke-width: 31px;
}
.b {
fill: #ff9400;
}
.c {
opacity: 0.8;
}
.d {
stroke: #d80100;
}
.d, .f, .j {
stroke-linejoin: bevel;
stroke-width: 13px;
}
.e {
fill: #d80100;
}
.f {
stroke: #57ed00;
}
.g {
fill: #57ed00;
}
.h {
fill: #c1c1c1;
}
.i {
opacity: 0.12;
}
.j {
stroke: #0076ba;
}
.k {
fill: #0076ba;
}
.l {
font-size: 50px;
font-family: Tahoma;
}
.m {
letter-spacing: -0.01em;
}
.n {
letter-spacing: -0.01em;
}
.o {
letter-spacing: 0em;
}
</style>
</defs>
<g>
<g>
<line class="a" x1="207.39" y1="402.28" x2="480.71" y2="309.67"/>
<line class="a" x1="259.93" y1="282.6" x2="429.86" y2="430.83"/>
<ellipse class="b" cx="259.59" cy="282.24" rx="88.68" ry="47.94"/>
<ellipse class="b" cx="480.77" cy="309.86" rx="88.68" ry="47.94"/>
<ellipse class="b" cx="427.96" cy="430.14" rx="88.68" ry="47.94"/>
<ellipse class="b" cx="207.45" cy="402.47" rx="88.68" ry="47.94"/>
</g>
<g class="c">
<g>
<line class="d" x1="344.89" y1="356.27" x2="422.02" y2="172.47"/>
<polygon class="e" points="440.89 187.79 435.01 141.5 397.86 169.74 440.89 187.79"/>
</g>
</g>
<g class="c">
<g>
<line class="f" x1="345.01" y1="356.5" x2="79.27" y2="319.17"/>
<polygon class="g" points="89.28 297.01 46.01 314.5 82.78 343.23 89.28 297.01"/>
</g>
</g>
<ellipse class="h" cx="344.6" cy="356.05" rx="47.21" ry="25.52"/>
<path class="i" d="M346.25,345.11l-14.41,12.95a4.32,4.32,0,0,0,2.34,7.5L351,367.67a4.33,4.33,0,0,0,4.8-5l-2.39-15.06A4.31,4.31,0,0,0,346.25,345.11Z"/>
<g class="c">
<g>
<line class="j" x1="345.01" y1="355" x2="345.01" y2="62.08"/>
<polygon class="k" points="368.35 68.91 345.01 28.5 321.67 68.91 368.35 68.91"/>
</g>
</g>
<text class="l" transform="translate(-0.76 281.71)">y/left</text>
<text class="l" transform="translate(371.65 38.02)">z/up</text>
<text class="l" transform="translate(455.23 152.62)">x/<tspan class="m" x="43.87" y="0">f</tspan><tspan x="59.3" y="0">or</tspan><tspan class="n" x="104.47" y="0">w</tspan><tspan x="141.14" y="0">a</tspan><tspan class="o" x="167.38" y="0">r</tspan><tspan x="185.16" y="0">d</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
docs/img/flix1.1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

BIN
docs/img/gy-521.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

67
docs/img/left-axes.svg Normal file
View File

@@ -0,0 +1,67 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 533 646.68">
<defs>
<style>
.a {
font-size: 50px;
font-family: Tahoma;
}
.b {
opacity: 0.8;
}
.c, .e, .g {
fill: none;
stroke-linejoin: bevel;
stroke-width: 13px;
}
.c {
stroke: #0076ba;
}
.d {
fill: #0076ba;
}
.e {
stroke: #57ed00;
}
.f {
fill: #57ed00;
}
.g {
stroke: #d80100;
}
.h {
fill: #d80100;
}
</style>
</defs>
<g>
<text class="a" transform="translate(500.62 556.12)">x</text>
<text class="a" transform="translate(370.06 43.18)">z</text>
<g class="b">
<g>
<line class="c" x1="347" y1="420.2" x2="347" y2="61.78"/>
<polygon class="d" points="370.34 68.61 347 28.2 323.66 68.61 370.34 68.61"/>
</g>
</g>
<g class="b">
<g>
<line class="e" x1="347" y1="420.2" x2="29.31" y2="597.81"/>
<polygon class="f" points="23.89 574.11 0 614.2 46.66 614.84 23.89 574.11"/>
</g>
</g>
<g class="b">
<g>
<line class="g" x1="347" y1="420.2" x2="503.22" y2="501.67"/>
<polygon class="h" points="486.38 519.2 533 517.2 507.96 477.82 486.38 519.2"/>
</g>
</g>
<text class="a" transform="translate(58.06 635.89)">y</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
docs/img/logitech.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 46 KiB

BIN
docs/img/motor-tape.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

89
docs/img/motors.svg Normal file
View File

@@ -0,0 +1,89 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 356.79 357.11">
<defs>
<style>
.a, .e {
fill: none;
stroke-miterlimit: 10;
}
.a {
stroke: #d5d5d5;
stroke-width: 31px;
}
.b {
fill: #c1c1c1;
}
.c {
fill: #ff9400;
}
.d {
opacity: 0.12;
}
.e {
stroke: #cc5200;
stroke-width: 8px;
}
.f {
fill: #cc5200;
}
.g {
font-size: 50px;
fill: #fff;
}
.g, .h {
font-family: Tahoma;
}
.h {
font-size: 20px;
}
.i {
letter-spacing: 0em;
}
</style>
</defs>
<g>
<g>
<line class="a" x1="77.4" y1="278.67" x2="277.4" y2="77.67"/>
<line class="a" x1="78.4" y1="78.67" x2="278.4" y2="279.67"/>
<circle class="b" cx="177.9" cy="178.17" r="41.5"/>
<circle class="c" cx="77.97" cy="78.17" r="77.95"/>
<circle class="c" cx="277.52" cy="77.95" r="77.95"/>
</g>
<path class="d" d="M174.29,163.6l-8.45,26.05A4.32,4.32,0,0,0,170,195.3h16.9a4.32,4.32,0,0,0,4.11-5.65l-8.45-26.05A4.32,4.32,0,0,0,174.29,163.6Z"/>
<g>
<path class="e" d="M307.47,122.53a52.66,52.66,0,1,0-72.08-76"/>
<polygon class="f" points="228.68 38.51 227.44 59.21 245.99 49.94 228.68 38.51"/>
</g>
<g>
<path class="e" d="M48.11,122.22a52.66,52.66,0,1,1,72.08-75.95"/>
<polygon class="f" points="109.59 49.63 128.14 58.91 126.9 38.2 109.59 49.63"/>
</g>
<text class="g" transform="translate(64.89 98.77)">3</text>
<text class="g" transform="translate(260.92 98.8)">2</text>
<text class="h" transform="translate(66.06 129.25)">p<tspan class="i" x="11.05" y="0">r</tspan><tspan x="18.16" y="0">op A</tspan></text>
<text class="h" transform="translate(232.55 128.95)">p<tspan class="i" x="11.05" y="0">r</tspan><tspan x="18.16" y="0">op B</tspan></text>
<circle class="c" cx="278.83" cy="277.92" r="77.95"/>
<g>
<path class="e" d="M249,322a52.66,52.66,0,1,1,72.09-76"/>
<polygon class="f" points="310.45 249.38 329 258.66 327.76 237.95 310.45 249.38"/>
</g>
<text class="g" transform="translate(265.76 298.52)">1</text>
<text class="h" transform="translate(266.92 329.01)">p<tspan class="i" x="11.05" y="0">r</tspan><tspan x="18.16" y="0">op A</tspan></text>
<circle class="c" cx="77.95" cy="277.92" r="77.95"/>
<g>
<path class="e" d="M107.9,322.49a52.66,52.66,0,1,0-72.08-76"/>
<polygon class="f" points="29.11 238.47 27.87 259.18 46.42 249.91 29.11 238.47"/>
</g>
<text class="g" transform="translate(61.35 298.76)">0</text>
<text class="h" transform="translate(32.98 328.92)">p<tspan class="i" x="11.05" y="0">r</tspan><tspan x="18.16" y="0">op B</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
docs/img/mx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/img/parameters.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
docs/img/qgc-attitude.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/img/qgc-proxy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

67
docs/img/right-axes.svg Normal file
View File

@@ -0,0 +1,67 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 533 646.91">
<defs>
<style>
.a {
opacity: 0.8;
}
.b, .d, .f {
fill: none;
stroke-linejoin: bevel;
stroke-width: 13px;
}
.b {
stroke: #57ed00;
}
.c {
fill: #57ed00;
}
.d {
stroke: #d80100;
}
.e {
fill: #d80100;
}
.f {
stroke: #0076ba;
}
.g {
fill: #0076ba;
}
.h {
font-size: 50px;
font-family: Tahoma;
}
</style>
</defs>
<g>
<g class="a">
<g>
<line class="b" x1="347" y1="420.2" x2="503.22" y2="501.67"/>
<polygon class="c" points="486.38 519.2 533 517.2 507.96 477.82 486.38 519.2"/>
</g>
</g>
<g class="a">
<g>
<line class="d" x1="347" y1="420.2" x2="29.31" y2="597.81"/>
<polygon class="e" points="23.89 574.11 0 614.2 46.66 614.84 23.89 574.11"/>
</g>
</g>
<g class="a">
<g>
<line class="f" x1="347" y1="420.2" x2="347" y2="61.78"/>
<polygon class="g" points="370.34 68.61 347 28.2 323.66 68.61 370.34 68.61"/>
</g>
</g>
<text class="h" transform="translate(58.62 636.12)">x</text>
<text class="h" transform="translate(505.06 562.18)">y</text>
<text class="h" transform="translate(370.06 43.18)">z</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/img/simulator1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
docs/img/user/chkroko/1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/img/user/chkroko/2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

BIN
docs/img/user/rudpa/1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

BIN
docs/img/user/rudpa/2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
docs/img/user/rudpa/3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
docs/img/user/user.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

BIN
docs/img/user/yi_lun/1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Some files were not shown because too many files have changed in this diff Show More