Computing stuff tied to the physical world

A subtle RF12 detail

In Software on May 20, 2010 at 00:01

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:

Dsc 1456

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:

Screen Shot 2010 05 17 at 17.40.42

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:

Screen Shot 2010 05 17 at 17.51.10

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.

  1. 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?

  2. 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.

  3. Is it me or would the following work as well ?

    if (sendTimer.poll(3000) && rf12_canSend()){
    ...
    }
    

    in C the (A && B) is executed as if A then B

    So one could also rewrite this as:

    if (sendTimer.poll(3000)){
      if (rf12_canSend()){
        ...
      }
    }
    

  4. 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:

    needToSend |= sendTimer.poll(3000);
    

    The nastiness is in the side-effect of poll() returning true: it rearms the timer for a new delay.

  5. The thing about calling rf12_canSend is that it … will stop reception…

    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.

    1. I did not look at the code (sorry) but why cannot canSend just check if a send is possible and leave the side effects to sendStart?

    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.

  6. 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.

    • I don’t see what can preempt you with the sending here

      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.

Comments are closed.