My I2CWRITE - timings and tricks


Closed Thread
Results 1 to 6 of 6
  1. #1
    Join Date
    Jun 2005
    Location
    Surrey, England
    Posts
    35

    Post My I2CWRITE - timings and tricks

    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.:
    Code:
    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:
    Code:
            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

  2. #2
    Join Date
    Jun 2005
    Location
    Surrey, England
    Posts
    35


    Did you find this post helpful? Yes | No

    Default Amendment

    Oops - just realised was running the 18F4550 at 20 MHz - so all the timings are out by a factor of 2'ish. ie my code example is actually sampling at 2 KHz. Same principles apply though.
    The Xtal frequency is 20 MHz - what with pre and post scalers etc it gets difficult to track what the Oscillator frequency is on the 18F4550 which has two potentially different clocks speeds in it.
    Also my ADC manipulation comments had an error, but the code was correct - should have been:
    Code:
            '10 bit to 16 bit and swap LSB/MSB because
            'STR sends LSB,MSB and DAC expects MSB,LSB, ie:
            'xxxxxx9876543210 > 10xxxxxx,xxxxxxxx + xxxxxxxx,98765432
            Buffer[pBuff] = (value<<13) + (Value>>2)
    Peter

  3. #3
    Join Date
    Jun 2005
    Location
    Surrey, England
    Posts
    35


    Did you find this post helpful? Yes | No

    Default Enlightenment

    The speed of my 18F4550 was frustrating me - I knew it has a 20 MHz crystal and I set the prescaler etc to do the usual divide by 5, multiply by 12 but it appeared to only run at 20 MHz. When I tried on the PICDEM-FSUSB demo board it ran at 48 MHz. I have finally twigged the difference and the explanation - I program my PICDEM board using ICD2 and the "real" board using the Microcode bootloader from Mecanique.

    Of course, the requisite R of TFM states clearly that the configuration fuses are set by the bootloader program (here 20 MHz) NOT my PBP program I am loading in with its OSC 48 - so I need to use OSC 20 with the loader and of course this also explains why the USB fails - not recognised by the PC because it is not running at 48 MHz. We actually chucked away a USB plug because we concluded that it MUST be the reason for the program to fail, there being no other logical explanation - the next one worked but of course we didnt use the loader for that - Doh!
    Last edited by FinchPJ; - 2nd March 2008 at 19:53. Reason: spelling

  4. #4
    Join Date
    Nov 2003
    Location
    Greece
    Posts
    4,127


    Did you find this post helpful? Yes | No

    Default

    Quote Originally Posted by FinchPJ View Post
    ...
    I2CWRITE SDA, SCL, $A0, 0, [Buffer\64]
    is illegal and wont work - just please use variables ONLY (not constants).

    .....

    I2CWRITE SDA, SCL, %10011000, %00010000, DAC
    Hi Peter.
    On the first example you state that it won't work because of the constants. But on the second example you are using constants! Now I got confused. Does it work or not? From my experience the control may well be a constant.

    Ioannis

  5. #5
    Join Date
    Jun 2005
    Location
    Surrey, England
    Posts
    35


    Did you find this post helpful? Yes | No

    Default Correction

    Well spotted - I apologise I was not as rigorous as I should have been - of course that was a throwaway comment on a topic already well documented in this forum. You are correct I am using a constant for the control byte - This is OK! If I had been more rigourous I would have been more specific and said please dont use constants for the Address when using a memory chip - that MUST be a variable - as per the PBP manual:

    The Address size sent (byte or word) is determined by the size of the variable that is used. If a byte-sized variable is used for the Address, an 8-bit address is sent. If a word-sized variable is used, a 16-bit address is sent. Be sure to use the proper sized variable for the device you wish to communicate with. Constants should not be used for the address as the size can vary dependent on the size of the constant. Also, expressions should not be used as they can cause an improper Address size to be sent..

    Of course these contraints do not apply to talking to the DAC, although I could have used byte variables.

    Peter Finch

  6. #6
    Join Date
    Nov 2003
    Location
    Greece
    Posts
    4,127


    Did you find this post helpful? Yes | No

    Default

    OK. We agree totaly.

    Ioannis

Members who have read this thread : 0

You do not have permission to view the list of names.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts