Introduction: MotorBike/PushBike Alarm System

A friend of mine purchased a new motorbike recently, along with it he also bought an alarm. However, as he lives in a second story apartment he was worried that if the alarm did sound, he would not be able to hear it.

He asked if I was able to come up with something that would alert him if anyone was to tamper with his bike.

I decided I was going to incorporate a bare-bones Arduino (ATMega 328p), an RFID reader (Mifare MFRC522), a MPU6050 6-axis gyro/accelerometer, and a NRF24L01 transceiver.

The idea behind the system was to have a small module hidden on the bike, powered by the bike's battery (or a rechargeable battery pack if it was for a pushbike). It would have an RFID reader to arm and disarm the system. Once armed, if the bike was moved in any way then it would send a signal to a receiver in the apartment which could then alert my friend in any number of ways.

Step 1: Creating a Bare-bones Arduino

There is a great guide on creating these by Nick Gammon here.

To program the chip you will need an Arduino Uno/Nano/Mega etc. To prepare the ATMega328p it needs to have a bootloader loaded into it.

If you have read through Nick Gammon's guide then you will have the 'Atmega_Board_Programmer' sketch. Upload this into the Uno/Nano/Mega. Using a breadboard and some wires connect the Arduino to the ATmega328p as follows:

  • Arduino Pin 13 (SCK) to ATmega328p Pin 19
  • Arduino Pin 12 (MISO) to ATmega328p Pin 18
  • Arduino Pin 11 (MOSI) to ATmega328p Pin 17
  • Arduino Pin 10 (SS) to ATmega328p Pin 1
  • Arduino +5V to ATmega328p Pin 7 and Pin 20
  • Arduino GND to ATmega328p Pin 8 and Pin 22

Open the Serial Monitor window in the Arduino IDE and you should see an option to burn either a 'Lilypad' or 'Uno' bootloader. I have chosen 'Lilypad' purely because it uses an internal oscillator for its clock source instead of an external crystal (less components). 8MHz clock speed is more than fast enough for this application. Go ahead and press 'L' and the Arduino should load your ATmega328p with the Lilypad bootloader.

If everything up to this point has worked, you should now have a ATmega328p that thinks it is a Lilypad Arduino. Now find the example sketch called 'Arduino as ISP' and upload that in to your Arduino Uno/Nano/Mega. This means that your Arduino is now acting as an ISP programmer, from this point on you use the 'Upload Using Programmer' option in the Arduino IDE > Sketch menu and NOT the 'Upload' button. If you do use the 'Upload' button you will overwrite the 'Arduino as ISP' sketch and your Arduino will no longer act as an ISP programmer. You will need to go back and re-upload the 'Arduino as ISP' sketch.

To test the programmer, select the board type 'Lilypad Arduino' from the 'Tools > Board' menu, go to the example sketches and select '01.Basics > Blink'. Now select 'Sketch > Upload Using Programmer'. Once the sketch has finished uploading connect a LED and 1K resistor in series to pin 19 of the ATMega328p and GND. At this point you can remove the programmer wires if you want and rather than use the Arduino for a power source you can connect a separate 5V supply (3 x 1.5V AA batteries in series will do) and you should see the LED flash. Congratulations you have programmed your first standalone MCU!

Step 2: Creating the Circuit

The peripherals being used for this are:

  • MFRC522 RFID reader - connect using SPI
  • MPU6050 Gyro/Accelerometer - connect using I2C
  • NRF24L01 Transceiver - connect using SPI
  • 5V buzzer - to provide audible feedback
  • Common Cathode RGB LED (optional) - to provide visual feedback
  • Push Button - to wipe the EEPROM

The RFID reader and NRF24L01 Transceiver use the SPI bus to communicate with the ATmega328p, so they will share connections to the MOSI, MISO, and SCK pins (17, 18 and 19 of the ATmega328p). The RFID reader also needs two extra pins to be defined as the SS pin and the RST pin, which I have selected pins 13 and 14 on the ATmega328p (these are digital pins 7 and 8 in the Arduino IDE). The NRF24L01 Transceiver also needs two extra pins for Chip Enable and Chip Select, which I have selected pins 15 and 16 on the ATmega328p (digital pins 9 and 10 in the Arduino IDE). These pins allow the sketch to call (and communicate with) each device over the SPI bus separately.

These two devices also need 3.3V for power, so I have included an LM1117T 3.3V voltage regulator to allow this.

The MPU6050 is an I2C device and so only need SDA and SCL connnections to the ATmega328p which can be found on pins 27 and 28. This device is powered from the same 5V that we need to provide the ATmega328p with, and as such a LM7805 5V voltage regulator provides this.

The buzzer is a simple 5V buzzer that works directly from the ATmega328p pin 5 (digital pin 3).

Although for this instance I haven't used an RGB LED there is provision to include one both in the software and the schematic on pins 6, 11 and 12 (digital pins 4, 5 and 6).

The push button is there to wipe the EEPROM. The way the circuit works is that if this button is held down for more than 5 seconds on power-up then the software will check for any RFID codes in the EEPROM and erase them. (This is not something that should be needed that often and the operation will become clear later)

Once the schematic was finished the circuit was designed on strip-board. You could, of course, make your own PCB at this stage, but I prefer the simplicity of strip-board for my projects and have little success at PCB making.

Step 3: Populating the Plastic Enclosure

The plastic enclosure that I was working with was extremely small considering the amount of peripherals I needed to fit inside, and although I managed it (pure luck rather than design) I would encourage you to leave a little more space to work with. In fact, once I had managed to fit everything inside and started to test I found that the RFID reader was not functioning correctly. It was intermittent and the range was severely affected. I found that if I removed the circuit from the box the problems went away. I decided that the proximity of the RFID reader to the underside of the strip-board circuit was causing Radio Frequency Interference (RFI). This was solved by rearranging the components so that the strip board was the first item to fit into the box and the RFID reader would sit on top of this, rather than underneath as shown in the above images.

The last picture shows the NRF24L01 Transceiver and MPU6050 placed in their approximate positions.

Step 4: Wiring the Circuits

I used size 24 AWG wire to connect the components. A larger enclosure would have been helpful at this point. I tried to ensure that the device could still be removed from the enclosure without disconnecting any wires. The only exception to this would be the DC connector wires. These needed to be long enough to allow the internal circuitry to be 'unfolded' from within the enclosure to allow access to the undersides of both the RFID reader and strip-board.

Another consideration is the LM7805. In my design this device has no heat-sink and is packed close to the wiring loom. I am using aircraft grade wire which has a higher temperature rating than standard cable, and I can appreciate is not easily available. I have also only tested using a 9V supply so am unsure how hot this device will get if powered from a motorbikes 12V electrical system. An alternative would be to use a DC-DC converter as these are more efficient and produce less heat.

Step 5: The Sending Code

So far we have only designed half of the system, the Transmitter.

Let's look at some of the code:

#include <EEPROM.h> // read and write PICC's UIDs from/to EEPROM
#include <SPI.h> // MFRC522 and NRF24L01 uses SPI protocol

// set up MFRC522 RFID reader #include <MFRC522.h> #define SS_PIN 7 #define RST_PIN 8 MFRC522 mfrc522(SS_PIN, RST_PIN); // create MFRC522 instance

The EEPROM.h header file is required as we are going to use the ATmega328p's EEPROM to store RFID tag unique ID codes so that if power is removed the access codes are retained in memory.

The SPI.h header file is required as the MFRC522 and NRF24L01 both communicate via the SPI bus.

Both these libraries are included with the Arduino IDE software.

The MFRC522.h library can be downloaded here. There are plenty of guides online for installing new libraries in Arduino IDE so will not discuss that here.

Next we can see that we need to create an instance of the MFRC522 RFID reader called mfrc522 and define the SS pin and RST pins as digital pins 7 and 8.

// set up NRF24L01 transceiver
#include "nRF24L01.h" #include "RF24.h" #define CHIP_ENABLE 9 #define CHIP_SELECT 10 RF24 radio (CHIP_ENABLE, CHIP_SELECT); // create NRF24L01 instance const uint64_t pipes[2] = { 0xF0F0F0F0E2LL, 0xF0F0F0F0D3LL }; // and declare pipe addresses

Then we follow up with creating an instance of the NRF24L01 Transceiver. This uses header files from the RF24 library which can be downloaded from here. Again, we can see how we have defined digital pins 9 and 10 as the Chip Enable and Chip Select pins. Here we can see how to declare the pipe addresses for the radio communication. So long as the receiving sketch uses the same pipe addresses then the two devices will be able to communicate with each other. (More about this later)

set up MPU6050 accelerometer/gyro
#include "I2Cdev.h" #include "MPU6050.h" #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE #include "Wire.h" #endif MPU6050 accelgyro; // create MPU6050 instance //MPU6050 accelgyro(0x69); // <-- use for AD0 high int16_t axPrev, ayPrev, azPrev; int16_t gxPrev, gyPrev, gzPrev; int16_t ax, ay, az; int16_t gx, gy, gz; #define ACCEL_SENSITIVITY 7000 #define GYRO_SENSITIVITY 3000

As before we need to set up the MPU6050. The I2Cdev.h library and MPU6050.h library are both included in this download. The MPU6050 is a great device which is capable of so much more than how I have implemented it in this project. The ACCEL_SENSITIVITY and GYRO_SENSITIVITY values can be changed to make the device more or less sensitive to movements depending on your situation, you may need to experiment with these values.

The System Configuration code that follows is just pin definitions and various variable declarations for the sketch.

Step 6: System Operation

After switch-on the first thing the code does is check if the Wipe button is pressed (if it is pressed and held for more than 5 seconds it will check the EEPROM for any values other than 0xFF and write 0xFF to those locations, clearing the EEPROM but minimizing the number of write cycles). This is what you will need to do should you ever lose the Master Card.

It then checks the EEPROM memory address 1. If this location doesn't hold the number 143 (which it won't at first switch on) then it knows that a Master Card has not been defined. The system will now wait until you scan a tag/card, when you do it will store the cards unique ID in to EEPROM and write the value 143 into memory address 1. This card then becomes the Master Card and is used to put the system in to 'Program Mode'.

The system now continually checks for a valid tag/card. At this point you will not have any valid tags/cards that have been added to the access list. To do this you will need to scan the Master Card again. The system will now go in to 'Program Mode', which will be evident by the intermittent beeps from the buzzer (or LED colors cycling through red, green and blue). To add a card/tag to the access list just scan it and the system will add the unique ID to the access list. If this card was already added previously the system will see that unique ID already in EEPROM and will remove it, handy should you wish to remove any cards from the access list. You can add/remove more tags/cards at this point, and when finished just scan the Master Card again to exit 'Program Mode'.

// Check if master card defined, if not let user choose a master card
// This also useful to just redefine Master Card // You can keep other EEPROM records just write other than 143 to EEPROM address 1 // EEPROM address 1 should hold magic number which is '143' if (EEPROM.read(1) != 143) { Serial.println(F("No Master Card Defined")); Serial.println(F("Scan A PICC to Define as Master Card")); do { successRead = getID(); // sets successRead to 1 when we get read from reader otherwise 0 digitalWrite(blueLed, LED_ON); // visualise Master Card need to be defined delay(200); digitalWrite(blueLed, LED_OFF); delay(200); } while (!successRead); // program goes no further while no successful read for (int j = 0; j < 4; j++) // loop 4 times { EEPROM.write( 2 + j, readCard[j] ); // write scanned PICC's UID to EEPROM, start from address 3 } EEPROM.write(1, 143); // write to EEPROM we have defined Master Card Serial.println(F("Master Card Defined")); } Serial.println(F("-------------------")); Serial.println(F("Master Card's UID")); for ( int i = 0; i < 4; i++ ) // read Master Card's UID from EEPROM { masterCard[i] = EEPROM.read(2 + i); // write it to masterCard Serial.print(masterCard[i], HEX); } Serial.println(""); Serial.println(F("-------------------")); delay(2000); Serial.println(F("Everything Ready")); Serial.println(F("Waiting for PICCs to be scanned")); cycleLeds(); // everything is ready, lets give the user some feedback

Now that you have a valid tag/card you can arm the system by scanning and you should hear one beep from the buzzer. Scanning it again will disarm the system and you will hear two beeps from the buzzer.

With the system armed, if the MPU6050 detects motion that exceeds the limits you previously set at the beginning of the sketch (ACCEL_SENSITIVITY and GYRO_SENSITIVITY) the NRF24L01 simply transmits the number 1 out to the receiver unit. The receiver unit will be configured to generate some alarm conditions when it receives the number 1 as shown later.

The following function within the sending sketch is what is used to transmit:

void triggerAlarm()
{ // now disabe MFRC522 pinMode(RST_PIN, OUTPUT); digitalWrite(RST_PIN, LOW); pinMode(SS_PIN, OUTPUT); digitalWrite(SS_PIN, LOW); delay(50); byte sendCommand = 1; digitalWrite (SS, HIGH); digitalWrite (CHIP_ENABLE, LOW); digitalWrite (CHIP_SELECT, HIGH); // Enable NRF24L01

SPI.begin(); radio.setPALevel(RF24_PA_MAX); // set Tx to max power radio.setDataRate(RF24_250KBPS); // better range radio.setChannel(108); // 2.508 GHz - above most WiFi channels radio.begin(); radio.setRetries(15, 15); radio.setPayloadSize(8); radio.openWritingPipe (pipes[0]); // sets Tx address radio.openReadingPipe (1, pipes[1]); // sets number of Rx's and addresses radio.startListening(); delay(10); radio.stopListening();

radio.write (&sendCommand, sizeof sendCommand); // send a command radio.startListening(); radio.powerDown();

// now re-enable MFRC522 SPI.begin(); mfrc522.PCD_Init(); mfrc522.PCD_SetAntennaGain(mfrc522.RxGain_max); }

Notice that the MFRC522 is disabled before we use the NRF24L01 as they share the SPI bus for communication. Also there are some other settings to take note of that we must also set in the receiver system. We have set the radio to max power, 250KB/s data rate and channel 108 to help with range.

Once the radio has finished transmitting the Command signal (the number 1) the radio is set to power down and the MFRC RFID reader is re-enabled.

Step 7: The Receiver Unit

My receiver unit is not a finished unit for this system, rather it is an earlier project that I have modified to act as a receiver unit. The unit consists of a bare-bones Arduino ATmega328p mounted in a plastic enclosure with an I2C 16x2 Liquid Crystal Display, a 240VAC 10A relay, an RGB LED, a buzzer and a NRF24L01 transceiver. The idea here is that when an alarm signal is received the LCD displays a message and flashes along with an RGB LED, the buzzer sounds and the relay switches on a security floodlight or something similarly high powered.

My receiver unit also includes another Arduino Uno with Ethernet Shield that is programmed to access an online service called Twilio which will send an SMS text message to my mobile phone. However, I will concentrate only on the ATmega328p receiver circuit, if you want to include this functionality in your device then head over to here to learn how. My code shows how I have sent a digital output from the ATmega328p receiver circuit to the Arduino Uno to trigger the send SMS code. Also notice that my receiver unit houses a 4-way relay board, yet my code only utilizes one of those relays.

If we take a look at the code:

// set up I2C 16x2 LCD display
#include <Wire.h> #include <FastIO.h> #include <I2CIO.h> #include <LCD.h> #include <LiquidCrystal.h> #include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // set the LCD I2C address

Here we are setting an instance for the LCD display called 'lcd'. The header files included are all part of the LiquidCrystal_I2C library found here.

// set up NRF24L01 transceiver
#include <SPI.h> #include "nRF24L01.h" #include "RF24.h" #define CHIP_ENABLE 9 #define CHIP_SELECT 10

RF24 radio (CHIP_ENABLE, CHIP_SELECT); const uint64_t pipes[2] = { 0xF0F0F0F0E2LL, 0xF0F0F0F0D3LL };

Next, we set up the NRF24L01 instance using the same method as the transmitter unit ensuring that we use the same pipe addresses.

/*********************************************************/
/****************** System Configuration *****************/ /*********************************************************

/ define output pins #define SMS_OUTPUT 2 #define BUZZER 4 #define GREEN_LED 5 #define RED_LED 6 #define BLUE_LED 7 #define FLOOD_LIGHT 8

// define durations #define ALARM_DURATION 10000 // time in milliseconds that alarm will activate #define BUZZER_DURATION 0000 // time in milliseconds that buzzer sounds #define DISABLE_DURATION 30 // time in minutes that the alarm will be disabled

// declare global variables unsigned long lastReading; unsigned int count = 0;

The next part of the code defines various variables for the sketch. The alarm duration and buzzer duration are configurable here - I have the buzzer duration set to 0 at this time as it drives my wife mad every time I test it :)

void setup(void)
{ Serial.begin(38400); pinMode (SMS_OUTPUT, OUTPUT); pinMode (BUZZER, OUTPUT); pinMode (GREEN_LED, OUTPUT); pinMode (RED_LED, OUTPUT); pinMode (BLUE_LED, OUTPUT); pinMode (FLOOD_LIGHT, OUTPUT);

digitalWrite(SMS_OUTPUT, HIGH); // don't send a text message until triggered

lcd.begin(16,2); lcd.clear(); lcd.backlight(); lcd.setCursor(0,0); lcd.print("System Status: "); lcd.setCursor(0,1); lcd.print(" ARMED ");

// flash green LED to indicate system reset for (int i = 0; i < 10; i++) { digitalWrite(GREEN_LED, HIGH); delay(50); digitalWrite(GREEN_LED, LOW); delay(50); } // leave green LED on to indicate system power digitalWrite(GREEN_LED, HIGH);

// set up and configure RF radio SPI.begin(); //radio.setPALevel(RF24_PA_MAX); radio.setDataRate(RF24_250KBPS); radio.setChannel(108); radio.begin(); radio.setRetries(15, 15); // delay between retries, # of retries radio.setPayloadSize (8); // reducing payload size improves reliability radio.openWritingPipe(pipes[1]); radio.openReadingPipe(1,pipes[0]); radio.startListening(); //radio.printDetails(); // dump RF configuration for debugging

} // END OF SETUP

The setup code configures the GPIO pins, prints a message to the LCD screen and flashes the green LED ten times. Then it configures the NRF24L01 with the same data rate and channel as the transmitter unit and starts listening for incoming transmissions.

The main loop simply calls the checkRadio() function over and over again.

void checkRadio(void)
{ // if there is data ready if ( radio.available() ) { // dump the payloads until we have everything unsigned int command; bool done = false; while (!done) { // fetch the payload, and see if it is the last one done = radio.read( &command, sizeof command);

if (command == 1) { // sound the alarm count++; Serial.print(count); Serial.println(" - Command Received"); triggerAlarm(); } // end of if(command == 1) } // end of reading the payload } // end of data available } // end of checkRadio

The checkRadio() function works simply by checking any data received and if that data is equal to 1 then jump to the triggerAlarm() function.

void triggerAlarm(void)
{ unsigned long startTime = millis(); // mark the beginning of the function unsigned long previousBuzzerMillis; // used for buzzer sequence byte buzzerState; // used for buzzer sequence const byte buzzer_interval = 100; // used for buzzer sequence unsigned long previousLedMillis; // used for LED flash sequence const byte led_interval = 100; // used for LED flash sequence byte redState; // used for LED flash sequence byte blueState; // used for LED flash sequence digitalWrite(SMS_OUTPUT, LOW); // send a text message delay(500); // Temboo SMS send code has delay(150) in loop digitalWrite(SMS_OUTPUT, HIGH);

do { // switch on the floodlight and switch off the green LED digitalWrite(FLOOD_LIGHT, HIGH); digitalWrite(GREEN_LED, LOW);

// start the buzzer sequence if (millis() - startTime < BUZZER_DURATION) { // alternate the buzzer ON/OFF for the entire buzzer sequence if (millis() - previousBuzzerMillis >= buzzer_interval) { previousBuzzerMillis = millis();

if (buzzerState == LOW) { buzzerState = HIGH; // switch ON } else { buzzerState = LOW; // switch OFF }

digitalWrite(BUZZER, buzzerState); } }

if (millis() - startTime > BUZZER_DURATION) { // make sure buzzer is off at the end of buzzer sequence digitalWrite(BUZZER, LOW); } // end of buzzer sequence

// start the LED flashing sequence if (millis() - previousLedMillis >= led_interval) { previousLedMillis = millis();

if (redState == LOW) { // print a message on LCD display lcd.setCursor(0,0); lcd.print(" ALERT!!! "); lcd.setCursor(0,1); lcd.print(" "); redState = HIGH; blueState = LOW; lcd.noBacklight(); // flash LCD display } else { redState = LOW; blueState = HIGH; lcd.backlight(); // flash LCD display }

digitalWrite(RED_LED, redState); digitalWrite(BLUE_LED, blueState); } } while (millis() - startTime < ALARM_DURATION);

// return system to "Armed" configuration digitalWrite(FLOOD_LIGHT, LOW); digitalWrite(BUZZER, LOW); digitalWrite(RED_LED, LOW); digitalWrite(BLUE_LED, LOW); digitalWrite(GREEN_LED, HIGH); lcd.backlight(); lcd.setCursor(0,0); lcd.print("System Status: "); lcd.setCursor(0,1); lcd.print(" ARMED "); } // end of triggerAlarm()

The triggerAlarm() function firstly sends a text message signal to my other Arduino (not included in this Instructable). Then the rest is what is called non-blocking code to allow us to work with time delays, and basically sounds the alarm for a pre-determined duration (ALARM_DURATION) before returning the system to an 'armed' state.

Have fun!

Bicycle Contest 2016

Participated in the
Bicycle Contest 2016