So you have a new board, that you want to get working with Blinka, our CircuitPython library compatibility layer for Single Board Computers, or SBCs. Adding a board to Blinka is a 2-part process with the first step being to add it to PlatformDetect so that Blinka knows what board it is working with.

In our PlatformDetect guide, we went over the steps needed to get your chip and board correctly detected so that Blinka knew what it was working with. In this guide, we will go over the second part to get your board functioning on Blinka.

This guide goes over everything that's left to get your board working with Blinka including:

  • Adding the Chip and Board files
  • Getting GPIO working
  • Getting I2C, SPI, and UART working

Along with some additional resources.

The way that Blinka works is it combines multiple techniques to access the parts of the board that it needs. For GPIO, libraries such as RPi.GPIO or libgpiod are used. For I2C, the PureIO Python library is used, and for SPI, we are using the spidev Python library.

When it comes to Blinka itself, there are two main components that need to be defined.

  • First there is the chip, the microprocessor definition.
  • Second, there is the board definition.

The chip defines which pins of the microprocessor are used by the GPIO library and the board defines which physical pin on the board maps to the pins defined by the chip file. There can be multiple boards that all use the same chip, but the specific implementation may be slightly different.

The overall layout of the way these files are accessed looks like this:

In this guide, we will be picking up where we left off in the PlatformDetect guide and going over what steps are needed to add a board such as the Pine A64 to Blinka.

Parts

The cable is easiest way ever to connect to your microcontroller/Raspberry Pi/WiFi router serial console port. Inside the big USB plug is a USB<->Serial conversion chip and at...
$9.95
In Stock
Just about all electronics use a UART serial port with RX and TX pins for debugging, bootloading, programming, serial output, etc. But it's rare for a computer to have a serial...
$19.95
In Stock
Need some big indicators? We are big fans of these huge diffused blue LEDs. They are really bright so they can be seen in daytime, and from any angle. They go easily into a breadboard...
$9.95
In Stock
Medium-sized clicky momentary switches are standard input "buttons" on electronic projects. These work best in a PCB but
$2.50
In Stock
Bosch has stepped up their game with their new BME280 sensor, an environmental sensor with temperature, barometric pressure and humidity! This sensor is great for all sorts...
$14.95
In Stock
Thermocouples are very sensitive, requiring a good amplifier with a cold-compensation reference. The MAX31855K does everything for you, and can be easily interfaced with any...
$14.95
In Stock
Thermocouples are best used for measuring temperatures that can go above 100 °C. This is a bare wires bead-probe which can measure air or surface temperatures. Most inexpensive...
Out of Stock
We carry a few different GPS modules here in the Adafruit shop, but none that satisfied our every desire - that's why we designed this little GPS breakout board. We believe this is...
$29.95
In Stock
This guide picks up where the PlatformDetect guide left off, so make sure to follow that first if you haven't.

The first thing we will need to do is decide on the GPIO library we will be using. If there is a manufacturer specific library available such as RPi.GPIO or Jetson.GPIO, then that is preferred, since the access times are much better. However, if that option isn't available, then we will turn to libgpiod which is available for a huge number of boards as it is built into the Linux Kernel.

For the Pine64, although RPi.GPIO was ported over, I ran into an unresolved issue, so we decided to go with libgpiod. Generally libgpiod is available as an installable package, but it's possible that you may need to compile it from source. For the Pine64, it was available as a package.

Installing the libgpiod as a package

First we want to check if it's available. To do this, we run the following command:

  • sudo apt-cache search gpiod

It should output something similar to the following:

The items of interest in this list are:

  • libgpiod2 which is the actual libgpiod
  • python3-libgpiod which is the Python binding that allows us to make use of the library through Python
  • gpiod which contains the tools that we'll need to figure out which microprocessor pins are the correct ones.

Let's go ahead and install all three with the following command:

  • sudo apt-get install libgpiod2 python3-libgpiod gpiod

Installing libgpiod from source

Normally compiling libgpiod from source can be complicated. We have a script available that will do just about everything for you. To run it, you need to start by becoming root, so type sudo bash. Then run the following commands:

  • cd ~
  • wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/libgpiod.sh
  • chmod +x libgpiod.sh
  • ./libgpiod.sh

This will take a few minutes to run. If you get an error, try adding the -l or --legacy flag to the last command.

Testing the libgpiod installation

After installation you should be able to import gpiod from within Python3

Install I2C Tools

To test I2C, you will want to install the I2C Tools with the following commands. If your username is different than pi, change it as appropriate:

  • sudo apt-get install -y python3-smbus python3-dev i2c-tools
  • sudo adduser pi i2c

Getting a copy of the code

Step 1 - Fork Blinka to your GitHub account

If you don't already have the code from Adafruit_Blinka forked to your local GitHub repository, you'll want to start off by doing that. If you are not sure how to use GitHub, we have an excellent guide available on using Git and GitHub.

Step 2 - git clone the Blinka fork to your single board computer

Once you have forked it, you can clone the repository both onto your local computer and onto your SBC. By using your local repository as a copy point, this makes things really easy. If you'd prefer to just edit right on your SBC and commit from there, you can do that too. In this guide, we'll assume you cloned it to your home directory on your SBC.

Some other options for copying over the files include using SFTP and an FTP client such as FileZilla or SCP. However, you decide to do it, the guide will assume that you have a copy of the repository in your home directory and that it is located inside the folder ~/Adafruit_Blinka.

This next step is probably the most difficult part of the entire process because there is no one-size-fits-all method for getting the information and every board is different. So rather than concentrating on a specific set of steps, we will be discussing various strategies for acquiring the information. It also depends on which GPIO library you went with.

Strategies can range from looking at the microprocessor data sheet to finding similar code that already has the pins figured out, to having the GPIO library just tell you. Another strategy is simple trial and error by just setting a line and seeing if it changes the value. Often times though, it is a combination of different strategies combined together.

It's entirely possible that you may not need to create a chip file. If a chip file for your board already exists or one that is close enough exists, you should definitely use that one. If you do find that you need to create one, it's probably best to see what already exists and make a copy of that file to use as a starting point. When you make a copy, you will want to copy the entire folder.

If you followed the instruction in the software setup, you should have the Adafruit_Blinka folder in your home directory. Inside of there you'll want to navigate to src/adafruit_blinka/microcontroller which is where all of the chip files are located, sorted by manufacturer.

In the case of the Pine64, the AllWinner H3 chip is already set up, so we will make a copy of the h3 folder and leave it under the allwinner folder. The new folder is called a64.

If you create a new folder from scratch, be sure that __init__.py exists inside the folder or PyPI won't install it along with Blinka.

At a minimum, the folder should contain an __init__.py and pin.py file. The init file will most likely be empty or only contain a comment. The pin.py file will contain all of the Chip pin information. Here's what the h3 pin.py file looks like:

"""Allwinner H3 pin names"""
from adafruit_blinka.microcontroller.generic_linux.libgpiod_pin import Pin

PA0 = Pin(0)
UART2_TX = PA0
PA1 = Pin(1)
UART2_RX = PA1
PA2 = Pin(2)
PA3 = Pin(3)
PA6 = Pin(6)
PA7 = Pin(7)
PA8 = Pin(8)
PA9 = Pin(9)
PA10 = Pin(10)
PA11 = Pin(11)
TWI0_SCL = PA11
PA12 = Pin(12)
TWI0_SDA = PA12
PA13 = Pin(13)
UART3_TX = PA13
PA14 = Pin(14)
UART3_RX = PA14
SPI1_SCLK = PA14
PA15 = Pin(15)
SPI1_MOSI = PA15
PA16 = Pin(16)
SPI1_MISO = PA16
PA17 = Pin(17)
PA18 = Pin(18)
PA19 = Pin(19)
PA20 = Pin(20)
PA21 = Pin(21)

PC0 = Pin(64)
SPI0_MOSI = PC0
PC1 = Pin(65)
SPI0_MISO = PC1
PC2 = Pin(66)
SPI0_SCLK = PC2
PC3 = Pin(67)
SPI0_CS = PC3
PC4 = Pin(68)
PC7 = Pin(71)

PD14 = Pin(110)

PG6 = Pin(198)
UART1_TX = PG6
PG7 = Pin(199)
UART1_RX = PG7
PG8 = Pin(200)
PG9 = Pin(201)
PG10 = Pin(202)
PG11 = Pin(203)
PG12 = Pin(204)
PG13 = Pin(205)

PL2 = Pin((1, 2))
PL4 = Pin((1, 4))

i2cPorts = ((0, TWI0_SCL, TWI0_SDA),)
# ordered as spiId, sckId, mosiId, misoId
spiPorts = (
    (0, SPI0_SCLK, SPI0_MOSI, SPI0_MISO),
    (1, SPI1_SCLK, SPI1_MOSI, SPI1_MISO),
)
# ordered as uartId, txId, rxId
uartPorts = ((3, UART3_TX, UART3_RX),)

Checking with the GPIO library

We'll start with the easiest strategy, which is to check with the GPIO library. The first thing we want to do is map out the chip. We will start off by seeing how many GPIO chips are detected. There should be at least one, but there may be a few. To do this we type:

  • sudo gpiodetect

This tells us that there are 3 GPIO chips on the Pine64 and the number of lines that each controls. It's likely the bulk of them are on gpiochip1.

Next we look for some more detailed information by typing:

  • sudo gpioinfo

In this case, most of the lines are unnamed. In some chips, they may actually have the pin numbers which makes things much easier.

For the Pine64, since there are multiple GPIO chips and there are only 32 lines associated with the first chip. This means the bulk of the lines will most likely be associated with the GPIO chip 1.

In the H3 chip, we are only passing an integer, which means that it associates all the pin numbers with GPIO chip 0. However, for the A64 chip, we will likely need to specify which chip each of the lines is on, so instead os specifying something like:

PC0 = Pin(64)

If line 64 was based off gpiochip1, we would need to use a tuple instead of an integer and specify it more like:

PC0 = Pin((1, 64))

