Compare commits

...

206 Commits
v1.0 ... master

Author SHA1 Message Date
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
Oleg Kalachev
fd30027ea4 Support AUTOPILOT_VERSION message request to make qgc connection faster
Don't have to wait until the request is timed out.
2024-12-23 17:59:35 +03:00
Oleg Kalachev
6f190295cf Fix building article regarding new parameters subsystem 2024-12-23 13:59:44 +03:00
Oleg Kalachev
ae349fb73c Implement parameters subsystem
* Unified parameters storage.
* Store parameters in flash on the hardware.
* Store parameters in text file in simulation.
* Work with parameters in command line.
* Support parameters in MAVLink for working with parameters in QGC.
2024-12-23 13:00:02 +03:00
Oleg Kalachev
28f6cfff60 Fix SBUS simulation logic
Don't consider zero values from not connected joystick
2024-12-23 04:04:00 +03:00
Oleg Kalachev
7533a9cbfa Move ONE_G definition to flix.ino 2024-12-23 02:37:03 +03:00
Oleg Kalachev
3cc3014ca0 Improve logic of passing channels data in simulated SBUS
Return the data the same way as on the real drone without touching channels global vairable
2024-12-23 02:04:22 +03:00
Oleg Kalachev
b6286a50b2 Minor change 2024-12-23 02:01:55 +03:00
Oleg Kalachev
4f2cf0c0b1 Don't let throttle be less than 0 in failsafe 2024-12-23 01:32:25 +03:00
Oleg Kalachev
f06a9301df Add notice on removing props in motor test commands in help 2024-12-23 01:14:05 +03:00
Oleg Kalachev
41cde3261a Minor troubleshooting article fix 2024-12-21 13:53:04 +03:00
Oleg Kalachev
f54da5bf42 Add CLI command for rebooting the drone 2024-12-20 20:59:59 +03:00
Oleg Kalachev
d01d5b7ecb Improve Markdown linting
* Move .markdownlint to the root so it applies to the main readme.
* Improve .markdownlint, enable proper names checks.
* Use markdownlint-cli2 instead of markdownlint-cli as it's more compatible with VSCode extension.
2024-12-17 17:16:19 +03:00
Oleg Kalachev
0608765347 Add link to textbook website to readme 2024-12-17 11:27:14 +03:00
Oleg Kalachev
b70d16c1f7 Update deploy-pages version to fix website deploy 2024-12-16 12:13:36 +03:00
Oleg Kalachev
f7253bed70 Temporarily disable macOS simulation build 2024-12-16 11:59:49 +03:00
Oleg Kalachev
9957205d8f Fix website deploy 2024-12-16 11:58:35 +03:00
Oleg Kalachev
8440ddd3ee
Create book and deploy it to the website (#6)
* Create book structure.
* Add workflow for linting the markdown using markdownlint.
* Add workflow for building the book with mdBook and deploying to the website.
* Restyle mdBook and support GitHub-style alerts.
* Add images zooming.
* Add index, firmware structure and gyroscope articles.
2024-12-16 11:53:43 +03:00
Oleg Kalachev
66ba9518ae Minor readme fix 2024-12-16 11:30:53 +03:00
Oleg Kalachev
d273b77ce2 Bring back macOS simulation build in Actions 2024-12-12 09:07:09 +03:00
Oleg Kalachev
77effa5577 Rotate IMU data to support standard axes orientation in new FlixPeriph 2024-12-11 06:17:37 +03:00
Oleg Kalachev
fcb426a16f Update MAVLink-Arduino to 2.0.11 2024-12-09 08:04:36 +03:00
Oleg Kalachev
eea1a6a83c Minor fix in troubleshooting article 2024-12-08 07:40:24 +03:00
Oleg Kalachev
9d470cbdfa Add info on required version of Ubuntu for the simulation 2024-12-05 09:28:44 +03:00
Oleg Kalachev
6e140d673c Cleanup unused utility functions 2024-12-05 08:29:21 +03:00
Oleg Kalachev
c75760e9e6 Some readme change regarding using different IMU board 2024-12-04 23:00:26 +03:00
Oleg Kalachev
172b6becc6 Use new FlixPeriph library with ICM-20948 support 2024-12-04 14:41:23 +03:00
Oleg Kalachev
475e9a87ba Configure IMU before calibrating the gyro which improves calibration 2024-12-04 12:25:07 +03:00
Oleg Kalachev
ea141f851f Use 'loop rate' term instead of misleading 'loop frequency' 2024-12-04 07:00:00 +03:00
Oleg Kalachev
7fa3baa76a Add some minor clarification under the IMU orientation picture 2024-11-30 04:59:04 +03:00
Oleg Kalachev
2c5eac92ea Add diagram for IMU orientation 2024-11-29 10:14:11 +03:00
Oleg Kalachev
048a3c6375 Use the new UART2 pins for RC by default
To make it consistent with the documentation
2024-11-27 23:02:20 +03:00
Oleg Kalachev
a65ec946c0 Update ESP32 core to 3.0.7 2024-11-24 01:45:41 +03:00
Oleg Kalachev
429aecbbad Temporarily disable macOS simulation build in CI 2024-11-24 01:08:03 +03:00
Oleg Kalachev
a7b69f99d0 Fix non-working motor control commands 2024-11-24 00:17:47 +03:00
Oleg Kalachev
b015c15a7e Remove non-working fullmot command 2024-11-24 00:10:37 +03:00
Oleg Kalachev
7a2f2d955b Minor fix to the troubleshooting 2024-11-23 18:18:19 +03:00
Oleg Kalachev
c611549f67 Update link to the troubleshooting article 2024-11-23 18:16:46 +03:00
Oleg Kalachev
be3c5bf312 Add troubleshooting article 2024-11-23 18:13:41 +03:00
Oleg Kalachev
f6ddeb4689 Clarify GY-91 pin names 2024-11-12 21:02:47 +03:00
Oleg Kalachev
f6006d3305 Fix c_cpp_properties.json to match updated ESP32 core version 2024-11-04 16:35:22 +03:00
Oleg Kalachev
eca48c6546 Minor fix 2024-11-04 16:28:54 +03:00
Oleg Kalachev
cd5f6721dc Updates to LED control code
Don't call digitaWrite on each setLED call
2024-11-04 16:28:43 +03:00
Oleg Kalachev
e7445599cc Update core and libraries to the most recent versions 2024-11-04 16:28:13 +03:00
Oleg Kalachev
6327585754 Print accel calibration parameters in more convenient way 2024-11-04 14:37:05 +03:00
Oleg Kalachev
ec832d4e37 Implement RC fail-safe 2024-11-04 11:51:17 +03:00
Oleg Kalachev
2fdad7bdb6 Remove LED horizontality signalization
It's better to control the attitude estimation using QGC
2024-11-03 17:41:13 +03:00
Oleg Kalachev
c5c889679b Fix simulation build 2024-10-31 19:27:27 +03:00
Oleg Kalachev
ad2c64625c Print the IMU information in imu command 2024-10-31 10:24:00 +03:00
Oleg Kalachev
39d4f39932 Some updates in docs 2024-10-30 09:45:27 +03:00
Oleg Kalachev
57fe3fef2a Upload STEP files for models 2024-10-29 14:18:03 +03:00
Oleg Kalachev
4ba9accf4b Fix image for washer-m3 model 2024-10-29 14:04:42 +03:00
144 changed files with 15532 additions and 624 deletions

View File

@ -5,6 +5,7 @@ on:
branches: [ '*' ]
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
build_linux:
@ -14,7 +15,16 @@ 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 without Wi-Fi
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
@ -43,7 +53,7 @@ jobs:
run: python3 tools/check_c_cpp_properties.py
build_simulator:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Install Arduino CLI
uses: arduino/setup-arduino-cli@v1.1.1
@ -54,7 +64,7 @@ jobs:
run: sudo apt-get install 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
@ -62,6 +72,7 @@ jobs:
build_simulator_macos:
runs-on: macos-latest
if: github.event_name == 'workflow_dispatch'
steps:
- name: Install Arduino CLI
run: brew install arduino-cli

51
.github/workflows/docs.yml vendored Normal file
View File

@ -0,0 +1,51 @@
name: Docs
on:
push:
branches: [ '*' ]
pull_request:
branches: [ master ]
permissions:
contents: read
pages: write
id-token: write
jobs:
markdownlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install markdownlint
run: npm install -g markdownlint-cli2
- name: Run markdownlint
run: markdownlint-cli2 "**/*.md"
build_book:
runs-on: ubuntu-latest
needs: markdownlint
steps:
- uses: actions/checkout@v4
- name: Install mdBook
run: cargo install mdbook --vers 0.4.43 --locked
- name: Build book
run: cd docs && mdbook build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/build
deploy:
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
concurrency:
group: "pages"
cancel-in-progress: true
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build_book
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

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:

2
.gitignore vendored
View File

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

68
.markdownlint.json Normal file
View File

@ -0,0 +1,68 @@
{
"MD004": {
"style": "asterisk"
},
"MD010": false,
"MD013": false,
"MD024": false,
"MD033": false,
"MD034": false,
"MD044": {
"html_elements": false,
"code_blocks": false,
"names": [
"FlixPeriph",
"Wi-Fi",
"STM",
"Li-ion",
"GitHub",
"github.com",
"PPM",
"PWM",
"Futaba",
"S.Bus",
"C++",
"PID",
"Arduino IDE",
"Arduino",
"Arduino Nano",
"ESP32",
"IMU",
"MEMS",
"imu.ino",
"InvenSense",
"MPU-6050",
"MPU-9250",
"GY-91",
"GY-521",
"ICM-20948",
"Linux",
"Windows",
"macOS",
"iOS",
"Android",
"Bluetooth",
"GPS",
"GPIO",
"USB",
"SPI",
"I²C",
"UART",
"GND",
"3V3",
"VCC",
"SCL",
"SDA",
"SAO",
"AD0",
"MOSI",
"MISO",
"NCS",
"MOSFET",
"ArduPilot",
"Betaflight",
"PX4"
]
},
"MD045": false
}

View File

@ -5,18 +5,18 @@
"includePath": [
"${workspaceFolder}/flix",
"${workspaceFolder}/gazebo",
"~/.arduino15/packages/esp32/hardware/esp32/3.0.3/cores/esp32",
"~/.arduino15/packages/esp32/hardware/esp32/3.0.3/libraries/**",
"~/.arduino15/packages/esp32/hardware/esp32/3.0.3/variants/d1_mini32",
"~/.arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.1-dc859c1e67/esp32/**",
"~/.arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.1-dc859c1e67/esp32/dio_qspi/include",
"~/.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/**"
],
"forcedInclude": [
"${workspaceFolder}/.vscode/intellisense.h",
"~/.arduino15/packages/esp32/hardware/esp32/3.0.3/cores/esp32/Arduino.h",
"~/.arduino15/packages/esp32/hardware/esp32/3.0.3/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,10 +28,10 @@
"${workspaceFolder}/flix/motors.ino",
"${workspaceFolder}/flix/rc.ino",
"${workspaceFolder}/flix/time.ino",
"${workspaceFolder}/flix/util.ino",
"${workspaceFolder}/flix/wifi.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": [
@ -51,19 +51,19 @@
"name": "Mac",
"includePath": [
"${workspaceFolder}/flix",
"${workspaceFolder}/gazebo",
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.0.3/cores/esp32",
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.0.3/libraries/**",
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.0.3/variants/d1_mini32",
"~/Library/Arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.1-dc859c1e67/esp32/include/**",
"~/Library/Arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.1-dc859c1e67/esp32/dio_qspi/include",
// "${workspaceFolder}/gazebo",
"~/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/**"
],
"forcedInclude": [
"${workspaceFolder}/.vscode/intellisense.h",
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.0.3/cores/esp32/Arduino.h",
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.0.3/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",
@ -75,10 +75,10 @@
"${workspaceFolder}/flix/motors.ino",
"${workspaceFolder}/flix/rc.ino",
"${workspaceFolder}/flix/time.ino",
"${workspaceFolder}/flix/util.ino",
"${workspaceFolder}/flix/wifi.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": [
@ -100,17 +100,17 @@
"includePath": [
"${workspaceFolder}/flix",
"${workspaceFolder}/gazebo",
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.0.3/cores/esp32",
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.0.3/libraries/**",
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.0.3/variants/d1_mini32",
"~/AppData/Local/Arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.1-dc859c1e67/esp32/**",
"~/AppData/Local/Arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.1-dc859c1e67/esp32/dio_qspi/include",
"~/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.3/cores/esp32/Arduino.h",
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.0.3/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",
@ -122,10 +122,10 @@
"${workspaceFolder}/flix/motors.ino",
"${workspaceFolder}/flix/rc.ino",
"${workspaceFolder}/flix/time.ino",
"${workspaceFolder}/flix/util.ino",
"${workspaceFolder}/flix/wifi.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

@ -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.3 --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.10
arduino-cli lib install "MAVLink"@2.0.16
touch .dependencies
gazebo/build cmake: gazebo/CMakeLists.txt

121
README.md
View File

@ -4,74 +4,89 @@
<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 and videos for students on writing a flight controller¹.*
* *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.
* Control using remote control or smartphone.
* Precise simulation with Gazebo.
* Wi-Fi and MAVLink support.
* Wireless command line interface and analyzing.
* 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.
See the [user builds gallery](docs/user.md).
<a href="https://t.me/opensourcequadcopter/42"><img width=500 src="docs/img/flight-video.jpg"></a>
<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).
## Articles
## Components (version 1)
* [Assembly instructions](docs/assembly.md).
* [Building and running the code](docs/build.md).
* [Troubleshooting](docs/troubleshooting.md).
* [Firmware architecture overview](docs/firmware.md).
* [Python library tutorial](tools/pyflix/README.md).
* [Log analysis](docs/log.md).
* [User builds gallery](docs/user.md).
## Components
|Type|Part|Image|Quantity|
|-|-|:-:|:-:|
|Microcontroller board|ESP32 Mini|<img src="docs/img/esp32.jpg" width=100>|1|
|IMU and barometer² board|GY-91 (or other MPU-9250 board)|<img src="docs/img/gy-91.jpg" width=100>|1|
|Motor|8520 3.7V brushed motor (**shaft 0.8mm!**)|<img src="docs/img/motor.jpeg" width=100>|4|
|IMU (and barometer²) board|GY91, MPU-9265 (or other MPU9250/MPU6500 board)<br>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|
|<span style="background:yellow">(Recommended) Buck-boost converter</span>|To be determined, output 5V or 3.3V, see [user-contributed schematics](https://miro.com/app/board/uXjVN-dTjoo=/?moveToWidget=3458764612179508274&cot=14)|<img src="docs/img/buck-boost.jpg" width=100>|1|
|Motor|8520 3.7V brushed motor (shaft 0.8mm).<br>Motor with exact 3.7V voltage is needed, not ranged working voltage (3.7V — 6V).|<img src="docs/img/motor.jpeg" width=100>|4|
|Propeller|Hubsan 55 mm|<img src="docs/img/prop.jpg" width=100>|4|
|MOSFET (transistor)|100N03A or [compatible](https://t.me/opensourcequadcopter/33)|<img src="docs/img/100n03a.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: [`flix-frame.stl`](docs/assets/flix-frame.stl)|<img src="docs/img/frame1.jpg" width=100>|1|
|Frame top part|3D printed: [`esp32-holder.stl`](docs/assets/esp32-holder.stl)|<img src="docs/img/esp32-holder.jpg" width=100>|1|
|Washer for IMU board mounting|3D printed: [`washer-m3.stl`](docs/assets/washer-m3.stl)|<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⁴:<br>[`flix-frame-1.1.stl`](docs/assets/flix-frame-1.1.stl) [`flix-frame-1.1.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:<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>|2|
|*RC transmitter (optional)*|*KINGKONG TINY X8 (warning: lacks USB support) 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|
|Wires|28 AWG recommended|<img src="docs/img/wire-28awg.jpg" width=100>||
|Tape, double-sided tape||||
*² — barometer is not used for now.*<br>
*³ — you may use any transmitter-receiver pair with SBUS interface.*
*³ — change `MPU9250` to `ICM20948` in `imu.ino` file if using ICM-20948 board.*<br>
*³⁻¹ — MPU-6050 supports I²C interface only (not recommended). To use it change IMU declaration to `MPU6050 IMU(Wire)`.*<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.*
Tools required for assembly:
@ -83,7 +98,7 @@ Tools required for assembly:
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)).
## Schematics (version 1)
## Schematics
### Simplified connection diagram
@ -93,20 +108,22 @@ 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.
See [assembly guide](docs/assembly.md) for instructions on assembling the drone.
### Notes
* Power ESP32 Mini with Li-Po battery using VCC (+) and GND (-) pins.
* Connect the GY-91 board to the ESP32 Mini using VSPI, power it using 3.3V and GND pins:
* Connect the IMU board to the ESP32 Mini using VSPI, power it using 3.3V and GND pins:
|GY-91 pin|ESP32 pin|
|IMU pin|ESP32 pin|
|-|-|
|GND|GND|
|3.3V|3.3V|
|SCK|SVP (GPIO18)|
|MOSI|GPIO23|
|MISO|GPIO19|
|SCL *(SCK)*|SVP (GPIO18)|
|SDA *(MOSI)*|GPIO23|
|SAO *(MISO)*|GPIO19|
|NCS|GPIO5|
* Solder pull-down resistors to the MOSFETs.
@ -114,10 +131,10 @@ Complete diagram is Work-in-Progress.
|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 0|Rear left|Counter-clockwise|Black & White|GPIO12 (*TDI*)|
|Motor 1|Rear right|Clockwise|Blue & Red|GPIO13 (*TCK*)|
|Motor 2|Front right|Counter-clockwise|Black & White|GPIO14 (*TMS*)|
|Motor 3|Front left|Clockwise|Blue & Red|GPIO15 (*TD0*)|
Counter-clockwise motors have black and white wires and clockwise motors have blue and red wires.
@ -126,14 +143,20 @@ Complete diagram is Work-in-Progress.
|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.*
## Version 0
### IMU placement
See the information on the obsolete version 0 in the [corresponding article](docs/version0.md).
Default IMU orientation in the code is **LFD** (Left-Forward-Down):
<img src="docs/img/gy91-lfd.svg" width=400 alt="GY-91 axes">
In case of using other IMU orientation, modify the `rotateIMU` function in the `imu.ino` file.
See [FlixPeriph documentation](https://github.com/okalachev/flixperiph?tab=readme-ov-file#imu-axes-orientation) to learn axis orientation of other IMU boards.
## Materials
@ -142,3 +165,11 @@ Subscribe to the Telegram channel on developing the drone and the flight control
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/.
See the information on the obsolete version 0 in the [corresponding article](docs/version0.md).
## Disclaimer
This is a fun 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. Theres no guarantee that it will work perfectly — or even work at all.
⚠️ 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

10
docs/Makefile Normal file
View File

@ -0,0 +1,10 @@
build:
mdbook build
serve:
mdbook serve
clean:
mdbook clean
.PHONY: build serve clean

31
docs/alerts.py Normal file
View File

@ -0,0 +1,31 @@
# https://rust-lang.github.io/mdBook/format/configuration/preprocessors.html
# https://rust-lang.github.io/mdBook/for_developers/preprocessors.html
import json
import sys
import re
def transform_markdown_to_html(markdown_text):
def replace_blockquote(match):
tag = match.group(1).lower()
content = match.group(2).strip().replace('\n> ', ' ')
return f'<div class="alert alert-{tag}">{content}</div>\n'
pattern = re.compile(r'> \[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\n>(.*?)\n?(?=(\n[^>]|\Z))', re.DOTALL)
transformed_text = pattern.sub(replace_blockquote, markdown_text)
return transformed_text
if __name__ == '__main__':
if len(sys.argv) > 1:
if sys.argv[1] == 'supports':
sys.exit(0)
context, book = json.load(sys.stdin)
for section in book['sections']:
if 'Chapter' in section:
section['Chapter']['content'] = transform_markdown_to_html(section['Chapter']['content'])
print(json.dumps(book))

29
docs/assembly.md Normal file
View File

@ -0,0 +1,29 @@
# 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>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

5113
docs/assets/flix-frame.step Normal file

File diff suppressed because it is too large Load Diff

200
docs/assets/washer-m3.step Normal file
View File

@ -0,0 +1,200 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(
/* description */ (''),
/* implementation_level */ '2;1');
FILE_NAME(
/* name */ 'washer-m3.step',
/* time_stamp */ '2024-10-29T13:59:42+03:00',
/* author */ (''),
/* organization */ (''),
/* preprocessor_version */ '',
/* originating_system */ '',
/* authorisation */ '');
FILE_SCHEMA (('AUTOMOTIVE_DESIGN { 1 0 10303 214 3 1 1 }'));
ENDSEC;
DATA;
#10=MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION('',(#13),#125);
#11=SHAPE_REPRESENTATION_RELATIONSHIP('SRR','None',#132,#12);
#12=ADVANCED_BREP_SHAPE_REPRESENTATION('',(#14),#124);
#13=STYLED_ITEM('',(#141),#14);
#14=MANIFOLD_SOLID_BREP('Body1',#65);
#15=FACE_BOUND('',#26,.T.);
#16=FACE_BOUND('',#28,.T.);
#17=PLANE('',#85);
#18=PLANE('',#86);
#19=FACE_OUTER_BOUND('',#23,.T.);
#20=FACE_OUTER_BOUND('',#24,.T.);
#21=FACE_OUTER_BOUND('',#25,.T.);
#22=FACE_OUTER_BOUND('',#27,.T.);
#23=EDGE_LOOP('',(#47,#48,#49,#50));
#24=EDGE_LOOP('',(#51,#52,#53,#54));
#25=EDGE_LOOP('',(#55));
#26=EDGE_LOOP('',(#56));
#27=EDGE_LOOP('',(#57));
#28=EDGE_LOOP('',(#58));
#29=LINE('',#112,#31);
#30=LINE('',#118,#32);
#31=VECTOR('',#93,1.7);
#32=VECTOR('',#100,2.7);
#33=CIRCLE('',#80,1.7);
#34=CIRCLE('',#81,1.7);
#35=CIRCLE('',#83,2.7);
#36=CIRCLE('',#84,2.7);
#37=VERTEX_POINT('',#109);
#38=VERTEX_POINT('',#111);
#39=VERTEX_POINT('',#115);
#40=VERTEX_POINT('',#117);
#41=EDGE_CURVE('',#37,#37,#33,.T.);
#42=EDGE_CURVE('',#37,#38,#29,.T.);
#43=EDGE_CURVE('',#38,#38,#34,.T.);
#44=EDGE_CURVE('',#39,#39,#35,.T.);
#45=EDGE_CURVE('',#39,#40,#30,.T.);
#46=EDGE_CURVE('',#40,#40,#36,.T.);
#47=ORIENTED_EDGE('',*,*,#41,.F.);
#48=ORIENTED_EDGE('',*,*,#42,.T.);
#49=ORIENTED_EDGE('',*,*,#43,.T.);
#50=ORIENTED_EDGE('',*,*,#42,.F.);
#51=ORIENTED_EDGE('',*,*,#44,.F.);
#52=ORIENTED_EDGE('',*,*,#45,.T.);
#53=ORIENTED_EDGE('',*,*,#46,.T.);
#54=ORIENTED_EDGE('',*,*,#45,.F.);
#55=ORIENTED_EDGE('',*,*,#44,.T.);
#56=ORIENTED_EDGE('',*,*,#41,.T.);
#57=ORIENTED_EDGE('',*,*,#46,.F.);
#58=ORIENTED_EDGE('',*,*,#43,.F.);
#59=CYLINDRICAL_SURFACE('',#79,1.7);
#60=CYLINDRICAL_SURFACE('',#82,2.7);
#61=ADVANCED_FACE('',(#19),#59,.F.);
#62=ADVANCED_FACE('',(#20),#60,.T.);
#63=ADVANCED_FACE('',(#21,#15),#17,.T.);
#64=ADVANCED_FACE('',(#22,#16),#18,.F.);
#65=CLOSED_SHELL('',(#61,#62,#63,#64));
#66=DERIVED_UNIT_ELEMENT(#68,1.);
#67=DERIVED_UNIT_ELEMENT(#127,-3.);
#68=(
MASS_UNIT()
NAMED_UNIT(*)
SI_UNIT(.KILO.,.GRAM.)
);
#69=DERIVED_UNIT((#66,#67));
#70=MEASURE_REPRESENTATION_ITEM('density measure',
POSITIVE_RATIO_MEASURE(7850.),#69);
#71=PROPERTY_DEFINITION_REPRESENTATION(#76,#73);
#72=PROPERTY_DEFINITION_REPRESENTATION(#77,#74);
#73=REPRESENTATION('material name',(#75),#124);
#74=REPRESENTATION('density',(#70),#124);
#75=DESCRIPTIVE_REPRESENTATION_ITEM('Steel','Steel');
#76=PROPERTY_DEFINITION('material property','material name',#134);
#77=PROPERTY_DEFINITION('material property','density of part',#134);
#78=AXIS2_PLACEMENT_3D('',#107,#87,#88);
#79=AXIS2_PLACEMENT_3D('',#108,#89,#90);
#80=AXIS2_PLACEMENT_3D('',#110,#91,#92);
#81=AXIS2_PLACEMENT_3D('',#113,#94,#95);
#82=AXIS2_PLACEMENT_3D('',#114,#96,#97);
#83=AXIS2_PLACEMENT_3D('',#116,#98,#99);
#84=AXIS2_PLACEMENT_3D('',#119,#101,#102);
#85=AXIS2_PLACEMENT_3D('',#120,#103,#104);
#86=AXIS2_PLACEMENT_3D('',#121,#105,#106);
#87=DIRECTION('axis',(0.,0.,1.));
#88=DIRECTION('refdir',(1.,0.,0.));
#89=DIRECTION('center_axis',(0.,0.,1.));
#90=DIRECTION('ref_axis',(1.,0.,0.));
#91=DIRECTION('center_axis',(0.,0.,-1.));
#92=DIRECTION('ref_axis',(1.,0.,0.));
#93=DIRECTION('',(0.,0.,-1.));
#94=DIRECTION('center_axis',(0.,0.,-1.));
#95=DIRECTION('ref_axis',(1.,0.,0.));
#96=DIRECTION('center_axis',(0.,0.,1.));
#97=DIRECTION('ref_axis',(1.,0.,0.));
#98=DIRECTION('center_axis',(0.,0.,1.));
#99=DIRECTION('ref_axis',(1.,0.,0.));
#100=DIRECTION('',(0.,0.,-1.));
#101=DIRECTION('center_axis',(0.,0.,1.));
#102=DIRECTION('ref_axis',(1.,0.,0.));
#103=DIRECTION('center_axis',(0.,0.,1.));
#104=DIRECTION('ref_axis',(1.,0.,0.));
#105=DIRECTION('center_axis',(0.,0.,1.));
#106=DIRECTION('ref_axis',(1.,0.,0.));
#107=CARTESIAN_POINT('',(0.,0.,0.));
#108=CARTESIAN_POINT('Origin',(0.,0.,0.));
#109=CARTESIAN_POINT('',(-1.7,-2.0818995585505E-16,2.));
#110=CARTESIAN_POINT('Origin',(0.,0.,2.));
#111=CARTESIAN_POINT('',(-1.7,-2.0818995585505E-16,0.));
#112=CARTESIAN_POINT('',(-1.7,-2.0818995585505E-16,0.));
#113=CARTESIAN_POINT('Origin',(0.,0.,0.));
#114=CARTESIAN_POINT('Origin',(0.,0.,0.));
#115=CARTESIAN_POINT('',(-2.7,-3.30654635769785E-16,2.));
#116=CARTESIAN_POINT('Origin',(0.,0.,2.));
#117=CARTESIAN_POINT('',(-2.7,-3.30654635769785E-16,0.));
#118=CARTESIAN_POINT('',(-2.7,-3.30654635769785E-16,0.));
#119=CARTESIAN_POINT('Origin',(0.,0.,0.));
#120=CARTESIAN_POINT('Origin',(0.,0.,2.));
#121=CARTESIAN_POINT('Origin',(0.,0.,0.));
#122=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(0.01),#126,
'DISTANCE_ACCURACY_VALUE',
'Maximum model space distance between geometric entities at asserted c
onnectivities');
#123=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(0.01),#126,
'DISTANCE_ACCURACY_VALUE',
'Maximum model space distance between geometric entities at asserted c
onnectivities');
#124=(
GEOMETRIC_REPRESENTATION_CONTEXT(3)
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#122))
GLOBAL_UNIT_ASSIGNED_CONTEXT((#126,#128,#129))
REPRESENTATION_CONTEXT('','3D')
);
#125=(
GEOMETRIC_REPRESENTATION_CONTEXT(3)
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#123))
GLOBAL_UNIT_ASSIGNED_CONTEXT((#126,#128,#129))
REPRESENTATION_CONTEXT('','3D')
);
#126=(
LENGTH_UNIT()
NAMED_UNIT(*)
SI_UNIT(.MILLI.,.METRE.)
);
#127=(
LENGTH_UNIT()
NAMED_UNIT(*)
SI_UNIT($,.METRE.)
);
#128=(
NAMED_UNIT(*)
PLANE_ANGLE_UNIT()
SI_UNIT($,.RADIAN.)
);
#129=(
NAMED_UNIT(*)
SI_UNIT($,.STERADIAN.)
SOLID_ANGLE_UNIT()
);
#130=SHAPE_DEFINITION_REPRESENTATION(#131,#132);
#131=PRODUCT_DEFINITION_SHAPE('',$,#134);
#132=SHAPE_REPRESENTATION('',(#78),#124);
#133=PRODUCT_DEFINITION_CONTEXT('part definition',#138,'design');
#134=PRODUCT_DEFINITION('washer-m3','washer-m3',#135,#133);
#135=PRODUCT_DEFINITION_FORMATION('',$,#140);
#136=PRODUCT_RELATED_PRODUCT_CATEGORY('washer-m3','washer-m3',(#140));
#137=APPLICATION_PROTOCOL_DEFINITION('international standard',
'automotive_design',2009,#138);
#138=APPLICATION_CONTEXT(
'Core Data for Automotive Mechanical Design Process');
#139=PRODUCT_CONTEXT('part definition',#138,'mechanical');
#140=PRODUCT('washer-m3','washer-m3',$,(#139));
#141=PRESENTATION_STYLE_ASSIGNMENT((#142));
#142=SURFACE_STYLE_USAGE(.BOTH.,#143);
#143=SURFACE_SIDE_STYLE('',(#144));
#144=SURFACE_STYLE_FILL_AREA(#145);
#145=FILL_AREA_STYLE('Steel - Satin',(#146));
#146=FILL_AREA_STYLE_COLOUR('Steel - Satin',#147);
#147=COLOUR_RGB('Steel - Satin',0.627450980392157,0.627450980392157,0.627450980392157);
ENDSEC;
END-ISO-10303-21;

116
docs/book.css Normal file
View File

@ -0,0 +1,116 @@
.sidebar-resize-handle { display: none !important; }
footer {
contain: content;
border-top: 3px solid #f4f4f4;
}
footer a.telegram, footer a.github {
display: block;
margin-bottom: 10px;
margin-top: 10px;
display: flex;
align-items: center;
text-decoration: none;
}
.content .github, .content .telegram {
display: flex;
align-items: center;
text-align: center;
justify-content: center;
}
.telegram::before, .github::before {
font-family: FontAwesome;
margin-right: 0.3em;
font-size: 1.6em;
color: black;
}
.github::before {
content: "\f09b";
}
.telegram::before {
font-size: 1.4em;
color: #0084c5;
content: "\f2c6";
}
.content hr {
border: none;
border-top: 2px solid #c9c9c9;
margin: 2em 0;
}
.content img {
display: block;
margin: 0 auto;
}
.content img.border {
border: 1px solid #c9c9c9;
}
@media (max-width: 600px) {
.MathJax_Display {
overflow-x: auto;
}
}
.firmware {
position: relative;
margin: 20px 0;
padding: 20px 20px;
padding-left: 60px;
color: var(--fg);
background-color: var(--quote-bg);
border-block-start: .1em solid var(--quote-border);
border-block-end: .1em solid var(--quote-border);
}
.firmware::before {
font-family: FontAwesome;
font-size: 1.5em;
content: "\f15b";
position: absolute;
width: 20px;
text-align: center;
left: 20px;
}
.alert {
margin-top: 20px;
margin-bottom: 20px;
position: relative;
border-left: 2px solid #0a69da;
padding: 20px;
padding-left: 60px;
}
.alert::before {
font-family: FontAwesome;
font-size: 1.5em;
color: #0a69da;
content: "\f05a";
position: absolute;
width: 20px;
text-align: center;
left: 20px;
}
.alert-tip { border-left-color: #1b7f37; }
.alert-tip::before { color: #1b7f37; content: '\f0eb'; }
.alert-caution { border-left-color: #cf212e; }
.alert-caution::before { color: #cf212e; content: '\f071'; }
.alert-important { border-left-color: #8250df; }
.alert-important::before { color: #8250df; content: '\f06a'; }
.alert-warning { border-left-color: #f0ad4e; }
.alert-warning::before { color: #f0ad4e; content: '\f071'; }
.alert-code { border-left-color: #333; }
.alert-code::before { color: #333; content: '\f121'; }

22
docs/book.toml Normal file
View File

@ -0,0 +1,22 @@
[book]
authors = ["Oleg Kalachev"]
language = "ru"
multilingual = false
src = "book"
title = "Полетный контроллер с нуля"
description = "Учебник по разработке полетного контроллера квадрокоптера"
[build]
build-dir = "build"
[output.html]
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
[output.html.code.hidelines]
cpp = "//~"
[preprocessor.alerts]
command = "python3 alerts.py"

10
docs/book/README.md Normal file
View File

@ -0,0 +1,10 @@
# Flix
> [!IMPORTANT]
> Flix — это проект по созданию открытого квадрокоптера на базе ESP32 с нуля и учебника по разработке полетных контроллеров.
<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>
<p class="telegram">Telegram-канал:&nbsp;<a href="https://t.me/opensourcequadcopter">@opensourcequadcopter</a>.</p>

23
docs/book/SUMMARY.md Normal file
View File

@ -0,0 +1,23 @@
<!-- markdownlint-disable MD041 -->
<!-- markdownlint-disable MD042 -->
[Главная](./README.md)
* [Архитектура прошивки](firmware.md)
# Учебник
* [Основы]()
* [Светодиод]()
* [Моторы]()
* [Радиоуправление]()
* [Вектор, кватернион](geometry.md)
* [Гироскоп](gyro.md)
* [Акселерометр]()
* [Оценка состояния]()
* [PID-регулятор]()
* [Режим ACRO]()
* [Режим STAB]()
* [Wi-Fi]()
* [MAVLink]()
* [Симуляция]()

32
docs/book/firmware.md Normal file
View File

@ -0,0 +1,32 @@
# Архитектура прошивки
<img src="img/dataflow.svg" width=800 alt="Firmware dataflow diagram">
Главный цикл работает на частоте 1000 Гц. Передача данных между подсистемами происходит через глобальные переменные:
* `t` *(float)* — текущее время шага, *с*.
* `dt` *(float)* — дельта времени между текущим и предыдущим шагами, *с*.
* `gyro` *(Vector)* — данные с гироскопа, *рад/с*.
* `acc` *(Vector)* — данные с акселерометра, *м/с<sup>2</sup>*.
* `rates` *(Vector)* — отфильтрованные угловые скорости, *рад/с*.
* `attitude` *(Quaternion)* — оценка ориентации (положения) дрона.
* `controlRoll`, `controlPitch`, ... *(float[])* — команды управления от пилота, в диапазоне [-1, 1].
* `motors` *(float[])* — выходные сигналы на моторы, в диапазоне [0, 1].
## Исходные файлы
Исходные файлы прошивки находятся в директории `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) — управление выходными сигналами на моторы через ШИМ.
Вспомогательные файлы включают:
* [`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) — реализация общего фильтра нижних частот.

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).

262
docs/book/gyro.md Normal file
View File

@ -0,0 +1,262 @@
# Гироскоп
<div class="firmware">
<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>
Поддержание стабильного полета квадрокоптера невозможно без датчиков обратной связи. Важнейший из них — это **MEMS-гироскоп**. MEMS-гироскоп это микроэлектромеханический аналог классического механического гироскопа.
Механический гироскоп состоит из вращающегося диска, который сохраняет свою ориентацию в пространстве. Благодаря этому эффекту возможно определить ориентацию объекта в пространстве.
В MEMS-гироскопе нет вращающихся частей, и он помещается в крошечную микросхему. Он может измерять только текущую угловую скорость вращения объекта вокруг трех осей: X, Y и Z.
|Механический гироскоп|MEMS-гироскоп|
|-|-|
|<img src="img/gyroscope.jpg" width="300" alt="Механический гироскоп">|<img src="img/mpu9250.jpg" width="100" alt="MEMS-гироскоп MPU-9250">|
MEMS-гироскоп обычно интегрирован в инерциальный модуль (IMU), в котором также находятся акселерометр и магнитометр. Модуль IMU часто называют 9-осевым датчиком, потому что он измеряет:
* Угловую скорость вращения по трем осям (гироскоп).
* Ускорение по трем осям (акселерометр).
* Магнитное поле по трем осям (магнитометр).
Flix поддерживает следующие модели IMU:
* InvenSense MPU-9250.
* InvenSense MPU-6500.
* InvenSense ICM-20948.
> [!NOTE]
> MEMS-гироскоп измеряет угловую скорость вращения объекта.
## Интерфейс подключения
Большинство модулей IMU подключаются к микроконтроллеру через интерфейсы I²C и SPI. Оба этих интерфейса являются *шинами данных*, то есть позволяют подключить к одному микроконтроллеру несколько устройств.
**Интерфейс I²C** использует два провода для передачи данных и тактового сигнала. Выбор устройства для коммуникации происходит при помощи передачи адреса устройства на шину. Разные устройства имеют разные адреса, и микроконтроллер может последовательно общаться с несколькими устройствами.
**Интерфейс SPI** использует два провода для передачи данных, еще один для тактового сигнала и еще один для выбора устройства. При этом для каждого устройства на шине выделяется отдельный GPIO-пин для выбора. В разных реализациях этот пин называется CS/NCS (Chip Select) или SS (Slave Select). Когда CS-пин устройства активен (напряжение на нем низкое), устройство выбрано для общения.
В полетных контроллерах IMU обычно подключают через SPI, потому что он обеспечивает значительно бо́льшую скорость передачи данных и меньшую задержку. Подключение IMU через интерфейс I²C (например, в случае нехватки пинов микроконтроллера) возможно, но не рекомендуется.
Подключение IMU к микроконтроллеру ESP32 через интерфейс SPI выглядит так:
|Пин платы IMU|Пин ESP32|
|-|-|
|VCC/3V3|3V3|
|GND|GND|
|SCL|IO18|
|SDA *(MOSI)*|IO23|
|SAO/AD0 *(MISO)*|IO19|
|NCS|IO5|
Кроме того, многие IMU могут «будить» микроконтроллер при наличии новых данных. Для этого используется пин INT, который подключается к любому GPIO-пину микроконтроллера. При такой конфигурации можно использовать прерывания для обработки новых данных с IMU, вместо периодического опроса датчика. Это позволяет снизить нагрузку на микроконтроллер в сложных алгоритмах управления.
> [!WARNING]
> На некоторых платах IMU, например, на ICM-20948, отсутствует стабилизатор напряжения, поэтому их нельзя подключать к пину VIN ESP32, который подает напряжение 5 В. Допустимо питание только от пина 3V3.
## Работа с гироскопом
Для взаимодействия с IMU, включая работу с гироскопом, в Flix используется библиотека *FlixPeriph*. Библиотека устанавливается через менеджер библиотек Arduino IDE:
<img src="img/flixperiph.png" width="300">
Чтобы работать с IMU, используется класс, соответствующий модели IMU: `MPU9250`, `MPU6500` или `ICM20948`. Классы для работы с разными IMU имеют единообразный интерфейс для основных операций, поэтому возможно легко переключаться между разными моделями IMU. Датчик MPU-6500 практически полностью совместим с MPU-9250, поэтому фактически класс `MPU9250` поддерживает обе модели.
## Ориентация осей гироскопа
Данные с гироскопа представляют собой угловую скорость вокруг трех осей: X, Y и Z. Ориентацию этих осей у IMU InvenSense можно легко определить по небольшой точке в углу чипа. Оси координат и направление вращения для измерений гироскопа обозначены на диаграмме:
<img src="img/imu-axes.svg" width="300" alt="Оси координат IMU">
Расположение осей координат в популярных платах IMU:
|GY-91|MPU-92/65|ICM-20948|
|-|-|-|
|<img src="https://github.com/okalachev/flixperiph/raw/refs/heads/master/img/gy91-axes.svg" width="200" alt="Оси координат платы GY-91">|<img src="https://github.com/okalachev/flixperiph/raw/refs/heads/master/img/mpu9265-axes.svg" width="200" alt="Оси координат платы MPU-9265">|<img src="https://github.com/okalachev/flixperiph/raw/refs/heads/master/img/icm20948-axes.svg" width="200" alt="Оси координат платы ICM-20948">|
Магнитометр IMU InvenSense обычно является отдельным устройством, интегрированным в чип, поэтому его оси координат могут отличаться. Библиотека FlixPeriph скрывает это различие и приводит данные с магнитометра к системе координат гироскопа и акселерометра.
## Чтение данных
Интерфейс библиотеки FlixPeriph соответствует стилю, принятому в Arduino. Для начала работы с IMU необходимо создать объект соответствующего класса и вызвать метод `begin()`. В конструктор класса передается интерфейс, по которому подключен IMU (SPI или I²C):
```cpp
#include <FlixPeriph.h>
#include <SPI.h>
MPU9250 IMU(SPI);
void setup() {
Serial.begin(115200);
bool success = IMU.begin();
if (!success) {
Serial.println("Failed to initialize IMU");
}
}
```
Для однократного считывания данных используется метод `read()`. Затем данные с гироскопа получаются при помощи метода `getGyro(x, y, z)`. Этот метод записывает в переменные `x`, `y` и `z` угловые скорости вокруг соответствующих осей в радианах в секунду.
Если нужно гарантировать, что будут считаны новые данные, можно использовать метод `waitForData()`. Этот метод блокирует выполнение программы до тех пор, пока в IMU не появятся новые данные. Метод `waitForData()` позволяет привязать частоту главного цикла `loop` к частоте обновления данных IMU. Это удобно для организации главного цикла управления квадрокоптером.
Программа для чтения данных с гироскопа и вывода их в консоль для построения графиков в Serial Plotter выглядит так:
```cpp
#include <FlixPeriph.h>
#include <SPI.h>
MPU9250 IMU(SPI);
void setup() {
Serial.begin(115200);
bool success = IMU.begin();
if (!success) {
Serial.println("Failed to initialize IMU");
}
}
void loop() {
IMU.waitForData();
float gx, gy, gz;
IMU.getGyro(gx, gy, gz);
Serial.printf("gx:%f gy:%f gz:%f\n", gx, gy, gz);
delay(50); // замедление вывода
}
```
После запуска программы в Serial Plotter можно увидеть графики угловых скоростей. Например, при вращениях IMU вокруг вертикальной оси Z графики будут выглядеть так:
<img src="img/gyro-plotter.png">
## Конфигурация гироскопа
В коде Flix настройка IMU происходит в функции `configureIMU`. В этой функции настраиваются три основных параметра гироскопа: диапазон измерений, частота сэмплов и частота LPF-фильтра.
### Частота сэмплов
Большинство IMU могут обновлять данные с разной частотой. В полетных контроллерах обычно используется частота обновления от 500 Гц до 8 кГц. Чем выше частота сэмплов, тем выше точность управления полетом, но и больше нагрузка на микроконтроллер.
Частота сэмплов устанавливается методом `setSampleRate()`. В Flix используется частота 1 кГц:
```cpp
IMU.setRate(IMU.RATE_1KHZ_APPROX);
```
Поскольку не все поддерживаемые IMU могут работать строго на частоте 1 кГц, в библиотеке FlixPeriph существует возможность приближенной настройки частоты сэмплов. Например, у IMU ICM-20948 при такой настройке реальная частота сэмплирования будет равна 1125 Гц.
Другие доступные для установки в библиотеке FlixPeriph частоты сэмплирования:
* `RATE_MIN` — минимальная частота сэмплов для конкретного IMU.
* `RATE_50HZ_APPROX` — значение, близкое к 50 Гц.
* `RATE_1KHZ_APPROX` — значение, близкое к 1 кГц.
* `RATE_8KHZ_APPROX` — значение, близкое к 8 кГц.
* `RATE_MAX` — максимальная частота сэмплов для конкретного IMU.
#### Диапазон измерений
Большинство MEMS-гироскопов поддерживают несколько диапазонов измерений угловой скорости. Главное преимущество выбора меньшего диапазона — бо́льшая чувствительность. В полетных контроллерах обычно выбирается максимальный диапазон измерений от 2000 до 2000 градусов в секунду, чтобы обеспечить возможность динамичных маневров.
В библиотеке FlixPeriph диапазон измерений гироскопа устанавливается методом `setGyroRange()`:
```cpp
IMU.setGyroRange(IMU.GYRO_RANGE_2000DPS);
```
### LPF-фильтр
IMU InvenSense могут фильтровать измерения на аппаратном уровне при помощи фильтра нижних частот (LPF). Flix реализует собственный фильтр для гироскопа, чтобы иметь больше гибкости при поддержке разных IMU. Поэтому для встроенного LPF устанавливается максимальная частота среза:
```cpp
IMU.setDLPF(IMU.DLPF_MAX);
```
## Калибровка гироскопа
Как и любое измерительное устройство, гироскоп вносит искажения в измерения. Наиболее простая модель этих искажений делит их на статические смещения (*bias*) и случайный шум (*noise*):
\\[ gyro_{xyz}=rates_{xyz}+bias_{xyz}+noise \\]
Для качественной работы подсистемы оценки ориентации и управления дроном необходимо оценить *bias* гироскопа и учесть его в вычислениях. Для этого при запуске программы производится калибровка гироскопа, которая реализована в функции `calibrateGyro()`. Эта функция считывает данные с гироскопа в состоянии покоя 1000 раз и усредняет их. Полученные значения считаются *bias* гироскопа и в дальнейшем вычитаются из измерений.
Программа для вывода данных с гироскопа с калибровкой:
```cpp
#include <FlixPeriph.h>
#include <SPI.h>
MPU9250 IMU(SPI);
float gyroBiasX, gyroBiasY, gyroBiasZ; // bias гироскопа
void setup() {
Serial.begin(115200);
bool success = IMU.begin();
if (!success) {
Serial.println("Failed to initialize IMU");
}
calibrateGyro();
}
void loop() {
float gx, gy, gz;
IMU.waitForData();
IMU.getGyro(gx, gy, gz);
// Устранение bias гироскопа
gx -= gyroBiasX;
gy -= gyroBiasY;
gz -= gyroBiasZ;
Serial.printf("gx:%f gy:%f gz:%f\n", gx, gy, gz);
delay(50); // замедление вывода
}
void calibrateGyro() {
const int samples = 1000;
Serial.println("Calibrating gyro, stand still");
gyroBiasX = 0;
gyroBiasY = 0;
gyroBiasZ = 0;
// Получение 1000 измерений гироскопа
for (int i = 0; i < samples; i++) {
IMU.waitForData();
float gx, gy, gz;
IMU.getGyro(gx, gy, gz);
gyroBiasX += gx;
gyroBiasY += gy;
gyroBiasZ += gz;
}
// Усреднение значений
gyroBiasX = gyroBiasX / samples;
gyroBiasY = gyroBiasY / samples;
gyroBiasZ = gyroBiasZ / samples;
Serial.printf("Gyro bias X: %f\n", gyroBiasX);
Serial.printf("Gyro bias Y: %f\n", gyroBiasY);
Serial.printf("Gyro bias Z: %f\n", gyroBiasZ);
}
```
График данных с гироскопа в состоянии покоя без калибровки. Можно увидеть статическую ошибку каждой из осей:
<img src="img/gyro-uncalibrated-plotter.png">
График данных с гироскопа в состоянии покоя после калибровки:
<img src="img/gyro-calibrated-plotter.png">
Откалиброванные данные с гироскопа вместе с данными с акселерометра поступают в *подсистему оценки состояния*.
## Дополнительные материалы
* [MPU-9250 datasheet](https://invensense.tdk.com/wp-content/uploads/2015/02/PS-MPU-9250A-01-v1.1.pdf).
* [MPU-6500 datasheet](https://invensense.tdk.com/wp-content/uploads/2020/06/PS-MPU-6500A-01-v1.3.pdf).
* [ICM-20948 datasheet](https://invensense.tdk.com/wp-content/uploads/2016/06/DS-000189-ICM-20948-v1.3.pdf).

1
docs/book/img Symbolic link
View File

@ -0,0 +1 @@
../img

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

@ -11,6 +11,8 @@ cd flix
### Ubuntu
The latest version of Ubuntu supported by Gazebo 11 simulator is 22.04. If you have a newer version, consider using a virtual machine.
1. Install Arduino CLI:
```bash
@ -82,7 +84,7 @@ cd flix
#### 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.
1. Install [QGroundControl mobile app](https://docs.qgroundcontrol.com/master/en/qgc-user-guide/getting_started/download_and_install.html#android) on your smartphone. For **iOS**, use [QGroundControl build from TAJISOFT](https://apps.apple.com/ru/app/qgc-from-tajisoft/id1618653051).
2. Connect your smartphone to the same Wi-Fi network as the machine running the simulator.
3. If you're using a virtual machine, make sure that its network is set to the **bridged** mode with Wi-Fi adapter selected.
4. Run the simulation.
@ -94,27 +96,35 @@ cd flix
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 and stop the simulation.
4. Copy the calibration results to the source code (`gazebo/joystick.h`).
5. Run the simulation again.
6. Use the USB remote control to fly the drone!
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.3 (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`.
* `MAVLink`, version 2.0.10.
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.
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. Install ESP32 core, version 3.2.0. 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.
4. 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.16.
5. Clone the project using git or [download the source code as a ZIP archive](https://codeload.github.com/okalachev/flix/zip/refs/heads/master).
6. Open the downloaded Arduino sketch `flix/flix.ino` in Arduino IDE.
7. Connect your ESP32 board to the computer and choose correct board type in Arduino IDE (*WEMOS D1 MINI ESP32* for ESP32 Mini) and the port.
8. [Build and upload](https://docs.arduino.cc/software/ide-v2/tutorials/getting-started/ide-v2-uploading-a-sketch) the firmware using Arduino IDE.
### Command line (Windows, Linux, macOS)
1. [Install Arduino CLI](https://arduino.github.io/arduino-cli/installation/).
On Linux, use:
```bash
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=~/.local/bin sh
```
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:
@ -136,19 +146,21 @@ cd flix
See other available Make commands in the [Makefile](../Makefile).
> [!TIP]
> You can test the firmware on a bare ESP32 board without connecting IMU and other peripherals. The Wi-Fi network `flix` should appear and all the basic functionality including CLI and QGroundControl connection should work.
### 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.
3. Copy calibration results to the source code (`flix/imu.ino`).
1. Open Serial Monitor in Arduino IDE (or 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.
3. Connect your smartphone to the appeared `flix` Wi-Fi network (password: `flixwifi`).
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!
@ -157,11 +169,36 @@ Before flight you need to calibrate the accelerometer:
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.
3. Copy calibration results to the source code (`flix/rc.ino`).
1. Open Serial Monitor in Arduino IDE (or use `make monitor` command in the command line).
2. Type `cr` command there and follow the instructions.
3. Use the remote control to fly the drone!
Then you can use your remote control to fly the drone!
#### Control with USB remote control
If your drone doesn't have RC receiver installed, you can use USB remote control and QGroundControl app to fly it.
1. Install [QGroundControl](https://docs.qgroundcontrol.com/master/en/qgc-user-guide/getting_started/download_and_install.html) app on your computer.
2. Connect your USB remote control to the computer.
3. Power up the drone.
4. Connect your computer to the appeared `flix` Wi-Fi network (password: `flixwifi`).
5. Launch QGroundControl app. It should connect and begin showing the drone's telemetry automatically.
6. Go the the QGroundControl menu ⇒ *Vehicle Setup**Joystick*. Calibrate you USB remote control there.
7. Use the USB remote control to fly the drone!
#### Adjusting parameters
You can adjust some of the drone's parameters (include PID coefficients) in QGroundControl app. In order to do that, go to the QGroundControl menu ⇒ *Vehicle Setup**Parameters*.
<img src="img/parameters.png" width="400">
#### CLI access
In addition to accessing the drone's command line interface (CLI) using the serial port, you can also access it with QGroundControl using Wi-Fi connection. To do that, go to the QGroundControl menu ⇒ *Vehicle Setup**Analyze Tools**MAVLink Console*.
<img src="img/cli.png" width="400">
> [!NOTE]
> If something goes wrong, go to the [Troubleshooting](troubleshooting.md) article.
### Firmware code structure

View File

@ -1,19 +1,21 @@
# Firmware overview
The firmware is a regular Arduino sketch, and 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 multiple files, each responsible for a specific part of the system.
## Dataflow
<img src="img/dataflow.svg" width=800 alt="Firmware dataflow diagram">
The main loop is running at 1000 Hz. All the dataflow is happening through global variables (for simplicity):
* `t` *(float)* — current step time, *s*.
* `t` *(double)* — 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`, ... *(float[])* pilot's control inputs, range [-1, 1].
* `motors` *(float[])* motor outputs, range [0, 1].
## Source files

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/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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

BIN
docs/img/flixperiph.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

78
docs/img/gy91-lfd.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

BIN
docs/img/gyro-plotter.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

BIN
docs/img/gyroscope.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
docs/img/icm-20948.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

119
docs/img/imu-axes.svg Normal file
View File

@ -0,0 +1,119 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 544.13 637.15">
<defs>
<style>
.a {
fill: #dbe1e2;
}
.b {
fill: #c2c1c0;
}
.c {
fill: #c6c6c5;
}
.d {
fill: #ec7d23;
}
.e {
font-size: 50px;
font-family: Tahoma;
}
.e, .n {
fill: #010101;
}
.f {
opacity: 0.8;
}
.g, .i, .k, .m {
fill: none;
stroke-width: 10px;
}
.g {
stroke: #0577ba;
}
.g, .i, .k {
stroke-linejoin: bevel;
}
.h {
fill: #0577ba;
}
.i {
stroke: #76c043;
}
.j {
fill: #76c043;
}
.k {
stroke: #d71f26;
}
.l {
fill: #d71f26;
}
.m {
stroke: #010101;
stroke-miterlimit: 10;
}
</style>
</defs>
<g>
<g>
<rect class="a" x="51.25" y="538.09" width="111.96" height="44.06"/>
<polygon class="b" points="204.47 515.98 163.21 582.15 163.21 538.09 204.47 471.91 204.47 515.98"/>
<polygon class="c" points="163.21 538.19 51.25 538.19 92.46 471.91 204.42 471.91 163.21 538.19"/>
<ellipse class="d" cx="101.09" cy="480" rx="7.45" ry="3.7" transform="translate(-117.09 40.67) rotate(-14.52)"/>
</g>
<text class="e" transform="translate(166.62 107.43)">Z</text>
<g class="f">
<g>
<line class="g" x1="127.84" y1="505.05" x2="127.84" y2="70.04"/>
<polygon class="h" points="145.79 75.3 127.84 44.21 109.89 75.3 145.79 75.3"/>
</g>
</g>
<g class="f">
<g>
<line class="i" x1="127.84" y1="505.05" x2="315.74" y2="203.61"/>
<polygon class="j" points="328.2 217.57 329.41 181.69 297.73 198.57 328.2 217.57"/>
</g>
</g>
<text class="e" transform="translate(338.14 279.7)">Y</text>
<g class="f">
<g>
<line class="k" x1="127.94" y1="504.62" x2="467.04" y2="504.62"/>
<polygon class="l" points="461.79 522.58 492.87 504.62 461.79 486.67 461.79 522.58"/>
</g>
</g>
<text class="e" transform="translate(438.99 582.15)">X</text>
<g class="f">
<g>
<path class="m" d="M80,98.74a52.66,52.66,0,1,0,98.43,36.72"/>
<polygon class="n" points="190.29 140.9 180.45 116.91 164.59 137.41 190.29 140.9"/>
</g>
</g>
<g class="f">
<g>
<path class="m" d="M474,467.75a52.66,52.66,0,1,0-59.23,86.77"/>
<polygon class="n" points="406.68 564.7 432.32 560.9 416.21 540.59 406.68 564.7"/>
</g>
</g>
<g class="f">
<g>
<path class="m" d="M222.38,257.69a52.66,52.66,0,1,1,93.83,47.25"/>
<polygon class="n" points="308.22 293.44 303.95 319.01 328.23 309.93 308.22 293.44"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 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/mpu9250.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 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

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

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

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

7
docs/js.js Normal file
View File

@ -0,0 +1,7 @@
// Enable zoom on images larger than 300px
document.querySelectorAll('.content img').forEach(function (img) {
var width = img.getAttribute('width');
if (!width || width >= 300) {
img.setAttribute('data-action', 'zoom');
}
});

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