Someone recently emailed about a baffling problem when trying to get two JeeNodes to alternately transmit and receive. It turns out that there is a subtle aspect to calling the rf12_canSend() routine in the RF12 driver, which really needs to be clarified.
The basic idea is to have two nodes running the same sketch. Each of them broadcasts a small packet every 3 seconds, and displays incoming data the rest of the time.
I used my favorite LED debugging technique to create a simple setup with two JeeNodes:
The idea is to blink the red LEDs on each send, and the green ones on each receive. Here’s a first attempt which doesn’t do what you’d expect:
There’s one minor and one major problem with the above code.
The minor problem is that since sending takes place in the background, you’ll probably not see the send LED blink at all: it’s only lit for a few microseconds. The same holds for the receiving end, although there it’ll probably be visible because serial I/O takes some time. The solution for this little problem is to insert delays so the LEDs stay on longer.
But the big problem with the above code is that it doesn’t work. Whoops!
The reason for this is that rf12_canSend gets called constantly, i.e. each time through the loop. Even when the timer hasn’t gone off, and we won’t be sending anything out.
The thing about calling rf12_canSend is that it is also being used to signal your intention to send out a packet. So when sending is possible, and rf12_canSend returns 1, the RF12 driver will stop reception and get ready to send your packet out. Which is what the subsequent rf12_sendStart will do.
The gotcha is that if rf12_canSend returns 1, then you have to also call rf12_sendStart.
How do we solve this? We can’t just exchange the calls to rf12_canSend and the timer poll, because we’d lose timer events (i.e. when the timer fires and just at that very moment rf12_canSend happens to return 0 – which it does, occasionally).
The problem can be solved by using a slight variation of the code:
I’ve added the complete code of this “pingPong.pde” sketch as example to the RF12 library.
Note: both nodes are set to ID 1. This isn’t a problem in this case, because the packets are sent as broadcasts. A node will never receive its own packet, since it is busy sending. With RF12, packets always go out to “everyone else but me”. When you’re only using broadcasts (and no ack’s), node IDs are irrelevant.
So, if rf12_sendStart must always follow rf12_canSend – who are they two different functions? Wouldn’t it be more fool-proof to have just one?
Because you need to build up the packet data once you know it can be sent. The actions between the two steps will be different in each application.
Is it me or would the following work as well ?
in C the (A && B) is executed as if A then B
So one could also rewrite this as:
Suppose poll returns true, and then canSend returns false. With your suggestion you’ll have to wait another 3 sec before poll returns true again.
Perhaps it would be clearer to write this instead of the if/poll:
The nastiness is in the side-effect of poll() returning true: it rearms the timer for a new delay.
What about renaming it to rf12_prepareSend()? This makes it obvious that there is a side-effect. The other option is to update the docs in lib and even then to receive these problem reports.
Mentioning/explaining the problem on a blog is not enough.
Yes, but “prepare” doesn’t inform you that you also have to ask for permission, so to me that does not sound like enough of an improvement to warrant breaking the API.
You’re absolutely right on the other point, though. I’ve updated http://cafe.jeelabs.net/man/rf12/rf12_cansend/
PS. We use manual rearm timers based on ProtoThreads example code in our products.
Because canSend is an atomic test-and-set type of operation. You can’t test in one place and set in the other without creating a race condition. Yes, ProtoThreads is another option, more advanced, but also more complicated. I may have to use it one day, but for now I’m hoping the current approach will be good enough.
jcw: I don’t see what can preempt you with the sending here but it’s probably just my ignorance of the code.
PS. I was just referring to the API of the timers (consisting of just timer_start(&timer, 3000) and timer_fired(&timer)), not the ProtoThreads implementation.
Hm. Good point. I was thinking of an incoming packet. But I could probably easily just abort that in the rf12_sendStart code. Getting rid of side-effects in rf12_canSend sounds like a good idea. There’s always the potential for collisions, but that remains no matter when the driver starts sending.