2

I'm looking to basically call functions based on the serial ID I assign to it. For example, if I want to call function A I would type on the serial A and if I want to call function B then I would type B.

Now I have taken a look at some arduino.cc ASCII and other lookup tables but I'm still not getting my results. YES I can indeed make up serial calls 1 through 9 using char datatype but as you can see I'm only limited to 9 function calls and so this is where I'm looking for tips. How can I expand this serial ID as I'm calling it?

char id;
int led = 6; // the PWM pin the LED is attached to

void setup() {
  Serial.begin(9600);
  pinMode(led, OUTPUT);
}

void loop() {
  id = Serial.read();
  /*
    will only read the 1st bit on the buffer so
    no 10s...90s because the whole char so we
    need to use Strings datatype somehow but
    the thing is that the Serial.Read func only
    reads the first byte. R&D?
  */
  if (id == '1') {
    digitalWrite(led, HIGH);
  } else if (id == '2') {
    digitalWrite(led, LOW);
  }
}

So in my above example I'm using char to communicate. I know there are other datatypes but not sure which is the best suited for the 100's of functions I would like to call via terminal.

I thought about using the alphabet A-Z as well as a-z to double the function calls but I would like to come up with something a bit more formal rather than single numbers or single letters (char).

7
  • 1
  • A char can be 256 different values. However, data is read in one byte at a time. You may want to develop your own communication protocol to send multiple bytes. It doesn't even have to be complicated: just read x bytes, and then stop.
    – uhours
    Aug 19, 2017 at 19:58
  • 1
    @uhours are you suggesting a for loop in the conditionals?
    – Frank
    Aug 19, 2017 at 20:00
  • 1
    @Frank, A for loop before the conditionals, reading x bytes into an array. Exit the loop when the array is full. Use strcmp() to evaluate the contents. Also, indent your code.
    – uhours
    Aug 19, 2017 at 20:05
  • 1
    @uhours thanks for the advise, I will read mute on that function you mentioned as I have never used it before.
    – Frank
    Aug 19, 2017 at 20:19

2 Answers 2

2

First, you could use a string for the commands. You just need to ensure there is some special char to mark the end of a command - like the newline char. Read the input from the port one char at a time, building the string as you go:

char buffer[256];
uint16_t buffer_index = 0;
void loop() {
  while (Serial.available()) {
    int c = Serial.read();
    if ('\n' == c) {
      buffer[buffer_index] = 0;   // Append EOS
      // Complete message - do something
      buffer_index = 0;   // Reset, ready for next message
    }
    else {
      // Append char to end of buffer
      buffer[buffer_index++] = c;
    }
    // Check for overflow
    if (256 == buffer_index) {
        buffer_index = 0;
    }
  }
}

There are plenty of examples like this on this site.

Next, I don't recommend using an if statement with a hundred or more conditions. Instead, you can store pointers to each function in an array:

// Use a typedef to simplfy
typedef void (*function_type)(); // A function with no params and no return, change as needed
// Fixed array size
const int max_functions = 200;
// Create an array of 200 function pointers
function_type all_functions[max_functions] = {0};

So to call function #11:

all_functions[10]();

Now you just need a way to marry the function pointers to a name. A hash table could work here. A hash function converts a string to a number. There are lots of examples on the web. Here's one:

uint16_t djb2_hash(const char *str)
{
    unsigned long hash = 5381;
    int c;

    while (0 != (c = *str++)) {
        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
    }

    // Note: we use the mod operator to make sure value
    // is less than array size
    return hash % max_functions;
}

Create two functions, one to add a function and one to retreive:

bool add_function(const char *name, function_type f)
{
  // Convert name to number
  uint16_t index = djb2_hash(name);  //  0 <= index <= max_functions 
  if (NULL != all_functions[index]) {
    // There is already a function stored at this index
    Serial.print("Adding ");
    Serial.print(name);
    Serial.print("failed. index = ");
    Serial.println(index);
    return false;
  }
  else {
    // Store the function in the array
    all_functions[index] = f;
    return true;
  }
}
bool run_function(const char *name)
{
  uint16_t index = djb2_hash(name);
  if (NULL == all_functions[index]) {
    // Name not found
    return false;
  }
  else {
    // Call the function
    all_functions[index]();
    return true;
  }
}

Last, create some functions and add to hash table:

void f1() {Serial.println("F1");}
void f2() {Serial.println("F2");}
void f3() {Serial.println("F3");}

void setup() {
  Serial.begin(9600);
  add_function("F1", f1);
  add_function("F2", f2);
  add_function("F3", f3);
}

All that's left is to modify loop() to call the functions when a complete message is received.

void loop() {
  while (Serial.available()) {
    int c = Serial.read();
    if ('\n' == c) {
      buffer[buffer_index] = 0;
      run_function(buffer);
      buffer_index = 0;
    }
    else {
      buffer[buffer_index++] = c;
    }
    // Check for overflow
    if (256 == buffer_index) {
        buffer_index = 0;
    }
  }
}

If you enter "F1", "F2", or "F3" in the serial monitor, those functions should execute. Just make sure you change the serial monitor to append a newline.

Some notes: the hash function may not create a unique id for each string. You need to handle this situation. Also, the names are case sensitive. If you don't want that, convert everything to all upper case or all lower.

2
  • Your code works great, but, What can I do to call a Funct with parameters void funct_X(char letterX, int numberX); ...... ?
    – codeMaster
    May 15, 2019 at 13:07
  • @NavasEma You could change the typedef to typedef void (*function_type)(char,int);. And the run function to all_functions[index](arg1, arg2);
    – 001
    May 15, 2019 at 16:16
0

a state machine would be the most efficient approach here.

first, you need to process each valid input - making sure that it is between 0..9. then you need to form a 2-digit number with that. for that to work, you will need to be able to tell when the input ends - lots of ways to do that.

the heart of the code would be something like this:

current_input = valid_input();  //obtain valid input
input = cmd_2dig(current_input); //2-dig commands
switch (cmd_2dig) {
  case 0: do_cmd0(); break;
  case 1: do_cmd1(); break;
  case ....
  default: reset_cmd(); //invalid command, reset command buffer
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.