Wednesday, March 5, 2014

driving a phone keypad matrix

I got given this old phone to take apart:

and decided to make it work as a numpad.

I had to solder the wires from the ribbon cable onto headers so that I could plug it into my breadboard.. that's really difficult to do - I think you're supposed to have a PCB between the plastic of the headers and the metal you're working with. I ended up soldering 11/12 pins successfully, luckily I didn't need the last one.

on the very first pin the head and tension managed to pull the pin up a bit, this happened even worse on the final pin.

For the first circuit I made a wrong assumption about the internals, plugged it in and tried it and it didn't work:



So I traced out the schematic of the keypad and the way it function is that when you press key on row r and column c it completes a circuit between pin Rr and Cc.


I spent a while trying to figure out how to drive this circuit without having to pull the header out of the breadboard:

I came up with the plan that I could scan through the columns by firing an output on them one by one, and check if that came though any of the row pins as inputs... but thinking back to the http://www.ruggedcircuits.com/10-ways-to-destroy-an-arduino I was aware of the danger of shorting a high output to a low output (if you held down two keys on the same row but different columns).

There are actually two solutions to this, I think. In the end I did both even though - I think - only one is necessary. First of all (and this works without pulling the thing out the breadboard) you can just set your pins to be inputs pins when they're not outputting HIGH, that means you'll never have a LOW output short to a HIGH output! N.B. when the teensy starts up its pins are all configured to be inputs, since that is safer. You can set the ones you're not using to be low outputs for reduced energy consumption. The other solution is that since LOW outputs can sink a few milliamps, just use resistors to limit the current.

In fact I went with that and coded it up and tried it and.... without pressing anything it entered "1" and then did absolutely nothing. I had to put debugging info in and it turned out all my inputs were coming in as 1's all the time. So I added pull-down resistors and it all worked nicely! I actually reasoned that I did not need pull-down resistors earlier, but I was wrong and don't understand why.0

Here's the schematic I ended up with


a photo of the working build!



and the code which is based on PJRC usb_keyboard_debug example:

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include "usb_keyboard_debug.h"

#define LED_ON        (PORTD |= (1<<6))
#define LED_OFF        (PORTD &= ~(1<<6))
#define LED_CONFIG    (DDRD |= (1<<6))

#define CPU_PRESCALE(n)    (CLKPR = 0x80, CLKPR = (n))

#define DIT 10
#define PRESS(r,c) (pressed_tmp = 1, (pressed?0:(pressed = 1, usb_keyboard_press(number_keys[c+4*r], 0))))

uint8_t number_keys[]=
  {KEY_1,KEY_2,KEY_3,KEY_MINUS,
   KEY_4,KEY_5,KEY_6,KEYPAD_PLUS,
   KEY_7,KEY_8,KEY_9,KEY_TILDE,
   KEYPAD_ASTERIX,KEY_0,KEY_SLASH,KEY_EQUAL};

int main(void) {
  uint8_t d;
  int pressed;
  int pressed_tmp;
 
  // set for 16 MHz clock, and make sure the LED is off
  CPU_PRESCALE(0);
  LED_CONFIG;
  LED_OFF;
 
  // we have the following pinout
  //
  // C7, C6, C5, C4, C3, C2, C1, C0
  // [ 10k outputs ] [ inputs     ]
  // 10k changd to 470
 
  // See the "Using I/O Pins" page for details.
  // http://www.pjrc.com/teensy/pins.html
  DDRC = 0; // make everything an input to start with
 
  // Initialize the USB, and then wait for the host to set configuration.
  // If the Teensy is powered without a PC connected to the USB port,
  // this will wait forever.
  usb_init();
  while (!usb_configured()) /* wait */ ;
 
  // Wait an extra second for the PC's operating system to load drivers
  // and do whatever it does to actually be ready for input
  _delay_ms(1000);
 
  pressed = 0;
  while(1) {
    pressed_tmp = 0;
  
    // col1
    DDRC |= 1<<4; // make this an output
    PORTC |= 1<<4; // enable it
    _delay_ms(DIT);
    d = PINC;
    usb_debug_putchar((d&(1<<0))?'#':'_');
    usb_debug_putchar((d&(1<<1))?'#':'_');
    usb_debug_putchar((d&(1<<2))?'#':'_');
    usb_debug_putchar((d&(1<<3))?'#':'_');
    usb_debug_putchar('\n');
    if(d&(1<<0)) { PRESS(0,0); } // row1
    if(d&(1<<1)) { PRESS(0,1); } // row2
    if(d&(1<<2)) { PRESS(0,2); } // row3
    if(d&(1<<3)) { PRESS(0,3); } // row4
    PORTD &= ~(1<<4); // disable it
    DDRC &= ~(1<<4); // make it an input again
  
    // col2
    DDRC |= 1<<5; // make this an output
    PORTC |= 1<<5; // enable it
    _delay_ms(DIT);
    d = PINC;
    usb_debug_putchar((d&(1<<0))?'#':'_');
    usb_debug_putchar((d&(1<<1))?'#':'_');
    usb_debug_putchar((d&(1<<2))?'#':'_');
    usb_debug_putchar((d&(1<<3))?'#':'_');
    usb_debug_putchar('\n');
    if(d&(1<<0)) { PRESS(1,0); } // row1
    if(d&(1<<1)) { PRESS(1,1); } // row2
    if(d&(1<<2)) { PRESS(1,2); } // row3
    if(d&(1<<3)) { PRESS(1,3); } // row4
    PORTD &= ~(1<<5); // disable it
    DDRC &= ~(1<<5); // make it an input again
  
    // col3
    DDRC |= 1<<6; // make this an output
    PORTC |= 1<<6; // enable it
    _delay_ms(DIT);
    d = PINC;
    usb_debug_putchar((d&(1<<0))?'#':'_');
    usb_debug_putchar((d&(1<<1))?'#':'_');
    usb_debug_putchar((d&(1<<2))?'#':'_');
    usb_debug_putchar((d&(1<<3))?'#':'_');
    usb_debug_putchar('\n');
    if(d&(1<<0)) { PRESS(2,0); } // row1
    if(d&(1<<1)) { PRESS(2,1); } // row2
    if(d&(1<<2)) { PRESS(2,2); } // row3
    if(d&(1<<3)) { PRESS(2,3); } // row4
    PORTD &= ~(1<<6); // disable it
    DDRC &= ~(1<<6); // make it an input again
  
    // col4
    DDRC |= 1<<7; // make this an output
    PORTC |= 1<<7; // enable it
    _delay_ms(DIT);
    d = PINC;
    usb_debug_putchar((d&(1<<0))?'#':'_');
    usb_debug_putchar((d&(1<<1))?'#':'_');
    usb_debug_putchar((d&(1<<2))?'#':'_');
    usb_debug_putchar((d&(1<<3))?'#':'_');
    usb_debug_putchar('\n');
    if(d&(1<<0)) { PRESS(3,0); } // row1
    if(d&(1<<1)) { PRESS(3,1); } // row2
    if(d&(1<<2)) { PRESS(3,2); } // row3
    if(d&(1<<3)) { PRESS(3,3); } // row4
    PORTD &= ~(1<<7); // disable it
    DDRC &= ~(1<<7); // make it an input again
  
    usb_debug_putchar('\n');
    if(!pressed_tmp) pressed = 0; // nothing was pressed this whole scan
    // so reset and allow a new keypress
  }
}

Tuesday, March 4, 2014

Flip Flop [standalone electronics]


Here's what I just built, it's called a flip-flop and it lets you select which LED is on by pressing the button. The schematic is like this:

here it is diagrammed in falstad (with two resistors missing by accident),

The circuit itself has to store a bit of memory (for which LED is on) somewhere, and is it the transistors that store this because a transistor is either saturated (when current to the base allows it to conduct from collector to emitter) or not.

