Sign in Join
Wiki copytemporary

AVR Embedded Tutorial - UART

Deutsch · English

UART polling

ATmega328p (Arduino Uno/Nano)

A terminal with the following settings is required for serial output:

Setting Value
Baud Rate 9600
Bits 8
Stopbits 1
Parity none

When pressing the Space Bar in the terminal program, a "Hello World!" message is returned.

Constants

pascal
 const
   CPU_Clock = 16000000;  // Arduino clock frequency, default 16MHz.
   Baud = 9600;           // baud rate
   Divider = CPU_Clock div(16 * baud) - 1;

Note: If the CPU clock is too slow for the baud rate, there are transmission errors. Example: With a clock of 1MHz, a baud rate of 9600 does not work. However, a 1MHz clock and 1200 baud will work.

Initialization

Configure the UART interface.

pascal
 procedure UARTInit;
   begin
     UBRR0  := divider;                         // Baud
     UCSR0A := 0;                               // Normal speed
     UCSR0B := (1 shl TXEN0) or (1 shl RXEN0);  // Receive and send
     UCSR0C := (%011 shl UCSZ0);                // 8-bit word, 1 stop bit
   end;

Receive characters

pascal
  function UARTReadChar : char;
   begin
     while UCSR0A and (1 shl RXC0) = 0 do
       ;                                        // Wait for a character to arrive
     Result := char(UDR0);                      // Read character
   end;

Send characters

pascal
 procedure UARTSendChar(c : char);
   begin
     while UCSR0A and (1 shl UDRE0) = 0 do
       ;                                        // Wait for the last character to be sent
     UDR0 := byte(c);                           // Send character
   end;

Send string

pascal
 procedure UARTSendString(s : ShortString);
   var
     i : integer;
   begin
     for i := 1 to Length(s) do 
       begin
         UARTSendChar(s[i]);  // send characters one by one
       end;
   end;

Main loop

Here you wait until the space character arrives, then transmit "Hello World!".

pascal
program Project1;
 begin
   UARTInit;

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

ATtiny2313

The registers on the ATtiny2313 have different names than on the ATmega328p.

It will look like this:

pascal
 UBRRH := divider shr 8;
 UBRRL := byte(divider);

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

(The code has not been tested)

UART interrupt

With UART polling, a character may be lost. This can happen when a character arrives and you cannot pick it up in time. For this reason it is advisable to have the reception of characters controlled by an interrupt. At higher baud rates, this is mandatory.

A simple 16-character ring buffer is used.

The examples show this using an ATmega328p (Arduino Uno/Nano).

Receive buffer

Declaration

Declare a character buffer: here a 16 character buffer is created.

pascal
 const
   Crxlen = 16;    // Buffer size
 var
   RXBuf   : array[0..Crxlen - 1] of byte;
   RXread  : byte = 0;
   RXwrite : byte = 0;

Initialization

This is similar to polling, the difference is that RXCIE0 is used to indicate that an interrupt is triggered when a character is received.

pascal
 procedure UARTInit;
   begin
     UBRR0  := divider;
     UCSR0A := (0 shl U2X0);
     UCSR0B := (1 shl TXEN0) or (1 shl RXEN0) or (1 shl RXCIE0);
     UCSR0C := %011 shl UCSZ0;
   end;

Interrupt

When the interrupt is triggered, the character is copied from the UART into the character buffer.

pascal
 procedure UART_RX_Receiving; public name 'USART__RX_ISR'; interrupt;
   var
     b : byte;
   begin
     // Read characters
     b := UDR0;

     // Write characters to buffer
     if b <> 0 then 
       begin
         RxBuf[Rxwrite] := b;
         Inc(RXwrite);

         if RXwrite >= Crxlen then 
           begin
             RXwrite := 0;
           end;
       end;
   end;

Main loop

The main loop checks whether something is in the receive buffer, and if so, a character is read from it. In this example, the characters received are converted to uppercase letters and then output.

pascal
 begin
   // Initialize UART which is interrupt controlled
   UARTInit;

   // Enable interrupts
   asm
     sei
   END;

   // Main loop
   repeat

     // Is there a character in the buffer?
     while RXwrite <> Rxread do 
       begin
         // Disable interrupts
         asm 
           cli
         END;

         // Read character from buffer
         ch := RXBuf[rxread];
         Inc(RXread);

         if RXread >= Crxlen then 
           begin
             RXread := 0;
           end;

         // Enable interrupts
         asm 
           sei
         END;

         // Convert lowercase letters, into uppercase ones
         if ch in[$61..$7A] then 
           begin
             Dec(ch, 32);
           end;

         // Output character
         UARTSendChar(char(ch));
       end;
   until 1 = 2;
 end.

Send buffer

Declaration

Declare a send buffer: here a 16 character buffer is created.

pascal
 const
   CTXlen = 16;    // Buffer size
 var
   TXBuf   : array[0..CTXlen - 1] of byte;
   TXread  : byte = 0;
   TXwrite : byte = 0;

Initializarion

This is similar to the receive buffer. Important: the interrupt function must not be activated yet, because there is nothing to send yet.

pascal
 procedure UARTInit;
   begin
     UBRR0  := divider;
     UCSR0A := (0 shl U2X0);
     UCSR0B := (1 shl TXEN0) or (1 shl RXEN0);
     UCSR0C := %011 shl UCSZ0;
   end;

Interrupt

The interrupt is triggered as soon as a character has been sent to the UART.

As soon as the ring buffer is empty, the interrupt function is deactivated again.

The query of UDRE0 is not needed here, since the interrupt is only triggered when the buffer is empty.

pascal
 procedure UART_UDRE_Send; public name 'USART__UDRE_ISR'; interrupt;
   begin
     // Send character
     UDR0 := TXBuf[TXread];
    
     // Move ring buffer
     Inc(TXread);

     // When pointer is at the end, start again
     if TXread >= CTXlen then 
       begin
         TXread :=0;
       end;

     // If the ring buffer is empty, an interrupt is no longer necessary
     if TXread = TXwrite then 
       begin
         UCSR0B := UCSR0B and not (1 shl UDRIE0);
       end;
   end;

Send string

As soon as something is in the send buffer, the interrupt is not activated.

pascal
 procedure UARTSendString(s : ShortString);
   var
     i : byte;
   begin
     // Fill the ring buffer with data
     for i := 1 to Length(s) do 
       begin
         TXBuf[TXwrite] := byte(s[i]);
         Inc(TXwrite);
         
         if TXwrite >= CTXlen then 
           begin
             TXwrite := 0;
           end;
     end;

     // Enable send interrupt
     UCSR0B := UCSR0B or (1 shl UDRIE0);
   end;

Main loop

The string is output in the background. In the example, an LED flashes during output.

pascal
 var
   z : byte;
   s : ShortString;
 begin
   DDRB := DDRB or (1 shl 5);

   // Initialize UART that is interrupt controlled
   UARTInit;

   // Enable interrupts
   asm 
     sei
   END;

   // Main loop
   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.

UART emulation in software

It is also possible to emulate a UART interface using software.

This is practical if you have an AVR which has no/too few hardware UARTs.

Note: This code only works with a 16MHz CPU clock and 9600 baud. Simply adjusting baud and CPU_Clock is not enough because the formula still has an error.

Important: Interrupts must be blocked during the transmission.

Reading and writing at the same time does not work because there is no buffer.

Ideally, this can be used to output errors to a UART terminal.

Constants

In this example, Pins 2 and 3 from Port D are used.

pascal
 const
   CPU_Clock = 16000000; // Arduino clock frequency, default 16MHz.
   Baud = 9600;          // Baud rate

   TXpin = (1 shl 3);    // Pin 3 (receive)
   RXpin = (1 shl 2);    // Pin 2 (send)

   softDivider = CPU_Clock div(Baud * 22);  // bit counter

Set the receive pin to input

pascal
 begin
   DDRD := DDRD or TXpin;
   ...
   // loop
   ...
 end.

Note: If you want to use the same pins as for the hardware UART, pins 0 and 1 must be enabled on the Arduino.

pascal
 begin
   UCSR0B := 0;  // Release blocked pins 0 and 1
   ...
   // loop
   ...
 end.

Send characters

pascal
 procedure UARTSoftSendByte(c : byte);
   var
     i, data : int16;
     j : Int8;
   begin
     asm// Disable interrupts 
       cli  // Disable interrupts
     END;  

     Data := (c shl 1) or $FE00;
 
     for j := 0 to 9 do  // Output bits, including stop bit
       begin
         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 softDivider do    // One bit break
           ;  
       end;

     asm 
       sei
     END;  // Enable interrupts
   end;

Receive characters

Note: If no character is received, the loop hangs forever.

pascal
  function UARTSoftReadByte : byte;
   var
     i : Int16;
     j : Int8;
   begin
     asm// Disable interrupts 
       cli   // Disable interrupts
     END;

     Result := 0;

     while PIND and RXpin > 0 do  // Wait until the first falling edge
       ;  

     for i := 0 to softDivider div 2 do  // Half a bit break
       ;

     for j : = 0 to 7 do 
       begin
         for i := 0 to softDivider do    // One bit break
           ;
       
         Result := Result shr 1;

         if PIND and RXpin <> 0 then 
           Inc(Result, $80);
       end;

     for i := 0 to softDivider           // One bit break
       ;

     asm// Enable interrupts 
       sei   // Enable interrupts
     END;
   end;

See also


From the Free Pascal wiki, page "AVR Embedded Tutorial - UART", as it stood on 2022-01-07 after a change by VamLykTan, out of the wiki's dump of 2024-05-26. 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.

Revision 2, last changed by WikiArchive .

Talk about this page

Nobody has opened a bench for this page yet. Sign in to open one.