Spacecraft Camera Simulator

Ishkabbible (forum user) is part of a team of scientists is building a camera simulator that’s being used to validate commands that will be sent to the cameras on the OSIRIS-REx spacecraft.

The OSIRIS-REx spacecraft is traveling to Bennu, and asteroid that has a relatively high probability of hitting impacting the Earth in the 22nd century.  The mission seeks to sample and study different aspects of the asteroid.  The team is working on the OSIRIS-REx Camera Suite (OSCAMS), one of the five sets of instruments that will be used to explore Bennu.  A Teensy 3.2 replaced an underpowered PIC processor in the camera simulator and is simulating the mechanical portion of the cameras.

More information on the mission can be found here.

Server Crash Cart

Frank Adams used a Teensy to turn an old laptop into a server crash cart.

A popular tool in many server farms, a KVM (Keyboard, Video display, Mouse) or “crash cart” is a diagnostic tool you can wheel up and plug into a crashed rack mount server to figure out what went wrong.   Frank converted an old, broken laptop into a KVM by replacing the motherboard with a Teensy and a video converter card.

Frank wrote up a great instructable detailing how to build your own.

Code for the project is available on GitHub.

For additional write ups on this project, check out HackADay and Hackster.io

Electron Microscope Image Capture

Ben Krasnow of Applied Science reverse engineered an electron microscope to add image capture using a Teensy-LC.

The image capture system allows Ben to connect a computer to the electron microscope and capture images directly.

He started with analog video signal that the SEM (scanning electron microscope) is outputting and digitized it using a Teensy-LC to send the digital values over USB to the computer.  On the computer a Processing.org script displays the image in real time and optionally saves the frames.

The microscope doesn’t have a port to capture an analog video signal, so by probing around on the circuit board along with the schematic, Ben was able to find a signal that would work.

He used opamps to translate the -12V to 12V signal to a zero to 3.3V signal, and also extract the video sync pulse.  After searching around around he found that using the programmable delay block (PDB) is recommended for projects like this.  This seemed like the way to go until it was discovered that he had a Teensy-LC on the shelf which doesn’t have a PDB.  When he started coding he had a challenge on his hands to made the signal processing code work without the PDB.  He ended up using interrupts, which proved to be challenging.  Interrupts can’t be trusted for accurate timing because there could be an undetermined delay when the interrupt fires leading to variable timing.  Playing with interrupt priorities and writing the code as efficiently as he could he was able to get consistent timing for 142 kHz sampling.

It’s well worth the time to watch the full YouTube video.  Ben does a great job explaining how he accomplished this project.  Ben has a link to his source code in the video’s description.

E-Skateboard Lighting

Ben Schwartz combined his passion for building electric skateboards with some NeoPixels to make an illuminated ride.

While Ben had experience building electric skateboards, he didn’t have a lot of experience with coding for NeoPixels.  He started off using an Aurduino Uno and modified the NeoPixel tutorial by Alex Glow.  He moved to the Teensy 3.2 because the compact size made it easier to fit into his project. A 24V to 5V buck converter was used to power the Teensy and NeoPixels from the skateboard battery.

To finish up the project Ben made a 3D-printed enclosure to waterproof the electronics.

Code, schematics, and the 3-D printing plans can all be found on the Hackster.io project page.

Bicycle Helmet Turn Signals

Simon Restrepo upgraded his bicycle helmet with a nifty safety improvement, LED turn signals.

After a close call with a bus while riding his bike one night, Simon wanted to improve his visability while riding his bike.  He had a Teensy 3.2 handy and decided to add LED turn signals to his bike helmet.

The turn signals are pretty easy to put together.  The Teensy fits into a tictac box to protect it and the project is powered up by a small power bank.  The turn signals are activated by a couple of push buttons – one for left and the other for right.

This instrucables page details how to make your own.

Code for the project is published on GitHub.

Vintage TRS-80 Model 100 Computer Upgrade

Trammel Hudson upgraded a TRS-80 Model 100 computer with modern hardware.

The TRS-80 Model 100 was introduced in 1983 and was one of the first notebook-style computers.  It features an 8-bit processor and 32k of RAM.  Trammel Hudson came across one of these and while it’s motherboard had failed, the LCD and keyboard were still working.  He decided to take this non-working model and give a new brain.  The original, bulky motherboard (check out all those  through hole parts) was replaced with a Teensy ++ 2.0

There was a bit of a challenge with making the original LCD and keyboard work with the Teensy.  He first identified how the LCD drivers worked and wrote libraries to replace them.  The LCD did not have a character generator, so a font set had to be created.

Check out this project page for details on how it came together.

Pirate Ship Fire Sequencer

Many years I built electronics for Lostmachine Andy‘s fire effects on his awesome pirate ship for Burning Man.

The electronics from this old project were reused in several other propane fire art projects.

To get a better idea of Andy’s amazing Pirate Ship project, check out the video on his kickstarter page. The info about the fire effects starts at 2:58 into the video.

Here is a video where 1 of the 8 fire nozzles was tested in Andy’s driveway!

My little piece of this huge project is the circuit board, which you can see in the center of this nice metal box Andy machined.

Here are images of the circuit board.

The 8 solenoid valves connect on the top edge. Eight manual fire pushbuttons connect on the bottom edge. Those pushbuttons directly turn on the transistor for each valve. Of course, the software running on a Teensy 2.0 can control the valves too. There’s inputs for 4 buttons and 2 knobs to the software. There’s a place to plug in a 16×2 LCD, which shows info about the sequence to be used.

Circuitry-wise, the board is pretty simple. There’s a big P-channel mosfet at the input, acting as a diode to protect against reverse polarity power. Each valve draws about 1.5 amps, so at 12 amps, a regular diode didn’t seem like a good idea. In the center is a Teensy 2.0 board in sockets, and to the right is a LM7805 regulator. The 5 volt power to the 2 knobs goes through little PTC fuses. The knobs and buttons just wire into analog and digital pins, through some little R-C filters, just in case there’s any radio frequency noise pickup. Since I’m not going to Burning Man this year and can’t be there to troubleshoot, I wanted to play it safe (and it only takes a few extra cheap parts).

On the top edge are the 8 transistor circuits. There too, I decided to play things safe, perhaps a bit overly cautious? The valves are switched with IRFR5305 P-channel mosfets. Rated at 31 amps, 55 volts, they’re a bit overkill. Then again, I wanted to make sure they wouldn’t get hot, so their on resistance of 0.065 ohms is nice. At 1.5 amps, that ought to be 0.15 watts, which isn’t much at all for a package with a metal tab. A lesser transistor would have worked, but might have needed a heatsink. This PCB was made at Sunstone and just barely fit into 9 square inches. Using heatsinks would have bumped the cost up to the next bracket, which is a lot more than the cost of these nicer transistors.

Andy was concerned about the valves switching quickly. So for the back-EMF catch diode, I used a B130 schottky in series with a 12 volt, 3 watt zener. That lets the valve create -12 volts while discharging, so it ought to switch from on-to-off about as far as off-to-on. We talked a bit about possibly using a very complex approach where the valves might be driven with about 50 volts until they get up to the correct current, and then sustain at the rated 12 volts. Yes, that’s risky, but I’m pretty sure I could do it safely. Maybe after the burn this year we’ll be able to play with the valves and experiment to see if forcing the valve to open and close faster than it can with only 12 volts actually allows any interesting fire effects?

A couple extra overly cautious things I did do were resistors to slow down the gate drive into the microseconds range (still far faster than any mechanical valve can move), and just to be extra cautious, I put a tiny R-C snubber on the output, just in case the transistor somehow switches faster that the reverse recovery time of those diodes. I guess over an amp of current flowing in an almost purely inductive load makes me a bit nervous. Those parts probably aren’t necessary and the 2 diodes are probably all the protection necessary from inductive spikes… but I wanted to play it extra safe since things are so hard to fix out on the playa at Burning Man (especially at night, when the fire will be in use).

So with all this circuitry hooked up, the good news is it’s all programmable with Arduino. Here’s the sketch I delivered to Andy. It reads a 12 position rotary knob and a speed pot, and plays 1 of the 12 sequences when a “Go” button is pressed. There’s a “stop” and “fire all” button, and the 4th button is unused.

While this code is fairly long, and a bit complex in the custom delay and other stuff near the end, I tried to keep the definition of the 12 sequences very simple. Just digitalWrite and the custom delay function.

#include <LiquidCrystal.h>
#include <Bounce.h>

// input pins
const int go_button_pin      = 4;
const int stop_button_pin    = 1;
const int all_button_pin     = 2;
const int speed_knob_pin     = 20;  // analog
const int rotary_select_pin  = 21; // analog

// output pins
const int valve1_pin         = 19;
const int valve2_pin         = 18;
const int valve3_pin         = 17;
const int valve4_pin         = 16;
const int valve5_pin         = 15;
const int valve6_pin         = 14;
const int valve7_pin         = 13;
const int valve8_pin         = 12;
const int lcd_rs_pin         = 5;
const int lcd_en_pin         = 6;
const int lcd_d4_pin         = 7;
const int lcd_d5_pin         = 8;
const int lcd_d6_pin         = 9;
const int lcd_d7_pin         = 10;
const int led_pin            = 11;

const byte valvelist[8] = {valve1_pin, valve2_pin, valve3_pin, valve4_pin,
                           valve5_pin, valve6_pin, valve7_pin, valve8_pin};

// unused pins
const int unused_button_pin  = 3;
const int unused1_pin        = 0;
const int unused2_pin        = 22;
const int unused3_pin        = 23;
const int unused4_pin        = 24;

// Objects for the buttons and display
Bounce go_button(go_button_pin, 10);
Bounce stop_button(stop_button_pin, 10);
Bounce all_button(all_button_pin, 10);
LiquidCrystal lcd(lcd_rs_pin, lcd_en_pin,
  lcd_d4_pin, lcd_d5_pin, lcd_d6_pin, lcd_d7_pin);




#define Pattern_Name_1 "Sweep Left+Right";
void play1()
{
  digitalWrite(valve1_pin, HIGH);
  if (delayCust(150, 100)) return;
  digitalWrite(valve1_pin, LOW);

  digitalWrite(valve2_pin, HIGH);
  if (delayCust(150, 100)) return;
  digitalWrite(valve2_pin, LOW);
  
  digitalWrite(valve3_pin, HIGH);
  if (delayCust(150, 100)) return;
  digitalWrite(valve3_pin, LOW);
  
  digitalWrite(valve4_pin, HIGH);
  if (delayCust(150, 100)) return;
  digitalWrite(valve4_pin, LOW);
  
  digitalWrite(valve5_pin, HIGH);
  if (delayCust(150, 100)) return;
  digitalWrite(valve5_pin, LOW);
  
  digitalWrite(valve6_pin, HIGH);
  if (delayCust(150, 100)) return;
  digitalWrite(valve6_pin, LOW);
  
  digitalWrite(valve7_pin, HIGH);
  if (delayCust(150, 100)) return;
  digitalWrite(valve7_pin, LOW);
  
  digitalWrite(valve8_pin, HIGH);
  if (delayCust(150, 100)) return;
  digitalWrite(valve8_pin, LOW);
}


#define Pattern_Name_2 "Sweep Right-Left"
void play2()
{
  digitalWrite(valve8_pin, HIGH);
  if (delayCust(150, 100)) return;
  digitalWrite(valve8_pin, LOW);

  digitalWrite(valve7_pin, HIGH);
  if (delayCust(150, 100)) return;
  digitalWrite(valve7_pin, LOW);
  
  digitalWrite(valve6_pin, HIGH);
  if (delayCust(150, 100)) return;
  digitalWrite(valve6_pin, LOW);
  
  digitalWrite(valve5_pin, HIGH);
  if (delayCust(150, 100)) return;
  digitalWrite(valve5_pin, LOW);
  
  digitalWrite(valve4_pin, HIGH);
  if (delayCust(150, 100)) return;
  digitalWrite(valve4_pin, LOW);
  
  digitalWrite(valve3_pin, HIGH);
  if (delayCust(150, 100)) return;
  digitalWrite(valve3_pin, LOW);
  
  digitalWrite(valve2_pin, HIGH);
  if (delayCust(150, 100)) return;
  digitalWrite(valve2_pin, LOW);
  
  digitalWrite(valve1_pin, HIGH);
  if (delayCust(150, 100)) return;
  digitalWrite(valve1_pin, LOW);
}

#define Pattern_Name_3 "Alt Side-Side"
void play3()
{
  for (int count=0; count < 6; count++) {
    digitalWrite(valve1_pin, HIGH);
    digitalWrite(valve2_pin, HIGH);
    digitalWrite(valve3_pin, HIGH);
    digitalWrite(valve4_pin, HIGH);
    if (delayCust(90, 75)) return;
    digitalWrite(valve1_pin, LOW);
    digitalWrite(valve2_pin, LOW);
    digitalWrite(valve3_pin, LOW);
    digitalWrite(valve4_pin, LOW);
  
    digitalWrite(valve5_pin, HIGH);
    digitalWrite(valve6_pin, HIGH);
    digitalWrite(valve7_pin, HIGH);
    digitalWrite(valve8_pin, HIGH);
    if (delayCust(90, 75)) return;
    digitalWrite(valve5_pin, LOW);
    digitalWrite(valve6_pin, LOW);
    digitalWrite(valve7_pin, LOW);
    digitalWrite(valve8_pin, LOW);
  }
}

#define Pattern_Name_4 "Alt Odd-Even"
void play4()
{
  for (int count=0; count < 6; count++) {
    digitalWrite(valve1_pin, HIGH);
    digitalWrite(valve3_pin, HIGH);
    digitalWrite(valve5_pin, HIGH);
    digitalWrite(valve7_pin, HIGH);
    if (delayCust(90, 75)) return;
    digitalWrite(valve1_pin, LOW);
    digitalWrite(valve3_pin, LOW);
    digitalWrite(valve5_pin, LOW);
    digitalWrite(valve7_pin, LOW);
  
    digitalWrite(valve2_pin, HIGH);
    digitalWrite(valve4_pin, HIGH);
    digitalWrite(valve6_pin, HIGH);
    digitalWrite(valve8_pin, HIGH);
    if (delayCust(90, 75)) return;
    digitalWrite(valve2_pin, LOW);
    digitalWrite(valve4_pin, LOW);
    digitalWrite(valve6_pin, LOW);
    digitalWrite(valve8_pin, LOW);
  }
}

#define Pattern_Name_5 "Mid to Outside";
void play5()
{
  digitalWrite(valve4_pin, HIGH);
  digitalWrite(valve5_pin, HIGH);
  if (delayCust(80, 25)) return;
  
  digitalWrite(valve3_pin, HIGH);
  digitalWrite(valve6_pin, HIGH);
  if (delayCust(80, 25)) return;
  
  digitalWrite(valve2_pin, HIGH);
  digitalWrite(valve7_pin, HIGH);
  if (delayCust(80, 25)) return;
  
  digitalWrite(valve1_pin, HIGH);
  digitalWrite(valve8_pin, HIGH);
  if (delayCust(80, 25)) return;
  
  digitalWrite(valve4_pin, LOW);
  digitalWrite(valve5_pin, LOW);
  if (delayCust(80, 25)) return;
  
  digitalWrite(valve3_pin, LOW);
  digitalWrite(valve6_pin, LOW);
  if (delayCust(80, 25)) return;
  
  digitalWrite(valve2_pin, LOW);
  digitalWrite(valve7_pin, LOW);
  if (delayCust(80, 25)) return;
  
  digitalWrite(valve1_pin, LOW);
  digitalWrite(valve8_pin, LOW);
}

#define Pattern_Name_6 "(unused) Six"
void play6()
{
  digitalWrite(valve1_pin, HIGH);
  if (delayCust(25, 50)) return;
  digitalWrite(valve1_pin, LOW);
  
}

#define Pattern_Name_7 "Random, 1 valve"
void play7()
{
  for (byte i=0; i<20; i++) {
    byte myValve = valvelist[random(0, 8)];
    digitalWrite(myValve, HIGH);
    if (delayCust(50, 75)) return;
    digitalWrite(myValve, LOW);
  }
}

