LCOV - code coverage report
Current view: top level - AP_Torqeedo - AP_Torqeedo_TQBus.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 469 0.0 %
Date: 2026-09-09 11:02:08 Functions: 0 23 0.0 %

          Line data    Source code
       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             : #include "AP_Torqeedo_TQBus.h"
      17             : 
      18             : #if HAL_TORQEEDO_ENABLED
      19             : 
      20             : #include <AP_Common/AP_Common.h>
      21             : #include <AP_Math/AP_Math.h>
      22             : #include <SRV_Channel/SRV_Channel.h>
      23             : #include <AP_Logger/AP_Logger.h>
      24             : #include <GCS_MAVLink/GCS.h>
      25             : #include <AP_SerialManager/AP_SerialManager.h>
      26             : #include <AP_ESC_Telem/AP_ESC_Telem_Backend.h>
      27             : 
      28             : #define TORQEEDO_SERIAL_BAUD        19200   // communication is always at 19200
      29             : #define TORQEEDO_PACKET_HEADER      0xAC    // communication packet header
      30             : #define TORQEEDO_PACKET_FOOTER      0xAD    // communication packet footer
      31             : #define TORQEEDO_PACKET_ESCAPE      0xAE    // escape character for handling occurrences of header, footer and this escape bytes in original message
      32             : #define TORQEEDO_PACKET_ESCAPE_MASK 0x80    // byte after ESCAPE character should be XOR'd with this value
      33             : #define TORQEEDO_LOG_TRQD_INTERVAL_MS                   5000// log TRQD message at this interval in milliseconds
      34             : #define TORQEEDO_SEND_MOTOR_SPEED_INTERVAL_MS           100 // motor speed sent at 10hz if connected to motor
      35             : #define TORQEEDO_SEND_MOTOR_STATUS_REQUEST_INTERVAL_MS  400 // motor status requested every 0.4sec if connected to motor
      36             : #define TORQEEDO_SEND_MOTOR_PARAM_REQUEST_INTERVAL_MS   400 // motor param requested every 0.4sec if connected to motor
      37             : #define TORQEEDO_BATT_TIMEOUT_MS    5000    // battery info timeouts after 5 seconds
      38             : #define TORQEEDO_REPLY_TIMEOUT_MS   25      // stop waiting for replies after 25ms
      39             : #define TORQEEDO_ERROR_REPORT_INTERVAL_MAX_MS   10000   // errors reported to user at no less than once every 10 seconds
      40             : 
      41             : extern const AP_HAL::HAL& hal;
      42             : 
      43             : // initialise driver
      44           0 : void AP_Torqeedo_TQBus::init()
      45             : {
      46             :     // only init once
      47             :     // Note: a race condition exists here if init is called multiple times quickly before thread_main has a chance to set _initialise
      48           0 :     if (_initialised) {
      49           0 :         return;
      50             :     }
      51             : 
      52             :     // create background thread to process serial input and output
      53             :     char thread_name[15];
      54           0 :     hal.util->snprintf(thread_name, sizeof(thread_name), "torqeedo%u", (unsigned)_instance);
      55           0 :     if (!hal.scheduler->thread_create(FUNCTOR_BIND_MEMBER(&AP_Torqeedo_TQBus::thread_main, void), thread_name, 2048, AP_HAL::Scheduler::PRIORITY_RCOUT, 1)) {
      56           0 :         return;
      57             :     }
      58             : }
      59             : 
      60             : // returns true if communicating with the motor
      61           0 : bool AP_Torqeedo_TQBus::healthy()
      62             : {
      63           0 :     if (!_initialised) {
      64           0 :         return false;
      65             :     }
      66             :     {
      67             :         // healthy if both receive and send have occurred in the last 3 seconds
      68           0 :         WITH_SEMAPHORE(_last_healthy_sem);
      69           0 :         const uint32_t now_ms = AP_HAL::millis();
      70           0 :         return ((now_ms - _last_received_ms < 3000) && (now_ms - _last_send_motor_ms < 3000));
      71           0 :     }
      72             : }
      73             : 
      74             : // initialise serial port and gpio pins (run from background thread)
      75           0 : bool AP_Torqeedo_TQBus::init_internals()
      76             : {
      77             :     // find serial driver and initialise
      78           0 :     const AP_SerialManager &serial_manager = AP::serialmanager();
      79           0 :     _uart = serial_manager.find_serial(AP_SerialManager::SerialProtocol_Torqeedo, _instance);
      80           0 :     if (_uart == nullptr) {
      81           0 :         return false;
      82             :     }
      83           0 :     _uart->begin(TORQEEDO_SERIAL_BAUD);
      84           0 :     _uart->set_flow_control(AP_HAL::UARTDriver::FLOW_CONTROL_DISABLE);
      85           0 :     _uart->set_unbuffered_writes(true);
      86             : 
      87             :     // if using tiller connection set on/off pin for 0.5 sec to turn on battery
      88           0 :     if (get_type() == AP_Torqeedo::ConnectionType::TYPE_TILLER) {
      89           0 :         if (_params.pin_onoff > -1) {
      90           0 :             hal.gpio->pinMode(_params.pin_onoff, HAL_GPIO_OUTPUT);
      91           0 :             hal.gpio->write(_params.pin_onoff, 1);
      92           0 :             hal.scheduler->delay(500);
      93           0 :             hal.gpio->write(_params.pin_onoff, 0);
      94             :         } else {
      95             :             // use serial port's RTS pin to turn on battery
      96           0 :             _uart->set_RTS_pin(true);
      97           0 :             hal.scheduler->delay(500);
      98           0 :             _uart->set_RTS_pin(false);
      99             :         }
     100             :     }
     101             : 
     102             :     // initialise RS485 DE pin (when high, allows send to motor)
     103           0 :     if (_params.pin_de > -1) {
     104           0 :         hal.gpio->pinMode(_params.pin_de, HAL_GPIO_OUTPUT);
     105           0 :         hal.gpio->write(_params.pin_de, 0);
     106             :     } else {
     107           0 :         _uart->set_CTS_pin(false);
     108             :     }
     109             : 
     110           0 :     return true;
     111             : }
     112             : 
     113             : // consume incoming messages from motor, reply with latest motor speed
     114             : // runs in background thread
     115           0 : void AP_Torqeedo_TQBus::thread_main()
     116             : {
     117             :     // initialisation
     118           0 :     if (!init_internals()) {
     119           0 :         return;
     120             :     }
     121           0 :     _initialised = true;
     122             : 
     123             :     while (true) {
     124             :         // 1ms loop delay
     125           0 :         hal.scheduler->delay_microseconds(1000);
     126             : 
     127             :         // check if transmit pin should be unset
     128           0 :         check_for_send_end();
     129             : 
     130             :         // check for timeout waiting for reply
     131           0 :         check_for_reply_timeout();
     132             : 
     133             :         // parse incoming characters
     134           0 :         uint32_t nbytes = MIN(_uart->available(), 1024U);
     135           0 :         while (nbytes-- > 0) {
     136           0 :             int16_t b = _uart->read();
     137           0 :             if (b >= 0 ) {
     138           0 :                 if (parse_byte((uint8_t)b)) {
     139             :                     // complete message received, parse it!
     140           0 :                     parse_message();
     141             :                     // clear wait-for-reply because if we are waiting for a reply, this message must be it
     142           0 :                     set_reply_received();
     143             :                 }
     144             :             }
     145             :         }
     146             : 
     147             :         // send motor speed
     148           0 :         bool log_update = false;
     149           0 :         if (safe_to_send()) {
     150           0 :             uint32_t now_ms = AP_HAL::millis();
     151             : 
     152             :             // if connected to motor
     153           0 :             if (get_type() == AP_Torqeedo::ConnectionType::TYPE_MOTOR) {
     154           0 :                 if (now_ms - _last_send_motor_ms > TORQEEDO_SEND_MOTOR_SPEED_INTERVAL_MS) {
     155             :                     // send motor speed every 0.1sec
     156           0 :                     _send_motor_speed = true;
     157           0 :                 } else if (now_ms - _last_send_motor_status_request_ms > TORQEEDO_SEND_MOTOR_STATUS_REQUEST_INTERVAL_MS) {
     158             :                     // send request for motor status
     159           0 :                     send_motor_msg_request(MotorMsgId::STATUS);
     160           0 :                     _last_send_motor_status_request_ms = now_ms;
     161           0 :                 } else if (now_ms - _last_send_motor_param_request_ms > TORQEEDO_SEND_MOTOR_PARAM_REQUEST_INTERVAL_MS) {
     162             :                     // send request for motor params
     163           0 :                     send_motor_msg_request(MotorMsgId::PARAM);
     164           0 :                     _last_send_motor_param_request_ms = now_ms;
     165             :                 }
     166             :             }
     167             : 
     168             :             // send motor speed
     169           0 :             if (_send_motor_speed) {
     170           0 :                 send_motor_speed_cmd();
     171           0 :                 _send_motor_speed = false;
     172           0 :                 log_update = true;
     173             :             }
     174             :         }
     175             : 
     176             :         // log high level status and motor speed
     177           0 :         log_TRQD(log_update);
     178           0 :     }
     179             : }
     180             : 
     181             : // returns a human-readable string corresponding the passed-in
     182             : // master error code (see page 93 of https://media.torqeedo.com/downloads/manuals/torqeedo-Travel-manual-DE-EN.pdf)
     183             : // If no conversion is available then nullptr is returned
     184           0 : const char * AP_Torqeedo_TQBus::map_master_error_code_to_string(uint8_t code) const
     185             : {
     186           0 :     switch (code) {
     187           0 :     case 2:
     188           0 :         return "stator high temp";
     189           0 :     case 5:
     190           0 :         return "propeller blocked";
     191           0 :     case 6:
     192           0 :         return "motor low voltage";
     193           0 :     case 7:
     194           0 :         return "motor high current";
     195           0 :     case 8:
     196           0 :         return "pcb temp high";
     197           0 :     case 21:
     198           0 :         return "tiller cal bad";
     199           0 :     case 22:
     200           0 :         return "mag bad";
     201           0 :     case 23:
     202           0 :         return "range incorrect";
     203           0 :     case 30:
     204           0 :         return "motor comm error";
     205           0 :     case 32:
     206           0 :         return "tiller comm error";
     207           0 :     case 33:
     208           0 :         return "general comm error";
     209           0 :     case 41:
     210             :     case 42:
     211           0 :         return "charge voltage bad";
     212           0 :     case 43:
     213           0 :         return "battery flat";
     214           0 :     case 45:
     215           0 :         return "battery high current";
     216           0 :     case 46:
     217           0 :         return "battery temp error";
     218           0 :     case 48:
     219           0 :         return "charging temp error";
     220             :     }
     221             : 
     222           0 :     return nullptr;
     223             : }
     224             : 
     225             : // report changes in error codes to user
     226           0 : void AP_Torqeedo_TQBus::report_error_codes()
     227             : {
     228             :     // skip reporting if we have already reported status very recently
     229           0 :     const uint32_t now_ms = AP_HAL::millis();
     230             : 
     231             :     // skip reporting if no changes in flags and already reported within 10 seconds
     232           0 :     const bool flags_changed = (_display_system_state_flags_prev.value != _display_system_state.flags.value) ||
     233           0 :                                (_display_system_state_master_error_code_prev != _display_system_state.master_error_code) ||
     234           0 :                                (_motor_status_prev.status_flags_value != _motor_status.status_flags_value) ||
     235           0 :                                (_motor_status_prev.error_flags_value != _motor_status.error_flags_value);
     236           0 :     if (!flags_changed && ((now_ms - _last_error_report_ms) < TORQEEDO_ERROR_REPORT_INTERVAL_MAX_MS)) {
     237           0 :         return;
     238             :     }
     239             : 
     240             :     // report display system errors
     241           0 :     const char* msg_prefix = "Torqeedo:";
     242             :     (void)msg_prefix;  // sometimes unused when HAL_GCS_ENABLED is false
     243           0 :     if (_display_system_state.flags.set_throttle_stop) {
     244           0 :         GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "%s zero throttle required", msg_prefix);
     245             :     }
     246           0 :     if (_display_system_state.flags.temp_warning) {
     247           0 :         GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "%s high temp", msg_prefix);
     248             :     }
     249           0 :     if (_display_system_state.flags.batt_nearly_empty) {
     250           0 :         GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "%s batt nearly empty", msg_prefix);
     251             :     }
     252           0 :     if (_display_system_state.master_error_code > 0) {
     253           0 :         const char *error_string = map_master_error_code_to_string(_display_system_state.master_error_code);
     254           0 :         if (error_string != nullptr) {
     255           0 :             GCS_SEND_TEXT(
     256             :                 MAV_SEVERITY_CRITICAL, "%s err:%u %s",
     257             :                 msg_prefix,
     258             :                 _display_system_state.master_error_code,
     259             :                 error_string);
     260             :         } else {
     261           0 :             GCS_SEND_TEXT(
     262             :                 MAV_SEVERITY_CRITICAL, "%s err:%u",
     263             :                 msg_prefix,
     264             :                 _display_system_state.master_error_code);
     265             :         }
     266             :     }
     267             : 
     268             :     // report motor status errors
     269           0 :     if (_motor_status.error_flags.overcurrent) {
     270           0 :         GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "%s overcurrent", msg_prefix);
     271             :     }
     272           0 :     if (_motor_status.error_flags.blocked) {
     273           0 :         GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "%s prop blocked", msg_prefix);
     274             :     }
     275           0 :     if (_motor_status.error_flags.overvoltage_static || _motor_status.error_flags.overvoltage_current) {
     276           0 :         GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "%s high voltage", msg_prefix);
     277             :     }
     278           0 :     if (_motor_status.error_flags.undervoltage_static || _motor_status.error_flags.undervoltage_current) {
     279           0 :         GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "%s low voltage", msg_prefix);
     280             :     }
     281           0 :     if (_motor_status.error_flags.overtemp_motor || _motor_status.error_flags.overtemp_pcb) {
     282           0 :         GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "%s high temp", msg_prefix);
     283             :     }
     284           0 :     if (_motor_status.error_flags.timeout_rs485) {
     285           0 :         GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "%s comm timeout", msg_prefix);
     286             :     }
     287           0 :     if (_motor_status.error_flags.temp_sensor_error) {
     288           0 :         GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "%s temp sensor err", msg_prefix);
     289             :     }
     290           0 :     if (_motor_status.error_flags.tilt) {
     291           0 :         GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "%s tilted", msg_prefix);
     292             :     }
     293             : 
     294             :     // display OK if all errors cleared
     295           0 :     const bool prev_errored = (_display_system_state_flags_prev.value != 0) ||
     296           0 :                               (_display_system_state_master_error_code_prev != 0) ||
     297           0 :                               (_motor_status_prev.error_flags_value != 0);
     298             : 
     299           0 :     const bool now_errored = (_display_system_state.flags.value != 0) ||
     300           0 :                              (_display_system_state.master_error_code != 0) ||
     301           0 :                              (_motor_status.error_flags_value != 0);
     302             : 
     303           0 :     if (!now_errored && prev_errored) {
     304           0 :         GCS_SEND_TEXT(MAV_SEVERITY_INFO, "%s OK", msg_prefix);
     305             :     }
     306             : 
     307             :     // record change in state and reporting time
     308           0 :     _display_system_state_flags_prev.value = _display_system_state.flags.value;
     309           0 :     _display_system_state_master_error_code_prev = _display_system_state.master_error_code;
     310           0 :     _motor_status_prev = _motor_status;
     311           0 :     _last_error_report_ms = now_ms;
     312             : }
     313             : 
     314             : // get latest battery status info.  returns true on success and populates arguments
     315           0 : bool AP_Torqeedo_TQBus::get_batt_info(float &voltage, float &current_amps, float &temp_C, uint8_t &pct_remaining) const
     316             : {
     317             : 
     318             :     // use battery info from display_system_state if available (tiller connection)
     319           0 :     if ((AP_HAL::millis() - _display_system_state.last_update_ms) <= TORQEEDO_BATT_TIMEOUT_MS) {
     320           0 :         voltage = _display_system_state.batt_voltage;
     321           0 :         current_amps = _display_system_state.batt_current;
     322           0 :         temp_C = MAX(_display_system_state.temp_sw, _display_system_state.temp_rp);
     323           0 :         pct_remaining = _display_system_state.batt_charge_pct;
     324           0 :         return true;
     325             :     }
     326             : 
     327             :     // use battery info from motor_param if available (motor connection)
     328           0 :     if ((AP_HAL::millis() - _motor_param.last_update_ms) <= TORQEEDO_BATT_TIMEOUT_MS) {
     329           0 :         voltage = _motor_param.voltage;
     330           0 :         current_amps = _motor_param.current;
     331           0 :         temp_C = MAX(_motor_param.pcb_temp, _motor_param.stator_temp);
     332           0 :         pct_remaining = 0; // motor does not know percent remaining
     333           0 :         return true;
     334             :     }
     335             : 
     336           0 :     return false;
     337             : }
     338             : 
     339             : // get battery capacity.  returns true on success and populates argument
     340           0 : bool AP_Torqeedo_TQBus::get_batt_capacity_Ah(uint16_t &amp_hours) const
     341             : {
     342           0 :     if (_display_system_setup.batt_capacity == 0) {
     343           0 :         return false;
     344             :     }
     345           0 :     amp_hours = _display_system_setup.batt_capacity;
     346           0 :     return true;
     347             : }
     348             : 
     349             : // process a single byte received on serial port
     350             : // return true if a complete message has been received (the message will be held in _received_buff)
     351           0 : bool AP_Torqeedo_TQBus::parse_byte(uint8_t b)
     352             : {
     353           0 :     bool complete_msg_received = false;
     354             : 
     355           0 :     switch (_parse_state) {
     356           0 :     case ParseState::WAITING_FOR_HEADER:
     357           0 :         if (b == TORQEEDO_PACKET_HEADER) {
     358           0 :             _parse_state = ParseState::WAITING_FOR_FOOTER;
     359             :         }
     360           0 :         _received_buff_len = 0;
     361           0 :         _parse_escape_received = false;
     362           0 :         break;
     363           0 :     case ParseState::WAITING_FOR_FOOTER:
     364           0 :         if (b == TORQEEDO_PACKET_FOOTER) {
     365           0 :             _parse_state = ParseState::WAITING_FOR_HEADER;
     366             : 
     367             :             // check message length
     368           0 :             if (_received_buff_len == 0) {
     369           0 :                 _parse_error_count++;
     370           0 :                 break;
     371             :             }
     372             : 
     373             :             // check crc
     374           0 :             const uint8_t crc_expected = crc8_maxim(_received_buff, _received_buff_len-1);
     375           0 :             if (_received_buff[_received_buff_len-1] != crc_expected) {
     376           0 :                 _parse_error_count++;
     377           0 :                 break;
     378             :             }
     379           0 :             _parse_success_count++;
     380             :             {
     381             :                 // record time of successful receive for health reporting
     382           0 :                 WITH_SEMAPHORE(_last_healthy_sem);
     383           0 :                 _last_received_ms = AP_HAL::millis();
     384           0 :             }
     385           0 :             complete_msg_received = true;
     386             :         } else {
     387             :             // escape character handling
     388           0 :             if (_parse_escape_received) {
     389           0 :                 b ^= TORQEEDO_PACKET_ESCAPE_MASK;
     390           0 :                 _parse_escape_received = false;
     391           0 :             } else if (b == TORQEEDO_PACKET_ESCAPE) {
     392             :                 // escape character received, record this and ignore this byte
     393           0 :                 _parse_escape_received = true;
     394           0 :                 break;
     395             :             }
     396             :             // add to buffer
     397           0 :             _received_buff[_received_buff_len] = b;
     398           0 :             _received_buff_len++;
     399           0 :             if (_received_buff_len > TORQEEDO_MESSAGE_LEN_MAX) {
     400             :                 // message too long
     401           0 :                 _parse_state = ParseState::WAITING_FOR_HEADER;
     402           0 :                 _parse_error_count++;
     403             :             }
     404             :         }
     405           0 :         break;
     406             :     }
     407             : 
     408           0 :     return complete_msg_received;
     409             : }
     410             : 
     411             : // process message held in _received_buff
     412           0 : void AP_Torqeedo_TQBus::parse_message()
     413             : {
     414             :     // message address (i.e. target of message)
     415           0 :     const MsgAddress msg_addr = (MsgAddress)_received_buff[0];
     416             : 
     417             :     // handle messages sent to "remote" (aka tiller)
     418           0 :     if ((get_type() == AP_Torqeedo::ConnectionType::TYPE_TILLER) && (msg_addr == MsgAddress::REMOTE1)) {
     419           0 :         RemoteMsgId msg_id = (RemoteMsgId)_received_buff[1];
     420           0 :         if (msg_id == RemoteMsgId::REMOTE) {
     421             :             // request received to send updated motor speed
     422           0 :             _send_motor_speed = true;
     423             :         }
     424           0 :         return;
     425             :     }
     426             : 
     427             :     // handle messages sent to Display
     428           0 :     if ((get_type() == AP_Torqeedo::ConnectionType::TYPE_TILLER) && (msg_addr == MsgAddress::DISPLAY)) {
     429           0 :         DisplayMsgId msg_id = (DisplayMsgId)_received_buff[1];
     430           0 :         switch (msg_id) {
     431           0 :         case DisplayMsgId::SYSTEM_STATE :
     432           0 :             if (_received_buff_len == 30) {
     433             :                 // fill in _display_system_state
     434           0 :                 _display_system_state.flags.value = UINT16_VALUE(_received_buff[2], _received_buff[3]);
     435           0 :                 _display_system_state.master_state = _received_buff[4]; // deprecated
     436           0 :                 _display_system_state.master_error_code = _received_buff[5];
     437           0 :                 _display_system_state.motor_voltage = UINT16_VALUE(_received_buff[6], _received_buff[7]) * 0.01;
     438           0 :                 _display_system_state.motor_current = UINT16_VALUE(_received_buff[8], _received_buff[9]) * 0.1;
     439           0 :                 _display_system_state.motor_power = UINT16_VALUE(_received_buff[10], _received_buff[11]);
     440           0 :                 _display_system_state.motor_rpm = (int16_t)UINT16_VALUE(_received_buff[12], _received_buff[13]);
     441           0 :                 _display_system_state.motor_pcb_temp = _received_buff[14];
     442           0 :                 _display_system_state.motor_stator_temp = _received_buff[15];
     443           0 :                 _display_system_state.batt_charge_pct = _received_buff[16];
     444           0 :                 _display_system_state.batt_voltage = UINT16_VALUE(_received_buff[17], _received_buff[18]) * 0.01;
     445           0 :                 _display_system_state.batt_current = UINT16_VALUE(_received_buff[19], _received_buff[20]) * 0.1;
     446           0 :                 _display_system_state.gps_speed = UINT16_VALUE(_received_buff[21], _received_buff[22]);
     447           0 :                 _display_system_state.range_miles = UINT16_VALUE(_received_buff[23], _received_buff[24]);
     448           0 :                 _display_system_state.range_minutes = UINT16_VALUE(_received_buff[25], _received_buff[26]);
     449           0 :                 _display_system_state.temp_sw = _received_buff[27];
     450           0 :                 _display_system_state.temp_rp = _received_buff[28];
     451           0 :                 _display_system_state.last_update_ms = AP_HAL::millis();
     452             : 
     453             :                 // update esc telem sent to ground station
     454           0 :                 const uint8_t esc_temp = MAX(_display_system_state.temp_sw, _display_system_state.temp_rp);
     455           0 :                 const uint8_t motor_temp = MAX(_display_system_state.motor_pcb_temp, _display_system_state.motor_stator_temp);
     456           0 :                 update_esc_telem(_display_system_state.motor_rpm,
     457             :                         _display_system_state.motor_voltage,
     458             :                         _display_system_state.motor_current,
     459             :                         esc_temp,
     460             :                         motor_temp);
     461             : 
     462             : #if HAL_LOGGING_ENABLED
     463             :                 // log data
     464           0 :                 if (option_enabled(AP_Torqeedo::options::LOG)) {
     465             :                     // @LoggerMessage: TRST
     466             :                     // @Description: Torqeedo System State
     467             :                     // @Field: TimeUS: Time since system startup
     468             :                     // @Field: I: instance
     469             :                     // @Field: F: Flags bitmask
     470             :                     // @Field: Err: Master error code
     471             :                     // @Field: MVolt: Motor voltage
     472             :                     // @Field: MCur: Motor current
     473             :                     // @Field: Pow: Motor power
     474             :                     // @Field: RPM: Motor RPM
     475             :                     // @Field: MTemp: Motor Temp (higher of pcb or stator)
     476             :                     // @Field: BPct: Battery charge percentage
     477             :                     // @Field: BVolt: Battery voltage
     478             :                     // @Field: BCur: Battery current
     479           0 :                     AP::logger().Write("TRST", "TimeUS,I,F,Err,MVolt,MCur,Pow,RPM,MTemp,BPct,BVolt,BCur",
     480             :                                       "s#--vAWqO%vA",   // units
     481             :                                       "F---00000000",   // multipliers
     482             :                                       "QBHBffHhBBff",   // formats
     483             :                                        AP_HAL::micros64(),
     484           0 :                                        _instance,
     485           0 :                                        _display_system_state.flags.value,
     486           0 :                                        _display_system_state.master_error_code,
     487           0 :                                        _display_system_state.motor_voltage,
     488           0 :                                        _display_system_state.motor_current,
     489           0 :                                        _display_system_state.motor_power,
     490           0 :                                        _display_system_state.motor_rpm,
     491             :                                        motor_temp,
     492           0 :                                        _display_system_state.batt_charge_pct,
     493           0 :                                       _display_system_state.batt_voltage,
     494           0 :                                       _display_system_state.batt_current);
     495             :                 }
     496             : #endif
     497             : 
     498             :                 // send to GCS
     499           0 :                 if (option_enabled(AP_Torqeedo::options::DEBUG_TO_GCS)) {
     500           0 :                     GCS_SEND_TEXT(MAV_SEVERITY_INFO,"TRST i:%u, F:%u Err:%u MV:%4.1f MC:%4.1f P:%u MT:%d B%%:%d BV:%4.1f BC:%4.1f",
     501             :                             (unsigned)_instance,
     502             :                             (unsigned)_display_system_state.flags.value,
     503             :                             (unsigned)_display_system_state.master_error_code,
     504             :                             (double)_display_system_state.motor_voltage,
     505             :                             (double)_display_system_state.motor_current,
     506             :                             (unsigned)_display_system_state.motor_power,
     507             :                             (int)motor_temp,
     508             :                             (unsigned)_display_system_state.batt_charge_pct,
     509             :                             (double)_display_system_state.batt_voltage,
     510             :                             (double)_display_system_state.batt_current);
     511             :                 }
     512             : 
     513             :                 // report any errors
     514           0 :                 report_error_codes();
     515             :             } else {
     516             :                 // unexpected length
     517           0 :                 _parse_error_count++;
     518             :             }
     519           0 :             break;
     520           0 :         case DisplayMsgId::SYSTEM_SETUP:
     521           0 :             if (_received_buff_len == 13) {
     522             :                 // fill in display system setup
     523           0 :                 _display_system_setup.flags = _received_buff[2];
     524           0 :                 _display_system_setup.motor_type = _received_buff[3];
     525           0 :                 _display_system_setup.motor_sw_version = UINT16_VALUE(_received_buff[4], _received_buff[5]);
     526           0 :                 _display_system_setup.batt_capacity = UINT16_VALUE(_received_buff[6], _received_buff[7]);
     527           0 :                 _display_system_setup.batt_charge_pct = _received_buff[8];
     528           0 :                 _display_system_setup.batt_type = _received_buff[9];
     529           0 :                 _display_system_setup.master_sw_version =  UINT16_VALUE(_received_buff[10], _received_buff[11]);
     530             : 
     531             : #if HAL_LOGGING_ENABLED
     532             :                 // log data
     533           0 :                 if (option_enabled(AP_Torqeedo::options::LOG)) {
     534             :                     // @LoggerMessage: TRSE
     535             :                     // @Description: Torqeedo System Setup
     536             :                     // @Field: TimeUS: Time since system startup
     537             :                     // @Field: I: instance
     538             :                     // @Field: Flag: Flags
     539             :                     // @Field: MotType: Motor type
     540             :                     // @Field: MotVer: Motor software version
     541             :                     // @Field: BattCap: Battery capacity
     542             :                     // @Field: BattPct: Battery charge percentage
     543             :                     // @Field: BattType: Battery type
     544             :                     // @Field: SwVer: Master software version
     545           0 :                     AP::logger().Write("TRSE", "TimeUS,I,Flag,MotType,MotVer,BattCap,BattPct,BattType,SwVer",
     546             :                                        "s#---a%--", // units
     547             :                                        "F----00--", // multipliers
     548             :                                        "QBBBHHBBH", // formats
     549             :                                        AP_HAL::micros64(),
     550           0 :                                        _instance,
     551           0 :                                        _display_system_setup.flags,
     552           0 :                                        _display_system_setup.motor_type,
     553           0 :                                        _display_system_setup.motor_sw_version,
     554           0 :                                        _display_system_setup.batt_capacity,
     555           0 :                                        _display_system_setup.batt_charge_pct,
     556           0 :                                        _display_system_setup.batt_type,
     557           0 :                                        _display_system_setup.master_sw_version);
     558             :                 }
     559             : #endif
     560             : 
     561             :                 // send to GCS
     562           0 :                 if (option_enabled(AP_Torqeedo::options::DEBUG_TO_GCS)) {
     563           0 :                     GCS_SEND_TEXT(MAV_SEVERITY_INFO,"TRSE i:%u v:%u F:%u Mot:%u/%u Bat:%u/%u/%u%%",
     564             :                             (unsigned)_instance,
     565             :                             (unsigned)_display_system_setup.master_sw_version,
     566             :                             (unsigned)_display_system_setup.flags,
     567             :                             (unsigned)_display_system_setup.motor_type,
     568             :                             (unsigned)_display_system_setup.motor_sw_version,
     569             :                             (unsigned)_display_system_setup.batt_type,
     570             :                             (unsigned)_display_system_setup.batt_capacity,
     571             :                             (unsigned)_display_system_setup.batt_charge_pct);
     572             :                 }
     573             :             } else {
     574             :                 // unexpected length
     575           0 :                 _parse_error_count++;
     576             :             }
     577           0 :             break;
     578           0 :         default:
     579             :             // ignore message
     580           0 :             break;
     581             :         }
     582           0 :         return;
     583             :     }
     584             : 
     585             :     // handle reply from motor
     586           0 :     if ((get_type() == AP_Torqeedo::ConnectionType::TYPE_MOTOR) && (msg_addr == MsgAddress::BUS_MASTER)) {
     587             :         // replies strangely do not return the msgid so we must have stored it
     588           0 :         MotorMsgId msg_id = (MotorMsgId)_reply_msgid;
     589           0 :         switch (msg_id) {
     590             : 
     591           0 :         case MotorMsgId::PARAM:
     592           0 :             if (_received_buff_len == 15) {
     593           0 :                 _motor_param.rpm = (int16_t)UINT16_VALUE(_received_buff[2], _received_buff[3]);
     594           0 :                 _motor_param.power = UINT16_VALUE(_received_buff[4], _received_buff[5]);
     595           0 :                 _motor_param.voltage = UINT16_VALUE(_received_buff[6], _received_buff[7]) * 0.01;
     596           0 :                 _motor_param.current = UINT16_VALUE(_received_buff[8], _received_buff[9]) * 0.1;
     597           0 :                 _motor_param.pcb_temp = (int16_t)UINT16_VALUE(_received_buff[10], _received_buff[11]) * 0.1;
     598           0 :                 _motor_param.stator_temp = (int16_t)UINT16_VALUE(_received_buff[12], _received_buff[13]) * 0.1;
     599           0 :                 _motor_param.last_update_ms = AP_HAL::millis();
     600             : 
     601             :                 // update esc telem sent to ground station
     602           0 :                 update_esc_telem(_motor_param.rpm,
     603             :                         _motor_param.voltage,
     604             :                         _motor_param.current,
     605             :                         _motor_param.pcb_temp,      // esc temp
     606             :                         _motor_param.stator_temp);  // motor temp
     607             : 
     608             : #if HAL_LOGGING_ENABLED
     609             :                 // log data
     610           0 :                 if (option_enabled(AP_Torqeedo::options::LOG)) {
     611             :                     // @LoggerMessage: TRMP
     612             :                     // @Description: Torqeedo Motor Param
     613             :                     // @Field: TimeUS: Time since system startup
     614             :                     // @Field: I: instance
     615             :                     // @Field: RPM: Motor RPM
     616             :                     // @Field: Pow: Motor power
     617             :                     // @Field: Volt: Motor voltage
     618             :                     // @Field: Cur: Motor current
     619             :                     // @Field: ETemp: ESC Temp
     620             :                     // @Field: MTemp: Motor Temp
     621           0 :                     AP::logger().Write("TRMP", "TimeUS,I,RPM,Pow,Volt,Cur,ETemp,MTemp",
     622             :                                        "s#qWvAOO",  // units
     623             :                                        "F-000000",  // multipliers
     624             :                                        "QBhHffff",  // formats
     625             :                                        AP_HAL::micros64(),
     626           0 :                                        _instance,
     627           0 :                                        _motor_param.rpm,
     628           0 :                                        _motor_param.power,
     629           0 :                                        _motor_param.voltage,
     630           0 :                                        _motor_param.current,
     631           0 :                                        _motor_param.pcb_temp,
     632           0 :                                        _motor_param.stator_temp);
     633             :                 }
     634             : #endif
     635             : 
     636             :                 // send to GCS
     637           0 :                 if (option_enabled(AP_Torqeedo::options::DEBUG_TO_GCS)) {
     638           0 :                     GCS_SEND_TEXT(MAV_SEVERITY_INFO, "TRMP i:%u rpm:%d p:%u V:%4.1f C:%4.1f PT:%4.1f MT:%4.1f",
     639             :                                                        (unsigned)_instance,
     640             :                                                        (int)_motor_param.rpm,
     641             :                                                        (unsigned)_motor_param.power,
     642             :                                                        (double)_motor_param.voltage,
     643             :                                                        (double)_motor_param.current,
     644             :                                                        (double)_motor_param.pcb_temp,
     645             :                                                        (double)_motor_param.stator_temp);
     646             :                 }
     647             :             } else {
     648             :                 // unexpected length
     649           0 :                 _parse_error_count++;
     650             :             }
     651           0 :             break;
     652             : 
     653           0 :         case MotorMsgId::STATUS:
     654           0 :             if (_received_buff_len == 6) {
     655           0 :                 _motor_status.status_flags_value = _received_buff[2];
     656           0 :                 _motor_status.error_flags_value = UINT16_VALUE(_received_buff[3], _received_buff[4]);
     657             : 
     658             : #if HAL_LOGGING_ENABLED
     659             :                 // log data
     660           0 :                 if (option_enabled(AP_Torqeedo::options::LOG)) {
     661             :                     // @LoggerMessage: TRMS
     662             :                     // @Description: Torqeedo Motor Status
     663             :                     // @Field: TimeUS: Time since system startup
     664             :                     // @Field: I: instance
     665             :                     // @Field: State: Motor status flags
     666             :                     // @Field: Err: Motor error flags
     667           0 :                     AP::logger().Write("TRMS", "TimeUS,I,State,Err",
     668             :                                        "s#--",  // units
     669             :                                        "F---",  // multipliers
     670             :                                        "QBBH",  // formats
     671             :                                        AP_HAL::micros64(),
     672           0 :                                         _instance,
     673           0 :                                        _motor_status.status_flags_value,
     674           0 :                                        _motor_status.error_flags_value);
     675             :                 }
     676             : #endif
     677             : 
     678             :                 // send to GCS
     679           0 :                 if (option_enabled(AP_Torqeedo::options::DEBUG_TO_GCS)) {
     680           0 :                     GCS_SEND_TEXT(MAV_SEVERITY_INFO,"TRMS i:%u S:%d Err:%d",
     681             :                                    (unsigned)_instance,
     682             :                                    _motor_status.status_flags_value,
     683             :                                    _motor_status.error_flags_value);
     684             :                 }
     685             : 
     686             :                 // report any errors
     687           0 :                 report_error_codes();
     688             :             } else {
     689             :                 // unexpected length
     690           0 :                 _parse_error_count++;
     691             :             }
     692           0 :             break;
     693             : 
     694           0 :         case MotorMsgId::INFO:
     695             :         case MotorMsgId::DRIVE:
     696             :         case MotorMsgId::CONFIG:
     697             :             // we do not process these replies
     698           0 :             break;
     699             : 
     700           0 :         default:
     701             :             // ignore unknown messages
     702           0 :             break;
     703             :         }
     704             :     }
     705             : }
     706             : 
     707             : // set DE Serial CTS pin to enable sending commands to motor
     708           0 : void AP_Torqeedo_TQBus::send_start()
     709             : {
     710             :     // set gpio pin or serial port's CTS pin
     711           0 :     if (_params.pin_de > -1) {
     712           0 :         hal.gpio->write(_params.pin_de, 1);
     713             :     } else {
     714           0 :         _uart->set_CTS_pin(true);
     715             :     }
     716           0 : }
     717             : 
     718             : // check for timeout after sending and unset pin if required
     719           0 : void AP_Torqeedo_TQBus::check_for_send_end()
     720             : {
     721           0 :     if (_send_delay_us == 0) {
     722             :         // not sending
     723           0 :         return;
     724             :     }
     725             : 
     726           0 :     if (AP_HAL::micros() - _send_start_us < _send_delay_us) {
     727             :         // return if delay has not yet elapsed
     728           0 :         return;
     729             :     }
     730           0 :     _send_delay_us = 0;
     731             : 
     732             :     // unset gpio or serial port's CTS pin
     733           0 :     if (_params.pin_de > -1) {
     734           0 :         hal.gpio->write(_params.pin_de, 0);
     735             :     } else {
     736           0 :         _uart->set_CTS_pin(false);
     737             :     }
     738             : }
     739             : 
     740             : // calculate delay require to allow bytes to be sent
     741           0 : uint32_t AP_Torqeedo_TQBus::calc_send_delay_us(uint8_t num_bytes)
     742             : {
     743             :     // baud rate of 19200 bits/sec
     744             :     // total number of bits = 10 x num_bytes
     745             :     // convert from seconds to micros by multiplying by 1,000,000
     746             :     // plus additional 300us safety margin
     747           0 :     const uint32_t delay_us = 1e6 * num_bytes * 10 / TORQEEDO_SERIAL_BAUD + 300;
     748           0 :     return delay_us;
     749             : }
     750             : 
     751             : // record msgid of message to wait for and set timer for timeout handling
     752           0 : void AP_Torqeedo_TQBus::set_expected_reply_msgid(uint8_t msg_id)
     753             : {
     754           0 :     _reply_msgid = msg_id;
     755           0 :     _reply_wait_start_ms = AP_HAL::millis();
     756           0 : }
     757             : 
     758             : // check for timeout waiting for reply message
     759           0 : void AP_Torqeedo_TQBus::check_for_reply_timeout()
     760             : {
     761             :     // return immediately if not waiting for reply
     762           0 :     if (_reply_wait_start_ms == 0) {
     763           0 :         return;
     764             :     }
     765           0 :     if (AP_HAL::millis() - _reply_wait_start_ms > TORQEEDO_REPLY_TIMEOUT_MS) {
     766           0 :         _reply_wait_start_ms = 0;
     767           0 :         _parse_error_count++;
     768             :     }
     769             : }
     770             : 
     771             : // mark reply received. should be called whenever a message is received regardless of whether we are actually waiting for a reply
     772           0 : void AP_Torqeedo_TQBus::set_reply_received()
     773             : {
     774           0 :     _reply_wait_start_ms = 0;
     775           0 : }
     776             : 
     777             : // send a message to the motor with the specified message contents
     778             : // msg_contents should not include the header, footer or CRC
     779             : // returns true on success
     780           0 : bool AP_Torqeedo_TQBus::send_message(const uint8_t msg_contents[], uint8_t num_bytes)
     781             : {
     782             :     // buffer for outgoing message
     783             :     uint8_t send_buff[TORQEEDO_MESSAGE_LEN_MAX];
     784           0 :     uint8_t send_buff_num_bytes = 0;
     785             : 
     786             :     // calculate crc
     787           0 :     const uint8_t crc = crc8_maxim(msg_contents, num_bytes);
     788             : 
     789             :     // add header
     790           0 :     send_buff[send_buff_num_bytes++] = TORQEEDO_PACKET_HEADER;
     791             : 
     792             :     // add contents
     793           0 :     for (uint8_t i=0; i<num_bytes; i++) {
     794           0 :         if (!add_byte_to_message(msg_contents[i], send_buff, ARRAY_SIZE(send_buff), send_buff_num_bytes)) {
     795           0 :             _parse_error_count++;
     796           0 :             return false;
     797             :         }
     798             :     }
     799             : 
     800             :     // add crc
     801           0 :     if (!add_byte_to_message(crc, send_buff, ARRAY_SIZE(send_buff), send_buff_num_bytes)) {
     802           0 :         _parse_error_count++;
     803           0 :         return false;
     804             :     }
     805             : 
     806             :     // add footer
     807           0 :     if (send_buff_num_bytes >= ARRAY_SIZE(send_buff)) {
     808           0 :         _parse_error_count++;
     809           0 :         return false;
     810             :     }
     811           0 :     send_buff[send_buff_num_bytes++] = TORQEEDO_PACKET_FOOTER;
     812             : 
     813             :     // set send pin
     814           0 :     send_start();
     815             : 
     816             :     // write message
     817           0 :     _uart->write(send_buff, send_buff_num_bytes);
     818             : 
     819             :     // record start and expected delay to send message
     820           0 :     _send_start_us = AP_HAL::micros();
     821           0 :     _send_delay_us = calc_send_delay_us(send_buff_num_bytes);
     822             : 
     823           0 :     return true;
     824             : }
     825             : 
     826             : // add a byte to a message buffer including adding the escape character (0xAE) if necessary
     827             : // this should only be used when adding the contents to the buffer, not the header and footer
     828             : // num_bytes is updated to the next free byte
     829           0 : bool AP_Torqeedo_TQBus::add_byte_to_message(uint8_t byte_to_add, uint8_t msg_buff[], uint8_t msg_buff_size, uint8_t &num_bytes) const
     830             : {
     831           0 :     bool escape_required = (byte_to_add == TORQEEDO_PACKET_HEADER ||
     832           0 :                             byte_to_add == TORQEEDO_PACKET_FOOTER ||
     833             :                             byte_to_add == TORQEEDO_PACKET_ESCAPE);
     834             : 
     835             :     // check if we have enough space
     836           0 :     if (num_bytes + (escape_required ? 2 : 1) >= msg_buff_size) {
     837           0 :         return false;
     838             :     }
     839             : 
     840             :     // add byte
     841           0 :     if (escape_required) {
     842           0 :         msg_buff[num_bytes++] = TORQEEDO_PACKET_ESCAPE;
     843           0 :         msg_buff[num_bytes++] = byte_to_add ^ TORQEEDO_PACKET_ESCAPE_MASK;
     844             :     } else {
     845           0 :         msg_buff[num_bytes++] = byte_to_add;
     846             :     }
     847           0 :     return true;
     848             : }
     849             : 
     850             : // Example "Remote (0x01)" reply message to allow tiller to control motor speed
     851             : // Byte     Field Definition    Example Value   Comments
     852             : // ---------------------------------------------------------------------------------
     853             : // byte 0   Header              0xAC
     854             : // byte 1   TargetAddress       0x00            see MsgAddress enum
     855             : // byte 2   Message ID          0x00            only master populates this. replies have this set to zero
     856             : // byte 3   Flags               0x05            bit0=pin present, bit2=motor speed valid
     857             : // byte 4   Status              0x00            0x20 if byte3=4, 0x0 is byte3=5
     858             : // byte 5   Motor Speed MSB     ----            Motor Speed MSB (-1000 to +1000)
     859             : // byte 6   Motor Speed LSB     ----            Motor Speed LSB (-1000 to +1000)
     860             : // byte 7   CRC-Maxim           ----            CRC-Maxim value
     861             : // byte 8   Footer              0xAD
     862             : //
     863             : // example message when rotating tiller handle forwards:  "AC 00 00 05 00 00 ED 95 AD"    (+237)
     864             : // example message when rotating tiller handle backwards: "AC 00 00 05 00 FF AE 2C 0C AD" (-82)
     865             : 
     866             : // send a motor speed command as a value from -1000 to +1000
     867             : // value is taken directly from SRV_Channel
     868             : // for tiller connection this sends the "Remote (0x01)" message
     869             : // for motor connection this sends the "Motor Drive (0x82)" message
     870           0 : void AP_Torqeedo_TQBus::send_motor_speed_cmd()
     871             : {
     872             :     // calculate desired motor speed
     873           0 :     if (!hal.util->get_soft_armed()) {
     874           0 :         _motor_speed_desired = 0;
     875             :     } else {
     876             :         // convert throttle output to motor output in range -1000 to +1000
     877             :         // ToDo: convert PWM output to motor output so that SERVOx_MIN, MAX and TRIM take effect
     878           0 :         _motor_speed_desired = constrain_int16(SRV_Channels::get_output_norm((SRV_Channel::Function)_params.servo_fn.get()) * 1000.0, -1000, 1000);
     879             :     }
     880             : 
     881             :     // updated limited motor speed
     882           0 :     int16_t mot_speed_limited = calc_motor_speed_limited(_motor_speed_desired);
     883             : 
     884             :     // by default use tiller connection command
     885           0 :     uint8_t mot_speed_cmd_buff[] = {(uint8_t)MsgAddress::BUS_MASTER, 0x0, 0x5, 0x0, HIGHBYTE(mot_speed_limited), LOWBYTE(mot_speed_limited)};
     886             : 
     887             :     // update message if using motor connection
     888           0 :     if (get_type() == AP_Torqeedo::ConnectionType::TYPE_MOTOR) {
     889           0 :         const uint8_t motor_power = (uint8_t)constrain_int16(_params.motor_power, 0, 100);
     890           0 :         mot_speed_cmd_buff[0] = (uint8_t)MsgAddress::MOTOR;
     891           0 :         mot_speed_cmd_buff[1] = (uint8_t)MotorMsgId::DRIVE;
     892           0 :         mot_speed_cmd_buff[2] = (mot_speed_limited == 0 ? 0 : 0x01) | (_motor_clear_error ? 0x04 : 0);  // 1:enable motor, 2:fast off, 4:clear error
     893           0 :         mot_speed_cmd_buff[3] = mot_speed_limited == 0 ? 0 : motor_power;   // motor power from 0 to 100
     894             : 
     895             :         // set expected reply message id
     896           0 :         set_expected_reply_msgid((uint8_t)MotorMsgId::DRIVE);
     897             : 
     898             :         // reset motor clear error request
     899           0 :         _motor_clear_error = false;
     900             :     }
     901             : 
     902             :     // send a message
     903           0 :     if (send_message(mot_speed_cmd_buff, ARRAY_SIZE(mot_speed_cmd_buff))) {
     904             :         // record time of send for health reporting
     905           0 :         WITH_SEMAPHORE(_last_healthy_sem);
     906           0 :         _last_send_motor_ms = AP_HAL::millis();
     907           0 :     }
     908           0 : }
     909             : 
     910             : // send request to motor to reply with a particular message
     911             : // msg_id can be INFO, STATUS or PARAM
     912           0 : void AP_Torqeedo_TQBus::send_motor_msg_request(MotorMsgId msg_id)
     913             : {
     914             :     // prepare message
     915           0 :     uint8_t mot_status_request_buff[] = {(uint8_t)MsgAddress::MOTOR, (uint8_t)msg_id};
     916             : 
     917             :     // send a message
     918           0 :     if (send_message(mot_status_request_buff, ARRAY_SIZE(mot_status_request_buff))) {
     919             :         // record waiting for reply
     920           0 :         set_expected_reply_msgid((uint8_t)msg_id);
     921             :     }
     922           0 : }
     923             : 
     924             : // calculate the limited motor speed that is sent to the motors
     925             : // desired_motor_speed argument and returned value are in the range -1000 to 1000
     926           0 : int16_t AP_Torqeedo_TQBus::calc_motor_speed_limited(int16_t desired_motor_speed)
     927             : {
     928           0 :     const uint32_t now_ms = AP_HAL::millis();
     929             : 
     930             :     // update dir_limit flag for forward-reverse transition delay
     931           0 :     const bool dir_delay_active = is_positive(_params.dir_delay);
     932           0 :     if (!dir_delay_active) {
     933             :         // allow movement in either direction
     934           0 :         _dir_limit = 0;
     935             :     } else {
     936             :         // by default limit motor direction to previous iteration's direction
     937           0 :         if (is_positive(_motor_speed_limited)) {
     938           0 :             _dir_limit = 1;
     939           0 :         } else if (is_negative(_motor_speed_limited)) {
     940           0 :             _dir_limit = -1;
     941             :         } else {
     942             :             // motor speed is zero
     943           0 :             if ((_motor_speed_zero_ms != 0) && ((now_ms - _motor_speed_zero_ms) > (_params.dir_delay * 1000))) {
     944             :                 // delay has passed so allow movement in either direction
     945           0 :                 _dir_limit = 0;
     946           0 :                 _motor_speed_zero_ms = 0;
     947             :             }
     948             :         }
     949             :     }
     950             : 
     951             :     // calculate upper and lower limits for forward-reverse transition delay
     952           0 :     int16_t lower_limit = -1000;
     953           0 :     int16_t upper_limit = 1000;
     954           0 :     if (_dir_limit < 0) {
     955           0 :         upper_limit = 0;
     956             :     }
     957           0 :     if (_dir_limit > 0) {
     958           0 :         lower_limit = 0;
     959             :     }
     960             : 
     961             :     // calculate dt since last update
     962           0 :     float dt = (now_ms - _motor_speed_limited_ms) * 0.001f;
     963           0 :     if (dt > 1.0) {
     964             :         // after a long delay limit motor output to zero to avoid sudden starts
     965           0 :         lower_limit = 0;
     966           0 :         upper_limit = 0;
     967             :     }
     968           0 :     _motor_speed_limited_ms = now_ms;
     969             : 
     970             :     // apply slew limit
     971           0 :     if (_params.slew_time > 0) {
     972           0 :        const float chg_max = 1000.0 * dt / _params.slew_time;
     973           0 :        _motor_speed_limited = constrain_float(desired_motor_speed, _motor_speed_limited - chg_max, _motor_speed_limited + chg_max);
     974             :     } else {
     975             :         // no slew limit
     976           0 :         _motor_speed_limited = desired_motor_speed;
     977             :     }
     978             : 
     979             :     // apply upper and lower limits
     980           0 :     _motor_speed_limited = constrain_float(_motor_speed_limited, lower_limit, upper_limit);
     981             : 
     982             :     // record time motor speed becomes zero
     983           0 :     if (is_zero(_motor_speed_limited)) {
     984           0 :         if (_motor_speed_zero_ms == 0) {
     985           0 :             _motor_speed_zero_ms = now_ms;
     986             :         }
     987             :     } else {
     988             :         // clear timer
     989           0 :         _motor_speed_zero_ms = 0;
     990             :     }
     991             : 
     992           0 :     return (int16_t)_motor_speed_limited;
     993             : }
     994             : 
     995             : // output logging and debug messages (if required)
     996             : // force_logging should be true if caller wants to ensure the latest status is logged
     997           0 : void AP_Torqeedo_TQBus::log_TRQD(bool force_logging)
     998             : {
     999             :     // exit immediately if logging and debug options are not set
    1000           0 :     if (!option_enabled(AP_Torqeedo::options::LOG) && option_enabled(AP_Torqeedo::options::DEBUG_TO_GCS)) {
    1001           0 :         return;
    1002             :     }
    1003             : 
    1004             :     // return if not enough time has passed since last output
    1005           0 :     const uint32_t now_ms = AP_HAL::millis();
    1006           0 :     if (!force_logging && (now_ms - _last_log_TRQD_ms < TORQEEDO_LOG_TRQD_INTERVAL_MS)) {
    1007           0 :         return;
    1008             :     }
    1009           0 :     _last_log_TRQD_ms = now_ms;
    1010             : 
    1011             : #if HAL_LOGGING_ENABLED || HAL_GCS_ENABLED
    1012           0 :     const bool health = healthy();
    1013           0 :     int16_t actual_motor_speed = get_motor_speed_limited();
    1014             : 
    1015             : #if HAL_LOGGING_ENABLED
    1016           0 :     if (option_enabled(AP_Torqeedo::options::LOG)) {
    1017             :         // @LoggerMessage: TRQD
    1018             :         // @Description: Torqeedo Status
    1019             :         // @Field: TimeUS: Time since system startup
    1020             :         // @Field: I: instance
    1021             :         // @Field: Health: Health
    1022             :         // @Field: DesMotSpeed: Desired Motor Speed (-1000 to 1000)
    1023             :         // @Field: MotSpeed: Motor Speed (-1000 to 1000)
    1024             :         // @Field: SuccCnt: Success Count
    1025             :         // @Field: ErrCnt: Error Count
    1026           0 :         AP::logger().Write("TRQD", "TimeUS,I,Health,DesMotSpeed,MotSpeed,SuccCnt,ErrCnt",
    1027             :                            "s#-----",   // units
    1028             :                            "F------",   // multipliers
    1029             :                            "QBBhhII",   // formats
    1030             :                            AP_HAL::micros64(),
    1031           0 :                            _instance,
    1032             :                            health,
    1033           0 :                            _motor_speed_desired,
    1034             :                            actual_motor_speed,
    1035             :                            _parse_success_count,
    1036             :                            _parse_error_count);
    1037             :     }
    1038             : #endif
    1039             : 
    1040           0 :     if (option_enabled(AP_Torqeedo::options::DEBUG_TO_GCS)) {
    1041           0 :         GCS_SEND_TEXT(MAV_SEVERITY_INFO,"TRQD i:%u h:%u dspd:%d spd:%d succ:%ld err:%ld",
    1042             :                 (unsigned)_instance,
    1043             :                 (unsigned)health,
    1044             :                 (int)_motor_speed_desired,
    1045             :                 (int)actual_motor_speed,
    1046             :                 (unsigned long)_parse_success_count,
    1047             :                 (unsigned long)_parse_error_count);
    1048             :     }
    1049             : #endif  // HAL_LOGGING_ENABLED || HAL_GCS_ENABLED
    1050             : }
    1051             : 
    1052             : // send ESC telemetry
    1053           0 : void AP_Torqeedo_TQBus::update_esc_telem(float rpm, float voltage, float current_amps, float esc_tempC, float motor_tempC)
    1054             : {
    1055             : #if HAL_WITH_ESC_TELEM
    1056             :     // find servo output channel
    1057           0 :     uint8_t telem_esc_index = 0;
    1058           0 :     IGNORE_RETURN(SRV_Channels::find_channel((SRV_Channel::Function)_params.servo_fn.get(), telem_esc_index));
    1059             : 
    1060             :     // fill in telemetry data structure
    1061           0 :     AP_ESC_Telem_Backend::TelemetryData telem_dat {};
    1062           0 :     telem_dat.temperature_cdeg = esc_tempC * 100;   // temperature in centi-degrees
    1063           0 :     telem_dat.voltage = voltage;                    // voltage in volts
    1064           0 :     telem_dat.current = current_amps;               // current in amps
    1065           0 :     telem_dat.motor_temp_cdeg = motor_tempC * 100;  // motor temperature in centi-degrees
    1066             : 
    1067             :     // send telem and rpm data
    1068           0 :     update_telem_data(telem_esc_index, telem_dat, AP_ESC_Telem_Backend::TelemetryType::TEMPERATURE |
    1069             :                                                   AP_ESC_Telem_Backend::TelemetryType::MOTOR_TEMPERATURE |
    1070             :                                                   AP_ESC_Telem_Backend::TelemetryType::CURRENT |
    1071             :                                                   AP_ESC_Telem_Backend::TelemetryType::VOLTAGE);
    1072             : 
    1073           0 :     update_rpm(telem_esc_index, rpm);
    1074             : #endif
    1075           0 : }
    1076             : 
    1077             : #endif // HAL_TORQEEDO_ENABLED

Generated by: LCOV version 1.14