Are there any good reference implementations for command line implementations for embedded systems?

I know this is nothing new and has been done several times. But I'm looking for some reference implementation (or even just a reference construct) as a “best practice guide”. We have a built-in real-time environment, and the idea is to be able to use the "debug shell" to invoke some commands. Example: "SomeDevice print reg xyz" will ask the SomeDevice subsystem to print a register value named xyz.

+4
source share
7 answers

I have a small set of routines, which essentially consists of three functions and a lookup table:

  • the function that collects the command line is simple; there is no command line history or anything else, just the ability to backtrack or hit escape to drop it all. But if it seemed to me that you need editing capabilities, it would not be difficult for you to add them here.
  • function that parses a text-style string argc / argv (see describe a string in argv / argc for some ideas on this)
  • a function that takes the first argument in the parsed command line and looks at it in the table of commands and function pointers to determine which function to call for the command, so command handlers should just match the prototype

    int command_handler( int argc, char* argv[]);

Then this function is called with the appropriate parameters argc / argv.

In fact, the lookup table also contains pointers to the main help text for each command, and if the command is followed by '-?' or '/?' this bit of help text is displayed. In addition, if help is used for a command, the command is reset (only a subset is possible if the parameter is passed to the help command).

Sorry, I can’t publish the actual source, but it is quite simple and easy to implement and functional enough for almost all the command line processing tasks that I had for developing embedded systems.

+2
source

You can check libcli . It emulates the Cisco CLI and apparently also includes a telnet server. It may be more than what you are looking for, but it may be useful as a reference.

+1
source

If your needs are simple enough, a debugging menu that accepts simple keystrokes rather than a command shell is one way to do this.

For registers and RAM, you may have a submenu that only dumps memory on demand.

Similarly, to enable or disable individual functions, you can control them by pressing keys from the main menu or submenu.

One way to implement this is through a simple state machine. Each screen has a corresponding state that waits for a key to be pressed, and then changes the state and / or refreshes the screen as necessary.

+1
source

vxWorks includes a command shell that inserts a character table and implements a C expression evaluator so you can call functions, evaluate expressions, and access global characters at runtime. The expression evaluator supports integer and string constants.

When I was working on a project migrated from vxWorks to embOS, I implemented the same functionality. Inserting a symbol table required a bit of gymnastics, as it does not exist until it contacts. I used the post build step to analyze the output of the GNU nm tool to create a symbol table as a separate loading module. In an earlier version, I didn’t insert a character table at all, but rather I created a host shell program that ran on the development node where the character table was located and passed using a debugging stub to a target that could make arbitrary address and read / write function calls arbitrary memory. This approach is better for devices with memory limitations, but you should be careful that the character table you use and the target code are for the same assembly. Again, this was an idea I borrowed from vxWorks that supports both a shell with a target and a host server with the same functionality. For the host shell, vxWorks checks the code to ensure that the symbol table matches; in my case it was a manual (and error prone) process, so I implemented a built-in symbol table.

Although initially I only implemented the read / write capabilities of memory and functions, I later added an expression evaluator based on the algorithm (but not the code) described here . Then after that I added simple scripting capabilities in the form of if-else, while constructs and procedures (using a very simple non-C syntax). Therefore, if you need new functions or tests, you can either write a new function or create a script (if performance was not a problem), so the functions were more like “built-in” scripting languages.

To make arbitrary function calls, I used a typedef function pointer, which took an arbitrarily large (24) number of arguments, then, using the character table, you find the address of the function, sketch it on the type of the function pointer and pass it the real arguments, plus enough dummy arguments to make up the expected number and thus create a suitable (if wasteful) stop frame.

On other systems, I implemented the Forth interpreter, which is a very simple language to implement, but may have less user-friendly syntax. You can also embed an existing solution, such as Lua or Ch.

+1
source

You could bristle in this answer, but many years ago we did something similar for a large-scale embedded telecommunications system using lex / yacc (currently I assume it will be flex / bison, it was literally 20 years ago).

Define your grammar, define parameter ranges, etc., and then let lex / yacc generate the code.

There is a small learning curve, as opposed to dragging and dropping an individual 1-off implementation, but then you can expand the grammar, add new commands and parameters, change ranges, etc. .... very quickly.

+1
source

One option is to use a very simple binary protocol to transfer the data you need, and then make the user interface on a PC using, for example, Python or your favorite development tool.

The advantage is that it minimizes the code in the built-in device and transfers it to the PC as much as possible. This is good because:

  • It uses less embedded code space - most of the code is on a PC.
  • In many cases, it is easier to create certain functionality on a PC, while the PC has more tools and resources.
  • This gives you more interface features. You can only use the command line interface if you want. Or you can go for a graphical interface, with graphs, data logging, any fancy stuff you might need.
  • It gives you flexibility. Embedded code is harder to update than PC code. You can change and improve your PC tool whenever you want, without making any changes to the built-in device.

If you want to look at variables - if your computer tool is able to read the ELF file created by the linker, then it can find the location of the variable from the symbol table. Better yet, read the DWARF debugging data and find out the type of variable. Then you just need a read-memory protocol message on the built-in device to receive data, and the PC performs decoding and display.

0
source

For a little easy thing you can use. Its easy to get (fourth cores - SMALL) look at figForth, LINa and GnuForth.

Disclaimer: I don’t know, but openboot and the PCI bus do and I used them and they work very well.

Alternate User Interface

Deploy the web server on your embedded device. Even serial will work with SLIP, and the user interface can be quite complex (or even serve the JAR and it is really very difficult).

If you really need a CLI, you can specify a link and get telnet.

0
source

All Articles