// this is a comment 
// You can type a description
// Or deep thoughts
#define Pattern_Name_8 "Random, 2 valves"
void play8()
{
   for (byte i=0; i<20; i++) {
    byte myValve1 = valvelist[random(0, 8)];
    byte myValve2 = myValve1;
    while (myValve2 == myValve1) {
      myValve2 = valvelist[random(0, 8)];
    }
    digitalWrite(myValve1, HIGH);
    digitalWrite(myValve2, HIGH);
    if (delayCust(50, 75)) return;
    digitalWrite(myValve1, LOW);
    digitalWrite(myValve2, LOW);
  } 
}

#define Pattern_Name_9 "Random, 2 Valves";
void play9()
{
  byte prev1 = 100;
  byte prev2 = 101;
  byte myValve1 = 102;
  byte myValve2 = 103;
  for (byte i=0; i<20; i++) { 
    while (myValve1 == prev1 || myValve1 == prev2 || myValve2 == prev1 || myValve2 == prev2) {
      myValve1 = valvelist[random(0, 8)];
      myValve2 = myValve1;
      while (myValve2 == myValve1) {
        myValve2 = valvelist[random(0, 8)];
      }
    }
    digitalWrite(myValve1, HIGH);
    digitalWrite(myValve2, HIGH);
    if (delayCust(50, 75)) return;
    digitalWrite(myValve1, LOW);
    digitalWrite(myValve2, LOW);
    prev1 = myValve1;
    prev2 = myValve2;
  } 
}

#define Pattern_Name_10 "Pi Pattern"
void play10()
{
  digitalWrite(valve3_pin, HIGH);
  if (delayCust(50, 75)) return;
  digitalWrite(valve3_pin, LOW);
  if (delayCust(50, 75)) return;
  
  digitalWrite(valve1_pin, HIGH);
  if (delayCust(50, 75)) return;
  digitalWrite(valve1_pin, LOW);
  if (delayCust(50, 75)) return;
  
  digitalWrite(valve4_pin, HIGH);
  if (delayCust(50, 75)) return;
  digitalWrite(valve4_pin, LOW);
  if (delayCust(50, 75)) return;
  
  digitalWrite(valve1_pin, HIGH);
  if (delayCust(50, 75)) return;
  digitalWrite(valve1_pin, LOW);
  if (delayCust(50, 75)) return;
  
  digitalWrite(valve5_pin, HIGH);
  if (delayCust(50, 75)) return;
  digitalWrite(valve5_pin, LOW);
  if (delayCust(50, 75)) return;
  
  digitalWrite(valve8_pin, HIGH);
  if (delayCust(50, 75)) return;
  digitalWrite(valve8_pin, LOW);
  
}

#define Pattern_Name_11 "(unused) Eleven"
void play11()
{
  
}

#define Pattern_Name_12 "(unused) Twelve"
void play12()
{
  
}



// initialize hardware - only done once at bootup
void setup() {
  pinMode(go_button_pin, INPUT_PULLUP);
  pinMode(stop_button_pin, INPUT_PULLUP);
  pinMode(all_button_pin, INPUT_PULLUP);
  pinMode(unused_button_pin, INPUT_PULLUP);
  pinMode(unused1_pin, INPUT_PULLUP);
  pinMode(unused2_pin, INPUT_PULLUP);
  pinMode(unused3_pin, INPUT_PULLUP);
  pinMode(unused4_pin, INPUT_PULLUP);
  pinMode(valve1_pin, OUTPUT);
  pinMode(valve2_pin, OUTPUT);
  pinMode(valve3_pin, OUTPUT);
  pinMode(valve4_pin, OUTPUT);
  pinMode(valve5_pin, OUTPUT);
  pinMode(valve6_pin, OUTPUT);
  pinMode(valve7_pin, OUTPUT);
  pinMode(valve8_pin, OUTPUT);
  lcd.begin(16, 2);
}

int prev_num = -100;
int prev_knob_reading = -100;


