In every device under development, I had a debugging output in the UART, as in the most common and simple interface.
And each time, sooner or later, besides the passive output, I wanted to make the input of commands through the same UART. This usually happened when I wanted to debug some very large amount of information on request (for example, the state of NANDFLASH when developing my own file system). And sometimes I wanted to programmatically manage the GPIO legs to rehearse work with some peripherals on the board.
Anyway, I needed a CLI that allows me to process different commands. If someone stumbles upon a ready-made tool for this purpose - I will be grateful for the link in the comments. In the meantime, I wrote my own.
Requirements, in order of decreasing importance:
- C language. I'm not ready to write software for microcontrollers on anything else, although the situation may change.
- Reception and processing of lines from UART. For simplicity, all lines end with '\ n' .
- Ability to pass parameters to the command. The set of parameters is different for different commands.
- Ease of adding new commands.
- Ability to add new commands in different source files. Those. starting to implement the next functionality in the " new_feature.c " file, I don’t touch the CLI sources, but add new commands in the same file " new_feature.c ".
- Minimum used resources (RAM, ROM, CPU).
I will not describe in detail the UART driver that saves received characters to a static buffer, throwing spaces at the beginning of a line and waiting for a newline character.
Let's start with the more interesting - we have a line ending with
'\ n' . Now you need to find the appropriate team and execute it.
Solution in the form of
typedef void (*cmd_callback_ptr)(const char*); typedef struct { const char *cmd_name; cmd_callback_ptr callback; }command_definition;
and searching in the set of registered teams commands with the desired name suggests itself. Only here is the catch - how to implement this search? Or, more precisely, how to make this set?
If it were in
C ++, the most obvious solution would be to use
std :: map <char *, cmd_callback_ptr> and search in it (no matter how). Then the process of registering a command would be reduced to adding a pointer to a handler function to the dictionary. But I am writing to
C , and I don’t want to switch to
C ++ yet.
The next idea is the global
command_definition array
registered_commands [] = {...} , but this path violates the requirement to add commands from different files.
To get an array of "more" and add commands like function
#define MAX_COMMANDS 100 command_definition registered_commands[MAX_COMMANDS]; void add_command(const char *name, cmd_callback_ptr callback) { static size_t commands_count = 0; if (commands_count == MAX_COMMANDS) return; registered_command[commands_count].cmd_name = name; registered_command[commands_count].callback = callback; commands_count++; }
I also do not want to, because you will have to either constantly
tweak the MAX_COMMANDS constant, or waste your memory ... In general, it's ugly somehow :-)
Doing the same with dynamic allocation of memory and increasing the allocated array with
realloc on each addition is probably a good way out, but I did not want to get involved with dynamic memory in general (it is not used anywhere else in the project, it takes a lot of code in ROM, and RAM is not rubber).
As a result, I came to the following curious, but, unfortunately, not the most portable solution:
#define REGISTER_COMMAND(name, func) const command_definition handler_##name __attribute__ ((section ("CONSOLE_COMMANDS"))) = \ { \ .cmd_name = name, \ .callback = func \ } extern const command_definition *start_CONSOLE_COMMANDS;
All the magic here is enclosed in the
REGISTER_COMMAND macro, which creates global variables so that when the code is executed they will go in memory exactly one after another. And this magic relies on the
section attribute, which indicates to the linker that this variable should be put in a separate memory section. Thus, at the output, we get something very similar to the array of
registered_commands from the previous example, but not requiring to know in advance how many elements it will have. And the pointers to the beginning and end of this array are provided by a linker.
Let's sum up, write down the pros and cons of this solution:
Pros:- The ability to produce a team until the memory runs out.
- Checking the uniqueness of command names at the assembly stage. Non-unique commands will result in the creation of two variables with the same name, which will be diagnosed by the linker as an error.
- Ability to declare commands in any unit of translation, without changing the rest.
- No dependencies on any external libraries.
- Lack of need for special run-time initialization (registration of commands, etc.).
- No memory overhead. The entire array of commands can be placed in the ROM.
Minuses:- Based on a specific toolchain. For others, you will have to edit the creation of the team and, possibly, the linker script.
- Not implemented on all architectures, because relies on the structure of the binary format of the executable file. (see gcc variable attributes )
- Linear search on registered teams, because array not sorted.
The last minus can be overcome with the price of the last plus - you can place commands in RAM, then sort them. Or even in advance to calculate some hash-function to compare not through
strcmp .