Stephen Smith's Blog

Musings on Machine Learning…

Archive for April 2021

I/O Co-processing on the Raspberry Pi Pico

with 4 comments

Introduction

Last time we looked at how to access the RP2040’s GPIO registers directly from the CPU in Assembly Language. This is a common technique to access and control hardware wired up to a microcontroller’s GPIO pins; however, the RP2040 contains a number of programmable I/O (PIO) coprocessors that can be used to offload this work from the main ARM CPUs. In this article we’ll give a quick overview of the PIO coprocessors and present an example that moves the LED blinking logic from the CPU over to the coprocessors, freeing the CPU to perform other work. There is a PIO blink program in the SDK samples, which blinks three LEDs at different frequencies, we’ll take that program and modify it to blink the LEDs in turn so that it works the same as the examples we’ve been working with.

PIO Overview

There are eight PIO coprocessors divided into two banks for four. Each bank has a single 32 word instruction memory that contains the program(s) that run on the coprocessors. 32 instructions aren’t very many, but you can do quite a bit with these. The SDK contains samples that implement quite a few communication protocols as well as showing how to do video output. 

Each PIO has an input and output FIFO buffer for exchanging data with the main CPUs.

The PIO coprocessors execute their own Assembly Language which the Raspberry folks call a state machine, though they also say they think it is Turing-complete. Below is a diagram showing one of the banks of four. This block is then duplicated twice in the RP2040 package.

Each processor has an X and Y 32-bit general purpose register, input and output shift registers for transferring data to and from the FIFOs, a clock divider register to help control timing, a program counter and then the register to hold the executing instruction as shown in the following diagram.

Each instruction can contain a few bits that specify a delay value, so for many protocols you can control the timing just by adding a timing delay to each instruction. Combine this with the clock divider register to slow down processing and you have a lot of control of timing without using extra instructions.

Sample LED Blinking Program

You write the Assembly Language PIO part of the program into a .pio file which is then compiled by the PIO Assembler into a .h file to include into your program. You can also include C helper functions here and the Pico SDK recommends including an initialization function. The various RP2040 SDK functions to support this are pretty standard and you tend to copy/paste these from the SDK samples.

We are blinking the LEDS using a 200ms delay time which by computer speeds is very slow, but for humans is quite quick. This means we can’t use the clock divider functionality and instruction delays as they don’t go this slow. Instead we have to rely on an old fashioned delay loop. We calculated the delay value in the main function using the frequency of the processor and then doing a loop. We do this delay loop twice because we need to wait for two other LEDs to flash before it’s our turn again. The pull instruction pulls the delay from the read FIFO, then out transfers it to the y register. We move y to x, turn on the pin and then do the delay loop decementing x until its zero. Then we turn the pin off and do the delay loop twice.

.program blink
    pull block
    out y, 32
.wrap_target
    mov x, y
    set pins, 1   ; Turn LED on
lp1:
    jmp x– lp1   ; Delay for (x + 1) cycles, x is a 32 bit number
    mov x, y
    set pins, 0   ; Turn LED off
lp2:
    jmp x– lp2   ; Delay for the same number of cycles again
    mov x, y
lp3:   ; Do it twice since need to wait for 2 other leds to blink
    jmp x– lp3   ; Delay for the same number of cycles again
.wrap             ; Blink forever!

% c-sdk {
// this is a raw helper function for use by the user which sets up the GPIO output, and configures the SM to output on a particular pin

void blink_program_init(PIO pio, uint sm, uint offset, uint pin) {
   pio_gpio_init(pio, pin);
   pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
   pio_sm_config c = blink_program_get_default_config(offset);
   sm_config_set_set_pins(&c, pin, 1);
   pio_sm_init(pio, sm, offset, &c);
}
%}

Now the main C program. In this one we configure the pins to use. Note that we will use a coprocessor for each pin, so three coprocessors but each one executing the same program. We start a pin flashing, sleep 200ms and then start the next  one. This way we achieve the same effect as we did in our previous programs.

After we get the LED flashing running on the coprocessors, we have an infinite loop that just prints a counter out to the serial port. This is to demonstrate that the CPU can go on and do anything it wants and the LEDs will keep flashing independently without any of the CPU’s attention.

#include <stdio.h>

#include “pico/stdlib.h”
#include “hardware/pio.h”
#include “hardware/clocks.h”
#include “blink.pio.h”

const uint LED_PIN1 = 18;
const uint LED_PIN2 = 19;
const uint LED_PIN3 = 20;
#define SLEEP_TIME 200

void blink_pin_forever(PIO pio, uint sm, uint offset, uint pin, uint freq);

int main() {
    int i = 0;

    setup_default_uart();

    PIO pio = pio0;
    uint offset = pio_add_program(pio, &blink_program);
    printf(“Loaded program at %d\n”, offset);
    blink_pin_forever(pio, 0, offset, LED_PIN1, 5);
    sleep_ms(SLEEP_TIME);
    blink_pin_forever(pio, 1, offset, LED_PIN2, 5);
    sleep_ms(SLEEP_TIME);
    blink_pin_forever(pio, 2, offset, LED_PIN3, 5);

    while(1)
    {
        i++;
        printf(“Busy counting away i = %d\n”, i);
    }
}

