ESR-2025/src/i2c.c
2025-07-03 08:37:46 +02:00

177 lines
4.7 KiB
C

/* 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 <stdint.h>
#include <stdbool.h>
/** 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;
}
}