Tero's Arduino Blog

Using Arduino with Ada

Software I2C master implementation

Some time ago I was spending evening with a friend and it came up that I had not ever written software I2C master routines, even though I have used I2C quite much in various projects.

So, to correct this omission, we armed ourselves with the I2C specification, sit down for few hours, and composed together a simple C implementation for I2C master using GPIO pins.

The code was for Atmel SAMD21 and as such could not be used with AVR-Ada. This led me to rewrite the code in Ada.

I abstracted the core logic into a generic package, which can be used on any platform as long as few procedures are provided by the user:

generic
   with procedure Pull_SDA_Down;
   with procedure Release_SDA_Up;
   with procedure Pull_SCL_Down;
   with procedure Release_SCL_Up;
   with procedure Delay_T_HD_STA; -- 4.0
   with procedure Delay_T_SU_STO; -- 4.0
   with procedure Delay_T_Buf; -- 4.7..5
   with procedure Delay_T_Low_Half; -- 2.4
   with procedure Delay_T_High; -- 5
   with function SDA_State return Boolean;
package Soft_I2C is

   type Byte_Array is
     array (Interfaces.Unsigned_8 range <>) of Interfaces.Unsigned_8;

   type Error_Status is
     (SOFT_I2C_OK,
      SOFT_I2C_NACK,
      SOFT_I2C_FAILED);

   procedure Start;
   procedure Stop;
   procedure Write_Byte (Byte : Interfaces.Unsigned_8;
                         Status : out Error_Status);
   procedure Read_Byte (Byte : out Interfaces.Unsigned_8; Ack : Boolean);

   procedure Write (Address : Interfaces.Unsigned_8; Bytes : Byte_Array;
                    Status  : out Error_Status);

   procedure Read (Address : Interfaces.Unsigned_8; Bytes : in out Byte_Array);
end Soft_I2C;

The user need to provide GPIO pin manipulation procedures/functions:

with procedure Pull_SDA_Down;
with procedure Release_SDA_Up;
with procedure Pull_SCL_Down;
with procedure Release_SCL_Up;
...
with function SDA_State return Boolean;

And procedures which implement the required delays between pin changes:

with procedure Delay_T_HD_STA; -- 4.0 usecs
with procedure Delay_T_SU_STO; -- 4.0 usecs
with procedure Delay_T_Buf; -- 4.7..5 usecs
with procedure Delay_T_Low_Half; -- 2.4 usecs
with procedure Delay_T_High; -- 5 usecs

An implementation for AVR-Ada and Arduino UNO (atmega328p) is provided in uno_i2c.ads and uno_i2c.adb.

TMP102 temperature sensor

Following example code shows how to read TMP102 temperature sensor value using the package:

procedure Test_I2C is
   use type Interfaces.Unsigned_8;

   TMP102_Address : constant := 16#90#;
   Data : Uno_I2C.I2C.Byte_Array (1..2) := (0, 0);
   Cmd : Uno_I2C.I2C.Byte_Array (1..1) := (1 => 16#00#);
   Status : Uno_I2C.I2C.Error_Status;
   Temp_Value : Integer;
begin
   AVR.UART.Init (103);
   loop
      Data := (0, 0);
      Uno_I2C.Write (Address => TMP102_Address, Bytes => Cmd, Status => Status);
      Uno_I2C.Read (Address => TMP102_Address, Bytes => Data);
      Temp_Value := Integer (Data (1)) * 256;
      Temp_Value := Temp_Value + Integer (Data (2));
      Temp_Value := Temp_Value / 256; -- Basically we ignore the second byte

      AVR.UART.Put ("T:");

      if Temp_Value > 0 then
         Data (1) := Interfaces.Unsigned_8 (Temp_Value);
      else
         AVR.UART.Put ("-");
         Data (1) := Interfaces.Unsigned_8 (-Temp_Value);
      end if;
      AVR.UART.Put (Data (1), Base => 10);
      AVR.UART.Put (" C");
      AVR.UART.CRLF;
      delay 2.0;
   end loop;
end Test_I2C;

Full code is available under ISC license at my arduino-blog Bitbucket repository.

As usual, some caveats:

  • Code doesn't implement all I2C master features, like clock stretching.
  • The example code for Arduino UNO uses 80kHz I2C bus speed. Faster is not possible easily.
  • Read and Write procedures expect 8-bit I2C addresses.

Copyright © 2012, 2013 Tero Koskinen - Theme Skeleton; Blogging engine Pelican; Powered by Python