Tutorial Arduino programmieren,

Antworten
Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Tutorial Arduino programmieren,

Beitrag von Mathias »

Es ist noch ein Tutorial dazugekommen, wie man ein EEPROM nutzt, welches am I²C-Bus hängt.
http://wiki.freepascal.org/AVR_Embedded_Tutorial_-_I%C2%B2C_EEPROM/de
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Sfaizst
Beiträge: 3
Registriert: Mo 2. Apr 2018, 01:21

Re: Tutorial Arduino programmieren,

Beitrag von Sfaizst »

Hallo zusammen,

habe mich eben extra hier im Forum angemeldet, habe das Tutorial durchgespielt und mirn paar Units zusammen getippelt um mir das ganze leichter zu machen...

Mein Problem ist aber: Ich bekomme I²C einfach nicht zum laufen, war vor 2-3 Wochen schon mal dran, wurmt mich aber immer noch...
Ich programmiere Atmega328p über USB-Programmer getaktet auf 8Mhz ohne externen Quartz (Fusebits entsprechend angepasst, dass er nicht auf 1Mhz läuft), dürfte ja bei einem Bus mit Taktgeber kein Problem sein (schätze ich??).
Ich habe versucht ne RTC (DS3231) über I²C anzusprechen, aber jedes mal, wenn ich den Bus anspreche hängt sich das gesamte Programm auf...
Richtig angeschlossen ist er (PC5 / PC4)...

Eventuell findet ihr ja einen Fehler...

Hier meine Unit für die RTC:

Code: Alles auswählen

 
unit avr_rtc;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  avr_hwi2c_master;
 
const
  RTC_ADDR = $68;
 
type
  Trtc_Time = record
    s: Byte;
    m: Byte;
    h: Byte;
    mday: Byte;  //Monatstag
    wday: Byte;  //Wochentag
    mon: Byte;   //Monat
    year: Int16;  //Jahr (0..99)
  end;
 
procedure rtc_init;
function rtc_recv(var ATime: Trtc_Time): Boolean;
function rtc_send(ATime: Trtc_Time): Boolean;
 
implementation
 
function dec2bcd(ADec: Byte): Byte;
begin
  Result := ((ADec div 10 * 16) + (ADec mod 10));
end;
 
function bcd2dec(ABcd: Byte): Byte;
begin
  Result := ((ABcd div 16 * 10) + (ABcd mod 16));
end;
 
procedure rtc_init;
begin
  I2CInit;
end;
 
function rtc_recv(var ATime: Trtc_Time): Boolean;
var
  I: Byte;
  Recv: Array [0..6] of Byte;
begin
  Result := I2CMasterStart((RTC_ADDR shl 1) or I2C_Write);
  If Result = True then
    begin
      I2CWrite($0);
      I2CStop;
      Result := I2CMasterStart((RTC_ADDR shl 1) or I2C_Read);
      If Result = True then
        begin
          for I := 0 to 5 do
              Recv[I] := I2CRead;
          Recv[6] := I2CReadLast; 
          I2CStop;
          ATime.s := Bcd2dec(Recv[0]);
          ATime.m := Bcd2dec(Recv[1]);
          ATime.h := Bcd2dec(Recv[2]);
          ATime.wday := Bcd2dec(Recv[3]);
          ATime.mday := Bcd2dec(Recv[4]);
          ATime.mon := Bcd2dec(Recv[5] and $1F);
          I := (Recv[5] and $80) shr 7;
          If I = 1 then
            ATime.year := 2000 + bcd2dec(Recv[6]) Else
            ATime.year := 1900 + bcd2dec(Recv[6]);
        end;
    end;
end;
 
 
function rtc_send(ATime: Trtc_Time): Boolean;
var
  I: Byte;
  Send: Array [0..6] of Byte;
begin
  Result := I2CMasterStart((RTC_ADDR shl 1) or I2C_Write);
  If Result = True then
    begin
      I2CWrite($0);
      Send[0] := Dec2Bcd(ATime.s);   
      Send[1] := Dec2Bcd(ATime.m);
      Send[2] := Dec2Bcd(ATime.h);
      Send[3] := Dec2Bcd(ATime.wday);   
      Send[4] := Dec2Bcd(ATime.mday);
      If ATime.year >= 2000 then
        begin
          I := $80;
          Send[6] := ATime.year - 2000;
        end Else
        begin 
          I := $0;
          Send[6] := ATime.year - 1900;
        end;
      Send[5] := Dec2Bcd(ATime.mday)+I;
      for I := 0 to 6 do
        I2CWrite(Send[I]);
      I2CStop;
    end;
end;
 
end.
 


