Skip to content

Commit 6fd15ea

Browse files
committed
add sensor fusion
1 parent 178cb9c commit 6fd15ea

2 files changed

Lines changed: 163 additions & 1 deletion

File tree

src/DiscreteKalmanFilter.jl

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
using StaticArrays
2+
3+
# System: x = [angle; bias], dynamics: ẋ = A_c*x + B_c*u, measurement: y = C*x
4+
# Continuous: A_c = [0 -1; 0 0], B_c = [1; 0]
5+
# Discretized with Ts = 0.005 (200 Hz)
6+
7+
const Ts = 0.005f0
8+
const Ad = @SMatrix [1.0f0 -Ts; 0.0f0 1.0f0] # exp(A_c * Ts), exact since A_c² = 0
9+
const Bd = @SVector [Ts, 0.0f0] # ∫₀ᵈᵗ exp(A_c*τ) B_c dτ
10+
const Cd = @SMatrix [1.0f0 0.0f0] # Measurement matrix (1×2)
11+
const I2 = @SMatrix [1.0f0 0.0f0; 0.0f0 1.0f0] # Identity for covariance update
12+
13+
"""
14+
DiscreteKalmanFilter
15+
16+
Discrete-time Kalman filter for angle estimation from gyro (input) and accelerometer (measurement).
17+
State is `[angle, gyro_bias]`.
18+
"""
19+
mutable struct DiscreteKalmanFilter
20+
x::SVector{2, Float32} # State [angle, bias]
21+
P::SMatrix{2, 2, Float32, 4} # Covariance
22+
Q::SMatrix{2, 2, Float32, 4} # Process noise covariance
23+
R::SMatrix{1, 1, Float32, 1} # Measurement noise covariance
24+
end
25+
26+
"""
27+
DiscreteKalmanFilter(; Q_angle=0.001f0, Q_bias=0.005f0, R_angle=0.5)
28+
29+
Create a discrete Kalman filter with specified noise covariances.
30+
Default values from BalanceCar.h.
31+
"""
32+
function DiscreteKalmanFilter(; Q_angle::Float32=0.001f0, Q_bias::Float32=0.005f0, R_angle::Float32=0.5f0)
33+
x = @SVector zeros(Float32, 2)
34+
P = I2
35+
Q = @SMatrix [Q_angle 0.0f0; 0.0f0 Q_bias]
36+
R = @SMatrix [R_angle;;]
37+
DiscreteKalmanFilter(x, P, Q, R)
38+
end
39+
40+
"""
41+
predict!(kf, u)
42+
43+
Prediction step: propagate state and covariance using gyro measurement `u` as input.
44+
45+
x = A * x + B * u
46+
P = A * P * A' + Q
47+
"""
48+
function predict!(kf::DiscreteKalmanFilter, u::Float32)
49+
kf.x = Ad * kf.x + Bd * u
50+
kf.P = Ad * kf.P * Ad' + kf.Q
51+
nothing
52+
end
53+
54+
"""
55+
correct!(kf, y)
56+
57+
Update step: correct state and covariance using accelerometer angle measurement `y`.
58+
59+
S = C * P * C' + R
60+
K = P * C' / S
61+
x = x + K * (y - C * x)
62+
P = (I - K * C) * P
63+
"""
64+
function correct!(kf::DiscreteKalmanFilter, y::Float32)
65+
# Innovation covariance (1×1 matrix)
66+
S = Cd * kf.P * Cd' + kf.R
67+
68+
# Kalman gain (2×1)
69+
K = kf.P * Cd' / S[1, 1]
70+
71+
# Innovation (scalar wrapped as 1×1 for matrix multiply)
72+
innovation = SA[y] - Cd * kf.x
73+
74+
# State update
75+
kf.x = kf.x + K * innovation
76+
77+
# Covariance update
78+
kf.P = (I2 - K * Cd) * kf.P
79+
80+
nothing
81+
end
82+
83+
"""
84+
update!(kf, u, y)
85+
86+
Combined predict + correct step.
87+
88+
- `u`: gyro measurement (angular velocity)
89+
- `y`: accelerometer angle measurement
90+
"""
91+
function update!(kf::DiscreteKalmanFilter, u::Float32, y::Float32)
92+
predict!(kf, u)
93+
correct!(kf, y)
94+
nothing
95+
end
96+
97+
"""
98+
angle(kf)
99+
100+
Get the estimated angle from the filter state.
101+
"""
102+
angle(kf::DiscreteKalmanFilter) = kf.x[1]
103+
104+
"""
105+
bias(kf)
106+
107+
Get the estimated gyro bias from the filter state.
108+
"""
109+
bias(kf::DiscreteKalmanFilter) = kf.x[2]
110+
111+
# IMU calibration constants (from original C++ code)
112+
const GYRO_OFFSET = 128.1f0
113+
const GYRO_SCALE = 131.0f0
114+
115+
"""
116+
angle, gyro_x, gyro_z = compute_angles(kf, ax, ay, az, gx, gy, gz)
117+
118+
Apply calibration to raw IMU readings and compute
119+
- `angle`: Tilt angle from accelerometer (radians)
120+
- `gyro_x`: Calibrated gyro x (angular velocity) for tile derivative control
121+
- `gyro_z`: Calibrated gyro z (angular velocity) for turn derivative control
122+
123+
Applies calibration to gyro readings and computes angle from accelerometer.
124+
125+
Use with Kalman filter like this:
126+
```julia
127+
angle, gyro_x, gyro_z = compute_angles(kf, ax, ay, az, gx, gy, gz)
128+
update!(kf, gyro_x, angle)
129+
```
130+
That is, the gyro x reading is used as control input, and the angle computed from accelerometer as measurement.
131+
132+
133+
# Arguments
134+
- `kf`: DiscreteKalmanFilter instance
135+
- `ax, ay, az`: Raw accelerometer readings (int16)
136+
- `gx, gy, gz`: Raw gyroscope readings (int16)
137+
"""
138+
function compute_angles(kf::DiscreteKalmanFilter, ax::Integer, ay::Integer, az::Integer,
139+
gx::Integer, gy::Integer, gz::Integer)
140+
# Calculate angle from accelerometer (radians to degrees)
141+
angle = atan(ay, az) * (180.0f0 / pi)
142+
143+
# Apply gyro calibration offset and scale
144+
gyro_x = (gx - GYRO_OFFSET) / GYRO_SCALE
145+
146+
147+
# Store z-axis gyro (for turn control)
148+
gyro_z = -gz / GYRO_SCALE
149+
150+
angle, gyro_x, gyro_z
151+
end
152+
153+
154+
155+
# test
156+
f = DiscreteKalmanFilter()
157+
158+
m_gyro_x = 0.1f0 # rad/s
159+
m_angle = 0.05f0 # rad
160+
update!(f, m_gyro_x, m_angle)
161+
@show angle(f)
162+
@show bias(f)

src/DyadBotComponents.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module DyadBotComponents
22

33
include("planar_multibody.jl")
4-
include("segway_3d.jl")
4+
# include("segway_3d.jl")
55

66
end # module DyadBotComponents

0 commit comments

Comments
 (0)