Hardware Extensions

circuitpython_c64.jpg
C64 board by Christian Taube CCA-SA 2.5

Hardware support is done with a two level approach.

Builtin Support

Firstly, the most basic capabilities have been added to the CircuitScheme runtime by way of builtin functions: the board module to give access to pins, digital and analog I/O, I2C bus creation, and sleep.

Let's look at the Hello, World of hardware: blink. It shows setting up a digital output on D13, setting its value, and sleeping. Note it uses recursion. The let structure simply binds the symbol led to the digital output for use in the code within the let.

Download: file
(define (blink)
  (let ((led (digital-pin (board "D13") **OUTPUT**)))
    (define (loop val)
      (pin-value! led val)
      (sleep 0.5)
      (loop (not val)))
    (loop #t)))

The functions below are supplied. They work as you would expect. Later in the guide, we'll see some examples.

board-pins - get a list of the names of all pins on the board
board - given a pin name, return the corresponding pin object
digital-pin - create a DigitalInOut from a pin object
analog-pin - create an analog pin from a pin name and direction
**INPUT** - use to create a digital or analog input
**OUTPUT** - use to create a digital or analog output
**PULLUP** - set a pull up resistor on a digital input
**PULLDOWN**- set a pull down resistor on a digital input
pin-value - get the value (digital or analog) of a pin
pin-value! - set the value (digital or analog) of a pin
i2c - create an I2C bus object given SCL and SDA pins objects
sleep - delay some number of seconds

Wrapping CircuitPython Driver Libraries

The second approach provides a facility to dynamically load wrappers around Python device driver modules. This will generally just be a Python file, but can involve a file of CircuitScheme code as well. A .py and .scm file with the same basename can be loaded from the /devices subdirectory on CIRCUITPY. If either file is not found, it is ignored. The code implementing this is:

def execfile(f):
    exec(open(f).read())

def load_device(device_driver_name):
    try:
        execfile('./devices/{0}.py'.format(device_driver_name))
    except OSError:
        pass

    try:
        load('./devices/{0}.scm'.format(device_driver_name))
    except OSError:
        pass

The load_device function is bound to load-device in CircuitScheme, so to load support for a device (e.g. the Si7021 temperature and humidity sensor) you would use:

(load-device "si7021")

Then the functions provided can be used. Here is devices/si7021.py (there is no si7021.scm):

Download: file
import adafruit_si7021

def make_si7021(i2c_bus):
    return adafruit_si7021.SI7021(i2c_bus)

def si7021_relative_humidity(device):
    return device.relative_humidity

def si7021_temperature(device):
    return device.temperature

global_env.storage.update({
    'make-si7021':make_si7021,
    'si7021-relative-humidity':si7021_relative_humidity,
    'si7021-temperature':si7021_temperature
    })

This defines three functions that we want to make available: create an Si7021 interface object, read the humidity, and read the temperature. It then makes them available to CircuitScheme code by adding them to the global environment's dictionary: global_env.storage

Once loaded, it can be used:

Download: file
(load-device "si7021")
(define sensor (make-si7021 (i2c (board "SCL") (board "SDA"))))
(display "Temperature: ")
(display (si7021-temperature sensor))
(newline)
(display "   Humidity: ")
(display (si7021-relative-humidity sensor))
(newline)

Which results in:

CircuitScheme version 1.0: 185280 free bytes
==> (load "si7021-sample")
Temperature: 24.3539
Humidity: 42.3818
==>

This guide was first published on Feb 14, 2019. It was last updated on Feb 14, 2019. This page (Hardware Extensions) was last updated on Sep 28, 2019.