Skip to main content

veloxity_core/world/
control.rs

1use super::*;
2
3impl<B, E, C, M, CI, PD, R> World<B, E, C, M, CI, PD, R>
4where
5    B: BoardIo,
6    E: Estimator<R>,
7    C: Controller<R, State = E::State> + RcTrimCalibrator,
8    M: crate::mixer::Mixer<R, MixerInput = C::ControlOutput>,
9    M::ActuatorCommands: AsRef<[R]> + Copy,
10    E::State: Copy + Default,
11    CI: CommInterface<B>,
12    PD: PwmDriver<R>,
13    R: FlightFloat,
14{
15    pub fn run_once(&mut self) -> bool {
16        self.run_communication_and_parameter_service_stage();
17        self.run_sensor_ingestion_and_health_stage();
18        let fresh_rc = self.processed_sensors.rc;
19        self.run_rc_command_state_stages(fresh_rc);
20        self.run_control_and_mixing_stage_if_new_imu();
21        self.run_telemetry_stage();
22        self.board.serial_flush();
23        self.board.run_deferred_board_actions();
24        true
25    }
26
27    pub fn set_telemetry_rates(&mut self, telemetry_rates: TelemetryRates) {
28        self.comm.set_telemetry_rates(telemetry_rates);
29    }
30
31    pub fn set_control_loop_rates(&mut self, control_loop_rates: ControlLoopRates) {
32        self.control_loop_rates = control_loop_rates;
33        self.last_control_update_us = self.board.clock_micros();
34    }
35
36    pub fn set_test_pin_3(&mut self, high: bool) {
37        self.board.set_test_pin_3(high);
38    }
39
40    pub fn set_test_pin_2(&mut self, high: bool) {
41        self.board.set_test_pin_2(high);
42    }
43
44    pub fn run_comm_param_sensor_stages(&mut self) {
45        self.run_communication_and_parameter_service_stage();
46        self.run_sensor_ingestion_and_health_stage();
47    }
48
49    pub fn imu_pending(&self) -> bool {
50        self.board.imu_pending()
51    }
52
53    pub fn realtime_scheduler_step(&self) -> RealtimeSchedulerStep {
54        if self.imu_pending() {
55            return RealtimeSchedulerStep::ImuControl;
56        }
57        let now_us = self.board.clock_micros();
58        if self.control_update_can_run_at(now_us) {
59            return RealtimeSchedulerStep::ControlUpdate;
60        }
61        if now_us >= self.next_realtime_service_us
62            && self.realtime_service_has_control_slack(now_us)
63        {
64            RealtimeSchedulerStep::Service
65        } else {
66            RealtimeSchedulerStep::Idle
67        }
68    }
69
70    pub fn run_imu_control_tick(&mut self) -> bool {
71        #[cfg(feature = "pre-control-scope")]
72        self.board.set_test_pin_3(true);
73        let now_us = self.board.clock_micros();
74        self.board.update_imu_sensor(&mut self.raw_sensors);
75        self.process_imu_sensor_after_update();
76        self.record_control_imu_candidate();
77        self.update_sensor_health_and_calibration(now_us);
78        #[cfg(feature = "pre-control-scope")]
79        self.board.set_test_pin_3(false);
80        let ran_control = self.run_control_and_mixing_stage_if_control_due(now_us);
81        if ran_control {
82            self.last_realtime_control_us = self.board.clock_micros();
83        }
84        ran_control
85    }
86
87    pub fn run_control_update_tick(&mut self) -> bool {
88        let now_us = self.board.clock_micros();
89        let ran_control = self.run_control_and_mixing_stage_if_control_due(now_us);
90        if ran_control {
91            self.last_realtime_control_us = self.board.clock_micros();
92        }
93        ran_control
94    }
95
96    pub fn run_rc_command_state_stages(&mut self, fresh_rc: Option<crate::packets::RcPacket>) {
97        let now_ms = self.board.clock_millis();
98
99        run_rc_command_state(RcCommandStateCtx {
100            now_ms,
101            fresh_rc,
102            rc: &mut self.rc,
103            command: &mut self.command,
104            state: &mut self.state,
105            params: &mut self.params,
106            param_events: Some(&mut self.param_events),
107        });
108        self.run_pwm_output_stage();
109        self.update_board_leds(now_ms);
110    }
111
112    pub fn run_pwm_output_stage(&mut self) -> bool {
113        let channel_outputs_disabled = matches!(
114            self.params
115                .get_by_id(crate::params::ParamId::PARAM_CHANNEL_OUTPUT_MASK),
116            crate::params::ParamValue::Int(0)
117        );
118        sync_pwm_output_state(PwmSyncCtx {
119            board: &mut self.board,
120            pwm: &mut self.pwm,
121            output: &mut self.pwm_output,
122            output_kill_active: channel_outputs_disabled
123                || (self.rc.switch_mapped(crate::rc::Switch::OutputKill)
124                    && self.rc.switch_on(crate::rc::Switch::OutputKill)),
125        })
126        .unwrap_or(false)
127    }
128
129    pub fn run_control_stages_if_new_imu(&mut self) -> bool {
130        self.run_control_and_mixing_stage_if_new_imu()
131    }
132
133    pub(super) fn control_update_due_at(&self, now_us: u64) -> bool {
134        let rate_hz = self.control_loop_rates.control_hz;
135        if rate_hz == 0 {
136            return false;
137        }
138
139        let interval_us = 1_000_000_u64 / rate_hz as u64;
140        now_us.saturating_sub(self.last_control_update_us) >= interval_us
141    }
142
143    pub(super) fn consume_control_update_deadline(&mut self, now_us: u64) {
144        let rate_hz = self.control_loop_rates.control_hz;
145        if rate_hz == 0 {
146            return;
147        }
148
149        let interval_us = 1_000_000_u64 / rate_hz as u64;
150        let elapsed_intervals = now_us.saturating_sub(self.last_control_update_us) / interval_us;
151        self.last_control_update_us = self
152            .last_control_update_us
153            .saturating_add(elapsed_intervals.saturating_mul(interval_us));
154    }
155
156    pub(super) fn control_update_can_run_at(&self, now_us: u64) -> bool {
157        self.control_update_due_at(now_us) && self.control_imu_accumulator.has_samples()
158    }
159
160    pub(super) fn realtime_service_has_control_slack(&self, now_us: u64) -> bool {
161        let rate_hz = self.control_loop_rates.control_hz;
162        if rate_hz == 0 {
163            return true;
164        }
165        if self.control_update_due_at(now_us) && !self.control_imu_accumulator.has_samples() {
166            return true;
167        }
168
169        let interval_us = 1_000_000_u64 / rate_hz as u64;
170        let elapsed_us = now_us.saturating_sub(self.last_control_update_us);
171        elapsed_us
172            .checked_add(REALTIME_SERVICE_MIN_CONTROL_SLACK_US)
173            .is_some_and(|required_us| required_us < interval_us)
174    }
175
176    pub(super) fn realtime_service_can_continue(&self) -> bool {
177        if self.imu_pending() {
178            return false;
179        }
180        let now_us = self.board.clock_micros();
181        !self.control_update_can_run_at(now_us) && self.realtime_service_has_control_slack(now_us)
182    }
183
184    pub(super) fn run_control_and_mixing_stage_if_control_due(&mut self, now_us: u64) -> bool {
185        if self.control_loop_rates.control_hz != 0 {
186            if !self.control_update_can_run_at(now_us) {
187                return false;
188            }
189            self.consume_control_update_deadline(now_us);
190            return self.run_control_and_mixing_stage_with_accumulated_imu();
191        }
192        if self.processed_sensors.imu.is_none() {
193            return false;
194        }
195        self.run_control_and_mixing_stage_if_new_imu()
196    }
197
198    pub(super) fn run_control_and_mixing_stage_with_accumulated_imu(&mut self) -> bool {
199        let Some(averaged_imu) = self.control_imu_accumulator.take_average() else {
200            return false;
201        };
202        let latest_imu = self.processed_sensors.imu;
203        self.processed_sensors.imu = Some(averaged_imu);
204        let ran_control = self.run_control_and_mixing_stage_if_new_imu();
205        self.processed_sensors.imu = latest_imu;
206        ran_control
207    }
208
209    pub fn run_control_and_mixing_stage_if_new_imu(&mut self) -> bool {
210        self.run_control_and_mixing_stage_if_new_imu_with_timing(None)
211    }
212
213    pub(super) fn run_control_and_mixing_stage_if_new_imu_with_timing(
214        &mut self,
215        timing: Option<&mut ControlPipelineTiming>,
216    ) -> bool {
217        run_control_pipeline_if_new_imu(ControlPipelineCtx {
218            board: &mut self.board,
219            params: &self.params,
220            sensors: &self.processed_sensors,
221            external_attitude: &mut self.external_attitude,
222            aux_commands: &self.aux_commands,
223            command: &self.command,
224            state: &mut self.state,
225            estimator: &mut self.estimator,
226            controller: &mut self.controller,
227            mixer: &mut self.mixer,
228            control_pipeline: &mut self.control_pipeline,
229            pwm_output: &self.pwm_output,
230            pwm: &mut self.pwm,
231            timing,
232        })
233    }
234    pub(super) fn update_board_leds(&mut self, now_ms: u32) {
235        if self.command.get_rc_override() != 0 {
236            self.board.led0_on();
237        } else {
238            self.board.led0_off();
239        }
240
241        if self.state.is_in_failsafe() {
242            if (now_ms / 100) % 2 == 0 {
243                self.board.led1_on();
244            } else {
245                self.board.led1_off();
246            }
247        } else if self.state.is_in_error_state() {
248            if (now_ms / 500) % 2 == 0 {
249                self.board.led1_on();
250            } else {
251                self.board.led1_off();
252            }
253        } else if self.state.is_armed() {
254            self.board.led1_on();
255        } else {
256            self.board.led1_off();
257        }
258    }
259}