Skip to content

Commit 66d09a1

Browse files
authored
Ilyhadev/gimbal (#128)
1 parent 5816828 commit 66d09a1

File tree

24 files changed

+747
-60
lines changed

24 files changed

+747
-60
lines changed

Libs/Dronecan

Src/common/algorithms.cpp

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
#include <math.h>
99
#include <algorithm>
1010

11-
1211
PwmDurationUs mapInt16CommandToPwm(int16_t command,
1312
PwmDurationUs min_pwm,
1413
PwmDurationUs max_pwm,
@@ -94,3 +93,144 @@ float AdaptiveAlphaFilter::linearly_interpolate_alpha(float delta) const {
9493

9594
return alpha;
9695
}
96+
97+
void rad_to_deg_array(float angles_rpy[3]) {
98+
constexpr float RAD_TO_DEG = 57.2957795131f; // 180.0f / PI
99+
angles_rpy[0] *= RAD_TO_DEG;
100+
angles_rpy[1] *= RAD_TO_DEG;
101+
angles_rpy[2] *= RAD_TO_DEG;
102+
}
103+
104+
float fast_atan2(float y, float x) {
105+
if (std::abs(x) < FLT_EPSILON_LOCAL && std::abs(y) < FLT_EPSILON_LOCAL) {
106+
return 0.0f;
107+
}
108+
109+
float angle = 0.0f;
110+
if (x < 0.0f) {
111+
angle = (y >= 0.0f) ? PI : -PI;
112+
// Effectively rotating by 180 degrees
113+
x = -x;
114+
y = -y;
115+
}
116+
117+
// Iterative CORDIC rotation
118+
for (int i = 0; i < 12; i++) {
119+
float x_new;
120+
float sigma = (y >= 0.0f) ? 1.0f : -1.0f;
121+
float factor = std::ldexp(1.0f, -i); // 2^-i
122+
123+
x_new = x + (sigma * y * factor);
124+
y = y - (sigma * x * factor);
125+
x = x_new;
126+
127+
angle += sigma * CORDIC_TABLE[i];
128+
}
129+
130+
return angle;
131+
}
132+
133+
// ZYX "decoding" (yaw-pitch-roll) convention
134+
void quaternion_to_euler(const float q[4],
135+
float angles_rpy[3]) {
136+
const float qx = q[0];
137+
const float qy = q[1];
138+
const float qz = q[2];
139+
const float qw = q[3];
140+
141+
// Calculate the Sine of Pitch
142+
float sinp = 2.0f * (qw * qy - qx * qz);
143+
144+
// Prevents NaN in sqrt/atan2 if the quaternion is slightly denormalized
145+
if (sinp > 1.0f) sinp = 1.0f;
146+
else if (sinp < -1.0f) sinp = -1.0f;
147+
148+
// Gimbal Lock Check
149+
if (std::abs(sinp) > 0.9995f) {
150+
angles_rpy[0] = 0.0f;
151+
angles_rpy[1] = std::copysign(PI_2, sinp);
152+
angles_rpy[2] = fast_atan2(2.0f * (qx * qy + qw * qz),
153+
1.0f - 2.0f * (qy * qy + qz * qz));
154+
} else {
155+
// Roll (x-axis)
156+
angles_rpy[0] = fast_atan2(2.0f * (qw * qx + qy * qz),
157+
1.0f - 2.0f * (qx * qx + qy * qy));
158+
159+
// Pitch (y-axis)
160+
float cosp = std::sqrt(1.0f - sinp * sinp);
161+
angles_rpy[1] = fast_atan2(sinp, cosp);
162+
163+
// Yaw (z-axis)
164+
angles_rpy[2] = fast_atan2(2.0f * (qw * qz + qx * qy),
165+
1.0f - 2.0f * (qy * qy + qz * qz));
166+
}
167+
}
168+
169+
void normalize_quaternion(float q[4]) {
170+
float norm_sq = q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3];
171+
172+
if (norm_sq > 0.0f && std::fabs(norm_sq - 1.0f) > 1e-3f) {
173+
float inv_norm = 1.0f / std::sqrt(norm_sq);
174+
q[0] *= inv_norm;
175+
q[1] *= inv_norm;
176+
q[2] *= inv_norm;
177+
q[3] *= inv_norm;
178+
}
179+
}
180+
181+
void fast_sin_cos(float theta_rad, float* s, float* c) {
182+
// Range Reduction. Works on [-PI, PI]
183+
bool flip = false;
184+
if (theta_rad > PI_2) {
185+
theta_rad = PI - theta_rad;
186+
flip = true;
187+
} else if (theta_rad < -PI_2) {
188+
theta_rad = -PI - theta_rad;
189+
flip = true;
190+
}
191+
192+
float x = CORDIC_GAIN;
193+
float y = 0.0f;
194+
float current_theta = 0.0f;
195+
196+
// Iterative CORDIC rotation
197+
for (int i = 0; i < 12; i++) {
198+
float x_new;
199+
float sigma = (theta_rad >= current_theta) ? 1.0f : -1.0f;
200+
float factor = std::ldexp(1.0f, -i); // 2^-i
201+
202+
x_new = x - (sigma * y * factor);
203+
y = y + (sigma * x * factor);
204+
x = x_new;
205+
206+
current_theta += sigma * CORDIC_TABLE[i];
207+
}
208+
209+
// Quadrant Adjustment
210+
if (flip) {
211+
*c = -x;
212+
*s = y;
213+
} else {
214+
*c = x;
215+
*s = y;
216+
}
217+
}
218+
219+
void euler_to_quaternion(float const angles_rpy[3], float q[4]) {
220+
// Conversion factor deg to rad and halving for quaternion math
221+
constexpr float DEG_TO_RAD_HALF = (PI / 180.0f) * 0.5f;
222+
223+
float sr, cr, sp, cp, sy, cy;
224+
225+
// Compute sin/cos for half-angles
226+
fast_sin_cos(angles_rpy[0] * DEG_TO_RAD_HALF, &sr, &cr);
227+
fast_sin_cos(angles_rpy[1] * DEG_TO_RAD_HALF, &sp, &cp);
228+
fast_sin_cos(angles_rpy[2] * DEG_TO_RAD_HALF, &sy, &cy);
229+
230+
// ZYX Convention
231+
// q = [x, y, z, w]
232+
q[0] = sr * cp * cy - cr * sp * sy; // x
233+
q[1] = cr * sp * cy + sr * cp * sy; // y
234+
q[2] = cr * cp * sy - sr * sp * cy; // z
235+
q[3] = cr * cp * cy + sr * sp * sy; // w
236+
}

Src/common/algorithms.hpp

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,21 @@
99

1010
#include <stdint.h>
1111

12-
typedef uint16_t PwmDurationUs;
12+
using PwmDurationUs = uint16_t;
13+
static constexpr float PI_2 = 1.5707963267f;
14+
static constexpr float LOOSE_ERR = 1e-3f;
15+
static constexpr float PI = 3.1415926535f;
16+
static constexpr float CORDIC_GAIN = 0.607252935f;
17+
// Using a small epsilon for float comparisons
18+
static constexpr float FLT_EPSILON_LOCAL = 1e-7f;
19+
20+
// Just precomputed arctan(2^-i) values for i = 0 to 11
21+
static const float CORDIC_TABLE[] = {
22+
0.785398163f, 0.463647609f, 0.244978663f, 0.124354995f,
23+
0.062418810f, 0.031239833f, 0.015623729f, 0.007812341f,
24+
0.003906230f, 0.001953123f, 0.000976562f, 0.000488281f
25+
};
26+
1327

1428
/**
1529
* @brief Maps a 16-bit integer command to a PWM duration.
@@ -58,6 +72,39 @@ float mapPwmToPct(uint16_t pwm_val, int16_t pwm_min, int16_t pwm_max);
5872

5973
void movingAverage(float* prev_avg, float crnt_val, uint16_t size);
6074

75+
/**
76+
* @brief Normalizes a quaternion to unit length
77+
* @param q Quaternion to normalize in-place [x, y, z, w]
78+
*/
79+
void normalize_quaternion(float q[4]);
80+
81+
/**
82+
* @brief Converts quaternion to Euler angles (roll, pitch, yaw)
83+
* @param q Quaternion in [x, y, z, w] order (body frame)
84+
* @param angles_rpy Output array for [roll, pitch, yaw] in radians
85+
*/
86+
void quaternion_to_euler(const float q[4],
87+
float angles_rpy[3]);
88+
89+
/**
90+
* @brief Converts radians to degrees for an array of three angles
91+
*/
92+
93+
/**
94+
* @brief Converts Euler angles (degrees) to a quaternion.
95+
* @param roll_deg, pitch_deg, yaw_deg Input angles in degrees.
96+
* @param q Output array for quaternion in [x, y, z, w] order.
97+
*/
98+
void euler_to_quaternion(float const angles_rpy[3], float q[4]);
99+
100+
/**
101+
* @brief Optimized CORDIC sin/cos calculation.
102+
* @param theta_rad Input angle in radians.
103+
* @param s, c Pointers to output sine and cosine.
104+
*/
105+
void fast_sin_cos(float theta_rad, float* s, float* c);
106+
107+
void rad_to_deg_array(float angles_rpy[3]);
61108
/**
62109
* @brief The Adaptive Alpha Filter is a variation of the exponential smoothing filter,
63110
* where the smoothing factor α is adjusted dynamically based on the magnitude of changes

Src/drivers/mpu9250/mpu9250.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ int8_t Mpu9250::read_gyroscope(SensorGyro* gyro) const {
4343
return 0;
4444
}
4545

46-
int8_t Mpu9250::read_magnetometer(SensorGyro* mag) const {
46+
int8_t Mpu9250::read_magnetometer(SensorMag* mag) const {
4747
(*mag)[0] = 0.0;
4848
(*mag)[1] = 0.0;
4949
(*mag)[2] = 0.0;

Src/drivers/mpu9250/mpu9250.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,17 @@ class Mpu9250 {
3838
/**
3939
* @return 0 on success, negative otherwise
4040
*/
41-
int8_t read_accelerometer(std::array<int16_t, 3>* accel) const;
41+
int8_t read_accelerometer(SensorAccel* accel) const;
4242

4343
/**
4444
* @return 0 on success, negative otherwise
4545
*/
46-
int8_t read_gyroscope(std::array<int16_t, 3>* gyro) const;
46+
int8_t read_gyroscope(SensorGyro* gyro) const;
4747

4848
/**
4949
* @return 0 on success, negative otherwise
5050
*/
51-
int8_t read_magnetometer(std::array<int16_t, 3>* mag) const;
51+
int8_t read_magnetometer(SensorMag* mag) const;
5252

5353
/**
5454
* @return 0 on success, negative otherwise

Src/drivers/rcpwm/rcpwm.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
#include "rcpwm.hpp"
99
#include <array>
1010
#include <algorithm>
11+
#include <cstdio>
1112
#include "params.hpp"
1213
#include "common/algorithms.hpp"
14+
#include "common/logging.hpp"
1315

1416
int8_t Driver::RCPWM::init() {
1517
for (const auto& param : channels) {
@@ -45,6 +47,18 @@ uint8_t Driver::RCPWM::get_pin_percent(uint8_t pin_idx) {
4547
(channels[pin_idx].pin, channels[pin_idx].min, channels[pin_idx].max);
4648
}
4749

50+
float Driver::RCPWM::get_current_angle(uint16_t max_servo_angle, uint8_t pin_idx) {
51+
if (!is_pin_enabled(pin_idx)) return 0.0f;
52+
53+
auto current_us = HAL::Pwm::get_duration(channels[pin_idx].pin);
54+
float normalized = mapFloat(current_us,
55+
channels[pin_idx].min,
56+
channels[pin_idx].max,
57+
-1.0f, +1.0f);
58+
59+
return normalized * (static_cast<float>(max_servo_angle) / 2.0f);
60+
}
61+
4862
int8_t Driver::RCPWM::get_pin_channel(uint8_t pin_idx) {
4963
return pin_idx < get_pins_amount() ? channels[pin_idx].channel : -1;
5064
}

Src/drivers/rcpwm/rcpwm.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ class RCPWM {
112112

113113
static int8_t get_pin_channel(uint8_t pin_idx);
114114
static bool is_pin_enabled(uint8_t pin_idx);
115-
115+
static float get_current_angle(uint16_t max_angle_deg, uint8_t pin_idx);
116116
// private:
117117
static std::array<RcpwmChannel, static_cast<uint8_t>(HAL::PwmPin::PWM_AMOUNT)> channels;
118118
};

Src/modules/feedback/dronecan/feedback.cpp

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
* See <https://www.gnu.org/licenses/> for details.
44
* Author: Dmitry Ponomarev <ponomarevda96@gmail.com>
55
* Author: Anastasiia Stepanova <asiiapine@gmail.com>
6+
* Author: Ilia Kliantsevich <iliawork112005@gmail.com>
67
*/
78

89
#include "feedback.hpp"
910
#include "peripheral/pwm/pwm.hpp"
1011
#include "peripheral/adc/circuit_periphery.hpp"
1112
#include "drivers/rcpwm/rcpwm.hpp"
13+
#include "modules/rcout/dronecan_frontend/dronecan_frontend.hpp"
1214
#include "params.hpp"
15+
#include "logging.hpp"
1316

1417
REGISTER_MODULE(DronecanFeedbackModule)
1518

@@ -22,6 +25,7 @@ void DronecanFeedbackModule::update_params() {
2225
feedback_esc_enabled = static_cast<bool>(paramsGetIntegerValue(IntParamsIndexes::PARAM_FEEDBACK_ESC_ENABLE));
2326
feedback_actuator_enabled = static_cast<bool>(paramsGetIntegerValue(IntParamsIndexes::PARAM_FEEDBACK_ACTUATOR_ENABLE));
2427
feedback_hardpoint_enabled = static_cast<bool>(paramsGetIntegerValue(IntParamsIndexes::PARAM_FEEDBACK_HARDPOINT_ENABLE));
28+
feedback_gimbal_enabled = static_cast<Gimbal_enable>(paramsGetIntegerValue(IntParamsIndexes::PARAM_FEEDBACK_GIMBAL_ENABLE));
2529
}
2630

2731
void DronecanFeedbackModule::spin_once() {
@@ -42,11 +46,18 @@ void DronecanFeedbackModule::spin_once() {
4246
publish_hardpoint_status(pin_idx);
4347
}
4448
}
49+
50+
if (feedback_gimbal_enabled == Gimbal_enable::ENABLE_DEG){
51+
publish_gimbal_status_rpy();
52+
}
53+
if (feedback_gimbal_enabled == Gimbal_enable::ENABLE_QUAT){
54+
publish_gimbal_status_quaternion();
55+
}
4556
}
4657

