Skip to content

Embedded programming

Summary

Part of my job is to teach embedded programming to bachelor students.
In my faculty, we mainly use PIC and PSoC microcontrollers (and some ESP32).
Hence, I decided to use this week assignments as an opportunity to discover the RP2040.
I wanted to try programming its PIO.

I decided to control a DC motor (with the RP2040 PWM peripheral) and measure its speed and position using a PIO.

Assignments

Group Assignment

  • Demonstrate and compare the toolchains and development workflows for available embedded architectures

individual assignment

  • Browse through the data sheet for a microcontroller
  • Write and test a program for an embedded system using a microcontroller to interact (with input &/or output devices) and communicate (with wired or wireless connections).

We often use mobile robots as support for embedded programming student projects.
These robots are driven by 2 DC motors, equipped with incremental encoders.
An incremental encoder is a position sensor, placed on the motor axis.
Some microcontrollers has a dedicated peripheral to interface with it. However, very few of them has two of these peripherals.
That means you have to handle the encoders (at least the second one) in software. That can use a lot of computation time, limiting the performances.

Hence, I decided to program one RP2040's PIO as an encoder interface.

MicroPython for RP2040

I never used MicroPython. Hence I start experimenting it on RP2040.

I followed Nicolas De Coster's documentation on how to start with MicroPhyton.

Simulations

I first simulate some small codes on Wokwi.

Of course, I started by blinking a LED, with Nicolas example.

PWM generation

Next, I read the MicroPython documentation.

The machine library contains functions to access and control the microcontroller hardware.

I look for the PWM functions. PWM stands for Pulse Width Modulation. It is used to control the speed of a DC motor.

I wrote a code to use it and tested it in simulation, based on the MicroPython wiki example:

Wokwi PWM example 1

Code explanation:

  • line1 imports the PWM class from the machine library
  • line 4 creates an instance (pwm) of PWM.
    The constructor has 3 parameters: the first is the id of the pin to use as PWM output (RP0), the second is the PWM frequency and the last is its initial duty cycle.
  • line 6 changes the duty cycle to 50% (max duty cycle is 65535)
  • lines 8 and 9 are an empty infinite loop.

The RP0 pin is connected to a logic analyzer.
When the simulation is run, the logic analyzer stores all changes on its inputs. When the simulation is stopped, a .vcd file is downloaded by the web browser.
This file contains the logic analyzer data. It can be visualized with pulseView, an open-source GUI for logic analyzers.

pulseview GUI

In the Open menu of PulseView, choose Import Value Change Dump data... to visualize the downloaded file.

I modified my code to change the duty cycle over time:

from machine import PWM, Pin
import time

# Creates pwm and dir signals to drive a DRI0044 motor driver
# Starting from 0, the motor speed is incremented to its max value, turning clockwise
# Then it decelerates to 0.
# It does the same counter-clockwise, then starts all over again

# create a PWM on GP0 with freq = 50 kHz and duty cycle = 0%
pwm = PWM(0, freq=50000, duty_u16=0)
# set GP1 as an general purpose output
dir = Pin(1, Pin.OUT)

# pwm duty cycle sets the motor speed, dir state sets the motor direction (0=CW or 1=CCW)
# This code is to use with a DRI0044 motor driver

def motor_set_speed(dc):
    """ set the motor duty cycle.
        parameter: dc is a float in [-1, 1] range.
        the motor turns clockwise for positive dc values
        and counter-clockwise for negative dc values """
    if dc < 0:
        dir.on();
        dc = -dc
    else:
        dir.off()
    if dc > 1:
        dc = 1
    pwm.duty_u16(int(dc*65535)) # duty_16 needs and unsigned 16-bit integer

dc = 0      # initial duty cycle, in per unit
incr = 0.1  # duty cycle increment

while True:
    dc = dc + incr
    if (dc >= 1) or (dc <=-1):
        incr = -incr
    motor_set_speed(dc)
    time.sleep(0.01)

Wokwi simulation: https://wokwi.com/projects/455756396006852609

Logic analyzer data: RP2040-PWM-2.vcd Using PulseView, we obtain:

simulated data

test on a Raspberry Pi Pico 1 board

Next, I wanted to test my last code on a RP2040 board.

First step is to install Thonny, a Python IDE.
I followed the instructions on Nicolas'page

I ran his "Hello World" without any problem

Hence, I copy my PWM code in Thonny and run it.
Again, no problem. I visualize both signals with a logic analyzer:

logic analyzer data

C/C++ SDK for RP2040

I also wanted to try the C/C++ SDK for the RP2040.

installation

I followed the installation procedure described in Getting started with Raspberry Pi Pico
Visual studio Code was already installed on my computer. Hence, I just needed to install the Raspberry Pi Pico code extension

To test the toolchain, I created a new project from the blink example:

blink creation

As it was the first project created for this code extension, it automatically installed and configure the needed tools.

Hello World

Next, I tried to run the project.
It failed with this error message: "No accessible RP-series in BOOTSEL mode were found".

After a quick search, I found the solution: the RP2040 needs to be booted with the BOOTSEL button pressed.
That means that, each time you want to program it, you need to unplug its USB cable, then re-plug it with the BOOTSEL button pressed.
Using this method, I was able to run the "Hello World".

PIO test

I wanted to test the PIO programming. Hence, I created a new project:

hello_pio creation

In the creation options:

  • I selected the "Board type" as Pico (I have a PICO 1 board)
  • I checked the PIO interface feature
  • I also checked Console over USB in Stdio support. This last feature tells the RP2040 to send the printf messages over the USB connexion. These messages can be seen directly in VSCode (in the serial monitor).

The SDK created me a complete project, with a nice example code, using a PIO state machine to blink the on-board LED. It also send "Hello World!" to the serial monitor.

PIO study

I started to read the PIO chapter of the RP2040 datasheet.

PIO overview

TODO: fill this section

Quadrature encoder PIO interface

Now that I have an idea of the PIO architecture and its instruction set, I have to think about how to use it to read an encoder's signals.

Obviously, I first search for an existing implementation. I found one in the pico-examples Github repository.

However, the easiest way to get it is to use the create a project from an example feature of the SDK:

I first tried the pio_quadrature_encoder example.

I used a simple circuit:

circuit

It works as intended:

pio_quadrature_encoder serial ouput

Adding the PWM

I added the PWM control for my motor:

void motor_set_speed(float dc) {
    if (dc < 0) {
        gpio_put(DIR_PIN, 1);
        dc = -dc;
    } else {
        gpio_put(DIR_PIN, 0);
    }
    if (dc > 1) {
        dc = 1;
    }
    pwm_set_chan_level(slice_num, PWM_CHAN_A, (uint16_t)(2500*dc));
}

I had an error when I tried to compile my code: the compiler couldn't find the pwm.h file.
I had to manually add it in the CMakeLists.txt file:

target_link_libraries(pio_quadrature_encoder PRIVATE
        pico_stdlib
        pico_multicore
        hardware_pio
        hardware_gpio
        hardware_pwm
        )

I was then able to run the code.
As it sends the encoder's position and speed on the serial monitor, I used Excel to plot boh positon and speed:

pos speed

What I learned this week

  • MicroPython programming (for RP2040)
  • RP2040 global architecture and its PIO programming
  • C/C++ SDK for raspberry pico 1