Red Light Camera Alerter

I had the chance to play with some new components – namely the Adafruit Monochrome 128×32 OLED display and the Adafruit UP501 66 channel GPS receiver. As I was pondering ideas of what to build, I thought that it would be neat to be alerted when approaching a red light camera.  In my local area (Southern Ontario), there are currently about a hundred or so of these cameras around the GTA.  However, it appears that new legislation may see this number grow much larger.   This is more of a proof of concept project to me than it is useful as, a) I don’t intend on trying to run any red lights, and b) there are only about 1 or 2 of them within the area.   However, it was fun to build and tweak to make it useful.  Read on…

The general idea is that the GPS tracks the coordinates while driving,  the Arduino sketch has an array of red light camera location coordinates that it compares against the current reading.    On a regular basis, it checks the current position of the car against the array of red light camera locations.   With each check, it finds the closest red light camera location.  If that location is less than a pre-determined buffer distance (e.g. 3 km away), it will begin reporting on it via the OLED as well as the Status LED.

The Status LED flashes with 4 modes:

  • green – indicates that the system is functional and GPS is returning results.
  • blue – early warning of approaching red light camera location less than 600 metres away.
  • yellow -warning of red light camera less than 200 metres away.
  • red – red light camera intersection less than 50 metres away.

The Parts:

The OLED will display the distance to the Red Light Camera in Km, Direction that the vehicle is travelling, Speed, # of Satellites, Latitude and Longitude of the vehicle.

The Build:

The main unit consists of an Atmega 328P mounted to an Adafruit Perma-Proto Quarter sized breadboard.   The board has both 5v and 3.3v rails running down either side to provide power for various components.  The rest is a spaghetti mess of wires for all the components.   I also decided to make this unit (somewhat) modular.   The main board has been fitted with female header pins to allow all components to be plugged/unplugged.  So, if I want to re-use the controller for another project, I can just unplug all the bits and I have a dual power supply-ready Arduino.     Given that I would likely want to re-use the GPS on other projects, I hacked in USB ports on the main unit as well as a mini-USB connector on the GPS module.

As noted above, the GPS module is modular.  With the mini-USB connector, I can move it over to other projects in a breeze.   There are only 4 connectors that need to be brought to the Arduino to use this unit, so USB cables do the trick.  Although there are more pins needed, the wiring can be done at the GPS module end itself (e.g resistor between two pins).  I also wanted a minimal USB cable, so I picked up one of those self-retracting flat cable USB dongle thingamabobs and just took out the self-retracting bit.  The cable is very flexible as well.

The Verdict:

Adafruit UP501 GPS Module: The UP501 is small, which makes it great for fitting into small packages.  Another nice feature is that you only require 4 wires between the controller and the GPS, which makes it a perfect candidate for using readily available USB cables.   I managed to get this one before the newer Adafruit Ultimate GPS that comes with a backup battery.  As a result, my unit takes up to 45 seconds to as it would cold start every time it is powered up.  This is negligible considering I don’t need the GPS functioning the moment I start my car.  However, for other projects, this could be an issue.  Once the unit acquires its first fix, it provides fast accurate updates.  It is also great at acquiring signals indoors..  When testing, I would put it on my window sill to get a fix, then once fixed, I could move the unit to my desk to continue testing.

Adafruit 128×32 OLED Module: I won’t go into detail here as I have already done a review on this unit here.  However, I will say that this is a nice, crisp, small display that works very well with small projects.


Looking for an Android remote control for your robot? Check out BTBotControl - I built this to remotely control IPCamBot and view a live feed of a mounted IP Camera.


The Code:


// Select which PWM-capable pins are to be used.
int redPin = 9;
int greenPin = 3;
int bluePin = 10;

//SoftwareSerial mySerial(rxPin, txPin)
//SoftwareSerial nss(2, 3);  //yellow wire = RX  Blue wire = TX
SoftwareSerial nss(0, 2);   //yellow wire = RX  Blue wire = TX

#include                   //for OLED
#include                       //for OLED

#define OLED_RESET 7
#define OLED_DC 8
#define OLED_CS 6     // SPI slave-select
#define OLED_MOSI 11   // hardware SPI MOSI
#define OLED_CLK 13    // hardware SPI clock

// Connect the GPS Power pin to 3.3V
// Connect the GPS Ground pin to ground
// Connect the GPS VBAT pin to 3.3V if no battery is used
// Connect the GPS TX (transmit) pin to Digital 2
// Connect the GPS RX (receive) pin to Digital 3
// For 3.3V only modules such as the UP501, connect a 10K
// resistor between digital 3 and GPS RX and a 10K resistor
// from GPS RX to ground.
TinyGPS gps;
//NewSoftSerial mySerial(rxPin, txPin);


