/* File: i2c.c */ /** * @file i2c.c * @brief I²C master routines and missing standard C90 constants for MSP430FR2355. * * Implements byte-wise I²C transmit and single-register read with automatic * STOP handling and interrupt-driven operation. * * @author (based on work by) Luis Wehrberger * @date 2025-07-02 */ #include "i2c.h" #include "msp430fr2355.h" #include #include /** Pointer to the current transmit buffer. */ static char *i2c_tx_buffer; /** Number of bytes left to send (buffer length). */ static unsigned int i2c_tx_length; /** Index of the next byte to transmit. */ static unsigned int i2c_tx_index; /** Last byte received by the ISR. */ static char i2c_rx_byte; /** Flag set when I²C transfer completes (STOP or NACK). */ static volatile bool i2c_transfer_complete = false; /** * @brief Initialize USCI_B0 module for I²C master mode at 50 kHz. * * Configures SMCLK source, sets clock divider, 7-bit addressing, * automatic STOP generation, port mapping for SDA/SCL, and enables * relevant interrupts. */ void i2c_init(void) { // Put eUSCI_B0 into reset state for configuration UCB0CTLW0 |= UCSWRST; // Select SMCLK and divide by 20 → 50 kHz SCL UCB0CTLW0 |= UCSSEL_3; UCB0BRW = 50; // Configure as I²C master with 7-bit addressing UCB0CTLW0 |= UCMODE_3 | UCMST; // Enable automatic STOP when byte counter (UCB0TBCNT) reaches zero UCB0CTLW1 |= UCASTP_2; // Assign P1.2 = SDA, P1.3 = SCL via port mapping P1SEL1 &= ~(BIT2 | BIT3); P1SEL0 |= (BIT2 | BIT3); // Release eUSCI_B0 for operation UCB0CTLW0 &= ~UCSWRST; // Enable RX, TX, STOP, and NACK interrupts UCB0IE |= UCRXIE0 | UCTXIE0 | UCSTPIE | UCNACKIE; __enable_interrupt(); } /** * @brief Transmit a sequence of bytes to a given I²C slave. * * @param slaveAddress 7-bit I²C address of the slave device. * @param data Pointer to data buffer to send. * @param length Number of bytes to transmit. */ void i2c_write(uint8_t slaveAddress, char data[], uint8_t length) { // Set target slave address UCB0I2CSA = slaveAddress; // Initialize transmit buffer and counters i2c_tx_buffer = data; i2c_tx_length = length; i2c_tx_index = 0; // Configure for master-transmit mode and set byte counter UCB0CTLW0 |= UCTR; UCB0TBCNT = length; // Generate START and wait for STOP flag in ISR UCB0CTLW0 |= UCTXSTT; i2c_transfer_complete = false; while (!i2c_transfer_complete) { LPM3; // Enter low-power mode, will wake on STOP or NACK } } /** * @brief Read a single register byte from an I²C slave. * * Performs a write of the register address, followed by a repeated START * to read one byte, with automatic STOP and interrupt-based wake-up. * * @param slaveAddress 7-bit I²C address of the slave device. * @param registerAddress Register address to read. * @return Byte read from the register. */ char i2c_read_reg(uint8_t slaveAddress, uint8_t registerAddress) { // Send register address first char addr_buf[1] = { (char)registerAddress }; i2c_write(slaveAddress, addr_buf, 1); // Switch to master-receive mode and request one byte UCB0CTLW0 &= ~UCTR; UCB0TBCNT = 1; // Generate repeated START condition UCB0CTLW0 |= UCTXSTT; i2c_transfer_complete = false; while (!i2c_transfer_complete) { LPM3; // Wait for ISR to clear STOP flag } return i2c_rx_byte; } /** * @brief USCI_B0 I²C interrupt handler. * * Handles NACK, STOP, RX, and TX events: * - NACK: terminate transfer and wake CPU * - STOP: terminate transfer and wake CPU * - RXBUF: store received byte * - TXBUF: send next byte or reset index */ #pragma vector = EUSCI_B0_VECTOR __interrupt void EUSCI_B0_I2C_ISR(void) { switch (__even_in_range(UCB0IV, USCI_I2C_UCBIT9IFG)) { case USCI_I2C_UCNACKIFG: // NACK received: signal completion and wake CPU i2c_transfer_complete = true; __bic_SR_register_on_exit(LPM3_bits); break; case USCI_I2C_UCSTPIFG: // STOP condition detected: signal completion and wake CPU i2c_transfer_complete = true; __bic_SR_register_on_exit(LPM3_bits); break; case USCI_I2C_UCRXIFG0: // Read received byte into buffer i2c_rx_byte = UCB0RXBUF; break; case USCI_I2C_UCTXIFG0: // Transmit next byte or reset index if done UCB0TXBUF = i2c_tx_buffer[i2c_tx_index++]; if (i2c_tx_index >= i2c_tx_length) { // Reset index for subsequent transactions i2c_tx_index = 0; } break; default: // Unhandled interrupts: no action break; } }