empowering creative people

Using the Accelerometer with CircuitPython: Adafruit Circuit Playground Express

At the heart of every Adafruit Circuit Playground Express lies a three-axis accelerometer. This allows us to write programs that take the orientation of the board into account. We can detect orientation and acceleration on any axis. With CircuitPython we can use the “cpx.acceleration” command to read the acceleration of the board on all three axis in m/s^2.


Adafruit Circuit Playground Express Accelerometer

If the board is sitting on a table:

  • the X-axis is aligned horizontally from left to right. If you tilt left, X is negative, tilt right X is positive.
  • the Y-axis is aligned vertically from bottom to top. If you tilt forward, Y is positive, tilt backward Y is negative.
  • the Z-axis is perpendicular to the board and pointing down. At rest, Z is aligned with earth gravity (9.8 m/s^2).

For this tutorial, we will create a sketch with two parts. When the slide switch is to the left we will have a simple sketch that mirrors the MakeCode tutorial results for the same project. Lights will be activated on each side of the board when it is tilted in each direction. In this mode, we will also print the live accelerometer data.

When the slide switch is to the right we will use the accelerometers and a bit of math to make the lights behave like a swinging pendulum that will be affected by the motion of the Circuit Playground Express!


The Code

Let's look at the code, and then break it down into parts.

# Accelerometer controlled NeoPixels 
# Created by Stephen @ core-electronics.com.au 

from adafruit_circuitplayground.express import cpx
import board
import math


cpx.pixels.fill((0, 0, 0))
cpx.pixels.show()

pos = 0  # Starting center position of pupil
increment = 2 * 3.14 / 10  # distance between pixels in radians
MomentumH = 0  # horizontal component of pupil rotational inertia
MomentumV = 0  # vertical component of pupil rotational inertia
 
# Tuning constants. (a.k.a. "Fudge Factors)  
# These can be tweaked to adjust the liveliness and sensitivity of the eyes.
friction = 0.90  # frictional damping constant.  1.0 is no friction.
swing = 10  # arbitrary divisor for gravitational force
gravity = 50  # arbitrary divisor for lateral acceleration
halfWidth = 1.25  # half-width of pupil (in pixels)
Pi = 3.14  # Pi for calculations - not the raspberry type

while True:
    x_float, y_float, z_float = cpx.acceleration  # read accelerometer  
    x = int(x_float)  # make int of it
    y = int(y_float)
    z = int(z_float)
    
    # change modes with slide switch left
    if cpx.switch:
        
        #              Y+
        #            .___.
        #         .         .
        #    X-  .           .  X+
        #        .    z+-    .
        #         .         .
        #            .|_|.
        #              Y-
        
        # print live accelerometer data
        print("x, y, z = ", x, " ", y, " ", z)
        if math.fabs(x) < 3 and y > 3:
           cpx.pixels[0] = (255, 0, 0)
           cpx.pixels[9] = (255, 0, 0)
        elif x > 3 and math.fabs(y) < 3:
            cpx.pixels[6] = (255, 0, 0)
            cpx.pixels[7] = (255, 0, 0)        
            cpx.pixels[8] = (255, 0, 0)
        elif math.fabs(x) < 3 and y < -3:
            cpx.pixels[4] = (255, 0, 0)
            cpx.pixels[5] = (255, 0, 0)        
        elif x < -3 and math.fabs(y) < 3:
            cpx.pixels[1] = (255, 0, 0)
            cpx.pixels[2] = (255, 0, 0)        
            cpx.pixels[3] = (255, 0, 0)
        else:
            cpx.pixels.fill((0, 0, 0))
            cpx.pixels.show()

    # when slide switch right
    else:
        # Calculate the horizontal and vertical effect on the virtual pendulum
        # 'pos' is a pixel address, so we multiply by 'increment' to get radians.
        TorqueH = math.cos(pos * increment)  # peaks at top and bottom of the swing
        TorqueV = math.sin(pos * increment)    # peaks when the pendulum is horizontal
    
        # Add the incremental acceleration to the existing momentum
        MomentumH += TorqueH * x_float / swing
        MomentumV += TorqueV * y_float / gravity
    
        # apply a little frictional damping to keep things in control
        MomentumH *= friction
        MomentumV *= friction

        # Calculate the new position
        pos += MomentumH + MomentumV
   
        # handle the wrap-arounds at the top
        while (round(pos) < -1): 
            pos += 11.0
        while (round(pos) > 10): 
            pos -= 11.0
 
        # Now re-compute the display
        for i in range(10):
            # Compute the distance bewteen the pixel and the center
            # point of the virtual pendulum.
            diff = i - pos
            # Light up nearby cpx.pixels proportional to their proximity to 'pos'
            if (math.fabs(diff) <= halfWidth):
                proximity = halfWidth - math.fabs(diff) * 200
                # pick a color based on heading & proximity to 'pos'
                cpx.pixels[i] = [int(proximity), 0, 0]
            else:  # all others are off
                cpx.pixels[i] = ((0, 0, 0))
        cpx.pixels.show()