void loop() {
  // first, update the status of the buttons
  go_button.update();
  stop_button.update();
  all_button.update();
  
  // the "ALL" button is highest priority
  if (all_button.read() == LOW) {
    lcd.clear();
    lcd.print(" All Valves ON");
    digitalWrite(valve1_pin, HIGH);
    digitalWrite(valve2_pin, HIGH);
    digitalWrite(valve3_pin, HIGH);
    digitalWrite(valve4_pin, HIGH);
    digitalWrite(valve5_pin, HIGH);
    digitalWrite(valve6_pin, HIGH);
    digitalWrite(valve7_pin, HIGH);
    digitalWrite(valve8_pin, HIGH);
    while (all_button.read() == LOW) {
      all_button.update();
      // do nothing, just wait for button release
    }
    digitalWrite(valve1_pin, LOW);
    digitalWrite(valve2_pin, LOW);
    digitalWrite(valve3_pin, LOW);
    digitalWrite(valve4_pin, LOW);
    digitalWrite(valve5_pin, LOW);
    digitalWrite(valve6_pin, LOW);
    digitalWrite(valve7_pin, LOW);
    digitalWrite(valve8_pin, LOW);
    lcd.clear();
    prev_num = -100;
    prev_knob_reading = -100;
  }
  
  // when not doing anything, read the knobs
  // and show status info on the LCD
  int rotary_reading = analogRead(rotary_select_pin);
  
  int num;
  const char *name;
  if (rotary_reading < 47) {
    num = 1;
    name = Pattern_Name_1;
  } else if (rotary_reading < 140) {
    num = 2;
    name = Pattern_Name_2;
  } else if (rotary_reading < 233) {
    num = 3;
    name = Pattern_Name_3;
  } else if (rotary_reading < 326) {
    num = 4;
    name = Pattern_Name_4;
  } else if (rotary_reading < 419) {
    num = 5;
    name = Pattern_Name_5;    
  } else if (rotary_reading < 512) {
    num = 6;
    name = Pattern_Name_6;      
  } else if (rotary_reading < 605) {
    num = 7;
    name = Pattern_Name_7;        
  } else if (rotary_reading < 698) {
    num = 8;
    name = Pattern_Name_8;          
  } else if (rotary_reading < 791) {
    num = 9;
    name = Pattern_Name_9;            
  } else if (rotary_reading < 884) {
    num = 10;
    name = Pattern_Name_10;              
  } else if (rotary_reading < 977) {
    num = 11;
    name = Pattern_Name_11;
  } else {
    num = 12;
    name = Pattern_Name_12;
  }
  if (num != prev_num) {
    lcd.setCursor(0, 0);
    lcd.print(name); 
    for (int i=strlen(name); i < 16; i++) {
      lcd.print(' ');
    }
    prev_num = num;
  }
  
  get_speed();
  lcd.setCursor(9, 1);
  lcd.print("Ready");
  
  if (go_button.fallingEdge()) {
    lcd.setCursor(9, 1);
    lcd.print("Running");
    switch (num) {
      case 1: play1(); break;
      case 2: play2(); break;
      case 3: play3(); break;
      case 4: play4(); break;
      case 5: play5(); break;
      case 6: play6(); break;
      case 7: play7(); break;
      case 8: play8(); break;
      case 9: play9(); break;
      case 10: play10(); break;
      case 11: play11(); break;
      case 12: play12(); break;
      default: play1();
    }
    // after playing, always make sure all valves are off
    digitalWrite(valve1_pin, LOW);
    digitalWrite(valve2_pin, LOW);
    digitalWrite(valve3_pin, LOW);
    digitalWrite(valve4_pin, LOW);
    digitalWrite(valve5_pin, LOW);
    digitalWrite(valve6_pin, LOW);
    digitalWrite(valve7_pin, LOW);
    digitalWrite(valve8_pin, LOW);
    // and reset the state, so the screen fully redraws
    lcd.clear();
    prev_num = -100;
    prev_knob_reading = -100;
  }
  
  delay(10); 
}


// Read the speed knob.  This is a bit tricky, because we don't want to
// rapidly alternate between two speed settings if the knob is exactly at
// the mid-point and a tiny bit of noise changes the reading slightly.
// So instead, this code remember the previous setting and implements a
// tiny dead band (hysteresis) to so the user always sees a smooth
// appearance on the LCD.
//
byte get_speed(void)
{
  static byte knob_speed = 1;
  int knob_reading = analogRead(speed_knob_pin);
  if (knob_reading < prev_knob_reading - 2 || knob_reading > prev_knob_reading + 2) {
     lcd.setCursor(0, 1);   
     lcd.print("Speed:");
     knob_speed = (knob_reading / 94) + 1;  // range is 1 to 11
     lcd.print((int)knob_speed);  
     if (knob_speed < 10) lcd.print(" ");
     prev_knob_reading = knob_reading;
  }
  return knob_speed;
}


// Delay based on speed setting.  "mult" is a multiplier for
// the speed, and "fixed" is a fixed delay that is always
// added regardless of the speed setting.  This fancy code
// allows the delay setting to change, and also aborts if the
// STOP or ALL buttons are pressed.
//
boolean delayCust(int mult, int fixed)
{
  unsigned long us = micros();
  unsigned long elapsed_ms = 0;
  unsigned long target_ms;
  
  while (1) {
    target_ms = (11 - get_speed()) * mult + fixed; // moving target
    if (elapsed_ms >= target_ms) return false;  // delay completed
    if (micros() - us >= 1000) {
      elapsed_ms = elapsed_ms + 1;
      us = us + 1000;
    }
    if (stop_button.update()) return true; // delay interrupted
    if (all_button.update()) return true;
  }
}

The last bit of technical info to share is the PCB gerber files, which were sent to Sunstone.  Here are the PCB gerber files.

If you use these files, please be aware 1 small error was discovered. The power for the LCD was mistakenly connected to +12 volts. If you use a LCD, you must cut that trace and solder a wire to the +5 volt output of the LM7805. Other that that 1 error, the board works great.

 

This article was originally published on the DorkbotPDX website, on August 23, 2011.  In late 2018, DorkbotPDX removed its blog section.  An archive of the original article is still available on the Internet Archive.  I am republishing this article here, so the info and files for this project can be found and used by anyone wanting to build it.

Radio Music DIY Eurorack Module

Tom Whitwell of Music Thing Modular created Radio Music, a DIY virtual radio module.

Radio Music is not a radio.  It is a eurorack module sample player that behaves like a radio and is designed to be a source of unexpected audio.

This module works on a series of banks and stations, just like a radio.  It uses a Teensy 3.2 to play files from an SD card to simulate a voltage controlled AM/FM/Shortwave/Time Travel radio.

This module is extremely versatile and can be turned into a different module simply by changing the firmware.  The Chord Organ firmware that synthesizes chords.

Radio Music is open hardware with extensive documentation available on GitHub.

To learn more about Radio Music, check out this great 5-part series from Voltage Control Lab that covers topics such as preparing samples and alternative firmware.

You can purchase a Radio Music DIY kit from Thonk.  It includes all the parts needed to build your own.  The kit features maker-friendly though hole parts.  Here’s a time lapse video of the assembly

 

Subaru Car Hacking

P1kachu has been hacking his 1997 Subaru Impreza STi.

The ’97 Impreza uses an engine control unit (ECU) and provides a diagnostic connector for external communication.  P1ckachu built a diagnostic interface device, got a dump of the ECU’s firmware, and reverse engineered the binary to figure out how to disable the speed limiter.  The custom interface uses a Teensy 3.2 and logic level converter to convert the Teensy’s 3.3v to the car’s 5v.

P1kachu has a great write up the project on this page.

Code for the project is published on GitHub.

 

 

Handstand Keyboard

alpage built a large keyboard to be played while doing a handstand.

When his handstand coach asked if anyone could build a piano that could be played with his feet while upside down, alpage stepped up to the task.  Using a Teensy 3.5 and wave table synthesis in the Audio Library, the piano came together.  The piano plays chords and has 4 different voices.

You can watch the keyboard being played with feet while in a handstand in this Facebook video.