void blink_pin_forever(PIO pio, uint sm, uint offset, uint pin, uint freq) {
    blink_program_init(pio, sm, offset, pin);
    pio_sm_set_enabled(pio, sm, true);
    printf(“Blinking pin %d at %d Hz\n”, pin, freq);
    pio->txf[sm] = clock_get_hz(clk_sys) / freq;
}

Summary

This was a quick introduction to the RP2040’s PIO coprocessors. The goal of any microcontroller is to control other interfaced hardware, whether measurement sensors or communications devices (like Wifi). The PIO coprocessors give the RP21040 programmer a powerful weapon to develop sophisticated integration projects without requiring a lot of specialized hardware to make things easier. It might be nice to have a larger instruction memory, but then in a $4 USD device, you can’t really complain.

For people playing with the Raspberry Pi Pico or another RP2040 based board, you can program in 32-bit ARM Assembly Language and might want to consider my book “Raspberry Pi Assembly Language Programming”.

Written by smist08

April 30, 2021 at 10:02 am

Bit-Banging the Raspberry Pi Pico’s GPIO Registers

with 5 comments

Introduction

Last week, I introduced my first Assembly Language program for the Raspberry Pi Pico. This was a version of my flashing LED program that I implemented in a number of programming languages for the regular Raspberry Pi. In the original article, I required three routines written in C to make things work. Yesterday, I showed how to remove one of these C routines, namely to have the main routine written in Assembly Language. Today, I’ll show how to remove the two remaining C routines, which were wrappers for two SDK routines which are implemented as inline C functions and as a consequence only usable from C code.

In this article, we’ll look at the structure for the GPIO registers on the RP2040 and how to access these. The procedure we are using is called bit-banging because we are using one of the two M0+ ARM CPU cores to loop banging the bits in the GPIO registers to turn them on and off. This isn’t the recommended way to do this on the RP2040. The RP2040 implements eight programmable I/O (PIO) co-processors that you can program to offload this sort of thing from the CPU. We’ll look at how to do that in a future article, but as a first step we are going to explore bit-banging mostly to understand the RP2040 hardware better.

The RP2040 GPIO Hardware Registers

There are 28 programmable GPIO pins on the Pico. There are 40 pins, but the others are ground, power and a couple of specialized pins (see the diagram below).

This means that we can assign each one to a bit in a 32-bit hardware register which is mapped to 32-bits of memory in the RP2040’s address space. The GPIO functions are controlled by writing a 1 bit to the correct position in the GPIO register. There is one register to turn on a GPIO pin and a different register to turn it off, this means you don’t need to read the register, change one bit and then write it back. It’s quite easy to program these since you just place one in a CPU register, shift it over by the pin number and then write it to the correct memory location. These registers start at memory location 0xd0000000 and are defined in sio.h. Note there are two sio.h files, one in hardware_regs which contains the offsets and is better for Assembly Language usage and then one in hardware_structs which contains a C structure to map over the registers. Following are the GPIO registers, note that there are a few other non-GPIO related registers at this location and a few unused gaps in case you are wondering why the addresses aren’t contiguous.

RegisterAddress
gpio_in0xd0000004
gpio_hi_in0xd0000008
gpio_out0xd0000010
gpio_set0xd0000014
gpio_clr0xd0000018
gpio_togl0xd000001c
gpio_oe0xd0000020
gpio_oe_set0xd0000024
gpio_oe_clr0xd0000028
gpio_togl0xd000002c
gpio_hi_out0xd0000030
gpio_hi_set0xd0000034
gpio_hi_clr0xd0000038
gpio_hi_togl0xd000003c
gpio_hi_oe0xd0000040
gpio_hi_oe_set0xd0000044
gpio_hi_oe_clr0xd0000048
gpio_hi_oe_togl0xd000004c

Notice that there are a number of _hi_ registers, perhaps indicating that Raspberry plans to come out with a future version with more than 32 GPIO pins.

In the SDK and my code below we just write one bit at a time, I don’t know if the RP2040’s circuitry can handle writing more bits at once, for instance can we set all three pins to output in one write instruction? Remember hardware registers tend to have minimal functionality to simplify the electronics circuitry behind them so often you can’t get too complicated in what you expect of them.

Bit-Banging the Registers in Assembly

Below is the new updated program that doesn’t require the C file. In our routines to control the GPIO pins, we pass the pin number as parameter 1, which means it is in R0. We place 1 in R3 and then shift it left by the value in R0 (the pin number). This gives the value we need to write. We then load the address of the register we need, which we specified in the .data section and write the value. Note that we need two LDR instructions, once to load the address of the memory address and then the second to load the actual value.

@
@ Assembler program to flash three LEDs connected to the
@ Raspberry Pi GPIO port using the Pico SDK.
@
@

.EQU LED_PIN1, 18
.EQU LED_PIN2, 19
.EQU LED_PIN3, 20
.EQU sleep_time, 200

