Skip to content

Commit 1205380

Browse files
committed
add test
1 parent 0bed6e6 commit 1205380

4 files changed

Lines changed: 61 additions & 41 deletions

File tree

Project.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
11
name = "DyadBotComponents"
22
uuid = "6baa8624-4e17-4d27-96ac-5a32f577bc5c"
3-
authors = ["Fredrik Bagge Carlson"]
43
version = "0.1.0"
4+
authors = ["Fredrik Bagge Carlson"]
55

66
[deps]
7+
ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e"
78
ControlSystemsMTK = "687d7614-c7e5-45fc-bfc3-9ee385575c88"
89
DyadControlSystems = "6f9e59d0-2838-4fee-9bce-c163a6c9592b"
910
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
1011
ModelingToolkitStandardLibrary = "16a59e39-deab-5bd0-87e4-056b12336739"
12+
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
1113
OrdinaryDiffEqDefault = "50262376-6c5a-4cf5-baba-aaf4f84d72d7"
1214
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
15+
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
16+
1317

1418
[compat]
1519
ControlSystemsMTK = "2.5.0"
1620
ModelingToolkitStandardLibrary = "2.25.0"
1721
Plots = "1.41.2"
22+
StaticArrays = "1.9.15"
1823

1924
[extras]
2025
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

src/DiscreteKalmanFilter.jl

Lines changed: 27 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using StaticArrays
2+
export IMUKalmanFilter, predict!, correct!, update!, compute_angles, bias, angle
23

34
# System: x = [angle; bias], dynamics: ẋ = A_c*x + B_c*u, measurement: y = C*x
45
# Continuous: A_c = [0 -1; 0 0], B_c = [1; 0]
@@ -11,75 +12,72 @@ const Cd = @SMatrix [1.0f0 0.0f0] # Measurement matrix (1×2)
1112
const I2 = @SMatrix [1.0f0 0.0f0; 0.0f0 1.0f0] # Identity for covariance update
1213

1314
"""
14-
DiscreteKalmanFilter
15+
IMUKalmanFilter
1516
1617
Discrete-time Kalman filter for angle estimation from gyro (input) and accelerometer (measurement).
1718
State is `[angle, gyro_bias]`.
1819
"""
19-
mutable struct DiscreteKalmanFilter
20+
mutable struct IMUKalmanFilter
2021
x::SVector{2, Float32} # State [angle, bias]
2122
P::SMatrix{2, 2, Float32, 4} # Covariance
2223
const Q::SMatrix{2, 2, Float32, 4} # Process noise covariance
2324
const R::SMatrix{1, 1, Float32, 1} # Measurement noise covariance
2425
end
2526

2627
"""
27-
DiscreteKalmanFilter(; Q_angle=0.001f0, Q_bias=0.005f0, R_angle=0.5)
28+
IMUKalmanFilter(; Q_angle=0.001f0, Q_bias=0.005f0, R_angle=0.5)
2829
2930
Create a discrete Kalman filter with specified noise covariances.
3031
Default values from BalanceCar.h.
3132
"""
32-
function DiscreteKalmanFilter(; Q_angle::Float32=0.001f0, Q_bias::Float32=0.005f0, R_angle::Float32=0.5f0)
33+
function IMUKalmanFilter(; Q_angle::Float32=0.001f0, Q_bias::Float32=0.005f0, R_angle::Float32=0.5f0)
3334
x = @SVector zeros(Float32, 2)
3435
P = I2
3536
Q = @SMatrix [Q_angle 0.0f0; 0.0f0 Q_bias]
3637
R = @SMatrix [R_angle;;]
37-
DiscreteKalmanFilter(x, P, Q, R)
38+
IMUKalmanFilter(x, P, Q, R)
3839
end
3940

4041
"""
4142
predict!(kf, u)
4243
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
44+
Prediction step: propagate state and covariance using gyro tilt rate (x-axis) measurement `u` as input.
4745
"""
48-
@inline @fastmath function predict!(kf::DiscreteKalmanFilter, u::Float32)
49-
kf.x = Ad * kf.x + Bd * u
50-
kf.P = Ad * kf.P * Ad' + kf.Q
46+
@inline @fastmath function predict!(kf::IMUKalmanFilter, u::Float32)
47+
kf.x = Ad*kf.x + Bd*u
48+
kf.P = Ad*kf.P*Ad' + kf.Q
5149
nothing
5250
end
5351

5452
"""
5553
correct!(kf, y)
5654
5755
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
6356
"""
64-
@inline @fastmath function correct!(kf::DiscreteKalmanFilter, y::Float32)
57+
@inline @fastmath function correct!(kf::IMUKalmanFilter, y::Float32)
6558
# Innovation covariance (1×1 matrix)
66-
S = Cd * kf.P * Cd' + kf.R
59+
S = Cd*kf.P*Cd' + kf.R
6760

