serial input and output at the same time for tacho

I want to control a continuous rotation servo using serial input (ie < or >) and get a read out showing me the RPM using an optical tachometer. I have both codes working independently, but want to fuse them together. I was successful at putting an analog servo control with the tachometer, but am having a terrible time putting the serial controls into place. Here are the codes:

/*

  • Optical Tachometer

  • Uses an IR LED and IR phototransistor to implement an optical tachometer.

  • The IR LED is connected to pin 13 and ran continually. A status LED is connected

  • to pin 12. Pin 2 (interrupt 0) is connected across the IR detector.

*/

int ledPin = 13; // IR LED connected to digital pin 13
int statusPin = 12; // LED connected to digital pin 12

volatile byte rpmcount;
volatile int status;

unsigned int rpm;

unsigned long timeold;

void rpm_fun()
{
//Each rotation, this interrupt function is run twice, so take that into consideration for

//calculating RPM
//Update count
rpmcount++;

//Toggle status LED
if (status == LOW) {
status = HIGH;
} else {
status = LOW;
}
digitalWrite(statusPin, status);

}

void setup()
{
Serial.begin(9600);
//Interrupt 0 is digital pin 2, so that is where the IR detector is connected
//Triggers on FALLING (change from HIGH to LOW)
attachInterrupt(0, rpm_fun, FALLING);

//Turn on IR LED
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, HIGH);

//Use statusPin to flash along with interrupts
pinMode(statusPin, OUTPUT);

rpmcount = 0;
rpm = 0;

timeold = 0;
status = LOW;
}

void loop()
{
//Update RPM every second
delay(1000);
//Don’t process interrupts during calculations
detachInterrupt(0);
//Note that this would be 60*1000/(millis() - timeold)*rpmcount if the interrupt

//happened once per revolution instead of twice. Other multiples could be used
//for multi-bladed propellers or fans
rpm = 30*1000/(millis() - timeold)*rpmcount;
timeold = millis();
rpmcount = 0;

//Write it out to serial port
Serial.println(rpm,DEC);

//Restart the interrupt processing
attachInterrupt(0, rpm_fun, FALLING);

}

/*

  • Serial Port Continuous Rotation Servo - Movement and Speed Control

  • Alteration of the control interface to use < and > keys
  • to slew the servo horn left and right. Works best with
  • the Linux/Mac terminal “screen” program.
  • Adapted for Continuous Rotation Servo (Parallax)
  • by Orfeus
  • v.200910211932
  • For Serial Port Terminal on Windows I used PuTTY SSH Client
  • Created 10 December 2007
  • copyleft 2007 Brian D. Wendt
  • Adapted from code by Tom Igoe

*/

/** Adjust these values for your servo and setup, if necessary **/
int servoPin = 9; // control pin for servo motor
int minPulse = 1170; // maximum servo speed clockwise
int maxPulse = 1770; // maximum servo speed anticlockwise
int turnRate = 50; // servo turn rate increment (larger value, faster rate)
int refreshTime = 20; // time (ms) between pulses (50Hz)

/** The Arduino will calculate these values for you **/
int centerServo; // center servo position
int pulseWidth; // servo pulse width
int moveServo; // raw user input
long lastPulse = 0; // recorded time (ms) of the last pulse

void setup() {
pinMode(servoPin, OUTPUT); // Set servo pin as an output pin
centerServo = maxPulse - ((maxPulse - minPulse)/2);
pulseWidth = centerServo; // Give the servo a stop command
Serial.begin(9600);
Serial.println(“Arduino Serial Continuous Rotation Servo Control”);
Serial.println(" by Orfeus for GRobot.gr");
Serial.println(" Press < or > to move, spacebar to center");
Serial.println();
}