Code: Alles auswählen

 
unit avr_hwi2c_master;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  avr_const;
 
  procedure I2CInit;
  procedure I2CUpdateStatus;
  function I2CMasterStart(Addr: Byte): Boolean;
  procedure I2CStop;
  procedure I2CWrite(u8data: byte);
  function I2CRead: byte;
  function I2CReadLast: byte;
 
const
  I2C_Write = 0;
  I2C_Read  = 1;
 
var
  //Status des I2C Bus:
  TW_STATUS_MASK: Byte;
  TW_STATUS: Byte;
 
implementation
 
procedure I2CInit;
const
  TWBR_val = byte((CPU_Clock div I2C_Speed) - 16) div 2;
begin
  TWSR := 0;
  TWBR := byte(TWBR_val);
end;
 
procedure I2CUpdateStatus;
var
  TWSR2: bitpacked array [0..7] of boolean absolute TWSR;     
  TWSRM: bitpacked array [0..7] of boolean absolute TW_STATUS_MASK;
  I: Byte;
begin
  TW_STATUS_MASK := 0;
  for I := 7 downto 3 do
    TWSRM[I] := TWSR2[I];
  TW_STATUS := TWSR and TW_STATUS_MASK;
end;
 
function I2CMasterStart(addr: byte): Boolean;
begin     
  Result := True;
  // Senden/Empfangen einleiten
  TWCR := 0;
  TWCR := (1 shl TWINT) or (1 shl TWSTA) or (1 shl TWEN);
  while ((TWCR and (1 shl TWINT)) = 0) do begin
  end;
 
  // Adresse des Endgerätes senden
  TWDR := addr;
  TWCR := (1 shl TWINT) or (1 shl TWEN);
  while ((TWCR and (1 shl TWINT)) = 0) do begin
  end;
end;
 
 {Versuch den Status vom i²c Bus mit zu überprüfen, Unterschied = 0...
function I2CMasterStart(Addr: byte): Boolean;
const
  TW_START = $08;
  TW_REP_START = $10;
  TW_MT_SLA_ACK = $18;
  TW_MR_SLA_ACK = $40;
var
  TWST: Byte;
begin
  Result := False;
  // Senden/Empfangen einleiten
  TWCR := 0;
  TWCR := (1 shl TWINT) or (1 shl TWSTA) or (1 shl TWEN);
  while ((TWCR and (1 shl TWINT)) = 0) do begin
  end;
  I2CUpdateStatus;
  TWST := TW_STATUS and $F8;
  if (TWST = TW_START) or (TWST = TW_REP_START) then
    begin
      // Adresse des Endgerätes senden
      TWDR := addr;
      TWCR := (1 shl TWINT) or (1 shl TWEN);
      while ((TWCR and (1 shl TWINT)) = 0) do begin
      end;
      I2CUpdateStatus;
      TWST := TW_STATUS and $F8;
      if (TWST <> TW_MT_SLA_ACK) and (TWST <> TW_MR_SLA_ACK) then
        Result := True;
    end;
end;                 }

 
procedure I2CStop;
begin
  TWCR := (1 shl TWINT) or (1 shl TWSTO) or (1 shl TWEN);
end;
 
procedure I2CWrite(u8data: byte);
begin
  TWDR := u8data;
  TWCR := (1 shl TWINT) or (1 shl TWEN);
  while ((TWCR and (1 shl TWINT)) = 0) do begin
  end;
end;
 
// Lesen bis zum vorletzten Zeichen.
function I2CRead: byte;
begin
  TWCR := (1 shl TWINT) or (1 shl TWEN) or (1 shl TWEA);
  while (TWCR and (1 shl TWINT)) = 0 do begin
  end;
  Result := TWDR;
end;
 
// Letztes Zeichen lesen.
function I2CReadLast: byte;
begin
  TWCR := (1 shl TWINT) or (1 shl TWEN);
  while (TWCR and (1 shl TWINT)) = 0 do begin
  end;
  Result := TWDR;
end;
 
end.
 


Code: Alles auswählen

 
unit avr_const;
 
{$mode objfpc}{$H+}
 
interface
 
const
  {
    CPU_Clock: Bei Start des Programms einzustellen,
               wenn nicht 16MHZ
               (16MHZ nur bei Arduino oder mit Externem Kristall).
               Flashing Script beachten!
  }

  //CPU_Clock =  1000000; // 1MHZ  Fuses auf 1/8 gesetzt
  CPU_Clock =  8000000; // 8MHZ  Fuses auf int. Osc. ohne 1/8 gesetzt
  //CPU_Clock = 16000000; //16MHZ  Fuses auf ext. Osc. (Kristall) gesetzt (z.b. im Arduino Uno)
  {
    Uart / Serielle Verbindung / Nachrichtenanzeige:
  }

  uart_Baud = 9600;     // Baudrate
  uart_Div  = CPU_Clock div (16 * uart_Baud) - 1;
  i2c_Speed = 400000;
 
