Help with MiP - Frequent wrong output from serial

Hello all.

I’ve been programming at the hobby level on and off for years but I’m new to Arduino. I’ve never programmed professionally as the code ahead will show.

I’ve physically connected Sparkfun’s Promini pack to the MiP robot by Wowwee.

SparkFun MiP ProMini-Pack
Wowwee MiP

I’m able to send commands to the MiP to make its head and chest blink and change the volume. Before I forge ahead much further I want to be able to get data out of the MiP. For now I’m just trying to implement a getVolume function.

To make things as simple for myself as I can I’ve written the following sketch. I set the volume to 2, then try to read it back.

const int8_t _UART_Select_S = 2;
const int8_t _UART_Select_R = 1;
const int8_t MIP_SET_VOLUME_CMD = 0x15;
const int8_t MIP_GET_VOLUME_CMD = 0x16;
const int8_t MIP_VOLUME_LVL = 0x02;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  pinMode(_UART_Select_S, OUTPUT);
  pinMode(_UART_Select_R, INPUT);
  uint8_t array_length = 9;
  uint8_t data_array[] = "TTM:OK\r\n";
  sendMessage(data_array, array_length);
  uint8_t set_volume[] = {MIP_SET_VOLUME_CMD, MIP_VOLUME_LVL};
  sendMessage(set_volume, 2);
  Serial.println("SETUP");
  delay(500);

}

void loop() {
  uint8_t question[] = {MIP_GET_VOLUME_CMD};
  sendMessage(question, 2);
}

void sendMessage(unsigned char *message, uint8_t array_length) {
  digitalWrite(_UART_Select_S, HIGH);
  delay(5);
  uint8_t i = 0;
  for (i; i < array_length; i++) {
    Serial.write(message[i]);
  }
  Serial.write(0x00);
  delay(5);
  digitalWrite(_UART_Select_S, LOW);
}

void serialEvent() {
  uint8_t answer[5];
  int bytes = Serial.available();
  Serial.print("Bytes read: ");
  Serial.print(bytes);
  Serial.print("\n");
  digitalWrite(_UART_Select_R, HIGH);
  for (int i = 0; i < bytes; i++) {
    answer[i] = Serial.read();
    Serial.println(answer[i], HEX);
  }
  digitalWrite(_UART_Select_R, LOW);

  // The delay makes it easier for the human to read output on the serial monitor.
  delay(3000);
}

I expect to see four bytes coming back: 31,36,30 and 32. That would be the “16” (get volume command that I asked MiP for) and “02”, as in the volume level. The above code produces the following results.

SETUP
Bytes read: 4
31
36
30
32
Bytes read: 4
31
36
30
32
Bytes read: 4
31
36
30
32
Bytes read: 4
31
36
30
E6
Bytes read: 3
31
13
92

So, you see, I’m getting the right answer, but only sometimes. I’m not sure in which direction I’m supposed to go to fix this. Am I doing something wrong? Obviously. Am I supposed to fix this with some kind of error detection/correction or is there something I can do to assure my code is getting what I expect?

As I said, I’m new to Arduino so if something looks completely ridiculous in my code, feel free to laugh out loud then correct me.

Thank you!

Try a slower baud rate like 9600.

In serialEvent you are assuming that because 1 char has arrived then all will be there. This is not true.

Mark

  uint8_t question[] = {MIP_GET_VOLUME_CMD};
  sendMessage(question, 2);

How many elements are in that array? Without even taking my shoes off, I can tell that there are not 2 elements in the array.

Thank you all for your suggestions.

ieee488:
Try a slower baud rate like 9600.

I’ve tried every rate from 300 to 115200 baud. 115200 is the only setting that produces intelligble results.

holmes4:
In serialEvent you are assuming that because 1 char has arrived then all will be there. This is not true.

Mark

I’ve also tried it without serialEvent, looping with a Serial.read for each byte available:

const int8_t _UART_Select_S = 2;
const int8_t _UART_Select_R = 1;
const int8_t MIP_SET_VOLUME_CMD = 0x15;
const int8_t MIP_GET_VOLUME_CMD = 0x16;
const int8_t MIP_VOLUME_LVL = 0x02;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  pinMode(_UART_Select_S, OUTPUT);
  pinMode(_UART_Select_R, INPUT);
  uint8_t array_length = 9;
  uint8_t data_array[] = "TTM:OK\r\n";
  sendMessage(data_array, array_length);
  uint8_t set_volume[] = {MIP_SET_VOLUME_CMD, MIP_VOLUME_LVL};
  sendMessage(set_volume, 2);
  Serial.println("SETUP");
  delay(500);

}

void loop() {
  uint8_t question[] = {MIP_GET_VOLUME_CMD};
  sendMessage(question, 1);
  uint8_t answer[5];
  int bytes = Serial.available();
  Serial.print("Bytes read: ");
  Serial.print(bytes);
  Serial.print("\n");
  digitalWrite(_UART_Select_R, HIGH);
  for (int i = 0; i < bytes; i++) {
    answer[i] = Serial.read();
    Serial.println(answer[i], HEX);
  }
  digitalWrite(_UART_Select_R, LOW);

  // The delay makes it easier to read output on the serial monitor.
  delay(3000);
}

void sendMessage(unsigned char *message, uint8_t array_length) {
  digitalWrite(_UART_Select_S, HIGH);
  delay(5);
  uint8_t i = 0;
  for (i; i < array_length; i++) {
    Serial.write(message[i]);
  }
  Serial.write(0x00);
  delay(5);
  digitalWrite(_UART_Select_S, LOW);
}

Results are really no different (that I can tell):

SETUP
Bytes read: 4
31
36
30
32
Bytes read: 3
31
13
92
Bytes read: 3
31
13
92
Bytes read: 4
31
36
30
E6

PaulS:
How many elements are in that array? Without even taking my shoes off, I can tell that there are not 2 elements in the array.

Oops. Copy/paste error. Thank you for catching that. I almost sent this thing to the moon. Unfortunately, that didn’t change my results any.

Tiogaplanet:
I've also tried it without serialEvent, looping with a Serial.read for each byte available:

The examples in Serial Input Basics may be helpful.

...R

Robin2:
The examples in Serial Input Basics may be helpful.

…R

Thank you very much and the read was indeed helpful. I edited my sketch to use both end markers and start and end markers. Then I realized that since I don’t have control (I think??) over what the MiP is sending me, I can’t inject markers into the incoming stream to trigger when my code should start and stop putting the string of characters together.

This is what my code looks like with the end markers method with a simple Serial.println(“Inside loop”); for debugging purposes.

const int8_t _UART_Select_S = 2;
const int8_t _UART_Select_R = 1;
const int8_t MIP_SET_VOLUME_CMD = 0x15;
const int8_t MIP_GET_VOLUME_CMD = 0x16;
const int8_t MIP_VOLUME_LVL = 0x02;
boolean newData = false;
boolean alreadyAsked = false;
const byte numChars = 32;
char receivedChars[numChars];

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  pinMode(_UART_Select_S, OUTPUT);
  pinMode(_UART_Select_R, INPUT);
  uint8_t array_length = 9;
  uint8_t data_array[] = "TTM:OK\r\n";
  sendMessage(data_array, array_length);
  uint8_t set_volume[] = {MIP_SET_VOLUME_CMD, MIP_VOLUME_LVL};
  sendMessage(set_volume, 2);
  Serial.println("SETUP");
  delay(500);

}

void loop() {
  uint8_t question[] = {MIP_GET_VOLUME_CMD};
  if (!alreadyAsked) {
    sendMessage(question, 1);
    alreadyAsked = true;
  }
  recvWithEndMarker();
  showNewData();
}

void recvWithEndMarker() {
  static byte ndx = 0;
  char endMarker = '\n';
  char rc;

  while (Serial.available() > 0 && newData == false) {
    Serial.println("Inside loop");
    rc = Serial.read();

    if (rc != endMarker) {
      receivedChars[ndx] = rc;
      ndx++;
      if (ndx >= numChars) {
        ndx = numChars - 1;
      }
    }
    else {
      receivedChars[ndx] = '\0'; // terminate the string
      ndx = 0;
      newData = true;
    }
  }
}

void sendMessage(unsigned char *message, uint8_t array_length) {
  digitalWrite(_UART_Select_S, HIGH);
  delay(5);
  uint8_t i = 0;
  for (i; i < array_length; i++) {
    Serial.write(message[i]);
  }
  Serial.write(0x00);
  delay(5);
  digitalWrite(_UART_Select_S, LOW);
}

void showNewData() {
  if (newData == true) {
    Serial.print("This just in ... ");
    Serial.println(receivedChars);
    newData = false;
  }
}

With output that looks like this:

SETUP
Inside loop
Inside loop
Inside loop
Inside loop

Any workarounds?

Your little message should say "Inside recvWithEndMarker" so you know exactly what is going on.

I think, however, that a message in that place is inappropriate because it will print too often.

If the data being received does not include a newline character the function showNewData() will never print anything.

Do you know how the sending system is terminating its messages?

If there is no terminating character but there is always the same number of bytes in a message you could probably get the data by checking if (Serial.available >= numBytesInMessage) but it would be a crude system with great scope for mixing things up.

...R

Robin2:
Do you know how the sending system is terminating its messages?

The messages are not being terminated. All I have to go on is knowing the header of the message, the number of bytes I expect the MiP to return for a given question and what reply values are valid. But that is a lot of error checking.

The list of MiP commands is available on Github.

MiP command list

getVolume should be simple - it's just four bytes. But I obviously should be writing this for the general case. Some replies will be longer than others.

I think the problem I have is that some of the replies are garbled. Any other suggestions?

I have had a quick look at that link. It seems that the first byte returned is the byte you sent.

My system was not designed for that but it could be adapted to it with a bit of effort.

I think I would make a 2-D array that contains the command bytes and the numbers of bytes in the replies. Then I would create a vesion of recvWithStartEndMarker() that takes the array index as a parameter and uses the data to set the start byte and the number of bytes to expect. Obviously the end byte would be irrelevant. A timeout in case something is missed would also be wise.

...R

Robin2:
I think I would make a 2-D array that contains the command bytes and the numbers of bytes in the replies. Then I would create a vesion of recvWithStartEndMarker() that takes the array index as a parameter and uses the data to set the start byte and the number of bytes to expect. Obviously the end byte would be irrelevant. A timeout in case something is missed would also be wise.

Taking your advice, I revised my code to getMessageWithByteCount() passing the buffer to store the message into and the number of bytes I expect. My getVolume() function recurses each time an error is encountered with a max retry value of 10. I haven’t yet implemented the 2-dimensional array you suggest but I will get to that next.

const int8_t _UART_Select_S = 2;
const int8_t _UART_Select_R = 1;
const int8_t MIP_SET_VOLUME_CMD = 0x15;
const int8_t MIP_GET_VOLUME_CMD = 0x16;
const int8_t MIP_VOLUME_LVL = 0x02;
const byte numChars = 32;
char receivedChars[numChars];
const int8_t MAX_RETRIES = 10;
int volume = -1;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  pinMode(_UART_Select_S, OUTPUT);
  pinMode(_UART_Select_R, INPUT);
  uint8_t array_length = 9;
  uint8_t data_array[] = "TTM:OK\r\n";
  sendMessage(data_array, array_length);
  uint8_t set_volume[] = {MIP_SET_VOLUME_CMD, MIP_VOLUME_LVL};
  sendMessage(set_volume, 2);
  Serial.println("SETUP");
  delay(500);

}

void loop() {
  volume = getVolume(MAX_RETRIES);
  if (volume == -1) {
    Serial.println("Failed getting volume");
  }
  Serial.print("Volume: ");
  Serial.print(volume);
  Serial.print('\n');
  Serial.flush();
  delay(3000);
}

int getVolume(int retryNo) {
  if (retryNo == 0)
    return -1;
  int answerVal = 0;
  uint8_t answer[4];
  uint8_t question[] = {MIP_GET_VOLUME_CMD};
  sendMessage(question, 1);
  if (Serial.available() == 4) {
    getMessageWithByteCount(answer, 4);
    answerVal = answer[3] - 48;
    if (answerVal < 0 || 7 < answerVal) {
      Serial.flush();
      return getVolume(retryNo - 1);
    }
  }
  else {
    while (Serial.available())
      Serial.read();
    return getVolume(retryNo - 1);
  }
  return answerVal;
}

void getMessageWithByteCount(unsigned char *answer, int byteCount) {
  digitalWrite(_UART_Select_R, HIGH);
  for (int i = 0; i < byteCount; i++) {
    answer[i] = Serial.read();
    //Serial.println(answer[i]);
  }
  digitalWrite(_UART_Select_R, LOW);
}

void sendMessage(unsigned char *message, uint8_t array_length) {
  digitalWrite(_UART_Select_S, HIGH);
  delay(5);
  uint8_t i = 0;
  for (i; i < array_length; i++) {
    Serial.write(message[i]);
  }
  Serial.write(0x00);
  delay(5);
  digitalWrite(_UART_Select_S, LOW);
}

I get my volume back out with occasional errors.

SETUP
Volume: 2
Volume: 2
Volume: 2
Volume: 2
Volume: 2
Volume: 2
Volume: 2
Volume: 2
Volume: 2
Volume: 2
Volume: 2
Volume: 2
Failed getting volume
Volume: -1
Volume: 2

So I guess now my question is: Is this a good way of doing business or am I nuts?

This may be a more general version of your function

void getMessageWithByteCount(unsigned char *answer, int byteCount, char expectedCmd) {
    if (Serial.available >= byteCount) {
        char firstChar = Serial.read();
        if (firstChar = expectedChar) {
            digitalWrite(_UART_Select_R, HIGH);
            for (int i = 0; i < byteCount; i++) {
                answer[i] = Serial.read();
                //Serial.println(answer[i]);
            }
            digitalWrite(_UART_Select_R, LOW);
        }
    }
}

I may have the digitalWrite()s in the wrong place - I don’t know what they are for.

I think you will receive the cmdChar plus the message (but I may be wrong)

…R