Start by installing these libraries in your Arduino IDE:

You can also install FastLED directly within the Arduino IDE (Sketch > Include Library > Manage Libraries...) 

Got everything installed?  Grab the code below and paste it into the Arduino IDE. If you have issues check out the Feather 32u4 HELP page

// Distance meter using the RSSI function of two RFM69 radios.
// This is a very VERY "ish" gauge of *relative* distance (not actual units
// like feet or meters or anything), and is prone to interference from
// walls, bodies, direction and the's intended for an artistic
// presentation, nothing scientific.

#include <RFM69.h> // get it here:
#include <SPI.h>
#include "FastLED.h"

// RADIO HARDWARE CONFIG ---------------------------------------------------

#define TX_NODE_ID   1
#define RX_NODE_ID   2
#define NETWORK_ID 100 // The same on all nodes that talk to each other
#define ENCRYPTKEY "sampleEncryptKey" // Same 16 characters on all nodes!

// Match frequency to the hardware version of the radio on your Feather:
//#define FREQUENCY   RF69_433MHZ
//#define FREQUENCY   RF69_868MHZ
#define FREQUENCY   RF69_915MHZ
#define IS_RFM69HCW true // Set true if using RFM69HCW module

// RFM69 pins on Feather 32U4 board:
#define RFM69_CS   8
#define RFM69_IRQ  7
#define RFM69_IRQN 4
#define RFM69_RST  4

// A jumper across two pins selects whether this board is the
// TRANSMITTER or the RECEIVER.  That way the SAME code can be uploaded
// to BOTH boards.  I got tired of having to comment out or enable a
// line every time I uploaded to one or the other.
#define TX_SENSE_1  9
#define TX_SENSE_2 10

RFM69 radio(RFM69_CS, RFM69_IRQ, IS_RFM69HCW, RFM69_IRQN);
bool  isTransmitter;

// SIGNAL FILTERING CONFIG -------------------------------------------------

#define MEDIAN_SIZE  5 // Number of RSSI readings to be median filtered
#define AVERAGE_SIZE 8 // Number of median results to be averaged

// Signal values are stored as abs(RSSI), hence the positive values here:
#define SIGNAL_MIN   26 // abs() of signal strength at closest distance
#define SIGNAL_MAX  100 // " furthest distance
uint8_t  medianBuf[MEDIAN_SIZE],   // Prior abs(RSSI) readings
         averageBuf[AVERAGE_SIZE], // Prior medians
         medianIdx  = 0,           // Current position in medianBuf[]
         averageIdx = 0;           // Current position in averageBuf[]
uint32_t sum        = SIGNAL_INIT * AVERAGE_SIZE + (AVERAGE_SIZE / 2);

// DISPLAY CONFIG ----------------------------------------------------------

#define LED_TYPE    WS2812
#define DATA_PIN  6      // NeoPixels are connected to this pin
#define NUM_LEDS 20      // How many leds in your strip?
CRGB leds[NUM_LEDS];     //create your NeoPixel array

int led = 13;
uint8_t average = 10;    
uint8_t distance = 15;   // Play with this number to affect the range

CRGBPalette16 currentPalette;
TBlendType    currentBlending;

int HUE = 60;            
int SATURATION = 255;          
int BRIGHTNESS = 255;   

#define FPS 25 // Animation rate (frames per second)

// SETUP FUNCTION - runs once at startup -----------------------------------

void setup() {
   // pinMode(led, OUTPUT);      // Turns the onboard LED on -- useful for testing.
   // digitalWrite(led, HIGH);   // Comment out these two lines if you want it off while running
  // Un-comment these 3 lines to refine your signal variables -----------
  // while (!Serial) {}
  // Serial.begin(9600);
  // Serial.println("hello");

  // Set up jumper detect on TX_SENSE pins
  pinMode(TX_SENSE_2, OUTPUT);
  digitalWrite(TX_SENSE_2, LOW);

  // Reset the RFM module:
  pinMode(RFM69_RST, OUTPUT);
  digitalWrite(RFM69_RST, HIGH);
  digitalWrite(RFM69_RST, LOW);

  // Test for jumper on TX_SENSE pins
  isTransmitter = digitalRead(TX_SENSE_1);

  // Initialize radio:
  radio.initialize(FREQUENCY, isTransmitter ? TX_NODE_ID : RX_NODE_ID, NETWORK_ID);
  if(IS_RFM69HCW) radio.setHighPower(); // Only for RFM69HCW & HW!
  radio.setPowerLevel(31); // Output range 0 (5dBm) to 31 (20dBm)

  memset(medianBuf , SIGNAL_INIT, sizeof(medianBuf));
  memset(averageBuf, SIGNAL_INIT, sizeof(averageBuf));

  // Set up a timer interrupt for LED animation.  This ensures uniform
  // timing for animation while radio ACK time may be unpredictable.
  // THIS CODE IS SPECIFIC TO ATMEGA32U4; will not run on M0, Teensy3, etc.
  TCCR1A = _BV(WGM11) | _BV(WGM10);                         // Fast PWM mode
  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS11) | _BV(CS10); // 1:64 prescale
  OCR1A  = ((F_CPU / 64) / FPS) - 1;
  TIMSK1 = _BV(TOIE1); // Enable overflow interrupt on Timer/Counter 1

   // tell FastLED about the LED strip configuration
    .setDither(BRIGHTNESS < 255);
  currentBlending = LINEARBLEND;

// LOOP FUNCTION - runs repeatedly -----------------------------------------

void loop() {
  if(isTransmitter) {
    // Send a small message to the receiver node...
    radio.sendWithRetry(RX_NODE_ID, "<3", 2);
  } else {
    // Receiver waits for message, sends acknowledgement.
    // The ACK is needed to gauge distance on BOTH ends.
    if(radio.ACKRequested()) radio.sendACK();

  if(radio.RSSI) { // Got signal strength?
    uint16_t s = abs(radio.RSSI);
    if(s < SIGNAL_MIN)      s = SIGNAL_MIN;
    else if(s > SIGNAL_MAX) s = SIGNAL_MAX;
    s -= SIGNAL_MIN; // s is now 0 to (MAX-MIN)

    medianBuf[medianIdx] = s; // Store new value in median buffer
    if(++medianIdx >= MEDIAN_SIZE) medianIdx = 0;

    // Median filter discards 'outliers,' the most spurious readings;
    // abrupt changes that are possibly inaccurate.

    // Make a sorted copy of median buffer to find actual current median
    // without disturbing original values.  Since it's just a few elements,
    // a crude insertion sort is done...
    uint8_t m[MEDIAN_SIZE], median, average, i, j, k;
    memcpy(m, medianBuf, sizeof(m));
    for(i=1; i<MEDIAN_SIZE; i++) {
      k = m[i];
      for(j=i; j && (m[j-1] > k); j--) m[j] = m[j-1];
      m[j] = k;
    median = m[MEDIAN_SIZE / 2];

    // Following median, the average of prior results is taken,
    // further smoothing out any jumpy bits...
    sum                   -= averageBuf[averageIdx];
    averageBuf[averageIdx] = median;
    sum                   += median;
    if(++averageIdx >= AVERAGE_SIZE) averageIdx = 0;

    average = sum / AVERAGE_SIZE;

    // 'average' is the signal strength from the radio...
    // it'll likely be between 0 (closest) and 75 or so
    // (, down the block). 
    // UNCOMMENT THESE SERIAL COMMANDS to see what your radios are sending while the Feather is plugged in via USB
    //Serial.print("Average  ");
    //Serial.print("Adjusted  ");
    //Serial.print("Brightness  ");
    //Serial.println(constrain(map ((300-(average*distance)),-400,300,0,255),10,255));

    //This line is your main calculation.  It's taking "average" (your radio signal, averaged out for smoothness) and 
    //mutliplying it by "distance" (currently set at 15, but play with that number to change the range),
    //and then constraining it between a brightness of 10 (dimmest setting) and 255 (brightest setting).
    BRIGHTNESS=(constrain(map ((300-(average*distance)),-400,300,0,255),10,255));

ISR(TIMER1_OVF_vect) { // Timer 1 interrupt for animation
  // Display *prior* frame of data at start of interrupt -- this ensures
  // uniform timing for effects that may have variable processing times.;

  // Render one frame of animation here...pick one function, comment out other

void Solid()  // all one color, currently set to yellow by the HUE variable

void Colors()  // I matched each LED to the color of its corresponding flower
  for(int yell = 0 ; yell < 3; yell++ ) { //LEDs 1-3 are Yellow (HUE 60)
    leds[yell] = CHSV(60, SATURATION, BRIGHTNESS);
  for (int wht = 3 ; wht < 9; wht++ ) {  //LEDs 4-9 are White (saturation 100)
    leds[wht] = CHSV(60, 100, BRIGHTNESS);
  for (int pur = 9 ; pur < 19; pur++ ) {  //LEDs 10-19 are Purple (hue 200)
    leds[pur] = CHSV(200, 255, BRIGHTNESS);

Testing & Tweaking your Code

Your environment or particular antenna may be different from mine.  Try uploading the code as-is and watch the behavior of the LEDs as you move them closer and further apart.  Blocking the signal with your body will also change your results, as will walls, trees, or excited grandmothers.  

Variables to Play With

Change the "distance" variable up or down to play with the range over which the brightness changes.

If they don't seem as responsive as you'd like, try changing the MEDIAN_SIZE and AVERAGE_SIZE values.

Look for "serial" commands in the code (currently all commented out).  If your LEDs aren't performing the way you want, uncomment these lines to watch what's happening with your numbers in your serial monitor.   Just be sure to comment them out again or your boards won't work without being plugged in.

Give the HUE and SATURATION variables a number between 0 and 255 to choose your LED color.  Or choose multiple colors by using "Colors();" instead of "Solid();" and assigning different colors to different pixels. 

Make sure the two boards are working and communicating before continuing. If no lights come on…aside from the usual checking for electrical shorts, etc., make sure you have the jumper wire installed between pins 9 and 10 on just one of the two boards.

This guide was first published on Sep 02, 2016. It was last updated on Sep 02, 2016.

This page (Software) was last updated on Mar 31, 2021.

Text editor powered by tinymce.