AVR Embedded Tutorial - Software I2C, TWI

From Free Pascal wiki

Deutsch (de) English (en)

Software I2C / TWI for ATmega328 / Arduino

Many AVR controllers offer a hardware I2C interface. Sometimes you need an I2C interface on other pins, or several I2C interfaces. Software I2C helps in these situations.

For the basics of I2C see this WikiPedia article.

TWI Master software

TWI header

The pins for SCL and SDA can be set as desired, but must all be on one port. For pins on different ports, the pin accesses have to be adjusted in the following routines.

 unit test_twi;

 {$mode objfpc}
 {$goto on}

 interface

 uses
   ;

 procedure twi_start();
 procedure twi_stop();

 function twi_write(data : uint8) : boolean;
 function twi_readbyte(ackn : boolean) : uint8;
 function twi_readword(ackn : boolean) : uint16;

 const
   Psclin = 1 shl 5;  // PC5 any pins, but on the same port
   Psdain = 1 shl 4;  // PC4

 var
   DDRtwi : byte absolute DDRC;  // TWI pins on port C
   PRTtwi : byte absolute PORTC;
   PINtwi : byte absolute PINC;

 implementation

Delays

The clock rate is adjusted over the duration of the delays, the numbers after ldi r16. Inline assembler does not allow comments here and for timing reasons the values ​​should be entered directly and not passed as variables.

With the given values ​​9 and 10, a clock rate of 100kHz results at 8MHz clock frequency, with 59 and 60 a clock rate of 10kHz can be used for long data lines.

 // pause TWI delays

 procedure delay2();
 label
   loop;
 begin
   asm
     ldi r16, 9
     loop:
       dec r16
       brne loop
   end['r16'];
 end;        

 procedure delay3();
 label
   loop;
 begin
   asm
     ldi r16, 10
     loop:
       dec r16
       brne loop
   end['r16'];
 end;

TWI start

The start sequence for a transmission.

 // Start TWI

 procedure twi_start();
 begin
   DDRtwi := DDRtwi or Psdain;  // SDA low
   delay3();
   DDRtwi := DDRtwi or Psclin;  // SCL low
   delay3();
 end;

TWI stop

The stop sequence. For a restart, as required by some data sheets, you simply use a stop sequence followed by a start sequence.

 // TWI stop sequence

 procedure twi_stop();
 begin
   DDRtwi := DDRtwi or Psdain;        // SDA low
   delay3();
   DDRtwi := DDRtwi and (not Psclin); // SCL high
   delay3();
   DDRtwi := DDRtwi and (not Psdain); // SDA high
   delay3();
 end;

Write TWI byte

Hand over byte and go. Duration is about 10x cycle time, that is 100µsec at 100kHz. The routine may be interrupted by interrupts, the slave then waits.

 // Write TWI byte

 function twi_write(data : uint8) : boolean;
 var
   i : uint8;
 begin
   for i := 0 to 7 do                      // output 8 bits
     begin
       if(data and (1 shl 7)) <> 0 then
         DDRtwi := DDRtwi and (not Psdain) // SDA high if bit is set
       else
         DDRtwi := DDRtwi or Psdain;       // SDA low if bit is not set

       data := data shl 1;                 // shift bit, 7 to 0
       delay2();
       DDRtwi := DDRtwi and (not Psclin);  // SCL high
       delay3();
       DDRtwi := DDRtwi or Psclin;         // SCL low
     end;

   DDRtwi := DDRtwi and (not Psdain);      // SDA high
   delay2();
   DDRtwi := DDRtwi and (not Psclin);      // SCL high
   delay2();

   if(PINtwi and Psdain) = 0 then          // test SDA on Acknowledge low
     twi_write := true                     // set ack flag
   else
     twi_write := false;                   // otherwise unset ack flag

   DDRtwi := DDRtwi or Psclin;             // SCL low
 end;

Read TWI data byte

 // read TWI byte

 function twi_readbyte(ackn : boolean) : uint8;
 var
   data : uint8 = 0;
   i    : uint8;
 begin
   for i := 0 to 7 do                     // output 8 bits
     begin
       data := data shl 1;                // shift bit, 7 to 0
       delay2();
       DDRtwi := DDRtwi and (not Psclin); // SCL high
       delay2();

       if(PINtwi and (Psdain)) <> 0 then  // test SDA on high
         data := data or (1 shl 0);       // if high, set bit 0
	
       DDRtwi := DDRtwi or Psclin;        // SCL low
     end;

   if ackn then
     DDRtwi := DDRtwi or Psdain           // SDA low, acknowledge
   else
     DDRtwi := DDRtwi and (not Psdain);   // SDA high, no acknowledge

   delay2();
   DDRtwi := DDRtwi and (not Psclin);     // SCL high
   delay3();
   DDRtwi := DDRtwi or Psclin;            // SCL low
   DDRtwi := DDRtwi and (not Psdain);     // SDA high

   twi_readbyte := data;                  // return data
 end;

Read TWI data word

 // Read TWI Word, MSB + LSB

 function twi_readword(ackn : boolean ) : uint16;
 var
   data : uint16 = 0;
   i    : uint8;
 begin
   for i := 0 to 7 do                     // output 8 bits
     begin
       data := data shl 1;                // shift bit, 7 to 0
       delay2();
       DDRtwi := DDRtwi and (not Psclin); // SCL high
       delay2();

       if(PINtwi and (Psdain)) <> 0 then  // test SDA on high
         data := data or (1 shl 0);       // if high, set bit 0

       DDRtwi := DDRtwi or Psclin;        // SCL low
     end;

   DDRtwi := DDRtwi or Psdain;            // SDA low, acknowledge
   delay2();
   DDRtwi := DDRtwi and (not Psclin);     // SCL high
   delay3();
   DDRtwi := DDRtwi or Psclin;            // SCL low
   DDRtwi := DDRtwi and (not Psdain);     // SDA high

   for i := 0 to 7 do                     // output 8 bits
     begin
       data := data shl 1;                // shift bit, 7 to 0
       delay2();
       DDRtwi := DDRtwi and (not Psclin); // SCL high
       delay2();

       if(PINtwi and (Psdain)) <> 0 then  // test SDA on high
         data := data or (1 shl 0);       // if high, set bit 0

       DDRtwi := DDRtwi or Psclin;        // SCL low
     end;

  if ackn then
    DDRtwi := DDRtwi or Psdain            // SDA low, acknowledge
  else
    DDRtwi := DDRtwi and (not Psdain);    // SDA high, no acknowledge

  delay2();
  DDRtwi := DDRtwi and (not Psclin);      // SCL high
  delay3();
  DDRtwi := DDRtwi or Psclin;             // SCL low
  DDRtwi := DDRtwi and (not Psdain);      // SDA high

  twi_readword := data;                   // return data
 end;

End

The unit is completed with the following line.

  end.

See also