Computing stuff tied to the physical world

Let’s take the bus

The I2C bus (pronounced eye-squared-see) is a wonderful invention: using just three wires (2 signal and one ground), you get the ability to connect multiple chips and talk to each of them at 100 or 400 Kbps (sometimes even more). It won’t handle audio or video, but it’s plenty for simple messages, collecting data from sensors, turning on relays, fans, lights, etc.

There is a huge variety of chips with I2C support built-in: simple I/O “port expanders”, A/D and D/A converters, and all sorts of sophisticated sensor chips, able to sense humidity, temperature, pressure, gravity, acceleration, compass headings, and more.

I2C is also a simple protocol to implement in software (at least in basic form), but almost every ┬ÁC today has one or more built-in hardware I2C peripherals. Including the LPC810.

Since it takes four pins to upload code to an LPC810 and talk to it serially, and two more pins are needed for power, this leaves two pins free for… I2C for example!

Here is an FTDI interface, the LPC810 w/ LED programming setup, and a header using the JeePort conventions to connect to all the I2C JeePlugs available from JeeLabs – in this case the RTC Plug, which has a battery-powered always on real-time clock chip on board:

DSC 4905

The LPC810 acts as a really simple I2C-to-serial bridge (RTC-specific, in this case).

Since I2C is a bus, you can in fact plug in more devices in a daisy-chained fashion, and reach each of them. All with just two wires for data, and two more wires to supply power.

The LPC810 has ROM drivers for I2C built-in, which considerably simplifies the code to periodically read out the RTC and report the results on the serial link:

#include "stdio.h"
#include "serial.h"

#include "lpc_types.h"
#include "romapi_8xx.h"

uint32_t i2cBuffer [24];
I2C_HANDLE_T* ih;

extern "C" void SysTick_Handler () {                                             
    // the only effect is to generate an interrupt, no work is done here         
}

void delay (int millis) {
    while (--millis >= 0)
        __WFI(); // wait for the next SysTick interrupt
}

void i2cSetup () {
    LPC_SWM->PINENABLE0 = 3<<2;             // disable SWCLK and SWDIO
    LPC_SWM->PINASSIGN7 = 0x02FFFFFF;       // SDA on P2, pin 4
    LPC_SWM->PINASSIGN8 = 0xFFFFFF03;       // SCL on P3, pin 3
    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<5);    // enable I2C clock

    ih = LPC_I2CD_API->i2c_setup(LPC_I2C_BASE, i2cBuffer);
    LPC_I2CD_API->i2c_set_bitrate(ih, 12000000, 400000);
}

const uint8_t* i2cSendRecv (uint8_t addr, uint8_t reg, int len) {
    static uint8_t buf [10];

    I2C_PARAM_T param;
    I2C_RESULT_T result;

    buf[0] = (addr << 1) | 1;
    buf[1] = reg;

    /* Setup parameters for transfer */
    param.num_bytes_send  = 2;
    param.num_bytes_rec   = 8;
    param.buffer_ptr_send = param.buffer_ptr_rec = buf;
    param.stop_flag       = 1;

    LPC_I2CD_API->i2c_set_timeout(ih, 100000);
    LPC_I2CD_API->i2c_master_tx_rx_poll(ih, &param, &result);

    printf("20%02x/%02x/%02x %02x:%02x:%02x %02x\n",
        buf[7], buf[6], buf[5], buf[3], buf[2], buf[1], buf[4]);

    return buf;
}

int main () {
    // send UART output to GPIO 4, running at 115200 baud
    LPC_SWM->PINASSIGN0 = 0xFFFFFF04UL;
    serial.init(LPC_USART0, 115200);

    printf("\n[master]\n");
    SysTick_Config(12000000/1000); // 1000 Hz
    i2cSetup();

    while (true) {
        i2cSendRecv(0x68, 0, 1);
        delay(1000);
    }
}

Here’s some sample output (the above code can also be found on GitHub):

    [master]
    2014/12/23 12:55:57 02
    2014/12/23 12:55:58 02
    2014/12/23 12:55:59 02
    2014/12/23 12:56:00 02
    2014/12/23 12:56:01 02
    2014/12/23 12:56:02 02
    2014/12/23 12:56:03 02

There’s in fact a lot more to I2C than meets the eye, but that’s a different story…

[Back to article index]