Shop OBEX P1 Docs P2 Docs Learn Events
Help with i2c code — Parallax Forums

Help with i2c code

mynet43mynet43 Posts: 644
edited 2012-03-03 22:18 in Propeller 1
I'm using a modified i2c routine to communicate with an autofocus camera module.

When doing a read from their register, I supply the clock signal and they return the data, bit by bit.

The data are clocked into the data line on the trailing edge of the clock signal.

I've attached a scope trace that shows the data should be $99. What I'm seeing from the code is $FF instead.

Would someone please take a look at the code below and the scope trace and see if you can figure out what I'm doing wrong.

The scope trace shows the clock in red and the data in blue.

Thank you for your help.

Jim
PUB i2cRead(ackbit)| i2cData
  ' Read in i2c data, Data byte is output MSB first
  if i2cStarted == true
    ' set the SCL to output and the SDA to input
    dira[i2cSCL]~~              ' SCL -> output
    dira[i2cSDA]~               ' SDA -> input
    outa[i2cSCL] := _PinLow
     
    ' clock in the byte
    i2cData := 0                       
    repeat 8
      outa[i2cSCL] := _PinHigh
      waitcnt(cnt+800)          ' delay 1/100000 sec
      outa[i2cSCL] := _PinLow
      waitcnt(cnt+800)          ' delay 1/100000 sec
      i2cData := (i2cData << 1) | ina[i2cSDA]                     
    waitcnt(cnt+800)            ' delay 1/100000 sec
      
    ' send the ACK or NAK
    outa[i2cSDA] := ackbit      ' init pin B4 dira[ ]
    dira[i2cSDA]~~              ' SDA -> output
    waitcnt(cnt+800)            ' delay 1/100000 sec
    
    outa[i2cSCL] := _PinHigh
    waitcnt(cnt+800)            ' delay 1/100000 sec
    outa[i2cSCL] := _PinLow     ' clock out ackbit
    waitcnt(cnt+800)            ' delay 1/100000 sec

    ' return the data
    return i2cData

