I2c in mikroPascal.

Below is a very short explanation on the I2c bus fundamentals with practical view on its usage. A lot of I2c details are not discussed here, only the issues needed to know to drive I2c devices from PIC's are pictured.
The document also only discusses the PIC being the master on the I2c bus, all examples below are also for the PIC being the master.
The code examples are based on mikroElektronika's I2c and SoftI2c libraries.

Table of contents:
Introduction A small introduction on I2c.
Connections between I2c devices How I2c devices are tied together electrically.
I2c device addresses What about write and read addresses, and selectable addresses.
I2c Protocol The I2c protocol in a nutshell, plus 2 code examples.
Internal I2c Device addressing The real world: I2c slaves with internal addressing.
The mE "Soft" I2c Doing I2c without I2c hardware in the PIC.

1. Introduction

The I2c bus is a serial 2 wire bus that is used by a number of peripheral devices: external Eeprom, Real time clocks, etc...
The 2 wires are the clock (SCK) and the data (DTA) lines. Of course there is always a third wire: the common ground connection between all I2c devices.

mE's mikroPascal libaries contain both the I2c (for usage with PIC's who have a hardware I2c capable serial communication peripheral built in) and the Soft_I2c library, which does not need a built in I2c hardware in the PIC.

On an I2c bus there is always 1 I2c Master and 1 or more I2c slaves. The master is the one generating the clock pulses on SCK (a slow slave however can "stretch" the clock a little bit).
Both master and slaves can put data on SDA: during a "write" (seen from the master) the "master" is driving SDA (and one of the slaves reads it), during a "read" (again seen from the master) one of the "slaves" drives SDA (and the master reads it).

The data transfer (in any direction) is always in blocks of 8 bits (so, in bytes). I2c however adds a 9th bit: the so-called "Acknowledge bit". This bit is always generated by the one who reads the data from SDA right after reception of a byte.
One peculiar but very important thing in the I2c specification: when the master reads, it should not acknowledge the last byte read!

2. Connections between I2c devices

These look very simple:

Do NOT forget the pull-up resistors on both SCL and SDA lines!

3. I2c device addresses

Since more than one slave can reside on the bus, each I2c slave device is assigned (by the manufacturer) its own "I2c Device address".
On some I2c slave devices it is possible to (partly) choose the I2c Device address by connecting pins of the devive to the +5V or to ground, e.g. the external X24Cxx device range.

It is the responsibility of the I2c master to address the correct I2c Slave device.

To make the difference between a read and a write operation the I2c slave devices have in fact 2 device addresses: one for writing and one for reading: the device write address is always even, the device read address is the device write address + 1 (always odd).

In the datasheet of the I2c slave device you will find something like this at the I2c device address:

1 0 1 0 0 0 1 R/W

This shows the binary representation of the I2c device addresses, in this case $A2 for the write address and $A3 for the read address. For the details of the binary representation, click here.

If you see this like below, it usually means you can choose one of the address bits by connecting a device pin to "+" or ground:
1 0 1 0 0 0 A0 R/W
In the case above there is one bit you can choose, this means you can place 2 of these devices on the I2c bus: one with the pin to ground (gives a "0" in the device address) and one with the pin to the + (gives a "1" in the device address). The device addresses of the one with the pin to the "+" are as in the first example, the one with the pin to ground has the I2c addresses $A0 (write) and A1 (read).
It is also possible that, if a device address looks like above, that the device uses more than one I2c read and write address. This is e.g. the case with devices like eeproms with a lot of memory inside. The different I2c read/write addresses allow then to e.g. select different memory banks or functions within one device.

Please see the datasheet of a device for its I2c device address(es).

4. I2c Protocol

Properties of the I2c protocol are as follows: So, here we go: suppose we want to send 3 bytes to the device with device (write) address $A0, then the code looks as follows:
  I2C_Init(100000);  // Initialise I2c at a clock speed of 100 Khz, only to be done once!
  ...
  I2C_Start();       // Issue I2C start signal
  I2c_Wr($A0);       // Send I2c Write Slave Address
  I2c_Wr(Byte1);     // write first byte
  I2c_Wr(Byte2);     // write second byte
  I2c_Wr(Byte2);     // write third byte
  I2C_Stop(); 
Reading is as easy (example reads also 3 bytes):
  I2C_Init(100000);        // Initialise I2c at a clock speed of 100 Khz, only to be done once!
  ...
  I2C_Start();             // Issue I2C start signal
  I2c_Wr($A1);             // Send I2c Read Slave Address
  Byte1 := I2c_Rd(true);   // read first byte  and give acknowledge
  Byte2 := I2c_Rd(true);   // read second byte and give acknowledge
  Byte3 := I2c_Rd(false);  // read last byte   and give no acknowledge !!!!
  I2C_Stop(); 
Keep in mind that, if the master read from a slave, the last byte read should not be acknowledged!

Important: It is entirely up to the slave device to handle and interprete the bytes written to it, and to decide what bytes to sent to the master when read by it.
Most I2c slave devices are much more complex than only having the ability to e.g. receive or send 3 bytes with a fixed meaning, so here comes the next chapter to handle that. So, knowing this, the code above will probably be not applicable for most actual devices!, see however the next chapter. :-)

5. Internal I2c Device addressing

The issues discussed so far are "standard" I2c, they hold for every I2c slave device. What follows is device dependant, and the datasheet of the device at hand should be looked in to see what the data send/received exactly means.

"Almost standard" is the following behaviour: A lot of I2c devices work internally with some kind of adressing with a "pointer" which holds the address of the place (or register) in the I2c device that must receive data during a master write, and must sent its content during a master read operation.
Also, in most devices (but again, see the datasheet), the "pointer" is "autoincremental": it is incremented after a byte is written to or read from the device, resulting in a socalled "sequentially write" or "sequentially read".
In most cases of I2c slave devices, the "internal address" range is 0..$FF, and so 1 byte suffices to represent the internal address. Also in most cases the pointer value is set to the value of the first byte after a device write address.

So, for this type of devices (with internal address pointer and auto increment) the code would look as follows:
  I2C_Init(100000);   // Initialise I2c at a clock speed of 100 Khz, only to be done once!
  ...
  I2C_Start();        // Issue I2C start signal
  I2c_Wr($A0);        // Send I2c Write Slave Address

  I2c_Wr(RegAddress)  // <-- added: send the address of the register you want to write to (here "RegAddress")

  I2c_Wr(Byte1);      // write first byte  to [RegAddress]
  I2c_Wr(Byte2);      // write second byte to [RegAddress + 1]
  I2c_Wr(Byte2);      // write third byte  to [RegAddress + 2]
  I2C_Stop(); 
Reading is a little bit more difficult, the pointer value has to be written before the read operation (example reads also 3 bytes):
  I2C_Init(100000);        // Initialise I2c at a clock speed of 100 Khz, only to be done once!
  ...
  I2C_Start();             // Issue I2C start signal

  I2c_Wr($A0);             // <-- added: Send I2c Write Slave Address
  I2c_Wr(RegAddress)       // <-- added: send the address of the register you want to read from (here "RegAddress")
  I2C_Repeated_Start;      // <-- added: new (repeated) start condition: the following byte should be interpreted as address

  I2c_Wr($A1);             // Send I2c Read Slave Address
  Byte1 := I2c_Rd(true);   // read first byte  from [RegAddress] and give acknowledge
  Byte2 := I2c_Rd(true);   // read second byte from [RegAddress + 1] and give acknowledge
  Byte3 := I2c_Rd(false);  // read last byte   from [RegAddress + 2] and give no acknowledge
  I2C_Stop(); 
As you can see, the "pointer" value is written before the read slave address is sent.
Important:

6. The mE "Soft" I2c

Sometimes, e.g. when the PIC has no hardware for I2c, or if that hardware is used by some other peripheral, one is obliged to use the Soft I2c library of mE. With this software the I2c bus is driven completely by software. The Soft I2c library functions are:
procedure Soft_I2C_Config(var port : byte; const SDA, SCL : byte);
procedure Soft_I2C_Start;
function Soft_I2C_Read(ack : byte) : byte;
function Soft_I2C_Write(data : byte) : byte;
procedure Soft_I2C_Stop;
As you can see, the I2c_Init routine has been replaced by Soft_I2C_Config which only defines both pins to be used as SDA and SCL lines. It is not possible here to select the clock speed.
I2c_Start and are both replaced by Soft_I2C_Start.
I2c_Stop is replaced by Soft_I2c_Stop, and
I2c_Wr and I2c_Rd are replaced by Soft_I2C_Write and Soft_I2C_Read respectively.

Example:
  Soft_I2C_Config(PORTC, 1, 0); // Initialise Soft I2c: SDA is PORTC.1 and SCL is PORTC.0, only to be done once!
  ...
  Soft_I2C_Start();             // Issue I2C start signal
  Soft_I2c_Write($A0);          // Send I2c Write Slave Address
  Soft_I2c_Write(RegAddress)    // send the address of the register you want to read from (here "RegAddress")
  Soft_I2C_Start;               // new start condition: the following byte should be interpreted as address
  Soft_I2c_Write($A1);          // Send I2c Read Slave Address
  Byte1 := Soft_I2c_Read(true); // read first byte  from [RegAddress] and give acknowledge
  Byte2 := Soft_I2c_Read(true); // read second byte from [RegAddress + 1] and give acknowledge
  Byte3 := Soft_I2c_Read(false);// read last byte   from [RegAddress + 2] and give no acknowledge
  Soft_I2C_Stop(); 

-------------------------------------------