Introduction to Arduino Bare-Metal Programming

This article provides an introduction to programming Arduino boards without using the Arduino IDE. This is often referred to as “bare-metal” programming. By programming Arduino boards without using the Arduino IDE, you have more control over the board and can create more efficient and faster programs.

To maximize the potential of your board, learn about bit-wise operations for register-level programming for Arduino!

What is Arduino bare-metal programming?

Arduino bare-metal programming is the process of programming an Arduino board without using the Arduino IDE. This can be done using a variety of different programming languages and tools, but the most common method is to use a C++ compiler.

The advantage of bare-metal programming is that it allows you to get closer to the hardware and take full control of the board. This can be useful for optimizing code or for developing programs that are not possible to create with the Arduino IDE.

Introduction to Arduino Bare-Metal Programming

Knowing how to utilize the C language to modify the 1’s and 0’s that make up these spaces in memory is essential for making the move from the more beginner-friendly Arduino IDE environment to programming microcontrollers at the register level (also known as bare metal). I’ll describe how to achieve that in this article. The bitwise operations set, clear, toggle, and read are demonstrated here.

Setting a Bit

A bit is set when a 1 is placed at a specific location in a register without impacting any other values present in the same register. The bitwise OR operator ” | ” is used for this. The truth table for the OR operator is as follows:

A | B = Y

Introduction to Arduino Bare-Metal Programming a y

When using the OR operator to compare two bits, the result will be 1 if only one of the inputs is 1. The output will be 0 if both inputs are the same, either 1 or 0.

We can do the following in order to set the bit at position N (keep in mind that C is 0 indexed, therefore the initial position is position 0) in a register to a 1:

REGISTER |= (1<<N)

The “1<<N” term is called the mask – it’s used to select the specific bit that we are interested in modifying.

The left shift operator, <<, pushes a number to the left the specified number of times, so 1<<N pushes a 1 to the Nth position of the mask. The register and the mask are ORd ( |= ) to obtain the result.

For example: setting the bit at position 5 in a register called PORTB.

PORTA = 00010001
PORTA |= (1<<5)

PORTA:              00010001
MASK:               00100000
PORTA |= (1<<5):    00110001

The bit in the register at location 5 has been set to 1. No matter if the other bits in the number were 1 or 0, they remain unchanged.

Clearing a Bit

Placing a 1 at a specific point in a register, while leaving the other values in the same register unaffected, is known as clearing a bit. The bitwise AND operator “&” is used to do this. The truth table for the AND operator is as follows:

A & B = Y

Introduction to Arduino Bare-Metal Programming a y h

When using the AND operator to compare two bits, the result will be 1 if both of the inputs are 1. If not, the result will be 0.

To clear the bit at position N in a register (set it to 0), we can do this:

REGISTER &= ~(1<<N)

Take note of the fact that when clearing a bit, we NOT (~) the mask. The NOT operator inverts every bit in a number – the 1’s become 0’s, the 0’s become 1’s. For example: clearing the bit at position 2 in a register called PORTB.

PORTB = 00011111
PORTB &= ~(1<<2)

PORTB:               00011111
MASK:              ~(00000100)
MASK:                11111011
PORTB &= ~(1<<2):    00011011
Introduction to Arduino Bare-Metal Programming image 5

The bit at position 2 in the register has been set to 1. The other bits in the number are unchanged – no matter if they were 1 or 0.

Toggling a Bit

Flipping a bit’s value, or toggling a bit, means changing its value from 0 to 1. Make it a 0 if it’s a 1. The bitwise XOR operator “” is used to do this. The truth table for the XOR operator is as follows:

A ^ B = Y

Introduction to Arduino Bare-Metal Programming a ysl

For example: toggling the bit at position 5 in a register called PORTC.

PORTC = 00000001 PORTC ^= (1<<6) PORTC: 00000001 MASK: 01000000 PORTA |= (1<<6): 01000001

The bit in the register at position 6 has been changed from a 0 to a 1. No matter if the other bits in the number were 1 or 0, they remain unchanged.

That converted a 0 to a 1, but the identical procedure also turns a 1 into a 0.

PORTC = 00100001 PORTC ^= (1<<6) PORTC: 01000001 MASK: 01000000 PORTA |= (1<<6): 00000001

Reading a Bit

Registers are read from in addition to being written to. We mask all of the other bits in a register to a 0 (leaving the target bit unaffected), then verify the resulting number to see whether the value of a specific bit in the register is a 0 or a 1. The desired bit was a 0 if the outcome is all zeros. If the outcome is different, we can be certain that a 1 was present in the desired location.

In this, the idea of bitwise TRUE and FALSE is explored. A true is anything larger than 0, whereas a false is 0.

For example: reading the bit at position 7 in register PORTD. We can check the result with an IF statement in the C language.

PORTD = 10000000 if (PORTD & (1<<7) ) { return 1; else{ return 0; } } PORTD: 10000000 MASK: 10000000 PORTA &= (1<<7): 10000000 The function will return 1, as the result isn’t all 0’s.

Another example: reading the bit at position 0 in register PORTD.

PORTD = 10000000 if (PORTD & (1<<0) ) { return 1; else{ return 0; } } PORTD: 10000000 MASK: 00000001 PORTA &= (1<<7): 00000000 The function will return 0, as the result is all 0’s.

The registers for all of a microcontroller’s peripherals, such as GPIOs, ADCs, and communication buses, are mapped out in the datasheet (or hardware reference document) of the microcontroller. The registers are typically concealed inside of more streamlined operations when utilizing a framework like Arduino, a hardware abstraction layer, or any other microcontroller library. However, comprehending the device’s functionality and having complete control over its features depend on being able to change registers.