Comments

  • T ChapT Chap Posts: 4,223
    edited 2012-03-03 11:26
    http://forums.parallax.com/showthread.php?138352-Eerpm-write-going-wrong

    How about use a proven i2c object like MinimalI2cDriver. See examples in this thread. You should post the entire code help sort out what you have. $FF may mean it is reading the lines high with no change. Using pullups?
  • Cluso99Cluso99 Posts: 18,069
    edited 2012-03-03 13:17
    I found the i2cLibrary_version1_3 from the obex an easy to use program to get the DS1340C working. However, it uses an LCD for display.
    Here is a zipped version I modified to test with my DS1340C using FullDuplexSerial and PST on the PC.
    i2cDemoApp_002 - Archive [Date 2012.03.04 Time 08.15].zip

    BTW xtal is set to 6.5MHz
  • mynet43mynet43 Posts: 644
    edited 2012-03-03 13:43
    Thanks for the feedback.

    The reason I can't use library code is that it's an SCCB protocol (camera), not standard i2c. The input data is not valid when the clock is high, it's set when the clock transitions to low. You can see this from the scope trace.

    Thank you for your help.

    OK, here's the entire code:

    This is the main program:
    CON
      _clkmode      = xtal1 + pll16x
      _xinfreq      = 5_000_000
      _stack        = 50
        
      i2cSCL        = 8 '28
      i2cSDA        = 9 '29 
      cam_reset     = 10            ' Camera Reset
      vsync         = 11            ' Vertical Sync
      href          = 16            ' Horizontal Sync
      xclk          = 17            ' Master Clock, to Camera from Prop                                   
      pclk          = 18            ' Pixel Output Clock from Camera
    
      dev_addr      = $78           ' write address of Camera
      
      vers_1st_byte = $300A         ' register address of version number in Camera
      vers_2nd_byte = $300B                                   
    
      MCP23016_Addr = %0100_0000  
      DS1621_Addr   = %1001_0000
      EEPROM_Addr   = %1010_0000  
      DS1307_Addr   = %1101_0000
      SRF08_Addr    = %1110_0010
      MD22_Addr     = %1011_0000  
      
    VAR
      long  i2cAddress, i2cSlaveCounter
      long  vers_1st, vers_2nd
    
    OBJ
      i2cObject : "i2cObject"
      serio     : "FullDuplexSerialPlus"
      
    pub Start
      waitcnt(cnt+clkfreq/10)       ' wait a tad
      
      ' Sync Pins
      dira[vsync]~                  ' Set vertical sync to input
      dira[href]~                   ' Set Horizontal sync to input
    
      ' Clock Pins
      dira[xclk]~~                  ' Set Master Clock to Output
      dira[pclk]~                   ' Set Pixel Clock to Input
    
      'Init Reset Pin
      outa[cam_reset]~~             ' init to high
      dira[cam_reset]~~             ' dir out
      waitcnt(cnt+clkfreq/10)       ' wait a tad
      
      'Power On Reset  
      outa[cam_reset]~              ' pull low to reset
      waitcnt(cnt+clkfreq/10)       ' wait a tad
      outa[cam_reset]~~             ' pull high to complete reset
      waitcnt(cnt+clkfreq/10)       ' wait another tad
    
      ' start Master Clock
      ctra := %00100_000<<23 + xclk ' start xclk @ 6.0 MHz, mode 00100
      frqa := 322_122_547           ' FRQA = freq*2^32/80_000_000 - 6MHz => 322122547
    ' repeat '**********
      serio.start(31,30,0,115200)   ' for debug output
      waitcnt(clkfreq/4+cnt)
    
      waitcnt(clkfreq*5+cnt)        ' debug, wait for terminal to start
      
      ' setup i2cObject
      i2cObject.Init(i2cSDA, i2cSCL, false)
      waitcnt(clkfreq+cnt)
    
      vers_1st := vers_2nd := 0     ' init to zero
      
      vers_1st := i2cObject.readLocation(dev_addr, vers_1st_byte, 16, 8)
      vers_2nd := i2cObject.readLocation(dev_addr, vers_2nd_byte, 16, 8)
    
      serio.str(string(16,"Camera Version: "))
      hex(vers_1st,4)
      spaces(1)
      hex(vers_2nd,4)
      tx(13)
      repeat
    

    This is the program i2cObject.spin. Included in the main program.:
    CON
      ' i2c bus contants
      _i2cNAK         = 1
      _i2cACK         = 0
      _PinHigh        = 1
      _PinLow         = 0
      _i2cByteAddress = 8  
      _i2cWordAddress = 16  
    
      ' arbitory error constants
      _None           = 1000
      _ObjNotInit     = 1001
      _SCLHold        = 1002
      _i2cSDAFault    = 1003
      _i2cSCLFault    = 1004
      _i2cStopFault   = 1005
      _i2cBothFault   = 1006 ' both SDC and SCL line faults
       
    VAR
      word  i2cSDA, i2cSCL
      long  ErrorState
      long  i2cStarted
      long  lastackbit
      byte  driveLines
    
    '' ******************************************************************************
    '' *  These are the high level routines                                         *
    '' ******************************************************************************
    
    PUB init(_i2cSDA, _i2cSCL, _driveSCLLine): okay
      if lookdown(_i2cSDA : 0..31) > 0 and lookdown(_i2cSCL : 0..31) > 0  
         ' init the I2C Object
         i2cSDA := _i2cSDA
         i2cSCL := _i2cSCL
         ' init the drive'n parameter for SCL lines
         driveLines := _driveSCLLine 
         ' init both i2c lines as inputs.
         if driveLines == false     
           dira[i2cSDA] ~  
           dira[i2cSCL] ~
         else
           dira[i2cSDA] ~~  
           dira[i2cSCL] ~~
           
         ' init no error state 
         ErrorState := _none
         i2cStarted := true
      else
         ErrorState := _ObjNotInit
         i2cStarted := false
        
      ' return true if init was OK
      return i2cStarted
    
    PUB readLocation(deviceAddress, deviceRegister, addressbits, databits) : i2cData | ackbit
      ' do a standard i2c address, then read
      ' read a device's register
      ackbit := _i2cACK
        
      if i2cStarted == true  
        i2cStart
        ackbit := (ackbit << 1) | i2cWrite(deviceAddress | 0,8)
    
        ' cope with bigger than 8 bit deviceRegisters, i.e. EEPROM's use 16 bit or more    
        case addressbits
          8:  ' send a 8 bit deviceRegister. (i2cWrite will shift left 24 bits)
              ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 24, 0)
          16: ' send a 16 bit deviceRegister 
              ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 16, 0)
              ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 24, 0)          
          24:  ' send a 24 bit deviceRegister
              ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 8,  0)
              ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 16, 0)
              ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 24, 0)  
          32:  ' send a 32 bit deviceRegister
              ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 0,  0)
              ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 8,  0)
              ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 16, 0)
              ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 24, 0)             
          other: ' any other value passed!
              ackbit := (ackbit << 1) | i2cWrite(deviceRegister << 24, 0)
        i2cStop
             
        i2cStart
        ackbit := (ackbit << 1) | i2cWrite(deviceAddress | 1, 8)
        i2cData := i2cRead(_i2cNAK)
        i2cStop
      else
        ackbit := _i2cNAK
      ' set the last i2cACK bit
      lastackbit := ackbit
      ' return the data      
      return i2cData
      
    ' ******************************************************************************
    ' *   These are the low level routines                                         *
    ' ******************************************************************************  
     
    PUB i2cStop
      ' i2c stop sequence - the SDA goes LOW to HIGH while SCL is HIGH
      outa[i2cSCL]~~
      dira[i2cSCL]~~            ' clock high
      waitcnt(cnt+800)          ' delay 1/100000 sec
      
      outa[i2cSDA]~             ' init data to low
      dira[i2cSDA]~~
      waitcnt(cnt+800)          ' delay 1/100000 sec
      outa[i2cSDA]~~            ' then data to high to stop transmission
      waitcnt(cnt+800)          ' delay 1/100000 sec
      
      dira[i2cSCL]~             ' then put both in tri-state
      dira[i2cSDA]~             ' they are pulled up
      waitcnt(cnt+800)          ' delay 1/100000 sec
    
        
    PUB i2cStart
      ' i2c Start sequence - the SDA goes HIGH to LOW while SCL is HIGH
      if i2cStarted == true
        if driveLines == false
           ' if the SDA and SCL lines are correctly pulled up to VDD  
           dira[i2cSDA] ~  
           dira[i2cSCL] ~
           waitcnt(cnt+800)          ' delay 1/100000 sec
           repeat until ina[i2cSCL] == _pinHigh     
           dira[i2cSDA] ~~       
           outa[i2cSDA] := _pinLow
        else
           ' if the SDA and SCL lines are left floating
           dira[i2cSDA] ~  
           dira[i2cSCL] ~~
           outa[i2cSCL] := _pinHigh       
           outa[i2cSDA] := _pinHigh
           outa[i2cSDA] := _pinLow     
      
    PUB i2cWrite(i2cData, i2cBits) : ackbit
      ' Write i2c data.  Data byte is output MSB first, SDA data line is valid
      ' only while the SCL line is HIGH
      ackbit := _i2cNAK 
     
      if i2cStarted == true
        ' set the i2c lines as outputs
        dira[i2cSDA] ~~
        dira[i2cSCL] ~~
    
         ' init the clock line                             
        outa[i2cSCL] := _PinLow
        waitcnt(cnt+800)          ' delay 1/100000 sec
    
        ' send the data
        i2cData <<= (32 - i2cbits)
        repeat 8
          ' set the SDA while the SCL is LOW 
          outa[i2cSDA] := (i2cData <-= 1) & 1
          waitcnt(cnt+800)          ' delay 1/100000 sec
          ' toggle SCL HIGH
          outa[i2cSCL] := _PinHigh
          waitcnt(cnt+800)          ' delay 1/100000 sec
          ' toogle SCL LOW
          outa[i2cSCL] := _PinLow
          waitcnt(cnt+800)          ' delay 1/100000 sec
        waitcnt(cnt+800)          ' delay 1/100000 sec
    
        ' setup for ACK - pin to input    
        dira[i2cSDA]~
         
        ' read in the ACK
        outa[i2cSCL] := _PinHigh
    '   ackbit := ina[i2cSDA]
        waitcnt(cnt+800)          ' delay 1/100000 sec
        outa[i2cSCL] := _PinLow
        waitcnt(cnt+800)          ' delay 1/100000 sec
    
        ackbit := ina[i2cSDA]     ' ack bit set on down stroke of SCL above
        waitcnt(cnt+800)          ' delay 1/100000 sec
    
        ' leave the SDA pin LOW
        dira[i2cSDA] ~~ 
        outa[i2cSDA] := _PinLow    
        waitcnt(cnt+800)          ' delay 1/100000 sec
    
      ' return the ackbit
      return ackbit  
    
    PUB i2cRead(ackbit)| i2cData
      ' Read in i2c data, Data byte is output MSB first
      if i2cStarted == true
        ' set the SCL to output and the SDA to input
        dira[i2cSCL]~~              ' SCL -> output
        dira[i2cSDA]~               ' SDA -> input
        outa[i2cSCL] := _PinLow
         
        ' clock in the byte
        i2cData := 0                       
        repeat 8
          outa[i2cSCL] := _PinHigh
          waitcnt(cnt+800)          ' delay 1/100000 sec
          outa[i2cSCL] := _PinLow
          waitcnt(cnt+800)          ' delay 1/100000 sec
          i2cData := (i2cData << 1) | ina[i2cSDA]                     
        waitcnt(cnt+800)            ' delay 1/100000 sec
          
        ' send the ACK or NAK
        outa[i2cSDA] := ackbit      ' init pin B4 dira[ ]
        dira[i2cSDA]~~              ' SDA -> output
        waitcnt(cnt+800)            ' delay 1/100000 sec
        
        outa[i2cSCL] := _PinHigh
        waitcnt(cnt+800)            ' delay 1/100000 sec
        outa[i2cSCL] := _PinLow     ' clock out ackbit
        waitcnt(cnt+800)            ' delay 1/100000 sec
    
        ' return the data
        return i2cData
    
  • Cluso99Cluso99 Posts: 18,069
    edited 2012-03-03 14:05
    I didn't realise it wasnt true I2C.

    Have you tried to do this inline instead of using the counters? It might be easier to get this running first.
  • mynet43mynet43 Posts: 644
    edited 2012-03-03 14:14
    Hi Cluso99,

    I'm not using the counter for the i2c clock, it's used for the pixel clock in the camera, different pin.

    The i2c clock is done in-line.

    Such as:
    outa[i2cSCL] := _PinHigh

    Please keep looking :) I think all of the code of interest is in-line.

    Thanks!

    Jim

    P.S. The code in i2cRead matches the scope traces exactly, except for the ina[i2cSDA].
  • mynet43mynet43 Posts: 644
    edited 2012-03-03 15:50
    OK, I've narrowed it down.

    It seems like the ina[ ] command isn't working. It's always returning a '1'.

    I thought this might be caused by the voltage not being low enough. So I changed the program to stop when the data line was low.

    When I measured the voltage, it was 0.01V. This is obviously low enough for the ina[ ] to return a '0'. But it always returns a '1'.

    Here's the code I used to stop it when the data line was low.

    Please let me know if you can see why the ina[ ] is always returning a '1'.

    Thank you for your help.
    PUB i2cRead(ackbit)| i2cData , i         
      ' Read in i2c data, Data byte is output MSB first, SDA data line is valid
      ' only while the SCL line is HIGH
      if i2cStarted == true
        ' set the SCL to output and the SDA to input
        dira[i2cSCL]~~              ' SCL -> output
        dira[i2cSDA]~               ' SDA -> input
        outa[i2cSCL] := _PinLow
         
        ' clock in the byte
        i2cData := 0
        i := 0                      ' ********** debug                      
        repeat 8
          outa[i2cSCL] := _PinHigh
          waitcnt(cnt+800)          ' delay 1/100000 sec
          outa[i2cSCL] := _PinLow
          waitcnt(cnt+800)          ' delay 1/100000 sec
          i2cData <<= 1             ' shift left to make room for next bit
          i2cData += ina[i2cSDA]
          if i == 0
            repeat
          i++                       ' ********** debug
        waitcnt(cnt+800)            ' delay 1/100000 sec
          
        ' send the ACK or NAK
        outa[i2cSDA] := ackbit      ' init pin B4 dira[ ]
        dira[i2cSDA]~~              ' SDA -> output
        waitcnt(cnt+800)            ' delay 1/100000 sec
        
        outa[i2cSCL] := _PinHigh
        waitcnt(cnt+800)            ' delay 1/100000 sec
        outa[i2cSCL] := _PinLow     ' clock out ackbit
        waitcnt(cnt+800)            ' delay 1/100000 sec
    
        ' return the data
        return i2cData
    
  • T ChapT Chap Posts: 4,223
    edited 2012-03-03 16:02
    If you are sure your pin voltage is a low, then try putting some test code at the very top of your program to show the pin state. If you get a good result at the very top, then keep bringing the test code down the code until you find where it only stays at a 1.
  • mynet43mynet43 Posts: 644
    edited 2012-03-03 16:04
    Good idea. I'll try it. Wish me luck.
  • mynet43mynet43 Posts: 644
    edited 2012-03-03 20:20
    I solved the mystery.

    As usual, operator error :(

    The code was not the problem. It was me...

    When I measured the data pin voltage, it was at the output of the camera module.

    What I didn't take into account was the fact that I had a 10K resistor between the camera and the Prop pin.

    Besides this, I had a 10K pullup on the Prop side of the signal. Looking at the circuit, this turned out to be a fine voltage divider. So when the camera dropped the voltage to '0', the resistors dropped the voltage going to the Prop to 3.3/2 or 1.65V. Of course this is high, so the Prop was always seeing a high input. Grrrr...

    I replaced the 10K current limiting resistor with a 1K and everything started working. The 'low' input to the Prop is now about 0.3V, which works fine.

    Thank you for all your help. It kept me going. If anyone needs a driver for an SCCB camera interface, you're welcome to the code.

    Jim
  • Cluso99Cluso99 Posts: 18,069
    edited 2012-03-03 22:18
    Glad you solved your problem :)
Sign In or Register to comment.