Traffic Detection and Counter Using FG-3+ and Arduino UNO R4 WiFi
- fgsensors
- Aug 27
- 6 min read
Create a low-cost, traffic monitoring system by deploying the FG-3+ fluxgate magnetometer near roads to detect magnetic disturbances caused by passing vehicles. Use the Arduino UNO R4 WiFi to detect vehicles, and optionally timestamp data, and upload real-time traffic analytics to the Arduino Cloud for remote monitoring and historical trend analysis.

Supplies
For this project we used:
About Fluxgate Magnetometers
Fluxgate Magnetometers are highly sensitive sensors designed to detect minute magnetic field variations, commonly used in geophysical surveying and magnetic anomaly detection. They can detect magnetic fields generated by vehicles, making them ideal for traffic detection and counting applications.
FG-3+ Sensor by FG Sensors offers exceptional sensitivity and low noise performance. They are perfect for detecting magnetic anomalies from hidden objects and vehicles, the FG-3+ provides reliable and accurate magnetic field measurements for traffic monitoring.
More details on FG sensors and fluxgate technology can be found at fgsensors.com.

Project Overview
Goal: Detect vehicles by sensing their magnetic disturbances using the FG-3+ near roadways.
Platform: Arduino UNO R4 WiFi for sensor interfacing and optional cloud connectivity.
Functionality: Detect vehicle, with possibility to upgrade system to log detection events with timestamps and send data wirelessly to Arduino Cloud.
Hardware Setup
FG-3+ magnetometer positioned close to the road to effectively sense magnetic disturbances from passing cars, trucks, or motorcycles.
Connect the FG-3+ sensor to Arduino UNO R4 WiFi.
FG-3+ VCC - 5V
FG-3+ GND – GND
FG-3+ OUT – connected to DIGITAL pin 2

How the Code Works
1. Sensor Signal Counting
The digital sensor output (frequency signal proportional to magnetic field) is connected to an interrupt pin. Rising edges trigger the ISR sensorDigHandler(), which increments a counter sensorDigCnt when counting is enabled (intEnable flag). This counter accumulates sensor pulses during a fixed measurement window (measureTime ms).
2. Baseline Establishment and Moving Average
The baseline frequency count (representing no vehicle traffic) is initialized at startup by measuring sensor pulses without disturbance. During normal operation, a moving average baseline is continuously updated with factor alpha smoothing to adapt slowly to slow environmental changes.
3. Peak Detection
The main logic checks if the instantaneous sensor counts deviates from the baseline by a threshold (base_line_change). If yes, it enters a peak event state, tracking how long the deviation lasts (peakSamplesCount) and its maximum magnitude (peakValue). When the deviation drops below the threshold or exceeds sample count (N), the peak event ends.
4. Vehicle Detection and Animation Trigger
When a peak event ends, it is treated as a detected vehicle event. The car animation on the matrix display is triggered to visually signal the vehicle passage.
5. Matrix Animation
The animation function playCarAnimation() simulates a car bitmap moving across the LED matrix from right to left with a blocking delay controlling frame speed.
ARDUINO CODE
#include <Arduino_LED_Matrix.h>
const int sensorDig = 2; // Digital pin 2 for sensor input (interrupt pin)
unsigned int updateRate = 100; // Sensor update rate in ms
unsigned int measureTime = 100; // Sensor measure time in ms
volatile unsigned int intEnable = 0; // Interrupt counter enable flag
volatile unsigned long sensorDigCnt = 0; // Count of sensor signal changes
unsigned long prevMillis = 0; // For timing updates
float base_line = 0.0; // Moving average baseline
const float alpha = 0.1; // Smoothing factor for baseline
const float base_line_change = 10.0; // Threshold for significant change
const int N = 10; // Number of samples for peak event duration
int peakSamplesCount = 0; // Samples counted during peak event
bool inPeakEvent = false; // Flag: tracking a peak event?
float peakValue = 0.0; // Maximum deviation recorded during peak event
int state = 0; // State machine variable
// LED matrix setup
ArduinoLEDMatrix matrix;
const int matrixWidth = 12;
const int matrixHeight = 8;
// Car bitmap 7x5 pixels
const uint8_t carWidth = 7;
const uint8_t carHeight = 5;
const uint8_t carBitmap[carHeight] = {
0b011110, // roof shape
0b1111111, // upper body
0b1111111, // lower body
0b0110110, // wheels
0b0000000 // chassis detail
};
uint8_t frame[matrixHeight][matrixWidth];
void clearFrame() {
for (int y = 0; y < matrixHeight; y++) {
for (int x = 0; x < matrixWidth; x++) {
frame[y][x] = 0;
}
}
}
void drawCar(int xPos) {
clearFrame();
for (int row = 0; row < carHeight; row++) {
for (int col = 0; col < carWidth; col++) {
bool pixelOn = carBitmap[row] & (1 << (carWidth - 1 - col));
int x = xPos + col;
int y = (matrixHeight - carHeight) + row; // bottom aligned
if (pixelOn && x >= 0 && x < matrixWidth && y >= 0 && y < matrixHeight) {
frame[y][x] = 1;
}
}
}
matrix.renderBitmap(frame, matrixHeight, matrixWidth);
}
void playCarAnimation() {
// Animate car moving from right to left
for (int x = matrixWidth; x >= -carWidth; x--) {
drawCar(x);
delay(150); // Simple blocking delay; consider millis() for non-blocking
}
matrix.clear();
}
void setup() {
Serial.begin(115200);
pinMode(sensorDig, INPUT);
attachInterrupt(digitalPinToInterrupt(sensorDig), sensorDigHandler, RISING);
matrix.begin();
matrix.clear();
// Initialize baseline with first measurement
sensorDigCnt = 0;
intEnable = 1;
delay(measureTime);
intEnable = 0;
base_line = sensorDigCnt;
Serial.print("Initial baseline: ");
Serial.println(base_line);
prevMillis = millis();
}
void loop() {
unsigned long currMillis = millis();
switch (state) {
case 1:
// Update baseline (moving average)
base_line = alpha * sensorDigCnt + (1.0 - alpha) * base_line;
// Calculate deviation from baseline
float diff = sensorDigCnt - base_line;
if (!inPeakEvent) {
// Not currently tracking a peak event
if (abs(diff) > base_line_change) {
inPeakEvent = true;
peakSamplesCount = 1;
peakValue = diff;
}
} else {
// Tracking peak event
peakSamplesCount++;
if (abs(diff) > abs(peakValue)) {
peakValue = diff;
}
// End peak detection when signal returns near baseline or max samples reached
if (abs(diff) <= base_line_change || peakSamplesCount > N) {
Serial.print("Peak detected: ");
Serial.println(peakValue);
inPeakEvent = false;
peakSamplesCount = 0;
peakValue = 0.0;
// Trigger car animation on new peak detected
playCarAnimation();
}
}
Serial.print("Sensor= ");
Serial.print(sensorDigCnt);
Serial.print(" Baseline= ");
Serial.println(base_line);
state = 0;
break;
}
if (currMillis - prevMillis >= updateRate) {
sensorDigCnt = 0;
prevMillis = currMillis;
intEnable = 1;
delay(measureTime);
intEnable = 0;
state = 1;
}
}
// Interrupt handler increments counter on rising edge
void sensorDigHandler() {
if (intEnable == 1) {
sensorDigCnt++;
}
}
Parameter Tuning
updateRate // How often sensor data is processed (ms) // Lower for faster response but higher CPU usage (100ms is good starting point)
measureTime // Duration during which pulses are counted (ms) // Should be less than updateRate; affects resolution of frequency measurement
alpha // Smoothing factor for moving average baseline //Lower (0.05-0.1) = slower baseline adjustment, more stable; higher = faster adaptation
base_line_change // Threshold to detect significant deviation (counts) // Tune empirically based on noise and vehicle signal amplitude, start with 10
N // Max duration of peak event in samples // Set to cover expected vehicle presence duration; too small might cut event early
How to test in the lab?
To test in the lab, simply move your keys or a magnet near the sensor—this creates a large enough magnetic disturbance to simulate a vehicle passing by, allowing quick verification of detection functionality.
Notice: Fluxgate magnetometers are extremely sensitive sensors that can detect very small magnetic field changes and are also affected by electromagnetic interference (EMI) in the lab – this can introduce falls triggers.
Optional Enhancements
Adding Non-Blocking Animation (Improvement Suggestion)
To avoid blocking sensor processing during animation, change the animation to run incrementally within the loop() using a state machine and millis() for timing:
Store animation state variables: current x-position and last frame time.
On peak detection, set a flag to start animation.
Each loop() iteration, check if animation flag is set and enough time elapsed since last frame.
Update car position, redraw frame, and clear when animation completes.
Meanwhile, sensor counting and peak detection continue running concurrently.
This approach allows vehicle detection to keep running without missing interrupts or updates due to delay() blocking.
Improving detection and logging algorithm
Optimize detection parameters: prevent false readings
Counting & Logging: Increment vehicle counts per detection event. Timestamp each event for historical data.
Use AI signal processing to identify different vehicle types
Use timestamps combined with geographic data (GPS) for spatial traffic analytics.
Connecting system with Arduino cloud
Cloud Upload: Send real-time counts, logs, and sensor data to Arduino Cloud for remote access and visualization.
Alerts & Analytics: Set thresholds for abnormal traffic flow or unusual magnetic signatures for notifications.
Analyse long-term traffic patterns remotely through Arduino Cloud dashboards.
Benefits of This Traffic Counter
Low-Cost & Easy to Deploy: Uses affordable, off-the-shelf components.
Highly Sensitive Detection: FG-3+ fluxgate magnetometer’s precision detects subtle magnetic changes from vehicles.
Scalable: Multiple units can create a wide-area traffic monitoring network.
Wireless & Cloud-Connected: No need for wired infrastructure, with real-time data available anywhere .
This project shows the real-world applicability of fluxgate magnetometer technology, especially the FG-3+ sensor from FG Sensors, in modern smart city solutions like traffic monitoring using Arduino’s versatile and connected platform.