Wednesday, December 10, 2014

Low level GPIO on Arduino Leonardo

The Arduino boards provide an easy entry in the microcontroller (MCU) world. With practically no knowledge on electronics and in no time yo can have a little project up and running.

The Arduino way to do coding builds over an extensive set of pre-coded libraries that hides the complexities of interfacing a MCU. That's all OK if don't need to go to the limits of what the MCU can do or if you don't mind to know about how the MCU does its things. But it you need to cut corners or if you want to learn about MCUs, you should try to depart a little from the Arduino way.

One good and bad thing about Arduinos is their uniformity. All arduinos use the same digitalWrite, digitalRead, analogRead and analogWrite functions. And they work the same, more or less, if you are using an Arduino UNO with an ATmega328 or an Arduino Galileo with an Intel Quark SoC X1000. But, as the MCUs used are different, they really don't work exactly the same.

If you want to go beyond the Arduino way, the uniformity falls off as the methods change depending on the MCU the board uses. If two boards use the same kind of MCU, the internal procedures will be similar, although not always equal. If two boards use a different MCU, the internal procedures can be quite different.

In this article I will target the Arduino Leonardo. This is a quite capable board that comes to substitute the old Arduino UNO. It includes an ATmega 32u4 MCU. This is part of the megaAVR familly of MCUs. Most classic arduino boards like the UNO (ATmega328), Mega 2560 (ATmega2560), Mini (ATmega168) are in this family. In fact, the Arduinos Micro and Esplora use the very same 32u4 MCU that uses the Leonardo.

Arduino Leonardo

The Arduino Leonardo don't need to use an additional chip to provide the USB communication because the MCU that includes can manage that itself. For the same reason, the board can emulate an USB peripheral like a mouse or a keyboard.

Using an Arduino to learn the insides of the ATmega 32u4 MCU has advantages and inconvenients. On one hand, the Arduino IDE provides all kinds of functions and libraries to do complex operations so you can do low level coding of only one part of the system. For instance you can leave the serial over USB communication to the Arduino libraries and do manual control of GPIO lines. On the other hand, the Arduino IDE tries to hide the low level sintax of the MCU and the bootloader method used to load the program don't provide any hardware debugging of your code.
To make things a little worse, the Arduino libraries make some use of the MCU internal resources and it's not always easy to know what libraries will you break if you take a step out of the Arduino way.

In this first article about the insides of the Arduino Leonardo I will talk about how the digital GPIO works. That is, I will talk about how to do low level operations equivalent, but faster, to the use of the pinMode, digitalRead and digitalWrite functions.

Arduino pins and Leonardo ports

The ATmega 32u4 MCU that powers the Arduino Leonardo features 5 digital GPIO (General Purpose Input/Output) ports. Each one is labeled with a leter, so we have ports B, C, D, E and F. 

Each port can have up to 8 digital lines (numbered from 0 to 7) associated with pins of the MCU package.

ATmega 32u4 pinout
As you can see in the above pinout, most package pins include references like PE6 (Line 6 of port E) and PB0 (Line 0 of port B). As the MCU package has 44 pins and as each of the 5 ports could have up to 8 lines, you can see that you could get out of pins if you include power supplies, Xtal and USB connections. I you obseve the pinout you will see, for instance, that there is no PD5 and that the port E includes only two lines. In fact, only ports B and D are complete (8 lines each).

The line distribution is:
  • Port B : 8 lines PB0 to PB7
  • Port C : 2 lines PC6 and PC7
  • Port D : 8 lines PD0 to PD7
  • Port E : 2 lines PE2 and PE6
  • Port F : 6 lines PF0, PF1 and PF4 to PF7
So you have 26 GPIO lines in total. You can also see this line distribution in the MCU internal block diagram description.

Internal ATmega 32u4 structure
The 26 GPIO lines are relabeled, in the Arduino functions, as lines 0 to 23. You can see that there are two lines missing. That's because PD5 and PE2 are not available as digital or analog lines in the Leonardo board. If you look at the board you will only see the numbers of 14 of the lines (0 to 13). The lines 14 to 17 are located on the male 6 pin ISP/SPI header and the lines 18 to 23 are alias names for the analog A0 to A5 lines that can also be used as digital I/O.

