Consolidated from tidbits in the main log. Description below is specific for ATmega16. Written on 2022-11-05.
Serial communication is sending data one bit at a time sequentially over a communication channel. This channel is typically directly a physical layer, where digital values of HIGH/1 and LOW/0 are encoded as voltages.
Serial communication can be performed synchronously, i.e. either a separate clock channel to synchronize sampling times on the receiver or transmission of clock signals during idle time, or asynchronously where the packets are synchronized by start and stop bits in each data frame (packet).
The transmission rate needs to be precoordinated to agree on the frame parameters (e.g. size of data bits, baud rates) before communication can begin - notably there exist schemes where auto-detection of baudrate on the receiver end is implemented to align with that of the transmitter.
The ATmega16 AVR chip provides a USART (Universal Synchronous and Asynchronous serial Receiver and Transmitter) that provides customizability when performing serial communication. It implements both synchronous and asynchronous serial communication.
There are three relevant physical pins for USART:
The TXD pin functions as an output pin, while the RXD pin functions as an input pin, switching between HIGH and LOW states. From the electrical characteristics section of the datasheet, for Vcc = 5V,
Data is transmitted in serial frames (packets) with the following format, with the definitions encoded in the following register bits:
UCSZ2:0
: One character of data bits $$ (n)$$ (ranging from 5 to 9 bits),USBS
: Synchronization bits (1 start $$ St$$, and 1 or 2 stop $$Sp$$ bits),UPM1:0
: An optional parity bit $$P$$ (even or odd) for error checkingCharacter bits have little bit-endianness (least-significant bit first). The diagram from the datasheet helps - a frame error (FE) can occur and is detectable only with first stop bit being LOW.
In the case of 5 to 8 data bits, transmission is performed by loading the transmit buffer when the data register (transmit buffer) is empty:
USART_TRANSMIT: sbis UCSRA,UDRE ; wait till UDR empty bit set rjmp USART_TRANSMIT out UDR,r16 ; load data (e.g. r16) into buffer ret
For the special case of 9-bit data, the ninth bit must be written to the TXB8 bit in UCSRB register first, before writing the rest into the UDR. Here we assume data is in r17:r16,
USART_TRANSMIT: sbis UCSRA,UDRE ; wait till UDR empty bit set rjmp USART_TRANSMIT cbi UCSRB,TXB8 ; clear 9th bit sbrc r17,0 ; skip if bit 0 in r17 is cleared... sbi UCSRB,TXB8 ; ... set 9th bit otherwise out UDR,r16 ; load data (e.g. r16) into buffer ret
Under the hood, the buffered data in the UDR is loaded into the Shift Register for transmission, when there is no ongoing transmission or when the last stop bit of previous frame is transmitted. This Shift Register is needed since the transmission rate (set by baudrate) can vary from the internal clock.
Interrupts can also be used for transmission:
Receiver starts data reception when a valid start bit (LOW) is detected, and starts shifting data into the receive Shift Register. The full frame is then moved into the receive UDR buffer. For 5 to 8 data bits, the following works:
USART_RECEIVE: sbis UCSRA,RXC ; wait until receive complete bit set rjmp USART_RECEIVE in r16,UDR ; store UDR data (e.g. into r16)
For 9 bits, the RXB8 bit needs to be read first before the UDR (which changes the state of the other flags):
USART_RECEIVE: sbis UCSRA,RXC ; wait till receive complete bit set rjmp USART_RECEIVE in r18,UCSRA ; store status (to check for errors) in r17,UCSRB ; store register containing 9th bit in r16,UDR ; store rest of data andi r18,(1<<FE)|(1<<DOR)|(1<<PE) ; check if errors (Z=0) breq USART_RECEIVENOERROR ; no error (Z=1) ldi r17,HIGH(-1) ; error => store -1 ldi r16,LOW(-1) ret USART_RECEIVENOERROR: lsr r17 ; retrieve RXB8 (UCSRB1) by right-shift... andi r17,0x01 ; ...then mask out bit 0 ret
Similar interrupt available:
The three error codes in particular signal errors in receipt:
If the receive buffer needs to be flushed, read UDR until the RXC flag is cleared:
USART_FLUSH: sbis UCSRA,RXC ret in r16,UDR rjmp USART_FLUSH
During asynchronous serial communication, receipt of the start bit in an incoming triggers the receiver clock to align with the frame. The rate at which bits arrive are determined by the user-defined baudrate.
This synchronization process is implemented by a clock recovery logic that oversamples each serial bit and applies a majority vote to the three samples closest to the center of the bit. There are two oversampling configurations available:
Use of three bits to perform majority vote essentially functions like a low pass filter. The central bits are used to avoid sampling near data stream transitions. A graphical representation explains this best:
Note that in double speed mode, there is a larger timing uncertainty to the first zero-sample, which affects the relative phase of the receiver clock to the incoming baudrate, i.e. larger chance of errors.
The same sampling process is applied to the data bits to recover HIGH and LOW states of the data. The stop bit is a little special: the next frame can come immediately after the required central three samples for the first stop bit is performed, so the oversampling rate dictates when the next start bit transition can occur:
The possible early start bit detection positions are marked (A) for Normal Mode, (B) for Double Speed Mode, and (C) for the full stop bit length. Isn't it strange then that there is an option to set 2 stop bits when the second stop bit can be ignored anyway? This is really a workaround to interface with devices that use 1.5 stop bits:
Frequency deviations between the transmitter and receiver can cause problems, with the maximum tolerated errors given by the following table:
To understand the formula, refer to the following diagram I made (central bit positions used below are arbitrary), source:
The last part is devoted to talking about how the clock is generated in the first place. Serial communication can either be synchronized in a master-slave configuration, or asynchronous - set by the UCSRC_UMSEL
bit. This determines how the clock is generated for the communication.
UCSRA_U2X
bit).DDR_XCK
register.Serial clock calculation
$$$$ \text{baud} = \frac{f_{osc}}{16(\text{UBRR}+1)} \quad\Rightarrow\quad{} \text{UBRR} = \frac{f_{osc}}{16\text{baud}} - 1 $$$$
This in the end gives the following error rates, when attempting to communicate with a transmitter at the listed common baudrates. This gives a measure of how much error is being induced, and thus what frame size should be used.