├── LICENSE
├── README.md
├── controller
├── GPS.h
├── PID.h
├── PilotCommandProcessor.h
├── SerialCommunication.h
├── controller.h
├── controller.ino
├── dataStorage.h
├── math.h
└── sensors.h
├── extras
├── Configurator
│ ├── background.js
│ ├── css
│ │ └── style.css
│ ├── js
│ │ ├── data_storage.js
│ │ ├── file_system.js
│ │ ├── flotr2.min.js
│ │ ├── jquery-2.0.2.min.js
│ │ ├── main.js
│ │ ├── serial_backend.js
│ │ └── union_process.js
│ ├── main.html
│ ├── manifest.json
│ ├── readme.txt
│ └── tabs
│ │ ├── about.html
│ │ ├── channel_assigner.html
│ │ ├── channel_assigner.js
│ │ ├── function_triggers.html
│ │ ├── function_triggers.js
│ │ ├── gps.html
│ │ ├── gps.js
│ │ ├── initial_setup.html
│ │ ├── initial_setup.js
│ │ ├── motor_output.html
│ │ ├── motor_output.js
│ │ ├── pid_tuning.html
│ │ ├── pid_tuning.js
│ │ ├── rx.html
│ │ ├── rx.js
│ │ ├── sensor_data.html
│ │ ├── sensor_data.js
│ │ ├── sensor_mapping.html
│ │ ├── sensor_mapping.js
│ │ ├── vehicle_view.html
│ │ └── vehicle_view.js
├── Filters
│ └── MAF.h
├── I2Cscanner
│ └── I2Cscanner.ino
└── PCB
│ └── Phoenix_shield_v0.1.zip
└── libraries
├── P_6DOF
├── mpu6050_10DOF_stick_px01.h
└── mpu6050_6DOF_stick_px01.h
├── P_Accelerometer
├── ADXL345.h
├── ADXL345_AQ_v21.h
└── BMA180_AQ_v20.h
├── P_Barometer
├── Barometer_bmp085.h
└── Barometer_ms5611.h
├── P_BatteryMonitor
├── BatteryMonitor_current.h
└── BatteryMonitor_voltage.h
├── P_ESC
├── ESC_328p_HW.h
└── ESC_teensy3_HW.h
├── P_FrameType
├── FrameType_QuadPlus.h
└── FrameType_QuadX.h
├── P_GPS
├── GPS_ublox.h
└── neo-6m_configuration.txt
├── P_Gyroscope
├── ITG3200_AQ_v20.h
└── ITG3200_AQ_v21.h
├── P_Kinematics
├── kinematics_ARG.h
├── kinematics_CMP.h
└── kinematics_DCM.h
├── P_Magnetometer
└── Magnetometer_HMC5883L.h
├── P_Receiver
├── Receiver_328p_HW_PPM.h
├── Receiver_teensy3_HW_PPM.h
└── Receiver_teensy3_HW_PWM.h
└── P_Sonar
└── Sonar_srf04.h
/README.md:
--------------------------------------------------------------------------------
1 | Flight Controller based on K20DX128 (Teensy 3.0)
2 | ------------------------------------------------
3 |
4 | PIN setup (Teensy 3.0 pin numbering)
5 | ------------------------------------
6 | - I2C SCL 19
7 | - I2C SDA 18
8 |
9 | - PPM in (receiver) 3
10 |
11 | - Rotor 1 22
12 | - Rotor 2 23
13 | - Rotor 3 9
14 | - Rotor 4 10
15 | - Rotor 5 6
16 | - Rotor 6 20
17 | - Rotor 7 21
18 | - Rotor 8 5
19 |
20 | - Orientation lights / Armed-Disarmed indicator 14
21 |
22 | - Battery Monitor (current sensor) 17
23 |
24 | Filters, kinematics, data handling
25 | ----------------------------------
26 | - Initial raw data from sensors (read every 1ms = 1000Hz) is being averaged by a simple averaging filter
27 | - Averaged data from sensors are being processed every 10ms (100Hz)
28 | - CMP kinematitcs (my own) selected by default
29 | - ARG kinematics (from aeroquad) is also supported (can be enabled by simple include change)
30 | - DCM kinematics (from FreeIMU)
31 | - Flight controller supports
32 | - Rate (ACRO) | gyro only
33 | - Attitude | gyro with accel corrections
34 | - Altitute hold | barometer or sonar
35 | - Pilot commands are being read by PPM sampling code via single PIN (with HW timer), both PPM and PWM in is supported
36 | - Stabilization and pilot commands are mixed together by 2 separate PID controllers
37 | - First (only used in attitude mode) mixes pilot commands with kinematics output
38 | - Second (used in both attitude and rate mode) mixes output from first PID or raw stick input with gyroRate output
39 | - For ESC signal output i am using an build in 8 channel FLEX timer (yes you can controll octocopter with this)
40 | - ESC PWM signal supports both 250Hz and 400Hz update rate (running at 400Hz by default)
41 |
42 | Accelerometer offset trimming via TX
43 | ------------------------------------
44 | - While in dis-armed state
45 | - To enter the trimming mode = Throttle stick UP & Rudder LEFT
46 | - To adjust the values = PITCH UP & DOWN || ROLL LEFT & RIGHT
47 | - To save the values in EEPROM = Throttle UP & Rudder RIGHT
48 | - (you dont have to save the values right away, you can test them, then return back to trimming mode and save them)
49 | - To leave trimming mode = Throttle stick DOWN
--------------------------------------------------------------------------------
/controller/GPS.h:
--------------------------------------------------------------------------------
1 | class GPS_navigator {
2 | public:
3 | // Constructor
4 | GPS_navigator() {
5 | };
6 |
7 | void initializeBaseStation() {
8 | // We need to get accurate fix, possible a while loop with accuracy check in the end
9 | // after this ground altitude should be saved
10 | };
11 |
12 | void processPositionHold() {
13 | };
14 |
15 | private:
16 | int32_t base_lat;
17 | int32_t base_lon;
18 | float base_ground_altitude;
19 | } gps;
--------------------------------------------------------------------------------
/controller/PID.h:
--------------------------------------------------------------------------------
1 | /* Proportional–integral–derivative controller (PID controller) is a control loop feedback mechanism.
2 |
3 | PID controller calculates an "error" value as the difference between a measured process variable and a desired setpoint.
4 | The controller attempts to minimize the error by adjusting the process control inputs.
5 |
6 | Input = Kinematics or sensor input
7 | Output = PID output
8 | SetPoint = Desired output
9 | windupGuard = maximum and minimum constrain on I term
10 |
11 | Empty constructor will allow us to declare PID object globally anywhere before the setup() function
12 | This will allow us to feed PID settings and other variables when they are ready
13 | (in our case, after EEPROM has been initialized / read into variables)
14 |
15 | Pointer arithmetic used to parse terms into PID controller @http://www.cs.umd.edu/class/sum2003/cmsc311/Notes/BitOp/pointer.html
16 | */
17 | class PID {
18 | public:
19 | // Constructor
20 | PID() {
21 | };
22 |
23 | PID(float* Input, float* Output, float* Setpoint, float* terms) {
24 | previous_error = 0.0;
25 | integral = 0.0;
26 |
27 | PID_input = Input;
28 | PID_output = Output;
29 | PID_setpoint = Setpoint;
30 |
31 | Kp = &terms[P];
32 | Ki = &terms[I];
33 | Kd = &terms[D];
34 |
35 | windupGuard = &terms[WG];
36 | };
37 |
38 | void Compute() {
39 | unsigned long now = micros();
40 | float delta_time = (now - last_time) / 1000000.0;
41 | float error = *PID_setpoint - *PID_input;
42 | integral = constrain(integral + error * delta_time, -*windupGuard, *windupGuard);
43 | float derivative = (*PID_input - previous_error) / delta_time;
44 |
45 | *PID_output = *Kp * error + *Ki * integral + *Kd * derivative;
46 |
47 | previous_error = *PID_input;
48 | last_time = now;
49 | };
50 |
51 | void IntegralReset() {
52 | integral = 0.0;
53 | };
54 |
55 | private:
56 | float *PID_input;
57 | float *PID_output;
58 | float *PID_setpoint;
59 |
60 | float *Kp, *Ki, *Kd;
61 | float *windupGuard;
62 |
63 | float previous_error;
64 | float integral;
65 | unsigned long last_time;
66 | };
--------------------------------------------------------------------------------
/controller/controller.h:
--------------------------------------------------------------------------------
1 | // Main loop variables
2 | unsigned long currentTime = 0;
3 | unsigned long previousTime = 0;
4 | unsigned long sensorPreviousTime = 0;
5 | uint8_t frameCounter = 0;
6 | uint32_t itterations = 0;
7 |
8 | bool all_ready = false;
9 | bool armed = false;
10 | bool flightMode = false;
11 | bool altitudeHoldBaro = false;
12 | bool altitudeHoldSonar = false;
13 | bool positionHoldGPS = false;
14 |
15 | // Flight modes
16 | #define RATE_MODE 0
17 | #define ATTITUDE_MODE 1
18 |
19 | // Primary channel definitions
20 | #define ROLL 0
21 | #define PITCH 1
22 | #define THROTTLE 2
23 | #define YAW 3
24 |
25 | // PID pseudo definitions
26 | #define P 0 // Proportional
27 | #define I 1 // Integral
28 | #define D 2 // Derivative
29 | #define WG 3 // WindupGuard
30 |
31 | // Axis definitions
32 | #define XAXIS 0
33 | #define YAXIS 1
34 | #define ZAXIS 2
35 |
36 | // Arbitrary ON/OFF definitions
37 | #define OFF 0
38 | #define ON 1
39 |
40 | // Blinking LED to indicate activity
41 | bool Arduino_LED_state = 0;
42 | uint8_t Beacon_LED_state = 0;
43 |
44 | // Modulo definitions (integer remainder)
45 | #define TASK_50HZ 2
46 | #define TASK_10HZ 10
47 | #define TASK_1HZ 100
48 |
49 | // Kinematics variable defnitions
50 | float kinematicsAngle[3];
51 |
52 | // FlightController commands definitions
53 | float commandYaw, commandYawAttitude, commandPitch, commandRoll, commandThrottle;
54 |
55 | // Heading related variables
56 | float headingError = 0.0;
57 | float headingSetpoint = 0.0;
58 |
59 | // PID variables
60 | float YawCommandPIDSpeed, PitchCommandPIDSpeed, RollCommandPIDSpeed;
61 | float YawMotorSpeed, PitchMotorSpeed, RollMotorSpeed, AltitudeHoldMotorSpeed;
62 | int16_t throttle = 1000;
63 |
64 | // +- PI normalization macro
65 | #define NORMALIZE(x) do { if ((x) < -PI) (x) += 2 * PI; else if ((x) > PI) (x) -= 2 * PI; } while (0);
66 |
67 | // Custom definitions
68 | //#define DISPLAY_ITTERATIONS
--------------------------------------------------------------------------------
/controller/dataStorage.h:
--------------------------------------------------------------------------------
1 | /* EEPROM based configuration data storage structure
2 |
3 | Whole flight controller configuration is being stored inside an CONFIG structure
4 | that can be accesed as data union (which is required for easy manipulation with
5 | the configuration data over serial and easy way of storing this data in EEPROM).
6 |
7 | This method allow us to do "smart" memory checking with the new data against
8 | data stored in EEPROM, which means we doesn't have to re-write whole configuration
9 | union inside the EEPROM, but we just modify bytes that changed.
10 |
11 | This will protect EEPROM from unnecessary writes, extending its lifetime
12 | (because EEPROM writes are limited, actual number of writes depends on the chip).
13 | */
14 |
15 | #if defined(__MK20DX128__)
16 | #define EEPROM_SIZE 512
17 | #endif
18 |
19 | #define EEPROM_VERSION 5
20 |
21 | struct AXIS_MAP_struct {
22 | uint8_t axis1:2;
23 | uint8_t axis1_sign:1;
24 | uint8_t axis2:2;
25 | uint8_t axis2_sign:1;
26 | uint8_t axis3:2;
27 | uint8_t axis3_sign:1;
28 | uint8_t initialized:1;
29 | };
30 |
31 | struct __attribute__((packed)) CONFIG_struct {
32 | uint8_t version;
33 | bool calibrateESC;
34 | uint16_t minimumArmedThrottle;
35 |
36 | // Sensor axis map
37 | struct AXIS_MAP_struct GYRO_AXIS_MAP;
38 | struct AXIS_MAP_struct ACCEL_AXIS_MAP;
39 | struct AXIS_MAP_struct MAG_AXIS_MAP;
40 |
41 | // Accelerometer
42 | int16_t ACCEL_BIAS[3];
43 |
44 | // RX
45 | uint8_t CHANNEL_ASSIGNMENT[16];
46 | uint64_t CHANNEL_FUNCTIONS[4];
47 |
48 | // Attitude
49 | float PID_YAW_c[4];
50 | float PID_PITCH_c[4];
51 | float PID_ROLL_c[4];
52 |
53 | // Rate
54 | float PID_YAW_m[4];
55 | float PID_PITCH_m[4];
56 | float PID_ROLL_m[4];
57 |
58 | float PID_BARO[4];
59 | float PID_SONAR[4];
60 |
61 | // GPS
62 | float PID_GPS[4];
63 | };
64 |
65 | union CONFIG_union {
66 | struct CONFIG_struct data;
67 | uint8_t raw[sizeof(struct CONFIG_struct)];
68 | };
69 |
70 | CONFIG_union CONFIG;
71 |
72 | void initializeEEPROM(void) {
73 | // Default settings should be initialized here
74 | CONFIG.data.version = EEPROM_VERSION;
75 | CONFIG.data.calibrateESC = 0;
76 | CONFIG.data.minimumArmedThrottle = 1100;
77 |
78 | // Accelerometer
79 | CONFIG.data.ACCEL_BIAS[XAXIS] = 0;
80 | CONFIG.data.ACCEL_BIAS[YAXIS] = 0;
81 | CONFIG.data.ACCEL_BIAS[ZAXIS] = 0;
82 |
83 | // RX
84 | CONFIG.data.CHANNEL_ASSIGNMENT[0] = 0;
85 | CONFIG.data.CHANNEL_ASSIGNMENT[1] = 1;
86 | CONFIG.data.CHANNEL_ASSIGNMENT[2] = 2;
87 | CONFIG.data.CHANNEL_ASSIGNMENT[3] = 3;
88 | CONFIG.data.CHANNEL_ASSIGNMENT[4] = 4;
89 | CONFIG.data.CHANNEL_ASSIGNMENT[5] = 5;
90 | CONFIG.data.CHANNEL_ASSIGNMENT[6] = 6;
91 | CONFIG.data.CHANNEL_ASSIGNMENT[7] = 7;
92 | CONFIG.data.CHANNEL_ASSIGNMENT[8] = 8;
93 | CONFIG.data.CHANNEL_ASSIGNMENT[9] = 9;
94 | CONFIG.data.CHANNEL_ASSIGNMENT[10] = 10;
95 | CONFIG.data.CHANNEL_ASSIGNMENT[11] = 11;
96 | CONFIG.data.CHANNEL_ASSIGNMENT[12] = 12;
97 | CONFIG.data.CHANNEL_ASSIGNMENT[13] = 13;
98 | CONFIG.data.CHANNEL_ASSIGNMENT[14] = 14;
99 | CONFIG.data.CHANNEL_ASSIGNMENT[15] = 15;
100 |
101 | CONFIG.data.CHANNEL_FUNCTIONS[0] = 0x04; // mode select ("stable mode" is set to trigger on AUX1-HIGH by default)
102 | CONFIG.data.CHANNEL_FUNCTIONS[1] = 0x00; // baro select
103 | CONFIG.data.CHANNEL_FUNCTIONS[2] = 0x00; // sonar select
104 | CONFIG.data.CHANNEL_FUNCTIONS[3] = 0x00; // GPS select
105 |
106 | // Altitude
107 | CONFIG.data.PID_YAW_c[P] = 4.0;
108 | CONFIG.data.PID_YAW_c[I] = 0.0;
109 | CONFIG.data.PID_YAW_c[D] = 0.0;
110 | CONFIG.data.PID_YAW_c[WG] = 25.0;
111 |
112 | CONFIG.data.PID_PITCH_c[P] = 4.0;
113 | CONFIG.data.PID_PITCH_c[I] = 0.0;
114 | CONFIG.data.PID_PITCH_c[D] = 0.0;
115 | CONFIG.data.PID_PITCH_c[WG] = 25.0;
116 |
117 | CONFIG.data.PID_ROLL_c[P] = 4.0;
118 | CONFIG.data.PID_ROLL_c[I] = 0.0;
119 | CONFIG.data.PID_ROLL_c[D] = 0.0;
120 | CONFIG.data.PID_ROLL_c[WG] = 25.0;
121 |
122 | // Rate
123 | CONFIG.data.PID_YAW_m[P] = 200.0;
124 | CONFIG.data.PID_YAW_m[I] = 5.0;
125 | CONFIG.data.PID_YAW_m[D] = 0.0;
126 | CONFIG.data.PID_YAW_m[WG] = 100.0;
127 |
128 | CONFIG.data.PID_PITCH_m[P] = 80.0;
129 | CONFIG.data.PID_PITCH_m[I] = 0.0;
130 | CONFIG.data.PID_PITCH_m[D] = -3.0;
131 | CONFIG.data.PID_PITCH_m[WG] = 1000.0;
132 |
133 | CONFIG.data.PID_ROLL_m[P] = 80.0;
134 | CONFIG.data.PID_ROLL_m[I] = 0.0;
135 | CONFIG.data.PID_ROLL_m[D] = -3.0;
136 | CONFIG.data.PID_ROLL_m[WG] = 1000.0;
137 |
138 | // Baro
139 | CONFIG.data.PID_BARO[P] = 25.0;
140 | CONFIG.data.PID_BARO[I] = 0.6;
141 | CONFIG.data.PID_BARO[D] = -1.0;
142 | CONFIG.data.PID_BARO[WG] = 25.0;
143 |
144 | // Sonar
145 | CONFIG.data.PID_SONAR[P] = 50.0;
146 | CONFIG.data.PID_SONAR[I] = 0.6;
147 | CONFIG.data.PID_SONAR[D] = -1.0;
148 | CONFIG.data.PID_SONAR[WG] = 25.0;
149 |
150 | // GPS
151 | CONFIG.data.PID_GPS[P] = 0.0;
152 | CONFIG.data.PID_GPS[I] = 0.0;
153 | CONFIG.data.PID_GPS[D] = 0.0;
154 | CONFIG.data.PID_GPS[WG] = 0.0;
155 |
156 | // This function will only initialize data variables
157 | // writeEEPROM() needs to be called manually to store this data in EEPROM
158 | }
159 |
160 | void writeEEPROM(void) {
161 | for (uint16_t i = 0; i < sizeof(struct CONFIG_struct); i++) {
162 | if (CONFIG.raw[i] != EEPROM.read(i)) {
163 | // Only re-write new data
164 | // blocks containing the same value will be left alone
165 | EEPROM.write(i, CONFIG.raw[i]);
166 | }
167 | }
168 | }
169 |
170 | void readEEPROM(void) {
171 | if (EEPROM.read(0) == 255) {
172 | // No EEPROM values detected, re-initialize
173 | initializeEEPROM();
174 | } else {
175 | // There "is" data in the EEPROM, read it all
176 | for (uint16_t i = 0; i < sizeof(struct CONFIG_struct); i++) {
177 | CONFIG.raw[i] = EEPROM.read(i);
178 | }
179 |
180 | // Verify version
181 | if (CONFIG.data.version != EEPROM_VERSION) {
182 | // Version doesn't match, re-initialize
183 | initializeEEPROM();
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/controller/math.h:
--------------------------------------------------------------------------------
1 | // Fast inverse square root implementation
2 | // @see: http://en.wikipedia.org/wiki/Fast_inverse_square_root
3 | float invSqrt(float number) {
4 | long i;
5 | float x, y;
6 | const float f = 1.5F;
7 |
8 | x = number * 0.5F;
9 | y = number;
10 | i = * ( long * ) &y;
11 | i = 0x5f375a86 - ( i >> 1 ); // older version with less accuracy = 0x5f3759df
12 | y = * ( float * ) &i;
13 | y = y * ( f - ( x * y * y ) );
14 |
15 | return y;
16 | }
17 |
18 | // Signed square root implementation
19 | float s_sqrt(float val) {
20 | if (val < 0.00) {
21 | // negative input = negative sqrt
22 | return -sqrt(abs(val));
23 | }
24 |
25 | return sqrt(val);
26 | }
27 |
28 | // Smooth Filter
29 | float filterSmooth(float currentData, float previousData, float smoothFactor) {
30 | return (previousData * (1.0 - smoothFactor) + (currentData * smoothFactor));
31 | }
--------------------------------------------------------------------------------
/controller/sensors.h:
--------------------------------------------------------------------------------
1 | #define GYROSCOPE_DETECTED 0x01
2 | #define ACCELEROMETER_DETECTED 0x02
3 | #define MAGNETOMETER_DETECTED 0x04
4 | #define BAROMETER_DETECTED 0x08
5 | #define GPS_DETECTED 0x10
6 |
7 | class SensorArray {
8 | public:
9 | uint16_t sensors_detected;
10 |
11 | SensorArray() {
12 | sensors_detected = 0x00;
13 | };
14 |
15 | void initializeGyro();
16 | void readGyroSum();
17 | void evaluateGyro();
18 | void initializeAccel();
19 | void calibrateAccel();
20 | void readAccelSum();
21 | void evaluateAccel();
22 | void initializeMag();
23 | void readMag();
24 | void evaluateMag();
25 | void initializeBaro();
26 | void readBaroSum();
27 | void evaluateBaroAltitude();
28 | void initializeGPS();
29 | void readGPS();
30 | void evaluateGPS();
31 |
32 | // I2C stuff
33 | void i2c_write8 (int16_t deviceAddress, uint8_t registerAddress, int16_t registerValue) {
34 | // I am using int16_t for dataValue becuase we don't know if we are writing
35 | // 8 bit signed or unsigned value.
36 |
37 | Wire.beginTransmission(deviceAddress);
38 | Wire.write(registerAddress);
39 | Wire.write(registerValue);
40 | Wire.endTransmission();
41 | };
42 |
43 | int16_t i2c_read16 (int16_t deviceAddress, uint8_t registerAddress) {
44 | int16_t data;
45 |
46 | Wire.beginTransmission(deviceAddress);
47 | Wire.write(registerAddress);
48 | Wire.endTransmission();
49 |
50 | Wire.requestFrom(deviceAddress, 2);
51 |
52 | data = (Wire.read() << 8) | Wire.read();
53 |
54 | return data;
55 | };
56 | };
57 |
58 | SensorArray sensors;
59 |
--------------------------------------------------------------------------------
/extras/Configurator/background.js:
--------------------------------------------------------------------------------
1 | chrome.app.runtime.onLaunched.addListener(function() {
2 | chrome.app.window.create('main.html', {
3 | frame: 'chrome',
4 | id: 'main-window',
5 | minWidth: 960,
6 | maxWidth: 960,
7 | minHeight: 550,
8 | maxHeight: 550
9 | }, function(main_window) {
10 | main_window.onClosed.addListener(function() {
11 | // connectionId is passed from the script side through the chrome.runtime.getBackgroundPage refference
12 | // allowing us to automatically close the port when application shut down
13 | if (connectionId != -1) {
14 | chrome.serial.close(connectionId, function() {
15 | console.log('CLEANUP: Connection to serial port was opened after application closed, closing the connection.');
16 | });
17 | }
18 | });
19 | });
20 | });
--------------------------------------------------------------------------------
/extras/Configurator/js/data_storage.js:
--------------------------------------------------------------------------------
1 | var sensors_alive = {
2 | gyro: 0,
3 | accel: 0,
4 | mag: 0,
5 | baro: 0,
6 | gps: 0
7 | }
8 |
9 | var AUX_triggered_mask = [0, 0];
10 |
11 | var sensor_data = {
12 | gyro: [0, 0, 0],
13 | accel: [0, 0, 0],
14 | mag: [0, 0, 0],
15 | baro: [0, 0],
16 | kinematics: [0, 0, 0],
17 |
18 | // plot data
19 | gyro_plot: [new Array(), new Array(), new Array()],
20 | accel_plot: [new Array(), new Array(), new Array()],
21 | mag_plot: [new Array(), new Array(), new Array()],
22 | baro_plot: new Array()
23 | }
24 |
25 | var receiver_data = {
26 | raw: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
27 | plot: [
28 | new Array(), new Array(), new Array(), new Array(), new Array(), new Array(), new Array(), new Array(),
29 | new Array(), new Array(), new Array(), new Array(), new Array(), new Array(), new Array(), new Array()
30 | ]
31 | }
32 |
33 | var motors = 0;
34 | var motors_output = [0, 0, 0, 0, 0, 0, 0, 0];
35 |
36 | var GPS_data = {
37 | lat: 0,
38 | lon: 0,
39 | course: 0,
40 | speed: 0,
41 | height: 0,
42 | accuracy: 0,
43 | fixtime: 0,
44 | sentences: 0,
45 | state: 0,
46 | sats: 0
47 | }
--------------------------------------------------------------------------------
/extras/Configurator/js/file_system.js:
--------------------------------------------------------------------------------
1 | function configuration_backup() {
2 | var chosenFileEntry = null;
3 |
4 | var accepts = [{
5 | extensions: ['txt']
6 | }];
7 |
8 | // generate timestamp for the backup file
9 | var d = new Date();
10 | var now = d.getUTCFullYear() + '.' + d.getDate() + '.' + (d.getMonth() + 1) + '.' + d.getHours() + '.' + d.getMinutes();
11 |
12 | // create or load the file
13 | chrome.fileSystem.chooseEntry({type: 'saveFile', suggestedName: 'phoenix_backup_' + now, accepts: accepts}, function(fileEntry) {
14 | if (!fileEntry) {
15 | command_log('No file selected');
16 | console.log('No file selected');
17 |
18 | return;
19 | }
20 |
21 | chosenFileEntry = fileEntry;
22 |
23 | // echo/console log path specified
24 | chrome.fileSystem.getDisplayPath(chosenFileEntry, function(path) {
25 | command_log('Backup file path: ' + path);
26 | console.log('Backup file path: ' + path);
27 | });
28 |
29 | // change file entry from read only to read/write
30 | chrome.fileSystem.getWritableEntry(chosenFileEntry, function(fileEntryWritable) {
31 | // check if file is writable
32 | chrome.fileSystem.isWritableEntry(fileEntryWritable, function(isWritable) {
33 | if (isWritable) {
34 | chosenFileEntry = fileEntryWritable;
35 |
36 | // crunch the config object
37 | var serialized_config_object = JSON.stringify(eepromConfig);
38 | var blob = new Blob([serialized_config_object], {type: 'text/plain'}); // first parameter for Blob needs to be an array
39 |
40 | chosenFileEntry.createWriter(function(writer) {
41 | writer.onerror = function (e) {
42 | console.error(e);
43 | };
44 |
45 | writer.onwriteend = function() {
46 | command_log('Write -- SUCCESSFUL ');
47 | console.log('Write SUCCESSFUL');
48 | };
49 |
50 | //writer.truncate(blob.size);
51 | writer.write(blob);
52 | }, function (e) {
53 | console.error(e);
54 | });
55 | } else {
56 | // Something went wrong or file is set to read only and cannot be changed
57 | command_log('File appears to be read ONLY ');
58 | console.log('File appears to be read only, sorry.');
59 | }
60 | });
61 | });
62 | });
63 | }
64 |
65 | function configuration_restore() {
66 | var chosenFileEntry = null;
67 |
68 | var accepts = [{
69 | extensions: ['txt']
70 | }];
71 |
72 | // load up the file
73 | chrome.fileSystem.chooseEntry({type: 'openFile', accepts: accepts}, function(fileEntry) {
74 | if (!fileEntry) {
75 | command_log('No file selected');
76 | console.log('No file selected');
77 |
78 | return;
79 | }
80 |
81 | chosenFileEntry = fileEntry;
82 |
83 | // echo/console log path specified
84 | chrome.fileSystem.getDisplayPath(chosenFileEntry, function(path) {
85 | command_log('Restore file path: ' + path);
86 | console.log('Restore file path: ' + path);
87 | });
88 |
89 | // read contents into variable
90 | chosenFileEntry.file(function(file) {
91 | var reader = new FileReader();
92 |
93 | reader.onerror = function (e) {
94 | console.error(e);
95 | };
96 |
97 | reader.onloadend = function(e) {
98 | command_log('Read SUCCESSFUL ');
99 | console.log('Read SUCCESSFUL');
100 |
101 | try { // check if string provided is a valid JSON
102 | var deserialized_config_object = JSON.parse(e.target.result);
103 |
104 | if (deserialized_config_object.version === undefined) throw 'no version parameter found';
105 | } catch (e) {
106 | // data provided != valid json object
107 | command_log('Data provided doesn\'t contain valid JSON string -- ABORTING ');
108 | console.log('Data provided != valid JSON string');
109 |
110 | return;
111 | }
112 |
113 | // replacing "old configuration" with configuration from backup file
114 | if (eepromConfig.version == deserialized_config_object.version) {
115 | command_log('EEPROM version number/pattern -- MATCH ');
116 | console.log('EEPROM version number/pattern matches backup EEEPROM file version/pattern');
117 | } else {
118 | command_log('EEPROM version number/pattern -- MISSMATCH (manual values re-validation is advised)');
119 | console.log('EEPROM version number/pattern doesn\'t match backup EEPROM file version/pattern (manual values re-validation is advised)');
120 | }
121 |
122 | eepromConfig = deserialized_config_object;
123 |
124 | // Send updated UNION to the flight controller
125 | sendUNION();
126 |
127 | // Refresh Initial UI
128 | $('#content').load("./tabs/initial_setup.html", tab_initialize_initial_setup);
129 | };
130 |
131 | reader.readAsText(file);
132 | });
133 | });
134 | }
--------------------------------------------------------------------------------
/extras/Configurator/js/union_process.js:
--------------------------------------------------------------------------------
1 | /* Code below contains an exact representation of eeprom configuration array
2 | that is stored inside the flight controller.
3 |
4 | Rest of the code extends the DataView low-level interface, that is now included
5 | by default inside the javascript engine (chrome v9, firefox v15, IE v10).
6 |
7 | You can read more about the DataView interface and its methods
8 | @ https://developer.mozilla.org/en-US/docs/JavaScript/Typed_arrays/DataView
9 | */
10 |
11 | var eepromConfig = {
12 | version: 0,
13 | calibrateESC: 0,
14 | minimumArmedThrottle: 0,
15 |
16 | SENSOR_AXIS_MAP: [0, 0, 0],
17 |
18 | ACCEL_BIAS: [0, 0, 0],
19 |
20 | CHANNEL_ASSIGNMENT: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
21 | CHANNEL_FUNCTIONS: [[0, 0], [0, 0], [0, 0], [0, 0]],
22 |
23 | PID_YAW_c: [0.0, 0.0, 0.0, 0],
24 | PID_PITCH_c: [0.0, 0.0, 0.0, 0],
25 | PID_ROLL_c: [0.0, 0.0, 0.0, 0],
26 |
27 | PID_YAW_m: [0.0, 0.0, 0.0, 0],
28 | PID_PITCH_m: [0.0, 0.0, 0.0, 0],
29 | PID_ROLL_m: [0.0, 0.0, 0.0, 0],
30 |
31 | PID_BARO: [0.0, 0.0, 0.0, 0],
32 | PID_SONAR: [0.0, 0.0, 0.0, 0],
33 | PID_GPS: [0.0, 0.0, 0.0, 0],
34 | };
35 | var eepromConfigSize;
36 |
37 | DataView.prototype.getString = function(str_length, byteOffset) {
38 | var value = '';
39 | for (var i = 0; i < str_length; ++i) {
40 | var char = this.getUint8(byteOffset + i);
41 | value += String.fromCharCode(char > 127 ? 65533 : char);
42 | }
43 |
44 | return value;
45 | }
46 |
47 | DataView.prototype.getChar = function(byteOffset) {
48 | return this.getString(1, byteOffset);
49 | }
50 |
51 | DataView.prototype.parseUNION = function (structure) {
52 | var needle = 0;
53 |
54 | structure.version = this.getUint8(needle++);
55 | structure.calibrateESC = this.getUint8(needle++);
56 | structure.minimumArmedThrottle = this.getUint16(needle, 1);
57 | needle += 2;
58 |
59 | for (var i = 0; i < structure.SENSOR_AXIS_MAP.length; i++) {
60 | structure.SENSOR_AXIS_MAP[i] = this.getInt16(needle, 1);
61 | needle += 2;
62 | }
63 |
64 | for (var i = 0; i < structure.ACCEL_BIAS.length; i++) {
65 | structure.ACCEL_BIAS[i] = this.getInt16(needle, 1);
66 | needle += 2;
67 | }
68 |
69 | for (var i = 0; i < structure.CHANNEL_ASSIGNMENT.length; i++) {
70 | structure.CHANNEL_ASSIGNMENT[i] = this.getUint8(needle, 1);
71 | needle += 1;
72 | }
73 |
74 | for (var i = 0; i < structure.CHANNEL_FUNCTIONS.length; i++) {
75 | structure.CHANNEL_FUNCTIONS[i][0] = this.getUint32(needle, 1);
76 | structure.CHANNEL_FUNCTIONS[i][1] = this.getUint32(needle + 4, 1);
77 | needle += 8;
78 | }
79 |
80 | for (var i = 0; i < 4; i++) {
81 | structure.PID_YAW_c[i] = this.getFloat32(needle, 1);
82 | structure.PID_PITCH_c[i] = this.getFloat32(needle + 16, 1);
83 | structure.PID_ROLL_c[i] = this.getFloat32(needle + 32, 1);
84 |
85 | structure.PID_YAW_m[i] = this.getFloat32(needle + 48, 1);
86 | structure.PID_PITCH_m[i] = this.getFloat32(needle + 64, 1);
87 | structure.PID_ROLL_m[i] = this.getFloat32(needle + 80, 1);
88 |
89 | structure.PID_BARO[i] = this.getFloat32(needle + 96, 1);
90 | structure.PID_SONAR[i] = this.getFloat32(needle + 112, 1);
91 | structure.PID_GPS[i] = this.getFloat32(needle + 128, 1);
92 |
93 | needle += 4;
94 | }
95 | }
96 |
97 | DataView.prototype.setUNION = function (structure) {
98 | var needle = 0;
99 |
100 | this.setUint8(needle++, structure.version);
101 | this.setUint8(needle++, structure.calibrateESC);
102 | this.setUint16(needle, structure.minimumArmedThrottle, 1);
103 | needle += 2;
104 |
105 | for (var i = 0; i < structure.SENSOR_AXIS_MAP.length; i++) {
106 | this.setInt16(needle, structure.SENSOR_AXIS_MAP[i], 1);
107 | needle += 2;
108 | }
109 |
110 | for (var i = 0; i < structure.ACCEL_BIAS.length; i++) {
111 | this.setInt16(needle, structure.ACCEL_BIAS[i], 1);
112 | needle += 2;
113 | }
114 |
115 | for (var i = 0; i < structure.CHANNEL_ASSIGNMENT.length; i++) {
116 | this.setUint8(needle, structure.CHANNEL_ASSIGNMENT[i], 1);
117 | needle += 1;
118 | }
119 |
120 | for (var i = 0; i < structure.CHANNEL_FUNCTIONS.length; i++) {
121 | this.setUint32(needle, structure.CHANNEL_FUNCTIONS[i][0], 1);
122 | this.setUint32(needle + 4, structure.CHANNEL_FUNCTIONS[i][1], 1);
123 | needle += 8;
124 | }
125 |
126 | for (var i = 0; i < 4; i++) {
127 | this.setFloat32(needle, structure.PID_YAW_c[i], 1);
128 | this.setFloat32(needle + 16, structure.PID_PITCH_c[i], 1);
129 | this.setFloat32(needle + 32, structure.PID_ROLL_c[i], 1);
130 |
131 | this.setFloat32(needle + 48, structure.PID_YAW_m[i], 1);
132 | this.setFloat32(needle + 64, structure.PID_PITCH_m[i], 1);
133 | this.setFloat32(needle + 80, structure.PID_ROLL_m[i], 1);
134 |
135 | this.setFloat32(needle + 96, structure.PID_BARO[i], 1);
136 | this.setFloat32(needle + 112, structure.PID_SONAR[i], 1);
137 | this.setFloat32(needle + 128, structure.PID_GPS[i], 1);
138 |
139 | needle += 4;
140 | }
141 | }
142 |
143 | function requestUNION() {
144 | command_log('Requesting configuration UNION from Flight Controller');
145 | send_message(PSP.PSP_REQ_CONFIGURATION, 1);
146 | }
147 |
148 | function sendUNION() {
149 | command_log('Sending Configuration UNION to Flight Controller ...');
150 |
151 | var eepromConfigBytes = new ArrayBuffer(eepromConfigSize);
152 | var view = new DataView(eepromConfigBytes, 0);
153 | view.setUNION(eepromConfig);
154 |
155 | var data = new Uint8Array(eepromConfigBytes);
156 | send_message(PSP.PSP_SET_CONFIGURATION, data);
157 | }
--------------------------------------------------------------------------------
/extras/Configurator/main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Phoenix Configurator
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | 115200
40 | 57600
41 | 38400
42 | 28800
43 | 19200
44 | 14400
45 | 9600
46 | 4800
47 | 2400
48 | 1200
49 | 300
50 |
51 |
52 |
53 |
54 | 0
55 | 1
56 | 2
57 | 3
58 | 4
59 | 5
60 | 6
61 | 7
62 | 8
63 | 9
64 | 10
65 |
66 |
67 |
68 | Connect
69 | Refresh
70 |
71 |
72 |
73 |
74 |
75 | Gyroscope
76 | Accelerometer
77 | Magnetometer
78 | Barometer
79 | GPS
80 |
81 |
82 |
83 |
84 | Port utilization: 0 %
85 |
86 |
90 |
91 |
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/extras/Configurator/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Phoenix Configurator",
3 | "description": "Application required to change Phoenix configuration stored in EEPROM",
4 | "version": "0.1",
5 | "manifest_version": 2,
6 | "minimum_chrome_version": "23",
7 |
8 | "app": {
9 | "background": {
10 | "scripts": [ "background.js" ]
11 | }
12 | },
13 |
14 | "permissions": [
15 | "serial",
16 | "storage",
17 | "fileSystem",
18 | "fileSystem.write"
19 | ]
20 | }
--------------------------------------------------------------------------------
/extras/Configurator/readme.txt:
--------------------------------------------------------------------------------
1 | Ubuntu (and ubuntu distro based) users might need to add their selfs to the dialout group
2 | otherwise they would not have enough permissions to join the serial bus and configurator
3 | would just end up returning an "Could not join the serial bus" error.
4 | sudo usermod -aG dialout YOUR_USERNAME
--------------------------------------------------------------------------------
/extras/Configurator/tabs/about.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Phoenix Configurator was designed to improve user experience by not requiring extensive programming knowledge
4 | needed to do proper sensor calibration and debugging of various flight control elements (kinematics, motor outputs, etc).
5 |
6 | This also allows users to do PID tuning right from the configurator (without the need to reflash the flight controller afterwards).
7 | Because all of the configuration changes are executed on the flight controllers side in real time.
8 |
9 |
10 |
11 | People that supported the Phoenix project (with money, hardware, or otherwise) [alphabetical]
12 |
13 |
14 | Andreas "fiendie" Tacke
15 | Christopher "Shadow6363" Cope
16 | Kari "kha" Hautio
17 | Marvin Stuart
18 | Matheus "X-warrior" Bratfisch
19 | p0staL
20 | ReadError
21 | Stuart Pollitt
22 |
23 |
24 |
25 |
26 | Created by cTn
27 |
28 |
--------------------------------------------------------------------------------
/extras/Configurator/tabs/channel_assigner.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/extras/Configurator/tabs/channel_assigner.js:
--------------------------------------------------------------------------------
1 | function tab_initialize_channel_assigner() {
2 | var i = 0;
3 |
4 | $('#content table.channel_assigner input').each(function() {
5 | $(this).val(eepromConfig.CHANNEL_ASSIGNMENT[i++]);
6 | });
7 |
8 | // UI hooks
9 | $('#content table.channel_assigner input').focus(function() {
10 | $(this).data('prev_val', $(this).val());
11 | $(this).val(''); // empty input field
12 | });
13 |
14 | $('#content table.channel_assigner input').blur(function() {
15 | if ($(this).val() == '') { // if input field is empty restore previous value
16 | $(this).val($(this).data('prev_val'));
17 | }
18 | });
19 |
20 | $('#content table.channel_assigner a.update').click(function() {
21 | var parent = $(this).parent().parent();
22 |
23 | // - 1 because .index() starts at 1 not 0
24 | eepromConfig.CHANNEL_ASSIGNMENT[parent.index() - 1] = parseInt($('input', parent).val());
25 |
26 | // Send updated UNION to the flight controller
27 | sendUNION();
28 | });
29 | }
--------------------------------------------------------------------------------
/extras/Configurator/tabs/function_triggers.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | AUX 1
5 | AUX 2
6 | AUX 3
7 | AUX 4
8 | AUX 5
9 | AUX 6
10 | AUX 7
11 | AUX 8
12 | AUX 9
13 | AUX 10
14 | AUX 11
15 | AUX 12
16 |
17 |
18 |
19 |
20 | Name
21 | L
22 | M
23 | H
24 |
25 | L
26 | M
27 | H
28 |
29 | L
30 | M
31 | H
32 |
33 | L
34 | M
35 | H
36 |
37 | L
38 | M
39 | H
40 |
41 | L
42 | M
43 | H
44 |
45 | L
46 | M
47 | H
48 |
49 | L
50 | M
51 | H
52 |
53 | L
54 | M
55 | H
56 |
57 | L
58 | M
59 | H
60 |
61 | L
62 | M
63 | H
64 |
65 | L
66 | M
67 | H
68 |
69 |
70 |
Update
71 |
72 |
73 | Options with orange background color indicate current AUX channel value .
74 | If you are unsure about how this works, try flipping the AUX switches on your Transmitter and watch how the output / background
75 | changes on the screen.
76 |
77 |
--------------------------------------------------------------------------------
/extras/Configurator/tabs/function_triggers.js:
--------------------------------------------------------------------------------
1 | function tab_initialize_function_triggers() {
2 | // AUX names manually defined
3 | var AUX_names = [
4 | 'Stable mode (gyro + acc)',
5 | 'Altitude hold (barometer)',
6 | 'Altitude hold (sonar)',
7 | 'GPS (not implemented)'
8 | ];
9 |
10 | // generate table from the supplied AUX names and AUX data
11 | for (var i = 0; i < eepromConfig.CHANNEL_FUNCTIONS.length; i++) {
12 | $('.tab-function_triggers .functions > tbody:last').append(
13 | '' +
14 | '' + AUX_names[i] + ' ' +
15 | // AUX 1
16 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 0) +
17 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 1) +
18 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 2) +
19 |
20 | // AUX 2
21 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 3) +
22 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 4) +
23 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 5) +
24 |
25 | // AUX 3
26 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 6) +
27 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 7) +
28 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 8) +
29 |
30 | // AUX 4
31 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 9) +
32 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 10) +
33 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 11) +
34 |
35 | // AUX 5
36 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 12) +
37 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 13) +
38 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 14) +
39 |
40 | // AUX 6
41 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 15) +
42 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 16) +
43 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 17) +
44 |
45 | // AUX 7
46 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 18) +
47 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 19) +
48 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 20) +
49 |
50 | // AUX 8
51 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 21) +
52 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 22) +
53 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 23) +
54 |
55 | // AUX 9
56 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 24) +
57 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 25) +
58 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 26) +
59 |
60 | // AUX 10
61 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 27) +
62 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 28) +
63 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 29) +
64 |
65 | // AUX 11
66 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 30) +
67 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][0], 31) +
68 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][1], 32) +
69 |
70 | // AUX 12
71 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][1], 33) +
72 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][1], 34) +
73 | box_check(eepromConfig.CHANNEL_FUNCTIONS[i][1], 35) +
74 | ' '
75 | );
76 | }
77 |
78 | $('.tab-function_triggers a.update').click(function() {
79 | // catch the input changes
80 | var main_needle = 0;
81 | var needle = 0;
82 | $('.tab-function_triggers .functions input').each(function() {
83 | if ($(this).is(':checked')) {
84 | if (needle < 32) {
85 | eepromConfig.CHANNEL_FUNCTIONS[main_needle][0] = bit_set(eepromConfig.CHANNEL_FUNCTIONS[main_needle][0], needle);
86 | } else if (needle >= 32 && needle < 64) {
87 | eepromConfig.CHANNEL_FUNCTIONS[main_needle][1] = bit_set(eepromConfig.CHANNEL_FUNCTIONS[main_needle][1], needle - 32);
88 | }
89 | } else {
90 | if (needle < 32) {
91 | eepromConfig.CHANNEL_FUNCTIONS[main_needle][0] = bit_clear(eepromConfig.CHANNEL_FUNCTIONS[main_needle][0], needle);
92 | } else if (needle >= 32 && needle < 64) {
93 | eepromConfig.CHANNEL_FUNCTIONS[main_needle][1] = bit_clear(eepromConfig.CHANNEL_FUNCTIONS[main_needle][1], needle - 32);
94 | }
95 | }
96 |
97 | needle++;
98 |
99 | if (needle >= 36) { // 12 aux * 3 checkboxes = 36 bits per line
100 | main_needle++;
101 |
102 | needle = 0;
103 | }
104 | });
105 |
106 | // Send updated UNION to the flight controller
107 | sendUNION();
108 | });
109 |
110 | // request AUX mask from flight controller
111 | timers.push(setInterval(AUX_pull, 50));
112 | }
113 |
114 | function AUX_pull() {
115 | // Update the classes
116 | var needle = 0;
117 | $('.tab-function_triggers .functions th:not(.name)').each(function() {
118 | if (needle < 32) {
119 | if (bit_check(AUX_triggered_mask[0], needle)) { // 1
120 | $(this).addClass('on');
121 | } else { // 0
122 | $(this).removeClass('on');
123 | }
124 | } else if (needle >= 32 && needle < 64) {
125 | if (bit_check(AUX_triggered_mask[1], needle)) { // 1
126 | $(this).addClass('on');
127 | } else { // 0
128 | $(this).removeClass('on');
129 | }
130 | }
131 |
132 | needle++;
133 | });
134 |
135 |
136 | // request new data
137 | send_message(PSP.PSP_REQ_AUX_TRIGGERED, 1);
138 | }
139 |
140 | function box_check(num, pos) {
141 | if (bit_check(num, pos)) { // 1
142 | return ' ';
143 | } else { // 0
144 | return ' ';
145 | }
146 | }
--------------------------------------------------------------------------------
/extras/Configurator/tabs/gps.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
GPS info
4 |
5 | Latitude: 0
6 | Longitude: 0
7 | Speed: 0
8 | Height: 0
9 | Accuracy: 0
10 | Heading: 0
11 | State: 0
12 | Satellites: 0
13 |
14 |
15 |
16 |
GPS Heading
17 |
18 |
N
19 |
E
20 |
S
21 |
W
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/extras/Configurator/tabs/gps.js:
--------------------------------------------------------------------------------
1 | function tab_initialize_gps() {
2 |
3 | // start pulling gps data (only if gps was detected
4 | if (sensors_alive.gps) {
5 | timers.push(setInterval(gps_pull, 100)); // 10 Hz - more then enough, as most of the modules run on 1-5Hz
6 | }
7 | }
8 |
9 | function gps_pull() {
10 | // update UI with latest data
11 | var e_inf = $('.tab-gps .gps-info dl');
12 | $('dd:eq(0)', e_inf).html((GPS_data.lat).toFixed(4) + ' deg');
13 | $('dd:eq(1)', e_inf).html((GPS_data.lon).toFixed(4) + ' deg');
14 | $('dd:eq(2)', e_inf).html((GPS_data.speed).toFixed(2) + ' km / h');
15 | $('dd:eq(3)', e_inf).html(GPS_data.height + ' m');
16 | $('dd:eq(4)', e_inf).html((GPS_data.accuracy).toFixed(2) + ' m');
17 | $('dd:eq(5)', e_inf).html((GPS_data.course).toFixed(0) + ' deg');
18 |
19 | if (GPS_data.state == 1) { // no fix
20 | $('dd:eq(6)', e_inf).html('No fix');
21 | } else if (GPS_data.state == 2) { // 2D fix
22 | $('dd:eq(6)', e_inf).html('2D fix');
23 | } else if (GPS_data.state == 3) { // 3D fix
24 | $('dd:eq(6)', e_inf).html('3D fix');
25 | }
26 |
27 | $('dd:eq(7)', e_inf).html(GPS_data.sats); // number of satellites
28 |
29 | $('div.compass .pointer').css('-webkit-transform', 'rotate(' + (GPS_data.course) + 'deg)');
30 |
31 | // request new data
32 | send_message(PSP.PSP_REQ_GPS, 1);
33 | }
--------------------------------------------------------------------------------
/extras/Configurator/tabs/initial_setup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Calibrate ESCs
4 |
5 | Requesting ESC calibration will result in ESC throttle range calibration on next flight controller startup.
6 | Its recommended to remove propellers before calibrating (for safety).
7 |
8 |
9 |
10 |
11 |
25 |
26 | Place your UAV on a flat surface (properly leveled desk works great) and click on the calibration button.
27 | Try not to bump into the desk while calibration is running.
28 | Calibration will take approximately 2 seconds , you can view the raw output from calibration in console.
29 |
30 | Manual calibration offset tuning is also available (if you choose to use it), this is an advanced feature implemented for advanced users and developers .
31 | Standard users should stick to the automatic accelerometer calibration.
32 | You can view the calibration "effectivity" in 3D Vehicle tab .
33 |
34 |
35 |
36 |
37 |
41 |
42 | Minimum Armed throttle defines the minimum "speed " of the motors when you arm your flight controller.
43 | Default is 1100 (that means 10% throttle, 2000 = 100% throttle).
44 |
45 |
46 |
47 |
Initialize EEPROM
48 |
49 | This step is mostly "optional" as Flight Controller automatically re-initializes the EEPROM after firmware upload.
50 | In case you encounter "weird " values/behaviour in the PID Tuning section, this is the button to press.
51 | Re-powering the Flight Controller afterwards is recommended .
52 |
53 |
54 |
55 |
56 |
60 |
61 | In case often re-flashing of the flight software (which in most cases wipes the EEPROM memory), you can backup
62 | and later restore most of the primary configuration values (PID, accel calibration, accel offset, channel mapping, etc) stored inside EEPROM memory.
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/extras/Configurator/tabs/initial_setup.js:
--------------------------------------------------------------------------------
1 | function tab_initialize_initial_setup() {
2 | // Fill in the manual calibration values from eeprom
3 | var i = 0;
4 | $('#content .calibrateAccelManual input').each(function() {
5 | $(this).val(eepromConfig.ACCEL_BIAS[i++]);
6 | });
7 |
8 | $('#content .minimumThrottle input').val(eepromConfig.minimumArmedThrottle);
9 |
10 | $('#content .calibrateESC').click(function() {
11 | eepromConfig.calibrateESC = parseInt(1);
12 |
13 | // Send updated UNION to the flight controller
14 | sendUNION();
15 | });
16 |
17 | $('#content .calibrateAccel').click(function() {
18 | // Start accel calibration
19 | command_log('Starting Accel calibration...');
20 | send_message(PSP.PSP_SET_ACCEL_CALIBRATION, 1);
21 | });
22 |
23 | $('#content .calibrateAccelManualUpdate').click(function() {
24 | // Update eeprom object
25 | var i = 0;
26 | $('.tab-initial_setup .calibrateAccelManual input').each(function() {
27 | eepromConfig.ACCEL_BIAS[i++] = parseInt($(this).val());
28 | });
29 |
30 | // Send updated UNION to the flight controller
31 | sendUNION();
32 | });
33 |
34 | $('#content .minimumThrottleUpdate').click(function() {
35 | // Update eeprom object
36 | eepromConfig.minimumArmedThrottle = parseInt($('#content .minimumThrottle input').val());
37 |
38 | // Send updated UNION to the flight controller
39 | sendUNION();
40 | });
41 |
42 | $('#content .initializeEEPROM').click(function() {
43 | // initialize EEPROM
44 | command_log('Requesting EEPROM re-initialization.');
45 | send_message(PSP.PSP_SET_EEPROM_REINIT, 1);
46 | });
47 |
48 | $('#content .backup').click(configuration_backup);
49 |
50 | $('#content .restore').click(configuration_restore);
51 | }
52 |
53 | function process_accel_calibration() {
54 | command_log('Accel Calibration data received: ' + eepromConfig.ACCEL_BIAS);
55 |
56 | // Update the manual calibration inputs with latest data
57 | var i = 0;
58 | $('#content .calibrateAccelManual input').each(function() {
59 | $(this).val(eepromConfig.ACCEL_BIAS[i++]);
60 | });
61 |
62 | $('#content .minimumThrottle input').val(eepromConfig.minimumArmedThrottle);
63 | }
--------------------------------------------------------------------------------
/extras/Configurator/tabs/motor_output.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Motor - 1
4 | Motor - 2
5 | Motor - 3
6 | Motor - 4
7 | Motor - 5
8 | Motor - 6
9 | Motor - 7
10 | Motor - 8
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
33 |
34 |
35 | 0 %
36 | 0 %
37 | 0 %
38 | 0 %
39 | 0 %
40 | 0 %
41 | 0 %
42 | 0 %
43 |
44 |
STOP ALL
45 |
--------------------------------------------------------------------------------
/extras/Configurator/tabs/motor_output.js:
--------------------------------------------------------------------------------
1 | function tab_initialize_motor_output() {
2 | // Request current state of motors
3 | send_message(PSP.PSP_REQ_MOTORS_OUTPUT, 1);
4 |
5 | // Enable specific titles
6 | for (var i = 0; i < motors; i++) {
7 | $('div.tab-motor_output .titles li:eq(' + i + ')').addClass('active');
8 | }
9 |
10 | // Disable the rest
11 | $('ul.sliders input').each(function() {
12 | var index = $(this).parent().index();
13 |
14 | if (index >= motors) {
15 | $(this).attr('disabled', 'disabled');
16 | }
17 | });
18 |
19 | $('.tab-motor_output .stop').click(function() {
20 | // reset all to 0
21 | $('ul.sliders input').each(function() {
22 | if ($(this).attr('disabled') != 'disabled') { // Protects .change event firing on motors that are disabled
23 | $(this).val(0);
24 |
25 | // trigger change events
26 | $(this).change();
27 | }
28 | });
29 |
30 | });
31 |
32 | // Delayed initialize (due to current motor output requirements
33 | setTimeout(function() {
34 | for (var i = 0; i < motors; i++) {
35 | $('ul.sliders input').eq(i).val((motors_output[i] - 1000) / 10);
36 | $('ul.values li').eq(i).html(((motors_output[i] - 1000) / 10) + ' %');
37 | }
38 |
39 | // Change handler is "hooked up" after all the data is processed (to avoid double sending of the values)
40 | $('ul.sliders input').change(function() {
41 | var motor_n = parseInt($(this).parent().index()); // motor number
42 | var motor_v = parseInt($(this).val()); // motor value
43 |
44 | // Update UI
45 | $('ul.values li').eq(motor_n).html(motor_v + ' %');
46 |
47 | // Send data to flight controller
48 | send_message(PSP.PSP_SET_MOTOR_TEST_VALUE, [motor_n, motor_v]);
49 | });
50 |
51 | // request motor out data from flight controller
52 | timers.push(setInterval(motor_pull, 50));
53 | }, 25);
54 | }
55 |
56 | function motor_pull() {
57 | // Render data
58 | for (var i = 0; i < motors; i++) {
59 | motors_output[i] -= 1000;
60 | var margin_top = 130.0 - (motors_output[i] * 0.13);
61 | var height = (motors_output[i] * 0.13);
62 | var color = parseInt(motors_output[i] * 0.256);
63 | $('.motor-' + i + ' .indicator').css({'margin-top' : margin_top + 'px', 'height' : height + 'px', 'background-color' : 'rgb(' + color + ',0,0)'});
64 | }
65 |
66 | // request new data
67 | send_message(PSP.PSP_REQ_MOTORS_OUTPUT, 1);
68 | }
69 |
--------------------------------------------------------------------------------
/extras/Configurator/tabs/pid_tuning.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/extras/Configurator/tabs/pid_tuning.js:
--------------------------------------------------------------------------------
1 | function tab_initialize_pid_tuning() {
2 | var i = 0;
3 |
4 | // Command
5 | $('#content .command-yaw input').each(function() {
6 | if (i != 3) {
7 | $(this).val(eepromConfig.PID_YAW_c[i++].toFixed(2));
8 | } else {
9 | $(this).val(eepromConfig.PID_YAW_c[i++].toFixed(0));
10 | }
11 | });
12 |
13 | i = 0;
14 | $('#content .command-pitch input').each(function() {
15 | if (i != 3) {
16 | $(this).val(eepromConfig.PID_PITCH_c[i++].toFixed(2));
17 | } else {
18 | $(this).val(eepromConfig.PID_PITCH_c[i++].toFixed(0));
19 | }
20 | });
21 |
22 | i = 0;
23 | $('#content .command-roll input').each(function() {
24 | if (i != 3) {
25 | $(this).val(eepromConfig.PID_ROLL_c[i++].toFixed(2));
26 | } else {
27 | $(this).val(eepromConfig.PID_ROLL_c[i++].toFixed(0));
28 | }
29 | });
30 |
31 | // Motor
32 | i = 0;
33 | $('#content .motor-yaw input').each(function() {
34 | if (i != 3) {
35 | $(this).val(eepromConfig.PID_YAW_m[i++].toFixed(2));
36 | } else {
37 | $(this).val(eepromConfig.PID_YAW_m[i++].toFixed(0));
38 | }
39 | });
40 |
41 | i = 0;
42 | $('#content .motor-pitch input').each(function() {
43 | if (i != 3) {
44 | $(this).val(eepromConfig.PID_PITCH_m[i++].toFixed(2));
45 | } else {
46 | $(this).val(eepromConfig.PID_PITCH_m[i++].toFixed(0));
47 | }
48 | });
49 |
50 | i = 0;
51 | $('#content .motor-roll input').each(function() {
52 | if (i != 3) {
53 | $(this).val(eepromConfig.PID_ROLL_m[i++].toFixed(2));
54 | } else {
55 | $(this).val(eepromConfig.PID_ROLL_m[i++].toFixed(0));
56 | }
57 | });
58 |
59 | // Baro
60 | i = 0;
61 | $('#content .baro input').each(function() {
62 | if (i != 3) {
63 | $(this).val(eepromConfig.PID_BARO[i++].toFixed(2));
64 | } else {
65 | $(this).val(eepromConfig.PID_BARO[i++].toFixed(0));
66 | }
67 | });
68 |
69 | // Sonar
70 | i = 0;
71 | $('#content .sonar input').each(function() {
72 | if (i != 3) {
73 | $(this).val(eepromConfig.PID_SONAR[i++].toFixed(2));
74 | } else {
75 | $(this).val(eepromConfig.PID_SONAR[i++].toFixed(0));
76 | }
77 | });
78 |
79 | // GPS
80 | i = 0;
81 | $('#content .GPS input').each(function() {
82 | if (i != 3) {
83 | $(this).val(eepromConfig.PID_GPS[i++].toFixed(2));
84 | } else {
85 | $(this).val(eepromConfig.PID_GPS[i++].toFixed(0));
86 | }
87 | });
88 |
89 | // UI hooks
90 | $('#content .pid_tuning a.update').click(function() {
91 | var parent = $(this).parent().parent();
92 |
93 | var i = 0;
94 | switch (parent.index()) {
95 | case 1: // command yaw
96 | $('input', parent).each(function() {
97 | eepromConfig.PID_YAW_c[i] = parseFloat($(this).val());
98 | i++;
99 | });
100 | break;
101 | case 2: // command pitch
102 | $('input', parent).each(function() {
103 | eepromConfig.PID_PITCH_c[i] = parseFloat($(this).val());
104 | i++;
105 | });
106 | break;
107 | case 3: // command roll
108 | $('input', parent).each(function() {
109 | eepromConfig.PID_ROLL_c[i] = parseFloat($(this).val());
110 | i++;
111 | });
112 | break;
113 | case 5: // yaw
114 | $('input', parent).each(function() {
115 | eepromConfig.PID_YAW_m[i] = parseFloat($(this).val());
116 | i++;
117 | });
118 | break;
119 | case 6: // pitch
120 | $('input', parent).each(function() {
121 | eepromConfig.PID_PITCH_m[i] = parseFloat($(this).val());
122 | i++;
123 | });
124 | break;
125 | case 7: // roll
126 | $('input', parent).each(function() {
127 | eepromConfig.PID_ROLL_m[i] = parseFloat($(this).val());
128 | i++;
129 | });
130 | break;
131 | case 9: // baro
132 | $('input', parent).each(function() {
133 | eepromConfig.PID_BARO[i] = parseFloat($(this).val());
134 | i++;
135 | });
136 | break;
137 | case 10: // sonar
138 | $('input', parent).each(function() {
139 | eepromConfig.PID_SONAR[i] = parseFloat($(this).val());
140 | i++;
141 | });
142 | break;
143 | case 11: // GPS
144 | $('input', parent).each(function() {
145 | eepromConfig.PID_GPS[i] = parseFloat($(this).val());
146 | i++;
147 | });
148 | break;
149 | }
150 |
151 | // Send updated UNION to the flight controller
152 | sendUNION();
153 | });
154 | };
--------------------------------------------------------------------------------
/extras/Configurator/tabs/rx.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/extras/Configurator/tabs/rx.js:
--------------------------------------------------------------------------------
1 | function tab_initialize_rx() {
2 | // Setup variables
3 | samples_i = 300;
4 |
5 | for (var i = 0; i < receiver_data.plot.length; i++) {
6 | receiver_data.plot[i] = [];
7 | }
8 |
9 | for (var i = 0; i <= 300; i++) {
10 | receiver_data.plot[0].push([i, 0]);
11 | receiver_data.plot[1].push([i, 0]);
12 | receiver_data.plot[2].push([i, 0]);
13 | receiver_data.plot[3].push([i, 0]);
14 | receiver_data.plot[4].push([i, 0]);
15 | receiver_data.plot[5].push([i, 0]);
16 | receiver_data.plot[6].push([i, 0]);
17 | receiver_data.plot[7].push([i, 0]);
18 | receiver_data.plot[8].push([i, 0]);
19 | receiver_data.plot[9].push([i, 0]);
20 | receiver_data.plot[10].push([i, 0]);
21 | receiver_data.plot[11].push([i, 0]);
22 | receiver_data.plot[12].push([i, 0]);
23 | receiver_data.plot[13].push([i, 0]);
24 | receiver_data.plot[14].push([i, 0]);
25 | receiver_data.plot[15].push([i, 0]);
26 | }
27 |
28 | // Graph definitions
29 | e_graph_receiver = document.getElementById("graph_receiver");
30 |
31 | receiver_options = {
32 | shadowSize: 0,
33 | yaxis : {
34 | max: 2200,
35 | min: 800
36 | },
37 | xaxis : {
38 | },
39 | grid : {
40 | backgroundColor: "#FFFFFF"
41 | },
42 | legend : {
43 | backgroundOpacity: 0
44 | }
45 | }
46 |
47 | // request receiver data from flight controller
48 | timers.push(setInterval(rx_poll, 50));
49 | }
50 |
51 | function RX_channel_name(num) {
52 | var name;
53 |
54 | switch (num) {
55 | case 0:
56 | name = 'ROLL';
57 | break;
58 | case 1:
59 | name = 'PITCH';
60 | break;
61 | case 2:
62 | name = 'THROTTLE';
63 | break;
64 | case 3:
65 | name = 'YAW/RUDDER';
66 | break;
67 | case 4:
68 | name = 'AUX 1';
69 | break;
70 | case 5:
71 | name = 'AUX 2';
72 | break;
73 | case 6:
74 | name = 'AUX 3';
75 | break;
76 | case 7:
77 | name = 'AUX 4';
78 | break;
79 |
80 | case 8:
81 | name = 'AUX 5';
82 | break;
83 | case 9:
84 | name = 'AUX 6';
85 | break;
86 | case 10:
87 | name = 'AUX 7';
88 | break;
89 | case 11:
90 | name = 'AUX 8';
91 | break;
92 | case 12:
93 | name = 'AUX 9';
94 | break;
95 | case 13:
96 | name = 'AUX 10';
97 | break;
98 | case 14:
99 | name = 'AUX 11';
100 | break;
101 | case 15:
102 | name = 'AUX 12';
103 | break;
104 | }
105 |
106 | return name;
107 | }
108 |
109 | function rx_poll() {
110 | // push latest data to the main array
111 | receiver_data.plot[0].push([samples_i, receiver_data.raw[0]]);
112 | receiver_data.plot[1].push([samples_i, receiver_data.raw[1]]);
113 | receiver_data.plot[2].push([samples_i, receiver_data.raw[2]]);
114 | receiver_data.plot[3].push([samples_i, receiver_data.raw[3]]);
115 | receiver_data.plot[4].push([samples_i, receiver_data.raw[4]]);
116 | receiver_data.plot[5].push([samples_i, receiver_data.raw[5]]);
117 | receiver_data.plot[6].push([samples_i, receiver_data.raw[6]]);
118 | receiver_data.plot[7].push([samples_i, receiver_data.raw[7]]);
119 | receiver_data.plot[8].push([samples_i, receiver_data.raw[8]]);
120 | receiver_data.plot[9].push([samples_i, receiver_data.raw[9]]);
121 | receiver_data.plot[10].push([samples_i, receiver_data.raw[10]]);
122 | receiver_data.plot[11].push([samples_i, receiver_data.raw[11]]);
123 | receiver_data.plot[12].push([samples_i, receiver_data.raw[12]]);
124 | receiver_data.plot[13].push([samples_i, receiver_data.raw[13]]);
125 | receiver_data.plot[14].push([samples_i, receiver_data.raw[14]]);
126 | receiver_data.plot[15].push([samples_i, receiver_data.raw[15]]);
127 |
128 | // Remove old data from array
129 | while (receiver_data.plot[0].length > 300) {
130 | receiver_data.plot[0].shift();
131 | receiver_data.plot[1].shift();
132 | receiver_data.plot[2].shift();
133 | receiver_data.plot[3].shift();
134 | receiver_data.plot[4].shift();
135 | receiver_data.plot[5].shift();
136 | receiver_data.plot[6].shift();
137 | receiver_data.plot[7].shift();
138 | receiver_data.plot[8].shift();
139 | receiver_data.plot[9].shift();
140 | receiver_data.plot[10].shift();
141 | receiver_data.plot[11].shift();
142 | receiver_data.plot[12].shift();
143 | receiver_data.plot[13].shift();
144 | receiver_data.plot[14].shift();
145 | receiver_data.plot[15].shift();
146 | }
147 |
148 | Flotr.draw(e_graph_receiver, [
149 | {data: receiver_data.plot[0], label: "CH-0 - " + RX_channel_name(eepromConfig.CHANNEL_ASSIGNMENT[0]) + " [" + receiver_data.raw[0] + "]"},
150 | {data: receiver_data.plot[1], label: "CH-1 - " + RX_channel_name(eepromConfig.CHANNEL_ASSIGNMENT[1]) + " [" + receiver_data.raw[1] + "]"},
151 | {data: receiver_data.plot[2], label: "CH-2 - " + RX_channel_name(eepromConfig.CHANNEL_ASSIGNMENT[2]) + " [" + receiver_data.raw[2] + "]"},
152 | {data: receiver_data.plot[3], label: "CH-3 - " + RX_channel_name(eepromConfig.CHANNEL_ASSIGNMENT[3]) + " [" + receiver_data.raw[3] + "]"},
153 | {data: receiver_data.plot[4], label: "CH-4 - " + RX_channel_name(eepromConfig.CHANNEL_ASSIGNMENT[4]) + " [" + receiver_data.raw[4] + "]"},
154 | {data: receiver_data.plot[5], label: "CH-5 - " + RX_channel_name(eepromConfig.CHANNEL_ASSIGNMENT[5]) + " [" + receiver_data.raw[5] + "]"},
155 | {data: receiver_data.plot[6], label: "CH-6 - " + RX_channel_name(eepromConfig.CHANNEL_ASSIGNMENT[6]) + " [" + receiver_data.raw[6] + "]"},
156 | {data: receiver_data.plot[7], label: "CH-7 - " + RX_channel_name(eepromConfig.CHANNEL_ASSIGNMENT[7]) + " [" + receiver_data.raw[7] + "]"},
157 | {data: receiver_data.plot[8], label: "CH-8 - " + RX_channel_name(eepromConfig.CHANNEL_ASSIGNMENT[8]) + " [" + receiver_data.raw[8] + "]"},
158 | {data: receiver_data.plot[9], label: "CH-9 - " + RX_channel_name(eepromConfig.CHANNEL_ASSIGNMENT[9]) + " [" + receiver_data.raw[9] + "]"},
159 | {data: receiver_data.plot[10], label: "CH-10 - " + RX_channel_name(eepromConfig.CHANNEL_ASSIGNMENT[10]) + " [" + receiver_data.raw[10] + "]"},
160 | {data: receiver_data.plot[11], label: "CH-11 - " + RX_channel_name(eepromConfig.CHANNEL_ASSIGNMENT[11]) + " [" + receiver_data.raw[11] + "]"},
161 | {data: receiver_data.plot[12], label: "CH-12 - " + RX_channel_name(eepromConfig.CHANNEL_ASSIGNMENT[12]) + " [" + receiver_data.raw[12] + "]"},
162 | {data: receiver_data.plot[13], label: "CH-13 - " + RX_channel_name(eepromConfig.CHANNEL_ASSIGNMENT[13]) + " [" + receiver_data.raw[13] + "]"},
163 | {data: receiver_data.plot[14], label: "CH-14 - " + RX_channel_name(eepromConfig.CHANNEL_ASSIGNMENT[14]) + " [" + receiver_data.raw[14] + "]"},
164 | {data: receiver_data.plot[15], label: "CH-15 - " + RX_channel_name(eepromConfig.CHANNEL_ASSIGNMENT[15]) + " [" + receiver_data.raw[15] + "]"} ], receiver_options);
165 |
166 | samples_i++;
167 |
168 | // request new data
169 | send_message(PSP.PSP_REQ_RC, 1);
170 | }
--------------------------------------------------------------------------------
/extras/Configurator/tabs/sensor_data.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/extras/Configurator/tabs/sensor_data.js:
--------------------------------------------------------------------------------
1 | function tab_initialize_sensor_data() {
2 | // Setup variables
3 | samples_i = 300;
4 |
5 | for (var i = 0; i < sensor_data.gyro_plot.length; i++) {
6 | sensor_data.gyro_plot[i] = [];
7 | sensor_data.accel_plot[i] = [];
8 | sensor_data.mag_plot[i] = [];
9 | }
10 | sensor_data.baro_plot = [];
11 |
12 | for (var i = 0; i <= 300; i++) {
13 | sensor_data.gyro_plot[0].push([i, 0]);
14 | sensor_data.gyro_plot[1].push([i, 0]);
15 | sensor_data.gyro_plot[2].push([i, 0]);
16 |
17 | sensor_data.accel_plot[0].push([i, 0]);
18 | sensor_data.accel_plot[1].push([i, 0]);
19 | sensor_data.accel_plot[2].push([i, 0]);
20 |
21 | sensor_data.mag_plot[0].push([i, 0]);
22 | sensor_data.mag_plot[1].push([i, 0]);
23 | sensor_data.mag_plot[2].push([i, 0]);
24 |
25 | sensor_data.baro_plot.push([i, 0]);
26 | }
27 |
28 | // Graph definitions
29 | e_graph_gyro = document.getElementById("graph_gyro");
30 | e_graph_accel = document.getElementById("graph_accel");
31 | e_graph_mag = document.getElementById("graph_mag");
32 | e_graph_baro = document.getElementById("graph_baro");
33 |
34 | gyro_options = {
35 | title: "Gyroscope (deg/s)",
36 | shadowSize: 0,
37 | yaxis: {
38 | max: 10,
39 | min: -10
40 | },
41 | xaxis: {
42 | //noTicks = 0
43 | },
44 | grid: {
45 | backgroundColor: "#FFFFFF"
46 | },
47 | legend: {
48 | position: "wn",
49 | backgroundOpacity: 0
50 | }
51 | }
52 |
53 | accel_options = {
54 | title: "Accelerometer (g)",
55 | shadowSize: 0,
56 | yaxis: {
57 | max : 1.5,
58 | min : -1.5
59 | },
60 | xaxis: {
61 | },
62 | grid: {
63 | backgroundColor : "#FFFFFF"
64 | },
65 | legend: {
66 | position: "wn",
67 | backgroundOpacity: 0
68 | }
69 | }
70 |
71 | mag_options = {
72 | title: "Magnetometer (Ga)",
73 | shadowSize: 0,
74 | yaxis: {
75 | },
76 | xaxis: {
77 | },
78 | grid: {
79 | backgroundColor : "#FFFFFF"
80 | },
81 | legend: {
82 | position: "wn",
83 | backgroundOpacity: 0
84 | }
85 | }
86 |
87 | baro_options = {
88 | title: "Barometer (meters above sea level)",
89 | shadowSize: 0,
90 | yaxis: {
91 | tickDecimals: 0,
92 | },
93 | xaxis: {
94 | },
95 | grid: {
96 | backgroundColor : "#FFFFFF"
97 | },
98 | legend: {
99 | position: "wn",
100 | backgroundOpacity: 0
101 | }
102 | }
103 |
104 | // disable plots for sensors that wasn't detected
105 | if (sensors_alive.gyro == 0) {
106 | $(e_graph_gyro).hide();
107 | }
108 |
109 | if (sensors_alive.accel == 0) {
110 | $(e_graph_accel).hide();
111 | }
112 |
113 | if (sensors_alive.mag == 0) {
114 | $(e_graph_mag).hide();
115 | }
116 |
117 | if (sensors_alive.baro == 0) {
118 | $(e_graph_baro).hide();
119 | }
120 |
121 | // request sensor data from flight controller
122 | timers.push(setInterval(sensor_pull, 50));
123 | }
124 |
125 | function sensor_pull() {
126 | if (sensors_alive.gyro == 1 || sensors_alive.accel == 1) {
127 | // push latest data to the main array
128 | sensor_data.gyro_plot[0].push([samples_i, sensor_data.gyro[0]]);
129 | sensor_data.gyro_plot[1].push([samples_i, sensor_data.gyro[1]]);
130 | sensor_data.gyro_plot[2].push([samples_i, sensor_data.gyro[2]]);
131 |
132 | sensor_data.accel_plot[0].push([samples_i, sensor_data.accel[0]]);
133 | sensor_data.accel_plot[1].push([samples_i, sensor_data.accel[1]]);
134 | sensor_data.accel_plot[2].push([samples_i, sensor_data.accel[2]]);
135 |
136 | // Remove old data from array
137 | while (sensor_data.gyro_plot[0].length > 300) {
138 | sensor_data.gyro_plot[0].shift();
139 | sensor_data.gyro_plot[1].shift();
140 | sensor_data.gyro_plot[2].shift();
141 |
142 | sensor_data.accel_plot[0].shift();
143 | sensor_data.accel_plot[1].shift();
144 | sensor_data.accel_plot[2].shift();
145 | };
146 |
147 | // Update graphs
148 | Flotr.draw(e_graph_gyro, [
149 | {data: sensor_data.gyro_plot[0], label: "X - rate [" + sensor_data.gyro[0].toFixed(2) + "]"},
150 | {data: sensor_data.gyro_plot[1], label: "Y - rate [" + sensor_data.gyro[1].toFixed(2) + "]"},
151 | {data: sensor_data.gyro_plot[2], label: "Z - rate [" + sensor_data.gyro[2].toFixed(2) + "]"} ], gyro_options);
152 |
153 | Flotr.draw(e_graph_accel, [
154 | {data: sensor_data.accel_plot[0], label: "X - acceleration [" + sensor_data.accel[0].toFixed(2) + "]"},
155 | {data: sensor_data.accel_plot[1], label: "Y - acceleration [" + sensor_data.accel[1].toFixed(2) + "]"},
156 | {data: sensor_data.accel_plot[2], label: "Z - acceleration [" + sensor_data.accel[2].toFixed(2) + "]"} ], accel_options);
157 |
158 | // request new data
159 | send_message(PSP.PSP_REQ_GYRO_ACC, 1);
160 | }
161 |
162 | if (sensors_alive.mag == 1) {
163 | // push latest data to the main array
164 | sensor_data.mag_plot[0].push([samples_i, sensor_data.mag[0]]);
165 | sensor_data.mag_plot[1].push([samples_i, sensor_data.mag[1]]);
166 | sensor_data.mag_plot[2].push([samples_i, sensor_data.mag[2]]);
167 |
168 | // Remove old data from array
169 | while (sensor_data.mag_plot[0].length > 300) {
170 | sensor_data.mag_plot[0].shift();
171 | sensor_data.mag_plot[1].shift();
172 | sensor_data.mag_plot[2].shift();
173 | }
174 |
175 | // Update graph
176 | Flotr.draw(e_graph_mag, [
177 | {data: sensor_data.mag_plot[0], label: "X - Ga [" + sensor_data.mag[0].toFixed(2) + "]"},
178 | {data: sensor_data.mag_plot[1], label: "Y - Ga [" + sensor_data.mag[1].toFixed(2) + "]"},
179 | {data: sensor_data.mag_plot[2], label: "Z - Ga [" + sensor_data.mag[2].toFixed(2) + "]"} ], mag_options);
180 |
181 | // request new data
182 | send_message(PSP.PSP_REQ_MAG, 1);
183 | }
184 |
185 | if (sensors_alive.baro == 1) {
186 | // push latest data to the main array
187 | sensor_data.baro_plot.push([samples_i, sensor_data.baro[1]]);
188 |
189 | // Remove old data from array
190 | while (sensor_data.baro_plot.length > 300) {
191 | sensor_data.baro_plot.shift();
192 | }
193 |
194 | // Update graph
195 | Flotr.draw(e_graph_baro, [
196 | {data: sensor_data.baro_plot, label: "X - Meters [" + sensor_data.baro[1].toFixed(2) + "]"} ], baro_options);
197 |
198 | // request new data
199 | send_message(PSP.PSP_REQ_BARO, 1);
200 | }
201 |
202 | samples_i++;
203 | }
--------------------------------------------------------------------------------
/extras/Configurator/tabs/sensor_mapping.html:
--------------------------------------------------------------------------------
1 |
2 |
35 |
36 |
Accelerometer
37 |
67 |
68 |
69 |
Magnetometer
70 |
100 |
101 |
Restore Default
102 |
Update
103 |
104 |
105 | Note : You can view the changes in real time inside the Sensor Data and Vehicle View tabs.
106 |
107 |
108 | Note : To complete the "restore to default" process,
109 | you need to restart / powercycle your flight controller.
110 |
111 |
--------------------------------------------------------------------------------
/extras/Configurator/tabs/sensor_mapping.js:
--------------------------------------------------------------------------------
1 | var TEMP_SENSOR_AXIS_MAP;
2 |
3 | function tab_initialize_sensor_mapping() {
4 | // populate the select boxes
5 | // gyroscope
6 | gyro_axis1 = check_axis_position(eepromConfig.SENSOR_AXIS_MAP[0], 0);
7 | gyro_axis2 = check_axis_position(eepromConfig.SENSOR_AXIS_MAP[0], 3);
8 | gyro_axis3 = check_axis_position(eepromConfig.SENSOR_AXIS_MAP[0], 6);
9 |
10 | $('table.gyroscope tr:eq(1) input').eq(gyro_axis1.axis).prop('checked', true);
11 | $('table.gyroscope tr:eq(1) input').eq(3).prop('checked', gyro_axis1.sign);
12 | $('table.gyroscope tr:eq(2) input').eq(gyro_axis2.axis).prop('checked', true);
13 | $('table.gyroscope tr:eq(2) input').eq(3).prop('checked', gyro_axis2.sign);
14 | $('table.gyroscope tr:eq(3) input').eq(gyro_axis3.axis).prop('checked', true);
15 | $('table.gyroscope tr:eq(3) input').eq(3).prop('checked', gyro_axis3.sign);
16 |
17 | // accelerometer
18 | accel_axis1 = check_axis_position(eepromConfig.SENSOR_AXIS_MAP[1], 0);
19 | accel_axis2 = check_axis_position(eepromConfig.SENSOR_AXIS_MAP[1], 3);
20 | accel_axis3 = check_axis_position(eepromConfig.SENSOR_AXIS_MAP[1], 6);
21 |
22 | $('table.accelerometer tr:eq(1) input').eq(accel_axis1.axis).prop('checked', true);
23 | $('table.accelerometer tr:eq(1) input').eq(3).prop('checked', accel_axis1.sign);
24 | $('table.accelerometer tr:eq(2) input').eq(accel_axis2.axis).prop('checked', true);
25 | $('table.accelerometer tr:eq(2) input').eq(3).prop('checked', accel_axis2.sign);
26 | $('table.accelerometer tr:eq(3) input').eq(accel_axis3.axis).prop('checked', true);
27 | $('table.accelerometer tr:eq(3) input').eq(3).prop('checked', accel_axis3.sign);
28 |
29 | // magnetometer
30 | mag_axis1 = check_axis_position(eepromConfig.SENSOR_AXIS_MAP[2], 0);
31 | mag_axis2 = check_axis_position(eepromConfig.SENSOR_AXIS_MAP[2], 3);
32 | mag_axis3 = check_axis_position(eepromConfig.SENSOR_AXIS_MAP[2], 6);
33 |
34 | $('table.magnetometer tr:eq(1) input').eq(mag_axis1.axis).prop('checked', true);
35 | $('table.magnetometer tr:eq(1) input').eq(3).prop('checked', mag_axis1.sign);
36 | $('table.magnetometer tr:eq(2) input').eq(mag_axis2.axis).prop('checked', true);
37 | $('table.magnetometer tr:eq(2) input').eq(3).prop('checked', mag_axis2.sign);
38 | $('table.magnetometer tr:eq(3) input').eq(mag_axis3.axis).prop('checked', true);
39 | $('table.magnetometer tr:eq(3) input').eq(3).prop('checked', mag_axis3.sign);
40 |
41 |
42 | // UI hooks
43 | $('table input').click(function() {
44 | parent = $(this).parent().parent();
45 |
46 | if ($(this).attr('name') == 'sign') {
47 | // Special handling for sign
48 | } else {
49 | // Standard handling for axis
50 | $('input', parent).not('[name="sign"]').removeAttr('checked');
51 | $(this).prop('checked', true);
52 | }
53 | });
54 |
55 | $('div.tab-sensor_mapping a.restore').click(function() {
56 | eepromConfig.SENSOR_AXIS_MAP = [0, 0, 0];
57 |
58 | // Send updated UNION to the flight controller
59 | sendUNION();
60 |
61 | // refresh the UI
62 | $('#content').load("./tabs/sensor_mapping.html", tab_initialize_sensor_mapping);
63 | });
64 |
65 | $('div.tab-sensor_mapping a.update').click(function() {
66 | // re-calculate temporary maps
67 | TEMP_SENSOR_AXIS_MAP = [0, 0, 0];
68 | $('table.gyroscope tr:eq(1) input:checked').each(function(index, element) {
69 | set_axis_position(element, 0, 0);
70 | });
71 | $('table.gyroscope tr:eq(2) input:checked').each(function(index, element) {
72 | set_axis_position(element, 0, 3);
73 | });
74 | $('table.gyroscope tr:eq(3) input:checked').each(function(index, element) {
75 | set_axis_position(element, 0, 6);
76 | });
77 |
78 | $('table.accelerometer tr:eq(1) input:checked').each(function(index, element) {
79 | set_axis_position(element, 1, 0);
80 | });
81 | $('table.accelerometer tr:eq(2) input:checked').each(function(index, element) {
82 | set_axis_position(element, 1, 3);
83 | });
84 | $('table.accelerometer tr:eq(3) input:checked').each(function(index, element) {
85 | set_axis_position(element, 1, 6);
86 | });
87 |
88 | $('table.magnetometer tr:eq(1) input:checked').each(function(index, element) {
89 | set_axis_position(element, 2, 0);
90 | });
91 | $('table.magnetometer tr:eq(2) input:checked').each(function(index, element) {
92 | set_axis_position(element, 2, 3);
93 | });
94 | $('table.magnetometer tr:eq(3) input:checked').each(function(index, element) {
95 | set_axis_position(element, 2, 6);
96 | });
97 |
98 | // we cant forget the initialization bit
99 | if (bit_check(eepromConfig.SENSOR_AXIS_MAP[0], 9)) {
100 | TEMP_SENSOR_AXIS_MAP[0] = bit_set(TEMP_SENSOR_AXIS_MAP[0], 9);
101 | }
102 |
103 | if (bit_check(eepromConfig.SENSOR_AXIS_MAP[1], 9)) {
104 | TEMP_SENSOR_AXIS_MAP[1] = bit_set(TEMP_SENSOR_AXIS_MAP[1], 9);
105 | }
106 |
107 | if (bit_check(eepromConfig.SENSOR_AXIS_MAP[2], 9)) {
108 | TEMP_SENSOR_AXIS_MAP[2] = bit_set(TEMP_SENSOR_AXIS_MAP[2], 9);
109 | }
110 |
111 | // push temp map values to the primary map
112 | eepromConfig.SENSOR_AXIS_MAP[0] = TEMP_SENSOR_AXIS_MAP[0];
113 | eepromConfig.SENSOR_AXIS_MAP[1] = TEMP_SENSOR_AXIS_MAP[1];
114 | eepromConfig.SENSOR_AXIS_MAP[2] = TEMP_SENSOR_AXIS_MAP[2];
115 |
116 |
117 | // Send updated UNION to the flight controller
118 | sendUNION();
119 | });
120 | }
121 |
122 | function check_axis_position(input, num) {
123 | var axis = 0;
124 | var sign = 0;
125 |
126 | if (bit_check(input, num)) { // offset start
127 | axis += 1;
128 | }
129 |
130 | if (bit_check(input, num + 1)) { // offset + 1
131 | axis += 2;
132 | }
133 |
134 | if (bit_check(input, num + 2)) { // offset + 2
135 | sign += 1;
136 | }
137 |
138 | return {axis: axis, sign: sign};
139 | }
140 |
141 | function set_axis_position(element, sensor, offset) {
142 | var name = $(element).attr('name');
143 |
144 | switch(name) {
145 | case 'x':
146 | TEMP_SENSOR_AXIS_MAP[sensor] = bit_clear(TEMP_SENSOR_AXIS_MAP[sensor], offset);
147 | TEMP_SENSOR_AXIS_MAP[sensor] = bit_clear(TEMP_SENSOR_AXIS_MAP[sensor], offset + 1);
148 | break;
149 | case 'y':
150 | TEMP_SENSOR_AXIS_MAP[sensor] = bit_set(TEMP_SENSOR_AXIS_MAP[sensor], offset);
151 | TEMP_SENSOR_AXIS_MAP[sensor] = bit_clear(TEMP_SENSOR_AXIS_MAP[sensor], offset + 1);
152 | break;
153 | case 'z':
154 | TEMP_SENSOR_AXIS_MAP[sensor] = bit_clear(TEMP_SENSOR_AXIS_MAP[sensor], offset);
155 | TEMP_SENSOR_AXIS_MAP[sensor] = bit_set(TEMP_SENSOR_AXIS_MAP[sensor], offset + 1);
156 | break;
157 | case 'sign':
158 | TEMP_SENSOR_AXIS_MAP[sensor] = bit_set(TEMP_SENSOR_AXIS_MAP[sensor], offset + 2);
159 | break;
160 | }
161 | }
--------------------------------------------------------------------------------
/extras/Configurator/tabs/vehicle_view.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Reset Z axis
4 |
5 |
6 |
7 |
8 |
BOTTOM
9 |
BACK
10 |
RIGHT
11 |
FRONT
12 |
LEFT
13 |
TOP
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/extras/Configurator/tabs/vehicle_view.js:
--------------------------------------------------------------------------------
1 | var yaw_fix = 0.0;
2 |
3 | function tab_initialize_vehicle_view() {
4 | // reset yaw button hook
5 | $('div#interactive_block > a.reset').click(function() {
6 | yaw_fix = sensor_data.kinematics[2];
7 | console.log("YAW reset to 0");
8 | });
9 |
10 | // request kinematics data from flight controller
11 | timers.push(setInterval(kinematics_pull, 50));
12 | }
13 |
14 | function kinematics_pull() {
15 | // update cube with latest data
16 | var cube = $('div#cube');
17 |
18 | cube.css('-webkit-transform', 'rotateY(' + (sensor_data.kinematics[2] - yaw_fix) + 'deg)');
19 | $('#cubePITCH', cube).css('-webkit-transform', 'rotateX(' + sensor_data.kinematics[1] + 'deg)');
20 | $('#cubeROLL', cube).css('-webkit-transform', 'rotateZ(' + sensor_data.kinematics[0] + 'deg)');
21 |
22 | // request new data
23 | send_message(PSP.PSP_REQ_KINEMATICS, 1);
24 | }
--------------------------------------------------------------------------------
/extras/Filters/MAF.h:
--------------------------------------------------------------------------------
1 | /* Moving Average Filter implementation
2 | Using ring buffer to store/swap between the received data.
3 | SmoothFactor (parsed as int to the constructor) can never be higher then
4 | MAF_ARRAYSIZE constant.
5 | */
6 |
7 | #define MAF_ARRAYSIZE 20
8 |
9 | class MAF {
10 | public:
11 | // Constructor
12 | MAF(uint8_t Size) {
13 | // Save array size
14 | smoothFactor = Size;
15 |
16 | // Bear in mind that data[N] array is defined in private
17 | // but is not initialized.
18 | // For some reason the implementation works, but in case you encounter
19 | // "Weird behaviour", this is the place to look.
20 |
21 | // Initialize head
22 | head = 0;
23 | };
24 |
25 | double update(double value) {
26 | // Store new value inside the array
27 | data[head] = value;
28 | head++;
29 |
30 | // If we reached end of the array, return to beginning
31 | if (head == smoothFactor) head = 0;
32 |
33 | double sum;
34 | for (uint8_t i = 0; i < smoothFactor; i++) {
35 | sum += data[i];
36 | }
37 |
38 | sum /= smoothFactor;
39 | return sum;
40 | };
41 |
42 | private:
43 | uint8_t smoothFactor;
44 | double data[MAF_ARRAYSIZE];
45 | uint8_t head;
46 | };
--------------------------------------------------------------------------------
/extras/I2Cscanner/I2Cscanner.ino:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | uint8_t address;
4 | int8_t status;
5 |
6 | void setup() {
7 | Serial.begin(115200);
8 | Wire.begin();
9 | }
10 |
11 | void loop() {
12 | Serial.println("Scanning...");
13 |
14 | for (address = 1; address <= 127; address++) {
15 | Wire.beginTransmission(address);
16 | status = Wire.endTransmission();
17 |
18 | if (status == 0) {
19 | Serial.print("I2C device found at 0x");
20 | Serial.println(address, HEX);
21 | }
22 | delay(5);
23 | }
24 |
25 | Serial.println("done");
26 |
27 | delay(5000);
28 | }
--------------------------------------------------------------------------------
/extras/PCB/Phoenix_shield_v0.1.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cTn-dev/Phoenix-FlightController/a7dbea2961efc2a3a4fe270170acadd7e617034b/extras/PCB/Phoenix_shield_v0.1.zip
--------------------------------------------------------------------------------
/libraries/P_Accelerometer/ADXL345.h:
--------------------------------------------------------------------------------
1 | /* ADXL345 - (ADXL345 is on separate breakout)
2 |
3 | Beware ! ADXL345 sends the data in reverse (LOW, HIGH)
4 |
5 | This library requires some more documentation
6 | */
7 |
8 | #define ADXL345_ADDRESS 0x53
9 | #define ADXL345_DEVID 0xE5
10 |
11 | float accel[3];
12 |
13 | class ADXL345 {
14 | public:
15 | // Constructor
16 | ADXL345() {
17 | accelScaleFactor = 9.81 / 256.0;
18 | accelSamples = 0;
19 | };
20 |
21 | void initialize() {
22 | // Check if sensor is alive
23 | Wire.beginTransmission(ADXL345_ADDRESS);
24 | Wire.write(0x00);
25 | Wire.endTransmission();
26 |
27 | Wire.requestFrom(ADXL345_ADDRESS, 1);
28 |
29 | uint8_t register_value = Wire.read();
30 |
31 | if (register_value == ADXL345_DEVID) {
32 | sensors.sensors_detected |= ACCELEROMETER_DETECTED;
33 | } else {
34 | return;
35 | }
36 |
37 | sensors.i2c_write8(ADXL345_ADDRESS, 0x2D, 0x08); // set device to *measure*
38 | sensors.i2c_write8(ADXL345_ADDRESS, 0x31, 0x09); // set full range and +/- 4G
39 | sensors.i2c_write8(ADXL345_ADDRESS, 0x2C, 0x0B); // 200hz sampling
40 |
41 | // setup axis mapping
42 | if (CONFIG.data.ACCEL_AXIS_MAP.initialized == 0) { // check if map was defined before, if not "save default" order set
43 | CONFIG.data.ACCEL_AXIS_MAP.axis1 = 0; // x
44 | CONFIG.data.ACCEL_AXIS_MAP.axis2 = 1; // y
45 | CONFIG.data.ACCEL_AXIS_MAP.axis3 = 2; // z
46 |
47 | CONFIG.data.ACCEL_AXIS_MAP.axis1_sign = 0;
48 | CONFIG.data.ACCEL_AXIS_MAP.axis2_sign = 0;
49 | CONFIG.data.ACCEL_AXIS_MAP.axis3_sign = 0;
50 |
51 | CONFIG.data.ACCEL_AXIS_MAP.initialized = 1;
52 |
53 | // save default in eeprom
54 | writeEEPROM();
55 | }
56 | };
57 |
58 | // ~1280ms (only runs when requested)
59 | void calibrate_accel() {
60 | uint8_t i, count = 128;
61 | int32_t xSum = 0, ySum = 0, zSum = 0;
62 |
63 | for(i = 0; i < count; i++) {
64 | readAccelRaw();
65 | xSum += accelRaw[XAXIS];
66 | ySum += accelRaw[YAXIS];
67 | zSum += accelRaw[ZAXIS];
68 | delay(10);
69 | }
70 |
71 | CONFIG.data.ACCEL_BIAS[XAXIS] = xSum / count;
72 | CONFIG.data.ACCEL_BIAS[YAXIS] = ySum / count;
73 | CONFIG.data.ACCEL_BIAS[ZAXIS] = (zSum / count) - 256; // - 1G
74 |
75 | // Reverse calibration forces
76 | CONFIG.data.ACCEL_BIAS[XAXIS] *= -1;
77 | CONFIG.data.ACCEL_BIAS[YAXIS] *= -1;
78 | CONFIG.data.ACCEL_BIAS[ZAXIS] *= -1;
79 | };
80 |
81 | void readAccelRaw() {
82 | Wire.beginTransmission(ADXL345_ADDRESS);
83 | Wire.write(0x32);
84 | Wire.endTransmission();
85 |
86 | Wire.requestFrom(ADXL345_ADDRESS, 6);
87 |
88 | accelRaw[CONFIG.data.ACCEL_AXIS_MAP.axis1] = (Wire.read() | (Wire.read() << 8)) * (CONFIG.data.ACCEL_AXIS_MAP.axis1_sign?-1:1);
89 | accelRaw[CONFIG.data.ACCEL_AXIS_MAP.axis2] = (Wire.read() | (Wire.read() << 8)) * (CONFIG.data.ACCEL_AXIS_MAP.axis2_sign?-1:1);
90 | accelRaw[CONFIG.data.ACCEL_AXIS_MAP.axis3] = (Wire.read() | (Wire.read() << 8)) * (CONFIG.data.ACCEL_AXIS_MAP.axis3_sign?-1:1);
91 | };
92 |
93 | void readAccelSum() {
94 | readAccelRaw();
95 |
96 | accelSum[XAXIS] += accelRaw[XAXIS];
97 | accelSum[YAXIS] += accelRaw[YAXIS];
98 | accelSum[ZAXIS] += accelRaw[ZAXIS];
99 |
100 | accelSamples++;
101 | };
102 |
103 | void evaluateAccel() {
104 | // Calculate average
105 | accel[XAXIS] = accelSum[XAXIS] / accelSamples;
106 | accel[YAXIS] = accelSum[YAXIS] / accelSamples;
107 | accel[ZAXIS] = accelSum[ZAXIS] / accelSamples;
108 |
109 | // Apply offsets
110 | accel[XAXIS] += CONFIG.data.ACCEL_BIAS[XAXIS];
111 | accel[YAXIS] += CONFIG.data.ACCEL_BIAS[YAXIS];
112 | accel[ZAXIS] += CONFIG.data.ACCEL_BIAS[ZAXIS];
113 |
114 | // Apply correct scaling (at this point accel reprensents +- 1g = 9.81 m/s^2)
115 | accel[XAXIS] *= accelScaleFactor;
116 | accel[YAXIS] *= accelScaleFactor;
117 | accel[ZAXIS] *= accelScaleFactor;
118 |
119 | // Reset SUM variables
120 | accelSum[XAXIS] = 0;
121 | accelSum[YAXIS] = 0;
122 | accelSum[ZAXIS] = 0;
123 | accelSamples = 0;
124 | };
125 |
126 | private:
127 | float accelScaleFactor;
128 |
129 | int16_t accelRaw[3];
130 | float accelSum[3];
131 |
132 | uint8_t accelSamples;
133 | };
134 |
135 | ADXL345 adxl;
136 |
137 | void SensorArray::initializeAccel() {
138 | adxl.initialize();
139 | }
140 |
141 | void SensorArray::calibrateAccel() {
142 | adxl.calibrate_accel();
143 | }
144 |
145 | void SensorArray::readAccelSum() {
146 | adxl.readAccelSum();
147 | }
148 |
149 | void SensorArray::evaluateAccel() {
150 | adxl.evaluateAccel();
151 | }
152 |
--------------------------------------------------------------------------------
/libraries/P_Accelerometer/ADXL345_AQ_v21.h:
--------------------------------------------------------------------------------
1 | /* ADXL345, 9 DOF stick library
2 |
3 | Beware ! ADXL345 sends the data in reverse (LOW, HIGH)
4 |
5 | This library requires some more documentation
6 | (code was tested and works perfectly fine)
7 | */
8 |
9 | #define ADXL345_ADDRESS 0x53
10 | #define ADXL345_DEVID 0xE5
11 |
12 | float accel[3];
13 |
14 | class ADXL345 {
15 | public:
16 | // Constructor
17 | ADXL345() {
18 | accelScaleFactor = 9.81 / 256.0;
19 | accelSamples = 0;
20 | };
21 |
22 | void initialize() {
23 | // Check if sensor is alive
24 | Wire.beginTransmission(ADXL345_ADDRESS);
25 | Wire.write(0x00);
26 | Wire.endTransmission();
27 |
28 | Wire.requestFrom(ADXL345_ADDRESS, 1);
29 |
30 | uint8_t register_value = Wire.read();
31 |
32 | if (register_value == ADXL345_DEVID) {
33 | sensors.sensors_detected |= ACCELEROMETER_DETECTED;
34 | } else {
35 | return;
36 | }
37 |
38 | sensors.i2c_write8(ADXL345_ADDRESS, 0x2D, 0x08); // set device to *measure*
39 | sensors.i2c_write8(ADXL345_ADDRESS, 0x31, 0x09); // set full range and +/- 4G
40 | sensors.i2c_write8(ADXL345_ADDRESS, 0x2C, 0x0B); // 200hz sampling
41 |
42 | // setup axis mapping
43 | if (CONFIG.data.ACCEL_AXIS_MAP.initialized == 0) { // check if map was defined before, if not "save default" order set
44 | CONFIG.data.ACCEL_AXIS_MAP.axis1 = 1; // y
45 | CONFIG.data.ACCEL_AXIS_MAP.axis2 = 0; // x
46 | CONFIG.data.ACCEL_AXIS_MAP.axis3 = 2; // z
47 |
48 | CONFIG.data.ACCEL_AXIS_MAP.axis1_sign = 0;
49 | CONFIG.data.ACCEL_AXIS_MAP.axis2_sign = 0;
50 | CONFIG.data.ACCEL_AXIS_MAP.axis3_sign = 0;
51 |
52 | CONFIG.data.ACCEL_AXIS_MAP.initialized = 1;
53 |
54 | // save default in eeprom
55 | writeEEPROM();
56 | }
57 | };
58 |
59 | // ~1280ms (only runs when requested)
60 | void calibrate_accel() {
61 | uint8_t i, count = 128;
62 | int32_t xSum = 0, ySum = 0, zSum = 0;
63 |
64 | for(i = 0; i < count; i++) {
65 | readAccelRaw();
66 | xSum += accelRaw[XAXIS];
67 | ySum += accelRaw[YAXIS];
68 | zSum += accelRaw[ZAXIS];
69 | delay(10);
70 | }
71 |
72 | CONFIG.data.ACCEL_BIAS[XAXIS] = xSum / count;
73 | CONFIG.data.ACCEL_BIAS[YAXIS] = ySum / count;
74 | CONFIG.data.ACCEL_BIAS[ZAXIS] = (zSum / count) - 256; // - 1G
75 |
76 | // Reverse calibration forces
77 | CONFIG.data.ACCEL_BIAS[XAXIS] *= -1;
78 | CONFIG.data.ACCEL_BIAS[YAXIS] *= -1;
79 | CONFIG.data.ACCEL_BIAS[ZAXIS] *= -1;
80 | };
81 |
82 | void readAccelRaw() {
83 | Wire.beginTransmission(ADXL345_ADDRESS);
84 | Wire.write(0x32);
85 | Wire.endTransmission();
86 |
87 | Wire.requestFrom(ADXL345_ADDRESS, 6);
88 |
89 | accelRaw[CONFIG.data.ACCEL_AXIS_MAP.axis1] = (Wire.read() | (Wire.read() << 8)) * (CONFIG.data.ACCEL_AXIS_MAP.axis1_sign?-1:1);
90 | accelRaw[CONFIG.data.ACCEL_AXIS_MAP.axis2] = (Wire.read() | (Wire.read() << 8)) * (CONFIG.data.ACCEL_AXIS_MAP.axis2_sign?-1:1);
91 | accelRaw[CONFIG.data.ACCEL_AXIS_MAP.axis3] = (Wire.read() | (Wire.read() << 8)) * (CONFIG.data.ACCEL_AXIS_MAP.axis3_sign?-1:1);
92 | };
93 |
94 | void readAccelSum() {
95 | readAccelRaw();
96 |
97 | accelSum[XAXIS] += accelRaw[XAXIS];
98 | accelSum[YAXIS] += accelRaw[YAXIS];
99 | accelSum[ZAXIS] += accelRaw[ZAXIS];
100 |
101 | accelSamples++;
102 | };
103 |
104 | void evaluateAccel() {
105 | // Calculate average
106 | accel[XAXIS] = accelSum[XAXIS] / accelSamples;
107 | accel[YAXIS] = -(accelSum[YAXIS] / accelSamples);
108 | accel[ZAXIS] = accelSum[ZAXIS] / accelSamples;
109 |
110 | // Apply offsets
111 | accel[XAXIS] += CONFIG.data.ACCEL_BIAS[XAXIS];
112 | accel[YAXIS] += CONFIG.data.ACCEL_BIAS[YAXIS];
113 | accel[ZAXIS] += CONFIG.data.ACCEL_BIAS[ZAXIS];
114 |
115 | // Apply correct scaling (at this point accel reprensents +- 1g = 9.81 m/s^2)
116 | accel[XAXIS] *= accelScaleFactor;
117 | accel[YAXIS] *= accelScaleFactor;
118 | accel[ZAXIS] *= accelScaleFactor;
119 |
120 | // Reset SUM variables
121 | accelSum[XAXIS] = 0;
122 | accelSum[YAXIS] = 0;
123 | accelSum[ZAXIS] = 0;
124 | accelSamples = 0;
125 | };
126 |
127 | private:
128 | float accelScaleFactor;
129 |
130 | int16_t accelRaw[3];
131 | float accelSum[3];
132 |
133 | uint8_t accelSamples;
134 | };
135 |
136 | ADXL345 adxl;
137 |
138 | void SensorArray::initializeAccel() {
139 | adxl.initialize();
140 | }
141 |
142 | void SensorArray::calibrateAccel() {
143 | adxl.calibrate_accel();
144 | }
145 |
146 | void SensorArray::readAccelSum() {
147 | adxl.readAccelSum();
148 | }
149 |
150 | void SensorArray::evaluateAccel() {
151 | adxl.evaluateAccel();
152 | }
153 |
--------------------------------------------------------------------------------
/libraries/P_Accelerometer/BMA180_AQ_v20.h:
--------------------------------------------------------------------------------
1 | /* BMA180 library
2 |
3 | Beware ! BMA180 sends the data in reverse (LOW, HIGH)
4 |
5 | Big thanks to remspoor on #aeroquad IRC for testing this integration
6 |
7 | This library requires some more documentation
8 | */
9 |
10 | #define BMA180_ADDRESS 0x40
11 |
12 | #define BMA180_IDENTITY 0x03
13 | #define BMA180_RESET_REGISTER 0x10
14 | #define BMA180_TRIGER_RESET_VALUE 0xB6
15 | #define BMA180_ENABLE_WRITE_CONTROL_REGISTER 0x0D
16 | #define BMA180_CONTROL_REGISTER 0x10
17 | #define BMA180_BW_TCS 0x20
18 | #define BMA180_LOW_PASS_FILTER_REGISTER 0x20
19 | #define BMA180_10HZ_LOW_PASS_FILTER_VALUE 0x0F
20 | #define BMA180_1200HZ_LOW_PASS_FILTER_VALUE 0x7F
21 | #define BMA180_OFFSET_REGISTER 0x35
22 | #define BMA180_READ_ROLL_ADDRESS 0x02
23 | #define BMA180_READ_PITCH_ADDRESS 0x04
24 | #define BMA180_READ_YAW_ADDRESS 0x06
25 | #define BMA180_BUFFER_SIZE 6
26 |
27 | float accel[3];
28 |
29 | class BMA180 {
30 | public:
31 | // Constructor
32 | BMA180() {
33 | accelScaleFactor = 9.81 / 2048.0;
34 |
35 | accel_bias[XAXIS] = 0;
36 | accel_bias[YAXIS] = 0;
37 | accel_bias[ZAXIS] = 0;
38 |
39 | accelSamples = 0;
40 | };
41 |
42 | void initialize() {
43 | // Check if sensor is alive
44 | Wire.beginTransmission(BMA180_ADDRESS);
45 | Wire.write(0x00);
46 | Wire.endTransmission();
47 |
48 | Wire.requestFrom(BMA180_ADDRESS, 1);
49 |
50 | uint8_t register_value = Wire.read();
51 |
52 | if (register_value == BMA180_IDENTITY) {
53 | sensors.sensors_detected |= ACCELEROMETER_DETECTED;
54 | } else {
55 | return;
56 | }
57 |
58 | initialize();
59 | };
60 |
61 | void initialize() {
62 | sensors.i2c_write8(BMA180_ADDRESS, BMA180_RESET_REGISTER, BMA180_TRIGER_RESET_VALUE); // reset
63 | delay(10);
64 |
65 | sensors.i2c_write8(BMA180_ADDRESS, BMA180_ENABLE_WRITE_CONTROL_REGISTER, BMA180_CONTROL_REGISTER); // enable writing to control registers
66 |
67 | Wire.beginTransmission(BMA180_ADDRESS);
68 | Wire.write(BMA180_BW_TCS);
69 | Wire.endTransmission();
70 |
71 | Wire.requestFrom(BMA180_ADDRESS, 1);
72 | uint8_t data = Wire.read();
73 |
74 | sensors.i2c_write8(BMA180_ADDRESS, BMA180_LOW_PASS_FILTER_REGISTER, data & BMA180_1200HZ_LOW_PASS_FILTER_VALUE); // set low pass filter to 1.2kHz (value = 0000xxxx)
75 |
76 | Wire.beginTransmission(BMA180_ADDRESS);
77 | Wire.write(BMA180_OFFSET_REGISTER);
78 | Wire.endTransmission();
79 |
80 | Wire.requestFrom(BMA180_ADDRESS, 1);
81 | data = Wire.read();
82 | data &= 0xF1;
83 | data |= 0x08; // set range select bits for +/-4g
84 | sensors.i2c_write8(BMA180_ADDRESS, BMA180_OFFSET_REGISTER, data);
85 |
86 | // setup axis mapping
87 | if (CONFIG.data.ACCEL_AXIS_MAP.initialized == 0) { // check if map was defined before, if not "save default" order set
88 | CONFIG.data.ACCEL_AXIS_MAP.axis1 = 0; // x
89 | CONFIG.data.ACCEL_AXIS_MAP.axis2 = 1; // y
90 | CONFIG.data.ACCEL_AXIS_MAP.axis3 = 2; // z
91 |
92 | CONFIG.data.ACCEL_AXIS_MAP.axis1_sign = 1;
93 | CONFIG.data.ACCEL_AXIS_MAP.axis2_sign = 0;
94 | CONFIG.data.ACCEL_AXIS_MAP.axis3_sign = 0;
95 |
96 | CONFIG.data.ACCEL_AXIS_MAP.initialized = 1;
97 |
98 | // save default in eeprom
99 | writeEEPROM();
100 | }
101 | };
102 |
103 | // ~1280ms (only runs when requested)
104 | void calibrate_accel() {
105 | uint8_t i, count = 128;
106 | int32_t xSum = 0, ySum = 0, zSum = 0;
107 |
108 | for(i = 0; i < count; i++) {
109 | readAccelRaw();
110 | xSum += accelRaw[XAXIS];
111 | ySum += accelRaw[YAXIS];
112 | zSum += accelRaw[ZAXIS];
113 | delay(10);
114 | }
115 |
116 | CONFIG.data.ACCEL_BIAS[XAXIS] = xSum / count;
117 | CONFIG.data.ACCEL_BIAS[YAXIS] = ySum / count;
118 | CONFIG.data.ACCEL_BIAS[ZAXIS] = (zSum / count) - 2048; // - 1G
119 |
120 | // Reverse calibration forces
121 | CONFIG.data.ACCEL_BIAS[XAXIS] *= -1;
122 | CONFIG.data.ACCEL_BIAS[YAXIS] *= -1;
123 | CONFIG.data.ACCEL_BIAS[ZAXIS] *= -1;
124 | };
125 |
126 | void readAccelRaw() {
127 | Wire.beginTransmission(BMA180_ADDRESS);
128 | Wire.write(BMA180_READ_ROLL_ADDRESS);
129 | Wire.endTransmission();
130 |
131 | Wire.requestFrom(BMA180_ADDRESS, BMA180_BUFFER_SIZE);
132 |
133 | accelRaw[CONFIG.data.ACCEL_AXIS_MAP.axis1] = ((Wire.read() | (Wire.read() << 8)) >> 2) * (CONFIG.data.ACCEL_AXIS_MAP.axis1_sign?-1:1);
134 | accelRaw[CONFIG.data.ACCEL_AXIS_MAP.axis2] = ((Wire.read() | (Wire.read() << 8)) >> 2) * (CONFIG.data.ACCEL_AXIS_MAP.axis2_sign?-1:1);
135 | accelRaw[CONFIG.data.ACCEL_AXIS_MAP.axis3] = ((Wire.read() | (Wire.read() << 8)) >> 2) * (CONFIG.data.ACCEL_AXIS_MAP.axis3_sign?-1:1);
136 | };
137 |
138 | void readAccelSum() {
139 | readAccelRaw();
140 |
141 | accelSum[XAXIS] += accelRaw[XAXIS];
142 | accelSum[YAXIS] += accelRaw[YAXIS];
143 | accelSum[ZAXIS] += accelRaw[ZAXIS];
144 |
145 | accelSamples++;
146 | };
147 |
148 | void evaluateAccel() {
149 | // Calculate average
150 | accel[XAXIS] = accelSum[XAXIS] / accelSamples;
151 | accel[YAXIS] = accelSum[YAXIS] / accelSamples;
152 | accel[ZAXIS] = accelSum[ZAXIS] / accelSamples;
153 |
154 | // Apply offsets
155 | accel[XAXIS] += CONFIG.data.ACCEL_BIAS[XAXIS];
156 | accel[YAXIS] += CONFIG.data.ACCEL_BIAS[YAXIS];
157 | accel[ZAXIS] += CONFIG.data.ACCEL_BIAS[ZAXIS];
158 |
159 | // Apply correct scaling (at this point accelNsumAvr reprensents +- 1g = 9.81 m/s^2)
160 | accel[XAXIS] *= accelScaleFactor;
161 | accel[YAXIS] *= accelScaleFactor;
162 | accel[ZAXIS] *= accelScaleFactor;
163 |
164 | // Reset SUM variables
165 | accelSum[XAXIS] = 0;
166 | accelSum[YAXIS] = 0;
167 | accelSum[ZAXIS] = 0;
168 | accelSamples = 0;
169 | };
170 |
171 | private:
172 | float accelScaleFactor;
173 |
174 | int16_t accelRaw[3];
175 | float accelSum[3];
176 |
177 | uint8_t accelSamples;
178 | };
179 |
180 | BMA180 bma;
181 |
182 | void SensorArray::initializeAccel() {
183 | bma.initialize();
184 | }
185 |
186 | void SensorArray::calibrateAccel() {
187 | bma.calibrate_accel();
188 | }
189 |
190 | void SensorArray::readAccelSum() {
191 | bma.readAccelSum();
192 | }
193 |
194 | void SensorArray::evaluateAccel() {
195 | bma.evaluateAccel();
196 | }
197 |
--------------------------------------------------------------------------------
/libraries/P_Barometer/Barometer_bmp085.h:
--------------------------------------------------------------------------------
1 | /* BMP085 barometer implementation in C++
2 |
3 | Big shoutout to whoever wrote the aeroquad BMP085 sensor integration
4 | (because it was used as example/base to create this one)
5 | */
6 |
7 | // BMP085 Registers
8 | #define BMP085_ADDRESS 0x77
9 |
10 | // Operating Modes
11 | #define BMP085_ULTRALOWPOWER 0
12 | #define BMP085_STANDARD 1
13 | #define BMP085_HIGHRES 2
14 | #define BMP085_ULTRAHIGHRES 3
15 |
16 | #define BMP085_CAL_AC1 0xAA // R Calibration data (16 bits)
17 | #define BMP085_CAL_AC2 0xAC // R Calibration data (16 bits)
18 | #define BMP085_CAL_AC3 0xAE // R Calibration data (16 bits)
19 | #define BMP085_CAL_AC4 0xB0 // R Calibration data (16 bits)
20 | #define BMP085_CAL_AC5 0xB2 // R Calibration data (16 bits)
21 | #define BMP085_CAL_AC6 0xB4 // R Calibration data (16 bits)
22 | #define BMP085_CAL_B1 0xB6 // R Calibration data (16 bits)
23 | #define BMP085_CAL_B2 0xB8 // R Calibration data (16 bits)
24 | #define BMP085_CAL_MB 0xBA // R Calibration data (16 bits)
25 | #define BMP085_CAL_MC 0xBC // R Calibration data (16 bits)
26 | #define BMP085_CAL_MD 0xBE // R Calibration data (16 bits)
27 |
28 | #define BMP085_CONTROL 0xF4
29 | #define BMP085_TEMPDATA 0xF6
30 | #define BMP085_PRESSUREDATA 0xF6
31 |
32 | #define BMP085_READTEMPCMD 0x2E
33 | #define BMP085_READPRESSURECMD 0x34
34 |
35 | float baroRawAltitude = 0.0;
36 | float baroGroundAltitude = 0.0;
37 | float baroAltitude = 0.0;
38 | float baroAltitudeRunning = 0.0;
39 |
40 | float baroAltitudeToHoldTarget = 0.0;
41 | uint16_t baroAltitudeHoldThrottle = 1000;
42 |
43 | class BMP085 {
44 | public:
45 | // constructor
46 | BMP085() {
47 | };
48 |
49 | void initialize() {
50 | baroAltitude = 0.0;
51 |
52 | overSamplingSetting = BMP085_STANDARD;
53 | pressureFactor = 1/5.255;
54 | baroSmoothFactor = 0.02;
55 |
56 | // Check if sensor is alive
57 | Wire.beginTransmission(BMP085_ADDRESS);
58 | Wire.write(0xD0); // BMP085_CHIP_ID_REG
59 | Wire.endTransmission();
60 |
61 | Wire.requestFrom(BMP085_ADDRESS, 1);
62 |
63 | uint8_t register_value = Wire.read();
64 |
65 | if (register_value == 0x55) {
66 | sensors.sensors_detected |= BAROMETER_DETECTED;
67 | } else {
68 | return;
69 | }
70 |
71 | // Read Calibration Data
72 | ac1 = sensors.i2c_read16(BMP085_ADDRESS, BMP085_CAL_AC1);
73 | ac2 = sensors.i2c_read16(BMP085_ADDRESS, BMP085_CAL_AC2);
74 | ac3 = sensors.i2c_read16(BMP085_ADDRESS, BMP085_CAL_AC3);
75 | ac4 = sensors.i2c_read16(BMP085_ADDRESS, BMP085_CAL_AC4);
76 | ac5 = sensors.i2c_read16(BMP085_ADDRESS, BMP085_CAL_AC5);
77 | ac6 = sensors.i2c_read16(BMP085_ADDRESS, BMP085_CAL_AC6);
78 |
79 | b1 = sensors.i2c_read16(BMP085_ADDRESS, BMP085_CAL_B1);
80 | b2 = sensors.i2c_read16(BMP085_ADDRESS, BMP085_CAL_B2);
81 |
82 | mb = sensors.i2c_read16(BMP085_ADDRESS, BMP085_CAL_MB);
83 | mc = sensors.i2c_read16(BMP085_ADDRESS, BMP085_CAL_MC);
84 | md = sensors.i2c_read16(BMP085_ADDRESS, BMP085_CAL_MD);
85 |
86 | requestRawTemperature(); // setup up next measure() for temperature
87 | isReadPressure = false;
88 |
89 | pressureCount = 0;
90 | measureBaro();
91 |
92 | delay(5); // delay for temperature
93 | measureBaro();
94 |
95 | delay(10); // delay for pressure
96 | measureGroundBaro();
97 |
98 | // check if measured ground altitude is valid
99 | while (abs(baroRawAltitude - baroGroundAltitude) > 10) {
100 | delay(26);
101 | measureGroundBaro();
102 | }
103 |
104 | baroAltitude = baroGroundAltitude;
105 | };
106 |
107 | void requestRawPressure() {
108 | sensors.i2c_write8(BMP085_ADDRESS, BMP085_CONTROL, BMP085_READPRESSURECMD + (overSamplingSetting << 6));
109 | };
110 |
111 | long readRawPressure() {
112 | Wire.beginTransmission(BMP085_ADDRESS);
113 | Wire.write(BMP085_PRESSUREDATA);
114 | Wire.endTransmission();
115 |
116 | Wire.requestFrom(BMP085_ADDRESS, 3);
117 |
118 | return (((uint32_t)Wire.read() << 16) | ((uint32_t)Wire.read() << 8) | ((uint32_t)Wire.read())) >> (8 - overSamplingSetting);
119 | };
120 |
121 | void requestRawTemperature() {
122 | sensors.i2c_write8(BMP085_ADDRESS, BMP085_CONTROL, BMP085_READTEMPCMD);
123 | };
124 |
125 | uint16_t readRawTemperature() {
126 | uint16_t data;
127 |
128 | Wire.beginTransmission(BMP085_ADDRESS);
129 | Wire.write(BMP085_TEMPDATA);
130 | Wire.endTransmission();
131 |
132 | Wire.requestFrom(BMP085_ADDRESS, 2);
133 |
134 | data = (Wire.read() << 8) | Wire.read();
135 |
136 | return data;
137 | };
138 |
139 | void measureBaro() {
140 | measureBaroSum();
141 | evaluateBaroAltitude();
142 | };
143 |
144 | void measureBaroSum() {
145 | // switch between pressure and temperature measurements
146 | // each loop, since it is slow to measure pressure
147 | if (isReadPressure) {
148 | rawPressureSum += readRawPressure();
149 | rawPressureSumCount++;
150 |
151 | if (pressureCount == 4) {
152 | requestRawTemperature();
153 | pressureCount = 0;
154 | isReadPressure = false;
155 | }
156 | else {
157 | requestRawPressure();
158 | }
159 | pressureCount++;
160 | } else { // select must equal TEMPERATURE
161 | rawTemperature = (long)readRawTemperature();
162 | requestRawPressure();
163 | isReadPressure = true;
164 | }
165 | };
166 |
167 | void measureGroundBaro() {
168 | // measure initial ground pressure (multiple samples)
169 | float altSum = 0.0;
170 | for (uint8_t i = 0; i < 25; i++) {
171 | measureBaro();
172 | altSum += baroRawAltitude;
173 | delay(12);
174 | }
175 |
176 | baroGroundAltitude = altSum / 25;
177 | };
178 |
179 | void evaluateBaroAltitude() {
180 | int32_t x1, x2, x3, b3, b5, b6, p;
181 | uint32_t b4, b7;
182 | int32_t tmp;
183 |
184 | //calculate true temperature
185 | x1 = ((int32_t)rawTemperature - ac6) * ac5 >> 15;
186 | x2 = ((int32_t) mc << 11) / (x1 + md);
187 | b5 = x1 + x2;
188 |
189 | if (rawPressureSumCount == 0) { // it may occur at init time that no pressure has been read yet!
190 | return;
191 | }
192 |
193 | rawPressure = rawPressureSum / rawPressureSumCount;
194 | rawPressureSum = 0.0;
195 | rawPressureSumCount = 0;
196 |
197 | //calculate true pressure
198 | b6 = b5 - 4000;
199 | x1 = (b2 * (b6 * b6 >> 12)) >> 11;
200 | x2 = ac2 * b6 >> 11;
201 | x3 = x1 + x2;
202 |
203 | // Real Bosch formula - b3 = ((((int32_t)ac1 * 4 + x3) << overSamplingSetting) + 2) >> 2;
204 | // The version below is the same, but takes less program space
205 | tmp = ac1;
206 | tmp = (tmp * 4 + x3) << overSamplingSetting;
207 | b3 = (tmp + 2) >> 2;
208 |
209 | x1 = ac3 * b6 >> 13;
210 | x2 = (b1 * (b6 * b6 >> 12)) >> 16;
211 | x3 = ((x1 + x2) + 2) >> 2;
212 | b4 = (ac4 * (uint32_t) (x3 + 32768)) >> 15;
213 | b7 = ((uint32_t) rawPressure - b3) * (50000 >> overSamplingSetting);
214 | p = b7 < 0x80000000 ? (b7 << 1) / b4 : (b7 / b4) << 1;
215 |
216 | x1 = (p >> 8) * (p >> 8);
217 | x1 = (x1 * 3038) >> 16;
218 | x2 = (-7357 * p) >> 16;
219 | pressure = (p + ((x1 + x2 + 3791) >> 4));
220 |
221 | baroRawAltitude = (1.0 - pow(pressure / 101325.0, pressureFactor)) * 44330.0; // returns absolute baroAltitude in meters
222 | baroAltitude = filterSmooth(baroRawAltitude, baroAltitude, baroSmoothFactor);
223 | };
224 |
225 | void getBaroAltitude() {
226 | baroAltitudeRunning = baroAltitude - baroGroundAltitude;
227 | };
228 | private:
229 | int16_t ac1, ac2, ac3, b1, b2, mb, mc, md;
230 | uint16_t ac4, ac5, ac6;
231 |
232 | uint8_t overSamplingSetting;
233 | bool isReadPressure;
234 |
235 | float rawPressureSum;
236 | uint8_t rawPressureSumCount;
237 |
238 | int32_t pressure, rawPressure, rawTemperature;
239 | uint8_t pressureCount;
240 |
241 | float pressureFactor;
242 | float baroSmoothFactor;
243 | };
244 |
245 | // Create Baro object
246 | BMP085 baro;
247 |
248 | void SensorArray::initializeBaro() {
249 | baro.initialize();
250 | }
251 |
252 | void SensorArray::readBaroSum() {
253 | baro.measureBaroSum();
254 | }
255 |
256 | void SensorArray::evaluateBaroAltitude() {
257 | baro.evaluateBaroAltitude();
258 | baro.getBaroAltitude();
259 | }
260 |
--------------------------------------------------------------------------------
/libraries/P_Barometer/Barometer_ms5611.h:
--------------------------------------------------------------------------------
1 | /* MS5611 Barometer implementation in C++
2 |
3 | Big shoutout to whoever wrote the aeroquad ms5611 sensor integration
4 | (because it was used as example/base to create this one)
5 | */
6 |
7 | #define MS5611_I2C_ADDRESS 0x77 // address can also be 0x76
8 |
9 | #define MS561101BA_PROM_BASE_ADDR 0xA0
10 | #define MS561101BA_PROM_REG_COUNT 8 // number of registers in the PROM
11 | #define MS561101BA_D1_Pressure 0x40
12 | #define MS561101BA_D2_Temperature 0x50
13 | #define MS561101BA_RESET 0x1E
14 |
15 | #define MS561101BA_D1D2_SIZE 3
16 |
17 | // OSR (Over Sampling Ratio) constants
18 | #define MS561101BA_OSR_256 0x00
19 | #define MS561101BA_OSR_512 0x02
20 | #define MS561101BA_OSR_1024 0x04
21 | #define MS561101BA_OSR_2048 0x06
22 | #define MS561101BA_OSR_4096 0x08
23 |
24 | float baroRawAltitude = 0.0;
25 | float baroGroundAltitude = 0.0;
26 | float baroAltitude = 0.0;
27 | float baroAltitudeRunning = 0.0;
28 |
29 | float baroAltitudeToHoldTarget = 0.0;
30 | uint16_t baroAltitudeHoldThrottle = 1000;
31 |
32 | class MS5611 {
33 | public:
34 | // constructor
35 | MS5611() {
36 | };
37 |
38 | void initialize() {
39 | baroAltitude = 0.0;
40 |
41 | pressureFactor = 1.0 / 5.255;
42 | baroSmoothFactor = 0.03;
43 |
44 | pressure = 0.0;
45 |
46 | // Reset the sensor (to populate its internal PROM registers)
47 | Wire.beginTransmission(MS5611_I2C_ADDRESS);
48 | Wire.write(MS561101BA_RESET);
49 | Wire.endTransmission();
50 | delay(100);
51 |
52 | // Check if sensor is alive & Read calibration data
53 | if (readPROM()) {
54 | sensors.sensors_detected |= BAROMETER_DETECTED;
55 | } else {
56 | return;
57 | }
58 |
59 | requestRawTemperature(); // setup up next measure() for temperature
60 | isReadPressure = false;
61 | pressureCount = 0;
62 | delay(10);
63 | measureBaroSum(); // read temperature
64 | delay(10);
65 | measureBaro(); // read pressure
66 | delay(10);
67 |
68 | measureGroundBaro();
69 | delay(500); // Wait for sensor to heat up properly
70 |
71 | // check if measured ground altitude is valid
72 | while (abs(baroRawAltitude - baroGroundAltitude) > 4) {
73 | delay(26);
74 | measureGroundBaro();
75 | }
76 |
77 | baroAltitude = baroGroundAltitude;
78 | };
79 |
80 | void requestRawPressure() {
81 | Wire.beginTransmission(MS5611_I2C_ADDRESS);
82 | Wire.write(MS561101BA_D1_Pressure + MS561101BA_OSR_4096);
83 | Wire.endTransmission();
84 | }
85 |
86 | float readRawPressure() {
87 | Wire.beginTransmission(MS5611_I2C_ADDRESS);
88 | Wire.write(0);
89 | Wire.endTransmission();
90 |
91 | Wire.requestFrom(MS5611_I2C_ADDRESS, 3);
92 |
93 | lastRawPressure = (Wire.read() << 16) | (Wire.read() << 8) | (Wire.read() << 0);
94 |
95 | return (((lastRawPressure * sens) >> 21) - offset) >> 15;
96 | };
97 |
98 | void requestRawTemperature() {
99 | Wire.beginTransmission(MS5611_I2C_ADDRESS);
100 | Wire.write(MS561101BA_D2_Temperature + MS561101BA_OSR_4096);
101 | Wire.endTransmission();
102 | };
103 |
104 | uint32_t readRawTemperature() {
105 | Wire.beginTransmission(MS5611_I2C_ADDRESS);
106 | Wire.write(0);
107 | Wire.endTransmission();
108 |
109 | Wire.requestFrom(MS5611_I2C_ADDRESS, 3);
110 |
111 | lastRawTemperature = (Wire.read() << 16) | (Wire.read() << 8) | (Wire.read() << 0);
112 |
113 | int64_t dT = lastRawTemperature - (((int32_t)MS5611Prom[5]) << 8);
114 | offset = (((int64_t)MS5611Prom[2]) << 16) + ((MS5611Prom[4] * dT) >> 7);
115 | sens = (((int64_t)MS5611Prom[1]) << 15) + ((MS5611Prom[3] * dT) >> 8);
116 |
117 | return lastRawTemperature;
118 | };
119 |
120 | void measureBaro() {
121 | measureBaroSum();
122 | evaluateBaroAltitude();
123 | };
124 |
125 | void measureBaroSum() {
126 | // switch between pressure and temperature measurements
127 | // each loop, since it is slow to measure pressure
128 | if (isReadPressure) {
129 | rawPressureSum += readRawPressure();
130 | rawPressureSumCount++;
131 |
132 | if (pressureCount == 20) {
133 | requestRawTemperature();
134 | pressureCount = 0;
135 | isReadPressure = false;
136 | }
137 | else {
138 | requestRawPressure();
139 | }
140 | pressureCount++;
141 | } else { // select must equal TEMPERATURE
142 | rawTemperature = (int32_t)readRawTemperature();
143 | requestRawPressure();
144 | isReadPressure = true;
145 | }
146 | };
147 |
148 | void measureGroundBaro() {
149 | // measure initial ground pressure (multiple samples)
150 | float altSum = 0.0;
151 | for (uint8_t i = 0; i < 25; i++) {
152 | measureBaro();
153 | altSum += baroRawAltitude;
154 | delay(12);
155 | }
156 |
157 | baroGroundAltitude = altSum / 25;
158 | };
159 |
160 | void evaluateBaroAltitude() {
161 | if (rawPressureSumCount == 0) { // it may occur at init time that no pressure has been read yet!
162 | return;
163 | }
164 |
165 | pressure = rawPressureSum / rawPressureSumCount;
166 | rawPressureSum = 0.0;
167 | rawPressureSumCount = 0;
168 |
169 | baroRawAltitude = (1.0 - pow(pressure / 101325.0, pressureFactor)) * 44330.0; // returns absolute baroAltitude in centimeters
170 |
171 | baroAltitude = filterSmooth(baroRawAltitude, baroAltitude, baroSmoothFactor);
172 | };
173 |
174 | void getBaroAltitude() {
175 | baroAltitudeRunning = baroAltitude - baroGroundAltitude;
176 | };
177 |
178 | private:
179 | // Private variables
180 | uint16_t MS5611Prom[MS561101BA_PROM_REG_COUNT];
181 | int64_t offset, sens;
182 |
183 | bool isReadPressure;
184 | float pressureFactor;
185 | float baroSmoothFactor;
186 |
187 | float rawPressureSum;
188 | uint8_t rawPressureSumCount;
189 |
190 | int32_t pressure, rawPressure, rawTemperature;
191 | uint8_t pressureCount;
192 |
193 | int32_t lastRawTemperature;
194 | int32_t lastRawPressure;
195 |
196 | // Taken from aeroquad
197 | uint8_t crc4(uint16_t n_prom[]) {
198 | uint16_t n_rem = 0; // crc reminder
199 | uint16_t crc_read; // original value of the crc
200 |
201 | crc_read = n_prom[7]; // save read CRC
202 | n_prom[7] = (0xFF00 & (n_prom[7])); // CRC byte is replaced by 0
203 |
204 | for (int16_t cnt = 0; cnt < 16; cnt++) { // operation is performed on bytes
205 | // choose LSB or MSB
206 | if (cnt % 2 == 1) {
207 | n_rem ^= (n_prom[cnt >> 1]) & 0x00FF;
208 | } else {
209 | n_rem ^= n_prom[cnt >> 1] >> 8;
210 | }
211 |
212 | for (int16_t n_bit = 8; n_bit > 0; n_bit--) {
213 | if (n_rem & (0x8000)) {
214 | n_rem = (n_rem << 1) ^ 0x3000;
215 | } else {
216 | n_rem = (n_rem << 1);
217 | }
218 | }
219 | }
220 |
221 | n_rem = (n_rem >> 12) & 0xF; // // final 4-bit reminder is CRC code
222 | n_prom[7] = crc_read; // restore the crc_read to its original place
223 |
224 | return (n_rem);
225 | }
226 |
227 | // Taken from aeroquad
228 | int16_t readPROM() {
229 | for (int16_t i = 0; i < MS561101BA_PROM_REG_COUNT; i++) {
230 | Wire.beginTransmission(MS5611_I2C_ADDRESS);
231 | Wire.write(MS561101BA_PROM_BASE_ADDR + 2 * i);
232 | Wire.endTransmission();
233 |
234 | if (Wire.requestFrom(MS5611_I2C_ADDRESS, 2) == 2) {
235 | MS5611Prom[i] = (Wire.read() << 8) | Wire.read();
236 | } else {
237 | return 0;
238 | }
239 | }
240 |
241 | int16_t crc = crc4(MS5611Prom);
242 | int16_t crcProm = MS5611Prom[7] & 0xf;
243 | if (crc == crcProm) {
244 | return 1;
245 | }
246 | return 0;
247 | }
248 | };
249 |
250 | // Create Baro object
251 | MS5611 baro;
252 |
253 | void SensorArray::initializeBaro() {
254 | baro.initialize();
255 | }
256 |
257 | void SensorArray::readBaroSum() {
258 | baro.measureBaroSum();
259 | }
260 |
261 | void SensorArray::evaluateBaroAltitude() {
262 | baro.evaluateBaroAltitude();
263 | baro.getBaroAltitude();
264 | }
265 |
--------------------------------------------------------------------------------
/libraries/P_BatteryMonitor/BatteryMonitor_current.h:
--------------------------------------------------------------------------------
1 | /* Current monitor based on ACS758X 100B Hall Effect Linear Current Sensor
2 |
3 | Could use some optimizations and better documentation.
4 | */
5 | #define CURRENT_SENSOR_PIN 17
6 | #define CURRENT_SENSOR_ZERO 512 // no current on the load (default 512)
7 | #define CURRENT_SENSOR_FULL_SCALE_V 3.3
8 | #define CURRENT_SENSOR_SENSITIVITY_5V 0.02
9 | #define CURRENT_SENSOR_SENSITIVITY_3V3 0.0132
10 |
11 | int16_t current;
12 | float currentAmps;
13 | float batteryMonitorCapacityUsed = 0.0;
14 | unsigned long batteryMonitorCurrent_timer;
15 |
16 | void readBatteryMonitorCurrent() {
17 | // Current time
18 | unsigned long now = millis();
19 | float dT = 1000.0 / (now - batteryMonitorCurrent_timer); // Delta Time = 1 second / time passed = Hz
20 |
21 | current = analogRead(CURRENT_SENSOR_PIN);
22 |
23 | // Dead band
24 | if (abs(current) - CURRENT_SENSOR_ZERO > 1) {
25 | currentAmps = (current - CURRENT_SENSOR_ZERO) * CURRENT_SENSOR_FULL_SCALE_V / 1024.0 / CURRENT_SENSOR_SENSITIVITY_3V3;
26 |
27 | // Update capacity used with new data
28 | batteryMonitorCapacityUsed += (currentAmps / dT);
29 |
30 | // Save time for next comparison
31 | batteryMonitorCurrent_timer = now;
32 | } else {
33 | currentAmps = 0.0;
34 | }
35 | }
36 |
37 | float getBatteryMonitorCapacityUsed() {
38 | // batteryMonitorCapacityUsed is saved in A / hour, in here we will multiply the saved value by 1000 to convert from A to mA
39 | // divide that by 60 = mAps / minute
40 | // and another division by 60 will get the output to mAps / hour.
41 | return batteryMonitorCapacityUsed * 1000.0 / 60.0 / 60.0;
42 | }
43 |
44 | float getBatteryMonitorLiveCurrent() {
45 | return currentAmps;
46 | }
47 |
--------------------------------------------------------------------------------
/libraries/P_BatteryMonitor/BatteryMonitor_voltage.h:
--------------------------------------------------------------------------------
1 | /*
2 | Simple Battery voltage monitor
3 | by cTn
4 |
5 | AnalogRead scale on Teensy 3.0 is 3.3 / 1024 = 0.0032
6 | My "default" voltage divider is using 10K R1 and 1K5 R2 with 100nF capacitor paralel to R2
7 | Voltage divider scale is (10 + 1.5) / 1.5 = 13%
8 | Real voltage = ADC scale / (13% * 100)
9 |
10 | Measured values will be always averaged over 5 samples
11 | */
12 |
13 | // Battery voltage monitor PIN
14 | #define BAT_V_MONITOR_PIN 15
15 | #define BAT_V_MONITOR_WARNING 10.5
16 | #define BAT_V_MONITOR_ALARM 9.9
17 |
18 | #define BAT_V_MONITOR_SCALE 0.0032
19 | #define BAT_V_MONITOR_SCALE_FACTOR (BAT_V_MONITOR_SCALE / 0.1304)
20 |
21 | float BatteryVoltage = 0;
22 | bool BatteryWarning = false;
23 | bool BatteryAlarm = false;
24 |
25 | float BatteryVoltageSum = 0;
26 | uint8_t BatteryVoltageSamples = 0;
27 |
28 | // Variables used for warning/alarm indication via orientation lights
29 | uint8_t BatteryBlinkCounter = 0;
30 | bool BatteryBlinkState = false;
31 |
32 | void measureBatteryVoltage() {
33 | // Read analog PIN value into variable
34 | BatteryVoltageSum += analogRead(BAT_V_MONITOR_PIN);
35 |
36 | BatteryVoltageSamples++;
37 |
38 | if (BatteryVoltageSamples >= 5) {
39 | // Calculate Average
40 | BatteryVoltageSum = BatteryVoltageSum / BatteryVoltageSamples;
41 |
42 | // Properly scale it
43 | BatteryVoltage = BatteryVoltageSum * BAT_V_MONITOR_SCALE_FACTOR;
44 |
45 | // Reset SUM variables
46 | BatteryVoltageSum = 0;
47 | BatteryVoltageSamples = 0;
48 |
49 | // Warning & critical battery voltage flag handling
50 | if (BatteryVoltage < BAT_V_MONITOR_ALARM) {
51 | // Battery critical
52 | BatteryAlarm = true;
53 | } else if (BatteryVoltage < BAT_V_MONITOR_WARNING) {
54 | // Battery low
55 | BatteryWarning = true;
56 | } else {
57 | // Reset flags to "OFF" state
58 | BatteryAlarm = false;
59 | BatteryWarning = false;
60 | }
61 |
62 | #ifdef DISABLE_BATTERY_ALARM
63 | BatteryAlarm = false;
64 | BatteryWarning = false;
65 | #endif
66 | }
67 | }
68 |
69 | /* This code should be in the main setup()
70 | pinMode(BAT_V_MONITOR_PIN, INPUT); // Battery voltage input pin
71 | */
72 |
73 | /* This code should be entered inside the 50Hz loop
74 |
75 | // Orientation lights are also used to indicate battery voltage during flight
76 | // Warning = slow blinking
77 | // Alarm = fast blinking
78 | if (BatteryAlarm || BatteryWarning) {
79 | BatteryBlinkCounter++;
80 |
81 | uint8_t BlinkSpeed = 6; // Default blink speed (for battery warning)
82 | if (BatteryAlarm) BlinkSpeed = 1; // Fast blink speed (for battery critical)
83 |
84 | if (BatteryBlinkCounter >= BlinkSpeed) {
85 | BatteryBlinkState = !BatteryBlinkState;
86 | BatteryBlinkCounter = 0;
87 |
88 | digitalWrite(LED_ORIENTATION, BatteryBlinkState);
89 | }
90 | } else {
91 | digitalWrite(LED_ORIENTATION, HIGH); // set to HIGH by default
92 | }
93 |
94 | */
--------------------------------------------------------------------------------
/libraries/P_ESC/ESC_328p_HW.h:
--------------------------------------------------------------------------------
1 | /* ESC signal generation via shared timer1.
2 |
3 | MotorOuts array was added to separate variables changed by the FC in real time and PWM motor variables
4 | that are used inside the interrupt (without this, unpleasant behavior could occrur while system is writing to the
5 | MotorOut array and timer triggers interrupt).
6 |
7 | Big thanks to kha from #aeroquad for setting up the shared timer.
8 |
9 | This section is unfinished, default motor amount is still hardcoded to 4
10 | */
11 |
12 | uint16_t MotorOuts[MOTORS];
13 | uint8_t motorCounter = 0;
14 | uint16_t motorTotal = 0;
15 | uint8_t motorPins[8] = {2, 3, 4, 5, 6, 7, 9, 10}; // pin 8 is used as ppm input (thats why its skipped in this sequence)
16 |
17 | // Comparison Interrupt Vector
18 | ISR(TIMER1_COMPA_vect) {
19 | if (motorCounter < MOTORS) {
20 | digitalWrite(motorPins[motorCounter], 0); // end previous channel
21 | }
22 |
23 | motorCounter = (motorCounter + 1) % (MOTORS + 1); // advance to next
24 |
25 | if (motorCounter < MOTORS) {
26 | unsigned int now = MotorOuts[motorCounter] * 2;
27 |
28 | if (now < 2000) now = 2000; // sanity
29 | else if (now > 4000) now = 4000; // sanity
30 |
31 | motorTotal += now; // tally up time
32 | OCR1A = TCNT1 + now; // interrupt
33 | digitalWrite(motorPins[motorCounter], 1); // start pulse
34 | } else { // motorCounter == MOTORS
35 | OCR1A = TCNT1 + MOTORS * 4000 + 50 - motorTotal;
36 | motorTotal = 0;
37 | }
38 | }
39 |
40 | // Timer1 setup (shared timer for PPM sampling & PWM servo signal generation)
41 | void setupTimer1Esc() {
42 | // Setup timer1 in normal mode, count at 2MHz
43 | TCCR1A = 0;
44 | TCCR1B = (1 << CS11) | (1 << ICES1);
45 | TIMSK1 |= (1 << ICIE1) | (1 << OCIE1A); // Enable ICP and OCRA interrupts
46 | }
47 |
48 | void updateMotors() {
49 | cli(); // disable interrupts
50 |
51 | #if MOTORS == 3
52 | MotorOuts[0] = MotorOut[0];
53 | MotorOuts[1] = MotorOut[1];
54 | MotorOuts[2] = MotorOut[2];
55 | #elif MOTORS == 4
56 | MotorOuts[0] = MotorOut[0];
57 | MotorOuts[1] = MotorOut[1];
58 | MotorOuts[2] = MotorOut[2];
59 | MotorOuts[3] = MotorOut[3];
60 | #elif MOTORS == 6
61 | MotorOuts[0] = MotorOut[0];
62 | MotorOuts[1] = MotorOut[1];
63 | MotorOuts[2] = MotorOut[2];
64 | MotorOuts[3] = MotorOut[3];
65 | MotorOuts[4] = MotorOut[4];
66 | MotorOuts[5] = MotorOut[5];
67 | #elif MOTORS == 8
68 | MotorOuts[0] = MotorOut[0];
69 | MotorOuts[1] = MotorOut[1];
70 | MotorOuts[2] = MotorOut[2];
71 | MotorOuts[3] = MotorOut[3];
72 | MotorOuts[4] = MotorOut[4];
73 | MotorOuts[5] = MotorOut[5];
74 | MotorOuts[6] = MotorOut[6];
75 | MotorOuts[7] = MotorOut[7];
76 | #endif
77 |
78 | sei(); // enable interrupts
79 | }
80 |
81 | void initializeESC() {
82 | // We will also initialize the separate MotorOuts array values here
83 | // PWM pins are also set to output over here
84 | for (uint8_t i = 0; i < MOTORS; i++) {
85 | MotorOuts[i] = MotorOut[i];
86 | pinMode(motorPins[i], OUTPUT);
87 | }
88 |
89 | // Standard timer initialization
90 | setupTimer1Esc();
91 |
92 | if (CONFIG.data.calibrateESC) {
93 | // Calibration sequence requested
94 |
95 | // Signal range TOP maximum
96 | for (uint8_t motor = 0; motor < MOTORS; motor++) {
97 | MotorOut[motor] = 2000;
98 | }
99 | updateMotors();
100 |
101 | // Wait for all ESCs to acknowledge (1 beep)
102 | delay(5000);
103 |
104 | // Signal range BOTTOM minimum
105 | for (uint8_t motor = 0; motor < MOTORS; motor++) {
106 | MotorOut[motor] = 1000;
107 | }
108 | updateMotors();
109 |
110 | // Wait for all ESCs to acknowledge (2 + 1 beep)
111 | delay(4000);
112 |
113 | // Calibration done
114 | // disabling the calibration flag and updating EEPROM
115 | CONFIG.data.calibrateESC = 0;
116 | writeEEPROM();
117 | }
118 | }
--------------------------------------------------------------------------------
/libraries/P_ESC/ESC_teensy3_HW.h:
--------------------------------------------------------------------------------
1 | /* ESC / Servo signal generation done in hardware by FLEX timer, without ISR (yay!)
2 |
3 | We are using flex timer0 which supports 8 channels.
4 |
5 | Currently generating 400 Hz PWM signal that is fed into electronic speed controllers
6 | corresponding to each rotor.
7 |
8 | This code will probably be expanded to also generate servo signal for
9 | gimbal stabilization (hopefully in near future).
10 |
11 | Big thanks to kha from #aeroquad for helping me get this up and running.
12 |
13 | == PIN Configuration ==
14 |
15 | Channel - PIN name - Teensy 3.0 PIN numbering
16 |
17 | FTM0_CH0 - PTC1 - 22
18 | FTM0_CH1 - PTC2 - 23
19 | FTM0_CH2 - PTC3 - 9
20 | FTM0_CH3 - PTC4 - 10
21 | FTM0_CH4 - PTD4 - 6
22 | FTM0_CH5 - PTD5 - 20
23 | FTM0_CH6 - PTD6 - 21
24 | FTM0_CH7 - PTD7 - 5
25 | */
26 |
27 | void setupFTM0() {
28 | // Flex timer0 configuration
29 | FTM0_SC = 0x0c; // TOF=0 TOIE=0 CPWMS=0 CLKS=01 PS=100 (divide by 16)
30 |
31 | #ifdef ESC_400HZ
32 | // 400Hz PWM signal
33 | FTM0_MOD = 7500;
34 | #else
35 | // 250Hz PWM signal
36 | FTM0_MOD = 12000;
37 | #endif
38 |
39 | FTM0_C0SC = 0x28;
40 |
41 | // Initial values (3000 = 1ms)
42 | #if MOTORS == 3
43 | FTM0_C0V = 3000;
44 | FTM0_C1V = 3000;
45 | FTM0_C2V = 3000;
46 |
47 | // PORT Configuration
48 | PORTC_PCR1 |= 0x400;
49 | PORTC_PCR2 |= 0x400;
50 | PORTC_PCR3 |= 0x400;
51 | #elif MOTORS == 4
52 | FTM0_C0V = 3000;
53 | FTM0_C1V = 3000;
54 | FTM0_C2V = 3000;
55 | FTM0_C3V = 3000;
56 |
57 | // PORT Configuration
58 | PORTC_PCR1 |= 0x400;
59 | PORTC_PCR2 |= 0x400;
60 | PORTC_PCR3 |= 0x400;
61 | PORTC_PCR4 |= 0x400;
62 | #elif MOTORS == 6
63 | FTM0_C0V = 3000;
64 | FTM0_C1V = 3000;
65 | FTM0_C2V = 3000;
66 | FTM0_C3V = 3000;
67 | FTM0_C4V = 3000;
68 | FTM0_C5V = 3000;
69 |
70 | // PORT Configuration
71 | PORTC_PCR1 |= 0x400;
72 | PORTC_PCR2 |= 0x400;
73 | PORTC_PCR3 |= 0x400;
74 | PORTC_PCR4 |= 0x400;
75 | PORTD_PCR4 |= 0x400;
76 | PORTD_PCR5 |= 0x400;
77 | #elif MOTORS == 8
78 | FTM0_C0V = 3000;
79 | FTM0_C1V = 3000;
80 | FTM0_C2V = 3000;
81 | FTM0_C3V = 3000;
82 | FTM0_C4V = 3000;
83 | FTM0_C5V = 3000;
84 | FTM0_C6V = 3000;
85 | FTM0_C7V = 3000;
86 |
87 | // PORT Configuration
88 | PORTC_PCR1 |= 0x400;
89 | PORTC_PCR2 |= 0x400;
90 | PORTC_PCR3 |= 0x400;
91 | PORTC_PCR4 |= 0x400;
92 | PORTD_PCR4 |= 0x400;
93 | PORTD_PCR5 |= 0x400;
94 | PORTD_PCR6 |= 0x400;
95 | PORTD_PCR7 |= 0x400;
96 | #endif
97 | }
98 |
99 | void updateMotors() {
100 | #if MOTORS == 3
101 | FTM0_C0V = MotorOut[0] * 3;
102 | FTM0_C1V = MotorOut[1] * 3;
103 | FTM0_C2V = MotorOut[2] * 3;
104 | #elif MOTORS == 4
105 | FTM0_C0V = MotorOut[0] * 3;
106 | FTM0_C1V = MotorOut[1] * 3;
107 | FTM0_C2V = MotorOut[2] * 3;
108 | FTM0_C3V = MotorOut[3] * 3;
109 | #elif MOTORS == 6
110 | FTM0_C0V = MotorOut[0] * 3;
111 | FTM0_C1V = MotorOut[1] * 3;
112 | FTM0_C2V = MotorOut[2] * 3;
113 | FTM0_C3V = MotorOut[3] * 3;
114 | FTM0_C4V = MotorOut[4] * 3;
115 | FTM0_C5V = MotorOut[5] * 3;
116 | #elif MOTORS == 8
117 | FTM0_C0V = MotorOut[0] * 3;
118 | FTM0_C1V = MotorOut[1] * 3;
119 | FTM0_C2V = MotorOut[2] * 3;
120 | FTM0_C3V = MotorOut[3] * 3;
121 | FTM0_C4V = MotorOut[4] * 3;
122 | FTM0_C5V = MotorOut[5] * 3;
123 | FTM0_C6V = MotorOut[6] * 3;
124 | FTM0_C7V = MotorOut[7] * 3;
125 | #endif
126 | }
127 |
128 | void initializeESC() {
129 | setupFTM0();
130 |
131 | if (CONFIG.data.calibrateESC) {
132 | // Calibration sequence requested
133 |
134 | // Signal range TOP maximum
135 | for (uint8_t motor = 0; motor < MOTORS; motor++) {
136 | MotorOut[motor] = 2000;
137 | }
138 | updateMotors();
139 |
140 | // Wait for all ESCs to acknowledge (1 beep)
141 | delay(5000);
142 |
143 | // Signal range BOTTOM minimum
144 | for (uint8_t motor = 0; motor < MOTORS; motor++) {
145 | MotorOut[motor] = 1000;
146 | }
147 | updateMotors();
148 |
149 | // Wait for all ESCs to acknowledge (2 + 1 beep)
150 | delay(4000);
151 |
152 | // Calibration done
153 | // disabling the calibration flag and updating EEPROM
154 | CONFIG.data.calibrateESC = 0;
155 | writeEEPROM();
156 | }
157 | }
--------------------------------------------------------------------------------
/libraries/P_FrameType/FrameType_QuadPlus.h:
--------------------------------------------------------------------------------
1 | /*
2 | FRONT
3 | CW (0)
4 |
5 | O
6 | |
7 | |
8 | |
9 | CCW (3) O-----#-----O CCW (1)
10 | LEFT | RIGHT
11 | |
12 | |
13 | O
14 |
15 | CW (2)
16 | BACK
17 | */
18 |
19 | #define MOTORS 4
20 | uint16_t MotorOut[MOTORS] = {1000, 1000, 1000, 1000};
21 |
22 | void updateMotorsMix() {
23 | // Limit YAW to +- 200 (20%)
24 | YawMotorSpeed = constrain(YawMotorSpeed, -200, 200);
25 |
26 | // All of the motor outputs are constrained to standard 1000 - 2000 us PWM
27 | // Minimum Armed Throttle variable was added to prevent motors stopping during flight (in extreme "stabilization" behaviours)
28 | MotorOut[0] = constrain(throttle + PitchMotorSpeed + YawMotorSpeed, CONFIG.data.minimumArmedThrottle, 2000);
29 | MotorOut[1] = constrain(throttle - RollMotorSpeed - YawMotorSpeed, CONFIG.data.minimumArmedThrottle, 2000);
30 | MotorOut[2] = constrain(throttle - PitchMotorSpeed + YawMotorSpeed, CONFIG.data.minimumArmedThrottle, 2000);
31 | MotorOut[3] = constrain(throttle + RollMotorSpeed - YawMotorSpeed, CONFIG.data.minimumArmedThrottle, 2000);
32 | }
--------------------------------------------------------------------------------
/libraries/P_FrameType/FrameType_QuadX.h:
--------------------------------------------------------------------------------
1 | /*
2 | FRONT
3 | CW (0) CCW (1)
4 |
5 | O O
6 | \ /
7 | \ /
8 | \ /
9 | \ /
10 | LEFT # RIGHT
11 | / \
12 | / \
13 | / \
14 | / \
15 | O O
16 |
17 | CCW (3) CW (2)
18 | BACK
19 | */
20 |
21 | #define MOTORS 4
22 | uint16_t MotorOut[MOTORS] = {1000, 1000, 1000, 1000};
23 |
24 | void updateMotorsMix() {
25 | // Limit YAW to +- 200 (20%)
26 | YawMotorSpeed = constrain(YawMotorSpeed, -200, 200);
27 |
28 | // All of the motor outputs are constrained to standard 1000 - 2000 us PWM
29 | // Minimum Armed Throttle variable was added to prevent motors stopping during flight (in extreme "stabilization" behaviours)
30 | MotorOut[0] = constrain(throttle + PitchMotorSpeed + RollMotorSpeed + YawMotorSpeed, CONFIG.data.minimumArmedThrottle, 2000);
31 | MotorOut[1] = constrain(throttle + PitchMotorSpeed - RollMotorSpeed - YawMotorSpeed, CONFIG.data.minimumArmedThrottle, 2000);
32 | MotorOut[2] = constrain(throttle - PitchMotorSpeed - RollMotorSpeed + YawMotorSpeed, CONFIG.data.minimumArmedThrottle, 2000);
33 | MotorOut[3] = constrain(throttle - PitchMotorSpeed + RollMotorSpeed - YawMotorSpeed, CONFIG.data.minimumArmedThrottle, 2000);
34 | }
--------------------------------------------------------------------------------
/libraries/P_GPS/neo-6m_configuration.txt:
--------------------------------------------------------------------------------
1 | MON-VER - 0A 04 28 00 37 2E 30 33 20 28 34 35 39 36 39 29 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 30 30 30 34 30 30 30 37 00 00
2 | CFG-ANT - 06 13 04 00 1B 00 8B A9
3 | CFG-DAT - 06 06 02 00 00 00
4 | CFG-FXN - 06 0E 24 00 0C 00 00 00 00 00 00 00 00 00 00 00 10 27 00 00 10 27 00 00 D0 07 00 00 18 FC FF FF 00 00 00 00 00 00 00 00
5 | CFG-INF - 06 02 0A 00 00 00 00 00 FF FF FF FF 00 00
6 | CFG-INF - 06 02 0A 00 01 00 00 00 87 87 87 87 87 87
7 | CFG-INF - 06 02 0A 00 03 00 00 00 00 00 00 00 00 00
8 | CFG-ITFM - 06 39 08 00 F3 AC 62 2D 1E 03 00 00
9 | CFG-MSG - 06 01 08 00 01 01 00 00 00 00 00 00
10 | CFG-MSG - 06 01 08 00 01 02 00 01 00 00 00 00
11 | CFG-MSG - 06 01 08 00 01 03 00 01 00 00 00 00
12 | CFG-MSG - 06 01 08 00 01 04 00 00 00 00 00 00
13 | CFG-MSG - 06 01 08 00 01 06 00 01 00 00 00 00
14 | CFG-MSG - 06 01 08 00 01 11 00 00 00 00 00 00
15 | CFG-MSG - 06 01 08 00 01 12 00 01 00 00 00 00
16 | CFG-MSG - 06 01 08 00 01 20 00 00 00 00 00 00
17 | CFG-MSG - 06 01 08 00 01 21 00 00 00 00 00 00
18 | CFG-MSG - 06 01 08 00 01 22 00 00 00 00 00 00
19 | CFG-MSG - 06 01 08 00 01 30 00 01 00 00 00 00
20 | CFG-MSG - 06 01 08 00 01 31 00 00 00 00 00 00
21 | CFG-MSG - 06 01 08 00 01 32 00 00 00 00 00 00
22 | CFG-MSG - 06 01 08 00 02 20 00 1E 00 00 00 00
23 | CFG-MSG - 06 01 08 00 02 23 00 00 00 00 00 00
24 | CFG-MSG - 06 01 08 00 0A 02 00 00 00 00 00 00
25 | CFG-MSG - 06 01 08 00 0A 05 00 00 00 00 00 00
26 | CFG-MSG - 06 01 08 00 0A 06 00 01 00 00 00 00
27 | CFG-MSG - 06 01 08 00 0A 07 00 00 00 00 00 00
28 | CFG-MSG - 06 01 08 00 0A 08 00 00 00 00 00 00
29 | CFG-MSG - 06 01 08 00 0A 09 00 00 00 00 00 00
30 | CFG-MSG - 06 01 08 00 0A 0A 00 00 00 00 00 00
31 | CFG-MSG - 06 01 08 00 0A 20 00 00 00 00 00 00
32 | CFG-MSG - 06 01 08 00 0A 21 00 00 00 00 00 00
33 | CFG-MSG - 06 01 08 00 0B 00 00 00 00 00 00 00
34 | CFG-MSG - 06 01 08 00 0B 05 00 00 00 00 00 00
35 | CFG-MSG - 06 01 08 00 0B 30 00 00 00 00 00 00
36 | CFG-MSG - 06 01 08 00 0B 31 00 00 00 00 00 00
37 | CFG-MSG - 06 01 08 00 0B 32 00 00 00 00 00 00
38 | CFG-MSG - 06 01 08 00 0B 33 00 00 00 00 00 00
39 | CFG-MSG - 06 01 08 00 0D 01 00 00 00 00 00 00
40 | CFG-MSG - 06 01 08 00 0D 03 00 00 00 00 00 00
41 | CFG-MSG - 06 01 08 00 0D 06 00 00 00 00 00 00
42 | CFG-MSG - 06 01 08 00 F0 00 01 01 01 01 01 01
43 | CFG-MSG - 06 01 08 00 F0 01 01 01 01 01 01 01
44 | CFG-MSG - 06 01 08 00 F0 02 01 01 01 01 01 01
45 | CFG-MSG - 06 01 08 00 F0 03 01 01 01 01 01 01
46 | CFG-MSG - 06 01 08 00 F0 04 01 01 01 01 01 01
47 | CFG-MSG - 06 01 08 00 F0 05 01 01 01 01 01 01
48 | CFG-MSG - 06 01 08 00 F0 06 00 00 00 00 00 00
49 | CFG-MSG - 06 01 08 00 F0 07 00 00 00 00 00 00
50 | CFG-MSG - 06 01 08 00 F0 08 00 00 00 00 00 00
51 | CFG-MSG - 06 01 08 00 F0 09 00 00 00 00 00 00
52 | CFG-MSG - 06 01 08 00 F0 0A 00 00 00 00 00 00
53 | CFG-MSG - 06 01 08 00 F1 00 00 00 00 00 00 00
54 | CFG-MSG - 06 01 08 00 F1 03 00 00 00 00 00 00
55 | CFG-MSG - 06 01 08 00 F1 04 00 00 00 00 00 00
56 | CFG-NAV5 - 06 24 24 00 FF FF 00 03 00 00 00 00 10 27 00 00 05 00 FA 00 FA 00 64 00 2C 01 00 3C 00 00 00 00 00 00 00 00 00 00 00 00
57 | CFG-NAVX5 - 06 23 28 00 00 00 FF FF 03 00 00 00 03 02 03 10 07 00 00 01 00 00 43 06 00 00 00 00 01 01 00 00 00 64 78 00 00 00 00 00 00 00 00 00
58 | CFG-NMEA - 06 17 04 00 00 23 00 02
59 | CFG-PM - 06 32 18 00 00 06 00 00 04 90 00 00 E8 03 00 00 10 27 00 00 00 00 00 00 02 00 00 00
60 | CFG-PM2 - 06 3B 2C 00 01 06 00 00 00 90 02 00 E8 03 00 00 10 27 00 00 00 00 00 00 02 00 00 00 2C 01 00 00 4F C1 03 00 86 02 00 00 FE 00 00 00 64 40 01 00
61 | CFG-PRT - 06 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
62 | CFG-PRT - 06 00 14 00 01 00 00 00 C0 08 00 00 00 96 00 00 01 00 01 00 00 00 00 00
63 | CFG-PRT - 06 00 14 00 02 00 00 00 C0 08 00 00 80 25 00 00 00 00 00 00 00 00 00 00
64 | CFG-PRT - 06 00 14 00 03 00 00 00 00 00 00 00 00 00 00 00 07 00 07 00 00 00 00 00
65 | CFG-PRT - 06 00 14 00 04 00 00 00 00 32 00 00 00 00 00 00 07 00 07 00 00 00 00 00
66 | CFG-RATE - 06 08 06 00 C8 00 01 00 01 00
67 | CFG-RINV - 06 34 18 00 00 4E 6F 74 69 63 65 3A 20 6E 6F 20 64 61 74 61 20 73 61 76 65 64 21 00
68 | CFG-RXM - 06 11 02 00 08 00
69 | CFG-SBAS - 06 16 08 00 01 03 03 00 51 62 06 00
70 | CFG-TP - 06 07 14 00 40 42 0F 00 A0 86 01 00 01 01 00 00 32 00 00 00 00 00 00 00
71 | CFG-TP5 - 06 31 20 00 00 00 00 00 32 00 00 00 40 42 0F 00 40 42 0F 00 00 00 00 00 A0 86 01 00 00 00 00 00 F7 00 00 00
72 | CFG-TP5 - 06 31 20 00 01 00 00 00 32 00 00 00 04 00 00 00 01 00 00 00 48 E8 01 00 A0 86 01 00 00 00 00 00 FE 00 00 00
73 | CFG-USB - 06 1B 6C 00 46 15 A6 01 00 00 00 00 64 00 00 01 75 2D 62 6C 6F 78 20 41 47 20 2D 20 77 77 77 2E 75 2D 62 6C 6F 78 2E 63 6F 6D 00 00 00 00 00 00 75 2D 62 6C 6F 78 20 36 20 20 2D 20 20 47 50 53 20 52 65 63 65 69 76 65 72 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
74 |
--------------------------------------------------------------------------------
/libraries/P_Gyroscope/ITG3200_AQ_v20.h:
--------------------------------------------------------------------------------
1 | /* ITG3200 - on AQ shield v 2.0
2 | (ITG3200 is on separate breakout)
3 |
4 | This library requires some more documentation
5 | */
6 |
7 | #define ITG3200_ADDRESS 0x69
8 |
9 | #define ITG3200_IDENTITY 0x68
10 | #define ITG3200_IDENTITY_MASK 0x7E
11 | #define ITG3200_MEMORY_ADDRESS 0x1D
12 | #define ITG3200_BUFFER_SIZE 6
13 | #define ITG3200_RESET_ADDRESS 0x3E
14 | #define ITG3200_RESET_VALUE 0x80
15 | #define ITG3200_LOW_PASS_FILTER_ADDR 0x16
16 | #define ITG3200_LOW_PASS_FILTER_VALUE 0x1D // 10Hz low pass filter
17 | #define ITG3200_OSCILLATOR_ADDR 0x3E
18 | #define ITG3200_OSCILLATOR_VALUE 0x01 // use X gyro oscillator
19 | #define ITG3200_SCALE_TO_RADIANS 823.626831 // 14.375 LSBs per °/sec, / Pi / 180
20 | #define ITG3200_TEMPERATURE_ADDRESS 0x1B
21 |
22 | float gyro[3];
23 |
24 | class ITG3200 {
25 | public:
26 | // Constructor
27 | ITG3200() {
28 | gyroScaleFactor = radians(1.0 / 14.375); // ITG3200 14.375 LSBs per °/sec
29 |
30 | gyroSamples = 0;
31 | };
32 |
33 | void initialize() {
34 | // Check if sensor is alive
35 | Wire.beginTransmission(ITG3200_ADDRESS);
36 | Wire.write(0x00);
37 | Wire.endTransmission();
38 |
39 | Wire.requestFrom(ITG3200_ADDRESS, 1);
40 |
41 | uint8_t register_value = Wire.read();
42 |
43 | if ((register_value & ITG3200_IDENTITY_MASK) == ITG3200_IDENTITY) {
44 | sensors.sensors_detected |= GYROSCOPE_DETECTED;
45 | } else {
46 | return;
47 | }
48 |
49 | sensors.i2c_write8(ITG3200_ADDRESS, ITG3200_RESET_ADDRESS, ITG3200_RESET_VALUE); // send a reset to the device
50 |
51 | // Startup delay
52 | delay(100);
53 |
54 | sensors.i2c_write8(ITG3200_ADDRESS, ITG3200_LOW_PASS_FILTER_ADDR, ITG3200_LOW_PASS_FILTER_VALUE); // 10Hz low pass filter
55 | sensors.i2c_write8(ITG3200_ADDRESS, ITG3200_RESET_ADDRESS, ITG3200_OSCILLATOR_VALUE); // use internal oscillator
56 |
57 | // Let the gyro heat up
58 | delay(1500);
59 |
60 | // setup axis mapping
61 | if (CONFIG.data.GYRO_AXIS_MAP.initialized == 0) { // check if map was defined before, if not "save default" order set
62 | CONFIG.data.GYRO_AXIS_MAP.axis1 = 0; // x
63 | CONFIG.data.GYRO_AXIS_MAP.axis2 = 1; // y
64 | CONFIG.data.GYRO_AXIS_MAP.axis3 = 2; // z
65 |
66 | CONFIG.data.GYRO_AXIS_MAP.axis1_sign = 0;
67 | CONFIG.data.GYRO_AXIS_MAP.axis2_sign = 1;
68 | CONFIG.data.GYRO_AXIS_MAP.axis3_sign = 0;
69 |
70 | CONFIG.data.GYRO_AXIS_MAP.initialized = 1;
71 |
72 | // save default in eeprom
73 | writeEEPROM();
74 | }
75 | };
76 |
77 | // ~1280ms
78 | void calibrate_gyro() {
79 | static uint8_t retry = 0;
80 | uint8_t i, count = 128;
81 | int16_t xSum = 0, ySum = 0, zSum = 0;
82 |
83 | for(i = 0; i < count; i++) {
84 | readGyroRaw();
85 | xSum += gyroRaw[XAXIS];
86 | ySum += gyroRaw[YAXIS];
87 | zSum += gyroRaw[ZAXIS];
88 | delay(10);
89 | }
90 |
91 | gyro_offset[XAXIS] = -xSum / count;
92 | gyro_offset[YAXIS] = -ySum / count;
93 | gyro_offset[ZAXIS] = -zSum / count;
94 |
95 | // Calibration sanity check
96 | // if suitable offset couldn't be established, break out of the loop after 10 retries
97 | if ((abs(gyro_offset[XAXIS]) > 140 || abs(gyro_offset[YAXIS]) > 140 || abs(gyro_offset[ZAXIS]) > 140) && retry < 10) {
98 | // gyro calibration failed, run again
99 | retry++;
100 |
101 | // small delay before next gyro calibration
102 | delay(500);
103 |
104 | calibrate_gyro();
105 | }
106 | };
107 |
108 | void readGyroRaw() {
109 | Wire.beginTransmission(ITG3200_ADDRESS);
110 | Wire.write(ITG3200_MEMORY_ADDRESS);
111 | Wire.endTransmission();
112 |
113 | Wire.requestFrom(ITG3200_ADDRESS, ITG3200_BUFFER_SIZE);
114 |
115 | gyroRaw[CONFIG.data.GYRO_AXIS_MAP.axis1] = ((Wire.read() << 8) | Wire.read()) * (CONFIG.data.GYRO_AXIS_MAP.axis1_sign?-1:1);
116 | gyroRaw[CONFIG.data.GYRO_AXIS_MAP.axis2] = ((Wire.read() << 8) | Wire.read()) * (CONFIG.data.GYRO_AXIS_MAP.axis2_sign?-1:1);
117 | gyroRaw[CONFIG.data.GYRO_AXIS_MAP.axis3] = ((Wire.read() << 8) | Wire.read()) * (CONFIG.data.GYRO_AXIS_MAP.axis3_sign?-1:1);
118 | };
119 |
120 | void readGyroSum() {
121 | readGyroRaw();
122 |
123 | gyroSum[XAXIS] += gyroRaw[XAXIS];
124 | gyroSum[YAXIS] += gyroRaw[YAXIS];
125 | gyroSum[ZAXIS] += gyroRaw[ZAXIS];
126 |
127 | gyroSamples++;
128 | };
129 |
130 | void evaluateGyro() {
131 | // Calculate average
132 | gyro[XAXIS] = gyroSum[XAXIS] / gyroSamples;
133 | gyro[YAXIS] = -(gyroSum[YAXIS] / gyroSamples);
134 | gyro[ZAXIS] = gyroSum[ZAXIS] / gyroSamples;
135 |
136 | // Apply offsets
137 | gyro[XAXIS] += gyro_offset[XAXIS];
138 | gyro[YAXIS] += gyro_offset[YAXIS];
139 | gyro[ZAXIS] += gyro_offset[ZAXIS];
140 |
141 | // Apply correct scaling (at this point gyroNsumRate is in radians)
142 | gyro[XAXIS] *= gyroScaleFactor;
143 | gyro[YAXIS] *= gyroScaleFactor;
144 | gyro[ZAXIS] *= gyroScaleFactor;
145 |
146 | // Reset SUM variables
147 | gyroSum[XAXIS] = 0;
148 | gyroSum[YAXIS] = 0;
149 | gyroSum[ZAXIS] = 0;
150 | gyroSamples = 0;
151 | };
152 | private:
153 | int16_t gyro_offset[3];
154 | int16_t gyroRaw[3];
155 | float gyroSum[3];
156 | float gyroScaleFactor;
157 |
158 | uint8_t gyroSamples;
159 | };
160 |
161 | ITG3200 itg;
162 |
163 | void SensorArray::initializeGyro() {
164 | itg.initialize();
165 | itg.calibrate_gyro();
166 | }
167 |
168 | void SensorArray::readGyroSum() {
169 | itg.readGyroSum();
170 | }
171 |
172 | void SensorArray::evaluateGyro() {
173 | itg.evaluateGyro();
174 | }
--------------------------------------------------------------------------------
/libraries/P_Gyroscope/ITG3200_AQ_v21.h:
--------------------------------------------------------------------------------
1 | /* ITG3200, 9DOF stick library
2 |
3 | This library requires some more documentation
4 | (code was tested and works perfectly fine)
5 | */
6 |
7 | #define ITG3200_ADDRESS 0x68
8 |
9 | #define ITG3200_IDENTITY 0x68
10 | #define ITG3200_IDENTITY_MASK 0x7E
11 | #define ITG3200_MEMORY_ADDRESS 0x1D
12 | #define ITG3200_BUFFER_SIZE 6
13 | #define ITG3200_RESET_ADDRESS 0x3E
14 | #define ITG3200_RESET_VALUE 0x80
15 | #define ITG3200_LOW_PASS_FILTER_ADDR 0x16
16 | #define ITG3200_LOW_PASS_FILTER_VALUE 0x1D // 10Hz low pass filter
17 | #define ITG3200_OSCILLATOR_ADDR 0x3E
18 | #define ITG3200_OSCILLATOR_VALUE 0x01 // use X gyro oscillator
19 | #define ITG3200_SCALE_TO_RADIANS 823.626831 // 14.375 LSBs per °/sec, / Pi / 180
20 | #define ITG3200_TEMPERATURE_ADDRESS 0x1B
21 |
22 | float gyro[3];
23 |
24 | class ITG3200 {
25 | public:
26 | // Constructor
27 | ITG3200() {
28 | gyroScaleFactor = radians(1.0 / 14.375); // ITG3200 14.375 LSBs per °/sec
29 |
30 | gyroSamples = 0;
31 | };
32 |
33 | void initialize() {
34 | // Check if sensor is alive
35 | Wire.beginTransmission(ITG3200_ADDRESS);
36 | Wire.write(0x00);
37 | Wire.endTransmission();
38 |
39 | Wire.requestFrom(ITG3200_ADDRESS, 1);
40 |
41 | uint8_t register_value = Wire.read();
42 |
43 | if ((register_value & ITG3200_IDENTITY_MASK) == ITG3200_IDENTITY) {
44 | sensors.sensors_detected |= GYROSCOPE_DETECTED;
45 | } else {
46 | return;
47 | }
48 |
49 | sensors.i2c_write8(ITG3200_ADDRESS, ITG3200_RESET_ADDRESS, ITG3200_RESET_VALUE); // send a reset to the device
50 |
51 | // Startup delay
52 | delay(100);
53 |
54 | sensors.i2c_write8(ITG3200_ADDRESS, ITG3200_LOW_PASS_FILTER_ADDR, ITG3200_LOW_PASS_FILTER_VALUE); // 10Hz low pass filter
55 | sensors.i2c_write8(ITG3200_ADDRESS, ITG3200_RESET_ADDRESS, ITG3200_OSCILLATOR_VALUE); // use internal oscillator
56 |
57 | // Let the gyro heat up
58 | delay(1500);
59 |
60 | // setup axis mapping
61 | if (CONFIG.data.GYRO_AXIS_MAP.initialized == 0) { // check if map was defined before, if not "save default" order set
62 | CONFIG.data.GYRO_AXIS_MAP.axis1 = 1; // y
63 | CONFIG.data.GYRO_AXIS_MAP.axis2 = 0; // x
64 | CONFIG.data.GYRO_AXIS_MAP.axis3 = 2; // z
65 |
66 | CONFIG.data.GYRO_AXIS_MAP.axis1_sign = 0;
67 | CONFIG.data.GYRO_AXIS_MAP.axis2_sign = 0;
68 | CONFIG.data.GYRO_AXIS_MAP.axis3_sign = 0;
69 |
70 | CONFIG.data.GYRO_AXIS_MAP.initialized = 1;
71 |
72 | // save default in eeprom
73 | writeEEPROM();
74 | }
75 | };
76 |
77 | // ~1280ms
78 | void calibrate_gyro() {
79 | static uint8_t retry = 0;
80 | uint8_t i, count = 128;
81 | int16_t xSum = 0, ySum = 0, zSum = 0;
82 |
83 | for(i = 0; i < count; i++) {
84 | readGyroRaw();
85 | xSum += gyroRaw[XAXIS];
86 | ySum += gyroRaw[YAXIS];
87 | zSum += gyroRaw[ZAXIS];
88 | delay(10);
89 | }
90 |
91 | gyro_offset[XAXIS] = -xSum / count;
92 | gyro_offset[YAXIS] = -ySum / count;
93 | gyro_offset[ZAXIS] = -zSum / count;
94 |
95 | // Calibration sanity check
96 | // if suitable offset couldn't be established, break out of the loop after 10 retries
97 | if ((abs(gyro_offset[XAXIS]) > 140 || abs(gyro_offset[YAXIS]) > 140 || abs(gyro_offset[ZAXIS]) > 140) && retry < 10) {
98 | // gyro calibration failed, run again
99 | retry++;
100 |
101 | // small delay before next gyro calibration
102 | delay(500);
103 |
104 | calibrate_gyro();
105 | }
106 | };
107 |
108 | void readGyroRaw() {
109 | Wire.beginTransmission(ITG3200_ADDRESS);
110 | Wire.write(ITG3200_MEMORY_ADDRESS);
111 | Wire.endTransmission();
112 |
113 | Wire.requestFrom(ITG3200_ADDRESS, ITG3200_BUFFER_SIZE);
114 |
115 | gyroRaw[CONFIG.data.GYRO_AXIS_MAP.axis1] = ((Wire.read() << 8) | Wire.read()) * (CONFIG.data.GYRO_AXIS_MAP.axis1_sign?-1:1);
116 | gyroRaw[CONFIG.data.GYRO_AXIS_MAP.axis2] = ((Wire.read() << 8) | Wire.read()) * (CONFIG.data.GYRO_AXIS_MAP.axis2_sign?-1:1);
117 | gyroRaw[CONFIG.data.GYRO_AXIS_MAP.axis3] = ((Wire.read() << 8) | Wire.read()) * (CONFIG.data.GYRO_AXIS_MAP.axis3_sign?-1:1);
118 | };
119 |
120 | void readGyroSum() {
121 | readGyroRaw();
122 |
123 | gyroSum[XAXIS] += gyroRaw[XAXIS];
124 | gyroSum[YAXIS] += gyroRaw[YAXIS];
125 | gyroSum[ZAXIS] += gyroRaw[ZAXIS];
126 |
127 | gyroSamples++;
128 | };
129 |
130 | void evaluateGyro() {
131 | // Calculate average
132 | gyro[XAXIS] = gyroSum[XAXIS] / gyroSamples;
133 | gyro[YAXIS] = gyroSum[YAXIS] / gyroSamples;
134 | gyro[ZAXIS] = gyroSum[ZAXIS] / gyroSamples;
135 |
136 | // Apply offsets
137 | gyro[XAXIS] += gyro_offset[XAXIS];
138 | gyro[YAXIS] += gyro_offset[YAXIS];
139 | gyro[ZAXIS] += gyro_offset[ZAXIS];
140 |
141 | // Apply correct scaling (at this point gyro is in radians)
142 | gyro[XAXIS] *= gyroScaleFactor;
143 | gyro[YAXIS] *= gyroScaleFactor;
144 | gyro[ZAXIS] *= gyroScaleFactor;
145 |
146 | // Reset SUM variables
147 | gyroSum[XAXIS] = 0;
148 | gyroSum[YAXIS] = 0;
149 | gyroSum[ZAXIS] = 0;
150 | gyroSamples = 0;
151 | };
152 | private:
153 | int16_t gyro_offset[3];
154 | int16_t gyroRaw[3];
155 | float gyroSum[3];
156 | float gyroScaleFactor;
157 |
158 | uint8_t gyroSamples;
159 | };
160 |
161 | ITG3200 itg;
162 |
163 | void SensorArray::initializeGyro() {
164 | itg.initialize();
165 | itg.calibrate_gyro();
166 | }
167 |
168 | void SensorArray::readGyroSum() {
169 | itg.readGyroSum();
170 | }
171 |
172 | void SensorArray::evaluateGyro() {
173 | itg.evaluateGyro();
174 | }
--------------------------------------------------------------------------------
/libraries/P_Kinematics/kinematics_ARG.h:
--------------------------------------------------------------------------------
1 | // Quaternion implementation of the 'DCM filter' [Mayhony et al].
2 | //
3 | // This is the EXACT ARG kinematics taken from aeroquad flight controll software
4 | // @see: https://github.com/AeroQuad/AeroQuad
5 | //
6 | // User must define 'halfT' as the (sample period / 2), and the filter gains 'Kp' and 'Ki'.
7 | //
8 | // Global variables 'q0', 'q1', 'q2', 'q3' are the quaternion elements representing the estimated
9 | // orientation.
10 | //
11 | // User must call 'IMUupdate()' every sample period and parse calibrated gyroscope ('gx', 'gy', 'gz')
12 | // and accelerometer ('ax', 'ay', 'ay') data. Gyroscope units are radians/second, accelerometer
13 | // units are irrelevant as the vector is normalised.
14 |
15 | float q0 = 1.0;
16 | float q1 = 0.0;
17 | float q2 = 0.0;
18 | float q3 = 0.0;
19 | float exInt = 0.0;
20 | float eyInt = 0.0;
21 | float ezInt = 0.0;
22 |
23 | float previousEx = 0;
24 | float previousEy = 0;
25 | float previousEz = 0;
26 |
27 | float Kp = 0.2;
28 | float Ki = 0.0005;
29 |
30 | unsigned long kinematics_timer;
31 |
32 | boolean isSwitched(float previousError, float currentError) {
33 | if ( (previousError > 0 && currentError < 0) || (previousError < 0 && currentError > 0)) {
34 | return true;
35 | }
36 | return false;
37 | }
38 |
39 | void argUpdate(float gx, float gy, float gz, float ax, float ay, float az, float G_Dt) {
40 |
41 | float norm;
42 | float vx, vy, vz;
43 | float q0i, q1i, q2i, q3i;
44 | float ex, ey, ez;
45 |
46 | float halfT = G_Dt / 2;
47 |
48 | // normalise the measurements
49 | norm = sqrt(ax * ax + ay * ay + az * az);
50 | ax = ax / norm;
51 | ay = ay / norm;
52 | az = az / norm;
53 |
54 | // estimated direction of gravity and flux (v and w)
55 | vx = 2 * (q1 * q3 - q0 * q2);
56 | vy = 2 * (q0 * q1 + q2 * q3);
57 | vz = q0 * q0 - q1 * q1 - q2 * q2 + q3 * q3;
58 |
59 | // error is sum of cross product between reference direction of fields and direction measured by sensors
60 | ex = (vy * az - vz * ay);
61 | ey = (vz * ax - vx * az);
62 | ez = (vx * ay - vy * ax);
63 |
64 | // integral error scaled integral gain
65 | exInt = exInt + ex * Ki;
66 | if (isSwitched(previousEx,ex)) {
67 | exInt = 0.0;
68 | }
69 | previousEx = ex;
70 |
71 | eyInt = eyInt + ey * Ki;
72 | if (isSwitched(previousEy,ey)) {
73 | eyInt = 0.0;
74 | }
75 | previousEy = ey;
76 |
77 | ezInt = ezInt + ez * Ki;
78 | if (isSwitched(previousEz,ez)) {
79 | ezInt = 0.0;
80 | }
81 | previousEz = ez;
82 |
83 | // adjusted gyroscope measurements
84 | gx = gx + Kp * ex + exInt;
85 | gy = gy + Kp * ey + eyInt;
86 | gz = gz + Kp * ez + ezInt;
87 |
88 | // integrate quaternion rate and normalise
89 | q0i = (-q1 * gx - q2 * gy - q3 * gz) * halfT;
90 | q1i = ( q0 * gx + q2 * gz - q3 * gy) * halfT;
91 | q2i = ( q0 * gy - q1 * gz + q3 * gx) * halfT;
92 | q3i = ( q0 * gz + q1 * gy - q2 * gx) * halfT;
93 | q0 += q0i;
94 | q1 += q1i;
95 | q2 += q2i;
96 | q3 += q3i;
97 |
98 | // normalise quaternion
99 | norm = sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
100 | q0 = q0 / norm;
101 | q1 = q1 / norm;
102 | q2 = q2 / norm;
103 | q3 = q3 / norm;
104 | }
105 |
106 | void kinematics_update(float gyroX, float gyroY, float gyroZ, float accelX, float accelY, float accelZ) {
107 | // Change signs on some of the variables
108 | float accelXfloat = accelX;
109 | float accelYfloat = -accelY;
110 | float accelZfloat = -accelZ;
111 |
112 | // Store current time
113 | float now = micros();
114 |
115 | argUpdate(gyroX, gyroY, gyroZ, accelXfloat, accelYfloat, accelZfloat, (now - kinematics_timer) / 1000000.0);
116 |
117 | // Save kinematics time for next comparison
118 | kinematics_timer = now;
119 |
120 | kinematicsAngle[XAXIS] = atan2(2 * (q0 * q1 + q2 * q3), 1 - 2 * (q1 * q1 + q2 * q2));
121 | kinematicsAngle[YAXIS] = asin(2 * (q0 * q2 - q1 * q3));
122 | kinematicsAngle[ZAXIS] = atan2(2 * (q0 * q3 + q1 * q2), 1 - 2 * (q2 * q2 + q3 * q3));
123 | }
--------------------------------------------------------------------------------
/libraries/P_Kinematics/kinematics_CMP.h:
--------------------------------------------------------------------------------
1 | /*
2 | Kinematics implementation using first order complementary filter by cTn
3 |
4 | Extra:
5 | 1. accelerometer data normalization
6 | 2. accelerometer data cut-off
7 | 3. kinematics-gyroscope angle overflow handling
8 |
9 | Kinematics input is averaged (from multiple samples) and scaled (gyro is in radians/s) and accel is in m/s^2
10 | accel measurement is normalized before any angles are computed.
11 | */
12 |
13 | unsigned long kinematics_timer;
14 |
15 | void kinematics_update(float gyroX, float gyroY, float gyroZ, float accelX, float accelY, float accelZ) {
16 |
17 | // Normalize accel values
18 | float norm = sqrt(accelX * accelX + accelY * accelY + accelZ * accelZ);
19 | accelX /= norm;
20 | accelY /= norm;
21 | accelZ /= norm;
22 |
23 | float accelXangle = atan2(accelY, accelZ);
24 | float accelYangle = atan2(accelX, accelZ);
25 | //float accelYangle = atan2(*accelX, sqrt(*accelY * *accelY + *accelZ * *accelZ));
26 |
27 | // Accelerometer cut-off
28 | float accelWeight = 0.0050; // normal operation
29 | if (norm > 13.0 || norm < 7.0) accelWeight = 0.00; // gyro only
30 |
31 | // Save current time into variable for better computation time
32 | unsigned long now = micros();
33 |
34 | // Fuse in gyroscope
35 | kinematicsAngle[XAXIS] += (gyroX * (float)(now - kinematics_timer) / 1000000);
36 | kinematicsAngle[YAXIS] += (gyroY * (float)(now - kinematics_timer) / 1000000);
37 | kinematicsAngle[ZAXIS] += (gyroZ * (float)(now - kinematics_timer) / 1000000);
38 |
39 | // Normalize gyro kinematics (+- PI)
40 | NORMALIZE(kinematicsAngle[XAXIS]);
41 | NORMALIZE(kinematicsAngle[YAXIS]);
42 | NORMALIZE(kinematicsAngle[ZAXIS]);
43 |
44 | // Fuse in accel
45 | // This is second order accelerometer cut off, which restricts accel data fusion in only
46 | // "up-side UP" orientation and restricts it further to avoid incorrect accelerometer data fusion.
47 | if (accelZ > 0.75) {
48 | if ((kinematicsAngle[XAXIS] - accelXangle) > PI) {
49 | kinematicsAngle[XAXIS] = (1.00 - accelWeight) * kinematicsAngle[XAXIS] + accelWeight * (accelXangle + TWO_PI);
50 | } else if ((kinematicsAngle[XAXIS] - accelXangle) < -PI) {
51 | kinematicsAngle[XAXIS] = (1.00 - accelWeight) * kinematicsAngle[XAXIS] + accelWeight * (accelXangle - TWO_PI);
52 | } else {
53 | kinematicsAngle[XAXIS] = (1.00 - accelWeight) * kinematicsAngle[XAXIS] + accelWeight * accelXangle;
54 | }
55 | }
56 |
57 | if (accelZ > 0.60) {
58 | if ((kinematicsAngle[YAXIS] - accelYangle) > PI) {
59 | kinematicsAngle[YAXIS] = (1.00 - accelWeight) * kinematicsAngle[YAXIS] + accelWeight * (accelYangle + TWO_PI);
60 | } else if ((kinematicsAngle[YAXIS] - accelYangle) < -PI) {
61 | kinematicsAngle[YAXIS] = (1.00 - accelWeight) * kinematicsAngle[YAXIS] + accelWeight * (accelYangle - TWO_PI);
62 | } else {
63 | kinematicsAngle[YAXIS] = (1.00 - accelWeight) * kinematicsAngle[YAXIS] + accelWeight * accelYangle;
64 | }
65 | }
66 | // Saves time for next comparison
67 | kinematics_timer = now;
68 | }
--------------------------------------------------------------------------------
/libraries/P_Kinematics/kinematics_DCM.h:
--------------------------------------------------------------------------------
1 | /*
2 | Quaternion implementation of the 'DCM filter' [Mayhony et al]. Incorporates the magnetic distortion
3 | compensation algorithms from Sebastian Madgwick's filter which eliminates the need for a reference
4 | direction of flux (bx bz) to be predefined and limits the effect of magnetic distortions to yaw axis only.
5 |
6 | This is the EXACT AHRS algorythm used in Free IMU, developed by Fabio Varesano (deceased) =(
7 | */
8 |
9 | #define twoKpDef (2.0f * 0.5f) // 2 * proportional gain
10 | #define twoKiDef (2.0f * 0.1f) // 2 * integral gain
11 |
12 | // initialize quaternion
13 | float q0 = 1.0f;
14 | float q1 = 0.0f;
15 | float q2 = 0.0f;
16 | float q3 = 0.0f;
17 | float exInt = 0.0;
18 | float eyInt = 0.0;
19 | float ezInt = 0.0;
20 | float twoKp = twoKpDef;
21 | float twoKi = twoKiDef;
22 | float integralFBx = 0.0f, integralFBy = 0.0f, integralFBz = 0.0f;
23 | float sampleFreq;
24 | unsigned long kinematics_timer = 0;
25 |
26 | void AHRSupdate(float gx, float gy, float gz, float ax, float ay, float az) {
27 | float recipNorm;
28 | float q0q0, q0q1, q0q2, q0q3, q1q1, q1q2, q1q3, q2q2, q2q3, q3q3;
29 | float halfex = 0.0f, halfey = 0.0f, halfez = 0.0f;
30 | float qa, qb, qc;
31 |
32 | // Auxiliary variables to avoid repeated arithmetic
33 | q0q0 = q0 * q0;
34 | q0q1 = q0 * q1;
35 | q0q2 = q0 * q2;
36 | q0q3 = q0 * q3;
37 | q1q1 = q1 * q1;
38 | q1q2 = q1 * q2;
39 | q1q3 = q1 * q3;
40 | q2q2 = q2 * q2;
41 | q2q3 = q2 * q3;
42 | q3q3 = q3 * q3;
43 |
44 | // Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation)
45 | if((ax != 0.0f) && (ay != 0.0f) && (az != 0.0f)) {
46 | float halfvx, halfvy, halfvz;
47 |
48 | // Normalise accelerometer measurement
49 | recipNorm = invSqrt(ax * ax + ay * ay + az * az);
50 | ax *= recipNorm;
51 | ay *= recipNorm;
52 | az *= recipNorm;
53 |
54 | // Estimated direction of gravity
55 | halfvx = q1q3 - q0q2;
56 | halfvy = q0q1 + q2q3;
57 | halfvz = q0q0 - 0.5f + q3q3;
58 |
59 | // Error is sum of cross product between estimated direction and measured direction of field vectors
60 | halfex += (ay * halfvz - az * halfvy);
61 | halfey += (az * halfvx - ax * halfvz);
62 | halfez += (ax * halfvy - ay * halfvx);
63 | }
64 |
65 | // Apply feedback only when valid data has been gathered from the accelerometer or magnetometer
66 | if(halfex != 0.0f && halfey != 0.0f && halfez != 0.0f) {
67 | // Compute and apply integral feedback if enabled
68 | if(twoKi > 0.0f) {
69 | integralFBx += twoKi * halfex * (1.0f / sampleFreq); // integral error scaled by Ki
70 | integralFBy += twoKi * halfey * (1.0f / sampleFreq);
71 | integralFBz += twoKi * halfez * (1.0f / sampleFreq);
72 | gx += integralFBx; // apply integral feedback
73 | gy += integralFBy;
74 | gz += integralFBz;
75 | }
76 | else {
77 | integralFBx = 0.0f; // prevent integral windup
78 | integralFBy = 0.0f;
79 | integralFBz = 0.0f;
80 | }
81 |
82 | // Apply proportional feedback
83 | gx += twoKp * halfex;
84 | gy += twoKp * halfey;
85 | gz += twoKp * halfez;
86 | }
87 |
88 | // Integrate rate of change of quaternion
89 | gx *= (0.5f * (1.0f / sampleFreq)); // pre-multiply common factors
90 | gy *= (0.5f * (1.0f / sampleFreq));
91 | gz *= (0.5f * (1.0f / sampleFreq));
92 | qa = q0;
93 | qb = q1;
94 | qc = q2;
95 | q0 += (-qb * gx - qc * gy - q3 * gz);
96 | q1 += (qa * gx + qc * gz - q3 * gy);
97 | q2 += (qa * gy - qb * gz + q3 * gx);
98 | q3 += (qa * gz + qb * gy - qc * gx);
99 |
100 | // Normalise quaternion
101 | recipNorm = invSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
102 | q0 *= recipNorm;
103 | q1 *= recipNorm;
104 | q2 *= recipNorm;
105 | q3 *= recipNorm;
106 | }
107 |
108 |
109 | void kinematics_update(float gyroX, float gyroY, float gyroZ, float accelX, float accelY, float accelZ) {
110 | unsigned long now = micros();
111 | sampleFreq = 1.0 / ((now - kinematics_timer) / 1000000.0);
112 | kinematics_timer = now;
113 |
114 | // Some sensors require different orientation, do the inverse here.
115 | // Also store sensor data in different variables as they are also used elsewhere
116 | float gyX = gyroX;
117 | float gyY = -gyroY;
118 | float gyZ = gyroZ;
119 | float acX = accelX;
120 | float acY = accelY;
121 | float acZ = accelZ;
122 |
123 | AHRSupdate(gyX, gyY, gyZ, acX, acY, acZ);
124 |
125 | float gx = 2 * (q1 * q3 - q0 * q2);
126 | float gy = 2 * (q0 * q1 + q2 * q3);
127 | float gz = q0 * q0 - q1 * q1 - q2 * q2 + q3 * q3;
128 |
129 | kinematicsAngle[XAXIS] = atan(gy / sqrt(gx * gx + gz * gz));
130 | kinematicsAngle[YAXIS] = atan(gx / sqrt(gy * gy + gz * gz));
131 | kinematicsAngle[ZAXIS] = atan2(2 * q1 * q2 - 2 * q0 * q3, 2 * q0 * q0 + 2 * q1 * q1 - 1);
132 |
133 | // invert
134 | kinematicsAngle[ZAXIS] = -kinematicsAngle[ZAXIS];
135 | }
--------------------------------------------------------------------------------
/libraries/P_Magnetometer/Magnetometer_HMC5883L.h:
--------------------------------------------------------------------------------
1 | /* HMC5883L magnetometer implementation
2 |
3 | Featuring accelerometer tilt compensation, for more information visit
4 | @ https://www.loveelectronics.co.uk/Tutorials/13/tilt-compensated-compass-arduino-tutorial
5 |
6 | Supporting 360 deg heading (+- 180deg), to use with 0-360 deg kinematics add + PI
7 |
8 | Output inside absolute heading is in radians.
9 | */
10 |
11 | #define HMC5883L_ADDRESS 0x1E
12 |
13 | #define HMC5883L_RA_CONFIG_A 0x00
14 | #define HMC5883L_RA_CONFIG_B 0x01
15 | #define HMC5883L_RA_MODE 0x02
16 |
17 | #define HMC5883L_MODE_CONTINUOUS 0x00
18 | #define HMC5883L_MODE_SINGLE 0x01
19 | #define HMC5883L_MODE_IDLE 0x02
20 |
21 | #define HMC5883L_GAIN_07 0x00 // +- 0.7 Ga
22 | #define HMC5883L_GAIN_10 0x20 // +- 1.0 Ga (default)
23 | #define HMC5883L_GAIN_15 0x40 // +- 1.5 Ga
24 | #define HMC5883L_GAIN_20 0x60 // +- 2.0 Ga
25 | #define HMC5883L_GAIN_32 0x80 // +- 3.2 Ga
26 | #define HMC5883L_GAIN_38 0xA0 // +- 3.8 Ga
27 | #define HMC5883L_GAIN_45 0xC0 // +- 4.5 Ga
28 | #define HMC5883L_GAIN_65 0xE0 // +- 6.5 Ga
29 |
30 | int16_t magRaw[3];
31 | float magHeadingX, magHeadingY;
32 | float magHeadingAbsolute = 0.0;
33 |
34 | class HMC5883L {
35 | public:
36 | // Constructor
37 | HMC5883L() {
38 | };
39 |
40 | void initialize() {
41 | // Check if sensor is alive
42 | Wire.beginTransmission(HMC5883L_ADDRESS);
43 | Wire.write(0x00);
44 | Wire.endTransmission();
45 |
46 | Wire.requestFrom(HMC5883L_ADDRESS, 1);
47 |
48 | uint8_t register_value = Wire.read();
49 |
50 | if (register_value == 0x10) {
51 | sensors.sensors_detected |= MAGNETOMETER_DETECTED;
52 | } else {
53 | return;
54 | }
55 |
56 | // Set gain to +- 1.0 Ga
57 | sensors.i2c_write8(HMC5883L_ADDRESS, HMC5883L_RA_CONFIG_B, HMC5883L_GAIN_10);
58 |
59 | // Start in single mode
60 | sensors.i2c_write8(HMC5883L_ADDRESS, HMC5883L_RA_MODE, HMC5883L_MODE_SINGLE);
61 |
62 | // setup axis mapping
63 | if (CONFIG.data.MAG_AXIS_MAP.initialized == 0) { // check if map was defined before, if not "save default" order set
64 | CONFIG.data.MAG_AXIS_MAP.axis1 = 0; // x
65 | CONFIG.data.MAG_AXIS_MAP.axis2 = 2; // z
66 | CONFIG.data.MAG_AXIS_MAP.axis3 = 1; // y
67 |
68 | CONFIG.data.MAG_AXIS_MAP.axis1_sign = 0;
69 | CONFIG.data.MAG_AXIS_MAP.axis2_sign = 0;
70 | CONFIG.data.MAG_AXIS_MAP.axis3_sign = 0;
71 |
72 | CONFIG.data.MAG_AXIS_MAP.initialized = 1;
73 |
74 | // save default in eeprom
75 | writeEEPROM();
76 | }
77 | };
78 |
79 | void readMagRaw() {
80 | Wire.beginTransmission(HMC5883L_ADDRESS);
81 | Wire.write(0x03);
82 | Wire.endTransmission();
83 |
84 | Wire.requestFrom(HMC5883L_ADDRESS, 6);
85 |
86 | magRaw[CONFIG.data.MAG_AXIS_MAP.axis1] = ((Wire.read() << 8) | Wire.read()) * (CONFIG.data.MAG_AXIS_MAP.axis1_sign?-1:1);
87 | magRaw[CONFIG.data.MAG_AXIS_MAP.axis2] = ((Wire.read() << 8) | Wire.read()) * (CONFIG.data.MAG_AXIS_MAP.axis2_sign?-1:1);
88 | magRaw[CONFIG.data.MAG_AXIS_MAP.axis3] = ((Wire.read() << 8) | Wire.read()) * (CONFIG.data.MAG_AXIS_MAP.axis3_sign?-1:1);
89 |
90 | // start single conversion
91 | sensors.i2c_write8(HMC5883L_ADDRESS, HMC5883L_RA_MODE, HMC5883L_MODE_SINGLE);
92 | };
93 |
94 | void evaluateMag() {
95 | float cosRoll = cos(kinematicsAngle[XAXIS]);
96 | float sinRoll = sin(kinematicsAngle[XAXIS]);
97 | float cosPitch = cos(kinematicsAngle[YAXIS]);
98 | float sinPitch = sin(kinematicsAngle[YAXIS]);
99 |
100 | // Apply accelerometer tilt corrections
101 | magX = magRaw[XAXIS] * cosPitch + magRaw[ZAXIS] * sinPitch;
102 | magY = magRaw[XAXIS] * sinRoll * sinPitch + magRaw[YAXIS] * cosRoll - magRaw[ZAXIS] * sinRoll * cosPitch;
103 |
104 | // Normalize measurements
105 | float norm = sqrt(magX * magX + magY * magY);
106 |
107 | magHeadingX = magX / norm;
108 | magHeadingY = -magY / norm;
109 |
110 | // Calculate absolute heading
111 | magHeadingAbsolute = atan2(magHeadingY, magHeadingX);
112 | };
113 |
114 | private:
115 | float magX, magY;
116 | };
117 |
118 | HMC5883L hmc5883l;
119 |
120 | void SensorArray::initializeMag() {
121 | hmc5883l.initialize();
122 | }
123 |
124 | void SensorArray::readMag() {
125 | hmc5883l.readMagRaw();
126 | }
127 |
128 | void SensorArray::evaluateMag() {
129 | hmc5883l.evaluateMag();
130 | }
--------------------------------------------------------------------------------
/libraries/P_Receiver/Receiver_328p_HW_PPM.h:
--------------------------------------------------------------------------------
1 | /* PPM (pulse position modulation) sampling done in hardware via timer1.
2 | Sampling is done via digital pin 8.
3 |
4 | Code below supports frame buffering, which is used to protect the controller from
5 | errors introduced (from simple noise to complete RX failure), if any of the channels in a single
6 | PPM frame triggered the sanity check, whole frame will be droped and PPM signal values will remain
7 | unchanged until a valid frame can be sampled.
8 |
9 | PPM_error variable will be increased every time a corrupted frame was detected.
10 |
11 | RX_signalReceived flag is set to 0 every time a valid frame is processed, this variables is used to
12 | "tell" software there was an error in communication (anything from a corrupted frame to a complete
13 | receiver failure), this flag is increased by 1 every 10ms, if it reaches 100 (represnting 1 second)
14 | an subroutine is triggered that should handle this failure condition, from the most simple routines
15 | that will just disarm the craft (making it fall from the sky like a boiled potato) to more adnvaced
16 | auto-descent routines.
17 |
18 | Please remember that this failsafe is here to prevent craft from "flying away on its own" and
19 | potentionaly harming someone in the proces (damaged craft from a crash is still better then cutting
20 | someones head off with uncontrollable craft).
21 |
22 | Big thanks to kha from #aeroquad for setting up the shared timer.
23 | */
24 | #define RX_PPM_SYNCPULSE 5000 // 2.5ms
25 |
26 | #define RX_CHANNELS 16 // dont change this
27 | volatile uint16_t RX[RX_CHANNELS] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000};
28 | volatile uint16_t PPM_temp[RX_CHANNELS] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000}; // Temporary buffer
29 |
30 | volatile uint16_t startPulse = 0;
31 | volatile uint8_t ppmCounter = RX_CHANNELS;
32 | volatile uint16_t PPM_error = 0;
33 |
34 | volatile uint8_t RX_signalReceived = 0;
35 |
36 | bool failsafeEnabled = false;
37 |
38 | #define TIMER1_FREQUENCY_HZ 50
39 | #define TIMER1_PRESCALER 8
40 | #define TIMER1_PERIOD (F_CPU/TIMER1_PRESCALER/TIMER1_FREQUENCY_HZ)
41 |
42 | // Capture Interrupt Vector
43 | ISR(TIMER1_CAPT_vect) {
44 | unsigned int stopPulse = ICR1;
45 | unsigned int pulseWidth = stopPulse - startPulse;
46 |
47 | // Error / Sanity check
48 | // if pulseWidth < 900us or pulseWidth > 2100us and pulseWidth < RX_PPM_SYNCPULSE
49 | if (pulseWidth < 1800 || (pulseWidth > 4200 && pulseWidth < RX_PPM_SYNCPULSE)) {
50 | PPM_error++;
51 |
52 | // set ppmCounter out of range so rest and (later on) whole frame is dropped
53 | ppmCounter = RX_CHANNELS + 1;
54 | }
55 |
56 | if (pulseWidth >= RX_PPM_SYNCPULSE) { // Verify if this is the sync pulse
57 | if (ppmCounter <= RX_CHANNELS) {
58 | // This indicates that we received an correct frame = push to the "main" PPM array
59 | // if we received an broken frame, it will get ignored here and later get over-written
60 | // by new data, that will also be checked for sanity.
61 | for (uint8_t i = 0; i < RX_CHANNELS; i++) {
62 | RX[i] = PPM_temp[i];
63 | }
64 |
65 | // Bring failsafe flag down every time we accept a valid signal / frame
66 | RX_signalReceived = 0;
67 | }
68 | ppmCounter = 0; // restart the channel counter
69 | } else {
70 | if (ppmCounter < RX_CHANNELS) { // extra RX_CHANNELS will get ignored here
71 | PPM_temp[ppmCounter] = pulseWidth / 2; // Store measured pulse length in us
72 | ppmCounter++; // Advance to next channel
73 | }
74 | }
75 |
76 | startPulse = stopPulse; // Save time at pulse start
77 | }
78 |
79 | // Timer1 setup (shared timer for PPM sampling & PWM servo signal generation)
80 | void setupTimer1RX() {
81 | // Setup timer1 in normal mode, count at 2MHz
82 | TCCR1A = 0;
83 | TCCR1B = (1 << CS11) | (1 << ICES1);
84 | TIMSK1 |= (1 << ICIE1) | (1 << OCIE1A); // Enable ICP and OCRA interrupts
85 | }
86 |
87 | void initializeReceiver() {
88 | setupTimer1RX();
89 | }
90 |
91 | void RX_failSafe() {
92 | RX_signalReceived++; // if this flag reaches 10, an auto-descent routine will be triggered.
93 |
94 | if (RX_signalReceived > 10) {
95 | RX_signalReceived = 10; // don't let the variable overflow
96 | failsafeEnabled = true; // this ensures that failsafe will operate in attitude mode
97 |
98 | // Bear in mind that this is here just to "slow" the fall, if you have lets say 500m altitude,
99 | // this probably won't help you much (sorry).
100 | // This will slowly (-2 every 100ms) bring the throttle to 1000 (still saved in the PPM array)
101 | // 1000 = 0 throttle;
102 | // Descending from FULL throttle 2000 (most unlikely) would take about 1 minute and 40 seconds
103 | // Descending from HALF throttle 1500 (more likely) would take about 50 seconds
104 | RX[CONFIG.data.CHANNEL_ASSIGNMENT[THROTTLE]] -= 2;
105 |
106 | if (RX[CONFIG.data.CHANNEL_ASSIGNMENT[THROTTLE]] < 1000) {
107 | RX[CONFIG.data.CHANNEL_ASSIGNMENT[THROTTLE]] = 1000; // don't let the value fall below 1000
108 | }
109 | } else {
110 | failsafeEnabled = false;
111 | }
112 | }
--------------------------------------------------------------------------------
/libraries/P_Receiver/Receiver_teensy3_HW_PPM.h:
--------------------------------------------------------------------------------
1 | /* PPM (pulse position modulation) sampling done in hardware via FLEX timer.
2 |
3 | Signal sampling is being done via PORTA_PCR12 (PIN 3).
4 |
5 | We are using flex timer1 which supports only 2 channels.
6 | This code only utilizes single edge capture which is more then enough in terms of accuracy.
7 |
8 | Code below supports frame buffering, which is used to protect the controller from
9 | errors introduced (from simple noise to complete RX failure), if any of the channels in a single
10 | PPM frame triggered the sanity check, whole frame will be droped and PPM signal values will remain
11 | unchanged until a valid frame can be sampled.
12 |
13 | PPM_error variable will be increased every time a corrupted frame was detected.
14 |
15 | RX_signalReceived flag is set to 0 every time a valid frame is processed, this variables is used to
16 | "tell" software there was an error in communication (anything from a corrupted frame to a complete
17 | receiver failure), this flag is increased by 1 every 10ms, if it reaches 100 (represnting 1 second)
18 | an subroutine is triggered that should handle this failure condition, from the most simple routines
19 | that will just disarm the craft (making it fall from the sky like a boiled potato) to more adnvaced
20 | auto-descent routines.
21 |
22 | Please remember that this failsafe is here to prevent craft from "flying away on its own" and
23 | potentionaly harming someone in the proces (damaged craft from a crash is still better then cutting
24 | someones head off with uncontrollable craft).
25 |
26 | Big thanks to kha from #aeroquad and Ragnorok from #arduino for helping me get this up and running.
27 |
28 | TODO: Sync pulse length varies among receivers, should probably provide a "define" style table for defining the
29 | sync pulse length.
30 | */
31 | #define RX_PPM_SYNCPULSE 7500 // 2.5ms
32 |
33 | #define RX_CHANNELS 16
34 | volatile uint16_t RX[RX_CHANNELS] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000};
35 | volatile uint16_t PPM_temp[RX_CHANNELS] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000}; // Temporary buffer
36 |
37 | volatile uint16_t startPulse = 0;
38 | volatile uint8_t ppmCounter = RX_CHANNELS;
39 | volatile uint16_t PPM_error = 0;
40 |
41 | volatile uint8_t RX_signalReceived = 0;
42 |
43 | bool failsafeEnabled = false;
44 |
45 | extern "C" void ftm1_isr(void) {
46 | // save current interrupt count/time
47 | uint16_t stopPulse = FTM1_C0V;
48 |
49 | // clear channel interrupt flag (CHF)
50 | FTM1_C0SC &= ~0x80;
51 |
52 | uint16_t pulseWidth = stopPulse - startPulse;
53 |
54 | // Error / Sanity check
55 | // if pulseWidth < 900us or pulseWidth > 2100us and pulseWidth < RX_PPM_SYNCPULSE
56 | if (pulseWidth < 2700 || (pulseWidth > 6300 && pulseWidth < RX_PPM_SYNCPULSE)) {
57 | PPM_error++;
58 |
59 | // set ppmCounter out of range so rest and (later on) whole frame is dropped
60 | ppmCounter = RX_CHANNELS + 1;
61 | }
62 |
63 | if (pulseWidth >= RX_PPM_SYNCPULSE) { // Verify if this is the sync pulse
64 | if (ppmCounter <= RX_CHANNELS) {
65 | // This indicates that we received an correct frame = push to the "main" PPM array
66 | // if we received an broken frame, it will get ignored here and later get over-written
67 | // by new data, that will also be checked for sanity.
68 |
69 | for (uint8_t i = 0; i < RX_CHANNELS; i++) {
70 | RX[i] = PPM_temp[i];
71 | }
72 |
73 | // Bring failsafe flag down every time we accept a valid signal / frame
74 | RX_signalReceived = 0;
75 | }
76 |
77 | // restart the channel counter
78 | ppmCounter = 0;
79 | } else {
80 | if (ppmCounter < RX_CHANNELS) { // extra channels will get ignored here
81 | PPM_temp[ppmCounter] = pulseWidth / 3; // Store measured pulse length in us
82 | ppmCounter++; // Advance to next channel
83 | }
84 | }
85 |
86 | startPulse = stopPulse; // Save time at pulse start
87 | }
88 |
89 | void setupFTM1() {
90 | // FLEX Timer1 input filter configuration
91 | // 4+4×val clock cycles, 48MHz = 4+4*7 = 32 clock cycles = 0.75us
92 | FTM1_FILTER = 0x07;
93 |
94 | // FLEX Timer1 configuration
95 | FTM1_SC = 0x0C; // TOF=0 TOIE=0 CPWMS=0 CLKS=01 (system clock) PS=100 (divide by 16)
96 | FTM1_MOD = 0xFFFF; // modulo to max
97 | FTM1_C0SC = 0x44; // CHF=0 CHIE=1 MSB=0 MSA=0 ELSB=0 ELSA=1 DMA=0
98 |
99 | // Enable FTM1 interrupt inside NVIC
100 | NVIC_ENABLE_IRQ(IRQ_FTM1);
101 |
102 | // PIN configuration, alternative function 3
103 | PORTA_PCR12 |= 0x300;
104 | }
105 |
106 | void initializeReceiver() {
107 | setupFTM1();
108 | }
109 |
110 | void RX_failSafe() {
111 | RX_signalReceived++; // if this flag reaches 10, an auto-descent routine will be triggered.
112 |
113 | if (RX_signalReceived > 10) {
114 | RX_signalReceived = 10; // don't let the variable overflow
115 | failsafeEnabled = true; // this ensures that failsafe will operate in attitude mode
116 |
117 | // Bear in mind that this is here just to "slow" the fall, if you have lets say 500m altitude,
118 | // this probably won't help you much (sorry).
119 | // This will slowly (-2 every 100ms) bring the throttle to 1000 (still saved in the PPM array)
120 | // 1000 = 0 throttle;
121 | // Descending from FULL throttle 2000 (most unlikely) would take about 1 minute and 40 seconds
122 | // Descending from HALF throttle 1500 (more likely) would take about 50 seconds
123 | RX[CONFIG.data.CHANNEL_ASSIGNMENT[THROTTLE]] -= 2;
124 |
125 | if (RX[CONFIG.data.CHANNEL_ASSIGNMENT[THROTTLE]] < 1000) {
126 | RX[CONFIG.data.CHANNEL_ASSIGNMENT[THROTTLE]] = 1000; // don't let the value fall below 1000
127 | }
128 | } else {
129 | failsafeEnabled = false;
130 | }
131 | }
--------------------------------------------------------------------------------
/libraries/P_Receiver/Receiver_teensy3_HW_PWM.h:
--------------------------------------------------------------------------------
1 | /* PWM (Pulse Width Modulation) sampling done in hardware via pin interrupts and PIT timer.
2 |
3 | I am using one, out of 4 PIT timers build into the Teensy 3.0.
4 |
5 | Pins used for PWM signal capture are fully definable by the user.
6 | However, i do recommend avoiding FLEX timer enabled pins, (Teensy 3.0 pin numbering) 5, 6, 9, 10, 20, 21, 22, 23,
7 | as those are used for PWM signal generation (for Electronic Speed Controllers).
8 |
9 | Using the hardware timer for timing / counting the pulses gives us superior accuracy of ~21 nano seconds.
10 |
11 | This receiver code also utilizes a more advanced failsafe sequence.
12 | In case of receiver malfunction / signal cable damage, RX Failasefe will kicks in
13 | which will start auto-descent sequence.
14 |
15 | */
16 |
17 | #define RX_CHANNELS 16
18 | uint8_t PWM_PINS[RX_CHANNELS] = {2, 3, 4, 5, 6, 7, 8, 9};
19 |
20 | volatile uint16_t RX[RX_CHANNELS] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000};
21 | volatile uint32_t PWM_time[RX_CHANNELS] = {0, 0, 0, 0, 0, 0, 0, 0};
22 |
23 | volatile uint16_t RX_failsafeStatus;
24 | volatile uint8_t RX_signalReceived = 0;
25 |
26 | bool failsafeEnabled = false;
27 |
28 | void readPWM(uint8_t channel) {
29 | uint32_t now = PIT_CVAL0; // Current counter value
30 | uint32_t delta = PWM_time[channel] - now; // Delta (calculated in reverse, because PIT is downcounting timer)
31 |
32 | // All of the number below are scaled to use with Teensy 3.0 running at 48Mhz
33 | if (delta < 120000 && delta > 43200) { // This is a valid pulse
34 | RX[channel] = delta / 48;
35 |
36 | // Bring failsafe status flag for current channel down every time we accept a valid signal
37 | RX_failsafeStatus &= ~(1 << channel);
38 | } else { // Beginning of the pulse
39 | PWM_time[channel] = now;
40 | }
41 | }
42 |
43 | // ISRs
44 | void PWM_ISR_0() {
45 | readPWM(0);
46 | }
47 | void PWM_ISR_1() {
48 | readPWM(1);
49 | }
50 | void PWM_ISR_2() {
51 | readPWM(2);
52 | }
53 | void PWM_ISR_3() {
54 | readPWM(3);
55 | }
56 | void PWM_ISR_4() {
57 | readPWM(4);
58 | }
59 | void PWM_ISR_5() {
60 | readPWM(5);
61 | }
62 | void PWM_ISR_6() {
63 | readPWM(6);
64 | }
65 | void PWM_ISR_7() {
66 | readPWM(7);
67 | }
68 |
69 | void (*PWM_Handlers [])(void) = {
70 | PWM_ISR_0,
71 | PWM_ISR_1,
72 | PWM_ISR_2,
73 | PWM_ISR_3,
74 | PWM_ISR_4,
75 | PWM_ISR_5,
76 | PWM_ISR_6,
77 | PWM_ISR_7
78 | };
79 |
80 | void initializeReceiver() {
81 | // initialize PIT timer (teensy running at 48000000)
82 | PIT_MCR = 0x00; // Turn on PIT
83 | PIT_LDVAL0 = 0xFFFFFFFF; // Load initial value of 4294967295
84 | PIT_TCTRL0 = 0x01; // Start the counter
85 |
86 | for (uint8_t i = 0; i < RX_CHANNELS; i++) {
87 | pinMode(PWM_PINS[i], INPUT);
88 | attachInterrupt(PWM_PINS[i], PWM_Handlers[i], CHANGE);
89 | }
90 | }
91 |
92 | void RX_failSafe() {
93 | if (RX_failsafeStatus > 0) {
94 | RX_signalReceived++; // if this flag reaches 10, an auto-descent routine will be triggered.
95 | } else {
96 | // Raise the FLAGS
97 | RX_failsafeStatus |= (1 << RX_CHANNELS) - 1;
98 |
99 | // Reset the counter
100 | RX_signalReceived = 0;
101 | }
102 |
103 | if (RX_signalReceived > 10) {
104 | RX_signalReceived = 10; // don't let the variable overflow
105 | failsafeEnabled = true; // this ensures that failsafe will operate in attitude mode
106 |
107 | // Bear in mind that this is here just to "slow" the fall, if you have lets say 500m altitude,
108 | // this probably won't help you much (sorry).
109 | // This will slowly (-2 every 100ms) bring the throttle to 1000 (still saved in the PPM array)
110 | // 1000 = 0 throttle;
111 | // Descending from FULL throttle 2000 (most unlikely) would take about 1 minute and 40 seconds
112 | // Descending from HALF throttle 1500 (more likely) would take about 50 seconds
113 | RX[CONFIG.data.CHANNEL_ASSIGNMENT[THROTTLE]] -= 2;
114 |
115 | if (RX[CONFIG.data.CHANNEL_ASSIGNMENT[THROTTLE]] < 1000) {
116 | RX[CONFIG.data.CHANNEL_ASSIGNMENT[THROTTLE]] = 1000; // don't let the value fall below 1000
117 | }
118 | } else {
119 | failsafeEnabled = false;
120 | }
121 | }
--------------------------------------------------------------------------------
/libraries/P_Sonar/Sonar_srf04.h:
--------------------------------------------------------------------------------
1 | #define SONAR_RANGE 300 // 300cm
2 | #define SONAR_PIN_TRIGGER 15
3 | #define SONAR_PIN_ECHO 16
4 |
5 | unsigned long sonar_start = 0;
6 | unsigned long sonar_end = 0;
7 | bool sonar_ready = true;
8 |
9 | float sonar_raw = 0.0;
10 | float sonarAltitude = 0.0;
11 | float sonarAltitudeToHoldTarget = 0.0;
12 | int16_t sonarAltitudeHoldThrottle = 1000;
13 |
14 | void readSonar() {
15 | // SRF04 Requires at least 10us long TTL pulse to trigger
16 | // This pin will go low in next 1000 <= us
17 | digitalWrite(SONAR_PIN_TRIGGER, HIGH);
18 | }
19 |
20 | void readSonarFinish() {
21 | digitalWrite(SONAR_PIN_TRIGGER, LOW);
22 | }
23 |
24 | void sonarEcho() {
25 | if (sonar_ready) {
26 | sonar_start = micros();
27 | sonar_ready = false;
28 | } else {
29 | // The speed of sound is 340 m/s or 29 microseconds per centimeter.
30 | // The ping travels out and back, so to find the distance of the
31 | // object we take half of the distance travelled.
32 | sonar_end = micros();
33 |
34 | sonar_raw = sonar_end - sonar_start;
35 | sonar_raw = sonar_raw / 29.0 / 2.0;
36 |
37 | if (sonar_raw > SONAR_RANGE) {
38 | sonar_raw = 0;
39 | } else {
40 | // Compute
41 | // Convert centimeters to meters (to match baro units)
42 | sonar_raw /= 100;
43 |
44 | sonarAltitude = filterSmooth(sonar_raw, sonarAltitude, 0.75);
45 | }
46 |
47 | sonar_ready = true;
48 | }
49 | }
50 |
51 | void initializeSonar() {
52 | pinMode(SONAR_PIN_TRIGGER, OUTPUT);
53 | pinMode(SONAR_PIN_ECHO, INPUT);
54 | attachInterrupt(SONAR_PIN_ECHO, sonarEcho, CHANGE);
55 | }
--------------------------------------------------------------------------------