LCOV - code coverage report
Current view: top level - AP_EFI - AP_EFI_Serial_Hirth.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 150 151 99.3 %
Date: 2026-09-09 11:02:08 Functions: 8 8 100.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             : 
      17             : 
      18             : #include "AP_EFI_config.h"
      19             : 
      20             : #if AP_EFI_SERIAL_HIRTH_ENABLED
      21             : #include <AP_HAL/AP_HAL.h>
      22             : #include <AP_EFI/AP_EFI_Serial_Hirth.h>
      23             : #include <AP_SerialManager/AP_SerialManager.h>
      24             : #include <SRV_Channel/SRV_Channel.h>
      25             : #include <AP_ICEngine/AP_ICEngine.h>
      26             : #include <AP_Math/definitions.h>
      27             : #include <AP_Logger/AP_Logger.h>
      28             : 
      29             : #define HIRTH_MAX_PKT_SIZE 100
      30             : #define HIRTH_MAX_RAW_PKT_SIZE 103
      31             : 
      32             : #define SERIAL_WAIT_TIMEOUT_MS 100
      33             : 
      34             : #define ENGINE_RUNNING 4
      35             : #define THROTTLE_POSITION_FACTOR 10
      36             : #define INJECTION_TIME_RESOLUTION 0.8
      37             : #define THROTTLE_POSITION_RESOLUTION 0.1
      38             : #define VOLTAGE_RESOLUTION 0.0049       /* 5/1024 */
      39             : #define ADC_CALIBRATION (5.0/1024.0)
      40             : #define MAP_HPA_PER_VOLT_FACTOR 248.2673
      41             : #define HPA_TO_KPA 0.1
      42             : #define TPS_SCALE 0.70
      43             : 
      44             : // request/response status constants
      45             : #define QUANTITY_REQUEST_STATUS    0x03
      46             : #define QUANTITY_SET_VALUE         0x17
      47             : #define CODE_REQUEST_STATUS_1      0x04
      48             : #define CODE_REQUEST_STATUS_2      0x0B
      49             : #define CODE_REQUEST_STATUS_3      0x0D
      50             : #define CODE_SET_VALUE             0xC9
      51             : #define CHECKSUM_REQUEST_STATUS_1  0xF9
      52             : #define CHECKSUM_REQUEST_STATUS_2  0xF2
      53             : #define CHECKSUM_REQUEST_STATUS_3  0xF0
      54             : #define QUANTITY_RESPONSE_STATUS_1 0x57
      55             : #define QUANTITY_RESPONSE_STATUS_2 0x65
      56             : #define QUANTITY_RESPONSE_STATUS_3 0x67
      57             : #define QUANTITY_ACK_SET_VALUES    0x03
      58             : 
      59             : extern const AP_HAL::HAL& hal;
      60             : 
      61             : /**
      62             :  * @brief Constructor with port initialization
      63             :  * 
      64             :  * @param _frontend 
      65             :  */
      66           1 : AP_EFI_Serial_Hirth::AP_EFI_Serial_Hirth(AP_EFI &_frontend) :
      67           1 :     AP_EFI_Backend(_frontend)
      68             : {
      69           1 :     port = AP::serialmanager().find_serial(AP_SerialManager::SerialProtocol_EFI, 0);
      70           1 :     set_default_coef1(1.0);
      71           1 : }
      72             : 
      73             : /**
      74             :  * @brief checks for response from or makes requests to Hirth ECU periodically
      75             :  * 
      76             :  */
      77        1216 : void AP_EFI_Serial_Hirth::update()
      78             : {
      79        1216 :     if (port == nullptr) {
      80           0 :         return;
      81             :     }
      82             : 
      83             :     // parse response from Hirth
      84        1216 :     check_response();
      85             : 
      86             :     // send request
      87        1216 :     send_request();
      88             : }
      89             : 
      90             : /**
      91             :  * @brief Checks if required bytes are available and proceeds with parsing
      92             :  * 
      93             :  */
      94        1216 : void AP_EFI_Serial_Hirth::check_response()
      95             : {
      96        1216 :     const uint32_t now = AP_HAL::millis();
      97             : 
      98             :     // waiting for response
      99        1216 :     if (!waiting_response) {
     100           1 :         return;
     101             :     }
     102             : 
     103        1215 :     const uint32_t num_bytes = port->available();
     104             : 
     105             :     // if already requested
     106        1215 :     if (num_bytes >= expected_bytes) {
     107             :         // read data from buffer
     108         609 :         uint8_t computed_checksum = 0;
     109         609 :         computed_checksum += res_data.quantity = port->read();
     110         609 :         computed_checksum += res_data.code = port->read();
     111             : 
     112         609 :         if (res_data.code == requested_code) {
     113       53195 :             for (int i = 0; i < (res_data.quantity - QUANTITY_REQUEST_STATUS); i++) {
     114       52586 :                 computed_checksum += raw_data[i] = port->read();
     115             :             }
     116             :         }
     117             : 
     118         609 :         res_data.checksum = port->read();
     119         609 :         if (res_data.checksum != (256 - computed_checksum)) {
     120          46 :             crc_fail_cnt++;
     121          46 :             port->discard_input();
     122             :         } else {
     123         563 :             uptime = now - last_packet_ms;
     124         563 :             last_packet_ms = now;
     125         563 :             internal_state.last_updated_ms = now;
     126         563 :             decode_data();
     127         563 :             copy_to_frontend();
     128         563 :             port->discard_input();
     129             :         }
     130             : 
     131         609 :         waiting_response = false;
     132             : 
     133             : #if HAL_LOGGING_ENABLED
     134         609 :         log_status();
     135             : #endif
     136             :     }
     137             : 
     138             :     // reset request if no response for SERIAL_WAIT_TIMEOUT_MS
     139        1215 :     if (waiting_response &&
     140         606 :         now - last_request_ms > SERIAL_WAIT_TIMEOUT_MS) {
     141           7 :         waiting_response = false;
     142           7 :         last_request_ms = now;
     143             : 
     144           7 :         port->discard_input();
     145           7 :         ack_fail_cnt++;
     146             :     }
     147             : }
     148             : 
     149             : /**
     150             :  * @brief Send Throttle and Telemetry requests to Hirth
     151             :  * 
     152             :  */
     153        1216 : void AP_EFI_Serial_Hirth::send_request()
     154             : {
     155        1216 :     if (waiting_response) {
     156         599 :         return;
     157             :     }
     158             : 
     159         617 :     const uint32_t now = AP_HAL::millis();
     160             :     bool request_was_sent;
     161             : 
     162             :     // get new throttle value
     163         617 :     const uint16_t new_throttle = (uint16_t)SRV_Channels::get_output_scaled(SRV_Channel::k_throttle);
     164             : 
     165             :     // check for change or timeout for throttle value
     166         617 :     if ((new_throttle != last_throttle) || (now - last_req_send_throttle_ms > 500)) {
     167             :         // send new throttle value, only when ARMED
     168          48 :         bool allow_throttle = hal.util->get_soft_armed();
     169          48 :         if (!allow_throttle) {
     170             : #if AP_ICENGINE_ENABLED
     171          44 :             const auto *ice = AP::ice();
     172          44 :             if (ice != nullptr) {
     173          44 :                 allow_throttle = ice->allow_throttle_while_disarmed();
     174             :             }
     175             : #endif  // AP_ICENGINE_ENABLED
     176             :         }
     177          48 :         if (allow_throttle) {
     178           4 :             request_was_sent = send_target_values(new_throttle);
     179             :         } else {
     180          44 :             request_was_sent = send_target_values(0);
     181             :         }
     182             : 
     183          48 :         last_throttle = new_throttle;
     184          48 :         last_req_send_throttle_ms = now;
     185          48 :     } else {
     186             :         // request Status request at the driver update rate if no throttle commands
     187         569 :         request_was_sent = send_request_status();
     188             :     }
     189             : 
     190         617 :     if (request_was_sent) {
     191         617 :         waiting_response = true;
     192         617 :         last_request_ms = now;
     193             :     }
     194             : }
     195             : 
     196             : 
     197             : /**
     198             :  * @brief sends the new throttle command to Hirth ECU
     199             :  * 
     200             :  * @param thr - new throttle value given by SRV_Channel::k_throttle
     201             :  * @return true - if success
     202             :  * @return false - currently not implemented
     203             :  */
     204          48 : bool AP_EFI_Serial_Hirth::send_target_values(uint16_t thr)
     205             : {
     206          48 :     uint8_t computed_checksum = 0;
     207          48 :     throttle_to_hirth = 0;
     208             : 
     209             :     // clear buffer
     210          48 :     memset(raw_data, 0, ARRAY_SIZE(raw_data));
     211             : 
     212             : #if AP_EFI_THROTTLE_LINEARISATION_ENABLED
     213             :     // linearise throttle input
     214          48 :     thr = linearise_throttle(thr);
     215             : #endif
     216             : 
     217          48 :     throttle_to_hirth = thr * THROTTLE_POSITION_FACTOR;
     218             : 
     219          48 :     uint8_t idx = 0;
     220             : 
     221             :     // set Quantity + Code + "20 bytes of records to set" + Checksum
     222          48 :     computed_checksum += raw_data[idx++] = QUANTITY_SET_VALUE;
     223          48 :     computed_checksum += raw_data[idx++] = requested_code = CODE_SET_VALUE;
     224          48 :     computed_checksum += raw_data[idx++] = throttle_to_hirth & 0xFF;
     225          48 :     computed_checksum += raw_data[idx++] = (throttle_to_hirth >> 8) & 0xFF;
     226             : 
     227             :     // checksum calculation
     228          48 :     raw_data[QUANTITY_SET_VALUE - 1] = (256 - computed_checksum);
     229             :     
     230          48 :     expected_bytes = QUANTITY_ACK_SET_VALUES;
     231             :     // write data
     232          48 :     port->write(raw_data, QUANTITY_SET_VALUE);
     233             : 
     234          48 :     return true;
     235             : }
     236             : 
     237             : 
     238             : /**
     239             :  * @brief cyclically sends different Status requests to Hirth ECU
     240             :  * 
     241             :  * @return true - when successful
     242             :  * @return false  - not implemented
     243             :  */
     244         569 : bool AP_EFI_Serial_Hirth::send_request_status() {
     245             : 
     246             :     uint8_t requested_quantity;
     247             :     uint8_t requested_checksum;
     248             : 
     249         569 :     switch (requested_code)
     250             :     {
     251         179 :     case CODE_REQUEST_STATUS_1:
     252         179 :         requested_quantity = QUANTITY_REQUEST_STATUS;
     253         179 :         requested_code = CODE_REQUEST_STATUS_2;
     254         179 :         requested_checksum = CHECKSUM_REQUEST_STATUS_2;
     255         179 :         expected_bytes = QUANTITY_RESPONSE_STATUS_2;
     256         179 :         break;
     257         177 :     case CODE_REQUEST_STATUS_2:
     258         177 :         requested_quantity = QUANTITY_REQUEST_STATUS;
     259         177 :         requested_code = CODE_REQUEST_STATUS_3;
     260         177 :         requested_checksum = CHECKSUM_REQUEST_STATUS_3;
     261         177 :         expected_bytes = QUANTITY_RESPONSE_STATUS_3;
     262         177 :         break;
     263         166 :     case CODE_REQUEST_STATUS_3:
     264         166 :         requested_quantity = QUANTITY_REQUEST_STATUS;
     265         166 :         requested_code = CODE_REQUEST_STATUS_1;
     266         166 :         requested_checksum = CHECKSUM_REQUEST_STATUS_1;
     267         166 :         expected_bytes = QUANTITY_RESPONSE_STATUS_1;
     268         166 :         break;
     269          47 :     default:
     270          47 :         requested_quantity = QUANTITY_REQUEST_STATUS;
     271          47 :         requested_code = CODE_REQUEST_STATUS_1;
     272          47 :         requested_checksum = CHECKSUM_REQUEST_STATUS_1;
     273          47 :         expected_bytes = QUANTITY_RESPONSE_STATUS_1;
     274          47 :         break;
     275             :     }
     276         569 :     raw_data[0] = requested_quantity;
     277         569 :     raw_data[1] = requested_code;
     278         569 :     raw_data[2] = requested_checksum;
     279             : 
     280         569 :     port->write(raw_data, QUANTITY_REQUEST_STATUS);
     281             : 
     282         569 :     return true;
     283             : }
     284             : 
     285             : 
     286             : /**
     287             :  * @brief parses the response from Hirth ECU and updates the internal state instance
     288             :  * 
     289             :  */
     290         563 : void AP_EFI_Serial_Hirth::decode_data()
     291             : {
     292         563 :     const uint32_t now = AP_HAL::millis();
     293             : 
     294         563 :     switch (res_data.code) {
     295         210 :     case CODE_REQUEST_STATUS_1: {
     296         210 :         struct Record1 *record1 = (Record1*)raw_data;
     297             : 
     298         210 :         internal_state.engine_speed_rpm = record1->rpm;
     299         210 :         internal_state.throttle_out = record1->throttle;
     300             : 
     301             :         // EFI2 log
     302         210 :         internal_state.engine_state = (Engine_State)record1->engine_status;
     303             : 
     304             :         // ECYL log
     305         210 :         internal_state.cylinder_status.injection_time_ms = record1->injection_time * INJECTION_TIME_RESOLUTION;
     306         210 :         internal_state.cylinder_status.ignition_timing_deg = record1->ignition_angle;
     307             : 
     308             :         // EFI3 log
     309         210 :         internal_state.ignition_voltage = record1->battery_voltage * VOLTAGE_RESOLUTION;
     310             : 
     311         210 :         sensor_status = record1->sensor_ok;
     312             : 
     313             :         // resusing mavlink variables as required for Hirth
     314             :         // add in ADC voltage of MAP sensor > convert to MAP in kPa
     315         210 :         internal_state.intake_manifold_pressure_kpa = record1->voltage_int_air_pressure * (ADC_CALIBRATION * MAP_HPA_PER_VOLT_FACTOR * HPA_TO_KPA);
     316         210 :         internal_state.intake_manifold_temperature = C_TO_KELVIN(record1->air_temperature);
     317         210 :         break;
     318             :     }
     319             : 
     320         177 :     case CODE_REQUEST_STATUS_2: {
     321         177 :         struct Record2 *record2 = (Record2*)raw_data;
     322             : 
     323             :         // EFI log
     324         177 :         const float fuel_consumption_rate_lph = record2->fuel_consumption * 0.1;
     325             : 
     326         177 :         internal_state.fuel_consumption_rate_cm3pm = (fuel_consumption_rate_lph * 1000.0 / 60.0) * get_coef1();
     327             : 
     328         177 :         if (last_fuel_integration_ms != 0) {
     329             :             // estimated_consumed_fuel_volume_cm3 is in cm3/pm
     330         176 :             const float dt_minutes = (now - last_fuel_integration_ms)*(0.001/60);
     331         176 :             internal_state.estimated_consumed_fuel_volume_cm3 += internal_state.fuel_consumption_rate_cm3pm * dt_minutes;
     332             :         }
     333         177 :         last_fuel_integration_ms = now;
     334             : 
     335         177 :         internal_state.throttle_position_percent = record2->throttle_percent_times_10 * 0.1;
     336         177 :         break;
     337             :     }
     338             : 
     339         176 :     case CODE_REQUEST_STATUS_3: {
     340         176 :         struct Record3 *record3 = (Record3*)raw_data;
     341             : 
     342             :         // EFI3 Log
     343         176 :         error_excess_temperature = record3->error_excess_temperature_bitfield;
     344             : 
     345             :         // ECYL log
     346         176 :         internal_state.cylinder_status.cylinder_head_temperature = C_TO_KELVIN(record3->excess_temperature_1);
     347         176 :         internal_state.cylinder_status.cylinder_head_temperature2 = C_TO_KELVIN(record3->excess_temperature_2);
     348         176 :         internal_state.cylinder_status.exhaust_gas_temperature = C_TO_KELVIN(record3->excess_temperature_3);
     349         176 :         internal_state.cylinder_status.exhaust_gas_temperature2 = C_TO_KELVIN(record3->excess_temperature_4);
     350         176 :         break;
     351             :     }
     352             :         
     353             :     // case CODE_SET_VALUE:
     354             :     //     // Do nothing for now
     355             :     //     break;
     356             :     }
     357         563 : }
     358             : 
     359             : #if HAL_LOGGING_ENABLED
     360         609 : void AP_EFI_Serial_Hirth::log_status(void)
     361             : {
     362             :     // @LoggerMessage: EFIS
     363             :     // @Description: Electronic Fuel Injection data - Hirth specific Status information
     364             :     // @Field: TimeUS: Time since system startup
     365             :     // @Field: EET: Error Excess Temperature Bitfield
     366             :     // @FieldBitmaskEnum: EET: AP_EFI_Serial_Hirth:::Error_Excess_Temp_Bitfield
     367             :     // @Field: FLAG: Sensor Status Bitfield
     368             :     // @FieldBitmaskEnum: FLAG: AP_EFI_Serial_Hirth:::Sensor_Status_Bitfield
     369             :     // @Field: CRF: CRC failure count
     370             :     // @Field: AKF: ACK failure count
     371             :     // @Field: Up: Uptime between 2 messages
     372             :     // @Field: ThO: Throttle output as received by the engine
     373             :     // @Field: ThM: Modified throttle_to_hirth output sent to the engine
     374        1218 :     AP::logger().WriteStreaming("EFIS",
     375             :                                 "TimeUS,EET,FLAG,CRF,AKF,Up,ThO,ThM",
     376             :                                 "s-------",
     377             :                                 "F-------",
     378             :                                 "QHBIIIfH",
     379             :                                 AP_HAL::micros64(),
     380         609 :                                 uint16_t(error_excess_temperature),
     381         609 :                                 uint8_t(sensor_status),
     382         609 :                                 uint32_t(crc_fail_cnt),
     383         609 :                                 uint32_t(ack_fail_cnt),
     384         609 :                                 uint32_t(uptime),
     385         609 :                                 float(internal_state.throttle_out),
     386         609 :                                 uint16_t(throttle_to_hirth));
     387         609 : }
     388             : #endif // HAL_LOGGING_ENABLED
     389             : 
     390             : #endif // AP_EFI_SERIAL_HIRTH_ENABLED

Generated by: LCOV version 1.14