Summary
In a previous article, I gnashed my teeth about my implementation of the FreeRTOS FAT SL. Specifically, I wasn’t very happy about my command line interface. What I did was cheap and easy, which sometimes is a good thing, but given that the FreeRTOS Command Line Interface comes as part of the FreeRTOS installation, it sure seemed like that would have been a better way to go. Now that I have finished the FreeRTOS Command Line Interface implementation, I am not totally in love with how it works. But, it was worth the time to figure it out. In this article In this article I will make a copy of the PSoC Real Time Clock Project, add the FreeRTOS Command Line Interface and explain how the CLI works. In the next article I will give the project the ability to set and read the time from the RTC component in the PSoC.
While I was working on the implementation I discovered that the example code used FreeRTOS_write and FreeRTOS_read which depend on a Peripheral Driver Library (don’t confuse that with the Cypress Peripheral Driver Library) and are part of FreeRTOS+IO. I did very a very simple implementation of those commands to make the FreeRTOS Command Line Interface work, and I will talk more about the FreeRTOS+IO in a future article.
FreeRTOS Command Line Interface
The FreeRTOS Command Line Interface is pretty straightforward to use. Basically you:
- Integrate FreeRTOS_CLI.h and .c into your project
- Create one or more command functions and register them for use by the CLI by calling FreeRTOS_CLIRegisterCommand
- Read input from the terminal UART into a buffer
- Call the CLI (when the users presses enter) and display the output
Integrate FreeRTOS Command Line Interface C Files
I start this whole thing by copying the project “RTC-Example” from the previous article into a new project called “RTC-Example-CLI”. Inside of the directory FreeRTOSv9.0.0/FreeRTOS-Plus/Source/FreeRTOS-Plus-CLI you will find FreeRTOS_CLI.h and FreeRTOS_CLI.c. These two files have all of the FreeRTOS Command Line implementation. To make this work I copied them into my project, and then did “Add –> Existing Item” to get PSoC Creator to make them part of the project.
Create a Command
To use the FreeRTOS Command Line Interface you need to create one or more functions which the CLI will then callback when it detects that the user has typed that command. Each command takes three input parameters
- A pointer to a character buffer where the command can store text to be printed on the screen
- The size of that buffer
- A pointer to a character buffer that holds the whole string that the user typed
The function is then responsible for
- Doing the command
- Writing some output into the output buffer (up to the length of the buffer)
- Returning pdFALSE (if the command has completed writing the output) or pdTRUE (if it needs to do some more processing or more output)
For example if you want to create a command to clear the screen you would create a function called “clearCommand” That command would
- Ignore the pcCommandString (there are no parameters)
- Keep track of the number of characters written into the buffer using a static int
- Copy the VT100 Clear String (AKA “\033[2J\033[H”) into the output buffer
- Null terminate the output string
- If you wrote less characters than the string then return pdTrue
- Otherwise return false
/*****************************************************************************\ * Function: clearCommand * Input: char *pcWriteBufer,size_t xWriteBufferLen,const char *pcCommandString * Returns: BaseType_t * Description: * This function clears the screen. It is run by the CLI interpreter \*****************************************************************************/ BaseType_t clearCommand( char *pcWriteBuffer,size_t xWriteBufferLen, const char *pcCommandString ) { (void)pcCommandString; static int processed = 0; char *clearScreen = VT100_CLEARSCREEN; // Only allowed to write up top xWriteBufferLen bytes ... strncpy(pcWriteBuffer,&clearScreen[processed],xWriteBufferLen-1); pcWriteBuffer[xWriteBufferLen-1]=0; processed = processed + xWriteBufferLen-1; if(processed < (int)strlen(clearScreen)) return pdTRUE; processed = 0; return pdFALSE; }
Once you have created the command function you then need to register it with the FreeRTOS Command Line Interface. To do that you create a structure of type “CLI_Command_Definition_t” which has 4 members.
- The ascii string which the user can type to trigger the command
- The help message
- A function pointer to the command function
- The number of arguments that the CLI should accept
static const CLI_Command_Definition_t clearCommandStruct = { "clear", "clear: Clear Screen by sending VT100 Escape Code\n", clearCommand, 0 };
Finally you need to register the command. You should be aware that this function uses malloc.
FreeRTOS_CLIRegisterCommand( &clearCommandStruct );
Call the CLI and Display
On the FreeRTOS Command Line Interface webpage they provided a sample task to handle the command line interpreter. I copied this task into my project and then made a few little changes.
- My Mac sends a ‘\r’ when you press the return key so I changed line 164 to reflect that
- My Mac sends 0x7F when you press the “Delete” key. So I changed the ‘\b’ (aka backspace) to be 0x7F aka “del”
- I fixed the stupid ‘\r\n’ stuff
One of the great annoyances in the world is the way that line breaks in text files and terminals are handled. It is typical in “unix” to use just a newline “\n”. However, in “DOS” it is typical to use a carriage return/newline “\r\n”. All over FreeRTOS it uses DOS mode. I typically like to use unix mode… so you will find that I made that changes in the FreeRTOS CLI code.
The cliTask function does a number of things.
- Initializes the IO System, RTC and Command Line Interface (lines 154-163)
- Gets 1 character from the UART using FreeRTOS_read
- If the character is a carriage return (\r) then it calls the CLI until there is no more output (lines 167-196). Remember that when you implement a command function for the CLI, if you return pdFALSE it means that you have no more output, and if you return pdTRUE then you have more output.
- If the character is the delete key (0x7F) then erase the character from the input buffer)
- Otherwise add it to the input buffer… assuming that you still have room. (lines 227-232)
/*****************************************************************************\ * Function: cliTask * Input: void *arg ... unused * Returns: void * Description: * This function is the inifite loop for the command line intepreter.. it * reads characters using the FreeRTOS_read function then sends them to the * cli when there is a \r \*****************************************************************************/ void cliTask(void *arg) { (void)arg; char pcOutputString[ MAX_OUTPUT_LENGTH ], pcInputString[ MAX_INPUT_LENGTH ]; int8_t cRxedChar, cInputIndex = 0; BaseType_t xMoreDataToFollow; FreeRTOS_open( (const int8_t *)"/uart",0 ); clearScreen(); #define INTRO_STRING "Command Line & RTC Demo\n" FreeRTOS_write(0,INTRO_STRING,strlen(INTRO_STRING)); RTC_Start(); FreeRTOS_CLIRegisterCommand( &clearCommandStruct ); FreeRTOS_CLIRegisterCommand( &setTimeCommandStruct ); FreeRTOS_CLIRegisterCommand( &timeCommandStruct ); while(1) { FreeRTOS_read( 0, &cRxedChar, sizeof( cRxedChar ) ); if( cRxedChar == '\r' ) { /* 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. */ FreeRTOS_write(0,&cRxedChar,1); /* 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. */ FreeRTOS_write( 0, pcOutputString, strlen( pcOutputString ) ); } 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 ); } 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 == 127 ) // delete character { FreeRTOS_write(0,&cRxedChar,1); /* Backspace was pressed. Erase the last character in the input buffer - if there are any. */ if( cInputIndex > 0 ) { cInputIndex--; pcInputString[ cInputIndex ] = '/*****************************************************************************\ * Function: cliTask * Input: void *arg ... unused * Returns: void * Description: * This function is the inifite loop for the command line intepreter.. it * reads characters using the FreeRTOS_read function then sends them to the * cli when there is a \r \*****************************************************************************/ void cliTask(void *arg) { (void)arg; char pcOutputString[ MAX_OUTPUT_LENGTH ], pcInputString[ MAX_INPUT_LENGTH ]; int8_t cRxedChar, cInputIndex = 0; BaseType_t xMoreDataToFollow; FreeRTOS_open( (const int8_t *)"/uart",0 ); clearScreen(); #define INTRO_STRING "Command Line & RTC Demo\n" FreeRTOS_write(0,INTRO_STRING,strlen(INTRO_STRING)); RTC_Start(); FreeRTOS_CLIRegisterCommand( &clearCommandStruct ); FreeRTOS_CLIRegisterCommand( &setTimeCommandStruct ); FreeRTOS_CLIRegisterCommand( &timeCommandStruct ); while(1) { FreeRTOS_read( 0, &cRxedChar, sizeof( cRxedChar ) ); if( cRxedChar == '\r' ) { /* 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. */ FreeRTOS_write(0,&cRxedChar,1); /* 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. */ FreeRTOS_write( 0, pcOutputString, strlen( pcOutputString ) ); } 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 ); } 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 == 127 ) // delete character { FreeRTOS_write(0,&cRxedChar,1); /* Backspace was pressed. Erase the last character in the input buffer - if there are any. */ if( cInputIndex > 0 ) { cInputIndex--; pcInputString[ cInputIndex ] = '\0'; } } 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 ) { FreeRTOS_write(0,&cRxedChar,1); pcInputString[ cInputIndex ] = cRxedChar; cInputIndex++; } } } } }'; } } 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 ) { FreeRTOS_write(0,&cRxedChar,1); pcInputString[ cInputIndex ] = cRxedChar; cInputIndex++; } } } } }
As always you can find all of this code on the IoT Expert GitHub website or your can git clone git@github.com:iotexpert/PSoC-FileSystem.git