In a recent project I've been having a bit of trouble programming a command processor. Basically I need the Arduino to take a serial command and break it up into three components, a designator character, sub-designator integer and value, that reads as follows: "V1 0.025" or "V2 100".
Once broken up, these values will then need to pass through a nested switch-case to in turn call a function with the value present in the above command.
Commands V1-V4 & G1 require float values, as they will be used for on the fly PID tuning, while values V5-V8 require integers. Commands that don't require a value are: G2,G3, M1-M4.
I've definitely though of using if-else statements, however, performance is absolutely critical in this application.
Any help would be appreciated, safe to say this is driving me insane!
Sorry, I should have clarified a bit better.
Yes, a person will be sending commands via a serial terminal, but I'm also executing a PID control loop in the background to drive a motor.
Code can be found below. The command processor section was modified from a forum post on a different website, unfortunately I can't find exactly where.
*Edit, fixed typo in code.
#define BUF_LEN 6
void handleSerialDebug(){
static char sdata[BUF_LEN], *pSdata=sdata;
char ch;
if(Serial.available()){
ch = Serial.read();
*pSdata = (char)ch;
*sdata = (char)ch;
pSdata--; //Dont add \r to string
//pSdata = sdata;
char Com1 = sdata[0];
int Com2 = atoi(sdata[1]);
*pSdata = '\0'; //Null Terminate String
Serial.print(Com1);
Serial.println(Com2);
switch(Com1){
case 'G': //G Commands
switch(Com2){
case 1: //G1 Traverse
break;
case 2: //G2 Home
RunMode = 1;
break;
case 3: //G3 Print RS485
break;
}
break;
case 'V': //V Commands
switch(Com2){
case 1: //V1 Set P
break;
case 2: //V2 Set I
break;
case 3: //V3 Set D
break;
case 4: //V4 Set Deadzone
break;
case 5: //V5 Set EndStop upper
break;
case 6: //V6 PID profile
break;
case 7: //V7 Assigns Controller ID
break;
}
break;
case 'M':
switch(Com2){
case 1: //V1 Default Settings
Serial.print("Default Settings Written");
EEPROMWriteDefault();
break;
case 2: //V2 Save Settings
Serial.print("Settings Saved");
EEPROMSaveSettings();
break;
case 3: //V3 Print Settings
EEPROMPrintSettings();
break;
case 4: //V4 Clear Error Codes
break;
}
break;
default:
Serial.println("Invalid Command");
break;
}
pSdata = sdata;
}
}
Then you need to grab one single character at a time, if it available, until you get to the end of a command. I assume you have something to tell you that, like a "cr" or "lf". When you get the whole command in YOUR buffer, then process it.
This lets your other processing continue until you have to stop and take care of the command. You can't continue processing because the new command will alter the parameters of the other processing.
Please post a complete program that compiles. Given that you've called what you posted insanity code, and only also showing a loop function leaves too much to do out here without the possibility of us doing something wrong trying to figure it out.
Post it in a fresh addition to this thread if you do.
So, it looks like everything you need is in the sdata character array. The main thing I see missing is then converting the numeric value in the array into either an Integer or a Float variable.
You could use something like this...
char inputA[] = "123";
char inputB[] = "789.456";
void setup()
{
Serial.begin(115200);
Serial.println(inputA);
Serial.println(convertToInt(inputA, sizeof(inputA)));
Serial.println(inputB);
Serial.println(convertToFloat(inputB, sizeof(inputB)),4);
}
void loop()
{
}
int convertToInt(char *input, uint8_t len)
{
int numberA = 0;
for (uint8_t x = 0; x < len - 1; x++)
{
numberA = numberA * 10 + input[x] - 48;
}
return numberA;
}
float convertToFloat(char *input, uint8_t len)
{
float numberB = 0.0;
uint8_t dp = 0;
for (uint8_t x = 0; x < len - 1; x++)
{
if (input[x] == '.')
dp = 1;
else if (dp == 0)
numberB = numberB * 10 + input[x] - 48;
else
numberB = numberB + (input[x] - 48) * pow(0.1,dp++);
}
return numberB;
}
Sorry for wasting your time but it might be easier to start from scratch.
I tried to run a basic version of the code in a separate IDE window, which compiled but wouldn't send anything back to the computer through serial, please see link attached.
There's no change between anything in the snippet and the code required in the main program.
Here is my command processor (it will not work directly w/o other code, e.g. UART, MEM_Pool,
but easy to provide the interfaces).
Features:
it parses commands line: [-OPTION] [par0 [par1 ...]]
it allows several commands on a single line, separated via semicolon ; as well as commented lines via hash #
Examples (for syntax):
help
debug 0x3 #set my debug flags
spitr 0 1 2 3 4 5 6 7 8 #N numbers of values
spitr -A 0x1 0x2 0x3 0x4 0x5 0x6 #values as decimal or hex
print AAAA BBBB CCCC #even strings or rest of line
It parses for CMDKEYWORD, can have (optional) OPTION (with -), a string parameter (rest of line), up to N numbers of values (where omitted values at the end are taken as zero)
It has a command definition table with help text and function pointers to the command handler.
help
displays all commands with short help for each
help
displays the second longer command help text (just for this CMD)
In order to make it to work:
call the function *ECMD_DEC_Status CMD_DEC_execute(char cmd, EResultOut out)
with a NUL terminated string (it can have a "\r", "\n" or both at the end and even neither of them,
OUT is just an enum where to send the response (for me: UART of HTTP request or SILENT
it needs a UART_Send() function for the output
This MEM_PoolAlloc(sizeof(TCMD_DEC_Results)); can be used as a single pointer to memory.
I have it there and use it when this command interpreter is called from different parallel
threads (multi-tasking, e.g. UART and from HTTP Web Server, concurrent).
Function is like malloc(). But for single use: use a pointer to a memory.
Have fun.
(I am using it for long time for many FW projects, I think, quite flexible).
help or help CMD
It is thread-save (multi-tasking) with supporting the MEM_Pool functions
(I avoid to use malloc, MEM_Pool is like malloc but with fix sized chunks and much faster,
w/o risk of memory fragmentation)
give it a try. Not very complicated.
Just see which external functions are needed (at least UART_Tx, other functions can be empty or very simple, e.g. this MEM_Pool stuff).
The "beauty": if you get it compile clean, it is very easy to add a new command, help and implementation for it.
Maybe I have commands already there which you do not need or hard to compile (missing stuff).
Just let the "help" command there, remove all other stuff, bring it to work and then add you functions.
You will easily understand how to add a new command:
define a prototype for the command handler (all have the same syntax, parameters etc.)
create a new entry in the command table, where keyword, help and the pointer to the command handler is defined
implement the command handler: parameters on call give you full access to all parts from the command line, e.g. res->val[0] ... for the parsed values (just see the code there)