Skip to content

Instantly share code, notes, and snippets.

@malja
Last active September 2, 2021 12:18
Show Gist options
  • Save malja/1073591f4c84dc9b83e26f96e7881f50 to your computer and use it in GitHub Desktop.
Save malja/1073591f4c84dc9b83e26f96e7881f50 to your computer and use it in GitHub Desktop.
MSP430FR50431 I2C master with interruptions
#include <i2c_master.h>
// BIT_SET is macro for REGISTER |= (BIT)
// BIT_CLEAR is macro for REGISTER &= ~(BIT)
//
#include <macros.h>
#include <driverlib.h>
#include <config.h>
#include <string.h>
#include <clock.h>
// Current bus status
typedef enum i2c_bus_status {
// Ready for write/read operation
I2C_BUS_STATUS_IDLE,
// Working on reading/writing operation
I2C_BUS_STATUS_BUSY,
// Last operation ended with error
I2C_BUS_STATUS_ERROR,
} i2c_bus_status;
#if CONFIG_I2C_BUFFER_LENGTH >= 15
#warning "CONFIG_I2C_BUFFER_LENGTH is set to value larger than 15 bytes. Please change index and length ragne in bus structure."
#endif
// Configuration for I2C bus
typedef struct i2c_bus {
struct {
// Buffer for writing is pre-allocated
uint8_t tx_buffer[CONFIG_I2C_BUFFER_LENGTH];
// Buffer for reading is provided by the user
uint8_t *rx_buffer;
// Index in tx/rx_buffer
uint8_t index:4;
// Used length of tx/rx_buffer
uint8_t length:4;
// Should write operation end with STOP?
// Note: read operation always end with STOP
bool end_with_stop;
} data;
// Current bus status
i2c_bus_status status;
} i2c_bus;
typedef enum i2c_mode {
I2C_MODE_READ,
I2C_MODE_WRITE
} i2c_mode;
///////////////////////////////////////////////////////////////////////////////
// POMOCNÉ FUNKCE
///////////////////////////////////////////////////////////////////////////////
static i2c_bus bus;
static void set_slave_address(uint8_t address) {
// Save slave address
UCB1I2CSA = address;
// Using 7-bit address
BIT_CLEAR(UCB1CTLW0, UCSLA10);
}
static void set_mode(i2c_mode mode) {
if (mode == I2C_MODE_WRITE) {
// Write mode
BIT_SET(UCB1CTLW0, UCTR);
// Turn off RX interrupt
BIT_CLEAR(UCB1IE, UCRXIE0);
// Turn on TX interrupt
BIT_SET(UCB1IE, UCTXIE0);
} else {
// Read mode
BIT_CLEAR(UCB1CTLW0, UCTR);
// Turn off TX interrupt
BIT_CLEAR(UCB1IE, UCTXIE0);
// Turn on RX interrupt
BIT_SET(UCB1IE, UCRXIE0);
}
}
static void send_start(uint8_t address, i2c_mode mode) {
set_slave_address(address);
set_mode(mode);
bus.status = I2C_BUS_STATUS_BUSY;
// Send START
BIT_SET(UCB1CTLW0, UCTXSTT);
// If only one byte is read, STOP must be sent before
// the last byte.
// https://www.ti.com/lit/an/slaa734a/slaa734a.pdf
if (bus.data.length == 1 && mode == I2C_MODE_READ) {
BIT_SET(UCB1CTLW0, UCTXSTP);
}
}
// Wait for the last operation to finish
static error wait_to_finish() {
uint32_t ms_start = millis();
while(bus.status == I2C_BUS_STATUS_BUSY) {
if (millis() - ms_start >= 10) {
return ERROR_I2C_TIMEOUT;
}
}
return bus.status == I2C_BUS_STATUS_ERROR ? ERROR_FAIL : ERROR_NONE;
}
///////////////////////////////////////////////////////////////////////////////
// VEŘEJNÉ FUNKCE
///////////////////////////////////////////////////////////////////////////////
void i2c_master_init() {
// Reset USCI, so I can set it up
BIT_SET(UCB1CTLW0, UCSWRST);
// Nastavení na I2C
// - UCSYNC = synchronous communication, probably not needed for
// eUSCI_B, since it is always set to 1
// - UCMST = master
// - UCMODE_3 = I2C protocol
// - UCSSEL_2 = clock source is set to SMCLK
// - UCWRST = keep reset mode
UCB1CTLW0 = UCSYNC | UCMST | UCMODE_3 | UCSSEL_2 | UCSWRST;
// Set prescaler (= CONFIG_I2C_PRESCALER equals to 10, since
// SMCLK = 1MHz and I want 100 kHz)
UCB1BRW = CONFIG_I2C_PRESCALER;
// Leave reset mode, eUSCI now works
// Note: IE and IFG is cleared automatically
BIT_CLEAR(UCB1CTLW0, UCSWRST);
}
error i2c_master_write(uint8_t device_address, uint8_t *data, uint8_t data_length, bool send_stop) {
ARG_NOT_NULL(data);
if (data_length > CONFIG_I2C_BUFFER_LENGTH) {
return ERROR_NO_MEMORY;
}
// Prepare buffer
memcpy(bus.data.tx_buffer, data, data_length);
bus.data.index = 0;
bus.data.length = data_length;
bus.data.end_with_stop = send_stop;
send_start(device_address, I2C_MODE_WRITE);
return wait_to_finish();
}
error i2c_master_read(uint8_t device_address, uint8_t *data, uint8_t data_length) {
ARG_NOT_NULL(data);
if (data_length > CONFIG_I2C_BUFFER_LENGTH) {
return ERROR_NO_MEMORY;
}
// Připraví buffer
bus.data.rx_buffer = data;
bus.data.index = 0;
bus.data.length = data_length;
send_start(device_address, I2C_MODE_READ);
return wait_to_finish();
}
#pragma vector = EUSCI_B1_VECTOR
__interrupt void i2c_isr() {
switch(UCB1IV) {
// TX buffer is empty
case UCIV__UCTXIFG0:
// Still have something to send
if (bus.data.index < bus.data.length) {
// Set next byte
UCB1TXBUF = bus.data.tx_buffer[bus.data.index++];
// No data to send
} else {
// Should the STOP be sent?
if (bus.data.end_with_stop) {
BIT_SET(UCB1CTLW0, UCTXSTP);
}
// Operation ended
bus.status = I2C_BUS_STATUS_IDLE;
}
break;
// RX buffer is full
case UCIV__UCRXIFG0:
// Reading only one byte is handled by send_start function.
// Now handle the case, that the next byte would be the last.
// We need to send STOP with the (N-1)th byte
if (bus.data.length > 1 && bus.data.index + 1 == bus.data.length) {
BIT_SET(UCB1CTLW0, UCTXSTP);
bus.status = I2C_STATUS_IDLE;
}
// Read byte (if expected)
if (bus.data.index < bus.data.length) {
bus.data.rx_buffer[bus.data.index++] = UCB1RXBUF;
} else if (bus.data.index == bus.data.length) {
bus.status = I2C_BUS_STATUS_IDLE;
// STOP je odeslán nahoře :)
} else {
// Na busu se vyskytla chyba!
bus.status = I2C_BUS_STATUS_ERROR;
}
break;
// SCL je držen low příliš dlouho
case UCIV__UCCLTOIFG:
bus.status = I2C_BUS_STATUS_ERROR;
break;
}
}
#ifndef INCLUDE_I2C_MASTER_H_
#define INCLUDE_I2C_MASTER_H_
#ifdef __cplusplus
extern "C"
{
#endif
#include <error.h>
#include <stdint.h>
#include <stdbool.h>
void i2c_master_init();
error i2c_master_write(uint8_t device_address, uint8_t *data, uint8_t data_length, bool send_stop);
error i2c_master_read(uint8_t device_address, uint8_t *data, uint8_t data_length);
#ifdef __cplusplus
}
#endif
#endif /* INCLUDE_I2C_MASTER_H_ */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment