RF12 MiMac Library

RF12 C library for PIC 18F (MiMac compatible)

Print
Category: Projects
Published Date Written by Francois

RF12 library example

Fig. 1 RF12 library example

Description:

This project came from a need of a basic wireless protocol using cheap sub-GHz rf modules, like the RF12 from HopeRF. It quickly evolved into something more and more complicated.

After some research, I settled on the MiWi™ Development Environment from Microchip. However, the library is not for the CCS compiler.

So here is the first section, providing a library which is compatible with the MiMac layer of the Microchip solution.

This first part is not a full network protocol, just the physical and MAC layer of it, which still allows reliable and secure communication between wireless nodes.

It features:

  • Easy to implement C library for the CCS compiler
  • Powerful enough for most short range low data rate wireless network
  • Optional acknowlegment on each packet
  • Optional security module (simple and strong)
  • Simple API to upper layer protocol libraries.

The example provided simply sends and receives packets between 2 "nodes".

In simple terms, the library provides a set of functions to transmit a packet with various options, and receives packet with interrupts and store them in a buffer until they are processed by the main program.

The MiMac layer is well documented on the Microchip web site. So the following description will be brief, to try to simplify it. The purpose of the MiMac layer is to be able to send and receive packets over wireless network. It can use various transceiver, and we concentrate here on the RF12 modeule from HopeRF. It is compatible with any subGHz transceivers based on the Si4420 transceiver chip from Silicon Labs.

A packet is formatted as follow: (numbers between brackets are number of bytes, or bit between square brackets)

Packet or Frame:       Preamble(x),sync(2),Length(1),MAC header(2-21),MAC payload(x),CRC(2)
The preamble is a number of similar byte to prepare the receiver, and immune from noise. Sync is a 2 byte "header" that signifies to the receiver that a packet os coming. This pattern is what generates the interrupt. Length is a one byte that tells how many bytes needs to be received. MAC header contains configuration flags, like broadcast, encryption, source and/or destination address. It is described below. Then comes the payload itself, finished by a 16bits CRC of the packet (From the length included to the last byte of the payload.
MAC header bytes:   Frame control(1), Extra control(0-3), Seqence number(1), destination address(0-8), source address(0-8)
The frame control (detailed below) contains the configuration flags around broadcast, encryption... Extra control is used by higher layers, not described or used here. The sequence number allows to identify a packet. It is an incremental number. When an Ack is requested, this byte is sent back as a broadcast to notify the sender that the node received the packet. Destination and source address are holders for the actual value of each.
Frame control (bit 7 to 0): Src present[1], Dest present[1], Ack requested[1], Repeat enabled[1], Encryption enabled[1], Broadcast[1], Packet type[2] (Packet type: 00= data, 01= command, 10= Ack, 11= reserved (raw data))

The functions provided are:

  • Interrupt when receiveing packet: when a packet is seen over the wireless frequency, it is received and checked. If it is a broadcast packet, or addressed for the receiving node, it is stored in a receive buffer. If a packet is encrypted, it is also decrypted before being stored. The variable RF_BANK_SIZE defines the maximum number of packets that can be stored in the buffer before being processed by the main program. If the buffer is full, the received packet is ignored.
  • MiMAC_Init: Initialise the MiMac layer and the transceiver with some options. The options are:
    • CCAenable: will check if the channel/frequency is free before sending packet if option is set.
    • PAddrLength: length of the permanent address of the node (similar to MAC address). It is set to 4 in this example.
    • Pointer to the permanent address of the node
      The repeater mode and network freeze features from the Microchip version are not implemented here.

      The function also initialise the transceiver module with the parameters set in the ConfigRF12.h file. It is described later.
  • MiMAC_SendPacket: sends packet with various options, set in the transParam variable passed to the function. The options selected will format the MiMac header when transmitting the packet.
  • MiMAC_ReceivedPacket: returns true if a packet is ready to be processed in the receive buffer. The variable ReceivedBankIndex will contain the buufer index of the packet received.
  • MiMAC_DiscardPacket: set the buffer of the received packet as free again. Used once a packet has been processed.
  • MiMAC_SetChannel: set the operating channel of the transceiver. The RF12 module doesn't implement channel as such, it just sets a centre frequency to operate on.
  • MiMAC_SetPower: sets the transmitting power of the module

A few other functions are present for future compatibilty with wireless protocols like MiWi or P2P.

You can download the files for this project in the zip file RF12_pic_C_library_1.zip.

Requirements:

Processor board2 boards, (boards 5 and 6 in example)

 Ideally with hardware SPI interface

Extension(s)2 RFM12 board

Power supplyPSU 1, 2 or 3. Depends on board used.

LanguageCCS C

ProgrammerPicKIT 2


Program:

The 2 boards are running exactly the same program.

It basically sends a packet every second, and display any packet received on the channel on the console.

//* define CPU board used: 4: 18F2320, 5: 18F26J11, 6: 18F26J50
#define board_id 5
#if (board_id == 4)
  #define RF_BANK_SIZE 2 // PIC 18F2320 has limited RAM, reduce bank size
#endif
#include "MiMac_example.h" // Load application configuration

#if (board_id == 5)
  int8 TEST_STRING[] = "Hello 18F26J11";
#else
  int8 TEST_STRING[] = "Hello World !!";
#endif
BOOLEAN ret;
MACINIT_PARAM MIP; // MiMac initialization parameters
MAC_TRANS_PARAM MTP; // MiMac configuration to transmit a packet
#if defined(ENABLE_SECURITY)
  DWORD_VAL IncomingFrameCounter[CONNECTION_SIZE];
#endif
TICK_T txTime1, txTime2;
#if (board_id == 5) // defines address to send packet too
  BYTE DestinationAddress[MY_ADDRESS_LENGTH] = {0x06,EUI_1,EUI_2,EUI_3};
#else
  BYTE DestinationAddress[MY_ADDRESS_LENGTH] = {0x05,EUI_1,EUI_2,EUI_3};
#endif

void main() {
  BYTE i;
  boot_up(); // Initialise processor board
  ConsoleInit(); // Initialise debug console
  MIP.actionFlags.Val = 0; // MiMac protocol configuration
  MIP.actionFlags.bits.PAddrLength = MY_ADDRESS_LENGTH;
  MIP.actionFlags.bits.CCAEnable = 1;
  MIP.PAddress = myMacAddress;
  MTP.flags.Val = 0; // Setup parameters to transmit packet
  MTP.flags.bits.packetType = PACKET_TYPE_DATA;
  #if (board_id == 6) // one packet is sent as broadcast, encrypted
    MTP.flags.bits.broadcast = 1; // Enable broadcast
    MTP.DestAddress = 0;
    MTP.flags.bits.secEn = 1; // Packet secured
    MTP.flags.bits.ackReq = 0; // No Ack required
  #else // the other one with destination and source address present, and with Ack request
    MTP.flags.bits.broadcast = 0; // Disable broadcast
    MTP.flags.bits.destPrsnt = 1; // Destination address present
    MTP.flags.bits.sourcePrsnt = 1; // Source address present
    MTP.DestAddress = DestinationAddress; // Update pointer to destination address
    MTP.flags.bits.secEn = 0; // Packet not secured
    MTP.flags.bits.ackReq = 1;
  #endif
  MiMAC_Init(MIP); // Initialise RF transceiver and MiMac configuration
  enable_interrupts(GLOBAL);
  printf(ConsolePutc, "RF12 Chan %d ", RF12_CHANNEL);
  txTime1 = TickGet(); // Read timer
  while(true) { // main loop
    txTime2 = TickGet();
    if (TickGetDiff(txTime2, txTime1) > ONE_SECOND) { // If more than 1 second elapsed since last transmit
      ret = MiMAC_SendPacket(MTP, TEST_STRING, sizeof(TEST_STRING)-1); // transmit packet
      printf(ConsolePutc, "Tx: %u\r\n", ret); // output Tx result
      txTime1 = txTime2;
    }
    if( TRUE == MiMAC_ReceivedPacket() ) { // Checks if a packet was received. If so, display it and then discard it
      printf(ConsolePutc, "Rx: %u,%X,%u,%u,", MACRxPacket.PayloadLen, MACRxPacket.flags.Val, MACRxPacket.RSSIValue, MACRxPacket.LQIValue);
      for(i=0; i<MACRxPacket.PayloadLen; i++) { // output characters received
        ConsolePutc(MACRxPacket.Payload[i]);
      }
      MiMAC_DiscardPacket();
      printf(ConsolePutc, "\r\n");
    }
  }
}


PicKIT2 UART tool

Fig. 2 PikKIT2 UART tool with console output

First, we define the board used. For the 18F2320, we reduce the number of packet that can be stored before being processed to 2, due to RAM limitation.
Board_id is also the last byte of the MiMAC address of the node.

We then load the header for the project, which basically configure various elements, like transceiver used, pin connections, MiMAC address, and loads all required libraries.

Then some variables are declared to run the program: sentence to transmit, configuration for MiMac layer and transmit parameters.
The destination needs to be the address of the receiving node. This doesn't matter if the packet is broadcasted, but if it is send to a specific MiMac address, it needs to match the recevieing node, else the packet will be ignored. Only the first byte is different from the transmitter's address.

The board is initialised, followed by the console. The console points to the programmer connection, so it is easy to switch the PicKIT UART tool on, and see the output on it.

The MiMac layer is initialised with the MIP variable: set the nodes address length (4 here), CCA is enabled, which means before transmitting, the MiMac layer will check that the channel is clear to transmit. Finally, it points to the MiMac address of the node.

The MTP variable defines how packets will be transmitted: packet type (data, control,...), broadcast, encrypt, repeater, ack, destination and source flags for the packet. Each nodes uses different combination to test various options.

The MiMac layer is then initialised, which also initialise the transceiver, and the main loop entered.

The main loop just checks if 1 second has elapsed since the last transmission, and if so, sends a packet with the configuration defined in MTP.
If a packet was received, it sends it to the console.

Now to the project header:

#ifndef _CONFIG_APP_H
  #define _CONFIG_APP_H
/*********************************************************************/
// following codes defines the platforms as well as the hardware configuration
/*********************************************************************/
  #if (board_id != 6) // define connections of the RF12 module to the mainboard
    #define RF12_CS CS_SPI0
    #define RF12_INT INT_EXT
    #define RF12_INT_PIN PIN_B0
    #define LED_1 PIN_A1
    #define LED_2 PIN_A2
  #endif
/*********************************************************************/
// ENABLE_CONSOLE will enable the print out on the PicKit2 serial terminal.
// This definition is very helpful in the debugging process
/*********************************************************************/
#define ENABLE_CONSOLE
//------------------------------------------------------------------------
// Definition of RF Transceiver. ONLY ONE TRANSCEIVER CAN BE CHOSEN
//------------------------------------------------------------------------
  #define RF12
/*********************************************************************/
// MY_ADDRESS_LENGTH defines the size of wireless node permanent
// address in byte.
/*********************************************************************/
  #define MY_ADDRESS_LENGTH 4
/*********************************************************************/
// EUI_x defines the xth byte of permanent address for the wireless node
/*********************************************************************/
  #define EUI_7 0x11
  #define EUI_6 0x22
  #define EUI_5 0x33
  #define EUI_4 0x44
  #define EUI_3 0x55
  #define EUI_2 0x66
  #define EUI_1 0x77
  #define EUI_0 board_id
// Constants Validation
  #if MY_ADDRESS_LENGTH > 8
    #error "Maximum address length is 8"
  #endif
  #if MY_ADDRESS_LENGTH < 2
    #error "Minimum address length is 2"
  #endif
  #include "../lib/Boards/mainboard.c" // Load basic libraries
  #include "../lib/Common/console.c"
  #include "../lib/Transceivers/MiMAC.c" // Load wireless libraries
#endif

It basically defines how the module is connected to the board, enables the console mode, select the RF12 transceiver, permanent address length and permanent address.
It then loads the board library, console library and the MiMac libraries.

For connection, this is how it is setup (The minimum is CS, MISO, MOSI, SCK and INT):

  • RF12_CS: pin where the CS line of the module is connected to on the processor.
  • RF12_INT: interrupt used by the module (default to int_ext)
  • RF12_INT_PIN: pin where the IRQ signal from the module is connected to on the processor (default to RB0)
  • MISO: master in / slave out for the SPI bus. Pin where the SDO pin from the module is connected to on the processor
  • MOSI: master out / slave in for the SPI bus. Pin where the SDI pin from the module is connected to on the processor
  • SCK: SPI bus clock.
  • nFFS (optional): if connected, the library will use that connection to read the FIFO from the transceiver, which is more efficient.
  • FFIT (optional): if connected, the library will use that signal to check if data is available in the FIFO of the transceiver, which is again more efficient.

The Led1 and Led2 are optional. They will be turned on when transmitting and receiving respectively.

The MiMac libray is fairly big, and I will not detail it here. The code is commented, which should be enough if you fancy getting into it.
However, a few things are critical:

  • RF12 needs to be defined. It tells the library which transceiver to work with, and it is the only one supported for now.
  • ConfigRF12.h is the file that contains the settings for the radio link, like operating frequency...

Here are the details for this file:

#ifndef _CONFIG_RF12_H
  #define _CONFIG_RF12_H
  #ifdef RF12
    // Hardware config
    #ifndef RF12_CS
      #define RF12_CS CS_SPI0 // default CS line to CS_SPI0
    #endif
    #ifndef RF12_INT // default interrupt to INT_EXT
      #define RF12_INT INT_EXT
    #endif
    #ifndef RF12_INT_PIN // pin for interrupt default to EXT0
      #define RF12_INT_PIN PIN_B0
    #endif
    // default operating radio channel (if not using scans or join network)
    #ifndef RF12_CHANNEL
      #define RF12_CHANNEL 0
    #endif
    //#define RF12_TRANSMIT_ONLY // Disable receiver functions
    // Defines band used (Only one)
    #define RF12_BASEBAND RF12_BAND_433
    //#define RF12_BASEBAND RF12_BAND_868
    //#define RF12_BASEBAND RF12_BAND_915
    // Defines transceiver baud rate (Only one)
    //#define RF12_DATA_RATE RF12_DATA_RATE_1200
    #define RF12_DATA_RATE RF12_DATA_RATE_9600
    //#define RF12_DATA_RATE RF12_DATA_RATE_19200
    //#define RF12_DATA_RATE RF12_DATA_RATE_38400
    //#define RF12_DATA_RATE RF12_DATA_RATE_57600
    //#define RF12_DATA_RATE RF12_DATA_RATE_115200
    // Defines the capacitor load on the external crystal
    #define RF12_XTAL_LD_CAP RF12_XTAL_10
    // Defines the internal low noise amplifier gain
    #define RF12_LNA_GAIN RF12_LNA_GAIN_0
    // Defines the output power for RF12
    #define RF12_TX_POWER RF12_TX_POWER_0
    // Defines the threshold for the RSSI digital output
    #define RF12_RSSI_THRESHOLD RF12_RSSI_THRESHOLD_97
    // Enables low battery detection
    #define ENABLE_LOW_BATTERY
    // RF_BANK_SIZE defines the number of received packet that can be stored waiting for processing.
    #ifndef RF_BANK_SIZE
      #if defined(__PCM__)
        #define RF_BANK_SIZE 1 // set to one for PCM devices
      #else
        #define RF_BANK_SIZE 4
      #endif
    #endif
    // MAX_ALLOWED_TX_FAILURE defines the maximum number of tries to
    // transmit a packet before a transmission failure can be issued to
    // the upper protocol layer. Transmission failure under this condition
    // are usually due to timeout from RF12 pin switch.
    #define MAX_ALLOWED_TX_FAILURE 4
    // Enables the module to perform Clear Channel Assessement before transmitting data.
    #define ENABLE_CCA
    // Enables RF12 to automatically send back an acknowledgement packet
    // after receiving a packet, when it is requested by the sender.
    #define ENABLE_ACK
    // Enables RF12 to retransmit the packet up to RETRANSMISSION_TIMES,
    // if ENABLE_ACK is defined, and a proper acknowledgement packet is not
    // received by the sender in predefined time period.
    #define ENABLE_RETRANSMISSION
    // RETRANSMISSION_TIMES defines the maximum number of transmit that can be performed
    // if a proper acknowledgement packet is not received in predefined
    // time period, if ENABLE_RETRANSMISSION is defined.
    #define RETRANSMISSION_TIMES 2
    // CCA_TIMES defines the total number of Clear Channel Assessment
    // in the CCA procedure. CCA procedure perform CCA for CCA_TIMES
    // and check if the times of CCA failure beyond the number defined
    // in CCA_THRESHOLD. In the case that CCA failure times is beyond
    // CCA_THRESHOLD, the whole procedure must be repeated up to
    // CCA_RETRIES times before transmission failure can be flagged.
    #define CCA_TIMES 5
    // CCA_THRESHOLD defines the threshold times of Clear Channel Assessment
    // failure in the CCA procedure.
    #define CCA_THRESHOLD 2
    // CCA_RETRIES defines the maximum retries can be performed in the case
    // of Clear Channel Assessment failure in the CCA procedure.
    #define CCA_RETRIES 4
    // ACK_INFO_SIZE defines the number of acknowledgement information
    // structure can be stored to avoid duplicate packet to the protocol
    // layer.
    #define ACK_INFO_SIZE 4
    // RF12 configuration verification
    #if !defined(RF12_DATA_RATE)
      #error "At least one of the data rate must be defined for RF12"
    #endif
    #if !defined(RF12_BASEBAND)
      #error "At least one of the frequency band must be defined for RF12"
    #endif
    #if defined(ENABLE_ACK) && !defined(ENABLE_RETRANSMISSION)
      #error "Ack needs ENABLE_RETRANSMISSION defined"
    #endif
  #endif
#endif

Again, the code is commented, but this is where you tell what type band your transceiver is working in (433, 860 or 915 MHz), the baudrate, default frequency...

Compile the program and burn it in to your target. Switch the UART tool in PicKit2, and check the output (default to 19200 baud).

You can see the following lines:

  • "Tx : 0" means the transmission failed, either because the channel was busy for too long, or no Ack was received in a given time, or a time out occured (connection problem...)
  • "Tx : 1" means the transmission was successfull
  • "Rx : 14, E0, 7, 1, Hello 18F26J11" means a packet was received. It displays:
    • payload length (14 bytes). This is the length of the payload in the receive buffer. The payload is decrypted if it was encrypted when sent.
    • the Frame Control (E0 means source and destination address present, ack requested for data type packet. 0C would be encrypted broadcast data packet).
    • RSSI value (7): this is indicative on the RF12, and correspond to the RSSI threshold set in the config_RF12.h file.
    • LQI value (1): indicate that the Data Quality Detector signal in the transceiver was on when the packet was received.
    • Payload (Hello 18F26J11).

The CRC are not displayed. If the CRC is incorrect, the packet is ignored, and does not end up in the receive buffer.

If only one module (or node) is powered, the you will only get the Tx lines, as it won't be able to receive anything.


Files and links:

RF12 library source files with example.
Microchip MiMac Application Note.
Processor boards.
RFM12 transceiver.

Post your comments...

    Copyright 2011. Poker Games. Copyright © 2012 riaDesign