FTDI USB chip + daisy-chained '595 shift register. Put a separate transistor on each relay.
More detail:
A chain of 74hc595 is described here: http://www.arduino.cc/en/Tutorial/ShiftOut (Only two are shown, but the concept can be expanded to any number of '595s.)
To drive a the chain, three signals are needed, data, latch, and clock.
You can use an FTDI part in "bitbang" mode to generate three signals from computer control. To use the FTDI in bitbang mode, use libftdi (You could also use FTDI's official drivers, but libftdi is less hassle in my experience). A FT232R has enough pins to do this. FTDI sells a DIP breakout, and Sparkfun sells some breakouts too.
Notice you will need 128 2N2222s and 128 330 ohm resistors. (you can get resistor arrays that might be easier to manage.)
I've drawn the circuit assuming a 12V supply for your relays and no less than 24 ohm coils. If this is not the case, you might need a sturdier transistor or logic-level MOSFETs. The 2222 is about as cheap a transistor as you will find, and when you're buying 128 pieces that makes a difference.
I didn't show bypass caps or the exact 232R hookup. Read the datasheet.
OK, I just saw the solenoid you are trying to control. 2N2222 won't work. You need to switch 120VAC to the solenoid. So you can either have a small relay (with 2N2222) to switch the 120V, or use a solid-state relay that can take logic inputs and connect it directly to the '595 output.
OK, here's code to drive this using libftdi. Pin assigments in the source code. apt-get install libftdi-dev, then compile like this: "gcc test_595.c -lftdi"
/* This program is distributed under the GPL, version 2 */
#include <stdio.h>
#include <ftdi.h>
#define HC595_CT (1) // number of '595 chips
int main(int argc, char **argv)
{
struct ftdi_context ftdic;
int f,i;
unsigned char buf[2*8*HC595_CT+1]; // latch pulse, 8*HC595_CT clock pulses
if ((f=ftdi_init(&ftdic)) < 0)
{
fprintf(stderr, "ftdi_init failed\n");
return f;
}
f = ftdi_usb_open(&ftdic, 0x0403, 0x6001);
if (f < 0 && f != -5)
{
fprintf(stderr, "unable to open ftdi device: %d (%s)\n", f, ftdi_get_error_string(&ftdic));
exit(-1);
}
printf("ftdi open succeeded: %d\n",f);
// FTDI cable assignments:
#define BIT_DATA (1<<0) // 1: orange. TXD, "data"
// 2: yellow. RXD, unused
#define BIT_CLOCK (1<<2) // 4: green. RTS, "clock"
#define BIT_LATCH (1<<3) // 8: brown. CTS, "latch"
ftdi_enable_bitbang(&ftdic, BIT_DATA | BIT_CLOCK | BIT_LATCH);
// set zero
*buf=0;
f = ftdi_write_data(&ftdic, buf, 1);
unsigned char *b=buf;
unsigned char data;
unsigned char state;
if (argc == 2) {
data=atof(argv[1]);
} else {
data=0x5a;
}
printf("sending data %d\n",data);
for (i=0; i<8; i++) {
state=(data & (128L>>i))?BIT_DATA:0;
*b++=state;
state |= BIT_CLOCK;
*b++=state;
}
*b++=BIT_LATCH;
f = ftdi_write_data(&ftdic, buf, (b-buf));
ftdi_disable_bitbang(&ftdic);
ftdi_usb_close(&ftdic);
ftdi_deinit(&ftdic);
}
And here's a picture of my test setup:
(I used the TTL-232R-3V3 cable.)
The sensor isn't perfect, if you aim even a really, really good sensor (Better than the, um, 'classic' Sharp IR sensors) at the same spot on the wall and take a few readings, there will be some variation. If you discard some number of least significant bits, it's possible to get the same reading every time, but your readings will be more granular. You should probably keep the maximum precision, and then try to fix errors in software (i.e., change your data so that an almost straight line really is straight).
What sensor are you using, and what range of measurement do you expect? The output of the sensor is very much nonlinear. This graph (from the datasheet) compares the output voltage on a linear scale with the distance:
You'll notice that it looks a lot like the graph of y=1/x, i.e volts=k(1/distance). This can be used to get a decent first approximation, but division on an Arduino is expensive. You'll have better luck calibrating the sensor and storing the voltage/distance pairs in a look-up table (in program memory, of course). This one comes calibrated from the factory such that a measurement at precisely 24cm measures to 24cm +/- 3cm. Unfortunately, they don't give you a way of knowing what the voltage at 24cm should be except by squinting at this graph.
The sensor will have higher precision at certain ranges. Imagine that there is a random variation of, say, +/- 250mV in your readings (Gaussian if you like, but random is easier). It's hopefully a lot smaller than that, but it makes it easy to visualize. The variation means that your readings with this sensor at, say, 50cm or 70cm will vary over 20 or 30cm, but measurements at 15cm should be within a few cm. This sensor is rated for 10-80cm, but you'll get more accurate readings if you only trust it for 10-25cm or so. If you're controlling the robot, you should be able to move to these distances and get the best readings.
The sensor draws current in large bursts and is probably mounted on a fairly long cable. A capacitor will help stabilize the readings. Don't jam it into the JST; solder it to the PCB on the back. It's drawing a lot of current and it's pretty slow, so small capacitances traditionally used for decoupling (0.1uF) probably won't work. I'd use a 10uF 1206 or 1210 ceramic SMD cap in parallel with a 100uF electrolytic. If you find that high frequency noise is still present, add a 0.1uF 1206 on top of the 10uF. Here's a picture of the locations for soldering (original image from Sparkfun):
You could also try adding a small capacitance on the Vo line to smooth the output. You should experiment to see whether or not that helps. I'd keep it below 100pF, starting at around 10pF. This will create a moving average of the readings in hardware. More circuitry (a series resistor) would enhance the effect, post a comment or another question if you want to build a more complex low-pass filter system for this purpose. Note: The Sharp sensor may not handle driving into a large capacitance well, and might balk or break if asked to change the voltage quickly. It's also possible that it's a single-sided output, and might require a resistor to ground to discharge the capacitor if you add more capacitance than is currently present in the parasitics.
Twisting the cables together will help reduce noise coupled on from external sources like 60Hz mains. Keeping them short will also help mitigate noise.
Best Answer
The potential exists to melt some parts on the interior. I would wash the rod of the solenoid in acetone to remove any residue, and check for visible charring within. If worse comes to worse, try some powdered graphite or white lithium grease to lubricate it.