Building a simple command line interface

I am attempting to write a command line interface for my embedded application (LM3S1439 based). I would like some advice on how to best approach it.

Some of my specifications would be:

-the CLI should communicate with any terminal emulator.

-the backspace key should work as intended

-other controls like arrow keys etc not necessary and should be ignored

-the enter key delimits the command entry

-command names vary in length

-command arguments vary in number

-multiple argument types (ex: cmr -p=4 -f=true -z=22.5)

-binary transfer mode (ymodem transfer?)

-new commands implemented with ease (subjective, I know, but the point is it should be extensible/flexible)

At the moment I have the UART receive interrupt grabbing chars from the input buffer examining them for a few control characters which I wish to observe (BS,CR,LF), as well as dumping chars I am not concerned about (other ASCII control chars) and stuffing the remaining chars into a command buffer. This all works quite well via several terminal emulators.

The idea was that once a CR or LF was detected the command buffer would be evaluated/compared against an array of structures which contained the name (string) of the command as well as a pointer to the appropriate function to be called. The function would be called and supplied the number of arguments and the pointer to the argument string.

I think that makes sense. Any suggestions on how best to structure this? This could get messy very quickly if not done right.

To further complicate issues I have the requirement to receive binary data. How do I best handle this transfer? I was thinking I could have a global flag that could be set as COMMAND or BINARY. If set as COMMAND then the incoming data would be processed as above. If not, then the data would be passed without interpretation to a buffer called out by the function requesting the binary transfer. Reasonable?

Regarding the deciphering of arguments. The functions atoi,atof, atol would really come in handy here ,but from what I have picked up in the recent months, including stdlib.h is not recommended in embedded apps? Comments?

Thanks!

Reply to
eeboy
Loading thread data ...

eeboy wrote:

... snip ...

The following code is well suited to embedded operation, since it is purely portable standard C, and it deals with streams. This eliminates the need for various buffers. I have also released it for free use anywhere. The only area I am unhappy with is the conversion of signs in rdxint, which I want to be universal. The code can easily be mounted in a library.

/* ------------------------------------------------- * * File txtinput.c * * ------------------------------------------------- */

#include /* xxxx_MAX, xxxx_MIN */ #include /* isdigit, isblank, isspace */ #include /* FILE, getc, ungetc */ #include "txtinput.h"

/* For licensing restrictions (GPL) see readme.txt in: * * * These stream input routines are written so that simple * conditionals can be used: * * if (readxint(&myint, stdin)) { * do_error_recovery; normally_abort_to_somewhere; * } * else { * do_normal_things; usually_much_longer_than_bad_case; * } * * They allow overflow detection, and permit other routines to * detect the character that terminated a numerical field. No * string storage is required, thus there is no limitation on * the length of input fields. For example, a number entered * with a string of 1000 leading zeroes will not annoy these. * * The numerical input routines *NEVER* absorb a terminating * char (including '\n'). Thus a sequence such as: * * err = readxint(&myint, stdin); * flushln(stdin); * * will always consume complete lines, and after execution of * readxint a further getc (or fgetc) will return the character * that terminated the numeric field. * * They are also re-entrant, subject to the limitations of file * systems. e.g interrupting readxint(v, stdin) operation with * a call to readxwd(wd, stdin) would not be well defined, if * the same stdin is being used for both calls. If ungetc is * interruptible the run-time system is broken. * * Originally issued 2002-10-07 * * Revised 2006-01-15 so that unsigned entry overflow (readxwd) uses the normal C modulo (UINT_MAX + 1) operation. readxwd still rejects an initial sign as an error. * * Modified to allow free use - C.B. Falconer 2008-01-16 */

/* ------------------------------------------------------------- * Skip to non-blank on f, and return that char. or EOF The next * char that getc(f) will return is unknown. Local use only. */ static int ignoreblks(FILE *f) { int ch;

do { ch = getc(f); } while ((' ' == ch) || ('\t' == ch)); /* while (isblank(ch)); */ /* for C99 */ return ch; } /* ignoreblks */

/*-------------------------------------------------------------- * Skip all blanks on f. At completion getc(f) will return * a non-blank character, which may be \n or EOF * * Skipblks returns the char that getc will next return, or EOF. */ int skipblks(FILE *f) { return ungetc(ignoreblks(f), f); } /* skipblks */

/*-------------------------------------------------------------- * Skip all whitespace on f, including \n, \f, \v, \r. At * completion getc(f) will return a non-blank character, which * may be EOF * * Skipwhite returns the char that getc will next return, or EOF. */ int skipwhite(FILE *f) { int ch;

do { ch = getc(f); } while (isspace(ch)); return ungetc(ch, f); } /* skipwhite */