void loop() {
// wait for serial input
if (Serial.available() > 0) {
// read the incoming byte:
moveServo = Serial.read();

// ASCII ‘<’ is 44, ASCII ‘>’ is 46 (comma and period, really)
if (moveServo == 44) { pulseWidth = pulseWidth + turnRate; }
if (moveServo == 46) { pulseWidth = pulseWidth - turnRate; }
if (moveServo == 32) { pulseWidth = centerServo; }

// stop servo pulse at min and max
if (pulseWidth > maxPulse) { pulseWidth = maxPulse; }
if (pulseWidth < minPulse) { pulseWidth = minPulse; }

// Show me the keys I pressed
//Serial.print("Key pressed: ");
//Serial.println(moveServo);

//print pulseWidth back to the Serial Monitor (comment to undebug)
Serial.print("Pulse Width: ");
Serial.print(pulseWidth);
Serial.println(“us”);
}

// pulse the servo every 20 ms (refreshTime) with current pulseWidth
// this will hold the servo’s rotation and speed till we told it to do something else.
if (millis() - lastPulse >= refreshTime) {
digitalWrite(servoPin, HIGH); // start the pulse
delayMicroseconds(pulseWidth); // pulse width
digitalWrite(servoPin, LOW); // stop the pulse
lastPulse = millis(); // save the time of the last pulse
}
}

/* Orfeus 200910212001 */

Thanks so much. I’ve been working on this for a while and need to finally get it to work.

Which is correct? is 44 a < or a comma?

// ASCII '<' is 44, ASCII '>' is 46 (comma and period, really)
   if (moveServo == 44) { pulseWidth = pulseWidth + turnRate; }
   if (moveServo == 46) { pulseWidth = pulseWidth - turnRate; }
   if (moveServo == 32) { pulseWidth = centerServo; }

There is nothing wrong with:
if(moveServo == ‘>’) // Do something
and there is no reason to consult a table to find the proper value, when you want to change the value.

Why are you sending . and , to increase or decrease the values? They are probably the least obvious characters to send. < and > or U and D make sense. . and , do not.

void loop()
{
  //Update RPM every second
  [glow]delay(1000);[/glow]

I guess it’s not important to be anything like real-time, huh?

Why are you sending . and , to increase or decrease the values?

I suspect because these are on the same keys as < and > but without having to press the shift key.

Why are you sending . and , to increase or decrease the values? They are probably the least obvious characters to send. < and > or U and D make sense. . and , do not.

On a standard keyboard , and < are are the same key but the < requires shift to be pressed. The same goes for . and >. Am I wrong in assuming I have to press shift to get < and > in Arduino serial interface?

I guess it’s not important to be anything like real-time, huh?

I don’t fully understand this comment. Isn’t updating every second real-time? I am dealing with 15-45RPM so updating much faster isn’t worth much. I’ve also coded for slower updates, but I like to average my RPM so the more data points the better.

I am very happy with each code individually, but want to put them both together so that I can get RPM info back from my serial controls. My fallback is to count the RPM for each serial step and and assume that stays about the same with time and use - big assumption for scientific research in my opinion.

Isn't updating every second real-time?

No it is not a second when you are dealing with control like this is a very long time.

On a standard keyboard , and < are are the same key but the < requires shift to be pressed. The same goes for . and >.

OK. But the comments say that you are looking for ‘<’ or ‘>’. If that is not the case, and it is not obvious to anyone who has not memorized the whole ascii table (which I presume is nearly everyone), you should either fix the comments or replace the ASCII value with the character that it represents (’,’ and ‘.’).

Isn’t updating every second real-time?

One a system that executes 16 million instructions per second, 1 second is an eternity.

Read up on the delay() function. Perhaps then you’ll see why you should not be using it on any system that at least pretends to be real-time.

Combining the two pieces of code should be pretty simple. You seem to have some concept of what each piece does. But, the delay() call, that stops everything (except interrupts) for a whole second is going to put a big dent in the systems responsiveness.

You can avoid issues with updating the LCD too often by only sending a new value if the value has changed. For a system that measures rotation in the 15 to 45 RPM range, that shouldn’t be too often.

As a rough guideline:

0.1 second response time is usually perceived as instantaneous.
1 second response time is is a little sluggish but still acceptable.
4 seconds response time and more is perceived as very slow. The user starts to wonder if he didn't do anything wrong of if the damn thing is working at all.

Korman

I guess in regards to the response time, instantaneous is not what I am looking for then. I just want to be able to see that my RPMs are steady - getting an update every second is more than enough time for this. In fact trying to reduce the time interval too much messes the RPM math up and I get false RPM readouts.

you should either fix the comments or replace the ASCII value with the character that it represents (’,’ and ‘.’).

The ASCII values 44 and 46 do correspond to , and . respectively. I think the comments are self explanatory. These particular comments were written by the original author of this code and I had no problem interpreting what they meant. I also think that if I use ASCII 60 and 62 (< and >) then I’d have to press shift before pressing the key. Anyway this is detracting from my purpose in seeking advice from this form.

Combining the two pieces of code should be pretty simple.

Any suggestions on how it could be done? I have tried a number of combinations, but I either get no function or I can get one half to work and not the other.

You can avoid issues with updating the LCD too often by only sending a new value if the value has changed.

OoooH. How do I do that? Very nice idea!!

OoooH. How do I do that? Very nice idea!!

Keep track of the last value printed. Compare the new value to the old value. If they are the same, skip it. Otherwise, print the new value, and save it as the old value.

So, here I am again, and I still can’t get this code to work as I would like. Below is what I have put together. The servo control is working fine and I can get tachometer RPM’s if I press any button. However, I would like to have the RPM update second or two. Every time I introduce a delay to calculate this, the servo no longer functions as it should. It pulses slightly. Any suggestions on how to integrate this code better?

//Adjust these values if necessary
int servoPin     =  9;    // control pin for servo motor
int minPulse     =  1170;  // maximum servo speed clockwise
int maxPulse     =  1770; // maximum servo speed anticlockwise
int turnRate     =  15;  // servo turn rate increment (larger value, faster rate)
int refreshTime  =  20;   // time (ms) between pulses (50Hz)

// Tachometer leds
int ledPin = 13;                // IR LED connected to digital pin 13
int statusPin = 12;             // LED connected to digital pin 12

// The Arduino will calculate these values for you 
int centerServo;         // center servo position
int pulseWidth;          // servo pulse width
int moveServo;           // raw user input
long lastPulse   = 0;    // recorded time (ms) of the last pulse

//Tach

volatile byte rpmcount;
volatile int status;

unsigned int rpm;

unsigned long timeold;

 void rpm_fun()
 {
   //Each rotation, this interrupt function is run twice, so take that into consideration for calculating RPM
   
   //Update count
      rpmcount++;
      
   //Toggle status LED  
   if (status == LOW) {
     status = HIGH;
   } else {
     status = LOW;
   }
   digitalWrite(statusPin, status);

 }


//Servo
void setup() 
{
  pinMode(servoPin, OUTPUT);  // Set servo pin as an output pin
  centerServo = maxPulse - ((maxPulse - minPulse)/2);
  pulseWidth = centerServo;   // Give the servo a stop command
  Serial.begin(9600);
 


//Tach
  //Interrupt 0 is digital pin 2, so that is where the IR detector is connected
   //Triggers on FALLING (change from HIGH to LOW)
   attachInterrupt(0, rpm_fun, FALLING);
  //Turn on IR LED
   pinMode(ledPin, OUTPUT);
   digitalWrite(ledPin, HIGH);
  
   //Use statusPin to flash along with interrupts
   pinMode(statusPin, OUTPUT);

   rpmcount = 0;
   rpm = 0;

   timeold = 0;
   status = LOW;
   

// Servo
  Serial.println("        Zebrafish OKR Rotation Control");
  Serial.println("          by D. Joshua Cameron, PhD");
  Serial.println("   Press < or > to move, spacebar to stop");
  Serial.println("         Press any key to update RPM");
  Serial.println();
}

void loop() 
{

  
// Servo
    
    // wait for serial input
  if (Serial.available() > 0) {
  
    // read the incoming byte:
    moveServo = Serial.read();

    // ASCII '<' is 44, ASCII '>' is 46 (comma and period, really)
    if (moveServo == 44) { pulseWidth = pulseWidth + turnRate; }
    if (moveServo == 46) { pulseWidth = pulseWidth - turnRate; }
    if (moveServo == 32) { pulseWidth = centerServo; }

    // stop servo pulse at min and max
    if (pulseWidth > maxPulse) { pulseWidth = maxPulse; }
    if (pulseWidth < minPulse) { pulseWidth = minPulse; }
   
     // Show me the keys I pressed
     //Serial.print("Key pressed: ");
     //Serial.println(moveServo);

     //print pulseWidth back to the Serial Monitor (comment to undebug)
     //Serial.print("Pulse Width: ");
     //Serial.print(pulseWidth);
     //Serial.println("us");
 
 

//Tach
//Update RPM every ...
  
   // delay(50);
   //Don't process interrupts during calculations
   detachInterrupt(0);
   //Note that this would be 60*1000/(millis() - timeold)*rpmcount if the interrupt happened once per revolution. 
   rpm = 4.286*1000/(millis() - timeold)*rpmcount;
   timeold = millis();
   rpmcount = 0;

  
   //Write it out to serial port
   Serial.println(rpm,DEC);
  
   //Restart the interrupt processing
   attachInterrupt(0, rpm_fun, FALLING);

}


//servo
  // pulse the servo every 20 ms (refreshTime) with current pulseWidth
  // this will hold the servo's rotation and speed till we told it to do something else.
  if (millis() - lastPulse >= refreshTime) {
    digitalWrite(servoPin, HIGH);   // start the pulse
    delayMicroseconds(pulseWidth);  // pulse width
    digitalWrite(servoPin, LOW);    // stop the pulse
    lastPulse = millis();           // save the time of the last pulse
  }
}
// ASCII '<' is 44, ASCII '>' is 46 (comma and period, really)
    if (moveServo == 44)

Your comment says two different things. You could get rid of the comment altogether, and make the code much easier to maintain by replacing the ASCII values with the character that they represent.

if(moveServo == ',')
  // Do something
else if(moveServo == '.')
  // Do something
else if(moveServo == ' ')
  // Do something
if (status == LOW) {
     status = HIGH;
   } else {
     status = LOW;
   }

This could be done much faster:

status = !status;

No if test required. Since ISRs are supposed to be quick, every little bit helps.

You might also want to look for the thread on digitalWriteFast, to speed up the LED transition.

You are computing RPM every pass through loop. Is that really necessary? If you used millis(), a stored last time, and an interval, you could calculate RPM less frequently. Perhaps the servo would like you better, then.

Is there some reason you don’t use the Servo library, and let it take care of updating the servo position regularly?

Thank you for the advice. I did the ASCII changes and also used the status = !status. Though, I don't think either of those gained me much.

You question about needing to calculate RPM every loop is intriguing. At first I thought yes for accurate results, but now I a not so sure. I certainly don't need it printed to the serial more than every few seconds.

I am not using the servo library because I did not find it useful in controlling the continuous rotating servo via the serial port. I initially used the servo library and a potentiometer for speed control and had that integrated just fine with the tachometer. Now I have these big ideas to use the computer to control the whole thing and find myself in this mess. Oh technology!

I certainly don’t need it printed to the serial more than every few seconds.

Even doing the calculation every 500 milliseconds should noticeably reduce the impact on the servo.

I hadn’t paid attention to the fact that you were using a continuous rotation “servo”. Still, the servo library manages them. Worth investigating whether using the servo library helps, or not.

rpm = [glow]4.286[/glow]*1000/(millis() - timeold)*rpmcount;

I love magic numbers.
Can you explain what this one is doing?
e1.455?

I’d probably move the digitalWrite out of the ISR altogether.

I love magic numbers.
Can you explain what this one is doing?
e1.455?

The equation with 1 blade/interrupt of the IR per revolution is 60*1000/(millis()-timeold)*rpm count.

I have 14 interrupts/blades per revolution so I divided 60 by 14. It is quite accurate.

I'd probably move the digitalWrite out of the ISR altogether.

Where could I put the digitalwrite? I want to keep it as it gives me a clear indication if something is wrong with my tachometer.

Where could I put the digitalwrite?

In "loop()" - it only need to roughly track "status".

I have 14 interrupts/blades per revolution so I divided 60 by 14

Fine. As long as you knew what it meant.
I'd probably have left it to the compiler to do the arithmetic, then it's clear to all, assuming that "INTERRUPTS_PER_REVOLUTION" is declared as 14.