📜 ⬆️ ⬇️

Command line interpreter on the microcontroller with your own hands

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:
  1. C language. I'm not ready to write software for microcontrollers on anything else, although the situation may change.
  2. Reception and processing of lines from UART. For simplicity, all lines end with '\ n' .
  3. Ability to pass parameters to the command. The set of parameters is different for different commands.
  4. Ease of adding new commands.
  5. 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 ".
  6. 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; //     CONSOLE_COMMANDS extern const command_definition *stop_CONSOLE_COMMANDS; //     CONSOLE_COMMANDS command_definition *findCommand(const char *name) { for (command_definition *cur_cmd = start_CONSOLE_COMMANDS; cur_cmd < stop_CONSOLE_COMMANDS; cur_cmd++) { if (strcmp(name, cur_cmd->cmd_name) == 0) { return cur_cmd; } } return NULL; } 
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:

Minuses:

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 .

')

Source: https://habr.com/ru/post/247507/


All Articles