How to Develop an Arduino-Based Optical Tachometer

In this post, I’ll explain how to develop an Arduino-Based Optical Tachometer that measures the speed of a brushless propeller. The speed of the propeller must be measured using an IR Emitter/Detector pair using either Round Robin with Interrupts or Function Queue Scheduling. The RPMs (revolutions per minute) must be captured over time, downloaded to a Host, and graphed.

If you have ever driven a car or looked at the dashboard of a car, you have seen a tachometer. It is that meter on the other side of your speedometer that measures the rotation speed of your engine’s crankshaft in revolutions per minute (RPM). A tachometer is an instrument that measures the rotation speed of a shaft or disk, such as in a motor.

tachometer

Requirements

Here are the requirements I created for this project:

  • The system must execute on an Arduino.
  • The speed of a brushless propeller must be measured using an Infrared (IR) Emitter/Detector pair
  • The speed of a brushless propeller must be measured using Round Robin with Interrupts.
  • The revolutions per minute (RPMs) must be captured over time.
  • The RPMs must be downloaded to a Host.
  • Time vs. RPMs must be graphed.

Hardware Design

The following components are used in this project. You will need:

Here is the diagram of the hardware setup:

hardware_diagram_optical_tachometer

For the motor setup on the left of the image above, the current (when the transistor is “turned on”) goes from the 5V power supply, through the green wire, through the motor, and then down the yellow wire. It then passes through the collector towards the emitter, and then down to ground. If the transistor is shut off, it is no longer conducting. The residual peak current that would normally go through the transistor from the yellow wire needs somewhere to go. Instead of being forced through the non-conducting transistor (and potentially damaging it), the current redirects (shorts through the motor) and dissipates through the diode (from left to right through the diode in the schematic above) since diodes only allow current to travel in one direction.

optical_tachometer_arduino (1)
optical_tachometer_arduino (1)
optical_tachometer_arduino (2)
optical_tachometer_arduino (2)
optical_tachometer_arduino (3)

Troubleshooting Tips

  • There might be occasions where you start the motor (after you run the code in the Implementation section below), and the RPM readings start firing even though the propeller is nowhere near the IR emitter/receiver pair. If this happens to you, power the propeller/motor from a separate breadboard and Arduino.
    • You will also need to take out the pieces of code from the code below that pertain to the operation of the motor and upload that code in a separate sketch to the other Arduino (the one that will power the propeller). It is a pain in the butt to do all this and can take several extra hours or so of development time, but it will help solve this problem.
  • It is also helpful to cover the propeller blades with black electrical tape in case the IR emitter beam is passing right through the propeller blades.

Implementation

Here is the source code that you will need to load to your Arduino:

/**
 * In this program, we develop an Arduino-Based Optical Tachometer 
 * that measures the speed of a brushless propeller.
 * 
 * The Infrared LED is connected to pin 13.
 * The Infrared Phototransistor is connected to pin 2 (interrupts).
 * 
 * @version 1.0 2019-02-18
 * @author Addison Sears-Collins
 */
 
// Assign a name to the DC motor pin on the Arduino Uno
const unsigned int MOTOR_PIN = 3;

// Assign a name to the infrared LED pin on the Arduino Uno
const unsigned int IR_LED = 13;

// The number of blades on the propeller. Adjust accordingly.
const unsigned int BLADE_COUNT = 3;

// Volatile keyword is used with interrupts
// This variable is subject to change inside an interrupt
// service routine
volatile unsigned int break_number = 0;

// Flag used to stop the program
bool done = false;

// Used for capturing the time
unsigned long time;

// Used for capturing the rpm (revolutions per minute)
unsigned int rpm;

/**
 *  Function runs only once, after each powerup or reset of the Arduino Uno
 */
void setup() {

  // Open the serial port and set the data transmission rate to 9600 bits 
  // per second. 9600 is the default baud rate for Arduino Uno.
  Serial.begin(9600);

  // Show a welcome message as human-readable ASCII text
  Serial.println("PROPELLER RPM PROGRAM");
  Serial.println("This program transmits the time and RPM of a propeller.");
  Serial.println("Created by Addison Sears-Collins");
  Serial.println("");
  Serial.println("Press ! to end the program");
  Serial.println("");
  Serial.println("Please enter the desired speed of the motor.");
  Serial.println("Must be a value between 100 and 255.");
  Serial.println("");
  Serial.println("TIME, RPM");

  // The Infrared phototransistor is connected to pin 2.
  // Interrupt triggers when signal goes from HIGH to LOW
  attachInterrupt(digitalPinToInterrupt(2), isr_break_count, FALLING); 

  // Turn on the IR Led
  pinMode(IR_LED, OUTPUT);
  digitalWrite(IR_LED, HIGH);

  // Enable output for the motor
  pinMode(MOTOR_PIN, OUTPUT);

  break_number = 0;
  rpm = 0;  
 
}

/**
 *  Main function
 */
void loop() {

  display_time_and_rpm();
  
  start_motor();

  while(!done) {    

    // Update time and rpm every second
    delay(1000);

    // Don't process interrupts during this calculation
    noInterrupts();

    // Calculate the RPM. If a 3-blade propeller, 3 breaks
    // per second would yield 1 rpm, which is 60 rpm. 
    rpm = (60 * break_number) / BLADE_COUNT;

    // Display the time and rpm
    display_time_and_rpm();

    // End program if sentinel is entered 
    end_program();    
    
    break_number = 0;
    rpm = 0;

    // Restart interrupts
    interrupts();
  }
  
  // Do nothing
  while (true) {}
}

/**
  * This function starts the motor
  */
void start_motor() {

  // Wait for the user to enter the speed of the motor
  while (Serial.available() == 0){};

  // Activate the motor
  int speed = Serial.parseInt();
  if (speed >= 100 && speed <= 255) {
    analogWrite(MOTOR_PIN, speed);
  }
}

/**
  * Interrupt service routine.
  * This function counts the number of interrupts
  */
void isr_break_count() {

  break_number++;
  
}

/**
  * Function displays the time and rpm
  */
void display_time_and_rpm() {
  
  // Capture the time and covert to seconds
  time = millis() / 1000;

  // Display the time
  Serial.print(time); 
  Serial.print(" , ");
  // Println so the next line begins on a new line
  // Display the rpm
  Serial.println(rpm);   
}

/**
  * This function ends the program
  */
void end_program() {
  
  // Used for reading data from the serial monitor
  char ch;

  // Check to see if ! is available to be read
  if (Serial.available()) {     
  
    // Read the character
    // Serial.read() returns the first (oldest) character in the buffer 
    // and removes that byte of data from the buffer
    ch = Serial.read();    

    // End the program if an exclamation point is entered in the
    // serial monitor
    if (ch == '!') {
      done = true;  

      // Turn off the motor
      analogWrite(MOTOR_PIN, 0);

      // Turn off the IR LED
      digitalWrite(IR_LED, LOW);
      
      Serial.println("Finished recording RPM. Goodbye.");
    }
  }    
}

rpm_vs_time_2

Video

How to Transmit Time and Temperature Using a TMP36 Sensor and Arduino

In this post, I’ll explain how to transmit time and temperature using a TMP36 sensor and an Arduino. The application that you will develop executes on an Arduino and transmits the time and temperature at a periodic rate of around 10 seconds across a Serial bus (e.g. USB) to a host computer (such as your personal laptop computer).

Requirements

Here are the requirements I created for this project:

  • The system must execute on an Arduino.
  • A temperature sensor connected to an Arduino must be calibrated.
  • The main program must use a Round Robin with interrupts design:
    • The temperature must be captured and converted to Fahrenheit.
    • The temperature must begin recording after the temperature has stabilized at room temperature.
    • The temperature must be recorded at a periodic rate of around 10 seconds (i.e. 10,000 milliseconds) at room temperature.
    • The temperature must then be recorded for 5 minutes at a periodic rate of around 10 seconds inside a refrigerator.
    • The temperature must then be recorded for 5 minutes at a periodic rate of around 10 seconds at room temperature.
  • The time and temperature must be transmitted across a Serial bus such as USB to my host.
  • The time and temperature data must be exported as a comma separated value file.
  • The comma separated value file must be read into a spreadsheet program (e.g. Microsoft Excel).
  • The temperature vs time must be plotted.

Hardware Design

The following components are used in this project. You will need:

Here is the diagram of the TMP36 temperature sensor:

tmp36_sensor_diagram

Here is the diagram of the hardware setup:

tmp36

Here are the steps for setting up the hardware of the Serial Transmit of Temperature system:

  • Step 1. Place the TMP36 sensor on the breadboard. Each lead of the sensor will be on a different rail section of the breadboard.
  • Step 2. Connect a jumper wire between the ground lead of the TMP36 sensor and the – rail of the breadboard.
  • Step 3. Connect a jumper wire between the – rail of the breadboard and the GND pin of the Arduino.
  • Step 4. Connect a jumper wire between the 5V pin of the Arduino and the + rail of the breadboard.
  • Step 5. Connect a jumper wire between + rail of the breadboard and the 5V lead of the TMP36 sensor.
  • Step 6. Connect a jumper wire between the middle output lead of the TMP36 sensor and the Analog 0 pin of the Arduino.
tmp36_sensor

Here are the steps for calibrating the TMP36 sensor:

We will use the One Point Calibration technique using a known temperature, 32°F, which is the freezing point of water.

  1. Crush several ice cubes and place in a plastic bag with some distilled water (Ada 2015).
  2. Close the bag up, making sure it is tied tight.
  3. Make sure the bag is completely dry on the outside and place it on top of the TMP36 sensor.
  4. Adjust temperature accordingly to calibrate.

Implementation

Here is the source code that you will need to load to your Arduino:

#include <TimerOne.h>
// This TimerOne.h library handles time-based interrupts

/**
 * In this program, we develop an application which executes on an Arduino 
 * and transmits the time and temperature at a periodic rate of around 10 
 * seconds across a Serial bus (USB) to the Host.
 * 
 * Uses the timer interrupts technique discussed on page 270 in 
 * "Exploring Arduino: Tools and Techniques for Engineering Wizardry" 
 * by Jeremy Blum
 * 
 * @version 7.0 2019-02-14
 * @author Addison Sears-Collins
 */
 
// Assign a name to the TMP36 sensor pin on the Arduino Uno
const unsigned int TEMP_SENSOR_PIN = A0;

// Flag used to stop the program
bool done = false;

// Used for capturing the time
unsigned long time;

// Used for capturing the temperature in Fahrenheit
float temp_fahrenheit;

/**
 *  Function runs only once, after each powerup or reset of the Arduino Uno
 */
void setup() {
  
  // Open the serial port and set the data transmission rate to 9600 bits 
  // per second. 9600 is the default baud rate for Arduino Uno.
  Serial.begin(9600); 
  
  // Show a welcome message as human-readable ASCII text
  Serial.println("SERIAL TRASMIT OF TEMPERATURE PROGRAM");
  Serial.println("This program transmits the time and temperature at a periodic");
  Serial.println("rate of ~10 seconds across a Serial bus (USB) to the Host.");
  Serial.println("Created by Addison Sears-Collins");
  Serial.println("");
  Serial.println("Press ! to end the program");
  Serial.println("");
  Serial.println("Recording temperature every 10 seconds...");
  Serial.println("");
  Serial.println("TIME, TEMPERATURE IN DEGREES FAHRENHEIT");

  // Interrupt: Set a timer of length 10000000 microseconds (10 seconds)
  Timer1.initialize(10000000);

  //Runs "isr_read_temperature" on each timer interrupt
  Timer1.attachInterrupt(isr_read_temperature); 
 
}

/**
 *  Main function
 */
void loop() {

  // Wait 100 seconds for temperature to stabilize
  // Temperature is being read in the background
  // via the interrupts
  delay(100000);

  while(!done) {    

    // Display the time and temperature
    display_time_and_temperature();
  
    // End program if sentinel is entered 
    end_program();    

    // Display time and temperature every 10 seconds
    delay(10000);
  }
  
  // Do nothing
  while (true) {}
}

/**
  * This function is the interrupt service routine.
  * It reads the voltage and converts to degrees Fahrenheit
  */
void isr_read_temperature() {
  
  // Read the voltage of the TMP36 sensor
  int sensor_voltage = analogRead(TEMP_SENSOR_PIN);
  
  // Calibrated. Equation taken from datasheet.
  // http://kookye.com/wp-content/uploads/samplecode/tempsensor.txt
  temp_fahrenheit = 5.1 + ((125 * sensor_voltage) >> 8);
}

/**
  * Function displays the time and temperature
  */
void display_time_and_temperature() {
  
  // Capture the time and covert to seconds
  time = millis() / 1000;

  // Display the time
  Serial.print(time); 
  Serial.print(" , ");
  // Println so the next line begins on a new line
  // Display the temperature in Fahrenheit
  Serial.println(temp_fahrenheit);   
}

/**
  * This function ends the program
  */
void end_program() {
  
  // Used for reading data from the serial monitor
  char ch;

  // Check to see if ! is available to be read
  if (Serial.available()) {     
  
    // Read the character
    // Serial.read() returns the first (oldest) character in the buffer 
    // and removes that byte of data from the buffer
    ch = Serial.read();    

    // End the program if an exclamation point is entered in the
    // serial monitor
    if (ch == '!') {
      done = true;  
      Serial.println("Finished recording temperature. Goodbye.");
    }
  }    
}

temperature_vs_time_tmp36_sensor
Graph of Temperature vs Time

Video

How to Display a String as Morse Code on an LED Using Arduino

In this post, I’ll show you how to develop an application which executes on an Arduino and displays a user typed string, such as “Hello World”, as Morse code on an LED.

Named after Samuel F.B. Morse, the inventor of the telegraph, Morse code is a character encoding scheme that represents letters as long and short pulses of sound or light.

Requirements

The system that is developed must satisfy the following requirements:

  • It must execute on an Arduino. The Arduino version used was version 1.8.8. Most Arduino versions will work.
  • It must display a user typed string, such as “Hello World”, as Morse code on an LED (or several LEDs), or an LCD.
    • A dash is three times as long as a dot.
    • The time between each dot or dash in the same letter is equal to the duration of one dot.
    • The time between two letters is the duration of one dash.
    • The time between two words is the same duration as seven dots.
  • It must use a Round Robin design where a loop:
    • Waits for a string
    • Displays the string in Morse code
    • Exits the loop only if a sentinel is entered (e.g. !)

Hardware Design

The following components are used for the Morse code LED system. You will need:

Here is the schematic:

Software Design

Here are the steps for the Morse code LED system:

  • Step 1. Prompt the user to enter a message on the Serial Monitor. The input for the message contains characters (letters and/or numbers).
  • Step 2. Read each character, one at a time.
  • Step 3. Convert each character into the equivalent Morse Code.
  • Step 4. Convert each Morse code sequence into the corresponding sequence of dots and dashes.
  • Step 5. Transmit the dots and dashes to the LED while transmitting the message to the Serial Monitor.
  • Step 6. Exit the loop if user enters the sentinel value of “!”

Implementation

Here is the source code that you will need to load to your Arduino:

/**
 * In this program, we develop an application which executes on an Arduino 
 * Uno and displays a user typed string, such as “Hello World”, as Morse 
 * code on an LED.   
 * 
 * @version 1.0 2019-01-31
 * @author Addison Sears-Collins
 */
 
// Assign a name to the digital pin 12
unsigned int led_pin = 12;

// Declare an array named letters that holds addresses of string literals 
// (i.e. an array of pointers to strings composed of dots and dashes) 
// Done to preserve memory because strings are not equal in size. A 2D array
// would be a waste of space.
char *letters[] = {
  // The letters A-Z in Morse code  
  ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..",    
  ".---", "-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.",  
  "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.."          
};

char *numbers[] = {
  // The numbers 0-9 in Morse code  
  "-----", ".----", "..---", "...--", "....-", ".....", "-....", 
  "--...", "---..", "----."
};

unsigned int dot_duration = 200;
bool done = false;

/**
 *  Function runs only once, after each powerup or reset of the Arduino
 */
void setup() {
  // Set the LED to output
  pinMode(led_pin, OUTPUT);
  
  // Open the serial port and set the data transmission rate to 9600 bits 
  // per second. 9600 is the default baud rate for Arduino.
  Serial.begin(9600); 
  
  // Show welcome message as human-readable ASCII text
  Serial.println("MORSE CODE LED PROGRAM");
  Serial.println("This program translates your message into Morse code");
  Serial.println("and flashes it on an LED.");
  Serial.println("Author: Addison Sears-Collins");
  Serial.println("");
  Serial.println("Enter your message(s) or ! to exit: ");
}

/**
 *  Main function that drives the system
 */
void loop() {
  char ch;
  
  // This loop waits for a string, displays it in Morse code, and only exits 
  // the loop if the sentinel is entered. 
  while (!done) {
  
    // Check to see if there are letters or numbers available to be read
    if (Serial.available()) {     
  
      // Read one letter or number at a time
      // Serial.read() returns the first (oldest) character in the buffer 
      // and removes that byte of data from the buffer
      ch = Serial.read();    
  
      // Check for uppercase letters
      if (ch >= 'A' && ch <= 'Z') {
        Serial.println(ch);
        flash_morse_code(letters[ch - 'A']);        
      }
      // Check for lowercase letters
      else  if (ch >= 'a' && ch <= 'z') {
        Serial.println(ch);
        flash_morse_code(letters[ch - 'a']);
      }
      // Check for numbers
      else if (ch >= '0' && ch <= '9') {
        Serial.println(ch);
        flash_morse_code(numbers[ch - '0']);
      }
      // Check for space between words
      else if (ch == ' ') {
      
        // Put space between two words in a message...equal to seven dots
        delay(dot_duration * 7);       
      }
      // Check for sentinel value
      else if (ch == '!') {
        done = true;  
        Serial.println("Thank you! Your messages were sent successfully.");
        Serial.println("Goodbye.");
      }
    }     
  }  
  // Do nothing
  while(true) {}
}

/**
  *  Flashes the Morse code for the input letter or number
  *  @param morse_code pointer to the morse code
  */
void flash_morse_code(char *morse_code) {
   
  unsigned int i = 0;
   
  // Read the dots and dashes and flash accordingly
  while (morse_code[i] != NULL) {
    flash_dot_or_dash(morse_code[i]);
    i++;
  }
   
  // Space between two letters is equal to three dots
  delay(dot_duration * 3);    
}

/**
  *  Flashes the dot or dash in the Morse code
  *  @param dot_or_dash character that is a dot or a dash
  */
void flash_dot_or_dash(char dot_or_dash) {
  
  // Make the LED shine
  digitalWrite(led_pin, HIGH);
  
  if (dot_or_dash == '.') { // If it is a dot
    delay(dot_duration);           
  }
  else { // Has to be a dash...equal to three dots
    delay(dot_duration * 3);           
  }
  
  // Turn the LED off
  digitalWrite(led_pin, LOW);

  // Give space between parts of the same letter...equal to one dot
  delay(dot_duration); 
}

Video