Trying to implement baremetal Serial.read & Serial.write

Hi, I am currently trying to use the USART to communicate with my linux machine (for now just debugging messages, later for sending instructions to the arduino) in c without the abstraction of the arduino IDE (for learning purposes). This is my code so far, trying to get some communication going:

#include "registers.h"
#define Biton(reg, bit) *reg |= (1 << bit)
#define Bitoff(reg, bit) *reg &= ~(1 << bit)
#define Readbit(reg, bit) (*reg & (1 << bit))
#define BAUD 9600

void
initSerial ()
{
  // set baud rate to 9600
  *UBRR0L = (F_CPU / 8 / BAUD - 1) / 2;
  *UBRR0H = ((F_CPU / 8 / BAUD - 1) / 2) >> 8;
  // enable transmission and reception
  Biton (UCSR0B, RXEN0);
  Biton (UCSR0B, TXEN0);
  // enable interrupt on RX complete
  //Biton (UCSR0B, RXCIE0);
  // enable interrupt on Data register empty
  //Biton (UCSR0B, UDRIE0);
  // enable asynchonus mode
  Bitoff (UCSR0C, UMSEL00);
  Bitoff (UCSR0C, UMSEL01);
  // disable parity mode
  Bitoff (UCSR0C, UPM00);
  Bitoff (UCSR0C, UPM01);
  // diable 2 stop bits
  Bitoff (UCSR0C, USBS0);
  // set data length to 8 bits
  Bitoff (UCSR0B, UCSZ02);
  Biton (UCSR0C, UCSZ00);
  Biton (UCSR0C, UCSZ01);
  // double transmission speed
  Biton (UCSR0A, U2X0);
}

unsigned char
serialRead ()
{
  volatile char cStopCompilerFromOptimizing;
  while (!Readbit (UCSR0A, RXC0))
    {
      cStopCompilerFromOptimizing++;
    }
  return *UDR0;
}

void
serialWrite (unsigned char c)
{
  volatile char cStopCompilerFromOptimizing;
  while (!Readbit (UCSR0A, UDRE0)) // flag that is set when transmit buffer is
                                   // empty and ready for new
    {
      cStopCompilerFromOptimizing++;
    }
  *UDR0 = c;
}

void setup() {
	initSerial();
}

void loop() {
	serialWrite('H');
	serialWrite('e');
	serialWrite('l');
	serialWrite('l');
	serialWrite('o');
	serialWrite('!');
	serialWrite('\n');
}

registers.h contains only define statements for register and byte names like this:

#define PORTB ((volatile unsigned char *)0x25)

I wrote it before I discovered avr/io.h.
Then I use to arduino cli (arduino-cli monitor -p /dev/ttyACM0)to get a serial monitor, so i can first can focus on the arduino side and then implement something on my computer, but i get only unprintable chars like this:
}�R�yv(2RPZ_�D�"RPZ]�D�:#4:��@c<#���
When i try to use my serialRead() it weirdly only works when i remove the NOT in the while loop(and use Serial.begin(9600)).
I am using a arduino uno.
Thanks for any help getting this to work.

That nearly always means the baud rate is incorrect, so I would check the setting of the baud rate registers. Try calculating the values manually and setting hex values, and/or print those registers to check you have the expected values set.

ETA: I tried running your code, and it transmits at a baud rate of 19200, so I guess the baud rate doubling feature is not programmed as you expect.

ETA2: the following modification transmits at 9600:

  // double transmission speed
  //Biton (UCSR0A, U2X0);
1 Like

Thanks for the help. I fixed that and now it works (also i had a wrong memory address in registers.h. I should really switch to avr/io.h)

do you really want a driver that needs to wait for input?

the more conventional approach is to use an interrupt that is triggered when the UART has received one or more bytes of data which it copies to some queue in RAM.

the interrupt might then update a count of the # of bytes in the queue.

higher level functions would check (e.g. Serial.available()) that count to determine if there is anythjing in the queue and a another function to read from the queue (e.g. Serial.read()) and decrementing the count of the # of bytes in the queue

1 Like

Yes, please.

Also code like this should be re-written to set the whole register at once, which avoids problems with bits that perhaps someone else has set, AND is more efficient:

  // enable asynchonus mode
  Bitoff (UCSR0C, UMSEL00);
  Bitoff (UCSR0C, UMSEL01);
  // disable parity mode
  Bitoff (UCSR0C, UPM00);
  Bitoff (UCSR0C, UPM01);
  // diable 2 stop bits
  Bitoff (UCSR0C, USBS0);
  // set data length to 8 bits
  Bitoff (UCSR0B, UCSZ02);
  Biton (UCSR0C, UCSZ00);
  Biton (UCSR0C, UCSZ01);

would be something like:

  *UCSR0C =
  // enable asynchonus mode
  (0<<UMSEL00) | (0<<UMSEL01) |
  // disable parity mode
  (0<<UPM00) | (0<<UPM01)
  // diable 2 stop bits
  (0<<USBS0) |
  // set data length to 8 bits
  (1<<UCSZ00) | (1<<UCSZ01) ;

that's the verbose version that specifies all the bits. You can leave out all the shifted 0s and get:

  // async mode, disable parity, 1 stop bit, 8 data bits
  *UCSR0C = (1<<UCSZ00) | (1<<UCSZ01);

(except, switch to io.h first, to get rid of the *'s and non-standard bit names.)

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.