Enginursday: Lightning Detector for the Trail

Prototyping an outdoor lightning warning system.

Favorited Favorite 9

The Idea ⚡

I feel quite lucky to live and work so close to the Rocky Mountains here in Colorado, and it’s a common hobby among us at SparkFun to be out hiking/biking/kayaking or climbing. The mountains contain numerous hazards, not the least of which is lighting strikes. The weather can change suddenly and drastically around these parts. There is a common Colorado saying, “If you don’t like the weather, wait 5 minutes." Unfortunately, this also applies to weather you are enjoying and it can be easy to find yourself suddenly looking at a stormfront that the weather station has assured you would pass to the south. Prevention is always ideal, but even a small amount of lightning prescience can allow you to find cover or start that rappel sooner.

Rooftop View from SparkFun

View from the rooftop lunch table at SparkFun

SparkFun is releasing an updated version of our AS3935 Lightning Detector this week. We have had fun using it around the office to confirm that indeed it is raining, and there was strong desire to see this outdoors where it belongs. I decided to start something and get this idea into the prototype phase.

The Make ⚡

My goals for the outdoor detector were:

  • portable and sturdy enough it could be clipped onto a harness
  • report out how close the approaching lighting is
  • give easy-to-understand indicators of a nearby strike

The new Lightning Detector goes live tomorrow, and luckily I have one that "fell off" the initial run.

SparkFun Lightning Detector - AS3935

SparkFun Lightning Detector - AS3935


To start, I wanted to pick a microcontroller that fit my needs: the RedBoard Turbo. This was an easy choice. It is battery powered, can communicate over SPI to the AS3935 Lightning Detector, uses 3.3V for its I/O, and even has a Qwiic connector! This is my go-to prototyping with a battery board.

SparkFun RedBoard Turbo - SAMD21 Development Board

SparkFun RedBoard Turbo - SAMD21 Development Board


Enter the Qwiic Micro OLED

Next, I needed a way to display the data the detector was receiving and sending back to the RedBoard Turbo. The Qwiic Micro OLED was a perfect choice.

SparkFun Micro OLED Breakout (Qwiic)

7 Retired

With a simple Qwiic Cable, this board connected over I2C and, using example code from the hookup guide, it was displaying data in minutes.

Data Displayed via the Qwiic micro OLED screen

I used a small buzzer from the SIK and some jumper cables for hookup and a lipo battery for power.

Code ⚡

Below is the code used with the SAMD21 RedBoard Turbo, Lightning Detector and Qwiic Micro OLED.

This example demonstrates the code used on the Outdoor Lighting Warning Prototype
License: This code is public domain


#include <SPI.h>
#include <Wire.h>
#include "SparkFun_AS3935.h"
#include <SFE_MicroOLED.h>  // Include the SFE_MicroOLED library
#define INDOOR 0x12
#define OUTDOOR 0xE
#define LIGHTNING_INT 0x08
#define DISTURBER_INT 0x04
#define NOISE_INT 0x01

SparkFun_AS3935 lightning;

const int lightningInt = 3;// Interrupt pin for lightning detection
int spiCS = 4; //SPI chip select pin

// This variable holds the number representing the lightning or non-lightning
// event issued by the lightning detector.
int intVal = 0;
int noise = 2; // Value between 1-7
int disturber = 2; // Value between 1-10

//The library assumes a reset pin is necessary. The Qwiic OLED has RST hard-wired, so pick an arbitrarty IO pin that is not     being used
#define PIN_RESET 9
//The DC_JUMPER is the I2C Address Select jumper. Set to 1 if the jumper is open (Default), or set to 0 if it's closed.
#define DC_JUMPER 1

bool warmedup = false;
int distanceview = 3;
const int buzzerPin = 9;
const int songLength = 18;
char notes[] = "cdfda ag cdfdg gf "; // a space represents a rest
int beats[] = {1, 1, 1, 1, 1, 1, 4, 4, 2, 1, 1, 1, 1, 1, 1, 4, 4, 2};
int tempo = 113;

// MicroOLED Object Declaration //
MicroOLED oled(PIN_RESET, DC_JUMPER);    // I2C declaration

void setup()
// When lightning is detected the interrupt pin goes HIGH.
 pinMode(lightningInt, INPUT);

 SerialUSB.println("AS3935 Franklin Lightning Detector");

 pinMode(buzzerPin, OUTPUT);


void loop()

if (warmedup == false)

if (digitalRead(lightningInt) == HIGH)

intVal = lightning.readInterruptReg();

if (intVal == NOISE_INT) {
  // Too much noise? Uncomment the code below, a higher number means better
  // noise rejection.
else if (intVal == DISTURBER_INT) {
  // Too many disturbers? Uncomment the code below, a higher number means better
  // disturber rejection.
else if (intVal == LIGHTNING_INT) {
  SerialUSB.println("Lightning Strike Detected!");
  // Lightning! Now how far away is it? Distance estimation takes into
  // account any previously seen events in the last 15 seconds.
  byte distance = lightning.distanceToStorm();
  SerialUSB.print("Approximately: ");
  SerialUSB.println("km away!");
  distanceview = distance;

delay(100); // Slow it down.


void warmup()

if ( !lightning.beginSPI(spiCS, 2000000) ) {
    SerialUSB.println ("Lightning Detector did not start up, freezing!");
 while (1);
    SerialUSB.println("Schmow-ZoW, Lightning Detector Ready!");
 warmedup = true;


int enviVal = lightning.readIndoorOutdoor();
SerialUSB.print("Are we set for indoor or outdoor: ");
if ( enviVal == INDOOR )
else if ( enviVal == OUTDOOR )
    SerialUSB.println(enviVal, BIN);


void lightningData()
oled.begin();    // Initialize the OLED
oled.clear(ALL); // Clear the display's internal memory
oled.display();  // Display what's in the buffer (splashscreen)
delay(1000);     // Delay 1000 ms
oled.clear(PAGE); // Clear the buffer.

  printTitle("Storm!", 0);

  oled.clear(PAGE);     // Clear the screen
  oled.setFontType(0);  // Set font to type 0
  oled.setCursor(0, 0); // Set cursor to top-left

  delay(50);  // Wait 500ms before next example

  oled.clear(PAGE);            // Clear the display
  oled.setCursor(0, 0);        // Set cursor to top-left
  oled.setFontType(0);         // Smallest font
  oled.print("Distance: ");
  oled.setCursor(16, 12);// Print "A0"
  oled.setCursor(0, 34);
  oled.print("Km Away!");


void printTitle(String title, int font)
  int middleX = oled.getLCDWidth() / 2;
  int middleY = oled.getLCDHeight() / 2;

  // Try to set the cursor in the middle of the screen
  oled.setCursor(middleX - (oled.getFontWidth() * (title.length() / 2)),
                 middleY - (oled.getFontHeight() / 2));
  // Print the title:

int frequency(char note)
  // This function takes a note character (a-g), and returns the
  // corresponding frequency in Hz for the tone() function.

int i;
const int numNotes = 8;  // number of notes we're storing

// The following arrays hold the note characters and their
 // corresponding frequencies. The last "C" note is uppercase
// to separate it from the first lowercase "c". If you want to
// add more notes, you'll need to use unique characters.

// For the "char" (character) type, we put single characters
// in single quotes.

  char names[] = { 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' };
  int frequencies[] = {262, 294, 330, 349, 392, 440, 494, 523};

  // Now we'll search through the letters in the array, and if
  // we find it, we'll return the frequency for that note.

  for (i = 0; i < numNotes; i++)  // Step through the notes
    if (names[i] == note)         // Is this the one?
      return (frequencies[i]);    // Yes! Return the frequency
  return (0); // We looked through everything and didn't find it,
  // but we still need to return a value, so return 0.

void buzzing()
  int i, duration;

  for (i = 0; i < songLength; i++) // step through the song arrays
 duration = beats[i] * tempo;  // length of note/rest in ms

if (notes[i] == ' ')          // is this a rest?
  delay(duration);            // then pause for a moment
else                          // otherwise, play the note
  tone(buzzerPin, frequency(notes[i]), duration);
  delay(duration);            // wait for tone to finish
delay(tempo / 10);            // brief pause between notes



Next was to create an enclosure for the project. Luckily we have a CO2 laser cutter and plenty of clear acrylic! I tossed together a box design with some 0.8-inch spaced standoff holes to mount the sensor, display and microcontroller.

Enclosure Design

Time to put the assembly together in the enclosure.

I later added large 0.75-inch holes to pass a sling through.

Laser Cut Enclosure

And Finally, Does It Fit on a Harness?

Here are a few pictures of the enclosure being used with a harness.

Harness View 1

Harness View 2 Harness View 3

Click on the images for a closer view.

Final Thoughts ⚡ ⚡ ⚡

I had a lightning emulator, which helped greatly in the development process, but there was a large amount of disturbance events here at SparkFun and the real test will be bringing it outside with me. With everything hooked up, we get both a auditory indication of a lightning strike being detected and a display of distance on the OLED. When all the screws, standoffs, lipo battery and sling were added, the total enclosure weighed about 5 oz or 0.3 lbs. Although this a tolerable weight to bring backpacking or climbing, I feel like there is a lot of room to decrease the weight of the project. I am very excited to bring this into the woods and the on the rocks with me, update the design and report back to you all with my findings.

Comments 8 comments

  • Member #134773 / about 5 years ago / 3

    One thought is to be sure to actually try the display outdoors. One of my (many) "back-burner" projects is an accessory for my riding lawnmower (an electronic "tilt-meter"). I did a quick bit of initial testing with a MicroView and found that the OLED display was unreadable in full sunlight, so I've switched to using a "transflective" LCD display for it.

  • Member #1535095 / about 5 years ago / 2

    Why did you make it km rather than mi?

  • Member #887589 / about 5 years ago / 2

    While this can be a fun project, PLEASE do not rely on it for ensure your or others safety!!!!! I was going to build several, similar units, based on a different sensor, for our county Emergency Preparedness Department. When an attorney, who also was a volunteer for the department, heard of the plan, he explained the ENORMOUS liability that would ensue if relying on the accurate predictions of this device caused injury or death to anyone..

    • J-Spark / about 5 years ago / 2

      We could not agree more! This for personal interest and possibly adding more warning of a strike in addition to the preventive steps I take when traveling outdoors.

      • Member #134773 / about 5 years ago / 2

        This is why I'm hesidant to provide much detail on my aforementioned "tilt-meter", even though it would be a good "learning tool" because of needing some "low-pass" filtering, and the math to convert data from a "3-DOF" accellerometer to tilt (plus a nifty way of correcting for the slope of the panel where it's mounted via a mag-mount. Also, though no real progess yet on implementing it, I may be able to use "high-pass" filtering of the 3-DOF data to determine when the mower engine is actually running (to collect some "run time" data on the mower).

        Anyway, methinks we have too many lawyers...

        • Member #783305 / about 5 years ago / 2

          Interesting! My middle yard has a significant hill in it so you've inspired me to build a simple data logger for my mower. Collect a few data sets and see what I might be able to infer from the data.

          • Member #134773 / about 5 years ago / 2

            OK, a couple of quick tips (which you might or might not find useful): First, if you're going from 3-DOF data to try to find "which way is down", I've found that the atan() function is useful, and the data vectors do NOT have to be normalized (i.e., have a length of 1) for it, and, IIRC, it takes the least amount of CPU effort to compute as an added bonus.

            The other tip is to remember that the engine is going to add a lot of vibrations, so you probably want some sort of low-pass filter on the data. If you're trying to do it in "real time", you might consider doing this: Have a variable for "fout", and then take every reading and subtract fout for it and divide by some value n, then add that to fout. If n happens to be a power of two, e.g., 8, 16, 32, etc., then it's very "cheap" to do the division (assuming integer numbers): just right shift (in C/C++, use the >> operator) by the "log base 2" of n (3 if n is 8, 4 if 16, 5 if 32, etc.). The math to find the actual "cutoff" frequencies is well beyond doing in this forum, but some tinkering should get you something useful.

Related Posts

Recent Posts


All Tags