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
| Old | New | Line | |
|---|---|---|---|
| 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 |
