-
Notifications
You must be signed in to change notification settings - Fork 148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TwoWire calls OnReceive() more then once for the same data #1090
Comments
I see where the problem comes from. Apparantly Spence is pretty busy right now, so it might take some time for it to be merged in. |
Hi MX682X Issue 1 Every path through TwoWire::HandleSlaveIRQ() when the address / STOP bit is set, results in the buffer being reset. This will ensure that user_onReceive() is only called once per group of bytes (i.e. all the bytes following a single address frame). EXCEPT if STOP doesn't happen as in a REPSTART. In this case the buffer is left populated and will trigger a user_onReceive() call on the next address received. Consider
Had the master sent STOP after receiving the requested data from the slave, the slave would have reset the buffer and all would have ben well in the next request (which would have been START rather than REPSTART). [I know that REPART and START are the same thing on the wire but I've distinguished them in the above description to try and be clear what is happening] A possible fix might be to add *head = 0; *tail = 0; to the Data bit set / master is reading / RXACK bit not set / no data available code path - i.e. where the action is "Wait for any Start (S/Sr) condition" following the completion of the read by the master around line 1365. See "ADDED" below:
Issue 2 I also think that pushSleep() / popSleep() are unsafe - this is so for both the old and new code. If there was an error on the physical wire such that a STOP was missed, the sleepStack top nibble would be >0 forever. I.e. set (sleepStack & 0x10) for TWI0 in a transaction and set (sleepStack & 0x20) for TWI1 in a transaction. So push and pop need to know which interface is being handled:
STOP:
And then we have to handle the combinations of interfaces using bitwise logic rather than arithmetic:
Also FWIW, pushSleep is a confusing name; pushLimitSleep() would be a clearer statement of intent. popLimitSleep() similarly. |
This fix would break the START recognition that is used to push the sleep State, so I decided to set a Flag on Master Transmissions only. This way the code should only call onReceive after a preceeding Master Write START Frame.
Subjectively, I find the "Limit" to be more confusing, so I won't change that yet I'm kinda short on time nowadays, so I'm not able to set up a test bed to check the code. Consider the following a preview / suggestion. Wire.h:
|
Sleep management in the single TWI case still won't work. Consider the message sequence START, START, STOP. This will result in pushSleep(), pushSleep(), popSleep(). Therefore pushSleep() and popSleep() need to handle the case of when pushSleep() is called multiple times without intervening popSleep(). One way is to test sleepStack > 0 as is done for the dual TWI case. The use of _hostDataSent seems conceptually correct. It does imply a trivial functional change in that a write from master with no data will now cause user_onReceive() to be called. This was not previously the case. Seems benign to me. Possibly it is actually more correct. Note: This is only from code inspection - your preview / suggestion code is from a significantly different base so I haven't attempted to get it running - I'm sure it is possible but I would have to guess a few things when would largely invalidate the test. |
Alright, tested on 1614 now. Haven't tested the I2C Bus Fail logic though, it would require writing a I2C bit-bang library and that will take more time on that then i want to spend on it. Came up with some better named functions for push/pop Sleep, aswell as adding a guard fo double START.
|
I think the test for As a minor speed optimisation the line Apologies for my ignorance but how do I get the 1614 code? |
I though about it aswell, but i'll keep it like that for now, I don't want to change too much in one go. Roll out, make sure there are no hidden/weird bugs, continue with optimization.
Nice catch.
If you mean the Code I used for testing, aka the Arduino Sketch, I can post it if you want. |
Summed up, made the Wire library more resistant to erroneous beahiour of the physical bus in Client/Slave mode
twi.c slave IRQ handler can call the receive callback more than once for the same data. This confuses the sketch code.
Use case 1
Reading from the slave, preceded by an write (which is the instruction as to what to read). I.e.
Use case 1A
Reading from the slave, preceded by an write (which is the instruction as to what to read). I.e.
Use case 2
Writing to the slave without reading. I.e.
My code contains use cases 1 and 2. I haven't tried use case 1A but I can't see any reason why it should be materially different from use case 1.
In twi.c slave IRQ handler:
When a REPSTART occurs when reading, user_onReceive() is invoked in order that the slave can process the command so that the slave can generate the reply. Needed for use case 1 (and 1A).
When STOP occurs at the end of the transaction, user_onReceive() is invoked in order that the slave can process the command. Needed for use case 2.
The problem is that in use case 1 (and 1A), both REPSTART and STOP occur in the transaction, so user_onReceive() is invoked again on the same data. But the master doesn't know this, so doesn't do the expected read - which isn't wanted anyway, nor is processing the command a second time on the slave.
My solution is that the receive buffer should be emptied after every user_onReceive() call. I've done this by adding
(*rxHead) = 0; (*rxTail) = 0;
in twi.c where user_onReceive() is called on REPSTART in the slave IRQ handler around line 566 as follows:The receive buffer is already emptied after the user_onReceive() call on STOP:
The text was updated successfully, but these errors were encountered: