Need advice to improve UART command parser logic

zazas321
Posts: 231
Joined: Mon Feb 01, 2021 9:41 am

Need advice to improve UART command parser logic

Postby zazas321 » Fri May 26, 2023 10:41 am

Hey. Every time I work on a project the first thing I do is write a simple debug UART parser. This allows me to send/receive commands via any Serial terminal software such as termite,mobaxterm and etc. This is very helpful for testing different things on your device.

I have been using the following approach for a long time:

In my UART.c component:

Code: Select all

void UART0_setup() {
    uart_config_t uart_config = {
        .baud_rate           = 115200,
        .data_bits           = UART_DATA_8_BITS,
        .parity              = UART_PARITY_DISABLE,
        .stop_bits           = UART_STOP_BITS_1,
        .flow_ctrl           = UART_HW_FLOWCTRL_DISABLE,
        .source_clk          = UART_SCLK_DEFAULT,

    };
    ESP_ERROR_CHECK(uart_param_config(UART_NUM_0, &uart_config));
    ESP_ERROR_CHECK(uart_driver_install(UART_NUM_0, UART0_COMMAND_LINE_MAX_SIZE, 0, 0, NULL, 0));
}


void UART0_task(void *argument)
{
	UART0_setup();
  	unsigned int char_received=EOF;
  	unsigned int char_counter=0;
  	char command_line[UART0_COMMAND_LINE_MAX_SIZE];
  	for (;;)
	{	
		int len = uart_read_bytes(UART_NUM_0, command_line, (UART0_COMMAND_LINE_MAX_SIZE - 1), 20 / portTICK_PERIOD_MS);
        if (len) {
            command_line[len] = 0;
			ParseSystemCmd(command_line, len); // Line is complete. Execute it!
			memset(&command_line, 0, sizeof(command_line));
        }
		vTaskDelay(10/portTICK_PERIOD_MS);

  }

}

bool ParseSystemCmd(char *line, uint16_t cmd_size)
{

    if (!strncmp("ping", line,4))
    {	
            printf("pong\n");
            return true;
    }
    
    if (!strncmp("command1", line,8))
    {	
            printf("command 1 response\n");
            return true;
    }
    
    return 0;
}


And in my main.c I simply create a task for UART0:

Code: Select all

xTaskCreate(UART0_task,"UART0_task",10000,NULL,5,NULL); // receiving commands from main uart



The method for parsing UART0 command shown above will work just fine, however, I dont think its very convenient.



I have come up with a little different approach:

In my UART.h:

Code: Select all

#define CMD(_cmd)                          _cmd, sizeof(_cmd) - 1
The CMD macro allows me to pass only 1 parameter that contains command and length. This is simply more clean. Alternative would be to pass command and length of command in 2 seperate variables.

In my UART.c:

Code: Select all


struct commandsTableDesc_s{
	char*		command;
	uint16_t 		length;
	void			(*function_pointer)();
};

struct commandsTableDesc_s CMD_table[3] = {
	{ CMD("command1"),&test_function1},
	{ CMD("command2"),&test_function2},
        { CMD("command3"),&test_function3},
};


void test_function1(){
	printf("command1 response \n");
}

void test_function2(){
	printf("command2 response \n");
}

void test_function3(){
	printf("command3 response \n");
}


void UART0_setup() {
    uart_config_t uart_config = {
        .baud_rate           = 115200,
        .data_bits           = UART_DATA_8_BITS,
        .parity              = UART_PARITY_DISABLE,
        .stop_bits           = UART_STOP_BITS_1,
        .flow_ctrl           = UART_HW_FLOWCTRL_DISABLE,
        .source_clk          = UART_SCLK_DEFAULT,

    };
    ESP_ERROR_CHECK(uart_param_config(UART_NUM_0, &uart_config));
    ESP_ERROR_CHECK(uart_driver_install(UART_NUM_0, UART0_COMMAND_LINE_MAX_SIZE, 0, 0, NULL, 0));
}

void UART0_task(void *argument)
{
	UART0_setup();
  	char command_line[UART0_COMMAND_LINE_MAX_SIZE];
  	for (;;)
	{	
		int len = uart_read_bytes(UART_NUM_0, command_line, (UART0_COMMAND_LINE_MAX_SIZE - 1), 20 / portTICK_PERIOD_MS);
        if (len) {
            command_line[len] = 0;
			for(uint8_t i=0; i < (sizeof(CMD_table)/sizeof(CMD_table[0])) ; i++) {
				if(strncmp(command_line, CMD_table[i].command, CMD_table[i].length) == 0) {
					printf("valid command detected = %s \n",command_line);
					execute_command(CMD_table[i].function_pointer);
				}
			}
        }
        vTaskDelay(10/portTICK_PERIOD_MS);
    }
}



As you can see, in this method, I have a structure of commands that contains command, length and function pointer that must be executed if a known command is received.


This example seems to be working fine, however, there is one problem that I cannot wrap my head arround:



If a function that needs to be executed does not contain any parameters such as test_function1, test_function2 and test_function3, everything will be okay. However, I might want to use some functions that contain one or more parameters, for example:

Code: Select all

void control_motor(uint8_t speed, uint8_t time);
or

Code: Select all

void random_function(bool parameter1, bool parameter2, bool parameter3);
How can I modify my UART parser to accept any function pointer regardless of how many parameters it contains?

MicroController
Posts: 1183
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Need advice to improve UART command parser logic

Postby MicroController » Fri May 26, 2023 12:01 pm

(Using strcmp() may be more convenient than strncmp().)
How can I modify my UART parser to accept any function pointer regardless of how many parameters it contains?
The common approach is to have your functions accept a pointer to where they can get their respective arguments (0 or more) from. This can be just the pointer into the input string for each function to parse arguments as needed, or maybe to a pre-split argument array like the argv[] argument to a standard C main(...) function.
The point is that you want a single generic function signature which a generic command parser can call without having to know or deal with the specifics of different functions.

zazas321
Posts: 231
Joined: Mon Feb 01, 2021 9:41 am

Re: Need advice to improve UART command parser logic

Postby zazas321 » Fri May 26, 2023 2:15 pm

MicroController wrote:
Fri May 26, 2023 12:01 pm
(Using strcmp() may be more convenient than strncmp().)
How can I modify my UART parser to accept any function pointer regardless of how many parameters it contains?
The common approach is to have your functions accept a pointer to where they can get their respective arguments (0 or more) from. This can be just the pointer into the input string for each function to parse arguments as needed, or maybe to a pre-split argument array like the argv[] argument to a standard C main(...) function.
The point is that you want a single generic function signature which a generic command parser can call without having to know or deal with the specifics of different functions.
Thanks for the reply. Would you be able to provide a simple example because im not getting it fully. Thanks in advance!

MicroController
Posts: 1183
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Need advice to improve UART command parser logic

Postby MicroController » Fri May 26, 2023 2:30 pm

Maybe see if you can just use the console component.

Who is online

Users browsing this forum: GooseGoose, Majestic-12 [Bot] and 81 guests