Difference between revisions of "AVR Embedded Tutorial - UART/de"

From Free Pascal wiki
Jump to navigationJump to search
m (→‎Hauptschleife: Added missing "end." to main loop code)
(12 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{Translate}}
+
{{LanguageBar}}
  
 
=UART Polling=
 
=UART Polling=
  
 
==ATmega328p (Arduino uno/nano)==
 
==ATmega328p (Arduino uno/nano)==
 +
 
Für die serielle Ausgabe wird ein Terminal mit folgenden Einstellungen gebraucht:
 
Für die serielle Ausgabe wird ein Terminal mit folgenden Einstellungen gebraucht:
 +
 
{| class="wikitable"
 
{| class="wikitable"
 
|-
 
|-
Line 19: Line 21:
  
 
===Konstanten für Inizialisierung===
 
===Konstanten für Inizialisierung===
<syntaxhighlight>
+
 
 +
<syntaxhighlight lang="pascal">
 
const
 
const
 
   CPU_Clock = 16000000; // Taktfrequenz Arduino, default 16MHz.
 
   CPU_Clock = 16000000; // Taktfrequenz Arduino, default 16MHz.
Line 25: Line 28:
 
   Teiler    = CPU_Clock div (16 * Baud) - 1;
 
   Teiler    = CPU_Clock div (16 * Baud) - 1;
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
'''Hinweis:''' Wen den der CPU-Clock zu klein ist in die Baudrate zu hoch, gibt es Übertragungsfehler. Beispiel: Clock '''1MHz''', Baud '''9600''' geht nicht. Aber '''1MHz''' und Baud '''1200''' gehen.
  
 
===Inizialisieren===
 
===Inizialisieren===
 +
 
Die UART-Schnittstelle konfigurierieren.
 
Die UART-Schnittstelle konfigurierieren.
<syntaxhighlight>
+
 
 +
<syntaxhighlight lang="pascal">
 
   procedure UARTInit;
 
   procedure UARTInit;
 
   begin
 
   begin
Line 40: Line 47:
  
 
===Zeichen empfangen===
 
===Zeichen empfangen===
<syntaxhighlight>
+
 
 +
<syntaxhighlight lang="pascal">
 
   function UARTReadChar: char;
 
   function UARTReadChar: char;
 
   begin
 
   begin
Line 49: Line 57:
  
 
===Zeichen senden===
 
===Zeichen senden===
<syntaxhighlight>
+
 
 +
<syntaxhighlight lang="pascal">
 
   procedure UARTSendChar(c: char);
 
   procedure UARTSendChar(c: char);
 
   begin
 
   begin
Line 58: Line 67:
  
 
===String senden===
 
===String senden===
<syntaxhighlight>
+
 
 +
<syntaxhighlight lang="pascal">
 
   procedure UARTSendString(s: ShortString);
 
   procedure UARTSendString(s: ShortString);
 
   var
 
   var
Line 70: Line 80:
  
 
===Hauptschleife===
 
===Hauptschleife===
 +
 
Hier wird gewartet, bis {{keypress|Space}} ankommt, anschliessend wir "Hello World !" gesendet.
 
Hier wird gewartet, bis {{keypress|Space}} ankommt, anschliessend wir "Hello World !" gesendet.
 +
 
<syntaxhighlight lang="pascal">program Project1;
 
<syntaxhighlight lang="pascal">program Project1;
 
begin
 
begin
Line 83: Line 95:
  
 
==ATtiny2313==
 
==ATtiny2313==
 +
 
Beim ATtiny2313 haben die Register eine andere Bezeichnung als beim ATmega328p.  
 
Beim ATtiny2313 haben die Register eine andere Bezeichnung als beim ATmega328p.  
 +
 
* Die '''0''' entfällt in der Bezeichnung.
 
* Die '''0''' entfällt in der Bezeichnung.
 
* '''UBRR0''' muss man in '''UBRRH''' und '''UBRRL''' zerlegen.
 
* '''UBRR0''' muss man in '''UBRRH''' und '''UBRRL''' zerlegen.
 +
 
Das sieht dann so aus:
 
Das sieht dann so aus:
  
Line 95: Line 110:
 
UCSRC := (%011 shl UCSZ);
 
UCSRC := (%011 shl UCSZ);
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 
(Der Code wurde nicht getestet)
 
(Der Code wurde nicht getestet)
  
 
=Interrupt=
 
=Interrupt=
 +
 
Beim Polling kann es passieren, das Zeichen verschluckt werden, dies passiert, wen ein Zeichen ankommt und man dieses nicht abholt. Aus diesem Grund empfiehlt es sich, den Zeichenempfang Interrupt gesteuert zum machen.
 
Beim Polling kann es passieren, das Zeichen verschluckt werden, dies passiert, wen ein Zeichen ankommt und man dieses nicht abholt. Aus diesem Grund empfiehlt es sich, den Zeichenempfang Interrupt gesteuert zum machen.
  
Line 104: Line 121:
 
Es wir ein einfacher Ringpuffer mit 16 Zeichen verwendet.
 
Es wir ein einfacher Ringpuffer mit 16 Zeichen verwendet.
  
==ATmega328p (Arduino uno/nano)==
+
Die Beispiele zeigen dies anhand eines ATmega328p (Arduino uno/nano).
 +
 
 +
==Empfangspuffer==
  
 
===Deklaration===
 
===Deklaration===
 +
 
Zeichenbuffer deklarieren, hier wir ein Puffer von 16 Zeichen angelegt.
 
Zeichenbuffer deklarieren, hier wir ein Puffer von 16 Zeichen angelegt.
<syntaxhighlight>
+
 
 +
<syntaxhighlight lang="pascal">
 
const
 
const
   Crxlen = 16;
+
   Crxlen = 16; // Puffergrösse
 
var
 
var
   RxBuf:  array[0..Crxlen - 1] of byte;
+
   RXBuf:  array[0..Crxlen - 1] of byte;
   rxread:  byte = 0;
+
   RXread:  byte = 0;
   rxwrite: byte = 0;     
+
   RXwrite: byte = 0;     
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
===Inzialisieren===
 
===Inzialisieren===
 +
 
Dies ist ähnlich des Polling verfahren, der Unterschied, man teilt mit '''RXCIE0''' mit, das beim Zeichenempfang ein Interrupt ausgelöst wird.
 
Dies ist ähnlich des Polling verfahren, der Unterschied, man teilt mit '''RXCIE0''' mit, das beim Zeichenempfang ein Interrupt ausgelöst wird.
<syntaxhighlight>
+
 
 +
<syntaxhighlight lang="pascal">
 
   procedure UARTInit;
 
   procedure UARTInit;
 
   begin
 
   begin
Line 131: Line 154:
  
 
===Interrupt===
 
===Interrupt===
 +
 
Bei auslösen des Interrupts, wird das Zeichen vom UART in Zeichenbuffer kopiert.
 
Bei auslösen des Interrupts, wird das Zeichen vom UART in Zeichenbuffer kopiert.
  
<syntaxhighlight>
+
<syntaxhighlight lang="pascal">
 
   procedure UART_RX_Empfang; public Name 'USART__RX_ISR'; interrupt;
 
   procedure UART_RX_Empfang; public Name 'USART__RX_ISR'; interrupt;
 
   var
 
   var
     ch: byte;
+
     b: byte;
 
   begin
 
   begin
  
 
     // Zeichen einlesen
 
     // Zeichen einlesen
     tchr := UDR0;
+
     b := UDR0;
  
 
     // Zeichen in Puffer schreiben.
 
     // Zeichen in Puffer schreiben.
     if ch <> 0 then begin
+
     if b <> 0 then begin
       RxBuf[rxwrite] := ch;
+
       RxBuf[rxwrite] := b;
       Inc(rxwrite);
+
       Inc(RXwrite);
       if rxwrite >= Crxlen then begin
+
       if RXwrite >= Crxlen then begin
         rxwrite := 0;
+
         RXwrite := 0;
 
       end;
 
       end;
 
     end;
 
     end;
Line 154: Line 178:
  
 
===Hauptschleife===
 
===Hauptschleife===
 +
 
In der Hauptschleife wird geprüft, ob sich etwas im Empfangspuffer, wen ja, wird ein Zeichen davon ausgelesen.
 
In der Hauptschleife wird geprüft, ob sich etwas im Empfangspuffer, wen ja, wird ein Zeichen davon ausgelesen.
 
In diesem Beispiel werden, die empfangenen Zeichen in Grossbuchstaben umgewandelt und anschiessend wieder ausgegeben.
 
In diesem Beispiel werden, die empfangenen Zeichen in Grossbuchstaben umgewandelt und anschiessend wieder ausgegeben.
  
<syntaxhighlight>
+
<syntaxhighlight lang="pascal">
 
begin
 
begin
   // UART inizialisieren, die Interrupt gesteuert.
+
   // UART inizialisieren, die Interrupt gesteuert ist.
 
   UARTInit;
 
   UARTInit;
  
Line 169: Line 194:
  
 
     // Ist ein Zeichen im Puffer ?
 
     // Ist ein Zeichen im Puffer ?
     while rxwrite <> rxread do begin
+
     while RXwrite <> rxread do begin
  
 
       // Interrupt sperren.
 
       // Interrupt sperren.
Line 175: Line 200:
  
 
       // Zeichen aus Puffer lesen
 
       // Zeichen aus Puffer lesen
       ch := RxBuf[rxread];
+
       ch := RXBuf[rxread];
       Inc(rxread);
+
       Inc(RXread);
       if rxread >= Crxlen then begin
+
       if RXread >= Crxlen then begin
         rxread := 0;
+
         RXread := 0;
 
       end;
 
       end;
  
Line 193: Line 218:
 
     end;
 
     end;
 
   until 1 = 2;
 
   until 1 = 2;
 +
end.
 +
</syntaxhighlight>
 +
 +
==Sendepuffer==
 +
 +
===Deklaration===
 +
 +
Sendepuffer deklarieren, hier wir ein Puffer von 16 Zeichen angelegt.
 +
 +
<syntaxhighlight lang="pascal">
 +
const
 +
  CTXlen = 16; // Puffergrösse
 +
var
 +
  TXBuf:  array[0..CTXlen - 1] of byte;
 +
  TXread:  byte = 0;
 +
  TXwrite: byte = 0;
 +
</syntaxhighlight>
 +
 +
===Inzialisieren===
 +
 +
Dies ist ähnlich des Empfangsbuffer verfahren, wichtig dabei, die Interrupt-Funktion darf noch '''nicht''' aktiviert werden, da es am Anfang noch nichts zu senden gibt.
 +
 +
<syntaxhighlight lang="pascal">
 +
  procedure UARTInit;
 +
  begin
 +
    UBRR0 := teiler;
 +
 +
    UCSR0A := (0 shl U2X0);
 +
    UCSR0B := (1 shl TXEN0) or (1 shl RXEN0);
 +
    UCSR0C := %011 shl UCSZ0;
 +
  end;
 +
</syntaxhighlight>
 +
 +
===Interrupt===
 +
 +
Der Interrupts, wird ausgelöst, sobald das Zeichen im UART gesendet wurde.
 +
 +
Sobald der Ringpuffer leer ist, wird die Interrupt-Funktion wider deaktiviert.
 +
 +
Die Abfrage von '''UDRE0''' wird hier nicht gebraucht, da der Interrupt nur ausgelöst wird, wen der Puffer leer ist.
 +
 +
<syntaxhighlight lang="pascal">
 +
  procedure UART_UDRE_Senden; public Name 'USART__UDRE_ISR'; interrupt;
 +
  begin
 +
    // Zeichen Senden.
 +
    UDR0 := TXBuf[TXread];
 +
   
 +
    // Ringpuffer verschieben.
 +
    Inc(TXread);
 +
 +
    // Wen Zeiger am Ende, wieder von vorn beginnen.
 +
    if TXread >= CTXlen then begin
 +
      TXread := 0;
 +
    end;
 +
 +
    // Ist Ringpuffer leer, kein Interrupt mehr nötig-
 +
    if TXread = TXwrite then begin
 +
      UCSR0B := UCSR0B and not (1 shl UDRIE0);
 +
    end;
 +
  end;
 +
</syntaxhighlight>
 +
 +
===String senden===
 +
 +
Sobald sich etwas im Rindpuffer befindet, wird der Interrupt weder aktiviert.
 +
 +
<syntaxhighlight lang="pascal">
 +
  procedure UARTSendString(s: ShortString);
 +
  var
 +
    i: byte;
 +
  begin
 +
 +
    // Ringpuffer mit Daten befüllen,
 +
    for i := 1 to Length(s) do begin
 +
      TXBuf[TXwrite] := byte(s[i]);
 +
      Inc(TXwrite);
 +
      if TXwrite >= CTXlen then begin
 +
        TXwrite := 0;
 +
      end;
 +
    end;
 +
 +
    // Sende Interrupt aktivieren.
 +
    UCSR0B := UCSR0B or (1 shl UDRIE0);
 +
  end;
 +
</syntaxhighlight>
 +
 +
===Hauptschleife===
 +
 +
Der String wird extra ein wenig koatisch  ausgegeben, dies sollte zeigen, das die Ausgabe im Hintergrund abläuft.
 +
Im Beispiel, blinkt sogar eine LED während der Ausgabe.
 +
 +
<syntaxhighlight lang="pascal">
 +
var
 +
  z: byte;
 +
  s: ShortString;
 +
begin
 +
  DDRB := DDRB or (1 shl 5);
 +
  // UART inizialisieren, die Interrupt gesteuert.
 +
  UARTInit;
 +
 +
  // Interrupt aktivieren.
 +
  asm Sei end;
 +
 +
  // Hauptschleife.
 +
  repeat
 +
    UARTSendString('Hello World');
 +
    UARTSendString(' !'#13#10);
 +
 +
    PORTB := PORTB or (1 shl 5);
 +
    mysleep(sl);
 +
 +
    Str(z: 3, s);
 +
    UARTSendString(s + '. ');
 +
    Inc(z);
 +
 +
    PORTB := PORTB and not (1 shl 5);
 +
    mysleep(sl);
 +
 +
  until 1 = 2;
 +
end.
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
= Software - UART / UART - Emulation =
 
= Software - UART / UART - Emulation =
Es ist auch möglich eine UART-Schnittstelle per Software zu emulieren.<br>
+
 
Dies ist praktisch, wen man einen AVR hat, welcher keinen/zuwenig Hardware-UART hat.<br>
+
Es ist auch möglich eine UART-Schnittstelle per Software zu emulieren.
<br>
+
 
'''Hinweis:''' Dieser Code funktioniert nur mit '''16MHz''' CPU-Takt und '''9600Baud'''. Einfach '''Baud''' und '''CPU_Clock''' anzupassen reicht nicht, weil die Formel noch einen Fehler hat.<br>
+
Dies ist praktisch, wen man einen AVR hat, welcher keinen/zuwenig Hardware-UART hat.
'''Wichtig:''' Während der Übertragung, müssen Interrupts gesperrt werden.<br>
+
 
Gleichzeigtes lesen und schreiben funktioniert nicht, da kein Puffer vorhanden ist.<br>
+
'''Hinweis:''' Dieser Code funktioniert nur mit '''16MHz''' CPU-Takt und '''9600Baud'''. Einfach '''Baud''' und '''CPU_Clock''' anzupassen reicht nicht, weil die Formel noch einen Fehler hat.
 +
 
 +
'''Wichtig:''' Während der Übertragung, müssen Interrupts gesperrt werden.
 +
 
 +
Gleichzeigtes lesen und schreiben funktioniert nicht, da kein Puffer vorhanden ist.
 +
 
 
Im Idealfall ist dies gut verwendbar, um Fehler auf ein UART-Terminal auszugeben.
 
Im Idealfall ist dies gut verwendbar, um Fehler auf ein UART-Terminal auszugeben.
  
 
== Konstanten ==
 
== Konstanten ==
 +
 
In diesem Beispiele werden die Pin 2 & 3 von PortD verwendet.
 
In diesem Beispiele werden die Pin 2 & 3 von PortD verwendet.
<syntaxhighlight>
+
 
 +
<syntaxhighlight lang="pascal">
 
const
 
const
 
   CPU_Clock = 16000000;  // Taktfrequenz Arduino, default 16MHz.
 
   CPU_Clock = 16000000;  // Taktfrequenz Arduino, default 16MHz.
Line 216: Line 368:
 
   SoftTeiler = CPU_Clock div (Baud * 22); // Bitzeitzähler
 
   SoftTeiler = CPU_Clock div (Baud * 22); // Bitzeitzähler
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 
Den Empfangs-Pin auf Input stellen.
 
Den Empfangs-Pin auf Input stellen.
<syntaxhighlight>
+
 
 +
<syntaxhighlight lang="pascal">
 
begin
 
begin
   DDRD   := DDRD or TXpin;
+
   DDRD := DDRD or TXpin;
 
   ...
 
   ...
 
   // Loop
 
   // Loop
Line 227: Line 381:
  
 
'''Hinweis:''' Will man die gleichen Pins wie bei der Hardware-UART verwenden, muss beim Arduino der Pin '''0 '''& '''1''' frei gegeben werden.
 
'''Hinweis:''' Will man die gleichen Pins wie bei der Hardware-UART verwenden, muss beim Arduino der Pin '''0 '''& '''1''' frei gegeben werden.
<syntaxhighlight>
+
 
 +
<syntaxhighlight lang="pascal">
 
begin
 
begin
 
   UCSR0B := 0; // Blockierter Pin 0 & 1 frei geben
 
   UCSR0B := 0; // Blockierter Pin 0 & 1 frei geben
Line 237: Line 392:
  
 
== Zeichen senden ==
 
== Zeichen senden ==
<syntaxhighlight>
+
 
 +
<syntaxhighlight lang="pascal">
 
   procedure UARTSoftSendByte(c: byte);
 
   procedure UARTSoftSendByte(c: byte);
 
   var
 
   var
Line 258: Line 414:
  
 
== Zeichen empfangen ==
 
== Zeichen empfangen ==
 +
 
'''Hinweis:''' Wen kein Zeichen im Empfang kommt, hängt die Schleife im Endlosen.
 
'''Hinweis:''' Wen kein Zeichen im Empfang kommt, hängt die Schleife im Endlosen.
<syntaxhighlight>
+
 
 +
<syntaxhighlight lang="pascal">
 
   function UARTSoftReadByte: byte;
 
   function UARTSoftReadByte: byte;
 
   var
 
   var
Line 281: Line 439:
  
  
== Siehe auch ==
+
= Siehe auch =
* Übersichtseite [[AVR Embedded Tutorial/de]]
+
 
 +
* Übersichtseite [[AVR Embedded Tutorial/de|AVR Embedded Tutorial]]
  
 
Autor: [[User:Mathias|Mathias]]
 
Autor: [[User:Mathias|Mathias]]
  
[[Category:Embedded]]
+
[[Category:AVR/de]]
[[Category:AVR]]
+
[[Category:Arduino/de]]
[[Category:Arduino]]
+
[[Category:Embedded/de]]
 +
[[Category:Tutorials/de]]
 +
{{AutoCategory}}

Revision as of 07:40, 25 January 2020

Deutsch (de) English (en)

UART Polling

ATmega328p (Arduino uno/nano)

Für die serielle Ausgabe wird ein Terminal mit folgenden Einstellungen gebraucht:

Baud Rate 9600
Bits 8
Stopbits 1
Parity none

Beim Betätigen von Space im Terminal-Programm, sollte ein "Hello World !" zurück kommen.

Konstanten für Inizialisierung

const
  CPU_Clock = 16000000; // Taktfrequenz Arduino, default 16MHz.
  Baud      = 9600;     // Baudrate
  Teiler    = CPU_Clock div (16 * Baud) - 1;

Hinweis: Wen den der CPU-Clock zu klein ist in die Baudrate zu hoch, gibt es Übertragungsfehler. Beispiel: Clock 1MHz, Baud 9600 geht nicht. Aber 1MHz und Baud 1200 gehen.

Inizialisieren

Die UART-Schnittstelle konfigurierieren.

  procedure UARTInit;
  begin
    UBRR0  := Teiler;                          // Baud

    UCSR0A := 0                               // Normale Geschwindigkeit
    UCSR0B := (1 shl TXEN0) or (1 shl RXEN0); // Empfangen und Senden
    UCSR0C := (%011 shl UCSZ0);               // 8-Bit Übertragung, 1 Stop-Bits.
  end;

Zeichen empfangen

  function UARTReadChar: char;
  begin
    while UCSR0A and (1 shl RXC0) = 0 do; // Warten, bis Zeichen ankommt.
    Result := char(UDR0);                 // Zeichen einlesen. 
  end;

Zeichen senden

  procedure UARTSendChar(c: char);
  begin
    while UCSR0A and (1 shl UDRE0) = 0 do; // Warten, bis letztes Zeichen gesendet.
    UDR0 := byte(c);                       // Zeichen senden. 
  end;

String senden

  procedure UARTSendString(s: ShortString);
  var
    i: integer;
  begin
    for i := 1 to length(s) do begin
      UARTSendChar(s[i]);   // Zeichen einzeln senden.
    end;
  end;

Hauptschleife

Hier wird gewartet, bis Space ankommt, anschliessend wir "Hello World !" gesendet.

program Project1;
begin
  UARTInit;
  repeat
    if UARTReadChar = #32 then begin         // #32 = space
      UARTSendString('Hello World !'#13#10); 
    end;
  until 1 = 2;
end.

ATtiny2313

Beim ATtiny2313 haben die Register eine andere Bezeichnung als beim ATmega328p.

  • Die 0 entfällt in der Bezeichnung.
  • UBRR0 muss man in UBRRH und UBRRL zerlegen.

Das sieht dann so aus:

UBRRH := Teiler shr 8;
UBRRL := Byte(Teiler);

UCSRB := (1 shl TXEN) or (1 shl RXEN);
UCSRC := (%011 shl UCSZ);

(Der Code wurde nicht getestet)

Interrupt

Beim Polling kann es passieren, das Zeichen verschluckt werden, dies passiert, wen ein Zeichen ankommt und man dieses nicht abholt. Aus diesem Grund empfiehlt es sich, den Zeichenempfang Interrupt gesteuert zum machen.

Bei höheren Baudraten, ist dies zwingend.

Es wir ein einfacher Ringpuffer mit 16 Zeichen verwendet.

Die Beispiele zeigen dies anhand eines ATmega328p (Arduino uno/nano).

Empfangspuffer

Deklaration

Zeichenbuffer deklarieren, hier wir ein Puffer von 16 Zeichen angelegt.

const
  Crxlen = 16; // Puffergrösse
var
  RXBuf:   array[0..Crxlen - 1] of byte;
  RXread:  byte = 0;
  RXwrite: byte = 0;

Inzialisieren

Dies ist ähnlich des Polling verfahren, der Unterschied, man teilt mit RXCIE0 mit, das beim Zeichenempfang ein Interrupt ausgelöst wird.

  procedure UARTInit;
  begin
    UBRR0  := teiler;

    UCSR0A := (0 shl U2X0);
    UCSR0B := (1 shl TXEN0) or (1 shl RXEN0) or (1 shl RXCIE0);
    UCSR0C := %011 shl UCSZ0;
  end;

Interrupt

Bei auslösen des Interrupts, wird das Zeichen vom UART in Zeichenbuffer kopiert.

  procedure UART_RX_Empfang; public Name 'USART__RX_ISR'; interrupt;
  var
    b: byte;
  begin

    // Zeichen einlesen
    b := UDR0;

    // Zeichen in Puffer schreiben.
    if b <> 0 then begin
      RxBuf[rxwrite] := b;
      Inc(RXwrite);
      if RXwrite >= Crxlen then begin
        RXwrite := 0;
      end;
    end;
  end;

Hauptschleife

In der Hauptschleife wird geprüft, ob sich etwas im Empfangspuffer, wen ja, wird ein Zeichen davon ausgelesen. In diesem Beispiel werden, die empfangenen Zeichen in Grossbuchstaben umgewandelt und anschiessend wieder ausgegeben.

begin
  // UART inizialisieren, die Interrupt gesteuert ist.
  UARTInit;

  // Interrupt aktivieren.
  asm sei end;

  // Hauptschleife.
  repeat

    // Ist ein Zeichen im Puffer ?
    while RXwrite <> rxread do begin

      // Interrupt sperren.
      asm cli end;

      // Zeichen aus Puffer lesen
      ch := RXBuf[rxread];
      Inc(RXread);
      if RXread >= Crxlen then begin
        RXread := 0;
      end;

      // Interrupt aktivieren. 
      asm sei end;

      // Wen kleiner Buchstaben, diesen in einen grossen umwandeln.       
      if ch in [$61..$7A] then begin
        Dec(ch, 32);
      end;

      // Zeichen ausgeben.
      UARTSendChar(char(ch));
    end;
  until 1 = 2;
end.

Sendepuffer

Deklaration

Sendepuffer deklarieren, hier wir ein Puffer von 16 Zeichen angelegt.

const
  CTXlen = 16; // Puffergrösse
var
  TXBuf:   array[0..CTXlen - 1] of byte;
  TXread:  byte = 0;
  TXwrite: byte = 0;

Inzialisieren

Dies ist ähnlich des Empfangsbuffer verfahren, wichtig dabei, die Interrupt-Funktion darf noch nicht aktiviert werden, da es am Anfang noch nichts zu senden gibt.

  procedure UARTInit;
  begin
    UBRR0 := teiler;

    UCSR0A := (0 shl U2X0);
    UCSR0B := (1 shl TXEN0) or (1 shl RXEN0);
    UCSR0C := %011 shl UCSZ0;
  end;

Interrupt

Der Interrupts, wird ausgelöst, sobald das Zeichen im UART gesendet wurde.

Sobald der Ringpuffer leer ist, wird die Interrupt-Funktion wider deaktiviert.

Die Abfrage von UDRE0 wird hier nicht gebraucht, da der Interrupt nur ausgelöst wird, wen der Puffer leer ist.

  procedure UART_UDRE_Senden; public Name 'USART__UDRE_ISR'; interrupt;
  begin
    // Zeichen Senden.
    UDR0 := TXBuf[TXread];
    
    // Ringpuffer verschieben.
    Inc(TXread);

    // Wen Zeiger am Ende, wieder von vorn beginnen.
    if TXread >= CTXlen then begin
      TXread := 0;
    end;

    // Ist Ringpuffer leer, kein Interrupt mehr nötig-
    if TXread = TXwrite then begin
      UCSR0B := UCSR0B and not (1 shl UDRIE0);
    end;
  end;

String senden

Sobald sich etwas im Rindpuffer befindet, wird der Interrupt weder aktiviert.

  procedure UARTSendString(s: ShortString);
  var
    i: byte;
  begin

    // Ringpuffer mit Daten befüllen,
    for i := 1 to Length(s) do begin
      TXBuf[TXwrite] := byte(s[i]);
      Inc(TXwrite);
      if TXwrite >= CTXlen then begin
        TXwrite := 0;
      end;
    end;

    // Sende Interrupt aktivieren.
    UCSR0B := UCSR0B or (1 shl UDRIE0);
  end;

Hauptschleife

Der String wird extra ein wenig koatisch ausgegeben, dies sollte zeigen, das die Ausgabe im Hintergrund abläuft. Im Beispiel, blinkt sogar eine LED während der Ausgabe.

var
  z: byte;
  s: ShortString;
begin
  DDRB := DDRB or (1 shl 5);
  // UART inizialisieren, die Interrupt gesteuert.
  UARTInit;

  // Interrupt aktivieren.
  asm Sei end;

  // Hauptschleife.
  repeat
    UARTSendString('Hello World');
    UARTSendString(' !'#13#10);

    PORTB := PORTB or (1 shl 5);
    mysleep(sl);

    Str(z: 3, s);
    UARTSendString(s + '. ');
    Inc(z);

    PORTB := PORTB and not (1 shl 5);
    mysleep(sl);

  until 1 = 2;
end.

Software - UART / UART - Emulation

Es ist auch möglich eine UART-Schnittstelle per Software zu emulieren.

Dies ist praktisch, wen man einen AVR hat, welcher keinen/zuwenig Hardware-UART hat.

Hinweis: Dieser Code funktioniert nur mit 16MHz CPU-Takt und 9600Baud. Einfach Baud und CPU_Clock anzupassen reicht nicht, weil die Formel noch einen Fehler hat.

Wichtig: Während der Übertragung, müssen Interrupts gesperrt werden.

Gleichzeigtes lesen und schreiben funktioniert nicht, da kein Puffer vorhanden ist.

Im Idealfall ist dies gut verwendbar, um Fehler auf ein UART-Terminal auszugeben.

Konstanten

In diesem Beispiele werden die Pin 2 & 3 von PortD verwendet.

const
  CPU_Clock = 16000000;   // Taktfrequenz Arduino, default 16MHz.
  Baud      = 9600;       // Baudrate

  TXpin     = (1 shl 3);  // Pin 3 ( Empfangen )
  RXpin     = (1 shl 2);  // Pin 2 ( Senden )

  SoftTeiler = CPU_Clock div (Baud * 22); // Bitzeitzähler

Den Empfangs-Pin auf Input stellen.

begin
  DDRD := DDRD or TXpin;
  ...
  // Loop
  ...
end.

Hinweis: Will man die gleichen Pins wie bei der Hardware-UART verwenden, muss beim Arduino der Pin 0 & 1 frei gegeben werden.

begin
  UCSR0B := 0; // Blockierter Pin 0 & 1 frei geben
  ...
  // Loop
  ...
end.

Zeichen senden

  procedure UARTSoftSendByte(c: byte);
  var
    i, Data: int16;
    j:       Int8;
  begin
    asm Cli end; // Interrupt sperren 
    Data := (c shl 1) or $FE00;
    for j := 0 to 9 do begin          // Bit ausgeben, inklusiv Stop-Bits.
      if (Data and 1) = 1 then 
        PORTD := PORTD or TXpin;      // HIGH
      else 
        PORTD := PORTD and not TXpin; // LOW
      Data := Data shr 1;
      for i := 0 to SoftTeiler do;    // Ein Takt Pause
    end;
    asm Sei end; // Interrupt erlauben
  end;

Zeichen empfangen

Hinweis: Wen kein Zeichen im Empfang kommt, hängt die Schleife im Endlosen.

  function UARTSoftReadByte: byte;
  var
    i: Int16;
    j: Int8;
  begin
    asm Cli end;                       // Interrupt sperren 
    Result := 0;
    while PIND and RXpin > 0 do;       // Warten bis erste fallende Flanke
    for i := 0 to SoftTeiler div 2 do; // Ein halber Takt Pause
    for j := 0 to 7 do begin
      for i := 0 to SoftTeiler do;     // Ein Takt Pause
      Result := Result shr 1;
      if PIND and RXpin <> 0 then 
        Inc(Result, $80);
    end;
    for i := 0 to SoftTeiler;          // Ein Takt Pause
    asm Sei end;                       // Interrupt erlauben
  end;


Siehe auch

Autor: Mathias