/* 20.02.2025: Flowchart of the code: 1. MCU requests ADC data over I2C 2. ADC is connected to the MULTIPLEXER. 3. Interrupt triggers when data is available. 4. Interrupt moves RECEIVED bytes into RXFIFO Buffer 5. Interrupt marks reception complete RX_DONE 6. Read ADC_Data and retrieve the final ADc value 7. Convert ADC value to Voltage 24.02.2025: Working Code for Reading Analog Signals from ADC to MCU through Multiplexer - configured the registers with Channel, Resolution, Mode(Continuous or Single-mode) and Gain: Construct_Config_Byte() - SET configuration registers to ADC: SetConfiguration() - Read the Analog Output: Read_ADC_Data() - Convert Analog output to voltage in Volts: Convert_ADC_To_Voltage() - Commented out interrupts for the time being. - Adding code for Pi to READ ADC voltages in mV. 27.02.2025: DAC function to write to Channel A in Fast Mode and return the Analog output */ #include "i2c_target.h" #include "ti/devices/msp/peripherals/hw_dac12.h" #include "ti/driverlib/dl_adc12.h" #include "ti/driverlib/dl_gpio.h" #include "ti/driverlib/dl_i2c.h" #include "ti/driverlib/m0p/dl_core.h" #include "ti_msp_dl_config.h" #include "ti/comm_modules/i2c/controller/i2c_comm_controller.h" #include #include #include #include "multiplexer.h" #include "adc.h" #include "dac.h" #include "battery.h" #include "i2c_target.h" #include "cc_cv_charging.h" #include I2C_Instance gI2C; I2C_ResponseInfo gResponse; BatteryData battery_data; /* Scans all the addresses of the peripherals: */ void I2C_scanBus() { printf("Scanning I2C Bus...\n"); // **Step 1: Reset I2C Controller if Busy** if (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS) { printf("I2C Bus Busy! Resetting I2C Controller...\n"); DL_I2C_disableController(I2C_controller_INST); // Disable I2C delay_cycles(20000); DL_I2C_enableController(I2C_controller_INST); // Re-enable I2C delay_cycles(20000); } // **Step 2: Scan I2C Bus** for (uint8_t addr = 0x08; addr < 0x78; addr++) { // Valid I2C Address Range DL_I2C_startControllerTransfer(I2C_controller_INST, addr, DL_I2C_CONTROLLER_DIRECTION_RX, 1); delay_cycles(5000); if (!(DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_ERROR)) { printf("Device found at: 0x%02X\n", addr); } } printf("I2C Scan Complete!\n"); } /*Interrupt for MCU -> ADC * CASE: DL_I2C_IIDX_CONTROLLER_RX_DONE: ADC Reception Complete - ADC has finished sending data and it's fully received. - gI2C.rxMsg.len = gI2C.rxMsg.ptr: - Stores the received data length in the response buffer. - I2C_decodeResponse(): - Decodes the received response. - gI2C.status = I2C_STATUS_RX_COMPLETE: - Marks reception is complete. * CASE: DL_I2C_IIDX_CONTROLLER_TX_DONE: Data Transmit to ADC complete - DL_I2C_disableInterrupt(..): Disables the TXFIFO interrupt since data is now sent * CASE: DL_I2C_IIDX_CONTROLLER_RXFIFO_TRIGGER: Receive Data in FIFO - The I2C Receive FIFO has data ready to be read. - while (DL_I2C_isControllerRXFIFOEmpty(...) != true): Loops until the RX FIFOis empty (READ all available bytes) - Inside the while loop: - If buffer has SPACE, store the received byte - Prints each received byte in HEXADECIMAL format for debugging - IF BUFFER is FULL, avoids OVERFLOW by discarding extra byte. * CASE: DL_I2C_IIDX_CONTROLLER_TXFIFO_TRIGGER: Transmit Data in FIFO - If there is still data to send: gI2C.txMsg.ptr += DL_I2C_fillControllerTXFIFO(I2C_controller_INST, &gI2C.txMsg.buffer[gI2C.txMsg.ptr], gI2C.txMsg.len - gI2C.txMsg.ptr); */ void I2C_controller_INST_IRQHandler(void) { //printf("I2C Interrupt Triggered to ADC!\n"); switch (DL_I2C_getPendingInterrupt(I2C_controller_INST)) { /*START Condition*/ case DL_I2C_IIDX_CONTROLLER_START: //gTxADCcount= 0; gRxADCcount= 0; DL_I2C_flushControllerTXFIFO(I2C_controller_INST); break; case DL_I2C_IIDX_CONTROLLER_RXFIFO_TRIGGER: gI2C.status= I2C_STATUS_RX_INPROGRESS; /* Store bytes received from target in Rx Msg Buffer */ while (DL_I2C_isControllerRXFIFOEmpty(I2C_controller_INST) != true) { if (gRxADCcount< gRxADClen) { gRxPacket[gRxADCcount] = DL_I2C_receiveControllerData(I2C_controller_INST); printf("Received Byte[%d]: 0x%02X\n", gRxADCcount, gRxPacket[gRxADCcount]); // Debug print gRxADCcount++; } else { //printf("ERROR: RX Buffer Overflow! ptr=%d MAX_BUFFER_SIZE=%d\n", gI2C.rxMsg.ptr, MAX_BUFFER_SIZE); /* Ignore and remove from FIFO if the buffer is full */ DL_I2C_receiveControllerData(I2C_controller_INST); } } if (gRxADCcount >= gRxADClen){ //printf("ADC Bytes Received!\n"); gRxComplete = true; DL_I2C_enableInterrupt(I2C_controller_INST, DL_I2C_INTERRUPT_CONTROLLER_STOP); } break; /*TRANSMIT data to ADC*/ case DL_I2C_IIDX_CONTROLLER_TXFIFO_TRIGGER: //printf("TX FIFO with data!\n"); gI2C.status= I2C_STATUS_TX_INPROGRESS; if(gTxADCcount slot_id; battery_data.voltage= battery-> voltage; battery_data.current= battery-> current; battery_data.temperature= battery-> temperature; while (DL_I2C_getTargetStatus(I2C_target_INST) & DL_I2C_TARGET_STATUS_BUS_BUSY); DL_I2C_fillTargetTXFIFO(I2C_target_INST, (uint8_t *)&battery_data, sizeof(BatteryData)); //piTxCount += DL_I2C_fillTargetTXFIFO(I2C_target_INST, (uint8_t*)&battery_data, piTxLen); piTxComplete= true; while (DL_I2C_getTargetStatus(I2C_target_INST) & DL_I2C_TARGET_STATUS_BUS_BUSY); if(piTxCount >= piTxLen){ piTxComplete= true; piTxCount=0; } } else{ printf("Invalid Slot ID: %d\n.", requestedSlot); } } } break; /* TARGET_Rx FIFO trigger (Pi is writing data to MCU) */ /*Pi SET battery data limits for each slot, where: - RXFIFO buffer is filled if the command from Pi is 0x03 - Creating a temporary buffer named ´rxbuffer´ - sizeof(BatteryLimitMsg): 11 bytes (1 byte: slot_id, 2 bytes: min_voltage; max_voltage; cut_off_current; capacitance; charge_fraction) - rx_buffer stores the data from Pi. - if all the expected bytes are received from Pi then, - memcpy() to copy the block of address from the temporary buffer to the BatteryLimitMsg structure - Why?, A: It copies the specified number of bytes from one memory location to another regardless of the type of the data stored. - verify if the received slot_id is less than NUM_SLOTS, where slot_id count starts from 0 then: - create a pointer variable for 'Battery' - battery_limits.slot_id: index of the battery slot to be updated - &batteries[battery_limits.slot_id]: gets the memory address of the battery in that slot - Accessing the structure members of Battery using -> operator. This allows efficient access to the structure's members without directly using the structure variable. */ case DL_I2C_IIDX_TARGET_RXFIFO_TRIGGER: printf("Pi SET Battery limit to MCU.....\n"); if(!DL_I2C_isTargetRXFIFOEmpty(I2C_target_INST)){ receivedCommand= DL_I2C_receiveTargetData(I2C_target_INST); printf("Received Command: 0x%02X\n", receivedCommand); if(receivedCommand == CMD_SET_BATTERY_LIMIT){ uint8_t rx_buffer[sizeof(BatteryLimitMsg)]; uint8_t index= 0; while (!DL_I2C_isTargetRXFIFOEmpty(I2C_target_INST)){ if(index < sizeof(BatteryLimitMsg)){ rx_buffer[index]= DL_I2C_receiveTargetData(I2C_target_INST); printf("Received Byte[%d]: 0x%02X\n", index, rx_buffer[index]); index++; } else{ DL_I2C_receiveTargetData(I2C_target_INST); } } printf("Total Bytes Received: %d (Expected: %d)\n", index, sizeof(BatteryLimitMsg)); if(index == sizeof(BatteryLimitMsg)){ printf("Received Battery Limits.\n"); BatteryLimitMsg battery_limits; memcpy(&battery_limits, rx_buffer, sizeof(BatteryLimitMsg)); if(battery_limits.slot_id < NUM_SLOTS){ Battery *battery = &batteries[battery_limits.slot_id]; battery -> min_voltage = battery_limits.min_voltage; battery -> max_voltage = battery_limits.max_voltage; battery -> cut_off_current = battery_limits.cut_off_current; battery -> capacitance = battery_limits.capacitance; battery -> charge_fraction = battery_limits.charge_fraction; printf("\n Received Battery Limits for slot %d: \n", battery_limits.slot_id); printf(" Min Voltage: %d mV (0x%04X)\n", battery_limits.min_voltage, battery_limits.min_voltage); printf(" Max Voltage: %d mV (0x%04X)\n", battery_limits.max_voltage, battery_limits.max_voltage); printf(" Cutoff Current: %d mA (0x%04X)\n", battery_limits.cut_off_current, battery_limits.cut_off_current); printf(" Capacitance: %d µF (0x%04X)\n", battery_limits.capacitance, battery_limits.capacitance); printf(" Charge Fraction: %d%% (0x%02X)\n", battery_limits.charge_fraction, battery_limits.charge_fraction); } } } } break; /* Arbitration lost or NACK */ case DL_I2C_IIDX_TARGET_ARBITRATION_LOST: printf("Arbitration Lost.\n"); break; default: printf("Unknown Interrupt.\n"); break; } } void Reset_I2C_Bus() { printf("I2C Bus is stuck! Resetting...\n"); // Disable I2C Controller DL_I2C_disableController(I2C_controller_INST); delay_cycles(50000); // Re-enable I2C Controller DL_I2C_enableController(I2C_controller_INST); delay_cycles(50000); // Check if bus is free now uint32_t status = DL_I2C_getControllerStatus(I2C_controller_INST); printf("I2C Bus Status After Reset: 0x%08X\n", status); } /********MAIN function*************/ int main(void) { // Initialize System and I2C SYSCFG_DL_init(); // Initialize battery array and default params Battery_Init(); //Reset_I2C_Bus(); //NVIC_EnableIRQ(I2C_target_INST_INT_IRQN); NVIC_EnableIRQ(I2C_controller_INST_INT_IRQN); printf("............System Configuration Enabled...............\n"); //Multiplexer Multiplexer_SelectChannel(I2C_CHANNEL); //I2C_scanBus(); I2C_init(&gI2C); // *Configure ADC for voltage: Channel 1 and Channel 2* ADC_PARAMS adc_voltage_params={ .channel= 1, .resolution= 12, .continuous= 1, .gain= 1 }; ADC_PARAMS adc_current_params={ .channel= 2, .resolution= 12, .continuous= 1, .gain= 1 }; uint16_t channel_a_value= 3300; // in mVolts ADC_SetConfigurationBytes(adc_voltage_params); delay_cycles(50000); ADC_SetConfigurationBytes(adc_current_params); delay_cycles(50000); //GPIO_setConfig(GPIO_Battery_Charging_PIN_PB0_PIN , GPIO_CFG_OUT_STD| GPIO_CFG_OUT_HIGH| GPIO_Battery_Charging_PIN_PB0_IOMUX); //GPIO_setConfig(GPIO_Battery_Discharging_PIN_PB8_PIN, GPIO_CFG_OUT_STD| GPIO_CFG_OUT_HIGH| GPIO_Battery_Discharging_PIN_PB8_IOMUX); while (1) { //Looping through the ADC Channels delay_cycles(50000); Battery_UpdateVoltage(adc_voltage_params); delay_cycles(100000); Battery_UpdateCurrent(adc_current_params); delay_cycles(50000); //DAC_fastWrite(channel_a_value); //delay_cycles(50000); //CC-CV Cycle for(uint8_t slot_id= 0; slot_id < NUM_SLOTS; slot_id++){ CC_CV_ControlCharging(slot_id); } } }