@@ -4,6 +4,7 @@ include(joinpath(@__DIR__, "DiscreteKalmanFilter.jl"))
44export BalanceController, compute_pwm!, handle_motion_mode!, balance_car!
55export MotionMode, STOP, START, FORWARD, BACKWARD, LEFT, RIGHT
66export car_stop!, apply_motor_output!
7+ export ParallelPID
78
89# =============================================================================
910# Hardware Interface Stubs (override for actual hardware)
@@ -26,30 +27,76 @@ const PWMB_RIGHT = 0
2627const STBY_PIN = 0
2728
2829# =============================================================================
29- # Constants (matching original BalanceCar.h parameters)
30+ # ParallelPID Controller Parameters
3031# =============================================================================
3132
32- # PID parameters
33- const KP_BALANCE = 55.0f0
34- const KD_BALANCE = 0.75f0
35- const KP_SPEED = 10.0f0
36- const KI_SPEED = 0.26f0
37- const KP_TURN = 2.5f0
38- const KD_TURN = 0.5f0
39-
40- # Angle limits (degrees)
41- const BALANCE_ANGLE_MIN = - 22.0f0
42- const BALANCE_ANGLE_MAX = 22.0f0
43-
44- # PWM limits
45- const PWM_MAX = 255.0f0
46- const PWM_MIN = - 255.0f0
33+ """
34+ ParallelPID
35+
36+ Controller parameters for parallel PD/PI architecture (original Tumbller).
37+ Stores all tunable gains and limits for the balance car controller.
38+ Default values match the original Tumbller code.
39+
40+ # Fields
41+ - `kp_balance`, `kd_balance`: Balance control (PD on tilt angle)
42+ - `kp_speed`, `ki_speed`: Speed control (PI on wheel speed)
43+ - `kp_turn`, `kd_turn`: Turn control
44+ - `balance_angle_min`, `balance_angle_max`: Angle limits (degrees)
45+ - `pwm_min`, `pwm_max`: PWM output limits
46+ - `integral_limit`: Anti-windup limit for speed integrator
47+ - `speed_control_period`: Decimation period for speed control loop
48+ """
49+ @kwdef mutable struct ParallelPID
50+ # Parameters
51+ # Balance control (PD)
52+ kp_balance:: Float32 = 55.0f0
53+ kd_balance:: Float32 = 0.75f0
54+
55+ # Speed control (PI)
56+ kp_speed:: Float32 = 10.0f0
57+ ki_speed:: Float32 = 0.26f0
58+
59+ # Turn control
60+ kp_turn:: Float32 = 2.5f0
61+ kd_turn:: Float32 = 0.5f0
62+
63+ # Angle limits (degrees)
64+ balance_angle_min:: Float32 = - 22.0f0
65+ balance_angle_max:: Float32 = 22.0f0
66+
67+ # PWM limits
68+ pwm_min:: Float32 = - 255.0f0
69+ pwm_max:: Float32 = 255.0f0
70+
71+ # Integral anti-windup limit
72+ integral_limit:: Float32 = 3000.0f0
73+
74+ # Speed control decimation period
75+ speed_control_period:: Int = 8
76+
77+ # State (mutable, initialized to zero)
78+ speed_filter:: Float32 = 0.0f0
79+ speed_filter_old:: Float32 = 0.0f0
80+ car_speed_integral:: Float32 = 0.0f0
81+ speed_control_period_count:: Int = 0
82+ speed_control_output:: Float32 = 0.0f0
83+ rotation_control_output:: Float32 = 0.0f0
84+ end
4785
48- # Integral anti-windup limit
49- const INTEGRAL_LIMIT = 3000.0f0
86+ """
87+ reset_state!(c::ParallelPID)
5088
51- # Speed control decimation (runs every N cycles)
52- const SPEED_CONTROL_PERIOD = 8
89+ Reset controller state to initial values (zeros).
90+ """
91+ function reset_state! (c:: ParallelPID )
92+ c. speed_filter = 0.0f0
93+ c. speed_filter_old = 0.0f0
94+ c. car_speed_integral = 0.0f0
95+ c. speed_control_period_count = 0
96+ c. speed_control_output = 0.0f0
97+ c. rotation_control_output = 0.0f0
98+ nothing
99+ end
53100
54101# =============================================================================
55102# Motion Mode Enum
@@ -61,24 +108,24 @@ const SPEED_CONTROL_PERIOD = 8
61108# Controller State Struct
62109# =============================================================================
63110
64- mutable struct BalanceController
111+ """
112+ BalanceController{C}
113+
114+ Balance controller state with parametric controller type `C`.
115+ The controller type determines the control algorithm (e.g., `ParallelPID`).
116+ Controller-specific state is stored in the controller itself.
117+ """
118+ mutable struct BalanceController{C}
119+ # Controller (contains parameters and algorithm-specific state)
120+ controller:: C
121+
65122 # Kalman filter for angle estimation
66123 kf:: IMUKalmanFilter
67124
68125 # Encoder pulse accumulators (signed based on PWM direction)
69126 encoder_left_pulse:: Int
70127 encoder_right_pulse:: Int
71128
72- # Speed control state (PI integrator)
73- speed_filter:: Float32
74- speed_filter_old:: Float32
75- car_speed_integral:: Float32
76- speed_control_period_count:: Int
77-
78- # Control outputs
79- speed_control_output:: Float32
80- rotation_control_output:: Float32
81-
82129 # Setpoints
83130 setting_car_speed:: Int
84131 setting_turn_speed:: Int
@@ -96,21 +143,20 @@ mutable struct BalanceController
96143end
97144
98145"""
99- BalanceController(; angle_zero=0.0f0, angular_velocity_zero=0.0f0)
146+ BalanceController(; controller=ParallelPID(), angle_zero=0.0f0, angular_velocity_zero=0.0f0)
100147
101- Create a balance controller with default parameters matching the original Tumbller code .
148+ Create a balance controller with the specified controller type (defaults to ParallelPID) .
102149"""
103- function BalanceController (; angle_zero:: Float32 = 0.0f0 , angular_velocity_zero:: Float32 = 0.0f0 )
104- BalanceController (
150+ function BalanceController (;
151+ controller:: C = ParallelPID (),
152+ angle_zero:: Float32 = 0.0f0 ,
153+ angular_velocity_zero:: Float32 = 0.0f0
154+ ) where C
155+ BalanceController {C} (
156+ controller,
105157 IMUKalmanFilter (), # kf
106158 0 , # encoder_left_pulse
107159 0 , # encoder_right_pulse
108- 0.0f0 , # speed_filter
109- 0.0f0 , # speed_filter_old
110- 0.0f0 , # car_speed_integral
111- 0 , # speed_control_period_count
112- 0.0f0 , # speed_control_output
113- 0.0f0 , # rotation_control_output
114160 0 , # setting_car_speed
115161 0 , # setting_turn_speed
116162 0.0f0 , # pwm_left
@@ -133,17 +179,18 @@ Updates `ctrl.pwm_left` and `ctrl.pwm_right` in place.
133179
134180Returns the Kalman-filtered angle for use in motion mode handling.
135181
136- # Control Architecture (parallel, not cascade):
182+ # Control Architecture for ParallelPID (parallel, not cascade):
137183- Balance control: PD on tilt angle
138- - Speed control: PI on wheel speed (runs every 8 cycles)
184+ - Speed control: PI on wheel speed (runs every N cycles)
139185- Turn control: P + D on turn rate
140186
141187Final: `pwm = balance - speed ± rotation`
142188"""
143- function compute_pwm! (ctrl:: BalanceController ,
189+ function compute_pwm! (ctrl:: BalanceController{ParallelPID} ,
144190 encoder_count_left:: Integer , encoder_count_right:: Integer ,
145191 ax:: Integer , ay:: Integer , az:: Integer ,
146192 gx:: Integer , gy:: Integer , gz:: Integer )
193+ c = ctrl. controller
147194
148195 # Accumulate encoder pulses with sign based on current PWM direction
149196 ctrl. encoder_left_pulse += ctrl. pwm_left < 0 ? - encoder_count_left : encoder_count_left
@@ -157,43 +204,43 @@ function compute_pwm!(ctrl::BalanceController,
157204 kalman_angle = angle (ctrl. kf)
158205
159206 # Balance control (PD on tilt angle)
160- balance_control_output = KP_BALANCE * (kalman_angle - ctrl. angle_zero) +
161- KD_BALANCE * (gyro_x - ctrl. angular_velocity_zero)
207+ balance_control_output = c . kp_balance * (kalman_angle - ctrl. angle_zero) +
208+ c . kd_balance * (gyro_x - ctrl. angular_velocity_zero)
162209
163- # Speed control (PI, runs every 8 cycles = 40ms at 5ms sample time )
164- ctrl . speed_control_period_count += 1
165- if ctrl . speed_control_period_count >= SPEED_CONTROL_PERIOD
166- ctrl . speed_control_period_count = 0
210+ # Speed control (PI, runs every N cycles)
211+ c . speed_control_period_count += 1
212+ if c . speed_control_period_count >= c . speed_control_period
213+ c . speed_control_period_count = 0
167214
168215 # Average wheel speed from encoder pulses
169216 car_speed = (ctrl. encoder_left_pulse + ctrl. encoder_right_pulse) * 0.5f0
170217 ctrl. encoder_left_pulse = 0
171218 ctrl. encoder_right_pulse = 0
172219
173220 # Low-pass filter
174- ctrl . speed_filter = ctrl . speed_filter_old * 0.7f0 + car_speed * 0.3f0
175- ctrl . speed_filter_old = ctrl . speed_filter
221+ c . speed_filter = c . speed_filter_old * 0.7f0 + car_speed * 0.3f0
222+ c . speed_filter_old = c . speed_filter
176223
177224 # PI integrator with setpoint
178- ctrl . car_speed_integral += ctrl . speed_filter - ctrl. setting_car_speed
225+ c . car_speed_integral += c . speed_filter - ctrl. setting_car_speed
179226
180227 # Anti-windup
181- ctrl . car_speed_integral = clamp (ctrl . car_speed_integral, - INTEGRAL_LIMIT, INTEGRAL_LIMIT )
228+ c . car_speed_integral = clamp (c . car_speed_integral, - c . integral_limit, c . integral_limit )
182229
183230 # Speed control output (negative feedback)
184- ctrl . speed_control_output = - KP_SPEED * ctrl . speed_filter - KI_SPEED * ctrl . car_speed_integral
231+ c . speed_control_output = - c . kp_speed * c . speed_filter - c . ki_speed * c . car_speed_integral
185232
186233 # Turn control output (computed at same rate as speed control)
187- ctrl . rotation_control_output = ctrl. setting_turn_speed + KD_TURN * gyro_z
234+ c . rotation_control_output = ctrl. setting_turn_speed + c . kd_turn * gyro_z
188235 end
189236
190237 # Combine control outputs
191- ctrl. pwm_left = balance_control_output - ctrl . speed_control_output - ctrl . rotation_control_output
192- ctrl. pwm_right = balance_control_output - ctrl . speed_control_output + ctrl . rotation_control_output
238+ ctrl. pwm_left = balance_control_output - c . speed_control_output - c . rotation_control_output
239+ ctrl. pwm_right = balance_control_output - c . speed_control_output + c . rotation_control_output
193240
194241 # Clamp to PWM limits
195- ctrl. pwm_left = clamp (ctrl. pwm_left, PWM_MIN, PWM_MAX )
196- ctrl. pwm_right = clamp (ctrl. pwm_right, PWM_MIN, PWM_MAX )
242+ ctrl. pwm_left = clamp (ctrl. pwm_left, c . pwm_min, c . pwm_max )
243+ ctrl. pwm_right = clamp (ctrl. pwm_right, c . pwm_min, c . pwm_max )
197244
198245 return kalman_angle
199246end
@@ -211,11 +258,12 @@ Modifies `ctrl.pwm_left`, `ctrl.pwm_right`, `ctrl.motion_mode`, and integrator s
211258Returns `true` if motors should be stopped (car_stop! should be called).
212259"""
213260function handle_motion_mode! (ctrl:: BalanceController , kalman_angle:: Float32 ; key_flag:: Char = ' 0' )
261+ c = ctrl. controller
214262 should_stop_motors = false
215263
216264 # Check angle limits - force STOP if exceeded (except during START or STOP modes)
217265 if ctrl. motion_mode != START && ctrl. motion_mode != STOP
218- if kalman_angle < BALANCE_ANGLE_MIN || kalman_angle > BALANCE_ANGLE_MAX
266+ if kalman_angle < c . balance_angle_min || kalman_angle > c . balance_angle_max
219267 ctrl. motion_mode = STOP
220268 should_stop_motors = true
221269 end
@@ -225,14 +273,14 @@ function handle_motion_mode!(ctrl::BalanceController, kalman_angle::Float32; key
225273 if ctrl. motion_mode == STOP
226274 if key_flag != ' 4'
227275 # Full stop - zero everything
228- ctrl . car_speed_integral = 0.0f0
276+ reset_state! (c)
229277 ctrl. setting_car_speed = 0
230278 ctrl. pwm_left = 0.0f0
231279 ctrl. pwm_right = 0.0f0
232280 should_stop_motors = true
233281 else
234282 # key_flag == '4': Reset state but don't stop motors (allows restart)
235- ctrl . car_speed_integral = 0.0f0
283+ reset_state! (c)
236284 ctrl. setting_car_speed = 0
237285 ctrl. pwm_left = 0.0f0
238286 ctrl. pwm_right = 0.0f0
0 commit comments