Lightning storm detectors have been around for a surprisingly long time. The early designs consisted of a pair of metal bells and a pendulum. When there was a charge applied, for example by connecting one bell to the ground and the other to a lightning rod, the bells would ring when a lightning storm was close by. In the mid 18th century, these devices were only practical for demonstration and research purposes, but very likely represent the earliest devices that convert electrostatic charge to mechanical force. A bit over a hundred years later, the first lightning detector was considered by some as the first radio receiver as well.
As soon as I found out about storm detector chips, I knew I would have to get one working. For about $25, I ordered an AMS AS3935 module from China. This chip has been featured before in a number of excellent projects such as Twittering lightning detectors, and networks of Sub-Saharan weather stations. While there’s an Arduino library for interfacing with this IC, I’m going to be connecting it up to an ESP8266 running the NodeMCU firware, which means digging into the datasheet and writing some SPI code. If any of the above tickles your fancy, read on!
Unlike the earliest charge-based detectors, this one works by picking up the RF signal produced by distant lightning strikes using a small 500 kHz antenna and doing some digital signal processing.
The detector is capable of differentiating between lightning strikes and other types of RF noise, then using the signal from the lightning strikes to estimate the distance to the stormfront, up to 40 kilometers away. This is quite nice for two reasons: it detects active storms from quite a bit further than you can with your eyes and ears, and it gives you a reasonable idea of how fast the storm is coming in.
It’s easy to think of possible applications. Golf courses, sports fields, pools, and beaches would all benefit from earlier storm detection. Besides outdoor activities, datacenters, airports, and construction crews also need to know about incoming lightning storms.
On my end, I live in Southeast Asia and rainy season can be a little epic. Downpours are very localized, incredibly intense, often cause floods, and occur with little warning. Like most residents I drive a motorbike, and being stuck in traffic or on the highway in that intensity of rain is miserable. Given the option, it’s usually better to stop, have a coffee, and let it pass.
Weather reporting isn’t terribly useful for planning trips because most showers are very localized, short, and frequent. The weather report throughout rainy season is simply “28 degrees Celsius, 75% chance of showers” every day, so adding a storm detector to my motorbike seemed practical and fun. Even if it only works some of the time, it would be fantastic. In fact, I’m surprised I’ve never seen this as a product – if it works, someone please do this.
To the Lab!
Back to our detector, there exists an Arduino library to interface with it, if that’s your style. It looks easy enough to use, but my personal preference is to use NodeMCU and the ESP8266, although it has no built-in library for this chip.
Luckily, we have a datasheet (PDF) for the AS3935, and the chip supports both SPI and I2C interfaces. I had been looking for an excuse to explore SPI in Lua on the NodeMCU, and this was perfect. Since we’re just using plain SPI, hopefully the code will be easier to port to other platforms as well.
Let’s start with the basic setup. To use SPI, the datasheet says that the Select Interface (SI) pin needs to be pulled to ground. I also wanted to use the on-chip voltage regulator, so the EN_V chip needed to be pulled high. Finally, the chip pulls an interrupt pin high on every event detection to let the connected microcontroller know that something interesting has happened.
To improve noise immunity on this pin, I used a 1kΩ pull down resistor to ground (not shown below). The latter was probably not necessary but helped reduce false positives during testing while handling the circuit without an enclosure.
Next, we connect all the pins required by SPI. There are 4: Master Out Slave In (MOSI), Master In Slave Out (MISO), clock (SCK) and Chip Select (CS). I’ve included a small table detailing what these pins are under NodeMCU, keep in mind they’re likely to be different on other platforms.
An important point is that the CS pin is not automatically managed by the NodeMCU during SPI communications. You’ll need to set its value like any other GPIO pin, which we’ll cover later. Before you begin programming, I highly recommend you double-check all your pins are connected correctly. I lost an hour that way and felt silly.
We start by initializing SPI and the relevant pins:
spi.setup(1, spi.MASTER, spi.CPOL_LOW, spi.CPHA_LOW, 8, 256); CS=8 IRQ = 2 gpio.mode(CS, gpio.OUTPUT) gpio.mode(IRQ, gpio.INPUT)
On the NodeMCU, SPI 0 is used to communicate with the onboard flash memory, so we’ve selected SPI 1, with mostly default features and 8 bits per data item. Of note is that we’ve set a clock divider of 256 on the SPI frequency. This is because the AS3935 supports a maximum SPI frequency of 2 Mhz and the NodeMCU defaults to a much faster speed (at least 20 Mhz). Note that the chip should not be set to an SPI frequency of 500 kHz, as this is the resonant frequency of the lightning detector antenna(!).
We’ve also set the chip select pin as an output on D8, and connected the interrupt pin (IRQ) as an input. IRQ will be raised high on each new event, and remain high until you read the interrupt register to determine the event type. Using it as a proper interrupt or trigger on the NodeMCU did not work reliably, so a normal input pin that we can poll will have to do for now.
Referring to the datasheet, reading from and writing to the AS3935 requires that we send eight bytes over SPI. The first two bits determine whether you are requesting a read or write, and the next six determine the target register. If you are reading from the chip, you first need to set CS low to initiate the read (before sending a request), then raise CS high-low-high when done. Let’s extend our program to read the interrupt register each time the chip outputs an event:
function reason() gpio.write(CS, gpio.LOW) tmr.delay(30) spi.send(1,0x43) tmr.delay(30) read = spi.recv(1, 8) print(string.byte(read)) gpio.write(CS, gpio.HIGH) tmr.delay(30) gpio.write(CS, gpio.LOW) tmr.delay(30) gpio.write(CS, gpio.HIGH) tmr.delay(30) end function poll() x = gpio.read(IRQ) if x == 1 then reason() end tmr.alarm(1, 100, 1, function() poll(0) end) end
In the code above, we poll the IRQ pin every 100 ms to see if it is high. If so, we set CS to low to indicate we are about to request a read. Then we send the command 0x43, which translates to binary 01000011. The first two bits (01) request a read, and the last six bits (000011) indicate we want to read register address 0x03. The last four bits of the response will contain the detected event type. Once the command is sent, we read eight bits from the SPI port and print the result, then finally set the CS pin high-low-high to signal we’re done reading.
The result of the above code is an irregular stream of ‘4’ (0100) to the terminal. If I touch the antenna, it outputs a ‘1’ instead. Looking at the datasheet, an interrupt reason of 0100 is a ‘disturber’: a radio signal at the frequency of interest, but not lightning. In big Asian cities, there’s always someone arc welding nearby; it twinkles like stars below when arriving on a nighttime flight. Interrupt reason 0001 means a problem with high electrical noise, which makes sense as well. A lightning event would output ‘8’ (1000), but it’s presently dry season so that’s unlikely at this time.
We can now successfully receive data from the device, but it’s not particularly useful yet. Next time, we’ll cover how to write to the configuration registers to set it up to work more practically, and have a bit of creative fun with what we do with the data.