/* 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" I2C_Instance gI2C; I2C_ResponseInfo gResponse; BatteryData battery_data; /*****************/ /*Register for the MCU*/ #define REGISTER_SIZE 256 uint8_t registers[REGISTER_SIZE]= {1, 2, 3, 4, 5}; /*Counters for TX length and bytes sent*/ uint32_t gTxLen, gTxCount; /* Counters for RX length and bytes sent*/ uint32_t gRxLen, gRxCount; /* Boolean to know when a stop command was issued */ bool gStopReceived = false; uint8_t gTxData[MAX_DATA_SIZE] = {0} ; /* 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"); } /*Function to get the RDY bit from ADC*/ bool Check_ADC_Ready() { uint8_t config_byte = 0; /* 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 */ DL_I2C_startControllerTransfer(I2C_controller_INST, ADC_TARGET_BASE_ADDRESS, DL_I2C_CONTROLLER_DIRECTION_RX, 1); config_byte = DL_I2C_receiveControllerData(I2C_controller_INST); return (config_byte & 0x80) == 0; // Check if RDY bit is cleared in bit 7 } /* Function to initialize MCU as target: Pi communicates as the Controller and MCU as the Target */ /*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"); DL_I2C_enableInterrupt(I2C_controller_INST, DL_I2C_INTERRUPT_CONTROLLER_STOP); //gRxComplete = true; } 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); piTxCount += DL_I2C_fillTargetTXFIFO(I2C_target_INST, (uint8_t*)&battery_data, piTxLen); 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("RX FIFO triggered from PI.\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* ADC_PARAMS adc_params={ .channel= 1, .resolution= 12, .continuous= 1, .gain= 1 }; //printf("Configuring ADC....\n"); //**Set Configuration Register for ADC** //ADC_SetConfigurationBytes(adc_params); //uint16_t channel_a_value= 3300; // in mVolts while (1) { //Battery_UpdateCurrentVoltage(adc_params); //for(uint8_t slot_id= 0; slot_id < NUM_SLOTS; slot_id++){ // CC_CV_ControlCharging(slot_id); //} //delay_cycles(5000); //DAC_fastWrite(channel_a_value); delay_cycles(100000); } }