Pages: [1]   Go Down
Author Topic: Simple-yet-powerfull - Serial Command input with numbers  (Read 1461 times)
0 Members and 1 Guest are viewing this topic.
Copenhagen, Denmark
Offline Offline
Edison Member
*
Karma: 32
Posts: 1211
Have you testrun your INO file today?
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

This is an invitation for comments by the those who feel experienced enough (you know who you are smiley )

Sometime(often) people ask here how to use the Serial input to give simple commands, possibly with some numeric argument to their sketch. The thread then revolves around ASCII <-> Integer conversion and what a "terminator" is. For my own use I have evolved the simple syntax: first the number is entered (ASCIIwise) in the Serial input and then terminated by a command character. The code accumulates so it does not block. Sending the ASCII sequence "67x3yp" would be interpreted as three commands: Command X with argument 67, command Y with argument 3 and Command P with argument 0.

Until recently I usually cut-n-paste this codesnippet in my various sketches, I have now rewritten it as a very small library for the community.

The nnnC.h module
Code:
/*--------------------------------------------------------------------------------*\
|   nnnC - Number Command input                                                    |
|                                                                                  |
|  Read serial input of simple argument values in format "nnnC"                    |
|   nnn is a series of (ASCII) digits, with optional sign (& decimalpoint for nnnF)|
|   C is a single character that is the command or argument name terminates string |
|   C is converted to uppercase.                                                   |
| Call "fffC" instead of "nnnC" for float value (See prototype declaration below)  |
|                                                                                  |
| The call is non-blocking, so command interpretation is done in its own "thread", |
| meaning : You can call it as often as you want in the loop()                     |
| Only when it returns true will the the arguments C and V contain valid values.   |
|                                                                                  |
| Example use:                                                                     |
|   static char C ; static int V ;                                                 |
|   if ( nnnC( &C, &V) ) {                                                         |
|     Serial.print("Command ");Serial.print(C);                                    |
|     Serial.print(" : ");Serial.print(V,DEC);                                     |
|     }                                                                            |
\*--------------------------------------------------------------------------------*/

/*-------------------------------------------------------------------------*\
| Not-a-bug; known limitations:                                             |
| Illegal numeric syntax, how the "-", "+" and "." are placed, are accepted |
| If you use both nnnC and fffC only "switch" between them after a command  |
| Note the parameters must be global or static                              |
| the nnnC only takes int - maximum is +/-32767 as usual                    |
| (But it is easy - just change "int" to "long" and you can do longs :-) )  |
\*-------------------------------------------------------------------------*/

#ifndef _nnnC
#define _nnnC

#if ARDUINO < 100
#include <WProgram.h>
#else
#include <Arduino.h>
#endif

boolean nnnC( char *, int * ) ;
boolean fffC( char *, float * ) ;

#endif


The nnnC.cpp module
Code:
/*--------------------------------------------------------------------------------*\
|   nnnC - Number Command input                                                    |
|                                                                                  |
|  Read serial input of simple argument values in format "nnnC"                    |
|   nnn is a series of (ASCII) digits, with optional sign                          |
|   C is a single character that is the command or argument name terminates string |
|   C is converted to uppercase.                                                   |
|                                                                                  |
| The call is non-blocking, so command interpretation is done in its own "thread", |
| meaning : You can call it as often as you want in the loop()                     |
| Only when it returns true will the the arguments C and V contain valid values.   |
|                                                                                  |
| Example use:                                                                     |
|   static char C ; static int V ;                                                 |
|   if ( nnnC( &C, &V) ) {                                                         |
|     Serial.print("Command ");Serial.print(C);                                    |
|     Serial.print(" : ");Serial.print(V,DEC);                                     |
|     }                                                                            |
| Use "fffC" instead of "nnnC" for float value                                     |
\*--------------------------------------------------------------------------------*/

#include "nnnC.h"

// Integer version
// NB: The "." acts a command character, not part of a number.
boolean nnnC (char * Cmd, int * Val) {
  static boolean Done = true ;
  static boolean Negative = false ;
  if ( Done ) {                        // if a valid value has been returned before
    Negative = Done = false ;          //   reset for new command
    *Val = 0 ;
    }
  while ( Serial.available()>0 ) {     // interpret all serial bytes ...
    *Cmd = Serial.read() ;
  if (*Cmd == '-')                     // negative value
    Negative = true  ;                 //    set flag
    else if ('0' <= *Cmd && *Cmd <= '9') // valid digit
          *Val = (*Val) * 10 + (*Cmd) - '0' ; //    accumulate input
  else if (*Cmd == '+')                // A "+" sign
      ;                                  // ignore
    else {                              // It is a non-numeric character, i.e. the command.
      if ('a' <= *Cmd && *Cmd <= 'z') *Cmd &= B01011111 ; // forces uppercase (for 7bit ASCII)
      if ( Negative ) *Val = -*Val ;     // Adjust for negative value
      Done = true ;                      // note we are done
    return true ;                      // and exit
     }
   }
  return false ;
}

 // Float version
boolean fffC (char * Cmd, float * Val) {
  static boolean Done = true ;
  static boolean Negative  ;
  static byte Fraction ;
  if ( Done ) {                         // reset for new interpretation.
    Negative = Done = false ;
    *Val = 0 ; Fraction = 0 ;
    }
  while ( Serial.available()>0 ) {      // interpret all serial bytes ...
    *Cmd = Serial.read() ;
if (*Cmd == '-') {                  // negative value
  Negative = true ;                 //    set flag
  return false ;                    //    and exit
  }
if (*Cmd == '.') {                  // decimal point
  Fraction = 1 ;                    //    start counting, and thus flag decimal seen.
  return false ;                    //    and exit
  }
    if ('0' <= *Cmd && *Cmd <= '9') {   // valid digit
      *Val = *Val * 10 + *Cmd - '0' ;   // accumulate input
  if ( Fraction > 0 ) Fraction++ ;  // adjusting for after decimal
      return false ;                    // and exit
  }
if (*Cmd == '+')                    // Accept and ignore a "+" sign
  return false ;
// It is a non-numeric character - treated as command character
    if ('a' <= *Cmd && *Cmd <= 'z') *Cmd &= B01011111 ; // forces uppercase (for 7bit ASCII)
    if ( Negative ) *Val = -*Val ;      //adjust for negive value
for( ; Fraction > 0 ; Fraction-- ) *Val /= 10.0 ;  // divide down decimal
    Done = true ;                       // note we are done
return true ;
   }
 }

Note: This is intended as a very lightweight routine, when a simple input commandparser is needed.

(Edit: fixed a bug on float fractions, cleaned up some comments, added some brackets for clarity)
« Last Edit: September 16, 2012, 03:04:27 pm by Msquare » Logged

Pittsburgh, PA, USA
Offline Offline
Faraday Member
**
Karma: 98
Posts: 4801
I learn a bit every time I visit the forum.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Inside both functions:

Code:
  while ( Serial.available()>0 ) {     // interpret all serial bytes ...

blocks other code.

You also seem to think that the whole command string is going to come through in one go every time. But you can debug that later, after you get bit.

Those who feel experienced enough expect things to go wrong and write error handing for it.


Logged

I find it harder to express logic in English than in Code.
Sometimes an example says more than many times as many words.

nr Bundaberg, Australia
Offline Offline
Tesla Member
***
Karma: 126
Posts: 8494
Scattered showers my arse -- Noah, 2348BC.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

It only "blocks" if there are characters to process though so that's not really a block.

I'd be inclined to ad some parens here

if ( Negative ) *Val = -(*Val) ;

just to force the operator precedence and make it more human friendly.

However I wonder if it would be simpler to just accumulate the string then apply ato?() or scanf() functions to it based on what you see in the string.

______
Rob
Logged

Rob Gray aka the GRAYnomad www.robgray.com

Pittsburgh, PA, USA
Offline Offline
Faraday Member
**
Karma: 98
Posts: 4801
I learn a bit every time I visit the forum.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I write code that gets serial and processes it a character at a time (and have posted examples), usually for user I/O but not always. In between serial arriving there's lots of time to do other things like check light level, make sound, etc, which in many cases is the main task with user I/O as the last priority. In those cases, doing nothing but waiting for serial to arrive mos' defnit'lee blocks!

Don't we have an abs() function?
Logged

I find it harder to express logic in English than in Code.
Sometimes an example says more than many times as many words.

Copenhagen, Denmark
Offline Offline
Edison Member
*
Karma: 32
Posts: 1211
Have you testrun your INO file today?
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi GoForSmoke - thanks for the comment, but you are reading the test the wrong way round: the while is ONLY busy while there ARE characters in the buffer. No characters, it will return at once. Likewise if the number/command is complete. At least that is the intention. And as far as I have tested, it seems to work that way. Here is my little test.
Code:
/* Test the nnnC library */

#include "nnnC.h"

void setup() {
  Serial.begin(9600);
  Serial.println("nnnC test");
}
void loop() {
   static char C ; static int V ; static float F ;                               
   if ( nnnC( &C, &V) ) {                           
     Serial.print("Command ");Serial.print(C);     
     Serial.print(" : ");Serial.println(V,DEC);       
     }                               

// An idle LED blink-without-delay to verify nonbloking
   static unsigned long Timer = 0 ;
   static boolean OnOff = true ;
   if ( millis() - Timer > 333 ) {
     Timer = millis() ;
     digitalWrite(13,(OnOff=!OnOff)?HIGH:LOW) ;
   }
}

Graynomad: thanks for the comment about brackets. Using atoi/atof and those require using a buffer (precious RAM bytes) and suddenly there is maximum inputsize (nnnC accepts a 100 digit number - it wont fit in an integer smiley but no parser or memory overflow). I have not exactly measured it, but have experienced that including the atoi/f will appreciably increase the code size.

Any/Everybody: The design is "minimalistic": It is not foolproof (like "7-5" is legal interpreted as -75) but functional enough.
Logged

Pittsburgh, PA, USA
Offline Offline
Faraday Member
**
Karma: 98
Posts: 4801
I learn a bit every time I visit the forum.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I read your code, I know what it says. Read my words again please: I do things in between serial characters coming in even the times it is only to evaluate the text as it comes in. Usually though in my code getting commands takes a total back seat to whatever the commands are -for-. Waiting for 2 to 20 serial characters before getting back to watching sensors and controlling leds or other things (gotta have the blinking lights!) -is- blocking.

Playing "little PC" with Arduino is a good way to waste the cycles you get.

Logged

I find it harder to express logic in English than in Code.
Sometimes an example says more than many times as many words.

UK
Offline Offline
Shannon Member
****
Karma: 223
Posts: 12631
-
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Waiting for 2 to 20 serial characters before getting back to watching sensors and controlling leds or other things (gotta have the blinking lights!) -is- blocking.

I don't understand why you say that. Maybe I'm missing something, but it looks to me as if nnnC() will consume all characters that have already been received and then return; I assume it would be called repeatedly and the return value indicates whether a complete command has been received. This looks to me like a sensible way to read and process incoming serial input. I don't see anything in it that would block waiting for new input on the serial port.
Logged

I only provide help via the forum - please do not contact me for private consultancy.

Pittsburgh, PA, USA
Offline Offline
Faraday Member
**
Karma: 98
Posts: 4801
I learn a bit every time I visit the forum.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

It won't block serial.... and it doesn't wait for available so no it's not really blocking.
Logged

I find it harder to express logic in English than in Code.
Sometimes an example says more than many times as many words.

Copenhagen, Denmark
Offline Offline
Edison Member
*
Karma: 32
Posts: 1211
Have you testrun your INO file today?
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

(Embarassment  smiley-eek Found a bug in the float version. Now fixed. Original post edited.)
Logged

Pages: [1]   Go Up
Jump to: