/*
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 .
*/
#include "AP_EFI_config.h"
#if AP_EFI_SERIAL_HIRTH_ENABLED
#include
#include
#include
#include
#include
#include
#include
#define HIRTH_MAX_PKT_SIZE 100
#define HIRTH_MAX_RAW_PKT_SIZE 103
#define SERIAL_WAIT_TIMEOUT_MS 100
#define ENGINE_RUNNING 4
#define THROTTLE_POSITION_FACTOR 10
#define CRANK_SHAFT_SENSOR_OK 0x0F
#define INJECTION_TIME_RESOLUTION 0.8
#define THROTTLE_POSITION_RESOLUTION 0.1
#define VOLTAGE_RESOLUTION 0.0049 /* 5/1024 */
#define ADC_CALIBRATION (5.0/1024.0)
#define MAP_HPA_PER_VOLT_FACTOR 248.2673
#define HPA_TO_KPA 0.1
#define TPS_SCALE 0.70
// request/response status constants
#define QUANTITY_REQUEST_STATUS 0x03
#define QUANTITY_SET_VALUE 0x17
#define CODE_REQUEST_STATUS_1 0x04
#define CODE_REQUEST_STATUS_2 0x0B
#define CODE_REQUEST_STATUS_3 0x0D
#define CODE_SET_VALUE 0xC9
#define CHECKSUM_REQUEST_STATUS_1 0xF9
#define CHECKSUM_REQUEST_STATUS_2 0xF2
#define CHECKSUM_REQUEST_STATUS_3 0xF0
#define QUANTITY_RESPONSE_STATUS_1 0x57
#define QUANTITY_RESPONSE_STATUS_2 0x65
#define QUANTITY_RESPONSE_STATUS_3 0x67
#define QUANTITY_ACK_SET_VALUES 0x03
extern const AP_HAL::HAL& hal;
/**
* @brief Constructor with port initialization
*
* @param _frontend
*/
AP_EFI_Serial_Hirth::AP_EFI_Serial_Hirth(AP_EFI &_frontend) :
AP_EFI_Backend(_frontend)
{
port = AP::serialmanager().find_serial(AP_SerialManager::SerialProtocol_EFI, 0);
set_default_coef1(1.0);
}
/**
* @brief checks for response from or makes requests to Hirth ECU periodically
*
*/
void AP_EFI_Serial_Hirth::update()
{
if (port == nullptr) {
return;
}
// parse response from Hirth
check_response();
// send request
send_request();
}
/**
* @brief Checks if required bytes are available and proceeds with parsing
*
*/
void AP_EFI_Serial_Hirth::check_response()
{
const uint32_t now = AP_HAL::millis();
// waiting for response
if (!waiting_response) {
return;
}
const uint32_t num_bytes = port->available();
// if already requested
if (num_bytes >= expected_bytes) {
// read data from buffer
uint8_t computed_checksum = 0;
computed_checksum += res_data.quantity = port->read();
computed_checksum += res_data.code = port->read();
if (res_data.code == requested_code) {
for (int i = 0; i < (res_data.quantity - QUANTITY_REQUEST_STATUS); i++) {
computed_checksum += raw_data[i] = port->read();
}
}
res_data.checksum = port->read();
if (res_data.checksum != (256 - computed_checksum)) {
crc_fail_cnt++;
port->discard_input();
} else {
uptime = now - last_packet_ms;
last_packet_ms = now;
internal_state.last_updated_ms = now;
decode_data();
copy_to_frontend();
port->discard_input();
}
waiting_response = false;
#if HAL_LOGGING_ENABLED
log_status();
#endif
}
// reset request if no response for SERIAL_WAIT_TIMEOUT_MS
if (waiting_response &&
now - last_request_ms > SERIAL_WAIT_TIMEOUT_MS) {
waiting_response = false;
last_request_ms = now;
port->discard_input();
ack_fail_cnt++;
}
}
/**
* @brief Send Throttle and Telemetry requests to Hirth
*
*/
void AP_EFI_Serial_Hirth::send_request()
{
if (waiting_response) {
return;
}
const uint32_t now = AP_HAL::millis();
bool request_was_sent;
// get new throttle value
const uint16_t new_throttle = (uint16_t)SRV_Channels::get_output_scaled(SRV_Channel::k_throttle);
// check for change or timeout for throttle value
if ((new_throttle != last_throttle) || (now - last_req_send_throttle_ms > 500)) {
// send new throttle value, only when ARMED
bool allow_throttle = hal.util->get_soft_armed();
if (!allow_throttle) {
#if AP_ICENGINE_ENABLED
const auto *ice = AP::ice();
if (ice != nullptr) {
allow_throttle = ice->allow_throttle_while_disarmed();
}
#endif // AP_ICENGINE_ENABLED
}
if (allow_throttle) {
request_was_sent = send_target_values(new_throttle);
} else {
request_was_sent = send_target_values(0);
}
last_throttle = new_throttle;
last_req_send_throttle_ms = now;
} else {
// request Status request at the driver update rate if no throttle commands
request_was_sent = send_request_status();
}
if (request_was_sent) {
waiting_response = true;
last_request_ms = now;
}
}
/**
* @brief sends the new throttle command to Hirth ECU
*
* @param thr - new throttle value given by SRV_Channel::k_throttle
* @return true - if success
* @return false - currently not implemented
*/
bool AP_EFI_Serial_Hirth::send_target_values(uint16_t thr)
{
uint8_t computed_checksum = 0;
// clear buffer
memset(raw_data, 0, ARRAY_SIZE(raw_data));
#if AP_EFI_THROTTLE_LINEARISATION_ENABLED
// linearise throttle input
thr = linearise_throttle(thr);
#endif
const uint16_t throttle = thr * THROTTLE_POSITION_FACTOR;
uint8_t idx = 0;
// set Quantity + Code + "20 bytes of records to set" + Checksum
computed_checksum += raw_data[idx++] = QUANTITY_SET_VALUE;
computed_checksum += raw_data[idx++] = requested_code = CODE_SET_VALUE;
computed_checksum += raw_data[idx++] = throttle & 0xFF;
computed_checksum += raw_data[idx++] = (throttle >> 8) & 0xFF;
// checksum calculation
raw_data[QUANTITY_SET_VALUE - 1] = (256 - computed_checksum);
expected_bytes = QUANTITY_ACK_SET_VALUES;
// write data
port->write(raw_data, QUANTITY_SET_VALUE);
return true;
}
/**
* @brief cyclically sends different Status requests to Hirth ECU
*
* @return true - when successful
* @return false - not implemented
*/
bool AP_EFI_Serial_Hirth::send_request_status() {
uint8_t requested_quantity;
uint8_t requested_checksum;
switch (requested_code)
{
case CODE_REQUEST_STATUS_1:
requested_quantity = QUANTITY_REQUEST_STATUS;
requested_code = CODE_REQUEST_STATUS_2;
requested_checksum = CHECKSUM_REQUEST_STATUS_2;
expected_bytes = QUANTITY_RESPONSE_STATUS_2;
break;
case CODE_REQUEST_STATUS_2:
requested_quantity = QUANTITY_REQUEST_STATUS;
requested_code = CODE_REQUEST_STATUS_3;
requested_checksum = CHECKSUM_REQUEST_STATUS_3;
expected_bytes = QUANTITY_RESPONSE_STATUS_3;
break;
case CODE_REQUEST_STATUS_3:
requested_quantity = QUANTITY_REQUEST_STATUS;
requested_code = CODE_REQUEST_STATUS_1;
requested_checksum = CHECKSUM_REQUEST_STATUS_1;
expected_bytes = QUANTITY_RESPONSE_STATUS_1;
break;
default:
requested_quantity = QUANTITY_REQUEST_STATUS;
requested_code = CODE_REQUEST_STATUS_1;
requested_checksum = CHECKSUM_REQUEST_STATUS_1;
expected_bytes = QUANTITY_RESPONSE_STATUS_1;
break;
}
raw_data[0] = requested_quantity;
raw_data[1] = requested_code;
raw_data[2] = requested_checksum;
port->write(raw_data, QUANTITY_REQUEST_STATUS);
return true;
}
/**
* @brief parses the response from Hirth ECU and updates the internal state instance
*
*/
void AP_EFI_Serial_Hirth::decode_data()
{
const uint32_t now = AP_HAL::millis();
switch (res_data.code) {
case CODE_REQUEST_STATUS_1: {
struct Record1 *record1 = (Record1*)raw_data;
internal_state.engine_speed_rpm = record1->rpm;
internal_state.throttle_out = record1->throttle;
// EFI2 log
internal_state.engine_state = (Engine_State)record1->engine_status;
internal_state.crankshaft_sensor_status = (record1->sensor_ok & CRANK_SHAFT_SENSOR_OK) ? Crankshaft_Sensor_Status::OK : Crankshaft_Sensor_Status::ERROR;
// ECYL log
internal_state.cylinder_status.injection_time_ms = record1->injection_time * INJECTION_TIME_RESOLUTION;
internal_state.cylinder_status.ignition_timing_deg = record1->ignition_angle;
// EFI3 log
internal_state.ignition_voltage = record1->battery_voltage * VOLTAGE_RESOLUTION;
engine_temperature_sensor_status = (record1->sensor_ok & 0x01) != 0;
air_temperature_sensor_status = (record1->sensor_ok & 0x02) != 0;
air_pressure_sensor_status = (record1->sensor_ok & 0x04) != 0;
throttle_sensor_status = (record1->sensor_ok & 0x08) != 0;
// resusing mavlink variables as required for Hirth
// add in ADC voltage of MAP sensor > convert to MAP in kPa
internal_state.intake_manifold_pressure_kpa = record1->voltage_int_air_pressure * (ADC_CALIBRATION * MAP_HPA_PER_VOLT_FACTOR * HPA_TO_KPA);
internal_state.intake_manifold_temperature = C_TO_KELVIN(record1->air_temperature);
break;
}
case CODE_REQUEST_STATUS_2: {
struct Record2 *record2 = (Record2*)raw_data;
// EFI log
const float fuel_consumption_rate_lph = record2->fuel_consumption * 0.1;
internal_state.fuel_consumption_rate_cm3pm = (fuel_consumption_rate_lph * 1000.0 / 60.0) * get_coef1();
if (last_fuel_integration_ms != 0) {
// estimated_consumed_fuel_volume_cm3 is in cm3/pm
const float dt_minutes = (now - last_fuel_integration_ms)*(0.001/60);
internal_state.estimated_consumed_fuel_volume_cm3 += internal_state.fuel_consumption_rate_cm3pm * dt_minutes;
}
last_fuel_integration_ms = now;
internal_state.throttle_position_percent = record2->throttle_percent_times_10 * 0.1;
break;
}
case CODE_REQUEST_STATUS_3: {
struct Record3 *record3 = (Record3*)raw_data;
// EFI3 Log
CHT_1_error_excess_temperature_status = (record3->error_excess_temperature_bitfield & 0x0007) != 0;
CHT_2_error_excess_temperature_status = (record3->error_excess_temperature_bitfield & 0x0038) != 0;
EGT_1_error_excess_temperature_status = (record3->error_excess_temperature_bitfield & 0x01C0) != 0;
EGT_2_error_excess_temperature_status = (record3->error_excess_temperature_bitfield & 0x0E00) != 0;
// ECYL log
internal_state.cylinder_status.cylinder_head_temperature = C_TO_KELVIN(record3->excess_temperature_1);
internal_state.cylinder_status.cylinder_head_temperature2 = C_TO_KELVIN(record3->excess_temperature_2);
internal_state.cylinder_status.exhaust_gas_temperature = C_TO_KELVIN(record3->excess_temperature_3);
internal_state.cylinder_status.exhaust_gas_temperature2 = C_TO_KELVIN(record3->excess_temperature_4);
break;
}
// case CODE_SET_VALUE:
// // Do nothing for now
// break;
}
}
#if HAL_LOGGING_ENABLED
void AP_EFI_Serial_Hirth::log_status(void)
{
// @LoggerMessage: EFIS
// @Description: Electronic Fuel Injection data - Hirth specific Status information
// @Field: TimeUS: Time since system startup
// @Field: ETS1: Status of EGT1 excess temperature error
// @Field: ETS2: Status of EGT2 excess temperature error
// @Field: CTS1: Status of CHT1 excess temperature error
// @Field: CTS2: Status of CHT2 excess temperature error
// @Field: ETSS: Status of Engine temperature sensor
// @Field: ATSS: Status of Air temperature sensor
// @Field: APSS: Status of Air pressure sensor
// @Field: TSS: Status of Temperature sensor
// @Field: CRCF: CRC failure count
// @Field: AckF: ACK failure count
// @Field: Up: Uptime between 2 messages
// @Field: ThrO: Throttle output as received by the engine
AP::logger().WriteStreaming("EFIS",
"TimeUS,ETS1,ETS2,CTS1,CTS2,ETSS,ATSS,APSS,TSS,CRCF,AckF,Up,ThrO",
"s------------",
"F------------",
"QBBBBBBBBIIIf",
AP_HAL::micros64(),
uint8_t(EGT_1_error_excess_temperature_status),
uint8_t(EGT_2_error_excess_temperature_status),
uint8_t(CHT_1_error_excess_temperature_status),
uint8_t(CHT_2_error_excess_temperature_status),
uint8_t(engine_temperature_sensor_status),
uint8_t(air_temperature_sensor_status),
uint8_t(air_pressure_sensor_status),
uint8_t(throttle_sensor_status),
uint32_t(crc_fail_cnt),
uint32_t(ack_fail_cnt),
uint32_t(uptime),
float(internal_state.throttle_out));
}
#endif // HAL_LOGGING_ENABLED
#endif // AP_EFI_SERIAL_HIRTH_ENABLED