ATmega128RFA1 Dev Board Hookup Guide

This Tutorial is Retired!

This tutorial covers concepts or technologies that are no longer current. It's still here for you to read and enjoy, but may not be as useful as our newest tutorials.

Pages
Contributors: jimblom
Favorited Favorite 1

Example Code

Example Sketch: RF Chat

You can download the RF chat example here (or grab it from github). This is a simple example of how to make use of the RF functionality of the ATmega128RFA1. The radio-specific functions are split off into a secondary file -- RadioFunctions.h.

You'll need two ATmega128RFA1's for this example (it's hard to demo the RF functionality without something to talk to!). They can be tied to the same computer, or a few rooms away. The maximum distance should be around 75m, but that's with complete line-of-sight, walls will diminish the range.

Upload the same sketch to each dev board. Then open up a serial monitor on each. You can use either the Arduino Serial Monitor, or a stand-alone terminal program (Tera Term is great for Windows users, Cool Term should work for Mac). Open up each serial port to 9600 bps (8 data bits, no parity, 1 stop bit), and start typing away! What's typed into one terminal should pop up into another.

Serial chat example

Understanding the Sketch

There are a few key functions, and a few interrupt routines that are key to the workings of the RF transceiver.

rfBegin()

First, the radio must be initialized by calling the rfBegin() function. This is called in the setup() portion of the sketch. Check out the comments within the code for a line-by-line explanation. This function initializes the ATmega128RFA1's radio. The lone parameter for this function sets the radio's 2.4GHz channel, which should be some value between 11 and 26. Radios must be on the same channel to communicate with each other.

language:c
// Initialize the RFA1's low-power 2.4GHz transciever.
// Sets up the state machine, and gets the radio into
// the RX_ON state. Interrupts are enabled for RX
// begin and end, as well as TX end.
uint8_t rfBegin(uint8_t channel)
{
  for (int i=0; i<128; i++)
  {
    radioRXBuffer.buffer[i] = 0;
  }
  radioRXBuffer.tail = 0;
  radioRXBuffer.head = 0;

  // Setup RX/TX LEDs: These are pins B6/34 (RX) and B7/35 (TX).
  pinMode(RX_LED, OUTPUT);
  digitalWrite(RX_LED, LOW);
  pinMode(TX_LED, OUTPUT);
  digitalWrite(TX_LED, LOW);

  // Transceiver Pin Register -- TRXPR.
  // This register can be used to reset the transceiver, without
  // resetting the MCU.
  TRXPR |= (1<<TRXRST);   // TRXRST = 1 (Reset state, resets all registers)

  // Transceiver Interrupt Enable Mask - IRQ_MASK
  // This register disables/enables individual radio interrupts.
  // First, we'll disable IRQ and clear any pending IRQ's
  IRQ_MASK = 0;  // Disable all IRQs

  // Transceiver State Control Register -- TRX_STATE
  // This regiseter controls the states of the radio.
  // First, we'll set it to the TRX_OFF state.
  TRX_STATE = (TRX_STATE & 0xE0) | TRX_OFF;  // Set to TRX_OFF state
  delay(1);

  // Transceiver Status Register -- TRX_STATUS
  // This read-only register contains the present state of the radio transceiver.
  // After telling it to go to the TRX_OFF state, we'll make sure it's actually
  // there.
  if ((TRX_STATUS & 0x1F) != TRX_OFF) // Check to make sure state is correct
    return 0;    // Error, TRX isn't off

  // Transceiver Control Register 1 - TRX_CTRL_1
  // We'll use this register to turn on automatic CRC calculations.
  TRX_CTRL_1 |= (1<<TX_AUTO_CRC_ON);  // Enable automatic CRC calc. 

  // Enable RX start/end and TX end interrupts
  IRQ_MASK = (1<<RX_START_EN) | (1<<RX_END_EN) | (1<<TX_END_EN);

  // Transceiver Clear Channel Assessment (CCA) -- PHY_CC_CCA
  // This register is used to set the channel. CCA_MODE should default
  // to Energy Above Threshold Mode.
  // Channel should be between 11 and 26 (2405 MHz to 2480 MHz)
  if ((channel < 11) || (channel > 26)) channel = 11;  
  PHY_CC_CCA = (PHY_CC_CCA & 0xE0) | 11; // Set the channel to 11

  // Finally, we'll enter into the RX_ON state. Now waiting for radio RX's, unless
  // we go into a transmitting state.
  TRX_STATE = (TRX_STATE & 0xE0) | RX_ON; // Default to receiver

  return 1;
}

rfWrite(uint8_t b) and rfPrint(String toPrint)

These two functions are your go-to routines for sending data out of the radio. There's one function to write a single byte at a time and another to send a whole string (up to 127 characters) at once.

In the main sketch, rfPrint() is called at the very beginning to shout out to other 128RFA1 devices that the board has booted up. rfWrite() is called whenever the board sees serial data come in.

language:c
// This function sends a string of characters out of the radio.
// Given a string, it'll format a frame, and send it out.
void rfPrint(String toPrint)
{
  uint8_t frame[127];  // We'll need to turn the string into an arry
  int length = toPrint.length();  // Get the length of the string
  for (int i=0; i<length; i++)  // Fill our array with bytes in the string
  {
    frame[i] = toPrint.charAt(i);
  }

  // Transceiver State Control Register -- TRX_STATE
  // This regiseter controls the states of the radio.
  // Set to the PLL_ON state - this state begins the TX.
  TRX_STATE = (TRX_STATE & 0xE0) | PLL_ON;  // Set to TX start state
  while(!(TRX_STATUS & PLL_ON))
    ;  // Wait for PLL to lock

  digitalWrite(TX_LED, HIGH);

  // Start of frame buffer - TRXFBST
  // This is the first byte of the 128 byte frame. It should contain
  // the length of the transmission.
  TRXFBST = length + 2;
  memcpy((void *)(&TRXFBST+1), frame, length);
  // Transceiver Pin Register -- TRXPR.
  // From the PLL_ON state, setting SLPTR high will initiate the TX.
  TRXPR |= (1<<SLPTR);   // SLPTR high
  TRXPR &= ~(1<<SLPTR);  // SLPTR low

  // After sending the byte, set the radio back into the RX waiting state.
  TRX_STATE = (TRX_STATE & 0xE0) | RX_ON;
}

// This function will transmit a single byte out of the radio.
void rfWrite(uint8_t b)
{
  uint8_t length = 3;

  // Transceiver State Control Register -- TRX_STATE
  // This regiseter controls the states of the radio.
  // Set to the PLL_ON state - this state begins the TX.
  TRX_STATE = (TRX_STATE & 0xE0) | PLL_ON;  // Set to TX start state
  while(!(TRX_STATUS & PLL_ON))
    ;  // Wait for PLL to lock

  digitalWrite(TX_LED, HIGH);  // Turn on TX LED

  // Start of frame buffer - TRXFBST
  // This is the first byte of the 128 byte frame. It should contain
  // the length of the transmission.
  TRXFBST = length;
  // Now copy the byte-to-send into the address directly after TRXFBST.
  memcpy((void *)(&TRXFBST+1), &b, 1);

  // Transceiver Pin Register -- TRXPR.
  // From the PLL_ON state, setting SLPTR high will initiate the TX.
  TRXPR |= (1<<SLPTR);   // SLPTR = 1
  TRXPR &= ~(1<<SLPTR);  // SLPTR = 0  // Then bring it back low

  // After sending the byte, set the radio back into the RX waiting state.
  TRX_STATE = (TRX_STATE & 0xE0) | RX_ON;
}

rfAvailable() and rfRead()

These functions operate in a manner similar to the Arduino Serial library, if you're familiar with that. Data coming into the radio is stored in a buffer It's up to you to empty that buffer. rfAvailable() returns a number explaining how many bytes of data are still un-read in the buffer. It can be 0, meaning the buffer is empty.

rfRead() returns a single byte from the front of the receive buffer. In the main sketch loop, we continuously check if RF data is available. If there is data, we use rfRead() to collect it and print it out to the serial monitor.

language:c
// Returns how many unread bytes remain in the radio RX buffer.
// 0 means the buffer is empty. Maxes out at RF_BUFFER_SIZE.
unsigned int rfAvailable()
{
  return (unsigned int)(RF_BUFFER_SIZE + radioRXBuffer.head - radioRXBuffer.tail) % RF_BUFFER_SIZE;
}

// This function reads the oldest data in the radio RX buffer.
// If the buffer is emtpy, it'll return a 255.
char rfRead()
{
  if (radioRXBuffer.head == radioRXBuffer.tail)
  {
    return -1;
  }
  else
  {
    // Read from the buffer tail, and update the tail pointer.
    char c = radioRXBuffer.buffer[radioRXBuffer.tail];
    radioRXBuffer.tail = (unsigned int)(radioRXBuffer.tail + 1) % RF_BUFFER_SIZE;
    return c;
  }
}

Interrupt Routines

Three interrupt routines are working behind the scenes, whenever there is a radio receive or transmit. Two simple interrupt service routines (ISRs) -- the transmit end, and receieve start -- trigger the RX/TX LEDs. The receive end ISR does a lot of heavy-lifting, on the other hand. It accesses the radio's received data registers and stores the data into the shard receive buffer. None of these functions can be called manually. They all just do their job whenever called upon by the processor.

language:c
// This interrupt is called when radio TX is complete. We'll just
// use it to turn off our TX LED.
ISR(TRX24_TX_END_vect)
{
  digitalWrite(TX_LED, LOW);
}

// This interrupt is called the moment data is received by the radio.
// We'll use it to gather information about RSSI -- signal strength --
// and we'll turn on the RX LED.
ISR(TRX24_RX_START_vect)
{
  digitalWrite(RX_LED, HIGH);  // Turn receive LED on
  rssiRaw = PHY_RSSI;  // Read in the received signal strength
}

// This interrupt is called at the end of data receipt. Here we'll gather
// up the data received. And store it into a global variable. We'll
// also turn off the RX LED.
ISR(TRX24_RX_END_vect)
{
  uint8_t length;
  // Maximum transmission is 128 bytes
  uint8_t tempFrame[RF_BUFFER_SIZE];

  // The received signal must be above a certain threshold.
  if (rssiRaw & RX_CRC_VALID)
  {
    // The length of the message will be the first byte received.
    length = TST_RX_LENGTH;
    // The remaining bytes will be our received data.
    memcpy(&tempFrame[0], (void*)&TRXFBST, length);

    // Now we need to collect the frame into our receive buffer.
    //  k will be used to make sure we don't go above the length
    //  i will make sure we don't overflow our buffer.
    unsigned int k = 0;
    unsigned int i = (radioRXBuffer.head + 1) % RF_BUFFER_SIZE; // Read buffer head pos and increment;
    while ((i != radioRXBuffer.tail) && (k < length-2))
    {
      // First, we update the buffer with the first byte in the frame
      radioRXBuffer.buffer[radioRXBuffer.head] = tempFrame[k++];
      radioRXBuffer.head = i; // Update the head
      i = (i + 1) % RF_BUFFER_SIZE; // Increment i % BUFFER_SIZE
    }
  }

  digitalWrite(RX_LED, LOW);  // Turn receive LED off, and we're out
}

Those functions should serve as a backbone for all of your radio needs. Instead of setting up a chat server, you could set one outside to monitor weather conditions and relay that back to a display indoors. Or set up one under your mattress, with pressure sensors strategically connected, while another dev board connected to your coffee machine could wait for a signal to start brewing a cup. These are neat little boards, we'd love to hear about what applications you've come up with!