Skip to content

Commit 2f70382

Browse files
committed
feat: Complete Phase 2 Refactoring - Controller Encapsulation
- Implemented `FlightController` class to manage PID controllers and motor scaling. - Created `InputProcessor` class for safe handling of RC packets with deadband application. - Developed `SafetyManager` class for arming state management and failsafe logic. - Added `SerialInterface` class for command handling with buffer overflow protection. - Refactored `main.cpp` to utilize new controller classes, improving structure and readability. - Enhanced setup and loop functions for clearer pipeline stages and improved safety checks. - Removed legacy global PID variables and replaced with encapsulated state in controller classes. - Ensured zero behavior changes while improving code quality and maintainability.
1 parent 51c829e commit 2f70382

File tree

13 files changed

+555
-285
lines changed

13 files changed

+555
-285
lines changed

REFACTOR_LOG.md

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,11 @@ Control Pipeline → Motor Output → Telemetry
8383
- [x] Create `CalibrationConfig` class (already exists, verified)
8484
- [x] Replace all #define with constexpr/const members
8585

86-
### Phase 2: Controller Encapsulation
87-
- [ ] Create `FlightController` class to own PID controllers
88-
- [ ] Create `InputProcessor` class for RC packet handling
89-
- [ ] Create `SafetyManager` class for arming/failsafe logic
90-
- [ ] Create `SerialInterface` class for command handling
86+
### Phase 2: Controller Encapsulation ✅ COMPLETED
87+
- [x] Create `FlightController` class to own PID controllers
88+
- [x] Create `InputProcessor` class for RC packet handling
89+
- [x] Create `SafetyManager` class for arming/failsafe logic
90+
- [x] Create `SerialInterface` class for command handling
9191

9292
### Phase 3: Main Loop Cleanup
9393
- [ ] Extract setup() into logical init phases
@@ -163,3 +163,78 @@ Control Pipeline → Motor Output → Telemetry
163163
- Phase 2: Create controller encapsulation classes
164164
- Remove legacy macros once all code migrated to new classes
165165

166+
---
167+
168+
### [2025-10-27] Phase 2: Controller Encapsulation - COMPLETED ✅
169+
170+
**New Classes Created:**
171+
172+
1. **FlightController** ([flight_controller.h/cpp](firmware/app/flight_controller.h))
173+
- Encapsulates all 5 PID controllers (angle roll/pitch, rate roll/pitch/yaw)
174+
- Manages motor scaling array
175+
- Methods: `init()`, `reset_all()`, `compute_angle_*()`, `compute_rate_*()`, `apply_anti_windup()`
176+
- All PID configuration done in constructor using `PIDConfig::` constants
177+
178+
2. **InputProcessor** ([input_processor.h/cpp](firmware/app/input_processor.h))
179+
- Safe RC packet handling from volatile `g_lastPkt`
180+
- Thread-safe memcpy with `volatile` qualifier
181+
- Deadband application
182+
- Setpoint conversion methods: `get_angle_roll_setpoint()`, etc.
183+
- **Eliminated** dangerous `String` usage
184+
185+
3. **SafetyManager** ([safety_manager.h/cpp](firmware/app/safety_manager.h))
186+
- Arming state management
187+
- Failsafe logic encapsulation
188+
- Methods: `update()`, `compute_stab_gain()`, `allow_yaw_integral()`, `is_takeoff_ready()`
189+
- Clean interface to `ArmState`
190+
191+
4. **SerialInterface** ([serial_interface.h/cpp](firmware/app/serial_interface.h))
192+
- Fixed-size char buffer (no String heap allocation)
193+
- Callback-based command handling
194+
- Buffer overflow protection
195+
- Supports both single-char and multi-char commands
196+
197+
**Main.cpp Refactoring:**
198+
199+
- **Before**: 401 lines, mixed responsibilities, global state everywhere
200+
- **After**: 281 lines (-120 lines, -30%), clear pipeline structure
201+
- **Removed globals**: `pidAngleRoll`, `pidAnglePitch`, `pidRoll`, `pidPitch`, `pidYaw`, `motorScale`, `armState`
202+
- **New globals**: `g_flight_controller`, `g_input_processor`, `g_safety_manager`, `g_serial_interface`
203+
204+
**Setup() improvements:**
205+
- Grouped into logical phases: System → Hardware → Sensor → Controller → Safety
206+
- Used `FlightConfig::SETUP_SETTLE_MS` instead of magic `delay(500)`
207+
- Lambda callbacks for serial commands
208+
209+
**Loop() improvements:**
210+
- Clear pipeline stages:
211+
1. Input Processing (serial + RC)
212+
2. Sensor Fusion (IMU + attitude)
213+
3. Safety Evaluation (horizon + calibration + ground gates)
214+
4. Control Pipeline (angle PID → rate PID)
215+
5. Motor Output (mixer + anti-windup)
216+
6. Telemetry
217+
- Consistent naming: `snake_case` for local variables
218+
- Used `FlightConfig::SAT_THRESHOLD` instead of magic `0.1f`
219+
220+
**CalConfig Migration:**
221+
- Changed from global object `g_cal_cfg` to static class `CalConfig::`
222+
- All references updated in `arm_calibration.cpp` and `imu.cpp`
223+
- No config.cpp file needed anymore
224+
225+
**Build Results:**
226+
- ✅ SUCCESS (RAM: 13.6%, Flash: 55.5%)
227+
- +100 bytes RAM (+0.1%)
228+
- +156 bytes Flash (+0.02%)
229+
- **Zero behavior changes** - pure refactoring
230+
231+
**Code Quality Improvements:**
232+
- ❌ Removed `String` (heap allocation danger)
233+
- ✅ Added `const` correctness
234+
- ✅ Encapsulated global state
235+
- ✅ Clear separation of concerns
236+
- ✅ RAII-friendly design
237+
238+
**Next Steps:**
239+
- Phase 3: Main loop cleanup and conditional compilation simplification
240+

firmware/app/flight_controller.cpp

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#include "flight_controller.h"
2+
#include "config.h"
3+
4+
FlightController::FlightController()
5+
: pid_angle_roll_(PIDConfig::AngleGains::ROLL_KP,
6+
PIDConfig::AngleGains::ROLL_KI,
7+
PIDConfig::AngleGains::ROLL_KD)
8+
, pid_angle_pitch_(PIDConfig::AngleGains::PITCH_KP,
9+
PIDConfig::AngleGains::PITCH_KI,
10+
PIDConfig::AngleGains::PITCH_KD)
11+
, pid_rate_roll_(PIDConfig::RateGains::ROLL_KP,
12+
PIDConfig::RateGains::ROLL_KI,
13+
PIDConfig::RateGains::ROLL_KD)
14+
, pid_rate_pitch_(PIDConfig::RateGains::PITCH_KP,
15+
PIDConfig::RateGains::PITCH_KI,
16+
PIDConfig::RateGains::PITCH_KD)
17+
, pid_rate_yaw_(PIDConfig::RateGains::YAW_KP,
18+
PIDConfig::RateGains::YAW_KI,
19+
PIDConfig::RateGains::YAW_KD)
20+
, motor_scale_{1.0f, 1.0f, 1.0f, 1.0f}
21+
{
22+
}
23+
24+
void FlightController::init() {
25+
// Configure angle PID limits
26+
pid_angle_roll_.setOutputLimits(PIDConfig::Limits::ANGLE_OUT_MIN,
27+
PIDConfig::Limits::ANGLE_OUT_MAX);
28+
pid_angle_roll_.setIntegralLimits(0.0f, 0.0f); // No integral on angle PIDs
29+
30+
pid_angle_pitch_.setOutputLimits(PIDConfig::Limits::ANGLE_OUT_MIN,
31+
PIDConfig::Limits::ANGLE_OUT_MAX);
32+
pid_angle_pitch_.setIntegralLimits(0.0f, 0.0f);
33+
34+
// Configure rate PID limits
35+
pid_rate_roll_.setOutputLimits(PIDConfig::Limits::RATE_OUT_MIN,
36+
PIDConfig::Limits::RATE_OUT_MAX);
37+
pid_rate_roll_.setIntegralLimits(PIDConfig::Limits::RATE_I_MIN,
38+
PIDConfig::Limits::RATE_I_MAX);
39+
40+
pid_rate_pitch_.setOutputLimits(PIDConfig::Limits::RATE_OUT_MIN,
41+
PIDConfig::Limits::RATE_OUT_MAX);
42+
pid_rate_pitch_.setIntegralLimits(PIDConfig::Limits::RATE_I_MIN,
43+
PIDConfig::Limits::RATE_I_MAX);
44+
45+
// Configure yaw PID limits
46+
pid_rate_yaw_.setOutputLimits(PIDConfig::Limits::RATE_OUT_MIN,
47+
PIDConfig::Limits::RATE_OUT_MAX);
48+
pid_rate_yaw_.setIntegralLimits(PIDConfig::Limits::YAW_I_MIN,
49+
PIDConfig::Limits::YAW_I_MAX);
50+
}
51+
52+
void FlightController::reset_all() {
53+
pid_angle_roll_.reset();
54+
pid_angle_pitch_.reset();
55+
pid_rate_roll_.reset();
56+
pid_rate_pitch_.reset();
57+
pid_rate_yaw_.reset();
58+
}
59+
60+
float FlightController::compute_angle_roll(float setpoint, float measurement, float dt) {
61+
return pid_angle_roll_.step(setpoint, measurement, dt);
62+
}
63+
64+
float FlightController::compute_angle_pitch(float setpoint, float measurement, float dt) {
65+
return pid_angle_pitch_.step(setpoint, measurement, dt);
66+
}
67+
68+
float FlightController::compute_rate_roll(float setpoint, float measurement, float dt) {
69+
return pid_rate_roll_.step(setpoint, measurement, dt);
70+
}
71+
72+
float FlightController::compute_rate_pitch(float setpoint, float measurement, float dt) {
73+
return pid_rate_pitch_.step(setpoint, measurement, dt);
74+
}
75+
76+
float FlightController::compute_rate_yaw(float setpoint, float measurement, float dt, bool allow_integral) {
77+
return pid_rate_yaw_.stepConditional(setpoint, measurement, dt, allow_integral);
78+
}
79+
80+
void FlightController::apply_anti_windup(float sat_roll, float sat_pitch, float sat_yaw, float dt) {
81+
const float K_aw = FlightConfig::K_ANTIWINDUP;
82+
pid_rate_roll_.applyAntiWindup(sat_roll, dt, K_aw);
83+
pid_rate_pitch_.applyAntiWindup(sat_pitch, dt, K_aw);
84+
pid_rate_yaw_.applyAntiWindup(sat_yaw, dt, K_aw);
85+
}
86+
87+
void FlightController::set_motor_scale(const MotorScaleArray& scale) {
88+
for (int i = 0; i < MAX_MOTORS; i++) {
89+
motor_scale_[i] = scale[i];
90+
}
91+
}

firmware/app/flight_controller.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#pragma once
2+
3+
#include "pid.h"
4+
#include "mixer.h"
5+
6+
// FlightController encapsulates all PID controllers and motor scaling
7+
class FlightController {
8+
public:
9+
FlightController();
10+
11+
// Initialize with config values
12+
void init();
13+
14+
// Reset all PIDs (called on disarm)
15+
void reset_all();
16+
17+
// Angle PIDs (outer loop: degrees -> deg/s setpoint)
18+
float compute_angle_roll(float setpoint, float measurement, float dt);
19+
float compute_angle_pitch(float setpoint, float measurement, float dt);
20+
21+
// Rate PIDs (inner loop: deg/s -> control output)
22+
float compute_rate_roll(float setpoint, float measurement, float dt);
23+
float compute_rate_pitch(float setpoint, float measurement, float dt);
24+
25+
// Yaw rate PID with conditional integration
26+
float compute_rate_yaw(float setpoint, float measurement, float dt, bool allow_integral);
27+
28+
// Apply anti-windup correction from motor saturation
29+
void apply_anti_windup(float sat_roll, float sat_pitch, float sat_yaw, float dt);
30+
31+
// Motor scaling accessors
32+
const MotorScaleArray& get_motor_scale() const { return motor_scale_; }
33+
void set_motor_scale(const MotorScaleArray& scale); // Implemented in .cpp (array copy)
34+
35+
private:
36+
// PID controllers
37+
PID pid_angle_roll_;
38+
PID pid_angle_pitch_;
39+
PID pid_rate_roll_;
40+
PID pid_rate_pitch_;
41+
PID pid_rate_yaw_;
42+
43+
// Motor scaling (CW vs CCW trim)
44+
MotorScaleArray motor_scale_;
45+
};

firmware/app/input_processor.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#include "input_processor.h"
2+
#include <string.h>
3+
4+
InputProcessor::InputProcessor() {
5+
memset(&packet_, 0, sizeof(packet_));
6+
}
7+
8+
void InputProcessor::update_from_radio(const volatile RcPacket& volatile_packet) {
9+
// Thread-safe copy from volatile
10+
memcpy(&packet_, (const void*)&volatile_packet, sizeof(RcPacket));
11+
12+
// Apply deadband to stick inputs
13+
packet_.roll = apply_deadband(packet_.roll, FlightConfig::RC_DEADBAND);
14+
packet_.pitch = apply_deadband(packet_.pitch, FlightConfig::RC_DEADBAND);
15+
packet_.yaw = apply_deadband(packet_.yaw, FlightConfig::RC_DEADBAND);
16+
}
17+
18+
float InputProcessor::get_angle_roll_setpoint() const {
19+
return rc_to_angle(packet_.roll, FlightConfig::ANG_MAX_DEG);
20+
}
21+
22+
float InputProcessor::get_angle_pitch_setpoint() const {
23+
return rc_to_angle(packet_.pitch, FlightConfig::ANG_MAX_DEG);
24+
}
25+
26+
float InputProcessor::get_yaw_rate_setpoint() const {
27+
return rc_to_rate(packet_.yaw, FlightConfig::YAW_RATE_MAX_DPS);
28+
}

firmware/app/input_processor.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#pragma once
2+
3+
#include "rc_packet.h"
4+
5+
// InputProcessor handles safe RC packet access and preprocessing
6+
class InputProcessor {
7+
public:
8+
InputProcessor();
9+
10+
// Update from volatile radio packet (thread-safe copy)
11+
void update_from_radio(const volatile RcPacket& volatile_packet);
12+
13+
// Get preprocessed RC inputs (deadband applied)
14+
const RcPacket& get_packet() const { return packet_; }
15+
16+
// Extract flags
17+
bool is_arm_flag_set() const { return (packet_.flags & 0x01) != 0; }
18+
bool is_cal_flag_set() const { return (packet_.flags & 0x02) != 0; }
19+
20+
// Convert RC values to control setpoints
21+
float get_angle_roll_setpoint() const;
22+
float get_angle_pitch_setpoint() const;
23+
float get_yaw_rate_setpoint() const;
24+
25+
private:
26+
RcPacket packet_;
27+
};

0 commit comments

Comments
 (0)