Bit Bashing I2C on a BS2

I2C (Inter-Integrated Circuits), as it's name suggests, is a bus standard for allowing IC's to talk to each other.

I2C is not very difficult to implement at all. Some Micro Controllers, such as the 80C552, and the BS2p, have I2C support built in, and it's a simple matter of calling the appropriate built in commands. But others (like the BS2) do not have native support for I2C.

Not to worry! I'll attempt to show you how to get I2C working with this page. Before we get in to the sample circuits and software, a little explanation of how I2C works is needed.

 

The Protocol:

I2C is a two wire protocol, and requires the following basic elements:

Bytes are sent MSB first

EXAMPLE CIRCUITS AND SOFTWARE:

 

EEPROM EXAMPLE:

To write an "A" to address 0 of an EEPROM who's device address is 011, the protocol would be as follows:

The above is represented in the figure below

 

To read from address 0 of the EEPROM:

The protocol is a little different to writing.

The above is represented in the figure below

 

The Circuit:

As you can see, it's very easy to wire up an I2C device. Pins 1 to 3 of the 24LC02 (2kb EEPROM - 256Bytes) determine the IC's address. Then there is SCL and SDA (Serial Clock and Serial Data). /WR isn't used in this case, but if it is high it will prevent accidental writes to the Chip (unwired, it is held low internally). To add more I2C devices (eg - more EEPROM), simply tie them to SDA and SCL, and set the address pins to a different address. The Outputs of I2C chips are open drain, so you will need the pull up resistors.

 

The Code:

The code is approached in two parts. The first program stores the message "Lennard Electronics www.lennard.net.nz" in the EEPROM.

There are a few debug routines so we can see what is happening (eg - Acknowledges, and to check that the bytes are converted to serial data correctly).

The second program reads the EEPROM, and displays the message in the debug window. Again, there are a few debug routines so we can see what is going on.

 

Program 1: Write to the EEPROM

Some notes:

