Baremetal Arduino: USB Serial

0 - Introduction

In the last baremetal arduino post we started a journey into the world of Arduinos without the IDE and their libraries. Today we will contiue that journey by learning how to do one of the most important debugging functionalities of the Arduino, how to print to the Serial Port (USB).

If you don’t have an Arduino, you can buy one here.

1 - Code

Like in the last article, we will use two header files, ‘avr/io’ (for register and port macros) and ‘util/delay’ (to add a small delay on loop). Throught the article I will reference a pdf with all data about the Arduino Nano’s MCU, you can find it here. Let’s start by adding the includes and making a define for your cpu frequency and baud rate:

#include <avr/io.h>
#include <util/delay.h>

// CPU frequency
#define F_CPU 16000000UL
// Serial speed in bps, check page 199 of the pdf
#define BAUD 16 //115200

Then we need to initialize uart, we do that by enabling double speed, the transmitter and setting the char size to 8 bits and then, setting the baud rate (you can check the baud rate table at page 199):

// Page 200, 201, 202
void uart_init() 
{
    UCSR0A |= (1 << U2X0); // Enable double speed
    UCSR0B |= (1 << TXEN0); //Enable transmitter (1 << RXEN0) for receiver
    UCSR0C |= (1 << UCSZ01) | (1 << UCSZ00); // 8-bit char size

    UBRR0 = BAUD;
}

Now after initializing the uart port we need to send data to it, you can easely do that by waiting until the tx buffer is empty, and when it is, write to it:

//Page 200 - 20.11.1 and 20.11.2 Bit 5
void uart_sendchar(unsigned char data) 
{
    // Wait until the transmit buffer is empty
    while (!(UCSR0A & (1 << UDRE0)));

    // Put data into buffer, sends the data
    UDR0 = data;
}

Then we create a helper function that sends all chars in the string one at a time:

void uart_sendstr(unsigned char* data)
{
    int i = 0;
    while (data[i] != '\0')
        uart_sendchar(data[i++]);
}

Finally, we create the main function where we initialize the uart and in a loop send a message every second:

int main() 
{
    //Init Uart
    uart_init();

    while (1) 
    {
        // Send Hello
        uart_sendstr("Hello\n");

        // Wait
        _delay_ms(1000);
    }

    return 0;
}

And that’s it, you can use the Wokwi emulator by clicking here and you can also find below the whole main file along with a makefile you can use to compile multiple files and upload the program to the arduino:

#include <avr/io.h>
#include <util/delay.h>

// https://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf

// CPU frequency
#define F_CPU 16000000UL
// Serial speed in bps, check page 199 of the pdf
#define BAUD 16 //115200

void uart_init() 
{
    UCSR0A |= (1 << U2X0); // Enable double speed
    UCSR0B |= (1 << RXEN0) | (1 << TXEN0); //Enable receiver and transmitter
    UCSR0C |= (1 << UCSZ01) | (1 << UCSZ00); // 8-bit char size

    UBRR0 = BAUD;
}

void uart_sendchar(unsigned char data) 
{
    // Wait until the transmit buffer is empty
    while (!(UCSR0A & (1 << UDRE0)));

    // Put data into buffer, sends the data
    UDR0 = data;
}

void uart_sendstr(unsigned char* data)
{
    int i = 0;
    while (data[i] != '\0')
        uart_sendchar(data[i++]);
}

int main() 
{
    //Init Uart
    uart_init();

    while (1) 
    {
        // Send Hello
        uart_sendstr("Hello\n");

        // Wait
        _delay_ms(1000);
    }

    return 0;
}

With this makefile you can easely compile and upload your code:

SPORT=/dev/ttyUSB0
BAUD=115200
F_CPU=16000000UL
MMCU=atmega328p

# ====================================================================================================
# directories
DIR_SRC=.
DIR_OUT=out
OUT_NAME=program

# ====================================================================================================
# files
ELF=$(DIR_OUT)/$(OUT_NAME).elf
HEX=$(DIR_OUT)/$(OUT_NAME).hex
EXE=$(DIR_OUT)/$(OUT_NAME)
# find all c files in src dir (ignore test.c file)
SOURCES := $(shell find $(DIR_SRC) -name '*.c' ! -name 'test.c') 
# comment line above and uncomment the one below to specify which c files to use
#SOURCES = $(DIR_SRC)/writeCfilesHere.c

# ====================================================================================================
# compilers
CC=avr-gcc
OCP=avr-objcopy
CCT=gcc
# upload to arduino cmd
UPLOADCMD = sudo avrdude -V -c arduino -p $(MMCU) -P $(SPORT) -b $(BAUD) -D -U flash:w:$(HEX):i

# ====================================================================================================
# compiler flags
CC_FLAGS= -Os -DF_CPU=$(F_CPU)
# linker flags
LD_FLAGS= 

# ====================================================================================================
# compile files

$(ELF): $(SOURCES)
	$(CC) $(LD_FLAGS) $(CC_FLAGS) -mmcu=$(MMCU) -o $@ $(SOURCES)

$(HEX): $(ELF)
	$(OCP) $(ELF) -O ihex $(HEX)

$(EXE): $(DIR_SRC)/test.c
	$(CCT) $(LD_FLAGS) $(CC_FLAGS) -o $@ $(DIR_SRC)/test.c

# ====================================================================================================
# 'functions'

.PHONY: all, test, upload, flash, clean, print

# compile
all: $(ELF) $(HEX) # compile hex file

# test
TestARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
test: $(EXE) # compile and run test exe on local machine
	$(EXE) $(TestARGS)

# uploads the hex file to the arduino
flash:
	$(UPLOADCMD)

# builds and uploads the hex file to the arduino 
upload: all 
	$(UPLOADCMD)

# rm build files
clean: 
	rm $(HEX) $(ELF) $(EXE)

# test makefile
print:
	$(info   SOURCES  := $(SOURCES))
	$(info   TESTARGS := $(TestARGS))

# fix to have test arguments without getting errors
%::
	@true

With this makefile you can do:

# compile all .c files (except test.c)
make all

# compile only test.c file with gcc and run it on your machine
make test # accepts arguments (ex: make test 123 abc)

# compile and upload program to arduino (same as running all and flash)
make upload

# only upload program to arduino
make flash

# delete any compiled files
make clean

# print in console which .c files are going to be compiled 
make print

And that’s all, thanks for reading and stay tuned for more tech insights and tutorials. Until next time, keep exploring the world of tech!