Command Line Interface: Difference between revisions

From Embedded Workshop
Jump to navigation Jump to search
Line 24: Line 24:
  serial port, and use the same configuration options.
  serial port, and use the same configuration options.
Serial Output, Serial Input, and printf() Support
Serial Output, Serial Input, and printf() Support
  To use the printf() library, sending printf() output to the USB serial port, we need to define our own fputc().
  To use the printf() library, sending printf() output to the USB serial port, we need to define our own __io_putchar().
  I put the following in main.c, in the "Private user code" section, #0 as follows:
  I put the following in main.c, in the "Private user code" section, #0 as follows:
   
   
  /* Private user code ---------------------------------------------------------*/
  /* Private user code ---------------------------------------------------------*/
  /* USER CODE BEGIN 0 */
  /* USER CODE BEGIN 0 */
  // Defining an fputc() function allows the printf() to work as expected
#define HAL_SMALL_DELAY  40
  int fputc(int ch, FILE *f)
  // Define serial input and output functions using UART2
// Write a character to the UART with small timeout
  int __io_putchar(int ch)
  {
  {
    UNUSED(f);
    HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_SMALL_DELAY); // infinite delay value
    uint8_t c = (uint8_t)ch;
    return 1;
    HAL_StatusTypeDef status = HAL_UART_Transmit(&huart2,&c,1,50); // send character to UART2 with 50ms timeout
    if(HAL_OK == status)
        return ch;
    else
        return EOF;
  }
  }
   
   
  // For serial input - read a character from the serial port:
  // Read a character from the UART with small timeout
  int fgetc(FILE *f)
// If HAL_UART_Receive() returns error, return EOF (-1), else return the character read
  int __io_getchar(void)
  {
  {
    UNUSED(f);
    uint8_t byte;
    uint8_t c;
    HAL_StatusTypeDef hal_status = HAL_UART_Receive(&huart2, &byte, sizeof(byte), HAL_SMALL_DELAY);
    HAL_StatusTypeDef status = HAL_UART_Receive(&huart2,&c,1,50); // read character from UART2 with 50ms timeout
    if(HAL_OK == hal_status)
    if(HAL_OK == status)
    {
        return c;
        return byte;
    else
    }
        return EOF;
    else
        return EOF;
  }
  }
  /* USER CODE END 0 */
  /* USER CODE END 0 */

Revision as of 10:58, 12 April 2022

Command Line Interface

Once you have a serial interface for debug / printf() output, and have the ability to read serial characters entered from a terminal program, you're ready to implement a command line interface.
Why a Command Line Interface?

Main reason - control
With the command line interface, you can interact with different functionality you've created within your project,
testing many of your functions, and validating your recently added code.

Software components needed:

* Serial character output method - printf() support
* Serial character input method
* Line buffer editor - minimal
* Line buffer parser - split the command line up into words, with the expectation that the first word
   is a command, with any following words being command line parameters/arguments
* Command table with expected number of arguments
* Argument/word counter - argc
* Array of character pointers - argv

Serial Character Input and Output

When using many of the STM32 NEUCLEO boards, the development board will often connect one of the processor's
serial ports to the on-board STM32F103CBT6 debugger/loader/JTAG/USB-Serial interface device.  This on-board device creates
a USB-Serial interface when you connect your target processor to a host computer.  (Check your NEUCLEO board's schematic.)
To use this interface, you need to indicate, via the new project setup wizard, that you need a "connectivity" module,
usually, UART2.  Enable this UART2 for full duplex operation, 115,200 baud, 8 data bits, no parity, 1 stop bit.  When you
connect from the host side, using a terminal program, such at Tera-term, you'll connect to the STM32-STLink USB
serial port, and use the same configuration options.

Serial Output, Serial Input, and printf() Support

To use the printf() library, sending printf() output to the USB serial port, we need to define our own __io_putchar().
I put the following in main.c, in the "Private user code" section, #0 as follows:

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#define HAL_SMALL_DELAY  40
// Define serial input and output functions using UART2
// Write a character to the UART with small timeout
int __io_putchar(int ch)
{
    HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_SMALL_DELAY); // infinite delay value
    return 1;
}

// Read a character from the UART with small timeout
// If HAL_UART_Receive() returns error, return EOF (-1), else return the character read
int __io_getchar(void)
{
    uint8_t byte;
    HAL_StatusTypeDef hal_status = HAL_UART_Receive(&huart2, &byte, sizeof(byte), HAL_SMALL_DELAY);
    if(HAL_OK == hal_status)
    {
        return byte;
    }
    else
        return EOF;
}
/* USER CODE END 0 */

Line buffer editor

Define the size of your command line buffer - 64 bytes / characters is usually enough, and a character counter:

#define CL_MAX_CHARACTERS  64
char cl_buf[CL_MAX_CHARACTERS]; // global buffer to collect input from user
int cl_char_count = 0; // initial count of characters stored in buffer

Create a loop that reads serial characters from the user

As characters are received, store them in the buffer and echo them to the serial output (up to one less than CL_MAX_CHARACTERS).
If BackSpace (BS) character received, and there are characters in the buffer, back them out:
  Decrement counter
  Send BS,SPACE,BS to remove the serial character displayed earlier
Repeat until the ENTER character is pressed.  Then, terminate the string with a NULL.
The command buffer is now ready to be parsed, and then executed.

Command Line Parser

The NULL terminated string needs to be broken up into "words", such that each "word" represents a command and possible arguments.

Creating the function, int cl_parse(void);
cl_parse() will break up the command line into "words", and return the number of "words" found.
Define an argument counter, argc, and an array of character pointers, argv
#define CL_MAX_WORDS 10
int argc = 0;
char * argv[CL_MAX_WORDS];
Within a while() loop, using a character pointer
  Walk past any white space (space character or tab), when non-whitespace is found, store the address of this character into the argv list,
  replace the whitespace with NULL, and increment the argc counter.
  Repeat until the terminating NULL is reached.

If you enter the following string: "   add   100   12  ",
argc should equal 3
argv[0] should point to "add"
argv[1] should point to "100"
argv[2] should point to "12"

Command Table

Create an array of commands, each defined by a data structure, that defines the command name, command usage, expected arguments, and pointer to the function that implements the command.

typedef struct {
  char * cmd;              // name of the command
  char * comment;          // comment for command usage
  int args;                // includes the command itself and minimum number of arguments
  int (*function) (void);  // pointer to function that implements the command
} CMD_ITEM;
// Initialize the command table
CMD_ITEM cmd_array[] = {
  "?",   "display list of commands", 1, help,
  "help","display list of commands", 1, help,
  "add", "add two numbers",          3, add};