.thumb_func
.global main             @ Provide program starting address to linker

.align  4 @ necessary alignment

main:

@ Init each of the three pins and set them to output

MOV R0, #LED_PIN1
BL gpio_init
MOV R0, #LED_PIN1
BL gpiosetout
MOV R0, #LED_PIN2
BL gpio_init
MOV R0, #LED_PIN2
BL gpiosetout
MOV R0, #LED_PIN3
BL gpio_init
MOV R0, #LED_PIN3
BL gpiosetout

loop:

@ Turn each pin on, sleep and then turn the pin off

MOV R0, #LED_PIN1
BL gpio_on
LDR R0, =sleep_time
BL sleep_ms
MOV R0, #LED_PIN1
BL gpio_off
MOV R0, #LED_PIN2
BL gpio_on
LDR R0, =sleep_time
BL sleep_ms
MOV R0, #LED_PIN2
BL gpio_off
MOV R0, #LED_PIN3
BL gpio_on
LDR R0, =sleep_time
BL sleep_ms
MOV R0, #LED_PIN3
BL gpio_off

B       loop @ loop forever

gpiosetout:
@ write a 1 bit to the pin position in the output set register
movs r3, #1
lsl r3, r0 @ shift over to pin position
ldr r2, =gpiosetdiroutreg @ address we want
ldr r2, [r2]
str r3, [r2]
bx lr

gpio_on:
movs r3, #1
lsl r3, r0 @ shift over to pin position
ldr r2, =gpiosetonreg @ address we want
ldr r2, [r2]
str r3, [r2]
bx lr

gpio_off:
movs r3, #1
lsl r3, r0 @ shift over to pin position
ldr r2, =gpiosetoffreg @ address we want
ldr r2, [r2]
str r3, [r2]
bx lr

.data
      .align  4 @ necessary alignment
gpiosetdiroutreg: .word   0xd0000024 @ mem address of gpio registers
gpiosetonreg: .word   0xd0000014 @ mem address of gpio registers
gpiosetoffreg: .word   0xd0000018 @ mem address of gpio registers

Having separate functions for gpio_in and gpio_out simplifies our code since we don’t need any conditional logic to load the correct register address.

We loaded the actual address from a shared location. We could have loaded the base address of 0xd000000 and then stored things via an offset, but I did this to be a little clearer. If you look at the disassembly of the SDK routine, it does something rather clever to get the base address. It does:

movs r2, #208 @ 0xd0
lsl r2, r2, #24 @ becomes 0xd0000000

And then uses something like:

str r3, [r2, #40] @ 0x28

To store the value using an index which is the offset to the correct register. I thought this was rather clever on the C compiler’s part and represents the optimizations that the ARM engineers have been adding to the GCC generation of ARM code. This technique takes the same time to execute, but doesn’t require saving any values in memory, saving a few bytes which may be crucial in a larger program.

Summary

Writing to the hardware registers directly on the Raspberry Pi Pico is a bit simpler than the Broadcom implementation in the full Raspberry Pi. With these routines we wrote our entire program in Assembly Language. There is still C code in the SDK which will be linked into our program and we are still calling both gpio_init and sleep_ms in the SDK. We could look at the source code in the SDK and reimplement these in Assembly Language, but I don’t think there is any need. Between the RP2040 documentation and the SDK’s source code it is possible to figure out a lot about how the Raspberry Pi Pico works.

For people playing with the Raspberry Pi Pico or another RP2040 based board, you can program in 32-bit ARM Assembly Language and might want to consider my book “Raspberry Pi Assembly Language Programming”.

Written by smist08

April 24, 2021 at 11:50 am

Calling Main in Assembly Language on the RP2040

with 2 comments

Introduction

In last week’s article, I presented my first Assembly Language program on the Raspberry Pi Pico. The program worked, but it included some C code that I wasn’t happy with. In this article, I’ll explain why I needed to have the main entry point in C, what I missed and how to correct this problem.

The entry point is a function main() with no parameters or return code called by the RP2040 initialization code after it initializes the RP2040 hardware. In C this worked no problem, but in Assembly Language it resulted in a hardware fault on executing the first instruction in my main() routine. This was a bit of a head scratcher and it took a couple of days before I realized what the problem was. My first thought was that it was alignment, but no it wasn’t that. Perhaps I needed to duplicate the first few instructions in the Assembly Language generated by the C compiler, but no that still caused a hardware fault. Rather mystifying and annoying.

Use the Source

The program you run on the Pico contains pretty much everything in a single executable, that initializes the CPU, peripheral hardware and then runs in an endless loop forever. There is no operating system, just your program. The Raspberry Pi Pico contains a bit of firmware which is activated when you power on with the bootsel button pressed, this allows the Pico to connect as a shareable flash drive to a USB host, and will allow you to copy files into the writable part of the Pico’s flash memory. After that it reboots to let the program run.

One of the good things about the Pico is that the SDK contains the source code for this whole thing, and when you build your program, it actually compiles all this source code alongside your code (there are no libraries in this environment). This means you can build a debug build where everything is debuggable including both your code and the SDK code. This means you can set a breakpoint before your code and single step through the SDK into your code. You can’t start debugging at the very first instruction, you need to let the first bit of the SDK initialize the processor before starting, but you can set a breakpoint fairly early. I found a good place was the platform_entry routine, which is an Assembly Language function in crt0.S. This is the function that initializes the SDK environment and then calls your main() starting point. The code for this routine is fairly innocuous:

platform_entry: // symbol for stack traces
    // Use 32-bit jumps, in case these symbols are moved out of branch range
    // (e.g. if main is in SRAM and crt0 in flash)
    ldr r1, =runtime_init
    blx r1
    ldr r1, =main
    blx r1
    ldr r1, =exit
    blx r1

Nothing special, it just loads the address of our main routine and calls it. Stepping through the C code, it works, stepping through the Assembly Language code, hardware fault.

At some point I thought to look at the documentation for the BLX instruction, why were they calling this rather than BL? This turned out to be the root of the problem.

On a full ARM A-series CPU, like those in a full Raspberry Pi or in your cell phone, it can execute a rich set of instructions, which are the regular ARM 32-bit instruction set, but on the microcontroller M-series CPU like in the Pico it only executes the so called “thumb” instructions. On the A-series CPU you switch back and forth between regular and thumb modes using the BLX instruction. Thumb instructions are 16-bit in length, regular instructions are 32-bit, both have to be aligned, on even bytes the other on 4-byte boundaries. Both of these are even addresses so the true address of any instruction is even, which means the low order bit isn’t really used (it has to be zero). The BLX instruction uses this low order bit to specify whether to switch to thumb mode or not. If it is one, then thumb mode, if even then regular instruction mode. Let’s look at the disassembly for this routine:

1000021a <platform_entry>:
1000021a: 4919      ldr r1, [pc, #100] ; (10000280 <__get_current_exception+0x1a>)
1000021c: 4788      blx r1
1000021e: 4919      ldr r1, [pc, #100] ; (10000284 <__get_current_exception+0x1e>)
10000220: 4788      blx r1
10000222: 4919      ldr r1, [pc, #100] ; (10000288 <__get_current_exception+0x22>)
10000224: 4788      blx r1

10000280: 100012bd .word 0x100012bd   ; runtime_init
10000284: 10000361 .word 0x10000360   ; main
10000288: 100013a9 .word 0x100013a9   ; exit

Notice the address for my main routine is even whereas the other two routines are odd. If I compile with the C routine then main has an odd address as well. I didn’t think of this because the RP2040’s M-series CPU only executes thumb instructions, so why have any functionality to switch between modes? I don’t know but if you do tell it to switch to regular instructions then you get a hardware fault.

The other question is why the author of crt0.S in the SDK calls routines with BLX rather than BL? Afterall the Pico doesn’t support regular instructions, so you are always in thumb mode. If platform_entry used BL instead, then I wouldn’t have had any problem. I wonder if this indicates they developed the SDK on an A-series CPU, perhaps before they obtained real RP2040’s and this indicates how they did early development on the SDK? Or perhaps there is a way to emulate the RP2040 on a full A-series CPU and this is how the developers at the Raspberry Pi foundation operate.

To correct the problem, we just need to indicate our main() routine is a thumb routine. We do this by placing a .thumb_func directive in front of the .global directive.

.thumb_func
.global main             @ Provide program starting address to linker

.align  4 @ necessary alignment

main:

The key point is that this is in front of the .global, since it is really just the linker that needs to process this to set up the correct address when it links in crt0.

Summary

This eliminates the need for the C main() function we had last week. Next time we’ll eliminate the two other C routines we had and explore how the Raspberry Pi Pico’s GPIO control registers work. As with most problems, working through the solution, teaches us a bit more about how the RP2040 works and reminds us that there are consequences of using a subset of the full ARM instruction set.

For people using this SDK, you can program in 32-bit ARM Assembly Language and might want to consider my book “Raspberry Pi Assembly Language Programming”.

Written by smist08

April 23, 2021 at 9:11 am

Assembly Language on the Raspberry Pi Pico

with 12 comments

Introduction

The Raspberry Pi Pico is the Raspberry Foundation’s first entry into the domain of Arduino style microcontrollers. The board contains Raspberry’s own designed SoC (System on a Chip) containing a dual core ARM Cortex-M0+ CPU along with memory and a collection of I/O circuitry. There are no keyboard, mouse or monitor ports on the board, only a micro-USB to connect to a host computer, a number of GPIO pins and three debug pins. This SoC is called the RP2040 and is licensed to other companies to use in their own boards. Raspberry supports programming this board in either C/C++ or MicroPython. The C/C++ SDK also supports Assembly Language programming to some degree and this article is a look at my first attempt to write an Assembly Language program for this board. I ran into a few problems and still have a few things to figure out and we’ll explain those in the article. We’ll write an Assembly Language version of the program we wrote in C last time to flash three connected LEDs.

ARM Cortex-M0+ Assembly Language

I blogged about 32-bit ARM Assembly Language here, and then presented the flashing LED Assembly Language program for the Raspberry Pi here. Further I wrote a whole book on 32-bit ARM Assembly Language Programming: “Raspberry Pi Assembly Language Programming”. These are all oriented to ARM’s full A-series processors which include floating point units (FPU), vector processors, virtual memory support and much more. The ARM M-series processors are a subset of these, designed to be low cost, use little memory and be very power efficient. The ARM M-series processors only contain what are called the ARM “thumb” instructions. Normally, on an A-series processor, each instruction takes 32-bits, but for some applications this uses too much memory, so ARM came up with “thumb” instructions where if the processor is operating in “thumb” mode then each instruction is only 16-bits in length, thus only using half the memory. The original set of “thumb” instructions was too limited, so ARM added a way to run some 32-bit instructions in with the 16-bit instructions and that makes the modern “thumb” instructions set used by the M-series processors. One consequence of using the “thumb” instructions is that registers R8 to R12 are not accessible and hence not implemented on the chip, thus saving circuitry. The registers you do have are all 32-bit and the Raspberry RP2040 has special multiplication and division circuitry to perform these operations quickly.

Code

This program uses the C/C++ SDK to access the GPIO pins, this means this Assembly Language program is quite similar to last week’s C program. To call a routine in Assembly, you put the first parameter in R0, the second in R1 and then call Branch with Link (BL). BL places the address of the next instruction into the LR register, so the called return returns by branching to the address contained in the LR register. When calling functions there is a convention on who has to save which register on the stack, but we don’t use any register over the function calls, so we don’t need to do this. This program is set up as an infinite loop, since there is nothing for the main routine to return to and if it does return the processor halts.

Assembly Language code:

@
@ Assembler program to flash three LEDs connected to the
@ Raspberry Pi Pico GPIO port using the Pico SDK.
@
@

.EQU LED_PIN1, 18
.EQU LED_PIN2, 19
.EQU LED_PIN3, 20
.EQU GPIO_OUT, 1
.EQU sleep_time, 200

.global main_asm             @ Provide program starting address to linker
main_asm:

MOV R0, #LED_PIN1
BL gpio_init
MOV R0, #LED_PIN1
MOV R1, #GPIO_OUT
BL link_gpio_set_dir
MOV R0, #LED_PIN2
BL gpio_init
MOV R0, #LED_PIN2
MOV R1, #GPIO_OUT
BL link_gpio_set_dir
MOV R0, #LED_PIN3
BL gpio_init
MOV R0, #LED_PIN3
MOV R1, #GPIO_OUT
BL link_gpio_set_dir
loop:   MOV R0, #LED_PIN1
MOV R1, #1
BL link_gpio_put
LDR R0, =sleep_time
BL sleep_ms
MOV R0, #LED_PIN1
MOV R1, #0
BL link_gpio_put
MOV R0, #LED_PIN2
MOV R1, #1
BL link_gpio_put
LDR R0, =sleep_time
BL sleep_ms
MOV R0, #LED_PIN2
MOV R1, #0
BL link_gpio_put
MOV R0, #LED_PIN3
MOV R1, #1
BL link_gpio_put
LDR R0, =sleep_time
BL sleep_ms
MOV R0, #LED_PIN3
MOV R1, #0
BL link_gpio_put
B       loop

.data

      .align  4 @ necessary alignment

I didn’t intend to include any C code, but I ran into a couple of problems that require it. One is that a large number of SDK functions are inline C functions which means they can’t be called from outside of C. In our case two functions gpio_set_dir and gpio_put are inline and required wrapping. The other problem is that if the main program is Assembly Language then the code to initialize the board doesn’t seem to be called. I think this is a matter of setting the correct CMake options, but I haven’t had a chance to figure it out yet. For now we have main in the C code and then call the Assembly Language main routine.

C code:

#include “hardware/gpio.h”

void link_gpio_set_dir(int pin, int dir)
{
gpio_set_dir(pin, dir);
}

void link_gpio_put(int pin, int value)
{
gpio_put(pin, value);
}

void main()
{
main_asm();
}

The Raspberry Pi Pico SDK uses the CMake system to manage builds. The SDK provides a large set of build rules. You run CMake and then it creates a makefile that compiles your program.

CMake file:

cmake_minimum_required(VERSION 3.13)

include(pico_sdk_import.cmake)

project(test_project C CXX ASM)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

pico_sdk_init()

include_directories(${CMAKE_SOURCE_DIR})

add_executable(flashledsasm
  mainmem.S
  sdklink.c
)

pico_enable_stdio_uart(flashledsasm 1)
pico_add_extra_outputs(flashledsasm)
target_link_libraries(flashledsasm pico_stdlib)

Still To-Do

The program works, but there are a few things I’m not happy about. The Raspberry Pi Pico SDK is pretty new, so there aren’t a lot of answers on StackOverflow yet. The good thing is that it is all open source, so it is just a matter of time to figure out the code. Here is what I’ll be working on:

  1. How to have main be in Assembly Language and have the board properly initialized. Match the C startup sequence.
  2. Figure out the details of the GPIO registers and have Assembly Language versions of the inline C code that accesses these. They are similar to those on the full Raspberry Pi, but different.
  3. How to get constants from the C include file, on first try this didn’t work and gave syntax errors, but the SDK says they should be usable from Assembly Language. They might need a couple of fixes.

Summary

I planned to write a 100% Assembly Language program, but didn’t quite make it. At least the program works, showing you can include Assembly Language in your RP2040 projects. The support to build using the GCC macro assembler is all there and besides some interactions with the SDK all seems to work well. Of course the Raspberry Pi Pico SDK is pretty new so there will be a lot of updates and there are still a number of undocumented holes to investigate.

Written by smist08

April 16, 2021 at 9:43 am

Raspberry Pi Pico First Project

with 7 comments

Introduction

Last week, I blogged about Adafruit’s Feather RP2040 microcontroller. I’ve now received a Raspberry Pi Pico which is also based on the ARM RP2040 processor. The Adafruit version is oriented around programming with CircuitPython and then interfacing to the various companion boards in Adafruit’s Feather lineup. The Raspberry Pi Pico is oriented around programming in C/C++ using the Raspberry Pico SDK and then designing interfaces to other devices yourself. Although you can do either with both boards, I’m going by the direction of the two companies tutorials and volume of documentation. The Raspberry Pi Pico also favours doing your development from a full Raspberry Pi, although you can use a Windows, Linux or Mac, it is quite a bit harder to setup serial communications and debugging.

In this article, I’m going to look at doing a small project with the Raspberry Pi Pico using the C SDK. I’ll look at the elements you use to program, debug and deploy code. I’ll use the same flashing LEDs project that I’ve written about previously.

More Soldering

Like the Adafruit board, if you want to use a breadboard to set up your electronics project, you need to solder pins to the Pico to attach it. Having practiced on the Adafruit Feather, this was no problem. In addition if you want to do proper C debugging using gdb then you need to solder three wires to the debug ports on the end of the board. Note you need wires that can connect to the Raspberry Pi 4’s GPIO pins. All the soldering went fine and when I wired everything up I got serial communications going as well as played a bit with debugging using the SDK’s sample blink program. I was lucky that I could find all the wires, connectors and pins required from previous various Arduino and Raspberry starter kits.

Setting up the LEDs

If you want to use print statements while you are debugging, then you need to use the separate serial communications pins rather than a serial communications channel through the USB port. This is because when you stop the processor in the debugger, it stops it dead and disconnects any USB connections.

The picture below shows the setup. The LEDs are connected each to a GPIO port and then go through a resistor to ground. Remember LEDs have low resistance and you don’t want to short out your device. Also remember that LEDs are directional and you want to connect the plus side to the GPIO pin. The plus side is usually indicated by the longer lead wire.

In the picture above you can also see the 3 wires at the end going off to the Raspberry Pi as well as the three wires by the USB port that lead to serial communications GPIO pins on the Pi. The USB connects to the Pi to provide power, as well as is used to load any program into the flash memory.

A single picture can be hard to see, here is a video showing the LEDs flashing as well as panning around a bit to see all the connections from different angles: https://youtu.be/ogbcMp5Asu4

Code

The C/C++ SDK for the RP2040 provides a set of libraries to help with your device programming along with support for using the GCC toolchain. This means you can write code in C/C++ and Assembly Language and then debug using gdb. If you look at the C code, it is similar to the code we wrote for the regular Raspberry Pi here, though we had to write some of these library type routines against the Linux device driver ourselves.

Here is the C code:

#include <stdio.h>
#include “pico/stdlib.h”
#include “hardware/gpio.h”
#include “pico/binary_info.h”

const uint LED_PIN1 = 18;
const uint LED_PIN2 = 19;
const uint LED_PIN3 = 20;

int main()
{

    bi_decl(bi_program_description(“Copyright (c) 2021 Stephen Smith”));

    stdio_init_all();
    gpio_init(LED_PIN1);
    gpio_init(LED_PIN2);
    gpio_init(LED_PIN3);
    gpio_set_dir(LED_PIN1, GPIO_OUT);
    gpio_set_dir(LED_PIN2, GPIO_OUT);
    gpio_set_dir(LED_PIN3, GPIO_OUT);

    while (1)
    {
        puts(“Flash Loop\n”);
        gpio_put(LED_PIN1, 1);
        sleep_ms(200);
        gpio_put(LED_PIN1, 0);
        sleep_ms(200);
        gpio_put(LED_PIN2, 1);
        sleep_ms(200);
        gpio_put(LED_PIN2, 0);
        sleep_ms(200);
        gpio_put(LED_PIN3, 1);
        sleep_ms(200);
        gpio_put(LED_PIN3, 0);
        sleep_ms(200);
    }

The SDK defines projects using CMake. When you run CMake it will generate a makefile that you use to build your executable using make. Here is the CMake file:

cmake_minimum_required(VERSION 3.13)

include(pico_sdk_import.cmake)

project(test_project C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

pico_sdk_init()

add_executable(flashleds
  flashleds.c
)

pico_enable_stdio_uart(flashleds 1)
pico_add_extra_outputs(flashleds)
target_link_libraries(flashleds pico_stdlib)

Print Statements

In the C code there is a puts() function call to send “Flash Loop” to stdout. You can define what this is in the CMake file, in our case it’s set to the serial port (as opposed to the USB port). We can then read this data by monitoring the serial port on the Raspberry Pi 4 that we’ve connected up. One way to display the output is with the minicom program by calling:

minicom -b 115200 -o -D /dev/serial0

Which then displays:

Debugging

The Pico and the C/++ SDK have support for gdb. You run gdb on the Raspberry Pi 4 and then gdb connects remotely through minicom to the Raspberry Pi Pico. The C tutorial has full instructions on how to do this and when running it works quite well, though it takes several long command lines to get debugging going.

Visual Studio Code

The C/C++ SDK has full support for Visual Studio Code. This is the reason Raspberry added Microsoft repositories to the Raspberry Pi OS to seamlessly install and update this. After all the outcry from users, the calls to the Microsoft repositories have been removed and you need to install this by hand, as documented in the C getting started manual. I’m not really keen on Visual Studio Code, but if you like it and want to use it, go for it.

The best thing about Visual Studio Code is that it automates setting up remote debugging, so you can just say debug and Visual Studio Code does all the setup and connecting behind the scenes.

Summary

Both the Raspberry Pi Pico and Adafruit Feather RP2040 are powerful computers and great value as $5 microcontrollers. The programming environments are rich and powerful. You have lots of choices on how to program these with a lot of good supporting libraries and SDKs. The RP2040 exposes all sorts of GPIO ports and interfacing technology for you to make use of in your projects. It will be interesting to see all the RP2040 based DIY projects being showcased.

For people using this SDK, you can program in 32-bit ARM Assembly Language and might want to consider my book “Raspberry Pi Assembly Language Programming”.

Written by smist08

April 8, 2021 at 3:12 pm

Adafruit Feather RP2040 First Impressions

with 6 comments

Introduction

Last week we talked about the Raspberry Pi’s entry into the microcontroller market with their new RP2040 chip. I had ordered a couple of these, but had yet to receive one. Last Wednesday I received my Adafruit Feather RP2040, this is Adafruit’s entry into this market using Raspberry’s new ARM based RP2040 microcontroller. In this article, I’ll give my first impressions of this Adafruit board, with the caveat that I’ve only had it two days now.

The Adafruit board is similar to the Raspberry Pi Pico, but it is in the form factor for Adafruit’s feather line of peripherals. This means you can stack and intermix the peripherals just like with any other feather microcontrollers. This means the pins correspond to the feather specification rather than those on the Raspberry Pi Pico. This is good since it means you have a large selection of peripherals that are easy to use and pretty much snap together. Further Adafruit has done a good job of providing Python drivers for all their devices. Beyond this, the Adafruit board has a connector for a battery and will manage running off the battery, or charging the battery when connected via USB. The Adafruit board also has an RGB LED that programs can use and a reset button in addition to the boot selection button.

Painful Shipping

The best thing about the Adafruit board was that it was the first RP2040 board that I found that I could actually order, no waiting list, no back order. Kudos, it actually shipped later in the day I ordered it on. The bad news was that the only shipping option was DHL at the exorbitant price of $20USD. I think a lot of companies that sell inexpensive microcontroller parts, usually at around $5 USD each, make all their money by overcharging for shipping. DHL used to be good when they had a big contract to do deliveries for Amazon, then they delivered here reliably every day. Since they lost the Amazon contract, they don’t even deliver to our community and as a result it took an extra four days for the package to get to Gibsons from Vancouver, because they reshipped it with a different courier company and were slow about the process. If Adafruit had an option to use the regular post, it would have been cheaper and would have gotten here quicker. They could have still overcharged for it, but perhaps not as much.

Unboxing

I ordered a few extras along with the Adafruit board, namely a FeatherWing 128×32 OLED display and a lithium ion battery and connector cables. This all arrived in a cardboard box full of bubble wrap with the actual parts safely tucked inside.

I saw that a Raspberry Pi Pico connects to your computer via a micro-USB cable, so I had one of these ready. Picking up the Adafruit RP2040 revealed it uses a USB-C cable, so I had to run upstairs and dig one of these out. This highlights that if there are easy choices in the board design, Adafruit chooses the alternative to what Raspberry chose, I guess to differentiate themselves. Anyway, plugging in the Adafruit to my Raspberry Pi worked, namely it displayed in the Raspberry Pi as a removable disk drive, showing a view to the 8MB flash ROM where you copy your programs to run. Yay, it is working. Next I installed CircuitPython and ran a test program that blinks one of the LEDs on the board. I’ll talk more about CircuitPython after a bit of soldering.

Assembly Requires Soldering

The nice thing about the Arduino Uno and regular Raspberry Pi’s is that they are easy to attach to breadboards for prototyping and experimenting with electronics, everything just snaps together, no soldering required. That isn’t true for either the Adafruit Feather nor the Raspberry Pi Pico. If you want to use a breadboard, you need to solder some pins to them. Both Adafruit and Raspberry have excellent tutorials on how to do this. It was a bit intimidating, since I have a cheap $10 soldering iron from Canadian Tire and the board is very small. Another good thing about the Adafruit RP2040 (and the OLED display board) is that they come with the pins you need for this (you need to order these separately for the Raspberry Pi Pico).

Anyway I went ahead to do this and it turned out to be easier than expected. Heat the pin up with the soldering iron first and then the solder easily flows into position. A bit of a relief since Adafruit recommends buying a $200 soldering iron.

Perhaps down the road, either the various board makers or the companies that assemble kits from these will include an option where these are already soldered in place and perhaps integrated with a breadboard, like the official Arduino Uno starter kit. I see other Adafruit feather boards can be ordered with or without the pins installed, so perhaps they’ll have this available soon.

The reason these pins aren’t automatically built in place, is that this leaves makers a lot of options to package their final products into much smaller packages, since you can solder wires and devices directly to these points.

CircuitPython

The Raspberry Pi tutorial and documentation is all oriented around MicroPython and then the Adafruit documentation and tutorials are all oriented around CircuitPython. CircuitPython is based on Micropython, but taken by Adafruit and made a bit easier to use, especially if you use Adafruit devices. These RP2040 boards basically run one program, so to run Python you need to install the Python runtime to be that one program. To do this you download the CircuitPython UF2 file from Adafruit’s website and copy it to the board’s flash ROM. The board then reboots and the removable drive changes to be a folder inside the CircuitPython environment where you copy your Python program to run. Basically if you copy a file called code.py to this folder then it will be run. You can use the bootsel button to boot into the original view, if you want to later replace the Python runtime with something else.

To actually develop, you need a Python IDE running on a proper computer. Raspberry recommends using Thonny and Adafruit recommends using mu-editor. Thonny comes pre-installed on the Raspberry Pi OS, but adding mu-editor was easy, since it is written in Python, you install it with pip3:

pip3 install mu-editor==1.1.0b3

With this I could cut/paste the blink program from the Adafruit tutorial in and save it as code.py to the device and see the LED on the board flashing. The CircuitPython runtime provides a serial connection back to the IDE via the USB port, so if you click the “Serial” button in the IDE you can see the Python console. At this point you can control Python to some degree through the command line as well as see the output from print statements.

Next up, I connected up the OLED display and wanted to get this to try. Below is a picture of the two devices connected together on a breadboard.

I copy/pasted the Python code for this from another Adafruit tutorial, saved it to the board and nothing happened. Looking at the serial port showed I was getting missing library errors. Of course the next step in the tutorial is to download all  the device specific CircuitPython libraries. Adafruit provides a bundle of all the drivers for their devices, so I downloaded this.

First I tried just copying the libraries I thought I needed, but I didn’t have much luck and kept getting errors. Then I just copied all the libraries and then everything worked as shown in the above photo. The whole library takes 1.9Meg of the 8Meg Flash ROM, so I might try deleting some later.

To give a flavour of CircuitPython programming, below is the sample program to display “Hello World!” in a box on the display:

# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT
“””
This test will initialize the display using displayio and draw a solid white
background, a smaller black rectangle, and some white text.
“””

import board
import displayio
import terminalio
from adafruit_display_text import label
import adafruit_displayio_ssd1306

displayio.release_displays()
i2c = board.I2C()
display_bus = displayio.I2CDisplay(i2c, device_address=0x3C)
display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=128, height=32)

# Make the display context

splash = displayio.Group(max_size=10)
display.show(splash)
color_bitmap = displayio.Bitmap(128, 32, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0xFFFFFF  # White
bg_sprite = displayio.TileGrid(color_bitmap, pixel_shader=color_palette, x=0, y=0)
splash.append(bg_sprite)

# Draw a smaller inner rectangle

inner_bitmap = displayio.Bitmap(118, 24, 1)
inner_palette = displayio.Palette(1)
inner_palette[0] = 0x000000  # Black
inner_sprite = displayio.TileGrid(inner_bitmap, pixel_shader=inner_palette, x=5, y=4)
splash.append(inner_sprite)

# Draw a label

text = “Hello World!”
text_area = label.Label(terminalio.FONT, text=text, color=0xFFFF00, x=28, y=15)
splash.append(text_area)

while True:
    pass

Summary

The Adafruit Feather RP2040 is a good choice for a more powerful ARM based microcontroller. Its advantage over the Raspberry Pi Pico is the feather hardware ecosystem. Many people will also find the battery connector convenient, since many people will want their final build running off a rechargeable battery. Adafruit has done a good job getting all their CircuitPython support going and there is quite a bit of documentation and tutorials on their website.

I haven’t gotten the RP2040 C/C++ SDK going with the Adafruit board yet, but there is support for this board in the SDK, with a header file of all the pin definitions. For people using this SDK, you can program in 32-bit ARM Assembly Language and might want to consider my book “Raspberry Pi Assembly Language Programming”.

Written by smist08

April 2, 2021 at 11:16 am