6
\$\begingroup\$

I'm trying to learn to use USART on an ATmega328P. I'm using this datasheet.

I'm using the following code to echo characters I type on my terminal:

#include <avr/io.h>

#define BAUD 9600
#define F_CPU 16000000UL 

static void usart_init(void) {
    unsigned int ubrr = F_CPU / 16 / BAUD - 1;
    UBRR0H = (unsigned char) (ubrr >> 8);
    UBRR0L = (unsigned char) ubrr;
    UCSR0B = (1 << TXEN0) | (1 << RXEN0);
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}

static void usart_transmit(unsigned char data) {
    while (!(UCSR0A & (1 << UDRE0)))
        ;
    UDR0 = data;
}

static unsigned char usart_receive(void) {
    while (!(UCSR0A & (1 << RXC0)))
        ;
    return UDR0;
}

int main(void) {
    unsigned char c;

    usart_init();   

    for (;;) {
        c = usart_receive();
        usart_transmit(c);
    }

    return 0;
}

Section 19.10 (pg 159) states that

The transmit buffer can only be written when the UDREn flag in the UCSRnA register is set. Data written to UDRn when the UDREn flag is not set, will be ignored by the USART transmitter. When data is written to the transmit buffer, and the transmitter is enabled, the transmitter will load the data into the transmit shift register when the shift register is empty. Then the data will be serially transmitted on the TxDn pin.

The only TXD pin I see on page 3 of the datasheet (pinout diagram) is PD1. How do I, for instance, use PD6 as as my transmitting pin? In the datasheet, I don't see anything like UDR6?

I came across this project in which he seems to be using PD6 as his transmitter. But his USART code looks identical to mine:

void initUSART(void) {                                /* requires BAUD */
  UBRR0H = UBRRH_VALUE;                        /* defined in setbaud.h */
  UBRR0L = UBRRL_VALUE;
#if USE_2X
  UCSR0A |= (1 << U2X0);
#else
  UCSR0A &= ~(1 << U2X0);
#endif
                                  /* Enable USART transmitter/receiver */
  UCSR0B = (1 << TXEN0) | (1 << RXEN0);
  UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);   /* 8 data bits, 1 stop bit */
}

void transmitByte(uint8_t data) {
                                     /* Wait for empty transmit buffer */
  loop_until_bit_is_set(UCSR0A, UDRE0);
  UDR0 = data;                                            /* send data */
}

uint8_t receiveByte(void) {
  loop_until_bit_is_set(UCSR0A, RXC0);       /* Wait for incoming data */
  return UDR0;                                /* return register value */
}

How does USART know which pin to use as the transmitter (and receiver)?

\$\endgroup\$
1
  • \$\begingroup\$ " seems to be using PD6 as his transmitter" .. that file has #define SPEAKER PD6, don't see anything about USARTs. \$\endgroup\$
    – jonathanjo
    Apr 2, 2023 at 5:56

1 Answer 1

14
\$\begingroup\$

The ATMega328P has only one hardware USART, hence you don't have to tell it anything. The USART is connected to PD0 (RXD) and PD1 (TXD) and you can't change it.

If you want to do serial on another pin, you can use "software serial" which drives whichever pin you want. There re many libraries for this, and it's not so hard to write a custom one if you need it. Usually your bit rate is somewhat limited, and you'll have potentially unwanted blocking or use interrupts.

The datasheet is written that way because some of the CPUs have more USARTs. For example the ATMega328PB has two USARTs and can use PB3 and PB4 as TXD1 and RXD1, with PD0 and PD1 being renamed RXD0 and TXD0. Libraries for programming this will have some way to say which you want.

From the ATMega 328pB datasheet

enter image description here

\$\endgroup\$
1
  • \$\begingroup\$ @rene thanks for finding the errors, I've corrected them with screenshot from datasheet. \$\endgroup\$
    – jonathanjo
    Apr 2, 2023 at 19:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.