The mechanism is that the circuit has two stable states (well if you run the circuit in a perfect simulation it has a third semi-stable state which you can't get in real life, a bit like balancing a coin on its side):


These states are stable because the large current loop flows much easier through 100 ohms + a transistor than 100 ohms + 1k ohms to try to saturate the other transistor.

In state a, when you press the switch s1 it shorts out the current that was trying to saturate the transistor, so that transistor can no longer be active and the large current is redirected through the 1k resistor to saturate the other transistor putting it into state b.

Saturday, March 1, 2014

Using a scalextrics controller with teensy through ADC

I wanted to try using a scalextrics controller as an input device to my computer via the teensy, the controller itself is just a variable resistor - I tried to measure its resistance (against the jack) using my multimeter but I got no reading which was frustrating.. I opened it up and it definitely is a variable resistor.

So I opened up the part of the scalextrics track which you plug these devices into and it contained this nice PCB:

(I took a green and white wire and soldered them onto the bottom two pins for my own use).

The way this works is that you have a transformer from the mains which goes from 240V to 15V, and goes into PLG1 - then if you trace out the schematic you can see that this is exactly a four diode full wave rectifier:

So this converts the sine-wave from AC mains into a purely positive "bump" wave that drives the cars forwards - since they have a bit of inertia you don't need any capacitors to smooth it out into a DC signal. Of course the speed is limited by the controllers.

I tried measuring the resistance again but this time through the wires on my modified microtrack PCB and I got readings from between 20 and 200 ohms as I squeezed it!

I needed to design a circuit to connect this to the teensy, so I first tried to limit the current (so as not to damage my nice micro) using a 470ohm resistor then into the variable resistor and to the ADC pin but I thought I should also have a plug into the ground for some reason?

I thought about the voltages at the various nodes (like in Kirchoffs laws) and realized actually this would not work at all - I think it wouldn't get any different readings. So I changed my design to this:

Now you can see the voltage will change nicely as the variable resistor changes. in fact this is a very basic voltage divider circuit!

I modified my earlier usb_serial code using the nice example code for ADC from pjrc to get a program that streamed the scalextric reading to my computer through serial:

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>

#include "usb_serial.h"
#include "analog.h"

#define CPU_PRESCALE(n) (CLKPR = 0x80, CLKPR = (n))

// Send a string to the USB serial port.  The string must be in
// flash memory, using PSTR
//
void send_str(const char *s)
{
    char c;
    while (1) {
        c = pgm_read_byte(s++);
        if (!c) break;
        usb_serial_putchar(c);
    }
}

int main(void) {
  int16_t a;
  int i;
 
  CPU_PRESCALE(0);
 
  // USB initialization routine taken from example.c
  usb_init();
  while (!usb_configured());
  _delay_ms(1000);
  // wait for the user to run their terminal emulator program
  // which sets DTR to indicate it is ready to receive.
  while (!(usb_serial_get_control() & USB_SERIAL_DTR)) /* wait */ ;
  // discard anything that was received prior.  Sometimes the
        // operating system or other software will send a modem
        // "AT command", which can still be buffered.
 
  usb_serial_putchar('s');
  usb_serial_putchar('c');
  usb_serial_putchar('a');
  usb_serial_putchar('l');
  usb_serial_putchar('e');
  usb_serial_putchar('x');
  usb_serial_putchar('t');
  usb_serial_putchar('r');
  usb_serial_putchar('i');
  usb_serial_putchar('c');
  usb_serial_putchar('\r');
  usb_serial_putchar('\n');
  while(1) {
    a = adc_read(0);
    usb_serial_putchar(hex[((a>>12)&0xF)]);
    usb_serial_putchar(hex[((a>>8)&0xF)]);
    usb_serial_putchar(hex[((a>>4)&0xF)]);
    usb_serial_putchar(hex[((a>>0)&0xF)]);
   
    usb_serial_putchar('\n');
   
    _delay_ms(70);
  }
}

Once I had calibrated the values I made it print lines of #'s so that I could draw waves by squeezing it:

It's nice to have it all working but the readings go a bit weird when you squeeze it fully, I don't think it's precise enough to use as an input device for games on the computer (for example), at least not without some clever post processing and averaging.

ADB working!

Since ADB had already been implemented and worked on very hard by other people I decided to use my connector and  just try out blarggs code. When I first tried it was sort of working but it limited my typing speed horribly.. so I tried the other. It had the same problem but when I started it up it printed 8888888888 and the 7 and 8 keys were broken, so I properly ripped them as you can see:

Then the ADB code works perfectly! I typed for the rest of the day on my ancient apple keyboard. I still have the other two keycaps so it might be possible to buy new keyswitches and repair it (?).

I have a wacom tablet which would be nice to use but it's so small, I'm not sure if I'll bother implementing drivers for it.