Pi bakery: rhythmic gymnastics

By Russell Barnes. Posted

Create hypnotic ribbon effects with this ribbon twirling simulator just like in the Olympics

This project was inspired by the recent Olympic games. We’ve always liked the rhythmic gymnastics, especially the section with the ribbons, so we set off to recreate this on the Pi. This project is a bit of a departure from normal Bakery stuff, in that for the first time we switch languages from Python to Processing. While Processing is a strange name for a programming language, the language itself is quite good. Basically, it’s an implementation of Java, and Java is implemented with a C syntax. It’s a language much beloved by the artistic, creative community, and there are plenty of stunning examples of its use. We used it for this project because there was already an excellent ribbon drawing class that makes the code writing so much easier.

The full article can be found in The MagPi 49 and was written by Mike Cook.

You'll need

  • MCP3004 – analogue-to-digital converter
  • 2× thumb joysticks
  • 13-by-10-hole stripboard
  • Wooden box (MDF)
  • 4× 15mm M3 tapped pillars
  • 0.1uF ceramic capacitor
  • 8-way ribbon cable

The hardware

The two ribbons are controlled by small thumb joysticks, and are read into the Pi with an MCP3004 analogue-to-digital converter (ADC). This is the cousin of the MCP3002 chip we used in the Spectrum Display and the Hairgrip sequencer of MagPi issues 45 and 46. This chip has four analogue inputs, but uses the same SPI software commands to interface with it as the smaller chip. You could also use the eight-channel MCP3008 if you like, and there are a few pre-built Pi interfaces that use this chip. The schematic is quite simple and is shown above; we used the surface-mount version of the chip, but through-hole chips are also available. Full construction notes for the through-hole chip are below.

 The full circuit diagram for this project - use it for reference!

Building the ribbon controller

STEP-01 Prepare the board

Get a piece of 13-by-10-hole stripboard and cut the tracks as shown, with the view from under the board. There’s just one IC on this board; the rest of the board is used to hold the input/output wires. Solder a 14‑pin socket to the other side of the board.

 Prepare the board

STEP-02 Mounting the components

Solder solid wire links between the holes as shown and fit the 0.1uF decoupling capacitor. Wire the two thumb joysticks to the board. Wire the connections to the Pi using a length of 8-way ribbon cable. We used a 26-way socket to plug onto the GPIO pins; that way it will work with all models of Pi.

 Mounting the components

STEP-03 Construct the box

The base plate is drilled with shallow holes to allow the Gorilla Glue to foam into them, and the sides glued on are made from 17mm strip pine. Make a top with two 25mm diameter holes for the joysticks and fasten to the base with tapped pillars. Make a small notch in the top side to allow the ribbon cable to come through.

 Construct the box

The language

As Processing has only recently been ported to the Pi, it’s likely you will need to install it; this is not complex but can take a little time. You can install it from the command line by typing:

curl https://processing.org/download/install-arm.sh | sudo sh

After a reboot, it will appear in the Programming section of the desktop’s Main Menu. The good news is that the Pi version of Processing has support for the GPIO pins built in; for this project we need to use the SPI port, so this needs to be enabled. These days this is easy to do: just open Raspberry Pi Configuration (under Menu>Preferences), navigate to the Interfaces tab, enable SPI, and then restart your Pi. After installing Processing, you will find a folder called sketchbook on the top level of your files; this is the normal place to put your Processing code. Start up Processing from the Programming menu; it’s not quick to start, especially the first time, so you’ll need to be a little patient. Have a look at the examples found in the File menu; most work, although some of the graphic demos don’t. One favourite of ours is found at Topics>Fractals>Tree.

The software

Now take the blank program that came up on startup and save it as ‘ribbons’, then click the arrow next to the tab, select New Tab, and name it ‘MCP3004’. Make three more tabs and call them ‘Ribbon’, ‘RibbonManager’, and ‘RibbonParticle’. Now we’re ready to start filling these tabs with code, so we’ll start with the pre-written classes. Go to the code page, copy the code from the comment //manager to // ribbon, and paste it into the RibbonManager tab. Next, copy the code from //ribbon to // particle and paste it into the Ribbon tab. Finally, copy the code from //==particle to the end of the file and paste it into the RibbonParticle tab. Now for our code. Type the Ribbons.pde listing into the ‘ribbons’ tab and MCP3004.pde into the MCP3004 tab. This last class was part of the Processing distribution, but it contained an error that took about a day to track down; make similar changes to the MCP3008 class if you want to use that. Now we need to make some minor changes to the RibbonManager class. Find the part of the file near the end that starts void setDragFlaire and after that line, type in the new method setNewColour found in the Change_colour.pde listing. The last tweak is in the Ribbon tab. Change:

float radiusMax = 8;

to

float radiusMax = 12;

