A Closer Look at Digital Output in the RP2040

Digital output seems pretty simple, specially if you are using the Arduino runtime. A call to digitalWrite() will put a pin in high or low level. Is that all? No... in this post we will look at some important details about digital output in the RP2040 (the microcontroller used in the Pi Pico).

A four digit 7 segment display connected to the Pi Pico


The RP2040 has 36 general purpose input/output (GPIO) pins, organized in two banks. One of this banks , with 6 pins, is normally dedicated to interface with the Flash memory where the firmware resides. What I am talking here refers mostly to the other bank (the User Bank) where we have GPIO0 to GPIO29.

The first thing to remember is that a pin can have multiple functions. The figure bellow shows the logic structure of a pin, highlighting the various parts of the microcontroller that can be connected to it:

source: RP2040 datasheet

The digital output function corresponds to the "GPIO" block in the figure and is implemented as a SIO ("Single-cycle I/O", I will talk about it shortly). We have basically three 32 bit registers, where each bit is associated with a pin:
  • GPIO_OUT determines the state (high or low) of the pins, if output is enabled and the pin is configured for GPIO.
  • GPIO_OE enables or disables output, if the pin is configure for GPIO.
  • GPIO_IN indicates the digital state (high or low) of the pin, regardless of the pin function.
There is a single set of these registers, accessible by the two ARM cores. The "Single-cycle I/O" means that that these registers can be accessed atomically through the SET, CLR and XOR instructions. This means that, as long as you use this instructions to update the registers, there is no risk of concurrency problems between the two core (or interrupts). If you use separate instructions to read a register, generate the new value and write back it, you can have problems if the other core or an interrupt changes the same register.

Looking back at the previous figure, notice the block labeled "IO PAD". It represents the electrical interface between the internal logic and the actual pin:

source: RP2040 datasheet

The PAD has many configurations, also controlled by SIO registers:
  • Slew rate controls how fast a pin changes state
  • Drive strenght controls how "strong" the signal is (more about that soon)
  • We can use pull-up and pull-down resistors (between 50k and 80k)
  • We can enable or disable the input buffer
  • We can enable or disable the schmitt trigger function on the input buffer. When enabled, different voltage levels are used detecting changes in the input form low to high and from high to low. This helps ignore small changes in the input signal
The graph bellow shows the effect of the drive strength configuration:

source: RP2040 datasheet

In other words, a higher drive strength result in an output voltage closer to ideal if more current flows through the pin. Independent of this configuration, the maximum sum of current of all GPIO pins is 50  mA. The default drive strenght is 4 mA.

While the datasheet details the addresses and bit associations of the registers, it is recommended that we use the SDK functions to control this functions. The following code shows how to light up all the segments in a 7 segment common cathode display (as in the photo at the top). Segments and the common cathode are connected to GPIO pins. 1K resistors in each segment limit its current to 1.4 mA (for the particular display used). The common cathode will supply the sum of these currents (7 x 1.4  = 9.8 mA), so I had to configure a greater drive strength than the default.
#include <stdio.h>
#include "pico/stdlib.h"  
#include "hardware/gpio.h"  
  
int main() {  
    gpio_init_mask (0xFF);  // configura os pinos para GPIO  
    gpio_set_dir_masked (0xFF, 0xFF); // configura os pinos como saída  
    gpio_set_drive_strength (7, GPIO_DRIVE_STRENGTH_12MA);  // GPIO7 "vitaminado"  
    gpio_clr_mask (0x7F);   // nível baixo nos pinos ligados aos segmentos  
    gpio_set_mask (0x80);   // nível alto no anodo comum  
    while (1)  
        ;  
    return 0;  
}
In the above code I used the "mask" versions of the functions where a bit mask selects the affected pins. In my circuit GPIO0 to GPIO6 are connected to the segments and GPIO7 is the connect to the common cathode.

Details about the RP2040 can be found at its datasheet, details on the SDK functions can be found in its manual. Both documents are available for download at


Comments

Popular posts from this blog