diff --git a/obc/CMakeLists.txt b/obc/CMakeLists.txt index 1352aaa56..76e7c9b4a 100644 --- a/obc/CMakeLists.txt +++ b/obc/CMakeLists.txt @@ -67,13 +67,16 @@ elseif(${CMAKE_BUILD_TYPE} MATCHES Examples) add_executable(${OUT_FILE_NAME} examples/test_app_uart_tx/main.c) elseif(${EXAMPLE_TYPE} MATCHES VN100) add_executable(${OUT_FILE_NAME} examples/vn100_demo/test_binary_reading/main.c) - elseif(${EXAMPLE_TYPE} MATCHES ADC) + elseif(${EXAMPLE_TYPE} MATCHES ADC) add_executable(${OUT_FILE_NAME} examples/test_app_adc/main.c) elseif(${EXAMPLE_TYPE} MATCHES CC1120_SPI) add_executable(${OUT_FILE_NAME} examples/test_app_cc1120_spi/main.c examples/test_app_cc1120_spi/cc1120_spi_tests.c) target_include_directories (${OUT_FILE_NAME} PRIVATE examples/test_app_cc1120_spi) elseif(${EXAMPLE_TYPE} MATCHES RS) add_executable(${OUT_FILE_NAME} examples/test_app_rs/main.c) + elseif(${EXAMPLE_TYPE} MATCHES INA230) + add_executable(${OUT_FILE_NAME} examples/test_ina230_driver/main.c app/drivers/ina230/ina230.c app/drivers/tca6424/tca6424.c) + target_include_directories (${OUT_FILE_NAME} PRIVATE app/drivers/ina230 app/drivers/tca6424) # ADD MORE EXAMPLES ABOVE THIS COMMENT else () diff --git a/obc/app/drivers/CMakeLists.txt b/obc/app/drivers/CMakeLists.txt index e9727a703..338abd8fa 100644 --- a/obc/app/drivers/CMakeLists.txt +++ b/obc/app/drivers/CMakeLists.txt @@ -14,6 +14,8 @@ SET(INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/tpl5010 ${CMAKE_CURRENT_SOURCE_DIR}/bd621x ${CMAKE_CURRENT_SOURCE_DIR}/vn100 + ${CMAKE_CURRENT_SOURCE_DIR}/ina230 + ${CMAKE_CURRENT_SOURCE_DIR}/tca6424 ) SET(SOURCES diff --git a/obc/app/drivers/ina230/ina230.c b/obc/app/drivers/ina230/ina230.c new file mode 100644 index 000000000..388a59887 --- /dev/null +++ b/obc/app/drivers/ina230/ina230.c @@ -0,0 +1,328 @@ +#include "ina230.h" +#include "obc_i2c_io.h" +#include "tca6424.h" +#include "obc_logging.h" +#include +#include "obc_board_config.h" +#include +#include + +// ------------------ TCA6424 IC Related Pins ------------- // + +// enable pins for device one +#define INA230_ONE_ENABLE_PIN TCA6424A_PIN_03 +#define INA230_TWO_ENABLE_PIN TCA6424A_PIN_01 + +// alert pins for device two +#define INA230_ONE_ALERT_PIN TCA6424A_PIN_02 +#define INA230_TWO_ALERT_PIN TCA6424A_PIN_00 + +#define INA230_ENABLE_LOAD TCA6424A_GPIO_HIGH +#define INA230_DISABLE_LOAD TCA6424A_GPIO_LOW +#define INA230_ALERT_HIGH TCA6424A_GPIO_HIGH + +#define INA_REG_CONF_BUFF_SIZE 2 +#define I2C_TRANSFER_TIMEOUT_TICKS pdMS_TO_TICKS(100) // 100 ms in RTOS ticks + +// ------------------ INA230 Device Configuration ------------- // +typedef struct { + uint8_t i2cDeviceAddress; + + // TCA GPIO Expander ports + uint8_t tcaAlertPort; + uint8_t tcaEnablePort; + + // Values for the configuration register + uint8_t configurationMode; + uint8_t configurationShunt; + uint8_t configurationAvg; + uint8_t configurationBus; + + // Values for other registers + uint16_t maskEnableRegister; + uint16_t calibrationRegister; + uint16_t alertRegister; +} ina230_config_t; + +static const ina230_config_t ina230Devices[] = { + [INA230_DEVICE_ONE] = {.i2cDeviceAddress = INA230_I2C_ADDRESS_ONE, + .tcaAlertPort = INA230_ONE_ALERT_PIN, + .tcaEnablePort = INA230_ONE_ENABLE_PIN, + .configurationMode = INA230_DEFAULT_MODE, + .configurationShunt = INA230_DEFAULT_SHUNT_CONV_TIME, + .configurationBus = INA230_DEFAULT_BUS_CONV_TIME, + .configurationAvg = INA230_DEFAULT_AVERAGING, + .calibrationRegister = INA230_CALIBRATION_VALUE, + .maskEnableRegister = INA230_MASK_ENABLE_NONE, + .alertRegister = INA230_ALERT_LIMIT_NONE}, + [INA230_DEVICE_TWO] = {.i2cDeviceAddress = INA230_I2C_ADDRESS_TWO, + .tcaAlertPort = INA230_TWO_ALERT_PIN, + .tcaEnablePort = INA230_TWO_ENABLE_PIN, + .configurationMode = INA230_DEFAULT_MODE, + .configurationShunt = INA230_DEFAULT_SHUNT_CONV_TIME, + .configurationBus = INA230_DEFAULT_BUS_CONV_TIME, + .configurationAvg = INA230_DEFAULT_AVERAGING, + .calibrationRegister = INA230_CALIBRATION_VALUE, + .maskEnableRegister = INA230_MASK_ENABLE_NONE, + .alertRegister = INA230_ALERT_LIMIT_NONE}, + // initialized only 2 devices for now; initialize the rest if used + [INA230_DEVICE_THREE] = {0}, + [INA230_DEVICE_FOUR] = {0}, + [INA230_DEVICE_FIVE] = {0}, + [INA230_DEVICE_SIX] = {0}, + [INA230_DEVICE_SEVEN] = {0}, + [INA230_DEVICE_EIGHT] = {0}, + [INA230_DEVICE_NINE] = {0}, + [INA230_DEVICE_TEN] = {0}, + [INA230_DEVICE_ELEVEN] = {0}}; + +static obc_error_code_t writeINA230Register(uint8_t regAddress, uint8_t* data, uint8_t size, ina230_device_t device); +static obc_error_code_t initTca6424PinState(); + +/** + * @brief Initializes the INA230 devices + * + * This function initializes all INA230 devices by configuring their registers + * and setting up the corresponding TCA6424 pins for alerts and enable control via initTca6424PinState(). + * + * @return OBC_ERR_CODE_SUCCESS if initialization is successful, + * otherwise returns an appropriate error code + */ +obc_error_code_t initINA230() { + obc_error_code_t errCode; + // for (uint8_t i = 0; i < INA230_DEVICE_COUNT; ++i) { + // const ina230_config_t device = ina230Devices[i]; + // const uint16_t configurationRegister = (device.configurationMode << INA230_CONFIG_MODE_SHIFT) | + // (device.configurationShunt << INA230_CONFIG_SHU_SHIFT) | + // (device.configurationAvg << INA230_CONFIG_AVG_SHIFT) | + // (device.configurationBus << INA230_CONFIG_BUS_SHIFT); + // + // uint8_t configRegisterUnpacked[] = {configurationRegister >> 8, configurationRegister & 0xFF}; + // uint8_t maskEnRegisterUnpacked[] = {device.maskEnableRegister >> 8, device.maskEnableRegister & 0xFF}; + // uint8_t alertRegisterUnpacked[] = {device.alertRegister >> 8, device.alertRegister & 0xFF}; + // uint8_t calibrationRegisterUnpacked[] = {device.calibrationRegister >> 8, device.calibrationRegister & 0xFF}; + // + // RETURN_IF_ERROR_CODE(writeINA230Register(INA230_CONFIG_REGISTER_ADDR, configRegisterUnpacked, + // sizeof(configRegisterUnpacked) / sizeof(configRegisterUnpacked[0]), i)); + // RETURN_IF_ERROR_CODE(writeINA230Register(INA230_MASK_ENABLE_REGISTER_ADDR, maskEnRegisterUnpacked, + // sizeof(maskEnRegisterUnpacked) / sizeof(maskEnRegisterUnpacked[0]), i)); + // RETURN_IF_ERROR_CODE(writeINA230Register(INA230_ALERT_LIMIT_REGISTER_ADDR, alertRegisterUnpacked, + // sizeof(alertRegisterUnpacked) / sizeof(alertRegisterUnpacked[0]), i)); + // RETURN_IF_ERROR_CODE( + // writeINA230Register(INA230_CALIBRATION_REGISTER_ADDR, calibrationRegisterUnpacked, + // sizeof(calibrationRegisterUnpacked) / sizeof(calibrationRegisterUnpacked[0]), i)); + // } + const ina230_config_t device = ina230Devices[0]; + const uint16_t configurationRegister = (device.configurationMode << INA230_CONFIG_MODE_SHIFT) | + (device.configurationShunt << INA230_CONFIG_SHU_SHIFT) | + (device.configurationAvg << INA230_CONFIG_AVG_SHIFT) | + (device.configurationBus << INA230_CONFIG_BUS_SHIFT); + + uint8_t configRegisterUnpacked[] = {configurationRegister >> 8, configurationRegister & 0xFF}; + uint8_t maskEnRegisterUnpacked[] = {device.maskEnableRegister >> 8, device.maskEnableRegister & 0xFF}; + uint8_t alertRegisterUnpacked[] = {device.alertRegister >> 8, device.alertRegister & 0xFF}; + uint8_t calibrationRegisterUnpacked[] = {device.calibrationRegister >> 8, device.calibrationRegister & 0xFF}; + + RETURN_IF_ERROR_CODE(writeINA230Register(INA230_CONFIG_REGISTER_ADDR, configRegisterUnpacked, + sizeof(configRegisterUnpacked) / sizeof(configRegisterUnpacked[0]), 0)); + RETURN_IF_ERROR_CODE(writeINA230Register(INA230_MASK_ENABLE_REGISTER_ADDR, maskEnRegisterUnpacked, + sizeof(maskEnRegisterUnpacked) / sizeof(maskEnRegisterUnpacked[0]), 0)); + RETURN_IF_ERROR_CODE(writeINA230Register(INA230_ALERT_LIMIT_REGISTER_ADDR, alertRegisterUnpacked, + sizeof(alertRegisterUnpacked) / sizeof(alertRegisterUnpacked[0]), 0)); + RETURN_IF_ERROR_CODE( + writeINA230Register(INA230_CALIBRATION_REGISTER_ADDR, calibrationRegisterUnpacked, + sizeof(calibrationRegisterUnpacked) / sizeof(calibrationRegisterUnpacked[0]), 0)); + + RETURN_IF_ERROR_CODE(initTca6424PinState()); + return OBC_ERR_CODE_SUCCESS; +} + +/** + * @brief Reads the TCA6424 input and disables INA230 devices if an alert is detected + * + * This function reads the complete input from the TCA6424 and checks for alerts + * on the INA230 devices. If an alert is detected, it disables the corresponding device. + * + * @param device INA230 device enum + * @return OBC_ERR_CODE_SUCCESS if operation is successful, + * otherwise returns an appropriate error code + */ +inline obc_error_code_t readAndDisableIfAlert(ina230_device_t device) { + uint32_t IOPortValue = 0; + obc_error_code_t errCode; + RETURN_IF_ERROR_CODE(readTCA642CompleteInput(&IOPortValue)); // reads 24 bit input of TCA GPIO Expander + uint8_t pinLocation = ina230Devices[device].tcaAlertPort; + uint8_t index = ((pinLocation & 0x0F) + ((pinLocation >> 1) & 0x18)); + if (IOPortValue & (0b1 << index)) { + uint8_t drivePort = INA230_DISABLE_LOAD; + RETURN_IF_ERROR_CODE(driveTCA6424APinOutput(pinLocation, drivePort)); + } + return OBC_ERR_CODE_SUCCESS; +} +/** + * @brief Writes data to an INA230 register + * + * This function writes data to a specified register of an INA230 device. + * + * @param regAddress The address of the register to write to + * @param data Pointer to the data to be written + * @param size The size of the data to be written + * @param device INA230 device index + * @return OBC_ERR_CODE_SUCCESS if write is successful, + * otherwise returns an appropriate error code + */ + +static obc_error_code_t writeINA230Register(uint8_t regAddress, uint8_t* data, uint8_t size, ina230_device_t device) { + if (data == NULL || device >= INA230_DEVICE_COUNT) { + return OBC_ERR_CODE_INVALID_ARG; + } + obc_error_code_t errCode; + uint8_t dataSize[1] = {0}; + RETURN_IF_ERROR_CODE(i2cWriteReg(INA230_I2C_ADDRESS_ONE, 0x00, dataSize, 1)); + gioSetBit(STATE_MGR_DEBUG_LED_GIO_PORT, STATE_MGR_DEBUG_LED_GIO_BIT, 1); + return OBC_ERR_CODE_SUCCESS; +} +/** + * @brief Initializes the TCA6424 pin states for INA230 devices + * + * This function configures the TCA6424 pins used for alerts and enable control + * of the INA230 devices. It sets up the alert pins as inputs and the enable pins as outputs. + * + * @return OBC_ERR_CODE_SUCCESS if initialization is successful, + * otherwise returns an appropriate error code + */ +static obc_error_code_t initTca6424PinState() { + obc_error_code_t errCode; + for (uint8_t i = 0; i < INA230_DEVICE_COUNT; ++i) { + ina230_config_t device = ina230Devices[i]; + RETURN_IF_ERROR_CODE(configureTCA6424APin( + device.tcaAlertPort, TCA6424A_GPIO_CONFIG_INPUT)); // alert pin is output of ina230 and input to tca + RETURN_IF_ERROR_CODE(configureTCA6424APin( + device.tcaEnablePort, TCA6424A_GPIO_CONFIG_OUTPUT)); // alert pin is input of ina230 and output of tca + RETURN_IF_ERROR_CODE( + driveTCA6424APinOutput(device.tcaEnablePort, INA230_ENABLE_LOAD)); // set the pin enable pin to high + } + return OBC_ERR_CODE_SUCCESS; +} + +/** + * @brief Gets INA230 shunt voltage + * + * Reads the 16-bit shunt voltage register (MSB first) from the specified INA230 device. + * Converts the raw register value to a signed voltage in volts (LSB = 2.5μV). + * + * @param device INA230 device enum + * @param shuntVoltage Pointer to store the shunt voltage in volts + * @return OBC_ERR_CODE_SUCCESS if operation is successful, + * otherwise returns an appropriate error code + */ +obc_error_code_t getINA230ShuntVoltage(ina230_device_t device, float* shuntVoltage) { + if (shuntVoltage == NULL || device >= INA230_DEVICE_COUNT) { + return OBC_ERR_CODE_INVALID_ARG; + } + + uint8_t shuntVoltageRaw[INA_REG_CONF_BUFF_SIZE] = {0}; // store 2 bytes of shunt voltage + obc_error_code_t errCode; + + // Read the 16-bit shunt voltage register + errCode = i2cReadReg(ina230Devices[device].i2cDeviceAddress, INA230_SHUNT_VOLTAGE_REGISTER_ADDR, shuntVoltageRaw, 2, + I2C_TRANSFER_TIMEOUT_TICKS); + if (errCode != OBC_ERR_CODE_SUCCESS) return errCode; + + // Combine the two bytes into a 16-bit value + int16_t shuntVoltageValue = (shuntVoltageRaw[0] << 8) | shuntVoltageRaw[1]; + + // Convert to actual voltage (signed value) + *shuntVoltage = shuntVoltageValue * INA230_SHUNT_VOLTAGE_LSB; + + return OBC_ERR_CODE_SUCCESS; +} + +// general disable function for ina230 device + +obc_error_code_t disableNoAlert(ina230_device_t device) { + uint32_t IOPortValue = 0; + obc_error_code_t errCode; + uint8_t pinLocation = + ina230Devices[device].tcaEnablePort; // specific pin on TCA that this ina230 controls, should this be alertPort? + uint8_t index = ((pinLocation & 0x0F) + + ((pinLocation >> 1) & 0x18)); // converts the pinLocation to an index in the 24 bit IOPortValue + // disbale + uint8_t drivePort = INA230_DISABLE_LOAD; + RETURN_IF_ERROR_CODE(driveTCA6424APinOutput(pinLocation, drivePort)); + return OBC_ERR_CODE_SUCCESS; +} + +/** + * @brief Gets INA230 bus voltage + * + * Reads the 16-bit bus voltage register (MSB first) from the specified INA230 device. + * Converts the raw register value to a signed voltage in volts (LSB = 1.25mV). + * + * @param device INA230 device enum + * @param busVoltage Pointer to store the bus voltage in volts + * @return OBC_ERR_CODE_SUCCESS if write is successful, + * otherwise return an appropriate error code + */ +obc_error_code_t getINA230BusVoltage(ina230_device_t device, float* busVoltage) { + if (busVoltage == NULL || device >= INA230_DEVICE_COUNT) { + return OBC_ERR_CODE_INVALID_ARG; + } + obc_error_code_t errCode; + uint8_t busVoltageRaw[2] = {}; + RETURN_IF_ERROR_CODE(i2cReadReg(ina230Devices[device].i2cDeviceAddress, INA230_BUS_VOLTAGE_REGISTER_ADDR, + busVoltageRaw, 2, I2C_TRANSFER_TIMEOUT_TICKS)); + uint16_t busVoltageValue = (busVoltageRaw[0] << 8) | busVoltageRaw[1]; + *busVoltage = busVoltageValue * INA230_BUS_VOLTAGE_LSB; + + return OBC_ERR_CODE_SUCCESS; +} + +/** + * @brief Gets INA230 power + * + * Reads the 16-bit power register (MSB first) from the specified INA230 device. + * Converts the raw register value to an unsigned power in watts (LSB = 25mW). + * + * @param ina230_device_t device enum + * @param busVoltage Pointer to store the power in watts + * @return OBC_ERR_CODE_SUCCESS if write is successful, + * otherwise return an appropriate error code + */ +obc_error_code_t getINA230Power(ina230_device_t device, float* power) { + obc_error_code_t errCode; + if (power == NULL || device >= INA230_DEVICE_COUNT) { + return OBC_ERR_CODE_INVALID_ARG; + } + uint8_t powerRaw[INA_REG_CONF_BUFF_SIZE] = {}; + RETURN_IF_ERROR_CODE(i2cReadReg(ina230Devices[device].i2cDeviceAddress, INA230_POWER_REGISTER_ADDR, powerRaw, 2, + I2C_TRANSFER_TIMEOUT_TICKS)); + uint16_t powerValue = (powerRaw[0] << 8) | powerRaw[1]; + *power = powerValue * (INA230_CURRENT_LSB * INA230_POWER_LSB_MULTIPLIER); + return OBC_ERR_CODE_SUCCESS; +} + +/** + * @brief Gets INA230 current + * + * Reads the 16-bit current register (MSB first) from the specified INA230 device. + * Converts the raw register value to a signed current in watts (LSB = 1mA). + * + * @param device INA230 device enum + * @param busVoltage Pointer to store the current in amperes + * @return OBC_ERR_CODE_SUCCESS if write is successful, + * otherwise return an appropriate error code + */ +obc_error_code_t getINA230Current(ina230_device_t device, float* current) { + obc_error_code_t errCode; + if (current == NULL || device >= INA230_DEVICE_COUNT) { + return OBC_ERR_CODE_INVALID_ARG; + } + + uint8_t currentRaw[INA_REG_CONF_BUFF_SIZE] = {}; + RETURN_IF_ERROR_CODE(i2cReadReg(ina230Devices[device].i2cDeviceAddress, INA230_CURRENT_REGISTER_ADDR, currentRaw, 2, + I2C_TRANSFER_TIMEOUT_TICKS)); + int16_t currentValue = (currentRaw[0] << 8) | currentRaw[1]; + *current = currentValue * INA230_CURRENT_LSB; + return OBC_ERR_CODE_SUCCESS; +} diff --git a/obc/app/drivers/ina230/ina230.h b/obc/app/drivers/ina230/ina230.h new file mode 100644 index 000000000..8ce411707 --- /dev/null +++ b/obc/app/drivers/ina230/ina230.h @@ -0,0 +1,122 @@ +#ifdef __cplusplus +extern "C" { +#endif + +#pragma once + +#include "obc_errors.h" +#include + +#ifdef USE_MOCK_I2C +#ifndef TICK_TYPE_H +typedef uint32_t TickType_t; +#endif +#else +#include "os_portmacro.h" +#endif + +#define INA230_I2C_ADDRESS_ONE 0b1000000U +#define INA230_I2C_ADDRESS_TWO 0b1000001U + +// ------------------ INA230 IC Related General Configuration Addresses/Bitfields ------------- // +#define INA230_CONFIG_REGISTER_ADDR 0x00U +#define INA230_MASK_ENABLE_REGISTER_ADDR 0x06U +#define INA230_ALERT_LIMIT_REGISTER_ADDR 0x07U +#define INA230_CALIBRATION_REGISTER_ADDR 0x05U +#define INA230_SHUNT_VOLTAGE_REGISTER_ADDR 0x01U +#define INA230_BUS_VOLTAGE_REGISTER_ADDR 0x02U +#define INA230_POWER_REGISTER_ADDR 0x03U +#define INA230_CURRENT_REGISTER_ADDR 0x04U + +#define INA230_CONFIG_MODE_SHIFT 0U +#define INA230_CONFIG_SHU_SHIFT 3U +#define INA230_CONFIG_AVG_SHIFT 9U +#define INA230_CONFIG_BUS_SHIFT 6U + +// ------------------ INA230 IC Configuration Masks/Flags ------------- // +#define INA230_MASK_ENABLE_SHUNT_OVER_ALERT_MASK (0b1 << 15) +#define INA230_MASK_ENABLE_SHUNT_UNDER_ALERT_MASK (0b1 << 14) +#define INA230_MASK_ENABLE_BUS_OVER_ALERT_MASK (0b1 << 13) +#define INA230_MASK_ENABLE_BUS_UNDER_ALERT_MASK (0b1 << 12) +#define INA230_MASK_ENABLE_POWER_OVER_ALERT_MASK (0b1 << 11) +#define INA230_MASK_ENABLE_TRANSPARENT_MODE_SET_MASK 1U + +// ------------------ INA230 IC Configuration Modes ------------- // + +// Operating Modes +#define INA230_MODE_POWER_DOWN 0b000 +#define INA230_MODE_SHUNT_TRIGGERED 0b001 +#define INA230_MODE_BUS_TRIGGERED 0b010 +#define INA230_MODE_SHUNT_BUS_TRIGGERED 0b011 +#define INA230_MODE_SHUNT_CONTINUOUS 0b101 +#define INA230_MODE_BUS_CONTINUOUS 0b110 +#define INA230_MODE_SHUNT_BUS_CONTINUOUS 0b111 + +// Conversion Time (time for sensor to take measurements) +#define INA230_CONV_TIME_140US 0b000 +#define INA230_CONV_TIME_204US 0b001 +#define INA230_CONV_TIME_332US 0b010 +#define INA230_CONV_TIME_588US 0b011 +#define INA230_CONV_TIME_1100US 0b100 // 1.1ms: good balance +#define INA230_CONV_TIME_2116US 0b101 +#define INA230_CONV_TIME_4156US 0b110 +#define INA230_CONV_TIME_8244US 0b111 // Most accurate + +// Averaging Modes (number of measurements taken and averaged) +#define INA230_AVG_1 0b000 // No averaging +#define INA230_AVG_4 0b001 +#define INA230_AVG_16 0b010 // Good default +#define INA230_AVG_64 0b011 +#define INA230_AVG_128 0b100 +#define INA230_AVG_256 0b101 +#define INA230_AVG_512 0b110 +#define INA230_AVG_1024 0b111 // Maximum smoothing + +// Macros for Defaults +#define INA230_DEFAULT_MODE INA230_MODE_SHUNT_BUS_CONTINUOUS +#define INA230_DEFAULT_SHUNT_CONV_TIME INA230_CONV_TIME_1100US +#define INA230_DEFAULT_BUS_CONV_TIME INA230_CONV_TIME_1100US +#define INA230_DEFAULT_AVERAGING INA230_AVG_16 + +// Mask and Alerts +#define INA230_MASK_ENABLE_NONE 0x0000 +#define INA230_ALERT_LIMIT_NONE 0x0000 + +// Macros for LSB, Shunt Resistor, and Calibration Value +#define INA230_SHUNT_VOLTAGE_LSB 0.0000025f +#define INA230_BUS_VOLTAGE_LSB 0.00125f +#define INA230_CURRENT_LSB 0.001f // 1 mA, current least significant bit +#define INA230_SHUNT_RESISTOR 0.1f // 0.1 ohms, shunt resistor value +#define INA230_CALIBRATION_VALUE (uint16_t)(0.00512 / (INA230_CURRENT_LSB * INA230_SHUNT_RESISTOR)) +#define INA230_POWER_LSB_MULTIPLIER 25 + +typedef enum { + INA230_DEVICE_ONE = 0x00, + INA230_DEVICE_TWO = 0x01, + INA230_DEVICE_THREE = 0x02, + INA230_DEVICE_FOUR = 0x03, + INA230_DEVICE_FIVE = 0x04, + INA230_DEVICE_SIX = 0x05, + INA230_DEVICE_SEVEN = 0x06, + INA230_DEVICE_EIGHT = 0x07, + INA230_DEVICE_NINE = 0x08, + INA230_DEVICE_TEN = 0x09, + INA230_DEVICE_ELEVEN = 0x0A, + INA230_DEVICE_COUNT = 0x0B +} ina230_device_t; + +// function pointers to switch between mock and real data +extern obc_error_code_t (*i2cReadRegFuncPtr)(uint8_t, uint8_t, uint8_t*, uint16_t, TickType_t); +extern obc_error_code_t (*i2cWriteRegFuncPtr)(uint8_t, uint8_t, uint8_t*, uint16_t); + +obc_error_code_t initINA230(); +obc_error_code_t readAndDisableIfAlert(ina230_device_t device); +obc_error_code_t getINA230ShuntVoltage(ina230_device_t device, float* shuntVoltage); +obc_error_code_t disableNoAlert(ina230_device_t device); +obc_error_code_t getINA230BusVoltage(ina230_device_t device, float* busVoltage); +obc_error_code_t getINA230Power(ina230_device_t device, float* power); +obc_error_code_t getINA230Current(ina230_device_t device, float* power); + +#ifdef __cplusplus +} +#endif diff --git a/obc/app/drivers/tca6424/tca6424.c b/obc/app/drivers/tca6424/tca6424.c new file mode 100644 index 000000000..8b7ed1e1b --- /dev/null +++ b/obc/app/drivers/tca6424/tca6424.c @@ -0,0 +1,104 @@ +#include "tca6424.h" +#include "obc_i2c_io.h" +#include "obc_logging.h" +#include "obc_errors.h" + +#define TCA6424A_I2C_TRANSFER_TIMEOUT pdMS_TO_TICKS(100) +#define TCA6424A_I2C_ADDRESS 0x22 + +#define TCA6424A_INPUT_PORT_ZERO_ADDR 0x00 +#define TCA6424A_INPUT_PORT_ONE_ADDR 0x01 +#define TCA6424A_INPUT_PORT_TWO_ADDR 0x02 + +#define TCA6424A_OUTPUT_PORT_ZERO_ADDR 0x04 +#define TCA6424A_OUTPUT_PORT_ONE_ADDR 0x05 +#define TCA6424A_OUTPUT_PORT_TWO_ADDR 0x06 + +#define TCA6424A_CONFIGURATION_PORT_ZERO_ADDR 0x0C +#define TCA6424A_CONFIGURATION_PORT_ONE_ADDR 0x0D +#define TCA6424A_CONFIGURATION_PORT_TWO_ADDR 0x0E + +#define MAX_PIN_COUNT 7U +#define MAX_PORT_COUNT 3U + +static obc_error_code_t writeTCA6424ARegister(uint8_t addr, uint8_t* data, uint8_t size); +static obc_error_code_t readTCA6424ARegister(uint8_t addr, uint8_t* data, uint8_t size); + +obc_error_code_t configureTCA6424APin(uint8_t pinLocation, TCA6424A_gpio_config_t gpioPinConfig) { + uint8_t pinPort = (pinLocation & 0xF0) >> 4; // port 0, 1, or 2 + uint8_t pinIndex = pinLocation & 0x0F; // pin number in a port + if (pinIndex > MAX_PIN_COUNT || pinPort >= MAX_PORT_COUNT) return OBC_ERR_CODE_INVALID_ARG; + + uint8_t pinState = (gpioPinConfig == TCA6424A_GPIO_CONFIG_INPUT) ? 0x01 : 0x00; // input is 1, output is 0 + uint8_t configurationPortAddress = + TCA6424A_CONFIGURATION_PORT_ZERO_ADDR + pinPort; // configuration address for specific port + uint8_t configurationPort = 0; + + obc_error_code_t errCode; + RETURN_IF_ERROR_CODE( + readTCA6424ARegister(configurationPortAddress, &configurationPort, 1)); // read current configuration + + configurationPort |= (pinState << pinIndex); // set the 1 bit for the port + RETURN_IF_ERROR_CODE(writeTCA6424ARegister(configurationPortAddress, &configurationPort, 1)); + return OBC_ERR_CODE_SUCCESS; +} + +obc_error_code_t driveTCA6424APinOutput(uint8_t pinLocation, uint8_t IOPortValue) { + uint8_t pinPort = (pinLocation & 0xF0) >> 4; + uint8_t pinIndex = pinLocation & 0x0F; + if (pinIndex > MAX_PIN_COUNT || pinPort >= MAX_PORT_COUNT) return OBC_ERR_CODE_INVALID_ARG; + + uint8_t outputPortAddress = TCA6424A_OUTPUT_PORT_ZERO_ADDR + pinPort; + uint8_t outputPort = 0; + + obc_error_code_t errCode; + RETURN_IF_ERROR_CODE(configureTCA6424APin(pinLocation, TCA6424A_GPIO_CONFIG_OUTPUT)); + RETURN_IF_ERROR_CODE(readTCA6424ARegister(outputPortAddress, &outputPort, 1)); + + outputPort |= ((IOPortValue & 0x01) << pinIndex); + RETURN_IF_ERROR_CODE(writeTCA6424ARegister(outputPortAddress, &outputPort, 1)); + return OBC_ERR_CODE_SUCCESS; +} + +// Relies on autoincrement mode. Must validate. +obc_error_code_t readTCA642CompleteInput(uint32_t* ioPortInput) { + obc_error_code_t errCode; + uint8_t results[3] = {0}; + uint8_t portAddress = TCA6424A_INPUT_PORT_ZERO_ADDR; + + RETURN_IF_ERROR_CODE(readTCA6424ARegister(portAddress, results, MAX_PORT_COUNT)); + *ioPortInput = ((uint32_t)results[0] | (uint32_t)(results[1] << 8) | (uint32_t)(results[2] << 16)); + return OBC_ERR_CODE_SUCCESS; +} + +obc_error_code_t readTCA6424APinInput(uint8_t pinLocation, uint8_t* IOPortValue) { + uint8_t pinPort = (pinLocation & 0xF0) >> 4; + uint8_t pinIndex = pinLocation & 0x0F; + if (pinIndex > MAX_PIN_COUNT || pinPort >= MAX_PORT_COUNT) return OBC_ERR_CODE_INVALID_ARG; + + obc_error_code_t errCode; + uint8_t IOPortAddress = TCA6424A_INPUT_PORT_ZERO_ADDR + pinPort; + uint8_t IOPortRegister = 0; + + RETURN_IF_ERROR_CODE(readTCA6424ARegister(IOPortAddress, &IOPortRegister, 1)); + *IOPortValue = (IOPortRegister & (0x01 << pinIndex)) ? TCA6424A_GPIO_HIGH : TCA6424A_GPIO_LOW; + return OBC_ERR_CODE_SUCCESS; +} + +static obc_error_code_t writeTCA6424ARegister(uint8_t addr, uint8_t* data, uint8_t size) { + if (data == NULL) return OBC_ERR_CODE_INVALID_ARG; + + uint8_t slaveReg = (size > 1) ? (addr | (0x01 << 7)) : addr; + obc_error_code_t errCode; + RETURN_IF_ERROR_CODE(i2cWriteReg(TCA6424A_I2C_ADDRESS, slaveReg, data, size)); + return OBC_ERR_CODE_SUCCESS; +} + +static obc_error_code_t readTCA6424ARegister(uint8_t addr, uint8_t* data, uint8_t size) { + if (data == NULL) return OBC_ERR_CODE_INVALID_ARG; + + uint8_t slaveReg = (size > 1) ? (addr | (0x01 << 7)) : addr; + obc_error_code_t errCode; + RETURN_IF_ERROR_CODE(i2cReadReg(TCA6424A_I2C_ADDRESS, slaveReg, data, size, TCA6424A_I2C_TRANSFER_TIMEOUT)); + return OBC_ERR_CODE_SUCCESS; +} diff --git a/obc/app/drivers/tca6424/tca6424.h b/obc/app/drivers/tca6424/tca6424.h new file mode 100644 index 000000000..55b61ed97 --- /dev/null +++ b/obc/app/drivers/tca6424/tca6424.h @@ -0,0 +1,43 @@ +#pragma once + +#include "obc_errors.h" + +#include +#include + +#define TCA6424A_PIN_00 0x00 +#define TCA6424A_PIN_01 0x01 +#define TCA6424A_PIN_02 0x02 +#define TCA6424A_PIN_03 0x03 +#define TCA6424A_PIN_04 0x04 +#define TCA6424A_PIN_05 0x05 +#define TCA6424A_PIN_06 0x06 +#define TCA6424A_PIN_07 0x07 + +#define TCA6424A_PIN_10 0x10 +#define TCA6424A_PIN_11 0x11 +#define TCA6424A_PIN_12 0x12 +#define TCA6424A_PIN_13 0x13 +#define TCA6424A_PIN_14 0x14 +#define TCA6424A_PIN_15 0x15 +#define TCA6424A_PIN_16 0x16 +#define TCA6424A_PIN_17 0x17 + +#define TCA6424A_PIN_20 0x20 +#define TCA6424A_PIN_21 0x21 +#define TCA6424A_PIN_22 0x22 +#define TCA6424A_PIN_23 0x23 +#define TCA6424A_PIN_24 0x24 +#define TCA6424A_PIN_25 0x25 +#define TCA6424A_PIN_26 0x26 +#define TCA6424A_PIN_27 0x27 + +#define TCA6424A_GPIO_HIGH 1U +#define TCA6424A_GPIO_LOW 0U + +typedef enum { TCA6424A_GPIO_CONFIG_OUTPUT = 0x00, TCA6424A_GPIO_CONFIG_INPUT } TCA6424A_gpio_config_t; + +obc_error_code_t configureTCA6424APin(uint8_t pinLocation, TCA6424A_gpio_config_t gpioPinConfig); +obc_error_code_t readTCA6424APinInput(uint8_t pinLocation, uint8_t* IOPortValue); +obc_error_code_t driveTCA6424APinOutput(uint8_t pinLocation, uint8_t IOPortValue); +obc_error_code_t readTCA642CompleteInput(uint32_t* ioPortInput); diff --git a/obc/examples/test_app_rs/main.c b/obc/examples/test_app_rs/main.c index bf9f91850..e521772bf 100644 --- a/obc/examples/test_app_rs/main.c +++ b/obc/examples/test_app_rs/main.c @@ -36,8 +36,7 @@ int vTask1(void *pvParameters) { sciPrintf("Decoded data: %s\r\n", decodedData); destroyRs(); - while (1) - ; + while (1); } int main(void) { @@ -56,8 +55,7 @@ int main(void) { vTaskStartScheduler(); - while (1) - ; + while (1); return 0; } diff --git a/obc/examples/test_ina230_driver/main.c b/obc/examples/test_ina230_driver/main.c new file mode 100644 index 000000000..f031a11a4 --- /dev/null +++ b/obc/examples/test_ina230_driver/main.c @@ -0,0 +1,63 @@ +#include "ina230.h" +#include "obc_sci_io.h" +#include "obc_i2c_io.h" +#include "obc_print.h" +#include "obc_board_config.h" +#include "tca6424.h" + +#include +#include +#include +#include +#include + +#include +#include + +static StaticTask_t taskBuffer; +static StackType_t taskStack[1024]; + +void vTaskCode(void* pvParameters) { + obc_error_code_t errCode = 0; + bool isToggled = true; + while (1) { + if (isToggled) { + errCode = configureTCA6424APin(0x02, TCA6424A_GPIO_HIGH); + sciPrintf("Toggled High\r\n"); + isToggled = false; + } else { + errCode = configureTCA6424APin(0x02, TCA6424A_GPIO_LOW); + sciPrintf("Toggled Low\r\n"); + isToggled = true; + } + if (errCode != OBC_ERR_CODE_SUCCESS) { + sciPrintf("Error Initializing - %d\r\n", (int)errCode); + } + sciPrintf("Success - %d\r\n", (int)errCode); + gioToggleBit(STATE_MGR_DEBUG_LED_GIO_PORT, STATE_MGR_DEBUG_LED_GIO_BIT); + + // Simple delay. + vTaskDelay(pdMS_TO_TICKS(2000)); + } +} + +int main(void) { + // Run hardware initialization code + gioInit(); + sciInit(); + i2cInit(); + + sciEnableNotification(UART_PRINT_REG, SCI_RX_INT); + + _enable_interrupt_(); + + // Initialize bus mutexes + initSciPrint(); + initI2CMutex(); + + + // Assume all tasks are created correctly + xTaskCreateStatic(vTaskCode, "Demo", 1024, NULL, 1, taskStack, &taskBuffer); + + vTaskStartScheduler(); +} diff --git a/test/mocks/mock_freertos.c b/test/mocks/mock_freertos.c new file mode 100644 index 000000000..23e0dea6b --- /dev/null +++ b/test/mocks/mock_freertos.c @@ -0,0 +1,15 @@ +#include + +void* MPU_xQueueCreateMutexStatic(void* pxStaticQueue) { return NULL; } +void* MPU_xQueueGenericCreateStatic(unsigned portBaseType, unsigned portBaseType2, unsigned char ucQueueType, + void* pxStaticQueue, void* pxStaticBuffer) { + return NULL; +} +int MPU_xQueueGenericReceive(void* xQueue, void* pvBuffer, unsigned int xTicksToWait, unsigned int xJustPeeking) { + return 0; +} +int MPU_xQueueGenericSend(void* xQueue, const void* pvItemToQueue, unsigned int xTicksToWait, + unsigned int xCopyPosition) { + return 0; +} +void xQueueGiveFromISR(void* xQueue, int* pxHigherPriorityTaskWoken) {} diff --git a/test/mocks/mock_i2c_hal.c b/test/mocks/mock_i2c_hal.c new file mode 100644 index 000000000..8f4e204ec --- /dev/null +++ b/test/mocks/mock_i2c_hal.c @@ -0,0 +1,65 @@ +#include +#include +#include "obc_errors.h" +#include "os_portmacro.h" + +static uint8_t mockData[2] = {0, 0}; + +void i2cSetSlaveAdd(void* i2c, unsigned int slaveAdd) {} +void i2cSetDirection(void* i2c, unsigned int direction) {} +void i2cSetCount(void* i2c, unsigned int count) {} +void i2cSetMode(void* i2c, unsigned int mode) {} +void i2cSetStop(void* i2c) {} +void i2cSetStart(void* i2c) {} +void i2cSend(void* i2c, unsigned char* data, unsigned int length) {} +void i2cReceive(void* i2c, unsigned char* data, unsigned int length) {} +void i2cClearSCD(void* i2c) {} + +obc_error_code_t i2cWriteReg(uint8_t sAddr, uint8_t reg, uint8_t* data, uint16_t numBytes) { + // Simulate a successful write + return OBC_ERR_CODE_SUCCESS; +} + +obc_error_code_t i2cReadReg(uint8_t sAddr, uint8_t reg, uint8_t* data, uint16_t numBytes, + TickType_t transferTimeoutTicks) { + if ((sAddr == 0b1000000U || sAddr == 0b1000001U) && numBytes == 2) { + // 0x01 - shunt voltage + // 0x02 - bus voltage + // 0x03 - power + // 0x04 - current + if (reg == 0x01 || reg == 0x02 || reg == 0x03 || reg == 0x04) { // Bus voltage register + data[0] = mockData[0]; // High byte + data[1] = mockData[1]; // Low byte + } + return OBC_ERR_CODE_SUCCESS; + } + // Default mock behavior + return OBC_ERR_CODE_SUCCESS; +} + +void setMockBusVoltageValue(float expectedVoltage) { + uint16_t expectedVal = (uint16_t)((expectedVoltage / 0.00125f) + 0.5f); + mockData[0] = (0xFF00 & expectedVal) >> 8; // High byte + mockData[1] = 0xFF & expectedVal; // Low bytes +} + +void setMockCurrentValue(float expectedCurrent) { + uint16_t expectedVal = expectedCurrent / 0.001f; + mockData[0] = (0xFF00 & expectedVal) >> 8; // High byte + mockData[1] = 0xFF & expectedVal; // Low bytes +} + +void setMockPowerValue(float expectedPower) { + // add 0.5 to round to the nearest integer (ensures that .5-.9 rounds up instead of truncating the decimal) + uint16_t expectedVal = (uint16_t)((expectedPower / 0.025f) + 0.5f); + mockData[0] = (0xFF00 & expectedVal) >> 8; // High byte + mockData[1] = 0xFF & expectedVal; // Low bytes +} + +void setMockShuntVoltageValue(float expectedShuntVoltage) { + // if expected value is positive, add 0.5; if negative, subtract 0.5 + int32_t raw = (int32_t)((expectedShuntVoltage / 0.0000025f) + (expectedShuntVoltage >= 0 ? 0.5f : -0.5f)); + uint16_t expectedVal = (uint16_t)raw; // two's complement encoding for negative values + mockData[0] = (expectedVal >> 8) & 0xFF; // High byte + mockData[1] = expectedVal & 0xFF; // Low byte +} diff --git a/test/mocks/mock_i2c_hal.h b/test/mocks/mock_i2c_hal.h new file mode 100644 index 000000000..8405db447 --- /dev/null +++ b/test/mocks/mock_i2c_hal.h @@ -0,0 +1,13 @@ +#ifdef __cplusplus // to ensure that mock C file works with test C++ file +extern "C" { +#endif + +// for putting a mock value on the i2c register for testing +void setMockBusVoltageValue(float expectedVoltage); +void setMockCurrentValue(float expectedCurrent); +void setMockPowerValue(float expectedPower); +void setMockShuntVoltageValue(float expectedPower); + +#ifdef __cplusplus +} +#endif diff --git a/test/test_obc/unit/CMakeLists.txt b/test/test_obc/unit/CMakeLists.txt index ec7cf7671..c40c4c761 100644 --- a/test/test_obc/unit/CMakeLists.txt +++ b/test/test_obc/unit/CMakeLists.txt @@ -7,20 +7,32 @@ set(TEST_DEPENDENCIES ${CMAKE_SOURCE_DIR}/interfaces/obc_gs_interface/common/obc_gs_crc.c ${CMAKE_SOURCE_DIR}/interfaces/data_pack_unpack/data_unpack_utils.c ${CMAKE_SOURCE_DIR}/obc/app/sys/persistent/obc_persistent.c + ${CMAKE_SOURCE_DIR}/obc/app/drivers/ina230/ina230.c + ${CMAKE_SOURCE_DIR}/obc/app/drivers/tca6424/tca6424.c ) set(TEST_MOCKS ${CMAKE_SOURCE_DIR}/test/mocks/mock_logging.c ${CMAKE_SOURCE_DIR}/test/mocks/mock_fram.c ${CMAKE_SOURCE_DIR}/test/mocks/mock_crc.c + ${CMAKE_SOURCE_DIR}/test/mocks/mock_freertos.c ) +option(USE_MOCK_I2C "Use mock I2C for testing" ON) + +if(USE_MOCK_I2C) + list(APPEND TEST_MOCKS ${CMAKE_SOURCE_DIR}/test/mocks/mock_i2c_hal.c) +else() + list(APPEND TEST_DEPENDENCIES ${CMAKE_SOURCE_DIR}/obc/app/drivers/rm46/obc_i2c_io.c) +endif() + set(TEST_SOURCES ${CMAKE_SOURCE_DIR}/test/test_obc/unit/main.cpp ${CMAKE_SOURCE_DIR}/test/test_obc/unit/test_obc_time_utils.cpp ${CMAKE_SOURCE_DIR}/test/test_obc/unit/test_image_processing.cpp ${CMAKE_SOURCE_DIR}/test/test_obc/unit/test_vn100_unpack.cpp ${CMAKE_SOURCE_DIR}/test/test_obc/unit/test_obc_persistent.cpp + ${CMAKE_SOURCE_DIR}/test/test_obc/unit/test_ina230.cpp ) set(TEST_SOURCES ${TEST_SOURCES} ${TEST_DEPENDENCIES} ${TEST_MOCKS}) @@ -29,10 +41,10 @@ add_executable(${TEST_BINARY} ${TEST_SOURCES}) target_include_directories(${TEST_BINARY} PRIVATE - ${CMAKE_SOURCE_DIR}/obc/shared/obc_errors + ${CMAKE_SOURCE_DIR}/obc/app/sys ${CMAKE_SOURCE_DIR}/obc/app/sys/time ${CMAKE_SOURCE_DIR}/obc/app/sys/utils - ${CMAKE_SOURCE_DIR}/obc/shared/logging + ${CMAKE_SOURCE_DIR}/obc/app/sys/logging ${CMAKE_SOURCE_DIR}/obc/app/sys/persistent ${CMAKE_SOURCE_DIR}/obc/app/drivers/ds3232 ${CMAKE_SOURCE_DIR}/obc/app/drivers/arducam @@ -45,15 +57,25 @@ target_include_directories(${TEST_BINARY} ${CMAKE_SOURCE_DIR}/obc/app/modules/alarm_mgr ${CMAKE_SOURCE_DIR}/obc/app/modules/command_mgr ${CMAKE_SOURCE_DIR}/interfaces/obc_gs_interface/commands + ${CMAKE_SOURCE_DIR}/obc/app/drivers/ina230 + ${CMAKE_SOURCE_DIR}/obc/app/drivers/rm46 + ${CMAKE_SOURCE_DIR}/obc/shared/hal/obc_rev1/include + ${CMAKE_SOURCE_DIR}/obc/app/drivers/tca6424 + ${CMAKE_SOURCE_DIR}/test/mocks + ${CMAKE_SOURCE_DIR}/obc/shared/obc_errors + ${CMAKE_SOURCE_DIR}/obc/shared/logging + ${CMAKE_SOURCE_DIR}/obc/app/sys/logging + ${CMAKE_SOURCE_DIR}/obc/shared/hal/freertos/include ${CMAKE_SOURCE_DIR}/obc/shared/commands ) -# Add peripheral configs -add_definitions(-DCONFIG_VN100 -DCONFIG_ARDUCAM) - target_link_libraries(${TEST_BINARY} PRIVATE GTest::GTest ) add_test(${TEST_BINARY} ${TEST_BINARY}) + +if(USE_MOCK_I2C) + target_compile_definitions(${TEST_BINARY} PRIVATE USE_MOCK_I2C) +endif() diff --git a/test/test_obc/unit/test_ina230.cpp b/test/test_obc/unit/test_ina230.cpp new file mode 100644 index 000000000..96edade93 --- /dev/null +++ b/test/test_obc/unit/test_ina230.cpp @@ -0,0 +1,194 @@ + + +#include "obc_errors.h" +#include "ina230.h" +#include "mock_i2c_hal.h" +#include +#include + +#define INA230_TEST_FLOAT_TOLERANCE 0.01f + +// for reference: +// OBC_ERR_CODE_SUCCESS = 0, +// OBC_ERR_CODE_INVALID_ARG = 2 +// OBC_ERR_CODE_MUTEX_TIMEOUT = 4, +// OBC_ERR_CODE_FAILED_FILE_READ = 704, + +// --------- INIT TESTS --------- + +TEST(TestINA230, InitSuccess) { EXPECT_EQ(initINA230(), OBC_ERR_CODE_SUCCESS); } + +TEST(TestINA230, ReadAndDisableIfAlert) { + ina230_device_t device = INA230_DEVICE_ONE; + EXPECT_EQ(readAndDisableIfAlert(device), OBC_ERR_CODE_SUCCESS); +} + +// --------- SHUNT VOLTAGE TESTS --------- + +TEST(TestINA230, ShuntVoltageValues) { + float voltage = 0; + // Max register value: 0xFFFF + // since each bit is 2.5 μV, the voltage is -1 * 0.0000025 = -0.0000025 V + setMockShuntVoltageValue(-0.0000025); + EXPECT_EQ(getINA230ShuntVoltage(INA230_DEVICE_ONE, &voltage), OBC_ERR_CODE_SUCCESS); + EXPECT_FLOAT_EQ(voltage, -0.0000025); + // Min register value: 0x0000 + setMockShuntVoltageValue(0); + EXPECT_EQ(getINA230ShuntVoltage(INA230_DEVICE_ONE, &voltage), OBC_ERR_CODE_SUCCESS); + EXPECT_FLOAT_EQ(voltage, 0); + // Lowest val: 0x8000 + // use 2's complement on 0x8000 = 1000 0000 0000 0000 --> -32768 + // since each bit is 2.5 μV, the voltage is -32768 * 0.0000025 = -0.08192 V + setMockShuntVoltageValue(-0.08192); + EXPECT_EQ(getINA230ShuntVoltage(INA230_DEVICE_ONE, &voltage), OBC_ERR_CODE_SUCCESS); + EXPECT_FLOAT_EQ(voltage, -0.08192); + // Test positive value + setMockShuntVoltageValue(0.08f); + EXPECT_EQ(getINA230ShuntVoltage(INA230_DEVICE_ONE, &voltage), OBC_ERR_CODE_SUCCESS); + EXPECT_FLOAT_EQ(voltage, 0.08f); + // Multiple function calls + for (int i = 0; i < 5; ++i) { + float voltage = 0; + setMockShuntVoltageValue(0.05f); + obc_error_code_t err = getINA230ShuntVoltage(INA230_DEVICE_ONE, &voltage); + EXPECT_EQ(err, OBC_ERR_CODE_SUCCESS); + EXPECT_NEAR(voltage, 0.05f, INA230_TEST_FLOAT_TOLERANCE); + } +} + +TEST(TestINA230, ShuntVoltageInvalidArguments) { + float voltage = 0; + EXPECT_EQ(getINA230ShuntVoltage(INA230_DEVICE_ONE, NULL), OBC_ERR_CODE_INVALID_ARG); + EXPECT_EQ(getINA230ShuntVoltage((ina230_device_t)0b1010110, &voltage), OBC_ERR_CODE_INVALID_ARG); +} + +TEST(TestINA230, GetShuntVoltage_I2CAddress) { + float voltage = 0; + EXPECT_EQ(getINA230ShuntVoltage(INA230_DEVICE_TWO, &voltage), OBC_ERR_CODE_SUCCESS); + EXPECT_EQ(getINA230ShuntVoltage(INA230_DEVICE_COUNT, &voltage), OBC_ERR_CODE_INVALID_ARG); +} + +// --------- BUS VOLTAGE TESTS --------- +// bus voltage is unsigned +TEST(TestINA230, BusVoltageValues) { + float voltage = 0; + // bus voltage register is 16-bits, so max value is 0xFFFF + // since each bit is 1.25 mV, the max voltage is 65535 * 0.00125 = 81.91875 V + setMockBusVoltageValue(81.91875f); + EXPECT_EQ(getINA230BusVoltage(INA230_DEVICE_ONE, &voltage), OBC_ERR_CODE_SUCCESS); + EXPECT_FLOAT_EQ(voltage, 81.91875f); + // Min register value: 0x0000 + setMockBusVoltageValue(0); + EXPECT_EQ(getINA230BusVoltage(INA230_DEVICE_ONE, &voltage), OBC_ERR_CODE_SUCCESS); + EXPECT_FLOAT_EQ(voltage, 0); + // Test positive value 0x1000 = 4096, voltage = 4096 * 0.00125 = 5.12 + setMockBusVoltageValue(5.12f); + EXPECT_EQ(getINA230BusVoltage(INA230_DEVICE_ONE, &voltage), OBC_ERR_CODE_SUCCESS); + EXPECT_FLOAT_EQ(voltage, 5.12f); + // Multiple function calls + for (int i = 0; i < 5; ++i) { + float voltage = 0; + setMockBusVoltageValue(5.12f); + obc_error_code_t err = getINA230BusVoltage(INA230_DEVICE_ONE, &voltage); + EXPECT_EQ(err, OBC_ERR_CODE_SUCCESS); + EXPECT_FLOAT_EQ(voltage, 5.12f); + } +} + +TEST(TestINA230, BusVoltageInvalidArguments) { + float voltage = 0; + EXPECT_EQ(getINA230BusVoltage(INA230_DEVICE_ONE, NULL), OBC_ERR_CODE_INVALID_ARG); + EXPECT_EQ(getINA230BusVoltage((ina230_device_t)0b1010110, &voltage), OBC_ERR_CODE_INVALID_ARG); +} + +TEST(TestINA230, BusVoltage_I2CAddress) { + float voltage = 0; + EXPECT_EQ(getINA230BusVoltage(INA230_DEVICE_TWO, &voltage), OBC_ERR_CODE_SUCCESS); + EXPECT_EQ(getINA230BusVoltage(INA230_DEVICE_COUNT, &voltage), OBC_ERR_CODE_INVALID_ARG); +} + +// --------- POWER TESTS --------- +// power is unsigned +TEST(TestINA230, PowerVoltageValues) { + float power = 0; + + // Max register value: 0xFFFF + // power LSB = 25 * current LSB = 25 * 0.001f = 0.025f + // since each bit is 2.5 mW, the max power is 65535 * 0.025 = 1638.375 W + setMockPowerValue(1638.375f); + EXPECT_EQ(getINA230Power(INA230_DEVICE_ONE, &power), OBC_ERR_CODE_SUCCESS); + EXPECT_FLOAT_EQ(power, 1638.375f); + // Min register value: 0x0000 + setMockPowerValue(0); + EXPECT_EQ(getINA230Power(INA230_DEVICE_ONE, &power), OBC_ERR_CODE_SUCCESS); + EXPECT_FLOAT_EQ(power, 0); + // Test positive value + setMockPowerValue(5.12f); + EXPECT_EQ(getINA230Power(INA230_DEVICE_ONE, &power), OBC_ERR_CODE_SUCCESS); + EXPECT_NEAR(power, 5.12f, INA230_TEST_FLOAT_TOLERANCE); + // Multiple function calls + for (int i = 0; i < 5; ++i) { + float power = 0; + setMockPowerValue(0.05f); + obc_error_code_t err = getINA230Power(INA230_DEVICE_ONE, &power); + EXPECT_EQ(err, OBC_ERR_CODE_SUCCESS); + EXPECT_NEAR(power, 0.05f, INA230_TEST_FLOAT_TOLERANCE); + } +} + +TEST(TestINA230, PowerInvalidArguments) { + float power = 0; + EXPECT_EQ(getINA230Power(INA230_DEVICE_ONE, NULL), OBC_ERR_CODE_INVALID_ARG); + EXPECT_EQ(getINA230Power((ina230_device_t)0b1010110, &power), OBC_ERR_CODE_INVALID_ARG); +} + +TEST(TestINA230, Power_I2CAddress) { + float power = 0; + EXPECT_EQ(getINA230Power(INA230_DEVICE_TWO, &power), OBC_ERR_CODE_SUCCESS); + EXPECT_EQ(getINA230Power(INA230_DEVICE_COUNT, &power), OBC_ERR_CODE_INVALID_ARG); +} + +// --------- CURRENT TESTS --------- + +TEST(TestINA230, CurrentValues) { + float current = 0; + // Max register value: 0xFFFF + // since each bit is 1 mA, -1 * 0.001 = -0.001 + setMockCurrentValue(-0.001); + EXPECT_EQ(getINA230Current(INA230_DEVICE_ONE, ¤t), OBC_ERR_CODE_SUCCESS); + EXPECT_FLOAT_EQ(current, -0.001); + // Min register value: 0x0000 + setMockCurrentValue(0); + EXPECT_EQ(getINA230Current(INA230_DEVICE_ONE, ¤t), OBC_ERR_CODE_SUCCESS); + EXPECT_FLOAT_EQ(current, 0); + // Lowest register value: 0x8000 + // use 2's complement on 0x8000 = 1000 0000 0000 0000 --> -32768 + // since each bit is 1 mA, -32768 * 0.001 = -32.768 + setMockCurrentValue(-32.768); + EXPECT_EQ(getINA230Current(INA230_DEVICE_ONE, ¤t), OBC_ERR_CODE_SUCCESS); + EXPECT_FLOAT_EQ(current, -32.768); + // Test positive value + setMockCurrentValue(5.12f); + EXPECT_EQ(getINA230Current(INA230_DEVICE_ONE, ¤t), OBC_ERR_CODE_SUCCESS); + EXPECT_NEAR(current, 5.12f, INA230_TEST_FLOAT_TOLERANCE); + // Multiple function calls + for (int i = 0; i < 5; ++i) { + float current = 0; + setMockCurrentValue(0.05f); + obc_error_code_t err = getINA230Current(INA230_DEVICE_ONE, ¤t); + EXPECT_EQ(err, OBC_ERR_CODE_SUCCESS); + EXPECT_NEAR(current, 0.05f, INA230_TEST_FLOAT_TOLERANCE); + } +} + +TEST(TestINA230, CurrentInvalidArguments) { + float current = 0; + EXPECT_EQ(getINA230Current(INA230_DEVICE_ONE, NULL), OBC_ERR_CODE_INVALID_ARG); + EXPECT_EQ(getINA230Current((ina230_device_t)0b1010110, ¤t), OBC_ERR_CODE_INVALID_ARG); +} + +TEST(TestINA230, Current_I2CAddress) { + float current = 0; + EXPECT_EQ(getINA230Current(INA230_DEVICE_TWO, ¤t), OBC_ERR_CODE_SUCCESS); + EXPECT_EQ(getINA230Current(INA230_DEVICE_COUNT, ¤t), OBC_ERR_CODE_INVALID_ARG); +}