#include "battery.h" #include "ti/driverlib/dl_i2c.h" #include "ti/driverlib/m0p/dl_core.h" #include "ti_msp_dl_config.h" #include #include #include volatile bool gRxComplete; volatile bool gTxComplete; uint8_t gTxPacket[I2C_TX_MAX_PACKET_SIZE]; uint8_t gRxPacket[I2C_RX_MAX_PACKET_SIZE]; uint32_t gTxADClen, gTxADCcount; uint32_t gRxADClen, gRxADCcount; static ADC_PARAMS adc_params; static ADC_MeasurementState adc_state = ADC_STATE_CONFIGURE; uint8_t ADC_ConstructConfigBytes(ADC_PARAMS params) { uint8_t config = 0; config |= ((params.channel) << 5); // Channel Selection (Bits 6-5) config |= (1 << 4); // One-Shot Mode switch (params.resolution) { case 12: config |= (0b00 << 2); break; case 14: config |= (0b01 << 2); break; case 16: config |= (0b10 << 2); break; default: printf("ERROR: Invalid Resolution!\n"); return 0; } switch (params.gain) { case 1: config |= (0b00); break; case 2: config |= (0b01); break; case 4: config |= (0b10); break; default: printf("ERROR: Invalid Gain!\n"); return 0; } return config; } /* Tansmit Data from MCU to ADC: Function to SET configuration to ADC over * I2C*/ void ADC_SetConfigurationBytes(ADC_PARAMS params) { // **Construct Configuration Byte** uint8_t config_byte = ADC_ConstructConfigBytes(params); // Wait for I2C Bus to be Free** while (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS) ; // **Start I2C Write Transaction** // Prepare TX Buffer: gTxPacket[0] = config_byte; gTxADClen = 1; gTxADCcount = 0; gTxComplete = false; DL_I2C_flushControllerTXFIFO(I2C_controller_INST); DL_I2C_fillControllerTXFIFO(I2C_controller_INST, (uint8_t *)&gTxPacket, 1); DL_I2C_startControllerTransfer(I2C_controller_INST, ADC_TARGET_BASE_ADDRESS, DL_I2C_CONTROLLER_DIRECTION_TX, gTxADClen); // DL_I2C_enableInterrupt(I2C_controller_INST, // DL_I2C_INTERRUPT_CONTROLLER_TXFIFO_TRIGGER); // while(!gTxComplete); // **Ensure STOP Condition is Sent** while (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS) ; // printf("Configuration Sent Successfully for 0x%X!\n", config_byte); } /* READY BIT: This bit is the data ready flag. In read mode, this bit indicates if the output register has been updated with a latest conversion result. In One-Shot Conversion mode, writing this bit to “1” initiates a new conversion. 1= Output Register has not been updated 0= Output Register has been updated */ bool ADC_CheckReadyBit(uint8_t slot_id, ADC_PARAMS params) { // Buffer for ADC data (MSB, LSB, Config Byte) uint8_t adc_data[3]; uint8_t adc_address = ADC_TARGET_BASE_ADDRESS + slot_id; printf("Reading ADC Data from MCP3428 for channel: %u\n", params.channel); gRxADClen = 3; gRxADCcount = 0; gRxComplete = false; DL_I2C_startControllerTransfer(I2C_controller_INST, adc_address, DL_I2C_CONTROLLER_DIRECTION_RX, gRxADClen); DL_I2C_enableInterrupt(I2C_controller_INST, DL_I2C_INTERRUPT_CONTROLLER_RXFIFO_TRIGGER); while (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS) ; while (!gRxComplete) ; uint8_t config_adc_byte = gRxPacket[2]; // Ready bit is bit 7 bool ready = (config_adc_byte & 0x80) == 0; printf("Slot: %d | Config Byte: 0x%02X | READY Bit: %d\n", slot_id, config_adc_byte, ready); return ready; } /* Function to read ADC DATA: This function simply retrieves the ADC raw digital output as 16-bit signed integer. * The value returned is not yet converted to VOLTAGE. * adc_data[2]: Buffer with an array of size 2, to store two bytes of ADC data. * Since, the ADC output consists of two 8-bit bytes: * - adc_data[0] (MSB) * - adc_data[1] (LSB) * Next, we verify if the the I2C bus is busy using the function in the driverlib: DL_I2C_get ControllerStatus * - Prevents collisions by ensuring no two I2C device is using the same bus before initializing. * BEGIN I2C READ operation to request 2 bytes from the ADC-> DL_I2C_startControllerTransfer() * Parameters: - I2C_controller_INST: Refererence to the I2C instance bring used. - DEF_TARGET_ADDR_ADC: Address of the ADC - DL_I2C_CONTROLLER_DIRECTION_RX: Indicates that we want to receive the data from ADC - 2: Specifies that we expect 2 bytes from the ADC * WAIT for the data to be received: (DL_I2C_getControllerStatus()) - Waits until the ADC sends the data over I2C. - Ensures that the data transfer is complete before proceeding. * READ the two bytes of ADc Data: - adc_data[0] : MSB - adc_data[1] : LSB - adc_data[2] : config_bytes ADC sends its 16-bit value in two parts over 8-bit I2C data frames. * COMBINE the two bytes into 16-bit Integer: - Shifts the MSB(first byte) left by 8 bits to make space for LSB. - Performs a bitwise OR operation to combine MSB and LSB into a single 16-bit number. * PRINT HEXADEC and DECIMAL format for DEBUGGING. * Output code is in binary and is proportional to the Input Voltage and PGA settings. * From the datasheet: https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/22226a.pdf - Equation 4.4 is being used to convert the output codes to input voltage * The ACK bits after conversion is issued by the Master, when the device receives a READ command, it outputs two data bytes followed by a configuration register in 16 bit conversion mode the MSB(=sign bit) of the first data type is D15. */ int16_t ADC_ReadData(uint8_t slot_id, ADC_PARAMS params) { // Buffer for ADC data (MSB, LSB, Config Byte) uint8_t adc_data[3] = {0}; int16_t raw_adc = 0; uint8_t bytes_to_read = 3; uint8_t adc_address = ADC_TARGET_BASE_ADDRESS + slot_id; // printf("Reading ADC Data from MCP3428 for channel: %u\n", params.channel); if (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS) { printf("Error: I2C bus stuck! Resetting..\n"); // DL_I2C_resetControllerTransfer(I2C_controller_INST); } gRxADClen = bytes_to_read; gRxADCcount = 0; gRxComplete = false; DL_I2C_startControllerTransfer(I2C_controller_INST, adc_address, DL_I2C_CONTROLLER_DIRECTION_RX, gRxADClen); DL_I2C_enableInterrupt(I2C_controller_INST, DL_I2C_INTERRUPT_CONTROLLER_RXFIFO_TRIGGER); while (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS) ; while (!gRxComplete) ; uint8_t msb = gRxPacket[0]; uint8_t lsb = gRxPacket[1]; uint8_t config_adc_byte = gRxPacket[2]; uint8_t gain_setting = (config_adc_byte & 0x03); uint8_t gain_multiplier = (1 << gain_setting); // Gain values: 1, 2, 4, 8 if (params.resolution == 16) { raw_adc = (msb << 8) | lsb; if (raw_adc > 32767) raw_adc -= 65536; } else if (params.resolution == 14) { raw_adc = ((msb & 0b00111111) << 8) | lsb; if (raw_adc > 8191) raw_adc -= 16384; } else if (params.resolution == 12) { raw_adc = ((msb & 0b00001111) << 8) | lsb; if (raw_adc > 2047) raw_adc -= 4096; } printf("Raw ADC Value: 0x%0X (%d)\n", raw_adc, raw_adc); return raw_adc; } // **Interrupt handler for ADC Read Completion** void I2C_ADC_IRQHandler(void) { if (DL_I2C_getPendingInterrupt(I2C_controller_INST) == DL_I2C_IIDX_CONTROLLER_RXFIFO_TRIGGER) { gRxComplete = true; } } /* Function to Convert ADC Reading to Voltage */ uint16_t ADC_ConvertToVoltage(int16_t adc_value, ADC_PARAMS params) { uint16_t measured_voltage = 0; uint16_t LSB = 0; uint32_t max_adc_value = 1; switch (params.resolution) { case 12: // 12-bit max_adc_value = 4095; break; case 14: // 14-bit max_adc_value = 16383; break; case 16: // 16-bit max_adc_value = 65535; break; default: printf("Error: Unknown ADC Resolution!\n"); return 0; } measured_voltage = (((uint32_t)adc_value * ADC_VREF_MV) * 3) / max_adc_value; return (uint16_t)measured_voltage; } /* Function to Convert ADC Reading to Voltage */ uint16_t ADC_ConvertToCurrent(int16_t adc_value, ADC_PARAMS params) { int16_t current_mA = 0; // uint8_t r_shunt= 0.1; // Convert ADC value to voltage across shunt resistor: uint16_t voltage_mV = ADC_ConvertToVoltage(adc_value, params); uint8_t gain_multiplier = (1 << (params.gain - 1)); // Convert voltage drop across shunt resistor to current current_mA = (voltage_mV) / (0.1 * gain_multiplier); // printf("Converted Current: %d mA.\n", current_mA); return (int16_t)current_mA; } void Battery_UpdateADCReading(uint8_t slot, uint8_t channel) { while (adc_state != ADC_STATE_DONE) { switch (adc_state) { case ADC_STATE_CONFIGURE: adc_params.channel = channel; // printf("Channel: %d\n", adc_params.channel); adc_params.resolution = 12; adc_params.continuous = 1; adc_params.gain = 1; ADC_SetConfigurationBytes(adc_params); adc_state = ADC_STATE_WAIT; break; case ADC_STATE_WAIT: if (ADC_CheckReadyBit(slot, adc_params)) { adc_state = ADC_STATE_READ; } break; case ADC_STATE_READ: if (channel == 0) { int16_t raw_adc_voltage = ADC_ReadData(slot, adc_params); batteries[slot].voltage = ADC_ConvertToVoltage(raw_adc_voltage, adc_params); printf("Battery voltage for slot %d is %u mV.\n", slot, batteries[slot].voltage); adc_state = ADC_STATE_DONE; } else if (channel == 1) { int16_t raw_adc_current = ADC_ReadData(slot, adc_params); batteries[slot].current = ADC_ConvertToCurrent(raw_adc_current, adc_params); printf("Battery current for slot %d is %u mA.\n", slot, batteries[slot].current); adc_state = ADC_STATE_DONE; } break; default: channel = 0; adc_state = ADC_STATE_CONFIGURE; break; } } adc_state = ADC_STATE_CONFIGURE; } void Battery_ReadState(uint8_t slot_id) { uint16_t voltage_mv = batteries[slot_id].voltage; uint16_t min_voltage = batteries[slot_id].min_voltage; uint16_t max_voltage = batteries[slot_id].max_voltage; /* Changing the battery states based on adc values*/ if (voltage_mv < 500) { batteries[slot_id].state = STATE_EMPTY; } else if (voltage_mv >= 500 && voltage_mv < 3000) { //TODO: change the voltage_mv to min_voltage batteries[slot_id].state = STATE_BATTERY_DETECTED; } else if (voltage_mv >= min_voltage && voltage_mv < max_voltage) { batteries[slot_id].state = STATE_MEASUREMENT_IN_PROGRESS; } // once MCU is done reading ADC: else if (adc_state== ADC_STATE_DONE && batteries[slot_id].state== STATE_MEASUREMENT_IN_PROGRESS) { batteries[slot_id].state = STATE_MEASUREMENT_DONE; } else { batteries[slot_id].state = STATE_OVERCHARGING; } }