Set values to zero when no new data after certain time has arrived from Serial

I am sending three values from a Raspberry Pi via Serial (USB). I want these three values to control three different motors, the first is a servo and the second and third are DC-motors. When I send data from the Pi, it is in a string which is then split into it's parts into floats. When the Arduino sees that there is new data it sets newData = true, and once it's all parsed it is reset to newData = false. What I want to do is to make it so that if there has been no new data for the last 500 ms, then motor2 and motor3 should be set to zero, meaning that they are at a standstill. This is to protect the system in case of an interruption in the communication.

However, when i try to do as the following code at the bottom shows, then the output in the Serial Monitor looks something like:

motor1: 1
motor2: 2
motor3: 3

motor1: 4
motor2: 5
motor3: 6

motor1: 7
motor2: 8
motor3: 9

motor1: 7
motor2: 0
motor3: 0

motor1: 10
motor2: 11
motor3: 12

which is repeated and evenly spaced. So for some reason, the 0's sneak in even though the data is streamed continously 4 times per second with no interruption, which I've controlled in the terminal of the Pi. I don't quite see why it would behave like this. Also, if someone knows a solution to my issue I would gladly take it!

Code:

#include "Arduino.h"
#include <Servo.h> 

#define motor1Pin 9
#define motor2Pin 10
#define motor3Pin 6 

const byte numChars = 32;
char receivedChars[numChars];
char tempChars[numChars];      // temporary array for use when parsing

// variables to hold the parsed data
float motor1PWM = 0.0;
float motor2PWM = 0.0;
float motor3PWM = 0.0;

boolean newData = false;

Servo motor1;        // create servo object to control rudder servo
Servo motor2;
Servo motor3;
int stillPWM = 50;                // Motors are still at 50

unsigned long startMillis;          // beginning of timing
unsigned long currentMillis;
const unsigned long period = 500;        // how long one should wait before doing something

//============

void setup() 
{
  Serial.begin(9600);
  startMillis = millis();
  motor1.attach(motor1Pin);        
  motor2.attach(motor2Pin);             
  motor3.attach(motor3Pin);            
  stillPWM = map(stillPWM, 0, 100, 0, 180);    // map the PWM so we use real PWM and not "servo PWM"
  motor1.write(90);
  motor2.write(stillPWM);                    // PWM initally set to 50, which is stand-still
  motor3.write(stillPWM);
  delay(3000);                                       // delay to wait for ESC to calm down
  
}

//============

void recvWithStartEndMarkers()       // Fix str to float
{
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '<';
    char endMarker = '>';
    char rc;
   
    while (Serial.available() > 0 && newData == false) 
    {
        rc = Serial.read();
        if (recvInProgress == true) 
        {
            if (rc != endMarker) 
            {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) 
                {
                    ndx = numChars - 1;
                }
            }
            else 
            {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
                currentMillis = millis(); 
            }
        }
        else if (rc == startMarker) 
        {
            recvInProgress = true;
        }
    }
}

//============

void parseData()                              // Fix str to float, split the data into its parts
{      
    char * strtokIndx;                        // this is used by strtok() as an index
    strtokIndx = strtok(tempChars,",");       
    motor1PWM = atof(strtokIndx);           // convert this part to an float
    strtokIndx = strtok(NULL, ",");           // this continues where the previous call left off
    motor2PWM = atof(strtokIndx);                // convert this part to an float
    strtokIndx = strtok(NULL, ",");           // this continues where the previous call left off
    motor3PWM = atof(strtokIndx);                // convert this part to a float
}

//============

void showParsedData()                         // plot the received data
{
    Serial.print("motor1: ");
    Serial.println(motor1PWM);
    Serial.print("motor2: ");
    Serial.println(motor2PWM);
    Serial.print("motor3: ");
    Serial.println(motor3PWM);
}

//============

void runMotor1()                              // run the rudder servo
{
      motor1.write(motor1PWM);
}

//============

void runMotor2()                            
{
    motor2PWM = map(motor2PWM, 0, 100, 0, 180);   
    motor2.write(stillPWM+motor2PWM);            
}

//============

void runMotor3()                            
{
    motor3PWM = map(motor3PWM, 0, 100, 0, 180);   
    motor3.write(stillPWM+motor3PWM);           
}

void loop() 
{
    
    // Expect a string like 10,10,10
    recvWithStartEndMarkers();
    if (newData == true) 
    {
        strcpy(tempChars, receivedChars);      // this temporary copy is necessary to protect the original data
        parseData();
        showParsedData();                    // print the receieved data
        newData = false;                       // Reset
    }
    if (currentMillis - startMillis >= period)
    {
        motor2PWM= 0;
        motor3PWM = 0; 
        showParsedData();
        startMillis = currentMillis;
    }
    
    runMotor1();                              
    runMotor2();                            
    runMotor3();
}

EDIT:
From

if (currentMillis - startMillis >= period)
    {
        motor2PWM= 0;
        motor3PWM = 0; 
        parseData();
        startMillis = currentMillis;
    }

To

if (currentMillis - startMillis >= period)
    {
        motor2PWM= 0;
        motor3PWM = 0; 
        showParsedData();
        startMillis = currentMillis;
    }

Hello,
did you placed some Serial.println() at POI as logic analyzer to see what happens?

