mod/math_sdr/math_sdr.c
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /** | ||
| 2 | ****************************************************************************** | ||
| 3 | * @file : math_sdr.c | ||
| 4 | * @brief : Contains math and utility functions for SDR code. | ||
| 5 | ****************************************************************************** | ||
| 6 | * @copyright | ||
| 7 | * | ||
| 8 | * Copyright (c) 2025 Sun Devil Rocketry. | ||
| 9 | * All rights reserved. | ||
| 10 | * | ||
| 11 | * This software is licensed under terms that can be found in the LICENSE file | ||
| 12 | * in the root directory of this software component. | ||
| 13 | * If no LICENSE file comes with this software, it is covered under the | ||
| 14 | * BSD-3-Clause. | ||
| 15 | * | ||
| 16 | * https://opensource.org/license/bsd-3-clause | ||
| 17 | * | ||
| 18 | ****************************************************************************** | ||
| 19 | */ | ||
| 20 | |||
| 21 | /* Standard Includes ---------------------------------------------------------*/ | ||
| 22 | #include <math.h> | ||
| 23 | |||
| 24 | /* Project Includes ----------------------------------------------------------*/ | ||
| 25 | #include "main.h" | ||
| 26 | #include "math_sdr.h" | ||
| 27 | |||
| 28 | /* API Functions -------------------------------------------------------------*/ | ||
| 29 | |||
| 30 | /** | ||
| 31 | * @brief Computes a CRC-32 checksum over a block of data. | ||
| 32 | * | ||
| 33 | * @param[in] data Pointer to the input data buffer. | ||
| 34 | * @param[in] len Number of bytes in the buffer. | ||
| 35 | * | ||
| 36 | * @return The 32-bit CRC checksum of the input data. | ||
| 37 | */ | ||
| 38 | 1 | uint32_t crc32 | |
| 39 | ( | ||
| 40 | const uint8_t *data, | ||
| 41 | size_t len | ||
| 42 | ) | ||
| 43 | { | ||
| 44 | 1 | uint32_t crc = 0xFFFFFFFF; | |
| 45 |
2/2✓ Branch 0 taken 48 times.
✓ Branch 1 taken 1 time.
|
49 | while (len--) |
| 46 | { | ||
| 47 | 48 | crc ^= *data++; | |
| 48 |
2/2✓ Branch 0 taken 384 times.
✓ Branch 1 taken 48 times.
|
432 | for (int i = 0; i < 8; ++i) |
| 49 | 384 | crc = (crc >> 1) ^ (0x82F63B78 & -(crc & 1)); | |
| 50 | } | ||
| 51 | 1 | return ~crc; | |
| 52 | |||
| 53 | } /* crc32 */ | ||
| 54 | |||
| 55 | /** | ||
| 56 | * @brief Clamps a floating-point value between minimum and maximum bounds. | ||
| 57 | * | ||
| 58 | * @param value Value to clamp. | ||
| 59 | * @param minimum Minimum value. | ||
| 60 | * @param maximum Maximum value. | ||
| 61 | */ | ||
| 62 | ✗ | float clamp_float | |
| 63 | ( | ||
| 64 | float value, | ||
| 65 | float minimum, | ||
| 66 | float maximum | ||
| 67 | ) | ||
| 68 | { | ||
| 69 | ✗ | if ( value < minimum ) | |
| 70 | { | ||
| 71 | return minimum; | ||
| 72 | } | ||
| 73 | |||
| 74 | ✗ | if ( value > maximum ) | |
| 75 | { | ||
| 76 | ✗ | return maximum; | |
| 77 | } | ||
| 78 | |||
| 79 | return value; | ||
| 80 | |||
| 81 | } /* clamp_float */ | ||
| 82 | |||
| 83 | /* Quaternions ---------------------------------------------------------------*/ | ||
| 84 | |||
| 85 | /** | ||
| 86 | * @brief Converts ZYX Euler angles to a quaternion. | ||
| 87 | * | ||
| 88 | * @note Angles must be provided in radians. | ||
| 89 | * @note The rotation order is ZYX (standard aerospace convention). | ||
| 90 | * @todo The rotation sequence has not been validated yet. | ||
| 91 | * | ||
| 92 | * @param yaw Rotation about the Z axis in radians. | ||
| 93 | * @param pitch Rotation about the Y axis in radians. | ||
| 94 | * @param roll Rotation about the X axis in radians. | ||
| 95 | * | ||
| 96 | * @return The quaternion representing the rotation. | ||
| 97 | */ | ||
| 98 | 4 | QUAT eul_to_quat | |
| 99 | ( | ||
| 100 | float yaw, | ||
| 101 | float pitch, | ||
| 102 | float roll | ||
| 103 | ) | ||
| 104 | { | ||
| 105 | 4 | float cos_yaw = cosf(yaw / 2.0f); | |
| 106 | 4 | float cos_pitch = cosf(pitch / 2.0f); | |
| 107 | 4 | float cos_roll = cosf(roll / 2.0f); | |
| 108 | |||
| 109 | 4 | float sin_yaw = sinf(yaw / 2.0f); | |
| 110 | 4 | float sin_pitch = sinf(pitch / 2.0f); | |
| 111 | 4 | float sin_roll = sinf(roll / 2.0f); | |
| 112 | |||
| 113 | 4 | QUAT q; | |
| 114 | 4 | q.w = cos_roll * cos_pitch * cos_yaw + sin_roll * sin_pitch * sin_yaw; | |
| 115 | 4 | q.x = sin_roll * cos_pitch * cos_yaw - cos_roll * sin_pitch * sin_yaw; | |
| 116 | 4 | q.y = cos_roll * sin_pitch * cos_yaw + sin_roll * cos_pitch * sin_yaw; | |
| 117 | 4 | q.z = cos_roll * cos_pitch * sin_yaw - sin_roll * sin_pitch * cos_yaw; | |
| 118 | |||
| 119 | 4 | return q; | |
| 120 | |||
| 121 | } /* eul_to_quat */ | ||
| 122 | |||
| 123 | |||
| 124 | /** | ||
| 125 | * @brief Hamilton product of two quaternions. | ||
| 126 | * | ||
| 127 | * @note Quaternion multiplication is NOT commutative. | ||
| 128 | * | ||
| 129 | * @param a The left-hand quaternion. | ||
| 130 | * @param b The right-hand quaternion. | ||
| 131 | * | ||
| 132 | * @return The quaternion product a * b. | ||
| 133 | */ | ||
| 134 | 14 | QUAT quat_mult | |
| 135 | ( | ||
| 136 | QUAT a, | ||
| 137 | QUAT b | ||
| 138 | ) | ||
| 139 | { | ||
| 140 | 14 | QUAT result; | |
| 141 | |||
| 142 | 14 | result.w = (a.w * b.w) - (a.x * b.x) - (a.y * b.y) - (a.z * b.z); | |
| 143 | 14 | result.x = (a.w * b.x) + (a.x * b.w) + (a.y * b.z) - (a.z * b.y); | |
| 144 | 14 | result.y = (a.w * b.y) - (a.x * b.z) + (a.y * b.w) + (a.z * b.x); | |
| 145 | 14 | result.z = (a.w * b.z) + (a.x * b.y) - (a.y * b.x) + (a.z * b.w); | |
| 146 | |||
| 147 | 14 | return result; | |
| 148 | |||
| 149 | } /* quat_mult */ | ||
| 150 | |||
| 151 | |||
| 152 | /** | ||
| 153 | * @brief Provides the dot product of two quaternions. | ||
| 154 | * | ||
| 155 | * @note Quaternion dot products are commutative. | ||
| 156 | * | ||
| 157 | * @param a The left-hand quaternion. | ||
| 158 | * @param b The right-hand quaternion. | ||
| 159 | * | ||
| 160 | * @return The quaternion scalar product a . b | ||
| 161 | */ | ||
| 162 | ✗ | float quat_dot | |
| 163 | ( | ||
| 164 | QUAT a, | ||
| 165 | QUAT b | ||
| 166 | ) | ||
| 167 | { | ||
| 168 | ✗ | return( a.w * b.w + | |
| 169 | ✗ | a.x * b.x + | |
| 170 | ✗ | a.y * b.y + | |
| 171 | ✗ | a.z * b.z ); | |
| 172 | |||
| 173 | } /* quat_mult */ | ||
| 174 | |||
| 175 | |||
| 176 | /** | ||
| 177 | * @brief Adds two quaternions component-wise. | ||
| 178 | * | ||
| 179 | * @param a The first quaternion. | ||
| 180 | * @param b The second quaternion. | ||
| 181 | * | ||
| 182 | * @return The quaternion sum a + b. | ||
| 183 | */ | ||
| 184 | 2 | QUAT quat_add | |
| 185 | ( | ||
| 186 | QUAT a, | ||
| 187 | QUAT b | ||
| 188 | ) | ||
| 189 | { | ||
| 190 | 2 | QUAT result; | |
| 191 | |||
| 192 | 2 | result.w = a.w + b.w; | |
| 193 | 2 | result.x = a.x + b.x; | |
| 194 | 2 | result.y = a.y + b.y; | |
| 195 | 2 | result.z = a.z + b.z; | |
| 196 | |||
| 197 | 2 | return result; | |
| 198 | |||
| 199 | } /* quat_add */ | ||
| 200 | |||
| 201 | |||
| 202 | /** | ||
| 203 | * @brief Scales a quaternion by a scalar value. | ||
| 204 | * | ||
| 205 | * @param q The quaternion to scale. | ||
| 206 | * @param s The scalar factor. | ||
| 207 | * | ||
| 208 | * @return The quaternion @p q scaled by @p s (q * s). | ||
| 209 | */ | ||
| 210 | 4 | QUAT quat_scale | |
| 211 | ( | ||
| 212 | QUAT q, | ||
| 213 | float s | ||
| 214 | ) | ||
| 215 | { | ||
| 216 | 4 | QUAT result; | |
| 217 | |||
| 218 | 4 | result.w = q.w * s; | |
| 219 | 4 | result.x = q.x * s; | |
| 220 | 4 | result.y = q.y * s; | |
| 221 | 4 | result.z = q.z * s; | |
| 222 | |||
| 223 | 4 | return result; | |
| 224 | |||
| 225 | } /* quat_scale */ | ||
| 226 | |||
| 227 | |||
| 228 | /** | ||
| 229 | * @brief Normalizes a quaternion to unit length. | ||
| 230 | * | ||
| 231 | * @param q The quaternion to normalize. | ||
| 232 | * | ||
| 233 | * @return The normalized unit quaternion. | ||
| 234 | * | ||
| 235 | * Divides each component by the quaternion's norm. If the norm is | ||
| 236 | * zero (e.g. a zero-initialized quaternion), returns the identity | ||
| 237 | * quaternion (1, 0, 0, 0) to avoid a divide-by-zero. Values near | ||
| 238 | * but not equal to zero are divided normally and may exhibit | ||
| 239 | * floating-point roundoff. | ||
| 240 | */ | ||
| 241 | 6 | QUAT quat_normalize | |
| 242 | ( | ||
| 243 | QUAT q | ||
| 244 | ) | ||
| 245 | { | ||
| 246 | 6 | QUAT result; | |
| 247 | |||
| 248 | 6 | float norm = sqrtf(q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z); | |
| 249 | |||
| 250 | /* Fallback for zero quaternion: initialize to identity quaternion */ | ||
| 251 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if ( norm == 0.0f ) |
| 252 | { | ||
| 253 | ✗ | result.w = 1.0f; | |
| 254 | ✗ | result.x = 0.0f; | |
| 255 | ✗ | result.y = 0.0f; | |
| 256 | ✗ | result.z = 0.0f; | |
| 257 | } | ||
| 258 | else | ||
| 259 | { | ||
| 260 | 6 | result.w = q.w / norm; | |
| 261 | 6 | result.x = q.x / norm; | |
| 262 | 6 | result.y = q.y / norm; | |
| 263 | 6 | result.z = q.z / norm; | |
| 264 | } | ||
| 265 | |||
| 266 | 6 | return result; | |
| 267 | |||
| 268 | } /* quat_normalize */ | ||
| 269 | |||
| 270 | |||
| 271 | /** | ||
| 272 | * @brief Computes the conjugate of a quaternion. | ||
| 273 | * | ||
| 274 | * @param q The input quaternion. | ||
| 275 | * | ||
| 276 | * @return The conjugate quaternion (w, -x, -y, -z). | ||
| 277 | */ | ||
| 278 | 6 | QUAT quat_conj | |
| 279 | ( | ||
| 280 | QUAT q | ||
| 281 | ) | ||
| 282 | { | ||
| 283 | 6 | QUAT result = { q.w, -q.x, -q.y, -q.z }; | |
| 284 | 6 | return result; | |
| 285 | |||
| 286 | } /* quat_conj */ | ||
| 287 | |||
| 288 | /** | ||
| 289 | * @brief Rotates a vector from the world frame into the body frame. | ||
| 290 | * | ||
| 291 | * The attitude quaternion represents the body-to-world rotation: | ||
| 292 | * | ||
| 293 | * vector_body = conjugate(q) * vector_world * q | ||
| 294 | * | ||
| 295 | * @param attitude Body-to-world attitude quaternion. | ||
| 296 | * @param vector_world Pure quaternion containing the world-frame vector. | ||
| 297 | * | ||
| 298 | * @return Pure quaternion containing the body-frame vector. | ||
| 299 | */ | ||
| 300 | 4 | QUAT quat_rotate_world_to_body | |
| 301 | ( | ||
| 302 | QUAT attitude, | ||
| 303 | QUAT vector_world | ||
| 304 | ) | ||
| 305 | { | ||
| 306 | 4 | QUAT attitude_conj = quat_conj(attitude); | |
| 307 | |||
| 308 | 4 | return quat_mult | |
| 309 | ( | ||
| 310 | quat_mult(attitude_conj, vector_world), | ||
| 311 | attitude | ||
| 312 | ); | ||
| 313 | |||
| 314 | } /* quat_rotate_world_to_body */ | ||
| 315 | |||
| 316 | |||
| 317 | /** | ||
| 318 | * @brief Rotates a vector from the body frame into the world frame. | ||
| 319 | * | ||
| 320 | * The attitude quaternion represents the body-to-world rotation: | ||
| 321 | * | ||
| 322 | * vector_world = q * vector_body * conjugate(q) | ||
| 323 | * | ||
| 324 | * @param attitude Body-to-world attitude quaternion. | ||
| 325 | * @param vector_body Pure quaternion containing the body-frame vector. | ||
| 326 | * | ||
| 327 | * @return Pure quaternion containing the world-frame vector. | ||
| 328 | */ | ||
| 329 | 2 | QUAT quat_rotate_body_to_world | |
| 330 | ( | ||
| 331 | QUAT attitude, | ||
| 332 | QUAT vector_body | ||
| 333 | ) | ||
| 334 | { | ||
| 335 | 2 | QUAT attitude_conj = quat_conj(attitude); | |
| 336 | |||
| 337 | 2 | return quat_mult | |
| 338 | ( | ||
| 339 | quat_mult(attitude, vector_body), | ||
| 340 | attitude_conj | ||
| 341 | ); | ||
| 342 | |||
| 343 | } /* quat_rotate_body_to_world */ | ||
| 344 | |||
| 345 | /** | ||
| 346 | * @brief Determines whether every quaternion component is finite. | ||
| 347 | */ | ||
| 348 | 6 | bool quat_is_finite | |
| 349 | ( | ||
| 350 | QUAT quaternion | ||
| 351 | ) | ||
| 352 | { | ||
| 353 | 6 | return | |
| 354 | ( | ||
| 355 | 6 | isfinite(quaternion.w) | |
| 356 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | && isfinite(quaternion.x) |
| 357 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | && isfinite(quaternion.y) |
| 358 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
|
12 | && isfinite(quaternion.z) |
| 359 | ); | ||
| 360 | |||
| 361 | } /* quat_is_finite */ | ||
| 362 | |||
| 363 | /* Vectors -------------------------------------------------------------------*/ | ||
| 364 | |||
| 365 | /** | ||
| 366 | * @brief Determines whether every vector component is finite. | ||
| 367 | */ | ||
| 368 | 8 | bool vector_is_finite | |
| 369 | ( | ||
| 370 | VECTOR_3F vector | ||
| 371 | ) | ||
| 372 | { | ||
| 373 | 8 | return | |
| 374 | ( | ||
| 375 | 8 | isfinite(vector.x) | |
| 376 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | && isfinite(vector.y) |
| 377 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
|
16 | && isfinite(vector.z) |
| 378 | ); | ||
| 379 | |||
| 380 | } /* vector_is_finite */ | ||
| 381 | |||
| 382 | /** | ||
| 383 | * @brief Calculates the magnitude of a three-dimensional vector. | ||
| 384 | */ | ||
| 385 | 4 | float vector_magnitude | |
| 386 | ( | ||
| 387 | VECTOR_3F vector | ||
| 388 | ) | ||
| 389 | { | ||
| 390 | 4 | return sqrtf | |
| 391 | ( | ||
| 392 | 4 | vector.x * vector.x + | |
| 393 | 4 | vector.y * vector.y + | |
| 394 | 4 | vector.z * vector.z | |
| 395 | ); | ||
| 396 | |||
| 397 | } /* vector_magnitude */ | ||
| 398 | |||
| 399 | /** | ||
| 400 | * @brief Normalizes a three-dimensional vector in place. | ||
| 401 | */ | ||
| 402 | 2 | bool vector_normalize | |
| 403 | ( | ||
| 404 | VECTOR_3F *vector | ||
| 405 | ) | ||
| 406 | { | ||
| 407 | 2 | float magnitude; | |
| 408 | |||
| 409 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if ( vector == NULL ) |
| 410 | { | ||
| 411 | return false; | ||
| 412 | } | ||
| 413 | |||
| 414 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if ( !vector_is_finite(*vector) ) |
| 415 | { | ||
| 416 | return false; | ||
| 417 | } | ||
| 418 | |||
| 419 | 2 | magnitude = vector_magnitude(*vector); | |
| 420 | |||
| 421 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | if ( magnitude <= 0.0f || !isfinite(magnitude) ) |
| 422 | { | ||
| 423 | return false; | ||
| 424 | } | ||
| 425 | |||
| 426 | 2 | vector->x /= magnitude; | |
| 427 | 2 | vector->y /= magnitude; | |
| 428 | 2 | vector->z /= magnitude; | |
| 429 | |||
| 430 | 2 | return true; | |
| 431 | |||
| 432 | } /* vector_normalize */ | ||
| 433 | |||
| 434 | |||
| 435 | /** | ||
| 436 | * @brief Calculates the cross product of two three-dimensional vectors. | ||
| 437 | */ | ||
| 438 | 2 | VECTOR_3F vector_cross | |
| 439 | ( | ||
| 440 | VECTOR_3F a, | ||
| 441 | VECTOR_3F b | ||
| 442 | ) | ||
| 443 | { | ||
| 444 | 2 | VECTOR_3F result; | |
| 445 | |||
| 446 | 2 | result.x = a.y * b.z - a.z * b.y; | |
| 447 | 2 | result.y = a.z * b.x - a.x * b.z; | |
| 448 | 2 | result.z = a.x * b.y - a.y * b.x; | |
| 449 | |||
| 450 | 2 | return result; | |
| 451 | |||
| 452 | } /* vector_cross */ | ||
| 453 | |||
| 454 | |||
| 455 | /** | ||
| 456 | * @brief Adds two three-dimensional vectors. | ||
| 457 | */ | ||
| 458 | 4 | VECTOR_3F vector_add | |
| 459 | ( | ||
| 460 | VECTOR_3F a, | ||
| 461 | VECTOR_3F b | ||
| 462 | ) | ||
| 463 | { | ||
| 464 | 4 | VECTOR_3F result; | |
| 465 | |||
| 466 | 4 | result.x = a.x + b.x; | |
| 467 | 4 | result.y = a.y + b.y; | |
| 468 | 4 | result.z = a.z + b.z; | |
| 469 | |||
| 470 | 4 | return result; | |
| 471 | |||
| 472 | } /* vector_add */ | ||
| 473 | |||
| 474 | |||
| 475 | /** | ||
| 476 | * @brief Scales a three-dimensional vector by a scalar. | ||
| 477 | */ | ||
| 478 | 2 | VECTOR_3F vector_scale | |
| 479 | ( | ||
| 480 | VECTOR_3F vector, | ||
| 481 | float scalar | ||
| 482 | ) | ||
| 483 | { | ||
| 484 | 2 | VECTOR_3F result; | |
| 485 | |||
| 486 | 2 | result.x = vector.x * scalar; | |
| 487 | 2 | result.y = vector.y * scalar; | |
| 488 | 2 | result.z = vector.z * scalar; | |
| 489 | |||
| 490 | 2 | return result; | |
| 491 | |||
| 492 | } /* vector_scale */ | ||
| 493 | |||
| 494 | /******************************************************************************* | ||
| 495 | * END OF FILE * | ||
| 496 | *******************************************************************************/ | ||
| 497 |