Bohemian Rhapsody, Queen |
Being near the equator, Malaysia is very frequently struck by lightning and has a flash rate density close to 100. ADSL broadband modems, cordless telephones, network switches, autogate controllers and monitors regularly get damaged.
A few times a year the storms get really bad: you can hear the lightning hiss followed by the thunderclap. The phone cables are fried, electric power is out and the dogs try to get on your lap, never mind the cat.
I often repair the electronics, but there is something about lightning damage: they are never quite the same. TVs seem more immune, but if you use them with a computer (as a monitor), they get damaged by lightning. Go figure.
But the most annoying damage is to the ADSL modem. This is because I run an IoT server 24/7. I have backup power via UPS but a fried modem often results in service interruption. Eventually I installed a backup 3G modem using a TP-Link MR-3420.
For some reason, things get a lot better if we simply disconnected the telephone line from the ADSL modem and phone. This causes the MR-3420 to fail over to 3G, and I put in a little fix to ensure a failback once the ADSL modem is reconnected. Things do not get fried as much maybe once a year a LAN switch or a monitor cops it.
And this works very well for us unless we are away on holiday. What we need is a system to disconnect the modem from the phone line when the storm is close and reconnect when it passes. And sure enough, cometh the hour cometh the chip: AS3935 from Austria Microsystems.
I got my AS3935 for RM92 (about USD20) |
But why not stay on 3G? The problem is in Malaysia we still have a data cap on 3G, maybe 10GB per month, and if your IoT device is a security camera it quickly adds up.
Since I had good results with the SPI interface on the NodeMCU ESP-12E before,
I made the following SPI cable to the CJMCU AS3935:
PIN# AS3935 diagram ESP-12E Arduino IDE no
1 VCC 5V
2 GND GND
3 SCL HSCLK D5 GPIO14 14
5 MOSI HMISO D6 GPIO12 12
4 MISO HMOSI D7 GPIO13 13
6 CS HCS D8 GPIO15 15
7 SI GND
8 IRQ D2 GPIO2 4
9 EN_VREG 5V
The AS3935 allows for 3V3 to be used but the CJMCU board did not bring out VREG to the header, so I needed to supply 5V and enable the onboard regulator by tying EN_VREG to 5V. The last column, 'Arduino IDE number' is the pin number to use for the Arduino sketch. I used a 12-way header with a 10-way ribbon cable because I wanted to use GPIO1 and GPIO3 to control a relay board.
For software, I used Eva Schindling's Thunder and Lightning. Even though it was written five years ago for the Arduino, it pretty much worked first time for the ESP-12E. I used her AS3935_example.ino sketch with just a few tweaks to accommodate my SPI cable and the ESP-12E.
Download her code in a zip file, unzip it and the make a symbolic link to your Arduino IDE library:
ln -s /home/guest/as3935/ThunderAndLightning-master/library/AS3935 /home/guest/Arduino/libraries/AS3935
And that was it. I knew Arduino SPI code did not travel very well to the ESP8266 and was well, thunderstruck it worked first time. And unlike Eva who back in 2014 seemed to have lacked good thunderstorms to test with, a big storm arrived just in time for testing.
Life indeed is good. Happy Trails.
/*
LightningDetector.pde - AS3935 Franklin Lightning Sensor™ IC by AMS library demo code
Copyright (c) 2012 Raivis Rengelis (raivis [at] rrkb.lv). All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <SPI.h>
#include <AS3935.h>
void printAS3935Registers();
// Function prototype that provides SPI transfer and is passed to
// AS3935 to be used from within library, it is defined later in main sketch.
// That is up to user to deal with specific implementation of SPI
// Note that AS3935 library requires this function to have exactly this signature
// and it can not be member function of any C++ class, which happens
// to be almost any Arduino library
// Please make sure your implementation of choice does not deal with CS pin,
// library takes care about it on it's own
byte SPItransfer(byte sendByte);
// Iterrupt handler for AS3935 irqs
// and flag variable that indicates interrupt has been triggered
// Variables that get changed in interrupt routines need to be declared volatile
// otherwise compiler can optimize them away, assuming they never get changed
void AS3935Irq();
volatile int AS3935IrqTriggered;
// First parameter - SPI transfer function, second - Arduino pin used for CS
// and finally third argument - Arduino pin used for IRQ
// It is good idea to chose pin that has interrupts attached, that way one can use
// attachInterrupt in sketch to detect interrupt
// Library internally polls this pin when doing calibration, so being an interrupt pin
// is not a requirement
#define IRQpin 4 // ESP-12E
#define CSpin 15
AS3935 AS3935(SPItransfer,CSpin,IRQpin);
void setup()
{
// Serial.begin(9600);
Serial.begin(115200); // 2019-10-19 ESP-12E default
// first begin, then set parameters
SPI.begin();
// NB! chip uses SPI MODE1
SPI.setDataMode(SPI_MODE1);
// NB! max SPI clock speed that chip supports is 2MHz,
// but never use 500kHz, because that will cause interference
// to lightning detection circuit
SPI.setClockDivider(SPI_CLOCK_DIV16);
// and chip is MSB first
SPI.setBitOrder(MSBFIRST);
// reset all internal register values to defaults
AS3935.reset();
// and run calibration
// if lightning detector can not tune tank circuit to required tolerance,
// calibration function will return false
//if(!AS3935.calibrate())
// Serial.println("Tuning out of range, check your wiring, your sensor and make sure physics laws have not changed!");
outputCalibrationValues();
recalibrate();
AS3935.setNoiseFloor(1);
AS3935.setSpikeRejection(2);
AS3935.setWatchdogThreshold(1);
outputCalibrationValues();
recalibrate();
// since this is demo code, we just go on minding our own business and ignore the fact that someone divided by zero
// first let's turn on disturber indication and print some register values from AS3935
// tell AS3935 we are indoors, for outdoors use setOutdoors() function
AS3935.setIndoors();
// AS3935.setOutdoors();
// turn on indication of distrubers, once you have AS3935 all tuned, you can turn those off with disableDisturbers()
AS3935.enableDisturbers();
// AS3935.disableDisturbers();
printAS3935Registers();
AS3935IrqTriggered = 0;
// Using interrupts means you do not have to check for pin being set continiously, chip does that for you and
// notifies your code
// demo is written and tested on ChipKit MAX32, irq pin is connected to max32 pin 2, that corresponds to interrupt 1
// look up what pins can be used as interrupts on your specific board and how pins map to int numbers
// ChipKit Max32 - irq connected to pin 2
// attachInterrupt(1,AS3935Irq,RISING);
// uncomment line below and comment out line above for Arduino Mega 2560, irq still connected to pin 2
attachInterrupt(digitalPinToInterrupt(IRQpin),AS3935Irq,RISING); // ESP-12E
}
void loop()
{
// here we go into loop checking if interrupt has been triggered, which kind of defeats
// the whole purpose of interrupts, but in real life you could put your chip to sleep
// and lower power consumption or do other nifty things
if(AS3935IrqTriggered)
{
// reset the flag
AS3935IrqTriggered = 0;
// wait 2 ms before reading register (according to datasheet?)
delay(2);
// first step is to find out what caused interrupt
// as soon as we read interrupt cause register, irq pin goes low
int irqSource = AS3935.interruptSource();
// returned value is bitmap field, bit 0 - noise level too high, bit 2 - disturber detected, and finally bit 3 - lightning!
if (irqSource & 0b0001)
Serial.println("Noise level too high, try adjusting noise floor");
if (irqSource & 0b0100)
Serial.println("Disturber detected");
if (irqSource & 0b1000)
{
// need to find how far that lightning stroke, function returns approximate distance in kilometers,
// where value 1 represents storm in detector's near victinity, and 63 - very distant, out of range stroke
// everything in between is just distance in kilometers
int strokeDistance = AS3935.lightningDistanceKm();
if (strokeDistance == 1)
Serial.println("Storm overhead, watch out!");
if (strokeDistance == 63)
Serial.println("Out of range lightning detected.");
if (strokeDistance < 63 && strokeDistance > 1)
{
Serial.print("Lightning detected ");
Serial.print(strokeDistance,DEC);
Serial.println(" kilometers away.");
}
}
}
}
void printAS3935Registers()
{
int noiseFloor = AS3935.getNoiseFloor();
int spikeRejection = AS3935.getSpikeRejection();
int watchdogThreshold = AS3935.getWatchdogThreshold();
int minLightning = AS3935.getMinimumLightnings();
Serial.print("Noise floor is: ");
Serial.println(noiseFloor,DEC);
Serial.print("Spike rejection is: ");
Serial.println(spikeRejection,DEC);
Serial.print("Watchdog threshold is: ");
Serial.println(watchdogThreshold,DEC);
Serial.print("Minimum Lightning is: ");
Serial.println(minLightning,DEC);
}
// this is implementation of SPI transfer that gets passed to AS3935
// you can (hopefully) wrap any SPI implementation in this
byte SPItransfer(byte sendByte)
{
return SPI.transfer(sendByte);
}
// this is irq handler for AS3935 interrupts, has to return void and take no arguments
// always make code in interrupt handlers fast and short
void ICACHE_RAM_ATTR AS3935Irq()
{
AS3935IrqTriggered = 1;
}
void recalibrate() {
delay(50);
Serial.println();
int calCap = AS3935.getBestTune();
Serial.print("antenna calibration picks value:\t ");
Serial.println(calCap);
delay(50);
}
void outputCalibrationValues() {
// output the frequencies that the different capacitor values set:
delay(50);
Serial.println();
for (byte i = 0; i <= 0x0F; i++) {
int frequency = AS3935.tuneAntenna(i);
Serial.print("tune antenna to capacitor ");
Serial.print(i);
Serial.print("\t gives frequency: ");
Serial.print(frequency);
Serial.print(" = ");
long fullFreq = (long) frequency*160; // multiply with clock-divider, and 10 (because measurement is for 100ms)
Serial.print(fullFreq,DEC);
Serial.println(" Hz");
delay(10);
}
}