First Impressions – Raspberry Pi PICO

The Raspberry Pi Pico is a very inexpensive microcontroller compared to other brands with ARM Cortex M0+ ( This one is a dual-core ), also has one special feature only found in more expensive microcontrollers like Cortex M4 it is the Raspberry PIO which is a way to create special hardware peripherals. In my Feather MK26F board, it is called FLEXBUS, and the equivalent can be found in other microcontrollers. So the remarkable feature for me of this microcontroller are:

  • XIP( Execute in place ) The ability to save and execute programs running on an external QUAD-SPI flash.
  • 246Kb of RAM, that is a lot of RAM, mostly seen on mid-range microcontrollers.
  • 133 Mhz Cortex M0+ dual-core
  • 8 PIO, Programmable IO State machines.
  • USB-Bootloader with SWD debug capabilities.
  • Low price ( only $4 USD for the entire board ).

The Raspberry Pi PICO also has some bad things to take into account, and this is the lack of the reset button and a good IDE for the developing process. These features could come in the future as they are minor changes. 

There are not many peripherals in this microcontroller but the PIO makes this even. it has 2 hardware SPI, 2 hardware UART, 2 hardware I2C, 3 12-bit ADC pins, 16 PWM channels, and Accelerated floating-point libraries on-chip ( We will see this in the example program below ).

RaspPico_pinout.png

I am not a fan of MicroPython or CircuitPython but it is nice to have in hand for a fast proof of concept. I like programming in C/C++ so I installed Visual Studio with the provided SDK.

PythonComp.png

The example that really grabbed my attention is the PIO ST7789, this example uses a custom peripheral with the PIO, making an impressive SPI  with a baud rate of 60 Mbps and the use of the hardware interpolation libraries.

/**
 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <stdio.h>
#include <math.h>

#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/gpio.h"
#include "hardware/interp.h"
#include "st7789_lcd.pio.h"
#include "ST7735.h"
#include "logoSavage.h"


#define SCREEN_WIDTH    480 
#define SCREEN_HEIGHT   160 
#define IMAGE_SIZE      256
#define LOG_IMAGE_SIZE  8

#define PIN_BL      21
#define PIN_RESET   20
#define PIN_DC      19
#define PIN_DIN     18
#define PIN_CLK     17
#define PIN_CS1     16
#define PIN_CS2     11
#define PIN_CS3     10
#define PIN_CS4     9
#define PIN_CS5     8
#define PIN_CS6     7

#define SERIAL_CLK_DIV 1.f

static inline void lcdSetDCCS(bool dc, bool cs1, bool cs2, bool cs3, bool cs4, bool cs5, bool cs6) {
    sleep_us(1);
    gpio_put_masked((1u << PIN_DC) | (1u << PIN_CS1) | (1u << PIN_CS2) | (1u << PIN_CS3) | (1u << PIN_CS4) | (1u << PIN_CS5) | (1u << PIN_CS6),
     !!dc << PIN_DC | !!cs1 << PIN_CS1 | !!cs2 << PIN_CS2 | !!cs3 << PIN_CS3 | !!cs4 << PIN_CS4 | !!cs5 << PIN_CS5 | !!cs6 << PIN_CS6);
    sleep_us(1);
}

static inline void lcdWriteCMD(PIO pio, uint sm, const uint8_t *cmd, size_t count) {
    st7789_lcd_wait_idle(pio, sm);
    lcdSetDCCS(0, 0,0,0,0,0,0);
    st7789_lcd_put(pio, sm, *cmd++);
    if (count >= 2) {
        st7789_lcd_wait_idle(pio, sm);
        lcdSetDCCS(1, 0,0,0,0,0,0);
        for (size_t i = 0; i < count - 1; ++i)
            st7789_lcd_put(pio, sm, *cmd++);
    }
    st7789_lcd_wait_idle(pio, sm);
    lcdSetDCCS(1, 1,1,1,1,1,1);
}

static inline void lcdInit(PIO pio, uint sm, const uint8_t *init_seq) {
    const uint8_t *cmd = init_seq;
    while (*cmd) {
        lcdWriteCMD(pio, sm, cmd + 2, *cmd);
        sleep_ms(*(cmd + 1) * 5);
        cmd += *cmd + 2;
    }
}

static inline void lcdStartPx(PIO pio, uint sm) {
    uint8_t cmd = ST7735_RAMWR;
    lcdWriteCMD(pio, sm, &cmd, 1);
    lcdSetDCCS(1, 0,0,0,0,0,0);
}


int main() {
    stdio_init_all();

    PIO pio = pio0;
    uint sm = 0;
    uint offset = pio_add_program(pio, &st7789_lcd_program);
    st7789_lcd_program_init(pio, sm, offset, PIN_DIN, PIN_CLK, SERIAL_CLK_DIV);

    gpio_init(PIN_CS1);
    gpio_init(PIN_CS2);
    gpio_init(PIN_CS3);
    gpio_init(PIN_CS4);
    gpio_init(PIN_CS5);
    gpio_init(PIN_CS6);

    gpio_init(PIN_DC);
    gpio_init(PIN_RESET);
    gpio_init(PIN_BL);
    
    gpio_set_dir(PIN_CS1, GPIO_OUT);
    gpio_set_dir(PIN_CS2, GPIO_OUT);
    gpio_set_dir(PIN_CS3, GPIO_OUT);
    gpio_set_dir(PIN_CS4, GPIO_OUT);
    gpio_set_dir(PIN_CS5, GPIO_OUT);
    gpio_set_dir(PIN_CS6, GPIO_OUT);

    gpio_set_dir(PIN_DC, GPIO_OUT);
    gpio_set_dir(PIN_RESET, GPIO_OUT);
    gpio_set_dir(PIN_BL, GPIO_OUT);

    gpio_put(PIN_CS1, 1);
    gpio_put(PIN_CS2, 1);
    gpio_put(PIN_CS3, 1);
    gpio_put(PIN_CS4, 1);
    gpio_put(PIN_CS5, 1);
    gpio_put(PIN_CS6, 1);
    gpio_put(PIN_RESET, 1);
    lcdInit(pio, sm, st7735_initSeq);
    gpio_put(PIN_BL, 1);

 
    // Other SDKs: static image on screen, lame, boring
    // Pico SDK: spinning image on screen, bold, exciting

    // Lane 0 will be u coords (bits 8:1 of addr offset), lane 1 will be v
    // coords (bits 16:9 of addr offset), and we'll represent coords with
    // 16.16 fixed point. ACCUM0,1 will contain current coord, BASE0/1 will
    // contain increment vector, and BASE2 will contain image base pointer
#define UNIT_LSB 16
    interp_config lane0_cfg = interp_default_config();
    interp_config_set_shift(&lane0_cfg, UNIT_LSB - 1); // -1 because 2 bytes per pixel
    interp_config_set_mask(&lane0_cfg, 1, 1 + (LOG_IMAGE_SIZE - 1));
    interp_config_set_add_raw(&lane0_cfg, true); // Add full accumulator to base with each POP
    interp_config lane1_cfg = interp_default_config();
    interp_config_set_shift(&lane1_cfg, UNIT_LSB - (1 + LOG_IMAGE_SIZE));
    interp_config_set_mask(&lane1_cfg, 1 + LOG_IMAGE_SIZE, 1 + (2 * LOG_IMAGE_SIZE - 1));
    interp_config_set_add_raw(&lane1_cfg, true);

    interp_set_config(interp0, 0, &lane0_cfg);
    interp_set_config(interp0, 1, &lane1_cfg);
    interp0->base[2] = (uint32_t) savageElectronics_256x256;

    float theta = 0.f;
    float theta_max = 2.f * (float) M_PI;
    
    while (1) {
        theta += 0.02f;
        
        if (theta > theta_max)
            theta -= theta_max;
        int32_t rotate[4] = {
                cosf(theta) * (1 << UNIT_LSB), -sinf(theta) * (1 << UNIT_LSB),
                sinf(theta) * (1 << UNIT_LSB), cosf(theta) * (1 << UNIT_LSB)
        };
        
        interp0->base[0] = rotate[0];
        interp0->base[1] = rotate[2];
        
        lcdStartPx(pio, sm);
        
        for (int y = 0; y < SCREEN_HEIGHT; ++y) {
            interp0->accum[0] = rotate[1] * y;
            interp0->accum[1] = rotate[3] * y;
            for (int x = 0; x < SCREEN_WIDTH; ++x) {
                uint16_t colour = *(uint16_t *) (interp0->pop[2]);
     
                 switch(x){
                    case 0:
                        lcdSetDCCS(1, 0,1,1,1,1,1);
                        break;
                    case 80:
                        lcdSetDCCS(1, 1,0,1,1,1,1);
                        break;
                    case 160:
                        lcdSetDCCS(1, 1,1,0,1,1,1);
                        break;
                    case 240:
                        lcdSetDCCS(1, 1,1,1,0,1,1);
                        break;
                    case 320:
                        lcdSetDCCS(1, 1,1,1,1,0,1);
                        break;
                    case 400:
                        lcdSetDCCS(1, 1,1,1,1,1,0);
                        break;
                }

                st7789_lcd_put(pio, sm, colour & 0xff);
                st7789_lcd_put(pio, sm, colour >> 8);     

            }
        }
    }
}

This code has been slightly modified in other to work with the display array and I will live the source code below with the UF2 file. 

Download