The code is a simple demonstration of getting I2C to work. Ideally, code should be added to act upon a NO Acknowledge condition (example, the eeprom may be busy writing internal data to it's memory cells, and the Master may therefore have to resend a byte). At this point in time, though, a pause for 10 milliseconds works fine as that is the longest time it takes for the eeprom to write to all it's cells.

'{$STAMP BS2}

'I2C test program. 28/8/2002 Ben Lennard
'Bit Bashing I2C on a Parallax BS2
'Write some data to an I2C EEPROM

'CONSTANTS
SCL CON 15
SDA CON 14
EEPROM_0_Write CON %10100000

'VARIABLES
ack VAR BIT
Internal_Address VAR BYTE
I2C_data VAR BYTE
shift_bits VAR BYTE
MASK VAR BYTE
Character VAR BYTE
i VAR BYTE

Company DATA "Lennard Electronics"
URL DATA " www.lennard.net.nz "
DIRS = %1100001111111111

'MAIN CODE
Main:
for i = company to (company + 38)
read i, Character
Internal_Address = i
GOSUB Send_to_bus
NEXT
END

Send_to_bus:
GOSUB StartI2C
I2C_data = EEPROM_0_Write
'The address of the IC on the I2C bus
GOSUB Write_data
I2C_data = Internal_Address
'The address in EEPROM 0
GOSUB Write_data
I2C_data = Character
'The byte to write to the EEPROM
GOSUB Write_data
GOSUB StopI2C
pause 10
RETURN

'Check for Receiver Acknowledge
RX_ACK:
HIGH SDA
DIRS = %1000001111111111
HIGH SCL
ack = IN14
LOW SCL
DIRS = %1100001111111111
if ack = 1 then indicate_NO_ACK
debug "Acknowledged", 13
RETURN
indicate_NO_ACK:
debug "NO Acknowledge", 13
RETURN

'Start Procedure. Set start condition on bus: SDA Hi-Lo while SCL Hi
StartI2C:
HIGH SDA
HIGH SCL
LOW SDA
LOW SCL
RETURN

'Stop Procedure. Set stop condition on bus: SDA Lo-Hi while SCL Hi
StopI2C:
LOW SDA
HIGH SCL
HIGH SDA
RETURN

'Write Address/Data Procedure
Write_data:
MASK = %10000000
LOW SCL
debug "Byte on Bus = "
'this for loop converts parallel data to serial data
for shift_bits = 0 to 7
'determine whether to put a 0 or 1 on to SDA
if (I2C_data & MASK) > 0 then SDA_HIGH
LOW SDA
Goto SKIP_SDA_HIGH
SDA_HIGH:
HIGH SDA
SKIP_SDA_HIGH:
debug dec OUT14
PULSOUT SCL,1
'Toggle the clock
MASK = MASK >> 1
'Shift MASK right 1 bit $80->$40->$20...$01
NEXT
debug 13
GOSUB RX_ACK
'Check reciever has acknowledged
RETURN

Screen shot of debug screen:

 

Program 2: Read from the EEPROM

'{$STAMP BS2}

'I2C test program. 28/8/2002 Ben Lennard
'Bit Bashing I2C on a Parallax BS2
'Write some data to an I2C EEPROM and then read it back

'CONSTANTS
SCL CON 15
SDA CON 14
EEPROM_0_Write CON %10100000
EEPROM_0_Read CON %10100001

'VARIABLES
ack VAR BIT
I2C_data VAR BYTE
shift_bits VAR BYTE
MASK VAR BYTE
i VAR BYTE
datain VAR BYTE
Memory_Address VAR BYTE

DIRS = %1100001111111111

'MAIN CODE
Main:
for Memory_Address = 0 to 38
GOSUB StartI2C
'READ EEPROM...
I2C_data = EEPROM_0_Write
GOSUB Write_data
I2C_data = Memory_Address
GOSUB Write_data
GOSUB StartI2C
I2C_data = EEPROM_0_Read
GOSUB Write_data
DIRS = %1000001111111111
'this for loop converts serial data to parallel data
for i = 1 to 8
datain = datain << 1
datain = datain + IN14
'debug dec IN14
pulsout SCL,1
NEXT
DIRS = %1100001111111111
GOSUB StopI2C
debug datain', 13
NEXT
stop
END

'Check for Receiver Acknowledge - not really needed in this example...
RX_ACK:
HIGH SDA
DIRS = %1000001111111111
HIGH SCL
ack = IN14
LOW SCL
DIRS = %1100001111111111
if ack = 1 then indicate_NO_ACK
'debug "Acknowledged", 13
RETURN
indicate_NO_ACK:
'debug "NO Acknowledge", 13
RETURN

'Start Procedure. Set start condition on bus: SDA Hi-Lo while SCL Hi
StartI2C:
HIGH SDA
HIGH SCL
LOW SDA
LOW SCL
RETURN

'Stop Procedure. Set stop condition on bus: SDA Lo-Hi while SCL Hi
StopI2C:
LOW SDA
HIGH SCL
HIGH SDA
RETURN

'Write Address/Data Procedure
Write_data:
MASK = %10000000
LOW SCL
'this for loop converts parallel data to serial data
for shift_bits = 0 to 7
'determine whether to put a 0 or 1 on to SDA
if (I2C_data & MASK) > 0 then SDA_HIGH
LOW SDA
Goto SKIP_SDA_HIGH
SDA_HIGH:
HIGH SDA
SKIP_SDA_HIGH:
'debug dec OUT14
PULSOUT SCL, 1
'Toggle the clock
MASK = MASK >> 1
'Shift MASK right 1 bit $80->$40->$20...$01
NEXT
'debug 13
GOSUB RX_ACK
'Check reciever has acknowledged
RETURN

Screen shot of debug screen:

After looking at the code above, and thinking how to optimise the code, the thought occurred that I could save memory space in the BS2, simplify the code, and make eeprom reads and writes faster if I were to use shiftout and shiftin for sending the bytes down the line, rather than using the for loops and a Mask byte. The For loops and Mask byte were adapted from a Pascal program I used to talk to a PCF8574 a few years ago using a 68HC11, but it makes sense to use shiftin and shiftout with the Stamp. For example, (and you will see it in the Serial to Parallel bus example below), the Write_data routine above can be simplified to this:

 Write_data:
shiftout SDA, SCL, msbfirst, [I2C_data]
GOSUB RX_ACK 'Check reciever has acknowledged
RETURN

IMPORTANT NOTE: Using Shiftin and Shiftout, there is the risk that a bus short could occur (example, if the I2C chip takes a line low, while the Stamp is outputting a High). One solution, that I can think of, would be to put a 220R resistor in series with each pin from the stamp, so that if there is a short, the current will be limited to below the maximum that the Stamp's pins can handle.

 

SIPO EXAMPLE:

Serial to Parallel conversion using a PCF8574A.

The 74HC245 is a bidirectional buffer. I use the buffer circuit to monitor the ports on my bread board prototypes, without loading them. You can connect the LEDs directly to the PCF8574A, but you will need to make sure total power dissipation of the PCF8574A is no more than 400mW.

The code simply displays the byte sent to the PCF8574A on 8 LEDs. Starting from 00000000 to 11111111.

'{$STAMP BS2}

'I2C test program. 4/9/2002 Ben Lennard
'Bit Bashing I2C on a Parallax BS2
'Write to the 8 ports of a PCF8574A Bidirectional 8 bit I/O chip

'CONSTANTS
SCL CON 0
SDA CON 1
PCF8574A_Write CON %01110000

'VARIABLES
ack VAR BIT
I2C_data VAR BYTE
i VAR BYTE

DIRS = %1111111111111111
'MAIN CODE
Main:
for i = 0 to 255
GOSUB StartI2C
I2C_data = PCF8574A_Write
'The address of the IC on the I2C bus
GOSUB Write_data
I2C_data = i
GOSUB Write_data
GOSUB StopI2C
pause 100
NEXT
goto Main

'Start Procedure. Set start condition on bus: SDA Hi-Lo while SCL Hi
StartI2C:
HIGH SDA
HIGH SCL
LOW SDA
LOW SCL
RETURN

'Stop Procedure. Set stop condition on bus: SDA Lo-Hi while SCL Hi
StopI2C:
LOW SDA
HIGH SCL
HIGH SDA
RETURN

'Write Address/Data Procedure
Write_data:
shiftout SDA, SCL, msbfirst, [I2C_data]
'Check for Receiver Acknowledge
RX_ACK:
HIGH SDA
DIRS = %111111111111101
HIGH SCL
ack = IN1
LOW SCL
DIRS = %1111111111111111
'Uncomment the following if you want to monitor things in the Debug
'Window
'if ack = 1 then indicate_NO_ACK
'debug "Acknowledged", 13
'RETURN
'indicate_NO_ACK:
'debug "NO Acknowledge", 13

RETURN

 

 RTC EXAMPLE:

This example uses the Philips PCF8583 Real Time Clock chip (RTC) and a Matrix Orbital Graphics LCD (122 x 32 pixels). Talking to the PCF8583 is very similar to the EEPROM examples above.

The PCF8583 is, basically, a 256byte RAM chip, hence it's ID code is the same as for an EEPROM (1010).

The only differences are:

Setting up the time, and reading the time, is a simple matter of reading and writing the appropriate memory locations.

The PCF8583 also understands leap years (if the year counter = 0, it's a leap year).

The RTC Circuit:

The BS2sx Circuit:

Circuit Notes:

i. I constructed the RTC circuit on a veroboard. The battery is mounted on the component side, and the components soldered "surfacemount style" on the copper side. That way, it becomes a useful module that I can reuse for future breadboarding of projects, rather than the RTC circuit taking up valuable space on the breadboard. The Matric Orbital's SDA and SCL lines are connected to the same points as the RTC's SDA and SCL lines.

ii. C1 and C2 are needed to compensate for stray capacitance and to ensure a stable supply rail. C1 should be in the range of 5pF to 25pF. The value of C1 depends on your component placing and trace layout. I kept the tracks short on the Veroboard, with the Crystal connected right up next to the IC. Without C1, the circuit gained 5 seconds a minute. With C1, it is spot on.

iii. The backup battery is a Rayovac Alkaline "841". I had great difficulty finding any information about it on Rayovac's web site - mainly because their site is terrible to navigate. However, Apple Computer uses this battery in some of their Macs. You can easily get one, just go to any Apple reseller (in New Zealand go to MagnumMac), and ask for a PRAM battery - part number 922-0750. Cost is about $25NZ plus GST.

iv. D1 prevents the battery from powering the other circuitry that the RTC may be connected to, and therefore going flat very quickly. D2 prevents the backup battery from being charged by the main supply. The RTC has data retention with Vdd down to *1V, so the Rayovac can be drained to 1.7V (1V plus the voltage drop of the diode of 0.7V) before it needs replacing - about 3 or 4 years. Using a more elaborate circuit, for example, one that disconnects the battery when Vdd is being supplied by the main power system, the battery will last a lot longer.

*Vdd must be 2.5V minimum when the chip is talking to other parts of the circuitry.

v. I've created and used various fonts for use on the display. You can read up on the Matrix Orbital website on how to do this, or just modify the code to talk to a normal 20 x 2 character LCD from Matrix Orbital (it's not hard). A quick intro on how to drive a Matrix Oribital Display using I2C is here.

 

The Code is here. It just fits in to a 2k space on the BS2sx's eeprom (95%). The code allows display of the date and time. Year is adjustable from 2000 to 2049, and leap years are accounted for as well. It is fully functional, but could do with some cleaning up. You can save about 12% of EEPROM space by using the RS232 port of the display to talk to it, rather than including it in the I2C comms. Just connect it's RX pin to the Stamps Sout pin, and use the code here.