Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Path: blob/master/libraries/AP_BLHeli/AP_BLHeli.cpp
Views: 1798
/*1This program is free software: you can redistribute it and/or modify2it under the terms of the GNU General Public License as published by3the Free Software Foundation, either version 3 of the License, or4(at your option) any later version.56This program is distributed in the hope that it will be useful,7but WITHOUT ANY WARRANTY; without even the implied warranty of8MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9GNU General Public License for more details.1011You should have received a copy of the GNU General Public License12along with this program. If not, see <http://www.gnu.org/licenses/>.13*/14/*15implementation of MSP and BLHeli-4way protocols for pass-through ESC16calibration and firmware update1718With thanks to betaflight for a great reference19implementation. Several of the functions below are based on20betaflight equivalent functions21*/2223#include "AP_BLHeli.h"2425#if HAVE_AP_BLHELI_SUPPORT2627#if CONFIG_HAL_BOARD == HAL_BOARD_CHIBIOS28#include <hal.h>29#endif3031#include <AP_Math/crc.h>32#include <AP_Vehicle/AP_Vehicle_Type.h>33#if APM_BUILD_TYPE(APM_BUILD_Rover)34#include <AR_Motors/AP_MotorsUGV.h>35#else36#include <AP_Motors/AP_Motors_Class.h>37#endif38#include <GCS_MAVLink/GCS_MAVLink.h>39#include <GCS_MAVLink/GCS.h>40#include <AP_SerialManager/AP_SerialManager.h>41#include <AP_BoardConfig/AP_BoardConfig.h>42#include <AP_ESC_Telem/AP_ESC_Telem.h>43#include <SRV_Channel/SRV_Channel.h>4445extern const AP_HAL::HAL& hal;4647#define debug(fmt, args ...) do { if (debug_level) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ESC: " fmt, ## args); } } while (0)4849// key for locking UART for exclusive use. This prevents any other writes from corrupting50// the MSP protocol on hal.console51#define BLHELI_UART_LOCK_KEY 0x201804025253// if no packets are received for this time and motor control is active BLH will disconnect (stoping motors)54#define MOTOR_ACTIVE_TIMEOUT 10005556const AP_Param::GroupInfo AP_BLHeli::var_info[] = {57// @Param: MASK58// @DisplayName: BLHeli Channel Bitmask59// @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)60// @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 3261// @User: Advanced62// @RebootRequired: True63AP_GROUPINFO("MASK", 1, AP_BLHeli, channel_mask, 0),6465#if APM_BUILD_COPTER_OR_HELI || APM_BUILD_TYPE(APM_BUILD_ArduPlane) || APM_BUILD_TYPE(APM_BUILD_Rover)66// @Param: AUTO67// @DisplayName: BLHeli pass-thru auto-enable for multicopter motors68// @Description: If set to 1 this auto-enables BLHeli pass-thru support for all multicopter motors69// @Values: 0:Disabled,1:Enabled70// @User: Standard71// @RebootRequired: True72AP_GROUPINFO("AUTO", 2, AP_BLHeli, channel_auto, 0),73#endif7475// @Param: TEST76// @DisplayName: BLHeli internal interface test77// @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.78// @Values: 0:Disabled,1:TestMotor1,2:TestMotor2,3:TestMotor3,4:TestMotor4,5:TestMotor5,6:TestMotor6,7:TestMotor7,8:TestMotor879// @User: Advanced80AP_GROUPINFO("TEST", 3, AP_BLHeli, run_test, 0),8182// @Param: TMOUT83// @DisplayName: BLHeli protocol timeout84// @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 timeout85// @Units: s86// @Range: 0 30087// @User: Standard88AP_GROUPINFO("TMOUT", 4, AP_BLHeli, timeout_sec, 0),8990// @Param: TRATE91// @DisplayName: BLHeli telemetry rate92// @Description: This sets the rate in Hz for requesting telemetry from ESCs. It is the rate per ESC. Setting to zero disables telemetry requests93// @Units: Hz94// @Range: 0 50095// @User: Standard96AP_GROUPINFO("TRATE", 5, AP_BLHeli, telem_rate, 10),9798// @Param: DEBUG99// @DisplayName: BLHeli debug level100// @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.101// @Values: 0:Disabled,1:Enabled102// @User: Standard103AP_GROUPINFO("DEBUG", 6, AP_BLHeli, debug_level, 0),104105// @Param: OTYPE106// @DisplayName: BLHeli output type override107// @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.108// @Values: 0:None,1:OneShot,2:OneShot125,3:Brushed,4:DShot150,5:DShot300,6:DShot600,7:DShot1200109// @User: Advanced110// @RebootRequired: True111AP_GROUPINFO("OTYPE", 7, AP_BLHeli, output_type, 0),112113// @Param: PORT114// @DisplayName: Control port115// @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.116// @Values: 0:Console,1:Mavlink Serial Channel1,2:Mavlink Serial Channel2,3:Mavlink Serial Channel3,4:Mavlink Serial Channel4,5:Mavlink Serial Channel5117// @User: Advanced118AP_GROUPINFO("PORT", 8, AP_BLHeli, control_port, 0),119120// @Param: POLES121// @DisplayName: BLHeli Motor Poles122// @Description: This allows calculation of true RPM from ESC's eRPM. The default is 14.123// @Range: 1 127124// @User: Advanced125// @RebootRequired: True126AP_GROUPINFO("POLES", 9, AP_BLHeli, motor_poles, 14),127128// @Param: 3DMASK129// @DisplayName: BLHeli bitmask of 3D channels130// @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.131// @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 32132// @User: Advanced133// @RebootRequired: True134AP_GROUPINFO("3DMASK", 10, AP_BLHeli, channel_reversible_mask, 0),135136#if defined(HAL_WITH_BIDIR_DSHOT) || HAL_WITH_IO_MCU_BIDIR_DSHOT137// @Param: BDMASK138// @DisplayName: BLHeli bitmask of bi-directional dshot channels139// @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.140// @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 32141// @User: Advanced142// @RebootRequired: True143AP_GROUPINFO("BDMASK", 11, AP_BLHeli, channel_bidir_dshot_mask, 0),144#endif145// @Param: RVMASK146// @DisplayName: BLHeli bitmask of reversed channels147// @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.148// @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 32149// @User: Advanced150// @RebootRequired: True151AP_GROUPINFO("RVMASK", 12, AP_BLHeli, channel_reversed_mask, 0),152153AP_GROUPEND154};155156#define RPM_SLEW_RATE 50157158AP_BLHeli *AP_BLHeli::_singleton;159160// constructor161AP_BLHeli::AP_BLHeli(void)162{163// set defaults from the parameter table164AP_Param::setup_object_defaults(this, var_info);165_singleton = this;166last_control_port = -1;167}168169/*170process one byte of serial input for MSP protocol171*/172bool AP_BLHeli::msp_process_byte(uint8_t c)173{174if (msp.state == MSP_IDLE) {175msp.escMode = PROTOCOL_NONE;176if (c == '$') {177msp.state = MSP_HEADER_START;178} else {179return false;180}181} else if (msp.state == MSP_HEADER_START) {182msp.state = (c == 'M') ? MSP_HEADER_M : MSP_IDLE;183} else if (msp.state == MSP_HEADER_M) {184msp.state = MSP_IDLE;185switch (c) {186case '<': // COMMAND187msp.packetType = MSP_PACKET_COMMAND;188msp.state = MSP_HEADER_ARROW;189break;190case '>': // REPLY191msp.packetType = MSP_PACKET_REPLY;192msp.state = MSP_HEADER_ARROW;193break;194default:195break;196}197} else if (msp.state == MSP_HEADER_ARROW) {198if (c > sizeof(msp.buf)) {199msp.state = MSP_IDLE;200} else {201msp.dataSize = c;202msp.offset = 0;203msp.checksum = 0;204msp.checksum ^= c;205msp.state = MSP_HEADER_SIZE;206}207} else if (msp.state == MSP_HEADER_SIZE) {208msp.cmdMSP = c;209msp.checksum ^= c;210msp.state = MSP_HEADER_CMD;211} else if (msp.state == MSP_HEADER_CMD && msp.offset < msp.dataSize) {212msp.checksum ^= c;213msp.buf[msp.offset++] = c;214} else if (msp.state == MSP_HEADER_CMD && msp.offset >= msp.dataSize) {215if (msp.checksum == c) {216msp.state = MSP_COMMAND_RECEIVED;217} else {218msp.state = MSP_IDLE;219}220}221return true;222}223224/*225update CRC state for blheli protocol226*/227void AP_BLHeli::blheli_crc_update(uint8_t c)228{229blheli.crc = crc_xmodem_update(blheli.crc, c);230}231232/*233process one byte of serial input for blheli 4way protocol234*/235bool AP_BLHeli::blheli_4way_process_byte(uint8_t c)236{237if (blheli.state == BLHELI_IDLE) {238if (c == cmd_Local_Escape) {239blheli.state = BLHELI_HEADER_START;240blheli.crc = 0;241blheli_crc_update(c);242} else {243return false;244}245} else if (blheli.state == BLHELI_HEADER_START) {246blheli.command = c;247blheli_crc_update(c);248blheli.state = BLHELI_HEADER_CMD;249} else if (blheli.state == BLHELI_HEADER_CMD) {250blheli.address = c<<8;251blheli.state = BLHELI_HEADER_ADDR_HIGH;252blheli_crc_update(c);253} else if (blheli.state == BLHELI_HEADER_ADDR_HIGH) {254blheli.address |= c;255blheli.state = BLHELI_HEADER_ADDR_LOW;256blheli_crc_update(c);257} else if (blheli.state == BLHELI_HEADER_ADDR_LOW) {258blheli.state = BLHELI_HEADER_LEN;259blheli.param_len = c?c:256;260blheli.offset = 0;261blheli_crc_update(c);262} else if (blheli.state == BLHELI_HEADER_LEN) {263blheli.buf[blheli.offset++] = c;264blheli_crc_update(c);265if (blheli.offset == blheli.param_len) {266blheli.state = BLHELI_CRC1;267}268} else if (blheli.state == BLHELI_CRC1) {269blheli.crc1 = c;270blheli.state = BLHELI_CRC2;271} else if (blheli.state == BLHELI_CRC2) {272uint16_t crc = blheli.crc1<<8 | c;273if (crc == blheli.crc) {274blheli.state = BLHELI_COMMAND_RECEIVED;275} else {276blheli.state = BLHELI_IDLE;277}278}279return true;280}281282283/*284send a MSP protocol ack285*/286void AP_BLHeli::msp_send_ack(uint8_t cmd)287{288msp_send_reply(cmd, 0, 0);289}290291/*292send a MSP protocol reply293*/294void AP_BLHeli::msp_send_reply(uint8_t cmd, const uint8_t *buf, uint8_t len)295{296uint8_t *b = &msp.buf[0];297*b++ = '$';298*b++ = 'M';299*b++ = '>';300*b++ = len;301*b++ = cmd;302// acks do not have a payload303if (len > 0) {304memcpy(b, buf, len);305}306b += len;307uint8_t c = 0;308for (uint8_t i=0; i<len+2; i++) {309c ^= msp.buf[i+3];310}311*b++ = c;312uart->write_locked(&msp.buf[0], len+6, BLHELI_UART_LOCK_KEY);313}314315void AP_BLHeli::putU16(uint8_t *b, uint16_t v)316{317b[0] = v;318b[1] = v >> 8;319}320321uint16_t AP_BLHeli::getU16(const uint8_t *b)322{323return b[0] | (b[1]<<8);324}325326void AP_BLHeli::putU32(uint8_t *b, uint32_t v)327{328b[0] = v;329b[1] = v >> 8;330b[2] = v >> 16;331b[3] = v >> 24;332}333334void AP_BLHeli::putU16_BE(uint8_t *b, uint16_t v)335{336b[0] = v >> 8;337b[1] = v;338}339340/*341process a MSP command from GCS342*/343void AP_BLHeli::msp_process_command(void)344{345debug("MSP cmd %u len=%u", msp.cmdMSP, msp.dataSize);346switch (msp.cmdMSP) {347case MSP_API_VERSION: {348debug("MSP_API_VERSION");349uint8_t buf[3] = { MSP_PROTOCOL_VERSION, API_VERSION_MAJOR, API_VERSION_MINOR };350msp_send_reply(msp.cmdMSP, buf, sizeof(buf));351break;352}353354case MSP_FC_VARIANT:355debug("MSP_FC_VARIANT");356msp_send_reply(msp.cmdMSP, (const uint8_t *)ARDUPILOT_IDENTIFIER, FLIGHT_CONTROLLER_IDENTIFIER_LENGTH);357break;358359/*360Notes:361version 3.3.1 adds a reply to MSP_SET_MOTOR which was missing362version 3.3.0 requires a workaround in blheli suite to handle MSP_SET_MOTOR without an ack363*/364case MSP_FC_VERSION: {365debug("MSP_FC_VERSION");366uint8_t version[3] = { 3, 3, 1 };367msp_send_reply(msp.cmdMSP, version, sizeof(version));368break;369}370case MSP_BOARD_INFO: {371debug("MSP_BOARD_INFO");372// send a generic 'ArduPilot ChibiOS' board type373uint8_t buf[7] = { 'A', 'R', 'C', 'H', 0, 0, 0 };374msp_send_reply(msp.cmdMSP, buf, sizeof(buf));375break;376}377378case MSP_BUILD_INFO: {379debug("MSP_BUILD_INFO");380// build date, build time, git version381uint8_t buf[26] {3820x4d, 0x61, 0x72, 0x20, 0x31, 0x36, 0x20, 0x32, 0x30,3830x31, 0x38, 0x30, 0x38, 0x3A, 0x34, 0x32, 0x3a, 0x32, 0x39,3840x62, 0x30, 0x66, 0x66, 0x39, 0x32, 0x38};385msp_send_reply(msp.cmdMSP, buf, sizeof(buf));386break;387}388389case MSP_REBOOT:390debug("MSP: ignoring reboot command, end serial comms");391hal.rcout->serial_end();392blheli.connected[blheli.chan] = false;393serial_start_ms = 0;394break;395396case MSP_UID:397// MCU identifier398debug("MSP_UID");399msp_send_reply(msp.cmdMSP, (const uint8_t *)UDID_START, 12);400break;401402// a literal "4" is used for the PWMType here to allow Rover403// to use the same number for the same protocol. At time of404// writing the AP_MotorsUGV::PWMType has not been unified with405// AP_Motors::PWMType.406case MSP_ADVANCED_CONFIG: {407debug("MSP_ADVANCED_CONFIG");408uint8_t buf[10];409buf[0] = 1; // gyro sync denom410buf[1] = 4; // pid process denom411buf[2] = 0; // use unsynced pwm412buf[3] = 4; // (uint8_t)AP_Motors::PWMType::DSHOT150;413putU16(&buf[4], 480); // motor PWM Rate414putU16(&buf[6], 450); // idle offset value415buf[8] = 0; // use 32kHz416buf[9] = 0; // motor PWM inversion417msp_send_reply(msp.cmdMSP, buf, sizeof(buf));418break;419}420421case MSP_FEATURE_CONFIG: {422debug("MSP_FEATURE_CONFIG");423uint8_t buf[4];424putU32(buf, (channel_reversible_mask.get() != 0) ? FEATURE_3D : 0); // from MSPFeatures enum425msp_send_reply(msp.cmdMSP, buf, sizeof(buf));426break;427}428429case MSP_STATUS: {430debug("MSP_STATUS");431uint8_t buf[21];432putU16(&buf[0], 1000); // loop time usec433putU16(&buf[2], 0); // i2c error count434putU16(&buf[4], 0x27); // available sensors435putU32(&buf[6], 0); // flight modes436buf[10] = 0; // pid profile index437putU16(&buf[11], 5); // system load percent438putU16(&buf[13], 0); // gyro cycle time439buf[15] = 0; // flight mode flags length440buf[16] = 18; // arming disable flags count441putU32(&buf[17], 0); // arming disable flags442msp_send_reply(msp.cmdMSP, buf, sizeof(buf));443break;444}445446case MSP_MOTOR_3D_CONFIG: {447debug("MSP_MOTOR_3D_CONFIG");448uint8_t buf[6];449putU16(&buf[0], 1406); // 3D deadband low450putU16(&buf[2], 1514); // 3D deadband high451putU16(&buf[4], 1460); // 3D neutral452msp_send_reply(msp.cmdMSP, buf, sizeof(buf));453break;454}455456case MSP_BATTERY_STATE: {457debug("MSP_BATTERY_STATE");458uint8_t buf[8];459buf[0] = 4; // cell count460putU16(&buf[1], 1500); // mAh461buf[3] = 16; // V462putU16(&buf[4], 1500); // mAh463putU16(&buf[6], 1); // A464msp_send_reply(msp.cmdMSP, buf, sizeof(buf));465break;466}467468case MSP_MOTOR_CONFIG: {469debug("MSP_MOTOR_CONFIG");470uint8_t buf[10];471putU16(&buf[0], 1030); // min throttle472putU16(&buf[2], 2000); // max throttle473putU16(&buf[4], 1000); // min command474// API 1.42475buf[6] = num_motors; // motorCount476buf[7] = motor_poles; // motorPoleCount477buf[8] = 0; // useDshotTelemetry478buf[9] = 0; // FEATURE_ESC_SENSOR479msp_send_reply(msp.cmdMSP, buf, sizeof(buf));480break;481}482483case MSP_MOTOR: {484debug("MSP_MOTOR");485// get the output going to each motor486uint8_t buf[16] {};487for (uint8_t i = 0; i < num_motors; i++) {488// if we have a mix of reversible and normal report a PWM of zero, this allows BLHeliSuite to conect489uint16_t v = mixed_type ? 0 : hal.rcout->read(motor_map[i]);490putU16(&buf[2*i], v);491debug("MOTOR %u val: %u",i,v);492}493msp_send_reply(msp.cmdMSP, buf, sizeof(buf));494break;495}496497case MSP_SET_MOTOR: {498debug("MSP_SET_MOTOR");499if (!mixed_type) {500// set the output to each motor501uint8_t nmotors = msp.dataSize / 2;502debug("MSP_SET_MOTOR %u", nmotors);503motors_disabled_mask = SRV_Channels::get_disabled_channel_mask();504SRV_Channels::set_disabled_channel_mask(0xFFFF);505motors_disabled = true;506EXPECT_DELAY_MS(1000);507hal.rcout->cork();508for (uint8_t i = 0; i < nmotors; i++) {509if (i >= num_motors) {510break;511}512uint16_t v = getU16(&msp.buf[i*2]);513debug("MSP_SET_MOTOR %u %u", i, v);514// map from a MSP value to a value in the range 1000 to 2000515uint16_t pwm = (v < 1000)?0:v;516hal.rcout->write(motor_map[i], pwm);517}518hal.rcout->push();519} else {520debug("mixed type, Motors Disabled");521}522msp_send_ack(msp.cmdMSP);523break;524}525526case MSP_SET_PASSTHROUGH: {527debug("MSP_SET_PASSTHROUGH");528if (msp.dataSize == 0) {529msp.escMode = PROTOCOL_4WAY;530} else if (msp.dataSize == 2) {531msp.escMode = (enum escProtocol)msp.buf[0];532msp.portIndex = msp.buf[1];533}534debug("escMode=%u portIndex=%u num_motors=%u", msp.escMode, msp.portIndex, num_motors);535uint8_t n = num_motors;536switch (msp.escMode) {537case PROTOCOL_4WAY:538break;539default:540n = 0;541hal.rcout->serial_end();542serial_start_ms = 0;543break;544}545// doing the serial setup here avoids delays when doing it on demand and makes546// BLHeliSuite considerably more reliable547EXPECT_DELAY_MS(1000);548if (!hal.rcout->serial_setup_output(motor_map[0], 19200, motor_mask)) {549msp_send_ack(ACK_D_GENERAL_ERROR);550break;551} else {552msp_send_reply(msp.cmdMSP, &n, 1);553}554break;555}556default:557debug("Unknown MSP command %u", msp.cmdMSP);558break;559}560}561562/*563send a blheli 4way protocol reply564*/565void AP_BLHeli::blheli_send_reply(const uint8_t *buf, uint16_t len)566{567uint8_t *b = &blheli.buf[0];568*b++ = cmd_Remote_Escape;569*b++ = blheli.command;570putU16_BE(b, blheli.address); b += 2;571*b++ = len==256?0:len;572memcpy(b, buf, len);573b += len;574*b++ = blheli.ack;575putU16_BE(b, crc_xmodem(&blheli.buf[0], len+6));576uart->write_locked(&blheli.buf[0], len+8, BLHELI_UART_LOCK_KEY);577debug("OutB(%u) 0x%02x ack=0x%02x", len+8, (unsigned)blheli.command, blheli.ack);578}579580/*581CRC used when talking to ESCs582*/583uint16_t AP_BLHeli::BL_CRC(const uint8_t *buf, uint16_t len)584{585uint16_t crc = 0;586while (len--) {587uint8_t xb = *buf++;588for (uint8_t i = 0; i < 8; i++) {589if (((xb & 0x01) ^ (crc & 0x0001)) !=0 ) {590crc = crc >> 1;591crc = crc ^ 0xA001;592} else {593crc = crc >> 1;594}595xb = xb >> 1;596}597}598return crc;599}600601bool AP_BLHeli::isMcuConnected(void)602{603return blheli.connected[blheli.chan];604}605606void AP_BLHeli::setDisconnected(void)607{608blheli.connected[blheli.chan] = false;609blheli.deviceInfo[blheli.chan][0] = 0;610blheli.deviceInfo[blheli.chan][1] = 0;611}612613/*614send a set of bytes to an RC output channel615*/616bool AP_BLHeli::BL_SendBuf(const uint8_t *buf, uint16_t len)617{618bool send_crc = isMcuConnected();619if (blheli.chan >= num_motors) {620return false;621}622EXPECT_DELAY_MS(1000);623if (!hal.rcout->serial_setup_output(motor_map[blheli.chan], 19200, motor_mask)) {624blheli.ack = ACK_D_GENERAL_ERROR;625return false;626}627if (serial_start_ms == 0) {628serial_start_ms = AP_HAL::millis();629}630uint32_t now = AP_HAL::millis();631if (serial_start_ms == 0 || now - serial_start_ms < 1000) {632/*633we've just started the interface. We want it idle for at634least 1 second before we start sending serial data.635*/636hal.scheduler->delay(1100);637}638memcpy(blheli.buf, buf, len);639uint16_t crc = BL_CRC(buf, len);640blheli.buf[len] = crc;641blheli.buf[len+1] = crc>>8;642if (!hal.rcout->serial_write_bytes(blheli.buf, len+(send_crc?2:0))) {643blheli.ack = ACK_D_GENERAL_ERROR;644return false;645}646// 19200 baud is 52us per bit - wait for half a bit between sending and receiving to avoid reading647// the end of the last sent bit by accident648hal.scheduler->delay_microseconds(26);649return true;650}651652/*653read bytes from the ESC connection654*/655bool AP_BLHeli::BL_ReadBuf(uint8_t *buf, uint16_t len)656{657bool check_crc = isMcuConnected() && len > 0;658uint16_t req_bytes = len+(check_crc?3:1);659EXPECT_DELAY_MS(1000);660uint16_t n = hal.rcout->serial_read_bytes(blheli.buf, req_bytes);661debug("BL_ReadBuf %u -> %u", len, n);662if (req_bytes != n) {663debug("short read");664blheli.ack = ACK_D_GENERAL_ERROR;665return false;666}667if (check_crc) {668uint16_t crc = BL_CRC(blheli.buf, len);669if ((crc & 0xff) != blheli.buf[len] ||670(crc >> 8) != blheli.buf[len+1]) {671debug("bad CRC");672blheli.ack = ACK_D_GENERAL_ERROR;673return false;674}675if (blheli.buf[len+2] != brSUCCESS) {676debug("bad ACK 0x%02x", blheli.buf[len+2]);677blheli.ack = ACK_D_GENERAL_ERROR;678return false;679}680} else {681if (blheli.buf[len] != brSUCCESS) {682debug("bad ACK1 0x%02x", blheli.buf[len]);683blheli.ack = ACK_D_GENERAL_ERROR;684return false;685}686}687if (len > 0) {688memcpy(buf, blheli.buf, len);689}690return true;691}692693uint8_t AP_BLHeli::BL_GetACK(uint16_t timeout_ms)694{695uint8_t ack;696uint32_t start_ms = AP_HAL::millis();697EXPECT_DELAY_MS(1000);698while (AP_HAL::millis() - start_ms < timeout_ms) {699if (hal.rcout->serial_read_bytes(&ack, 1) == 1) {700return ack;701}702}703// return brNONE, meaning no ACK received in the timeout704return brNONE;705}706707bool AP_BLHeli::BL_SendCMDSetAddress()708{709// skip if adr == 0xFFFF710if (blheli.address == 0xFFFF) {711return true;712}713debug("BL_SendCMDSetAddress 0x%04x", blheli.address);714uint8_t sCMD[] = {CMD_SET_ADDRESS, 0, uint8_t(blheli.address>>8), uint8_t(blheli.address)};715if (!BL_SendBuf(sCMD, 4)) {716return false;717}718return BL_GetACK() == brSUCCESS;719}720721bool AP_BLHeli::BL_ReadA(uint8_t cmd, uint8_t *buf, uint16_t n)722{723if (BL_SendCMDSetAddress()) {724uint8_t sCMD[] = {cmd, uint8_t(n==256?0:n)};725if (!BL_SendBuf(sCMD, 2)) {726return false;727}728bool ret = BL_ReadBuf(buf, n);729if (ret && n == sizeof(esc_status) && blheli.address == esc_status_addr) {730// display esc_status structure if we see it731struct esc_status status;732memcpy(&status, buf, n);733debug("Prot %u Good %u Bad %u %x %x %x x%x\n",734(unsigned)status.protocol,735(unsigned)status.good_frames,736(unsigned)status.bad_frames,737(unsigned)status.unknown[0],738(unsigned)status.unknown[1],739(unsigned)status.unknown[2],740(unsigned)status.unknown2);741}742return ret;743}744return false;745}746747/*748connect to a blheli ESC749*/750bool AP_BLHeli::BL_ConnectEx(void)751{752if (blheli.connected[blheli.chan] != 0) {753debug("Using cached interface 0x%x for %u", blheli.interface_mode[blheli.chan], blheli.chan);754return true;755}756debug("BL_ConnectEx %u/%u at %u", blheli.chan, num_motors, motor_map[blheli.chan]);757setDisconnected();758const uint8_t BootInit[] = {0,0,0,0,0,0,0,0,0,0,0,0,0x0D,'B','L','H','e','l','i',0xF4,0x7D};759if (!BL_SendBuf(BootInit, 21)) {760return false;761}762763uint8_t BootInfo[8];764if (!BL_ReadBuf(BootInfo, 8)) {765return false;766}767768// reply must start with 471769if (strncmp((const char *)BootInfo, "471", 3) != 0) {770blheli.ack = ACK_D_GENERAL_ERROR;771return false;772}773774// extract device information775blheli.deviceInfo[blheli.chan][2] = BootInfo[3];776blheli.deviceInfo[blheli.chan][1] = BootInfo[4];777blheli.deviceInfo[blheli.chan][0] = BootInfo[5];778779blheli.interface_mode[blheli.chan] = 0;780781uint16_t devword;782memcpy(&devword, blheli.deviceInfo[blheli.chan], sizeof(devword));783switch (devword) {784case 0x9307:785case 0x930A:786case 0x930F:787case 0x940B:788blheli.interface_mode[blheli.chan] = imATM_BLB;789debug("Interface type imATM_BLB");790break;791case 0xF310:792case 0xF330:793case 0xF410:794case 0xF390:795case 0xF850:796case 0xE8B1:797case 0xE8B2:798blheli.interface_mode[blheli.chan] = imSIL_BLB;799debug("Interface type imSIL_BLB");800break;801default:802// BLHeli_32 MCU ID hi > 0x00 and < 0x90 / lo always = 0x06803if ((blheli.deviceInfo[blheli.chan][1] > 0x00) && (blheli.deviceInfo[blheli.chan][1] < 0x90) && (blheli.deviceInfo[blheli.chan][0] == 0x06)) {804blheli.interface_mode[blheli.chan] = imARM_BLB;805debug("Interface type imARM_BLB");806} else {807blheli.ack = ACK_D_GENERAL_ERROR;808debug("Unknown interface type 0x%04x", devword);809break;810}811}812blheli.deviceInfo[blheli.chan][3] = blheli.interface_mode[blheli.chan];813if (blheli.interface_mode[blheli.chan] != 0) {814blheli.connected[blheli.chan] = true;815}816return true;817}818819bool AP_BLHeli::BL_SendCMDKeepAlive(void)820{821uint8_t sCMD[] = {CMD_KEEP_ALIVE, 0};822if (!BL_SendBuf(sCMD, 2)) {823return false;824}825if (BL_GetACK() != brERRORCOMMAND) {826return false;827}828return true;829}830831bool AP_BLHeli::BL_PageErase(void)832{833if (BL_SendCMDSetAddress()) {834uint8_t sCMD[] = {CMD_ERASE_FLASH, 0x01};835if (!BL_SendBuf(sCMD, 2)) {836return false;837}838return BL_GetACK(3000) == brSUCCESS;839}840return false;841}842843void AP_BLHeli::BL_SendCMDRunRestartBootloader(void)844{845uint8_t sCMD[] = {RestartBootloader, 0};846blheli.deviceInfo[blheli.chan][0] = 1;847BL_SendBuf(sCMD, 2);848}849850uint8_t AP_BLHeli::BL_SendCMDSetBuffer(const uint8_t *buf, uint16_t nbytes)851{852uint8_t sCMD[] = {CMD_SET_BUFFER, 0, uint8_t(nbytes>>8), uint8_t(nbytes&0xff)};853if (!BL_SendBuf(sCMD, 4)) {854return false;855}856uint8_t ack;857if ((ack = BL_GetACK()) != brNONE) {858debug("BL_SendCMDSetBuffer ack failed 0x%02x", ack);859blheli.ack = ACK_D_GENERAL_ERROR;860return false;861}862if (!BL_SendBuf(buf, nbytes)) {863debug("BL_SendCMDSetBuffer send failed");864blheli.ack = ACK_D_GENERAL_ERROR;865return false;866}867return (BL_GetACK(40) == brSUCCESS);868}869870bool AP_BLHeli::BL_WriteA(uint8_t cmd, const uint8_t *buf, uint16_t nbytes, uint32_t timeout_ms)871{872if (BL_SendCMDSetAddress()) {873if (!BL_SendCMDSetBuffer(buf, nbytes)) {874blheli.ack = ACK_D_GENERAL_ERROR;875return false;876}877uint8_t sCMD[] = {cmd, 0x01};878if (!BL_SendBuf(sCMD, 2)) {879return false;880}881return (BL_GetACK(timeout_ms) == brSUCCESS);882}883blheli.ack = ACK_D_GENERAL_ERROR;884return false;885}886887uint8_t AP_BLHeli::BL_WriteFlash(const uint8_t *buf, uint16_t n)888{889return BL_WriteA(CMD_PROG_FLASH, buf, n, 500);890}891892bool AP_BLHeli::BL_VerifyFlash(const uint8_t *buf, uint16_t n)893{894if (BL_SendCMDSetAddress()) {895if (!BL_SendCMDSetBuffer(buf, n)) {896return false;897}898uint8_t sCMD[] = {CMD_VERIFY_FLASH_ARM, 0x01};899if (!BL_SendBuf(sCMD, 2)) {900return false;901}902uint8_t ack = BL_GetACK(40);903switch (ack) {904case brSUCCESS:905blheli.ack = ACK_OK;906break;907case brERRORVERIFY:908blheli.ack = ACK_I_VERIFY_ERROR;909break;910default:911blheli.ack = ACK_D_GENERAL_ERROR;912break;913}914return true;915}916return false;917}918919/*920process a blheli 4way command from GCS921*/922void AP_BLHeli::blheli_process_command(void)923{924debug("BLHeli cmd 0x%02x len=%u", blheli.command, blheli.param_len);925blheli.ack = ACK_OK;926switch (blheli.command) {927case cmd_InterfaceTestAlive: {928debug("cmd_InterfaceTestAlive");929BL_SendCMDKeepAlive();930if (blheli.ack != ACK_OK) {931setDisconnected();932}933uint8_t b = 0;934blheli_send_reply(&b, 1);935break;936}937case cmd_ProtocolGetVersion: {938debug("cmd_ProtocolGetVersion");939uint8_t buf[1];940buf[0] = SERIAL_4WAY_PROTOCOL_VER;941blheli_send_reply(buf, sizeof(buf));942break;943}944case cmd_InterfaceGetName: {945debug("cmd_InterfaceGetName");946uint8_t buf[5] = { 4, 'A', 'R', 'D', 'U' };947blheli_send_reply(buf, sizeof(buf));948break;949}950case cmd_InterfaceGetVersion: {951debug("cmd_InterfaceGetVersion");952uint8_t buf[2] = { SERIAL_4WAY_VERSION_HI, SERIAL_4WAY_VERSION_LO };953blheli_send_reply(buf, sizeof(buf));954break;955}956case cmd_InterfaceExit: {957debug("cmd_InterfaceExit");958msp.escMode = PROTOCOL_NONE;959uint8_t b = 0;960blheli_send_reply(&b, 1);961hal.rcout->serial_end();962serial_start_ms = 0;963if (motors_disabled) {964motors_disabled = false;965SRV_Channels::set_disabled_channel_mask(motors_disabled_mask);966}967if (uart_locked) {968debug("Unlocked UART");969uart->lock_port(0, 0);970uart_locked = false;971}972memset(blheli.connected, 0, sizeof(blheli.connected));973break;974}975case cmd_DeviceReset: {976debug("cmd_DeviceReset(%u)", unsigned(blheli.buf[0]));977if (blheli.buf[0] >= num_motors) {978debug("bad reset channel %u", blheli.buf[0]);979blheli.ack = ACK_I_INVALID_CHANNEL;980blheli_send_reply(&blheli.buf[0], 1);981break;982}983blheli.chan = blheli.buf[0];984switch (blheli.interface_mode[blheli.chan]) {985case imSIL_BLB:986case imATM_BLB:987case imARM_BLB:988BL_SendCMDRunRestartBootloader();989break;990case imSK:991break;992}993blheli_send_reply(&blheli.chan, 1);994setDisconnected();995break;996}997998case cmd_DeviceInitFlash: {999debug("cmd_DeviceInitFlash(%u)", unsigned(blheli.buf[0]));1000if (blheli.buf[0] >= num_motors) {1001debug("bad channel %u", blheli.buf[0]);1002blheli.ack = ACK_I_INVALID_CHANNEL;1003blheli_send_reply(&blheli.buf[0], 1);1004break;1005}1006blheli.chan = blheli.buf[0];1007blheli.ack = ACK_OK;1008BL_ConnectEx();1009uint8_t buf[4] = {blheli.deviceInfo[blheli.chan][0],1010blheli.deviceInfo[blheli.chan][1],1011blheli.deviceInfo[blheli.chan][2],1012blheli.deviceInfo[blheli.chan][3]}; // device ID1013blheli_send_reply(buf, sizeof(buf));1014break;1015}10161017case cmd_InterfaceSetMode: {1018debug("cmd_InterfaceSetMode(%u)", unsigned(blheli.buf[0]));1019blheli.interface_mode[blheli.chan] = blheli.buf[0];1020blheli_send_reply(&blheli.interface_mode[blheli.chan], 1);1021break;1022}10231024case cmd_DeviceRead: {1025uint16_t nbytes = blheli.buf[0]?blheli.buf[0]:256;1026debug("cmd_DeviceRead(%u) n=%u", blheli.chan, nbytes);1027uint8_t buf[nbytes];1028uint8_t cmd = blheli.interface_mode[blheli.chan]==imATM_BLB?CMD_READ_FLASH_ATM:CMD_READ_FLASH_SIL;1029if (!BL_ReadA(cmd, buf, nbytes)) {1030nbytes = 1;1031}1032blheli_send_reply(buf, nbytes);1033break;1034}10351036case cmd_DevicePageErase: {1037uint8_t page = blheli.buf[0];1038debug("cmd_DevicePageErase(%u) im=%u", page, blheli.interface_mode[blheli.chan]);1039switch (blheli.interface_mode[blheli.chan]) {1040case imSIL_BLB:1041case imARM_BLB: {1042if (blheli.interface_mode[blheli.chan] == imARM_BLB) {1043// Address =Page * 10241044blheli.address = page << 10;1045} else {1046// Address =Page * 5121047blheli.address = page << 9;1048}1049debug("ARM PageErase 0x%04x", blheli.address);1050BL_PageErase();1051blheli.address = 0;1052blheli_send_reply(&page, 1);1053break;1054}1055default:1056blheli.ack = ACK_I_INVALID_CMD;1057blheli_send_reply(&page, 1);1058break;1059}1060break;1061}10621063case cmd_DeviceWrite: {1064uint16_t nbytes = blheli.param_len;1065debug("cmd_DeviceWrite n=%u im=%u", nbytes, blheli.interface_mode[blheli.chan]);1066uint8_t buf[nbytes];1067memcpy(buf, blheli.buf, nbytes);1068switch (blheli.interface_mode[blheli.chan]) {1069case imSIL_BLB:1070case imATM_BLB:1071case imARM_BLB: {1072BL_WriteFlash(buf, nbytes);1073break;1074}1075case imSK: {1076debug("Unsupported flash mode imSK");1077break;1078}1079}1080uint8_t b=0;1081blheli_send_reply(&b, 1);1082break;1083}10841085case cmd_DeviceVerify: {1086uint16_t nbytes = blheli.param_len;1087debug("cmd_DeviceWrite n=%u im=%u", nbytes, blheli.interface_mode[blheli.chan]);1088switch (blheli.interface_mode[blheli.chan]) {1089case imARM_BLB: {1090uint8_t buf[nbytes];1091memcpy(buf, blheli.buf, nbytes);1092BL_VerifyFlash(buf, nbytes);1093break;1094}1095default:1096blheli.ack = ACK_I_INVALID_CMD;1097break;1098}1099uint8_t b=0;1100blheli_send_reply(&b, 1);1101break;1102}11031104case cmd_DeviceReadEEprom: {1105uint16_t nbytes = blheli.buf[0]?blheli.buf[0]:256;1106uint8_t buf[nbytes];1107debug("cmd_DeviceReadEEprom n=%u im=%u", nbytes, blheli.interface_mode[blheli.chan]);1108switch (blheli.interface_mode[blheli.chan]) {1109case imATM_BLB: {1110if (!BL_ReadA(CMD_READ_EEPROM, buf, nbytes)) {1111blheli.ack = ACK_D_GENERAL_ERROR;1112}1113break;1114}1115default:1116blheli.ack = ACK_I_INVALID_CMD;1117break;1118}1119if (blheli.ack != ACK_OK) {1120nbytes = 1;1121buf[0] = 0;1122}1123blheli_send_reply(buf, nbytes);1124break;1125}11261127case cmd_DeviceWriteEEprom: {1128uint16_t nbytes = blheli.param_len;1129uint8_t buf[nbytes];1130memcpy(buf, blheli.buf, nbytes);1131debug("cmd_DeviceWriteEEprom n=%u im=%u", nbytes, blheli.interface_mode[blheli.chan]);1132switch (blheli.interface_mode[blheli.chan]) {1133case imATM_BLB:1134BL_WriteA(CMD_PROG_EEPROM, buf, nbytes, 3000);1135break;1136default:1137blheli.ack = ACK_D_GENERAL_ERROR;1138break;1139}1140uint8_t b = 0;1141blheli_send_reply(&b, 1);1142break;1143}11441145case cmd_DeviceEraseAll:1146case cmd_DeviceC2CK_LOW:1147default:1148// ack=unknown command1149blheli.ack = ACK_I_INVALID_CMD;1150debug("Unknown BLHeli protocol 0x%02x", blheli.command);1151uint8_t b = 0;1152blheli_send_reply(&b, 1);1153break;1154}1155}11561157/*1158process an input byte, return true if we have received a whole1159packet with correct CRC1160*/1161bool AP_BLHeli::process_input(uint8_t b)1162{1163bool valid_packet = false;11641165if (msp.escMode == PROTOCOL_4WAY && blheli.state == BLHELI_IDLE && b == '$') {1166debug("Change to MSP mode");1167msp.escMode = PROTOCOL_NONE;1168hal.rcout->serial_end();1169serial_start_ms = 0;1170}1171if (msp.escMode != PROTOCOL_4WAY && msp.state == MSP_IDLE && b == '/') {1172debug("Change to BLHeli mode");1173memset(blheli.connected, 0, sizeof(blheli.connected));1174msp.escMode = PROTOCOL_4WAY;1175}1176if (msp.escMode == PROTOCOL_4WAY) {1177blheli_4way_process_byte(b);1178} else {1179msp_process_byte(b);1180}1181if (msp.escMode == PROTOCOL_4WAY) {1182if (blheli.state == BLHELI_COMMAND_RECEIVED) {1183valid_packet = true;1184last_valid_ms = AP_HAL::millis();1185if (uart->lock_port(BLHELI_UART_LOCK_KEY, 0)) {1186uart_locked = true;1187}1188blheli_process_command();1189blheli.state = BLHELI_IDLE;1190msp.state = MSP_IDLE;1191}1192} else if (msp.state == MSP_COMMAND_RECEIVED) {1193if (msp.packetType == MSP_PACKET_COMMAND) {1194valid_packet = true;1195if (uart->lock_port(BLHELI_UART_LOCK_KEY, 0)) {1196uart_locked = true;1197}1198last_valid_ms = AP_HAL::millis();1199msp_process_command();1200}1201msp.state = MSP_IDLE;1202blheli.state = BLHELI_IDLE;1203}12041205return valid_packet;1206}12071208/*1209protocol handler for detecting BLHeli input1210*/1211bool AP_BLHeli::protocol_handler(uint8_t b, AP_HAL::UARTDriver *_uart)1212{1213uart = _uart;1214if (hal.util->get_soft_armed()) {1215// don't allow MSP control when armed1216return false;1217}1218return process_input(b);1219}12201221/*1222run a connection test to the ESCs. This is used to test the1223operation of the BLHeli ESC protocol1224*/1225void AP_BLHeli::run_connection_test(uint8_t chan)1226{1227run_test.set_and_notify(0);1228debug_uart = hal.console;1229uint8_t saved_chan = blheli.chan;1230if (chan >= num_motors) {1231GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ESC: bad channel %u", chan);1232return;1233}1234blheli.chan = chan;1235GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ESC: Running test on channel %u", blheli.chan);1236bool passed = false;1237for (uint8_t tries=0; tries<5; tries++) {1238EXPECT_DELAY_MS(3000);1239blheli.ack = ACK_OK;1240setDisconnected();1241if (BL_ConnectEx()) {1242uint8_t buf[256];1243uint8_t cmd = blheli.interface_mode[blheli.chan]==imATM_BLB?CMD_READ_FLASH_ATM:CMD_READ_FLASH_SIL;1244passed = true;1245blheli.address = blheli.interface_mode[blheli.chan]==imATM_BLB?0:0x7c00;1246passed &= BL_ReadA(cmd, buf, sizeof(buf));1247if (blheli.interface_mode[blheli.chan]==imARM_BLB) {1248if (passed) {1249// read status structure1250blheli.address = esc_status_addr;1251passed &= BL_SendCMDSetAddress();1252}1253if (passed) {1254struct esc_status status;1255passed &= BL_ReadA(CMD_READ_FLASH_SIL, (uint8_t *)&status, sizeof(status));1256}1257}1258BL_SendCMDRunRestartBootloader();1259break;1260}1261}1262hal.rcout->serial_end();1263SRV_Channels::set_disabled_channel_mask(motors_disabled_mask);1264motors_disabled = false;1265serial_start_ms = 0;1266blheli.chan = saved_chan;1267GCS_SEND_TEXT(MAV_SEVERITY_INFO, "ESC: Test %s", passed?"PASSED":"FAILED");1268debug_uart = nullptr;1269}12701271/*1272update BLHeli1273*/1274void AP_BLHeli::update(void)1275{1276bool motor_control_active = false;1277for (uint8_t i = 0; i < num_motors; i++) {1278bool reversed = ((1U<< motor_map[i]) & channel_reversible_mask.get()) != 0;1279if (hal.rcout->read( motor_map[i]) != (reversed ? 1500 : 1000)) {1280motor_control_active = true;1281}1282}12831284uint32_t now = AP_HAL::millis();1285if (initialised && uart_locked &&1286((timeout_sec && now - last_valid_ms > uint32_t(timeout_sec.get())*1000U) ||1287(motor_control_active && now - last_valid_ms > MOTOR_ACTIVE_TIMEOUT))) {1288// we're not processing requests any more, shutdown serial1289// output1290if (serial_start_ms) {1291hal.rcout->serial_end();1292serial_start_ms = 0;1293}1294if (motors_disabled) {1295motors_disabled = false;1296SRV_Channels::set_disabled_channel_mask(motors_disabled_mask);1297}1298if (uart != nullptr) {1299debug("Unlocked UART");1300uart->lock_port(0, 0);1301uart_locked = false;1302}1303if (motor_control_active) {1304for (uint8_t i = 0; i < num_motors; i++) {1305bool reversed = ((1U<<motor_map[i]) & channel_reversible_mask.get()) != 0;1306hal.rcout->write(motor_map[i], reversed ? 1500 : 1000);1307}1308}1309}13101311if (initialised || (channel_mask.get() == 0 && channel_auto.get() == 0)) {1312if (initialised && run_test.get() > 0) {1313run_connection_test(run_test.get() - 1);1314}1315}1316}13171318/*1319Initialize BLHeli, called by SRV_Channels::init()1320Used to install protocol handler1321The motor mask of enabled motors can be passed in1322*/1323void AP_BLHeli::init(uint32_t mask, AP_HAL::RCOutput::output_mode otype)1324{1325initialised = true;13261327run_test.set_and_notify(0);13281329#if HAL_GCS_ENABLED1330// only install pass-thru protocol handler if either auto or the motor mask are set1331if (channel_mask.get() != 0 || channel_auto.get() != 0) {1332if (last_control_port > 0 && last_control_port != control_port) {1333gcs().install_alternative_protocol((mavlink_channel_t)(MAVLINK_COMM_0+last_control_port), nullptr);1334last_control_port = -1;1335}1336if (gcs().install_alternative_protocol((mavlink_channel_t)(MAVLINK_COMM_0+control_port),1337FUNCTOR_BIND_MEMBER(&AP_BLHeli::protocol_handler,1338bool, uint8_t, AP_HAL::UARTDriver *))) {1339debug("BLHeli installed on port %u", (unsigned)control_port);1340last_control_port = control_port;1341}1342}1343#endif // HAL_GCS_ENABLED13441345#if HAL_WITH_IO_MCU1346if (AP_BoardConfig::io_enabled()) {1347// with IOMCU the local (FMU) channels start at 81348chan_offset = 8;1349}1350#endif13511352mask |= uint32_t(channel_mask.get());13531354/*1355allow mode override - this makes it possible to use DShot for1356rovers and subs, plus for quadplane fwd motors1357*/1358// +1 converts from AP_Motors::pwm_type to AP_HAL::RCOutput::output_mode and saves doing a param conversion1359// this is the only use of the param, but this is still a bit of a hack1360const int16_t type = output_type.get() + 1;1361if (otype == AP_HAL::RCOutput::MODE_PWM_NONE) {1362otype = ((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;1363}1364switch (otype) {1365case AP_HAL::RCOutput::MODE_PWM_ONESHOT:1366case AP_HAL::RCOutput::MODE_PWM_ONESHOT125:1367case AP_HAL::RCOutput::MODE_PWM_BRUSHED:1368case AP_HAL::RCOutput::MODE_PWM_DSHOT150:1369case AP_HAL::RCOutput::MODE_PWM_DSHOT300:1370case AP_HAL::RCOutput::MODE_PWM_DSHOT600:1371case AP_HAL::RCOutput::MODE_PWM_DSHOT1200:1372if (mask) {1373hal.rcout->set_output_mode(mask, otype);1374}1375break;1376default:1377break;1378}13791380uint32_t digital_mask = 0;1381// setting the digital mask changes the min/max PWM values1382// it's important that this is NOT done for non-digital channels as otherwise1383// PWM min can result in motors turning. set for individual overrides first1384if (mask && hal.rcout->is_dshot_protocol(otype)) {1385digital_mask = mask;1386}13871388#if APM_BUILD_COPTER_OR_HELI || APM_BUILD_TYPE(APM_BUILD_ArduPlane) || APM_BUILD_TYPE(APM_BUILD_Rover)1389/*1390plane and copter can use AP_Motors to get an automatic mask1391*/1392#if APM_BUILD_TYPE(APM_BUILD_Rover)1393AP_MotorsUGV *motors = AP::motors_ugv();1394#else1395AP_Motors *motors = AP::motors();1396#endif1397if (motors) {1398uint32_t motormask = motors->get_motor_mask();1399// set the rest of the digital channels1400if (motors->is_digital_pwm_type()) {1401digital_mask |= motormask;1402}1403mask |= motormask;1404}1405#endif1406// tell SRV_Channels about ESC capabilities1407SRV_Channels::set_digital_outputs(digital_mask, uint32_t(channel_reversible_mask.get()) & digital_mask);1408// the dshot ESC type is required in order to send the reversed/reversible dshot command correctly1409hal.rcout->set_dshot_esc_type(SRV_Channels::get_dshot_esc_type());1410hal.rcout->set_reversible_mask(uint32_t(channel_reversible_mask.get()) & digital_mask);1411hal.rcout->set_reversed_mask(uint32_t(channel_reversed_mask.get()) & digital_mask);1412#ifdef HAL_WITH_BIDIR_DSHOT1413// possibly enable bi-directional dshot1414hal.rcout->set_motor_poles(motor_poles);1415#endif1416#if defined(HAL_WITH_BIDIR_DSHOT) || HAL_WITH_IO_MCU_BIDIR_DSHOT1417hal.rcout->set_bidir_dshot_mask(uint32_t(channel_bidir_dshot_mask.get()) & digital_mask);1418#endif1419// add motors from channel mask1420for (uint8_t i=0; i<16 && num_motors < max_motors; i++) {1421if (mask & (1U<<i)) {1422motor_map[num_motors] = i;1423num_motors++;1424}1425}1426motor_mask = mask;1427debug("ESC: %u motors mask=0x%08lx", num_motors, mask);14281429// check if we have a combination of reversible and normal1430mixed_type = (mask != (mask & channel_reversible_mask.get())) && (channel_reversible_mask.get() != 0);14311432if (num_motors != 0 && telem_rate > 0) {1433AP_SerialManager *serial_manager = AP_SerialManager::get_singleton();1434if (serial_manager) {1435telem_uart = serial_manager->find_serial(AP_SerialManager::SerialProtocol_ESCTelemetry,0);1436}1437}1438}14391440/*1441read an ESC telemetry packet1442*/1443void AP_BLHeli::read_telemetry_packet(void)1444{1445#if HAL_WITH_ESC_TELEM1446uint8_t buf[telem_packet_size];1447if (telem_uart->read(buf, telem_packet_size) < telem_packet_size) {1448// short read, we should have 10 bytes ready when this function is called1449return;1450}14511452// calculate crc1453uint8_t crc = 0;1454for (uint8_t i=0; i<telem_packet_size-1; i++) {1455crc = crc8_dvb(buf[i], crc, 0x07);1456}14571458if (buf[telem_packet_size-1] != crc) {1459// bad crc1460debug("Bad CRC on %u", last_telem_esc);1461return;1462}1463// record the previous rpm so that we can slew to the new one1464uint16_t new_rpm = ((buf[7]<<8) | buf[8]) * 200 / motor_poles;1465const uint8_t motor_idx = motor_map[last_telem_esc];1466// we have received valid data, mark the ESC as now active1467hal.rcout->set_active_escs_mask(1<<motor_idx);1468update_rpm(motor_idx, new_rpm);14691470TelemetryData t {1471.temperature_cdeg = int16_t(buf[0] * 100),1472.voltage = float(uint16_t((buf[1]<<8) | buf[2])) * 0.01,1473.current = float(uint16_t((buf[3]<<8) | buf[4])) * 0.01,1474.consumption_mah = float(uint16_t((buf[5]<<8) | buf[6])),1475};14761477update_telem_data(motor_idx, t,1478AP_ESC_Telem_Backend::TelemetryType::CURRENT1479| AP_ESC_Telem_Backend::TelemetryType::VOLTAGE1480| AP_ESC_Telem_Backend::TelemetryType::CONSUMPTION1481| AP_ESC_Telem_Backend::TelemetryType::TEMPERATURE);14821483if (debug_level >= 2) {1484uint16_t trpm = new_rpm;1485if (has_bidir_dshot(last_telem_esc)) {1486trpm = hal.rcout->get_erpm(motor_idx);1487if (trpm != 0xFFFF) {1488trpm = trpm * 200 / motor_poles;1489}1490}1491DEV_PRINTF("ESC[%u] T=%u V=%f C=%f con=%f RPM=%u e=%.1f t=%u\n",1492last_telem_esc,1493t.temperature_cdeg,1494t.voltage,1495t.current,1496t.consumption_mah,1497trpm, hal.rcout->get_erpm_error_rate(motor_idx), (unsigned)AP_HAL::millis());1498}1499#endif // HAL_WITH_ESC_TELEM1500}15011502/*1503log bidir telemetry - only called if BLH telemetry is not active1504*/1505void AP_BLHeli::log_bidir_telemetry(void)1506{1507uint32_t now = AP_HAL::millis();15081509if (debug_level >= 2 && now - last_log_ms[last_telem_esc] > 100) {1510if (has_bidir_dshot(last_telem_esc)) {1511const uint8_t motor_idx = motor_map[last_telem_esc];1512uint16_t trpm = hal.rcout->get_erpm(motor_idx);1513if (trpm != 0xFFFF) { // don't log invalid values as they are never used1514trpm = trpm * 200 / motor_poles;1515}15161517if (trpm > 0) {1518last_log_ms[last_telem_esc] = now;1519DEV_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());1520}1521}1522}15231524if (!SRV_Channels::have_digital_outputs()) {1525return;1526}15271528// ask the next ESC for telemetry1529uint8_t idx_pos = last_telem_esc;1530uint8_t idx = (idx_pos + 1) % num_motors;1531for (; idx != idx_pos; idx = (idx + 1) % num_motors) {1532if (SRV_Channels::have_digital_outputs(1U << motor_map[idx])) {1533break;1534}1535}1536if (SRV_Channels::have_digital_outputs(1U << motor_map[idx])) {1537last_telem_esc = idx;1538}1539}15401541/*1542update BLHeli telemetry handling1543This is called on push() in SRV_Channels1544*/1545void AP_BLHeli::update_telemetry(void)1546{1547#ifdef HAL_WITH_BIDIR_DSHOT1548// we might only have bi-dir dshot1549if (channel_bidir_dshot_mask.get() != 0 && !telem_uart) {1550log_bidir_telemetry();1551}1552#endif1553if (!telem_uart || !SRV_Channels::have_digital_outputs()) {1554return;1555}1556uint32_t now = AP_HAL::micros();1557uint32_t telem_rate_us = 1000000U / uint32_t(telem_rate.get() * num_motors);1558if (telem_rate_us < 2000) {1559// make sure we have a gap between frames1560telem_rate_us = 2000;1561}1562if (!telem_uart_started) {1563// we need to use begin() here to ensure the correct thread owns the uart1564telem_uart->begin(115200);1565telem_uart_started = true;1566}15671568uint32_t nbytes = telem_uart->available();15691570if (nbytes > telem_packet_size) {1571// if we have more than 10 bytes then we don't know which ESC1572// they are from. Throw them all away1573telem_uart->discard_input();1574return;1575}1576if (nbytes > 0 &&1577nbytes < telem_packet_size &&1578(last_telem_byte_read_us == 0 ||1579now - last_telem_byte_read_us < 1000)) {1580// wait a bit longer, we don't have enough bytes yet1581if (last_telem_byte_read_us == 0) {1582last_telem_byte_read_us = now;1583}1584return;1585}1586if (nbytes > 0 && nbytes < telem_packet_size) {1587// we've waited long enough, discard bytes if we don't have 10 yet1588telem_uart->discard_input();1589return;1590}1591if (nbytes == telem_packet_size) {1592// we have a full packet ready to parse1593read_telemetry_packet();1594last_telem_byte_read_us = 0;1595}1596if (now - last_telem_request_us >= telem_rate_us) {1597// ask the next ESC for telemetry1598uint8_t idx_pos = last_telem_esc;1599uint8_t idx = (idx_pos + 1) % num_motors;1600for (; idx != idx_pos; idx = (idx + 1) % num_motors) {1601if (SRV_Channels::have_digital_outputs(1U << motor_map[idx])) {1602break;1603}1604}1605uint32_t mask = 1U << motor_map[idx];1606if (SRV_Channels::have_digital_outputs(mask)) {1607hal.rcout->set_telem_request_mask(mask);1608last_telem_esc = idx;1609last_telem_request_us = now;1610}1611}1612}16131614#endif // HAVE_AP_BLHELI_SUPPORT161516161617