diff --git a/lib/interfaces/include/CurrentSensorInterface.h b/lib/interfaces/include/CurrentSensorInterface.h new file mode 100644 index 0000000..bdd30a6 --- /dev/null +++ b/lib/interfaces/include/CurrentSensorInterface.h @@ -0,0 +1,41 @@ +#ifndef CURRENTSENSOR_H +#define CURRENTSENSOR_H +#include +#include +#include "etl/singleton.h" +#include "MCPSPIInterface.h" + +struct CurrentSensorConfig { + float acceptable_error = 0.02f; // Volts + int cs = 0; // MCP3208 chip-select pin + float voltage_divider_gain = 1.0f; + float sensor_v_per_a = 0.005f; // V per A for your current sensor (e.g., 0.005 or 5mV/A) + CHANNEL_CODES_e ch_current = CHANNEL_CODES_e::CH3; // channel to read current from +}; + +/** + * Minimal Current sensor wrapped around one MCP3208 + */ +class CurrentSensorInterface +{ +public: + static constexpr volt ERROR_CODE = -1; + + void init(const CurrentSensorConfig& cfg); + + // One-shot read: returns and validates bus voltage and current. + float read_current(); + +private: + volt _read_voltage(CHANNEL_CODES_e ch, bool isSingleEnded = true); + + float _calculate_current(volt voltage); + volt _read_and_validate(CHANNEL_CODES_e ch, bool isSingleEnded = true); + +private: + CurrentSensorConfig _config; + float _current; + +}; +using CurrentSensorInstance = etl::singleton; +#endif // CurrentSENSOR_H diff --git a/lib/interfaces/include/MCPSPIInterface.h b/lib/interfaces/include/MCPSPIInterface.h new file mode 100644 index 0000000..c6224fe --- /dev/null +++ b/lib/interfaces/include/MCPSPIInterface.h @@ -0,0 +1,67 @@ +#ifndef MCPSPIINTERFACE_H +#define MCPSPIINTERFACE_H + +#include +#include +#include + +enum class CHANNEL_CODES_e : uint8_t +{ + CH0 = 0, + CH1 = 1, + CH2 = 2, + CH3 = 3, + CH4 = 4, + CH5 = 5, + CH6 = 6, + CH7 = 7, +}; + + +namespace mcp_spi_interface { + + // Fixed SPI settings + static constexpr uint32_t MCP_SPI_HZ = 1'000'000; // 1 MHz + static constexpr uint8_t MCP_SPI_MODE = SPI_MODE0; // MCP3208 supports Mode 0 or 1 + static constexpr uint8_t MCP_START_READING = 0x01; + static constexpr uint8_t MCP_READING_SINGLE = 0x01; + static constexpr uint8_t MCP_READING_DIFF = 0x00; + static constexpr uint16_t RESOLUTION = 4095; + static constexpr float REFERENCE_VOLTAGE = 3.3; + + // CS helpers (matching the LTC style) + void _write_and_delay_low (int cs, int delay_microSeconds); + void _write_and_delay_high(int cs, int delay_microSeconds); + + /** + * Build the 3-byte MCP3208 command. + * @param channel 0..7 (channel code) + * @param singleEnded true = SGL/DIFF=1 (single-ended), false = 0 (differential) + * @return {byte0, byte1, byte2} + */ + byte make_cmd(uint8_t channel, bool singleEnded); + + /** + * Perform one MCP3208 read using the provided command. + * Sends 3 bytes, returns assembled 12-bit raw result + * + * @param cs chip-select pin + * @param cmd 3-byte command (from make_cmd) + * @return 12-bit conversion code (0..4095) + */ + uint16_t read_channel(int cs, byte cmd); + + /** + * Perform one MCP3208 read using the provided command. + * Sends 3 bytes, returns voltage + * + * @param cs chip-select pin + * @param cmd 3-byte command (from make_cmd) + * @return volt + */ + volt read_channel_voltage(int cs, byte cmd); + +} + +#include +#endif diff --git a/lib/interfaces/include/MCPSPIInterface.tpp b/lib/interfaces/include/MCPSPIInterface.tpp new file mode 100644 index 0000000..05c62cf --- /dev/null +++ b/lib/interfaces/include/MCPSPIInterface.tpp @@ -0,0 +1,44 @@ +#pragma once +#include +#include +#include "MCPSPIInterface.h" + +// --- CS helpers --- +void mcp_spi_interface::_write_and_delay_low(int cs, int us) { + digitalWrite(cs, LOW); + delayMicroseconds(us); +} + +void mcp_spi_interface::_write_and_delay_high(int cs, int us) { + digitalWrite(cs, HIGH); + delayMicroseconds(us); +} + +byte mcp_spi_interface::make_cmd(uint8_t channel, bool singleEnded) { + const uint8_t read_type = singleEnded ? MCP_READING_SINGLE : MCP_READING_DIFF; + const byte command = ((MCP_START_READING << 7) | // start bit + (read_type << 6) | // single or differential + ((channel & 0x07) << 3)); // channel number + return command; +} + +// --- Read Once --- +uint16_t mcp_spi_interface::read_channel(int cs, byte cmd) { + SPI.beginTransaction(SPISettings(MCP_SPI_HZ, MSBFIRST, MCP_SPI_MODE)); + _write_and_delay_low(cs, 0); + byte command, b0, b1, b2; + + b0 = SPI.transfer(command); + b1 = SPI.transfer(0x00); + b2 = SPI.transfer(0x00); + + _write_and_delay_high(cs, 1); + SPI.endTransaction(); + uint16_t read_data = (b0 & 0x01) << 11 | (b1 & 0xFF) << 3 | (b2 & 0xE0) >> 5; + return static_cast(read_data & 0x0FFF); +} + +volt mcp_spi_interface::read_channel_voltage(int cs, byte cmd) { + return static_cast(read_channel(cs, cmd)) * REFERENCE_VOLTAGE / RESOLUTION; + +} diff --git a/lib/interfaces/src/CurrentSensorInterface.cpp b/lib/interfaces/src/CurrentSensorInterface.cpp new file mode 100644 index 0000000..cd2dd57 --- /dev/null +++ b/lib/interfaces/src/CurrentSensorInterface.cpp @@ -0,0 +1,45 @@ +#include "CurrentSensorInterface.h" +#include "MCPSPIInterface.h" +#include +#include +#include "SharedFirmwareTypes.h" + +using mcp_spi_interface::make_cmd; +using mcp_spi_interface::read_channel_voltage; + +// Initialize the ADC chip-select pin. +void CurrentSensorInterface::init(const CurrentSensorConfig& cfg){ + _config = cfg; + pinMode(_config.cs, OUTPUT); + digitalWrite(_config.cs, HIGH); +} + +//Reads single values not differential +volt CurrentSensorInterface::_read_voltage(CHANNEL_CODES_e ch, bool isSingleEnded) { + auto cmd = make_cmd(static_cast(ch), isSingleEnded); + return read_channel_voltage(_config.cs, cmd); +} + +//Checks if the data from the channel is correct +volt CurrentSensorInterface::_read_and_validate(CHANNEL_CODES_e ch, bool isSingleEnded){ + volt first_read = _read_voltage(ch, isSingleEnded); + volt second_read = _read_voltage(ch, isSingleEnded); + if (abs(first_read - second_read) <= _config.acceptable_error){ + return second_read; + } + return ERROR_CODE; +} + +float CurrentSensorInterface::_calculate_current(volt voltage){ + return (voltage / _config.voltage_divider_gain) / _config.sensor_v_per_a; +} + + +float CurrentSensorInterface::read_current() { + const volt voltage_current = _read_and_validate(_config.ch_current); + const bool has_valid_current_data = (voltage_current != ERROR_CODE); + if (has_valid_current_data){ + _current = _calculate_current(voltage_current); + } + return _current; +} diff --git a/src/main.cpp b/src/main.cpp index 49c97bd..709f4da 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,7 +8,7 @@ #include "BMSDriverGroup.h" #include "WatchdogInterface.h" #include "ACUCANInterfaceImpl.h" - +#include "CurrentSensorInterface.h" /* System Includes */ #include "ACUController.h" #include "ACUStateMachine.h" @@ -18,6 +18,7 @@ #include "ht_task.hpp" + /* Scheduler setup */ HT_SCHED::Scheduler& scheduler = HT_SCHED::Scheduler::getInstance();