Go Down

Topic: Servo Control with Serial.read (Read 4 times) previous topic - next topic

T86157

I am pretty sure this is a faq but how can I control a servo using inputs from the serial monitor with serial.read()?

I have spent the last two days and nights trying to figure this out. I understand enough now to know that if I enter in a number into the serial input, it reads as a numerical value with atoi (i.e. '9' serial is 9 integer). Now I'm having the issue of being able to get angles with a numerical value larger than the tens or hundredth place.

I still do not understand how to have Serial.read() the value '999' as 999 integer or '39' as 39 integer.
I've tried modifying and using this tid bit here but is giving me undesired values such that if I enter in 129. It enters (Serial.print) 1 on first line, 12 on 2nd line and 129 on 3rd line. I just want to have '129' be 129 to set the angle to 129.

Here's the code. A different approach I figured out was to define the serial read to be a character value which can then just converted straight to integer if it's a numerical value.
Code: [Select]
int numFromPort;
void setup(){
Serial.begin(9600);
}
void loop()
{
numFromPort = 0;
while(Serial.available() > 0)
{
char inChar = Serial.read();
if(inChar >= '0' && inChar <= '9')
{

numFromPort = numFromPort*10 + (inChar - '0');

}
Serial.println(numFromPort);
}


// numFromPort now contains the data read from the port, as an integer
}


The problem still remains where it prints 1, 12, and 129. I know that the serial.read has to read the number in queue one after the other and not all at once but I want just the "final" number to be what the angle should set to be is.

My criteria for a servo program is
*pushbutton is pressed for the first time, servo should go to center of its range of motion and on serial monitor display "System active" followed by something like "please enter in an angle between 0 and 180".

*after the system is "activated" the user can enter in a value into the serial and the servo will set to that angle from 0 - 180. any other value should display an error message on the serial

*if pushbutton is pressed a 2nd time or 999 is entered into the serial monitor, the system displays "system deactivated" and turns off the program until push button is pressed again.

If anyone has a code similar to this criteria, can you help me? I thought this was going to be simple and ends up that I spend friday, saturday and sunday working on this and still don't understand a lot of the c language. I'm still somewhat a beginner but I am trying to learn but a lot on the internet on c language on arrays, atoi, char is foreign to me.

T86157

#1
Oct 04, 2010, 06:42 am Last Edit: Oct 04, 2010, 06:57 am by T86157 Reason: 1
this is what I have tried so far with my servo control program and the button part seems to work ok for activating the system and deactivating the system. it's the input from the serial monitor that's giving me the biggest hunk of the trouble. If someone can please evaluate my program and help me out, I'd greatly appreciate it. I'm pulling hair out of my head stressing about this.

Code: [Select]
#include <Servo.h>

const int  buttonPin = 2;    
const int motorPin = 3;      


int buttonPushCounter = 0;  
int buttonState = 0;        
int lastButtonState = 0;    

char val = 0;
char reader = 0;

Servo myservo;
void setup() {
 pinMode(buttonPin, INPUT);
 pinMode(motorPin, OUTPUT);

 Serial.begin(9600);
 myservo.attach(3);
}
void loop() {
 buttonState = digitalRead(buttonPin);
 if (buttonState != lastButtonState) {
   if (buttonState == HIGH) {
     buttonPushCounter++;
   }
   else {
   }
   lastButtonState = buttonState;
 }
 
if(buttonPushCounter % 2 == 1){
 if(buttonState == HIGH){
     Serial.println("System active");
     myservo.write(90);
     delay(1000);
 }
 else{
   reader = Serial.read();
   if (reader >= '0' && reader <= '180'){    
     val = reader;
 Serial.println(val, DEC);
 myservo.write(val);
 delay(23);
   }
 
 else if(reader == '999'){

     Serial.println("System deactivated");
     buttonPushCounter++;
     delay(1000);
 }
 else if(reader > '180' && reader < '999'){
   Serial.println("Error");
 }
 
 }
}
if (buttonPushCounter % 2 == 0){
   if(buttonState == HIGH){
     Serial.println("System deactivated");
     delay(1000);
}
}




}
 



Edit:
I was able to now get part of the problem done.... When I enter in 120. The code reads it at 1, then 2, then 0 rather than 120 and I am not sure how to implement what I put in my first post to work here.

Code: [Select]
#include <Servo.h>

const int  buttonPin = 2;    
const int motorPin = 3;      


int buttonPushCounter = 0;  
int buttonState = 0;        
int lastButtonState = 0;    

unsigned int val = 0;
unsigned int reader = 0;