implementation
 
end.   
 


Vielen Dank für die Hilfe
Mfg

Edit: Es hat sich doch ein kleiner Fehler beim Senden eingeschlichen, korrigiert...
Zuletzt geändert von Sfaizst am Mo 2. Apr 2018, 12:35, insgesamt 1-mal geändert.

Benutzeravatar
kupferstecher
Beiträge: 418
Registriert: Do 17. Nov 2016, 11:52

Re: Tutorial Arduino programmieren,

Beitrag von kupferstecher »

Hallo Sfaizst,

Du sagst, dass das Programm hängenbleibt, das kann ja eigentlich nur bei einer der Flag-Abfragen der Fall sein. ("while ((TWCR and (1 shl TWINT)) = 0) do begin end;"). Also wenn das Flag nie umschällt. Du müsstest rauszufinden an welcher Stelle es genau hängen bleibt. Z.B. kannst du jeweils vor dem Senden/Empfangen eines Bytes den Status per UART an den Computer schicken.

Sfaizst
Beiträge: 3
Registriert: Mo 2. Apr 2018, 01:21

Re: Tutorial Arduino programmieren,

Beitrag von Sfaizst »

Keine Ahnung, was ich heute anders gemacht habe... aber es funktioniert... (Das erste Erfolgserlebnis mit I²C)
Habe die Vermutung, dass die Leitungen aufm Breadboard nicht immer sauber steckten...
Zudem scheint die RTC wirklich sehr viel Wert auf die 400khz beim I²C zu legen, langsamere Verbindungen hat sie bei mir stets verweigert...

Uhrzeit ist zwar noch nicht gestellt, aber die Zeit vergeht schon mal stimmig (habe nur das Auslesen probiert)...
Zeit: 15:42:25
Datum: 1.1.1900

Am Quelltext oben musste ich nichts verändern...

Trotzdem Vielen Dank, wenn gewollt kann der Quelltext weiter verwendet werden...

Sfaizst
Beiträge: 3
Registriert: Mo 2. Apr 2018, 01:21

Re: Tutorial Arduino programmieren,

Beitrag von Sfaizst »

Noch eine Frage...
Gibt es keine Möglichkeit, dass man den I²C so umprogrammiert, dass er nicht das ganze Programm blockiert, sollte etwas nicht stimmen (z.B.: der Slave nicht erreichbar sein)?
Was für Interrupts würde es geben? Kann man die (endlos) Schleifen bis zu einem Maximalwert hochzählen lassen und dann abbrechen o.ä.?
Ich würde es gerne Vermeiden so eine Falle in einem System zu haben... (ich plane eine Terrariumssteuerung für nen Kumpel zu basteln, wenn sich da dann das komplette Programm aufhängt wäre das nicht sonderlich praktisch)...

Vielen Dank

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: Tutorial Arduino programmieren,

Beitrag von Timm Thaler »

Also erstens: Kann bitte ein Mod den TWI-Teil abtrennen und in einen eigene Thread packen, das wird hier unübersichtlich.

Zweitens: Die RTC sollte problemlos auch mit 100kHz oder weniger laufen.

Drittens: Das Hardware-TWI ist "empfindlich" für Busfehler.

Das Problem ist bei TWI / I2C prinzipiell, dass ein Slave theoretisch den Bus komplett stillegen kann, wenn er Clock Stretch macht (der Slave legt SCL solange auf low, bis er bereit ist zu antworten) und sich dabei aufhängt, oder wenn es einen Kurzschluss auf SCL gibt. Die Hardware-TWI wartet dann ewig darauf, dass der Bus fertig wird. Sowas kann auch bei Timing-Fehlern zwischen SDA und SCL passieren, die schon auftreten können wenn eine Oszi-Leitung dranhängt und deren Kapazität das Signal verzögert.

Das Problem ist jetzt, dass dann solche Schleifen wie

Code: Alles auswählen

  while (TWCR and (1 shl TWINT)) = 0 do begin
  end;

nie fertig werden, weil der Controller auf das Zeichen wartet welches aber nicht kommt. Man sollte solche potentiellen "Endlosschleifen" immer mit einer Abbruchbedingung durch ein Timeout versehen: Also einen Zähler mitführen, und wenn der abläuft die Schleife mit einem Fehler (Noack) verlassen.

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Tutorial Arduino programmieren,

Beitrag von Mathias »

Das TWIRead müsste ich mal im Tutorial anpassen.
Wie du schon sagst, mit einem Counter und einer Statusvariable.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Tutorial Arduino programmieren,

