February 2021

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


Dynamixel, Smart motor configurator

There has been sometimes that it has been hard to communicate with this type of smart motors, I don’t always remember the set ID or even the last configured baud rate for serial communication, or even the simple task of changing a single parameter requires to write a custom program to change that value to that specific motor, for that reason I decided to start building a handheld device that will make this kind of tasks easier and faster.

A simple UI in a TFT LCD will let configure these motors in a few steps, the project is going to integrate electronics used in other of my projects to make this development easier and faster, hoping to have feedback from the robotics community who have work with this kind of motors by letting me know the best path to follow.

In this first step I will have a first approach for the graphical interface, here are some ideas that I have tried, and the most recent one.

I will be using an ST7789V controller 2.8 inch TFT LCD for this project as I think it is a good size for the data it will work with. I will also be using the MK26F microcontroller that I have in a Feather format and a Dynamixel interface board for communication with the motors. So it needs to be all wired, connecting the SPI, ADCs, and GPIOs from the feather board to the TFT LCD Breakout board.

I have added some interesting things to the graphics for a more fluid and friendly interface as movement on the main menu icons, as shown in the next picture.

I have noticed that there are a lot of Dynamixel motors, the classic AX-12A use the protocol V1.0 but the newer one uses the V2.0 and also they change some registers address making this whole idea a bit difficult, I am still thinking how many motor models this device will support, as a first step I will make the firmware ready for the AX-12A and the XL-320 as I think are most popular one and also the cheapest.

The first step is to port the Dynamixel library for Arduino that I wrote many years ago to my microcontroller and then also update the library to be compatible with the V2.0 Dynamixel protocol.

So there are some slight differences between registers address and all of this have to take in count when showing the info the configurator, so I have started to register this tables in a model file and maybe would be updated in a more compressible file in the future.

Motor scanning and selection menu

In order to test or modify the motor register, we need to scan and select the connected motors, for this task I have created a short function that Ping the bus and waits for each motor ID, and adds it to the motor list at a given baud rate and protocol.

for(motorID = 0; motorID < 254; motorID++){

             if( !(Dynamixelping(motorID,protocol) == -128) ){

                    motorList[motorIndex++] = motorID;

             }

             displaydrawRectangle(64,0,((motorID*240)/253),4,GREEN);

}

After the motor scanning, it’s useful to know certain values of the motor so I decided to read these and display them in the same window with the capability to scroll the motor list and at the same time select the motor of interest.

Configure Registers Window

In this window will be able to read and write directly to register of the selected motor in the previous windows, it will be accessible to both the EEPROM and RAM registers in order to properly test the written values, limits, or alarms.The new values will be written on a numerical touchscreen keyboard.

This window also shows if the EEPROM data is write-protected, in order to write the torque of the motor should be disabled, this can be done by writing a 0 to the Torque Enable register in address 24.

Factory Reset Window

The factory reset window is a simple but very efficient way to restore motor parameters to factory defaults with a press of a button.

This option will perform a reset function on the Dynamixel instructions set, so all it been handled by the motor itself, the motor list is also accessible from this window but it has to be in consideration that motors should not have the same ID as it can cause errors in the communication.

When a reset has finished successfully this window will also inform and will let you continue with the reset operations that you may need for other motors.

Configuration Window

The configuration windows allow to change the Baud rate speed, the Dynamixel protocol and perform a touchscreen calibration if needed.

As you have already noticed the touchscreen calibration options are accessible with the tact switches in case that the touchscreen values have changed over time, the only action that needs the touchscreen is the keyboard on the register configuration window.


Download

nRF52832 PRO Panel

This is a new project using the nRF52832 BLE MCU as a very versatile development platform that will allow me to do many projects that requires a connection over Bluetooth and also can perform actions to control basic systems, as data acquisition, data display, motor control, graphical interfaces and much more.

This board dimension is 48x18mm with 30 castellated holes on the side, a micro USB connector for power, and programming through a USB to serial converter CP2104, two tactile switches for user interface and firmware programming, 1 user LED.

I have made a small panel for easy fabrication, This panel dimensions are 92x66mm including the tooling rails at the sides. In this panel I want to test several things that a usually do not order in a regular PCB, such as tooling rails with tooling holes, external fiducial, Logo with EING finish at the top tooling rail, solder mask and cooper restrictions for text and logos, and castellated holes in a routing job, v-score at the edges of the board as well on the tooling rails.

I quoted this panel in many PCB Manufacturing houses and order some boards for the best price in the market that includes all the characteristics described above with PCBWay. (Even cheaper than the well-know 2 USD – 5PCS PCB Manufacturing House).

These board are now getting manufactured, the customer service has been awesome so far, as I ask to place the UL Marking on the Top Silkscreen layer in the bottom layer of this panel and received a preview of confirmation of this requirement, and received awesome feedback regarding the Vscore and Solder mask capabilities.

Making this board possible required some knowledge in PCB Manufacturing that I will below describe the research and application.

