Memory-mapped driver for I2C:Another way to access I2C with TINI is through a memory-mapped driver. The circuit in the figure below shows the necessary hardware for this. The circuit uses a 74ACT138 address decoder to select the logic chips when the CPU is reading or writing I2C information. This is connected in the exact same manner as the address decoder we used in Chapter 8 for connecting other memory-mapped devices. The 74ACT244 input buffer is only enabled on data reads and the 74ACT753 latch is wired so it is enabled on data writes. These two chips are connected so that we have a bidirectional data flow between the data bus and the I2C bus and so that the input buffer reads the data on the outputs of the latch. This is necessary so we can make this a bi-directional bus and we can read data from I2C slave devices.
Figure 11-7: Memory-mapped I2C driver
TINI and I2C: Software
The TINI API contains a java class, I2CPort, which can be used to generate the proper I2C signals. The I2CPort class has an overloaded constructor which provides for two ways of constructing I2CPort objects. These correspond to the two ways of generating I2C signals with TINI: directly using P5.0 and P5.1 or through memory- mapped I/O. For direct use of P5.0 and P5.1 the I2Cport constructor takes no arguments. For memory-mapped I2C drivers, the constructor requires that you pass the address of the address decoder and a mask that tells the API which data line is to drive the SCL line and which data line is to drive the SDA line. Below is a list of the methods that we will use to talk I2C on TINI.
• public void setAddress(byte address)
• public void setClockDelay(byte delay)
• public int write(byte dataArray, int offset, int length)
• public int read(byte dataArray, int offset, int length)
The setAddress() method accepts a byte representing the address of the I2C device we are communicating with. It’s important to note that the address we provide is the 7-bit device address right justified. So, the 7 bits of our address make up the 7 least significant digits of this byte, and the most significant bit is a 0. This is somewhat different than the address byte that actually gets sent out on the I2C bus. That byte will be left justified (dropping the most significant bit which is 0) and the least significant bit will be the read/write bit. The conversion between the 7-bit address we provide the setAddress() method and the actual address byte sent on the I2C bus is done for us by the I2CPort class.
The setClockDelay() method is used to modify the frequency of the SCL line. It accepts a byte as an argument, representing an additional increment of delay between all edges. When using the P5.0 and P5.1 pins for access to I2C, our frequency is said to vary between 2.5 kbits/sec and 250 kbits/second. The documentation accompanying the API notes that each increment of clock delay byte adds .109 µsec between the clock edges. So a larger value as an argument to the setClockDelay() method leads to a slower clock, and vice versa. Dallas Semiconductor, in their TINI news group, has provided the following relationship between the clock period and the clock delay argument:
SCL period = (1µs + .109µs * clock delay)*4
Experimentally, we’ve noticed that the relationship is more like this:
SCL period = clock delay*1.25 µs + 5 µs
This was derived from testing clock delay values of 2, 4, 8, 12, 14, 24, and 127. It should also be noted that the value in our test circuit (presented at the end of this section) worked for all of those values. The discrepancy between the clock period that we see and what TINI should be producing according to the documentation may have to do with the specifics of our setup.
The write() method writes an array of bytes to the slave device currently being addressed. It takes the byte array as an argument, along with an offset and a length. The offset indicates where in the array you wish to begin the write and the length indicates how many bytes after the offset you wish to write. It’s important to note that when constructing the array, you should not put the address byte into the array. The addressing is handled by the setAddress() method. The method returns a 0 if it received an acknowledge from the slave and a -1 if it didn’t.
The read() method behaves very much like the write() method. It reads data from the currently addressed slave and places it into an array. It takes the byte array as an argument, along with an offset and a length. The offset indicates where in the array you wish to begin placing the data and the length indicates how many bytes after the offset you wish to put there. The TINI 1.02 API notes that the method returns the number of bytes read on success, or -1 if it fails to receive an acknowledge from the slave. This appears to be a typo in the Javadocs. It actually returns 0 on success, like the write() method.
The process of connecting TINI to an I2C device and communicating is best illustrated with an example. For our example, we’re going to connect an I2C- compatible 7-segment LED driver to TINI. We’ll make a generic Java class that allows us to display digits, then use that class with one of our earlier example classes, Thermometer, to create a standalone TINI digital thermometer. This and all of the examples in this chapter will use the microprocessor ports pins for I2C communication. If you wish to use memory-mapped I2C then you simply need to use an alternate form of the constructor. Here are the two different forms of constructors:
• For Microcontroller Port driven I2C, use the following constructor:
LEDPort = new I2CPort();
This simply creates and returns a new I2CPort.
• For memory-mapped I2C following the schematic shown above, use the
LEDPort = new I2CPort(0×0080001C, (byte)0×1, 0×0080001C, (byte)0×2 );
This creates and returns a new I2CPort based on a memory-mapped driver. The address decoder is wired to decode the line driver and latch at 0×80001C in TINI memory and designates data lines D0 to drive SCL and D1 to drive SDA.
Example: Using TINI and I2C to drive a 7-segment LED display
We’ve chosen this as an example because it’s easy to do, while illustrating all of the features key to understanding the TINI to I2C communication link. This example also shows an alternative way to implement an LED display from what we discussed in Chapter 8. We’ll start with a quick discussion of the I2C-compatible 7-segment LED driver, the Philips SAA1064.
The SAA1064 is available in a 24-pin DIP package. It provides two 8-bit busses that can each handle one or two 7-segment LED displays. If they each are handling two displays, they are to be multiplexed. For simplicity, we’re going to make a nonmultiplexed display in which each 8-bit bus drives a single 7-segment display. You can build the 4-digit display by following the schematic provided in the SAA1064 datasheet. The device pinout and descriptions of the pins are shown in Figure 11-8.
SAA1064 Data Sheet, dated feb 1991, page 4
ADR (pin 1) - This pin is used to configure the programmable address bits of each device. The two least significant bits of the 7-bit address of the SAA1064 are determined by the voltage applied to the ADR pin.
CEXT (pin 2) – This pin can be used to control the frequency at which we multiplex between two pairs of 7-segment displays. We won’t be using this feature and will simply ground this pin.
P8-P1 (pins 3-10) – These pins are the data bus for one of the two data ports on the device. They each drive one segment of the 7-segment displays. They are designed to sink current, so the 7-segment displays need to be common anode. P9 is the MSB, P1 is the LSB.
MX1 (pin 11), MX2(pin 14) – These output pins can be used to power the anode of the LED displays, or to control transistors used to power them. They are switching outputs that switch at the frequency of the multiplexing oscillator. We will not be using them and will leave them unconnected.
Vee ( pin 12) – This is the chip ground.
Vcc ( pin 13) – This is the chip power. We will set it to 5V, but it has a maximum of 18V.
P9-P16 (pins 15-22) – These pins are the data bus for the second of the two data ports on the device. They each will drive one segment of the 7-segment displays. They are designed to sink current, so the 7-segment displays need to be common anode. P16 is the MSB, P9 is the LSB.
SDA (pins 23) – This is the I2C data line.
SCL (pins 24) – This is the I2C clock line.
We will connect each of the two data buses on the SAA1064 to a single common anode 7-segment display. Then we will connect the SDA line to TINI pin 11 (CRX) and the SCL line to TINI pin 10 (CTX). Since there are weak pull-ups in the TINI microcontroller on those pins, we’re not going to put additional pull-ups on these lines. The schematic for this is shown in Figure 11-9.
Figure 11-10: LED segments
For reference, a table and figure showing the mapping between bits on the data bus and the character that is displayed by those bits is also presented.
Table 11-4: Data bits to character displayed
The next thing we need to consider is how to talk to the SAA1064.