19 May 2008

18. All meshed up – Part 4 – Working in code

Finally, we’ll have a look at how the packet routines are called. The best place to start is the pic_packet.h file, where we find the definitions of the config parameters and status messages. Pull up the packet demo project and open the pic_packet.h file. Here are the things we need to set in our config.h:

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// pic_packet defines

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


You’ll need as big a transmit queue as possible, but this really depends on how many close nodes you’ll have in the mesh:

#define PKT_TX_QUEUE_SIZE 5

The “Seen List”, is surprisingly, a list of all the packets that we have seen (that are destined for us). The smaller the seen list, the more likely that we’ll think that a packet is new when it’s not, that is, the smaller the seen list, the more likely we’ll see duplicate packets.

#define PKT_SEEN_LIST_SIZE 5

Send Max Tries is the number of times a node will try and send a packet to the destination. The first time is always a direct send, after that, all other tries are routed sends. Of course, higher retry counts mean the packet spends more time taking up space in the TX queue as well.

#define PKT_SEND_MAX_TRIES 4

As we’ll see later, the tick time for packet fun should be about 0.25ms. In the packet example we set a resend delay of 10,000 ticks – or about 2.5 seconds. In real life, this is way too large, but for demo purposes, it allows us to understand how the packet network works. You should set this delay based on the speed of transmission and the possible number of hops in your mesh to the destination node. Remember, even in ideal conditions you need to transmit the packet through three intermediaries to the destination, then the destination node needs to send a packet back in the same way. It should be as small as possible, of course, but the best value will be found by experimentation.

#define PKT_RESEND_TICK_DELAY 10000

The payload is the data that actually makes it to the other end. This delivery system is designed to handle small messages – not large data transfers. 8 bytes might not sound like a lot, but for controlling lights, sending temperature readings, time and date and status checks, it’s really plenty. It’s a stack bigger than what X10 can move around. You can, of course make this as large as your given transceiver can cope with. The protocol we’ll layer on top of the packet network, however, expects a payload size of at least 8.

#define PKT_PAYLOAD_SIZE 8

There are a couple of callbacks that are not enabled in this demo, but can be defined when you want your code to control a little more of what goes on. You can get a call back when a send fails:

// Define if you want to get a callback on send failure
// #define PKT_CALLBACK_ON_SEND_FAILED
// if you define it, you'll need to include this routine in your code:
// void pkt_send_failed_callback(uns16 dest_addr, uns16 pkt_id) {
// }

Or when a send succeeds:

// Define if you want to get a callback on send success
// #define PKT_CALLBACK_ON_SEND_SUCCEEDED
// if you define it, you'll need to include this routine in your code:
// void pkt_send_succeeded_callback(uns16 dest_addr, uns16 pkt_id) {
// }

The packet delivery system expects at this stage to be running on a Nordic nRF2401A or a nRF24L01 chip. You can mix these interchangeably on the same network, but a given node can of course use only use one or the other. Define one of them here:

// define one or other of these:
#define PKT_USE_2401A
//#define PKT_USE_24L01

And lastly, define the level of debug you want. Note that more debug means more serial_print_str statements, which means much larger code size.

// define if you want debug
//#define PKT_DEBUG
// define if you want high amounts of debug
//#define PKT_DEBUG_HIGH

I’m assuming here that you also have #defines in the config.h file for the RF chipset – this has been covered in a previous tutorial.

Unlike other PicPack libraries we’ve used before, there’s no “setup” routine for the packet library. The setup routine normally gets the ports and pins set up correctly as inputs or outputs, ready for communication with the outside world. This library talks to the world via the RF library, so it doesn’t need to set up any ports and pins itself. It does have an “init” routine though, which is used in a library to get any internal data set to the right values and initialise communication with a device so that it’s ready for use.

pkt_init(my_addr, last_pkt);

pkt_init gets the packet delivery system ready for use. It stores the address given as the local node’s address, and also stores the last pkt ID. All the transmit and last seen queues are cleared and everything is set ready for packet reception or delivery.

So what happens when a chunk of RF data actually arrives? Well, assuming we’re using a SparkFun Terminal Development Node (TDN), you’ll remember that we tied the DR1 line to the PortB, pin 0.

set_bit(intcon, INTE); // Enable interrupts on rb0

The default for the port B, pin 0 interrupt is that it is triggered on a rising edge, that is, when the input signal goes from low to high. Perfect for us, so no changes are required here.

In the packet demo, a routine called configure_pkt() does the work of getting the packet delivery system in order, ready for us.


void configure_pkt() {
uns16 my_addr;
uns16 last_pkt;
my_addr = eeprom_read(EE_MY_ADDR_H);
my_addr <<= 8;
my_addr |= eeprom_read(EE_MY_ADDR_L);
last_pkt = eeprom_read(EE_MY_LAST_PKT_ID_H);
last_pkt <<= 8;
last_pkt |= eeprom_read(EE_MY_LAST_PKT_ID_L);
serial_print_str("My addr: ");
serial_print_int_hex_16bit(my_addr);
serial_print_nl();
serial_print_str("Last pkt: ");
serial_print_int_hex_16bit(last_pkt);
serial_print_nl();
pkt_init(my_addr, last_pkt);
}

Essentially, this just pulls the address and last packet ID sent from the eeprom and calls pkt_init. It also prints out the local address and last packet ID – not essential, but very, very handy! Okay, so the packet system is all ready for use, let’s see what happens when the nRF2401A lets us know that a packet has arrived. It does this by taking the IRQ line high, which results in the INT (Port B, pin 0) being triggered. Here’s the interrupt routine:

void interrupt( void ) {
timer_handle_0_isr();
if ( test_bit(intcon, INTF) ) {
if (pkt_receive_level < MAX_PACKET_QUEUE) { // if we're not already processing a receive
if (pkt_receive_level == 0) {
pic_rf_receive(rf_rx_buffer, PKT_PACKET_SIZE);
} else {
pic_rf_receive(rf_rx_buffer2, PKT_PACKET_SIZE);
}
pkt_receive_level++;
} else {
dropped_packet++;
}
clear_bit( intcon, INTF);
} // pkt available interrupt
serial_handle_tx_isr();
serial_handle_rx_isr();
}

The PKT_FLAG_RESEND is used to indicate that you want the system to resend the packet if it doesn’t get through, up to the maximum number of tries you’ve set in your config.h file. This is the usual case. In normal circumstances, it’s only acknowledgements that get sent using PKT_FLAG_NO_RESEND – and these are taken care of automatically.

That’s it! Not much code required to build your own meshed packet network. Next exciting episode, we’ll look at how we can put an application layer on top of this system for the purpose of delivering actual data across the network. We’ll turn a SparkFun TDN into a remote temperature sensor and display the temperature on the base station on an LCD screen.

16 May 2008

PicPack 1.2 released

PicPack 1.2 has been released! You'll need this version for the complete packet tutorial.

Many thanks to Peter Lawson in sunny South Australia who helped tirelessly getting the BoostBloader running on a new class of Pic chips. With his help, the BoostBloader now supports 16f88, 16f876a, 16f877a, 18f252, 18f452, 18f2620, and 18f4520. It should be quite easy to add other pics as well, I'll write a tutorial about doing that shortly.

Enjoy!



Version 1.2
-----------

16 May 2008

pic_pack_lib

ds1631.c

Rewrote to use i2c base library


packet.c\.h

General update, documentation, a few logic errors fixed,
new callbacks, functions better named

pic_serial.c\.h

Added 16bit hex print routine

Pic_utils.c\.h
Added support for 18f452 for turn_analog_inputs_off()


protocol.h

Tidied up, moved to bit positions to indicate capabilities,

expanded relay and dimmer options

demos

packet

All new meshed packet demo, based on SparkFun Terminal Development

Node (TDN), but will work on your breadboard of course as well. See tutorial on the website

Boostbloader/Screamer

Support for pics that have a write chunk smaller than 16 bytes (older 18f types)

17. All meshed up – Part 3 – Packet chatting

Enough theory. Let’s get our delivery network on the road. Rather than look at the code in this episode (boring), let’s jump straight in and get our nodes chatting. Time enough to see how the code works later.

