ardupilot/libraries/AP_Torqeedo/AP_Torqeedo_TQBus.h

333 lines
16 KiB
C++

/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
This driver supports communicating with Torqeedo motors that implement the "TQ Bus" protocol
which includes the Ultralight, Cruise 2.0 R, Cruise 4.0 R, Travel 503, Travel 1003 and Cruise 10kW
The autopilot should be connected either to the battery's tiller connector or directly to the motor
as described on the ArduPilot wiki. https://ardupilot.org/rover/docs/common-torqeedo.html
TQ Bus is a serial protocol over RS-485 meaning that a serial to RS-485 converter is required.
Tiller connection: Autopilot <-> Battery (master) <-> Motor
Motor connection: Autopilot (master) <-> Motor
Communication between the components is always initiated by the master with replies sent within 25ms
Example "Remote (0x01)" reply message to allow tiller to control motor speed
Byte Field Definition Example Value Comments
---------------------------------------------------------------------------------
byte 0 Header 0xAC
byte 1 TargetAddress 0x00 see MsgAddress enum
byte 2 Message ID 0x00 only master populates this. replies have this set to zero
byte 3 Flags 0x05 bit0=pin present, bit2=motor speed valid
byte 4 Status 0x00 0x20 if byte3=4, 0x0 is byte3=5
byte 5 Motor Speed MSB ---- Motor Speed MSB (-1000 to +1000)
byte 6 Motor Speed LSB ---- Motor Speed LSB (-1000 to +1000)
byte 7 CRC-Maxim ---- CRC-Maxim value
byte 8 Footer 0xAD
More details of the TQ Bus protocol are available from Torqeedo after signing an NDA.
*/
#pragma once
#include "AP_Torqeedo_config.h"
#if HAL_TORQEEDO_ENABLED
#include "AP_Torqeedo_Backend.h"
#define TORQEEDO_MESSAGE_LEN_MAX 35 // messages are no more than 35 bytes
class AP_Torqeedo_TQBus : public AP_Torqeedo_Backend {
public:
// constructor
using AP_Torqeedo_Backend::AP_Torqeedo_Backend;
CLASS_NO_COPY(AP_Torqeedo_TQBus);
// initialise driver
void init() override;
// returns true if communicating with the motor
bool healthy() override;
// clear motor errors
void clear_motor_error() override { _motor_clear_error = true; }
// get latest battery status info. returns true on success and populates arguments
bool get_batt_info(float &voltage, float &current_amps, float &temp_C, uint8_t &pct_remaining) const override;
bool get_batt_capacity_Ah(uint16_t &amp_hours) const override;
private:
// consume incoming messages from motor, reply with latest motor speed
// runs in background thread
void thread_main();
// report changes in error codes to user
void report_error_codes();
// message addresses
enum class MsgAddress : uint8_t {
BUS_MASTER = 0x00,
REMOTE1 = 0x14,
DISPLAY = 0x20,
MOTOR = 0x30,
BATTERY = 0x80
};
// Remote specific message ids
enum class RemoteMsgId : uint8_t {
INFO = 0x00,
REMOTE = 0x01,
SETUP = 0x02
};
// Display specific message ids
enum class DisplayMsgId : uint8_t {
INFO = 0x00,
SYSTEM_STATE = 0x41,
SYSTEM_SETUP = 0x42
};
// Motor specific message ids
enum class MotorMsgId : uint8_t {
INFO = 0x00,
STATUS = 0x01,
PARAM = 0x03,
CONFIG = 0x04,
DRIVE = 0x82
};
enum class ParseState {
WAITING_FOR_HEADER = 0,
WAITING_FOR_FOOTER,
};
// initialise serial port and gpio pins (run from background thread)
// returns true on success
bool init_internals();
// process a single byte received on serial port
// return true if a complete message has been received (the message will be held in _received_buff)
bool parse_byte(uint8_t b);
// process message held in _received_buff
void parse_message();
// returns true if it is safe to send a message
bool safe_to_send() const { return ((_send_delay_us == 0) && (_reply_wait_start_ms == 0)); }
// set pin to enable sending a message
void send_start();
// check for timeout after sending a message and unset pin if required
void check_for_send_end();
// calculate delay required to allow message to be completely sent
uint32_t calc_send_delay_us(uint8_t num_bytes);
// record msgid of message to wait for and set timer for reply timeout handling
void set_expected_reply_msgid(uint8_t msg_id);
// check for timeout waiting for reply
void check_for_reply_timeout();
// mark reply received. should be called whenever a message is received regardless of whether we are actually waiting for a reply
void set_reply_received();
// send a message to the motor with the specified message contents
// msg_contents should not include the header, footer or CRC
// returns true on success
bool send_message(const uint8_t msg_contents[], uint8_t num_bytes);
// add a byte to a message buffer including adding the escape character (0xAE) if necessary
// this should only be used when adding the contents to the buffer, not the header and footer
// num_bytes is updated to the next free byte
bool add_byte_to_message(uint8_t byte_to_add, uint8_t msg_buff[], uint8_t msg_buff_size, uint8_t &num_bytes) const;
// send a motor speed command as a value from -1000 to +1000
// value is taken directly from SRV_Channel
void send_motor_speed_cmd();
// send request to motor to reply with a particular message
// msg_id can be INFO, STATUS or PARAM
void send_motor_msg_request(MotorMsgId msg_id);
// calculate the limited motor speed that is sent to the motors
// desired_motor_speed argument and returned value are in the range -1000 to 1000
int16_t calc_motor_speed_limited(int16_t desired_motor_speed);
int16_t get_motor_speed_limited() const { return (int16_t)_motor_speed_limited; }
// log TRQD message which holds high level status and latest desired motors peed
// force_logging should be true to immediately write log bypassing timing check to avoid spamming
void log_TRQD(bool force_logging);
// send ESC telemetry
void update_esc_telem(float rpm, float voltage, float current_amps, float esc_tempC, float motor_tempC);
// members
AP_HAL::UARTDriver *_uart; // serial port to communicate with motor
bool _initialised; // true once driver has been initialised
bool _send_motor_speed; // true if motor speed should be sent at next opportunity
int16_t _motor_speed_desired; // desired motor speed (set from within update method)
uint32_t _last_send_motor_ms; // system time (in millis) last motor speed command was sent (used for health reporting)
bool _motor_clear_error; // true if the motor error should be cleared (sent in "Drive" message)
uint32_t _send_start_us; // system time (in micros) when last message started being sent (used for timing to unset DE pin)
uint32_t _send_delay_us; // delay (in micros) to allow bytes to be sent after which pin can be unset. 0 if not delaying
// motor speed limit variables
float _motor_speed_limited; // limited desired motor speed. this value is actually sent to the motor
uint32_t _motor_speed_limited_ms; // system time that _motor_speed_limited was last updated
int8_t _dir_limit; // acceptable directions for output to motor (+1 = positive OK, -1 = negative OK, 0 = either positive or negative OK)
uint32_t _motor_speed_zero_ms; // system time that _motor_speed_limited reached zero. 0 if currently not zero
// health reporting
HAL_Semaphore _last_healthy_sem;// semaphore protecting reading and updating of _last_send_motor_ms and _last_received_ms
uint32_t _last_log_TRQD_ms; // system time (in millis) that TRQD was last logged
// message parsing members
ParseState _parse_state; // current state of parsing
bool _parse_escape_received; // true if the escape character has been received so we must XOR the next byte
uint32_t _parse_error_count; // total number of parsing errors (for reporting)
uint32_t _parse_success_count; // number of messages successfully parsed (for reporting)
uint8_t _received_buff[TORQEEDO_MESSAGE_LEN_MAX]; // characters received
uint8_t _received_buff_len; // number of characters received
uint32_t _last_received_ms; // system time (in millis) that a message was successfully parsed (for health reporting)
// reply message handling
uint8_t _reply_msgid; // replies expected msgid (reply often does not specify the msgid so we must record it)
uint32_t _reply_wait_start_ms; // system time that we started waiting for a reply message
// Display system state flags
typedef union PACKED {
struct {
uint8_t set_throttle_stop : 1; // 0, warning that user must set throttle to stop before motor can run
uint8_t setup_allowed : 1; // 1, remote is allowed to enter setup mode
uint8_t in_charge : 1; // 2, master is in charging state
uint8_t in_setup : 1; // 3, master is in setup state
uint8_t bank_available : 1; // 4
uint8_t no_menu : 1; // 5
uint8_t menu_off : 1; // 6
uint8_t reserved7 : 1; // 7, unused
uint8_t temp_warning : 1; // 8, motor or battery temp warning
uint8_t batt_charge_valid : 1; // 9, battery charge valid
uint8_t batt_nearly_empty : 1; // 10, battery nearly empty
uint8_t batt_charging : 1; // 11, battery charging
uint8_t gps_searching : 1; // 12, gps searching for satellites
uint8_t gps_speed_valid : 1; // 13, gps speed is valid
uint8_t range_miles_valid : 1; // 14, range (in miles) is valid
uint8_t range_minutes_valid : 1; // 15, range (in minutes) is valid
};
uint16_t value;
} DisplaySystemStateFlags;
// Display system state
struct DisplaySystemState {
DisplaySystemStateFlags flags; // flags, see above for individual bit definitions
uint8_t master_state; // deprecated
uint8_t master_error_code; // error code (0=no error)
float motor_voltage; // motor voltage in volts
float motor_current; // motor current in amps
uint16_t motor_power; // motor power in watts
int16_t motor_rpm; // motor speed in rpm
uint8_t motor_pcb_temp; // motor pcb temp in C
uint8_t motor_stator_temp; // motor stator temp in C
uint8_t batt_charge_pct; // battery state of charge (0 to 100%)
float batt_voltage; // battery voltage in volts
float batt_current; // battery current in amps
uint16_t gps_speed; // gps speed in knots * 100
uint16_t range_miles; // range in nautical miles * 10
uint16_t range_minutes; // range in minutes (at current speed and current draw)
uint8_t temp_sw; // master PCB temp in C (close to motor power switches)
uint8_t temp_rp; // master PCB temp in C (close to reverse voltage protection)
uint32_t last_update_ms; // system time that system state was last updated
} _display_system_state;
// Display system setup
struct DisplaySystemSetup {
uint8_t flags; // 0 : battery config valid, all other bits unused
uint8_t motor_type; // motor type (0 or 3:Unknown, 1:Ultralight, 2:Cruise2, 4:Cruise4, 5:Travel503, 6:Travel1003, 7:Cruise10kW)
uint16_t motor_sw_version; // motor software version
uint16_t batt_capacity; // battery capacity in amp hours
uint8_t batt_charge_pct; // battery state of charge (0 to 100%)
uint8_t batt_type; // battery type (0:lead acid, 1:Lithium)
uint16_t master_sw_version; // master software version
} _display_system_setup;
// Motor status
struct MotorStatus {
union PACKED {
struct {
uint8_t temp_limit_motor : 1; // 0, motor speed limited due to motor temp
uint8_t temp_limit_pcb : 1; // 1, motor speed limited tue to PCB temp
uint8_t emergency_stop : 1; // 2, motor in emergency stop (must be cleared by master)
uint8_t running : 1; // 3, motor running
uint8_t power_limit : 1; // 4, motor power limited
uint8_t low_voltage_limit : 1; // 5, motor speed limited because of low voltage
uint8_t tilt : 1; // 6, motor is tilted
uint8_t reserved7 : 1; // 7, unused (always zero)
} status_flags;
uint8_t status_flags_value;
};
union PACKED {
struct {
uint8_t overcurrent : 1; // 0, motor stopped because of overcurrent
uint8_t blocked : 1; // 1, motor stopped because it is blocked
uint8_t overvoltage_static : 1; // 2, motor stopped because voltage too high
uint8_t undervoltage_static : 1; // 3, motor stopped because voltage too low
uint8_t overvoltage_current : 1; // 4, motor stopped because voltage spiked high
uint8_t undervoltage_current: 1; // 5, motor stopped because voltage spiked low
uint8_t overtemp_motor : 1; // 6, motor stopped because stator temp too high
uint8_t overtemp_pcb : 1; // 7, motor stopped because pcb temp too high
uint8_t timeout_rs485 : 1; // 8, motor stopped because Drive message not received for too long
uint8_t temp_sensor_error : 1; // 9, motor temp sensor is defective (motor will not stop)
uint8_t tilt : 1; // 10, motor stopped because it was tilted
uint8_t unused11to15 : 5; // 11 ~ 15 (always zero)
} error_flags;
uint16_t error_flags_value;
};
} _motor_status;
uint32_t _last_send_motor_status_request_ms; // system time (in milliseconds) that last motor status request was sent
// Motor params
struct MotorParam {
int16_t rpm; // motor rpm
uint16_t power; // motor power consumption in Watts
float voltage; // motor voltage in volts
float current; // motor current in amps
float pcb_temp; // pcb temp in C
float stator_temp; // stator temp in C
uint32_t last_update_ms;// system time that above values were updated
} _motor_param;
uint32_t _last_send_motor_param_request_ms; // system time (in milliseconds) that last motor param request was sent
// error reporting
DisplaySystemStateFlags _display_system_state_flags_prev; // backup of display system state flags
uint8_t _display_system_state_master_error_code_prev; // backup of display system state master_error_code
uint32_t _last_error_report_ms; // system time that flag changes were last reported (used to prevent spamming user)
MotorStatus _motor_status_prev; // backup of motor status
// returns a human-readable string corresponding the passed-in
// master error code (see page 93 of https://media.torqeedo.com/downloads/manuals/torqeedo-Travel-manual-DE-EN.pdf)
// If no conversion is available then nullptr is returned
const char *map_master_error_code_to_string(uint8_t code) const;
};
#endif // HAL_TORQEEDO_ENABLED