1+ #include "ina230.h"
2+ #include "obc_i2c_io.h"
3+ #include "tca6424.h"
4+ #include "obc_logging.h"
5+ #include <stdio.h>
6+ #include <assert.h>
7+
8+ #define INA230_I2C_ADDRESS_ONE 0b1000000U
9+ #define INA230_I2C_ADDRESS_TWO 0b1000001U
10+
11+ // ------------------ TCA6424 IC Related Pins ------------- //
12+
13+ // enable pins for device one
14+ #define INA230_ONE_ENABLE_PIN TCA6424A_PIN_03
15+ #define INA230_TWO_ENABLE_PIN TCA6424A_PIN_01
16+
17+ // alert pins for device two
18+ #define INA230_ONE_ALERT_PIN TCA6424A_PIN_02
19+ #define INA230_TWO_ALERT_PIN TCA6424A_PIN_00
20+
21+ #define INA230_ENABLE_LOAD TCA6424A_GPIO_HIGH
22+ #define INA230_DISABLE_LOAD TCA6424A_GPIO_LOW
23+ #define INA230_ALERT_HIGH TCA6424A_GPIO_HIGH
24+
25+ // ------------------ INA230 IC Related General Configuration Addresses/Bitfields ------------- //
26+ #define INA230_CONFIG_REGISTER_ADDR 0x00U
27+ #define INA230_MASK_ENABLE_REGISTER_ADDR 0x06U
28+ #define INA230_ALERT_LIMIT_REGISTER_ADDR 0x07U
29+ #define INA230_CALIBRATION_REGISTER_ADDR 0x05U
30+ #define INA230_SHUNT_VOLTAGE_REGISTER_ADDR 0x01U
31+ #define INA230_BUS_VOLTAGE_REGISTER_ADDR 0x02U
32+ #define INA230_POWER_REGISTER_ADDR 0x03U
33+ #define INA230_CURRENT_REGISTER_ADDR 0x04U
34+
35+ #define INA230_CONFIG_MODE_SHIFT 0U
36+ #define INA230_CONFIG_SHU_SHIFT 3U
37+ #define INA230_CONFIG_AVG_SHIFT 9U
38+ #define INA230_CONFIG_BUS_SHIFT 6U
39+
40+ // ------------------ INA230 IC Configuration Masks/Flags ------------- //
41+ #define INA230_MASK_ENABLE_SHUNT_OVER_ALERT_MASK (0b1 << 15)
42+ #define INA230_MASK_ENABLE_SHUNT_UNDER_ALERT_MASK (0b1 << 14)
43+ #define INA230_MASK_ENABLE_BUS_OVER_ALERT_MASK (0b1 << 13)
44+ #define INA230_MASK_ENABLE_BUS_UNDER_ALERT_MASK (0b1 << 12)
45+ #define INA230_MASK_ENABLE_POWER_OVER_ALERT_MASK (0b1 << 11)
46+
47+ #define INA230_MASK_ENABLE_TRANSPARENT_MODE_SET_MASK 1U
48+
49+ // macros for lsb, shunt resistor, and calibration value
50+ #define INA230_SHUNT_VOLTAGE_LSB 0.0000025f
51+ #define INA230_CURRENT_LSB 0.001f // 1 mA, current least significant bit
52+ #define INA230_SHUNT_RESISTOR 0.1f // 0.1 ohms, shunt resistor value
53+ #define INA230_CALIBRATION_VALUE (uint16_t)(0.00512 / (INA230_CURRENT_LSB * INA230_SHUNT_RESISTOR))
54+
55+ // buffer sizes
56+ #define INA_REG_CONF_BUFF_SIZE 2
57+
58+ #define I2C_TRANSFER_TIMEOUT_TICKS pdMS_TO_TICKS(100) // 100 ms in RTOS ticks
59+
60+ // function pointers to switch between mock and real data
61+ obc_error_code_t (* i2cReadRegFuncPtr )(uint8_t , uint8_t , uint8_t * , uint16_t , TickType_t ) = NULL ;
62+ obc_error_code_t (* i2cWriteRegFuncPtr )(uint8_t , uint8_t , uint8_t * , uint16_t ) = NULL ;
63+
64+ // ------------------ INA230 Device Configuration ------------- //
65+ typedef struct {
66+ uint8_t i2cDeviceAddress ;
67+
68+ // TCA GPIO Expander ports
69+ uint8_t tcaAlertPort ;
70+ uint8_t tcaEnablePort ;
71+
72+ // Values for the configuration register
73+ uint8_t configurationMode ;
74+ uint8_t configurationShunt ;
75+ uint8_t configurationAvg ;
76+ uint8_t configurationBus ;
77+
78+ // Values for other registers
79+ uint16_t maskEnableRegister ;
80+ uint16_t calibrationRegister ;
81+ uint16_t alertRegister ;
82+ } ina230_config_t ;
83+
84+ static const ina230_config_t ina230Devices [] = {[INA230_DEVICE_ONE ] = {.i2cDeviceAddress = INA230_I2C_ADDRESS_ONE ,
85+ .tcaAlertPort = INA230_ONE_ALERT_PIN ,
86+ .tcaEnablePort = INA230_ONE_ENABLE_PIN },
87+
88+ [INA230_DEVICE_TWO ] = {.i2cDeviceAddress = INA230_I2C_ADDRESS_TWO ,
89+ .tcaAlertPort = INA230_TWO_ALERT_PIN ,
90+ .tcaEnablePort = INA230_TWO_ENABLE_PIN }};
91+
92+ static obc_error_code_t writeINA230Register (uint8_t regAddress , uint8_t * data , uint8_t size , uint8_t i2cAddress );
93+ static obc_error_code_t initTca6424PinState ();
94+
95+ /**
96+ * @brief Initializes the INA230 devices
97+ *
98+ * This function initializes all INA230 devices by configuring their registers
99+ * and setting up the corresponding TCA6424 pins for alerts and enable control via initTca6424PinState().
100+ *
101+ * @return OBC_ERR_CODE_SUCCESS if initialization is successful,
102+ * otherwise returns an appropriate error code
103+ */
104+ obc_error_code_t initINA230 () {
105+ #ifndef USE_MOCK_I2C
106+ i2cReadRegFuncPtr = i2cReadReg ;
107+ i2cWriteRegFuncPtr = i2cWriteReg ;
108+ #endif
109+ obc_error_code_t errCode ;
110+ for (uint8_t i = 0 ; i < INA230_DEVICE_COUNT ; ++ i ) {
111+ const ina230_config_t device = ina230Devices [i ];
112+ const uint16_t configurationRegister = (device .configurationMode << INA230_CONFIG_MODE_SHIFT ) |
113+ (device .configurationShunt << INA230_CONFIG_SHU_SHIFT ) |
114+ (device .configurationAvg << INA230_CONFIG_AVG_SHIFT ) |
115+ (device .configurationBus << INA230_CONFIG_BUS_SHIFT );
116+
117+ uint8_t configRegisterUnpacked [] = {configurationRegister & 0xFF , configurationRegister >> 8 };
118+ uint8_t maskEnRegisterUnpacked [] = {device .maskEnableRegister & 0xFF , device .maskEnableRegister >> 8 };
119+ uint8_t alertRegisterUnpacked [] = {device .alertRegister & 0xFF , device .alertRegister >> 8 };
120+ uint8_t calibrationRegisterUnpacked [] = {device .calibrationRegister & 0xFF , device .calibrationRegister >> 8 };
121+
122+ uint8_t deviceAddress = device .i2cDeviceAddress ;
123+ RETURN_IF_ERROR_CODE (writeINA230Register (INA230_CONFIG_REGISTER_ADDR , configRegisterUnpacked ,
124+ sizeof (configRegisterUnpacked ) / sizeof (configRegisterUnpacked [0 ]),
125+ deviceAddress ));
126+ RETURN_IF_ERROR_CODE (writeINA230Register (INA230_MASK_ENABLE_REGISTER_ADDR , maskEnRegisterUnpacked ,
127+ sizeof (maskEnRegisterUnpacked ) / sizeof (maskEnRegisterUnpacked [0 ]),
128+ deviceAddress ));
129+ RETURN_IF_ERROR_CODE (writeINA230Register (INA230_ALERT_LIMIT_REGISTER_ADDR , alertRegisterUnpacked ,
130+ sizeof (alertRegisterUnpacked ) / sizeof (alertRegisterUnpacked [0 ]),
131+ deviceAddress ));
132+ // uint16_t calibrationValue = INA230_CALIBRATION_VALUE;
133+ // uint8_t calibrationRegisterUnpacked[] = {calibrationValue & 0xFF, calibrationValue >> 8};
134+
135+ RETURN_IF_ERROR_CODE (writeINA230Register (
136+ INA230_CALIBRATION_REGISTER_ADDR , calibrationRegisterUnpacked ,
137+ sizeof (calibrationRegisterUnpacked ) / sizeof (calibrationRegisterUnpacked [0 ]), deviceAddress ));
138+ }
139+
140+ RETURN_IF_ERROR_CODE (initTca6424PinState ());
141+ return OBC_ERR_CODE_SUCCESS ;
142+ }
143+
144+ /**
145+ * @brief Reads the TCA6424 input and disables INA230 devices if an alert is detected
146+ *
147+ * This function reads the complete input from the TCA6424 and checks for alerts
148+ * on the INA230 devices. If an alert is detected, it disables the corresponding device.
149+ *
150+ * @param device The INA230 device to check (currently unused in the function)
151+ * @return OBC_ERR_CODE_SUCCESS if operation is successful,
152+ * otherwise returns an appropriate error code
153+ */
154+ inline obc_error_code_t readAndDisableIfAlert (ina230_device_t device ) {
155+ uint32_t IOPortValue = 0 ;
156+ obc_error_code_t errCode ;
157+ RETURN_IF_ERROR_CODE (readTCA642CompleteInput (& IOPortValue )); // reads 24 bit input of TCA GPIO Expander
158+
159+ for (uint8_t i = 0 ; i < INA230_DEVICE_COUNT ; ++ i ) {
160+ uint8_t pinLocation =
161+ ina230Devices [i ].tcaEnablePort ; // specific pin on TCA that this ina230 controls, should this be alertPort?
162+ uint8_t index = ((pinLocation & 0x0F ) +
163+ ((pinLocation >> 1 ) & 0x18 )); // converts the pinLocation to an index in the 24 bit IOPortValue
164+ if (IOPortValue & (0b1 << index )) { // if it's high
165+ uint8_t drivePort = INA230_DISABLE_LOAD ;
166+ RETURN_IF_ERROR_CODE (driveTCA6424APinOutput (pinLocation , drivePort ));
167+ }
168+ }
169+ return OBC_ERR_CODE_SUCCESS ;
170+ }
171+ /**
172+ * @brief Writes data to an INA230 register
173+ *
174+ * This function writes data to a specified register of an INA230 device.
175+ *
176+ * @param regAddress The address of the register to write to
177+ * @param data Pointer to the data to be written
178+ * @param size The size of the data to be written
179+ * @param i2cAddress The I2C address of the INA230 device
180+ * @return OBC_ERR_CODE_SUCCESS if write is successful,
181+ * otherwise returns an appropriate error code
182+ */
183+
184+ static obc_error_code_t writeINA230Register (uint8_t regAddress , uint8_t * data , uint8_t size , uint8_t i2cAddress ) {
185+ if (data == NULL ) return OBC_ERR_CODE_INVALID_ARG ;
186+ if (i2cAddress != INA230_I2C_ADDRESS_ONE && i2cAddress != INA230_I2C_ADDRESS_TWO ) return OBC_ERR_CODE_INVALID_ARG ;
187+
188+ obc_error_code_t errCode ;
189+ RETURN_IF_ERROR_CODE (i2cWriteRegFuncPtr (i2cAddress , regAddress , data , size ));
190+ return OBC_ERR_CODE_SUCCESS ;
191+ }
192+ /**
193+ * @brief Initializes the TCA6424 pin states for INA230 devices
194+ *
195+ * This function configures the TCA6424 pins used for alerts and enable control
196+ * of the INA230 devices. It sets up the alert pins as inputs and the enable pins as outputs.
197+ *
198+ * @return OBC_ERR_CODE_SUCCESS if initialization is successful,
199+ * otherwise returns an appropriate error code
200+ */
201+ static obc_error_code_t initTca6424PinState () {
202+ obc_error_code_t errCode ;
203+ for (uint8_t i = 0 ; i < INA230_DEVICE_COUNT ; ++ i ) {
204+ ina230_config_t device = ina230Devices [i ];
205+ RETURN_IF_ERROR_CODE (configureTCA6424APin (
206+ device .tcaAlertPort , TCA6424A_GPIO_CONFIG_INPUT )); // alert pin is output of ina230 and input to tca
207+ RETURN_IF_ERROR_CODE (configureTCA6424APin (
208+ device .tcaEnablePort , TCA6424A_GPIO_CONFIG_OUTPUT )); // alert pin is input of ina230 and output of tca
209+ RETURN_IF_ERROR_CODE (
210+ driveTCA6424APinOutput (device .tcaEnablePort , INA230_ENABLE_LOAD )); // set the pin enable pin to high
211+ }
212+ return OBC_ERR_CODE_SUCCESS ;
213+ }
214+
215+ // function to get shunt voltage
216+ // i2cAddress = ina230 device address
217+ // shuntVoltage = pointer to store shuntVoltage
218+
219+ obc_error_code_t getINA230ShuntVoltage (uint8_t i2cAddress , float * shuntVoltage ) {
220+ if (shuntVoltage == NULL ) return OBC_ERR_CODE_INVALID_ARG ;
221+
222+ uint8_t shuntVoltageRaw [INA_REG_CONF_BUFF_SIZE ] = {0 }; // store 2 bytes of shunt voltage
223+ obc_error_code_t errCode ;
224+
225+ if (i2cAddress != INA230_I2C_ADDRESS_ONE && i2cAddress != INA230_I2C_ADDRESS_TWO ) return OBC_ERR_CODE_INVALID_ARG ;
226+
227+ // Read the 16-bit shunt voltage register
228+ errCode = i2cReadRegFuncPtr (i2cAddress , INA230_SHUNT_VOLTAGE_REGISTER_ADDR , shuntVoltageRaw , 2 ,
229+ I2C_TRANSFER_TIMEOUT_TICKS ); // last param not sure
230+ if (errCode != OBC_ERR_CODE_SUCCESS ) return errCode ;
231+
232+ // Combine the two bytes into a 16-bit value
233+ int16_t shuntVoltageValue = (shuntVoltageRaw [0 ] << 8 ) | shuntVoltageRaw [1 ];
234+
235+
236+ // Convert to actual voltage (signed value)
237+ * shuntVoltage = shuntVoltageValue * INA230_SHUNT_VOLTAGE_LSB ;
238+
239+ return OBC_ERR_CODE_SUCCESS ;
240+ }
241+
242+ obc_error_code_t getINA230ShuntVoltageForDevice (uint8_t deviceIndex , float * shuntVoltage ) {
243+ if (shuntVoltage == NULL ) return OBC_ERR_CODE_INVALID_ARG ;
244+
245+ if (deviceIndex >= INA230_DEVICE_COUNT ) return OBC_ERR_CODE_INVALID_ARG ; // Check valid device index
246+
247+ // Retrieve the I2C address for the specified INA230 device
248+ uint8_t i2cAddress = ina230Devices [deviceIndex ].i2cDeviceAddress ;
249+
250+ // Get the shunt voltage
251+ return getINA230ShuntVoltage (i2cAddress , shuntVoltage );
252+ }
253+
254+ void main_usage () {
255+ float shuntVoltage = 0 ;
256+
257+ // Call the function for INA230 device 1 (index 0)
258+ obc_error_code_t errCode = getINA230ShuntVoltageForDevice (0 , & shuntVoltage );
259+ // print shunt volatge
260+ printf ("Shunt Voltage for INA230 Device 1: %f V\n" , shuntVoltage );
261+
262+ if (errCode == OBC_ERR_CODE_SUCCESS ) {
263+ printf ("Shunt Voltage for INA230 Device 1: %f V\n" , shuntVoltage );
264+ } else {
265+ printf ("Error reading shunt voltage for INA230 Device 1\n" );
266+ }
267+ }
268+
269+ // general disable function for ina230 device
270+
271+ obc_error_code_t disableNoAlert (ina230_device_t device ) {
272+ uint32_t IOPortValue = 0 ;
273+ obc_error_code_t errCode ;
274+
275+ for (uint8_t i = 0 ; i < INA230_DEVICE_COUNT ; ++ i ) {
276+ uint8_t pinLocation =
277+ ina230Devices [i ].tcaEnablePort ; // specific pin on TCA that this ina230 controls, should this be alertPort?
278+ uint8_t index = ((pinLocation & 0x0F ) +
279+ ((pinLocation >> 1 ) & 0x18 )); // converts the pinLocation to an index in the 24 bit IOPortValue
280+ // disbale
281+ uint8_t drivePort = INA230_DISABLE_LOAD ;
282+ RETURN_IF_ERROR_CODE (driveTCA6424APinOutput (pinLocation , drivePort ));
283+ }
284+ return OBC_ERR_CODE_SUCCESS ;
285+ }
286+
287+ // Notes (delete later)
288+ // Bus voltage register:
289+ // Address: 0x02
290+ // Size: 16 bits (2 bytes)
291+ // Each bit on the register represents 1.25 mV
292+
293+ /**
294+ * @brief Gets the bus voltage
295+ *
296+ * @param i2cAddress The I2C address of the INA230 device
297+ * @return OBC_ERR_CODE_SUCCESS if write is successful,
298+ * otherwise return an appropriate error code
299+ */
300+ // function to get bus voltage
301+ obc_error_code_t getINA230BusVoltage (uint8_t i2cAddress , float * busVoltage ) {
302+ if (busVoltage == NULL ) return OBC_ERR_CODE_INVALID_ARG ;
303+ if (i2cAddress != INA230_I2C_ADDRESS_ONE && i2cAddress != INA230_I2C_ADDRESS_TWO ) return OBC_ERR_CODE_INVALID_ARG ;
304+
305+ obc_error_code_t errCode ;
306+ uint8_t busVoltageRaw [2 ] = {};
307+ RETURN_IF_ERROR_CODE (
308+ i2cReadRegFuncPtr (i2cAddress , INA230_BUS_VOLTAGE_REGISTER_ADDR , busVoltageRaw , 2 , I2C_TRANSFER_TIMEOUT_TICKS ));
309+ uint16_t busVoltageValue = (busVoltageRaw [0 ] << 8 ) | busVoltageRaw [1 ];
310+ * busVoltage = busVoltageValue * 0.00125f ;
311+
312+ return OBC_ERR_CODE_SUCCESS ;
313+ }
314+
315+ obc_error_code_t getINA230BusVoltageForDevice (uint8_t deviceIndex , float * busVoltage ) {
316+ if (busVoltage == NULL || deviceIndex >= INA230_DEVICE_COUNT ) return OBC_ERR_CODE_INVALID_ARG ;
317+ uint8_t i2cAddress = ina230Devices [deviceIndex ].i2cDeviceAddress ;
318+ return getINA230BusVoltage (i2cAddress , busVoltage );
319+ }
320+
321+ obc_error_code_t getINA230Power (uint8_t i2cAddress , float * power ) {
322+ obc_error_code_t errCode ;
323+ if (power == NULL || (i2cAddress != INA230_I2C_ADDRESS_ONE && i2cAddress != INA230_I2C_ADDRESS_TWO )) return OBC_ERR_CODE_INVALID_ARG ;
324+ uint8_t powerRaw [INA_REG_CONF_BUFF_SIZE ] = {};
325+ RETURN_IF_ERROR_CODE (
326+ i2cReadRegFuncPtr (i2cAddress , INA230_POWER_REGISTER_ADDR , powerRaw , 2 , I2C_TRANSFER_TIMEOUT_TICKS ));
327+ uint16_t powerValue = (powerRaw [0 ] << 8 ) | powerRaw [1 ];
328+ * power = powerValue * (INA230_CURRENT_LSB * 25 );
329+ return OBC_ERR_CODE_SUCCESS ;
330+ }
331+
332+ obc_error_code_t getINA230PowerForDevice (uint8_t deviceIndex , float * power ) {
333+ if (power == NULL || deviceIndex >= INA230_DEVICE_COUNT ) return OBC_ERR_CODE_INVALID_ARG ;
334+ uint8_t i2cAddress = ina230Devices [deviceIndex ].i2cDeviceAddress ;
335+ return getINA230Power (i2cAddress , power );
336+ }
337+
338+ // function to get current
339+ obc_error_code_t getINA230Current (uint8_t i2cAddress , float * current ) {
340+ obc_error_code_t errCode ;
341+ if (current == NULL || (i2cAddress != INA230_I2C_ADDRESS_ONE && i2cAddress != INA230_I2C_ADDRESS_TWO )) return OBC_ERR_CODE_INVALID_ARG ;
342+ uint8_t currentRaw [INA_REG_CONF_BUFF_SIZE ] = {};
343+ RETURN_IF_ERROR_CODE (
344+ i2cReadRegFuncPtr (i2cAddress , INA230_CURRENT_REGISTER_ADDR , currentRaw , 2 , I2C_TRANSFER_TIMEOUT_TICKS ));
345+ int16_t currentValue = (currentRaw [0 ] << 8 ) | currentRaw [1 ];
346+ * current = currentValue * INA230_CURRENT_LSB ;
347+ return OBC_ERR_CODE_SUCCESS ;
348+ }
349+
350+ obc_error_code_t getINA230CurrentForDevice (uint8_t deviceIndex , float * current ) {
351+ if (current == NULL || deviceIndex >= INA230_DEVICE_COUNT ) return OBC_ERR_CODE_INVALID_ARG ;
352+ uint8_t i2cAddress = ina230Devices [deviceIndex ].i2cDeviceAddress ;
353+ return getINA230Current (i2cAddress , current );
354+ }
355+
356+ // allow loop through
0 commit comments