/* 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. - */ #include "ti/devices/msp/peripherals/hw_dac12.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 I2C_Instance gI2C; /*MULTIPLEXER SECTION:*/ /*Multiplexer Address*/ #define MULTIPLEXER_I2C_ADDR (0x70) /*Multiplexer channel*/ #define ADC_I2C_CHANNEL (0x01) /*****************DAC*********************/ /*Fast write command for DAC*/ #define FAST_WRITE_COMMAND 0x00 #define MULTI_WRITE 0x40 #define SINGLE_WRITE 0x58 #define SEQ_WRITE 0x50 #define VREF_CMD 0x80 #define GAIN_CMD 0xC0 #define PWR_DWN_CMD 0xA0 //DAC Channel Selection typedef enum{ DAC_A = 0, DAC_B, DAC_C, DAC_D } DAC_CH; typedef enum{ VREF_VDD= 0, VREF_INTERNAL= 1 }VREF_SELECTION; typedef enum{ GAIN_X1= 0, GAIN_X2= 1 }GAIN_SELECTION; typedef enum{ PD_NORMAL=0, PD_GND_1KOHM= 1, PD_GND_100KOHM= 2, PD_GND_500KOHM= 3 }PWR_DOWN_SELECTION; // Structure to Hold DAC Register Data typedef struct { VREF_SELECTION vref; PWR_DOWN_SELECTION pd; GAIN_SELECTION gain; uint16_t data; } DACRegisterData; // Create Global Storage for DAC Values DACRegisterData read_reg_[4]; // Holds Active Register Values DACRegisterData read_eep_[4]; // Holds EEPROM Stored Values /***********************/ /* ADC SETTINGS: Fixed Reference Voltage is 2.048V and PGA Gain setting default: x1 Mode: can be 1: Continuous or 0: for One-Shot Conversion ADC_RESOLUTION: 16 bits -> A higher resolution means better precision but slower conversion time. Calculation for V(IN)= (ADC Value/2^ (Resolution-1))* V(REF) */ /*Voltage is calculated in ADC in milliVolts: - Reference voltage in milliVolts to be read from Pi - Register address where ADC result would be stored */ #define VREF 2.048 // address register for ADC Voltage Read: #define ADC_REGISTER 0x10 /*Register for the MCU*/ #define REGISTER_SIZE 256 #define I2C_BUFFER_SIZE (16) //#define DAC12_REF_VOLTAGE_mV (2500) uint8_t registers[REGISTER_SIZE]= {1, 2, 3, 4, 5}; /* Buffers for data exchange with Pi*/ uint16_t gRxBuffer[I2C_BUFFER_SIZE]= {0}; uint16_t gTxBuffer[I2C_BUFFER_SIZE]={0}; /*Counter for data length and bytes*/ uint32_t gTxLen, gTxCount; uint32_t gRxLen, gRxCount; volatile I2C_CommandInfo gCommand; I2C_ResponseInfo gResponse; volatile bool gSendCommand = true; volatile bool newMeasurementAvailable = 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"); } /* Multiplexer TCA9548A: Address:0x70 The TCA9548A is example of a single-register device, which is controlled via I2C commands. Since it has 1 bit to enable or disable a channel, there is only 1 register needed, and the controller merely writes the register data after the target address, skipping the register number. ADC connected to channel 0 Both SDA and SL lines of the multiplexer is connected to VIN through pull-up resistors The following is the general procedure for a controller to access a target device: 1. If a controller wants to send data to a target: • Controller-transmitter sends a START condition and addresses the target-receiver. • Controller-transmitter sends data to target-receiver. • Controller-transmitter terminates the transfer with a STOP condition. 2. If a controller wants to receive or read data from a target: • Controller-receiver sends a START condition and addresses the target-transmitter. • Controller-receiver sends the requested register to read to target-transmitter. • Controller-receiver receives data from the target-transmitter. • Controller-receiver terminates the transfer with a STOP condition. The TCA9548A is example of a single-register device, which is controlled via I2C commands. Since it has 1 bit to enable or disable a channel, there is only 1 register needed, and the controller merely writes the register data after the target address, skipping the register number. */ /*Function for Multiplexer*/ void Multiplexer_SelectChannel(uint8_t channel) { uint8_t data = channel; printf("Selecting Multiplexer Channel: 0x%02X\n", data); // Ensure bus is idle before starting communication while (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS); // SEND Command to Multiplexer DL_I2C_startControllerTransfer(I2C_controller_INST, MULTIPLEXER_I2C_ADDR, DL_I2C_CONTROLLER_DIRECTION_TX, 1); DL_I2C_fillControllerTXFIFO(I2C_controller_INST, &data, 1); // **Ensure STOP condition is sent** while (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS); // **Slightly Increase Delay for Multiplexer to Process Command** delay_cycles(30000); // Verify Multiplexer Response: uint8_t response= 0x00; DL_I2C_startControllerTransfer(I2C_controller_INST, MULTIPLEXER_I2C_ADDR, DL_I2C_CONTROLLER_DIRECTION_RX, 1); // Wait for a response from the multiplexer while (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS); response = DL_I2C_receiveControllerData(I2C_controller_INST); // **Debug: Print Expected vs. Received Response** printf("Multiplexer Response: 0x%02X (Expected: 0x%02X)\n", response, data); // **CHECK FOR ADDRESS ACKNOWLEDGMENT** /*if (!(DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_ERROR)) { printf("Multiplexer detected at 0x70.\n"); return; } else{ printf("ERROR: Multiplexer (0x70) detected.\n"); }*/ // Wait for transaction completion //while (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS); if(response != data){ printf("ERROR: Multiplexer did not set the correct channel!\n"); // **Retry Mechanism: Attempt to Set the Channel Again** delay_cycles(10000); Multiplexer_SelectChannel(channel); } else { printf("Multiplexer Active Channel: 0x%X\n", data); } } /* CONFIGURATION REGISTER: 8-bit wide configuration register to select for: input channel, conversion mode, conversion rate, and PGA gain. Device allows the user to changethe operating condition of the device: */ uint8_t Construct_Config_Byte(uint8_t channel, uint8_t resolution, bool continuous, uint8_t gain) { uint8_t config = 0; config |= ((channel - 1) << 5); // Channel Selection (Bits 6-5) if (continuous) config |= (1 << 4); // Continuous Mode (Bit 4) switch (resolution) { case 12: config |= (0b00 << 2); break; case 14: config |= (0b01 << 2); break; case 16: config |= (0b10 << 2); break; case 18: config |= (0b11 << 2); break; default: printf("ERROR: Invalid Resolution!\n"); return 0; } switch (gain) { case 1: config |= (0b00); break; case 2: config |= (0b01); break; case 4: config |= (0b10); break; case 8: config |= (0b11); 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 SetConfiguration(uint8_t channel, uint8_t resolution, bool mode, uint8_t gain) { // **Step 1: Construct Configuration Byte** uint8_t config_byte = Construct_Config_Byte(channel, resolution, mode, gain); printf("Writing Config: 0x%02X to ADC (0x%X)...\n", config_byte, DEF_TARGET_ADDR_ADC); // **Step 2: Wait for I2C Bus to be Free** while (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS); // **Step 3: Start I2C Write Transaction** DL_I2C_startControllerTransfer(I2C_controller_INST, DEF_TARGET_ADDR_ADC, DL_I2C_CONTROLLER_DIRECTION_TX, 1); // **Step 4: Load Configuration Byte into TX FIFO** DL_I2C_fillControllerTXFIFO(I2C_controller_INST, &config_byte, 1); // **Step 5: Ensure STOP Condition is Sent** while (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS); printf("Configuration Byte: 0x%02X\n", config_byte); printf("Configuration Sent Successfully!\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, DEF_TARGET_ADDR_ADC, 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 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 */ int16_t Read_ADC_Data(uint8_t resolution) { uint8_t adc_data[3] = {0}; // Buffer for ADC data (MSB, LSB, Config Byte) int16_t raw_adc = 0; printf("Reading ADC Data from MCP3428...\n"); while (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS); // Determine bytes to read printf("Resolution for reading ADC is %d\n", resolution); delay_cycles(10000); // Request 3 Bytes from ADC** DL_I2C_startControllerTransfer(I2C_controller_INST, DEF_TARGET_ADDR_ADC, DL_I2C_CONTROLLER_DIRECTION_RX, 3); // Read Data into Buffer** int i = 0; while(i<3){ while (DL_I2C_isControllerRXFIFOEmpty(I2C_controller_INST)); printf("Loop no: %d\n",i); while (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS); adc_data[i] = DL_I2C_receiveControllerData(I2C_controller_INST); printf("Received Byte[%d]: 0x%02X\n", i, adc_data[i]); i++; } newMeasurementAvailable = true; /*Combining MSB and LSB for ADC output*/ raw_adc= adc_data[0] << 8 | adc_data[1]; printf("Raw ADC Value: 0x%0X (%d)\n", raw_adc, raw_adc); //printf("Resolution: %d \n", resolution_bits ); return raw_adc; } /* Function to Convert ADC Reading to Voltage */ float Convert_ADC_To_Voltage(int16_t adc_value, uint8_t resolution, uint8_t gain) { if (!newMeasurementAvailable) { return -1; } float voltage= 0; float LSB= 0; uint32_t max_adc_value= 1; switch (resolution) { case 12: // 12-bit max_adc_value = 2048; break; case 14: // 14-bit max_adc_value = 8192; break; case 16: // 16-bit max_adc_value = 32768; break; case 18: // 18-bit max_adc_value = 131072; break; default: printf("Error: Unknown ADC Resolution!\n"); return 0.0; } /*Considering that MSB is always 0*/ printf("Max Resolution %d\n", max_adc_value); LSB= ((VREF)/max_adc_value); printf("LSB: %f\n", LSB); float inputVoltage= ((float)adc_value*(LSB/gain)); /*Formula for ADC to Voltage Conversion*/ /*voltage = ((float)adc_value / max_adc_value) * VREF; printf("Converted Voltage: %.2f V\n", voltage);*/ printf("Input Voltage Measured: %.2f V\n", inputVoltage); /*To send the voltage to Pi in milliVolts*/ uint16_t voltage_mV= inputVoltage * 1000; /*Storing ADC voltages in mVolts */ registers[ADC_REGISTER] = (voltage_mV>>8) & 0xFF; //Highest Byte registers[ADC_REGISTER + 1] = (voltage_mV) & 0xFF; //Lowest Byte newMeasurementAvailable = false; printf("ADC Voltage Stored %d mV in MCU: MSB= 0x%02X, LSB= 0x%02X\n", voltage_mV, registers[ADC_REGISTER], registers[ADC_REGISTER + 1]); return voltage; } /* DAC Configuration function: Tx to DAC channel DAC expects 3 bytes from the channel: Byte 0: write command + channel_selection Byte 1: configuration + high byte of DAC Byte 2: low byte of DAC */ /*bool SET_CHANNEL_VALUE(DAC_CH ch, uint16_t new_value, VREF_SELECTION new_vref, GAIN_SELECTION new_gain, PWR_DOWN_SELECTION new_pwr_dwn, bool udac){ uint8_t output_buffer[8]; //Build the setter header (Address) -> 01000 DAC0 DAC1 DAC2 uint8_t channel_write_cmd = FAST_WRITE_COMMAND; //0x00; FAST write mode //channel_write_cmd |= (ch << 1); //SET DAC channel //channel_write_cmd|= udac; // if udac is SET to 0; VOUT is updated after the 4th byte's ACK is issued output_buffer[0]= channel_write_cmd; // Construct the 12bit DAC value with the configuration settings // For Fast write command it does not gives VREF: // PD1 PD2 Gx D11 D10 D9 D8 [Byte 1] // D7 D6 D5 D4 D3 D2 D1 D0 [Byte 2] //new_value &= 0x0FFF; //Ensure only 12 bits are set //new_value |= (new_vref << 15); new_value |= (new_pwr_dwn << 13); //new_value |= (new_gain << 12); new_value |= new_value<< 9; new_value |= new_value<< 8; output_buffer[1]= new_value >> 8; //Upper Byte output_buffer[2]= new_value & 0xFF; //Lower Byte //Send the data via I2C: if (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS){ printf("I2C Bus is busy! Retry..\n"); return false; } //start I2C transmission: DL_I2C_startControllerTransfer(I2C_controller_INST, DEF_TARGET_ADDR_DAC, DL_I2C_CONTROLLER_DIRECTION_TX, 3); DL_I2C_fillControllerTXFIFO(I2C_controller_INST, output_buffer, 3); while (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS); if (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_ERROR) { printf("I2C Write Error: Failed to write to DAC!\n"); return false; // Return failure if there was an error } return true; }*/ //The device updates all DAC analog output(vout) at the same time void update_DAC_Output() { uint8_t general_call_command = 0x08; // General Call Update Command while (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS); DL_I2C_fillControllerTXFIFO(I2C_controller_INST, &general_call_command, 1); // Start I2C transaction DL_I2C_startControllerTransfer(I2C_controller_INST, 0x00, DL_I2C_CONTROLLER_DIRECTION_TX, 1); while (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS); printf("DAC Outputs Updated via General Call Software Update!\n"); } /**Function for FAST write command, sending values over I2c to every channel of DAC**/ bool fastWrite(uint16_t channel_a_value){ /*DAC has a 12 bit resolution that ranges from 0 to 4095. 0x00: sets the Power Mode to NORMAL for Channel A (channel_a_value >> 8): shifts the value to 8 places right and gives upper 4 bits */ uint8_t output_buffer[8]; output_buffer[0]= (0x00)|(channel_a_value >> 8)&0x0F; //output_buffer[0] = channel_a_value >> 8; output_buffer[1]= channel_a_value & 0xFF; output_buffer[2]= 0x40; // Power down for the other channels output_buffer[3]= 0x00; output_buffer[4]= 0x40; output_buffer[5]= 0x00; output_buffer[6]= 0x40; output_buffer[7]= 0x00; if (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_ERROR) { printf("I2C Write Error: Failed to write to DAC channels for FAST write mode!\n"); return false; // Return failure if there was an error } // **Wait for I2C Bus to be Free** while (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS); // **Start I2C Write Transaction** DL_I2C_startControllerTransfer(I2C_controller_INST, DEF_TARGET_ADDR_DAC, DL_I2C_CONTROLLER_DIRECTION_TX, 8); // **Load Configuration Byte into TX FIFO** DL_I2C_fillControllerTXFIFO(I2C_controller_INST, (uint8_t*)&output_buffer, 8); // **Ensure STOP Condition is Sent** while (DL_I2C_getControllerStatus(I2C_controller_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS); printf("DAC Fast write Successful!\n"); update_DAC_Output(); return true; } /*void readRegisters(){ uint8_t data[24]; uint8_t channel; bool isEeprom; printf("Reading MCP4728 Registers...\n"); // ** Request 24 Bytes from MCP4728 over I2C ** DL_I2C_startControllerTransfer(I2C_controller_INST, DEF_TARGET_ADDR_DAC, DL_I2C_CONTROLLER_DIRECTION_RX, 24); for (int i = 0; i < 24; i++) { while (DL_I2C_isControllerRXFIFOEmpty(I2C_controller_INST)); data[i] = DL_I2C_receiveControllerData(I2C_controller_INST); } // ** Process the Received Data (8 Iterations for 4 Channels) ** for (uint8_t i = 0; i < 8; i++) { isEeprom = i % 2; // Even indices: Register, Odd indices: EEPROM //Extract channel: uint8_t ch= (data[0] & 0x30)>>4; if (isEeprom) { read_eep_[ch].vref = (VREF_SELECTION) ((data[i * 3 + 1] & 0b10000000) >> 7); read_eep_[ch].pd = (PWR_DOWN_SELECTION) ((data[i * 3 + 1] & 0b01100000) >> 5); read_eep_[ch].gain = (GAIN_SELECTION) ((data[i * 3 + 1] & 0b00010000) >> 4); read_eep_[ch].data = (uint16_t) ((data[i * 3 + 1] & 0b00001111) << 8 | data[i * 3 + 2]); } else { read_reg_[ch].vref = (VREF_SELECTION) ((data[i * 3 + 1] & 0b10000000) >> 7); read_reg_[ch].pd = (PWR_DOWN_SELECTION) ((data[i * 3 + 1] & 0b01100000) >> 5); read_reg_[ch].gain = (GAIN_SELECTION) ((data[i * 3 + 1] & 0b00010000) >> 4); read_reg_[ch].data = (uint16_t) ((data[i * 3 + 1] & 0b00001111) << 8 | data[i * 3 + 2]); } } printf("MCP4728 Registers Read Successfully!\n"); } */ /* DAC Function: -12 bit Voltage Output DAC with Four Buffered outputs. -Using Internal VREF (2.048V): - Output voltage range: - 0.000V to 2.048V with Gain setting= 1 - 0.000V to 4.096V with Gain setting= 2 - User can select internal reference or external voltage (VDD) for each channel - VOUTA: Buffered analog voltage of Channel A. - Channel Selection bits: 0x00 for Channel A */ /*int16_t read_DAC_Output() { readRegisters(); //Read current register DAC values uint16_t dac_a = read_reg_[DAC_A].data; uint16_t dac_b = read_reg_[DAC_B].data; uint16_t dac_c = read_reg_[DAC_C].data; uint16_t dac_d = read_reg_[DAC_D].data; printf("DAC Output Digital Values: A=%d, B=%d, C=%d, D=%d\n", dac_a, dac_b, dac_c, dac_d); return dac_a; } float get_DAC_Output_Voltage(DAC_CH channel){ if(channel< DAC_A || channel> DAC_D){ printf("Error: Invalid DAC channel!\n"); return -1.0; } uint16_t dac_value = read_reg_[channel].data; VREF_SELECTION vref = read_reg_[channel].vref; GAIN_SELECTION gain = read_reg_[channel].gain; //GET correct VREF value: float vref_value= (vref==VREF_INTERNAL)? 2.048: 3.3; uint8_t gain_factor= (gain==GAIN_X2)? 2: 1; // Calculate Output Voltage float vout = ((float)dac_value / 4095.0) * vref_value * gain_factor; printf("DAC Channel %d - Digital Value: %d, VOUT: %.3fV\n", channel, dac_value, vout); return vout; }*/ /**Function to call DAC*/ /*void get_Analog_Output(){ bool success= fastWrite(2048, 0, 0, 0); if(!success){ printf("Error: DAC Fast Write Failed.\n"); return; } update_DAC_Output(); read_DAC_Output(); float vout_a= get_DAC_Output_Voltage(DAC_A); float vout_b= get_DAC_Output_Voltage(DAC_B); float vout_c= get_DAC_Output_Voltage(DAC_C); float vout_d= get_DAC_Output_Voltage(DAC_D); printf("\nMeasured DAC output Voltages:\n"); printf("Channel A: %.2f V\n", vout_a); printf("Channel B: %.2f V\n", vout_b); printf("Channel C: %.2f V\n", vout_c); printf("Channel D: %.2f V\n", vout_d); } */ /* Function to initialize MCU as target: Pi communicates as the Controller and MCU as the Target */ void I2C_Target_Init(void) { // Set the target address DL_I2C_setTargetOwnAddress(I2C_target_INST, I2C_target_TARGET_OWN_ADDR); // Enable target mode DL_I2C_enableTarget(I2C_target_INST); /* Enable relevant I2C interrupts */ DL_I2C_enableInterrupt(I2C_target_INST, DL_I2C_INTERRUPT_TARGET_START); DL_I2C_enableInterrupt(I2C_target_INST, DL_I2C_INTERRUPT_TARGET_RXFIFO_TRIGGER); DL_I2C_enableInterrupt(I2C_target_INST, DL_I2C_INTERRUPT_TARGET_TXFIFO_TRIGGER); DL_I2C_enableInterrupt(I2C_target_INST, DL_I2C_INTERRUPT_TARGET_STOP); } /* MAIN function: */ int main(void) { // **Step 1: Initialize System and I2C** SYSCFG_DL_init(); printf("............System Configuration Enabled...............\n"); // **Step 2: Select ADC Channel via Multiplexer** printf("Selecting Multiplexer Channel...\n"); Multiplexer_SelectChannel(ADC_I2C_CHANNEL); //I2C_scanBus(); // **Step 3: Enable I2C Interrupts** //NVIC_EnableIRQ(I2C_controller_INST_INT_IRQN); //NVIC_EnableIRQ(I2C_target_INST_INT_IRQN); I2C_init(&gI2C); gTxCount = 0; gTxLen = REGISTER_SIZE; gRxCount = 0; gRxLen = REGISTER_SIZE; // **Step 4: Configure ADC (Example: CH1, 16-bit, Continuous Mode, Gain x1)** uint8_t channel= 4; uint8_t resolution= 16; bool continuous= 1; uint8_t gain= 1; printf("Configuring ADC...\n"); //**Set Configuration Register for ADC** //SetConfiguration(channel, resolution, continuous, gain); // CH1, 16-bit, Continuous mode, Gain x1 while (1) { // Read ADC Value and DAC values: //int16_t adc_value = Read_ADC_Data(resolution); //Convert_ADC_To_Voltage(adc_value, resolution, gain); //delay_cycles(10000); fastWrite(3300); //Add Delay for Next Conversion** delay_cycles(100000); } } /*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)) { case DL_I2C_IIDX_CONTROLLER_RX_DONE: gI2C.rxMsg.len = gI2C.rxMsg.ptr; I2C_decodeResponse(&gI2C,&gResponse); gI2C.status = I2C_STATUS_RX_COMPLETE; printf("I2C RX COMPLETE!\n"); break; case DL_I2C_IIDX_CONTROLLER_TX_DONE: DL_I2C_disableInterrupt( I2C_controller_INST, DL_I2C_INTERRUPT_CONTROLLER_TXFIFO_TRIGGER); gI2C.status = I2C_STATUS_TX_COMPLETE; printf("I2C TX COMPLETE!\n"); 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 (gI2C.rxMsg.ptr < MAX_BUFFER_SIZE) { gI2C.rxMsg.buffer[gI2C.rxMsg.ptr++] = DL_I2C_receiveControllerData(I2C_controller_INST); if(gI2C.rxMsg.ptr>0){ //printf("Received Byte[%d]: 0x%02X\n", gI2C.rxMsg.ptr, gI2C.rxMsg.buffer[gI2C.rxMsg.ptr - 1]); } } 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); } } break; case DL_I2C_IIDX_CONTROLLER_TXFIFO_TRIGGER: printf("TX FIFO with data!\n"); gI2C.status = I2C_STATUS_TX_INPROGRESS; /* Fill TX FIFO with bytes to send */ if (gI2C.txMsg.ptr < gI2C.txMsg.len) { gI2C.txMsg.ptr += DL_I2C_fillControllerTXFIFO( I2C_controller_INST, &gI2C.txMsg.buffer[gI2C.txMsg.ptr], gI2C.txMsg.len - gI2C.txMsg.ptr); } break; case DL_I2C_IIDX_CONTROLLER_ARBITRATION_LOST: printf("Interrupt index for I2C controller Arbitration Lost!\n"); break; case DL_I2C_IIDX_CONTROLLER_NACK: printf("I2C NACK Received\n"); break; default: break; } } void I2C_target_INST_IRQHandler(void) { static bool DataRx= false; static uint16_t registerAddress=0; //printf("I2C Interrupt Triggered to MCU (TARGET)!\n"); uint32_t status = DL_I2C_getPendingInterrupt(I2C_target_INST); switch (status) { /* START condition detected */ case DL_I2C_IIDX_TARGET_START: //printf("START condition detected.\n"); gTxCount= 0; gRxCount= 0; DataRx= false; DL_I2C_flushTargetTXFIFO(I2C_target_INST); break; /* STOP condition detected */ case DL_I2C_IIDX_TARGET_STOP: //printf("STOP condition detected.\n"); if (DataRx == true){ //printf("Data received from Pi: "); for (uint8_t i = 0; i < gRxCount; i++) { //printf("0x%02X ", gRxBuffer[i]); } //printf("\n"); DataRx= false; } DL_I2C_flushTargetTXFIFO(I2C_target_INST); break; /* RX FIFO trigger (Pi is writing data to MCU) */ case DL_I2C_IIDX_TARGET_RXFIFO_TRIGGER: //printf("receiving data from Pi.\n"); DataRx= true; while (!DL_I2C_isTargetRXFIFOEmpty(I2C_target_INST)) { if (gRxCount == 0) { // First byte received is the register address registerAddress = DL_I2C_receiveTargetData(I2C_target_INST); DL_I2C_flushTargetTXFIFO(I2C_target_INST); //printf("Register Address Set: 0x%02X\n", registerAddress); } else if (registerAddress < REGISTER_SIZE) { //Storing the received data from the controller correctly registers[registerAddress] = DL_I2C_receiveTargetData(I2C_target_INST); printf("Stored 0x%02X in Register 0x%02X\n", registers[registerAddress], registerAddress); //gRxBuffer[gRxCount++] = DL_I2C_receiveTargetData(I2C_0_INST); } else { printf("ERROR: RX Buffer Overflow!\n"); break; } gRxCount++; } break; /* TX FIFO trigger (Pi is reading data from MCU) */ case DL_I2C_IIDX_TARGET_TXFIFO_TRIGGER: //printf("transmitting data to Pi...\n"); if(!DL_I2C_isTargetTXFIFOFull(I2C_target_INST)) { if (registerAddress < REGISTER_SIZE) { //DL_I2C_fillTargetTXFIFO(I2C_0_INST, &gTxBuffer[gTxCount], 1); // Retrieve stored value from the correct register uint8_t value = registers[registerAddress]; //uint8_t value_test[2] = { 0x01, 0x03 }; DL_I2C_fillTargetTXFIFO(I2C_target_INST, &value, 1); //printf("Sending to 0x%02X: 0x%02X\n", registerAddress, value); // Debugging } else { // **Fix: Avoid infinite while loop** printf("WARNING: TX Buffer Underflow! Sending default value.\n"); DL_I2C_fillTargetTXFIFO(I2C_target_INST, (uint8_t[]){0x00}, 1); break; } } break; /* Arbitration lost or NACK */ case DL_I2C_IIDX_TARGET_ARBITRATION_LOST: default: printf("Unknown Interrupt.\n"); break; } }