177 lines
4.7 KiB
C
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;
|
|
}
|
|
}
|