How does UART transfer data out of the TX pins?

Go To Last Post
11 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

My question is manly regarding this function in ArduinoCore-avr:

 

https://github.com/arduino/Ardui...

 

size_t HardwareSerial::write(uint8_t c)

I can see that it moves the input character into the buffer:

_tx_buffer[_tx_buffer_head] = c;

2 questions arise:

 

1- What is the tx buffer? I'm using this page to read over the details of UART:

http://www.avrbeginners.net/arch...

 

Is the tx buffer the same the UDR Data Register? or is it just a temp location in memory?

 

2- How is the data actually transferred out the pin?

 

In code I can see this snippet at the very end:

sbi(*_ucsrb, UDRIE0);

Which basically sets the "UDRIE" pin to 1. Above link has this explanation:

 

 If this bit is set, an interrupt occurs if UDR is empty. That allows writing the next byte to UDR while the currently being sent byte is still in the shift register.

I still don't fully get it. So setting UDRIE to 1 makes data in "_tx_buffer" move into UDR? then from there how does it move to the shift register?

Then once it is moved into the shift register does it atomically get transmitted on each clock cycle?

 

This topic has a solution.
Last Edited: Wed. Feb 3, 2021 - 02:06 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avruser1523 wrote:
How is the data actually transferred out the pin?

One bit at a time! angel

 

Examine how well known USART code is written, either the Arduino or Peter Fleury's, basically, the putc() function places data into a ram buffer, and enable the UDRE interrupt.

Then the UDRE ISR will place characters from the buffer to the UDR register until the buffer is empty and disables the UDRE interrupt.  Easy Peasy!

 

Jim

edit putchar() changed to putc()

 

FF = PI > S.E.T

 

Last Edited: Mon. Feb 1, 2021 - 03:56 PM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avruser1523 wrote:
Is the tx buffer the same the UDR Data Register?

No - the UDR is part of the microcontroller hardware; _tx_buffer is just a variable defined in the code

 

avruser1523 wrote:
or is it just a temp location in memory?

yes.

 

How is the data actually transferred out the pin?

by writing it, one byte at a time, to UDR.

 

The hardware process of how data gets from UDR to the physical pin is described in the AVR datasheet.

 

avruser1523 wrote:
setting UDRIE to 1 makes data in "_tx_buffer" move into UDR?

No. Setting it allows an interrupt to occur when UDR is ready.

 

When the interrupt occurs, the Interrupt Service Routine (ISR) is called; it is code in that ISR which puts the data into UDR

 

 

 

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I see, so when UDR is empty an interrupt occurs. Processor halts current execution and jumps to the interrupt handler which basically puts "_tx_buffer" into the UDR register.

 

So from there how does UDR data is transferred to the Transmit Shift Register?

 

And then from there how does it get transmitted out the port, 1 bit at a time?

 

Are the last 2 steps done per clock cycle automatically?

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


avruser1523 wrote:
Are the last 2 steps done per clock cycle automatically?

Yes as shown in the datasheet:

the hardware handles the data once it's in the UDR reg.

Jim

 

 

FF = PI > S.E.T

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You picked one of the most complex UART drivers to examine. Can I suggest something like Peter Fleury's UART lib as an easier starting point?

 

code: http://www.peterfleury.epizy.com...

docs: http://www.peterfleury.epizy.com...

 

The journey out in the Fleury code starts with:

void uart_putc(unsigned char data)
{
    unsigned char tmphead;

    
    tmphead  = (UART_TxHead + 1) & UART_TX_BUFFER_MASK;
    
    while ( tmphead == UART_TxTail ){
        ;/* wait for free space in buffer */
    }
    
    UART_TxBuf[tmphead] = data;
    UART_TxHead = tmphead;

    /* enable UDRE interrupt */
    UART0_CONTROL    |= _BV(UART0_UDRIE);

}/* uart_putc */

That just puts a character into the output buffer but then, as a last act it set UDRIE in the UART control register. That in itself is a synonym that depends on which AVR is being used:

D:\fleury_uart>grep -w UART0_CONTROL uart.c
 #define UART0_CONTROL     UCR
 #define UART0_CONTROL     UCSRB
 #define UART0_CONTROL     UCSRB
 #define UART0_CONTROL     UCSRB
 #define UART0_CONTROL     UCSRB
 #define UART0_CONTROL     UCSRB
 #define UART0_CONTROL     UCSR0B
 #define UART0_CONTROL     UCSRB
 #define UART0_CONTROL     UCSR0B
 #define UART0_CONTROL     UCSRB
 #define UART0_CONTROL     UCSR0B
 #define UART0_CONTROL     UCSR0B
 #define UART0_CONTROL     UCSR1B

