Does atmega328p have special instructions for Serial Communication?

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

atmega328p has a few dedicated Serial pins to use with UART and SPI communication.

This is the Arduino code which implements the UART Serial communication:

https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/HardwareSerial.cpp

 

There doesn't seem to be a use of any special code/instructions in the above code. It seems like everything is bit banged and emulated with software.

 

Looking at the atmega328p ISA, there doesn't seem to be any special instructions for them either:

http://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf

 

My question is, is every serial communication emulated on this chip? if so why do they have dedicated PINs? they could use any of the IO pins.

If not then how is the hardware being specialized for this? I don't see any instructions.

 

 

This topic has a solution.
Last Edited: Mon. Jul 6, 2020 - 02:00 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avruser1523 wrote:

 

There doesn't seem to be a use of any special code/instructions in the above code. It seems like everything is bit banged and emulated with software.

 

How did you come to that conclusion?

What do you mean by 'special code/instructions'?  

The usart in the AVR is a peripheral device - it is accessed using i/o or memory instructions as for the other peripherals. Besides, you're looking at C/C++ code - the compiler generates the instructions.

As we've explained before - peripherals are designed to do common tasks more efficiently and in parallel with the processor. Receiving serial data with a 'simple' processor with no hardware usart to sit in a loop and poll an input pin along with timing loops. Whilst doing this, the processor is not doing anything else. Enter the usart/uart - it does all this in hardware and can interrupt the processor when it has something for it to process. Or the processor can poll a status bit to see if it has anything.

Similarly with a timer - it counts clocks so the processor doesn't have to.

 

With many of the single chip processors, the hardware peripherals are tied to specific pins. Some have multiplexing options and some even have the option of routing any peripheral to any pin - like the nrf51/52 series and esp32. Flexibility comes with a price - extra configuration and extra silicon to implement the features.

 

I've said it before to you - you're approaching 'learning' processors from the wrong end. You're observing from the outside and trying to figure out what is happening on the inside and asking some strange questions. If you learn about digital logic and how to build a processor from first principles, then things will begin to make perfect sense. You'll be able to answer your own questions and have a fundamental understanding. At the moment everything looks like black boxes of magic to you.

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

Kartman wrote:
learn about digital logic and how to build a processor from first principles
Sorry i'm not going to do an electrical engineering degree to learn how Arduino works. 

I'm a software guy and have little interest in hardware. I'm trying to understand how Arduino's software is written at it's core. When I do a  "Serial.begin()" I want to understand what it does behind the scenes, and that's why i'm here to get help with some of the hardware stuff.

 

So with above explanation you are saying pins are pretty much there to support special interrupts? Sure that takes care of reading data and takes some of the burden away from the CPU.

 

But how about sending and outputting data, surely it seems like this is done by the CPU itself and other harware have not much work in generating them?

 

 

 

Last Edited: Mon. Jul 6, 2020 - 12:04 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you don't want to learn, then don't ask questions! Unfortunately with embedded systems you're dealing with hardware - that's the nature of embedded systems. 

 

So with above explanation you are saying pins are pretty much there to support special interrupts? Sure that takes care of reading data and takes some of the burden away from the CPU.

 

errr.... not precisely. You wrote a confusion of words.

 

With the usart you configure it - that's the purpose of Serial.begin(). Read the mega328 datasheet on the usart to find what these configuration options are. Think of the usart like a software object - it has an interface. Being hardware, then 'instance' is created for you. Once configured, serial data arriving on the required hardware pin is decoded by the usart hardware. You code can then query the usart for that data. It can also generate an event (interrupt) if configured as such. Similarly with the sending, you write the data you want to send one byte at a time and you need to wait whilst the usart hardware sends it. Again, an event can be generated when the byte has been transmitted. For all intents and purposes, the interface to the usart is hidden from you. From the code's perspective, the interface is specially named variables- ie: UBRR,UDR,UCSRA and so on. Once configured, when you assign a value to UDR, the usart hardware magically sends that value out the required hardware pin. You read the value of UCSRA to determine the status.

 

You need to formulate your questions a lot better. Ask a precise question and you'll most likely get a precise answer. Your current questions are so broad it is hard to understand exactly what you are asking.

 

 

 

 

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

Kartman wrote:
If you don't want to learn, then don't ask questions!

I do like to learn but I want to stay in my boundary, I don't understand much when I cross the boundary to hardware in too much detail. I know registers, memory addresses, caches, data buses, but that's it.

If you ask me how "addition" is done using logic gates in a cpu then that's crossing the boundary for me. I'm trying to learn Arduino as close as I can get to the hardware (not too close).

 

alright now:

Once configured, when you assign a value to UDR, the usart hardware magically sends that value out the required hardware pin

I want to learn the magic part. I thought the CPU just outputs a positive volt on a wire which goes out that desired UART pin! so that is not the case then and a special hardware is doing that?

 

Also where is the above info written? is it part of the data sheet?

Last Edited: Mon. Jul 6, 2020 - 01:08 AM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A UART is a special shift register. Parallel in, serial  out on the transmit side,  serial in with parallel out on the receive side. 

 

When you write to the UDR, it takes your data, adds a start bit, a parity bit (if you tell it to do so) and one or two stop bits and shifts it out, one bit at a time for each UART clock (well, not exactly, but that is close enough for transmit). 

 

That is the sum total of the "magic" on the  transmit side. The receive side  has a bit more magical. When the edge of a start bit is detected, it starts shifting in what ever is present at the receive pin. When the stop bit is received, it stops shifting and alerts you that a new character is available. There is a lot more that happens on receive behind the scenes, but that is basically it. 

 

The UART has a working shift register than data is shifted into. Once that stop bit is detected, it copies the contents of the working register into the receive UDR. Thus, the contents of the UDR can sit there for a while, undisturbed, until the next character is fully received. That gives you one whole character time to read the UDR.

 

Hope this helps

Jim

 

 

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

 

 

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

Thanks for the detailed info.

 

So it seems like for transmitting, the cpu has not much work to do except maybe "load" an immediate to a register/memory address which then will be picked up directly by the UART hardware and the rest is taken care by it?

At the receive side it does the interrupt handling as well as copy the received "character" from the buffer to some other location I need and the rest is done again by the UART hardware?

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

Yes, the receive interrupt fires when a complete character is received. Actually, it does not wait for the stop bit. It simply counts so many clocks.

 

If there is no stop bit, one of the status bits is set in the UART status register. Same is true if there is a parity error. Ditto, if the UDR is not read soon enough and a new value is written over an unhandled one; this is an over-run error. It cannot, by the way, tell if the baud rate is wrong, but a stop-bit error is a good indicator.

 

For both receive and transmit, the cpu does NOTHING until the end of the character. It is ALL handled in hardware.

 

Jim

 

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

 

 

Last Edited: Mon. Jul 6, 2020 - 02:27 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

It's clearing up for me now that, there are no "specific instructions" to let say send a UART byte, you just write to a register/mem address using regular "load/store" instructions, the hardware picks it up and does the rest.

 

I'm guessing the same goes on (or at least similar) for TWI and SPI? Their hardware component does the heavy lifting as UART?

 

 

Last Edited: Mon. Jul 6, 2020 - 02:35 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Have you had a thorough look at the datasheet for the mega328?  That would certainly give you some basics about the chip...it it an easy read-through, no heavy-duty logic diagrams or k-maps to be found.

When I started, I'd make a copy and higlight the things I wanted to remebber & filter out the things I didn't for that time.

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

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

I don't understand much when I cross the boundary to hardware in too much detail. I know registers, memory addresses, caches, data buses, but that's it.

If it helps, you can think of the peripherals on the AVR (like the UART) as co-processors, capable of parallel execution just like the much fancier parallel execution blocks in a modern desktop CPU.

The AVR lacks actual co-processor instructions, so you tell them to do stuff by writing to certain memory/io addresses, and then the peripheral goes off and does them while the AVR gets to continue executing code.

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

avruser1523 wrote:
This is the Arduino code which implements the UART Serial communication:
avruser1523 wrote:
There doesn't seem to be a use of any special code/instructions in the above code. It seems like everything is bit banged and emulated with software.
If you think that then you have not read the code completely. Take a function such as this:

void HardwareSerial::begin(unsigned long baud, byte config)
{
  // Try u2x mode first
  uint16_t baud_setting = (F_CPU / 4 / baud - 1) / 2;
  *_ucsra = 1 << U2X0;

  // hardcoded exception for 57600 for compatibility with the bootloader
  // shipped with the Duemilanove and previous boards and the firmware
  // on the 8U2 on the Uno and Mega 2560. Also, The baud_setting cannot
  // be > 4095, so switch back to non-u2x mode if the baud rate is too
  // low.
  if (((F_CPU == 16000000UL) && (baud == 57600)) || (baud_setting >4095))
  {
    *_ucsra = 0;
    baud_setting = (F_CPU / 8 / baud - 1) / 2;
  }

  // assign the baud_setting, a.k.a. ubrr (USART Baud Rate Register)
  *_ubrrh = baud_setting >> 8;
  *_ubrrl = baud_setting;

  _written = false;

  //set the data bits, parity, and stop bits
#if defined(__AVR_ATmega8__)
  config |= 0x80; // select UCSRC register (shared with UBRRH)
#endif
  *_ucsrc = config;

  sbi(*_ucsrb, RXEN0);
  sbi(*_ucsrb, TXEN0);
  sbi(*_ucsrb, RXCIE0);
  cbi(*_ucsrb, UDRIE0);
}

do you understand what _ucsrc, _ucsrb and so on are in this code? You need to know a little about C++, constructors and member variables. But basically the instantiation of this class consists of something like this found in HardwareSerial0.cpp, HardwareSerial1.cpp etc that contain:

#if defined(UBRRH) && defined(UBRRL)
  HardwareSerial Serial(&UBRRH, &UBRRL, &UCSRA, &UCSRB, &UCSRC, &UDR);
#else
  HardwareSerial Serial(&UBRR0H, &UBRR0L, &UCSR0A, &UCSR0B, &UCSR0C, &UDR0);
#endif

So say this is a 328P that the code is being built for. It has registers called UBRR0H and UBRR0L, they are not called UBRRH and UBRRL so it's the #else that is used here. Now look at the constructor this invokes. First the class has this:

  public:
    inline HardwareSerial(
      volatile uint8_t *ubrrh, volatile uint8_t *ubrrl,
      volatile uint8_t *ucsra, volatile uint8_t *ucsrb,
      volatile uint8_t *ucsrc, volatile uint8_t *udr);

and the implementation of that is:

HardwareSerial::HardwareSerial(
  volatile uint8_t *ubrrh, volatile uint8_t *ubrrl,
  volatile uint8_t *ucsra, volatile uint8_t *ucsrb,
  volatile uint8_t *ucsrc, volatile uint8_t *udr) :
    _ubrrh(ubrrh), _ubrrl(ubrrl),
    _ucsra(ucsra), _ucsrb(ucsrb), _ucsrc(ucsrc),
    _udr(udr),
    _rx_buffer_head(0), _rx_buffer_tail(0),
    _tx_buffer_head(0), _tx_buffer_tail(0)
{
}

Now again you need to know a bit about C++ but while the body of this function is empty the key thing is the initalizer list (the stuff after the parameter list and colon). So this is saying that at the point of instatiation/construction the parameter given as ubrrh should be copied into _ubrrh, the parameter given as ucsra should be copied into _ucsra and so on. Those variables (with leading '_') are class members:

class HardwareSerial : public Stream
{
  protected:
    volatile uint8_t * const _ubrrh;
    volatile uint8_t * const _ubrrl;
    volatile uint8_t * const _ucsra;
    volatile uint8_t * const _ucsrb;
    volatile uint8_t * const _ucsrc;
    volatile uint8_t * const _udr;

So the c'tor is really acting (in more "C like" terms) as if:

HardwareSerial::HardwareSerial(
  volatile uint8_t *ubrrh, volatile uint8_t *ubrrl,
  volatile uint8_t *ucsra, volatile uint8_t *ucsrb,
  volatile uint8_t *ucsrc, volatile uint8_t *udr)
{
    this->_ubrrh = ubrrh;
    this->_ubrrl = ubrrl;
    this->_ucsra = ucsra;
    this->_ucsrb = ucsrb;
    this->_ucsrc = ucsrc;
    this->_rx_buffer_head = 0;
    this->_rx_buffer_tail = 0;
    this->_tx_buffer_head = 0;
    this->_tx_buffer_tail = 0;
}

then the parts of the begin() function that access the hardware such as:

  // assign the baud_setting, a.k.a. ubrr (USART Baud Rate Register)
  *_ubrrh = baud_setting >> 8;
  *_ubrrl = baud_setting;

  _written = false;

  //set the data bits, parity, and stop bits
#if defined(__AVR_ATmega8__)
  config |= 0x80; // select UCSRC register (shared with UBRRH)
#endif
  *_ucsrc = config;

  sbi(*_ucsrb, RXEN0);
  sbi(*_ucsrb, TXEN0);
  sbi(*_ucsrb, RXCIE0);
  cbi(*_ucsrb, UDRIE0);

are really writing to things like UBRR0H, UBRR0L, UCSRC, UCSRB in the hardware as pointers to these things were passed in at the point of instantiation:

  HardwareSerial Serial(&UBRR0H, &UBRR0L, &UCSR0A, &UCSR0B, &UCSR0C, &UDR0);

Note the '&' which are passing in the "address of" these hardware registers. So the code is acting as if it were:

  // assign the baud_setting, a.k.a. ubrr (USART Baud Rate Register)
  UBRR0H = baud_setting >> 8;
  UBRR0L = baud_setting;

  _written = false;

  //set the data bits, parity, and stop bits
#if defined(__AVR_ATmega8__)
  config |= 0x80; // select UCSRC register (shared with UBRRH)
#endif
  UCSR0C = config;

  sbi(UCSR0BB, RXEN0);
  sbi(UCSR0BB, TXEN0);
  sbi(UCSR0BB, RXCIE0);
  cbi(UCSR0BB, UDRIE0);

 

So if you read this code and took away from it

It seems like everything is bit banged and emulated

then you have missed some detail of the above. 

Last Edited: Mon. Jul 6, 2020 - 09:21 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


avruser1523 wrote:
there are no "specific instructions" to let say send a UART byte, you just write to a register/mem address using regular "load/store" instructions, the hardware picks it up and does the rest

Yes - that's it!

 

avruser1523 wrote:
the same goes on (or at least similar) for TWI and SPI?

Yes - and all the other hardware peripherals of the chip:

 

You can see this from the block diagram in the datasheet:

 

 

It shows you that all the peripherals connect to the CPU via the Data Bus - so accessing them is just a matter of data reads and data writes.

 

Specifically for the USART:

 

Again, you can see that each of the registers is just accessed via the Data Bus.

 

 

 

As others have said, this is pretty much the same for any microcontroller - it's not specific to AVR.

 

 

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

westfw wrote:
If it helps, you can think of the peripherals on the AVR (like the UART) as co-processors, capable of parallel execution just like the much fancier parallel execution blocks in a modern desktop CPU.

possibly not such a good analogy, as co-processors in PCs do tend to have special instructions - eg, a Floating-Point co-processor has a set of floating point instructions.

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

clawson wrote:
then you have missed some detail of the above. 

thanks, I know c++. All the things you mentioned are memory addresses/registers. they have nothing to do with special instructions, there are no such instructions in this pdf:

http://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf

 

I was expecting something written in "asm()" directly.

 

Like I said, I thought there are special instructions which do the work but as I learned that is not the case. You just assign data to a memory address and the hardware picks it up from there.

Last Edited: Mon. Jul 6, 2020 - 01:49 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Correct.

 

Sounds like time to mark the solution ...

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

Some AVR's have the MUL instruction & its cousins to work the optional multiplier block, so that is a (somewhat poor) example.

Generally instructions devcoding resides in very valuable & limited real estate, so they can't reserve them for options (and possibly unknown) hardware features.   One exception, as noted, is a coprocessor.

IN,  OUT, and sleep are close to hardware-specific commands.

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

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

avrcandies wrote:
Generally instructions decoding resides in very valuable & limited real estate, so they can't reserve them for options (and possibly unknown) hardware features.   One exception, as noted, is a coprocessor.

One way to not have to "reserve" a whole range of instructions is to have just one "trap" instruction which signals that a coprocessor instruction follows.

 

IIRC, this is what the 8086 did  to support the external 8087 FP coprocessor

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

awneil wrote:

One way to not have to "reserve" a whole range of instructions is to have just one "trap" instruction which signals that a coprocessor instruction follows.

 

IIRC, this is what the 8086 did  to support the external 8087 FP coprocessor

Or what Wos did to create his Sweet 16 dream machine using the 6502!

 

 

 

FF = PI > S.E.T

 

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

avruser1523 wrote:
All the things you mentioned are memory addresses/registers.

If you knew that where are you seeing the "bit bang", "emulated" code you referred to?

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

clawson wrote:

If you knew that where are you seeing the "bit bang", "emulated" code you referred to?

I thought there had to be an instruction like  "ssnd" (serial send) or something to actually ask the cpu to send a byte on the UART pin (just imagined it), but learned that is not the case.

since I didn't see such instructions I thought things might be emulated by writing to memory addresses.

 

Now I learned you DO write to certain memory addresses but dedicated hardware (like UART) are picking that up and actually transmitting them. cpu does nothing after writing to the spiced addresses.

Last Edited: Mon. Jul 6, 2020 - 06:25 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This thread is a good example of (one of) the reasons why we are here. I hope that we don't forget that and I hope that we don't get too short tempered with folks who ask the basic questions. The questions that "averuser1523" has been asking may seem a bit off the wall to some, but for folks with a bit of programming experience and little or no hardware knowledge, these questions are pretty typical. 

 

We may also grumble about all these things having been explained before or explained elsewhere, but search skills really are dependent on having some critical knowledge before you start. It is quite obvious that this is often not the case. 

 

So, lets remember to be gentle with the newbies (except for those few cases where sharp responses really are in order). Lets remember that we were once in this situation, though it may have been so long ago that it is now hard to remember. Lets remember that newbies do not have our reference books, or experience to jump to StackOverflow (or other help sites), or even knowledge of what we consider core fundamentals (like Ohm's Law!).

 

This one has come out pretty well, with a few rough spots. Thats great. Before all that long, its going to be necessary to rinse and repeat.

 

Jim

 

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

 

 

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

Jim,

To whom are you directing your post?

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

Directed to all of us. There seems, recently, to have been a whole spate of real newbie questions, some of which have been poorly framed and, occasionally, with terse responses. I was simply trying to remind us of some of the basics about handling these queries. Maybe it should not have been tacked on the end of this specific thread.

 

Jim

 

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

 

 

Last Edited: Mon. Jul 6, 2020 - 08:56 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you're 'reading' through the Arduino source, you'll find that a lot of the code ends up somewhere in avr-libc, the C library for AVR microcontrollers using the GCC toolchain. As this is distributed in the Arduino core in binary (.a) form, it's worth knowing how to find the source and documentation: https://www.nongnu.org/avr-libc/