Castellated Holes: Also know as castellations are plated through-holes or vias at the edge of a board cut in the middle by a router making them pads that can be soldered to another PCB and make subassemblies.

The PCBs have finally arrived, here are some photos.

If you are thinking about ordering some PCBs and start your own prototypes I recommend you to try PCBWay.

https://www.pcbway.com/

ISSI IS31FL3731 Crossplexing LED driver

The IS31FL3731 from ISSI is an LED driver that uses cross-plexing in order to control up to 144 LED with a PWM of 8 bits, it also has on-chip memory to store 8 complete frames for animations and an input pin for audio interaction.

I design this board to test this chip and also added an MMA5284 Accelerometer from NXP, this board has components on both sides so I decided to solder the components with solder paste a the T962 reflow oven.

A very important step when you solder with a reflow oven it is to correctly apply the solder paste to the PCB, for better results it highly recommended to use a steel stencil and apply the solder paste in a single movement for proper solder quantity to the board.

After applying the solder paste just pick and place the components and reflow them in the oven and proceed with the other side of PCB the same way as previously.

MKL26 and MK26F Feather prototyping boards

Most of the Adafruit Feather use microcontrollers from Atmel, Esspresif and Nordic and it’s hard to find them with other brand microcontrollers, There are some feathers with the newest NXP Crossover MCUs, and that is why I have decided to add some MID-Range and Low power consumption ones.

The MKL26 Feather is a board that packs the NXP/Freescale MKL26 which has an ARM Cortex M0+ core at 48Mhz with 256kb of Flash and 32Kb of RAM. The board includes a microSD socket and RGB LED.

The MK26F Feather is a board that packs the NXP/Freescale MK26F which has an ARM Cortex M4F core at 180Mhz with 2Mb of Flash and 256Kb of RAM, it also has an RGB LED and socket for microSD but with an SDHC interface instead of SPI.

Having the Adafruit Feather format it’s very useful in case that you are working with other microcontrollers with different characteristics and this way it is easy to switch for project capabilities expansion.

I will use these boards with other projects to show you what kind of applications we can do with them. If you are interested you can purchase them at my Tindie Store.

Raspberry Pi Zero Dynamixel Hat

The Dynamixel Hat is a board capable of communicating the raspberry Pi ( Serial ) with the Dynamixel servos by using the 74LS241 Tri-state Buffer connected to 3-pin Molex connectors.

The board contains a TI TPS62143 regulator that steps down the voltage to power up the Raspberry Pi Zero that can be power on or off with the onboard slide switch. The TPS62143 is a synchronous step-down DC-DC converter optimized for applications with high power density.

This board accepts 2S and 3S Lipo batteries, the DC-DC converter accepts up to 17 Volts but the motors recommended voltage is 11.1V ( 3S Lipo ) as the MAX Input voltage is around 16 Volts.

The boards incorporate two I2C ports for sensors for your robotic project. These are JST 1 mm pitch and 4-pin connectors compatible with many boards on the QWIIC family.

There are also 3 LEDs and 3 Buttons for notification and interaction with the electronics, the buttons are programable through the Raspberry GPIOs and 1 LED also GPIO Configurable, the other 2 LEDs are the Activity and PowerOn notification.

Pinout

Test code:

import RPi.GPIO as GPIO
import serial
import time

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(18,GPIO.OUT)     # Control Data Direction Pin
GPIO.setup(6,GPIO.OUT)      # Blue LED Pin

GPIO.setup(26,GPIO.IN)      # S2 Push Button Pin
GPIO.setup(19,GPIO.IN)      # S3 Push Button Pin
GPIO.setup(13,GPIO.IN)      # S4 Push Button Pin


Dynamixel=serial.Serial("/dev/ttyS0",baudrate=1000000,timeout=0.1, bytesize=8)   # UART in ttyS0 @ 1Mbps


