LCOV - code coverage report
Current view: top level - AP_EFI - AP_EFI_Serial_MS.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 117 126 92.9 %
Date: 2026-09-09 11:02:08 Functions: 6 6 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             : #include "AP_EFI_config.h"
      17             : 
      18             : #if AP_EFI_SERIAL_MS_ENABLED
      19             : 
      20             : #include <AP_HAL/AP_HAL.h>
      21             : #include <AP_Math/AP_Math.h>
      22             : #include <AP_SerialManager/AP_SerialManager.h>
      23             : 
      24             : #include "AP_EFI_Serial_MS.h"
      25             : 
      26             : extern const AP_HAL::HAL &hal;
      27             : 
      28           1 : AP_EFI_Serial_MS::AP_EFI_Serial_MS(AP_EFI &_frontend):
      29           1 :     AP_EFI_Backend(_frontend)
      30             : {
      31           1 :     internal_state.estimated_consumed_fuel_volume_cm3 = 0; // Just to be sure
      32           1 :     port = AP::serialmanager().find_serial(AP_SerialManager::SerialProtocol_EFI, 0);
      33           1 : }
      34             : 
      35             : 
      36        1216 : void AP_EFI_Serial_MS::update()
      37             : {
      38        1216 :     if (!port) {
      39           0 :         return;
      40             :     }
      41             : 
      42        1216 :     uint32_t now = AP_HAL::millis();
      43             : 
      44        1216 :     const uint32_t expected_bytes = 2 + (RT_LAST_OFFSET - RT_FIRST_OFFSET) + 4;
      45        1216 :     if (port->available() >= expected_bytes && read_incoming_realtime_data()) {
      46         235 :         copy_to_frontend();
      47             :     }
      48             : 
      49        1216 :     const uint32_t last_request_delta = (now - last_request_ms);
      50        1216 :     const uint32_t available = port->available();
      51        1216 :     if (((last_request_delta > 150) && (available > 0)) || // nothing in our input buffer 150 ms after request
      52         244 :         ((last_request_delta > 90)  && (available == 0))) { // we requested something over 90 ms ago, but didn't get any data
      53         244 :         port->discard_input();
      54         244 :         last_request_ms = now;
      55             :         // Request an update from the realtime table (7).
      56             :         // The data we need start at offset 6 and ends at 129
      57         244 :         send_request(7, RT_FIRST_OFFSET, RT_LAST_OFFSET);
      58             :     }
      59             : }
      60             : 
      61         235 : bool AP_EFI_Serial_MS::read_incoming_realtime_data() 
      62             : {
      63             :     // Data is parsed directly from the buffer, otherwise we would need to allocate
      64             :     // several hundred bytes for the entire realtime data table or request every
      65             :     // value individually
      66         235 :     uint16_t message_length = 0;
      67             : 
      68             :     // reset checksum before reading new data
      69         235 :     checksum = 0;
      70             :     
      71             :     // Message length field begins the message (16 bits, excluded from CRC calculation)
      72             :     // Message length value excludes the message length and CRC bytes 
      73         235 :     message_length = port->read() << 8;
      74         235 :     message_length += port->read();
      75             : 
      76         235 :     if (message_length >= 256) {
      77             :         // don't process invalid messages
      78             :         // hal.console->printf("message_length: %u\n", message_length);
      79           0 :         return false;
      80             :     }
      81             : 
      82             :     // Response Flag (see "response_codes" enum)
      83         235 :     response_flag = read_byte_CRC32();
      84         235 :     if (response_flag != RESPONSE_WRITE_OK) {
      85             :         // abort read if we did not receive the correct response code;
      86           0 :         return false;
      87             :     }
      88             :     
      89             :     // Iterate over the payload bytes 
      90       27495 :     for (uint16_t offset=RT_FIRST_OFFSET; offset < (RT_FIRST_OFFSET + message_length - 1); offset++) {
      91       27260 :         uint8_t data = read_byte_CRC32();
      92             :         float temp_float;
      93       27260 :         switch (offset) {
      94         235 :             case PW1_MSB:
      95         235 :                 internal_state.cylinder_status.injection_time_ms = (float)((data << 8) + read_byte_CRC32())*0.001f;
      96         235 :                 offset++;  // increment the counter because we read a byte in the previous line
      97         235 :                 break;
      98         235 :             case RPM_MSB:
      99             :                 // Read 16 bit RPM
     100         235 :                 internal_state.engine_speed_rpm = (data << 8) + read_byte_CRC32();
     101         235 :                 offset++;
     102         235 :                 break;
     103         235 :             case ADVANCE_MSB:
     104         235 :                 internal_state.cylinder_status.ignition_timing_deg = (float)((data << 8) + read_byte_CRC32())*0.1f;
     105         235 :                 offset++;
     106         235 :                 break;
     107         235 :             case ENGINE_BM:
     108         235 :                 break;
     109         235 :             case BAROMETER_MSB:
     110         235 :                 internal_state.atmospheric_pressure_kpa = (float)((data << 8) + read_byte_CRC32())*0.1f;
     111         235 :                 offset++;
     112         235 :                 break;
     113         235 :             case MAP_MSB:
     114         235 :                 internal_state.intake_manifold_pressure_kpa = (float)((data << 8) + read_byte_CRC32())*0.1f;
     115         235 :                 offset++;
     116         235 :                 break;
     117         235 :             case MAT_MSB:
     118         235 :                 temp_float = (float)((data << 8) + read_byte_CRC32())*0.1f;
     119         235 :                 offset++;
     120         235 :                 internal_state.intake_manifold_temperature = degF_to_Kelvin(temp_float);
     121         235 :                 break;
     122         235 :             case CHT_MSB:
     123         235 :                 temp_float = (float)((data << 8) + read_byte_CRC32())*0.1f;
     124         235 :                 offset++;
     125         235 :                 internal_state.cylinder_status.cylinder_head_temperature = degF_to_Kelvin(temp_float);
     126         235 :                 break;
     127         235 :             case TPS_MSB:
     128         235 :                 temp_float = (float)((data << 8) + read_byte_CRC32())*0.1f;
     129         235 :                 offset++;
     130         235 :                 internal_state.throttle_position_percent = roundf(temp_float);
     131         235 :                 break;
     132         235 :             case AFR1_MSB:
     133         235 :                 temp_float = (float)((data << 8) + read_byte_CRC32())*0.1f;
     134         235 :                 offset++;
     135         235 :                 internal_state.cylinder_status.lambda_coefficient = temp_float;
     136         235 :                 break;
     137         235 :             case DWELL_MSB:
     138         235 :                 temp_float = (float)((data << 8) + read_byte_CRC32())*0.1f;
     139         235 :                 internal_state.spark_dwell_time_ms = temp_float;
     140         235 :                 offset++;
     141         235 :                 break;
     142         235 :             case LOAD:
     143         235 :                 internal_state.engine_load_percent = data;
     144         235 :                 break;
     145           0 :             case FUEL_PRESSURE_MSB:
     146             :                 // MS Fuel Pressure is unitless, store as KPA anyway
     147           0 :                 temp_float = (float)((data << 8) + read_byte_CRC32());
     148           0 :                 internal_state.fuel_pressure = temp_float;
     149           0 :                 offset++;
     150           0 :                 break;   
     151             :                 
     152             :         }
     153             :     }
     154             :     
     155             :     // Read the four CRC bytes
     156             :     uint32_t received_CRC;
     157         235 :     received_CRC = port->read() << 24;
     158         235 :     received_CRC += port->read() << 16;
     159         235 :     received_CRC += port->read() << 8;
     160         235 :     received_CRC += port->read();
     161             :                         
     162         235 :     if (received_CRC != checksum) {
     163             :         // hal.console->printf("EFI CRC: 0x%08x 0x%08x\n", received_CRC, checksum);
     164           0 :         return false;
     165             :     }
     166             : 
     167             :     // Calculate Fuel Consumption 
     168             :     // Duty Cycle (Percent, because that's how HFE gives us the calibration coefficients)
     169         235 :     float duty_cycle = (internal_state.cylinder_status.injection_time_ms * internal_state.engine_speed_rpm)/600.0f;
     170         235 :     uint32_t current_time = AP_HAL::millis();
     171             :     // Super Simplified integration method - Error Analysis TBD
     172             :     // This calculation gives erroneous results when the engine isn't running
     173         235 :     if (internal_state.engine_speed_rpm > RPM_THRESHOLD) {
     174          15 :         internal_state.fuel_consumption_rate_cm3pm = duty_cycle*get_coef1() - get_coef2();
     175          15 :         internal_state.estimated_consumed_fuel_volume_cm3 += internal_state.fuel_consumption_rate_cm3pm * (current_time - internal_state.last_updated_ms)/60000.0f;
     176             :     } else {
     177         220 :         internal_state.fuel_consumption_rate_cm3pm = 0;
     178             :     }
     179         235 :     internal_state.last_updated_ms = current_time;
     180             :     
     181         235 :     return true;
     182             :          
     183             : }
     184             : 
     185         244 : void AP_EFI_Serial_MS::send_request(uint8_t table, uint16_t first_offset, uint16_t last_offset)
     186             : {
     187         244 :     uint16_t length = last_offset - first_offset + 1;
     188             :     // Fixed message size (0x0007)
     189             :     // Command 'r' (0x72)
     190             :     // Null CANid (0x00)
     191         244 :     const uint8_t data[9] = {
     192             :         0x00,
     193             :         0x07,
     194             :         0x72,
     195             :         0x00,
     196             :         (uint8_t)table,
     197         244 :         (uint8_t)(first_offset >> 8),
     198             :         (uint8_t)(first_offset),
     199         244 :         (uint8_t)(length >> 8),
     200             :         (uint8_t)(length)   
     201         244 :     };
     202             :     
     203         244 :     uint32_t crc = 0;
     204             :     
     205             :     // Write the request and calc CRC
     206        2440 :     for (uint8_t i = 0;  i != sizeof(data) ; i++) {
     207             :         // Message size is excluded from CRC
     208        2196 :         if (i > 1) {
     209        1708 :             crc = CRC32_compute_byte(crc, data[i]);
     210             :         }
     211        2196 :         port->write(data[i]);
     212             :     }
     213             :     
     214             :     // Write the CRC32
     215         244 :     port->write((uint8_t)(crc >> 24));
     216         244 :     port->write((uint8_t)(crc >> 16));
     217         244 :     port->write((uint8_t)(crc >> 8));
     218         244 :     port->write((uint8_t)crc);
     219             : 
     220         244 : }
     221             : 
     222       29845 : uint8_t AP_EFI_Serial_MS::read_byte_CRC32()
     223             : {   
     224             :     // Read a byte and update the CRC 
     225       29845 :     uint8_t data = port->read();
     226       29845 :     checksum = CRC32_compute_byte(checksum, data);
     227       29845 :     return data;
     228             : }
     229             : 
     230             : // CRC32 matching MegaSquirt
     231       31553 : uint32_t AP_EFI_Serial_MS::CRC32_compute_byte(uint32_t crc, uint8_t data)
     232             : {
     233       31553 :     crc ^= ~0U;
     234       31553 :     crc = crc_crc32(crc, &data, 1);
     235       31553 :     crc ^= ~0U;
     236       31553 :     return crc;
     237             : }
     238             : 
     239             : #endif  // AP_EFI_SERIAL_MS_ENABLED

Generated by: LCOV version 1.14