6861
# Kalman gain (2×1)
69-
K = kf.P * Cd' / S[1, 1]
62+
K = kf.P*Cd' / S[1, 1]
7063

7164
# Innovation (scalar wrapped as 1×1 for matrix multiply)
72-
innovation = SA[y] - Cd * kf.x
65+
innovation = SA[y] - Cd*kf.x
7366

7467
# State update
75-
kf.x = kf.x + K * innovation
68+
kf.x = kf.x + K*innovation
7669

7770
# Covariance update
78-
kf.P = (I2 - K * Cd) * kf.P
71+
kf.P = symmetrize((I2 - K * Cd) * kf.P)
7972

8073
nothing
8174
end
8275

76+
function symmetrize(P)
77+
m = P[2,1] + P[1,2]
78+
SA[P[1,1] m; m P[2,2]]
79+
end
80+
8381
"""
8482
update!(kf, u, y)
8583
@@ -88,7 +86,7 @@ Combined predict + correct step.
8886
- `u`: gyro measurement (angular velocity)
8987
- `y`: accelerometer angle measurement
9088
"""
91-
function update!(kf::DiscreteKalmanFilter, u::Float32, y::Float32)
89+
function update!(kf::IMUKalmanFilter, u::Float32, y::Float32)
9290
predict!(kf, u)
9391
correct!(kf, y)
9492
nothing
@@ -99,14 +97,14 @@ end
9997
10098
Get the estimated angle from the filter state.
10199
"""
102-
angle(kf::DiscreteKalmanFilter) = kf.x[1]
100+
angle(kf::IMUKalmanFilter) = kf.x[1]
103101

104102
"""
105103
bias(kf)
106104
107105
Get the estimated gyro bias from the filter state.
108106
"""
109-
bias(kf::DiscreteKalmanFilter) = kf.x[2]
107+
bias(kf::IMUKalmanFilter) = kf.x[2]
110108

111109
# IMU calibration constants (from original C++ code)
112110
const GYRO_OFFSET = 128.1f0
@@ -117,7 +115,7 @@ const GYRO_SCALE = 131.0f0
117115
118116
Apply calibration to raw IMU readings and compute
119117
- `angle`: Tilt angle from accelerometer (radians)
120-
- `gyro_x`: Calibrated gyro x (angular velocity) for tile derivative control
118+
- `gyro_x`: Calibrated gyro x (angular velocity) for tilt derivative control
121119
- `gyro_z`: Calibrated gyro z (angular velocity) for turn derivative control
122120
123121
Applies calibration to gyro readings and computes angle from accelerometer.
@@ -131,11 +129,11 @@ That is, the gyro x reading is used as control input, and the angle computed fro
131129
132130
133131
# Arguments
134-
- `kf`: DiscreteKalmanFilter instance
132+
- `kf`: IMUKalmanFilter instance
135133
- `ax, ay, az`: Raw accelerometer readings (int16)
136134
- `gx, gy, gz`: Raw gyroscope readings (int16)
137135
"""
138-
function compute_angles(kf::DiscreteKalmanFilter, ax::Integer, ay::Integer, az::Integer,
136+
function compute_angles(kf::IMUKalmanFilter, ax::Integer, ay::Integer, az::Integer,
139137
gx::Integer, gy::Integer, gz::Integer)
140138
# Calculate angle from accelerometer (radians to degrees)
141139
angle = atan(ay, az) * (180.0f0 / pi)
@@ -150,13 +148,3 @@ function compute_angles(kf::DiscreteKalmanFilter, ax::Integer, ay::Integer, az::
150148
angle, gyro_x, gyro_z
151149
end
152150

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: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
module DyadBotComponents
22

3-
include("planar_multibody.jl")
3+
include("DiscreteKalmanFilter.jl")
4+
5+
# include("planar_multibody.jl")
46
# include("segway_3d.jl")
57

68
end # module DyadBotComponents

test/runtests.jl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
using DyadBotComponents
3+
using Test
4+
5+
# include("../generated/tests.jl")
6+
7+
# test
8+
f = IMUKalmanFilter()
9+
10+
m_gyro_x = 0.0f0 # rad/s
11+
m_angle = 0.05f0 # rad
12+
for i = 1:100
13+
update!(f, m_gyro_x, m_angle)
14+
end
15+
@test angle(f) m_angle rtol = 1e-4
16+
@test abs(bias(f)) < 1e-4
17+
18+
19+
m_gyro_x = 0.1f0 # rad/s
20+
m_angle = 0.05f0 # rad
21+
for i = 1:100
22+
update!(f, m_gyro_x, m_angle)
23+
end
24+
@test angle(f) m_angle rtol = 1e-4
25+
@test bias(f) 0.1 rtol = 1e-4

0 commit comments

Comments
 (0)