Sign in Join
Wiki copytemporary

Revision 2 of 3, by WikiArchive on 11-4-2026. The page as it is now.

AVR Embedded Tutorial - UART/de

Contents

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

pascal
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.

pascal
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

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

Zeichen senden

pascal
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

pascal
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.

pascal
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.

Das sieht dann so aus:

pascal
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.

pascal
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.

pascal
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.

pascal
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.

pascal
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.

pascal
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.

pascal
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.

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;

String senden

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

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;

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.

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.

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.

pascal
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.

pascal
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.

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

Zeichen senden

pascal
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.

pascal
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


From the Free Pascal wiki, page "AVR Embedded Tutorial - UART/de", as the Internet Archive kept it on 2023-03-20. Copyright stays with the author who wrote it, according to the old wiki's Licensing page. If you have concerns about this material, message the admin.