Magnetometers work quite well to detect vehicles, moving or not. They do need to be within 2-3 meters of the vehicle for reliable detection, though, and you will probably need to average a few readings. You will detect a distortion in the Earth's magnetic field, so use vector calculations to measure both a change in direction as well as magnitude.
I've been using an LSM303 at the gate to my property for years. Detection time is about 0.1 second using an LSM303.
Here is the code, written for AVRStudio
/*
Gate Alarm. LSM303DLH magnetometer vehicle detector
version 3.3 03/04/2017
eBay Pro Mini board.
this version runs on 4.5 V battery, 8 MHz internal RC
reads LSM303DLH magnetometer module
Transmit data via VirtualWire
uses I2C hardware interface on the atmega328.
RF module connections:
TX on PC3 (A3) blue
RX on PC2 (A2)
GND brown
Vdd red
MCU Mag
------ -----
Gnd (Vss) Pin 1 blue
+5 (Vdd) Pin 2 red
PC4 (SDA) Pin 3 yellow Don't forget pullup resistors on SDA and SCL!
PC5 (SCL) Pin 4 orange
CPU Clock speed is 8 MHz, I2C clock=100 kHz.
*/
// debug print on uart if DEBUG defined
//#define DEBUG
#define F_CPU 8000000UL
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <avr/sleep.h>
#include <avr/io.h>
#include <util/delay.h>
#include <avr/wdt.h>
#include "I2C.c"
#include "vector.c"
#ifdef DEBUG
// debug printing
#include "uart.c"
static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);
#endif
#include "VirtualWireC3.c"
// long delay
void delay_ms(int ms) {
while (ms--) _delay_ms(1);
}
// prototypes and globals
void read_mag_raw(int *m);
int readVcc(void);
float R2D = 180.0/3.14159265;
// watchdog timer interrupt, wake from sleep each 1/4 second (approx)
ISR (WDT_vect) {} //nothing to do
//
// MAIN
//
int main(void)
{
int m[3],i=0;
// read the magnetometer 16 times each sample (at 220 Hz, about 75 ms)
unsigned int nreadings=16,test=0,max_test=0;
vector fm, fm_avg, diff;
float d2;
float n_avg = 1.0/nreadings;
unsigned int bmsg[3]; //binary value message buffer
char passcounter = 128; //keep alive watchdog timeout of ~60 seconds
DDRB = 1<<PB5; //LED output (D13)
for (i=0; i<2; i++) {
PORTB |= (1<<PB5); //on
delay_ms(1000);
PORTB &= ~(1<<PB5); //off
delay_ms(500);
}
DDRC = 0; // all inputs
PORTC = (1 << PORTC4) | (1 << PORTC5); // enable pull-ups on SDA and SCL, respectively
#ifdef DEBUG
uart_init(9600);
stdout = &mystdout; //Required for printf init
delay_ms(10); //let TX stabilize in idle
printf("I,Gate V3.3\r\n");
#endif
I2C_Init(); // initialize I2C interface (check F_CPU and TWI register settings)
//check for LSM303DLM presence
//set up magnetometer
char who_am_i_m=I2C_ReadRegister(0x3c,0x0F); //should be 0x3C
if (who_am_i_m != 0x3C) {
while (1) { //device not found. blink LED rapidly
PORTB |= (1<<PB5); //on
delay_ms(300);
PORTB &= ~(1<<PB5); //off
delay_ms(300);
}
} // and hang
//#ifdef DEBUG
// printf("I,%02X\r\n",who_am_i_m);
//#endif
// disable accelerometer and set data rate & sleep mode for magnetometer
I2C_WriteRegister(0x30,0x20,0x00); // accelerometer, CTRL_REG1_A = 0 power down
I2C_WriteRegister(0x3C,0x00,0x1C); // magnetometer, MR_REG_A = 0x1C (220Hz data rate)
I2C_WriteRegister(0x3C,0x02,0x03); // magnetometer, MR_REG_M = 3 (sleep)
// set up VirtualWire transmit (A3 or PC3)
vw_setup(1000); // Bits per sec
// initialize low pass filter for average mag vector
read_mag_raw(m);
fm_avg.x = m[0];
fm_avg.y = m[1];
fm_avg.z = m[2];
#ifdef DEBUG
printf("M,%d,%d,%d\r\n",m[0],m[1],m[2]);
delay_ms(10); //finish printing before sleep
#endif
// power reduction
// disable ADC
ADCSRA = 0;
// set PRR Power Reduction Register (set PRADC after ADCSRA=0)
//Bit 7 - PRTWI: Power Reduction TWI
//Bit 6 - PRTIM2: Power Reduction Timer/Counter2
//Bit 5 - PRTIM0: Power Reduction Timer/Counter0
//Bit 4 - Res: Reserved bit
//Bit 3 - PRTIM1: Power Reduction Timer/Counter1
//Bit 2 - PRSPI: Power Reduction Serial Peripheral Interface
//Bit 1 - PRUSART0: Power Reduction USART0
//Bit 0 - PRADC: Power Reduction ADC
PRR |= (1<<PRTWI)|(1<<PRTIM0)|(1<<PRTIM2)|(1<<PRSPI)|(1<<PRADC)|(1<<PRUSART0);
/* sleep modes on ATmega328p
SLEEP_MODE_IDLE
SLEEP_MODE_ADC
SLEEP_MODE_PWR_DOWN
SLEEP_MODE_PWR_SAVE
SLEEP_MODE_STANDBY
SLEEP_MODE_EXT_STANDBY
*/
set_sleep_mode(SLEEP_MODE_PWR_SAVE);
// clear various "reset" flags
MCUSR = 0;
// allow changes, disable reset
WDTCSR = (1<<WDCE) | (1<<WDE);
// set interrupt mode and an interval
WDTCSR = (1<<WDIE) | (1<<WDP2) | (1<<WDP0); // set WDIE, and 1/2 second timeout
wdt_reset(); // reset it
// main loop. Sleep, wake up on watchdog, make measurements
while(1) { //loop to read and print battery voltage and compass bearings
sleep_enable();
cli(); //time critical steps follow
MCUCR = (1<<BODS) | (1<<BODSE); // turn on brown-out enable select
MCUCR = (1<<BODS); //Brown out off. This must be done within 4 clock cycles of above
sei();
sleep_cpu();
PRR &= ~((1<<PRTWI)|(1<<PRADC)); //turn ADC and I2C back on
#ifdef DEBUG
PRR &= ~(1<<PRUSART0); //usart on
#endif
// read magnetometer
fm.x = 0.;
fm.y = 0.;
fm.z = 0.;
for (i=1; i<nreadings; i++) {
read_mag_raw(m);
fm.x += m[0];
fm.y += m[1];
fm.z += m[2];
}
fm.x *= n_avg; //average the readings
fm.y *= n_avg;
fm.z *= n_avg;
diff.x = fm.x - fm_avg.x; //difference to background
diff.y = fm.y - fm_avg.y;
diff.z = fm.z - fm_avg.z;
//low pass filter, after differencing
fm_avg.x += (fm.x - fm_avg.x)*0.02;
fm_avg.y += (fm.y - fm_avg.y)*0.02;
fm_avg.z += (fm.z - fm_avg.z)*0.02;
// distance^2 from this point to average
d2 = diff.x*diff.x + diff.y*diff.y + diff.z*diff.z + 0.5;
if (d2 < 999.0) test=d2;
else test = 999; //big enough!
if(test > max_test) max_test=test;
#ifdef DEBUG
{
int mx=diff.x+0.5;
int my=diff.y+0.5;
int mz=diff.z+0.5;
printf("O,%d,%d,%d,%d\r\n",mx,my,mz,test);
delay_ms(2); //wait till printing done for sleep
}
#endif
passcounter++;
// the magic alarm value 14 (below) was determined by experiment
// send a message, either upon alarm or at regular intervals
if( (test > 14) || passcounter == 0) { //alarm or keepalive, every ~60 seconds
bmsg[0] = test;
bmsg[1] = max_test;
bmsg[2] = readVcc();
vw_send((uint8_t *)&bmsg,sizeof(bmsg));
vw_wait_tx(); // Wait until message is sent
ADCSRA = 0; // disable ADC
PRR |= (1<<PRTWI)|(1<<PRADC); //turn ADC and I2C off
#ifdef DEBUG
PRR |= (1<<PRUSART0); //usart off
#endif
} // end if (value>alarm)
if(passcounter == 0) {
max_test = 0; //reset max for next interval
passcounter = 128; //next timeout in ~60 seconds
}
}
}
// Returns a set of raw magnetic readings
void read_mag_raw(int *m)
{
unsigned char sr=0;
I2C_WriteRegister(0x3C,0x02,0x01); //make single measurement 0x02=MR_REG_M
delay_ms(4); //4 ms ~ 220 Hz
sr=I2C_ReadRegister(0x3C,0x09); //check RDY bit of Status Reg
// while ((PINC & 2) == 0) { //DRDY input on A1 (PC1)
while ((sr&1) == 0) { //DRDY bit
delay_ms(1);
sr=I2C_ReadRegister(0x3C,0x09);
}
// read 3 16 bit values, device automatically returns to SLEEP mode
I2C_Start(0x3C);
I2C_Write(0x03); // OUTXH_M
I2C_Start(0x3D); // repeated start as read
unsigned char mxh = I2C_ReadACK();
unsigned char mxl = I2C_ReadACK();
unsigned char myh = I2C_ReadACK();
unsigned char myl = I2C_ReadACK();
unsigned char mzh = I2C_ReadACK();
unsigned char mzl = I2C_ReadNACK();
I2C_Stop();
*m++ = (mxh << 8) | mxl;
*m++ = (myh << 8) | myl;
*m = (mzh << 8) | mzl;
}
// function to read 1.1V reference against AVcc
// return battery voltage in millivolts
// must be individually calibrated for each CPU
int readVcc(void) {
int result;
ADCSRA = (1<<ADEN); //enable and
ADCSRA |= (1<<ADPS0) | (1<<ADPS1) | (1<<ADPS2); // set prescaler to 128
// set the reference to Vcc and the measurement to the internal 1.1V reference
ADMUX = (1<<REFS0) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1);
delay_ms(1); // Wait for ADC and Vref to settle
ADCSRA |= (1<<ADSC); // Start conversion
while (bit_is_set(ADCSRA,ADSC)); // wait until done
result = ADC;
// second time is a charm
ADCSRA |= (1<<ADSC); // Start conversion
while (bit_is_set(ADCSRA,ADSC)); // wait until done
result = ADC;
// must be individually calibrated for EACH BOARD
result = 1148566UL / (unsigned long)result; //1126400 = 1.1*1024*1000
return result; // Vcc in millivolts
}