4758
void DronecanFeedbackModule::publish_esc_status(uint8_t pin_idx) {
4859
esc_status.msg = {
49-
.error_count = esc_status.msg.error_count + 1,
60+
.error_count = esc_status.msg.error_count + 1, // FIXME: implement proper error counting
5061
.voltage = CircuitPeriphery::voltage_vin(),
5162
.current = CircuitPeriphery::current(),
5263
.temperature = static_cast<float>(CircuitPeriphery::temperature()),
@@ -68,7 +79,6 @@ void DronecanFeedbackModule::publish_actuator_status(uint8_t pin_idx) {
6879
.force = CircuitPeriphery::current(),
6980
.speed = static_cast<float>(CircuitPeriphery::temperature()),
7081

71-
.reserved = 0,
7282
.power_rating_pct = Driver::RCPWM::get_pin_percent(pin_idx),
7383
};
7484

@@ -89,3 +99,58 @@ void DronecanFeedbackModule::publish_hardpoint_status(uint8_t pin_idx) {
8999
// Hardpoint serialization has a bug, let's skip it until it is fixed
90100
// hardpoint_status.publish();
91101
}
102+
103+
void DronecanFeedbackModule::publish_gimbal_status_rpy() {
104+
105+
gimbal_status_pub.msg.gimbal_id = 0; //
106+
gimbal_status_pub.msg.mode.command_mode = 1;
107+
108+
gimbal_status_pub.msg.camera_orientation_in_body_frame_xyzw[0] = 0; // X
109+
gimbal_status_pub.msg.camera_orientation_in_body_frame_xyzw[1] = 0; // Y
110+
gimbal_status_pub.msg.camera_orientation_in_body_frame_xyzw[2] = 0; // Z
111+
gimbal_status_pub.msg.camera_orientation_in_body_frame_xyzw[3] = 1; // W
112+
113+
float roll_deg = 0, pitch_deg = 0, yaw_deg = 0;
114+
for (size_t i = 0; i < Driver::RCPWM::get_pins_amount(); ++i) {
115+
switch (Driver::RCPWM::get_pin_channel(i)) {
116+
case 0: // Roll
117+
roll_deg = Driver::RCPWM::get_current_angle(gimbal::get_max_servos_angle(), i);
118+
break;
119+
case 1: // Pitch
120+
pitch_deg = Driver::RCPWM::get_current_angle(gimbal::get_max_servos_angle(), i);
121+
break;
122+
case 2: // Yaw
123+
yaw_deg = Driver::RCPWM::get_current_angle(gimbal::get_max_servos_angle(), i);
124+
break;
125+
default:
126+
break;
127+
}
128+
}
129+
130+
gimbal_status_pub.msg.camera_orientation_in_body_frame_xyzw[0] = roll_deg; // X
131+
gimbal_status_pub.msg.camera_orientation_in_body_frame_xyzw[1] = pitch_deg; // Y
132+
gimbal_status_pub.msg.camera_orientation_in_body_frame_xyzw[2] = yaw_deg; // Z
133+
134+
gimbal_status_pub.publish();
135+
}
136+
137+
void DronecanFeedbackModule::publish_gimbal_status_quaternion() {
138+
139+
gimbal_status_pub.msg.gimbal_id = 0;
140+
gimbal_status_pub.msg.mode.command_mode = 1;
141+
142+
// Set default values for safety
143+
gimbal_status_pub.msg.camera_orientation_in_body_frame_xyzw[0] = 0; // X
144+
gimbal_status_pub.msg.camera_orientation_in_body_frame_xyzw[1] = 0; // Y
145+
gimbal_status_pub.msg.camera_orientation_in_body_frame_xyzw[2] = 0; // Z
146+
gimbal_status_pub.msg.camera_orientation_in_body_frame_xyzw[3] = 1; // W
147+
148+
const float* q = gimbal::get_quaternion();
149+
150+
gimbal_status_pub.msg.camera_orientation_in_body_frame_xyzw[0] = q[0]; // X
151+
gimbal_status_pub.msg.camera_orientation_in_body_frame_xyzw[1] = q[1]; // Y
152+
gimbal_status_pub.msg.camera_orientation_in_body_frame_xyzw[2] = q[2]; // Z
153+
gimbal_status_pub.msg.camera_orientation_in_body_frame_xyzw[3] = q[3]; // W
154+
155+
gimbal_status_pub.publish();
156+
}

0 commit comments

Comments
 (0)