Bascom and AVR, I²C.
I²C means Inter-IC. It is a
Philips
development, started in the eighties. In those days consumer
electronics such as televisions and radio's went through a rapid phase of getting more and more compilcated.
Lots of integrated circuits were added to these products resulting in more complicated wiring on crowded
PCB's. Philips considered that a simple serial bus was a possible solution. Instead of coupling
parallel IC busses, all these IC's would connect to one two-wire bus. The two-wire bus has one wire for data
and for a clock, calLed SDA and SCL respectively.
The 1992 I²C standard uses a seven-bit addressing scheme, allowing for 128 IC's
to be connected to one bus.
Read more on I²C at the
inventor's website. Also, read the
I²C
bus specification.
I²C addresses.
The number of IC's on an I²C bus is limited to 128, because each IC needs a unique seven-bit
address. Compare this to
houses in street. Each house needs a unique house number. If two or more houses had the same number the mailman
would not know where to deliver the mail.
I²C addresses are partly hard-coded in an IC, partly
a designer can select the address by tying address pin either to Vcc or Ground. This approach allows for
a number of the same type of IC on an I²C bus.
Take for example the well-known PCF8574P. This is a general purpose eight-bit input/output chip. The hard-coded address
is: 0100.A2A1A0
The 0100 part is hard-coded in the PCF8574P. The
A2A1A0 is for us to choose. Make these
bits one or zero by tying the corresponding pins to Vcc or Ground:

Here, the left PCF8574P has address 0100.100 and the IC in the centre has address 0100.000
(Interpunction added for readability)
I²C standards
The original I²C standard specified a maximum speed on the bus of 100kHz. In 1992, a Fast
mode was added with a maximum speed of 400kHz. Also, in this version a 10-bit addressing mode was added. In 1998 a version
2 was released. This version has a High speed mode with a maximum of 3.4Mbit/s. (Perhaps Philips realised that
Mbit/s is a better unit than kHz) Fast and High speed IC's should work on the 100kHz bus as well.
More on I²C
Philips holds the patent on I²C and that means that other chip manufacturers who want to make
I²C IC's have to pay royalties and must sign an agreement stating that they will adhere to
the I²C standards.
Most of Atmel's AVR controllers have a two-wire interface, at least that is what the documentation says. This is of course
an I²C interface. Perhaps this
terminology is used to avoid royalty issues with Philips?
The I²C standard prescribes pull-up resistors on the SDA and SCL line. This is one of the most
common mistakes when first using an I²C interface. Forget the pull-ups and your interface
will not work. Most often, resistors of 10k will do. Sometimes, when tweaking an interface to be as fast as possible,
lower values may be used. Check the datasheet of your controller and I²C IC.

Sometimes you see resistors of app. 100 - 330 Ohm in the SDA and SCL leads to the controller. This may help to
reduce interference.
The maximum lenght of the I²C bus wires is not in the standard. It only says that for the
100kHz variant, the total capacitance of the bus wiring and attached devices must not exceed 400pF.
In I²C terminology, the device that takes the initiative to start a communication sequence is a
master. The device that the master talks to is a slave. In our examples here, the AT90S2313 controller is
always the master. A slave always keeps silent until it is addressed by a master. The clock on the SCL line is furnished
by the master. The master will time the data it wants to send on the SDA line. If a slave needs to send data back to the
master, the slave has to take care of the data timing on the SDA line. The I²C standard allows
for multiple masters on the bus. In our examples however, there is always one master and one or more slaves.
What can you do with I²C
The main advantage of I²C is of course that you only need two pins on the controller. Especially
on a small controller you are always puzzling with the available I/O pins. Two pins to communicate with a large number of
IC's is very attractive. Of course, there is a price to pay: putting one byte on the I²C bus
results in eight bits being transferred one at the time. Also, starting end ending I²C
communication takes time. So, I²C is of course slower than a parallel bus. Nevertheless, if
you do not need the utmost in speed, I²C is ideal.
Many semiconductor companies make interesting I²C IC's. Philips, being the inventor, seems to
be in the lead in this field. (Check the Philips
list of I²C products)
Sometimes you will find complete units (TV-tuner modules, displays) that use I²C.
What have we done with I²C:
We have used the following IC's:
- Philips PCF8574
- TI PCF8574
- On-Semi JLC1562B
- Philips PCF8591
- Natsem LM76
- Philips graphics display LPH7653
- Linear Technology LT6904
The PCF8574 is a general purpose 100kHz eight-bit bidirectional (input/output) port.
In effect, you are adding an extra eight I/O pins to your
controller. Use this IC to drive Led's, relays, or read switches or buttons.
The chip has an interrupt output that goes low after any change on input pins. After reading out the state of the pins,
the interrupt output returns to high. This is a simple way to interrupt the controller on an input change. The controller
can then read the state of the input pins and decide wich one has changed.
NOTE: Philips produces two variants of this IC: the PCF8574 and the PCF8574A. The only difference is the
I²C chip address!
PCF8574 0100.A2A1A0
PCF8574A 0111.A2A1A0
Check this and other details in the Philips
datasheet for detailed info.
If you need more I/O in one chip: Philips also has the 400kHz 16-bit
PCF8575
.
The Texas Instruments
PCF8574
and
PCF8574A
are a carbon-copy of the Philips PCF8574/PCF8574A.
The
On-Semi
JLC1562B
is also a general purpose eight-bit bidirectional port, but has something extra: it has a
six-bit DAC and offers a comparator function on the five lower input/output pins.
It does not have the interrupt output though. The comparator
and DAC offers the possibility, together with some software to make a six-bit ADC.
The Philips PCF8591 has one eight-bit DAC output and four eight-bit ADC inputs. The analog inputs can be programmed as
single-ended or differential inputs or a mix of both. Read the
datasheet
for more details.
The
National Semiconductor
LM76 is a thermometer chip. National Semiconductor calls these type of chips
'temperature estimator', so don't get your hopes too high with regards to accuracy. Read more in the
datasheet.
The chip has a temperature sensor and a 12-bit (plus sign) ADC.
The Philips LPH7653 is a small graphics display, probably from a cell-phone. I have not found a datasheet, but
detailed information on how it works is available.
The Linear Technology LT6904 is an oscillator chip, capable of generating frequencies from 1kHz to 64MHz.
Of course, this list is just a small sample of what is available for experimentation. There is a lot more,
try to search for I2C in the main IC manufacturer's websites.
Standard I²C write and read
I²C write and read commands process one byte at the time. A standard write is:
I2cstart
I2cwbyte ICaddress
I2cwbyte Bytetosend
I2cwbyte ...
I2cstop
I2cstart generates a start condition on the I²C bus, all devices listen. Then, the I²C address of the IC we want to
talk to is sent on the bus. This IC acknowLedges, all others remain silent. Now one or more bytes are sent to this IC
with I2cwbyte. Each byte is read and acknowLedged by the IC. Lastly, an I2cstop is sent to free the bus.
A standard read is:
I2cstart
I2cwbyte ICaddress
I2crbyte Bytetoread, Ack
I2crbyte Bytetoread, Nack
I2cstop
I2cstart generates a start condition on the I²C bus, all devices listen. Then, the I²C address of the IC we want to
talk to is sent on the bus. This IC acknowLedges, all others remain silent. Now one or more bytes are read with
I2crbyte. After the variable to read the keyword Ack or Nack follows. Ack signifies that there are more reads to follow,
Nack means that this was the last read. (To learn more on Ack and Nack, read the Philips I²C documents described earlier.
I²C errors
Bascom has a reserved bit variable Err that will have the value 1 if an I²C error has occurred. I am not sure what to do
with this Err variable. What to do when an error occurs? Most often, is is caused by a hardware problem and the user of
your application will not know how to correct it.
A simple Led-flasher
Let's start with a simple Led flasher using a PCF8574. Build the following schematic on you breadboard:

Enter the following program in Bascom:
i2c-pcf8574-Led.bas
$regfile = "2313def.dat"
$crystal = 4000000
Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 10
Config Pind.6 = Output
Const Pcf8574write = &H40
Const Pcf8574read = &H41
Set Portd.6
Waitms 1000
Reset Portd.6
Waitms 1000
Do
Set Portd.6
I2cstart
I2cwbyte Pcf8574write
I2cwbyte 255
I2cstop
Waitms 500
Reset Portd.6
I2cstart
I2cwbyte Pcf8574write
I2cwbyte 0
I2cstop
Waitms 500
Loop
End
With Config SDA/SCL, two Portd pins are assigned to a I²C port.
I2Cdelay sets I²C speed to 200kHz.
Const Pcf8574write/read is used for readability. In the remainder of the program text, these constants are replaced by the
compiler by the hex value assigned to the constants.
Pind.6 is one pin of Portd set as output. An Led is attached to this pin.
I2cstart starts an I²C communication.
I2cwbyte Pcf8574write send the write address of the Pcf8574 on the I²C bus to make the Pcf8574
listen.
I2cwbyte 255 sends the byte value 255 to the Pcf8574. All eight bits on the Pcf8574 will be high.
I2cstop ends the I²C communication.
In the second part of the Do loop, I2cwbyte 0 will make all Pcf8574 bits low.
Compile and send to chip. Now the Led on the controller as well as the Led on the PCF8574 flashes.
But note that when the Led on the controller goes out, the Led on the PCF8574 goes on! This is because this Led is wired
with the cathode side to the PCF8574. If the output ia at logic level "1", the Led will not conduct because
it 'sees' app. Vcc on both sides. For a logic level "0", current flows from Vcc through the current-limiting resistor
throught the Led into the PCF8574 output pin and the Led will light up.
Note that if you want to use a PCF8574 I/O pin as an input, you must first make the pin high by writing a "1"
to the top Fet. Then, use an external button, switch, etc. to pull the pin low (read a "0") or not (read a "1"), but we will
go into this later.
I2CDelay
Bascom uses a default I²C speed of 200kHz. The I2Cdelay keyword can be used to specify other
speeds
Config I2Cdelay = 10
is for 100kHz. Use 5 for 200kHz (default). Probably, 2 is for 500kHz and 1 for 1MHz. Use higher values than 10 for
low speed applications. Sometimes with long wires (high capacity) you must lower the speed to avoid errors.
On the other hand most I²C IC's can be used with much higher speeds than specified in the
datasheet. There is no guarantee of course that it will always work under any circumstances, so make
sure you thoroughly check your application.
Using I2cinit
The AVR pins used for I²C are specified with the Config SDA/SCL keywords. After a controller
power up or reset these pins will be low. But after a Portx command or after you changed the ports input/output
setting, these pins may not be in the right state. Use:
I2cinit
to force the pins in the low state before starting or resuming I²C communications.
Reading a switch setting with the Pcf8574
Try the following schematic:

And enter the following program in Bascom:
i2c-pcf8574-switch.bas
$regfile = "2313def.dat"
$crystal = 4000000
Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 10
Config Pind.6 = Output
Const Pcf8574write = &H40
Const Pcf8574read = &H41
Const Shortwait = 50
Const Longwait = 250
Const Switchbit = 0
Dim Ledwait As Byte
Dim Pcf8574port As Byte
'Make all Pcf8574 pins high -> input
I2cstart
I2cwbyte Pcf8574write
I2cwbyte 255
I2cstop
Do
'Read Pcf8574 port
I2cstart
I2cwbyte Pcf8574read
I2crbyte Pcf8574port , Nack
I2cstop
'Determine state of Switchbit bit
If Pcf8574port.switchbit = 1 Then
Ledwait = Shortwait
Else
Ledwait = Longwait
End If
Set Portd.6
Waitms Ledwait
Reset Portd.6
Waitms Ledwait
Loop
End
Constants are defined for a short (50msec) and long (250msec) Led flash.
A constant Switchbit is used to define the position of the switch on pin 0 (bit 0) of the Pcf8574 port.
Two byte variables are Dimensioned for the Ledwait flash time and the value of the Pcf8574 port.
The program starts by making the Pcf8574 port bits all high, so they can be used as inputs.
In the Do Loop, the Pcf8574 port value is read by first sending an I2cstart, then the Pcf8574 read address,
then an I2crbyte is used to get the actual Pcf8574 port value. An I2cstop ends the I²C
communication.
Pcf8574port.switchbit gets the value of bit 0 of Pcf8574port. If it is 1 then the Led flashes a short time, if it
is 0 the Led flashes a longer time.
Compile and send to chip. Observe that if the switch is open, the Led flashes fast, if the switch is closed,
the Led flashes slower.
Using the Pcf8574 interrupt
The program shown above is not very smart. The timing of reading the switch state depends on the current
value of Ledwait. This is not good programming practice. We better start using the Pcf8574 interrupt feature.
You may want to read more about
interrupts
before going on.
As explained earlier, the Pcf8574 interrupt output will go low at every change in input/output pins.
Try the following schematic:

And enter the following program in Bascom:
i2c-pcf8574-switch-int.bas
$regfile = "2313def.dat"
$crystal = 4000000
Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 10
Config Pind.6 = Output
Config Pind.2 = Input
Config Int0 = Falling
Const Pcf8574write = &H40
Const Pcf8574read = &H41
Const Shortwait = 50
Const Longwait = 250
Const Switchbit = 0
Dim Ledwait As Byte
Dim Pcf8574port As Byte
Ledwait = Shortwait
On Int0 Pcfint
Enable Interrupts
Enable Int0
'Make all Pcf8574 pins high -> input
I2cstart
I2cwbyte Pcf8574write
I2cwbyte 255
I2cstop
Do
Set Portd.6
Waitms Ledwait
Reset Portd.6
Waitms Ledwait
Loop
'PCF8574 interrupt routine
Pcfint:
'debounce wait time
Waitms 10
'read the input pins
I2cstart
I2cwbyte Pcf8574read
I2crbyte Pcf8574port , Nack
I2cstop
'Determine state of Switchbit bit
If Pcf8574port.switchbit = 1 Then
Ledwait = Shortwait
Else
Ledwait = Longwait
End If
Gifr = 64
Return
End
The config part now has an extra item: Config Int0 = Falling: only the falling edges of pulses
on the Int0 input will trigger the Int0 interrupt.
Also, Int0 input on Portd.2 is defined as input.
Ledwait is given the value of Shortwait before starting the program.
When an Int0 interrupt occurs, the program will jump to the Pcfint: label.
Interrupts are enabLed in general, Int0 is enabLed in particular.
The program will Loop around flashing the Led until an interrupt occurs.
In the interrupt routine, first a debounce wait for 10 milliseconds to skip all extraneous interrupts from the bouncing
switch, after which the Pcf8574 port value is read, masked with the Switchbit.
If the result is 1, Ledwait is made equal to Shortwait, else to Longwait.
Compile and send to chip. Observe that the Led on the AT90S2313 flashes fast. Close the switch,
the Led will flash slowly. Open the switch and the Led will flash fast again. A button instead
of the switch may be used.
Reading an optical rotary encoder on the PCF8574
The AT90S2313 can
directly read an optical rotary encoder
, but if you are short on I/O pins,
a Pcf8574 may also be used. Read
more on encoders
if you need to.
Try the following schematic:

And enter the following program in Bascom:
i2c-pcf8574-int-encoder.bas
$regfile = "2313def.dat"
$crystal = 4000000
Config Sda = Portd.5
Config Scl = Portd.4
'default I2C speed
Config I2cdelay = 5
'Led on this pin
Config Pind.6 = Output
'Int from PCF8574 to Int0 pin
Config Pind.2 = Input
Config Int0 = Falling
Config Lcd = 16 * 2
Config Lcdpin = Pin , Db4 = Portb.4 , Db5 = Portb.5 , Db6 = Portb.6 , Db7 = Portb.7 , E = Portb.3 , Rs = Portb.2
Config Lcdmode = Port
Const Pcf8574write = &H40
Const Pcf8574read = &H41
'Optical encoder AB is on two low bits
Const Optencmask = &B00000011
'Need to keep the B-bit
Const Optencbmask = &B000000001
Dim Pcf8574input As Byte
Dim Encoderval As Byte
Dim Encounter As Integer
Dim Oldbval As Byte
Encounter = 0
Oldbval = 0
Cls
Lcd "encounter:"
On Int0 Pcfint
'Set PCF8574 to input, masked pins high (pull-up)
I2cstart
I2cwbyte Pcf8574write
I2cwbyte Optencmask
I2cstop
Enable Interrupts
Enable Int0
'Report encoder counter value in an otherwise
'empty main loop.
'Observe Led on Portd.6 : if it goes off or stays on,
'probably too many interrupts arrive
Do
Set Portd.6
Waitms 5
Locate 1 , 12
Lcd Encounter ; " "
Reset Portd.6
Waitms 5
Loop
'PCF8574 interrupt routine
Pcfint:
'read the input pins
I2cstart
I2cwbyte Pcf8574read
I2crbyte Pcf8574input , Nack
I2cstop
'mask to keep only encoder AB value
Encoderval = Pcf8574input And Optencmask
'add old B value if it is 1
If Oldbval = 1 Then
Encoderval = Encoderval + 4
End If
'<2 or >5 means up, remainder means down
If Encoderval < 2 Then
Decr Encounter
Elseif Encoderval > 5 Then
Decr Encounter
Else
Incr Encounter
End If
'keep value of B for next interrupt
Oldbval = Pcf8574input And Optencbmask
Return
End
Config Lcdpin is used to configure the LCD pinning (it is identical to the defaults though)
A mask byte Optencmask is used to mask out the two encoder bits on the I²C port
A mask byte Optencbmask is used to retein only the "B" bit of the encoder bits
Interrupts in general and the Int0 interrupt in particular are enabLed
In a Do Loop the Led on Portd.6 is flashed and the value of encoder is written to the Lcd
In the interrupt routine the I²C port byte is read, the encoder bits are retained
and the encoder direction is determined. The value of encoder is updated
Compile and send to chip. Turn the encoder and observe the encoder value shown on the Lcd. If you have a high-resolution
encoder you may observe that when turning the encoder fast, the Led will stay of or on for as long as you turn. The
amount of interrupts is now so large that there is hardly any time left to run around in the Do Loop. It is quite
possible that you will miss interrupts and that the encoder value does not change so fast as you expected. You may
'tweak' the Config I2cdelay to a lower value to get a faster I²C response but watch out if
there are more I²C devices on the bus.
Reading a 4x4 keyboard on the Pcf8574
A small
keyboard may directly be attached to the AT90S2313
, but it needs 8 I/O pins!. A 4x4 keyboard
could also be attached to a Pcf8574.
Try the following schematic:

And enter the following program in Bascom:
i2c-pcf8574-int-keyboard.bas
$regfile = "2313def.dat"
$crystal = 4000000
'I2C lines
Config Sda = Portd.5
Config Scl = Portd.4
'default I2C speed
Config I2cdelay = 10
'Led on this pin
Config Pind.6 = Output
'Int from PCF8574 to Int0 pin
Config Pind.2 = Input
Config Int0 = Falling
Const Pcf8574write = &H40
Const Pcf8574read = &H41
'Mask for reading columns and rows state
Const Columnsmask = &B11110000
Const Rowsmask = &B00001111
'Keyboard debounce time
Const Debouncetime = 150
Const Keystringlength = 10
Const Wtime = 100
Dim Pcf8574input As Byte
Dim Keycoderow As Byte
Dim Keycodecol As Byte
Dim Keycode As Byte
Dim Keychar As String * 1
Dim Keystring As String * Keystringlength
Dim Keycharpos As Byte
Dim Endofstring As Bit
Cls
On Int0 Pcfint
'Set PCF8574A to input, masked pins high (pull-up)
I2cstart
I2cwbyte Pcf8574write
I2cwbyte Columnsmask
I2cstop
Enable Interrupts
Enable Int0
Do
Endofstring = 0
Keystring = Space(keystringlength)
Keycharpos = 0
Cls
Lcd "? "
While Endofstring = 0
Set Portd.6
Waitms Wtime
Reset Portd.6
Waitms Wtime
Wend
Lowerline
Lcd "S " ; Keystring
Wait 1
Loop
'PCF8574 interrupt routine
Pcfint:
'read the input pins
I2cstart
I2cwbyte Pcf8574read
I2crbyte Keycodecol , Nack
I2cstop
'shift upper nibble to lower nibble
Shift Keycodecol , Right , 4
'switch column and row input/output state
I2cstart
I2cwbyte Pcf8574write
I2cwbyte Rowsmask
I2cstop
'read the input pins
I2cstart
I2cwbyte Pcf8574read
I2crbyte Keycoderow , Nack
I2cstop
'Cls
'Lcd Bin(keycodecol) ; Bin(keycodecol)
'switch column and row input/output state back
I2cstart
I2cwbyte Pcf8574write
I2cwbyte Columnsmask
I2cstop
Select Case Keycoderow
Case 7 : Keycode = 0
Case 11 : Keycode = 4
Case 13 : Keycode = 8
Case 14 : Keycode = 12
Case Else : Keycode = 99
End Select
'make final keycode from portb pins read
Select Case Keycodecol
Case 7 : Keycode = Keycode + 0
Case 11 : Keycode = Keycode + 1
Case 13 : Keycode = Keycode + 2
Case 14 : Keycode = Keycode + 3
Case Else : Keycode = Keycode + 99
End Select
'illegal keycode from bounce effects
If Keycode > 15 Then Keycode = 16
'Cls
'Lcd Keycoderow ; " " ; Keycodecol ; " " ; Keycode
Keychar = Lookupstr(keycode , Keycodes)
If Keychar <> "?" Then
Lcd Keychar
If Keychar <> "E" Then
Keycharpos = Keycharpos + 1
Mid(keystring , Keycharpos , 1) = Keychar
Print "keystring: " ; Keystring
If Keycharpos = 16 Then
Endofstring = 1
Else
Endofstring = 0
End If
Else
Endofstring = 1
End If
End If
Waitms Debouncetime
Gifr = 64
Return
End
Keycodes:
Data "1" , "2" , "3" , "A" , "4" , "5" , "6" , "B" ,
Data "7" , "8" , "9" , "C" , "R" , "0" , "E" , "D" , "?"
The method of deciding which key is pressed is described in detail in
the keyboards chapter.
Not that the actual wiring of the keyboard to the PCF8574 used here is a little different and thus also the
decoding details. The method is the same though.
Using the ON Semi JLC1562B instead of the Pcf8574
The On-Semi JLC1562B does not have the PCF8574 interrupt output. This pin has a six-bit DAC output, the output can be
set in 64 steps of 0.0625V between 0 and app. 4V.
The input pins of the JLC1562B have a comparator function available. The lower five bits can have a comparator ("B")
reference of either Vcc/2 or the DAC output, the upper three bits always have the comparator ("A") reference
set to Vcc/2.
Contrary to the PCF5874, the JLC1562B DAC supports different write and read modes. This is necessary to be able to use
the chip as a PCF8574 replacement, and also to be able to use the DAC and control the comparator reference input. If the
JLC1562B is used as a PCF8574 replacement, the I²C read and write sequence is unchanged.
If you want to use the DAC output:
- send the JLC1562B write address
- send the byte value of the JLC1562B I/O port pins
- send the value of the six-bit DAC (0-63 decimal)in the six lower bits
- in the two upper bits, bit6 is the DAC reference voltage control,
0 sets the reference to Vcc/2,
1 sets the reference to the DAC output
bit7 is the read data latch control,
0 sets the data to be latched after an ack following a read command,
1 sets the data to be latched when the comparator B switches from 0 to 1
To use simple the DAC output try the following schematic:

And enter the following program in Bascom:
i2c-jlc1562b-dac.bas
$regfile = "2313def.dat"
$crystal = 4000000
Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 10
Config Pind.6 = Output
Const Jcl1562bwrite = &H70
Dim Dacval As Byte
Do
Set Portd.6
For Dacval = 0 To 63
I2cstart
I2cwbyte Jcl1562bwrite
I2cwbyte 0
I2cwbyte Dacval
I2cstop
Next Dacval
Reset Portd.6
Loop
End
The JLC1562B write address is set to 70 hexadecimal (check the datasheet!)
In a Do Loop the Led is switched on, the value 0 is written to the JLC1562B I/O port in the first byte and in the
second byte the loop count value between 0 and 63 is written to the DAC. In this second byte both the Comparator
reference control bit and the latch data control bit remain at zero.
Compile and send to chip. The waveform on the DAC output pin 13 looks like a sawtooth:

The JLC1562B used as a six-bit ADC
You can configure the JLC1562 comparator to use the DAC output as reference. If we set the DAC output to, say, 2Volts
and apply a slightly lower voltage to one of the D0..D4 pins, the input pin will read as a zero. A slightly higher
voltage will read as a one. So, any of the D0..D4 input pins can be made to function as a crude six-bit ADC if you
ramp up the DAC voltage as shown above in 64 steps and read back the state of the input pins after each increment.
Try the following schematic:

And enter the following program in Bascom:
i2c-jlc1562b-adc.bas
$regfile = "2313def.dat"
$crystal = 4000000
Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 10
Config Pind.6 = Output
Const Jcl1562bwrite = &H70
Const Jcl1562bread = &H71
'mask out all but the lower six dac bits
Const Jcl1562bcompon = &B11000000
'make this pin high to use it as input
Const Jclmask = &B00000001
Dim Dacval As Byte
Dim Dacwrite As Byte
Dim Jclinput As Byte
Cls
Do
Reset Portd.6
For Dacval = 0 To 63 Step 1
Dacwrite = Dacval Or Jcl1562bcompon
I2cstart
I2cwbyte Jcl1562bwrite
I2cwbyte Jclmask
I2cwbyte Dacwrite
I2cstop
I2cstart
I2cwbyte Jcl1562bread
I2crbyte Jclinput , Nack
I2cstop
Jclinput = Jclinput And Jclmask
If Jclinput = Jclmask Then
Exit For
End If
Next Dacval
Locate 1 , 1
Lcd Dacval ; " "
Loop
End
Jlc1562bcompon is used to 'OR' with Dacval, so that both the comparator "B" reference control bit, as the
read data latch control bit is one
Jlcmask is used to set only bit 0 of the JLC1562B I/O port to one, making it function as an input
In the Do Loop, the Led is switched on,
in a for next loop Dacval is increased from 0 to a maximum value of 63 (0 to 63*0.0625=app. 4V), after every
increment, the JLC1562B input is read and bit 0 is masked out. If the remaining value is equal to the mask value
(1 = 1), the comparator has switched. The For Next loop is 'exited' and the value of Dacval is written to the LCD
Compile and send to chip. Observe the value shown on the LCD as you turn the potentiometer.
The Philips PCF8591 A/D-D/A
The
Philips
PCF8591
has one eight-bit DAC output and four eight-bit ADC inputs. For the DAC and ADC to function, the EXT pin has to be
connected to Ground. The OSC output pin then shows a somewhat jittery 1MHz clock:

You can use your own clock signal (0.75 - 1.25MHz) connected to OSC, the EXT pin then has to be connected to Vcc.
The PCF8591 is controlled by sending three bytes to the chip:
- address byte
- control byte
- dac output byte
The control byte is constructed as follows:
76543210 (bit-number)
0app0icc (control byte)
where:
a = 1 to enable analog output (must also be enabled if ADC is to be used)
pp = ADC input configuration, equals 00 for four single-ended inputs on A0, A1, A2 and A3, see datasheet for other configurations
i = 1 to enable auto-increment for ADC input channels, this increments cc after each read
cc = ADC channel number, 0, 1, 2 or 3
To use the PCF8591's DAC output or ADC inputs, try this schematic:

For testing the DAC output, enter this program:
i2c-pcf8591-dac-triangle.bas
$regfile = "2313def.dat"
$crystal = 4000000
Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 10
Config Pind.6 = Output
Const Pcf8591write = &H90
Const Pcf8591read = &H91
Const Pcf8591dacconfig = &B01000000
' |
' -------- enable analog output
' (used for adc as well)
Dim Dacout As Byte
Set Portd.6
Waitms 1000
Reset Portd.6
Waitms 1000
Do
Set Portd.6
For Dacout = 1 To 255 Step 1
I2cstart
I2cwbyte Pcf8591write
I2cwbyte Pcf8591dacconfig
I2cwbyte Dacout
I2cstop
Next Dacout
Reset Portd.6
For Dacout = 255 To 1 Step -1
I2cstart
I2cwbyte Pcf8591write
I2cwbyte Pcf8591dacconfig
I2cwbyte Dacout
I2cstop
Next Dacout
Loop
End
A constant Pcf8591dacconfig has bit 6 set, enabling analog output.
In the Do Loop, a For Next loop counts Dacout from 1 to 255, and sends Dacout as the third control byte.
Then a For Next loop follows that counts Dacout from 255 down to 1.
On Aout pin 15 a triangle waveform can be observed:

Changing Config I2cdelay to 1 instead of 10 speeds things up:

This of course violates the maximum 100kHz I²C speed, the PCF8591 can handle theoretically.
But the program could also be rewritten to get a higher outout frequency:
i2c-pcf8591-triangle-faster.bas
$regfile = "2313def.dat"
$crystal = 4000000
Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 10
Config Pind.6 = Output
Const Pcf8591write = &H90
Const Pcf8591read = &H91
Const Pcf8591dacconfig = &B01000000
' |
' -------- enable analog output
' (used for adc as well)
Dim Dacout As Byte
Set Portd.6
Waitms 1000
Reset Portd.6
Waitms 1000
I2cstart
I2cwbyte Pcf8591write
I2cwbyte Pcf8591dacconfig
Do
For Dacout = 1 To 255 Step 1
I2cwbyte Dacout
Next Dacout
For Dacout = 255 To 1 Step -1
I2cwbyte Dacout
Next Dacout
Loop
End
Now, the first two control bytes are sent before the Do Loop.
In the Do Loop, only the Dacout value is repeatedly written to the PCF8591.
Note that there is no I2cstop in this program! In a real application program this is probably not realistic because
there will be other things going on with the I²C bus.
The output waveform is now:
We could again speed things up by changing the Config I2cdelay to 1:

And we could cheat even more by inserting a 10MHz crystal without telling Bascom:

You will of course not do these horrible things if you are designing commercial products! But a hobbiest
will accept that breaking the rules also means accepting the possible consequences.
The National Semiconductor LM76 temperature 'estimator'
The
National Semiconductor
LM76
is one of many temperature monitoring IC's with an I²C interface. Most of the
'big' manufacturers have such IC's in their portfolio. The LM76 does not have great accuracy, it is sometimes
calLed a 'temperature estimator'. Do not count on better than 1.0 degrees Centigrade accuracy.
Read the
datasheet
for more details.
The LM76 has six registers of which the temperature readout register is selected by default after power-up. More on these registers
later. Temperature is measured and stored in the LM76 in a resolution of 0.0625 0C.
The temperature is in a 16-bit word, read as two bytes:

D0-D2 are undefined. D15 is the sign bit, temperature is in D3-D14. The format used is two's complement. This means that
positive numbers are stored as is with a zero for sign bit, negative numbers have a one for the sign bit and all
data bits are inverted and incremented by one. If you only deal with positive temperatures, forget the two's complement
format and just use the data in bits D3-D14. If you want the whole range of negative as well as positive temperatures
use the following recipe:
...
Dim Tempint as Word
Dim Tempbytelo as Byte
Dim Tempbytehi as Byte
...
'get the two temperature bytes from the lm76
...
Tempint = Makeint(Tempbytelo, Tempbytehi)
Tempsign = Tempbytehi And &B1000000
If Tempsign = &B10000000 Then
Tempint = Not Tempint
Tempint = Tempint + 1
End If
Shift Tempint, Right, 3
...
Makeint merges the two bytes into an 16-bit word variable Tempint. Then, the signbit is extracted from Tempbytehi
by just looking at the leftmost (most significant) bit. If the signbit is set, we need to invert all the bits in
Tempint (losing the signbit) and increment the result by one. Lastly, all bits in Tempint are shifted three places to
the right, losing
bits D0-D2. Tempint has the absolute temperature in a resolution of 0.0625 oC and Tempsign has the sign.
Try the following schematic:

And enter the following program in Bascom:
i2c-lm76.bas
$regfile = "2313def.dat"
$crystal = 4000000
Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 100
Config Pind.6 = Output
Const Lm76write = &H90
Const Lm76read = &H91
Dim Tempint As Integer At $80
Dim Tempbytelo As Byte At $80 Overlay
Dim Tempbytehi As Byte At $81 Overlay
Dim Templong As Long
Cls
Do
Set Portd.6
I2cstart
I2cwbyte Lm76write
'pointer set to temperature register
I2cwbyte 0
I2cstart
I2cwbyte Lm76read
I2crbyte Tempbytehi , Ack
I2crbyte Tempbytelo , Nack
I2cstop
Cls
Lcd Tempbytehi ; " " ; Tempbytelo
Lowerline
Shift Tempint , Right , 3
Templong = Tempint * 65
Lcd Tempint ; " " ; Templong
Reset Portd.0.6
Wait 1
Loop
End
In this simple example, Tempint is not constructed with the Makeint command, but is dimensioned in such a way that
Tempbytehi and Tempbytelo are 'overlayed' at the same location that Tempint occupies in memory. Tempint is declared as
Integer at memory location $80 where it occupies addresses $80 and $81. Tempbyte is at $80 so it fills the first
half of Tempint. Tempbytehi is at $81 where it fills the second half of Tempint. If you have a lot of Makeint
operations, the overlay construction is cheaper in terms of controller cycles used.
In the Do Loop, the LM76 pointer is set to the temperature register. This not strictly necessary, as it is the
default after a power-up
Then the two temperature bytes are read. The first I2crbyte is followed by an Ack to signal that another read will
follow. The second I2crbyte is followed by a Nack to signal the end of reads.
Both Tempbytehi and Tempbytelo are written to the Lcd.
Tempint is shifted three bits to the right, losing the D0-D2 bits.
Tempint is multiplied by 625 to get the temperature in 0.0001oC.
In the example above, the sign of the temperature was ignored. A more complete example that also displays the
temperature in oC is:
i2c-lm76-fusing.bas
$regfile = "2313def.dat"
$crystal = 4000000
Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 100
Config Pind.6 = Output
Const Lm76write = &H90
Const Lm76read = &H91
Const Lm76resolution = 0.0625
Dim Tempint As Word
Dim Tempbytelo As Byte
Dim Tempbytehi As Byte
Dim Temperature As Single
Dim Tempstring As String * 4
Dim Tempsign As Byte
Dim Flashnumber As Byte
Dim Flashloop As Byte
Dim Flashtime As Word
Do
Set Portd.6
Wait 1
Cls
I2cstart
I2cwbyte Lm76read
I2crbyte Tempbytehi , Ack
I2crbyte Tempbytelo , Nack
I2cstop
Lcd Tempbytehi ; " " ; Tempbytelo ; " "
Tempint = Makeint(tempbytelo , Tempbytehi)
Tempsign = Tempbytehi And 128
If Tempsign = 128 Then
Tempint = Not Tempint
Tempint = Tempint + 1
End If
Shift Tempint , Right , 3
Lcd Tempint ; " "
Temperature = Tempint * Lm76resolution
Lowerline
Lcd Temperature ; " "
Tempstring = Fusing(temperature , "##.#")
If Tempsign = 128 Then Lcd "-"
Lcd Tempstring
Reset Portd.6
Wait 1
Loop
End
The sign of the temperature is tested by 'anding' Tempbytehi with 128 (leftmost bit).
Temperature as a floating point number is calculated by multiplying Tempint with the LM76 resolution of 0.0625 oC.
The fusing command (strange name by the way) is used to format the temperature in a string with two digits before and
one digit after the decimal point.
The resulting string is written to the Lcd, preceded by a '-' if the signbit was set.
This example program uses a floating point operation. Therefore the floating point library is loaded. Although the program
itself is quite small, it will use 99% of the AT90S2313 flash memory!.
When dealing with small controllers it is often necessary to avoid using floating point numbers. A variant of the program above
uses only integer variables and is much smaller:
i2c-lm76-usingintegers.bas
$regfile = "2313def.dat"
$crystal = 4000000
Config Sda = Portd.5
Config Scl = Portd.4
Config I2cdelay = 100
Config Pind.6 = Output
Const Lm76write = &H90
Const Lm76read = &H91
Const Lm76resolution = 625
Dim Tempint As Word
Dim Tempbytelo As Byte
Dim Tempbytehi As Byte
Dim Tempall As Long
Dim Temprem As Long
Dim Temptemp As Long
Dim Tempdeg As Long
Dim Tempsign As Byte
Dim Tempdigit As Integer
Do
Cls
Set Portd.6
I2cstart
I2cwbyte Lm76read
I2crbyte Tempbytehi , Ack
I2crbyte Tempbytelo , Nack
I2cstop
Lcd Tempbytehi ; Tempbytelo ; " "
Tempint = Makeint(tempbytelo , Tempbytehi)
Tempsign = Tempbytehi And 128
If Tempsign = 128 Then
Tempint = Not Tempint
Tempint = Tempint + 1
End If
Shift Tempint , Right , 3
Lcd Tempint ; " "
Tempall = Tempint * Lm76resolution
Lcd Tempall ; " "
Tempdeg = Tempall / 10000
Temptemp = Tempdeg * 10000
Temprem = Tempall - Temptemp
Lowerline
Lcd Tempdeg ; " " ; Temprem ; " "
Tempdigit = Temprem / 1000
Temptemp = Tempdigit * 1000
Temprem = Temprem - Temptemp
If Temprem > 499 Then Tempdigit = Tempdigit + 1
If Tempdigit > 9 Then
Tempdigit = 0
Tempdeg = Tempdeg + 1
End If
If Tempsign = 128 Then Lcd "-"
Lcd Tempdeg ; "." ; Tempdigit
Loop
End
I leave it up to the reader to figure out how the temperature is calculated and presented in a +-##.# format.
This program has more lines, but is smaller nevertheless: it occupies 85% of the AT90S2313 flash memory, leaving some room
for other things.
Controlling a Philips LPH7653 graphics display
The Philips LPH7653 display was probably made for an early generation cell-phone. It has a graphics area of 97x35 pixels.
Detailed info is available on
Lous's LPH7653 page
Make sure you read his description! You may have to juggle a bit with the Contrast and Vcc voltages to get a good image. These
voltages both seem to have effect on the contrast. The background Led's seem to be 5V tolerant, as well as SCL and SDA lines.
The LPH7653 is available dirtcheap from
Electronic Goldmine
and in the Netherlands from
Voti.
The LP7653 is
a pure graphics display, you must make your own character set if you want to display text. This is not as
difficult as it sounds. I used Lous's example written in C and translated it to Bascom.
The LPH7653 display shows 97 pixels horizontal and 35 pixels vertical. Internally, the display seems to be 101 pixels wide and
35 pixels high, but the spillover is not shown. Each vertical row of 8 pixels can be addressed individually:

Programmatically, the display is addressed as follows:
I2cstart
I2cwbyte lph7653writeaddress '(7Ahex)
I2cwbyte linenumber '(60, 61, 62, 63 or 64hex for line 0, 1, 2, 3 and 4)
I2cwbyte pixelcolumn '(00...60hex for column 0...97)
I2cwbyte pixelbyte '(least significant bit at the top)
I2cwbyte pixelbyte '(pixelcolumn/linenumber wraps around after pixelcolumn 64hex)
I2cwbyte ...
I2cstop
If you want to clear the display, you can write 5 x 101 zero bytes starting at the first linenumber and at the first
pixelcolumn. The display will wrap around the pixel addresses.
If you plan on using the display for text only, the obvious approach is to use it as a 4-line display. Then, a character set can be defined
with a height of maximum eight pixels. Lous defined a 7x5 pixel character set as well as a 7x6 pixel proportional character set. This always
leaves the lowest pixel line of the eight pixel column empty, providing for some line spacing. The fifth line is available, but
only the top three pixels (35 - (4 x 8)) are shown on the display. Also, the lines are internally 101 pixels wide, but only the first 97 are
shown on the display.
Try the following schematic to write these character sets to the display:

And enter the following program in Bascom:
i2c-lph7653-75.bas for the 7x5 pixel character set
or
i2c-lph7653-75prop.bas for the 7x6 pixel proportional character set
(The listings are not here as they are quite long)
The i2c-lph7653-75.bas program uses Data statements to enter the character set.
The characters are defined with five Data bytes, one for each pixelcolumn:

(example shown for the '#' character)
In the Do Loop, the display is erased by writing 5 x 101 zero bytes.
Then, a For Loop cycles through the Data bytes in Rownumber steps of five.
Another For Loop cycles through the five pixelcolumns. The index of the Data byte to write is calculated by adding the
Rowinnumber with the Arrnr Loop counter. The actual Data byte is retrieved from the Data block with a Lookup command.
After each fifth pixelcolumn a zero byte is written to act as a character separator.
The i2c-lph7653-76prop.bas program uses a 7x6 character set, so the Data lines each have six bytes.
Lous came up with the clever trick to mark the end of a character (it is proportional, so the actual width may vary) with
the bit pattern &B10000000. First, the byte to write is 'anded' with &B01111111 to get rid of a possible no.7 bit set,
and after the write, the byte is 'anded' with &B10000000 to see if it was an end-of-character. The result is a nice
proportional character set, with automatic character separators as the last pixelcolumn written for each character is
alsway zero.

If you want to use the LPH7653 as a graphics display, you must build the image in eight bit pixelcolumns.
This is different from using Toshiba T6963 display, for which Bascom has a bitmpa conversion utility.
If you want to simply
dump an image to the display it is best to make this image 101 pixels wide and 35 pixels high and keeping the actual image
of 97 pixels high and 32 pixels high in the upper-left corner.
You can of course send smaller images to the LPH7653 display. You will have to look at the pixel addressing carefully though.
To convert images to a Data block, I have written a
small Delphi program
. It reads a black-and-white bmp format image and
writes it as text in a window from which you can copy/paste to Bascom. You can make your images in any bitmap program
(CorelPaint, Paintshop etc.) first, save them as black-and-white bmp files, and then convert them to a DATA block.
I have included the source code so you can adapt the program to your own needs.
As an example, I made the following bitmaps:









Loading the first bitmap file in convertbmp gives this result:

You may load all bitmap files after each other in convertbmp, then copy/paste all the DATA lines to the appropiate
place in your Bascom program.
For an example of this, see the
fan controller
chapter.
Controlling a Linear Technology LT6904 oscillator
The
LT6904
is one of a family of
Linear Technology
oscillator chips. The LT6903 has an SPI interface and the LT6904 uses I²C. There is also
the LT1799, LT6900, LT6902 and LT6905 which use an external resistor to set the frequency. See the Linear Technology
website
for an overview of this range of IC's. (click on 'View Table')
The LT6904 is controlled in the following way (from the datasheet):


where OCT is a four-bit and DAC a 10-bit word. Also, a two-bit configuration setting is used to control the
output pins. Make sure you disable an unused output pin. (Read the datasheet!) The total is 16 bits which are sent as:
Oct3, Oct2, Oct1, Oct0, DAC9, DAC8...DAC0, Cfg1, Cfg0.
Try the following schematic:

Note that the LT6904 datasheets states that Vcc may be between 2.7 and 5.5V. I have noticed however, that with the
power supply voltage set at 5V, the LT6904 output occasionally 'dissapears'. This nasty behaviour disappeared
after I lowered Vcc to 3.3V.
Enter the following program in Bascom:
i2c-lt6904.bas
Here we run into a problem: Bascom reports the amount of flash memory used as 162%. Clearly, this program is a bit
too large for the AT90S2313. Time to move up to the
larger AVR's...
TOC