To learn about I2C I have implemented a serial EEPROM programmer using the same AVR based ATMega8 as my old school paralled programmer. I2C, like SPI, is nice because all signalling between the microcontroller and the memory is along only a handful of wires. This also, at I2C speeds at least, makes it slower but adequate for many applications like data logging.
I2C, as a fairly modern tech, is extremely well documented with many good tutorials available on the subject. I got a lot of information from this nice tutorial which roughly describes I2C and how I2C is implemented in the AVR microcontrollers. The serial EEPROM I had to hand, an AT24C256 (PDF), was my target I2C device. This is a 32KByte serial memory in an 8 pin PDIP package. Quite a bit smaller then the parallel version of the same capacity my 6809 uses!
Though the bus is radically different, many things about this serial EEPROM are the same as its parallel sibling, including having a 64byte page size and the rough time taken to write a page (5ms as opposed to 10ms).
Below is the circuit for the programmer. As you can see, it is very much simpler then the parallel programmer:
It is also fits easily on a single slab of breadboard:
From left to right –
- Reset button
- ISP programming header
- USB serial interface (which also powers the circuit)
Because these EEPROMs are so similar to the parallel version, the programmer code can be similar. The same commands and mechanisms, like upblock, dumphex etc as divised for the parallel programmer can be used with the serial memory. Only the implementation of the functions which read and write bytes and pages to the memory need be different.
For this reason, I decided to indulge in a little refactoring: the programmer software is now split in two, with a “front end”, which handles the UART text interface to the computer, and a “back end” which does the actual memory reads and writes. The cool part is that because the serial interface to the computer is the same in each case the same front end code can be used for both programmer types. It also means the same upload program can be used; it neither knows or cares what type of memory is used.
This is made easier because the page size, which is the unit of memory written to at a time, is the same in both instances: 64bytes. But there is one slight “regression” compared to previously: it isn’t possible to disable page writes in the upblock command. This is because doing single byte writes is pointless except for testing.
The code is now in four files:
- main.c : the shared front end
- parallel.c : parallel backend for the AT28C256 and similar memories
- i2c.c : I2C backend for AT24LC256 and equivalents
- programmer.h : shared header containing an “API” that the memory backends have to implement, as well as services, like UART output main.c provides
Two AVR EEPROM images (.hex files) are now built by linking main.o with either i2c.o or parallel.o.
An interesting aspect of this change relates to the address counter. The parallel programmer circuit contains two 8 bit counters which generate the address bus signals, but this isn’t needed for the I2C programmer. Indeed, it would be possible to program the memory “randomly” to any memory address. But to keep everything consistent with the parallel programmer, and not introduce any new commands, the counter is retained purely as a software construct. The reading and writing memory address is incremented and reset just as if there was a real counter present, but its value is maintained by the code instead of in a piece of hardware.
One final change was needed to the programmer. Because the I2C programming code is a bit more complex then the parallel equivalent, the ATMega8 flash was filled. The ‘Mega8, as its name implies, has 8KByte of program space and this was initially not enough.
One of the reasons the code does not fit is that it makes use of some standard C functions including strtok, strcmp and snprintf. While its not really possible to write a simpler, and therefore smaller, implementation of a function like strcmp, it is possible to write a simpler printf-type function, and thus save enough room to make everything fit.
The standard printf can format all simple types including integers, floats, character arrays, and format them in various ways including printing integers in binary, hex, octal etc. They can also be padded, printed to various precisions, printed as signed or unsigned etc. This makes a full printf implementation somewhat large considering the limited space available in a typical microcontroller.
The programmer firmware only really needs to output hex values, and the odd decimal. And even outputting decimal is only done once, for the getcount function. So that can go, leaving just hex output for byte and double byte (word) values.
Thus the “data formatting” requirements for the programmer are much like the 6809 asm code written for the monitor, and indeed writing that code helped me write the same code in C. Instead of a fancy formatting function which would write into a character buffer, I have implemented two functions alongside wtitechar: writehexbyte and writehexword. Both use shifting and hex to ASCII arithmetic to form a character, then sendchar is used to output the hex on the UART. dumphex and a few other commands have been changed to form up the output using these new functions. The code is a little less readable, but this is the price you pay for a “lower level” implementation.
The parallel programmer has been retested and still works nicely, as does the brand new I2C programmer.
As usual, the code is on github.
Now that’s working, the next thing to do is integrate an I2C interface in my 6808 computer. There are a couple of ways to do this, but the one I will try is to use a Philips PCF8584 (PDF), an 8 bit parallel interface to I2C. This chip, designed to be used with Z80, 68000 and other microprocessors, gives a system an I2C bus. Hopefully it will also work with a 6809. I can then attempt to repeat the EEPROM reading and writing, but this time with the 6809 computer. Once this has been accomplished, which isn’t very useful in and of itself, I can look at interfacing more useful peripherals, like a real time clock…