Thought I'd just start with Serial.println("something");
Compiles fine but when it runs there is no output. I have put an #error directive into Wire.cpp and the compiler sees this and reports and stops as expected.
The file is being edited is here C:\Arduino\hardware\arduino\avr\libraries\Wire\src
I deleted C:\Users\acboo\AppData\Local\Arduino and the compile process made a fresh one.
TheMemberFormerlyKnownAsAWOL:
Did you remember a Serial.begin?
Lots of Serial output coming through nicely before the code even gets to any Wire calls. This "debug" Serial.println s are in
uint8_t TwoWire::endTransmission(uint8_t sendStop)
and
uint8_t TwoWire::endTransmission(void)
The addition of the Serial.println has not affected the behaviour of the sketch which is working just fine... except it blocks when the target device (another UNO/Nano/Mega) on I2C is unpowered.
The problem has moved on a bit. Uploaded latest version of the IDE 1.8.13. Initially had compiler problems wjere it reported that my
char * something = "something";
was not allowed. After a couple of retries (and a spurious (?) swap to verbose complier output) it started to compile again. Also my Serial output from Wire.cpp worked Now I tried to add more debug into twi.c but no amount of encouragement would get the compiler to like it. Typically getting this in the IDE
C:_ACB\arduino\hardware\arduino\avr\libraries\Wire\src\utility\twi.c: In function 'twi_writeTo':
C:_ACB\arduino\hardware\arduino\avr\libraries\Wire\src\utility\twi.c:256:3: error: 'Serial' undeclared (first use in this function); did you mean 'ceil'?
Serial.println("#log~C");
^~~~~~
ceil
C:_ACB\arduino\hardware\arduino\avr\libraries\Wire\src\utility\twi.c:256:3: note: each undeclared identifier is reported only once for each function it appears in
exit status 1
Error compiling for board Arduino Nano.
Unless someone tells me how to put a Serial.println into twi.c I have to move on (and even if I do I'm not sure I could solve the underlying problem - blocking/hanging.
There are loops or low level blocking calls with no get out, so I guess one of these is causing the Wire.endTransmission() to hang when the target device has been removed. Real bugger that!
Yes, we know. A low-level timeout was added, but not yet documented. It has to be turned on.
The Wire library for the AVR core took many years to get the bugs out. I think you are wasting your time trying to make a non-blocking version.
There is a non-blocking I2C library for the Teensy, but I am not completely happy with that one.
The faster processors such as the ATSAMD21G would benefit from a non-blocking version. However, the Arduino Uno would not, because it is already busy with the Wire functions as it is. A non-blocking version for the Uno will take up about the same amount of time.
I think that between ten en twenty people tried to make a "better" I2C library for Arduino boards. They all failed, and not in a nice way if I may say so.
The only other I2C library that is successful is the non-blocking I2C library for the Teensy.
If you want your processes to run smoother, then you can try a Teensy with that library, but if you really want to upgrade, then try a ESP32 with FreeRTOS.
Well I got the timeout (sort of ?) working. My code doesn't block on endTransmission() anymore and I get some info as to what has happened so I can make adjustments in the calling logic.
However its proving a nightmare project. When the Arduino gets an onReceive event the code goes and sends a message via an NRF24, waits for a reply and sticks that reply away until the onRequest event is called where it give over the info it got via the NRF.
The NRF Arduino 2 way comms seems bullet proof now. So does I2C Arduino 2 way comms (when connection is good) is also so. But when the Arduino 1 sends an I2C message to Arduino 2 whicch sends an NRF message to Arduino 3 and 3 replies to 2 and in turn 2 replies 1 it becomes flakey. Mostly it works, occasionally the original calling code stops in the onReceive event before it gets to the send via NRF even after its done loads of prior sends. It stops before a memcpy or another time before a Serial.println... it's just dead. I have tried a regualr call to Wire.end, Wire.begin just to try and keep it afloat but no joy.
I trace the function calls so I know I'm not recursing down into a bottomless pit. Absolutely stuck for ideas now.
All I wanted was an Wire<->NRF bidirectional device... was it too much to ask for?
acboother:
All I wanted was an Wire<->NRF bidirectional device... was it too much to ask for?
Yes, way way way too much. The I2C bus is not that kind of bus.
I wrote a page to scare Arduino users for the I2C bus, hopefully preventing that someone might go down that path that you did: How to make a reliable I2C bus.
The I2C bus can be used to get a few bytes from a sensor.
Using the I2C bus between Arduino boards is not a good idea. I can be done if you know almost everything about the I2C bus and the Wire library or if you have a good tutorial.
The onReceive and onRequest handlers are run from a interrupt. There is not much you can do in those functions.
However I'd spent four days battling with Wire and my NRF<->I2C thingy... I knew it was going wrong as more error handling code was being added to try and identify, avoid and manage a problem. Whilst I sometimes expect error handling code to be the bigger part of the code it always ended up feeling "wrong".
I've rewritten the Wire part to not do the processing in the event handlers. The code is small again and so far reliable but testing not completed and this has surfaced a little bit more of the asynchronous nature (NRF<-> NRF) of the whole comms path for messaging. Nothing unmanageable but lets see where it goes now.
Here is a typical architecture for the peer to peer relationship between "useful" Arduinos that I am trying to achieve. The Arduinos 3 and 4 know nothing about what is being transmitted/received - they are just "pipework". I really should have gotten this solved on day 1
When the Slave Arduino is in the onRequest handler, then it makes the SCL low.
That puts the Master on hold and the Slave has time to run the onRequest handler in software and do Wire.write() to put data in a buffer.
As soon as the Slave releases the SCL, then the Master continues and makes clock pulses and reads the buffered data and closes the transaction with a STOP.
If you want to return data in the same I2C session, then you have to stay in the onRequest handler. That is called from an interrupt, so it should be as fast and as short as possible. There is no time to do other things.
I have mentioned just one problem here. If you find a way to workaround this problem, then there are more problems and your workaround will probably create a whole bunch of new problems.
More tests tonight but, as I said, I ditched using the interrupt handler (it simply returns immediately after being called).
The "caller" goes into an idle in loop() after a "send" waiting for the "called" to turn the conversation around and send back the required information.
The "caller" is prevented from sending any more messages (but does not block loop() ) until it receives the returned information or a timeout period has elapsed. The returned stuff is matched up with the call... So data is not returned in the same I2C session.
But as I say - more tests this evening. I'm quite prepared for it to turn to sh1t but I have to get to the point where I know I can or cannot make it work. Failing elegantly (or as Buzz Lightyear said "Falling with style").
It is my intention to support a send (call/return) and a post (call without return - the easier option) very loosely based in the send and post concept in the lower parts of Windows and OS/2 message processing.
acboother:
Unless someone tells me how to put a Serial.println into twi.c
'Serial' is a C++ object and 'twi.c' is a C (not C++) source file. The first step to using Serial would be to change 'twi.c' to 'twi.cpp'. Then change this part of Wire.cpp:
johnwasser:
'Serial' is a C++ object and 'twi.c' is a C (not C++) source file. The first step to using Serial would be to change 'twi.c' to 'twi.cpp'. Then change this part of Wire.cpp:
I have done exactly that in the past, but most of the (interesting) time it spent inside an interrupt so no Serial.print() calls. Instead, I did make some of the local variables public so I could examine them at various times. In the end, I just used the newer version that has timeouts.
You can call Serial.print() inside an ISR. It doesn't even take very long IF THERE IS ROOM IN THE OUTPUT BUFFER. As soon as the buffer fills, the Serial.print() will not return until the last character has made it into the buffer and that time will depend on the baud rate and the number of characters. Crank the baud rate up as far as your hardware allows. Serial Monitor can be set up to 250000 baud.
That information about Serial.print in a .C file is very interesting (obvious if you think about it... which I obviously didn't). Will apply to other C++ C compatibility things so very useful to know.
Serial.printing in an ISR sounds like it has to be done with care and awareness. Also good to know.
As for my project of an NRF-I2C bit of pipework... well, its gone well Its actually just a part of another project which I've almost forgotten I was doing!
So I avoided using the interrupt handling in Wire and also the auto ACK feature of NRF. I spent ages trying to get both working but in the end I use both technologies by turning the conversation round for the reply. Consequently this means the comms is asynchronous which makes programming using these a little more complex than a synchronous call. However, I have both the I2C and NRF code as classes and they both have the same external interfaces (which feels right).
To be honest this is better from my perspective as it's not good to block awaiting a response (its the blink without delay scenario you really need to aim for). This is a peer to peer architecture which means that both 'ends' are equals and can initiate a call whenever needed.
I have Send, which gets a reply from the target and Post which doesn't. You can't do another send until the reply to the previous one has returned so you can wait (in loop() looping) for as long as you feel it appropriate. If a reply doesn't come then a flag can be cleared to allow Sending to recommence (if it makes sense in the code to do so).
Now to get on with the original project.... and keep an eye on things going wrong just in case.
PC<-ProMicro|WIRE <-Nano|NRF<- ---- AIR ----- <-NRF<-WIRE|Mega260
for a CAD puck I'm making.
The Mega2560 is the puck end and the ProMicro is the HID interface into the PC. I am using it with "post" messages so I'm not getting anything back from the ProMicro. I have used "send" and it works but doesn't give me anything useful unless I want to confirm message delivery - which I'm not since the delivery has been 100% and I can tell in my CAD software if the message hadn't gone properly aanyway.
So far everything runs smoothly - the ProMicro just picks up messages to send keyboard instructions to the PC and knows nothing about what I want the keys on the puck to represent. The Nano also simply passes the instructions on and similarly knows nothing about what is going on.
Consequently the development requires me to work on only one end (the Mega2560) and avoid the irritation that seems to come about with the ProMicro. Its dual connection to the PC (for Serial and HID)?
I'm so glad all the potential issues with Wire were worked around and new ones didn't surface.
My own learning advice to pass on is:
A) to get the latest Wire library as it has the timeout functionality
B) do as little as possible in the Wire event handlers - I do nothing not even read/write
C) come up with your own handshaking comms logic between two Arduinos.