The packet demo is designed to run on the Sparkfun Terminal Development Nodes (TDNs), which are a very handy piece of gear for testing this all out. They have an RS232 connection on one end, a socket for the nRF2401a / nRF24L01 module on the other and a 16f88 in the middle plus a few leds, which help us know a little of what’s going on. Of course you can run this demo on a breadboard with an nRF module or if you’re feeling adventurous, write some code to support a different RF module all together. There are a bunch out there, do send your code in to include in the library if you get something working. I’m pretty interested in seeing some of the CC2500-based modules going, which are similar in nature to the Nordic ones (but are cheaper and have carrier detection – hooray!) or the modules from the fabulous Futurlec (even cheaper, carrier detection but fascinatingly obscure datasheets – fun!).

For the purpose of this experiment, however, I’ll assume you’ve got a couple of SparkFun TDN (Terminal Development Node) and Nordic modules and a couple of serial ports (USB to serial converters will work fine) or a couple of computers with a serial port each.

The first step, naturally, is to get Boostbloader downloaded onto the 16f88 on each TDN. You’ll need the little mini adapter from SparkFun in order to reach their mini-ICSP connector. See the 5 holes together in the middle of the board? That's the ICSP "socket". Make sure you work out which way round the connector goes – pin 1 is designated by the square pad. The datasheet shows how this is connected, but it’s a pain having to check the datasheet every time you want to use the ICSP connector. Remember: 1 is square.

Once this is done and you’ve confirmed the Boostbloader is doing its thing by downloading a flashing-led test app of your choice (a see the PicPack demo directory if you’re feeling unadventurous), it’s time to look at the packet demo and how it works. Download the “packet” demo into the 16f88 and get ready for some wireless magic.

The 256 bytes of EEPROM in the 16f88 is a great place for a small amount of persistent data. In this example we use it for storing the address of the node, and the ID of the last packet sent.

In the mini terminal mode, there are several commands that we’ll use to control the node. The first is the “w” command, which allows you to set EEPROM bytes at runtime. The first two digits specify the memory location and the third and fourth digits specify the value you want to set it to. “w0523”, for example, sets EEPROM location 0x05 to the value of 0x23 (all values in hex). In our case, we want to set the address of the node and the last sent packet ID like this:

w0000
w0101
w02ff
w03ff
You only need to do this once. The first two memory locations (00 and 01) store the local address; in this case we’re storing a 16 bit value of 0x0001. The third and fourth EEPROM memory locations hold the ID of the last packet we sent. In a more complete example later on, we’ll see how we update this with the last packet ID over time. For the moment, setting it to 0xffff as we do here is fine. This means the first packet this node sends will have an ID of 0x0000, so this is great for understanding what’s going on.

Set your other node to an address of something else other than 0x0001 (I suggest 0x0002), and also set its last sent packet ID to 0xffff.

Right, so now you have two nodes up and running and ready to communicate. One is set to address 0x0001 and the other is set to 0x0002. You can check that these addresses are stored correctly by resetting the TDN (by pressing the reset button) and seeing the serial terminal program come up with something like:

My addr: 01
Last pkt: 65535
Pkt demo
<17:10:36>
Now it’s time to send a payload from one to the other. Here we’ll send a dummy payload that in future we’ll use to query the temperature on another node. Don’t worry about what’s in the payload for the moment, we’re more interested in seeing a packet make it’s way to the destination and get an acknowledgement back. Go to the terminal connected to your 0x0001 node and type:

s02<enter>
This sets the “send to” (destination) address to 0x0002. This demo is slightly slack by only allowing 8 bit address entry, but I’m sure this won’t bother you with only two nodes right now. This setting of the send address needs to be done whenever you boot.

Now, let’s send a packet. Type:

x<enter>
This should send a packet to the specified destination address (0x0002). Since we only have two nodes right now and hopefully they’re quite close, you should find that node 0x0002 responds pretty much immediately. This is what you should see on the node 0x0001 terminal:

>s02
S: 2
>x
>Send...<Snd good to 0002 id 0000> RX:5
And here’s what you should see on the node 0x0002 terminal:

<<s: 01p: 06 01 FF FF FF FF BF FF >> RX:3
So what’s the deal about these numbers? The demo prints out the return values from the functions that get called. We won’t worry about the functions themselves and what they do right now, but we will look at these values, since they reveal the inner workings of the system. Have a look at pic_packet.h for the #define definitions.

RX:3 - A value of ‘3’ on reception means “PKT_STATUS_PKT_IS_FOR_ME”. It’s a real packet and we haven’t seen it before. We can happily act on this packet. In this demo our “acting” on this packet just involves printing it out to see what we’ve got. You can see the packet was sent from a source address of 01, and had a payload of 06 01 FF FF FF FF BF FF.

RX: 5 - A value of ‘5’ on reception means “PKT_STATUS_PKT_IS_FACK_FOR_ME”. It means we’ve received an acknowledgement and found that it is for a packet we’ve sent. That is, we have successfully confirmed delivery of a packet. Woohoo! We’ve just sent our first packet.

If everything is correct and there are no transmission problems, these are pretty much the only values that you’re going to see with a two-node mesh. Not much of a mesh, really. However, we can simulate what happens in the mesh to help us understand how it works in this simple case. Do note that in this example, the delay between retries has been set deliberately, excruciatingly large so that you can clearly see what’s going on.

Set the sending (destination) address of the 0x0001 node to a node that you know is not on your network (eg, 0x0003) by typing:

s03<enter>
Now from the 0x0001 node try and send a packet to node addressed 0x0003 by typing:

x<enter>
Now, remember from our previous tutorial that we will first try and send to node 0x0003 directly (a “direct send”). You’ll see that node 0x0002 reports a status of PKT_STATUS_DIRECT_SEND (6). This means that it has received the packet, but will not rebroadcast it since the originating node tried to send it directly (no routing).

Since the first packet never got to its destination (an acknowledgement was not received), the node will try to send it again. On the next try however, our node 0x0001 will try and route the packet via anyone that’s listening (a “routed send”). In this simple case, we know that node 0x0002 is listening. Node 0x0002 should respond with PKT_STATUS_NEED_TO_REBROADCAST (9). So 0x0002 rebroadcasts to try and get it to 0x0003 (of course, it doesn’t know that 0x0003 doesn’t exist on our little network).

Now here’s where it gets a bit tricky, but once you’ve got your head around this, you’ll be well on your way to understanding how this all works.

To recap up to this point:

0x0001 has tried to send directly to 0x0003
0x0002 saw it and printed PKT_STATUS_DIRECT_SEND (6) showing that it decided to ignore the packet (it saw that it’s a direct send not destined for itself)
…in time…
0x0001 tried again, this time routing via anyone that’s listening (not a direct send)
0x0002 saw it and responded PKT_STATUS_NEED_TO_REBROADCAST (9), and rebroadcast the packet out to anyone listening (hopefully it can reach 0x0003, or at least someone closer to 0x0003)

Now at this point, 0x0001 should receive this rebroadcast. But it’s the one that sent the packet in the first place! So it should respond with PKT_STATUS_I_AM_SENDER (2) and ignore the packet.

Finally, 0x0001 will give up, printing out

<Send failed to 0003 id 0001>
to show that we’ve had a failed send.

This confirms that 0x0002 has tried to pass the packet on deeper into the mesh, and also confirmed that 0x0001 is happily ignoring this bounce-back. Of course since our imaginary 0x0003 never received the packet, our originating node will try once more. Our plucky little node 0x0002 tries again to forward on into the mesh, but alas, the packet never makes it. If you have no friends, they’re not going to answer, not matter how many times you call.

Clear this is all complete overkill for two nodes (although you do get retries and acknowledged delivery for free). Once you get three or more nodes where some nodes can’t see all the others, I hope you’ll see how powerful this can be for delivering short messages. Next time we’ll have a look at the calls into this library and how they work, finishing our series on the provided packet delivery system works – then later we’ll also cover some implementations of the system, in the first instance delivering temperature data from different nodes.

13 May 2008

16. All meshed up - Part 2 - Packet network basics

Now we’ve worked out how to get a chunk of data from one place to another without any wires, it’s time to look at how the PicPack packet network actually works. In the next enthralling instalment we’ll get the actual packet network up and running in code.

Okay, so here’s the problem I was trying to solve. How do you get a message to a device on the other side of the house, reliably? The distance (with brick walls) is going to be too great for a given, cheap, RF transceiver to reach, but we’re pretty much guaranteed to have devices in between the two – so could we use those as relays? It would be pretty nice not to have to configure the network (ie, direct exactly how the network gets messages from one place to another) - since the network may change and configuration of little hardware devices is difficult from the other side of the planet.

So what I wanted was effectively a meshed packet network. But there’s a catch. We want to do this as cheaply as possible, not using devices with large chunks of memory, but on something like a 16f88 – 4k instructions, 384 bytes of RAM. Could it be done?

The trick here is working out a way to discover network routes automatically in small amounts of code. My first thought was a method by which nodes could ask their neighbours if they knew about the node we wanted to talk to. And then if they didn’t know, they could ask their neighbours. Once we had the route, we could store it. Great, so now we’re storing a routing table. And how long do we store it before we throw it out and have to rediscover again when something has changed? Timewise, by the time we’ve asked all our neighbours to ask their neigbours about the node I want to send to, collected the responses, sorted them, then sent the message, well, this is all getting complicated and memory intensive, along with the next ice age having kicked off.

Well, it’s all about tradeoffs, as we know from other tutorials. So here’s our strategy. If our destination node is within range, we’ll send it directly. It then sends an acknowledgement so we know it arrived safely. If it’s not within range, we get everyone that can hear us to broadcast the message. Hopefully, it will be in range of them, and we’ll get an acknowledgement back. In fact, we’ll allow up to three “hops” of rebroadcasts before giving up.

This does mean we’re going to have a lot of traffic bouncing around at times. We minimise this by the three hop rule and also by checking to see we haven’t seen this packet recently (if we have, just ignore it). It will also mean that a packet may find its way by several means to the destination address. We cope with this by sending an acknowledge back for the first one, and then acting on the packet. If another copy of the same packet arrives – we realise that the packet has already been received and don’t act on the packet, but we do send another acknowledge back, since this one may have come a second time because the originator didn’t get our first acknowledge.

It all sounds a little complex, but in use you just set your local address and start sending out packets. It’s a good project to show how the various parts of the PicPack we’ve seen so far come together. And despite all this complexity, we have a meshed packet RF network in 3k of code. Not bad. That leaves you with at least another 1k of code to actually do something with those packets.

Next time we'll have a look at the code that gets the packet network up and running.

6 May 2008

15. All meshed up - Part 1 - RF comms

I mean, really. Who doesn’t want to create their own wireless, meshed packet delivery network? This stuff is just, like, cool.

This project all began when my elderly but still energetic parents rebuilt their house and decided in a moment of weakness to allow me to design the lighting and control systems. So naturally I had all the light switches installed as X10 controllers and all the lights themselves connected via X10 lamp dimmers. Then a bunch of X10 PIR sensors send movement information to the house controller PC via a third party receiver. Then as you walk through the house the lights come on when you’re in the room and the controller PC also knows which lights are on.

So it goes in theory. In practice, every now and then an X10 lamp dimmer dies which is a total pain – they’re mounted in the wall cavities after all. Sometimes the modules forget their settings, and the X10 protocol doesn’t actually make it all the way through the house, despite adding an expensive module that’s meant to relay and amplify the signals. And with me living in London and them in Australia, it’s all a little difficult when it goes wrong. Still, it’s cool being able to control their lights from the other side of the planet.

My plan was to replace the unreliable X10 controllers and lamp dimmers with RF modules. Of course you could never guarantee that the modules could talk to each other directly, my parents have internal brick walls that really wreak havoc with anything wireless. So the network would need to be resilient to this type of problem.

And as soon as you start creating something like this, you start to see lots of ways it could be used. Temperature and humidity sensors, real time clock information, movement sensors… you name it!

I did look at Zigbee of course, and that’s great in theory, but the most popular modules require a star network with controllers and don’t actually do proper meshing. Besides, I thought it would be fun to design my own.

I started out with the Sparkfun nrf2041a modules and a couple of their Serial Development Node (version 1). These are designed to work together and the SDNs provide translation of signals to RS232, a 16f88 pic on board and spot to plug a 2401a module straight into. Perfect!

The first chunk of work was to get the actual Nordic transceiver nrf2401a chips working and chatting away to each other. Let’s look at that first, and then in the next article, look at how the packet delivery network works.

The PicPack library supports both the nRF2401A and the nRFL01 chips, seamlessly and simultaneously on the same network. We’ll have a look at the 2401a here. Open up the packet_demo project.

Naturally the first thing you need to define the ports and pins used to communicate with the 2401a in your config.h file:

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// pic_rf include
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// For SFE_TDN_V1 board
#define rf_ce_port PORTA
#define rf_ce_pin 6
#define rf_dr1_port PORTA
#define rf_dr1_pin 3
#define rf_cs_port PORTA
#define rf_cs_pin 0
#define rf_data_port PORTA
#define rf_data_pin 2
#define rf_clk1_port PORTA
#define rf_clk1_pin 1
These are the ports and pins used by the SparkFun Terminal Development Node modules. There is, however, a catch. In their ultimate wisdom, the SparkFun guys (bless ‘em) wired the DR1 output from the 2401a to port a, pin 3. The DR1 “Data Ready 1” output goes high when a packet has been received. Now, this doesn’t seem like much of an issues, except that we’d really like to interrupt whatever the pic is doing at the time when a packet arrives. All this polling to find out what’s going on stuff is just silly. So you’ll need to wire the porta pin 3 to port b pin 0. It’s easy, you just need to install a “link” in the alternative connector on the board. You can see from the picture that it's the second and fourth holes on the second row back that need linking.

So, the first thing you need to do, as usual, is run the setup routine that’s part of the library to set the ports and pins to be the right inputs or outputs:

pic_rf_setup();
Then, as usual, you’ll need to actually initialise the hardware itself for use. The trickiest part about using the 2401a is that it pretty much sits there like a dead duck until you get the configuration right, and then it springs into life. Until then, you really have no idea what you’ve got wrong. PicPack provides two routines for configuring your 2401a. We’ll look at the more verbose one first. You need to setup the rf_config struct with all the settings you care about – then pass a pointer to it to the pic_rf_init() routine. Let’s work through the config.

rf_config my_config;
The Nordic transceivers can receive on two (or more) channels at once. We don’t care about this for our purposes, so we set the payload size (or width, in bits) to zero.

rf_config my_config;
For our primary channel, we do care – it has to be the same size as (in this case) our packet size.

my_config.payload_width_ch1 = PKT_PACKET_SIZE * 8;
Here we fill in the address for channel 2, although it’s actually not important want you do or don’t do with these values:

my_config.address_ch2[0] = 0xf0;
my_config.address_ch2[1] = 0xf0;
my_config.address_ch2[2] = 0xf0;
my_config.address_ch2[3] = 0xf0;
my_config.address_ch2[4] = 0xf0;
When it comes to the channel 1 address, we do care. Here’s the default address for Nordic chips, and in this case, we’re using address 0xE7E7E7. We specify two more bytes than we need just for completeness.

my_config.address_ch1[0] = 0b11100111; // addr starts here
my_config.address_ch1[1] = 0b11100111;
my_config.address_ch1[2] = 0b11100111;
my_config.address_ch1[3] = 0b11100111; // only used three but fill
my_config.address_ch1[4] = 0b11100111; // ...for the fun of it
In order to tell the chip we’re using 3 byte addresses, we set the address_width to 24 (8*3).

my_config.address_width = 24; // (6 bits valid)

We want to have a 16 bit CRC check on the packet. This is how the chip tells a real packet from noise in the air. At 1 Mbps you’d be amazed how often noise turns out to give correct CRC values with 8 bit CRCs. 16 bit CRC is much better, but is still not 100%. That’s why the PicPack library has its own check byte as well.

set_bit(my_config.options, OP_LONG_CRC);
Now we enable having a CRC as part of the transmission:

set_bit(my_config.options, OP_ENABLE_CRC);
Turn off the second channel:

clear_bit(my_config.options, OP_ENABLE_CH2);
Shockburst is the name Nordic gives for clocking your packet in to the chip and then sending it out on-air at the correct bit rate in one go.

set_bit(my_config.options, OP_ENABLE_SHOCKBURST);
You can turn this bit off to enable 250kbps rate, which does give slightly longer range. 1 Mbps gives you less chance of packet collisions, which is why I recommend it (as well as giving compatibility with the nrf24L01 chip).

set_bit(my_config.options, OP_ENABLE_1_MBPS);
On the SparkFun boards, they’re wired up with a 16Mhz crystal, which is why we choose that option here:

// (3 bits valid) -> 16Mhz
my_config.crystal = 0b011;
You have two bits (4 levels) of output power:

// (2 bits valid) 11 -> max power!
my_config.output_power = 0b11;
We can choose any of a gazillion channels, and the Nordic chip can hop around then quite quickly. Still, at this stage of the library, we pick one at the start and leave it at that. I thought it would be good to look at a frequency hopping algorithm, but this does get hard when you have a meshed network. Fine when it’s one on one, but when there’s three or more units, how does everyone keep in sync? If you have any ideas, I’d love to hear them. In the mean time:

my_config.channel = 2; // (7 bits valid)
Last but not least, we put the chip into the receive mode:

set_bit(my_config.options, OP_ENABLE_RECEIVE);
And then with a little flourish, call the routine that takes this information and passed it to the 2401a to get it initialised.

pic_rf_init(&my_config);
As you can imagine, this programmatic way of telling the Nordic chip what to do is easy to understand and gives you the ability to change things at run time. You’ll also realise that this does take a chunk of code (and hence flash) to do all this. If all you want to do is set the config right at the start and not change it after that (other than the channel) then you can use the quick method:

pic_rf_quick_init("\x00\xa8\xf0\xf0\xf0\xf0\xf0
\xe7\xe7\xe7\xe7\xe7\x63\x6f\x05",
2, 1);
This does exactly what the previous chunk of code did. The ‘2’ parameter is the channel number, and ‘1’ turns the receiver on. To generate this config string, use the nrf2401a_config.pl script in the tools directory. You don’t need to know anything about perl (other than having it installed somewhere!) – just edit the file and run it to get it to spit out the config string.

So how do you go about sending data across the link? Well, here’s how the packet routines do it:

void pkt_send_packet(rf_packet *packet) {
// +3 for RF address (fixed)
uns8 tx_buffer[PKT_PACKET_SIZE + 3];
uns8 count;
tx_buffer[0] = 0b11100111; // address
tx_buffer[1] = 0b11100111; // address
tx_buffer[2] = 0b11100111; // address
for (count = 0; count < PKT_PACKET_SIZE; count++) {
tx_buffer[count+3] = packet->a[count];
}
pic_rf_transmit(tx_buffer, PKT_PACKET_SIZE + 3);
}
You can see it’s as simple as whacking the destination address on the start. In the case of the packet network, we’re assuming everyone’s on the same address. Of course, you could use these routines to direct your data to a particular node directly using this addressing.

Receiving data is only slightly more complicated. Since we’ve wired up the DR1 line to the port B, pin 0, it means that we can use an interrupt to detect when the nRF2401a wants to tell us something. This is much better than having to poll for data all the time. The 16f88 has two ways of detecting interrupts, but the one we want is when port B, pin 0 goes high. It’s set up like this:

make_input(PORTB, 0);
set_bit(intcon, INTE);
You’ll need to turn interrupts on as well:

turn_global_ints_on();
In your interrupt() routine you can use something like this:

if ( test_bit(intcon, INTF) ) {
pic_rf_receive(rf_rx_buffer, MY_BUFFER_SIZE);
clear_bit( intcon, INTF);
}
Notice how you test for the interrupt flag being set, respond if necessary, and then clear the flag.

That’s all there is to it. Next tutorial we’ll start looking at how to create a packet network on top of the RF communication we’ve set up here.