__all__ = [
'create_analog_devices_pib_gyros',
'create_gnb_rate_sensors',
'create_ICM20948_IMU',
'create_adis16405_gyros',
'create_bmx055_gyros',
'create_itg3200_gyros',
'create_intrepid_mainboard_gyros',
]
import numpy as np
from typing import Optional, List
from ADCS.satellite_hardware.sensors import Gyro
from ADCS.satellite_hardware.errors import Noise, Bias
from ADCS.helpers.math_helpers import random_n_unit_vec
[docs]
def create_ICM20948_IMU(axes: np.ndarray = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]), bias: Bias | None = None, noise: Noise | None = None, estimate_bias: bool = False) -> List[Gyro]:
r"""
Create an ICM-20948 gyro triad.
Default error model provenance:
The default bias/noise values in this helper are package-level
representative simulation values for BeaverCube-style examples. They
are not currently tied to a published ICM-20948 paper, data sheet table,
or BeaverCube flight-calibration result. Pass explicit ``bias`` and
``noise`` objects when a traceable mission or vendor model is required.
"""
if bias is None:
e_bias = random_n_unit_vec(3)*np.random.uniform(0.01,0.2)*(np.pi/180.0)
std_bias = 0.0004*np.pi/180.0*np.ones(3)
bias = [Bias(bias=e_bias[j], std_bias=std_bias[j]) for j in range(3)]
if noise is None:
e_noise = np.zeros(3)
std_noise = 0.03*np.pi/180.0*np.ones(3)
noise = [Noise(noise=e_noise[j], std_noise=std_noise[j]) for j in range(3)]
return [Gyro(axis=axes[j], bias=bias[j], noise=noise[j], estimate_bias=estimate_bias) for j in range(3)]
[docs]
def create_gnb_rate_sensors(
axes: np.ndarray = np.eye(3),
bias: List[Bias] | None = None,
noise: List[Noise] | None = None,
estimate_bias: bool = False,
) -> List[Gyro]:
r"""
Create the nominal BRITE/GNB orthogonal rate-sensor triad.
The BRITE ADCS design gives gyro noise density ``0.05 deg/s/sqrt(Hz)`` and
long-term bias variation ``0.2 deg/s``. The factory represents the bias
variation as a bounded per-axis bias uncertainty, with ``0.0004
deg/s/sqrt(s)`` used as a conservative assumed bias random walk.
"""
n_axes = len(axes)
if bias is None:
bias_bound = 0.2 * np.pi / 180.0
std_bias = 0.0004 * np.pi / 180.0
bias = [Bias(bias=0.0, std_bias=std_bias, bounds=(-bias_bound, bias_bound)) for _ in range(n_axes)]
if noise is None:
noise = [Noise(noise=0.0, std_noise=0.05 * np.pi / 180.0) for _ in range(n_axes)]
return [Gyro(axis=axes[j], bias=bias[j], noise=noise[j], estimate_bias=estimate_bias) for j in range(n_axes)]
[docs]
def create_analog_devices_pib_gyros(
axes: np.ndarray = np.eye(3),
bias: List[Bias] | None = None,
noise: List[Noise] | None = None,
estimate_bias: bool = False,
) -> List[Gyro]:
r"""
Create the LightSail 2 primary PIB gyro triad.
The default biases are the Dec 2020-Feb 2021 flight cross-calibration
values ``[0.47837, 0.14098, 0.043055] deg/s`` and the default white-noise
standard deviation is ``0.27 deg/s`` per axis.
"""
n_axes = len(axes)
if bias is None:
bias_values = np.array([0.47837, 0.14098, 0.043055]) * np.pi / 180.0
bias = [Bias(bias=bias_values[j], std_bias=0.0) for j in range(n_axes)]
if noise is None:
noise = [Noise(noise=0.0, std_noise=0.27 * np.pi / 180.0) for _ in range(n_axes)]
return [Gyro(axis=axes[j], bias=bias[j], noise=noise[j], estimate_bias=estimate_bias) for j in range(n_axes)]
[docs]
def create_intrepid_mainboard_gyros(
axes: np.ndarray = np.eye(3),
bias: List[Bias] | None = None,
noise: List[Noise] | None = None,
estimate_bias: bool = False,
) -> List[Gyro]:
r"""
Create the LightSail 2 secondary mainboard gyro triad.
These gyros were uncalibrated and used in modes that did not require
accurate attitude knowledge. The default ``noise`` uses ``1 deg/s`` per
axis as a conservative approximate white-noise floor so estimator
measurement covariances remain non-singular. This noise floor is a modeling
assumption, not a source-backed LightSail 2 calibration value.
"""
n_axes = len(axes)
if bias is None:
bias = [Bias() for _ in range(n_axes)]
if noise is None:
noise = [Noise(noise=0.0, std_noise=1.0 * np.pi / 180.0) for _ in range(n_axes)]
return [Gyro(axis=axes[j], bias=bias[j], noise=noise[j], estimate_bias=estimate_bias) for j in range(n_axes)]
[docs]
def create_adis16405_gyros(
axes: np.ndarray = np.eye(3),
bias: List[Bias] | None = None,
noise: List[Noise] | None = None,
estimate_bias: bool = False,
) -> List[Gyro]:
r"""
Create the Analog Devices ADIS16405 gyro channels used on RAX-1 and RAX-2.
RAX used one ADIS16405 IMU on the attitude determination board.
Default error model provenance:
``bias`` defaults to a Gaussian turn-on bias with 3 deg/s one-sigma
component error, ``std_bias`` uses the reported rate-random-walk
``3.14e-5 rad/s^(3/2)`` value, and the bias is bounded to wander within
the reported 0.007 deg/s in-run stability around that sampled turn-on
bias. ``noise`` defaults to 0.9 deg/s output-noise RMS. These values are
ADIS16405 component characterization values reported for the RAX
attitude-determination system; they are not a single fixed RAX
flight-estimated bias vector.
The RAX estimator simulation also reported angle-random-walk
``4.89e-4 rad/s^(1/2)``. That value is not represented directly by this
scalar white-noise :class:`~ADCS.satellite_hardware.sensors.Gyro` model.
Source:
J. C. Springmann, *Satellite Attitude Determination with Low-Cost
Sensors*, Ph.D. dissertation, University of Michigan, 2013.
https://deepblue.lib.umich.edu/bitstream/handle/2027.42/102312/jspringm_1.pdf?sequence=1
"""
n_axes = len(axes)
if bias is None:
bias_std = 3.0 * np.pi / 180.0
stability = 0.007 * np.pi / 180.0
e_bias = np.random.normal(0.0, bias_std, size=n_axes)
bias = [
Bias(
bias=e_bias[j],
std_bias=3.14e-5,
bounds=(e_bias[j] - stability, e_bias[j] + stability),
)
for j in range(n_axes)
]
if noise is None:
std_noise = 0.9 * np.pi / 180.0
noise = [Noise(noise=0.0, std_noise=std_noise) for _ in range(n_axes)]
return [Gyro(axis=axes[j], bias=bias[j], noise=noise[j], estimate_bias=estimate_bias) for j in range(n_axes)]
[docs]
def create_bmx055_gyros(
axes: np.ndarray = np.eye(3),
bias: List[Bias] | None = None,
noise: List[Noise] | None = None,
estimate_bias: bool = False,
) -> List[Gyro]:
r"""
Create one Bosch Sensortec BMX055 gyroscope triad used on MOVE-II.
The BMX055 gyroscope is represented as three one-dimensional
:class:`~ADCS.satellite_hardware.sensors.Gyro` channels.
Default error model provenance:
``bias`` defaults to the MOVE-II HIL simulation vector
``[1.75e-3, 3.49e-3, -1.75e-3] rad/s`` for one BMX055 triad. ``noise``
defaults to additive Gaussian white noise with ``sigma = 8.727e-4
rad/s``. The source also models scale factor,
misalignment, nonorthogonality, quantization, time sampling,
low-pass filtering, and gyro-bias random walk; those terms are not
represented directly by this scalar gyro factory.
Source:
`Hardware-in-the-Loop Verification of the Distributed,
Magnetorquer-Based ADCS of the CubeSat MOVE-II
<https://mediatum.ub.tum.de/doc/1483411/document.pdf>`__
"""
n_axes = len(axes)
if bias is None:
triad_bias = np.array([1.75e-3, 3.49e-3, -1.75e-3])
bias = [Bias(bias=triad_bias[j], std_bias=0.0) for j in range(n_axes)]
if noise is None:
noise = [Noise(noise=0.0, std_noise=8.727e-4) for _ in range(n_axes)]
return [Gyro(axis=axes[j], bias=bias[j], noise=noise[j], estimate_bias=estimate_bias) for j in range(n_axes)]
[docs]
def create_itg3200_gyros(
axes: np.ndarray = np.eye(3),
bias: List[Bias] | None = None,
noise: List[Noise] | None = None,
estimate_bias: bool = False,
) -> List[Gyro]:
r"""
Create one ESTCube-1 InvenSense ITG-3200 gyro triad.
The ITG-3200 gyroscope is represented as three one-dimensional
:class:`~ADCS.satellite_hardware.sensors.Gyro` channels.
Default error model provenance:
``bias`` defaults to a uniform initial bias over +/-0.5 deg/s per
measurement channel. ``noise`` defaults to Gaussian 1.8 deg/s standard
deviation. These are the ESTCube-1 preflight simulator values. The
ground characterization also reported 0.9 deg/s statistical noise, and
flight calibration reported approximately 0.5 deg/s expanded
uncertainty; those are documented here but are not the default.
Source:
J. Slavinskis et al., "ESTCube-1 attitude determination system flight
results," *Journal of Aerospace Engineering*, 2016.
https://ascelibrary.org/doi/10.1061/%28ASCE%29AS.1943-5525.0000504
"""
n_axes = len(axes)
if bias is None:
bias_bound = 0.5 * np.pi / 180.0
e_bias = np.random.uniform(-bias_bound, bias_bound, size=n_axes)
bias = [Bias(bias=e_bias[j], std_bias=0.0, bounds=(-bias_bound, bias_bound)) for j in range(n_axes)]
if noise is None:
std_noise = 1.8 * np.pi / 180.0
noise = [Noise(noise=0.0, std_noise=std_noise) for _ in range(n_axes)]
return [Gyro(axis=axes[j], bias=bias[j], noise=noise[j], estimate_bias=estimate_bias) for j in range(n_axes)]