Giacomo's Blog Ambitious, but rubbish.

22Sep/120

Stupid simple interrupt-driver IR remote decoder for ATtiny/ATmega

Quick one. For a project, I needed a really simple IR decoder program for an ATtiny85 that (a) Used interrupts for pulse processing, but (b) didn't use a timer interrupt, and (c) worked with an apple remote I had laying around. None of the libraries I found did this (mostly b, plus none really had the 85 in mind), so I whipped up my own. It's stupid. It's simple. It works well enough for me. Just whack your IR decoder's output to the 85's physical pin 3, hook a 9600,8,n,1 uart input to physical pin 2, press buttons on your remote, and marvel at the numbers and stuff appearing on your screen. Enough blab, here's the goods. This should work fine with pretty much any AVR, as long as you fix up the interrupt configuration in setup() for your particular chip.

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

/* SuperSimpleIR -
 * Bare-minimum no-frills code to interpret input from an IR decoder, using pin change interrupts.
 * Does no fancy decoding - it's up to you to figure out which button corresponds with the
 * output from this snippet. The provided message codes work with an Apple remote, but this is by
 * no means limited to such remotes (for instance, my Yamaha receiver's remote works fine with this).
 * Possible improvements:
 * - Use a timer interrupt instead of timeToCommitMessage. I didn't bother
 *   implementing this as the project I developed this for will need the timer for other tasks.
 * - The message commit code is (barely) vulnerable to concurrency issues if a pulse comes in while
 *   we try to commit a message. In practice I don't think this matters.
 * - It gets confused easily if the signal gets weak and corrupted.
 */

// From the Tiny core.
#include <TinyDebugSerial.h>

TinyDebugSerial mySerial;

// Pin defs.
#define IN_IR 4 // If this is changed, change the code that enables interrupts.

#define IR_MSG_PLUS               2011287570
#define IR_MSG_MINUS              2011279378
#define IR_MSG_REW                2011238418
#define IR_MSG_FFWD               2011291666
#define IR_MSG_PLAYPAUSE          2011242514
#define IR_MSG_MENU               2011250706
#define IR_MSG_REPEAT_LAST_BUTTON 0

// After this much time has elapsed after the last transition, consider the
// message complete.
#define MSG_COMMIT_TIME_MICROSEC 30000

enum IRProtocolState { Idle, Building };

unsigned long timeOfLastIRPinChange;  // Time when the last pin change happened on the IR decoder.
unsigned long timeToCommitMessage;    // Time after which we should consider the message complete.
unsigned long message;                // Workspace where the message is built up.
IRProtocolState irProtocolState;      // What our protocol decoder is doing.

void processIRMessage(long message);

ISR(PCINT0_vect) {
  boolean isPinHigh = digitalRead(IN_IR);
  long currentTime = micros();
  long microsSinceLastChange = (currentTime - timeOfLastIRPinChange);
  timeOfLastIRPinChange = currentTime;
  
  // Bump up the commit time, since we got a level change.
  timeToCommitMessage = MSG_COMMIT_TIME_MICROSEC + timeOfLastIRPinChange;
  
  if (!isPinHigh)
  {
     // Going low.
    if (microsSinceLastChange < 700)
    {
      // Space
      message = message << 1;
    }
    else if (microsSinceLastChange < 2000)
    {
      // Mark.
      message = message << 1;
      message |= 1;
    }
    else if (microsSinceLastChange < 20000)
    {
      // First LOW.
      message = 0;
      irProtocolState = Building;
    }
  }
  
}

void setup()
{ 
  mySerial.begin(9600);
  mySerial.println("IR test up.");

  timeOfLastIRPinChange = micros();
  timeToCommitMessage = -1;
  irProtocolState = Idle;
  
  GIMSK = _BV(PCIE);    // Enable pin change interrupt
  PCMSK = _BV(PCINT4);  // Enable the interrupt for only pin 4.
}

void loop() {
  // Check to see if we can commit a message. If so, process it.
  if (micros() >= timeToCommitMessage && irProtocolState == Building)
  {
    long localMessage = message;
    message = 0;
    irProtocolState = Idle;
    processIRMessage(localMessage);
  }
}

void processIRMessage(long message)
{
  switch (message)
    {
      case IR_MSG_PLUS: mySerial.println("Plus"); break;
      case IR_MSG_MINUS: mySerial.println("Minus"); break;
      case IR_MSG_REW: mySerial.println("REW"); break;
      case IR_MSG_FFWD: mySerial.println("FFWD"); break;
      case IR_MSG_PLAYPAUSE: mySerial.println("Play/Pause"); break;
      case IR_MSG_MENU: mySerial.println("Menu"); break;
      case IR_MSG_REPEAT_LAST_BUTTON: mySerial.println("(repeat last)"); break;
      default: mySerial.print("Unknown code: "); mySerial.println(message); break;
    }
}

25Aug/120

Classic Mac -> PS2 mouse adapter

So I made a little adapter so I can connect PS/2 mice to the pre-ADB Classic Macs (128k, 512k, 512ke, probably Lisa). They use a really simple scheme: two pairs of quadrature inputs (one for X, one for Y), and one input for the single button. 5v is provided for the mouse. Perfect for a little microcontroller like the ATtiny to interface to (which is funny, because the ATtiny is capable of a far faster clock speed than the Macs...). Anyways here is the adapter, whipped up across a couple evenings:

There isn't much to it, just an ATtiny84 and a couple connectors. The software is actually compatible with most AVRs, so you can use whatever AVR you have handy if you're willing to rework the PCB (they just need enough pins: 7 at bare minimum if you get rid of the status LED). You'll notice a USB plug in there. This thing does not speak USB, only mouse PS2. Thing is, most USB mice can act as PS2 devices (those little PS2->USB adapters are just wires). So instead of fiddling with adapters, I opted to directly use a USB plug. Beware though that some really modern mice don't speak PS2. If your mouse is worth less than $5, you're set ;) . Here's a view from the other side:

Youtube goodness:

I've made all the code/schematics/PCB layouts available as open-source hardware (TAPR Open Hardware License, www.tapr.org/OHL), so grab your copy here:
Mac2PS2.zip

Note! If you'd be interested in buying a kit or fully assembled product from me, drop me a line (YouTube comment would be best). If there's at least a little bit of demand, I'll set something up. It'll be cheap; I don't plan on making money from this :)

29Jun/120

WS2801 LED Driver breakout

Just a quickie, companion to my bedroom lighting project video here: http://youtu.be/QDfqHmQUCMA

Here is the breakout board I made for this project (with the addition of a spot for a larger bypass cap, which I found necessary).

WS2801 Breakout 3d view

Download: WS2801_Breakout

Creative Commons License
WS2801 Breakount by Giacomo Ferrari is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
Based on a work at giferrari.net.

17Jun/117

Fixing Altec “Show n Go” Retractible license plate holder

So my Altec Show'n'Go motorized plate holder broke. Brought it inside and searched a while for pictures of the underside of the device so I could see what was wrong. I found this thread. Look at the second pic in that post - see the pulley at the top of the device (covered by that square bit of metal)? Mine fell off. So I dug out a junk pulley and attached it using a screw instead of a pressed-in fastener. This means there's now a screw and a nut sticking out of the device, but that doesn't matter in my application because I mounted the holder so it juts out a bit from the bumper. I used a zip-tie to replace the square bit of metal I mentioned (presumably it's a guard to prevent the cable from falling off the pulley). Works well - the stiffness of the tie keeps it raised at just about the height of the pulley's groove. Adjust the cable tension using the nuts and enjoy. Hope this helps someone - I sure missed being 007 for a while :) .

B0rked.

Pulley goes where?

Underside.

Top side. Bits barely clear the lip of my bumper.

Color coded routing of cables (click for full size).

11Jun/111

Flash Arduino sketches on the move via Zipit Z2

One thing I've found annoying with Arduinos is the relative lack of options when flashing sketches. A PC is pretty much required. Flashing in the field requires lugging around a laptop. I came up with a solution using the Zipit Z2 instant messenger client. It's a tiny device similar in form and size to one of those newer foldable GameBoy Advance handlelds. It's an interesting platform because of ability (Can run Debian Linux, has built-in WiFi), cost ($30), and battery life (around 5 hours). Unfortunately, it has no external serial port. Fortunately, it's easy to add. Just see this blog post by Geordy Rostad: Adding a Serial Port to the Zipit Z2. I did exactly what he did, except I omitted the level converter, connecting the serial lines directly to the new connector (such a converter would be superfluous when interfacing with an Arduino). Now, my Arduinos are generally at 5V, but the Zipit works with 3.3V. This isn't a problem with data going from the Z2 to the Arduino - 3.3V is plenty to drive the inputs of a 5V device. The other way around requires a voltage divider so we don't harm the Z2. Here's a quick schematic of my converter built into the cable:

Z2->Arduino cable

As for the OS I used on the Zipit: z2sid. It's my distro of choice because it's Debian (meaning apt-get works!) and gcc works. To get avrdude (the flashing program), I just did apt-get install avrdude. I whipped up this little script so I don't have to type out the rather long avrdude commands:

avrdude -v -patmega328p -cstk500v1 -P/dev/ttyS2 -b57600 -D -Uflash:w:$1:i

Save that as flash.sh, chmod it +x, and use (./flash.sh my-sketch.hex). You might want to change the -patmega328p to whatever is appropriate for you. Note that the Z2 doesn't have a DTR line, meaning auto-reset won't work. You'll have to reset the Arduino manually, then run avrdude. Timing takes a bit of practice.

The product.