const int GPSActive = 4;               // Indicates GPS is getting data
const int statusLED = 5;               //System hearbeat LED

int updateFreq = 1000;                 //frequency to update GPS polls.  Good not to go below 1000 ms.

float flat, flon;                      //lat and long variables used in calculations
unsigned long age;                     //GPS reading age variable
float spd;                             // GPS Speed
float minDist = 100000;                //preset the minDist to some very high value (in meters)

//int md[] = {600, 180, 35};
//int j = 0;

const float ToRad = PI / 180.0;        // for HaverSine function
const float R = 6371;                  // radius earth in Km - for HaverSine function
int mappedDist = 0;                    // mapped variable converted for placement of icons on OLED
int spdProRate = 0;                    // pro rated speed value movement of road graphic on OLED - to mimic speed changes
int i = 128;                           // used for the looping through animations of the OLED
int state = LOW;                       // The input state of the status LED
int gpsStatCounter = 0;
// setup variables for Tiny GPS
void gpsdump(TinyGPS &gps);
bool feedgps();

char cVal[32];
int sats;
int hdop;
String cardinal;

// use for testing
boolean debug = true;            //print debugging statements to the serial port.

// different commands to set the update rate from once a second (1 Hz) to 10 times a second (10Hz)
#define PMTK_SET_NMEA_UPDATE_1HZ  "$PMTK220,1000*1F"
#define PMTK_SET_NMEA_UPDATE_5HZ  "$PMTK220,200*2C"
#define PMTK_SET_NMEA_UPDATE_10HZ "$PMTK220,100*2F"
// turn on only the second sentence (GPRMC)
#define PMTK_SET_NMEA_OUTPUT_RMCONLY "$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29"
// turn on ALL THE DATA
//#define PMTK_SET_NMEA_OUTPUT_ALLDATA "$PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0*28"
// to generate your own sentences, check out the MTK command datasheet and use a checksum calculator
// such as the awesome

//Define list of waypoints to search
//format WayPt[Lat][Long]
//multidimensional array [howmany dn] [howmany across]
const float wayPt[175][2] PROGMEM =

void setup()

  pinMode(statusLED, OUTPUT);

  //setup the RGB LED
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);

  //set the RGB status LED to off which is HIGH (255) reason: Common-Annode.
  analogWrite(redPin, 255);
  analogWrite(greenPin, 255);
  analogWrite(bluePin, 255);

  //Serial.println("Adafruit MTK3329 NMEA test!");
  nss.begin(9600);  //needed to change this to 9600 baud for the UP501 GPS module

  //*********  OLED SETUP STUFF ******************************************************************/
  SPI.begin ();  //for OLED
  // OLED: By default, we'll generate the high voltage from the 3.3v line internally! (neat!)

  //*********  GPS SETUP STUFF ******************************************************************/
  // 9600 NMEA is the default baud rate
  // uncomment this line to turn on only the "minimum recommended" data for high update rates!
  // uncomment this line to turn on all the available data - for 9600 baud you'll want 1 Hz rate
  // Set the update rate
  // 1 Hz update rate
  // 5 Hz update rate- for 9600 baud you'll have to set the output to RMC only (see above)
  // 10 Hz update rate - for 9600 baud you'll have to set the output to RMC only (see above)
  //*********  END GPS SETUP STUFF ******************************************************************/



void loop()
  bool newdata = false;
  unsigned long start = millis();

  blinkLED();   // heartbeat to ensure system is working
  cardinal= "";

  // Every xxxx milliseconds we print an update
  while (millis() - start < updateFreq)
    if (feedgps())
      newdata = true;

  heartbeat(newdata);  //shows external heartbeat signal through GPS status LED

      if (newdata)
                  feedgps(); // If we don't feed the gps during this long routine, we may drop characters and get checksum errors
                  gps.f_get_position(&flat, &flon, &age);
                  if (debug) {
                    Serial.print("Lat/Long(float): "); Serial.print(flat, 5); Serial.print(", "); Serial.print(flon, 5);
                    Serial.print(" Fix age: "); Serial.print(age); Serial.print("ms.");
                    Serial.print(" (kmph): "); Serial.print(gps.f_speed_kmph(),4); Serial.println();
                    //Serial.print(" Sats: "); Serial.print(gps.satellites()); Serial.println();
                    feedgps(); // If we don't feed the gps during this long routine, we may drop characters and get checksum errors
                    Serial.print(" Sats: "); print_int(gps.satellites(), TinyGPS::GPS_INVALID_SATELLITES, 5);
                    Serial.print(" HDOP: "); print_int(gps.hdop(), TinyGPS::GPS_INVALID_HDOP, 5);
                    Serial.print(" Course: "); print_float(gps.f_course(), TinyGPS::GPS_INVALID_F_ANGLE, 7, 2);
                    Serial.print(" Cardinal: "); print_str(gps.f_course() == TinyGPS::GPS_INVALID_F_ANGLE ? "*** " : TinyGPS::cardinal(gps.f_course()), 6);

                  //compare the latest reading with the array of red light camera points and find which is closest.
                  minDist = getClosestPt(flat, flon);
                  //minDist = md[j];
                  //if (j == 2){j = 0;}else{j++;}

                  //indicate that GPS is active and receiving data
                  flash(1);  //flashes the LED on the project box showing GPS signal

                  if (debug) {
                    Serial.print("Delta = ");Serial.print(minDist, 2);Serial.println(" Meters   ");

                  //Process alerts via various means based on pre-defined distance thresholds.

                  if (minDist < 50)                 //Warning Alert < 50 meters - in range if red light camera
                  else if (minDist < 200)          //Approaching Alert < 200 meters
                  else if (minDist <= 600)          //Initial Alert - 600 meters
                    //blue - early warning
                    //just show status that GPS is working.

                  //for testing
                  //minDist = 40;
                  //updateDisplay(minDist, 65.00);
                  //end testing

                  //updateDisplay(minDist, gps.f_speed_kmph());


          if (debug) {Serial.println("Polling GPS... waiting for fix...");}


      //Distance to next red light
      oled.drawstring(0, 0, "Dist:");
      if (minDist == 100000)
        oled.drawstring(30, 0, "?");
        ftoa(cVal, minDist/1000, 2);
        oled.drawstring(30, 0, cVal);
      oled.drawstring(55, 0, "km");

      //Cardinal Direction of vehicle
      //strcpy ( cVal, TinyGPS::cardinal(gps.f_course()) );
      strcpy ( cVal, gps.f_course() == TinyGPS::GPS_INVALID_F_ANGLE ? "?" : TinyGPS::cardinal(gps.f_course()));
      oled.drawstring(75, 0, "Dir:");
      oled.drawstring(100, 0, cVal);

      oled.drawline(0, 12, 128, 12, WHITE);

      spd = gps.f_speed_kmph();
      if (spd < 0) {spd=0;}
      ftoa(cVal, spd, 0);
      oled.drawstring(0, 2, "Spd:");
      oled.drawstring(25, 2, cVal);

      itoa(gps.satellites(), cVal, 0);
      oled.drawstring(65, 2, "Sats:");
      oled.drawstring(90, 2, cVal);

      ftoa(cVal, flat, 2);
      oled.drawstring(0, 3, "Lat:");
      oled.drawstring(25, 3, cVal);
      ftoa(cVal, flon, 2);
      oled.drawstring(65, 3, "Lng:");
      oled.drawstring(90, 3, cVal);

      //Display all the results

      //reset minDist for the next round
      minDist = 100000;     //meters


char *ftoa(char *a, double f, int precision)
  long p[] = {0,10,100,1000,10000,100000,1000000,10000000,100000000};

  char *ret = a;
  long heiltal = (long)f;
  itoa(heiltal, a, 10);
  while (*a != '\0') a++;
  *a++ = '.';
  long desimal = abs((long)((f - heiltal) * p[precision]));
  itoa(desimal, a, 10);
  return ret;

void setupDisplay()
    // Draw animated graphics to OLED - we will use the GPS speed to mimic changes in speed of the lanes.
    oled.clear();   // clears the screen and buffer
    //oled.drawbitmap(0, 2,  Road, 128, 32, 1);     //road - dash lines - draw the animating road
    //oled.drawbitmap(95, 4,  JukeIcon, 34, 40, 1);  //keep drawing the car icon in place
    oled.fillrect(0, 0, 128, 2, WHITE);            //keep drawing the top continuous line
    oled.fillrect(0, 30, 128, 2, WHITE);           //keep drawing the bottom continous line

void updateDisplay(int minDist, float spd)
    if (i==0){i=128;}  //we have 128 pixels to loop through for animation of the road dashed lines. reset when it gets to 0.

    // Draw animated graphics to OLED - we will use the GPS speed to mimic changes in speed of the lanes.
    //oled.drawbitmap(i, 16,  Road, 128, 32, 1);     //road - dash lines - draw the animating road
    //oled.drawbitmap(i-128, 16,  Road, 128, 32, 1);  //road - dash lines - draw the road ahead to keep contents on the screen

    //map values based on dist from camera, min dist on ground of 20m, max dist on ground of 500m, min dist in px to car, max dist in px from car
    mappedDist = map(minDist, 20, 500, 40, 110);
    //oled.drawbitmap(mappedDist, 10,  RedLightCam, 10, 15, 1);  //move the red light camera as it approaches

    //oled.drawbitmap(95, 12,  JukeIcon, 34, 40, 1);  //keep drawing the car icon in place
    oled.fillrect(0, 0, 128, 4, WHITE);            //keep drawing the top continuous line
    oled.fillrect(0, 61, 128, 4, WHITE);           //keep drawing the bottom continous line
    oled.clear();   // clears the screen and buffer

    // convert speed to an int so it can be used in the map funciton.  1.25 km/h = 125
    spdProRate = map(int(spd*10), 0, 900, 0, 30);
    i = i - spdProRate;
    if (i < 0) {i=0;}

    //mappedDist = 0;

float getClosestPt(float flat, float flon)
  float d;
  float d2;
  float md = 10000;
  //int minCoordIndex;
  //int coordsProcessed;

              if (flat != 0 && flon != 0)
                for (unsigned int x=0; x<175; x++){
                      //In array lat is 2nd col, long is 1st col  e.g. lat:43.xxxx,long:-79.xxxxx - need to get right ones for calc
                      //flat2 =  pgm_read_float_near(&wayPt[x][0]);
                      //flon2 = pgm_read_float_near(&wayPt[x][1]);
                      //if (debug)
                      //  Serial.print("Flat2:  ");Serial.print(flat2, 5);
                      //  Serial.print("  Flon2:  ");Serial.println(flon2, 5);
                      //d = HaverSine(flat, flon, pgm_read_float_near(&wayPt[x][0]), pgm_read_float_near(&wayPt[x][1]));
                      d = gps.distance_between(flat, flon, pgm_read_float_near(&wayPt[x][0]), pgm_read_float_near(&wayPt[x][1]))/1000 ;
                      if (debug)
                        //Serial.print("DistanceCalcualted d1: ");Serial.print(d, 4); Serial.print(" d2:"); Serial.print(d2, 4); Serial.println("");
                        //Serial.print("DistanceCalcualted d1: ");Serial.print(d, 4); Serial.println("");
                      if (d < md)
                        md = d;
                        //minCoordIndex = x;

                  //convert to meters
                  md = md * 1000;

                  //Serial.print(" Processed:");Serial.println(coordsProcessed);
                  Serial.print(" Index:");Serial.println(minCoordIndex);
                  Serial.print(" Processed:");Serial.println(coordsProcessed);
                //coordsProcessed = 0;
 return md;

 float HaverSine(float lat1, float lon1, float lat2, float lon2)
  //float ToRad = PI / 180.0;
  //float R = 6371;   // radius earth in Km

  float dLat = (lat2-lat1) * ToRad;
  float dLon = (lon2-lon1) * ToRad;

  float a = sin(dLat/2) * sin(dLat/2) +
	  cos(lat1 * ToRad) * cos(lat2 * ToRad) *
	  sin(dLon/2) * sin(dLon/2);

  float c = 2 * atan2(sqrt(a), sqrt(1-a));

  float d = R * c;
  return d;  //distance returned in Kilometers

bool feedgps()
  while (nss.available())
    if (gps.encode(
      return true;
  return false;

static void print_float(float val, float invalid, int len, int prec)
  char sz[32];
  if (val == invalid)
    strcpy(sz, "*******");
    sz[len] = 0;
        if (len > 0)
          sz[len-1] = ' ';
    for (int i=7; i= 1000 ? 4 : vi >= 100 ? 3 : vi >= 10 ? 2 : 1;
    for (int i=flen; i 0)
    sz[len-1] = ' ';

void blinkLED()
state = !state;
digitalWrite(statusLED, state);

11 Responses to “Red Light Camera Alerter”

  1. Me says:

    Where did you get the cases?

  2. John says:

    Awesome project. And one that could be modified to any application (gas station finder, free wi-fi finder, White Castle finder, geocaching, etc).

    My suggestion would be to try to eliminate the status LED and use the backlight on one of these instead

  3. Andy says:

    Where did you get the list of camera locations? That seems to be the most difficult part

  4. Kevin Hoyt says:

    Great project! Where does the unit get its power? Are running the USB to a cigarette lighter? If so, what lighter adapter did you chose?

    • Dave says:

      The USB goes between the GPS receiver and the Arduino controller. The power is wired from the controller to 12V at a mains connector I added to my fuse panel (for this and other projects in the future).

  5. Covert says:

    After 2 speeding fines in 4 days (4 years ago for my last one) I have decided to build something like this. The source code looks like a great place to start. I will be dropping the OLED screen and just using the status LED that will be mounted in the drivers view. As a driver you don’t have time to look at a screen but a flashing LED quickly gets ones attention.

    • Dave says:

      The purpose of this DIY isn’t to help you get away speeding and running red light cameras. You got speeding fines …because you were speeding. Obey the signals pls.. This DIY should only be used for information purposes.

  6. can you please provide me the schematic for this project? thanks

Leave a Reply


Get every new post delivered to your Inbox

Join other followers: