This is a project I've had simmering on the back burner for a while. Still at the early stages but thought it might be fun to keep track of each step here
A few months back I got a 20kps laser scanner galvo set off ebay with the intention of making my own laser projector and a vision of using it to play some old vector arcade games... particularly my old fave Atari Battlezone. The arcade game bit seemed pretty easy, since you can play BZ on the open source MAME emulator so I thought I could hook into the vector terminal emulation.
I found the asynchronous UART on an Arduino board was not quite fast enough to cope with the data... dropping bits all over the place, so I started looking at a USB conneciton to a PIC2455. As a SourceBoost C user I was not able to find any easy to understand USB CDC (Communication Device Class, a.k.a serial port) implementations for the PIC - so I decided to make my own, leaning heavily on sample code I found online.
Well I finally got to the point where my PIC would connect via USB show up as a COM port and be easy to access from a Windows program. Then I hooked up an 12-bit SPI dual DAC and connected it to the galvo setup and tried the first random hacking into MAMEs vector module.
I didn't expect it to work first time, and didn't! but my impatient hacking did produce some interesting squiggles at about 2 fps. I needed to use a long exposure photograph to actually make sense of it, but eventually I recognised a couple of parts of the display and got quite excited that the concept was proved!
The coordinate handling is obviously messed up and the image is wrapping on itself multiple times, also there is no attempt at blanking yet - so there are stray lines all over. The big job will be to find some way to optimise the render list to stop throwing the galvos all over the place and improve on the 2 fps refresh!
As you can see I have a long way to go!
Here is the plot showing the bits I recognised
Here is an actual MAME screen showing what it should look like
Following some requests, I have listed pre-programmed PICs on ebay for a couple of my projects. If there is much interest (and its not all a massive hassle) I might also look into getting some PCBs made up and putting kits together
For now here are the PICs http://cgi.ebay.co.uk/ws/eBayISAPI.dll?ViewItem&item=150426687494
The Atari POKEY was the classic soundchip in the Atari 8-bit home computers and many 1980's arcade games. This clip shows some of my experiments in driving a POKEY from MIDI. A PIC receives MIDI data and two 74HC595 shift registers are used to assemble the 12 lines of bus data for the POKEY so it can be driven from a humble 14 pin PIC16F688. A 6N139 isolator is placed between MIDI in from PC and the PIC's serial input. The POKEY is clocked at 2MHz from the PIC's internal clock output.
I am using REAPER to sequence some MIDI files I found on the internet. Credit goes out to the authors of these MIDI files.. also to YouTube member little-scale, whose clips inspired me to poke about with the POKEY in the first place, and Bryan Edewaard, whose crib sheet I could not have done this without.
Here is the schematic for the circuit as built on breadboard (I am working on neater, stripboard based version)
And the source code for SOURCEBOOST C on the PIC16F688
#include <system.h>
#include <memory.h>
// PIC CONFIG (_INTRC_OSC_CLKOUT is needed so we output clock
// clock signal on pin 3)
#pragma DATA _CONFIG, _MCLRE_OFF & _WDT_OFF & _INTRC_OSC_CLKOUT
#pragma CLOCK_FREQ 8000000
typedef unsigned char byte;
// define the pins
#define P_DATA portc.0
#define P_SHCK portc.2
#define P_STCK portc.1
#define P_POKEY portc.3
// define "pure" tone sound mode. Other settings
// of bits 4-7 will add varying levels of distortion
One empty baked bean tin, some lego and a stack of little magnets... stick magnets on the tin and slide them about to 'program' the sequencer, then grab hold of the 'transport control' and crank away.... The breadboard contains 5 hall-effect switches and a PIC16F688 to generate MIDI note on/off information. This is piped to Reason in the first half of the clip and to a Dave Smith Mopho synth in the second half.
I reckon with a baked bean tin about 16ft in diameter and about 25,000 magnets you could dump your sequencer software.. and you'd be getting some good aerobic exercise to boot :o)
Here is the schematic (if you make one, note that hall effect switches need the magnet to be the right way round.. if it does not trigger, flip the magnet over)
And the code (SourceBoost C... NOTE: you'll need programmer hardware like PICKit2 to burn the program to the PIC chip)
// HALL SENSOR TO MIDI NOTES
// J.Hotchkiss Mar2010
#include <system.h>
#include <memory.h>
// PIC CONFIG
#pragma DATA _CONFIG, _MCLRE_OFF&_WDT_OFF&_INTRC_OSC_NOCLKOUT
Somewhere between the Omnichord and the Stylophone lies this thing... simple but suprisingly effective... a PIC16F688 microcontroller, 2 shift registers IC's, 36 switches and a bunch of wire. The buttons select major/minor/maj7/min7/7/dim/aug chords based on any root note, and you "strum" across 3-4 octaves of notes from the chord by touching bits of exposed wire with a "stylus". The output is all MIDI (circuit makes no sound by itself) and Reason is being used here for sounds.
Note - If you are new to PIC stuff and want to make your own version of this project, remember you will need some way to program the PIC chip (its like a tiny computer and it comes without any software installed). The code is included below, but you'll need to compile it (using the free SourceBoost compiler) and "burn" it to the PIC... you can buy a programmer (e.g. PICkit2) or maybe borrow one. If there is enough demand I might be able to provide pre-programmed PIC16F688's for this, or my other PIC projects. Drop me a message if you'd be interested.
Schematic
The business end...
The mess on the back...
How it works (if you are interested)...
It's the tried and trusted principle of the keyboard matrix - the 74HC595 IC's are "shift registers" which are simply used to scan a single "on" bit across 16 lines, one at a time (all 16 are used for the stylus, the first 12 are used for the columns of the kepad). The program running on the PIC chip reads the voltage coming back from each row of the keypad and also from the stylus. Since the program knows which one of the 16 shift register outputs it has switched "on" at any moment in time it then knows which buttons are pressed / which "strings" the stylus is touching at any moment in time by which input lines (if any) it reads the voltage back on. The rest is down to the program code to convert this info into MIDI notes and send them to a synth. One other important things are the 10k "pull down" resistors on each of the 3 keyboard rows and the stylus line... they make sure that an unconnected line settles at 0V rather than reading spurious random values.
The source code
// STRUM CHORD CONTROLLER
// (c) 2010 J.Hotchkiss
// SOURCEBOOST C FOR PIC16F688
#include <system.h>
#include <memory.h>
// PIC CONFIG
#pragma DATA _CONFIG, _MCLRE_OFF&_WDT_OFF&_INTRC_OSC_NOCLKOUT
A few months ago I used an Arduino clone board to send MIDI messages out of a Stylophone. I always intended to take it to the next level and get another Stylophone (preferably a broken one) and rip out the guts to fit all the electronics inside, and also add a few buttons and pots for perfomance controllers.
Well, I finally got round to it. This time I am using a PIC16F688 microcontroller.. this little monkey only has 14 pins and costs a mere £1 yet it has a built in clock, serial port and ADC, which means its pretty much the *only* component needed in this project (with the exception of a couple of resistors and switches).
I added a pitchbend pot, a modwheel and a pot to control the note velocity. And pushbuttons to shift octaves and "hold" a MIDI note (basically force the code to forget to send note-off message so the last note rings on after lifting the stylus). This allows a kind of polyphonic drone out of the usually strictly monophonic stylophone.
I will include the code below. I wont bother with a schematic, but the wiring to the PIC16F688 is as follows
pin 1 - 5 volt supply 2 - octave UP momentary switch (other side of switch connected to ground) 3 - octave DOWN momentary switch (other side of switch connected to ground) 6 - to pin 5 of MIDI out socket via a 220R resistor. Pin 4 of the socket is pulled up to 5V via another 220R resitor 7 - wiper of PITCHBEND pot (100k). Pot terminal between from ground/+5V 8 - wiper of VELOCITY pot (100k). Pot terminal between from ground/+5V 9 - to the stylus. Also pulled up to +5v via 470k resistor 10 - activity LED via 1k resistor 11 - wiper of MOD WHEEL pot (100k). Pot terminal between from ground/+5V 13 - HOLD NOTE momentary switch (other side of switch connected to ground) 14 - to ground
If you want to run it from a PP3 you'll need a 5V voltage regulator. You also need to connect the stylophone keyboard/resistor ladder between 0V and 5V and you will need to set up the scale[] array based on the ADC values you get from each pad on *your* stylophone keyboard (which are almost certainly different to mine)
Another PIC project... this one using a 14-pin 16F688 and playing a version of "Breakout". I might see if I can get a convincing version of "Space Invaders" to run on that 8x8 matrix too....
Usual setup of 74HC595 shift registers (x3) and ULN2803 NPN transistor arrays (x2). Columns are driven directly thru 100R resistors from one of the 595's... rows alternate red LEDs/green LEDs and are driven via 2 x chained 595's (data out from one goes to data in on the other) which in turn drive the NPN arrays, so only 5 I/O's from the MCU are needed to drive the display..
- Data in for the 595 driving the columns - Shift clock for the 595 driving the columns - Data in for the first of chained 595's driving the rows - Shift clock for the pair of 595's driving the columns - Store clock line for all 3 x 595's
There are 4 buttons: 3 are connected to PIC I/O for controlling game (only 2 used for Breakout game) and other is MCU reset (grounds MCLR#). 12k pull up resistors on all 4 lines.
The piezo buzzer is connected to the remaining I/O via a 0.1uF capacitor.
The LED matrix was from Sure Electronics (on eBay).. I got 10 of them for about £10. It is red/green but by driving both you get orange.
Here is the PIC code
#include <system.h> #include <memory.h>
// Config bits #pragma DATA _CONFIG, _WDT_OFF & _INTRC_OSC_NOCLKOUT
////////////////////////////////////////////////////// // // refresh // // update the LED matrix based on content of the // disp[] array // void refresh() { int i;
// clear vertical shift register and load a logic 1 at // bit position 0. This bit will be shifted along to // drive each row of the LED matrix in turn for(i=0;i<16;++i) { P_SH_ROW = 0; P_DT_ROW = (i==15)?1:0; P_SH_ROW = 1; } P_DT_ROW = 0;
// for each row of data (8 x red, 8 x green) for(i=0;i<16;++i) { // this cross reference of vertical bit position to row of the // disp[] array is used since the matrix is connected for wiring // convenience and the order of rows is different byte ix[16] = { 15, 7, 14, 6, 13, 5, 12, 4, 0, 8, 1, 9, 2, 10, 3, 11 };
// look up the row data byte byte d=disp[ix[i]];
// store clock low P_STORE = 0;
// load the 8 bits of data for(int j=0;j<8;++j) { // shift a column bit P_SH_COL = 0; P_DT_COL = d&1; P_SH_COL = 1; d>>=1; }
// store clock high.. row data is clocked to // the output of shift registers, simultaneously // with the clocking in of a new scan row in the // vertical shift registers P_STORE = 1;
// set pins low again and add a "display delay" // while the row data is shown, before it is // hidden again P_SH_ROW = 0; P_DT_COL = 0; delay_ms(1); P_SH_ROW = 1; P_STORE = 0; } P_SH_COL = 0; P_SH_ROW = 0; }
// loop for each level for(;;) { // setup the wall memset(bricks,0,sizeof(bricks)); memset(bricks,255,rowsOfBricks);
// init variables char x=3; // position of bat char bx=4; // position of ball char by=6; char dx=0; // direction of ball char dy=-1;
// ball movement counter. Set to a value to // give a short delay at the start of a level byte bc = 100;
// counter used to debounce the movement buttons byte buttonDebounce = 0;
// loop until level is complete for(;;) { // do we need to move the ball? if(++bc == 0) { // reset the counter bc = speed;
// calc next ball position char nx = bx + dx; char ny = by + dy; if(nx<0||nx>7) // off screen left or right { dx=-dx; nx=bx; } if(ny<0||ny>7) // off screen top or bottom { dy=-dy; ny=by; } if(ny==7) // on the bottom row? { if(bx==x) // flat hit left side { if(dx>0) dx=0; else dx=-1; beep(200,50); } else if(bx==x+1) // hit right side { if(dx<0) dx=0; else dx=1; beep(200,50); } else if(nx==x) // hit left end { dx=-1; beep(100,50); } else if(nx==x+1) // hit right end { dx=1; beep(100,50); } else { // ball has dropped off bottom of screen for(i=0;i<3;++i) { // death routine refresh(); beep(50,100); delay_ms(100); refresh(); beep(150,100); delay_ms(100); }
// lose a life if(lives-- <= 0) { // all lives gone for(;;) { for(i=0;i<50;++i) refresh(); delay_ms(500); } } else { // start of next round x=3; bx=4; by=6; dx=0; bc=100; } }
// common stuff nx=bx; ny=by; dy=-1; }
// move the ball bx = nx; by = ny;
// hit a brick? if(bricks[by]&(1<<(7-bx))) { // remove the brick and bounce bricks[by]&=~(1<<(7-bx)); dy=-dy; beep(100,200);
I love the tiny 8 pin PICs from Microchip.. an entire computer in a package the size of a fingernail that costs pennies and can be programmed from your PC using just C and run on a watch battery. They're great.. but its taken me a while to find a use for one.
This was a quick and silly project to play a tune on a piezo sounder. Hopefully the comments in the source code included below are enough to work out whats going on.. this was actually a great project to work out how to use timers and interrupts on PICs, which I'd not done before. There were a few little hoops to jump through to fit the melody data into the tiny EEPROM space (128 bytes) of the 12F629.
I used SourceBoost C and PICkit 2 USB programmer.
#include <system.h>
// config word; internal oscillator, watchdog and master clear are off #pragma DATA _CONFIG, _INTRC_OSC_NOCLKOUT & _WDT_OFF & _MCLRE_OFF
//Set clock frequency #pragma CLOCK_FREQ 4000000
// This is the tune data... high nybble of each byte is the relative duration (1-16) and the // low nybble is the note (1-16) or a break (0). Note numbers are mapped to frequencies in // code. A melody can use only 15 different notes in total and the total number of bytes that // can be stored in EEPROM on a PIC12F629 is 128. There is a null terminator at the end of the // melody data #pragma DATA _EEPROM, 0x44, 0x20, 0x44, 0x10, 0x18, 0x16, 0x10, 0x14, 0x16, 0x10, 0x18, 0x1b, 0x10, 0x18, 0x16, 0x10, 0x14, 0x41, 0x20, 0x41, 0x10, 0x15, 0x13, 0x10, 0x11, 0x13, 0x10, 0x15, 0x17, 0x10, 0x15, 0x13, 0x10, 0x11, 0x44, 0x20, 0x44, 0x10, 0x18, 0x16, 0x10, 0x14, 0x16, 0x10, 0x18, 0x1b, 0x10, 0x18, 0x16, 0x10, 0x24, 0x10, 0x1b, 0x1a, 0x10, 0x19, 0x18, 0x10, 0x17, 0x16, 0x10, 0x15, 0x14, 0x20, 0x16, 0x20, 0x14, 0x10, 0x14, 0x16, 0x10, 0x17, 0x48, 0x20, 0x48, 0x10, 0x18, 0x19, 0x10, 0x1a, 0x1b, 0x10, 0x1a, 0x19, 0x10, 0x18, 0x17, 0x10, 0x16, 0x15, 0x10, 0x16, 0x17, 0x10, 0x16, 0x15, 0x10, 0x11, 0x12, 0x10, 0x13, 0x44, 0x20, 0x44, 0x20, 0x44, 0x20, 0x44, 0x20, 0x24, 0x1b, 0x1a, 0x10, 0x19, 0x18, 0x10, 0x17, 0x16, 0x10, 0x15, 0x34, 0x36, 0x44, 0x40, 0x00
typedef unsigned char byte;
// info used by the interrupt handler byte next_tmr1h = 0; byte next_tmr1l = 0; byte wave = 0;
// interrupt handler called when the timer1 overflows void interrupt( void ) { // check if this is timer1 overflow event if( pir1 & (1 << TMR1IF) ) { // set up the timer 1 counters so they will overflow // again after the appropriate time delay tmr1h = next_tmr1h; tmr1l = next_tmr1l;
// toggle pin state GPIO5, which drives the piezo sounder wave=!wave; gpio = wave? 0b100000 : 0b000000;
// loop through the tune for(;;) { // read byte from EEPROM eeadr = addr; eecon1.0 = 1; data = eedata; ++addr;
// a zero byte indicates end of the tune if(!data || addr > 0x7f) break;
// extract the note number from the low nybble byte note = data & 0x0f;
// play a note? if(note) { // lookup corresponding frequency long freq = 0; switch(note) { case 1: freq=196; break; // G case 2: freq=220; break; // A case 3: freq=247; break; // B case 4: freq=262; break; // C case 5: freq=294; break; // D case 6: freq=330; break; // E case 7: freq=349; break; // F case 8: freq=392; break; // G case 9: freq=440; break; // A case 10: freq=494; break; // B case 11: freq=524; break; // C }
// convert this into the correct timer count values // Internal clock is 4MHz and Timer1 counts at 1/4 // of this frequency (1MHz). We need to call the // interrupt handler at double the pitch frequency so // that we can generate the 2 phases of the square wave // pulse. What we calculate here is the initial 16 bit // Timer1 value that will overflow (at 0xffff) after the // appropriate period of time. long l = (0xffff - (500000L/freq)); next_tmr1h = l >> 8; next_tmr1l = l&0xff; tmr1h = next_tmr1h; tmr1l = next_tmr1l; wave = 0;
// enable the timer (start sound) t1con.0=1; } else { // disable the timer (stop sound) t1con.0=0; }
// duration is in top 4 bits. We'll just // us an empty "for" loop to provide a delay int dur = 70*(data >> 4); for(int p=0;p<dur;++p) { // empty for loop for delay for(byte q=1;q;++q); }