Making FreeRTOS CLI more CLI-ish : minor tweak & backspace

 

In this short a sweet post we will simply tweak how we register commands to save a few keystrokes.

Instead of separate command structs lets just turn them into an array of those structs. By letting the last element in that array always be a NULL command we can use that as delimiter to signal the end of the struct. Alternatively you can use the sizeof function and calculate the size and number of elements in your struct but the first method works just as fine.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static const CLI_Command_Definition_t cmd_list[] = {

    {
        .pcCommand = "ok",                                  // command to type
        .pcHelpString = "ok :\r\n Shows an OK message\r\n", // help string
        .pxCommandInterpreter = cmd_ok,                     // command handler
        .cExpectedNumberOfParameters = 0                    // num of pasrameters to expect
    },
    {
        .pcCommand = "start",                            // command to type
        .pcHelpString = "start :\r\n Starts a test\r\n", // help string
        .pxCommandInterpreter = cmd_start_test,          // command handler
        .cExpectedNumberOfParameters = 0                 // num of pasrameters to expect
    },
    {
        .pcCommand = "stop",                           // command to type
        .pcHelpString = "stop :\r\n Stops a test\r\n", // help string
        .pxCommandInterpreter = cmd_stop_test,         // command handler
        .cExpectedNumberOfParameters = 0               // num of pasrameters to expect
    },
    {.pcCommand = NULL}

};


Now the register function is updated and never touch again.

1
2
3
4
5
6
7
8
void vRegisterCLICommands(void)
{
    uint8_t command_index = 0;
    while (cmd_list[command_index].pcCommand != NULL)
    {
        FreeRTOS_CLIRegisterCommand(&cmd_list[command_index++]);
    }
}

Backspace

Next up is to improve or rather make functional the backspace feature. If you attempt to erase a type character it will not work because the sequence to send that character to the terminal has not been implemented. 

Below is the vCommandTask with the feature implemented followed by it's explanation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
void vCommandConsoleTask(void *pvParameters)
{
    int8_t cInputIndex = 0;
    BaseType_t xMoreDataToFollow;
    /* The input and output buffers are declared static to keep them off the
     * stack. */
    static int8_t pcOutputString[MAX_OUTPUT_LENGTH], pcInputString[MAX_INPUT_LENGTH];

    vRegisterCLICommands();
    show_prompt();
    for (;;)
    {

        if (cRxedChar != 0x00)
        {

            if (cRxedChar == '\r' || cRxedChar == '\n')
            {
                printf("\r\n");
                fflush(stdout);
                /* A newline character was received, so the input command string is
                complete and can be processed.  Transmit a line separator, just to
                make the output easier to read. */

                /* The command interpreter is called repeatedly until it returns
                pdFALSE.  See the "Implementing a command" documentation for an
                exaplanation of why this is. */
                do
                {
                    /* Send the command string to the command interpreter.  Any
                    output generated by the command interpreter will be placed in the
                    pcOutputString buffer. */
                    xMoreDataToFollow =
                        FreeRTOS_CLIProcessCommand(pcInputString,    /* The command string.*/
                                                   pcOutputString,   /* The output buffer. */
                                                   MAX_OUTPUT_LENGTH /* The size of the output buffer. */
                        );

                    /* Write the output generated by the command interpreter to the
                    console. */

                    for (int x = 0; x < (xMoreDataToFollow == pdTRUE ? MAX_OUTPUT_LENGTH : strlen(pcOutputString)); x++)
                    {
                        printf("%c", *(pcOutputString + x));
                        fflush(stdout);
                    }

                } while (xMoreDataToFollow != pdFALSE);

                /* All the strings generated by the input command have been sent.
                Processing of the command is complete.  Clear the input string ready
                to receive the next command. */
                cInputIndex = 0;
                memset(pcInputString, 0x00, MAX_INPUT_LENGTH);
                memset(pcOutputString, 0x00, MAX_INPUT_LENGTH);
                show_prompt();
            }
            else
            {
                /* The if() clause performs the processing after a newline character
                is received.  This else clause performs the processing if any other
                character is received. */

                if (cRxedChar == '\b')
                {
                    /* Backspace */
                    if (cInputIndex > 0)
                    {
                        cInputIndex--;
                        memset(&pcInputString[cInputIndex], 0x00, 1);
                        printf("%s", (const uint8_t *)    );
                    }
                    fflush(stdout);
                }
                else
                {
                    /* A character was entered.  It was not a new line, backspace
                    or carriage return, so it is accepted as part of the input and
                    placed into the input buffer.  When a n is entered the complete
                    string will be passed to the command interpreter. */
                    if (cInputIndex < MAX_INPUT_LENGTH)
                    {
                        pcInputString[cInputIndex] = cRxedChar;
                        cInputIndex++;
                        printf("%c", cRxedChar);
                    }
                }
            }
            cRxedChar = 0x00;
            fflush(stdout);
        }
    }
}

The lines that have changed are lines 66 through 73 but lets start at line 64.
  • Line 64 : If the backspace character is received (0x08)
  • Line 67 : checks that we have something actually type. cInputIndex keeps track of the number of typed characters, while also serving as a pointer to the next available slot in the input string buffer.
  • Line 69 : The cInputIndex is decremented so that the next available slot in the string buffer now points to the last typed character which is the one the user wants to delete. So that when they type again it gets over written in the buffer
  • Line 70 : The current cInputIndex is also set to zero or “cleared” just incase the user does not actually want to type anything else and thus never “overwrites” it.
  • Line 71 : We send/print the sequence of characters that tells the terminal to perform a backspace action.

The backspace variable is defined at the global scope at the top of this file as

1
2
/* CLI escape sequences*/
uint8_t backspace[] = "\x08 \x08";


That pretty much takes care of this short post. In the next post things will get slightly more involved as we try to capture input sequences. These are keys on the keyboard that do not exists on the ASCII table but are instead represented as a sequence of characters, such as the arrow keys.
We will use the arrow keys up/down to cycle through the history of typed commands.

<< Previous | Next >>

Comments

  1. I think you forgot to put the variable name "backspace" on line 71: it should read "printf("%s", (const uint8_t *) backspace );

    ReplyDelete
Share your comments with me

Archive

Contact Form

Send