Bizarre serial input behavior - Input from experienced users appreciated!

I'm trying to write a program that controls servo position via input from the serial monitor and I'm getting some very odd behavior. The problem portion of the code is included below:

if (Serial.available() == 1)
{
servoPosition = Serial.read() - '0';
if (servoPosition > 0 && servoPosition < 180)
{
lastServoPosition = servoPosition;
}
}
else if (Serial.available() == 2)
{
k = 0;
for (k; k <= 1; k++)
{
if (k == 0)
{
buffer10 = Serial.read() - '0';
}
else if (k == 1);
{
buffer1 = Serial.read() - '0';
}
}
servoPosition = (buffer1010 + buffer1);
if (servoPosition > 0 && servoPosition < 180)
{
lastServoPosition = servoPosition;
}
}
else if (Serial.available() == 3)
{
k = 0;
for (k; k <= 2; k++)
{
if (k == 0)
{
buffer100 = Serial.read() - '0';
}
else if (k == 1)
{
buffer10 = Serial.read() - '0';
}
else if (k == 2)
{
buffer1 = Serial.read() - '0';
}
}
servoPosition = (buffer100
100 + buffer10*10 + buffer1);
if (servoPosition > 0 && servoPosition < 180)
{
lastServoPosition = servoPosition;
}

When I enter a single digit value (0-9), the program interprets it properly. If I enter a 3 digit value (100-180), the program also interprets it properly. If I enter a 2 digit value however, it goes crazy. For instance, if I enter 45, servoPosition is assigned a value of -21. If I enter 98, servoPosition is assigned a value of 30. I can't figure it out why!

You aren't reading the serial input properly. You need to store the input string into a character array until you see the linefeed (and/or carriage return) at the end of the line and then convert the value.

Pete

First of all there's a thing in the IDE that says it's to copy code for forum posting.... don't use it. It's crap.

In the IDE, Edit->Select All, Edit->Copy

In the forum post edit window, click the 2nd row button with the # on it to make code tags then paste the code between them.

Second, you probably won't get far without posting the complete code.

Third, I don't know what this will do:

        k = 0;
        for (k; k <= 1; k++)

but usually we use:

        for (k = 0; k <= 1; k++)

They look like they should work the same FWIW.

Also don't assume that everything through serial will be a digit or transfer correctly.

Last:
Arduino is faster than serial. You may send 2 digits and when the code looking at Serial.available() may see 1 digit and go merrily processing that before the 2nd digit arrives.

The good practice is to send delimiters between messages (like numbers). The simplest way is to go down to the lower right corner of serial monitor and set it up to send a carriage return or line feed at the end of every line sent. Another way would be to put a space or other marker after each number you enter (can put a series of numbers on 1 line that way).
Of course your code needs to watch for the delimiters and not consider a number received until it gets one.

You aren't reading the serial input properly. You need to store the input string into a character array until you see the linefeed (and/or carriage return) at the end of the line and then convert the value.

Not really. I convert 'on the fly', ie 1 char at a time. Unless I -need- to save the text for later I don't use no steenking buffers! Guess which way is finished faster?

How to use this forum

Post all your code please, inside code tags.

      if (Serial.available() == 1)
  ...
      else if (Serial.available() == 2)
...
      else if (Serial.available() == 3)

This could go on forever, couldn't it?

Try reading this: http://www.gammon.com.au/serial

Thanks for all of the responses!

You aren't reading the serial input properly. You need to store the input string into a character array until you see the linefeed (and/or carriage return) at the end of the line and then convert the value.

Yes, that would be a lot cleaner and I may change it, but isn't this essentially the same thing? Instead of assigning them to an array element using Serial.readBytes(), I'm just assigning the next available byte to a discrete variable with each pass of the for loop and then converting. Why does this method work just fine for 1 and 3 byte inputs?

Arduino is faster than serial. You may send 2 digits and when the code looking at Serial.available() may see 1 digit and go merrily processing that before the 2nd digit arrives.

The good practice is to send delimiters between messages (like numbers). The simplest way is to go down to the lower right corner of serial monitor and set it up to send a carriage return or line feed at the end of every line sent. Another way would be to put a space or other marker after each number you enter (can put a series of numbers on 1 line that way).
Of course your code needs to watch for the delimiters and not consider a number received until it gets one.

Would changing the baudrate help with that? Also, I'd prefer to not use a delimiter. This is for an ME control systems class and I don't want to have to tell me professor to setup the serial monitor a certain way before he runs it, but it I have to than so be it. I want to limit the amount of data retrieved to 1, 2 or 3 bytes. That's why I used the Serial.available() = xxx as a test condition. If more than 3 bytes are available then an error message should be returned.

And Yeah, I was wondering why the code posted like that... Now I know XD

Simple servo test code that uses a delay instead of a delimiter.

// zoomkat 10-22-11 serial servo test
// type servo position 0 to 180 in serial monitor
// or for writeMicroseconds, use a value like 1500
// for IDE 0022 and later
// Powering a servo from the arduino usually *DOES NOT WORK*.

String readString;
#include <Servo.h> 
Servo myservo;  // create servo object to control a servo 

void setup() {
  Serial.begin(9600);
  myservo.writeMicroseconds(1500); //set initial servo position if desired
  myservo.attach(7);  //the pin for the servo control 
  Serial.println("servo-test-22-dual-input"); // so I can keep track of what is loaded
}

void loop() {
  while (Serial.available()) {
    char c = Serial.read();  //gets one byte from serial buffer
    readString += c; //makes the string readString
    delay(2);  //slow looping to allow buffer to fill with next character
  }

  if (readString.length() >0) {
    Serial.println(readString);  //so you can see the captured string 
    int n = readString.toInt();  //convert readString into a number

    // auto select appropriate value, copied from someone elses code.
    if(n >= 500)
    {
      Serial.print("writing Microseconds: ");
      Serial.println(n);
      myservo.writeMicroseconds(n);
    }
    else
    {   
      Serial.print("writing Angle: ");
      Serial.println(n);
      myservo.write(n);
    }

    readString=""; //empty for next input
  } 
}

The delimiter can be simply a space added after the last digit. Having your terminal program do it automatically is just easier and it never forgets. It doesn't have to be a space, just anything non-numeric.

You could also check available and if there is, wait 100 millis to be sure all the digits are in but that is way less certain and way more kludgy. If the prof sees the code it'd likely be worse than telling that the terminal program needs to add newlines.

tani357:
Also, I'd prefer to not use a delimiter. This is for an ME control systems class and I don't want to have to tell me professor to setup the serial monitor a certain way before he runs it, but it I have to than so be it.

Yes, but a newline is a pretty standard delimiter. You don't have to do much setting up to get the newline to be sent.

And the other way is flawed. You are testing for 1, 2 or 3 characters, but at the speed of the processor you might think you got only one because the next two are still arriving.

Why not to simply do like this?:

void serialread(){
  if (Serial.available() > 0 ) tempKey = Serial.parseInt();
}

It needs just one line, and it is almost guaranteed that you will receive all of your digits (default timeout is 1 sec). Just make sure to send newline, or any other non-integer at the end (this will stop Serial.parseInt to wait for more ints), or it will make a 1sec delay (but will work anyway). Just make sure you use if not while, or it will return "0".

Just make sure to send newline, or any other non-integer at the end

That sounds like a delimiter.

Only depending on time to know when an entry is done is known poor practice and poor design.

GoForSmoke:
Only depending on time to know when an entry is done is known poor practice and poor design.

But in a specific case it may meet the specified design requirement of no delimiter.

I'd prefer to not use a delimiter. This is for an ME control systems class and I don't want to have to tell me professor to setup the serial monitor a certain way before he runs it, but it I have to than so be it.

If changing the serial monitor setup is the main issue with not using a delimiter, then use a delimiter that is typed in the serial monitor at the end of the data. the below uses a , as the delimiter and no default serial monitor change is required. Just tell the professor to type a , at the end of the data and hit the keyboard enter key or click send in the serial monitor.

//zoomkat 3-5-12 simple delimited ',' string parce 
//from serial port input (via serial monitor)
//and print result out serial port
// CR/LF could also be a delimiter

String readString;

void setup() {
  Serial.begin(9600);
  Serial.println("serial delimit test 1.0"); // so I can keep track of what is loaded
}

void loop() {

  //expect a string like wer,qwe rty,123 456,hyre kjhg,
  //or like hello world,who are you?,bye!,
  
  if (Serial.available())  {
    char c = Serial.read();  //gets one byte from serial buffer
    if (c == ',') {
      //do stuff
      Serial.println(readString); //prints string to serial port out
      readString=""; //clears variable for new input      
     }  
    else {     
      readString += c; //makes the string readString
    }
  }
}

zoomkat:

GoForSmoke:
Only depending on time to know when an entry is done is known poor practice and poor design.

But in a specific case it may meet the specified design requirement of no delimiter.

Sure. Anyone can force conditions. But that is poor design.

Have you been coding for many users long now? I learned better in my first year writing for engineers and office workers. That was 1980 and I never saw different since. The only code where I ignore such things is Q&D code I write for myself then get embarrassed over later when others see it. See how many programmers you know that say the same basic thing.

We call some things poor practice because they prove to be just that. Poor design also becomes evident.

Alright, I got it going using zoomkat's string-to-int+delay suggestion. The system works just fine. I would absolutely agree that it would be poor practice to not use a concrete delimiter, but alas, I'm a Mechanical Engineering student, not EE. The professor is looking for function, not form or good practice :P. He's incredibly impatient. Screw the drop down menu at the bottom of the screen. This is a load and demo affair. Thanks to all of you for your help! I've absorbed a cornucopia of knowledge in the past few hours.

GoForSmoke:

zoomkat:

GoForSmoke:
Only depending on time to know when an entry is done is known poor practice and poor design.

But in a specific case it may meet the specified design requirement of no delimiter.

Sure. Anyone can force conditions. But that is poor design.

Have you been coding for many users long now? I learned better in my first year writing for engineers and office workers. That was 1980 and I never saw different since. The only code where I ignore such things is Q&D code I write for myself then get embarrassed over later when others see it. See how many programmers you know that say the same basic thing.

We call some things poor practice because they prove to be just that. Poor design also becomes evident.

I really don't consider my self a coder, so that point is N/A. I do know that in an engineering environment if you can't code to a particular design requirement, you will probably soon be replaced by someone who can. You can report potential issues, but bottom line it may be get it done or hit the highway. Those that can, do. Those that can't usually whine about those that can. 8)

I would have to agree with zoomkat on this one.

In this particular instance we are told to design a system that meets the assignments requirements. The servo must start @ 90 degrees and the serial monitor must display "SYSTEM OFF." After a button push the serial monitor must "clear" (a crap load of new lines), display "SYSTEM ACTIVE" and prompt the user for an angle. After a valid angle is entered, the servo must rotate at a rate of 45 deg/sec to the requested position. A second button push must clear the monitor again and return the system to it's initial state.

There are NO programming guidelines or requirements. Hell, we haven't even been given any pointers and it's been 3 years since I took a course on C. As long as it's stable, consistent and doesn't respond to invalid entries it's good.

A good engineer designs away from bad requirements when an idiot manager doesn't know better.

The whole company can eat it's shirt on a poor design when the competition comes out with better. Sure you can make carts with solid wooden wheels but those guys with the spokes thing.....
well you just hang on to what you know, it's much easier than thinking.

In this particular instance we are told to design a system that meets the assignments requirements. The servo must start @ 90 degrees and the serial monitor must display "SYSTEM OFF." After a button push the serial monitor must "clear" (a crap load of new lines), display "SYSTEM ACTIVE" and prompt the user for an angle. After a valid angle is entered, the servo must rotate at a rate of 45 deg/sec to the requested position. A second button push must clear the monitor again and return the system to it's initial state.

So nothing saying you can't have the serial monitor add newline? No -requirement-?

Just use parseInt. At least it has some safeguards you wouldn't think of.

My teachers gave higher grades to better solutions but a pass is a pass and chances are your prof doesn't know better anyway.

A good engineer designs away from bad requirements when an idiot manager doesn't know better.

Probably time for you to box up your stuff and go to your exit interview. :wink:

There is a difference between real requirements and hissy-fit wants by power-tripping ignorant little tyrants that don't know WTF they're on about.

Good engineering and design has another name: innovation. Designing to high tolerances or unnecessary requirements just because you can leads to overpriced, poorly working products.

I and I bet others here with experience have been down enough roads to know the ones to avoid.
Just because I try and warn people about poor choices doesn't give anyone grounds to "fire" me.
Especially someone who isn't more than a hack coder if that.