Sign in Join
Wiki copytemporary

AVR Embedded Tutorial - UART: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: AVR Embedded Tutorial - UART

OldNewLine
1+# UART polling
2+
3+## ATmega328p (Arduino Uno/Nano)
4+
5+A terminal with the following settings is required for serial output:
6+
7+| Setting | Value |
8+|-----------|-------|
9+| Baud Rate | 9600 |
10+| Bits | 8 |
11+| Stopbits | 1 |
12+| Parity | none |
13+
14+When pressing the <span class="kbd">Space Bar</span> in the terminal program, a "Hello World!" message is returned.
15+
16+### Constants
17+
18+``` pascal
19+ const
20+ CPU_Clock = 16000000; // Arduino clock frequency, default 16MHz.
21+ Baud = 9600; // baud rate
22+ Divider = CPU_Clock div(16 * baud) - 1;
23+```
24+
25+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.
26+
27+### Initialization
28+
29+Configure the UART interface.
30+
31+``` pascal
32+ procedure UARTInit;
33+ begin
34+ UBRR0 := divider; // Baud
35+ UCSR0A := 0; // Normal speed
36+ UCSR0B := (1 shl TXEN0) or (1 shl RXEN0); // Receive and send
37+ UCSR0C := (%011 shl UCSZ0); // 8-bit word, 1 stop bit
38+ end;
39+```
40+
41+### Receive characters
42+
43+``` pascal
44+ function UARTReadChar : char;
45+ begin
46+ while UCSR0A and (1 shl RXC0) = 0 do
47+ ; // Wait for a character to arrive
48+ Result := char(UDR0); // Read character
49+ end;
50+```
51+
52+### Send characters
53+
54+``` pascal
55+ procedure UARTSendChar(c : char);
56+ begin
57+ while UCSR0A and (1 shl UDRE0) = 0 do
58+ ; // Wait for the last character to be sent
59+ UDR0 := byte(c); // Send character
60+ end;
61+```
62+
63+### Send string
64+
65+``` pascal
66+ procedure UARTSendString(s : ShortString);
67+ var
68+ i : integer;
69+ begin
70+ for i := 1 to Length(s) do
71+ begin
72+ UARTSendChar(s[i]); // send characters one by one
73+ end;
74+ end;
75+```
76+
77+### Main loop
78+
79+Here you wait until the space character arrives, then transmit "Hello World!".
80+
81+``` pascal
82+program Project1;
83+ begin
84+ UARTInit;
85+
86+ repeat
87+ if UARTReadChar = #32 then // #32 = space character
88+ begin
89+ UARTSendString('Hello World!'#13#10);
90+ end;
91+ until 1 = 2;
92+ end.
93+```
94+
95+## ATtiny2313
96+
97+The registers on the ATtiny2313 have different names than on the ATmega328p.
98+
99+- The 0 is omitted in the name.
100+- UBRR0 must be broken down into UBRRH and UBRRL .
101+
102+It will look like this:
103+
104+``` pascal
105+ UBRRH := divider shr 8;
106+ UBRRL := byte(divider);
107+
108+ UCSRB := (1 shl TXEN ) or (1 shl RXEN);
109+ UCSRC := (%011 shl UCSZ);
110+```
111+
112+(The code has not been tested)
113+
114+# UART interrupt
115+
116+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.
117+
118+A simple 16-character ring buffer is used.
119+
120+The examples show this using an ATmega328p (Arduino Uno/Nano).
121+
122+## Receive buffer
123+
124+### Declaration
125+
126+Declare a character buffer: here a 16 character buffer is created.
127+
128+``` pascal
129+ const
130+ Crxlen = 16; // Buffer size
131+ var
132+ RXBuf : array[0..Crxlen - 1] of byte;
133+ RXread : byte = 0;
134+ RXwrite : byte = 0;
135+```
136+
137+### Initialization
138+
139+This is similar to polling, the difference is that **RXCIE0** is used to indicate that an interrupt is triggered when a character is received.
140+
141+``` pascal
142+ procedure UARTInit;
143+ begin
144+ UBRR0 := divider;
145+ UCSR0A := (0 shl U2X0);
146+ UCSR0B := (1 shl TXEN0) or (1 shl RXEN0) or (1 shl RXCIE0);
147+ UCSR0C := %011 shl UCSZ0;
148+ end;
149+```
150+
151+### Interrupt
152+
153+When the interrupt is triggered, the character is copied from the UART into the character buffer.
154+
155+``` pascal
156+ procedure UART_RX_Receiving; public name 'USART__RX_ISR'; interrupt;
157+ var
158+ b : byte;
159+ begin
160+ // Read characters
161+ b := UDR0;
162+
163+ // Write characters to buffer
164+ if b <> 0 then
165+ begin
166+ RxBuf[Rxwrite] := b;
167+ Inc(RXwrite);
168+
169+ if RXwrite >= Crxlen then
170+ begin
171+ RXwrite := 0;
172+ end;
173+ end;
174+ end;
175+```
176+
177+### Main loop
178+
179+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.
180+
181+``` pascal
182+ begin
183+ // Initialize UART which is interrupt controlled
184+ UARTInit;
185+
186+ // Enable interrupts
187+ asm
188+ sei
189+ end;
190+
191+ // Main loop
192+ repeat
193+
194+ // Is there a character in the buffer?
195+ while RXwrite <> Rxread do
196+ begin
197+ // Disable interrupts
198+ asm
199+ cli
200+ end;
201+
202+ // Read character from buffer
203+ ch := RXBuf[rxread];
204+ Inc(RXread);
205+
206+ if RXread >= Crxlen then
207+ begin
208+ RXread := 0;
209+ end;
210+
211+ // Enable interrupts
212+ asm
213+ sei
214+ end;
215+
216+ // Convert lowercase letters, into uppercase ones
217+ if ch in[$61..$7A] then
218+ begin
219+ Dec(ch, 32);
220+ end;
221+
222+ // Output character
223+ UARTSendChar(char(ch));
224+ end;
225+ until 1 = 2;
226+ end.
227+```
228+
229+## Send buffer
230+
231+### Declaration
232+
233+Declare a send buffer: here a 16 character buffer is created.
234+
235+``` pascal
236+ const
237+ CTXlen = 16; // Buffer size
238+ var
239+ TXBuf : array[0..CTXlen - 1] of byte;
240+ TXread : byte = 0;
241+ TXwrite : byte = 0;
242+```
243+
244+### Initializarion
245+
246+This is similar to the receive buffer. Important: the interrupt function must not be activated yet, because there is nothing to send yet.
247+
248+``` pascal
249+ procedure UARTInit;
250+ begin
251+ UBRR0 := divider;
252+ UCSR0A := (0 shl U2X0);
253+ UCSR0B := (1 shl TXEN0) or (1 shl RXEN0);
254+ UCSR0C := %011 shl UCSZ0;
255+ end;
256+```
257+
258+### Interrupt
259+
260+The interrupt is triggered as soon as a character has been sent to the UART.
261+
262+As soon as the ring buffer is empty, the interrupt function is deactivated again.
263+
264+The query of **UDRE0** is not needed here, since the interrupt is only triggered when the buffer is empty.
265+
266+``` pascal
267+ procedure UART_UDRE_Send; public name 'USART__UDRE_ISR'; interrupt;
268+ begin
269+ // Send character
270+ UDR0 := TXBuf[TXread];
271+
272+ // Move ring buffer
273+ Inc(TXread);
274+
275+ // When pointer is at the end, start again
276+ if TXread >= CTXlen then
277+ begin
278+ TXread :=0;
279+ end;
280+
281+ // If the ring buffer is empty, an interrupt is no longer necessary
282+ if TXread = TXwrite then
283+ begin
284+ UCSR0B := UCSR0B and not (1 shl UDRIE0);
285+ end;
286+ end;
287+```
288+
289+### Send string
290+
291+As soon as something is in the send buffer, the interrupt is not activated.
292+
293+``` pascal
294+ procedure UARTSendString(s : ShortString);
295+ var
296+ i : byte;
297+ begin
298+ // Fill the ring buffer with data
299+ for i := 1 to Length(s) do
300+ begin
301+ TXBuf[TXwrite] := byte(s[i]);
302+ Inc(TXwrite);
303+
304+ if TXwrite >= CTXlen then
305+ begin
306+ TXwrite := 0;
307+ end;
308+ end;
309+
310+ // Enable send interrupt
311+ UCSR0B := UCSR0B or (1 shl UDRIE0);
312+ end;
313+```
314+
315+### Main loop
316+
317+The string is output in the background. In the example, an LED flashes during output.
318+
319+``` pascal
320+ var
321+ z : byte;
322+ s : ShortString;
323+ begin
324+ DDRB := DDRB or (1 shl 5);
325+
326+ // Initialize UART that is interrupt controlled
327+ UARTInit;
328+
329+ // Enable interrupts
330+ asm
331+ sei
332+ end;
333+
334+ // Main loop
335+ repeat
336+ UARTSendString('Hello World');
337+ UARTSendString('!'#13#10);
338+
339+ PORTB := PORTB or (1 shl 5);
340+ mysleep(sl);
341+
342+ Str(z : 3, s);
343+ UARTSendString(s + '.');
344+ Inc(z);
345+
346+ PORTB := PORTB and not (1 shl 5);
347+ mysleep(sl);
348+
349+ until 1 = 2;
350+ end.
351+```
352+
353+# UART emulation in software
354+
355+It is also possible to emulate a UART interface using software.
356+
357+This is practical if you have an AVR which has no/too few hardware UARTs.
358+
359+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.
360+
361+Important: Interrupts must be blocked during the transmission.
362+
363+Reading and writing at the same time does not work because there is no buffer.
364+
365+Ideally, this can be used to output errors to a UART terminal.
366+
367+## Constants
368+
369+In this example, Pins 2 and 3 from Port D are used.
370+
371+``` pascal
372+ const
373+ CPU_Clock = 16000000; // Arduino clock frequency, default 16MHz.
374+ Baud = 9600; // Baud rate
375+
376+ TXpin = (1 shl 3); // Pin 3 (receive)
377+ RXpin = (1 shl 2); // Pin 2 (send)
378+
379+ softDivider = CPU_Clock div(Baud * 22); // bit counter
380+```
381+
382+## Set the receive pin to input
383+
384+``` pascal
385+ begin
386+ DDRD := DDRD or TXpin;
387+ ...
388+ // loop
389+ ...
390+ end.
391+```
392+
393+Note: If you want to use the same pins as for the hardware UART, pins 0 and 1 must be enabled on the Arduino.
394+
395+``` pascal
396+ begin
397+ UCSR0B := 0; // Release blocked pins 0 and 1
398+ ...
399+ // loop
400+ ...
401+ end.
402+```
403+
404+## Send characters
405+
406+``` pascal
407+ procedure UARTSoftSendByte(c : byte);
408+ var
409+ i, data : int16;
410+ j : Int8;
411+ begin
412+ asm
413+ cli // Disable interrupts
414+ end;
415+
416+ Data := (c shl 1) or $FE00;
417+
418+ for j := 0 to 9 do // Output bits, including stop bit
419+ begin
420+ if(Data and 1) = 1 then
421+ PORTD := PORTD or TXpin // HIGH
422+ else
423+ PORTD := PORTD and not TXpin; // LOW
424+
425+ Data := Data shr 1;
426+
427+ for i := 0 to softDivider do // One bit break
428+ ;
429+ end;
430+
431+ asm
432+ sei
433+ end; // Enable interrupts
434+ end;
435+```
436+
437+## Receive characters
438+
439+Note: If no character is received, the loop hangs forever.
440+
441+``` pascal
442+ function UARTSoftReadByte : byte;
443+ var
444+ i : Int16;
445+ j : Int8;
446+ begin
447+ asm
448+ cli // Disable interrupts
449+ end;
450+
451+ Result := 0;
452+
453+ while PIND and RXpin > 0 do // Wait until the first falling edge
454+ ;
455+
456+ for i := 0 to softDivider div 2 do // Half a bit break
457+ ;
458+
459+ for j : = 0 to 7 do
460+ begin
461+ for i := 0 to softDivider do // One bit break
462+ ;
463+
464+ Result := Result shr 1;
465+
466+ if PIND and RXpin <> 0 then
467+ Inc(Result, $80);
468+ end;
469+
470+ for i := 0 to softDivider // One bit break
471+ ;
472+
473+ asm
474+ sei // Enable interrupts
475+ end;
476+ end;
477+```
478+
479+# See also
480+
481+- [[AVR Embedded Tutorial|AVR Embedded Tutorials]] - Overview
482+
483+---
484+*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](/wiki-contact).*
485+Category: AVR
486+Category: Arduino
487+Category: Embedded
488+Category: Tutorials

Back to history