Help with i2c code
mynet43
Posts: 644
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
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
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?
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
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) repeatThis 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 i2cDataHave you tried to do this inline instead of using the counters? It might be easier to get this running first.
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
Thanks!
Jim
P.S. The code in i2cRead matches the scope traces exactly, except for the ina[i2cSDA].
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 i2cDataAs 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