while True:

    if GPIO.input(26):
        GPIO.output(6,GPIO.LOW)
    else:
        GPIO.output(6,GPIO.HIGH)
        GPIO.output(18,GPIO.HIGH)
        Dynamixel.write(bytearray.fromhex("FF FF 01 05 03 1E CD 00 0B"))  # Move Servo with ID = 1 to position 205
        GPIO.output(18,GPIO.LOW)
        startDynamixel = Dynamixel.read()
        startDynamixel = Dynamixel.read()
        idDynamixel = Dynamixel.read()
        lenghtDynamixel = Dynamixel.read()
        errorDynamixel = Dynamixel.read()
        chkDynamixel = Dynamixel.read()
        print("Servo ID = " , int.from_bytes(idDynamixel,byteorder='big') , " Errors = ", int.from_bytes(errorDynamixel,byteorder='big'))
        time.sleep(1)

    if GPIO.input(19):
        GPIO.output(6,GPIO.LOW)
    else:
        GPIO.output(6,GPIO.HIGH)
        GPIO.output(18,GPIO.HIGH)
        Dynamixel.write(bytearray.fromhex("FF FF 01 04 02 2A 01 CD"))   # Read Voltage of Servo with ID = 1 
        GPIO.output(18,GPIO.LOW)
        startDynamixel = Dynamixel.read()
        startDynamixel = Dynamixel.read()
        idDynamixel = Dynamixel.read()
        lenghtDynamixel = Dynamixel.read()
        errorDynamixel = Dynamixel.read()
        voltDynamixel = Dynamixel.read()
        chkDynamixel = Dynamixel.read()
        print("Servo Voltage = " , int.from_bytes(voltDynamixel,byteorder='big'))
        GPIO.output(18,GPIO.HIGH)
        Dynamixel.write(bytearray.fromhex("FF FF 01 04 02 2B 01 CC"))   # Read Temperature of Servo with ID = 1
        GPIO.output(18,GPIO.LOW)
        startDynamixel = Dynamixel.read()
        startDynamixel = Dynamixel.read()
        idDynamixel = Dynamixel.read()
        lenghtDynamixel = Dynamixel.read()
        errorDynamixel = Dynamixel.read()
        tempDynamixel = Dynamixel.read()
        chkDynamixel = Dynamixel.read()
        print("Servo Temperature = " , int.from_bytes(tempDynamixel,byteorder='big'))
        GPIO.output(18,GPIO.HIGH)
        Dynamixel.write(bytearray.fromhex("FF FF 01 04 02 24 02 D2"))   # Read Position of Servo with ID = 1
        GPIO.output(18,GPIO.LOW)
        startDynamixel = Dynamixel.read()
        startDynamixel = Dynamixel.read()
        idDynamixel = Dynamixel.read()
        lenghtDynamixel = Dynamixel.read()
        errorDynamixel = Dynamixel.read()
        posDynamixel = Dynamixel.read(2)
        chkDynamixel = Dynamixel.read()
        print("Servo Position = " , int.from_bytes(posDynamixel,byteorder='little'))
        time.sleep(1)

    if GPIO.input(13):
        GPIO.output(6,GPIO.LOW)
    else:
        GPIO.output(6,GPIO.HIGH)
        GPIO.output(18,GPIO.HIGH)
        Dynamixel.write(bytearray.fromhex("FF FF 01 05 03 1E 32 03 A3"))    # Move Servo with ID = 1 to position 816
        GPIO.output(18,GPIO.LOW)
        startDynamixel = Dynamixel.read()
        startDynamixel = Dynamixel.read()
        idDynamixel = Dynamixel.read()
        lenghtDynamixel = Dynamixel.read()
        errorDynamixel = Dynamixel.read()
        chkDynamixel = Dynamixel.read()
        print("Servo ID = " , int.from_bytes(idDynamixel,byteorder='big') , " Errors = ", int.from_bytes(errorDynamixel,byteorder='big'))
        time.sleep(1)

Enable UART at 1Mbps in your Raspberry Pi Zero

Make sure to add the following to your Raspberry config file at /boot/config.txt:

enable_uart=1
init_uart_clock=16000000

Assembly Video

This board will be available in my Tindie store very soon: Savage Electronics Tindie

Download



MK26F Development Board

I personally like to work with NXP/Freescale microcontrollers, and that is why I have designed a development board with the intention to also expand to other brands of microcontrollers.

I have called this board a Pyramid, stating with the MK26F2M0VMD18 which come in a BGA144 footprint with a pitch between balls of 1mm, This MCU is a very nice microcontroller as its an ARM Cortex M4F with a CPU clock of 180Mhz in the High-Speed mode configuration with 2MB of Flash memory and 256Kb of SRAM. The board that I have designed includes an SDRAM of 16MB and an SDHC socket for SD Cards.

This board also includes a USB2.0 Host connector a couple of notification LEDs and an RGB LED. The board can be programmed through the JTAG/SWD port or by installing a USB Bootloader.

To this development board, I have also designed some boards called Blocks that will be swappable between the other brand MCU Pyramids.

If you are interested in this project please let me know in the comments section below.

SMD Components dispenser

There are some times when I am in need of a resistor, or a single LED or capacitor and the cost of 500 or 1000 of them are incredibly cheap, under a dollar, so when I caught that kind of offer I do not think it twice, these components usually are shipped in reels inside a bag and they are very inefficient space-wise so looking into the internet I have found a system that allows easy handling.

SMD components reels

I have re-spinned the original design that does not need any kind of metal spring, bearings, and screws. It’s easy to replace those components with the properties of plastic when you have access to a 3D printer.

SMD reel to 3D printed cassetes

The cassettes are very easy to print and are very useful when you are starting to make your own collection of SMD parts.

Download


JTAG Adapter board to SWD

If you ever wanted to reduce the footprint of your programming interface a good option is to use the SWD port to program your microcontroller instead of the 20 pin JTAG connector, most development boards use the 10 Pin 1.27 mm pitch SWD connector so I design a J-link Segger adaptor board to SWD.

JTAG Adapter board

The schematic is very simple as it just re-routes the SWD signals of the SWD, so I have also connected the other JTAG signal to make a reduced footprint JTAG.

Download