Introduction: NRF24L01+ ATmega328P-PU Radio Sensor

This project began as an attempt to create a custom sensor board that functioned with an nRF24L01+ radio transmitter. I was tired of looping wires around an Arduino Uno. I thought I could create a custom solution in a fairly small footprint, that would allow the connection of a number of common sensors. After a number of prototypes and two custom PCB attempts, this is the design I came up with.

The design is centered around an ATmega328P-PU and the nRF24L01+ 2.4GHz transceiver module. This device will allow the transceiver to be plugged directly into the board, and supply it with 3.3volts. The ATmega328P will be supplied with 5 volts. This is all contained in a 40 x 60mm footprint (the nRF24L01+ does create a slight overhang along the long axis). Also two small prototyping areas (3 x 8 holes, and 2 x 4 holes) are created on the board, allowing simple prototyping development.

The ATmega328P-PU pins are replicated with either male or female headers allowing sensors and small custom boards to be plugged into and onto the device.

Step 1: Schematic

I decided to use a general bare-bones ATmega328P-PU micro controller. The circuit board was designed in Fritzing. The Gerber files were exported and then compressed into a zip file. This file was then tested using Gerblook. The Gerber files were then uploaded to PcbWay for manufacture. The boards were of excellent quality, and delivered to my door within five days. The sensor build took around 15 minutes.

Step 2: Prototype and Custom PCB

I took the design through several prototypes, before I considered attempting to create a custom PCB. I eventually settled on a foot print of 40mm x 60mm.

Not all of the prototypes were successful, however they did prove to be a useful learning tool as the project design moved forward.

There are two prototyping areas in the design, although the small 2 x 4 area behind the 5V regulator most probably will not be used. The 5V regulator will lie flat across this space. The larger 3 x 8 area has proven to be quite useful, and will accommodate one small sensor readily. Regulated output voltages are available at the [3V3], [GND] and [5V] pin connections on the side of the board. An FTDI232 pin connector allows the board to be programmed, and an LED on CLK or Arduino digital pin 13 serves as a general function indicator.

I decided to use one of the fabrication services available rather than etch and cut a PCB design myself. My reasons were varied, but I considered the small cost of a limited run to be well worth it. The price of each board, taking the 10 piece option was around $4 per board.

Step 3: Parts List

Description, No. Required, Source, Unit Cost, Total Unit Cost

  1. Custom PCB, 1 , PCBWay , $4.30 each , $4.30(when 10 units are produced, unit price drops when more boards are ordered for production)
  2. 10uF Electrolytic Capacitor, 3 , Amazon , $0.43 each , $1.29
  3. 22pF Capacitor, 3 , Amazon , $0.13 each , $0.39
  4. 100nF Capacitor, 1 , Amazon , $0.01 each , $0.01
  5. AMS1117 (3V3) 1 Amazon , $0.57 each , $0.57
  6. L7805(5V) 1 Amazon , $1.01 each , $1.01
  7. LED (5mm) 1 Amazon , $0.04 each , $ 0.04
  8. ATmega328P-PU 1 Amazon , $2.10 each , $2.10
  9. ATmega328P label, 1, Adafruit, Free
  10. 16Mhz Crystal, 1 , Amazon , $0.06 each , $0.06
  11. 28 pin DIP IC socket, 1 , Amazon , $0.26 each , $0.26
  12. 220Ohm Resistor 1 Amazon , $0.04 each , $0.04
  13. 10KOhm Resistor, 1 , Amazon , $0.05 each , $0.05
  14. Mini Micro Tactile Switch, 1 , Amazon , $0.03 each , $0.03
  15. Male Header pins(40) 6 pins FTDI header 2 pins Voltage in 3 pins Regulated Voltage out Total 11 pins, 1 , Amazon , $0.57 each , $0.57
  16. Female Header pins(40) 14 pins Left Header 14 pins Right Header 4 pins nRF24 Left 4 pins nRF24 Right + 4 sacrificed pins Total 40 pins, 1 , Amazon , $0.21 each , $0.21
  17. nRF24L01+ 2.4GHz Radio Transceiver, 1 , Amazon , $1.76 each , $1.76
  18. FTDI232 USB to Serial Adapter, 1 , Amazon , $5.69 each , $5.69

Total cost of parts $12.69

and $18.38 including the FTDI232 USB/Serial Adapter.

The FTDI232 USB to Serial Adapter would be a one time purchase, allowing the programming of multiple devices using FTDI.

Step 4: Install a Bootloader

The ATmega328P-PU micro controllers I purchased came with a boot loader already installed. However I decided to install the optibootLoader.

I have one of my prototypes set up as an OptiLoader device. This flashes the Optiboot Loader to the ATmega micro controllers with very little interaction.

  1. Down load the OptiLoader libraries
  2. Unzip the folder
  3. Rename the uncompressed folder to OptiLoader
  4. Copy or move the uncompressed folder to the Arduino libraries folder
  5. Start the Arduino IDE
  6. Load the sketch [Examples]/[OptiLoader]/[OptiLoader]
  7. Upload the sketch to the basic ATmega328P-PU device

Make connections:

use jumper wires for the following connections

  1. OptibootLoader pin D9(15) to ATmega328P-PU pin VCC(7)
  2. OptibootLoader pin D10(16) to ATmega328P-PU pin RST(1)
  3. OptibootLoader pin D11(17) to ATmega328P-PU pin D11(17), MOSI
  4. OptibootLoader pin D12(18) to ATmega328P-PU pin D12(18), MISO
  5. OptibootLoader pin D13(19) to ATmega328P-PU pin D13(19), SCK
  6. OptibootLoader pin GND(8) to ATmega328P-PU pin GND(8)

Flash the Boot Loader:

  1. Provide power to the OptiLoader device
  2. Press Reset
  3. The device will power ON and try to flash the ATmega microcontroller
  4. The on board led(pin D13/SCK) will flash
  5. If the process was successful, the led will extingish indicating that power is OFF
  6. If the process failed, then the on board led will stay on, and flash periodically
  7. Remove the newly flashed BootLoaded device from the Optiboot device
  8. Attach another ATmega to be Boot Loaded (if we have one)
  9. Press Reset (or G in the serial port) to repeat the process

Links to bootloader Instructables:

A short list of other tutorials, giving some alternative means for flashing a boot loader to the ATmega micro controller.

Step 5: ATmega328P-PU Pin Assignments

The ATmega328P-PU pins have been assigned as indicated in the table. In this way the device may be considered a standard sensor. The DHT11 and DS18B20 and photo resistor and nRF24L01+ will be considered as standard sensor assignments for this device. However only the nRF24L01+ assignments are set in hardware. The other sensors will be plugged into the headers around the board. These may be adjusted as seen fit in the software.

The two DHT11 sensor prototypes will become standard, they are easily created and will become a standard sensor type for my network.

Step 6: Optimized NRF24 Libraries

The Optimized nRF24L01 Libraries by Tmh20(blog) are used here. The main documentation for this and related libraries may be found here.

  1. Download the Newly Optimized RF24 NRF24L01 Radio Library for Arduino to a work folder.
  2. Unzip the file.
  3. Copy the unzipped file folder to the libraries folder of the Arduino IDE.
  4. Start the Arduino IDE and check that the RF24 Library examples are available.

Step 7: Modify the NRF24L01+ Antenna

The nRF24L01+ radios were modified according to this instructable. The improvement in radio operation was significant. The modification brings about clear communication benefits in both range and reliability.

I found that when I first constructed these devices, that radio communication was extremely poor. I searched for a solution for a long time, until I happened upon the antenna modification. I now perform this modification as a matter of course, and as a result I now have extremely reliable communication between all of my radio devices.

Step 8: Using the Prototyping Area

I decided to use the prototyping area and fabricate two units for use with the led_remote.pde. I followed the comments in the sketch and built the two units according to the schematic shown above. I then used these with a modified version of the led_remote.pde sketch provided in the RF24 libraries to test my devices.

These devices may be readily created using a breadboard, following the schematic above, or use more led's and follow the original sketch more closely. Ensure that each led is pulled down to ground using a resistor, something of the order 200 to 330 Ohms. The switches should be pulled down to ground using a resistor of 10K Ohms.

I powered the devices with 9 Volt batteries. The advantage of using this particular sketch to test the units is that it gives a visual response on the receiver unit. Allowing the range of the radios to be tested without the need to have the receiver device attached to a personal computer.

Step 9: Test the Device

The device was tested using the Optimized nRF24 example libraries. Using the following sketches:

  1. A slightly modified version of the led_remote.pde sketch
  2. Also see this Instructable

Step 10: Modified Example Sketch: Led_remote.cpp

File: RF24/examples/Usage/led_remote/led_remote.pde

/*Copyright (C) 2011 J. Coliz 

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 version 2 as published by the Free Software Foundation.
 *
 *
 * modified 11/7/2015 ST
 * 
 * Using pins D2 and D3
 * 
 * A2 to GND sets board as receiver (LED Board)
 * 
 *      
 *
 *
 *
 * Example LED Remote
 *
 * This is an example of how to use the RF24 class to control a remote
 * bank of LED's using buttons on a remote control.
 *
 * On the 'remote', connect any number of buttons or switches from
 * an arduino pin to ground.  Update 'button_pins' to reflect the
 * pins used.
 *
 * On the 'led' board, connect the same number of LED's from an
 * arduino pin to a resistor to ground.  Update 'led_pins' to reflect
 * the pins used.  Also connect a separate pin to ground and change
 * the 'role_pin'.  This tells the sketch it's running on the LED board.
 *
 * Every time the buttons change on the remote, the entire state of
 * buttons is sent to the led board, which displays the state.
 */

Standard preamble, with modification note.

#include 
#include "nRF24L01.h"
#include "RF24.h"
#include "printf.h"

Load the libraries

//
// Hardware configuration
// Set up nRF24L01 radio on SPI bus plus pins 7 & 8 (CE & CS)

RF24 radio(7,8);

CE and CSN pin assignments, these I modified as I am using pins 7 and 8 not 9 and 10.

// sets the role of this unit in hardware.  Connect to GND to be the 'led' board receiver
// Leave open to be the 'remote' transmitter
const int role_pin = A2;

I modified the Role_pin, as I would like to use A0 for a photo resistor, but still maintain some compatibility with this modified code.

// Pins on the remote for buttons
const uint8_t button_pins[] = { 2,3 };
const uint8_t num_button_pins = sizeof(button_pins);

I am only using two buttons, more than enough for my purposes and also will have some future purpose.

// Pins on the LED board for LED's
const uint8_t led_pins[] = { 2,3};
const uint8_t num_led_pins = sizeof(led_pins);
//
// Topology
// Single radio pipe address for the 2 nodes to communicate.
const uint64_t pipe = 0xE8E8F0F0E1LL;
//
// Role management
//
// Set up role.  This sketch uses the same software for all the nodes in this
// system.  Doing so greatly simplifies testing.  The hardware itself specifies
// which node it is.
//
// This is done through the role_pin
// The various roles supported by this sketch
typedef enum { role_remote = 1, role_led } role_e;

// The debug-friendly names of those roles
const char* role_friendly_name[] = { "invalid", "Remote", "LED Board"};

// The role of the current running sketch
role_e role;

//
// Payload
//
uint8_t button_states[num_button_pins];
uint8_t led_states[num_led_pins];
//
// Setup
//
void setup(void)
{
  //
  // Role
  //

  // set up the role pin
  pinMode(role_pin, INPUT);
  digitalWrite(role_pin,HIGH);
  delay(20); // Just to get a solid reading on the role pin

  // read the address pin, establish our role
  if ( digitalRead(role_pin) )
    role = role_remote;
  else
    role = role_led;
  //
  // Print preamble
  //
  Serial.begin(57600);
  printf_begin();
  printf("\n\rRF24/examples/led_remote/\n\r");
  printf("ROLE: %s\n\r",role_friendly_name[role]);
  //
  // Setup and configure rf radio
  //
  radio.begin();
  radio.setDataRate(RF24_250KBPS); // options are; RF24_250KBPS, RF24_1MBPS, RF24_2MBPS
  //
  // Open pipes to other nodes for communication
  //
  // This simple sketch opens a single pipes for these two nodes to communicate
  // back and forth.  One listens on it, the other talks to it.
  if ( role == role_remote )
  {
    radio.openWritingPipe(pipe);
  }
  else
  {
    radio.openReadingPipe(1,pipe);
  }
  //
  // Start listening
  //
  if ( role == role_led )
    radio.startListening();
  //
  // Dump the configuration of the rf unit for debugging
  //
  radio.printDetails();
  //
  // Set up buttons / LED's
  //
  // Set pull-up resistors for all buttons
  if ( role == role_remote )
  {
    int i = num_button_pins;
    while(i--)
    {
      pinMode(button_pins[i],INPUT);
      digitalWrite(button_pins[i],HIGH);
    }
  }
  // Turn LED's ON until we start getting keys
  if ( role == role_led )
  {
    int i = num_led_pins;
    while(i--)
    {
      pinMode(led_pins[i],OUTPUT);
      led_states[i] = HIGH;
      digitalWrite(led_pins[i],led_states[i]);
    }
    pinMode(4,OUTPUT);
    digitalWrite(4,LOW);
  }

}
//
// Loop
//
void loop(void)
{
  //
  // Remote role.  If the state of any button has changed, send the whole state of
  // all buttons.
  //

  if ( role == role_remote )
  {
    // Get the current state of buttons, and
    // Test if the current state is different from the last state we sent
    int i = num_button_pins;
    bool different = false;
    while(i--)
    {
      uint8_t state = ! digitalRead(button_pins[i]);
      if ( state != button_states[i] )
      {
        different = true;
        button_states[i] = state;
      }
    }
    // Send the state of the buttons to the LED board
    if ( different )
    {
      printf("Now sending...");
      bool ok = radio.write( button_states, num_button_pins );
      if (ok)
        printf("ok\n\r");
      else
        printf("failed\n\r");
    }
    // Try again in a short while
    delay(20);
  }
  //
  // LED role.  Receive the state of all buttons, and reflect that in the LEDs
  //
  if ( role == role_led )
  {
    // if there is data ready
    if ( radio.available() )
    {
      // Dump the payloads until we've gotten everything
      while (radio.available())
      {
        // Fetch the payload, and see if this was the last one.
        radio.read( button_states, num_button_pins );
        // Spew it
        printf("Got buttons\n\r");
        
        digitalWrite(4,HIGH);
        delay(20);
        digitalWrite(4,LOW);
        delay(30);
        digitalWrite(4,HIGH);
        delay(20);
        digitalWrite(4,LOW);
        delay(30);
        digitalWrite(4,HIGH);
        delay(20);
        digitalWrite(4,LOW);

Indicate a read on digital pin 4, to flash the read indicator led, before changing the state of the other two led's according to the button press.

        // For each button, if the button now on, then toggle the LED
        int i = num_led_pins;
        while(i--)
        {
          if ( button_states[i] )
          {
            led_states[i] ^= HIGH;
            digitalWrite(led_pins[i],led_states[i]);
          }
        }
      }
    }
  }
}
// vim:ai:cin:sts=2 sw=2 ft=cpp

Step 11: Conclusion

This device in it's current revision functions well, it performs the requirements it was designed for. There is room for improvement, but for the moment the device design will remain static.

Design Goals Met:

  1. Device footprint 40mm x 60mm
  2. 3.3V voltage regulation
  3. 5V voltage regulation
  4. nRF24L01+ plug and play ready (using Optimized RF24 libraries)
  5. FTDI232 programming interface
  6. Small prototyping area for sensors and small circuits

Improvements that might be made:

  1. Replace the LM7805 5V regulator(TO-220 package) with an LD7111 5V(SOT-223 package). This would release more of the prototyping area for real use.
  2. Reverse the nRF24L01+ so that it is contained within the main board foot print, rather than hang outside of the board.
  3. Perhaps revise the nRF24L01+ control pins, have CE and CSN on D9 and D10. This would place everything on the right side of the IC. However this is not a priority at the moment.