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.

No comments: