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.

mynewapp

Any questions ? Use form below…

 

 

 

 

Leave a Reply