The RF12 driver used for the RFM12B module on JeeNodes makes a bunch of assumptions and has a number of fixed design decisions built-in.
Here are a couple of obvious ones:
- nodes can only talk to each other if they use the same “net group” (1..250)
- nodes normally each have a unique ID in that netgroup (1..31)
- packets must be 0..66 bytes long
- packets need an extra 9 bytes of overhead, including the preamble
- data is sent at approximately 50,000 baud
- each byte takes ≈ 160 µs, i.e. a max-size packet can be sent in 12 milliseconds
So in the limiting case you could have up to 7,500 different nodes, as long as you keep in mind that they have to share the same frequency and therefore should never transmit at the same time.
For simple signaling purposes that’s plenty, but it’s obvious that you can’t keep a serious high-speed datastream going this way, let alone multiple data streams, audio, or video.
On the 433 or 868 MHz bands, the situation is often worse than that – sometimes much worse, because simple OOK (which is a simple version of ASK) transmitters tend to completely monopolize those same frequency bands, and more often than not, they don’t even wait for their turn so they also disturb transmissions which are already in progress! Add to that the fact that OOK transmitters often operate at 1000 baud or less, and tend to repeat their packets a number of times, and you can see how that “cheap” sensor you just installed could mess up everything!
So if you’ve got a bunch of wireless weather sensors, alarm sensors, or remotely controlled switches, chances are that your RF12-based transmissions will frequently fail to reach their intended destination.
Which is why “ACKs” are so important. These make it possible to detect when packets get damaged or fail to arrive altogehter. An ACK is just what the name says: an acknowledgement that the receiver got a proper packet. No more no less. And the implementation is equally simple, at least in concept: an ACK is nothing but a little packet, sent the other way, i.e. back from the receiver to the original transmitter.
With ACKs, transmitters have a way to find out whether their packet arrived properly. What they do is send out the packet, and then wait for a valid reply packet. Such an “ACK packet” need not contain any payload data – it just needs to be verifiably correct (using a checksum), and the transmitter must somehow be able to tell that the ACK indeed refers to its original packet.
And this is where the RF12 driver starts to make a number of not-so-obvious (and in some cases even unconventional) design decisions.
I have to point out that wireless communication is a bit different from its wired counterpart. For one, everyone can listen in. Radio waves don’t aim, they reach all nodes (unless the nodes are at the limit of the RF range). So in fact, each transmission is a broadcast. Whether a receiver picks up a transmitted packet is only a matter of whether it decides to let it through.
This is reflected in the design of the RF12 driver. At the time, I was trying to address both cases: broadcasts, aimed at anyone who cares to listen, and directed transmissions which target a specific node. The former is accomplished by sending to pseudo node ID zero, the latter requires passing the “destination” node ID as first argument to rf12_sendStart().
For the ACK, we need to send a packet the other way. The usual way to do this, is to include both source and destination node ID’s in the packet. The receiver then swaps those fields and voilá… a packet ready to go the other way!
But that’s in fact overkill. All we really need is a single bit, saying the packet is an ACK packet. And in the simplest case, we could avoid even that one bit by using the convention that data packets must have one or more bytes of data, whereas ACKs may not contain any data.
This is a bit restrictive though, so instead I chose to re-use a single field for either source or destination ID, plus a bit indicating which of those it is, plus a bit indicating that the packet is an ACK.
With node ID’s in the range 1..31, we can encode the address as 5 bits. Plus the src-vs-dest bit, plus the ACK bit. Makes seven bits.
Why this extreme frugality and trying to save bits? Well, keep in mind that the main use of these nodes is for battery-powered Wireless Sensor Networks (WSN), so reducing power usage is normally one of the most important design goals. It may not seem like much, but one byte less to send in an (empty) ACK packet reduces the packet length by 10%. Since the transmitter is a power hog, that translates to 10% less power needed to send an ACK. Yes, every little bit helps – literally!
That leaves one unused bit in the header, BTW. Whee! :)
I’m not using that spare bit right now, but it will become important in the future to help filter out duplicate packets (a 1-bit sequence “number”).
So here is the format of the “header byte” included in each RF12 packet:
And for completeness, here is the complete set of bytes sent out:
So what are the implications of not having both source and destination address in each packet?
One advantage of using a broadcast model, is that you don’t have to know where to send your packet to. This can be pretty convenient for sensor nodes which don’t really care who picks up their readings. In some cases, you don’t even care whether the data arrived, because new readings are periodically being sent anyway. This is the case for the Room Nodes, when they send out temperature / humidity / light-level readings. Lost one? Who cares, another one will come in soon enough.
With the PIR motion detector on Room Nodes, we do want to get immediate reporting, especially if it’s the first time that motion is being detected. So in this case, the Room Node code is set up to send out a packet and request an ACK. If one doesn’t come in very soon, the packet is sent again, and so on. This repeats a few times, so that motion detection packets reach their destination as quickly as possible. Of course, this being wireless, there are no guarantees: someone could be jamming the RF frequency band, for example. But at least we now have a node which tries very hard to quickly overcome an occasional lost packet.
All we need for broadcasts to work with ACKs, is that exactly one node in the same netgroup acts as receiver and sends out an ACK when it gets a packet which asks to get an ACK back. We do not want more than one node doing so, because then ACKs would come from different nodes at the same time and interfere with each other.
So normally, a WSN based on RFM12B’s looks like this:
The central node is the one sending back ACKs when requested. The other nodes should just ignore everything not intended for them, including broadcasts.
Note that it is possible to use more than one receiving node. The trick is to still use only a single one to produce the ACKs. If you’re using the RF12demo sketch as central receiver, then there is a convenient (but badly-named) “collect” option to disable ACK replies. Just give “1c” as command to the second node, and it’ll stop automatically sending out ACKs (“0c” re-enables normal ACK behavior). In such a “lurking” mode, you can have as many extra nodes listening in on the same netgroup as you like.
To get back to netgroups: these really act as a way to partition the network into different groups of nodes. Nodes only communicate with other nodes in the same netgroup. Nodes in other netgroups are unreachable, and data from those other nodes cannot be received (unless you set up a relay, as described a few days ago). If you want to have say hundreds of nodes all reporting to one central server, then one way to do it with RF12 is to set up a number of separate netgroups, each with one central receiving node (taking care of ACKs for that netgroup), and then collect the data coming from all the “central nodes”, either via USB, Ethernet, or whatever other mechanism you choose. This ought to provide plenty of leeway for home-based WSN’s and home-automation, which is what the RF12 was designed for.
So there you have it. There is a lot more to say about ACKs, payloads, and addressing… some other time.
Another topic worth a separate post, is using (slightly) different frequencies to allow multiple transmissions to take place at the same time. Lots of things still left to explore, yummie!