veloxity_core/world/
control.rs1use 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}