Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Images/Gen7FW/.rte-gen7/generated/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ target_sources(${CMAKE_PROJECT_NAME} PRIVATE
Src/Inverter/Command/Commands/CanCommands.cpp
Src/Inverter/Command/Commands/OpenLoopCommands.cpp
Src/Inverter/Command/Commands/TraceCommands.cpp
Src/Inverter/Command/Commands/I2cCommands.cpp
Src/Inverter/Control/ControlSupervisor.cpp
Src/Inverter/Control/OpenLoopController.cpp
Src/Inverter/Control/FocControlManager.cpp
Expand Down Expand Up @@ -131,6 +132,10 @@ target_sources(${CMAKE_PROJECT_NAME} PRIVATE
Src/Inverter/Drivers/Sensors/ApplicationSensors.cpp
Src/Inverter/Drivers/Sensors/SpikeRecorder.cpp
Src/Inverter/Drivers/Sensors/SampleScheduler.cpp
Src/Inverter/Drivers/I2C/HalI2cBus.cpp
Src/Inverter/Drivers/I2C/OnboardTempSensor.cpp
Src/Inverter/Drivers/I2C/RailMonitor.cpp
Src/Inverter/Drivers/I2C/I2cSensors.cpp
Src/Inverter/Drivers/CAN/CanBus.cpp
Src/Inverter/Drivers/CAN/CanProtocolTransport.cpp
Src/Inverter/Drivers/CAN/CanSession.cpp
Expand Down
5 changes: 2 additions & 3 deletions Images/Gen7FW/.rte-gen7/generated/Inc/Inverter/AppState.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#pragma once
#include "../../generated/domain_vsense_generated.h"
#include "../../generated/domain_adc_isr_generated.h"
#include "../../generated/domain_tim_isr_generated.h"
#include "../../generated/domain_app_loop_generated.h"
#include "../../generated/domain_tim_isr_generated.h"
#include "../../generated/domain_vsense_generated.h"

/* ============================================================================
* RTE codegen top-level state container.
Expand Down Expand Up @@ -53,4 +53,3 @@ struct AppState {
* markers. Individual domain structs are owned by the generated code.
*/
extern AppState appState;

Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ enum class FaultSource : uint32_t {
OvertemperatureMotor = 1u << 25, /**< Motor temperature above limit */
OvertemperatureInverter = 1u << 26, /**< Board temperature above limit */
CurrentSensorRef = 1u << 27, /**< Current-sensor reference out of window */
OnboardOvertemperature = 1u << 28, /**< I2C5 onboard temp sensor over limit */
RailOvervoltage = 1u << 29, /**< I2C4 rail monitor bus overvoltage */
RailUndervoltage = 1u << 30, /**< I2C4 rail monitor bus undervoltage */
};

constexpr FaultSource operator|(FaultSource a, FaultSource b) {
Expand Down Expand Up @@ -112,6 +115,9 @@ enum class FaultReason : uint8_t {
OvertemperatureInv3,
OvertemperatureMotor,
SensorRefOutOfRange,
OnboardOvertemperature,
RailOvervoltage,
RailUndervoltage,
Count
};

Expand Down Expand Up @@ -238,6 +244,9 @@ class FaultManager {
{ FaultSource::OvertemperatureMotor, "OvertemperatureMotor", "Temperature", "motor temperature above limit", FaultSeverity::Critical },
{ FaultSource::OvertemperatureInverter, "OvertemperatureInverter", "Temperature", "board temperature above limit", FaultSeverity::Critical },
{ FaultSource::CurrentSensorRef, "CurrentSensorRef", "Current Sense", "current-sensor reference out of window", FaultSeverity::Warning },
{ FaultSource::OnboardOvertemperature, "OnboardOvertemperature", "Temperature", "I2C5 onboard temp sensor over limit", FaultSeverity::Warning },
{ FaultSource::RailOvervoltage, "RailOvervoltage", "Rail Monitor", "I2C4 rail bus overvoltage", FaultSeverity::Warning },
{ FaultSource::RailUndervoltage, "RailUndervoltage", "Rail Monitor", "I2C4 rail bus undervoltage", FaultSeverity::Warning },
};
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once

#include "Inverter/Drivers/I2C/I2cBus.h"

#include "i2c.h"

namespace Inverter {

/**
* @brief II2cBus backed by a HAL I2C handle (firmware builds only).
*
* Blocking polling-mode HAL calls with a caller-supplied timeout; no
* interrupts or DMA. Errors bump public counters for shell diagnostics.
*/
class HalI2cBus : public II2cBus {
public:
explicit HalI2cBus(I2C_HandleTypeDef* hi2c) : m_hi2c(hi2c) {}

bool transfer(uint8_t addr7,
const uint8_t* tx, uint8_t tx_len,
uint8_t* rx, uint8_t rx_len,
uint32_t timeout_ms) override;
bool isReady(uint8_t addr7, uint32_t timeout_ms) override;

uint32_t errorCount() const { return m_errors; }
uint32_t timeoutCount() const { return m_timeouts; }

private:
I2C_HandleTypeDef* m_hi2c;
uint32_t m_errors = 0;
uint32_t m_timeouts = 0;
};

} // namespace Inverter
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#pragma once

#include <cstdint>

namespace Inverter {

/**
* @brief Abstract I2C master port — the single funnel for all I2C driver I/O.
*
* Every Gen7 I2C driver (on-board temp sensor, rail monitor) talks to the bus
* exclusively through this interface, so the drivers and their register
* decode/encode math can be compiled and unit-tested on the host against a
* mock bus (see tests/host/), with no HAL dependency.
*
* Transaction model matches the register-pointer devices we drive
* (TMP102-family, INA226/INA228/INA3221):
* transfer(addr, tx={reg[, data...]}, rx=n) ==
* START, addr+W, tx bytes, [repeated START, addr+R, rx bytes], STOP
* A null/empty @p rx reads nothing (pure register write); @p tx always starts
* with the register pointer byte.
*
* All calls are blocking-with-timeout and must only be made from main-loop /
* shell context — never from an ISR.
*/
class II2cBus {
public:
virtual ~II2cBus() = default;

/**
* @brief Register-pointer transaction (see class comment).
* @param addr7 7-bit slave address (0x08..0x77).
* @param tx Bytes to write; tx[0] is the register pointer.
* @param tx_len Number of bytes in @p tx (>= 1).
* @param rx Receive buffer; may be nullptr when rx_len == 0.
* @param rx_len Bytes to read after a repeated START (0 = write only).
* @param timeout_ms Per-phase bus timeout.
* @return true on success; false on NACK/bus error/timeout.
*/
virtual bool transfer(uint8_t addr7,
const uint8_t* tx, uint8_t tx_len,
uint8_t* rx, uint8_t rx_len,
uint32_t timeout_ms) = 0;

/**
* @brief Address-only presence probe (HAL_I2C_IsDeviceReady equivalent).
* @return true if the slave ACKed its address.
*/
virtual bool isReady(uint8_t addr7, uint32_t timeout_ms) = 0;
};

} // namespace Inverter
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#pragma once

#include "Inverter/Drivers/I2C/HalI2cBus.h"
#include "Inverter/Drivers/I2C/OnboardTempSensor.h"
#include "Inverter/Drivers/I2C/RailMonitor.h"

#include <cstdint>
#include <cmath>

namespace Inverter {

/**
* @brief Application-level owner of the Gen7 I2C sensors.
*
* Owns the two bus objects (I2C5 = on-board temperature, I2C4 = rail
* monitor), the configurable parameters, the poll/re-probe scheduling, and
* the telemetry + warning-fault wiring. Follows the ApplicationSensors
* pattern: init() never blocks boot (short-timeout probe, one log line when
* absent), update() runs from the main loop only (never from an ISR) at
* POLL_MS cadence, and absent devices are lazily re-probed every
* REPROBE_MS.
*
* Persistent config (RteParamStore KV, `config set <key> <value>` +
* `config saveall`):
* OnbTemp.Addr 7-bit temp-sensor address (default 0x48)
* OnbTemp.CritC over-temperature warning (default 85 degC)
* RailMon.Addr 7-bit rail-monitor address (default 0x40)
* RailMon.ShuntOhm shunt resistor [ohm] (default 0.005)
* RailMon.MaxA max expected current [A] (default 10)
* RailMon.OvV rail OV warning [V] (default 0 = disabled)
* RailMon.UvV rail UV warning [V] (default 0 = disabled)
*
* Telemetry keys: onb_temp_c, rail_v, rail_a, rail_w (plus rail2_v/rail2_a
* and rail3_v/rail3_a for the INA3221's extra channels).
*
* Warning faults (latched, log-only): OnboardOvertemperature,
* RailOvervoltage, RailUndervoltage.
*/
class I2cSensors {
public:
bool init();
void update();
void reloadConfig();

/* Status for the shell commands. */
bool tempPresent() const { return m_temp.part() != OnboardTempSensor::Part::None; }
float lastTempC() const { return m_temp_c; }
float tempCritC() const { return m_temp_crit_c; }
const OnboardTempSensor& tempSensor() const { return m_temp; }

bool railPresent() const { return m_rail.part() != RailMonitor::Part::None; }
const RailMonitor& railMonitor() const { return m_rail; }
const RailMonitor::Sample& railSample(uint8_t ch) const { return m_rail_sample[ch < RailMonitor::MAX_CHANNELS ? ch : 0]; }

HalI2cBus& tempBus() { return m_bus_temp; }
HalI2cBus& railBus() { return m_bus_rail; }

/* Scan a bus for the `i2cscan` shell command; counts ACKed addresses in
* 0x08..0x77 into out[0..119]. Returns the number found. */
uint32_t scan(uint8_t bus, uint8_t* out_addrs, uint32_t max_out);

private:
static constexpr uint32_t POLL_MS = 200; /**< 5 Hz */
static constexpr uint32_t REPROBE_MS = 5000; /**< lazy re-probe */
static constexpr uint32_t PROBE_TIMEOUT_MS = 20; /**< boot-blocking cap */
static constexpr uint32_t POLL_TIMEOUT_MS = 10;
static constexpr uint32_t FAULT_SUSTAIN_MS = 500;
static constexpr float HYST_C = 5.0f;
static constexpr float HYST_V = 0.5f;
static constexpr uint8_t MAX_POLL_ERRORS = 3; /**< -> mark absent */

bool probeTemp();
bool probeRail();
void pollTemp(uint32_t now_ms);
void pollRail(uint32_t now_ms);
void evaluateFaults(uint32_t now_ms);

HalI2cBus m_bus_temp{&hi2c5}; /**< onboard temp bus */
HalI2cBus m_bus_rail{&hi2c4}; /**< rail monitor bus */
OnboardTempSensor m_temp;
RailMonitor m_rail;

float m_temp_c = NAN;
RailMonitor::Sample m_rail_sample[RailMonitor::MAX_CHANNELS] = {};

uint8_t m_temp_addr = 0x48;
float m_temp_crit_c = 85.0f;
uint8_t m_rail_addr = 0x40;
float m_shunt_ohm = 0.005f;
float m_rail_max_a = 10.0f;
float m_rail_ov_v = 0.0f; /**< 0 = disabled */
float m_rail_uv_v = 0.0f; /**< 0 = disabled */

uint8_t m_temp_errors = 0;
uint8_t m_rail_errors = 0;
uint32_t m_last_poll_ms = 0;
uint32_t m_temp_reprobe_ms = 0;
uint32_t m_rail_reprobe_ms = 0;

bool m_ot_cond = false, m_ot_raised = false;
uint32_t m_ot_since_ms = 0;
bool m_ov_cond = false, m_ov_raised = false;
uint32_t m_ov_since_ms = 0;
bool m_uv_cond = false, m_uv_raised = false;
uint32_t m_uv_since_ms = 0;

bool m_initialized = false;
};

/** @brief Global I2C sensors instance. */
I2cSensors& i2cSensors();

} // namespace Inverter
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#pragma once

#include "Inverter/Drivers/I2C/I2cBus.h"

#include <cstdint>

namespace Inverter {

/**
* @brief On-board digital temperature sensor (I2C5, PF0/PF1).
*
* The assembled part number is not yet known, so this driver targets the
* ubiquitous pointer-register, 12-bit, 0.0625 degC/LSB family all of which
* share register map and decode math:
* - TI TMP102 (no device-id register)
* - TI TMP1075 (DEVICE_ID reg 0x0F = 0x7500)
* - NXP PCT2075 (no device-id register)
*
* Register map (big-endian 16-bit words behind an 8-bit pointer):
* 0x00 Temperature (12-bit code in bits 15:4; 13-bit when EM=1)
* 0x01 Config (EM = bit 4 selects 13-bit extended mode)
* 0x02 Tlow, 0x03 Thigh -- unused here, kept for the alert pin feature
* 0x0F DEVICE_ID (TMP1075 only; reads 0x0000 elsewhere)
*
* init() probes with a short timeout and never blocks boot; a missing device
* leaves part() == Part::None and the caller keeps re-probing lazily.
*
* All decode/encode math is static and free of bus state so the host test
* harness (tests/host/) can verify vectors directly.
*/
class OnboardTempSensor {
public:
enum class Part : uint8_t {
None = 0, /**< not detected / not initialised */
Generic = 1, /**< TMP102 / PCT2075-compatible (no readable ID) */
Tmp1075 = 2, /**< DEVICE_ID 0x7500 matched */
};

static constexpr uint8_t REG_TEMP = 0x00;
static constexpr uint8_t REG_CONFIG = 0x01;
static constexpr uint8_t REG_TLOW = 0x02;
static constexpr uint8_t REG_THIGH = 0x03;
static constexpr uint8_t REG_ID = 0x0F;
static constexpr uint16_t TMP1075_DEVICE_ID = 0x7500;
static constexpr float LSB_C = 0.0625f;

/**
* @brief Probe the sensor and latch its identity.
* @return true if the address ACKed (device present, even if the ID
* register reads back 0 -> Generic); false on NACK/bus error.
*/
bool init(II2cBus& bus, uint8_t addr7, uint32_t timeout_ms);

/**
* @brief Read the temperature register and decode it.
* @return false on bus error; @p temp_c untouched on failure.
*/
bool poll(float& temp_c, uint32_t timeout_ms) const;

Part part() const { return m_part; }
uint8_t address() const { return m_addr; }
bool extendedMode() const { return m_extended; }
const char* partName() const;

/* -------- pure register math (host-testable) -------- */

/**
* @brief Decode the 16-bit temperature register to degC.
* @param reg Register value, host byte order (hi<<8|lo).
* @param extended true when config EM=1 (13-bit, value in bits 15:3).
*
* 12-bit (EM=0): degC = arithmetic_sar(reg, 4) * 0.0625
* 13-bit (EM=1): degC = arithmetic_sar(reg, 3) * 0.0625
* E.g. 0x7FF0 -> 127.9375 C; 0x1900 -> 25.0 C; EM 0x0C80 -> 25.0 C.
*/
static float decodeTempC(uint16_t reg, bool extended);

/** @brief Encode degC to a 16-bit temperature register (tests/mock). */
static uint16_t encodeTempReg(float temp_c, bool extended);

/** @brief true when config register value has the EM (13-bit) bit set. */
static bool configIsExtended(uint16_t cfg) { return (cfg & 0x0010u) != 0u; }

private:
bool readReg(uint8_t reg, uint16_t& value, uint32_t timeout_ms) const;
bool writeReg(uint8_t reg, uint16_t value, uint32_t timeout_ms) const;

II2cBus* m_bus = nullptr;
uint8_t m_addr = 0;
Part m_part = Part::None;
bool m_extended = false;
};

} // namespace Inverter
Loading
Loading