STM32F4 Discovery virtual com port – part III – COmmand line interpreter
After successful installation of “bare” virtual com port it’s now time to add something useful to the communication. This example is based on tutorial projects :
1. STM32F4 discovery – Keil example step-by-step
2. STM32F4 discovery – Virtual COM port, step-by-step
3. STM32F4 Discovery virtual com port – part II
If you followed first three parts, then it’s time to continue here. If not, I suggest you do to gain proper background and learn about creating virtual COM port application.
For command line interpreter, we first need command line. It is generated with line editor in function void getline (char *line, int n), see previous post. Let’s start with some definitions for our commands. We will add two commands. First is “LED” and second is “KEY”. We need command identifiers for simpler identification and more readable code:
// Command identifiers enum { CMD_LED, CMD_KEY, // Add more commands here }; |
Next, we need a table with all commands listed. First define new structure, next store table in flash (“const”):
// command table struct cmd_st { const char *cmdstr; int id; }; const struct cmd_st main_cmd_tbl [] = { { "LED", CMD_LED }, { "L", CMD_LED }, { "LEDS", CMD_LED }, { "KEY", CMD_KEY }, }; #define CMD_UNKNOWN -1 |
Final line defines the “unknown” command – if our typed command don’t match any known command in the list. Note that we defined two synonims for command “LED”: “L” and “LEDS”. They have both same command identifier CMD_LED.
Next is function, which searches the above command line table cmd_tbl for a specific command and returns the ID associated with that command or CMD_UNKNOWN if there is no matching command.
static int cmdid_search ( char *cmdstr, const struct cmd_st *cmd_tbl, int tbl_len) { const struct cmd_st *ctp; for (ctp = cmd_tbl; ctp < &cmd_tbl [tbl_len]; ctp++) { if (strcmp (ctp->cmdstr, cmdstr) == 0) return (ctp->id); } return (CMD_UNKNOWN); } |
There is also a function which returns upper-case string. Our commands are case insensitive. The Command list table has upper case strings and commend line is converted to upper case before command is searched. The function to convert complete null-terminated string is:
char *strupr ( char *src) { char *s; for (s = src; *s != '\0'; s++) *s = toupper (*s); return (src); } |
And here’s the most important function of the command line interpreter, the processing of the command line. First, the command line is separated to command and arguments, next the command is compared to known commands and finally the individual commands are called based on found command identifier.
/** * Function: void cmd_proc (const char *cmd) * This function processes the *cmd command. **/ void cmd_proc (char *cmd) { char *argsep; int id; static char cmdstr_buf [1 + CMDLEN]; static char argstr_buf [1 + CMDLEN]; //First, copy the command and convert it to all uppercase. strncpy (cmdstr_buf, cmd, sizeof (cmdstr_buf) - 1); cmdstr_buf [sizeof (cmdstr_buf) - 1] = '\0'; strupr (cmdstr_buf); // Next, find the end of the first thing in the buffer. // Since the command ends with a space, we'll look for that. // NULL-Terminate the command and keep a pointer to the arguments. argsep = strchr (cmdstr_buf, ' '); // Cleanup if (argsep == NULL) { argstr_buf [0] = '\0'; } else { strcpy (argstr_buf, argsep + 1); *argsep = '\0'; } // Search for a command ID, then switch on it. //Each function invoked here. id = cmdid_search (cmdstr_buf, &main_cmd_tbl[0], MAIN_CMD_TBL_LEN); switch (id) { case CMD_LED: cmd_led(argstr_buf); break; case CMD_KEY: cmd_key(argstr_buf); break; /* Invalid command */ case CMD_UNKNOWN: // print info usb_prints("? Unknown command!\n\r"); break; } } |
Each individual command may have syntax, which is similar to main command. In our case we would like to control LEDs with following command: LED Color State. Color is one of the LED colors on STM32F4 demo board, Since blue LED is blinking from the example STM32F4 discovery – Keil example step-by-step we will add RED, ORANGE and GREEN. The state is either 0 or 1.
// LED Command sub-identifiers enum { CMD_LED_RED, CMD_LED_GREEN, CMD_LED_ORANGE }; /* Sub level commands strings - match command with command ID */ const struct cmd_st led_cmd_tbl [] = { { "RED", CMD_LED_RED }, { "R", CMD_LED_RED }, { "GREEN", CMD_LED_GREEN }, { "G", CMD_LED_GREEN }, { "ORANGE", CMD_LED_ORANGE }, { "O", CMD_LED_ORANGE }, }; #define LED_CMD_TBL_LEN (sizeof (led_cmd_tbl) / sizeof (led_cmd_tbl [0])) |
The cmd_led function accepts argument string, which is processed exactly the same way as main command. We can nest the sub-command arguments as deep as needed for a given command. It is not practical to go deeper than two levels using presented algorithm.
void cmd_led(char *argstr_buf) { char *argsep; int id; char argstrled_buf [1 + CMDLEN]; argsep = strchr (argstr_buf, ' '); if (argsep == NULL) { argstrled_buf [0] = '\0'; } else { strcpy (argstrled_buf, argsep + 1); *argsep = '\0'; } // Search for a command ID, then switch on it. //Each function invoked here. id = cmdid_search (argstr_buf, &led_cmd_tbl[0], LED_CMD_TBL_LEN); switch (id) { case CMD_LED_RED: // switch Red LED switch (argstrled_buf[0]) { case '0' : LED_Off(LED_RED); break; case '1' : LED_On(LED_RED); break; default : usb_prints("? Syntax error, invalid argument in \"LED RED\" command!\n\r"); } break; case CMD_LED_GREEN: // switch green LED switch (argstrled_buf[0]) { case '0' : LED_Off(LED_GREEN); break; case '1' : LED_On(LED_GREEN); break; default : usb_prints("? Syntax error, invalid argument in \"LED GREEN\" command!\n\r"); } break; case CMD_LED_ORANGE: // switch green LED switch (argstrled_buf[0]) { case '0' : LED_Off(LED_ORANGE); break; case '1' : LED_On(LED_ORANGE); break; default : usb_prints("? Syntax error, invalid argument in \"LED ORANGE\" command!\n\r"); } break; /* Invalid command */ case CMD_UNKNOWN: // print info usb_prints("? Syntax error, invalid argument in \"LED\" command!\n\r"); break; } } |
We have to modify the command line thread. After command line editor is added call to to the command line processor. I also modified LED driver. Now the functions have additional argument representing which LED is switched on or off. In LED.H are four defines for LED colors.
File LED.C
/*------------------------------------------------------------------------ * File LED.c *----------------------------------------------------------------------*/ #include "stm32f4xx.h" // Device header #include "cmsis_os.h" // RTOS:Keil RTX header #include "LED.H" void blink_LED (void const *argument); // Prototype function osThreadDef (blink_LED, osPriorityNormal, 1, 0); // Define blinky thread // Initialize GPIO Port void LED_Initialize (void) { RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; // Enable Port D clock GPIOD->MODER |= GPIO_MODER_MODER15_0; // Port D.15 output GPIOD->MODER |= GPIO_MODER_MODER14_0; // Port D.15 output GPIOD->MODER |= GPIO_MODER_MODER13_0; // Port D.15 output GPIOD->MODER |= GPIO_MODER_MODER12_0; // Port D.15 output } /* Turn LED on */ void LED_On (unsigned int led) { if (led<4) GPIOD->BSRRL = (1<<(led+12)); // LED on: set Port } /* Turn LED off */ void LED_Off (unsigned int led) { if (led<4) GPIOD->BSRRH = (1<<(led+12)); // LED off: clear Port } // Blink LED function void blink_LED(void const *argument) { for (;;) { LED_On (LED_BLUE); // Switch blue LED on osDelay (500); // Delay 500 ms LED_Off (LED_BLUE); // Switch blue off osDelay (500); // Delay 500 ms } } void Init_BlinkyThread (void) { osThreadCreate (osThread(blink_LED), NULL); // Create thread } |
File LED.H
/*------------------------------------------------------------------------ * File LED.h *----------------------------------------------------------------------*/ void LED_Initialize ( void ); // Initialize GPIO void LED_On ( unsigned int led ); // Switch Pin on void LED_Off ( unsigned int led ); // Switch Pin off void blink_LED ( void const *argument ); // Blink LEDs in a thread void Init_BlinkyThread ( void ); // Initialize thread #define LED_GREEN 0 #define LED_ORANGE 1 #define LED_RED 2 #define LED_BLUE 3 |
and updated thread in command.c:
// Command processor function void process_cmd(void const *argument) { #define CMDLEN 40 // Max. command length char cmd[CMDLEN]; for (;;) { USBD_CDC_ACM_PutChar (0, '>'); getline(cmd, CMDLEN); cmd_proc(cmd); } } |
Finally, here’s complete source code for this project. Have fun playing with it.
Any questions ? Use form below…