/*-------------------------------------------------------------- * Read an unsigned value. Signal error for overflow or no * valid number found. Returns 1 for error, 0 for noerror, EOF * for EOF encountered before parsing a value. * * Skip all leading blanks on f. At completion getc(f) will * return the character terminating the number, which may be \n * or EOF among others. Barring EOF it will NOT be a digit. The * combination of error, 0 result, and the next getc returning * \n indicates that no numerical value was found on the line. * * If the user wants to skip all leading white space including * \n, \f, \v, \r, he should first call "skipwhite(f);" * * Peculiarity: This specifically forbids a leading '+' or '-'. */ int readxwd(unsigned int *wd, FILE *f) { unsigned int value, digit; int status; int ch;

#define UWARNLVL (UINT_MAX / 10U) #define UWARNDIG (UINT_MAX - UWARNLVL * 10U)

value = 0; /* default */ status = 1; /* default error */

ch = ignoreblks(f);

if (EOF == ch) status = EOF; else if (isdigit(ch)) status = 0; /* digit, no error */

while (isdigit(ch)) { digit = ch - '0'; if ((value > UWARNLVL) || ((UWARNLVL == value) && (digit > UWARNDIG))) { status = 1; /* overflow */ value -= UWARNLVL; } value = 10 * value + digit; ch = getc(f); } /* while (ch is a digit) */

*wd = value; ungetc(ch, f); return status; } /* readxwd */

/*-------------------------------------------------------------- * Read a signed value. Signal error for overflow or no valid * number found. Returns true for error, false for noerror. On * overflow either INT_MAX or INT_MIN is returned in *val. * * Skip all leading blanks on f. At completion getc(f) will * return the character terminating the number, which may be \n * or EOF among others. Barring EOF it will NOT be a digit. The * combination of error, 0 result, and the next getc returning * \n indicates that no numerical value was found on the line. * * If the user wants to skip all leading white space including * \n, \f, \v, \r, he should first call "skipwhite(f);" * * Peculiarity: an isolated leading '+' or '-' NOT immediately * followed by a digit will return error and a value of 0, when * the next getc will return that following non-digit. This is * caused by the single level ungetc available. * * This module needs further work. CBF 2008-01-16 */ int readxint(int *val, FILE *f) { unsigned int value; int status, negative; int ch;

*val = value = 0; /* default */ status = 1; /* default error */ negative = 0;

ch = ignoreblks(f);

if (EOF != ch) { if (('+' == ch) || ('-' == ch)) { negative = ('-' == ch); ch = ignoreblks(f); /* absorb any sign */ }

if (isdigit(ch)) { /* digit, no error */ ungetc(ch, f); status = readxwd(&value, f); ch = getc(f); /* This terminated readxwd */ }

if (0 == status) { /* got initial digit and no readxwd overflow */ if (!negative && (value

Reply to
CBFalconer

nd

e
e

to

n
d

You seem to be focused on the C language. If you take a look at the Forth language, you will find that it already does some 70% or more of what you are asking for. If you make a few adjustments to your command line syntax, it will do as much as 100% of what you want in terms of parsing your command line. For example, you wrote cmr -p=3D4 - f=3Dtrue -z=3D22.5. Forth typically requires that the words it natively recognizes be separated by spaces. So you might change your syntax to cmr -p=3D 4 -f=3D true -z=3D 22.5, then it will be very simple to write som= e code to parse this line. Even better might be to reverse the order of parameters, such as 4 -p=3D true -f=3D 22.5 -z=3D cmr, but this is not necessary, it just simplifies the code a bit.

You can get Forth from both Forth, Inc and MPE (MicroProcessor Engineering, Ltd). The low water mark of price is about $500, IIRC. I'm not certain they directly support the ARM CM3, but I expect they might be willing to work with you on that. I know the CM3 is the new market for ARM and will only be increasing market share.

Rick

Reply to
rickman

eeboy schrieb:

NAME strtok, strtok_r - extract tokens from strings

SYNOPSIS #include

char *strtok(char *str, const char *delim);

char *strtok_r(char *str, const char *delim, char **saveptr);

DESCRIPTION The strtok() function parses a string into a sequence of tokens. On the first call to strtok() the string to be parsed should be specified in str. In each subsequent call that should parse the same string, str should be NULL.

The delim argument specifies a set of characters that delimit the tokens in the parsed string. The caller may specify different strings in delim in successive calls that parse the same string.

Falk

Reply to
Falk Willberg

... snip ...

... snip ...

Three things. First, the description of functions in the C standard is much more accurate than various help manuals. Second, strtok_r does not exist in standard C, so you can't count on it. Even if it exists in your system, there is no guarantee it follows the above description.

Third, strtok is a nuisance to use, because it doesn't detect missing tokens (i.e. successive delimiters). Therefore I have build tknsplit, whose code follows, and is written in standard C (thus portable) and is put in the public domain.

/* ------- file tknsplit.c ----------*/ #include "tknsplit.h"

/* copy over the next tkn from an input string, after skipping leading blanks (or other whitespace?). The tkn is terminated by the first appearance of tknchar, or by the end of the source string.

The caller must supply sufficient space in tkn to receive any tkn, Otherwise tkns will be truncated.

Returns: a pointer past the terminating tknchar.

This will happily return an infinity of empty tkns if called with src pointing to the end of a string. Tokens will never include a copy of tknchar.

A better name would be "strtkn", except that is reserved for the system namespace. Change to that at your risk.

released to Public Domain, by C.B. Falconer. Published 2006-02-20. Attribution appreciated. Revised 2006-06-13 2007-05-26 (name)

*/

const char *tknsplit(const char *src, /* Source of tkns */ char tknchar, /* tkn delimiting char */ char *tkn, /* receiver of parsed tkn */ size_t lgh) /* length tkn can receive */ /* not including final '\0' */ { if (src) { while (' ' == *src) src++;

while (*src && (tknchar != *src)) { if (lgh) { *tkn++ = *src; --lgh; } src++; } if (*src && (tknchar == *src)) src++; } *tkn = '\0'; return src; } /* tknsplit */

#ifdef TESTING #include

#define ABRsize 6 /* length of acceptable tkn abbreviations */

/* ---------------- */

static void showtkn(int i, char *tok) { putchar(i + '1'); putchar(':'); puts(tok); } /* showtkn */

/* ---------------- */

int main(void) { char teststring[] = "This is a test, ,, abbrev, more";

const char *t, *s = teststring; int i; char tkn[ABRsize + 1];

puts(teststring); t = s; for (i = 0; i < 4; i++) { t = tknsplit(t, ',', tkn, ABRsize); showtkn(i, tkn); }

puts("\nHow to detect 'no more tkns' while truncating"); t = s; i = 0; while (*t) { t = tknsplit(t, ',', tkn, 3); showtkn(i, tkn); i++; }

puts("\nUsing blanks as tkn delimiters"); t = s; i = 0; while (*t) { t = tknsplit(t, ' ', tkn, ABRsize); showtkn(i, tkn); i++; } return 0; } /* main */

#endif /* ------- end file tknsplit.c ----------*/

/* ------- file tknsplit.h ----------*/ #ifndef H_tknsplit_h # define H_tknsplit_h

# ifdef __cplusplus extern "C" { # endif

#include

/* copy over the next tkn from an input string, after skipping leading blanks (or other whitespace?). The tkn is terminated by the first appearance of tknchar, or by the end of the source string.

The caller must supply sufficient space in tkn to receive any tkn, Otherwise tkns will be truncated.

Returns: a pointer past the terminating tknchar.

This will happily return an infinity of empty tkns if called with src pointing to the end of a string. Tokens will never include a copy of tknchar.

released to Public Domain, by C.B. Falconer. Published 2006-02-20. Attribution appreciated. revised 2007-05-26 (name)

*/

const char *tknsplit(const char *src, /* Source of tkns */ char tknchar, /* tkn delimiting char */ char *tkn, /* receiver of parsed tkn */ size_t lgh); /* length tkn can receive */ /* not including final '\0' */

# ifdef __cplusplus } # endif #endif /* ------- end file tknsplit.h ----------*/

--
 [mail]: Chuck F (cbfalconer at maineline dot net) 
 [page]: 
            Try the download section.
Reply to
CBFalconer

Thanks! tknsplit will work quite nicely for parsing out commands.

Reply to
eeboy

I guess I am ignorant on streams. What is the difference between a buffer and a stream and how do I implement a stream?

Reply to
eeboy

You don't, the C system does. The natural form for file i/o in C is a stream. Just open the file with fopen, read it with getc, fgets, etc. and the system does its own buffering.

--
 [mail]: Chuck F (cbfalconer at maineline dot net) 
 [page]: 
            Try the download section.
Reply to
CBFalconer

What is the difference between a reservoir and a pipeline?

You don't. You use it.

Reply to
Hans-Bernhard Bröker

"stream" differentiates from "block". In block I/O all reads/writes have the same length - think of how a file is stored on disk. Stream I/O allows read and writes of variable length.

Buffering, per se, has nothing to do with either type of I/O ... it is an optimization that is applicable to both forms.

C's I/O model is stream based. It's unlikely that you have to implement anything unless your chip is very new. If you can read/write/ioctl the device then you're all set.

George

Reply to
George Neuner

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.