Shop OBEX P1 Docs P2 Docs Learn Events
Even more minimal I2C driver — Parallax Forums

Even more minimal I2C driver

scanlimescanlime Posts: 106
edited 2010-05-15 08:52 in Propeller 1
Just sharing a little code snippet that I thought might be useful to others.

I'm working on a library that needs to make some of its DAT data persistent. After a batch of modifications to this data, it wants to flush the whole block to EEPROM. The data may not be aligned on EEPROM page boundaries, and it may be multiple pages. I'm also really trying to save RAM, so I wanted the smallest I2C routines I could find.

So, I wrote a single compact routine that does just this. It compiles to 180 bytes, which is about half the size of the "Minimal I2C Driver". I think this routine is a bit unconventional in that it combines the page loop, I2C header, and I2C data into one compact set of nested loops. This means it only does one thing, but it does that pretty well and with very little memory. If this code is useful, feel free to take it. I'm releasing it into the public domain. Code is below and attached.

CON
  EEPROM_SCL = 28
  EEPROM_SDA = 29
  EEPROM_ADDRESS = $A0

  E_EEPROM = -50
  
PUB EEWrite(address, size) | shiftreg, bytecount
  '' Copy data from RAM to EEPROM, writing all EEPROM pages that
  '' overlap the specified data range. On error, aborts with E_EEPROM.

  outa[noparse][[/noparse]EEPROM_SCL]~~   ' SCL high
  dira[noparse][[/noparse]EEPROM_SCL]~~   ' Drive SCL
  outa[noparse][[/noparse]EEPROM_SCL]~    ' SCL low
  outa[noparse][[/noparse]EEPROM_SCL]~~   ' SCL high
                       
  ' The 24LC256 has 64-byte pages. Page-align the beginning and ending addresses,
  ' and convert 'size' into a page count.
  
  size += address
  address &= !63
  repeat ((size + 63) - address) >> 6
  
    ' Issue page-write I2C commands. 

    ' I2C Start
    dira[noparse][[/noparse]EEPROM_SDA]~~    ' Drive SDA low
    outa[noparse][[/noparse]EEPROM_SCL]~     ' Drive SCL low
    
    ' We have a 32-bit data buffer that starts out filled with the three-byte header,
    ' then we re-fill it with longs from hub memory. We're sending the least significant
    ' byte first, but most significant bit first. To be consistent with this byte order,
    ' we need to swap the bytes in our address.

    shiftreg := EEPROM_ADDRESS | (address & $FF00) | (address << 16)
    bytecount := 3
    
    ' One 64-byte pages: 16 data longs plus header
    repeat 17

      shiftreg ->= 8  ' Left-justify the least-significant byte

      ' Loop over each byte in the shift register (3 for header, 4 for data)
      repeat bytecount
        repeat 8
          dira[noparse][[/noparse]EEPROM_SDA] := shiftreg > 0    ' SDA = shiftreg MSB
          outa[noparse][[/noparse]EEPROM_SCL]~~                  ' Drive SCL high
          shiftreg <-= 1                      ' Right one bit
          outa[noparse][[/noparse]EEPROM_SCL]~                   ' Drive SCL low

        dira[noparse][[/noparse]EEPROM_SDA]~                     ' Let SDA float
        outa[noparse][[/noparse]EEPROM_SCL]~~                    ' Drive SCL high
        if ina[noparse][[/noparse]EEPROM_SDA]                    ' Check for ACK
          abort E_EEPROM 
        outa[noparse][[/noparse]EEPROM_SCL]~                     ' Drive SCL low
  
        shiftreg ->= 16                       ' Next byte
    
      shiftreg := LONG[noparse][[/noparse]address]
      address += 4
      bytecount := 4

    ' Undo the one extra address step we took above (because of the header)
    address -= 4

    ' I2C Stop
    dira[noparse][[/noparse]EEPROM_SDA]~~    ' Drive SDA low
    outa[noparse][[/noparse]EEPROM_SCL]~~    ' Drive SCL high
    dira[noparse][[/noparse]EEPROM_SDA]~     ' Float SDA high

    ' Wait for page write. Datasheet says the max write time is 5ms.
    ' This is just a really conservative hardcoded delay. It's 5ms at 96 MHz.
    ' (This takes less memory than polling for completion.)
    waitcnt(cnt + constant(96_000_000 / 1000 * 5))

Comments

Sign In or Register to comment.