back to index

AS3935 lightning sensor

            Power modifications (red)
      Antenna tuning
            Resonator modification (yellow), faulty board
            Frequency measurement and adjustment
            board attachment (green)
            state communication
            interrupt vs polling
            antenna calibration
            false positives example


The AS3935 is a single-chip lightning sensor, capable of detecting lightnings up to 40 km away, and guesstimating the distance with up to 1km accuracy.

The sensing is done by a ferrite coil antenna, with a LC circuit tuned to 500 kHz. The built-in algorithm differentiates between genuine lightnings and man-made "disturbers" (which in the vicinity of other electronics are rich and plentiful).


The chip can take 2.4 to 5.5 volts (3.6 volts with internal regulator disabled). The operating current is between 70 and 350 microamps (listening vs evaluating).

For direct interfacing with raspberry pi, 3.3v is recommended.

Power modifications (red)

As many rf analog circuits, the device is fairly sensitive to power noise. Due to minuscule current, a series resistor of reasonably small value (100 ohms was chosen) was connected in series, and a tantalum capacitor (10 µF was chosen) was added to its chip-end in parallel, forming a single-stage RC filter.

A ferrite toroid core with about 3 turns of the flat ribbon cable was used as pre-filtering of all the signals.

There is an onboard ceramic capacitor filtering the chip power, with value assumed in microfarads. A pair of smaller capacitors, 6.8 pF and 68 pF, was added to bypass the big one's inductance; the values were chosen on the basis of availability, more usual 10pF/100pF would do even better.

A header pin was attached to the ground plane at the side of the board, for the scope probe ground clip. Such attachment points are handy to have around projects; few seconds of extra work can and will ease the frustration later.

The additional filtering resulted in, give or take, two levels down of noise floor.

Antenna tuning

The antenna consists of a parallel LC circuit, with resonant frequency of 500 kHz and Q factor approx. 15.

The coil is a little piece of ferrite, with several turns of thin copper wire. The coil inductance is approx. 100 µH (calculated).

The capacitor is usually composed of two ceramic chip SMD capacitors, soldered in parallel to add together the desired capacitance.

The usual capacitor combination is 680 pF and 270 pF, about 950 pF total.

There is also about a 10 kΩ resistor soldered in parallel to the caps, to adjust the Q factor.

The AS3935 chip has a bank of 15 additional on-chip capacitors, 8 pF each, 120 pF total, for tuning the antenna and compensating component variations. This is achieved by writing to lower 4 bits of register 0x08.

Resonator modification (yellow), faulty board

Some CJMCU-brand breakout boards have wrong capacitors installed; 1000 pF and 100 pF, for 1100 pF total. This results in too low antenna resonant frequency.[ref]

Fortunately, the caps can be easily removed and replaced with a combination that is on hand and reasonably close; eg. 680 pF, 150 pF, 33 pF.

Frequency measurement and adjustment

The resonant frequency can be easily measured, by using the on-chip function.

Setting bit 7 (0x80, DISP_LCO) of register 0x08 will output a square wave derived from the antenna resonant frequency to the IRQ pin.

Beware, the datasheet is not completely clear that this frequency is not the raw antenna resonance, but it is divided by 16, 32, 64 or 128, depending on the values of bits 7 and 6 (LCO_FDIV) of register 0x03.

Use value 00 (default, 0x00) for division by 16 (for oscilloscope or frequency counter), and 11 (0xC0) for division by 128. For raspberry pi interrupt the latter is recommended; faster interrupts on slower raspi tend to lose interrupts and report falsely low frequency. On a 4-core Pi2B the difference was between 460 kHz and 500 kHz.

The higher the capacitance, the lower the frequency.

The frequency has to be within +-3.5% from the recommended 500 kHz:

The internal capacitor gives steps of approx. 2 kHz per 8 pF, roughly 30 kHz total. Adding capacitance lowers frequency. The frequency-capacitance relation is nonlinear, but can be approximated as roughly linear in this range.


The sensor can be interfaced via I2C or SPI, selected by the SI input.

For SPI, connect SI to GND. Use MISO, MOSI, SCL and CS pins.

For I2C, connect SI to Vcc. Use SCL as clock and MOSI as data/SDA. Connect CS and MISO to ground (not strictly necessary, may increase stability, generally bad to leave inputs floating). The A1 and A0, if present, should be connected to Vcc in order to set the I2C address to 0x03. (If only one of them is high, addresses are 0x02 and 0x01. Address 0x00 shall not be used for it is reserved.)

Of course some versions of i2cdetect exclude probing of 0x00..0x07 range so the sensor is not visible on them.

Avoid running the interface clock (namely the SCK signal) too close to 500 kHz (and 250 kHz, 125 kHz..., the squeare wave signals have a long ton of harmonics). It will cause interference.


The chip announces presence of data by the rising edge of the IRQ signal, the pulse taking about 5 microseconds. (Milliseconds?)

The interrupt type is read from the lower four bits of register 0x03. The reasons are as follows:

When INT_NH is detected, the sensor gain should be lowered. As it may be a result of a temporary interference, it may be attempted to increase gain. If the gain is too high, the sensor within 100 or few 100s milliseconds complains with another INT_NH.

INT_D can be plentiful and really annoying. It can be switched off, masked by bit 5 (MASK_DIST) of register 0x03. Or it can be measured statistically to assess the current level of low-freq EMI around.


It is also possible to go without the IRQ line, and poll the sensor, calling get_interrupt() (return bottom 4 bits from the 0x03 register) in suitable intervals (eg. 500 msec). In that case the 0x00 value means there was no event detected.

There is a chance of missing an event if things are happening too fast.

board attachment (green)

The board is attached to a flat ribbon cable with a house-standard 4-pin I2C connector. The IRQ signal is broken out to a header pin bent to the side, to conserve vertical space and allow attaching IRQ to the raspi header with a patch cable if needed. The ground is broken to a bent header pin soldered to the top ground plane, to facilitate clipping of scope probe ground.


For simplicity, python was chosen.


The RPi_AS3935 was chosen. Install by

pip3 install RPi_AS3935

Another dependency that may not be already met is I2C/smbus:

pip3 install smbus


The functions exposed on the library object.


The original code was taken from and then heavily modified. The LCD and speed of sound related code was thrown out. A lot of other code was added.

The I2C interfacing is done by smbus library. The interrupt handling is done by RPi.GPIO library; some code variants use pigpio daemon to avoid having to be run as root.

state communication

Two status log files were added:

These are used for looking from outside of the running process, if it is doing anything.

interrupt vs polling

The interrupt vs polling interfacing can be chosen by setting the AS3935_IRQPIN value to False (polling) or to pin number to which IRQ is connected.


The code tries to maintain high sensitivity by periodically (once per minute or two) attempting to call lower_noise_floor(), and adjusting up by calling raise_noise_floor() whenever the chip complains via INT_NH/0001. Usual code without the lower_noise_floor() calls is liable to raising noise floor to max when a transient interference is encountered, and then staying there. Calling lower_noise_floor() when it should not be done results in almost immediate INT_NH. Such "AGC" code therefore hovers at the lowest noise floor achievable at current conditions, and recovers from loud interferences in minutes.

antenna calibration

The calibration uses the set_disp_lco(True) call to enable sending the divided oscillator data to interrupt line, after setting the divider to 11 (/128). The interrupts are counted over several seconds. The high divider is chosen to avoid interrupt losses and pulse undercounting on slower boards.

Another function just enables set_disp_lco() and waits until ctrl-c is pressed. This is handy for oscilloscope or counter based adjustments.


The code just outputs events to stdout. To do something with them, eg. a shell script can be used:

 ./ | while read x; do
   mosquitto_pub -t 'sensors/lightning' -m "$x"
   echo "`date +%FT%T` | $x" >> as3935.log

false positives example

observed at a very low noise floor

AS3935 init; sensor polling; disturbers hidden; Waiting for lightning/EMI...
Noise level too high - adjusted to 1
Noise level too high - adjusted to 2
Noise level too high - adjusted to 3
Lightning! 1km away, energy=453890, noisefloor=3 (22:42:36 - 2022/10/31)
Lightning! 1km away, energy=0, noisefloor=3 (22:48:16 - 2022/10/31)
Lightning! 1km away, energy=0, noisefloor=3 (22:52:16 - 2022/10/31)
Lightning! 1km away, energy=0, noisefloor=3 (23:03:33 - 2022/10/31)
Lightning! 1km away, energy=0, noisefloor=3 (23:12:51 - 2022/10/31)
Lightning! 1km away, energy=302500, noisefloor=3 (23:15:08 - 2022/10/31)
Lightning! 1km away, energy=452502, noisefloor=3 (23:15:42 - 2022/10/31)
Lightning! 1km away, energy=315472, noisefloor=3 (23:16:36 - 2022/10/31)
Lightning! 1km away, energy=0, noisefloor=3 (23:17:15 - 2022/10/31)
Lightning! 1km away, energy=52154, noisefloor=3 (23:31:37 - 2022/10/31)
Lightning! 5km away, energy=225722, noisefloor=3 (23:37:06 - 2022/10/31)
Lightning! 5km away, energy=147104, noisefloor=3 (23:37:40 - 2022/10/31)
Lightning! 1km away, energy=0, noisefloor=3 (23:45:56 - 2022/10/31)
Lightning! 1km away, energy=375501, noisefloor=3 (23:49:30 - 2022/10/31)
Lightning! 1km away, energy=0, noisefloor=3 (23:50:36 - 2022/10/31)
Lightning! 1km away, energy=0, noisefloor=3 (23:56:16 - 2022/10/31)
Lightning! 1km away, energy=175235, noisefloor=3 (23:57:07 - 2022/10/31)
Lightning! 1km away, energy=349721, noisefloor=3 (23:58:11 - 2022/10/31)
Lightning! 1km away, energy=0, noisefloor=3 (00:06:24 - 2022/11/01)
Lightning! 1km away, energy=105636, noisefloor=3 (00:18:05 - 2022/11/01)
Lightning! 1km away, energy=0, noisefloor=3 (00:22:59 - 2022/11/01)
Lightning! 1km away, energy=0, noisefloor=3 (00:36:55 - 2022/11/01)
Lightning! 1km away, energy=0, noisefloor=3 (00:44:33 - 2022/11/01)
Lightning! 1km away, energy=0, noisefloor=3 (00:57:40 - 2022/11/01)
Lightning! 1km away, energy=0, noisefloor=3 (01:22:31 - 2022/11/01)
Lightning! 1km away, energy=462629, noisefloor=3 (01:26:30 - 2022/11/01)
Lightning! 1km away, energy=0, noisefloor=3 (01:35:30 - 2022/11/01)
Lightning! 5km away, energy=200045, noisefloor=3 (01:48:50 - 2022/11/01)
Lightning! 5km away, energy=0, noisefloor=3 (01:59:38 - 2022/11/01)
Lightning! 5km away, energy=85291, noisefloor=3 (02:18:13 - 2022/11/01)
Lightning! 5km away, energy=0, noisefloor=3 (02:32:31 - 2022/11/01)
Lightning! 1km away, energy=0, noisefloor=3 (02:32:46 - 2022/11/01)
Lightning! 8km away, energy=0, noisefloor=3 (02:51:07 - 2022/11/01)
Lightning! 1km away, energy=0, noisefloor=3 (02:52:34 - 2022/11/01)
Lightning! 1km away, energy=179528, noisefloor=3 (02:59:10 - 2022/11/01)
Lightning! 1km away, energy=0, noisefloor=3 (03:02:12 - 2022/11/01)
Lightning! 1km away, energy=0, noisefloor=3 (03:09:30 - 2022/11/01)
Lightning! 1km away, energy=103409, noisefloor=3 (03:23:22 - 2022/11/01)
Lightning! 1km away, energy=280475, noisefloor=3 (03:29:14 - 2022/11/01)
Lightning! 1km away, energy=0, noisefloor=3 (03:36:42 - 2022/11/01)
Lightning! 1km away, energy=0, noisefloor=3 (03:42:51 - 2022/11/01)
Lightning! 1km away, energy=59944, noisefloor=3 (03:43:25 - 2022/11/01)
Lightning! 1km away, energy=47132, noisefloor=3 (03:46:24 - 2022/11/01)
Lightning! 1km away, energy=123698, noisefloor=3 (03:58:19 - 2022/11/01)
Lightning! 1km away, energy=0, noisefloor=3 (04:00:29 - 2022/11/01)
Lightning! 1km away, energy=0, noisefloor=3 (04:00:30 - 2022/11/01)


board top

board top

board top, ferrite, connector

board bottom



If you have any comments or questions about the topic, please let me know here:
Your name:
Your email:
Leave this empty!
Only spambots enter stuff here.