Top view of the Leonardo Board
The ATmega 32u4 features 12 analog inputs. That's more that the 6 A0 to A5 marked on the board. Those inputs are avalilable as the lines 4, 6, 8, 9, 10 and 12 and you can use the alias names A6, A7, A8, A9, A10 and A11 for those lines.

Why aren't all the extra analog lines marked as A6 to A11?
Well, because the original Arduino, with PDIP package, only had 6 lines and keeping the pin form factor of the board is important to provide board to board compatibility.
In other words, the Leonardo, with its 32u4 MCU is different from the UNO with its 328 MCU but the board is marked to use the same pin names.

Arduino UNO top view
The different port lines are not related to the standard Arduino pin names. That's because, to keep the board pinout compatibility, the lines that can give PWM signals using the analogWrite function need to be associated to lines 3, 5, 6, 9, 10, 11 and 13. 
In the UNO board the pin numbering is easy, digital pins 0 to 7 go to lines 0 to 7 of port D, lines 8 to 13 go to lines 0 to 5 of port B and analog lines 0 to 5 go to lines 0 to 5 of port C.
In the Leonardo, to keep things compatible, there is a mess of lines. Digital lines 0 and up are connected to PD2, PD3, PD1, PD0, PD4, PC6, PD7, PE6.... All messed up.

The only way to know with Arduino line relates to which port line on the Leonardo is using a written list as there is no logical distribution as it was on the Arduino UNO.

I you want to see which line goes with what, you can use the Arduino Leonardo Spreadsheet I wrote on Google Drive from the link below.

Bitwise variables and registers

Every digital port B, C, D, E and F has three related 8 bit registers. You can access those registers in a similar way that accessing byte variables. You only have to know their names. Every register has one pin associated to each port line. So, for instance, bit 3 (with weight 2^3 = 8), is related to the line 3 of the port. You can think of port registers as sets of 8 boolean values.

If you want to set bits 0, 4 and 6 of a 8 bit variable you can add its weights:

VARIABLE = 2^0 + 2^4 + 2^6;

That is:

VARIABLE = 1 + 16 + 64;

Or, in a more "C" style:

VARIABLE = 1 | (1 << 4) | (1 << 6);

To ease the naming of bits you can use the bit( ) macro that is defined in Arduino.h.
Using this macro you can use an easier bit assignment:

VARIABLE = bit(0) + bit(4) + bit(6);

To set one bit of a variable you can use the bitwise OR operator:

VARIABLE = VARIABLE | bit(3);   // Sets bit 3 of VARIABLE

To clear one bit of a variable you can use the bitwise AND and complement ~ operators.

VARIABLE = VARIABLE & (~bit(3));  // Clears bit 3 of VARIABLE

To ease clear and set of variable you can define two macros:


So, you can use:

SET_FLAG(VARIABLE,bit(3));   // Sets bit 3 of VARIABLE
CLEAR_FLAG(VARIABLE,bit(3);  // Clears bit 3 of VARIABLE

You can also set several bits at the same time:

SET_FLAG(VARIABLE,bit(2)|bit(3));   // Sets bits 2 and 3 of VARIABLE

CLEAR_FLAG(VARIABLE,bit(2)|bit(3)); // Clears bits 2 and 3 of VARIABLE

If you want, you can use the stock bitRead, bitSet, bitClear and bitWrite macros defined in Arduino.h:

It is important to know which are the Arduino macros as they are compliled in place and, because of that, they don't have the overhead associated to a function call.

Observe that, using the stock Arduino macros, you should not use the bit( ) macro in the calls:

if (bitRead(VARIABLE,3) 
       // Do something if bit 3 of VARIABLE is set
bitSet(VARIABLE,3);         // Sets bit 3 of VARIABLE
bitClear(VARIABLE,3);       // Clears bit 3 of VARIABLE
bitWrite(VARIABLE,3,value); // Sets or Clears bit 3 of 
                            // VARIABLE depending on value

Beware that you cannot use the bitSet and bitClear macros to set or clear several bits at the same time.

Low level digital I/O

The three registers associated to each port controls the port direction (similar to pinMode) the port output (similar to digitalWrite) and the port input (similar to digitalRead). Let's see the one by one.

Port Data Direction Registers (DDRB, DDRC, DDRD, DDRE, DDRF)

Those registers indicate, for each line, if it is an input or an output. Each bit on the register can only have two values:

  • "0" Indicates that the line is an input
  • "1" Indicates that the line is an output
So, for instance,  to set Arduino pins 10 and 11, associated to PB6 and PB7 in output mode you can write:


This way, the above line is equivalent to:


But ocupies less program space and requires less time to execute.

Port Output Registers (PORTB, PORTC, PORTD, PORTE, PORTF)

When a port line is in output mode, as set with a DDRx register, this register holds the value that will be seen at the output line.
  • "0" Indicates a low value (next to 0 Volts)
  • "1" Indicates a high value (next to 5 Volts)
For instance:


Is equivalent to:


The low and high output values can be obtained from the datasheet of the ATmega 42u4.
DC Voltage Data
You can see that the low level output voltage Vol is guaranteed to be below 0.7V up to 10mA of sink current when the supply is 5V. In a similar way, you can see that the high level voltage is guaranteed to be over 4.2V up to 10mA of source current.

When a port line is in input mode, however, the PORTx register indicates if the port will feature or not a pull-up resistor.

  • "0" Indicates that no Pull-Up will be used
  • "1" Indicates that a Pull-Up will be used
It is important to note that when you configure one port from output to input mode, the previous value on the Port Output Register determine if it will feature or not a Pull-Up.

Pull-Up resistor data
As you can see from the data obtained from the MCU datasheet the Pull-Up resistors on the I/O ports are guaranteed to be between 20kOhm and 50kOhm.

That way:


Configures the Arduino pin 10, associated to PB6, in input mode.
And is equivalent to:




Configures the Arduino pin 10, in input mode with a Pull-Up resistor to Vdd.
And is equivalent to:


Port Input Registers (PINB, PINC, PIND, PINE, PINF)

This register always returns, when read, the logic state at the related port pin. This is independendent on the Input or Output configuration of the port.

This register is normally read for input pins to determine the state of the related lines, but it can also be read for output pins to verify that no external element is forcing a different logic state that the one we set.

Each bit reads:

  • "0" If the related line is at low level (Voltage below Vil_max)
  • "1" If the related line is at high level (Voltage above Vih_min)
For instance:

if (PINB&bit(6))  { // Do something 

Is equivalent to:

if (digitalRead(10)) { // Do something 

You can find the Vil_max and Vih_min thresholds in the table shown before from the ATmega 32u4 datasheet. As you can see they are:

  • Vil max = 0,2Vcc - 0,1V =  0.9V at Vcc = 5V
  • Voh min = 0,2Vcc + 0,9V = 1.9V at Vcc = 5V
Writing to the PINx registers have not relation with reading them. In fact, writing to a PINx register bit to "1" affects the corresponding Output Register PORTx bit toggling its value. Writting a bit to "0" doesn't make any effect.  

For instance, the line:

PINB=BIT(6);  // Toggles PORTB bit 6

Is much faster than the equivalent line:

digitalWrite(10,!digitalRead(10));  // Toggles Arduino pin 10

The following code implements two functions: highLevelToggle and lowLevelTogge. The first one uses standard Arduino calls and the second uses low level port accesses.

Testing the functions you can see that the high level one gives an output frequency of about 43kHz whereas the low level one gives a frequency of about 2.5MHz. The low level function is 58 times faster than the high level one so, when you need to get all the available speed from the MCU, nothing beats low level programming. The next step will be using assembler, but the gain wont be so big in this case.

Code on Github (11/02/2018)

The code is now on Github:

1 comment: