FinchPJ
- 29th February 2008, 16:38
Recent endeavours with a 18F4550 at 48 MHz talking to both a 24LC256 EEPROM and a DAC8571 on the I2C bus have taught me a lot about I2CWRITE which I am now sharing "for the record".
I wont bother mentioning that
I2CWRITE SDA, SCL, $A0, 0, [Buffer\64]
is illegal and wont work - just please use variables ONLY (not constants).
No, my experimentation with the DAC revealed an undocumented feature of I2CWRITE (aka useful bug) - namely that if you don’t bother reading the PBP manual properly and you write:
I2CWRITE SDA, SCL, %10011000, %00010000, DAC
where DAC is a word to send to the DAC, the compiler does not complain and compiles BUT when you run the program, it doesn’t send a stop condition. This can be most useful, leaving the way to leaving the I2C bus connected to the DAC with it listening and waiting for more data which can then be sent as:
I2CWRITE SDA, SCL, DAC
and the bus can be closed down and restored either by doing it properly (note the brackets around the data!!!):
I2CWRITE SDA, SCL, %10011000, %00010000, [DAC]
or by a
GOSUB I2CStop 'see later for this routine
The same technique works fine for the EEPROM also.
Sometimes PicBasic commands are deceptive - take:
I2CWRITE SDA, SCL, ControlByte, Address, [Buffer\64]
This looks like a pretty slick and fast way of sending data to an EEPROM such as the 24LC256. And intuitively it seems that it is best to send the maximum number of bytes in one go..... However:
I2C bus timings for the 24LC256 are defined as a maximum bit rate of 400 KHz (assuming Vcc >2.5v, 100 KHz if Vcc <2.5v), giving a minimum pulse of 2.5 uSec, but also a minimum CLK hi of 0.6 uSec (if Vcc>2.5v) and minimum CLK low of 1.3 uSec. It also needs at least 5 mSec to complete a write, regardless of the size of the write which can be a maximum page size of 64 bytes. In practice this means that in PBP each I2CWrite takes 40 uSec per byte (5 uSec per bit).
And if you think you can do better than MELabs and write your own routines - e.g.:
I2CWrBuff: 'I2CWRITE SDA, SCL, CntrB, Addr, [str Buffer\8]
HIGH SDA 'Start
HIGH SCL 'Start
LOW SDA 'Start
LOW SCL 'Start
j = CntrB
gosub I2COut
j = Addr.highbyte 'Note must send Address Hi, Low
GOSUB I2cOut
j = Addr.lowbyte
GOSUB I2cOut
for i = 0 to 7
Value = Buffer[i] 'Buffer[i].Lowbyte is syntax error
j = Value.lowbyte 'Send array words Lo, Hi
GOSUB I2cOut
j = Value.highbyte
GOSUB I2cOut
next
I2CStop: LOW SDA 'Stop
HIGH SCL 'Stop
HIGH SDA 'Stop
RETURN
I2COut: SHIFTOUT SDA, SCL, 1, [j] '1=MSBFIRST
INPUT SDA
HIGH SCL 'Receive Acknowledge
LOW SCL
RETURN
Don’t bother because this takes longer than I2CWRITE (because of the pacing of SHIFTOUT.
My task then was to maximise the sample rate of an ADC read while storing the result in a 24LC256 EEPROM. So:
Each I2CWrite requires a 3 byte header (Control byte, Address word), and then the data. Without batching the data for a page write, this means the minimum sample interval is determined by having to wait 5 mSec for the I2CWrite to finish = 200 Hz.
On the other hand if we batch the maximum amount of data to send together with an array write of 64 bytes (64 byte page max), the actual I2CWrite command will take at least:
120 + 40*64 = 2680 uSec = 370 Hz.
So the optimum number of bytes (b) to send in a batch is achieved by solving the equation:
b*(40*b + 120) > 5000
4*b^2 +12b -500 > 0
b > 11.046
So transferring 12 bytes for each I2Cwrite achieves the fastest sample rate of 600 uSec per sample or 1.67 KHz.
However, as the datasheet says:
Write operations are limited to writing bytes within a single physical page, regardless of the number of bytes actually being written. Physical page boundaries start at addresses that are integer multiples of the page buffer size (or ‘page size’) and end at addresses that are integer multiples of [page size - 1].
This means that the answer to my puzzle is the optimum solution is to send 16 bytes (to avoid crossing a page boundary) - this gives a sample interval of 760 uSec or 1.32 KHz.
As my 18F4550 has a 10bit ADC, I am going to need to send 8 words of data at a time.
My routine (including the method of timing the loops using TIMER0) follows:
T0CON=%00010011 'Timer 0: Off[0], 16Bit[0], Fosc/4[0], [1], 1:16 prescaler [0011]
'16*4/48 usec per tick = 1.333 (4/3) uSec per tick
ADC_in: lcdout $FE, 1, "ADC to Memory"
pbuff =0
Addr = 0
block = 1
CntrB = (%10100000|(Block<<1))
t0con.7 = 1 'Turn Timer0 on
ADC1: Tick.Lowbyte = TMR0L 'Read Low first - High latched
Tick.highbyte = TMR0H
TMR0H = 0 'Write High first - Latched until write Low
TMR0L = 0
high led2 'Indicate acquisition - Menu (gButt) turns off
adcin 0, value
'10 bit to 16 bit and swap LSB/MSB because
'STR sends LSB,MSB and DAC expects MSB,LSB, ie:
'xxxxxx9876543210 > 210xxxxx,xxxxxxxx + xxxxxxxx,A9876543
'Buffer[pBuff] = (value<<13) + (Value>>2)
Buffer[pBuff] = Tick 'Borrow the data area to display timings - remove later
pbuff = pbuff +1
if pbuff = 8 then
I2CWRITE SDA, SCL, CntrB, Addr, [str Buffer\8]
pbuff = 0
addr = addr + 16 ' 8 words
if addr=32768 then
addr = 0
block = block +1
CntrB = (%10100000|(Block<<1))
endif
if block = 3 then Memful
endif
if pBuff <>0 then
pauseus 987 '13 uSec per loop
else
pauseus 237 '763 uSec per loop
endif
gosub gButt 'Routine to check for Button press (has two states: short/long)
if Butt = 0 then ADC1
goto Menu
Enjoy and if I have made any mistakes - be kind.
Peter Finch
I wont bother mentioning that
I2CWRITE SDA, SCL, $A0, 0, [Buffer\64]
is illegal and wont work - just please use variables ONLY (not constants).
No, my experimentation with the DAC revealed an undocumented feature of I2CWRITE (aka useful bug) - namely that if you don’t bother reading the PBP manual properly and you write:
I2CWRITE SDA, SCL, %10011000, %00010000, DAC
where DAC is a word to send to the DAC, the compiler does not complain and compiles BUT when you run the program, it doesn’t send a stop condition. This can be most useful, leaving the way to leaving the I2C bus connected to the DAC with it listening and waiting for more data which can then be sent as:
I2CWRITE SDA, SCL, DAC
and the bus can be closed down and restored either by doing it properly (note the brackets around the data!!!):
I2CWRITE SDA, SCL, %10011000, %00010000, [DAC]
or by a
GOSUB I2CStop 'see later for this routine
The same technique works fine for the EEPROM also.
Sometimes PicBasic commands are deceptive - take:
I2CWRITE SDA, SCL, ControlByte, Address, [Buffer\64]
This looks like a pretty slick and fast way of sending data to an EEPROM such as the 24LC256. And intuitively it seems that it is best to send the maximum number of bytes in one go..... However:
I2C bus timings for the 24LC256 are defined as a maximum bit rate of 400 KHz (assuming Vcc >2.5v, 100 KHz if Vcc <2.5v), giving a minimum pulse of 2.5 uSec, but also a minimum CLK hi of 0.6 uSec (if Vcc>2.5v) and minimum CLK low of 1.3 uSec. It also needs at least 5 mSec to complete a write, regardless of the size of the write which can be a maximum page size of 64 bytes. In practice this means that in PBP each I2CWrite takes 40 uSec per byte (5 uSec per bit).
And if you think you can do better than MELabs and write your own routines - e.g.:
I2CWrBuff: 'I2CWRITE SDA, SCL, CntrB, Addr, [str Buffer\8]
HIGH SDA 'Start
HIGH SCL 'Start
LOW SDA 'Start
LOW SCL 'Start
j = CntrB
gosub I2COut
j = Addr.highbyte 'Note must send Address Hi, Low
GOSUB I2cOut
j = Addr.lowbyte
GOSUB I2cOut
for i = 0 to 7
Value = Buffer[i] 'Buffer[i].Lowbyte is syntax error
j = Value.lowbyte 'Send array words Lo, Hi
GOSUB I2cOut
j = Value.highbyte
GOSUB I2cOut
next
I2CStop: LOW SDA 'Stop
HIGH SCL 'Stop
HIGH SDA 'Stop
RETURN
I2COut: SHIFTOUT SDA, SCL, 1, [j] '1=MSBFIRST
INPUT SDA
HIGH SCL 'Receive Acknowledge
LOW SCL
RETURN
Don’t bother because this takes longer than I2CWRITE (because of the pacing of SHIFTOUT.
My task then was to maximise the sample rate of an ADC read while storing the result in a 24LC256 EEPROM. So:
Each I2CWrite requires a 3 byte header (Control byte, Address word), and then the data. Without batching the data for a page write, this means the minimum sample interval is determined by having to wait 5 mSec for the I2CWrite to finish = 200 Hz.
On the other hand if we batch the maximum amount of data to send together with an array write of 64 bytes (64 byte page max), the actual I2CWrite command will take at least:
120 + 40*64 = 2680 uSec = 370 Hz.
So the optimum number of bytes (b) to send in a batch is achieved by solving the equation:
b*(40*b + 120) > 5000
4*b^2 +12b -500 > 0
b > 11.046
So transferring 12 bytes for each I2Cwrite achieves the fastest sample rate of 600 uSec per sample or 1.67 KHz.
However, as the datasheet says:
Write operations are limited to writing bytes within a single physical page, regardless of the number of bytes actually being written. Physical page boundaries start at addresses that are integer multiples of the page buffer size (or ‘page size’) and end at addresses that are integer multiples of [page size - 1].
This means that the answer to my puzzle is the optimum solution is to send 16 bytes (to avoid crossing a page boundary) - this gives a sample interval of 760 uSec or 1.32 KHz.
As my 18F4550 has a 10bit ADC, I am going to need to send 8 words of data at a time.
My routine (including the method of timing the loops using TIMER0) follows:
T0CON=%00010011 'Timer 0: Off[0], 16Bit[0], Fosc/4[0], [1], 1:16 prescaler [0011]
'16*4/48 usec per tick = 1.333 (4/3) uSec per tick
ADC_in: lcdout $FE, 1, "ADC to Memory"
pbuff =0
Addr = 0
block = 1
CntrB = (%10100000|(Block<<1))
t0con.7 = 1 'Turn Timer0 on
ADC1: Tick.Lowbyte = TMR0L 'Read Low first - High latched
Tick.highbyte = TMR0H
TMR0H = 0 'Write High first - Latched until write Low
TMR0L = 0
high led2 'Indicate acquisition - Menu (gButt) turns off
adcin 0, value
'10 bit to 16 bit and swap LSB/MSB because
'STR sends LSB,MSB and DAC expects MSB,LSB, ie:
'xxxxxx9876543210 > 210xxxxx,xxxxxxxx + xxxxxxxx,A9876543
'Buffer[pBuff] = (value<<13) + (Value>>2)
Buffer[pBuff] = Tick 'Borrow the data area to display timings - remove later
pbuff = pbuff +1
if pbuff = 8 then
I2CWRITE SDA, SCL, CntrB, Addr, [str Buffer\8]
pbuff = 0
addr = addr + 16 ' 8 words
if addr=32768 then
addr = 0
block = block +1
CntrB = (%10100000|(Block<<1))
endif
if block = 3 then Memful
endif
if pBuff <>0 then
pauseus 987 '13 uSec per loop
else
pauseus 237 '763 uSec per loop
endif
gosub gButt 'Routine to check for Button press (has two states: short/long)
if Butt = 0 then ADC1
goto Menu
Enjoy and if I have made any mistakes - be kind.
Peter Finch