One final thing to do is to take two JPEG images and save them in the ribbons folder of the sketchbook folder. These should be called swatch01.jpg and swatch02.jpg and are the images that will be used to generate the colours of the ribbons. Two random pixels are chosen from each for the colours. These are best as just tiny images, like a 16-by-16 pixel image of colours that go together, but any image file will do.

Running the code

Click the triangle in the top-left corner of the code window to run the code; it’s set up to run in full screen mode, although you can set a window size if you want, by commenting out the full screen and uncommenting the line below. The code will run faster in a small window than full screen. Note that in Processing the double slash // is the comment symbol. Moving the joypads will move the ribbon round the screen, and pressing on the joypad will click a switch and change the ribbon colour.

All the code, ready to run and with two swatch images, is available on the GitHub repository. It's also posted below.

Taking it further

There are lots of default parameters you can change about the ribbons, either in the Ribbon tab or when you instantiate the ribbon in the main ‘ribbons’ tab. For example, you can change the ribbon parameters of friction, gravity, dragFlare, and ribbonAmount. This last one controls the number of strands in a ribbon. Also, making small swatch images is a good way to change the colours in a way you like. Happy twirling!

https://vimeo.com/180026835

Code listings

Ribbons.pde

// Ribbons by Mike Cook August 2016
// with credit to http://www.zenbullets.com
import processing.io.*;
MCP3004 adc;

int ribbonAmount = 2; // number of ribbon strands
int ribbonParticleAmount = 20;
float randomness = .2;
RibbonManager ribbonManager1;
RibbonManager ribbonManager2;
float xPad1 = 0.120, yPad1 = 0.120; 
float xPad2 = 0.120, yPad2 = 0.120;
boolean rightClick = false;
boolean leftClick = false;

void setup()
{   
  fullScreen();
  //size(600, 450);
  frameRate(30);
  background(0);
  GPIO.pinMode(2, GPIO.INPUT);
  GPIO.pinMode(3, GPIO.INPUT);  
  ribbonManager1 = new RibbonManager(ribbonAmount, ribbonParticleAmount, randomness, "swatch_01.jpg");     
  ribbonManager2 = new RibbonManager(ribbonAmount, ribbonParticleAmount, randomness, "swatch_02.jpg");  
  adc = new MCP3004(SPI.list()[0]);
} 


void draw()
{
  fill(0, 255);
  rect(0, 0, width, height);
  doClick();
   xPad1 = 0.5 - (adc.getAnalog(0)/2.0);
   yPad1 = adc.getAnalog(1);
   xPad2 = 0.5 + (adc.getAnalog(2)/2.0);
   yPad2 = adc.getAnalog(3);
   stroke(255,255,255);
   ellipse(xPad1*width, yPad1*height, 15, 15);
   ellipse(xPad2*width, yPad2*height, 15, 15);
  ribbonManager1.update(int(xPad2*width), int(yPad2*height));
  ribbonManager2.update(int(xPad1*width), int(yPad1*height));
}

void doClick(){
  if (GPIO.digitalRead(2) == GPIO.LOW && !rightClick) {
    print("right press ");
    rightClick = true;
     ribbonManager2.setNewColour();
  }
  if (GPIO.digitalRead(2) == GPIO.HIGH && rightClick) {
    println("release ");
    rightClick = false;
  }
  if (GPIO.digitalRead(3) == GPIO.LOW && !leftClick) {
    print("left press ");
    leftClick = true;
    ribbonManager1.setNewColour();
  }
  if (GPIO.digitalRead(3) == GPIO.HIGH && leftClick) {
    println("release ");
    leftClick = false;
  }
}

MCP3004.pde

import processing.io.SPI;

class MCP3004 extends SPI {

  MCP3004(String dev) {
    super(dev);
    super.settings(500000, SPI.MSBFIRST, SPI.MODE0);
  }

  float getAnalog(int channel) {
    if (channel < 0 || channel > 3) {
      System.err.println("The channel needs to be from 0 to 3");
      throw new IllegalArgumentException("Unexpected channel");
    }
    byte[] out = { 0, 0, 0 };
    // encode the channel number in the first byte
    out[0] = (byte)(0x18 | channel);
    byte[] in = super.transfer(out);
    int val = ((in[1] & 0x3f)<< 4 ) | ((in[2] & 0xf0) >> 4);
    // val is between 0 and 1023
    return float(val)/1023.0;
  }
}

Change_colour.pde

void setNewColour() {
    for (int i = 0; i < ribbonAmount; i++) {
      int xpos = int(random(img.width));
      int ypos = int(random(img.height));
      color newColor = img.get(xpos, ypos);
      ribbons[i].ribbonColor = newColor;
    }
  }

From The MagPi store

Subscribe

Subscribe to the newsletter

Get every issue delivered directly to your inbox and keep up to date with the latest news, offers, events, and more.