Monday, July 25, 2016

Jump-activated rainbow beanie (retrospective)

This was a project I did about a year ago after attending a workshop on arduino wearables. The project was run at the MAAS, which does some great events for kids and young adults, but doesn't often run events for adults. So I was happy when I heard they were doing an adults course, and came along. I'd seen some super cool wearable projects, and the mix of electronics and textiles/soft surfaces seemed cool. During the workshop everyone was given an arduino Gemma, some neopixels, a bunch of little sensors and some conductive thread and encouraged to see what we could make. I ended up connecting four neopixels up to the Gemma with a little vibration switch and sewed them all into a beanie: voila! a jump activated rainbow beanie.


The neopixels take power, ground and are driven by one digital output pin that transmits individually addressed RGB data to each and every neopixel attached along a chain. The vibration switch is triggered when shaken or given a good jolt, and one of the Gemma pins was used to read its state. The code on the Gemma would then just trigger a preprogrammed rainbow sequence along the neopixels.


All of connections were made using conductive thread sewed into the rim of the beanie and powered from a small 500 mAh lipo battery. Because the beanie was quite stretchy, I had to sew the conductive thread lines in a sort of zig-zag fashion: this meant they would sort of flex with the beanie when someone stretched it over their head (instead of just snap). Definitely the most time consuming part of the project was sewing the conductive thread circuit!

Here's the final code for my project sketch:

/* rainbow_jump_hat.ino - arduino sketch using four neopixels 
displaying a rainbow pattern triggered by a vibration switch
*/

#include 

#define LED_PIN 0
#define VIBSWITCH_PIN 1

Adafruit_NeoPixel strip = 
    Adafruit_NeoPixel(4, LED_PIN, NEO_GRB + NEO_KHZ800);

int VibSwitchState = 0;
int event = 0;
int eventcount = 0;

void setPixelHue(int pixel, int hue)
{
  while(hue < 0)
  {
    hue += 360;
  }
  
  float h = hue % 360;
    
  float sectorPos = h / 60;
  int sectorNumber = (int)floor(sectorPos);
  float fractionalSector = sectorPos - sectorNumber;
        
  float q = 1 - fractionalSector;
  float t = fractionalSector;
        
  switch(sectorNumber)
  {
    case 0:
      strip.setPixelColor(pixel, 255, 255 * t, 0);
      break;
    case 1:
      strip.setPixelColor(pixel, 255 * q, 255, 0);
      break;
    case 2:
      strip.setPixelColor(pixel, 0, 255, 255 * t);
      break;
    case 3:
      strip.setPixelColor(pixel, 0, 255 * q, 255);
      break;
    case 4:
      strip.setPixelColor(pixel, 255 * t, 0, 255);
      break;
    case 5:
      strip.setPixelColor(pixel, 255, 0, 255 * q);
      break;
  }
}

void DoRainbow() {
  for(int i = 0; i < 360; i++) {
    strip.setBrightness(255);
    setPixelHue(0, i);
    setPixelHue(1, i + 90);
    setPixelHue(2, i + 180);
    setPixelHue(3, i + 270);
    strip.show();
    delay(10);
  }
}

void setup()
{
  strip.begin();
  strip.setBrightness(255);
  strip.show();
  pinMode(VIBSWITCH_PIN, INPUT);
}

void loop()
{
  // Look for motion
  VibSwitchState = digitalRead(VIBSWITCH_PIN);
  if (VibSwitchState == HIGH) {
    event = 1;
  }
  else {
    event = 0;
  }
  
  if (event == 1) {
    DoRainbow();
    DoRainbow();
    event = 0;
  }
  else {
    for(int i = 0; i < 4; i++) {
      strip.setBrightness(0);
    }
    strip.show();
    delay(10);
  }
}

Friday, July 15, 2016

Homemade Handheld Videogame Console: Part 4: Games


Once I had the hardware finished up, I was ready to finalise the on-board software and load up a few games. I already had three ex-pyweek games ready to go: all I needed was some sort of main menu that would come up when the console was switched on and would allow the player to navigate options and choose games using the dpad and buttons. I wrote a quick and dirty main menu interface using pygame that allowed the player to choose between the three games (I will probably come back and upgrade this to something more visually impressive somewhere down the track). I added a line to the end of /etc/rc.local to run this on start-up.

To get the menu interface to quit itself and start up a selected game, I used "os.execv", a nice function that does just this: shuts down the current instance of python and finally runs another command on the shell with specified arguments. I set it up so when a game was selected, the code would run:

pygame.quit()
os.execv('/usr/bin/python', ['foo', resources.gamedata[select_ind].path])

I used the same mechanism to get games to shift control back to the main menu when the player quit them. This was convenient, because it meant I had to make very little change to the existing game code.

All games were working nicely. I also adapted an existing game that used the mouse to work with the touchscreen input. Here's the console in action:


Current Games:

"The Wizard's Data": pyweek 20 entry by Team Chimera
"Adrift" pyweek 21 entry by Team Chimera
"Underworld": a beta version of a game I've worked with on and off for about a year :)

Overall I was super happy with the outcome for this project. There are however a few things that I learnt and would do differently for next time:

Buttons: I didn't really like the buttons in the end, they make an annoying loud clicky sound and don't depress far enough. To their credit they look nice and are responsive. They just don't feel as good as a proper games controller: next time I do this sort of thing I will definitely invest in some proper arcade buttons, or at least something that has a built-in spring or rubbery inside to produce a smooth, but still responsive feel.

Audio Volume: I don't know why, but the audio volume is super quiet. Not sure if I botched something or not. I'm pretty sure I've got the trimpot ramped up correctly ... will try to figure this out somewhere down the track.

Sunday, July 10, 2016

Homemade Handheld Videogame Console: Part 3: Building


When my case was fresh off the printer, I started lining up all the components to see that everything would fit as I'd planned. I think I had convinced myself that I would have gotten at least one measurement wrong somewhere and that this first print would be just a test run. I was happy to discover that I had actually not stuffed up anything too critical and that I could get it all to work. The thing that didn't really work as well as I had hoped was the standoffs for the smaller components (amp and boost converter): I had designed a cylinder to fit through the small mounting holes on these PCBs with a diameter of 1.6mm, but when they came out of the printer, these parts of the structure were just a stringy mess: I guess they were just a bit below the resolution the printer was capable of. The standoff cylinders for the pitft screen and RPi mounting holes were 2.5mm diameter, and these came out fine, but they were pretty fragile. In the end, I decided to break them off, drill 2 mm holes in their place and screw the pitft, RPi and larger holes on the boost converter onto the standoffs: lesson learnt for next time!

Once I was happy that everything would fit, I started assembling the buttons, RPi and screen into the case. I cut two square(ish) sections from perf board to hold the buttons (main buttons and dpad) on either side of the screen, and drilled 2 mm holes into these so I could screw them down to standoffs on the case. Buttons were then soldered on before screwing the perf board onto the case.

At this point I realised I'd miscalculated the heights of the standoffs for the dpad side: it was short by about 2 mm such that the buttons would be pressed down permanently upon screwing it in. I didn't have any washers that were small enough, so, because I'm super impatient, I improvised with snipped off bits of wall plugs :) ... Total hack, but hey, it did the job. A little bit of bluetack on the back of each button stopped them from have that slightly annoying rattle.

I then screwed in the tft screen to the case; the RPi would then slot on top of the 40 pin header; conveniently the tft also provides an additional breakout of this same header, which I connected a 40 pin ribbon cable connector to, chopped off at one end so I could feed out the GPIOs/ground to the buttons and 5V/ground in from the boost converter. I soldered up all those connections, plugged the pi on top and powered it up to check all the buttons were working well.

Next I wired up the amp to the speaker, and positioned these in the case. The standoff pins in the case for the amp hadn't come out properly in the 3d print, but it was ok with no further modification, found the board could sort of float in the case with no issue. Next step was to screw in the boost convertor to the case using two of the four mount holes. Soldered the power out pins to the RPi (via header cables) and across to the amp.

Placed the battery into the case, held in place with a tiny bit of gaffer. I fit the slide switch into its hole in the case and soldered it on to the boost convertor. Last step was to solder the signal pins from the amp to the solder points on the RPi PCB that sat adjacent to the 3.5 mm audio jack. Closed the case up, switched it on and everything was working a-ok!


Homemade Handheld Videogame Console: Part 2: Setting up the RPi

After I finalised my case design, I shifted attention towards getting the Raspberry Pi configured to run games in the way I wanted. I basically wanted to be able to run existing python/pygame games on the device, making sure that the screen, audio and input from buttons via GPIO would all be working smoothly.

I started with the 27-05-2016 build of Raspbian, standard config for expanding the boot partition and configuring for a 104 key keyboard. I followed this guide for installing the kernel modules for the PiTFT 2.8 support. I wanted to use pygame and have support for the touchscreen, so I followed this guide to ensure the SDL version underneath pygame would support the screen (apparently needs to be SDL 1.2, there are reports of issues with SDL 2.0). I wrote a simple pygame app to test the touchscreen and test pre-installed gpio-connected buttons that came with the screen. I added a line at the top of the script before pygame.init() to ensure the SDL output was set to the framebuffer:

os.putenv('SDL_FBDEV', '/dev/fb1')

The touchscreen was working great, and very conveniently linked straight into the mouse drivers, such that touches come up as mouse click events in pygame. I added these lines in to get this happening:

os.putenv('SDL_MOUSEDRV', 'TSLIB')
os.putenv('SDL_MOUSEDEV', '/dev/input/touchscreen')

I used the python RPi.GPIO package, that came pre-insalled, to try and read button presses and at this point discovered the performance was fairly underwhelming: I was using event detection, and tried a combination of logic rules to read whether the pin was high or low, to detect short presses and long button holds, also tried playing with the bouncetime argument: all to no avail. I ended up realising that Adafruit had actually written a really nice c-based daemon, "Adafruit-Retrogame" which would map gpio events to key strokes: exactly what I wanted! And it worked perfectly: configured it to the keys I wanted, compiled it and added it to /etc/rc.local to run in the background on boot up.

Wireless: I setup my RPi to connect automatically to my wireless access point using this guide. I really wanted to get wireless working, as I knew from the case shell I wanted, it would be hard to feed one of the USB ports to the side for a keyboard or usb drive etc. (the other alternative for debugging would be a Bluetooth keyboard, but I still also wanted to be able to copy games on/off the device). I found the wireless would drop out constantly: turned off the pre-installed wireless power management: single line "sudo iwconfig wlan0 power off" added to /etc/rc.local to turn off every time on boot: wireless then working perfectly.

The last thing I did was to test the performance of one of my previous pyweek entries to see how it would go on the pi. The first thing I noticed was considerable audio lag for sounds (music was ok, but sounds would play at least 300-400 ms after being triggered) ... Found out I could fix this by reducing the buffer size using the following pre initialisation line in all python/pygame code:

pygame.mixer.pre_init(44100,-16,1,512)

More details on this here. The game was set to run at 30 fps, but I was only getting about 22-23 fps and the graphics were pretty choppy. I tweaked the PiTFT frame rate and SPI frequency by editing the settings in /boot/config.txt editing the relevant line to:

dtoverlay=pitft28r,rotate=90,speed=42000000,fps=30

After a bit of experimentation, these settings seemed the best balance for a not too choppy graphics and enough CPU left over to run the game smoothly.

Now that the Pi was setup, it was time to start putting all the hardware together!