So far with my Project:65 computer, I’ve had fun making the 65c02 initialize itself and start running in circles. Of course, things will get much more interesting once its running an actual program instead of an endless stream of no-ops. In order to get to that stage, I’ve been experimenting with programming EEPROMs. I’ll probably get into the how and why of that a bit more next week. For now, I just want to take a little aside to talk about one of the challenges I ran into.
I’ve found enough information online to know (or at least to think) that I can program an EEPROM with an Arduino, instead of using a dedicated programmer. However, I quickly realized that I was going to need more I/O pins than my Arduino Uno has – quite a few more pins.
There are ways around this. A while back I did a few projects using 74hc595 shift registers, which I could control over an SPI serial interface using just a few pins. Each 595 gave me 8 more outputs, and I could string multiple chips together. The downside of using the shift registers is that they’re output only, and for my programmer I wanted to use a number of pins as both input and output (so I could write out data and read it back to validate it).
Searching for something more flexible, I came across Microchip’s MCP23017 port expander. This chip provides 16 pins that can be used as inputs or outputs. It’s hugely configurable – of course that means it’s also pretty complicated. Also, it uses I²C – a different kind of serial bus than the SPI I’ve been using, and that I didn’t have any experience with. Luckily, the Arduino’s Wire library provides I²C support, so I didn’t have to bit-bang my own solution. Interestingly, the Arduino’s I²C interface uses two of the analog input pins (A0 and A1), so it doesn’t use any of my precious digital outputs.
I²C is a bit more complicated of a protocol than SPI. With SPI, you need a select line going from the Arduino to each of the slaves. With I²C, each slave device has a 7-bit address, instead. Every request from the Arduino starts with the address of the device it’s meant for. Every one of the slave devices precedes its responses with its unique address. With the 23017, the first 4 bits of the address are hard-coded. The last 3 bits are set by a trio of input pins. Because of this, you can have up to 8 different 23017s running on the same serial bus, each controlled individually. It’s pretty cool.
The other important point about the I²C protocol is that all communications is done on a single wire, instead of the separate lines for each direction in SPI. So it’s a two-wire interface: the data line and the clock line.
I²C by itself didn’t seem too hard to understand, but I was concerned about the complexity of the 23017 interface. The chip is controlled by a set of 22 registers. Conceptually, that’s not unlike the programming I’ve been doing for C64 graphics, for example – you control the chip by reading and writing to its registers. What that looks like with the I2C protocol is that a write message (for example) contains the address of the chip (plus a read/write bit), then the address of the register, and then the data to be written.
The complexity comes from the chip’s configurability. Some of that’s useful to me – I can set each pin as an input or output, individually set up internal pullup resistors for the inputs, and so on. There are interesting features that I don’t think I’m going to need – for example, there are several different ways to make the 23017 trigger an interrupt line when the data on its inputs changes. And then there are more esoteric options like changing the polarity – the mapping of high and low voltages to zeroes and ones – which I’d just as soon do in software.
The configuration option that gave me the most pause is that there are actually two different options for how addresses are mapped to the individual registers. Yes, that means that there’s a bit in the configuration register that changes the address of the configuration register! To make matters worse, the datasheet isn’t very explicit about which option is the default. There was a parenthetical comment in a separate section indicating that bit defaulted to zero, which made sense to me, so that’s what I went with for my first try. Happily, that was the right guess.
To make sure I understood how the protocol was supposed to work, and how the messages for the 23017 were supposed to be set up, I constructed a little test circuit similar to the ones I used for testing the shift registers. I used a pair of 23017s, just to make sure I could address each one correctly. In the test circuit, each of the 23017s is controlling 8 LEDs, and one of them is monitoring a set of 5 switches. The switches are used to turn the LEDs in the first set on and off, while the second set of LEDs is always the inverse of the first set.
To give you an idea of what the Arduino Wire library code looks like, here are the instructions I used to set up the two 23017s in my circuit. Note that the 16 IO pins are divided into two ports (A and B) with their own sets of registers.
// Chip addresses are 0100 + 3 bits of unique address int addr1 = 0b00100000; int addr2 = 0b00100001; // Chip 1, Port A is going to control 8 LEDs // Set the data direction register for port A to all outputs Wire.beginTransmission (addr1); Wire.write (0x00); // IO direction register port A Wire.write (0x00); // set all as outputs Wire.endTransmission (); // Chip 1, Port B is hooked up to 5 buttons // Set the first five pins on port B to inputs Wire.beginTransmission (addr1); Wire.write (0x01); // IO direction register port B Wire.write (0x1F); // set bottom 5 as inputs Wire.endTransmission (); // Turn on pullup resistors for first five inputs of port B Wire.beginTransmission (addr1); Wire.write (0x0D); // Pullup resister register for port B Wire.write (0x1F); // Wire.endTransmission (); // Chip 2, Port A is another set of 8 LEDs // Set the data direction register for port A to all outputs Wire.beginTransmission (addr2); Wire.write (0x00); // IO direction register port A Wire.write (0x00); // set all as outputs Wire.endTransmission ();
It’s really not that complicated once you know what the ports are doing. Down in the loop part of my sketch I can turn LEDs on and off with code like this:
byte val = 0x0F; Wire.beginTransmission (addr1); Wire.write (0x12); // Port A data register Wire.write (val); Wire.endTransmission (); /* set the second chip port a as the inverse of first chip's port a */ byte invert_val = ~val; Wire.beginTransmission (addr2); Wire.write (0x12); // Port A data register Wire.write (invert_val); Wire.endTransmission ();
And reading a value is just slightly more complicated:
// The diagram in the datasheet isn't completely clear to me, but this // works for reading a value from a register. // First, write the address of the register we want to the chip: Wire.beginTransmission (addr1); Wire.write (0x13); // Port B data register Wire.endTransmission (); // Then we try to read one byte from that chip Wire.requestFrom (addr1, 1); byte new_val = Wire.read();
With a pair of 23017s hooked up to my Arduino, I’ve got 32 extra IO pins for free – which should be more than enough to get my EEPROM programmer off the ground. Come back in a week, and we’ll see if this was the easy part, or the hard part.