reset a timestamp whenever you receive a msg. if the current time exceeds that timestamp by some interval, take some action.

if there's no new data to transmit, a null msg can be sent causing the timestamp to be reset. such a (null) msg might be considered a heartbeat. !?

if (currentMillis - startMillis >= period)
    {
        motor2PWM= 0;
        motor3PWM = 0; 
        parseData();
        startMillis = currentMillis;
    }

When you call parseData() in this timed section you are working on an old copy of tempChars.

Hi,
yes I have placed a print in the if-statement at the end, everytime the 0's are printed then the code is in that statement. This is where I am lost since, according to me, that should not happen. I made an edit since I made a typo and mixed up two different functions... But now I changed the code to what it is supposed to be but I still have the same issue

Hi,
yes that was a typo where I mixed up two functions. I edited the post and my code but the issue is still the same.

Hi,
I'm not sure I follow by what you mean with a null msg and it being "considered a heartbeat". Could you please elaborate a bit more.

Hello,

You do it wrong. This line currentMillis = millis(); should be startMillis = millis();, and currentMillis = millis(); should be at the top of loop()

Then, the code to check if time has elapsed should be:

if (startMillis != 0 && currentMillis - startMillis >= period)
{
  motor2PWM= 0;
  motor3PWM = 0; 
  showParsedData();
  startMillis = 0;
}

Thank you! This solved my issue.

I will post my full code below incase someone else needs to see what worked.

#include "Arduino.h"
#include <Servo.h> 

#define motor1Pin 9
#define motor2Pin 10
#define motor3Pin 6 

const byte numChars = 32;
char receivedChars[numChars];
char tempChars[numChars];      // temporary array for use when parsing

// variables to hold the parsed data
float motor1PWM = 0.0;
float motor2PWM = 0.0;
float motor3PWM = 0.0;

boolean newData = false;

Servo motor1;        // create servo object to control rudder servo
Servo motor2;
Servo motor3;
int stillPWM = 50;                // Motors are still at 50

unsigned long startMillis;          // beginning of timing
unsigned long currentMillis;
const unsigned long period = 500;        // how long one should wait before doing something

//============

void setup() 
{
  Serial.begin(9600);
  startMillis = millis();
  motor1.attach(motor1Pin);        
  motor2.attach(motor2Pin);             
  motor3.attach(motor3Pin);            
  stillPWM = map(stillPWM, 0, 100, 0, 180);    // map the PWM so we use real PWM and not "servo PWM"
  motor1.write(90);
  motor2.write(stillPWM);                    // PWM initally set to 50, which is stand-still
  motor3.write(stillPWM);
  delay(3000);                                       // delay to wait for ESC to calm down
  
}

//============

void recvWithStartEndMarkers()       // Fix str to float
{
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '<';
    char endMarker = '>';
    char rc;
   
    while (Serial.available() > 0 && newData == false) 
    {
        rc = Serial.read();
        if (recvInProgress == true) 
        {
            if (rc != endMarker) 
            {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) 
                {
                    ndx = numChars - 1;
                }
            }
            else 
            {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
                startMillis = millis(); 
            }
        }
        else if (rc == startMarker) 
        {
            recvInProgress = true;
        }
    }
}

//============

void parseData()                              // Fix str to float, split the data into its parts
{      
    char * strtokIndx;                        // this is used by strtok() as an index
    strtokIndx = strtok(tempChars,",");       
    motor1PWM = atof(strtokIndx);           // convert this part to an float
    strtokIndx = strtok(NULL, ",");           // this continues where the previous call left off
    motor2PWM = atof(strtokIndx);                // convert this part to an float
    strtokIndx = strtok(NULL, ",");           // this continues where the previous call left off
    motor3PWM = atof(strtokIndx);                // convert this part to a float
}

//============

void showParsedData()                         // plot the received data
{
    Serial.print("motor1: ");
    Serial.println(motor1PWM);
    Serial.print("motor2: ");
    Serial.println(motor2PWM);
    Serial.print("motor3: ");
    Serial.println(motor3PWM);
}

//============

void runMotor1()                              // run the rudder servo
{
      motor1.write(motor1PWM);
}

//============

void runMotor2()                            
{
    motor2PWM = map(motor2PWM, 0, 100, 0, 180);   
    motor2.write(stillPWM+motor2PWM);            
}

//============

void runMotor3()                            
{
    motor3PWM = map(motor3PWM, 0, 100, 0, 180);   
    motor3.write(stillPWM+motor3PWM);           
}

void loop() 
{
    currentMillis = millis();
    // Expect a string like 10,10,10
    recvWithStartEndMarkers();
    if (newData == true) 
    {
        strcpy(tempChars, receivedChars);      // this temporary copy is necessary to protect the original data
        parseData();
        showParsedData();                    // print the receieved data
        newData = false;                       // Reset
    }
    if (startMillis != 0 && currentMillis - startMillis >= period)
    {
        motor2PWM= 0;
        motor3PWM = 0; 
        showParsedData();
        startMillis = 0;
    }
    
    runMotor1();                              
    runMotor2();                            
    runMotor3();
}
1 Like

see Heartbeat (computing). my point was that their don't need to be explicit heartbeat msgs sent while other msgs are present and that a heartbeat msg can be a regular msg without and data.

any received msg can reset the timeout

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.