Line data Source code
1 : /*
2 : * This file is free software: you can redistribute it and/or modify it
3 : * under the terms of the GNU General Public License as published by the
4 : * Free Software Foundation, either version 3 of the License, or
5 : * (at your option) any later version.
6 : *
7 : * This file is distributed in the hope that it will be useful, but
8 : * WITHOUT ANY WARRANTY; without even the implied warranty of
9 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10 : * See the GNU General Public License for more details.
11 : *
12 : * You should have received a copy of the GNU General Public License along
13 : * with this program. If not, see <http://www.gnu.org/licenses/>.
14 : *
15 : * Author: Oliver Walters / Currawong Engineering Pty Ltd
16 : */
17 :
18 :
19 : #include "AP_PiccoloCAN_config.h"
20 :
21 : #if AP_PICCOLOCAN_ENABLED
22 :
23 : #include <AP_HAL/AP_HAL.h>
24 : #include <AP_AHRS/AP_AHRS.h>
25 :
26 : #include "AP_PiccoloCAN.h"
27 :
28 : #include <AP_Param/AP_Param.h>
29 : #include <AP_BoardConfig/AP_BoardConfig.h>
30 : #include <AP_CANManager/AP_CANManager.h>
31 : #include <AP_Common/AP_Common.h>
32 : #include <AP_Scheduler/AP_Scheduler.h>
33 : #include <AP_HAL/utility/sparse-endian.h>
34 : #include <SRV_Channel/SRV_Channel.h>
35 : #include <GCS_MAVLink/GCS.h>
36 : #include <AP_CANManager/AP_CANManager.h>
37 :
38 : #include <AP_EFI/AP_EFI_Currawong_ECU.h>
39 : #include <AP_Generator/AP_Generator_Cortex.h>
40 : #include <AP_Servo_Telem/AP_Servo_Telem.h>
41 :
42 : #include <stdio.h>
43 :
44 : // Protocol files for the Velocity ESC
45 : #include <AP_PiccoloCAN/piccolo_protocol/ESCVelocityProtocol.h>
46 : #include <AP_PiccoloCAN/piccolo_protocol/ESCPackets.h>
47 :
48 : // Protocol files for the CBS servo
49 : #include <AP_PiccoloCAN/piccolo_protocol/ServoProtocol.h>
50 : #include <AP_PiccoloCAN/piccolo_protocol/ServoPackets.h>
51 :
52 : extern const AP_HAL::HAL& hal;
53 :
54 : // table of user-configurable Piccolo CAN bus parameters
55 : const AP_Param::GroupInfo AP_PiccoloCAN::var_info[] = {
56 :
57 : // @Param: ESC_BM
58 : // @DisplayName: ESC channels
59 : // @Description: Bitmask defining which ESC (motor) channels are to be transmitted over Piccolo CAN
60 : // @Bitmask: 0: ESC 1, 1: ESC 2, 2: ESC 3, 3: ESC 4, 4: ESC 5, 5: ESC 6, 6: ESC 7, 7: ESC 8, 8: ESC 9, 9: ESC 10, 10: ESC 11, 11: ESC 12, 12: ESC 13, 13: ESC 14, 14: ESC 15, 15: ESC 16, 16: ESC 17, 17: ESC 18, 18: ESC 19, 19: ESC 20, 20: ESC 21, 21: ESC 22, 22: ESC 23, 23: ESC 24, 24: ESC 25, 25: ESC 26, 26: ESC 27, 27: ESC 28, 28: ESC 29, 29: ESC 30, 30: ESC 31, 31: ESC 32
61 : // @User: Advanced
62 : AP_GROUPINFO("ESC_BM", 1, AP_PiccoloCAN, _esc_bm, 0xFFFF),
63 :
64 : // @Param: ESC_RT
65 : // @DisplayName: ESC output rate
66 : // @Description: Output rate of ESC command messages
67 : // @Units: Hz
68 : // @User: Advanced
69 : // @Range: 1 500
70 : AP_GROUPINFO("ESC_RT", 2, AP_PiccoloCAN, _esc_hz, PICCOLO_MSG_RATE_HZ_DEFAULT),
71 :
72 : // @Param: SRV_BM
73 : // @DisplayName: Servo channels
74 : // @Description: Bitmask defining which servo channels are to be transmitted over Piccolo CAN
75 : // @Bitmask: 0: Servo 1, 1: Servo 2, 2: Servo 3, 3: Servo 4, 4: Servo 5, 5: Servo 6, 6: Servo 7, 7: Servo 8, 8: Servo 9, 9: Servo 10, 10: Servo 11, 11: Servo 12, 12: Servo 13, 13: Servo 14, 14: Servo 15, 15: Servo 16
76 : // @User: Advanced
77 : AP_GROUPINFO("SRV_BM", 3, AP_PiccoloCAN, _srv_bm, 0xFFFF),
78 :
79 : // @Param: SRV_RT
80 : // @DisplayName: Servo command output rate
81 : // @Description: Output rate of servo command messages
82 : // @Units: Hz
83 : // @User: Advanced
84 : // @Range: 1 500
85 : AP_GROUPINFO("SRV_RT", 4, AP_PiccoloCAN, _srv_hz, PICCOLO_MSG_RATE_HZ_DEFAULT),
86 : #if AP_EFI_CURRAWONG_ECU_ENABLED
87 : // @Param: ECU_ID
88 : // @DisplayName: ECU Node ID
89 : // @Description: Node ID to send ECU throttle messages to. Set to zero to disable ECU throttle messages. Set to 255 to broadcast to all ECUs.
90 : // @Range: 0 255
91 : // @User: Advanced
92 : AP_GROUPINFO("ECU_ID", 5, AP_PiccoloCAN, _ecu_id, PICCOLO_CAN_ECU_ID_DEFAULT),
93 :
94 : // @Param: ECU_RT
95 : // @DisplayName: ECU command output rate
96 : // @Description: Output rate of ECU command messages
97 : // @Units: Hz
98 : // @User: Advanced
99 : // @Range: 1 500
100 : AP_GROUPINFO("ECU_RT", 6, AP_PiccoloCAN, _ecu_hz, PICCOLO_MSG_RATE_HZ_DEFAULT),
101 : #endif
102 : AP_GROUPEND
103 : };
104 :
105 0 : AP_PiccoloCAN::AP_PiccoloCAN()
106 : {
107 0 : AP_Param::setup_object_defaults(this, var_info);
108 0 : }
109 :
110 0 : AP_PiccoloCAN *AP_PiccoloCAN::get_pcan(uint8_t driver_index)
111 : {
112 0 : if (driver_index >= AP::can().get_num_drivers() ||
113 0 : AP::can().get_driver_type(driver_index) != AP_CAN::Protocol::PiccoloCAN) {
114 0 : return nullptr;
115 : }
116 :
117 0 : return static_cast<AP_PiccoloCAN*>(AP::can().get_driver(driver_index));
118 : }
119 :
120 0 : bool AP_PiccoloCAN::add_interface(AP_HAL::CANIface* can_iface) {
121 0 : if (_can_iface != nullptr) {
122 0 : return false;
123 : }
124 :
125 0 : _can_iface = can_iface;
126 :
127 0 : if (_can_iface == nullptr) {
128 0 : return false;
129 : }
130 :
131 0 : if (!_can_iface->is_initialized()) {
132 0 : return false;
133 : }
134 :
135 0 : if (!_can_iface->set_event_handle(&sem_handle)) {
136 0 : return false;
137 : }
138 0 : return true;
139 : }
140 :
141 : // initialize PiccoloCAN bus
142 0 : void AP_PiccoloCAN::init(uint8_t driver_index)
143 : {
144 0 : _driver_index = driver_index;
145 :
146 0 : if (_initialized) {
147 0 : return;
148 : }
149 : // start calls to loop in separate thread
150 0 : if (!hal.scheduler->thread_create(FUNCTOR_BIND_MEMBER(&AP_PiccoloCAN::loop, void), _thread_name, 4096, AP_HAL::Scheduler::PRIORITY_MAIN, 1)) {
151 0 : return;
152 : }
153 :
154 0 : _initialized = true;
155 :
156 0 : snprintf(_thread_name, sizeof(_thread_name), "PiccoloCAN_%u", driver_index);
157 : }
158 :
159 : // loop to send output to CAN devices in background thread
160 0 : void AP_PiccoloCAN::loop()
161 : {
162 0 : AP_HAL::CANFrame txFrame {};
163 0 : AP_HAL::CANFrame rxFrame {};
164 :
165 0 : uint16_t esc_tx_counter = 0;
166 0 : uint16_t servo_tx_counter = 0;
167 : #if AP_EFI_CURRAWONG_ECU_ENABLED
168 0 : uint16_t ecu_tx_counter = 0;
169 : #endif
170 :
171 : // CAN Frame ID components
172 : uint8_t frame_id_group; // Piccolo message group
173 : uint16_t frame_id_device; // Device identifier
174 :
175 : while (true) {
176 :
177 0 : if (!_initialized) {
178 0 : hal.scheduler->delay_microseconds(10000);
179 0 : continue;
180 : }
181 :
182 : // Calculate the output rate for ESC commands
183 0 : _esc_hz.set(constrain_int16(_esc_hz, PICCOLO_MSG_RATE_HZ_MIN, PICCOLO_MSG_RATE_HZ_MAX));
184 :
185 0 : uint16_t escCmdRateMs = 1000 / _esc_hz;
186 :
187 : // Calculate the output rate for servo commands
188 0 : _srv_hz.set(constrain_int16(_srv_hz, PICCOLO_MSG_RATE_HZ_MIN, PICCOLO_MSG_RATE_HZ_MAX));
189 :
190 0 : uint16_t servoCmdRateMs = 1000 / _srv_hz;
191 : #if AP_EFI_CURRAWONG_ECU_ENABLED
192 0 : _ecu_hz.set(constrain_int16(_ecu_hz, PICCOLO_MSG_RATE_HZ_MIN, PICCOLO_MSG_RATE_HZ_MAX));
193 :
194 0 : uint16_t ecuCmdRateMs = 1000 / _ecu_hz;
195 : #endif
196 :
197 : // 1ms loop delay
198 0 : hal.scheduler->delay_microseconds(1000);
199 :
200 : // Transmit ESC commands at regular intervals
201 0 : if (esc_tx_counter++ > escCmdRateMs) {
202 0 : esc_tx_counter = 0;
203 0 : send_esc_messages();
204 : }
205 :
206 : // Transmit servo commands at regular intervals
207 0 : if (servo_tx_counter++ > servoCmdRateMs) {
208 0 : servo_tx_counter = 0;
209 0 : send_servo_messages();
210 : }
211 :
212 : #if AP_EFI_CURRAWONG_ECU_ENABLED
213 : // Transmit ecu throttle commands at regular intervals
214 0 : if (ecu_tx_counter++ > ecuCmdRateMs) {
215 0 : ecu_tx_counter = 0;
216 0 : send_ecu_messages();
217 : }
218 : #endif
219 :
220 : // Look for any message responses on the CAN bus
221 0 : while (read_frame(rxFrame, 0)) {
222 :
223 : // Extract group and device ID values from the frame identifier
224 0 : frame_id_group = (rxFrame.id >> 24) & 0x1F;
225 0 : frame_id_device = (rxFrame.id >> 8) & 0xFF;
226 :
227 : // Only accept extended messages
228 0 : if ((rxFrame.id & AP_HAL::CANFrame::FlagEFF) == 0) {
229 0 : continue;
230 : }
231 :
232 0 : switch (PiccoloCAN_MessageGroup(frame_id_group)) {
233 : // ESC messages exist in the ACTUATOR group
234 0 : case PiccoloCAN_MessageGroup::ACTUATOR:
235 :
236 0 : switch (PiccoloCAN_DeviceType(frame_id_device)) {
237 0 : case PiccoloCAN_DeviceType::SERVO:
238 0 : handle_servo_message(rxFrame);
239 0 : break;
240 0 : case PiccoloCAN_DeviceType::ESC:
241 0 : handle_esc_message(rxFrame);
242 0 : break;
243 0 : default:
244 : // Unknown actuator type
245 0 : break;
246 : }
247 :
248 0 : break;
249 0 : case PiccoloCAN_MessageGroup::ECU_OUT:
250 : #if AP_EFI_CURRAWONG_ECU_ENABLED
251 0 : handle_ecu_message(rxFrame);
252 : #endif
253 0 : break;
254 0 : case PiccoloCAN_MessageGroup::BATTERY:
255 0 : handle_cortex_message(rxFrame);
256 0 : break;
257 0 : default:
258 0 : break;
259 : }
260 : }
261 0 : }
262 : }
263 :
264 : // write frame on CAN bus, returns true on success
265 0 : bool AP_PiccoloCAN::write_frame(AP_HAL::CANFrame &out_frame, uint32_t timeout_us)
266 : {
267 0 : if (!_initialized) {
268 0 : return false;
269 : }
270 :
271 0 : bool read_select = false;
272 0 : bool write_select = true;
273 0 : const uint64_t deadline_us = AP_HAL::micros64() + timeout_us;
274 0 : bool ret = _can_iface->select(read_select, write_select, &out_frame, deadline_us);
275 :
276 0 : if (!ret || !write_select) {
277 0 : return false;
278 : }
279 :
280 0 : return (_can_iface->send(out_frame, deadline_us, AP_HAL::CANIface::AbortOnError) == 1);
281 : }
282 :
283 : // read frame on CAN bus, returns true on succses
284 0 : bool AP_PiccoloCAN::read_frame(AP_HAL::CANFrame &recv_frame, uint32_t timeout_us)
285 : {
286 0 : if (!_initialized) {
287 0 : return false;
288 : }
289 :
290 0 : bool read_select = true;
291 0 : bool write_select = false;
292 0 : bool ret = _can_iface->select(read_select, write_select, nullptr, AP_HAL::micros64() + timeout_us);
293 :
294 0 : if (!ret || !read_select) {
295 : // No frame available
296 0 : return false;
297 : }
298 :
299 : uint64_t time;
300 0 : AP_HAL::CANIface::CanIOFlags flags {};
301 :
302 0 : return (_can_iface->receive(recv_frame, time, flags) == 1);
303 : }
304 :
305 : // called from SRV_Channels
306 0 : void AP_PiccoloCAN::update()
307 : {
308 : /* Read out the servo commands from the channel mixer */
309 0 : for (uint8_t ii = 0; ii < PICCOLO_CAN_MAX_NUM_SERVO; ii++) {
310 :
311 0 : if (is_servo_channel_active(ii)) {
312 :
313 0 : uint16_t output = 0;
314 :
315 0 : SRV_Channel::Function function = SRV_Channels::channel_function(ii);
316 :
317 0 : if (SRV_Channels::get_output_pwm(function, output)) {
318 0 : _servos[ii].command = output;
319 0 : _servos[ii].newCommand = true;
320 : }
321 : }
322 : }
323 :
324 : /* Read out the ESC commands from the channel mixer */
325 0 : for (uint8_t ii = 0; ii < PICCOLO_CAN_MAX_NUM_ESC; ii++) {
326 :
327 0 : if (is_esc_channel_active(ii)) {
328 :
329 0 : uint16_t output = 0;
330 :
331 0 : SRV_Channel::Function motor_function = SRV_Channels::get_motor_function(ii);
332 :
333 0 : if (SRV_Channels::get_output_pwm(motor_function, output)) {
334 0 : _escs[ii].command = output;
335 0 : _escs[ii].newCommand = true;
336 : }
337 : }
338 : }
339 :
340 : #if AP_EFI_CURRAWONG_ECU_ENABLED
341 0 : if (_ecu_id != 0) {
342 0 : _ecu_info.command = SRV_Channels::get_output_scaled(SRV_Channel::k_throttle);
343 0 : _ecu_info.newCommand = true;
344 : }
345 : #endif // AP_EFI_CURRAWONG_ECU_ENABLED
346 :
347 : #if AP_SERVO_TELEM_ENABLED
348 0 : AP_Servo_Telem *servo_telem = AP_Servo_Telem::get_singleton();
349 0 : if (servo_telem != nullptr) {
350 0 : for (uint8_t ii = 0; ii < PICCOLO_CAN_MAX_NUM_SERVO; ii++) {
351 0 : AP_PiccoloCAN_Servo &servo = _servos[ii];
352 0 : if (servo.newTelemetry) {
353 : union {
354 : Servo_ErrorBits_t ebits;
355 : uint8_t errors;
356 : } err;
357 0 : err.ebits = servo.status.statusA.errors;
358 :
359 0 : const AP_Servo_Telem::TelemetryData telem_data {
360 0 : .command_position = servo.commandedPosition(),
361 0 : .measured_position = servo.position(),
362 0 : .speed = servo.speed(),
363 0 : .voltage = servo.voltage(),
364 0 : .current = servo.current(),
365 0 : .duty_cycle = servo.dutyCycle(),
366 0 : .motor_temperature_cdeg = int16_t(servo.temperature() * 100),
367 0 : .status_flags = err.errors,
368 : .present_types = AP_Servo_Telem::TelemetryData::Types::COMMANDED_POSITION |
369 : AP_Servo_Telem::TelemetryData::Types::MEASURED_POSITION |
370 : AP_Servo_Telem::TelemetryData::Types::SPEED |
371 : AP_Servo_Telem::TelemetryData::Types::VOLTAGE |
372 : AP_Servo_Telem::TelemetryData::Types::CURRENT |
373 : AP_Servo_Telem::TelemetryData::Types::DUTY_CYCLE |
374 : AP_Servo_Telem::TelemetryData::Types::MOTOR_TEMP |
375 : AP_Servo_Telem::TelemetryData::Types::STATUS
376 0 : };
377 :
378 0 : servo_telem->update_telem_data(ii, telem_data);
379 :
380 0 : servo.newTelemetry = false;
381 : }
382 : }
383 : }
384 : #endif
385 0 : }
386 :
387 :
388 : // send servo messages over CAN
389 0 : void AP_PiccoloCAN::send_servo_messages(void)
390 : {
391 0 : AP_HAL::CANFrame txFrame {};
392 :
393 : // No servos are selected? Don't send anything!
394 0 : if (_srv_bm == 0x00) {
395 0 : return;
396 : }
397 :
398 0 : bool send_cmd = false;
399 0 : int16_t cmd[4] {};
400 : uint8_t idx;
401 :
402 : // Transmit bulk command packets to 4x servos simultaneously
403 0 : for (uint8_t ii = 0; ii < PICCOLO_CAN_MAX_GROUP_SERVO; ii++) {
404 :
405 0 : send_cmd = false;
406 :
407 0 : for (uint8_t jj = 0; jj < 4; jj++) {
408 :
409 0 : idx = (ii * 4) + jj;
410 :
411 : // Set default command value if an output field is unused
412 0 : cmd[jj] = 0x7FFF;
413 :
414 : // Skip servo if the output is not enabled
415 0 : if (!is_servo_channel_active(idx)) {
416 0 : continue;
417 : }
418 :
419 : /* Check if the servo is enabled.
420 : * If it is not enabled, send an enable message.
421 : */
422 :
423 0 : if (!is_servo_present(idx) || !is_servo_enabled(idx)) {
424 : // Servo is not enabled
425 0 : encodeServo_EnablePacket(&txFrame);
426 0 : txFrame.id |= (idx + 1);
427 0 : write_frame(txFrame, 1000);
428 0 : } else if (_servos[idx].newCommand) {
429 : // A new command is provided
430 0 : send_cmd = true;
431 0 : cmd[jj] = _servos[idx].command;
432 0 : _servos[idx].newCommand = false;
433 : }
434 : }
435 :
436 0 : if (send_cmd) {
437 0 : encodeServo_MultiPositionCommandPacket(
438 : &txFrame,
439 0 : cmd[0],
440 0 : cmd[1],
441 0 : cmd[2],
442 0 : cmd[3],
443 : (PKT_SERVO_MULTI_COMMAND_1 + ii)
444 : );
445 :
446 : // Broadcast the command to all servos
447 0 : txFrame.id |= 0xFF;
448 :
449 0 : write_frame(txFrame, 1000);
450 : }
451 : }
452 : }
453 :
454 :
455 : // send ESC messages over CAN
456 0 : void AP_PiccoloCAN::send_esc_messages(void)
457 : {
458 0 : AP_HAL::CANFrame txFrame {};
459 :
460 : // No ESCs are selected? Don't send anything
461 0 : if (_esc_bm == 0x00) {
462 0 : return;
463 : }
464 :
465 : // System is armed - send out ESC commands
466 0 : if (hal.util->get_soft_armed()) {
467 :
468 0 : bool send_cmd = false;
469 0 : int16_t cmd[4] {};
470 : uint8_t idx;
471 :
472 : // Transmit bulk command packets to 4x ESC simultaneously
473 0 : for (uint8_t ii = 0; ii < PICCOLO_CAN_MAX_GROUP_ESC; ii++) {
474 :
475 0 : send_cmd = false;
476 :
477 0 : for (uint8_t jj = 0; jj < 4; jj++) {
478 :
479 0 : idx = (ii * 4) + jj;
480 :
481 : // Set default command value if an output field is unused
482 0 : cmd[jj] = 0x7FFF;
483 :
484 : // Skip an ESC if the motor channel is not enabled
485 0 : if (!is_esc_channel_active(idx)) {
486 0 : continue;
487 : }
488 :
489 : /* Check if the ESC is software-inhibited.
490 : * If so, send a message to enable it.
491 : */
492 0 : if (is_esc_present(idx) && !is_esc_enabled(idx)) {
493 0 : encodeESC_EnablePacket(&txFrame);
494 0 : txFrame.id |= (idx + 1);
495 0 : write_frame(txFrame, 1000);
496 : }
497 0 : else if (_escs[idx].newCommand) {
498 0 : send_cmd = true;
499 0 : cmd[jj] = _escs[idx].command;
500 0 : _escs[idx].newCommand = false;
501 : } else {
502 : // A command of 0x7FFF is 'out of range' and will be ignored by the corresponding ESC
503 0 : cmd[jj] = 0x7FFF;
504 : }
505 : }
506 :
507 0 : if (send_cmd) {
508 0 : encodeESC_CommandMultipleESCsPacket(
509 : &txFrame,
510 0 : cmd[0],
511 0 : cmd[1],
512 0 : cmd[2],
513 0 : cmd[3],
514 : (PKT_ESC_SETPOINT_1 + ii)
515 : );
516 :
517 : // Broadcast the command to all ESCs
518 0 : txFrame.id |= 0xFF;
519 :
520 0 : write_frame(txFrame, 1000);
521 : }
522 : }
523 :
524 : } else {
525 : // System is NOT armed - send a "disable" message to all ESCs on the bus
526 :
527 : // Command all ESC into software disable mode
528 0 : encodeESC_DisablePacket(&txFrame);
529 :
530 : // Set the ESC address to the broadcast ID (0xFF)
531 0 : txFrame.id |= 0xFF;
532 :
533 0 : write_frame(txFrame, 1000);
534 : }
535 : }
536 :
537 :
538 : // interpret a servo message received over CAN
539 0 : bool AP_PiccoloCAN::handle_servo_message(AP_HAL::CANFrame &frame)
540 : {
541 : // The servo address is the lower byte of the frame ID
542 0 : uint8_t addr = frame.id & 0xFF;
543 :
544 : // Ignore servo with an invalid node ID
545 0 : if (addr == 0x00) {
546 0 : return false;
547 : }
548 :
549 : // Subtract to get the address in memory
550 0 : addr -= 1;
551 :
552 : // Maximum number of servos allowed
553 0 : if (addr >= PICCOLO_CAN_MAX_NUM_SERVO) {
554 0 : return false;
555 : }
556 :
557 : // Pass the CAN frame off to the specific servo
558 0 : return _servos[addr].handle_can_frame(frame);
559 : }
560 :
561 :
562 : // interpret an ESC message received over CAN
563 0 : bool AP_PiccoloCAN::handle_esc_message(AP_HAL::CANFrame &frame)
564 : {
565 : // The ESC address is the lower byte of the frame ID
566 0 : uint8_t addr = frame.id & 0xFF;
567 :
568 : // Ignore any ESC with node ID of zero
569 0 : if (addr == 0x00) {
570 0 : return false;
571 : }
572 :
573 : // Subtract to get the address in memory
574 0 : addr -= 1;
575 :
576 : // Maximum number of ESCs allowed
577 0 : if (addr >= PICCOLO_CAN_MAX_NUM_ESC) {
578 0 : return false;
579 : }
580 :
581 0 : return _escs[addr].handle_can_frame(frame);
582 : }
583 :
584 : #if AP_EFI_CURRAWONG_ECU_ENABLED
585 0 : void AP_PiccoloCAN::send_ecu_messages(void)
586 : {
587 0 : AP_HAL::CANFrame txFrame {};
588 :
589 : // No ECU node id set, don't send anything
590 0 : if (_ecu_id == 0) {
591 0 : return;
592 : }
593 :
594 0 : if (_ecu_info.newCommand) {
595 0 : encodeECU_ThrottleCommandPacket(&txFrame, _ecu_info.command);
596 0 : txFrame.id |= (uint8_t) _ecu_id;
597 :
598 0 : _ecu_info.newCommand = false;
599 :
600 0 : write_frame(txFrame, 1000);
601 : }
602 : }
603 :
604 0 : bool AP_PiccoloCAN::handle_ecu_message(AP_HAL::CANFrame &frame)
605 : {
606 : // Get the ecu instance
607 0 : AP_EFI_Currawong_ECU* ecu = AP_EFI_Currawong_ECU::get_instance();
608 0 : if (ecu != nullptr) {
609 0 : return ecu->handle_message(frame);
610 : }
611 0 : return false;
612 : }
613 : #endif // AP_EFI_CURRAWONG_ECU_ENABLED
614 :
615 :
616 0 : bool AP_PiccoloCAN::handle_cortex_message(AP_HAL::CANFrame &frame)
617 : {
618 : #if AP_GENERATOR_CORTEX_ENABLED
619 : // Get the generator instance
620 0 : AP_Generator_Cortex* gen = AP_Generator_Cortex::get_instance();
621 :
622 0 : if (gen != nullptr) {
623 0 : return gen->handle_message(frame, *this);
624 : }
625 : #endif // AP_GENERATOR_CORTEX_ENABLED
626 :
627 0 : return false;
628 : }
629 :
630 :
631 : /**
632 : * Check if a given servo channel is "active" (has been configured for Piccolo control output)
633 : */
634 0 : bool AP_PiccoloCAN::is_servo_channel_active(uint8_t chan)
635 : {
636 : // First check if the particular servo channel is enabled in the channel mask
637 0 : if (((_srv_bm >> chan) & 0x01) == 0x00) {
638 0 : return false;
639 : }
640 :
641 0 : SRV_Channel::Function function = SRV_Channels::channel_function(chan);
642 :
643 : // Ignore if the servo channel does not have a function assigned
644 0 : if (function <= SRV_Channel::k_none) {
645 0 : return false;
646 : }
647 :
648 : // Ignore if the assigned function is a motor function
649 0 : if (SRV_Channel::is_motor(function)) {
650 0 : return false;
651 : }
652 :
653 : // We can safely say that the particular servo channel is active
654 0 : return true;
655 : }
656 :
657 : /**
658 : * Check if a given ESC channel is "active" (has been configured for Piccolo control output)
659 : */
660 0 : bool AP_PiccoloCAN::is_esc_channel_active(uint8_t chan)
661 : {
662 : // First check if the particular ESC channel is enabled in the channel mask
663 0 : if (((_esc_bm >> chan) & 0x01) == 0x00) {
664 0 : return false;
665 : }
666 :
667 : // Check if a motor function is assigned for this motor channel
668 0 : SRV_Channel::Function motor_function = SRV_Channels::get_motor_function(chan);
669 :
670 0 : if (SRV_Channels::function_assigned(motor_function)) {
671 0 : return true;
672 : }
673 :
674 0 : return false;
675 : }
676 :
677 :
678 : /**
679 : * Determine if a servo is present on the CAN bus (has telemetry data been received)
680 : */
681 0 : bool AP_PiccoloCAN::is_servo_present(uint8_t chan, uint32_t timeout_us)
682 : {
683 0 : if (chan >= PICCOLO_CAN_MAX_NUM_SERVO) {
684 0 : return false;
685 : }
686 :
687 0 : return _servos[chan].is_connected(timeout_us);
688 : }
689 :
690 :
691 : /**
692 : * Determine if an ESC is present on the CAN bus (has telemetry data been received)
693 : */
694 0 : bool AP_PiccoloCAN::is_esc_present(uint8_t chan, uint32_t timeout_us)
695 : {
696 0 : if (chan >= PICCOLO_CAN_MAX_NUM_ESC) {
697 0 : return false;
698 : }
699 :
700 0 : return _escs[chan].is_connected(timeout_us);
701 : }
702 :
703 :
704 : /**
705 : * Check if a given servo is enabled
706 : */
707 0 : bool AP_PiccoloCAN::is_servo_enabled(uint8_t chan)
708 : {
709 0 : if (chan >= PICCOLO_CAN_MAX_NUM_SERVO) {
710 0 : return false;
711 : }
712 :
713 : // If the servo is not present, we cannot determine if it is enabled or not
714 0 : if (!is_servo_present(chan)) {
715 0 : return false;
716 : }
717 :
718 0 : return _servos[chan].is_enabled();
719 : }
720 :
721 :
722 : /**
723 : * Check if a given ESC is enabled (both hardware and software enable flags)
724 : */
725 0 : bool AP_PiccoloCAN::is_esc_enabled(uint8_t chan)
726 : {
727 0 : if (chan >= PICCOLO_CAN_MAX_NUM_ESC) {
728 0 : return false;
729 : }
730 :
731 : // If the ESC is not present, we cannot determine if it is enabled or not
732 0 : if (!is_esc_present(chan)) {
733 0 : return false;
734 : }
735 :
736 0 : return _escs[chan].is_enabled();
737 : }
738 :
739 :
740 0 : bool AP_PiccoloCAN::pre_arm_check(char* reason, uint8_t reason_len)
741 : {
742 : // Check that each required servo is present on the bus
743 0 : for (uint8_t ii = 0; ii < PICCOLO_CAN_MAX_NUM_SERVO; ii++) {
744 :
745 0 : if (is_servo_channel_active(ii)) {
746 :
747 0 : if (!is_servo_present(ii)) {
748 0 : snprintf(reason, reason_len, "Servo %u not detected", ii + 1);
749 0 : return false;
750 : }
751 : }
752 : }
753 :
754 : // Check that each required ESC is present on the bus
755 0 : for (uint8_t ii = 0; ii < PICCOLO_CAN_MAX_NUM_ESC; ii++) {
756 :
757 : // Skip any ESC channels where the motor channel is not enabled
758 0 : if (is_esc_channel_active(ii)) {
759 :
760 0 : if (!is_esc_present(ii)) {
761 0 : snprintf(reason, reason_len, "ESC %u not detected", ii + 1);
762 0 : return false;
763 : }
764 :
765 0 : if (_escs[ii].is_hw_inhibited()) {
766 0 : snprintf(reason, reason_len, "ESC %u is hardware inhibited", (ii + 1));
767 0 : return false;
768 : }
769 : }
770 : }
771 :
772 0 : return true;
773 : }
774 :
775 :
776 : #endif // AP_PICCOLOCAN_ENABLED
|