Beitrag von Mathias »

Das TWIRead müsste ich mal im Tutorial anpassen.

Dies habe ich gerade ergänzt, wen bei mehreren While-Durchläufen nicht passiert, wird die Schleife mit einem Fehler abgebrochen. Der AVR bleibt dann nicht mehr hängen.
Anstelle von SendString, könnten man auch eine Variable ErrorState benutzen.

Code: Alles auswählen

function TWIReadACK_Error: byte;
var
  err:byte;
begin
  err := 0;
  TWCR := (1 shl TWINT) or (1 shl TWEN) or (1 shl TWEA);
  while ((TWCR and (1 shl TWINT)) = 0) and (err < 255) do begin
    Inc(err);
  end;
  if err = 255 then begin
    UARTSendString('I²C-Timeout');
    Result := 0;
  end else begin
    Result := TWDR;
  end;
end;


Der erste Thread habe ich noch die toten Links angepasst. So das auch ein Einsteiger das Tutorial findet. :wink:
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Tutorial Arduino programmieren,

Beitrag von Mathias »



Ich habe dein Turorial zu der Software I²C angeguckt.
Dies kann gar nicht funktionieren, da du DDRx und PORTx verwechselt hast.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: Tutorial Arduino programmieren,

Beitrag von Timm Thaler »

Mathias hat geschrieben:Dies kann gar nicht funktionieren, da du DDRx und PORTx verwechselt hast.


Oh, das muss ich gleich meiner Heizung mit MAX127, DS1307, PCF8574, meinem Displayexpander mit DS13017 und meiner Wetterstation mit BCM180, HTC21 erzählen.

Entweder funktionieren die mit Magie - oder Du hast nicht verstanden, wie I2C funktioniert.

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Tutorial Arduino programmieren,

Beitrag von Mathias »

Was mich verwundert, das man mit dem Konfiguration-Register Daten ausgeben kann.
Da es bei dir funktioniert, hast du sicher nichts neues erfunden. :wink:

Ich habe noch ein C++ - Code angeguckt, dort ist es auch mit DDR. http://extremeelectronics.co.in/avrtuto ... or_AVR.zip
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: Tutorial Arduino programmieren,

Beitrag von Timm Thaler »

Mathias hat geschrieben:Was mich verwundert, das man mit dem Konfiguration-Register Daten ausgeben kann.


Also nochmal für Dich, I2C und TWI funktionieren so, dass für ein logisch Null der Master oder der Slave den Bus auf GND ziehen. Für ein logisch Eins "lassen" Master oder Slave den Bus "los", und die Pull-Up-Widerstände ziehen den Bus gegen VCC. Deswegen ist I2C im Vergeich zu anderen Bustypen auch recht langsam und in der Leitungslänge begrenzt, weil hier die Pull-Up-Widerstände gegen die Leitungskapazität arbeiten müssen.

Wäre das anders, könnte ein Low am Master und ein Eins am Slave einen Kurzschluss erzeugen, oder Multi-Slave-Betrieb wäre nicht möglich. Andere Bustypen, die mit aktiven Low- und High-Pegeln arbeiten, müssen das anders lösen, zum Beispiel durch Kollisionerkennung, Strombegrenzung oder unidirektionale Übertragung.

Technisch gesehen verwenden Master und Slave jeweils Open-Collector-Ausgänge, der obere Transistor im Ausgangstreiber wird dabei nicht verwendet. Und bei den AVRs kann man dieses Open-Collector-Verhalten am Einfachsten erreichen, indem man den Port auf low läßt und die Datenrichtung zwischen Eingang (offen) und Ausgang (gegen GND geschlossen) umschaltet.

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Tutorial Arduino programmieren,

Beitrag von Mathias »

Kurz gesagt, High, wird mit den Pullup Widerständen erzeugt ?
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Horst_h
Beiträge: 72
Registriert: Mi 20. Mär 2013, 08:57

Re: Tutorial Arduino programmieren,

Beitrag von Horst_h »

kurz gesagt, ja

Gruß Horst

Benutzeravatar
kupferstecher
Beiträge: 418
Registriert: Do 17. Nov 2016, 11:52

Re: Tutorial Arduino programmieren,

Beitrag von kupferstecher »

Mathias hat geschrieben:Kurz gesagt, High, wird mit den Pullup Widerständen erzeugt ?

Was heisst mit "den" Pullup Widerstaenden?
Nicht mit den in den Ports integrierten, diese waeren nur aktiv, wenn das Port-Register 1 waere (bei Eingang).
Hier muss man im Schaltplan explizit Pullups vorsehen fuer die High-Pegel.

Antworten