Skip to content

Commit

Permalink
feat(drivers): I2C
Browse files Browse the repository at this point in the history
The range sensors (VL53L0X) have I2C interface, so implement an I2C
driver to talk with them. Keep it simple by making it polling-based,
since for this application there is nothing useful we can do while
waiting for the transmission anyway. Verify that the driver works by
sending simple register writes/reads to an actual VL53L0X.

Rework the pin assignment a tiny bit because I2C is only available on
pin 1.6 and 1.7. Also fix a bug with UART where uart_putchar_polling
hangs.

video: 23
  • Loading branch information
niklasab committed Jun 10, 2023
1 parent 03e915f commit 6e2d09f
Show file tree
Hide file tree
Showing 8 changed files with 325 additions and 16 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ SOURCES_WITH_HEADERS = \
src/drivers/tb6612fng.c \
src/drivers/adc.c \
src/drivers/qre1113.c \
src/drivers/i2c.c \
src/app/drive.c \
src/app/enemy.c \
src/app/line.c \
Expand Down
2 changes: 1 addition & 1 deletion src/common/assert_handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ static void assert_blink_led(void)
// Stop the motors when something unexpected happens (assert) to prevent damage
static void assert_stop_motors(void)
{
GPIO_OUTPUT_LOW(1, 6); // Left PWM (Launchpad)
GPIO_OUTPUT_LOW(2, 6); // Left PWM (Launchpad)
GPIO_OUTPUT_LOW(2, 1); // Left CC1 (Launchpad)
GPIO_OUTPUT_LOW(2, 2); // Left CC2 (Launchpad)
GPIO_OUTPUT_LOW(2, 4); // Left CC2 (Nsumo)
Expand Down
233 changes: 233 additions & 0 deletions src/drivers/i2c.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
#include "drivers/i2c.h"
#include "drivers/io.h"
#include "common/assert_handler.h"
#include "common/defines.h"
#include <msp430.h>
#include <stdbool.h>

#define DEFAULT_SLAVE_ADDRESS (0x29)
#define RETRY_COUNT (UINT16_MAX)

static inline void i2c_set_tx_byte(uint8_t byte)
{
UCB0TXBUF = byte;
}

static i2c_result_e i2c_wait_tx_byte(void)
{
uint16_t retries = RETRY_COUNT;
while (!(IFG2 & UCB0TXIFG) && retries--) { }
if (retries == 0) {
return I2C_RESULT_ERROR_TIMEOUT;
}
return (UCB0STAT & UCNACKIFG) ? I2C_RESULT_ERROR_TX : I2C_RESULT_OK;
}

static i2c_result_e i2c_wait_rx_byte(void)
{
uint16_t retries = RETRY_COUNT;
while (!(IFG2 & UCB0RXIFG) && retries--) { }
if (retries == 0) {
return I2C_RESULT_ERROR_TIMEOUT;
}
return (UCB0STAT & UCNACKIFG) ? I2C_RESULT_ERROR_RX : I2C_RESULT_OK;
}

static inline uint8_t i2c_get_rx_byte(void)
{
return UCB0RXBUF;
}

static inline void i2c_send_start_condition(void)
{
// Send start condition (and slave addr)
UCB0CTL1 |= UCTXSTT;
}

static i2c_result_e i2c_wait_start_condition(void)
{
uint16_t retries = RETRY_COUNT;
while ((UCB0CTL1 & UCTXSTT) && --retries) { }
if (retries == 0) {
return I2C_RESULT_ERROR_TIMEOUT;
}
return (UCB0STAT & UCNACKIFG) ? I2C_RESULT_ERROR_START : I2C_RESULT_OK;
}

static i2c_result_e i2c_send_addr(const uint8_t *addr, uint8_t addr_size)
{
// Configure as sender
UCB0CTL1 |= UCTR;
i2c_send_start_condition();

// Note, when master is TX, we must write to TXBUF before waiting for UCTXSTT
i2c_set_tx_byte(addr[0]);
i2c_result_e result = i2c_wait_start_condition();
if (result) {
return result;
}
result = i2c_wait_tx_byte();
if (result) {
return result;
}

// Send from most to least significant byte
for (uint8_t i = 1; i < addr_size; i++) {
i2c_set_tx_byte(addr[i]);
result = i2c_wait_tx_byte();
if (result) {
return result;
}
}

return result;
}

static i2c_result_e i2c_start_tx_transfer(const uint8_t *addr, uint8_t addr_size)
{
return i2c_send_addr(addr, addr_size);
}

static i2c_result_e i2c_start_rx_transfer(const uint8_t *addr, uint8_t addr_size)
{
i2c_result_e result = i2c_send_addr(addr, addr_size);
if (result) {
return result;
}

// Configure receiver
UCB0CTL1 &= ~UCTR;
i2c_send_start_condition();
result = i2c_wait_start_condition();
return result;
}

i2c_result_e i2c_stop_transfer(void)
{
// Send stop condition
UCB0CTL1 |= UCTXSTP;
uint16_t retries = RETRY_COUNT;
while ((UCB0CTL1 & UCTXSTP) && --retries) { }
if (retries == 0) {
return I2C_RESULT_ERROR_TIMEOUT;
}
return (UCB0STAT & UCNACKIFG) ? I2C_RESULT_ERROR_STOP : I2C_RESULT_OK;
}

i2c_result_e i2c_write(const uint8_t *addr, uint8_t addr_size, const uint8_t *data,
uint8_t data_size)
{
ASSERT(addr);
ASSERT(addr_size > 0);
ASSERT(data);
ASSERT(data_size > 0);

i2c_result_e result = i2c_start_tx_transfer(addr, addr_size);
if (result) {
return result;
}

// Send from most to least significant byte
for (uint16_t i = 0; i < data_size; i++) {
i2c_set_tx_byte(data[i]);
result = i2c_wait_tx_byte();
if (result) {
return result;
}
}

result = i2c_stop_transfer();
return result;
}

i2c_result_e i2c_read(const uint8_t *addr, uint8_t addr_size, uint8_t *data, uint8_t data_size)
{
ASSERT(addr);
ASSERT(addr_size > 0);
ASSERT(data);
ASSERT(data_size > 0);

i2c_result_e result = i2c_start_rx_transfer(addr, addr_size);
if (result) {
return result;
}

// Read bytes from most to least significant byte
for (uint16_t i = data_size - 1; 1 <= i; i--) {
result = i2c_wait_rx_byte();
if (result) {
return result;
}
data[i] = i2c_get_rx_byte();
}

// Must stop before last byte
result = i2c_stop_transfer();
if (result) {
return result;
}
result = i2c_wait_rx_byte();
if (result) {
return result;
}
data[0] = i2c_get_rx_byte();

return I2C_RESULT_OK;
}

i2c_result_e i2c_read_addr8_data8(uint8_t addr, uint8_t *data)
{
return i2c_read(&addr, 1, data, 1);
}

i2c_result_e i2c_read_addr8_data16(uint8_t addr, uint16_t *data)
{
return i2c_read(&addr, 1, (uint8_t *)data, 2);
}

i2c_result_e i2c_read_addr8_data32(uint8_t addr, uint32_t *data)
{
return i2c_read(&addr, 1, (uint8_t *)data, 4);
}

i2c_result_e i2c_write_addr8_data8(uint8_t addr, uint8_t data)
{
return i2c_write(&addr, 1, &data, 1);
}

void i2c_set_slave_address(uint8_t addr)
{
UCB0I2CSA = addr;
}

static bool initialized = false;
void i2c_init(void)
{
ASSERT(!initialized);
static const struct io_config i2c_config = {
.select = IO_SELECT_ALT3,
.resistor = IO_RESISTOR_DISABLED,
.dir = IO_DIR_OUTPUT,
.out = IO_OUT_LOW,
};
struct io_config current_config;
io_get_current_config(IO_I2C_SCL, &current_config);
ASSERT(io_config_compare(&i2c_config, &current_config));
io_get_current_config(IO_I2C_SDA, &current_config);
ASSERT(io_config_compare(&i2c_config, &current_config));

// Must set reset while configuring
UCB0CTL1 |= UCSWRST;
// Single master, synchronous mode, I2C mode
UCB0CTL0 = UCMST + UCSYNC + UCMODE_3;
// SMCLK
UCB0CTL1 |= UCSSEL_2;
// SMCLK/160 ~= 100 kHz
UCB0BR0 = 160;
UCB0BR1 = 0;
// Clear reset
UCB0CTL1 &= ~UCSWRST;
i2c_set_slave_address(DEFAULT_SLAVE_ADDRESS);

initialized = true;
}
32 changes: 32 additions & 0 deletions src/drivers/i2c.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#ifndef I2C_H
#define I2C_H

#include <stdint.h>

// Polling-based I2C master driver

typedef enum
{
I2C_RESULT_OK,
I2C_RESULT_ERROR_START,
I2C_RESULT_ERROR_TX,
I2C_RESULT_ERROR_RX,
I2C_RESULT_ERROR_STOP,
I2C_RESULT_ERROR_TIMEOUT,
} i2c_result_e;

void i2c_init(void);
void i2c_set_slave_address(uint8_t addr);

// These functions send data in order from most to least significant byte
i2c_result_e i2c_write(const uint8_t *addr, uint8_t addr_size, const uint8_t *data,
uint8_t data_size);
i2c_result_e i2c_read(const uint8_t *addr, uint8_t addr_size, uint8_t *data, uint8_t data_size);

// Convenient wrapper functions
i2c_result_e i2c_read_addr8_data8(uint8_t addr, uint8_t *data);
i2c_result_e i2c_read_addr8_data16(uint8_t addr, uint16_t *data);
i2c_result_e i2c_read_addr8_data32(uint8_t addr, uint32_t *data);
i2c_result_e i2c_write_addr8_data8(uint8_t addr, uint8_t data);

#endif // I2C_H
20 changes: 10 additions & 10 deletions src/drivers/io.c
Original file line number Diff line number Diff line change
Expand Up @@ -123,22 +123,23 @@ static const struct io_config io_initial_configs[IO_PORT_CNT * IO_PIN_CNT_PER_PO

[IO_LINE_DETECT_FRONT_LEFT] = ADC_CONFIG,

[IO_XSHUT_FRONT] = { IO_SELECT_GPIO, IO_RESISTOR_DISABLED, IO_DIR_OUTPUT, IO_OUT_LOW },

/* I2C clock/data
* Resistor: Disabled (there are external pull-up resistors)
* Direction: Not applicable
* Output: Not applicable */
[IO_I2C_SCL] = { IO_SELECT_ALT3, IO_RESISTOR_DISABLED, IO_DIR_OUTPUT, IO_OUT_LOW },
[IO_I2C_SDA] = { IO_SELECT_ALT3, IO_RESISTOR_DISABLED, IO_DIR_OUTPUT, IO_OUT_LOW },

#if defined(LAUNCHPAD)
// Unused pins
[IO_UNUSED_2] = UNUSED_CONFIG,
[IO_UNUSED_5] = UNUSED_CONFIG,
[IO_UNUSED_3] = UNUSED_CONFIG,
[IO_UNUSED_9] = UNUSED_CONFIG,
[IO_UNUSED_10] = UNUSED_CONFIG,
[IO_UNUSED_11] = UNUSED_CONFIG,
[IO_UNUSED_12] = UNUSED_CONFIG,
[IO_UNUSED_13] = UNUSED_CONFIG,
#elif defined(NSUMO)
/* I2C clock/data
* Resistor: Disabled (there are external pull-up resistors)
* Direction: Not applicable
* Output: Not applicable */
[IO_I2C_SCL] = { IO_SELECT_ALT3, IO_RESISTOR_DISABLED, IO_DIR_OUTPUT, IO_OUT_LOW },
[IO_I2C_SDA] = { IO_SELECT_ALT3, IO_RESISTOR_DISABLED, IO_DIR_OUTPUT, IO_OUT_LOW },

// Output
[IO_MOTORS_RIGHT_CC_1] = { IO_SELECT_GPIO, IO_RESISTOR_DISABLED, IO_DIR_OUTPUT, IO_OUT_LOW },
Expand All @@ -154,7 +155,6 @@ static const struct io_config io_initial_configs[IO_PORT_CNT * IO_PIN_CNT_PER_PO
IO_OUT_HIGH },

// Outputs
[IO_XSHUT_FRONT] = { IO_SELECT_GPIO, IO_RESISTOR_DISABLED, IO_DIR_OUTPUT, IO_OUT_LOW },
[IO_XSHUT_FRONT_LEFT] = { IO_SELECT_GPIO, IO_RESISTOR_DISABLED, IO_DIR_OUTPUT, IO_OUT_LOW },
[IO_XSHUT_RIGHT] = { IO_SELECT_GPIO, IO_RESISTOR_DISABLED, IO_DIR_OUTPUT, IO_OUT_LOW },
[IO_XSHUT_LEFT] = { IO_SELECT_GPIO, IO_RESISTOR_DISABLED, IO_DIR_OUTPUT, IO_OUT_LOW },
Expand Down
8 changes: 4 additions & 4 deletions src/drivers/io.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ typedef enum
IO_LINE_DETECT_FRONT_LEFT = IO_13,
IO_UNUSED_2 = IO_14,
IO_UNUSED_3 = IO_15,
IO_PWM_MOTORS_LEFT = IO_16,
IO_UNUSED_5 = IO_17,
IO_I2C_SCL = IO_16,
IO_I2C_SDA = IO_17,
IO_IR_REMOTE = IO_20,
IO_MOTORS_LEFT_CC_1 = IO_21,
IO_MOTORS_LEFT_CC_2 = IO_22,
IO_UNUSED_9 = IO_23,
IO_UNUSED_10 = IO_24,
IO_XSHUT_FRONT = IO_24,
IO_UNUSED_11 = IO_25,
IO_UNUSED_12 = IO_26,
IO_PWM_MOTORS_LEFT = IO_26,
IO_UNUSED_13 = IO_27,
#elif defined(NSUMO) // Nsumo rev 2 (MSP430G2553IPW28)
IO_LINE_DETECT_FRONT_RIGHT = IO_10,
Expand Down
2 changes: 1 addition & 1 deletion src/drivers/uart.c
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ static void uart_putchar_polling(char c)
if (c == '\n') {
uart_putchar_polling('\r');
}
while (!(IFG2 & UCA0TXIFG)) { }
UCA0TXBUF = c;
while (!(IFG2 & UCA0TXIFG)) { }
}

void uart_trace_assert(const char *string)
Expand Down
Loading

0 comments on commit 6e2d09f

Please sign in to comment.