How do we know which line is associated with which GPIO chip? That involves a little bit of guess work. We know there are 32 lines in the First GPIO chip and 256 lines in the second. The third has only 2 lines, so it most likely will not be used.

Looking Up a Pinout Diagram

Sometimes looking up a Pinout diagram can help yield clues. A web search for Pine64 Pinout, yielded this pinout diagram available at http://joey.hazlett.us/pine64/pine64_pins.html, which is very helpful:

According to the diagram, it appears PC0 is using GPIO 64, which matches what we see in the h3 pin mappings.

Checking Data Sheets

The next thing we will do is try and find a data sheet with a web search. The Pine A64 uses the AllWinner A64 chip, which we can figure by doing some web searches and doing some reading. For this chip, there is a data sheet available here.

The important part of this data sheet is section 4 titled Pin Characteristics. It shows us the Pin Names grouped by GPIO blocks starting with GPIO B. You may notice that these Pin names match those in the Pinout Diagram.

Testing a physical GPIO Pin

Just to help out with figuring out the layout, we can test to see if setting a libgpiod line on a chip corresponds to the pin we think it should correspond to. Let's take pin PC0, which appears to be physically connected to Pin 19 of the 40-pin Pi header accord to the Pinout diagram. Let's connect an LED up to it so we can test turning it off and on:

We know it's on gpiochip1 and it uses line 64 and that's the only GPIO chip with enough lines. So we will type in the gpioset command like:

  • sudo gpioset gpiochip1 64=1

If everything is correct, the LED should turn on, which confirms our hypothesis. If we were to try out the same thing with pins such as PB1, which says GPIO 33, it would also likely be on gpiochip1, and after testing, this is in fact the case.

Interpolating the Numbering

Now you may notice that according to the Pinout Diagram, it says pins such as PL10 are using GPIO 362 and we know that none of the GPIO chips have that many lines, so we will take a different approach. There was no GPIO block A, so what if gpiochip0 controls GPIO block L onwards? Let's try connecting PL10, which appears to be connected to Physical Pin 7 up to the LED.

So we will hypothesize that for PL10, it is using gpiochip0 and line 10, so we will type the command:

  • sudo gpioset gpiochip0 10=1

Unfortunately, it didn't turn on the LED. However, after digging through some documentation a bit further, I found that PL10 does in fact correspond to GPIO 10 on gpiochip1. According to the Pinout, 362 refers to the GPIO pin number when accessing the pin via SysFs, which is an old way of accessing the pins and is not terribly efficient. When I set the pin via SysFs, it also did not light up the LED, but when running the gpioset command again, this time I got the error message:

gpioset: error setting the GPIO line values: Device or resource busy

The LED isn't turning on either way, which means we may not be able to run 3 of the pins on the Pi 2 header, but this is acceptable and perhaps we can revisit this in the future. The important thing is we have verified the pin mapping.

For the rest of the GPIO pins, it appears that all of the pins from GPIO B through GPIO H are fine and definitely running off gpiochip1. Let's take a look at a few known values and we will see if a pattern emerges.

According to the pinout diagram, we can see that PC0, PC1, and PC2 correspond to 64, 65, and 66 respectively, so it appears they are in order. PC5 is 69 and PC9 is 73. We can thus infer PC6 through PC8 are likely 70 through 72.

Ok, let's look that the pins starting with PB0, which appears to correspond to 32. According to the data sheet, there are only 10 pins in GPIO block B, so we will likely ignore 42 through 63. In fact, based on the note at the bottom of the Pinout, we can see that every GPIO block has 32 numbers allocated to it which makes figuring out the exact numbers much easier.

Finishing up the GPIO Pins

If there are any GPIO pins that don't belong, go ahead and remove those from the chip.py file. Add only the pins that are going to be used. If the chip.py file already existed from another board, add any new pins that your board uses that the other board may not have used.

Pin Aliases

If you need any aliases, after defining a pin, just refer to the already defined pin rather than making a new pin. For instance, if we wanted to create Pin PC0 and also have the name SPI0_MOSI, we would do something like the following:

PC0 = Pin((1, 64))
SPI0_MOSI = PC0

I2C, SPI and UART Ports

We will add any I2C, SPI, and UART pins in a later section and address both the chip and board files at the same time.

Additional Resources

The board file is much easier than the chip file. It basically involves loading the chip file and just mapping the chip pins to the physical pins on the board. At this time there hasn't a consistent naming of the pins due to the documentation provided by different manufacturers referencing the pins by different names.

For instance, naming pins like D4, to imply it is a digital pin number 4, or GPIO22 are pretty common. Some boards also use the same name as the microcontroller pin name like PC0. However, if the board you are using has a Raspberry Pi style connector, we recommended that you go with the D naming scheme since using a name like D4 will make it so it is more consistent with existing CircuitPython examples without needing changes.

If you followed the instruction in the software setup, you should have the Adafruit_Blinka folder in your home directory. Inside of there you'll want to navigate to src/adafruit_blinka/board which is where all of the board files are located, some of them sorted by manufacturer.

If you create a new folder, be sure that __init__.py exists inside the folder or PyPI won't install it.

For the Pine64, we're just going to create a file called pine64.py. We'll start with creating each of the D pins and assigning it to the pins we defined in the chip file. Once that's done, we'll create any aliases that we need such as pins for I2C. We'll go over those a bit more in depth later in this guide. Here are the contents of the board file for the Pine64:

"""Pin definitions for the Pine64."""

from adafruit_blinka.microcontroller.allwinner.a64 import pin

D2 = pin.PH3
D3 = pin.PH2
D4 = pin.PL10
D5 = pin.PH5
D6 = pin.PH6
D7 = pin.PH7
D8 = pin.PC3
D9 = pin.PC1
D10 = pin.PC0
D11 = pin.PC2
D12 = pin.PC4
D13 = pin.PC5
D14 = pin.PB0
D15 = pin.PB1
D16 = pin.PC6
D17 = pin.PC7
D18 = pin.PC8
D19 = pin.PC9
D20 = pin.PC10
D21 = pin.PC11
D22 = pin.PC12
D23 = pin.PC13
D24 = pin.PC14
D25 = pin.PC15
D26 = pin.PC16
D27 = pin.PH9

SDA = D2
SCL = D3

SCL2 = pin.PL8
SDA2 = pin.PL9

SCLK = D11
MOSI = D10
MISO = D9
CS = D8
SCK = SCLK

UART_TX = D14
UART_RX = D15

UART3_TX = pin.PD0
UART3_RX = pin.PD1

UART4_TX = pin.PD2
UART4_RX = pin.PD3

There's one very important thing we have left to get GPIO working. We will need to add the correct imports to a few files. To get it working as a minimum, you will need to check the following files:

  • Adafruit_Blinka/src/board.py
  • Adafruit_Blinka/src/digitalio.py
  • Adafruit_Blinka/src/microcontroller/__init__.py
  • Adafruit_Blinka/src/microcontroller/pin.py

For board.py, if there is another existing almost identical board, you can modify the large if statement to just use that Board file without creating a duplicate file.

For the remaining files, if you are using an existing microcontroller file, you will most likely not need to make any changes, but it's always good to check.

Be sure you've added your board definition file to board.py which is where Blinka loads it from.

Testing

Now that we've gone ahead and added the GPIO to the Chip and Board files, the next step is to go and test out GPIO.

First we'll start with checking the output. We can test that with a simple test script in Python to blink an LED. Let's go ahead and wire up a simple circuit with a button and LED. Go ahead and choose one GPIO for output and one for input. For this diagram, we'll use D17 and D18. We're using a pull-up resistor because libgpiod doesn't support built-in pull-ups, but if you are using a different library that does support that, feel free to remove them.

Parts Used

Need some big indicators? We are big fans of these huge diffused blue LEDs. They are really bright so they can be seen in daytime, and from any angle. They go easily into a breadboard...
$9.95
In Stock
ΩMG! You're not going to be able to resist these handy resistor packs! Well, axially, they do all of the resisting for you!This is a 25 Pack of...
$0.75
In Stock
Medium-sized clicky momentary switches are standard input "buttons" on electronic projects. These work best in a PCB but
$2.50
In Stock
ΩMG! You're not going to be able to resist these handy resistor packs! Well, axially, they do all of the resisting for you!This is a 25 Pack of...
$0.75
In Stock
Handy for making wire harnesses or jumpering between headers on PCB's. These premium jumper wires are 6" (150mm) long and come in a 'strip' of 40 (4 pieces of each of...
$3.95
In Stock
Handy for making wire harnesses or jumpering between headers on PCB's. These premium jumper wires are 6" (150mm) long and come in a 'strip' of 40 (4 pieces of each of...
$3.95
In Stock
This cute 3.2″ × 2.1″ (82 × 53mm) solderless half-size breadboard has four bus lines and 30 rows of pins, our favorite size of solderless breadboard for...
$5.00
In Stock

Wiring

  • Connect the SBC Ground pin to the blue ground rail on the breadboard.
  • Connect one side of the tactile switch to SBC D17 (Pin #11)
  • Connect a ~1K-10K pull up resistor from D17 to 3.3V
  • Connect the other side of the tactile switch to the ground rail
  • Connect the longer/positive pin of the LED to SBC D18 (Pin #12)
  • Connect the shorter/negative pin of the LED to a 220 ohm-2.2K resistor, the other side of the resistor goes to the ground rail

Testing Output

D18 will be used to test the output of GPIO by flashing the LED with this simple script. Go ahead and copy the code and save it on your Single Board Computer as blink.py. Be sure to change the GPIO to whichever one you are testing.

import time
import board
import digitalio
 
led = digitalio.DigitalInOut(board.D18)
led.direction = digitalio.Direction.OUTPUT
 
while True:
    led.value = True
    time.sleep(0.5)
    led.value = False
    time.sleep(0.5)

No go ahead and run it by typing:

sudo python3 blink.py

If everything is working properly, you should see the LED blinking on and off.

Testing Input

We'll go ahead and use the same circuit that we used to test output. This time we'll use a slightly different script. Go ahead and save this script as button.py. Be sure to change the GPIO to whichever one you are testing.

import time
import board
import digitalio

led = digitalio.DigitalInOut(board.D18)
led.direction = digitalio.Direction.OUTPUT

button = digitalio.DigitalInOut(board.D17)
button.direction = digitalio.Direction.INPUT

while True:
    led.value = not button.value # light when button is pressed!

No go ahead and run it by typing:

sudo python3 button.py

If everything is working properly, you should see the LED turn on if the button is pressed.

Troubleshooting Tips

If neither of the above scripts work, here are some troubleshooting tips:

  • The problem is more likely to be in the Chip file.
  • Try a different GPIO and see if that works
  • Check the Data Sheet
  • Sometimes Pins are Multiplexed meaning they are used in different ways and something may need to be disabled for them to work.
  • If there's a Python Error check to see if you can trace it back
  • Sometimes they just don't work
  • Try asking in Discord if nothing else works

Next we'll work on getting I2C working. To get I2C working, you will first need to make sure your I2C is enabled. The exact method to enable it can vary from manufacturer to manufacturer. You can check if it's already enabled by typing the following command:

ls /dev/i2c*

If it is enabled, there should be at least one i2C port listed.

Add your board to busio.py

You will also need to be sure your board is correctly importing in Adafruit_Blinka/src/busio.py. Inside of that file is a large import statement for I2C, SPI, and UART. You will want to add your board to all three if you plan on supporting all three.

Adding to the Chip File

Near the bottom of the Chip File, you will want an i2cPorts tuple variable that contains all of the I2C ports. It should end up looking something like the following:

# ordered as i2cId, sclId, sdaId
i2cPorts = (
    (0, I2C0_SCL, I2C0_SDA),
    (1, I2C1_SCL, I2C1_SDA)
)

The values in each of the tuple items are as follows:

  • The I2C bus number. For instance, a value of 1 corresponds to /dev/i2c-1.
  • The SCL or Serial Clock pin. This should be an alias for readability.
  • The SDA or Serial Data pin. This should also be an alias for readability.

Adding to the Board File

The only thing you will need to add to the board file are any aliases for the Pins.

Clock Stretching

Occasionally certain Single Board Computers such as the Raspberry Pi need some settings changed to enable Clock Stretching. You will need to check with the board manufacturer to be certain, but you can read more about it in our Raspberry Pi Clock Stretching guide.

Parts Used

To test, we'll need an I2C controlled board such as the BME280, which is a temperature sensor along with a couple other parts to connect it to the board.

Some boards operate at a lower voltage than 3.3v and may need to be wired through an I2C-safe Level Shifter as well.
Bosch has stepped up their game with their new BME280 sensor, an environmental sensor with temperature, barometric pressure and humidity! This sensor is great for all sorts...
$14.95
In Stock
This cute 3.2″ × 2.1″ (82 × 53mm) solderless half-size breadboard has four bus lines and 30 rows of pins, our favorite size of solderless breadboard for...
$5.00
In Stock
Handy for making wire harnesses or jumpering between headers on PCB's. These premium jumper wires are 6" (150mm) long and come in a 'strip' of 40 (4 pieces of each of...
$3.95
In Stock

Wiring

  • Connect the SBC 3.3V power pin to Vin
  • Connect the SBC GND pin to GND
  • Connect the SBC SDA (Pin #3) pin to the BME280 SDI
  • Connect the SBC SCL (Pin #5) pin to to the BME280 SCK

Check the Connection

After wiring it up, double-check your connections and run the following command to check bus 1:

sudo i2cdetect -y 1

If it gives you an error, you may need to add an -r parameter such as:

sudo i2cdetect -r -y 1

You should see a device. If not, you may want to try changing the I2C bus number until you get something. The BME280 has an I2C address of 0x77. It should look like the following:

Run the Test Script

Testing is really the most involved part about enabling I2C. First make sure the BME280 library is installed. If you chose a different sensor, make sure that library is installed:

sudo pip3 install adafruit-circuitpython-bme280

Next save the simpletest code to your board as bme280_simpletest.py:

# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import time
import board
from adafruit_bme280 import basic as adafruit_bme280

# Create sensor object, using the board's default I2C bus.
i2c = board.I2C()  # uses board.SCL and board.SDA
bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c)

# OR create sensor object, using the board's default SPI bus.
# spi = board.SPI()
# bme_cs = digitalio.DigitalInOut(board.D10)
# bme280 = adafruit_bme280.Adafruit_BME280_SPI(spi, bme_cs)

# change this to match the location's pressure (hPa) at sea level
bme280.sea_level_pressure = 1013.25

while True:
    print("\nTemperature: %0.1f C" % bme280.temperature)
    print("Humidity: %0.1f %%" % bme280.relative_humidity)
    print("Pressure: %0.1f hPa" % bme280.pressure)
    print("Altitude = %0.2f meters" % bme280.altitude)
    time.sleep(2)

Now go ahead and run that code using the following command:

sudo python3 bme280_simpletest.py

You should see it outputting a temperature and some other data every 2 seconds or so:

You will want to perform the same test procedure on each I2C port that you want to test. Be sure to change both your wiring and the corresponding pins in the test script if necessary.

Troubleshooting Tips

  • Make Sure I2C is enabled on your board
  • Make sure i2cdetect is seeing the sensor
  • Check your Wiring
  • Check Permissions
  • The board may just not support I2C.
If you haven't already done so, be sure your board is added to the large if statement in Adafruit_Blinka/src/busio.py. See the I2C page for more details.

Next we'll work on getting SPI working. To get SPI working, you will first need to make sure your SPI is enabled. The exact method to enable it can vary from manufacturer to manufacturer. For the Pine64, we used armbian-config and enabled spi-spidev. You can check if it's enabled by typing the following command:

ls /dev/spi*

If it is enabled, there should be at least one SPI port listed.

Adding to the Chip File

Near the bottom of the Chip File, you'll want an spiPorts tuple variable that contains all of the SPI ports. It should end up looking something like the following:

# ordered as spiId, sckId, mosiId, misoId
spiPorts = (
    (0, SPI0_SCLK, SPI0_MOSI, SPI0_MISO),
    (1, SPI1_SCLK, SPI1_MOSI, SPI1_MISO),
)

The values in each of the tuple items are as follows:

  • The SPI bus number. For instance, a value of 1 corresponds to /dev/spidev1.0. At this time, only Spidev device 0 is supported.
  • The SCLK or Serial Clock pin. This should be an alias for readability.
  • The MOSI or Microcontroller Out Serial In pin. This should also be an alias for readability.
  • The MISO or Microcontroller In Serial Out pin. This should also be an alias for readability.

Adding to the Board File

The only thing you will need to add to the board file are any aliases for the Pins.

Parts Used

To test, we'll need an SPI controlled board such as the MAX31855, which is a Thermocouple Amplifier sensor (for temperature measurement) along with a couple other parts to connect it to the board.

Some boards operate at a lower voltage than 3.3v and may need to be wired through a Level Shifter as well.
Thermocouples are very sensitive, requiring a good amplifier with a cold-compensation reference. The MAX31855K does everything for you, and can be easily interfaced with any...
$14.95
In Stock
Thermocouples are best used for measuring temperatures that can go above 100 °C. This is a bare wires bead-probe which can measure air or surface temperatures. Most inexpensive...
Out of Stock
This cute 3.2″ × 2.1″ (82 × 53mm) solderless half-size breadboard has four bus lines and 30 rows of pins, our favorite size of solderless breadboard for...
$5.00
In Stock
Handy for making wire harnesses or jumpering between headers on PCB's. These premium jumper wires are 6" (150mm) long and come in a 'strip' of 40 (4 pieces of each of...
$3.95
In Stock

Wiring

  • Connect the SBC 3.3V power pin to Vin
  • Connect the SBC GND pin to GND
  • Connect the SBC SCLK pin to the MAX31855 CLK
  • Connect the SBC MISO pin to to the MAX31855 DO
  • Connect the SBC D5 pin to to the MAX31855 CS

Run the Test Script

After wiring it up, double-check your connections. Next, make sure the MAX31855 library is installed. If you chose a different sensor, make sure that library is installed:

sudo pip3 install adafruit-circuitpython-max31855

Next save the simpletest code to your board as max31855_simpletest.py:

# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import time
import board
import digitalio
import adafruit_max31855

spi = board.SPI()
cs = digitalio.DigitalInOut(board.D5)

max31855 = adafruit_max31855.MAX31855(spi, cs)
while True:
    tempC = max31855.temperature
    tempF = tempC * 9 / 5 + 32
    print("Temperature: {} C {} F ".format(tempC, tempF))
    time.sleep(2.0)

Now go ahead and run that code using the following command:

sudo python3 max31855_simpletest.py

You should see it outputting the temperature every 2 seconds or so:

You will want to perform the same test procedure on each SPI port that you want to test. Be sure to change both your wiring and the corresponding pins in the test script if necessary.

Troubleshooting Tips

  • Make Sure SPI is enabled on your board
  • Try a different GPIO for the Chip Select Pin
  • Check your Wiring
  • The board may just not support SPI. It often doesn't work for us.

Next we'll work on getting Serial UART working. To get UART working, you will first need to make sure the correct serial ports are enabled. The exact method to enable it can vary from manufacturer to manufacturer. For the Pine64, we used armbian-config and enabled uart2, uart3, and uart4. You can check which serial ports are enabled by typing the following command:

ls /dev/tty*

You'll likely see a lot of ports listed. Finding which port is the proper one is usually found with searching the internet. They often have a letter between the tty part and the number. Here's what we have on the Pine64:

The only ones that have a letter between the tty and number are /dev/ttyS0, /dev/ttyS2, and /dev/ttyS3, which are in fact the correct ones for this board. The reason there is no /dev/ttyS1 is because that port is being used for the Serial Console, so we didn't enable it. You could try narrowing it down with the following command:

ls /dev/tty[a-zA-Z]*

This will list only ports with a letter A-Z (lower case or capital) after the tty part.

Parts Used

To test, we'll need a serial controlled board such as a GPS module, along with a couple other parts to connect it to the board. There's a couple version of the GPS module that are useful. The breakout version is useful for hooking up to GPIO and the USB version makes it easy to test serial via the USB port. For the purposes of this guide though, the breakout version is more useful as it can be connected to a USB to TTL serial cable.

We carry a few different GPS modules here in the Adafruit shop, but none that satisfied our every desire - that's why we designed this little GPS breakout board. We believe this is...
$29.95
In Stock
The cable is easiest way ever to connect to your microcontroller/Raspberry Pi/WiFi router serial console port. Inside the big USB plug is a USB<->Serial conversion chip and at...
$9.95
In Stock
This cute 3.2″ × 2.1″ (82 × 53mm) solderless half-size breadboard has four bus lines and 30 rows of pins, our favorite size of solderless breadboard for...
$5.00
In Stock
Handy for making wire harnesses or jumpering between headers on PCB's. These premium jumper wires are 6" (150mm) long and come in a 'strip' of 40 (4 pieces of each of...
$3.95
In Stock
Handy for making wire harnesses or jumpering between headers on PCB's. These premium jumper wires are 6" (150mm) long and come in a 'strip' of 40 (4 pieces of each of...
$3.95
In Stock

Testing Serial over Built-in UART

For the purposes of adding a new board, we only need to be concerned with whether the Serial over GPIO pins is working as there isn't really anything to configure to get Serial working over USB.

On several boards, the Built-in UART and serial console share the same pins and getting this working isn't always possible.

Wiring

  • Connect the SBC 3.3V power pin to V+ Rail
  • Connect the SBC GND pin to GND Rail
  • Connect the SBC TX pin to the GPS RX
  • Connect the SBC RX pin to to the GPS TX
  • Connect the GPS Vin power pin to V+ Rail
  • Connect the GPS GND pin to GND Rail

Run the Test Script

After wiring it up, double-check your connections. Next, make sure PySerial and the GPS library are installed. If you chose a different sensor, make sure that library is installed:

sudo pip3 install pyserial adafruit-circuitpython-gps

Next save the simpletest code to your board as gps_simpletest.py:

# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

# Simple GPS module demonstration.
# Will wait for a fix and print a message every second with the current location
# and other details.
import time
import board
import busio

import adafruit_gps

# Create a serial connection for the GPS connection using default speed and
# a slightly higher timeout (GPS modules typically update once a second).
# These are the defaults you should use for the GPS FeatherWing.
# For other boards set RX = GPS module TX, and TX = GPS module RX pins.
uart = busio.UART(board.TX, board.RX, baudrate=9600, timeout=10)

# for a computer, use the pyserial library for uart access
# import serial
# uart = serial.Serial("/dev/ttyUSB0", baudrate=9600, timeout=10)

# If using I2C, we'll create an I2C interface to talk to using default pins
# i2c = board.I2C()

# Create a GPS module instance.
gps = adafruit_gps.GPS(uart, debug=False)  # Use UART/pyserial
# gps = adafruit_gps.GPS_GtopI2C(i2c, debug=False)  # Use I2C interface

# Initialize the GPS module by changing what data it sends and at what rate.
# These are NMEA extensions for PMTK_314_SET_NMEA_OUTPUT and
# PMTK_220_SET_NMEA_UPDATERATE but you can send anything from here to adjust
# the GPS module behavior:
#   https://cdn-shop.adafruit.com/datasheets/PMTK_A11.pdf

# Turn on the basic GGA and RMC info (what you typically want)
gps.send_command(b"PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0")
# Turn on just minimum info (RMC only, location):
# gps.send_command(b'PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0')
# Turn off everything:
# gps.send_command(b'PMTK314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0')
# Turn on everything (not all of it is parsed!)
# gps.send_command(b'PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0')

# Set update rate to once a second (1hz) which is what you typically want.
gps.send_command(b"PMTK220,1000")
# Or decrease to once every two seconds by doubling the millisecond value.
# Be sure to also increase your UART timeout above!
# gps.send_command(b'PMTK220,2000')
# You can also speed up the rate, but don't go too fast or else you can lose
# data during parsing.  This would be twice a second (2hz, 500ms delay):
# gps.send_command(b'PMTK220,500')

# Main loop runs forever printing the location, etc. every second.
last_print = time.monotonic()
while True:
    # Make sure to call gps.update() every loop iteration and at least twice
    # as fast as data comes from the GPS unit (usually every second).
    # This returns a bool that's true if it parsed new data (you can ignore it
    # though if you don't care and instead look at the has_fix property).
    gps.update()
    # Every second print out current location details if there's a fix.
    current = time.monotonic()
    if current - last_print >= 1.0:
        last_print = current
        if not gps.has_fix:
            # Try again if we don't have a fix yet.
            print("Waiting for fix...")
            continue
        # We have a fix! (gps.has_fix is true)
        # Print out details about the fix like location, date, etc.
        print("=" * 40)  # Print a separator line.
        print(
            "Fix timestamp: {}/{}/{} {:02}:{:02}:{:02}".format(
                gps.timestamp_utc.tm_mon,  # Grab parts of the time from the
                gps.timestamp_utc.tm_mday,  # struct_time object that holds
                gps.timestamp_utc.tm_year,  # the fix time.  Note you might
                gps.timestamp_utc.tm_hour,  # not get all data like year, day,
                gps.timestamp_utc.tm_min,  # month!
                gps.timestamp_utc.tm_sec,
            )
        )
        print("Latitude: {0:.6f} degrees".format(gps.latitude))
        print("Longitude: {0:.6f} degrees".format(gps.longitude))
        print("Fix quality: {}".format(gps.fix_quality))
        # Some attributes beyond latitude, longitude and timestamp are optional
        # and might not be present.  Check if they're None before trying to use!
        if gps.satellites is not None:
            print("# satellites: {}".format(gps.satellites))
        if gps.altitude_m is not None:
            print("Altitude: {} meters".format(gps.altitude_m))
        if gps.speed_knots is not None:
            print("Speed: {} knots".format(gps.speed_knots))
        if gps.track_angle_deg is not None:
            print("Track angle: {} degrees".format(gps.track_angle_deg))
        if gps.horizontal_dilution is not None:
            print("Horizontal dilution: {}".format(gps.horizontal_dilution))
        if gps.height_geoid is not None:
            print("Height geoid: {} meters".format(gps.height_geoid))

Next, we need to make a few changes to the test script:

  • Comment out the lines that reference board.TXboard.RX and busio.uart
  • Uncomment the line to import serial
  • Uncomment the line to define the serial device.
  • Change the Serial port to the one you are testing

Now go ahead and run that code using the following command:

sudo python3 gps_simpletest.py

You should see it outputting some NMEA sentences about every 5 seconds or so:

You will want to perform the same test procedure on each UART port that you want to test. Be sure to change both your wiring and the corresponding pins in the test script if necessary.

Troubleshooting Tips

  • Make Sure the Serial UART is enabled on your board
  • Make sure the Serial Console isn't sharing the pins
  • Check your wiring
  • The board may just not work with Serial.

This guide covers the basics of adding a board that can support GPIO, I2C, SPI, and UART Serial. However, Blinka supports other features such as PWM Output using the pulseio module, NeoPixel Support, and Analog Support using the analogio module.

Since each of these is implemented differently for each board, we don't have a specific set of steps to follow. However, the separate files for each supported module should be stored in the same folder as your Chip file.

PWM Output

There are a couple of ways that you can add PWM support to your board. Either you can use built-in PWM support and use the sysfs PWM library, or you can use a custom driver such as one provided by the manufacturer. Custom manufacturer drivers vary widely and is beyond the scope of this guide. Additionally, not all boards support PWM. You could check that it is supported natively by running the command:

ls /sys/class/pwm

The check if anything shows up. There was nothing for the Pine64, but there was when we added the Coral:

If you do find something, you can get some more information by typing:

sudo cat /sys/kernel/debug/pwm

None of these PWMs in the example photo are in use. You can tell by the (null) and that the period and duty cycle are 0. If they were in use, you couldn't use them. Also, sometimes the pwm chips can control more than 1 pin.

To add natively supported PWM, you'll first need to add pwmOuts to your Chip file such as:

pwmOuts = (
  ((0, 0), PWM1),
  ((1, 0), PWM2),
  ((2, 0), PWM3),
)

The first parameter is a tuple and consists of the PWM chip number and the PWM channel of that chip.

The second parameter is the PWM pin and can be found by either a web search or referring to the manufacturer data.

Don't forget to add the pins to the Board file as well in the same way as the GPIO.

Regardless of whether you are using a manufacturer driver or adding it natively, you will also want to add your board to Adafruit_Blinka/src/pulseio.py.

NeoPixel Support

To add NeoPixel support, you will need a driver that is capable of running the NeoPixels at a rate of 800Hz since they run at a fixed frequency. If you are able to find or write a driver for the board, you will want to be sure to add your board to Adafruit_Blinka/src/neopixel_write.py.

Analog Support

For adding Analog Support, your board will first need to have PINs that have either an ADC or Analog-to-Digital Converter, or a DAC, which is the opposite of an ADC. You will most likely need to create a custom Pin file for your board that adds support for those pins and you will want to add your board to Adafruit_Blinka/src/analogio.py.

Now that you have your board being correctly detected, the next step is to create a Pull Request to the Adafruit_Blinka repo. If you are not sure how to create a Pull Request, check out this page from the GitHub guide we mentioned earlier. You'll want to be sure to run Black to format your code as well as Pylint so that it passes the automated code check.

Once your Pull Request is approved and merged, your new board will now be a part of Blinka!

With all that hard work finished, it's time to show off that new board on circuitpython.org!

To get your board listed on circuitpython.org, you will want to submit a separate Pull Request for that. Details about adding your board can be found in the How to add a New Board to the circuitpython.org website guide.

This guide was first published on Apr 01, 2020. It was last updated on Apr 01, 2020.