Computing stuff tied to the physical world

RF12 broadcasts and ACKs

In Software on Jun 10, 2011 at 00:01

In yesterday’s post, the general design of the RF12 driver was presented, and the format of the packets it supports.

The driver also support broadcasting, i.e. sending packets to all interested nodes, and ACKs, i.e. sending a short “acknowledge” packet from receiver to transmitter to let the latter know that the packet was properly received.

Broadcasting and ACKs can be combined, with some care: only one node should send back the ACK, so the usefulness of ACKs with broadcasts is limited if the goal was to reliably get a packet across to multiple listeners.

Broadcasts and ACKs use the HDR byte in each packet:

Rf12 Packets

There are three bits: C = CTL, D = DST, and A = ACK, and there is a 5-bit node ID. Node ID 0 and 31 are special, so there can be 30 different nodes in the same net group.

The A bit (ACK) indicates whether this packet wants to get an ACK back. The C bit needs to be zero in this case (the name is somewhat confusing).

The D bit (DST) indicates whether the node ID specifies the destination node or the source node. For packets sent to a specific node, DST = 1. For broadcasts, DST = 0, in which case the node ID refers to the originating node.

The C bit (CTL) is used to send ACKs, and in turn must be combined with the A bit set to zero.

To summarize, the following combinations are used:

  • normal packet, no ACK requested: CTL = 0, ACK = 0
  • normal packet, wants ACK: CTL = 0, ACK = 1
  • ACK reply packet: CTL = 1, ACK = 0
  • the CTL = 1, ACK = 1 combination is not currently used

In each of these cases, the DST bit can be either 0 or 1. When packets are received with DST set to 1, then the receiving node has no other way to send ACKs back than using broadcasts. This is not really a problem, because the node receiving the ACK can check that it was sent by the proper node. Also, since ACKs are always sent immediately, each node can easily ignore an incoming ACK if it didn’t send a packet shortly before.

Note that both outgoing packets and ACKs can contain payload data, although ACKs are often sent without any further data. Another point to make, is that broadcasts are essentially free: every node will get every packet (in the same group) anyway – it’s just that the driver filters out the ones not intended for it. A recent RF12 driver change: node 31 is now special, in that it will see packets sent to any node ID’s, not just its own.

It turns out that for Wireless Sensor Networks, broadcasts are quite useful. You just kick packets into the air, in the hope that someone will pick them up. Often, the remote nodes don’t really care who picked them up. For important events, a remote node can choose to request an ACK. In that case, one central node should always be listening and send ACKs back when requested. An older design of the Room Node sketch failed to deal with the case where the central node would be missing, off, or out of range, and would retry very often – quickly draining its own battery as a result. The latest code reduces the rate at which it resends an ACK, and stops asking for ACKs after 8 attempts. The next time an important event needs to be sent again, this process then repeats.

  1. Hi jcw, a couple of questions:

    1) If you want to send a packet to a specific node, you can do that, but you would need to put the sender’s node id into the payload if you want to reply specifically to that node.

    Would you do:

    SenderID = 5; rf12_sendStart(RF12_ACK_REPLY | SenderID,0,0);

    2) And if you wanted to include data in the ACK reply, you would just enter it and its length as in a normal rf12_sendStart?

    3) If you are checking if a packet is ok to read (via rf12_recvDone() ) this line: (rf12_hdr & RF12_HDR_CTL) == 0 Checks that the packet received is not an ACK, right?

    4) For a RF12B packet sniffer, the only if would be on rf12_recvDone(), and from there you could split on normal packet and acks with the above #3 in an if, I think. When I wrote a simple sniffer, it didn’t work very well (didn’t calc correct CRC values until I added (rf12_hdr & RF12_HDR_CTL) == 0 to the if along with rf12_recvDone(). Not sure why that would be the case.

    Specifically I had: if (rf12_recvDone()) {

    only produced intermittently correct crcs and once changed to:

    if (rf12_recvDone() && rf12_crc == 0 && (rf12_hdr & RF12_HDR_CTL) == 0 ) {

    it came up with correct CRC values all the time. I didn’t want to have the rf12_crc == as I wanted to see if it was correct or not (like in rf12_demo.pde).

    Thank you for writing this entry and yesterday’s (as well as the rest). It really helps to get a handle on consistent sending.

  2. 1) Yes to the “If…”. I’d use the flags explicitly – perhaps “rf12_sendStart(RF12_HDR_CTL | RF12_HDR_DST | senderID, 0, 0)” ?

    2) Yes.

    3) Yes.

    4) There is often a lot of noise, which leads to false packet sync triggers. So reporting crc != 0 doesn’t show only mangled packets, most of the time the “packet” isn’t really one at all. A better way to get an idea of reception quality would be to include a sequence number in each sent packet, then the receiver sees gaps in the incoming sequence numbers when it misses packets.

    I’d use “if (rf12_recvDone() && rf12_crc == 0) {…}” as main check. You’ll see all valid packets and ACKs. ACKs can be quite important: e.g. in the boot load mechanism I’m working on, the request packets are tiny, while the ACKs contain the sketch payload, i.e. it’s a way for a node to “pull” data.

    Great questions – I hope this clears at least some of it up.

    Update – made some changes to this reply, sorry for the confusion.

  3. Thanks, I just posted the sniffer in the forum: I don’t see ACKs which are being sent directly back to the sender (which is sending out broadcasts requesting ACKs).

    I have just rf12_recvDone() to see the amount of noise that I’m getting.

Comments are closed.