Finding rotation speed using Arduino

Ooops, it’s been a while since I was last active here, sorry about that.

But to offer someting in retribution, here’s a device that can count revolutions or any other event that closes a switch, and tell you how many times that happens in a minute.

I have a need for this device, because I am building a dolly for timelapse images. The dolly has a 2m long worm screw that makes the dolly travel along an axis. If the trip takes, say, 3 hours, and the dolly carries a camera that is set to take an image every 5 seconds, we get a 3 x 60 x 20 image timelapse, ie. 4,800 images. With 25 images per second in a video, that gives you a 192 seconds, or, a little over 3 minute time lapse. During which the camera moves, you see, it’s not just a timelapse.

But this created the need to know the speed at which the servo is turning the worm screw. I am using an M8 size screw and nut to move the dolly, and incredibly, it has a travel of one millimeter per turn of the screw. Therefore a 10 rpm speed on the servo will make the dolly travel 10 millimeters. I have a potentiometer attached to the Arduino that runs the servo, so I can adjust the rotation speed of the servo, but before this device, I had no idea of travel speed.

I made numerous tests to match the servo control value to the speed of the dolly, but especially at slow speeds which are essential, I wasn’t getting good results. Then when I found the travel of the screw, the focus shifted to RPM finding. Of course, this has been done a million times before on the Web so I set myself a target of going solo from the ground up.

This is the result (in the video the Delta value has some extra trailing digits, don’t worry about those):

The essentials are

  • Arduino
  • 360 degree servo of the type PowerHD AR-360HB, because that one has good control
  • a potentiometer
  • a microswitch with a lid
  • A LCD display panel if you want to show off but the Serial Monitor of the Arduino works fine too
  • a 3D printed test bench

Let’s look at each in detail.

Reading a switch

So, the first thing is to get Arduino to recognize the switch. I am using a micro switch with a little metal flap to ease the closing of the switch. There are three connectors on it: the Normally Open on the left, Common in the middle, and Normally Closed on the right. For this app, you want to attach leads to Common and Normally Open (although you can wire it any way you like and adjust behavior in the code).

The leads go to Ground and a digital pin of your choice on the Arduino. The pin is declared in the intro to the program like this:

int mySwitchPin = 6;
pinMode(mySwitchPin, INPUT);
digitalWrite(mySwitchPin, HIGH);

The first defines pin 6 as the input pin, pinMode sets its mode as input, and the digitalWrite makes sure it is grounded.

When the switch is closed, a circuit is formed, and its existence can be detected by the Arduino, using this piece of code:

boolean mySwitchDown() {
 mySwitchDown = digitalRead(mySwitchPin);
 if (LOW == mySwitchDown) {
 hitOn = true;
 return true;
 }
 else {
 return false;
 }
}

This is an example of a Boolean function. During the main loop, execution of the program goes to the function mySwitchDown. It checks to see if there is a closed circuit in the mySwitchPin. If yes, it will return true, and the main program learns of the hit. If not, it returns false, and the main program keeps running and expecting to see a hit later.

Then, you want to do it repeatedly and calculate the time between two closing events of the switch (which I call delta). To do this, you need to have a way to track time and delta time. Fortunately Arduino has a feature that can do just this: it’s called Millis(). Millis is a system variable that tracks the time, in milliseconds, since the program started running. Therefore you can mark the time at each time the switch is closed, and deduct the previous value from this latest value, to get the delta time between switch closings. Calculating RPM from that is rather easy, all you do is divide 60 with the delta.

This is the main loop:

void loop() {
driveForward();
 if (mySwitchDown()) {
 lastMillis = currentMillis;
 currentMillis = millis();
 deltaMillis = currentMillis - lastMillis;
 rpm = 60 / (float) deltaMillis * 1000;
 if (deltaMillis > 400) {
 hits++;
 Serial.print(" CurrentMillis: ");
 Serial.print(currentMillis);
 Serial.print(" LastMillis: ");
 Serial.print(lastMillis);
 Serial.print(" DeltaMillis: ");
 Serial.print(deltaMillis);
 Serial.print(" Hits: ");
 Serial.print(hits);
 Serial.print(" RPM: ");
 Serial.println(rpm);
 workLCD();
 }
 else {
 }
 }
 else {
 hitOn = false;
 }
}

The only issue is that at very slow speeds, the switch remains closed and you record multiple hits per pass of the servo horn. To counter this, I held the switch down, and saw that at the fastest this system can record deltas of 160 milliseconds, which would be more than 370 RPM. Therefore I set a filter: if the delta time is less than 400ms, it means the switch hasn’t even been open, and hits are not counted.

The Serial.print statements use the Serial Monitor to display your switch data. You will see the current milliseconds value, the last hit milliseconds, and the delta between the two. Hits are counted here with the hits++ statement, but only if the delta is bigger than 400 milliseconds. The data can be cut and pasted from the Serial Monitor window to Excel, if you want to do a closer analysis.

3D design

This is a simple piece of modeling. First you create a cube with the dimensions of the servo. Then you create another, elongated cube, and use a Boolean modifier to deduct the servo cube from the mounting cube. After that, just extrude a pole for the switch mount, and deduct a cylinder from the pole for a screw.

When printed, it looks like this:

The 3D printed mount with switch

The 3D printed mount with switch arm

The switch arm is constructed similarly. I will upload the STL files to my account on Thingiverse so you can just download it. As you can see in the pics, I am using tape to attach the servo to the test bench, and in fact my advice is always to use the fastest working solution when tinkering.

Servo in mount with switch in place

Servo in mount with switch in place

Using the LCD (optional)

The LCD I am using is a standard issue type **LCD1602 from www.geetech.com. It has a full set of connectors for hardcore people, but also, a four pin connector set for us lowly hackers. To run this, you connect the GND pin to ground, VCC pin to Arduino’s 5V pin, and the SDA and SCL to Arduino’s A4 and A5 pins respectively.

To operate this panel easily, you need a library for your Arduino. What you want to do is to go here and watch a tutorial on how to get this LCD to show your content.

Setting up the LCD takes the following code:

#include <LiquidCrystal_I2C.h>      //this goes at the very top of the file
LiquidCrystal_I2C lcd(0x27, 16, 4); // set the LCD address to 0x27 for a 16 chars and 4 line display

void setup() {
 lcd.init();                         //this initializes the LCD
 lcd.backlight();                   //open the backlight
}

What I am using this for can be seen in this bit of code:

void workLCD() {
 lcd.setCursor(0, 0); // set the cursor to column 0, line 1
 lcd.print("Hits: "); // Print the label to the LCD.
 lcd.print(hits); // Print the current number of hits to the LCD.
 lcd.setCursor(0, 1); // set the cursor to column 0, line 0
 lcd.print("Delta: "); // Print the label to the LCD.
 lcd.print(deltaMillis); // Print the current deltaMillis to the LCD.
 lcd.setCursor(0, 2); // set the cursor to column 0, line 0
 lcd.print("RPM: "); // Print the label to the LCD.
 lcd.print(rpm); // Print the current value of RPM to the LCD.
 lcd.setCursor(0, 3); // set the cursor to column 0, line 2
 lcd.print("Signal: "); // Print the label to the LCD.
 lcd.print(signalLength); // Print the current signal length to the LCD.
}

The setCursor places the cursor anywhere on the 16 x 4 matrix. You can use the lcd.print command to write strings (in quotes) or variable values on the screen. LCDs are very neat for showing information, but it’s one way only, so if you want to collect data, use the Serial monitor instead.

And there you have it. The entire source file is here:

#include <Servo.h>
#include <Math.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

int tim = 500; //the value of delay time
LiquidCrystal_I2C lcd(0x27, 16, 4); // set the LCD address to 0x27 for a 16 chars and 2 line display

Servo driverServo;
int mySwitchPin = 4;
int driverServoPin = 8;
int mySwitchDown = false;
int sensorValue;
int hits = 0;
boolean hitOn = false;
float signalLength;
unsigned long startMillis;
unsigned long lastMillis = 0;
unsigned long currentMillis = 0;
unsigned long deltaMillis = 0;
unsigned long rpmDiff = 60;
float rpm;

void setup() {
 startMillis = millis();
 lcd.init(); //initialize the lcd
 lcd.backlight(); //open the backlight
 Serial.begin(9600);
 pinMode(mySwitchPin, INPUT);
 pinMode (driverServoPin, OUTPUT);
 digitalWrite(mySwitchPin, HIGH);
 driverServo.attach(driverServoPin);
}

void loop() {
 driveForward();
 if (mySwitchDown()) {
 lastMillis = currentMillis;
 currentMillis = millis();
 deltaMillis = currentMillis - lastMillis;
 rpm = 60 / (float) deltaMillis * 1000;
 if (deltaMillis > 400) {
 hits++;
 Serial.print(" CurrentMillis: ");
 Serial.print(currentMillis);
 Serial.print(" LastMillis: ");
 Serial.print(lastMillis);
 Serial.print(" DeltaMillis: ");
 Serial.print(deltaMillis);
 Serial.print(" Hits: ");
 Serial.print(hits);
 Serial.print(" RPM: ");
 Serial.println(rpm);
 workLCD();
 }
 else {
 }
 }
 else {
 hitOn = false;
 }
}

void workLCD() {
 lcd.setCursor(0, 0); // set the cursor to column 0, line 1
 lcd.print("Hits: "); // Print a message to the LCD.
 lcd.print(hits); // Print a message to the LCD.
 lcd.setCursor(0, 1); // set the cursor to column 0, line 0
 lcd.print("Delta: "); // Print a message to the LCD.
 lcd.print(deltaMillis); // Print a message to the LCD.
 lcd.setCursor(0, 2); // set the cursor to column 0, line 0
 lcd.print("RPM: "); // Print a message to the LCD.
 lcd.print(rpm); // Print a message to the LCD.
 lcd.setCursor(0, 3); // set the cursor to column 0, line 2
 lcd.print("Signal: "); // Print a message to the LCD.
 lcd.print(signalLength); // Print a message to the LCD.
}


void driveForward() {  //This function defines the rotation speed
 sensorValue = analogRead(A0); //Read the value from the potentiometer
 signalLength = map(sensorValue, 20, 1023, 1495, 1800); //map it to servo signal length
 driverServo.writeMicroseconds(signalLength); //run the servo at the given signal length
 delay(10);
}


boolean mySwitchDown() {
 mySwitchDown = digitalRead(mySwitchPin);
 if (LOW == mySwitchDown) {
 hitOn = true;
 return true;
 }
 else {
 return false;
 }
}


Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.