If the UDR is already empty that will immediately raise the flag to trigger an interrupt but if there was a character just written and it's waiting for the transmit shift register to clear a previous one it may be some time before the interrupt flag is raised. When it is raised it will trigger the ISR() handler:

ISR (UART0_TRANSMIT_INTERRUPT)
/*************************************************************************
Function: UART Data Register Empty interrupt
Purpose:  called when the UART is ready to transmit the next byte
**************************************************************************/
{
    unsigned char tmptail;

    
    if ( UART_TxHead != UART_TxTail) {
        /* calculate and store new buffer index */
        tmptail = (UART_TxTail + 1) & UART_TX_BUFFER_MASK;
        UART_TxTail = tmptail;
        /* get one byte from buffer and write it to UART */
        UART0_DATA = UART_TxBuf[tmptail];  /* start transmission */
    }else{
        /* tx buffer empty, disable UDRE interrupt */
        UART0_CONTROL &= ~_BV(UART0_UDRIE);
    }
}

That then plucks the oldest character in the holding buffer out and writes it to the UDR register (again covered by synonym to support multiple devices) but if the buffer is now empty it disables the UDRIE so that this ISR will not continually be called (until later writes enable it again).

 

That is the "simple" version.

 

The Arduino version you were looking at is doing pretty much the same but it's complicated by the fact that it is C++ code and a potentially multi instance C++ class has some difficulty connecting to a single instance ISR which is what then complicates the Arduino C++ stuff further.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avruser1523 wrote:
So from there how does UDR data is transferred to the Transmit Shift Register?

I had already said that:

I wrote:
The hardware process of how data gets from UDR to the physical pin is described in the AVR datasheet

See the diagram Jim posted - there is also a text description in the datasheet describing how it all happens.

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Physically, a UART is "just" a shift register. The shift register usually has a predetermined location in RAM memory space though it may not be physically part of the cells thought of as RAM - it may be somewhere else on the chip. This is called "mapping"; it is something that is wired up to behave like RAM even though it is (or may be) somewhere else.

 

While the UART is just a shift register, it is a somewhat special shift register. Some of the locations in the shift register, such as the first bit, are hard wired to always send the same logic level, e,g, the "start bit". Other locations in the register carry the "message", which is often called a character; this is the part that is loaded when you write a character to the data registers. A few other bits are controlled by other configuration registers that are part of the UART block. These include the parity bit and the stop bit(s).

 

The UART includes a bunch of other bits and pieces. There is a clock scaler that converts the MCU clock into a signal that controls the timing of shifting bits in or out. There are other configuration or control registers that manage the main shift register. And, there is a second shift register that handles incoming bits. Further, there is logic that controls lots of things, like detecting when a character has been received or the end of a character is reached on the way out. This logic is also capable, under certain circumstances, of detecting some kinds of errors on incoming characters. 

 

So, there are lots of little wheels (virtually) spinning around in there, all working to send and receive characters, When everything works right, it is a marvel to behold, When it does NOT work right, it can be a vexing puzzle. The most common instance of "not working right" is to not have the same baud (bit) shift rate at both ends of the link. And, the most common cause of that is that the MCU clock frequency is not what you think it is.

 

Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


The problem with the datasheet picture that Jim showed in #5 is that it doesn't really indicate the paths to status feedback.

 

The "A" register in the UART is principally a "status flags" register. It is the act of some of these flags being raised that is the actual source of the interrupt. The diagram could show something like

 

 

It's a kind of IFTTT - if a byte is transferred from UDR to TX shift then at that moment the UDRE0 flag in the UCSR0A register is set. When the AVR core then scans flags during opcode execution to see if any interrupt conditions have occurred it will encounter this and cause CPU execution to vector to the UDRE interrupt vector. That gives the ISR to the opportunity to either put the next character waiting in its buffer into UDR or to switch off UDRE as a potential interrupt source if there's no more at tha ttime.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


clawson wrote:
The problem with the datasheet picture that Jim showed in #5 is that it doesn't really indicate the paths to status feedback.

Yes - you do need to read the accompanying text for that.

 

EDIT

 

https://ww1.microchip.com/downlo...

 

via https://www.microchip.com/wwwproducts/en/ATmega328p

 

 

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
Last Edited: Mon. Feb 1, 2021 - 06:13 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Thanks for all the help.