#include #include #include #include "battery.h" #include "ti/driverlib/dl_i2c.h" #include "ti/driverlib/m0p/dl_core.h" #include "ti_msp_dl_config.h" 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; uint8_t ADC_ConstructConfigBytes(ADC_PARAMS params) { uint8_t config = 0; config |= ((params.channel - 1) << 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_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); } // **Interrupt handler for configuration completion** void I2C_ConfigInterruptHandler(void) { if (DL_I2C_getPendingInterrupt(I2C_controller_INST) == DL_I2C_IIDX_CONTROLLER_TXFIFO_TRIGGER) { gTxComplete = true; } } /* 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){ uint8_t config_byte = ADC_ConstructConfigBytes(params); DL_I2C_startControllerTransfer(I2C_controller_INST, ADC_TARGET_BASE_ADDRESS+slot_id, DL_I2C_CONTROLLER_DIRECTION_RX, 1); config_byte= DL_I2C_receiveControllerData(I2C_controller_INST); bool ready= (config_byte & 0x80)==0; //Ready bit is bit 7 printf("Slot: %d | Config Byte: 0x%02X | READY Bit: %d\n", slot_id, config_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) { //Request ADC Conversion //ADC_SetConfigurationBytes(params); //Wait until data is ready while(!ADC_CheckReadyBit(slot_id, params)){ delay_cycles(50000); } // 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("Gain %d: \n", gain_multiplier); 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_UpdateVoltage(ADC_PARAMS params){ for(uint8_t slot=0; slot< NUM_SLOTS; slot++ ){ //CH1: Voltage Setup params.channel= 1; int16_t raw_adc_voltage= ADC_ReadData(slot, params); batteries[slot].voltage= ADC_ConvertToVoltage(raw_adc_voltage, params); printf("Battery Voltage for slot %d is %u mV.\n", slot, batteries[slot].voltage); } } void Battery_UpdateCurrent(ADC_PARAMS params){ for(uint8_t slot=0; slot< NUM_SLOTS; slot++ ){ //CH2: Charge Current params.channel= 2; int16_t raw_adc_current= ADC_ReadData(slot, params); batteries[slot].current= ADC_ConvertToCurrent(raw_adc_current, params); printf("Battery current for slot %d is %u mA.\n", slot, batteries[slot].current); } } void Battery_UpdateADCReading(){ ADC_PARAMS adc_params; // For Channel 1: Voltage adc_params.channel= 1; adc_params.resolution= 12; adc_params.continuous= 0; adc_params.gain= 1; ADC_SetConfigurationBytes(adc_params); delay_cycles(50000); for(uint8_t slot=0; slot< NUM_SLOTS; slot++ ){ 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); } //For Channel 2: Current adc_params.channel= 2; ADC_SetConfigurationBytes(adc_params); delay_cycles(50000); for(uint8_t slot=0; slot< NUM_SLOTS; slot++ ){ 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); } }