Servo myservo;
void setup() {
 pinMode(buttonPin, INPUT);
 pinMode(motorPin, OUTPUT);

 Serial.begin(9600);
 myservo.attach(3);
}
void loop() {
 buttonState = digitalRead(buttonPin);
 if (buttonState != lastButtonState) {
   if (buttonState == HIGH) {
     buttonPushCounter++;
   }
   else {
   }
   lastButtonState = buttonState;
 }
 
if(buttonPushCounter % 2 == 1){
 if(buttonState == HIGH){
     Serial.println("System active");
     myservo.write(90);
     delay(1000);
 }
 else{

   char reader = Serial.read();
   if ((reader >= '0') && (reader <= '180')){    
 Serial.println(reader);
 myservo.write(reader);
 delay(23);
   }
 
 else if(reader == '999'){

     Serial.println("System deactivated");
     buttonPushCounter++;
     delay(1000);
 }
 else if(reader > 180 && reader < 999){
   Serial.println("Error");
 }
 
 }
}
if (buttonPushCounter % 2 == 0){
   if(buttonState == HIGH){
     Serial.println("System deactivated");
     Serial.end();
     delay(1000);
}
}




}
 

James C4S

Reading multiple numbers has been covered many times.  You need some way to delineate when you are done typing in numbers.  An example would be pressing enter.  Or always type in 3 digits for your angles.  001, 025, 147, etc.

Try looking through this search: http://www.google.com/search?ie=UTF-8&oe=UTF-8&q=serial.read+multiple+characters&btnG=search&domains=http%3A%2F%2Fwww.arduino.cc%2F&sitesearch=http%3A%2F%2Fwww.arduino.cc%2F

This post has an example of how to bracket the numbers with a NL and CR:http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1269313028

In your second post, the line:
Code: [Select]
   if (reader >= '0' && reader <= '180'){  
won't work the way you expect it to.  The '0' is how C handles characters.  So '180' is meaningless since it is more than 1 character.
Capacitor Expert By Day, Enginerd by night.  ||  Personal Blog: www.baldengineer.com  || Electronics Tutorials for Beginners:  www.addohms.com

zoomkat

#3
Oct 04, 2010, 07:13 am Last Edit: Oct 04, 2010, 07:14 am by zoomkat Reason: 1
The below might be of use. It works with IDE 0018. IDE 0020 includes a string library.

Code: [Select]

// zoomkat 7-30-10 serial servo test
// type servo position 0 to 180 in serial monitor
// for writeMicroseconds, use a value like 1500
// http://arduino.cc/en/uploads/Tutorial/String.zip for WString.h

#include <WString.h> //provides easy string handling
String readString = String(100);
#include <Servo.h>
Servo myservo;  // create servo object to control a servo

void setup() {
     Serial.begin(9600);
       myservo.attach(9);
       }

void loop() {

       while (Serial.available()) {
       delay(10);  
         if (Serial.available() >0) {
       char c = Serial.read();  //gets one byte from serial buffer
       readString.append(c); } //makes the string readString
       }
       
     if (readString.length() >0) {
     Serial.println(readString);
     int n;
     n = atoi(readString); //convert string to number
     myservo.writeMicroseconds(n);
     //myservo.write(n);
     readString="";
     }
  }

Consider the daffodil. And while you're doing that, I'll be over here, looking through your stuff.   8)

T86157

#4
Oct 04, 2010, 07:23 am Last Edit: Oct 04, 2010, 07:31 am by T86157 Reason: 1
hey zoomkat, I just got back to my thread as I found what you just posted here haha: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1281382858/5

Another thread I thought was close to what I am trying to do is from here: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1261452335/0

for some reason the
Code: [Select]
#include <WString.h>
String readString = String(100);

gives me a compiling error saying "21: error: WString.h: No such file or directory"


@James C4S, I'll take a look through arduino.google search again to see if I can get more help on the issue. I know it's been probably covered dozens of times and a lot of the threads that I found through google and arduino says the same thing but they're not referencing any sources that do help on the issue so I'm left to find threads talking about finding threads on reading multiple serial inputs.

zoomkat

Quote
gives me a compiling error saying "21: error: WString.h: No such file or directory"


WString is an added library you get per below:

// http://arduino.cc/en/uploads/Tutorial/String.zip for WString.h
Consider the daffodil. And while you're doing that, I'll be over here, looking through your stuff.   8)

T86157

#6
Oct 04, 2010, 07:45 am Last Edit: Oct 04, 2010, 07:47 am by T86157 Reason: 1
Thanks zoomkat. I'll take a look at your code again.

here's another code I found. This one is written by mem found at http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1251836677/all

can either you, zoomkat or someone else help me interpret some of the coding in it?
I understand enough that if I enter in "+23" it'll move the servo one direction 23 degrees from it's current position while if I enter in "-23" it goes the other direction 23 degrees from that current position.

Again, it's along the lines of what I want but without the "+" and "-" so that it's just a flat out 0 to 180 degrees with a special case of "999" turning off the button/function.

Code: [Select]
#include <Servo.h>

Servo myServo;

const int servoPin = 9; // the pin the servo is connected to

int val = 0;    // a value accumulated from data on the serial port
int angle = 90; // the current angle of the servo



void setup()
{
 Serial.begin(9600);
 myServo.attach(servoPin);
 myServo.write(angle);  // center the servo
}


void loop()
{
 if ( Serial.available())
 {
   char ch = Serial.read();

   if(ch >= '0' && ch <= '9')          // is ch a number?
   val = val * 10 + ch - '0';         // yes, accumulate the value
   else if(ch == '-') // is this the minus sign?
   {
   angle = angle - val;
   if(angle < 0)
      angle = 0;
   myServo.write(angle);       // write the new angle
   val = 0;
   }
   else if(ch == '+') // is this the plus sign?
   {
   angle = angle + val;
   if(angle > 180)
      angle = 180;
   myServo.write(angle);       // write the new angle
   val = 0;
   }
 }
}



I am fuzzy on how the accumulation part works when the serial reads it and arduino compiles it. Can someone please explain that to me?

T86157

#7
Oct 04, 2010, 08:33 am Last Edit: Oct 04, 2010, 08:51 am by T86157 Reason: 1
Well zoomkat, your code works like a charm


I tried working it into my push button criteria but am still have some trouble. I feel like I'm still making progress but now the additional criteria from yours is that if I enter in "0" to "180" it'll give me those angles which it does when I use the
Code: [Select]
myservo.write(n);
and not the
Code: [Select]
myservo.writeMicroseconds(n);

but if I use the integer n in my if/else statements (i.e. if (n>=0) && (n<=180)) a compiling error shows up saying that n is not defined when it was defined earlier.

..from your code, I am trying to set it so that the angle that it is set to is set at a certain speed. Along the lines of:
Code: [Select]
for (int i= myservo.read(); i<= n; i+=1){
   myservo.write(i);
delay(timer);
Serial.println(myservo.read());
if (i == n) break;
}

But again, the compiling error says n is not define even though it was through your code.

Is there a place I could possibly learn more about the library <WString.h>?

for example what does this part (below) do in the code?
Code: [Select]
readString="";


or this line in the code:
Code: [Select]
if (readString.length() >0) {
the readString.length part- is that a value that is equal to what I entered in through the serial or is that the number of places the string takes up? (i.e. if I entered in 12 it's got a length of 2 digits and if I enter in 138 it's got a length of 3 digits)

I am sorry, I guess I should warn all of you I am a visual learner (not exactly a literature learner but pictures and examples help me a lot).

Groove

@JamesC4S:  '180' is not meaningless, it is a perfectly valid multicharacter constant.
It is not a string, but can be used in simple comparisons and assignments.
However, in the context here, it should not be used.
Per Arduino ad Astra

Groove

Don't use flush unless you really understand what you're doing.
Per Arduino ad Astra

zoomkat

I posted what was on the WString page in the below post. I'm now working on converting this code to work with 0021 which should have the same string handling incorporated. myservo.writeMicroseconds(n); is set in the servo.h and servo.ccp to the range of ~500 to ~2400, so input 0-180 will probably defult to the ~500 value.

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1285129740
Consider the daffodil. And while you're doing that, I'll be over here, looking through your stuff.   8)

T86157

Yeah,  I wrote a separate program and 'manually' found all my angles associated with their myservo.writeMicroseconds()

turns out for my servo its full range in microseconds pulse is from 396 to 2422. From 0 to 180 degrees in microseconds for mine is about 553 and 2399. It was a bit of a pain since more than one microsecond pulse would come up as the same angle (i.e. 553, 554 and 552 came up as 0 degrees) so I averaged some of the values which seems to work for me. I'm not looking for super accurate or precise angles to the nearest second or minute.

zoomkat

Hobby type servos can vary as to their timing range and what timing will put them in a particular position. The below in servo.h sets some limits on the range available, possibly to protect the typical servo from straining against the internal hard stop and causing damage. I've tinkered with a standard hobby servos and it can resolve ~425 discrete positions in ~190 deg of rotation, or ~.4 deg.

Code: [Select]

#define MIN_PULSE_WIDTH       544     // the shortest pulse sent to a servo  
#define MAX_PULSE_WIDTH      2400     // the longest pulse sent to a servo
#define DEFAULT_PULSE_WIDTH  1500     // default pulse width when servo is attached

Consider the daffodil. And while you're doing that, I'll be over here, looking through your stuff.   8)

T86157

Thanks, zoomkat. I'll try that out.

@Groove:
Quote
Don't use flush unless you really understand what you're doing.

Can you explain how to use flush and in what situation would it be necessary and unnecessary? My 'guess' based on what the description says is is flush clears or flushes any data that the serial has stored or read so that it can have more memory for later storage of variables.

PaulS

The flush function dumps anything in the serial buffer. There is no reason to ever need to do this. If the thing sending serial data is sending it too fast, you should implement some form of handshaking so that the sender only sends data when the receiver is ready.

Some systems buffer data that is to be written to a relatively slow device, such as a disk drive. On those systems, flush is used to force sending the data to the device even though the buffer is not full. This is not what the Serial.flush() function does. It does exactly like the handle on a flush toilet.

Quote
My 'guess' based on what the description says is is flush clears or flushes any data that the serial has stored or read so that it can have more memory for later storage of variables.

Flushing the serial buffer does not make the space reserved for the serial buffer available for any other use.

Go Up