CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
Ardupilot

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

GitHub Repository: Ardupilot/ardupilot
Path: blob/master/libraries/AP_BLHeli/AP_BLHeli.cpp
Views: 1798
1
/*
2
This program is free software: you can redistribute it and/or modify
3
it under the terms of the GNU General Public License as published by
4
the Free Software Foundation, either version 3 of the License, or
5
(at your option) any later version.
6
7
This program is distributed in the hope that it will be useful,
8
but WITHOUT ANY WARRANTY; without even the implied warranty of
9
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
GNU General Public License for more details.
11
12
You should have received a copy of the GNU General Public License
13
along with this program. If not, see <http://www.gnu.org/licenses/>.
14
*/
15
/*
16
implementation of MSP and BLHeli-4way protocols for pass-through ESC
17
calibration and firmware update
18
19
With thanks to betaflight for a great reference
20
implementation. Several of the functions below are based on
21
betaflight equivalent functions
22
*/
23
24
#include "AP_BLHeli.h"
25
26
#if HAVE_AP_BLHELI_SUPPORT
27
28
#if CONFIG_HAL_BOARD == HAL_BOARD_CHIBIOS
29
#include <hal.h>
30
#endif
31
32
#include <AP_Math/crc.h>
33
#include <AP_Vehicle/AP_Vehicle_Type.h>
34
#if APM_BUILD_TYPE(APM_BUILD_Rover)
35
#include <AR_Motors/AP_MotorsUGV.h>
36
#else
37
#include <AP_Motors/AP_Motors_Class.h>
38
#endif
39
#include <GCS_MAVLink/GCS_MAVLink.h>
40
#include <GCS_MAVLink/GCS.h>
41
#include <AP_SerialManager/AP_SerialManager.h>
42
#include <AP_BoardConfig/AP_BoardConfig.h>
43
#include <AP_ESC_Telem/AP_ESC_Telem.h>
44
#include <SRV_Channel/SRV_Channel.h>
45
46
extern const AP_HAL::HAL& hal;
47
48
#define debug(fmt, args ...) do { if (debug_level) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ESC: " fmt, ## args); } } while (0)
49
50
// key for locking UART for exclusive use. This prevents any other writes from corrupting
51
// the MSP protocol on hal.console
52
#define BLHELI_UART_LOCK_KEY 0x20180402
53
54
// if no packets are received for this time and motor control is active BLH will disconnect (stoping motors)
55
#define MOTOR_ACTIVE_TIMEOUT 1000
56
57
const AP_Param::GroupInfo AP_BLHeli::var_info[] = {
58
// @Param: MASK
59
// @DisplayName: BLHeli Channel Bitmask
60
// @Description: Enable of BLHeli pass-thru servo protocol support to specific channels. This mask is in addition to motors enabled using SERVO_BLH_AUTO (if any)
61
// @Bitmask: 0:Channel1,1:Channel2,2:Channel3,3:Channel4,4:Channel5,5:Channel6,6:Channel7,7:Channel8,8:Channel9,9:Channel10,10:Channel11,11:Channel12,12:Channel13,13:Channel14,14:Channel15,15:Channel16, 16:Channel 17, 17: Channel 18, 18: Channel 19, 19: Channel 20, 20: Channel 21, 21: Channel 22, 22: Channel 23, 23: Channel 24, 24: Channel 25, 25: Channel 26, 26: Channel 27, 27: Channel 28, 28: Channel 29, 29: Channel 30, 30: Channel 31, 31: Channel 32
62
// @User: Advanced
63
// @RebootRequired: True
64
AP_GROUPINFO("MASK", 1, AP_BLHeli, channel_mask, 0),
65
66
#if APM_BUILD_COPTER_OR_HELI || APM_BUILD_TYPE(APM_BUILD_ArduPlane) || APM_BUILD_TYPE(APM_BUILD_Rover)
67
// @Param: AUTO
68
// @DisplayName: BLHeli pass-thru auto-enable for multicopter motors
69
// @Description: If set to 1 this auto-enables BLHeli pass-thru support for all multicopter motors
70
// @Values: 0:Disabled,1:Enabled
71
// @User: Standard
72
// @RebootRequired: True
73
AP_GROUPINFO("AUTO", 2, AP_BLHeli, channel_auto, 0),
74
#endif
75
76
// @Param: TEST
77
// @DisplayName: BLHeli internal interface test
78
// @Description: Setting SERVO_BLH_TEST to a motor number enables an internal test of the BLHeli ESC protocol to the corresponding ESC. The debug output is displayed on the USB console.
79
// @Values: 0:Disabled,1:TestMotor1,2:TestMotor2,3:TestMotor3,4:TestMotor4,5:TestMotor5,6:TestMotor6,7:TestMotor7,8:TestMotor8
80
// @User: Advanced
81
AP_GROUPINFO("TEST", 3, AP_BLHeli, run_test, 0),
82
83
// @Param: TMOUT
84
// @DisplayName: BLHeli protocol timeout
85
// @Description: This sets the inactivity timeout for the BLHeli protocol in seconds. If no packets are received in this time normal MAVLink operations are resumed. A value of 0 means no timeout
86
// @Units: s
87
// @Range: 0 300
88
// @User: Standard
89
AP_GROUPINFO("TMOUT", 4, AP_BLHeli, timeout_sec, 0),
90
91
// @Param: TRATE
92
// @DisplayName: BLHeli telemetry rate
93
// @Description: This sets the rate in Hz for requesting telemetry from ESCs. It is the rate per ESC. Setting to zero disables telemetry requests
94
// @Units: Hz
95
// @Range: 0 500
96
// @User: Standard
97
AP_GROUPINFO("TRATE", 5, AP_BLHeli, telem_rate, 10),
98
99
// @Param: DEBUG
100
// @DisplayName: BLHeli debug level
101
// @Description: When set to 1 this enabled verbose debugging output over MAVLink when the blheli protocol is active. This can be used to diagnose failures.
102
// @Values: 0:Disabled,1:Enabled
103
// @User: Standard
104
AP_GROUPINFO("DEBUG", 6, AP_BLHeli, debug_level, 0),
105
106
// @Param: OTYPE
107
// @DisplayName: BLHeli output type override
108
// @Description: When set to a non-zero value this overrides the output type for the output channels given by SERVO_BLH_MASK. This can be used to enable DShot on outputs that are not part of the multicopter motors group.
109
// @Values: 0:None,1:OneShot,2:OneShot125,3:Brushed,4:DShot150,5:DShot300,6:DShot600,7:DShot1200
110
// @User: Advanced
111
// @RebootRequired: True
112
AP_GROUPINFO("OTYPE", 7, AP_BLHeli, output_type, 0),
113
114
// @Param: PORT
115
// @DisplayName: Control port
116
// @Description: This sets the mavlink channel to use for blheli pass-thru. The channel number is determined by the number of serial ports configured to use mavlink. So 0 is always the console, 1 is the next serial port using mavlink, 2 the next after that and so on.
117
// @Values: 0:Console,1:Mavlink Serial Channel1,2:Mavlink Serial Channel2,3:Mavlink Serial Channel3,4:Mavlink Serial Channel4,5:Mavlink Serial Channel5
118
// @User: Advanced
119
AP_GROUPINFO("PORT", 8, AP_BLHeli, control_port, 0),
120
121
// @Param: POLES
122
// @DisplayName: BLHeli Motor Poles
123
// @Description: This allows calculation of true RPM from ESC's eRPM. The default is 14.
124
// @Range: 1 127
125
// @User: Advanced
126
// @RebootRequired: True
127
AP_GROUPINFO("POLES", 9, AP_BLHeli, motor_poles, 14),
128
129
// @Param: 3DMASK
130
// @DisplayName: BLHeli bitmask of 3D channels
131
// @Description: Mask of channels which are dynamically reversible. This is used to configure ESCs in '3D' mode, allowing for the motor to spin in either direction. Do not use for channels selected with SERVO_BLH_RVMASK.
132
// @Bitmask: 0:Channel1,1:Channel2,2:Channel3,3:Channel4,4:Channel5,5:Channel6,6:Channel7,7:Channel8,8:Channel9,9:Channel10,10:Channel11,11:Channel12,12:Channel13,13:Channel14,14:Channel15,15:Channel16, 16:Channel 17, 17: Channel 18, 18: Channel 19, 19: Channel 20, 20: Channel 21, 21: Channel 22, 22: Channel 23, 23: Channel 24, 24: Channel 25, 25: Channel 26, 26: Channel 27, 27: Channel 28, 28: Channel 29, 29: Channel 30, 30: Channel 31, 31: Channel 32
133
// @User: Advanced
134
// @RebootRequired: True
135
AP_GROUPINFO("3DMASK", 10, AP_BLHeli, channel_reversible_mask, 0),
136
137
#if defined(HAL_WITH_BIDIR_DSHOT) || HAL_WITH_IO_MCU_BIDIR_DSHOT
138
// @Param: BDMASK
139
// @DisplayName: BLHeli bitmask of bi-directional dshot channels
140
// @Description: Mask of channels which support bi-directional dshot telemetry. This is used for ESCs which have firmware that supports bi-directional dshot allowing fast rpm telemetry values to be returned for the harmonic notch.
141
// @Bitmask: 0:Channel1,1:Channel2,2:Channel3,3:Channel4,4:Channel5,5:Channel6,6:Channel7,7:Channel8,8:Channel9,9:Channel10,10:Channel11,11:Channel12,12:Channel13,13:Channel14,14:Channel15,15:Channel16, 16:Channel 17, 17: Channel 18, 18: Channel 19, 19: Channel 20, 20: Channel 21, 21: Channel 22, 22: Channel 23, 23: Channel 24, 24: Channel 25, 25: Channel 26, 26: Channel 27, 27: Channel 28, 28: Channel 29, 29: Channel 30, 30: Channel 31, 31: Channel 32
142
// @User: Advanced
143
// @RebootRequired: True
144
AP_GROUPINFO("BDMASK", 11, AP_BLHeli, channel_bidir_dshot_mask, 0),
145
#endif
146
// @Param: RVMASK
147
// @DisplayName: BLHeli bitmask of reversed channels
148
// @Description: Mask of channels which are reversed. This is used to configure ESCs to reverse motor direction for unidirectional rotation. Do not use for channels selected with SERVO_BLH_3DMASK.
149
// @Bitmask: 0:Channel1,1:Channel2,2:Channel3,3:Channel4,4:Channel5,5:Channel6,6:Channel7,7:Channel8,8:Channel9,9:Channel10,10:Channel11,11:Channel12,12:Channel13,13:Channel14,14:Channel15,15:Channel16, 16:Channel 17, 17: Channel 18, 18: Channel 19, 19: Channel 20, 20: Channel 21, 21: Channel 22, 22: Channel 23, 23: Channel 24, 24: Channel 25, 25: Channel 26, 26: Channel 27, 27: Channel 28, 28: Channel 29, 29: Channel 30, 30: Channel 31, 31: Channel 32
150
// @User: Advanced
151
// @RebootRequired: True
152
AP_GROUPINFO("RVMASK", 12, AP_BLHeli, channel_reversed_mask, 0),
153
154
AP_GROUPEND
155
};
156
157
#define RPM_SLEW_RATE 50
158
159
AP_BLHeli *AP_BLHeli::_singleton;
160
161
// constructor
162
AP_BLHeli::AP_BLHeli(void)
163
{
164
// set defaults from the parameter table
165
AP_Param::setup_object_defaults(this, var_info);
166
_singleton = this;
167
last_control_port = -1;
168
}
169
170
/*
171
process one byte of serial input for MSP protocol
172
*/
173
bool AP_BLHeli::msp_process_byte(uint8_t c)
174
{
175
if (msp.state == MSP_IDLE) {
176
msp.escMode = PROTOCOL_NONE;
177
if (c == '$') {
178
msp.state = MSP_HEADER_START;
179
} else {
180
return false;
181
}
182
} else if (msp.state == MSP_HEADER_START) {
183
msp.state = (c == 'M') ? MSP_HEADER_M : MSP_IDLE;
184
} else if (msp.state == MSP_HEADER_M) {
185
msp.state = MSP_IDLE;
186
switch (c) {
187
case '<': // COMMAND
188
msp.packetType = MSP_PACKET_COMMAND;
189
msp.state = MSP_HEADER_ARROW;
190
break;
191
case '>': // REPLY
192
msp.packetType = MSP_PACKET_REPLY;
193
msp.state = MSP_HEADER_ARROW;
194
break;
195
default:
196
break;
197
}
198
} else if (msp.state == MSP_HEADER_ARROW) {
199
if (c > sizeof(msp.buf)) {
200
msp.state = MSP_IDLE;
201
} else {
202
msp.dataSize = c;
203
msp.offset = 0;
204
msp.checksum = 0;
205
msp.checksum ^= c;
206
msp.state = MSP_HEADER_SIZE;
207
}
208
} else if (msp.state == MSP_HEADER_SIZE) {
209
msp.cmdMSP = c;
210
msp.checksum ^= c;
211
msp.state = MSP_HEADER_CMD;
212
} else if (msp.state == MSP_HEADER_CMD && msp.offset < msp.dataSize) {
213
msp.checksum ^= c;
214
msp.buf[msp.offset++] = c;
215
} else if (msp.state == MSP_HEADER_CMD && msp.offset >= msp.dataSize) {
216
if (msp.checksum == c) {
217
msp.state = MSP_COMMAND_RECEIVED;
218
} else {
219
msp.state = MSP_IDLE;
220
}
221
}
222
return true;
223
}
224
225
/*
226
update CRC state for blheli protocol
227
*/
228
void AP_BLHeli::blheli_crc_update(uint8_t c)
229
{
230
blheli.crc = crc_xmodem_update(blheli.crc, c);
231
}
232
233
/*
234
process one byte of serial input for blheli 4way protocol
235
*/
236
bool AP_BLHeli::blheli_4way_process_byte(uint8_t c)
237
{
238
if (blheli.state == BLHELI_IDLE) {
239
if (c == cmd_Local_Escape) {
240
blheli.state = BLHELI_HEADER_START;
241
blheli.crc = 0;
242
blheli_crc_update(c);
243
} else {
244
return false;
245
}
246
} else if (blheli.state == BLHELI_HEADER_START) {
247
blheli.command = c;
248
blheli_crc_update(c);
249
blheli.state = BLHELI_HEADER_CMD;
250
} else if (blheli.state == BLHELI_HEADER_CMD) {
251
blheli.address = c<<8;
252
blheli.state = BLHELI_HEADER_ADDR_HIGH;
253
blheli_crc_update(c);
254
} else if (blheli.state == BLHELI_HEADER_ADDR_HIGH) {
255
blheli.address |= c;
256
blheli.state = BLHELI_HEADER_ADDR_LOW;
257
blheli_crc_update(c);
258
} else if (blheli.state == BLHELI_HEADER_ADDR_LOW) {
259
blheli.state = BLHELI_HEADER_LEN;
260
blheli.param_len = c?c:256;
261
blheli.offset = 0;
262
blheli_crc_update(c);
263
} else if (blheli.state == BLHELI_HEADER_LEN) {
264
blheli.buf[blheli.offset++] = c;
265
blheli_crc_update(c);
266
if (blheli.offset == blheli.param_len) {
267
blheli.state = BLHELI_CRC1;
268
}
269
} else if (blheli.state == BLHELI_CRC1) {
270
blheli.crc1 = c;
271
blheli.state = BLHELI_CRC2;
272
} else if (blheli.state == BLHELI_CRC2) {
273
uint16_t crc = blheli.crc1<<8 | c;
274
if (crc == blheli.crc) {
275
blheli.state = BLHELI_COMMAND_RECEIVED;
276
} else {
277
blheli.state = BLHELI_IDLE;
278
}
279
}
280
return true;
281
}
282
283
284
/*
285
send a MSP protocol ack
286
*/
287
void AP_BLHeli::msp_send_ack(uint8_t cmd)
288
{
289
msp_send_reply(cmd, 0, 0);
290
}
291
292
/*
293
send a MSP protocol reply
294
*/
295
void AP_BLHeli::msp_send_reply(uint8_t cmd, const uint8_t *buf, uint8_t len)
296
{
297
uint8_t *b = &msp.buf[0];
298
*b++ = '$';
299
*b++ = 'M';
300
*b++ = '>';
301
*b++ = len;
302
*b++ = cmd;
303
// acks do not have a payload
304
if (len > 0) {
305
memcpy(b, buf, len);
306
}
307
b += len;
308
uint8_t c = 0;
309
for (uint8_t i=0; i<len+2; i++) {
310
c ^= msp.buf[i+3];
311
}
312
*b++ = c;
313
uart->write_locked(&msp.buf[0], len+6, BLHELI_UART_LOCK_KEY);
314
}
315
316
void AP_BLHeli::putU16(uint8_t *b, uint16_t v)
317
{
318
b[0] = v;
319
b[1] = v >> 8;
320
}
321
322
uint16_t AP_BLHeli::getU16(const uint8_t *b)
323
{
324
return b[0] | (b[1]<<8);
325
}
326
327
void AP_BLHeli::putU32(uint8_t *b, uint32_t v)
328
{
329
b[0] = v;
330
b[1] = v >> 8;
331
b[2] = v >> 16;
332
b[3] = v >> 24;
333
}
334
335
void AP_BLHeli::putU16_BE(uint8_t *b, uint16_t v)
336
{
337
b[0] = v >> 8;
338
b[1] = v;
339
}
340
341
/*
342
process a MSP command from GCS
343
*/
344
void AP_BLHeli::msp_process_command(void)
345
{
346
debug("MSP cmd %u len=%u", msp.cmdMSP, msp.dataSize);
347
switch (msp.cmdMSP) {
348
case MSP_API_VERSION: {
349
debug("MSP_API_VERSION");
350
uint8_t buf[3] = { MSP_PROTOCOL_VERSION, API_VERSION_MAJOR, API_VERSION_MINOR };
351
msp_send_reply(msp.cmdMSP, buf, sizeof(buf));
352
break;
353
}
354
355
case MSP_FC_VARIANT:
356
debug("MSP_FC_VARIANT");
357
msp_send_reply(msp.cmdMSP, (const uint8_t *)ARDUPILOT_IDENTIFIER, FLIGHT_CONTROLLER_IDENTIFIER_LENGTH);
358
break;
359
360
/*
361
Notes:
362
version 3.3.1 adds a reply to MSP_SET_MOTOR which was missing
363
version 3.3.0 requires a workaround in blheli suite to handle MSP_SET_MOTOR without an ack
364
*/
365
case MSP_FC_VERSION: {
366
debug("MSP_FC_VERSION");
367
uint8_t version[3] = { 3, 3, 1 };
368
msp_send_reply(msp.cmdMSP, version, sizeof(version));
369
break;
370
}
371
case MSP_BOARD_INFO: {
372
debug("MSP_BOARD_INFO");
373
// send a generic 'ArduPilot ChibiOS' board type
374
uint8_t buf[7] = { 'A', 'R', 'C', 'H', 0, 0, 0 };
375
msp_send_reply(msp.cmdMSP, buf, sizeof(buf));
376
break;
377
}
378
379
case MSP_BUILD_INFO: {
380
debug("MSP_BUILD_INFO");
381
// build date, build time, git version
382
uint8_t buf[26] {
383
0x4d, 0x61, 0x72, 0x20, 0x31, 0x36, 0x20, 0x32, 0x30,
384
0x31, 0x38, 0x30, 0x38, 0x3A, 0x34, 0x32, 0x3a, 0x32, 0x39,
385
0x62, 0x30, 0x66, 0x66, 0x39, 0x32, 0x38};
386
msp_send_reply(msp.cmdMSP, buf, sizeof(buf));
387
break;
388
}
389
390
case MSP_REBOOT:
391
debug("MSP: ignoring reboot command, end serial comms");
392
hal.rcout->serial_end();
393
blheli.connected[blheli.chan] = false;
394
serial_start_ms = 0;
395
break;
396
397
case MSP_UID:
398
// MCU identifier
399
debug("MSP_UID");
400
msp_send_reply(msp.cmdMSP, (const uint8_t *)UDID_START, 12);
401
break;
402
403
// a literal "4" is used for the PWMType here to allow Rover
404
// to use the same number for the same protocol. At time of
405
// writing the AP_MotorsUGV::PWMType has not been unified with
406
// AP_Motors::PWMType.
407
case MSP_ADVANCED_CONFIG: {
408
debug("MSP_ADVANCED_CONFIG");
409
uint8_t buf[10];
410
buf[0] = 1; // gyro sync denom
411
buf[1] = 4; // pid process denom
412
buf[2] = 0; // use unsynced pwm
413
buf[3] = 4; // (uint8_t)AP_Motors::PWMType::DSHOT150;
414
putU16(&buf[4], 480); // motor PWM Rate
415
putU16(&buf[6], 450); // idle offset value
416
buf[8] = 0; // use 32kHz
417
buf[9] = 0; // motor PWM inversion
418
msp_send_reply(msp.cmdMSP, buf, sizeof(buf));
419
break;
420
}
421
422
case MSP_FEATURE_CONFIG: {
423
debug("MSP_FEATURE_CONFIG");
424
uint8_t buf[4];
425
putU32(buf, (channel_reversible_mask.get() != 0) ? FEATURE_3D : 0); // from MSPFeatures enum
426
msp_send_reply(msp.cmdMSP, buf, sizeof(buf));
427
break;
428
}
429
430
case MSP_STATUS: {
431
debug("MSP_STATUS");
432
uint8_t buf[21];
433
putU16(&buf[0], 1000); // loop time usec
434
putU16(&buf[2], 0); // i2c error count
435
putU16(&buf[4], 0x27); // available sensors
436
putU32(&buf[6], 0); // flight modes
437
buf[10] = 0; // pid profile index
438
putU16(&buf[11], 5); // system load percent
439
putU16(&buf[13], 0); // gyro cycle time
440
buf[15] = 0; // flight mode flags length
441
buf[16] = 18; // arming disable flags count
442
putU32(&buf[17], 0); // arming disable flags
443
msp_send_reply(msp.cmdMSP, buf, sizeof(buf));
444
break;
445
}
446
447
case MSP_MOTOR_3D_CONFIG: {
448
debug("MSP_MOTOR_3D_CONFIG");
449
uint8_t buf[6];
450
putU16(&buf[0], 1406); // 3D deadband low
451
putU16(&buf[2], 1514); // 3D deadband high
452
putU16(&buf[4], 1460); // 3D neutral
453
msp_send_reply(msp.cmdMSP, buf, sizeof(buf));
454
break;
455
}
456
457
case MSP_BATTERY_STATE: {
458
debug("MSP_BATTERY_STATE");
459
uint8_t buf[8];
460
buf[0] = 4; // cell count
461
putU16(&buf[1], 1500); // mAh
462
buf[3] = 16; // V
463
putU16(&buf[4], 1500); // mAh
464
putU16(&buf[6], 1); // A
465
msp_send_reply(msp.cmdMSP, buf, sizeof(buf));
466
break;
467
}
468
469
case MSP_MOTOR_CONFIG: {
470
debug("MSP_MOTOR_CONFIG");
471
uint8_t buf[10];
472
putU16(&buf[0], 1030); // min throttle
473
putU16(&buf[2], 2000); // max throttle
474
putU16(&buf[4], 1000); // min command
475
// API 1.42
476
buf[6] = num_motors; // motorCount
477
buf[7] = motor_poles; // motorPoleCount
478
buf[8] = 0; // useDshotTelemetry
479
buf[9] = 0; // FEATURE_ESC_SENSOR
480
msp_send_reply(msp.cmdMSP, buf, sizeof(buf));
481
break;
482
}
483
484
case MSP_MOTOR: {
485
debug("MSP_MOTOR");
486
// get the output going to each motor
487
uint8_t buf[16] {};
488
for (uint8_t i = 0; i < num_motors; i++) {
489
// if we have a mix of reversible and normal report a PWM of zero, this allows BLHeliSuite to conect
490
uint16_t v = mixed_type ? 0 : hal.rcout->read(motor_map[i]);
491
putU16(&buf[2*i], v);
492
debug("MOTOR %u val: %u",i,v);
493
}
494
msp_send_reply(msp.cmdMSP, buf, sizeof(buf));
495
break;
496
}
497
498
case MSP_SET_MOTOR: {
499
debug("MSP_SET_MOTOR");
500
if (!mixed_type) {
501
// set the output to each motor
502
uint8_t nmotors = msp.dataSize / 2;
503
debug("MSP_SET_MOTOR %u", nmotors);
504
motors_disabled_mask = SRV_Channels::get_disabled_channel_mask();
505
SRV_Channels::set_disabled_channel_mask(0xFFFF);
506
motors_disabled = true;
507
EXPECT_DELAY_MS(1000);
508
hal.rcout->cork();
509
for (uint8_t i = 0; i < nmotors; i++) {
510
if (i >= num_motors) {
511
break;
512
}
513
uint16_t v = getU16(&msp.buf[i*2]);
514
debug("MSP_SET_MOTOR %u %u", i, v);
515
// map from a MSP value to a value in the range 1000 to 2000
516
uint16_t pwm = (v < 1000)?0:v;
517
hal.rcout->write(motor_map[i], pwm);
518
}
519
hal.rcout->push();
520
} else {
521
debug("mixed type, Motors Disabled");
522
}
523
msp_send_ack(msp.cmdMSP);
524
break;
525
}
526
527
case MSP_SET_PASSTHROUGH: {
528
debug("MSP_SET_PASSTHROUGH");
529
if (msp.dataSize == 0) {
530
msp.escMode = PROTOCOL_4WAY;
531
} else if (msp.dataSize == 2) {
532
msp.escMode = (enum escProtocol)msp.buf[0];
533
msp.portIndex = msp.buf[1];
534
}
535
debug("escMode=%u portIndex=%u num_motors=%u", msp.escMode, msp.portIndex, num_motors);
536
uint8_t n = num_motors;
537
switch (msp.escMode) {
538
case PROTOCOL_4WAY:
539
break;
540
default:
541
n = 0;
542
hal.rcout->serial_end();
543
serial_start_ms = 0;
544
break;
545
}
546
// doing the serial setup here avoids delays when doing it on demand and makes
547
// BLHeliSuite considerably more reliable
548
EXPECT_DELAY_MS(1000);
549
if (!hal.rcout->serial_setup_output(motor_map[0], 19200, motor_mask)) {
550
msp_send_ack(ACK_D_GENERAL_ERROR);
551
break;
552
} else {
553
msp_send_reply(msp.cmdMSP, &n, 1);
554
}
555
break;
556
}
557
default:
558
debug("Unknown MSP command %u", msp.cmdMSP);
559
break;
560
}
561
}
562
563
/*
564
send a blheli 4way protocol reply
565
*/
566
void AP_BLHeli::blheli_send_reply(const uint8_t *buf, uint16_t len)
567
{
568
uint8_t *b = &blheli.buf[0];
569
*b++ = cmd_Remote_Escape;
570
*b++ = blheli.command;
571
putU16_BE(b, blheli.address); b += 2;
572
*b++ = len==256?0:len;
573
memcpy(b, buf, len);
574
b += len;
575
*b++ = blheli.ack;
576
putU16_BE(b, crc_xmodem(&blheli.buf[0], len+6));
577
uart->write_locked(&blheli.buf[0], len+8, BLHELI_UART_LOCK_KEY);
578
debug("OutB(%u) 0x%02x ack=0x%02x", len+8, (unsigned)blheli.command, blheli.ack);
579
}
580
581
/*
582
CRC used when talking to ESCs
583
*/
584
uint16_t AP_BLHeli::BL_CRC(const uint8_t *buf, uint16_t len)
585
{
586
uint16_t crc = 0;
587
while (len--) {
588
uint8_t xb = *buf++;
589
for (uint8_t i = 0; i < 8; i++) {
590
if (((xb & 0x01) ^ (crc & 0x0001)) !=0 ) {
591
crc = crc >> 1;
592
crc = crc ^ 0xA001;
593
} else {
594
crc = crc >> 1;
595
}
596
xb = xb >> 1;
597
}
598
}
599
return crc;
600
}
601
602
bool AP_BLHeli::isMcuConnected(void)
603
{
604
return blheli.connected[blheli.chan];
605
}
606
607
void AP_BLHeli::setDisconnected(void)
608
{
609
blheli.connected[blheli.chan] = false;
610
blheli.deviceInfo[blheli.chan][0] = 0;
611
blheli.deviceInfo[blheli.chan][1] = 0;
612
}
613
614
/*
615
send a set of bytes to an RC output channel
616
*/
617
bool AP_BLHeli::BL_SendBuf(const uint8_t *buf, uint16_t len)
618
{
619
bool send_crc = isMcuConnected();
620
if (blheli.chan >= num_motors) {
621
return false;
622
}
623
EXPECT_DELAY_MS(1000);
624
if (!hal.rcout->serial_setup_output(motor_map[blheli.chan], 19200, motor_mask)) {
625
blheli.ack = ACK_D_GENERAL_ERROR;
626
return false;
627
}
628
if (serial_start_ms == 0) {
629
serial_start_ms = AP_HAL::millis();
630
}
631
uint32_t now = AP_HAL::millis();
632
if (serial_start_ms == 0 || now - serial_start_ms < 1000) {
633
/*
634
we've just started the interface. We want it idle for at
635
least 1 second before we start sending serial data.
636
*/
637
hal.scheduler->delay(1100);
638
}
639
memcpy(blheli.buf, buf, len);
640
uint16_t crc = BL_CRC(buf, len);
641
blheli.buf[len] = crc;
642
blheli.buf[len+1] = crc>>8;
643
if (!hal.rcout->serial_write_bytes(blheli.buf, len+(send_crc?2:0))) {
644
blheli.ack = ACK_D_GENERAL_ERROR;
645
return false;
646
}
647
// 19200 baud is 52us per bit - wait for half a bit between sending and receiving to avoid reading
648
// the end of the last sent bit by accident
649
hal.scheduler->delay_microseconds(26);
650
return true;
651
}
652
653
/*
654
read bytes from the ESC connection
655
*/
656
bool AP_BLHeli::BL_ReadBuf(uint8_t *buf, uint16_t len)
657
{
658
bool check_crc = isMcuConnected() && len > 0;
659
uint16_t req_bytes = len+(check_crc?3:1);
660
EXPECT_DELAY_MS(1000);
661
uint16_t n = hal.rcout->serial_read_bytes(blheli.buf, req_bytes);
662
debug("BL_ReadBuf %u -> %u", len, n);
663
if (req_bytes != n) {
664
debug("short read");
665
blheli.ack = ACK_D_GENERAL_ERROR;
666
return false;
667
}
668
if (check_crc) {
669
uint16_t crc = BL_CRC(blheli.buf, len);
670
if ((crc & 0xff) != blheli.buf[len] ||
671
(crc >> 8) != blheli.buf[len+1]) {
672
debug("bad CRC");
673
blheli.ack = ACK_D_GENERAL_ERROR;
674
return false;
675
}
676
if (blheli.buf[len+2] != brSUCCESS) {
677
debug("bad ACK 0x%02x", blheli.buf[len+2]);
678
blheli.ack = ACK_D_GENERAL_ERROR;
679
return false;
680
}
681
} else {
682
if (blheli.buf[len] != brSUCCESS) {
683
debug("bad ACK1 0x%02x", blheli.buf[len]);
684
blheli.ack = ACK_D_GENERAL_ERROR;
685
return false;
686
}
687
}
688
if (len > 0) {
689
memcpy(buf, blheli.buf, len);
690
}
691
return true;
692
}
693
694
uint8_t AP_BLHeli::BL_GetACK(uint16_t timeout_ms)
695
{
696
uint8_t ack;
697
uint32_t start_ms = AP_HAL::millis();
698
EXPECT_DELAY_MS(1000);
699
while (AP_HAL::millis() - start_ms < timeout_ms) {
700
if (hal.rcout->serial_read_bytes(&ack, 1) == 1) {
701
return ack;
702
}
703
}
704
// return brNONE, meaning no ACK received in the timeout
705
return brNONE;
706
}
707
708
bool AP_BLHeli::BL_SendCMDSetAddress()
709
{
710
// skip if adr == 0xFFFF
711
if (blheli.address == 0xFFFF) {
712
return true;
713
}
714
debug("BL_SendCMDSetAddress 0x%04x", blheli.address);
715
uint8_t sCMD[] = {CMD_SET_ADDRESS, 0, uint8_t(blheli.address>>8), uint8_t(blheli.address)};
716
if (!BL_SendBuf(sCMD, 4)) {
717
return false;
718
}
719
return BL_GetACK() == brSUCCESS;
720
}
721
722
bool AP_BLHeli::BL_ReadA(uint8_t cmd, uint8_t *buf, uint16_t n)
723
{
724
if (BL_SendCMDSetAddress()) {
725
uint8_t sCMD[] = {cmd, uint8_t(n==256?0:n)};
726
if (!BL_SendBuf(sCMD, 2)) {
727
return false;
728
}
729
bool ret = BL_ReadBuf(buf, n);
730
if (ret && n == sizeof(esc_status) && blheli.address == esc_status_addr) {
731
// display esc_status structure if we see it
732
struct esc_status status;
733
memcpy(&status, buf, n);
734
debug("Prot %u Good %u Bad %u %x %x %x x%x\n",
735
(unsigned)status.protocol,
736
(unsigned)status.good_frames,
737
(unsigned)status.bad_frames,
738
(unsigned)status.unknown[0],
739
(unsigned)status.unknown[1],
740
(unsigned)status.unknown[2],
741
(unsigned)status.unknown2);
742
}
743
return ret;
744
}
745
return false;
746
}
747
748
/*
749
connect to a blheli ESC
750
*/
751
bool AP_BLHeli::BL_ConnectEx(void)
752
{
753
if (blheli.connected[blheli.chan] != 0) {
754
debug("Using cached interface 0x%x for %u", blheli.interface_mode[blheli.chan], blheli.chan);
755
return true;
756
}
757
debug("BL_ConnectEx %u/%u at %u", blheli.chan, num_motors, motor_map[blheli.chan]);
758
setDisconnected();
759
const uint8_t BootInit[] = {0,0,0,0,0,0,0,0,0,0,0,0,0x0D,'B','L','H','e','l','i',0xF4,0x7D};
760
if (!BL_SendBuf(BootInit, 21)) {
761
return false;
762
}
763
764
uint8_t BootInfo[8];
765
if (!BL_ReadBuf(BootInfo, 8)) {
766
return false;
767
}
768
769
// reply must start with 471
770
if (strncmp((const char *)BootInfo, "471", 3) != 0) {
771
blheli.ack = ACK_D_GENERAL_ERROR;
772
return false;
773
}
774
775
// extract device information
776
blheli.deviceInfo[blheli.chan][2] = BootInfo[3];
777
blheli.deviceInfo[blheli.chan][1] = BootInfo[4];
778
blheli.deviceInfo[blheli.chan][0] = BootInfo[5];
779
780
blheli.interface_mode[blheli.chan] = 0;
781
782
uint16_t devword;
783
memcpy(&devword, blheli.deviceInfo[blheli.chan], sizeof(devword));
784
switch (devword) {
785
case 0x9307:
786
case 0x930A:
787
case 0x930F:
788
case 0x940B:
789
blheli.interface_mode[blheli.chan] = imATM_BLB;
790
debug("Interface type imATM_BLB");
791
break;
792
case 0xF310:
793
case 0xF330:
794
case 0xF410:
795
case 0xF390:
796
case 0xF850:
797
case 0xE8B1:
798
case 0xE8B2:
799
blheli.interface_mode[blheli.chan] = imSIL_BLB;
800
debug("Interface type imSIL_BLB");
801
break;
802
default:
803
// BLHeli_32 MCU ID hi > 0x00 and < 0x90 / lo always = 0x06
804
if ((blheli.deviceInfo[blheli.chan][1] > 0x00) && (blheli.deviceInfo[blheli.chan][1] < 0x90) && (blheli.deviceInfo[blheli.chan][0] == 0x06)) {
805
blheli.interface_mode[blheli.chan] = imARM_BLB;
806
debug("Interface type imARM_BLB");
807
} else {
808
blheli.ack = ACK_D_GENERAL_ERROR;
809
debug("Unknown interface type 0x%04x", devword);
810
break;
811
}
812
}
813
blheli.deviceInfo[blheli.chan][3] = blheli.interface_mode[blheli.chan];
814
if (blheli.interface_mode[blheli.chan] != 0) {
815
blheli.connected[blheli.chan] = true;
816
}
817
return true;
818
}
819
820
bool AP_BLHeli::BL_SendCMDKeepAlive(void)
821
{
822
uint8_t sCMD[] = {CMD_KEEP_ALIVE, 0};
823
if (!BL_SendBuf(sCMD, 2)) {
824
return false;
825
}
826
if (BL_GetACK() != brERRORCOMMAND) {
827
return false;
828
}
829
return true;
830
}
831
832
bool AP_BLHeli::BL_PageErase(void)
833
{
834
if (BL_SendCMDSetAddress()) {
835
uint8_t sCMD[] = {CMD_ERASE_FLASH, 0x01};
836
if (!BL_SendBuf(sCMD, 2)) {
837
return false;
838
}
839
return BL_GetACK(3000) == brSUCCESS;
840
}
841
return false;
842
}
843
844
void AP_BLHeli::BL_SendCMDRunRestartBootloader(void)
845
{
846
uint8_t sCMD[] = {RestartBootloader, 0};
847
blheli.deviceInfo[blheli.chan][0] = 1;
848
BL_SendBuf(sCMD, 2);
849
}
850
851
uint8_t AP_BLHeli::BL_SendCMDSetBuffer(const uint8_t *buf, uint16_t nbytes)
852
{
853
uint8_t sCMD[] = {CMD_SET_BUFFER, 0, uint8_t(nbytes>>8), uint8_t(nbytes&0xff)};
854
if (!BL_SendBuf(sCMD, 4)) {
855
return false;
856
}
857
uint8_t ack;
858
if ((ack = BL_GetACK()) != brNONE) {
859
debug("BL_SendCMDSetBuffer ack failed 0x%02x", ack);
860
blheli.ack = ACK_D_GENERAL_ERROR;
861
return false;
862
}
863
if (!BL_SendBuf(buf, nbytes)) {
864
debug("BL_SendCMDSetBuffer send failed");
865
blheli.ack = ACK_D_GENERAL_ERROR;
866
return false;
867
}
868
return (BL_GetACK(40) == brSUCCESS);
869
}
870
871
bool AP_BLHeli::BL_WriteA(uint8_t cmd, const uint8_t *buf, uint16_t nbytes, uint32_t timeout_ms)
872
{
873
if (BL_SendCMDSetAddress()) {
874
if (!BL_SendCMDSetBuffer(buf, nbytes)) {
875
blheli.ack = ACK_D_GENERAL_ERROR;
876
return false;
877
}
878
uint8_t sCMD[] = {cmd, 0x01};
879
if (!BL_SendBuf(sCMD, 2)) {
880
return false;
881
}
882
return (BL_GetACK(timeout_ms) == brSUCCESS);
883
}
884
blheli.ack = ACK_D_GENERAL_ERROR;
885
return false;
886
}
887
888
uint8_t AP_BLHeli::BL_WriteFlash(const uint8_t *buf, uint16_t n)
889
{
890
return BL_WriteA(CMD_PROG_FLASH, buf, n, 500);
891
}
892
893
bool AP_BLHeli::BL_VerifyFlash(const uint8_t *buf, uint16_t n)
894
{
895
if (BL_SendCMDSetAddress()) {
896
if (!BL_SendCMDSetBuffer(buf, n)) {
897
return false;
898
}
899
uint8_t sCMD[] = {CMD_VERIFY_FLASH_ARM, 0x01};
900
if (!BL_SendBuf(sCMD, 2)) {
901
return false;
902
}
903
uint8_t ack = BL_GetACK(40);
904
switch (ack) {
905
case brSUCCESS:
906
blheli.ack = ACK_OK;
907
break;
908
case brERRORVERIFY:
909
blheli.ack = ACK_I_VERIFY_ERROR;
910
break;
911
default:
912
blheli.ack = ACK_D_GENERAL_ERROR;
913
break;
914
}
915
return true;
916
}
917
return false;
918
}
919
920
/*
921
process a blheli 4way command from GCS
922
*/
923
void AP_BLHeli::blheli_process_command(void)
924
{
925
debug("BLHeli cmd 0x%02x len=%u", blheli.command, blheli.param_len);
926
blheli.ack = ACK_OK;
927
switch (blheli.command) {
928
case cmd_InterfaceTestAlive: {
929
debug("cmd_InterfaceTestAlive");
930
BL_SendCMDKeepAlive();
931
if (blheli.ack != ACK_OK) {
932
setDisconnected();
933
}
934
uint8_t b = 0;
935
blheli_send_reply(&b, 1);
936
break;
937
}
938
case cmd_ProtocolGetVersion: {
939
debug("cmd_ProtocolGetVersion");
940
uint8_t buf[1];
941
buf[0] = SERIAL_4WAY_PROTOCOL_VER;
942
blheli_send_reply(buf, sizeof(buf));
943
break;
944
}
945
case cmd_InterfaceGetName: {
946
debug("cmd_InterfaceGetName");
947
uint8_t buf[5] = { 4, 'A', 'R', 'D', 'U' };
948
blheli_send_reply(buf, sizeof(buf));
949
break;
950
}
951
case cmd_InterfaceGetVersion: {
952
debug("cmd_InterfaceGetVersion");
953
uint8_t buf[2] = { SERIAL_4WAY_VERSION_HI, SERIAL_4WAY_VERSION_LO };
954
blheli_send_reply(buf, sizeof(buf));
955
break;
956
}
957
case cmd_InterfaceExit: {
958
debug("cmd_InterfaceExit");
959
msp.escMode = PROTOCOL_NONE;
960
uint8_t b = 0;
961
blheli_send_reply(&b, 1);
962
hal.rcout->serial_end();
963
serial_start_ms = 0;
964
if (motors_disabled) {
965
motors_disabled = false;
966
SRV_Channels::set_disabled_channel_mask(motors_disabled_mask);
967
}
968
if (uart_locked) {
969
debug("Unlocked UART");
970
uart->lock_port(0, 0);
971
uart_locked = false;
972
}
973
memset(blheli.connected, 0, sizeof(blheli.connected));
974
break;
975
}
976
case cmd_DeviceReset: {
977
debug("cmd_DeviceReset(%u)", unsigned(blheli.buf[0]));
978
if (blheli.buf[0] >= num_motors) {
979
debug("bad reset channel %u", blheli.buf[0]);
980
blheli.ack = ACK_I_INVALID_CHANNEL;
981
blheli_send_reply(&blheli.buf[0], 1);
982
break;
983
}
984
blheli.chan = blheli.buf[0];
985
switch (blheli.interface_mode[blheli.chan]) {
986
case imSIL_BLB:
987
case imATM_BLB:
988
case imARM_BLB:
989
BL_SendCMDRunRestartBootloader();
990
break;
991
case imSK:
992
break;
993
}
994
blheli_send_reply(&blheli.chan, 1);
995
setDisconnected();
996
break;
997
}
998
999
case cmd_DeviceInitFlash: {
1000
debug("cmd_DeviceInitFlash(%u)", unsigned(blheli.buf[0]));
1001
if (blheli.buf[0] >= num_motors) {
1002
debug("bad channel %u", blheli.buf[0]);
1003
blheli.ack = ACK_I_INVALID_CHANNEL;
1004
blheli_send_reply(&blheli.buf[0], 1);
1005
break;
1006
}
1007
blheli.chan = blheli.buf[0];
1008
blheli.ack = ACK_OK;
1009
BL_ConnectEx();
1010
uint8_t buf[4] = {blheli.deviceInfo[blheli.chan][0],
1011
blheli.deviceInfo[blheli.chan][1],
1012
blheli.deviceInfo[blheli.chan][2],
1013
blheli.deviceInfo[blheli.chan][3]}; // device ID
1014
blheli_send_reply(buf, sizeof(buf));
1015
break;
1016
}
1017
1018
case cmd_InterfaceSetMode: {
1019
debug("cmd_InterfaceSetMode(%u)", unsigned(blheli.buf[0]));
1020
blheli.interface_mode[blheli.chan] = blheli.buf[0];
1021
blheli_send_reply(&blheli.interface_mode[blheli.chan], 1);
1022
break;
1023
}
1024
1025
case cmd_DeviceRead: {
1026
uint16_t nbytes = blheli.buf[0]?blheli.buf[0]:256;
1027
debug("cmd_DeviceRead(%u) n=%u", blheli.chan, nbytes);
1028
uint8_t buf[nbytes];
1029
uint8_t cmd = blheli.interface_mode[blheli.chan]==imATM_BLB?CMD_READ_FLASH_ATM:CMD_READ_FLASH_SIL;
1030
if (!BL_ReadA(cmd, buf, nbytes)) {
1031
nbytes = 1;
1032
}
1033
blheli_send_reply(buf, nbytes);
1034
break;
1035
}
1036
1037
case cmd_DevicePageErase: {
1038
uint8_t page = blheli.buf[0];
1039
debug("cmd_DevicePageErase(%u) im=%u", page, blheli.interface_mode[blheli.chan]);
1040
switch (blheli.interface_mode[blheli.chan]) {
1041
case imSIL_BLB:
1042
case imARM_BLB: {
1043
if (blheli.interface_mode[blheli.chan] == imARM_BLB) {
1044
// Address =Page * 1024
1045
blheli.address = page << 10;
1046
} else {
1047
// Address =Page * 512
1048
blheli.address = page << 9;
1049
}
1050
debug("ARM PageErase 0x%04x", blheli.address);
1051
BL_PageErase();
1052
blheli.address = 0;
1053
blheli_send_reply(&page, 1);
1054
break;
1055
}
1056
default:
1057
blheli.ack = ACK_I_INVALID_CMD;
1058
blheli_send_reply(&page, 1);
1059
break;
1060
}
1061
break;
1062
}
1063
1064
case cmd_DeviceWrite: {
1065
uint16_t nbytes = blheli.param_len;
1066
debug("cmd_DeviceWrite n=%u im=%u", nbytes, blheli.interface_mode[blheli.chan]);
1067
uint8_t buf[nbytes];
1068
memcpy(buf, blheli.buf, nbytes);
1069
switch (blheli.interface_mode[blheli.chan]) {
1070
case imSIL_BLB:
1071
case imATM_BLB:
1072
case imARM_BLB: {
1073
BL_WriteFlash(buf, nbytes);
1074
break;
1075
}
1076
case imSK: {
1077
debug("Unsupported flash mode imSK");
1078
break;
1079
}
1080
}
1081
uint8_t b=0;
1082
blheli_send_reply(&b, 1);
1083
break;
1084
}
1085
1086
case cmd_DeviceVerify: {
1087
uint16_t nbytes = blheli.param_len;
1088
debug("cmd_DeviceWrite n=%u im=%u", nbytes, blheli.interface_mode[blheli.chan]);
1089
switch (blheli.interface_mode[blheli.chan]) {
1090
case imARM_BLB: {
1091
uint8_t buf[nbytes];
1092
memcpy(buf, blheli.buf, nbytes);
1093
BL_VerifyFlash(buf, nbytes);
1094
break;
1095
}
1096
default:
1097
blheli.ack = ACK_I_INVALID_CMD;
1098
break;
1099
}
1100
uint8_t b=0;
1101
blheli_send_reply(&b, 1);
1102
break;
1103
}
1104
1105
case cmd_DeviceReadEEprom: {
1106
uint16_t nbytes = blheli.buf[0]?blheli.buf[0]:256;
1107
uint8_t buf[nbytes];
1108
debug("cmd_DeviceReadEEprom n=%u im=%u", nbytes, blheli.interface_mode[blheli.chan]);
1109
switch (blheli.interface_mode[blheli.chan]) {
1110
case imATM_BLB: {
1111
if (!BL_ReadA(CMD_READ_EEPROM, buf, nbytes)) {
1112
blheli.ack = ACK_D_GENERAL_ERROR;
1113
}
1114
break;
1115
}
1116
default:
1117
blheli.ack = ACK_I_INVALID_CMD;
1118
break;
1119
}
1120
if (blheli.ack != ACK_OK) {
1121
nbytes = 1;
1122
buf[0] = 0;
1123
}
1124
blheli_send_reply(buf, nbytes);
1125
break;
1126
}
1127
1128
case cmd_DeviceWriteEEprom: {
1129
uint16_t nbytes = blheli.param_len;
1130
uint8_t buf[nbytes];
1131
memcpy(buf, blheli.buf, nbytes);
1132
debug("cmd_DeviceWriteEEprom n=%u im=%u", nbytes, blheli.interface_mode[blheli.chan]);
1133
switch (blheli.interface_mode[blheli.chan]) {
1134
case imATM_BLB:
1135
BL_WriteA(CMD_PROG_EEPROM, buf, nbytes, 3000);
1136
break;
1137
default:
1138
blheli.ack = ACK_D_GENERAL_ERROR;
1139
break;
1140
}
1141
uint8_t b = 0;
1142
blheli_send_reply(&b, 1);
1143
break;
1144
}
1145
1146
case cmd_DeviceEraseAll:
1147
case cmd_DeviceC2CK_LOW:
1148
default:
1149
// ack=unknown command
1150
blheli.ack = ACK_I_INVALID_CMD;
1151
debug("Unknown BLHeli protocol 0x%02x", blheli.command);
1152
uint8_t b = 0;
1153
blheli_send_reply(&b, 1);
1154
break;
1155
}
1156
}
1157
1158
/*
1159
process an input byte, return true if we have received a whole
1160
packet with correct CRC
1161
*/
1162
bool AP_BLHeli::process_input(uint8_t b)
1163
{
1164
bool valid_packet = false;
1165
1166
if (msp.escMode == PROTOCOL_4WAY && blheli.state == BLHELI_IDLE && b == '$') {
1167
debug("Change to MSP mode");
1168
msp.escMode = PROTOCOL_NONE;
1169
hal.rcout->serial_end();
1170
serial_start_ms = 0;
1171
}
1172
if (msp.escMode != PROTOCOL_4WAY && msp.state == MSP_IDLE && b == '/') {
1173
debug("Change to BLHeli mode");
1174
memset(blheli.connected, 0, sizeof(blheli.connected));
1175
msp.escMode = PROTOCOL_4WAY;
1176
}
1177
if (msp.escMode == PROTOCOL_4WAY) {
1178
blheli_4way_process_byte(b);
1179
} else {
1180
msp_process_byte(b);
1181
}
1182
if (msp.escMode == PROTOCOL_4WAY) {
1183
if (blheli.state == BLHELI_COMMAND_RECEIVED) {
1184
valid_packet = true;
1185
last_valid_ms = AP_HAL::millis();
1186
if (uart->lock_port(BLHELI_UART_LOCK_KEY, 0)) {
1187
uart_locked = true;
1188
}
1189
blheli_process_command();
1190
blheli.state = BLHELI_IDLE;
1191
msp.state = MSP_IDLE;
1192
}
1193
} else if (msp.state == MSP_COMMAND_RECEIVED) {
1194
if (msp.packetType == MSP_PACKET_COMMAND) {
1195
valid_packet = true;
1196
if (uart->lock_port(BLHELI_UART_LOCK_KEY, 0)) {
1197
uart_locked = true;
1198
}
1199
last_valid_ms = AP_HAL::millis();
1200
msp_process_command();
1201
}
1202
msp.state = MSP_IDLE;
1203
blheli.state = BLHELI_IDLE;
1204
}
1205
1206
return valid_packet;
1207
}
1208
1209
/*
1210
protocol handler for detecting BLHeli input
1211
*/
1212
bool AP_BLHeli::protocol_handler(uint8_t b, AP_HAL::UARTDriver *_uart)
1213
{
1214
uart = _uart;
1215
if (hal.util->get_soft_armed()) {
1216
// don't allow MSP control when armed
1217
return false;
1218
}
1219
return process_input(b);
1220
}
1221
1222
/*
1223
run a connection test to the ESCs. This is used to test the
1224
operation of the BLHeli ESC protocol
1225
*/
1226
void AP_BLHeli::run_connection_test(uint8_t chan)
1227
{
1228
run_test.set_and_notify(0);
1229
debug_uart = hal.console;
1230
uint8_t saved_chan = blheli.chan;
1231
if (chan >= num_motors) {
1232
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ESC: bad channel %u", chan);
1233
return;
1234
}
1235
blheli.chan = chan;
1236
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ESC: Running test on channel %u", blheli.chan);
1237
bool passed = false;
1238
for (uint8_t tries=0; tries<5; tries++) {
1239
EXPECT_DELAY_MS(3000);
1240
blheli.ack = ACK_OK;
1241
setDisconnected();
1242
if (BL_ConnectEx()) {
1243
uint8_t buf[256];
1244
uint8_t cmd = blheli.interface_mode[blheli.chan]==imATM_BLB?CMD_READ_FLASH_ATM:CMD_READ_FLASH_SIL;
1245
passed = true;
1246
blheli.address = blheli.interface_mode[blheli.chan]==imATM_BLB?0:0x7c00;
1247
passed &= BL_ReadA(cmd, buf, sizeof(buf));
1248
if (blheli.interface_mode[blheli.chan]==imARM_BLB) {
1249
if (passed) {
1250
// read status structure
1251
blheli.address = esc_status_addr;
1252
passed &= BL_SendCMDSetAddress();
1253
}
1254
if (passed) {
1255
struct esc_status status;
1256
passed &= BL_ReadA(CMD_READ_FLASH_SIL, (uint8_t *)&status, sizeof(status));
1257
}
1258
}
1259
BL_SendCMDRunRestartBootloader();
1260
break;
1261
}
1262
}
1263
hal.rcout->serial_end();
1264
SRV_Channels::set_disabled_channel_mask(motors_disabled_mask);
1265
motors_disabled = false;
1266
serial_start_ms = 0;
1267
blheli.chan = saved_chan;
1268
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ESC: Test %s", passed?"PASSED":"FAILED");
1269
debug_uart = nullptr;
1270
}
1271
1272
/*
1273
update BLHeli
1274
*/
1275
void AP_BLHeli::update(void)
1276
{
1277
bool motor_control_active = false;
1278
for (uint8_t i = 0; i < num_motors; i++) {
1279
bool reversed = ((1U<< motor_map[i]) & channel_reversible_mask.get()) != 0;
1280
if (hal.rcout->read( motor_map[i]) != (reversed ? 1500 : 1000)) {
1281
motor_control_active = true;
1282
}
1283
}
1284
1285
uint32_t now = AP_HAL::millis();
1286
if (initialised && uart_locked &&
1287
((timeout_sec && now - last_valid_ms > uint32_t(timeout_sec.get())*1000U) ||
1288
(motor_control_active && now - last_valid_ms > MOTOR_ACTIVE_TIMEOUT))) {
1289
// we're not processing requests any more, shutdown serial
1290
// output
1291
if (serial_start_ms) {
1292
hal.rcout->serial_end();
1293
serial_start_ms = 0;
1294
}
1295
if (motors_disabled) {
1296
motors_disabled = false;
1297
SRV_Channels::set_disabled_channel_mask(motors_disabled_mask);
1298
}
1299
if (uart != nullptr) {
1300
debug("Unlocked UART");
1301
uart->lock_port(0, 0);
1302
uart_locked = false;
1303
}
1304
if (motor_control_active) {
1305
for (uint8_t i = 0; i < num_motors; i++) {
1306
bool reversed = ((1U<<motor_map[i]) & channel_reversible_mask.get()) != 0;
1307
hal.rcout->write(motor_map[i], reversed ? 1500 : 1000);
1308
}
1309
}
1310
}
1311
1312
if (initialised || (channel_mask.get() == 0 && channel_auto.get() == 0)) {
1313
if (initialised && run_test.get() > 0) {
1314
run_connection_test(run_test.get() - 1);
1315
}
1316
}
1317
}
1318
1319
/*
1320
Initialize BLHeli, called by SRV_Channels::init()
1321
Used to install protocol handler
1322
The motor mask of enabled motors can be passed in
1323
*/
1324
void AP_BLHeli::init(uint32_t mask, AP_HAL::RCOutput::output_mode otype)
1325
{
1326
initialised = true;
1327
1328
run_test.set_and_notify(0);
1329
1330
#if HAL_GCS_ENABLED
1331
// only install pass-thru protocol handler if either auto or the motor mask are set
1332
if (channel_mask.get() != 0 || channel_auto.get() != 0) {
1333
if (last_control_port > 0 && last_control_port != control_port) {
1334
gcs().install_alternative_protocol((mavlink_channel_t)(MAVLINK_COMM_0+last_control_port), nullptr);
1335
last_control_port = -1;
1336
}
1337
if (gcs().install_alternative_protocol((mavlink_channel_t)(MAVLINK_COMM_0+control_port),
1338
FUNCTOR_BIND_MEMBER(&AP_BLHeli::protocol_handler,
1339
bool, uint8_t, AP_HAL::UARTDriver *))) {
1340
debug("BLHeli installed on port %u", (unsigned)control_port);
1341
last_control_port = control_port;
1342
}
1343
}
1344
#endif // HAL_GCS_ENABLED
1345
1346
#if HAL_WITH_IO_MCU
1347
if (AP_BoardConfig::io_enabled()) {
1348
// with IOMCU the local (FMU) channels start at 8
1349
chan_offset = 8;
1350
}
1351
#endif
1352
1353
mask |= uint32_t(channel_mask.get());
1354
1355
/*
1356
allow mode override - this makes it possible to use DShot for
1357
rovers and subs, plus for quadplane fwd motors
1358
*/
1359
// +1 converts from AP_Motors::pwm_type to AP_HAL::RCOutput::output_mode and saves doing a param conversion
1360
// this is the only use of the param, but this is still a bit of a hack
1361
const int16_t type = output_type.get() + 1;
1362
if (otype == AP_HAL::RCOutput::MODE_PWM_NONE) {
1363
otype = ((type > AP_HAL::RCOutput::MODE_PWM_NONE) && (type < AP_HAL::RCOutput::MODE_NEOPIXEL)) ? AP_HAL::RCOutput::output_mode(type) : AP_HAL::RCOutput::MODE_PWM_NONE;
1364
}
1365
switch (otype) {
1366
case AP_HAL::RCOutput::MODE_PWM_ONESHOT:
1367
case AP_HAL::RCOutput::MODE_PWM_ONESHOT125:
1368
case AP_HAL::RCOutput::MODE_PWM_BRUSHED:
1369
case AP_HAL::RCOutput::MODE_PWM_DSHOT150:
1370
case AP_HAL::RCOutput::MODE_PWM_DSHOT300:
1371
case AP_HAL::RCOutput::MODE_PWM_DSHOT600:
1372
case AP_HAL::RCOutput::MODE_PWM_DSHOT1200:
1373
if (mask) {
1374
hal.rcout->set_output_mode(mask, otype);
1375
}
1376
break;
1377
default:
1378
break;
1379
}
1380
1381
uint32_t digital_mask = 0;
1382
// setting the digital mask changes the min/max PWM values
1383
// it's important that this is NOT done for non-digital channels as otherwise
1384
// PWM min can result in motors turning. set for individual overrides first
1385
if (mask && hal.rcout->is_dshot_protocol(otype)) {
1386
digital_mask = mask;
1387
}
1388
1389
#if APM_BUILD_COPTER_OR_HELI || APM_BUILD_TYPE(APM_BUILD_ArduPlane) || APM_BUILD_TYPE(APM_BUILD_Rover)
1390
/*
1391
plane and copter can use AP_Motors to get an automatic mask
1392
*/
1393
#if APM_BUILD_TYPE(APM_BUILD_Rover)
1394
AP_MotorsUGV *motors = AP::motors_ugv();
1395
#else
1396
AP_Motors *motors = AP::motors();
1397
#endif
1398
if (motors) {
1399
uint32_t motormask = motors->get_motor_mask();
1400
// set the rest of the digital channels
1401
if (motors->is_digital_pwm_type()) {
1402
digital_mask |= motormask;
1403
}
1404
mask |= motormask;
1405
}
1406
#endif
1407
// tell SRV_Channels about ESC capabilities
1408
SRV_Channels::set_digital_outputs(digital_mask, uint32_t(channel_reversible_mask.get()) & digital_mask);
1409
// the dshot ESC type is required in order to send the reversed/reversible dshot command correctly
1410
hal.rcout->set_dshot_esc_type(SRV_Channels::get_dshot_esc_type());
1411
hal.rcout->set_reversible_mask(uint32_t(channel_reversible_mask.get()) & digital_mask);
1412
hal.rcout->set_reversed_mask(uint32_t(channel_reversed_mask.get()) & digital_mask);
1413
#ifdef HAL_WITH_BIDIR_DSHOT
1414
// possibly enable bi-directional dshot
1415
hal.rcout->set_motor_poles(motor_poles);
1416
#endif
1417
#if defined(HAL_WITH_BIDIR_DSHOT) || HAL_WITH_IO_MCU_BIDIR_DSHOT
1418
hal.rcout->set_bidir_dshot_mask(uint32_t(channel_bidir_dshot_mask.get()) & digital_mask);
1419
#endif
1420
// add motors from channel mask
1421
for (uint8_t i=0; i<16 && num_motors < max_motors; i++) {
1422
if (mask & (1U<<i)) {
1423
motor_map[num_motors] = i;
1424
num_motors++;
1425
}
1426
}
1427
motor_mask = mask;
1428
debug("ESC: %u motors mask=0x%08lx", num_motors, mask);
1429
1430
// check if we have a combination of reversible and normal
1431
mixed_type = (mask != (mask & channel_reversible_mask.get())) && (channel_reversible_mask.get() != 0);
1432
1433
if (num_motors != 0 && telem_rate > 0) {
1434
AP_SerialManager *serial_manager = AP_SerialManager::get_singleton();
1435
if (serial_manager) {
1436
telem_uart = serial_manager->find_serial(AP_SerialManager::SerialProtocol_ESCTelemetry,0);
1437
}
1438
}
1439
}
1440
1441
/*
1442
read an ESC telemetry packet
1443
*/
1444
void AP_BLHeli::read_telemetry_packet(void)
1445
{
1446
#if HAL_WITH_ESC_TELEM
1447
uint8_t buf[telem_packet_size];
1448
if (telem_uart->read(buf, telem_packet_size) < telem_packet_size) {
1449
// short read, we should have 10 bytes ready when this function is called
1450
return;
1451
}
1452
1453
// calculate crc
1454
uint8_t crc = 0;
1455
for (uint8_t i=0; i<telem_packet_size-1; i++) {
1456
crc = crc8_dvb(buf[i], crc, 0x07);
1457
}
1458
1459
if (buf[telem_packet_size-1] != crc) {
1460
// bad crc
1461
debug("Bad CRC on %u", last_telem_esc);
1462
return;
1463
}
1464
// record the previous rpm so that we can slew to the new one
1465
uint16_t new_rpm = ((buf[7]<<8) | buf[8]) * 200 / motor_poles;
1466
const uint8_t motor_idx = motor_map[last_telem_esc];
1467
// we have received valid data, mark the ESC as now active
1468
hal.rcout->set_active_escs_mask(1<<motor_idx);
1469
update_rpm(motor_idx, new_rpm);
1470
1471
TelemetryData t {
1472
.temperature_cdeg = int16_t(buf[0] * 100),
1473
.voltage = float(uint16_t((buf[1]<<8) | buf[2])) * 0.01,
1474
.current = float(uint16_t((buf[3]<<8) | buf[4])) * 0.01,
1475
.consumption_mah = float(uint16_t((buf[5]<<8) | buf[6])),
1476
};
1477
1478
update_telem_data(motor_idx, t,
1479
AP_ESC_Telem_Backend::TelemetryType::CURRENT
1480
| AP_ESC_Telem_Backend::TelemetryType::VOLTAGE
1481
| AP_ESC_Telem_Backend::TelemetryType::CONSUMPTION
1482
| AP_ESC_Telem_Backend::TelemetryType::TEMPERATURE);
1483
1484
if (debug_level >= 2) {
1485
uint16_t trpm = new_rpm;
1486
if (has_bidir_dshot(last_telem_esc)) {
1487
trpm = hal.rcout->get_erpm(motor_idx);
1488
if (trpm != 0xFFFF) {
1489
trpm = trpm * 200 / motor_poles;
1490
}
1491
}
1492
DEV_PRINTF("ESC[%u] T=%u V=%f C=%f con=%f RPM=%u e=%.1f t=%u\n",
1493
last_telem_esc,
1494
t.temperature_cdeg,
1495
t.voltage,
1496
t.current,
1497
t.consumption_mah,
1498
trpm, hal.rcout->get_erpm_error_rate(motor_idx), (unsigned)AP_HAL::millis());
1499
}
1500
#endif // HAL_WITH_ESC_TELEM
1501
}
1502
1503
/*
1504
log bidir telemetry - only called if BLH telemetry is not active
1505
*/
1506
void AP_BLHeli::log_bidir_telemetry(void)
1507
{
1508
uint32_t now = AP_HAL::millis();
1509
1510
if (debug_level >= 2 && now - last_log_ms[last_telem_esc] > 100) {
1511
if (has_bidir_dshot(last_telem_esc)) {
1512
const uint8_t motor_idx = motor_map[last_telem_esc];
1513
uint16_t trpm = hal.rcout->get_erpm(motor_idx);
1514
if (trpm != 0xFFFF) { // don't log invalid values as they are never used
1515
trpm = trpm * 200 / motor_poles;
1516
}
1517
1518
if (trpm > 0) {
1519
last_log_ms[last_telem_esc] = now;
1520
DEV_PRINTF("ESC[%u] RPM=%u e=%.1f t=%u\n", last_telem_esc, trpm, hal.rcout->get_erpm_error_rate(motor_idx), (unsigned)AP_HAL::millis());
1521
}
1522
}
1523
}
1524
1525
if (!SRV_Channels::have_digital_outputs()) {
1526
return;
1527
}
1528
1529
// ask the next ESC for telemetry
1530
uint8_t idx_pos = last_telem_esc;
1531
uint8_t idx = (idx_pos + 1) % num_motors;
1532
for (; idx != idx_pos; idx = (idx + 1) % num_motors) {
1533
if (SRV_Channels::have_digital_outputs(1U << motor_map[idx])) {
1534
break;
1535
}
1536
}
1537
if (SRV_Channels::have_digital_outputs(1U << motor_map[idx])) {
1538
last_telem_esc = idx;
1539
}
1540
}
1541
1542
/*
1543
update BLHeli telemetry handling
1544
This is called on push() in SRV_Channels
1545
*/
1546
void AP_BLHeli::update_telemetry(void)
1547
{
1548
#ifdef HAL_WITH_BIDIR_DSHOT
1549
// we might only have bi-dir dshot
1550
if (channel_bidir_dshot_mask.get() != 0 && !telem_uart) {
1551
log_bidir_telemetry();
1552
}
1553
#endif
1554
if (!telem_uart || !SRV_Channels::have_digital_outputs()) {
1555
return;
1556
}
1557
uint32_t now = AP_HAL::micros();
1558
uint32_t telem_rate_us = 1000000U / uint32_t(telem_rate.get() * num_motors);
1559
if (telem_rate_us < 2000) {
1560
// make sure we have a gap between frames
1561
telem_rate_us = 2000;
1562
}
1563
if (!telem_uart_started) {
1564
// we need to use begin() here to ensure the correct thread owns the uart
1565
telem_uart->begin(115200);
1566
telem_uart_started = true;
1567
}
1568
1569
uint32_t nbytes = telem_uart->available();
1570
1571
if (nbytes > telem_packet_size) {
1572
// if we have more than 10 bytes then we don't know which ESC
1573
// they are from. Throw them all away
1574
telem_uart->discard_input();
1575
return;
1576
}
1577
if (nbytes > 0 &&
1578
nbytes < telem_packet_size &&
1579
(last_telem_byte_read_us == 0 ||
1580
now - last_telem_byte_read_us < 1000)) {
1581
// wait a bit longer, we don't have enough bytes yet
1582
if (last_telem_byte_read_us == 0) {
1583
last_telem_byte_read_us = now;
1584
}
1585
return;
1586
}
1587
if (nbytes > 0 && nbytes < telem_packet_size) {
1588
// we've waited long enough, discard bytes if we don't have 10 yet
1589
telem_uart->discard_input();
1590
return;
1591
}
1592
if (nbytes == telem_packet_size) {
1593
// we have a full packet ready to parse
1594
read_telemetry_packet();
1595
last_telem_byte_read_us = 0;
1596
}
1597
if (now - last_telem_request_us >= telem_rate_us) {
1598
// ask the next ESC for telemetry
1599
uint8_t idx_pos = last_telem_esc;
1600
uint8_t idx = (idx_pos + 1) % num_motors;
1601
for (; idx != idx_pos; idx = (idx + 1) % num_motors) {
1602
if (SRV_Channels::have_digital_outputs(1U << motor_map[idx])) {
1603
break;
1604
}
1605
}
1606
uint32_t mask = 1U << motor_map[idx];
1607
if (SRV_Channels::have_digital_outputs(mask)) {
1608
hal.rcout->set_telem_request_mask(mask);
1609
last_telem_esc = idx;
1610
last_telem_request_us = now;
1611
}
1612
}
1613
}
1614
1615
#endif // HAVE_AP_BLHELI_SUPPORT
1616
1617