This is the part of the code that relates to the slide switch right. We take a reading from the accelerometer and store it in three variables. Even if you only need the data from one axis, you must create three variables to query the accelerometer. We then convert our values from floating data point to integers. This is a non-essential step that makes the serial printed values easier to read. We then compare the values of the x and y-axis to turn on the desired NeoPixels when the board is tilted. We used math.fabs() to return the absolute value of an axis to make the logic simpler.

while True:
    x_float, y_float, z_float = cpx.acceleration  # read accelerometer  
    x = int(x_float)  # make int of it
    y = int(y_float)
    z = int(z_float)
    
    # change modes with slide switch left
    if cpx.switch:
        
        # print live accelerometer data
        print("x, y, z = ", x, " ", y, " ", z)
        if math.fabs(x) < 3 and y > 3:
           cpx.pixels[0] = (255, 0, 0)
           cpx.pixels[9] = (255, 0, 0)
        elif x > 3 and math.fabs(y) < 3:
            cpx.pixels[6] = (255, 0, 0)
            cpx.pixels[7] = (255, 0, 0)        
            cpx.pixels[8] = (255, 0, 0)
        elif math.fabs(x) < 3 and y < -3:
            cpx.pixels[4] = (255, 0, 0)
            cpx.pixels[5] = (255, 0, 0)        
        elif x < -3 and math.fabs(y) < 3:
            cpx.pixels[1] = (255, 0, 0)
            cpx.pixels[2] = (255, 0, 0)        
            cpx.pixels[3] = (255, 0, 0)
        else:
            cpx.pixels.fill((0, 0, 0))
            cpx.pixels.show()

When the slide switch is to the right, we use the accelerometer data to create a pendulum effect. The first block of our code identifies all our control variables. You can play around with these values to change the effect. Within the loop, we have the math needed to create the motion reactive effect of the pendulum and compute a light dimming with proximity to the pendulum.

pos = 0  # Starting center position of pupil
increment = 2 * 3.14 / 10  # distance between pixels in radians
MomentumH = 0  # horizontal component of pupil rotational inertia
MomentumV = 0  # vertical component of pupil rotational inertia
 
# Tuning constants. (a.k.a. "Fudge Factors)  
# These can be tweaked to adjust the liveliness and sensitivity of the eyes.
friction = 0.90  # frictional damping constant.  1.0 is no friction.
swing = 10  # arbitrary divisor for gravitational force
gravity = 50  # arbitrary divisor for lateral acceleration
halfWidth = 1.25  # half-width of pupil (in pixels)
Pi = 3.14  # Pi for calculations - not the raspberry type

while True:
   x_float, y_float, z_float = cpx.acceleration  # read accelerometer  
    x = int(x_float)  # make int of it
    y = int(y_float)
    z = int(z_float)

   # when slide switch right
    else:
        # Calculate the horizontal and vertical effect on the virtual pendulum
        # 'pos' is a pixel address, so we multiply by 'increment' to get radians.
        TorqueH = math.cos(pos * increment)  # peaks at top and bottom of the swing
        TorqueV = math.sin(pos * increment)    # peaks when the pendulum is horizontal
    
        # Add the incremental acceleration to the existing momentum
        MomentumH += TorqueH * x_float / swing
        MomentumV += TorqueV * y_float / gravity
    
        # apply a little frictional damping to keep things in control
        MomentumH *= friction
        MomentumV *= friction

        # Calculate the new position
        pos += MomentumH + MomentumV
   
        # handle the wrap-arounds at the top
        while (round(pos) < -1): 
            pos += 11.0
        while (round(pos) > 10): 
            pos -= 11.0
 
        # Now re-compute the display
        for i in range(10):
            # Compute the distance bewteen the pixel and the center
            # point of the virtual pendulum.
            diff = i - pos
            # Light up nearby cpx.pixels proportional to their proximity to 'pos'
            if (math.fabs(diff) <= halfWidth):
                proximity = halfWidth - math.fabs(diff) * 200
                # pick a color based on heading & proximity to 'pos'
                cpx.pixels[i] = [int(proximity), 0, 0]
            else:  # all others are off
                cpx.pixels[i] = ((0, 0, 0))
        cpx.pixels.show()

Now you're ready to get out there and create using the accelerometers on your Adafruit Circuit Playground Express! This is a great sensor that you can incorporate into many projects, and there is a lot of potential for wearables and mobile projects. If you want to learn more about how to use the Adafruit Circuit Playground Express check out our Circuit Playground Tutorials! We have a tutorial for every piece of onboard hardware on the board, in both MakeCode and CircuitPython!

At the heart of every Adafruit Circuit Playground Express lies a three-axis accelerometer. This allows us to write programs t...

Have a question? Ask the Author of this guide today!