diff --git a/ECU_V2/README.md b/ECU_V2/README.md new file mode 100644 index 0000000..b832782 --- /dev/null +++ b/ECU_V2/README.md @@ -0,0 +1,6 @@ +## Generate hash number to file name mapping +(Note: untested) You should be able to generate a reverse mapping table to +recover file names from the reported hash by running +```bash +$ pio test -e native --filter="test_generate_file_hash_mapping" -v +``` \ No newline at end of file diff --git a/ECU_V2/lib/common/assert.hpp b/ECU_V2/lib/common/assert.hpp index db4252d..07b2170 100644 --- a/ECU_V2/lib/common/assert.hpp +++ b/ECU_V2/lib/common/assert.hpp @@ -37,6 +37,7 @@ enum class AssertCode: uint8_t { TorqueLessThanZero = 4, SmoothTorqueLessThanZero = 5, ThrottleOverflow = 6, + BadMessage = 7, }; enum class AssertLevel { @@ -60,8 +61,5 @@ void assert_failed(AssertLevel level, const char* file, int line, AssertCode err } \ } while (0) -#define SOFT_ASSERT(condition) GENERIC_ASSERT(AssertLevel::Soft, (condition), AssertCode::Unknown) -#define SAFETY_ASSERT(condition) GENERIC_ASSERT(AssertLevel::Safety, (condition), AssertCode::Unknown) - -#define SOFT_ASSERT_CODE(condition, code) GENERIC_ASSERT(AssertLevel::Soft, (condition), (code)) -#define SAFETY_ASSERT_CODE(condition, code) GENERIC_ASSERT(AssertLevel::Safety, (condition), (code)) +#define SOFT_ASSERT(condition, code) GENERIC_ASSERT(AssertLevel::Soft, (condition), (code)) +#define SAFETY_ASSERT(condition, code) GENERIC_ASSERT(AssertLevel::Safety, (condition), (code)) diff --git a/ECU_V2/lib/common/can_serde.cpp b/ECU_V2/lib/common/can_serde.cpp index d8eeb9f..ac24b9d 100644 --- a/ECU_V2/lib/common/can_serde.cpp +++ b/ECU_V2/lib/common/can_serde.cpp @@ -11,7 +11,7 @@ /* Guaranteed to generate a message filled with zeroes. */ CAN_message_t empty_can_message(MessageId id, uint8_t len) { - CAN_message_t result = {0}; + CAN_message_t result = {}; /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. */ result.id = static_cast(id); result.len = len; @@ -70,7 +70,7 @@ void write_f32_le(uint8_t* buf, float value) { bool parse_start_switch(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::StartSwitch) && msg.len >= 1); + SAFETY_ASSERT(msg.id == static_cast(MessageId::StartSwitch) && msg.len >= 1, AssertCode::BadMessage); return msg.buf[0] != 0; } @@ -83,7 +83,7 @@ CAN_message_t create_start_switch(bool value) { uint16_t parse_throttle_one_position(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::ThrottleOnePosition) && msg.len >= 2); + SAFETY_ASSERT(msg.id == static_cast(MessageId::ThrottleOnePosition) && msg.len >= 2, AssertCode::BadMessage); return read_u16_le(msg.buf); } @@ -96,7 +96,7 @@ CAN_message_t create_throttle_one_position(uint16_t value) { uint16_t parse_throttle_two_position(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::ThrottleTwoPosition) && msg.len >= 2); + SAFETY_ASSERT(msg.id == static_cast(MessageId::ThrottleTwoPosition) && msg.len >= 2, AssertCode::BadMessage); return read_u16_le(msg.buf); } @@ -109,7 +109,7 @@ CAN_message_t create_throttle_two_position(uint16_t value) { uint16_t parse_brake_pressure(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::BrakePressure) && msg.len >= 2); + SAFETY_ASSERT(msg.id == static_cast(MessageId::BrakePressure) && msg.len >= 2, AssertCode::BadMessage); return read_u16_le(msg.buf); } @@ -122,10 +122,10 @@ CAN_message_t create_brake_pressure(uint16_t value) { RvcMessage parse_rvc(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::Rvc) && msg.len >= 5); + SAFETY_ASSERT(msg.id == static_cast(MessageId::Rvc) && msg.len >= 5, AssertCode::BadMessage); // Make sure the rvc type is one of the six valid types. uint8_t rvc_type = msg.buf[0]; - SAFETY_ASSERT(rvc_type <= 5); + SAFETY_ASSERT(rvc_type <= 5, AssertCode::BadMessage); float value = read_f32_le(&msg.buf[1]); @@ -146,10 +146,10 @@ CAN_message_t create_rvc(RvcMessage value) { TireRpmMessage parse_tire_rpm(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::TireRpm) && msg.len == 5); + SAFETY_ASSERT(msg.id == static_cast(MessageId::TireRpm) && msg.len == 5, AssertCode::BadMessage); // Make sure the tire position is one of the valid positions. uint8_t tire_position = msg.buf[0]; - SAFETY_ASSERT(tire_position <= 3); + SAFETY_ASSERT(tire_position <= 3, AssertCode::BadMessage); float value = read_f32_le(&msg.buf[1]); @@ -170,10 +170,10 @@ CAN_message_t create_tire_rpm(TireRpmMessage value) { TireTemperatureMessage parse_tire_temperature(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::TireTemperature) && msg.len == 7); + SAFETY_ASSERT(msg.id == static_cast(MessageId::TireTemperature) && msg.len == 7, AssertCode::BadMessage); /* Make sure the tire position is one of the valid positions. */ uint8_t tire_position = msg.buf[0]; - SAFETY_ASSERT(tire_position <= 3); + SAFETY_ASSERT(tire_position <= 3, AssertCode::BadMessage); TireTemperatureMessage temps; temps.position = static_cast(tire_position); @@ -198,10 +198,10 @@ CAN_message_t create_tire_temperature(TireTemperatureMessage value) { LapMessage parse_lap(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::Lap) && msg.len >= 1); + SAFETY_ASSERT(msg.id == static_cast(MessageId::Lap) && msg.len >= 1, AssertCode::BadMessage); /* Make sure the lap message is one of the valid types. */ uint8_t lap_type = msg.buf[0]; - SAFETY_ASSERT(lap_type < 5); + SAFETY_ASSERT(lap_type < 5, AssertCode::BadMessage); return static_cast(lap_type); } @@ -217,7 +217,7 @@ CAN_message_t create_lap(LapMessage value) { MotorTemperaturesOne parse_motor_temperatures_one(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::TemperaturesOne) && msg.len == 8); + SAFETY_ASSERT(msg.id == static_cast(MessageId::TemperaturesOne) && msg.len == 8, AssertCode::BadMessage); MotorTemperaturesOne temps; temps.phase_a_temp = read_i16_le(&msg.buf[0]); @@ -231,7 +231,7 @@ MotorTemperaturesOne parse_motor_temperatures_one(CAN_message_t msg) { MotorTemperaturesTwo parse_motor_temperatures_two(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::TemperaturesTwo) && msg.len == 8); + SAFETY_ASSERT(msg.id == static_cast(MessageId::TemperaturesTwo) && msg.len == 8, AssertCode::BadMessage); MotorTemperaturesTwo temps; temps.control_board_temp = read_i16_le(&msg.buf[0]); @@ -245,7 +245,7 @@ MotorTemperaturesTwo parse_motor_temperatures_two(CAN_message_t msg) { MotorTemperaturesThree parse_motor_temperatures_three(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::TemperaturesThree) && msg.len == 8); + SAFETY_ASSERT(msg.id == static_cast(MessageId::TemperaturesThree) && msg.len == 8, AssertCode::BadMessage); MotorTemperaturesThree temps; temps.coolant_temp = read_i16_le(&msg.buf[0]); @@ -259,7 +259,7 @@ MotorTemperaturesThree parse_motor_temperatures_three(CAN_message_t msg) { MotorAnalogInputVoltages parse_motor_analog_input_voltages(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::AnalogInputVoltages) && msg.len == 8); + SAFETY_ASSERT(msg.id == static_cast(MessageId::AnalogInputVoltages) && msg.len == 8, AssertCode::BadMessage); MotorAnalogInputVoltages voltages; voltages.analog_input_one = read_i16_le(&msg.buf[0]); @@ -273,7 +273,7 @@ MotorAnalogInputVoltages parse_motor_analog_input_voltages(CAN_message_t msg) { MotorDigitalInputStatus parse_motor_digital_input_status(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::DigitalInputStatus) && msg.len == 8); + SAFETY_ASSERT(msg.id == static_cast(MessageId::DigitalInputStatus) && msg.len == 8, AssertCode::BadMessage); MotorDigitalInputStatus status; status.forward_switch = msg.buf[0] != 0; @@ -291,7 +291,7 @@ MotorDigitalInputStatus parse_motor_digital_input_status(CAN_message_t msg) { MotorPositionInfo parse_motor_position_info(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::PositionInfo) && msg.len == 8); + SAFETY_ASSERT(msg.id == static_cast(MessageId::PositionInfo) && msg.len == 8, AssertCode::BadMessage); MotorPositionInfo info; info.motor_angle = read_i16_le(&msg.buf[0]); @@ -305,7 +305,7 @@ MotorPositionInfo parse_motor_position_info(CAN_message_t msg) { MotorCurrentInfo parse_motor_current_info(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::CurrentInfo) && msg.len == 8); + SAFETY_ASSERT(msg.id == static_cast(MessageId::CurrentInfo) && msg.len == 8, AssertCode::BadMessage); MotorCurrentInfo info; info.phase_a_current = read_i16_le(&msg.buf[0]); @@ -319,7 +319,7 @@ MotorCurrentInfo parse_motor_current_info(CAN_message_t msg) { MotorVoltageInfo parse_motor_voltage_info(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::VoltageInfo) && msg.len == 8); + SAFETY_ASSERT(msg.id == static_cast(MessageId::VoltageInfo) && msg.len == 8, AssertCode::BadMessage); MotorVoltageInfo info; info.dc_bus_voltage = read_i16_le(&msg.buf[0]); @@ -333,7 +333,7 @@ MotorVoltageInfo parse_motor_voltage_info(CAN_message_t msg) { MotorFluxInfo parse_motor_flux_info(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::FluxInfo) && msg.len == 8); + SAFETY_ASSERT(msg.id == static_cast(MessageId::FluxInfo) && msg.len == 8, AssertCode::BadMessage); MotorFluxInfo info; info.flux_command = read_i16_le(&msg.buf[0]); @@ -347,7 +347,7 @@ MotorFluxInfo parse_motor_flux_info(CAN_message_t msg) { MotorInternalVoltages parse_motor_internal_voltages(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::InternalVoltages) && msg.len == 8); + SAFETY_ASSERT(msg.id == static_cast(MessageId::InternalVoltages) && msg.len == 8, AssertCode::BadMessage); MotorInternalVoltages voltages; voltages.reference_1_5_v = read_i16_le(&msg.buf[0]); @@ -361,18 +361,18 @@ MotorInternalVoltages parse_motor_internal_voltages(CAN_message_t msg) { MotorInternalStates parse_motor_internal_states(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::InternalStates)); + SAFETY_ASSERT(msg.id == static_cast(MessageId::InternalStates), AssertCode::BadMessage); MotorInternalStates result; /* Make sure buf[0] is safe to cast to VsmState. */ - SAFETY_ASSERT(msg.buf[0] <= 7 || (msg.buf[0] >= 14 && msg.buf[0] <= 15)); + SAFETY_ASSERT(msg.buf[0] <= 7 || (msg.buf[0] >= 14 && msg.buf[0] <= 15), AssertCode::BadMessage); result.vsm_state = static_cast(msg.buf[0]); result.pwm_frequency = msg.buf[1]; /* Make sure buf[2] is safe to cast to InverterState. */ - SAFETY_ASSERT(msg.buf[2] <= 12); + SAFETY_ASSERT(msg.buf[2] <= 12, AssertCode::BadMessage); result.inverter_state = static_cast(msg.buf[2]); RelayState relay_state; @@ -397,7 +397,7 @@ MotorInternalStates parse_motor_internal_states(CAN_message_t msg) { /* FIXME this assert may be too strong, as the docs state that * "All other states are reserved for future use." May be better * to provide a default if it's out of range. */ - SAFETY_ASSERT(inverter_active_discharge_state <= 4); + SAFETY_ASSERT(inverter_active_discharge_state <= 4, AssertCode::BadMessage); result.inverter_active_discharge_state = static_cast(inverter_active_discharge_state); @@ -447,7 +447,7 @@ MotorInternalStates parse_motor_internal_states(CAN_message_t msg) { MotorFaultCodes parse_motor_fault_codes(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::FaultCodes) && msg.len == 8); + SAFETY_ASSERT(msg.id == static_cast(MessageId::FaultCodes) && msg.len == 8, AssertCode::BadMessage); MotorFaultCodes result; @@ -529,7 +529,7 @@ MotorFaultCodes parse_motor_fault_codes(CAN_message_t msg) { MotorTorqueAndTimerInfo parse_motor_torque_and_timer_info(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::TorqueAndTimerInfo) && msg.len == 8); + SAFETY_ASSERT(msg.id == static_cast(MessageId::TorqueAndTimerInfo) && msg.len == 8, AssertCode::BadMessage); MotorTorqueAndTimerInfo result; result.commanded_torque = read_i16_le(&msg.buf[0]); @@ -542,7 +542,7 @@ MotorTorqueAndTimerInfo parse_motor_torque_and_timer_info(CAN_message_t msg) { MotorModIndexAndFlux parse_motor_mod_index_and_flux(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::ModIndexAndFlux) && msg.len == 8); + SAFETY_ASSERT(msg.id == static_cast(MessageId::ModIndexAndFlux) && msg.len == 8, AssertCode::BadMessage); MotorModIndexAndFlux result; result.modulation_index = read_i16_le(&msg.buf[0]); @@ -556,7 +556,7 @@ MotorModIndexAndFlux parse_motor_mod_index_and_flux(CAN_message_t msg) { int16_t parse_motor_torque_capability(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::TorqueCapability) && msg.len >= 2); + SAFETY_ASSERT(msg.id == static_cast(MessageId::TorqueCapability) && msg.len >= 2, AssertCode::BadMessage); return read_i16_le(&msg.buf[0]); } @@ -592,7 +592,7 @@ CAN_message_t create_critical_fault_command(CriticalFault value) { MotorControlCommand parse_motor_control_command(CAN_message_t msg) { /* Why use `static_cast`? You can think of it as a normal cast, but with fewer surprises. * We use the cast to extract the CAN message id from its name. */ - SAFETY_ASSERT(msg.id == static_cast(MessageId::ControlCommand) && msg.len == 8); + SAFETY_ASSERT(msg.id == static_cast(MessageId::ControlCommand) && msg.len == 8, AssertCode::BadMessage); MotorControlCommand result; result.torque = read_i16_le(&msg.buf[0]); diff --git a/ECU_V2/lib/common/ecu.cpp b/ECU_V2/lib/common/ecu.cpp index e8b88a8..cf1d4d7 100644 --- a/ECU_V2/lib/common/ecu.cpp +++ b/ECU_V2/lib/common/ecu.cpp @@ -6,9 +6,10 @@ #include "can_serde.hpp" #include "constants.hpp" - -int64_t map(int32_t input_32, int32_t old_min_32, int32_t old_max_32, int32_t new_min_32, int32_t new_max_32) { - if (old_min_32 == old_max_32 || new_min_32 == new_max_32) { +int64_t map(int32_t input_32, int32_t old_min_32, int32_t old_max_32, int32_t new_min_32, int32_t new_max_32) +{ + if (old_min_32 == old_max_32 || new_min_32 == new_max_32) + { /* Avoid division by zero. */ return 0; } @@ -32,28 +33,48 @@ int64_t map(int32_t input_32, int32_t old_min_32, int32_t old_max_32, int32_t ne return output_shifted; } -int16_t Ecu::smooth_torque(uint16_t current_time_ms, uint16_t torque) { - /* smooth out throttle values by averaging out previous four values */ +int16_t Ecu::smoothTorque(uint32_t current_time_ms, int16_t torque) +{ + /* Smooth out throttle values by averaging out previous four values. */ + + static_assert(sizeof(this->torque_memory) / sizeof(this->torque_memory[0]) == 4); /* If recieves a value of 0 return 0 and set all values to 0 ;) */ - if (torque == 0) { - for (int i = 0; i < 4; i++) + if (torque == 0) + { + for (size_t i = 0; i < 4; i++) { this->torque_memory[i] = 0; } + return 0; + } + else + { + int16_t total_torque = 0; + /* cycle through last 4 torque values*/ + this->torque_memory[3] = this->torque_memory[2]; + this->torque_memory[2] = this->torque_memory[1]; + this->torque_memory[1] = this->torque_memory[0]; + this->torque_memory[0] = torque; + + /* sum last 4 torque values */ + for (int i = 0; i < 4; i++) + { + total_torque += this->torque_memory[i]; + } this->last_output_torque = 0; return 0; } /* Continues to send same torque message until 20ms has passed */ - if ((uint16_t)(current_time_ms - last_smooth_update_ms) < SMOOTH_PERIOD_MS) + if (current_time_ms - last_smooth_update_ms < SMOOTH_PERIOD_MS) { return this->last_output_torque; } /* Starts timer for next cycle when 20ms has passed */ this->last_smooth_update_ms = current_time_ms; - uint16_t total_torque = 0; + int16_t total_torque = 0; /* cycle through last 4 torque values*/ this->torque_memory[3] = this->torque_memory[2]; this->torque_memory[2] = this->torque_memory[1]; @@ -61,22 +82,22 @@ int16_t Ecu::smooth_torque(uint16_t current_time_ms, uint16_t torque) { this->torque_memory[0] = torque; /* sum last 4 torque values */ - for (int i = 0; i < 4; i++) { + for (int i = 0; i < 4; i++) + { total_torque += this->torque_memory[i]; } /* average out torque and check for errors */ total_torque = total_torque / 4; - SAFETY_ASSERT_CODE((total_torque >= 0), AssertCode::SmoothTorqueLessThanZero); + SAFETY_ASSERT((total_torque >= 0), AssertCode::SmoothTorqueLessThanZero); this->last_output_torque = total_torque; return total_torque; } - -int16_t throttle_map(uint16_t throttle1, uint16_t throttle2) { +int16_t throttle_map(uint16_t throttle1, uint16_t throttle2) +{ /* Make sure the throttle values are in range. */ - SAFETY_ASSERT_CODE((throttle1 >= THROTTLE1_MIN && throttle1 <= THROTTLE1_MAX), AssertCode::ThrottleOutOfRange); - SAFETY_ASSERT(throttle2 >= THROTTLE2_MIN && throttle2 <= THROTTLE2_MAX); + SAFETY_ASSERT((throttle1 >= THROTTLE1_MIN && throttle1 <= THROTTLE1_MAX), AssertCode::ThrottleOutOfRange); int64_t throttle1_percent = map(throttle1, THROTTLE1_MIN, THROTTLE1_MAX, 0, 100); /* DEBUG ONLY!!!! Throttle 2 set to throttle 1 :) */ @@ -87,22 +108,17 @@ int16_t throttle_map(uint16_t throttle1, uint16_t throttle2) { // FIXME this is throwing an error /* Make sure the two throttle values haven't diverged too far. */ - SAFETY_ASSERT_CODE((abs(throttle1_percent - throttle2_percent) < THROTTLE_DISAGREE), AssertCode::ThrottleDisagree); + SAFETY_ASSERT((abs(throttle1_percent - throttle2_percent) < THROTTLE_DISAGREE), AssertCode::ThrottleDisagree); int64_t average = (throttle1_percent + throttle2_percent) / 2; - // FIXME this throwing an error - /* Make sure the average can fit into the new size (int16_t). */ - SAFETY_ASSERT_CODE((average >= MIN_THROTTLE && average <= MAX_THROTTLE), AssertCode::ThrottleOverflow); + int64_t torque_mapped = map(average, MIN_THROTTLE, MAX_THROTTLE, 0, MAX_TORQUE); - int16_t torque_mapped = map(average, MIN_THROTTLE, MAX_THROTTLE, 0, MAX_TORQUE); - SAFETY_ASSERT_CODE((torque_mapped >= 0), AssertCode::TorqueLessThanZero); - /* TODO look into this */ - if (torque_mapped < 0) { - torque_mapped = 0; - } + /* Make sure the average can fit into the new size (int16_t). */ + SAFETY_ASSERT((torque_mapped >= MIN_THROTTLE && torque_mapped <= MAX_THROTTLE), AssertCode::ThrottleOverflow); + SAFETY_ASSERT((torque_mapped >= 0), AssertCode::TorqueLessThanZero); - return torque_mapped; + return static_cast(torque_mapped); } void Ecu::processMessage(uint32_t current_time_ms, CAN_message_t msg) @@ -143,7 +159,7 @@ void Ecu::processMessage(uint32_t current_time_ms, CAN_message_t msg) // FIXME this isn't working /* Brake and throttle cannot be pressed at the same time. */ // SAFETY_ASSERT_CODE(!(mapped_torque > 0 && this->brake_pressure >= BRAKE_CONSIDERED_PRESSED), AssertCode::BrakeAndThrottle); - int16_t smoothed_torque = smooth_torque(current_time_ms, mapped_torque); + int16_t smoothed_torque = this->smoothTorque(current_time_ms, mapped_torque); this->calculated_torque = smoothed_torque; } @@ -200,15 +216,14 @@ void Ecu::processMessage(uint32_t current_time_ms, CAN_message_t msg) * Once these preconditions are met, we can do the startup sequence. * We will also wait two seconds before fully starting up, or abort if * one of the preconditions stops holding. */ - - /* DEBUG ONLY */ - // TODO this needs to be looked over - // if ((this->brake_pressure >= BRAKE_CONSIDERED_PRESSED) && this->start_switch_on) { - if (this->start_switch_on) { + if ((this->brake_pressure >= BRAKE_CONSIDERED_PRESSED) && debounced_switch) + { if (!this->startup_countdown.started()) { this->startup_countdown.start(current_time_ms, STARTUP_DELAY_MS); - } else if (this->startup_countdown.triggerReached(current_time_ms)) { + } + else if (this->startup_countdown.triggerReached(current_time_ms)) + { /* Good to go! */ this->car_fully_on = true; } @@ -248,16 +263,8 @@ std::optional Ecu::emitMessage(uint32_t current_time_ms) torque_to_use = this->calculated_torque; } - /* We need to pace how often motor control messages are passed along the CAN bus. - * We do this by using a trigger. To use the trigger, we first start it. Once - * enough time has passed since we started it, `triggerReached` will return true, - * and mark the trigger as not started. Next time, we'll see that the trigger is - * marked as not started, and we'll start it again. This loops forever. */ - if (!this->motor_control_message_pacing.started()) - { - this->motor_control_message_pacing.start(current_time_ms, 15 /*ms*/); - } - else if (this->motor_control_message_pacing.triggerReached(current_time_ms)) + /* Pace how often we send a motor command by pacing it with a timer. */ + if (this->motor_control_pacing.shouldFire(current_time_ms)) { MotorControlCommand cmd; cmd.torque = torque_to_use; @@ -303,5 +310,4 @@ void Ecu::printState() } PRINTF("startup_countdown: %s\n", this->startup_countdown.started() ? "started" : "not started"); - PRINTF("motor_control_message_pacing: %s\n", this->motor_control_message_pacing.started() ? "started" : "not started"); } diff --git a/ECU_V2/lib/common/ecu.hpp b/ECU_V2/lib/common/ecu.hpp index b5bb299..5fa86e8 100644 --- a/ECU_V2/lib/common/ecu.hpp +++ b/ECU_V2/lib/common/ecu.hpp @@ -17,7 +17,7 @@ class Ecu { std::optional emitMessage(uint32_t current_time_ms); /* smooth torque */ - int16_t smooth_torque(uint16_t current_time, uint16_t torque); + int16_t smoothTorque(uint32_t current_time, int16_t torque); /* Uses PRINTF for all printing. */ void printState(); @@ -58,8 +58,8 @@ class Ecu { * wait 2 seconds before enabling the motor. This is that startup countdown. */ Trigger startup_countdown; - /* Time since we last sent a motor control command. */ - Trigger motor_control_message_pacing; + /* Timer to pace how often we send motor control commands. */ + Timer motor_control_pacing = Timer(0, 15); /* throttle mapping memory */ uint16_t torque_memory[4] = {0, 0, 0, 0}; diff --git a/ECU_V2/lib/common/util.cpp b/ECU_V2/lib/common/util.cpp index be21bc0..07be624 100644 --- a/ECU_V2/lib/common/util.cpp +++ b/ECU_V2/lib/common/util.cpp @@ -31,6 +31,33 @@ void Trigger::cancel() { started_at = std::nullopt; } + +/* Timer */ +Timer::Timer(uint32_t current_time, uint32_t duration_ms) { + this->last_fired_at = current_time; + this->duration_ms = duration_ms; +} + +/* Important: this will reset the timer when this returns true. */ +bool Timer::shouldFire(uint32_t current_time) { + if (current_time - this->last_fired_at >= this->duration_ms) { + this->last_fired_at = current_time; + return true; + } else { + return false; + } +} + +/* Tells us how long until this timer will fire again, or std::nullopt if + * it's due to fire. */ +std::optional Timer::timeUntilNextFiring(uint32_t current_time_ms) { + if (current_time_ms - this->last_fired_at < this->duration_ms) { + return this->duration_ms - (current_time_ms - this->last_fired_at); + } else { + return std::nullopt; + } +} + /* Used to condense the file name into something short enough we can send * along the CAN bus in the case of a fault. */ uint32_t str_hash(const char *str) { @@ -38,7 +65,7 @@ uint32_t str_hash(const char *str) { uint32_t hash = 5381; uint32_t c; - while (c = *str++) + while ((c = *str++)) hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ return hash; diff --git a/ECU_V2/lib/common/util.hpp b/ECU_V2/lib/common/util.hpp index c210e3b..229a084 100644 --- a/ECU_V2/lib/common/util.hpp +++ b/ECU_V2/lib/common/util.hpp @@ -16,6 +16,16 @@ class Trigger { uint32_t target_duration = 0; }; +class Timer { +public: + Timer(uint32_t current_time, uint32_t frequency); + bool shouldFire(uint32_t current_time); + std::optional timeUntilNextFiring(uint32_t current_time_ms); +private: + uint32_t last_fired_at = 0; + uint32_t duration_ms = 0; +}; + #ifdef BUILD_MODE_TEST #include #define PRINTF(...) printf(__VA_ARGS__) @@ -24,4 +34,6 @@ class Trigger { #define PRINTF(...) Serial.printf(__VA_ARGS__) #endif +#define UNUSED(x) ((void)x) + uint32_t str_hash(const char *str); diff --git a/ECU_V2/platformio.ini b/ECU_V2/platformio.ini index 8f7546c..7eda1f3 100644 --- a/ECU_V2/platformio.ini +++ b/ECU_V2/platformio.ini @@ -8,18 +8,28 @@ ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html -[env:teensy41] +[env:teensy41_debug] platform = teensy +build_type = debug +build_flags = -DLOG_FILE_PATH="logs/log_${date}_${time}.log" -DENABLE_DEBUGGING -Wall -Wunused-parameter board = teensy41 framework = arduino monitor_filters = time, log2file +lib_deps = https://github.com/tonton81/FlexCAN_T4 + +[env:teensy41_production] +platform = teensy +build_type = release monitor_speed = 115200 -build_flags = -DLOG_FILE_PATH="logs/log_${date}_${time}.log" -DENABLE_DEBUGGING +build_flags = -DLOG_FILE_PATH="logs/log_${date}_${time}.log" -Wall -Wunused-parameter +board = teensy41 +framework = arduino +monitor_filters = time, log2file lib_deps = https://github.com/tonton81/FlexCAN_T4 -[env:native] +[env:native_tests] platform = native test_framework = unity lib_compat_mode = off # If we're building it for a native target (e.g. local computer), it means we're building it to test it. -build_flags = -DBUILD_MODE_TEST -DENABLE_DEBUGGING -g3 -Wall +build_flags = -DBUILD_MODE_TEST -DENABLE_DEBUGGING -fsanitize=address -fsanitize=undefined -g3 -Wall -Wextra -Wpedantic diff --git a/ECU_V2/test/test_can_serde/main.cpp b/ECU_V2/test/test_can_serde/main.cpp index 6f02d9a..fb0e17e 100644 --- a/ECU_V2/test/test_can_serde/main.cpp +++ b/ECU_V2/test/test_can_serde/main.cpp @@ -8,6 +8,7 @@ #include "assert.hpp" #include "can_serde.hpp" +#include "util.hpp" void setUp(void) { // set stuff up here @@ -17,11 +18,13 @@ void tearDown(void) { // clean stuff up here } -void safety_assert_failed_handler(AssertLevel level, const char *file, int line, AssertCode error_code) { +void assert_failed_handler(AssertLevel level, const char *file, int line, AssertCode error_code) { + UNUSED(level); + /* If an SAFETY_ASSERT fails somewhere in the code, this will let the testing environment * know that we failed the test. */ std::stringstream fail_msg; - fail_msg << "Assert failed at " << file << ":" << line; + fail_msg << "Assert failed at " << file << ":" << line << " with code " << static_cast(error_code); TEST_FAIL_MESSAGE(fail_msg.str().c_str()); } @@ -64,7 +67,7 @@ void test_can_dump_parsing() { iss >> msg_num >> time_offset >> type >> id_hex >> direction >> len; /* Construct the message that we'll be using. */ - CAN_message_t msg = {0}; + CAN_message_t msg = {}; msg.id = std::stoul(id_hex, nullptr, 16); /* Hex -> number. */ msg.len = len; @@ -189,8 +192,8 @@ void test_inverter_creation_message() { TEST_ASSERT(created.buf[7] == 0x00); } -int main(int argc, char **argv) { - register_assert_failed_handler(safety_assert_failed_handler); +int main() { + register_assert_failed_handler(assert_failed_handler); UNITY_BEGIN(); RUN_TEST(test_start_switch); diff --git a/ECU_V2/test/test_ecu/main.cpp b/ECU_V2/test/test_ecu/main.cpp index 70c6ae7..36221fc 100644 --- a/ECU_V2/test/test_ecu/main.cpp +++ b/ECU_V2/test/test_ecu/main.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -17,13 +18,15 @@ void tearDown(void) {} volatile bool expecting_safety_violation = false; volatile bool hit_safety_violation = false; void safety_assert_failed_handler(AssertLevel level, const char *file, int line, AssertCode error_code) { + UNUSED(level); + if (expecting_safety_violation) { hit_safety_violation = true; } else { /* If an SAFETY_ASSERT fails somewhere in the code, this will let the testing environment * know that we failed the test. */ std::stringstream fail_msg; - fail_msg << "Assert failed at " << file << ":" << line; + fail_msg << "Assert failed at " << file << ":" << line << " with code " << static_cast(error_code); TEST_FAIL_MESSAGE(fail_msg.str().c_str()); } } @@ -51,7 +54,7 @@ std::tuple ecu_after_startup_sequence() { /* 2 seconds later... */ current_time_ms += 2000; /* Now we should be generating a motor command. */ - /* forward vv vv enabled */ + /* forward vv vv enabled */ uint8_t enabled_but_no_torque[] = {0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00}; test_can_msg_eql( ecu.emitMessage(current_time_ms).value(), /* `.value()` will throw if std::nullopt. */ @@ -101,7 +104,7 @@ void test_ecu() { ecu = std::get<0>(new_ecu); current_time_ms = std::get<1>(new_ecu); - /* Lift the brake this time, before puttind down the throttle. */ + /* Lift the brake this time, before putting down the throttle. */ ecu.processMessage(current_time_ms, create_brake_pressure(BRAKE_PRESSURE_MIN)); ecu.processMessage(current_time_ms, create_throttle_one_position(50)); ecu.processMessage(current_time_ms, create_throttle_two_position(22)); @@ -114,10 +117,79 @@ void test_ecu() { TEST_ASSERT(ecu.emitMessage(current_time_ms) == std::nullopt); } -int main(int argc, char **argv) { +void test_switch_debounce() { + /* This test verifies the switch debouncing logic when turning off. */ + std::tuple new_ecu = ecu_after_startup_sequence(); + Ecu ecu = std::get<0>(new_ecu); + uint32_t current_time_ms = std::get<1>(new_ecu); + + ecu.processMessage(current_time_ms, create_brake_pressure(80)); + + current_time_ms += 10; + /* Lift the brake so the car is fully operational. */ + ecu.processMessage(current_time_ms, create_brake_pressure(BRAKE_PRESSURE_MIN)); + current_time_ms += 20; + + /* At this point, the car should be on and the switch should be on. */ + /* Now turn off the switch. */ + ecu.processMessage(current_time_ms, create_start_switch(false)); + + /* The car should still be on immediately after turning off the switch, + * because of the 1000ms debounce delay. */ + current_time_ms += 20; + std::optional msg = ecu.emitMessage(current_time_ms); + TEST_ASSERT(msg != std::nullopt); + /* Verify the inverter is still enabled. */ + TEST_ASSERT((msg.value().buf[5] & 0x01) == 0x01); /* enable_inverter should be true */ + + /* Wait 500ms (halfway through the debounce period). */ + current_time_ms += 500; + msg = ecu.emitMessage(current_time_ms); + TEST_ASSERT(msg != std::nullopt); + /* Car should still be on. */ + TEST_ASSERT((msg.value().buf[5] & 0x01) == 0x01); + + /* Now turn the switch back on before the debounce completes. */ + ecu.processMessage(current_time_ms, create_start_switch(true)); + current_time_ms += 20; + + /* The car should remain on since we canceled the debounce. */ + msg = ecu.emitMessage(current_time_ms); + TEST_ASSERT(msg != std::nullopt); + TEST_ASSERT((msg.value().buf[5] & 0x01) == 0x01); + + /* Wait another 1000ms to make sure it stays on. */ + current_time_ms += 1000; + msg = ecu.emitMessage(current_time_ms); + TEST_ASSERT(msg != std::nullopt); + TEST_ASSERT((msg.value().buf[5] & 0x01) == 0x01); + + /* Now turn the switch off again and let the full debounce period elapse. */ + ecu.processMessage(current_time_ms, create_start_switch(false)); + current_time_ms += 20; + + /* Car should still be on. */ + msg = ecu.emitMessage(current_time_ms); + TEST_ASSERT(msg != std::nullopt); + TEST_ASSERT((msg.value().buf[5] & 0x01) == 0x01); + + /* Wait for the full 1000ms debounce period to complete. */ + current_time_ms += 1000; + ecu.processMessage(current_time_ms, create_start_switch(false)); + + /* Now the car should be off. */ + current_time_ms += 20; + msg = ecu.emitMessage(current_time_ms); + TEST_ASSERT(msg != std::nullopt); + /* Verify the inverter is now disabled. */ + TEST_ASSERT((msg.value().buf[5] & 0x01) == 0x00); /* enable_inverter should be false */ +} + +int main() { register_assert_failed_handler(safety_assert_failed_handler); UNITY_BEGIN(); - RUN_TEST(test_ecu); + // RUN_TEST(test_ecu); + RUN_TEST(test_switch_debounce); UNITY_END(); } diff --git a/ECU_V2/test/test_generate_file_hash_mapping/main.cpp b/ECU_V2/test/test_generate_file_hash_mapping/main.cpp new file mode 100644 index 0000000..81e588e --- /dev/null +++ b/ECU_V2/test/test_generate_file_hash_mapping/main.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include + +#include "unity.h" + +#include "assert.hpp" +#include "util.hpp" + +namespace fs = std::filesystem; + +void setUp(void) {} + +void tearDown(void) {} + +int main() { + std::vector target_dirs = {"lib", "src"}; + std::stringstream csv_entries; + + for (const auto& target_dir : target_dirs) { + for (const auto& dir_entry : fs::recursive_directory_iterator(target_dir)) { + if (!fs::is_regular_file(dir_entry)) continue; + + auto path = dir_entry.path(); + auto extension = path.extension(); + if (extension == ".hpp" || extension == ".cpp" || extension == ".c" || extension == ".h") { + /* This is a code file, so we should generate its hash name. */ + + /* Because some developers are on windows, we create a mapping for both forward and + * backward slashes. */ + std::string forward = path.generic_string(); + uint32_t forward_hash = str_hash(forward.c_str()); + std::string backward = path.generic_string(); + std::replace(backward.begin(), backward.end(), '/', '\\'); + uint32_t backward_hash = str_hash(backward.c_str()); + + std::cerr << forward_hash << ",\"" << forward << "\"\n"; + std::cerr << backward_hash << ",\"" << backward << "\"\n"; + } + } + } +}