Need a fast I2C object and having some trouble grasping the PASM_I2C_Driver
photomankc
Posts: 943
I'm goinig to need some fast(ish) I2C communication as I get started writing up a stepper drive for my robot project. I downloaded the PASM and Spin versions of the I2C driver but I see they are heavily biased to accessing EEPROMs and have built in assumptions that I will be using register addresses. Before I go cross-eyed trying to figure out how to alter the PASM and not bust it peices I have a question or two.
One, has anyone modified this already for simple reading and writing with just (I2C ADDR) + (DATA) + (DATA) ...... ???
Failing that. I'm lost on the whole device select thing. As hard as I look I don't get what is being done there. All I'll want to do is take a normal 7 bit I2C address and shift it left then set the R/W bit and transmit. I can't figure out what's going on though between addrReg and devSelect in the driver so I'm not sure how to alter this to suit me.
My protocol will be simple and both devices will agree on this. A single byte command ID and then a specific number of bytes to follow with the data for what to do. Several will be single byte commands to take instant action like soft stop, hard stop, and reset. No registers needed. So am I correct to assume I can take this:
and make it something like this instead:
So now I pass in a full address, it shifts left one to set or clear the R/W bit and transmit that using the read or write functions. I'd also cut out all the writing of the register data from those functions as well I think. So Strike all this:
One, has anyone modified this already for simple reading and writing with just (I2C ADDR) + (DATA) + (DATA) ...... ???
Failing that. I'm lost on the whole device select thing. As hard as I look I don't get what is being done there. All I'll want to do is take a normal 7 bit I2C address and shift it left then set the R/W bit and transmit. I can't figure out what's going on though between addrReg and devSelect in the driver so I'm not sure how to alter this to suit me.
My protocol will be simple and both devices will agree on this. A single byte command ID and then a specific number of bytes to follow with the data for what to do. Several will be single byte commands to take instant action like soft stop, hard stop, and reset. No registers needed. So am I correct to assume I can take this:
mov t1, addrreg1 shr t1, #15 and t1, #%1110 or devsel1, t1 call #startfunc
and make it something like this instead:
mov t1, addr shl t1, #1 or t1, #RECV call #startfunc
So now I pass in a full address, it shifts left one to set or clear the R/W bit and transmit that using the read or write functions. I'd also cut out all the writing of the register data from those functions as well I think. So Strike all this:
' ackbit := (ackbit << 1) | Write(SCL, addrReg >> 8 & $FF) mov parm1, addrreg1 shr parm1, #8 and parm1, #$ff call #writebytefunc shl ackbit2, #1 or ackbit2, parm1 ' ackbit := (ackbit << 1) | Write(SCL, addrReg & $FF) mov parm1, addrreg1 and parm1, #$ff call #writebytefunc shl ackbit2, #1 or ackbit2, parm1
Comments
pasm_i2c_driver is basically a PASM version of Mike Green's basic_i2c_driver. It was based on an earlier version of basic_i2c_driver that generated a device select and 2 address bytes. Since then, Mike Green added a feature that enabled sending 0 or 1 address bytes by oring the address with either $200000 or $300000. This could be added to pasm_i2c_driver, or you could just remove the code that sends the extra address bytes like you suggested in your first post.
You can adjust the value of DELAY_CYCLES to set the bus speed that you want. The current value yields a bus speed of about 400 KHz on an 80 MHz processor.
Dave
When I added in the Arduino after that then it went to h@#$. It would alternate between SDA starting high and being left low till the next start, or starting low and then being left high till the next start back and forth like that. Also was seeing some weird 'half' clocks where they were short by several volts but the correct duration. That's got to be electrical somehow but I can't see how right off the bat. I did disable the Arduinos built-in pull-ups to 5V and used my own to 3.3V, but the Arduino is a 5V device. Now, If I added delays between the bytes as well as some to the start and stop it gets better but still goes crazy now and then but most of the data makes it to the other side. Every now and then however the stop condition gets missed no matter how many delays I add and then the next few bytes go back to the flip-flopping data line and midget clock pulses only to settle down again and behave a few bytes later.
I'm going to try to test against some simple 3.3V native device like an EEPROM or temp sensor and simply see if I can get just a simple ACKs to all my writes to that. I was a little lost in what was going on with the 'ackbit2' variable. Looked like perhaps it's just accumulating the ACKs but I wasn't sure if it's doing something more subtle elswhere that I'm missing. Maybe you can look at the 'writepagefunc' as I have altered it and see if I'm doing anything blatently stupid there? writepagefunc is the only one I have been really working on thus far. As far as I know the Arduino's built in I2C hardware doesn't try to drive the clock but I sure can't figure out if not... then how do those midget clock pulses happen?
Ultimately I'd like to alter this to be open-drain and not drive SCL or SDA directly at all. Attached are the original driver and then my modified driver and the simple test code
You may want to try a longer delay until you get things working. Set DELAY_CYCLES to 200 to see if that helps. You can speed it up later once you get it working. Also, pasm_i2c_driver generates an asymetrical clock that is high for one-third of the cycle, and low for two-thirds. This is consistent with the timing used by most EEPROMS. The high clock pulse may be too short for the arduino, so you may need to add an extra call to the delay routine when pulling the clock high. You can then reduce DELAY_CYCLES to get 400 KHz.
I also need to make some proper level shifters for the two of them and eliminate weirdness from everyone trying to pull the bus to different voltage levels on each side.
BTW: Thanks a ton for taking the time to answer! My last work in PASM was 2-1/2 years ago and it was nowhere near the complexity of this.
There is a WAITCNT delay before picking up a new command. This was put in to cut down on power consumption by not having the cog sit in a tight loop waiting for a byte to become non-zero, but it could be removed without affecting anything.
I think the most educational PASM code I have written was an I2C driver, but mine is really striped down, no interface and such. I have it outputting to an 8 channel DAC 8 bit signals and the address and channel data every 360us. and its all based on blinking LED tutorial in the WebPM.pdf.
Here we are looking at 8 bytes recieved where byte one is the command and the rest is data. The second byte is incremented by one each time the write occurs. This was written using the WritePage function.
Caught Command - F1
Cmd Buffer - F1, 78, F1, F0, D3, C2, B1, A0, 0, 0, 0, 0, 0, 0, 0, 0,
Command = CMD_HARD_STOP
Caught Command - F1
Cmd Buffer - F1, 79, F1, F0, D3, C2, B1, A0, 0, 0, 0, 0, 0, 0, 0, 0,
Command = CMD_HARD_STOP
Caught Command - F1
Cmd Buffer - F1, 7A, F1, F0, D3, C2, B1, 41, 8, F1, 7B, F1, F0, D3, C2, B1,
Command = CMD_HARD_STOP
Caught Command - F1
Cmd Buffer - F1, 7C, F1, F0, D3, C2, B1, A0, 8, F1, 7B, F1, F0, D3, C2, B1,
Command = CMD_HARD_STOP
The frustrating thing is it can take 20 minutes to happen or it can happen twice in a row in seconds. Slowin down and delaying more just doesn't seem to help much. Uggghhhh. On the positive note I think I understand the code well enough to alter it and see if it works with other I2C stuff. I might not have the skill to write that from scratch in PASM but I think I can hack this one into what I want.
I'm tired now and this is becoming 'unfun'.
The resistors are 4.7K and signals look good, just the modest rise humps but not like shark fins or anything.
Here's something I wrote just to experiment with the EEPROM. It just reads the first 500 bytes and displays them on a serial terminal. It works up to about 450KHz, though the EEPROM is the only device I've tried it with.
I2C PASM 01.spin
After getting that one figured out, I rewrote it to see just how far I could push the tolerances. Turns out they can be pushed quite a bit. This one is completely reliable up to 500KHz, which I suspect is the limit of the EEPROM.
I2C PASM.spin
What finally made me see it was that when the Arduino was held in reset and no ACKs were coming in the last clock low just before the stop condition was followed by an immediate rise just as it was coded. When the Arduino comes out of reset and starts ACKing the bytes then the last clock low is dragging on for several microseconds longer. If that got long enough to just go past the point where the prop released the SDA line then that stop was missed. The ACKs can't affect my time-keeping so it has to be the slave holding the clock low. I think I have the code figured out well enough and so far, as long as I keep the delay between bytes at 12uSecs then it works without any errors. So I think I'll look at implementing clock-stretching awareness in the code. That would be a better solution then trying to add enough delay for the worst-case and really I shouold have thought of it earlier given this is an MController not a dedicated hardware system. The way the two-wire library worked on the Arduino had me thinking it had a multi-byte buffer but it dont.
Earlier this year I wrote an IIC slave suite which had to be in PASM because the master was running at 90 KHz, and I was quite glad I had done the earlier master conversion because I would never have been able to debug the damn thing otherwise.
Here's the snipets that are relevent:
I made the clock highs longer to be more realistic with the waveforms I'm seeing much faster and I get some nasty shark fins. Possible that some of that could be removed if the resistors were lower than 4.7K and a not dealing with breadboard capacitance. I also made up a little level shifter board from some BSS138 mosfets to allow each side to have the bus running at it's native voltage. First forey into surface mount components and it's working nicely.
Thanks to all that chimed in with help and suggestions. All major functions seem to be working as expected and the timing all suggests that it's meeting specs for I2C even on the big capacitor known as my breadboard. I now have two way coms going between the devices so now it's just tweaks and getting into the guts of making the steppers move how I tell them.
Got a new toy tonight! USBee Logic Analyzer. This thing is just wicked cool. I don't have to count bits on the O-Scope anymore!