LPC812 MAX Experiment: I2C

Experiment: Work with a Serial Bus - I2C

In this experiment you will learn how to work with the Inter-Integrated Circuit Bus, or I2C bus for short. It is a multi-master bus for (relatively) low-speed peripherals. The basic clock frequency is 100 kHz but there are newer specifications that support higher speeds, for example 400 kHz that is often supported, called Fast-mode (Fm). Higher frequencies of 1 MHz (Fm+), 3.4 MHz (High-speed mode, Hs) and 5 MHz (Ultra Fast-mode, UFm) also exist but are less widespread.

The I2C bus is a synchronous bus meaning that there is an explicit clock signal. It builds on the masterslave concept where one unit is a master and controls the communication. One slave is addressed on the bus and is the other end of the master-slave communication. There can be many masters on the bus, but only one active at a time.

The I2C bus uses two bidirectional open-drain lines pulled up by resistors:

  • SCK: serial clock, the master always generates the clock
  • SDA: serial data, the master generates the data when transmitting to the slave. The slave generates the data when transmitting to the master

The picture below illustrates how many masters and slaves can share one I2C bus.

/media/uploads/embeddedartists/max_exp_i2c.png

For more information about I2C, see http://en.wikipedia.org/wiki/I%C2%B2C. There is a lot of details about the I2C bus that have not been covered in this short overview, like how addressing works, how bus arbitration works, how read and write operations work, how acknowledge of data works, etc.

Have a look in chapter 16 - LPC800 I2C-bus interface in the LPC800 user’s manual for a description of the how the I2C block works. It is more complicated interface than for the timers and SSP peripherals. The basic principle is to send commands to the I2C peripheral block. These commands are carried out in the (external) I2C bus and a status is presented as result. Based on the status the I2C driver gives the next command.

The mbed library have an I2C class to make it easy to use the bus:

Import library

Public Member Functions

I2C (PinName sda, PinName scl)
Create an I2C Master interface, connected to the specified pins.
void frequency (int hz)
Set the frequency of the I2C interface.
int read (int address, char *data, int length, bool repeated=false)
Read from an I2C slave.
int read (int ack)
Read a single byte from the I2C bus.
int write (int address, const char *data, int length, bool repeated=false)
Write to an I2C slave.
int write (int data)
Write single byte out on the I2C bus.
void start (void)
Creates a start condition on the I2C bus.
void stop (void)
Creates a stop condition on the I2C bus.

Not Implemented

At the time this guide was written the LPC800 I2C support was not fully functional. The block reading/writing functions:

read (int address, char *data, int length, bool repeated=false)

and

write (int address, const char *data, int length, bool repeated=false)

should not be used! Although it is annoying it is possible to work around it by using the byte functions instead. The examples in this lab uses the byte functions.

1) Hardware

In this lab you will need:

  • 1x breadboard
  • 4x 330 ohm resistor
  • 1x yellow LED
  • 1x green LED
  • 2x trimming potentiometer
  • cables

Mount the components on the breadboard and connect the breadboard to the LPC812 as show in the image below.

Breadboard Setup

The pin(s) are specified in the mbed library with the actual pin names as well as some useful aliases:

Schematic Namembed Pin NameArduino Shield AliasDescription
PIO0_10P0_10A4I2C SDC
PIO0_11P0_11A5I2C SCL
IOEXPAN4N/AD5Yellow LED on I/O Expander
IOEXPAN5N/AD6Green LED on I/O Expander
AIN0_XPIO0 (SJ5 in 1-2 pos)N/AA0Trimming capacitor 1
AIN0_XPIO1 (SJ6 in 1-2 pos)N/AA1Trimming capacitor 2

1) Description

In this experiment a GPIO expansion chip, PCA9672, will be used. The chip features according to the user manual:

  • 1 MHz I2C-bus interface (Fast-mode Plus I2C-bus)
  • 8-bit remote I/O pins that default to inputs at power-up
  • Active LOW reset input
  • Active LOW open-drain interrupt output

The LPC812 MAX board has the PC9672 already mounted and the user push-button is connected to pin P7 of the expander.

According to the schematics the PCA9672 has address 0b0100011.

We have prepared a PCA9672 class to make it easier to use the gpio expander:

PCA9672.h

#ifndef PCA9672_H
#define PCA9672_H
 
#include "mbed.h"
 
//  PCA9672 IIC slave address
#define  PCA9672_ADDR 0x46
 
 
//!Library for the PCA9672 I/O expander.
/*!
The PCA9672 is an I2C I/O expander. It has 8 I/O pins.
*/
class PCA9672
{
public:
  /*!
  Connect PCA9672 to I2C port pins sda and scl.
  */
  PCA9672(PinName sda, PinName scl);
  
  /*!
  Set the frequency of the I2C interface.
  */
  void frequency(int hz);
 
  /*!
  Setup pin direction (bit = 1 for inputs, 0 for outputs)
  */
  void direction(uint8_t inputs);
  
  /*!
  Write the value to the IO Expander (pins XP0-XP7 output)
  */
  void write(char value);
  
  /*!
  Read the value of the IO Expander (pins XP0-XP7 input)
  */
  int read(void);
    
  /*!
  Destroys instance.
  */ 
  ~PCA9672();
  
private:
  
  I2C _i2c;
  uint8_t _pins;
 
};
 
#endif


PCA9672.cpp

#include "PCA9672.h"
 
PCA9672::PCA9672(PinName sda, PinName scl) : _i2c(sda, scl)
{
    // Software Reset
    _i2c.start();
    while(_i2c.write(0x00) !=1);
    while(_i2c.write(0x06) !=1);
    _i2c.stop();
 
    /* Software reset is not required. But, gives an 
       indication if the selected I2C bus frequency works */
}
 
void PCA9672::frequency(int hz)
{
    _i2c.frequency(hz);
}

void PCA9672::direction(uint8_t inputs)
{
    _pins = inputs & 0xff;
}
 
void PCA9672::write(char value)
{
    if (_pins > 0)
    {
        _i2c.start();
        _i2c.write(PCA9672_ADDR);
        _i2c.write(value | _pins); // all input pins must have a logic 1 as value
        _i2c.stop();
    }
}
 
int PCA9672::read(void)
{
    _i2c.start();
    _i2c.write(PCA9672_ADDR | 1);
    uint8_t val = _i2c.read(0); // expect NACK
    _i2c.stop();
    return val;
}
 
PCA9672::~PCA9672()
{
 
}


main.cpp

#include "mbed.h"
#include "PCA9672.h"
 
PCA9672 ioxp(P0_10, P0_11); //I2C connected to PCA9672 in LPC800-MAX
 
Serial pc(USBTX, USBRX); // tx, rx

int main()
{
    ioxp.frequency(100000);
    ioxp.direction(0x80); // mark the push-button pin as input and the rest as outputs
 
    while (1) {
        uint8_t val = ioxp.read();
        pc.printf("Reading 0x%02x - bit 7 is %d\n", val, val>>7);
        if (val & 0x80)
        {
            ioxp.write(0x10);
        }
        else
        {
            ioxp.write(0x20);
        }
        wait(.50);
    }
}

Look at the code above and compare with the user manual and try to understand what the commands do.

Try to modify the program by replacing one of the LEDs on the breadboard with a push-button and then control the remaining LED to be on only when either push-button is pressed but not when both are pressed.

2) Hardware

The same setup is used so no changes are needed.

2) Description

There is no ADC (Analog to Digital Converter) or DAC (Digital to Analog Converter) on the LPC812 microcontroller, however the LPC812 MAX board has a PCF8591 chip which have four analog inputs and one analog output and is accessed with I2C.

You were given a PCF8591 helper class in the Analog Input experiment and now it is time to take a closer look at it.

Import librarylpc812_exp_lib_PCF8591

Library with PCF8591 support for the experiments for LPC812 MAX

PCF8591.cpp

#include "mbed.h"
#include "PCF8591.h"
 
PCF8591::PCF8591(PinName sda, PinName scl, int i2cAddr) : m_i2c(sda, scl)
{
    m_i2c.frequency(100000);
    m_addr = i2cAddr;
}
    
int PCF8591::read(AnalogIn port)
{
    char cmd = (port & 0x3); // read from selected port
    char data[2];
    
    // select the port
    m_i2c.start();
    m_i2c.write(m_addr);
    m_i2c.write(cmd);
    m_i2c.stop();
    
    // get the sample
    m_i2c.start();
    m_i2c.write(m_addr | 1);
    data[0] = m_i2c.read(1); // expect ACK
    data[1] = m_i2c.read(0); // expect NACK
    m_i2c.stop();
    
    // data[0] is a dummy byte so ignore it
    // data[1] is the new value
    return data[1];
}

The read function above starts by sending the Control byte as explained in section 8.2 of the user manual to prepare the chip to send data for the selected A/D channel.

After configuring the chip a sample is read. The A/D conversion is explained in section 8.3 and even if we only want one sample, two samples must be read. According to the user manual:

The first byte transmitted in a read cycle contains the conversion result code of the previous read cycle.

However the value seems to be random so we discard it and keep the second one instead.

Solution(s)

Import programlpc812_exp_solution_i2c

Solutions for the I2C experiments for LPC812 MAX


Please log in to post comments.