Bascom and AVR, IC.


IC 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 IC standard uses a seven-bit addressing scheme, allowing for 128 IC's to be connected to one bus.
Read more on IC at the inventor's website. Also, read the IC bus specification.
IC addresses.
The number of IC's on an IC 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.
IC 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)

IC standards
The original IC 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 IC
Philips holds the patent on IC and that means that other chip manufacturers who want to make IC IC's have to pay royalties and must sign an agreement stating that they will adhere to the IC 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 IC interface. Perhaps this terminology is used to avoid royalty issues with Philips?

The IC standard prescribes pull-up resistors on the SDA and SCL line. This is one of the most common mistakes when first using an IC 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 IC 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 IC 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 IC 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 IC 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 IC
The main advantage of IC 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 IC bus results in eight bits being transferred one at the time. Also, starting end ending IC communication takes time. So, IC is of course slower than a parallel bus. Nevertheless, if you do not need the utmost in speed, IC is ideal.
Many semiconductor companies make interesting IC IC's. Philips, being the inventor, seems to be in the lead in this field. (Check the Philips list of IC products)
Sometimes you will find complete units (TV-tuner modules, displays) that use IC.

What have we done with IC:

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 IC 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 IC write and read
IC 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 IC bus, all devices listen. Then, the IC 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 IC bus, all devices listen. Then, the IC 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 IC documents described earlier.

IC errors
Bascom has a reserved bit variable Err that will have the value 1 if an IC 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 IC port.
I2Cdelay sets IC 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 IC communication.
I2cwbyte Pcf8574write send the write address of the Pcf8574 on the IC 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 IC 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 IC 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 IC 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 IC 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 IC 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 IC 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 IC 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 IC 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 IC response but watch out if there are more IC 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 IC 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 IC 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 IC 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 IC 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 IC. 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