In the previous episode, you discovered how to write a shell command’s function and what its execution looks like. Now it’s time to find out how to present them to the user in the form of hierarchical menus known as “pseudo filesystem” (PFS).
STM Shell parses command lines with its internal state function shell_state_parser. The first word of the command line (everything until the first space) is interpreted as the command to be executed. The parser’s job is to look for a matching command function in the PFS blocks and assign it to the shell_fp function pointer for execution.
Your job is to write PFS blocks that tie a command’s name (character string) to a command function (function pointer) so shell_state_parser can do its thing.
Again, the easiest way to learn is by example. Here’s what the demo root block in “shell_pfs.c” looks like :
t_shell_block_entry root_block[] = { {"STM32", BLOCK_LEN 5, 0}, {"sm1 - submenu example", 0, CMD_BLOCK level_1_block}, {"led - toggles the blue LED", command_led_toggle, 0}, {"flash N - flash the LED 'N' times", command_flash, 0}, {"cnt - displays its own call count", command_cnt, 0}, {"load - performance test", command_load, 0} };
This is the declaration and initialization of an array of t_shell_block_entry. That type is a simple structure composed of :
- A string
- A function pointer meant for a command function
- A t_shell_block_entry pointer meant for “submenu” block pointer.
Note that there must always be a block named root_block. That’s the minimum requirement for STM Shell to compile.
There are three possible sorts of entries in a block :
1. Block Title
{"STM32", BLOCK_LEN 5, 0}
The first entry in a block is the title entry. It’s not a command. Its fields are treated like so :
- The whole string will be appended to the shell prompt, after a slash. It will also be displayed by “ls” as a header.
- The function pointer is used to store the number of entries in the block, not counting the title entry. The BLOCK_LEN macro hides the relevant typecasting.
- The block pointer must be zero.
2. Command
{"flash N - flash the LED 'N' times", command_flash, 0}
A command entry is essentially a string and a function pointer. Again, the block pointer must be zero : it lets the parser tell commands and submenus apart.
The first word of the string, meaning every character until the first space, is what the user must type to launch the command. However, the whole string is printed out to the terminal by the “ls” command, and you should use it to give the user a hint about the command. In the example above, “N” isn’t part of the command word since it’s after the first space, but it does tell the user that the command expects an argument.
The function pointer must point to a command function. If you’ve been reading the STM Shell documentation in order, you know that command functions must meet an important requirement : handing execution back to the shell after they complete.
If you’re not used to C pointers, the address of a function is just the name of that function, without parentheses.
3. Submenu
{"sm1 - submenu example", 0, CMD_BLOCK level_1_block}
A submenu entry lets the user navigate to a different block one level below the current block.
Unlike a UNIX-like shell, STM Shell does not use the “cd” command for that. Mainly for the sake of user convenience, all you need to do is enter the submenu’s name.
The code snippet above, for instance, declares a submenu implemented by block “level_1_block”, and to enter it you just need to type “sm1” as if it were a command.
To go back up one level towards the root, use the “cd..” command (no space).
The entry’s string works just like that of a command entry. However, this time the function pointer must be zero and the block pointer must point to a valid block (array of t_shell_block_entry). The CMD_BLOCK macro hides the relevant typecasting.
STM Shell stores your “navigation history” in the unused block pointer of each block you navigate through to your destination. This is what allows it to support an infinite directly nesting. It also lets you point to the same block from different blocks (meaning you can place a submenu in multiple locations) and “cd..” will always return you up the same path you went down.
4. Quick Recap
You need to create a PFS to present your command functions to the user.
That PFS must have at least a root block but can implement an infinitely deep and wide hierarchy of submenus.
Each menu, including the root, is implemented as a block. That’s an array of entries that covers the name of a menu and the commands and submenus it contains. Here, we’ve seen the declaration of a static root block, created at compile time. However, if you feel the need to, STM Shell will let you create your blocks dynamically at run time. I just haven’t found a need for such an advanced technique.
As with commands, the same submenu can be placed in multiple menus. This feature reduces the amount of navigation the user needs to do, letting them access commonly-used commands and menus from different paths.
5. The Cake is a Lie
Guess what, you’ve actually read through all the documentation ! You did RTFM !
Unfortunately, I am all out of celebratory cake. I blame GLaDOS.
Also, this isn’t quite the end. It’s only the end if you just needed to add a custom shell to your STM32 project. There are a few annex topics I still need to tell you about, such as the STM Shell logging facility and how to use an STM32 USB device peripheral instead of a UART for your shell.
Over time, I may also add pages covering integration details on specific STM32 chips or boards. And I’m not just saying that to make you come back regularly. I’m expecting addiction will take care of that.