AVR Embedded Tutorial - Software I2C, TWI/de

From Lazarus wiki
Jump to navigationJump to search

Deutsch (de) English (en)

Zur Übersichtseite AVR Embedded Tutorial/de.

Software-I2C/TWI für ATmega328 / Arduino

Viele AVR Controller bieten eine Hardware-I2C-Schnittstelle. Mitunter benötigt man aber einen I2C auf anderen Pins, oder mehrere I2C. Dann hilft ein Software-I2C.

Zu den Grundlagen von I2C siehe Wikipedia: https://de.wikipedia.org/wiki/I2c

Software TWI Master

TWI Header

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 beliebige Pins, aber auf gleichem Port
  Psdain    = 1 shl 4;  // PC4

var
  DDRtwi : byte absolute DDRC;  // TWI Pins auf Port C
  PRTtwi : byte absolute PORTC;
  PINtwi : byte absolute PINC;

implementation

Die Pins für SCL und SDA können beliebig festgelegt werden, müssen aber auf einem Port liegen. Bei Pins auf verschiedenen Ports müssen die Pinzugriffe in den folgenden Routinen angepasst werden.

Pausen

// Pause TWI

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;

Die Taktrate wird über die Laufzeit der Pausen angepasst, die Zahlen hiner ldi r16. Inline-Assembler läßt hier keine Kommentare zu und aus Timinggründen sollten die Werte direkt eingetragen und nicht als Variablen übergeben werden.

Mit den angegebenen Werten 9 und 10 ergibt sich bei 8MHz Taktfrequenz eine Clockrate von 100kHz, mit 59 und 60 erreicht man 10kHz für lange Datenleitungen.

TWI Start

// Start TWI

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

Die Startsequenz für eine Übertragung.

TWI Stop

// Stop TWI

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;

Die Stopsequenz. Für ein Restart, wie es manche Datenblätter fordern nimmt man einfach eine Stopsequenz gefolgt von einer Startsequenz.

TWI Byte schreiben

// TWI schreiben

function twi_write(data : uint8) : boolean;
var
  i : uint8;
begin
	for i := 0 to 7 do begin  // 8 Bit ausgeben
		if (data and (1 shl 7)) <> 0 then
			DDRtwi := DDRtwi and (not Psdain)  // SDA high, wenn Bit gesetzt
		else
			DDRtwi := DDRtwi or Psdain;  // SDA low, wenn Bit nicht gesetzt
		data := data shl 1;			// Bit schieben, 7 bis 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  // Testen SDA auf Acknowledge low
		twi_write := true  // Flag Ack setzen
	else
		twi_write := false;  // sonst Flag Ack löschen

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

Byte übergeben und los. Dauer ist etwa 10x Taktdauer, das heisst 100µsec bei 100kHz. Die Routine darf von Interrupts unterbrochen werden, der Slave wartet dann.

TWI Datenbyte lesen

// TWI Byte lesen

function twi_readbyte(ackn : boolean) : uint8;
var
  data : uint8 = 0;
  i    : uint8;
begin
	for i := 0 to 7 do begin  // 8 Bit ausgeben
		data := data shl 1;			// Bit schieben, 7 bis 0
  		delay2();
		DDRtwi := DDRtwi and (not Psclin);  // SCL high
  		delay2();
		if (PINtwi and (Psdain)) <> 0 then  // Testen SDA auf high
			data := data or (1 shl 0);  // wenn high, Bit 0 setzen
		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;  // Daten zurückgeben
end;

TWI Datenword lesen

// TWI Word lesen, MSB + LSB

function twi_readword(ackn : boolean) : uint16;
var
  data : uint16 = 0;
  i    : uint8;
begin
	for i := 0 to 7 do begin  // 8 Bit ausgeben
		data := data shl 1;			// Bit schieben, 7 bis 0
  		delay2();
		DDRtwi := DDRtwi and (not Psclin);  // SCL high
  		delay2();
		if (PINtwi and (Psdain)) <> 0 then  // Testen SDA auf high
			data := data or (1 shl 0);  // wenn high, Bit 0 setzen
		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 begin  // 8 Bit ausgeben
		data := data shl 1;			// Bit schieben, 7 bis 0
  		delay2();
		DDRtwi := DDRtwi and (not Psclin);  // SCL high
  		delay2();
		if (PINtwi and (Psdain)) <> 0 then  // Testen SDA auf high
			data := data or (1 shl 0);  // wenn high, Bit 0 setzen
		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;  // Daten zurückgeben
end;

End

Mit folgender Zeile ist die Unit komplett.

end.

Siehe auch