3

I'm using an Arduino Leonardo and need to perform two time-sensitive tasks like this:

// Task 1 - Gets executed once a second (1Hz)
// Execution of task needs approx 70ms
   task1(); //Sending a message via Serial

// Task 2 - Gets executed 50 times a second (50Hz)
// Execution of task need a few micros
   task2(); //digitalWrite

As you can see, the problem is, that task one needs very long for execution and therefore task2 is executed too late to send a 50Hz signal. I'm not sure how, but I think there must be a way to give task 1 higher priority to interrupt task 2?

Thanks in advance! :)

Just for completion, here is my (not so clean) code:

#include <SoftwareSerial.h>
SoftwareSerial mySerial(7, 6); //232_TX,232_

int triggerFreq = 10;
unsigned long timestamp_pps;
unsigned long timestamp;
unsigned long triggerStartTime;
const unsigned long dt_pps = 1000000; //micros
const unsigned long dt_trig = 1000000/triggerFreq; //micros
unsigned long ts_pps_high;
unsigned long ts_pps_low;
unsigned long ts_msg_high;
unsigned long ts_msg_low;


unsigned long i_pps = 0;
unsigned long i_trig = 0;

int hh = 04;
int mm = 51;
int ss = 00;
unsigned int pos = 2307;
bool msg;
bool trigMsgSent;


void setup() {
  // put your setup code here, to run once:
  pinMode(11, OUTPUT); // PPS
  pinMode(12, OUTPUT); // Indicator of msg_sent
  pinMode(13, OUTPUT); // FPS
  Serial.begin(9600);
  mySerial.begin(9600);
  triggerStartTime = micros();
}

void loop() {
  // ================== Part for the GPS Simulation ==================
  timestamp_pps = micros()-triggerStartTime;
  if (timestamp_pps>= dt_pps*i_pps+350000)
  {
    pos += 1;
    digitalWrite(12,HIGH);
    i_pps +=1;
    if (ss == 59)
    {
      ss=0;
      if (mm==59)
      {
        mm=0;
        hh+=1;
      }
      else mm+=1;
    }
    else ss+=1;

    ts_msg_high = micros();
    mySerial.print("$GPRMC,");
    if (hh<10) mySerial.print(F("0"));
    mySerial.print(hh);
    if (mm<10) mySerial.print(F("0"));
    mySerial.print(mm);
    if (ss<10) mySerial.print(F("0"));
    mySerial.print(ss);
    mySerial.print(".000,A,3015.");
    mySerial.print(pos);
    mySerial.println(",N,09749.2872,W,0.67,161.46,030913,,,A*7C");

    // Print Debug Messages
    digitalWrite(12,LOW);
    ts_msg_low=micros();
    Serial.print("PPS at: ");
    Serial.print(ts_pps_high);
    Serial.print("\t PPS low after: ");
    Serial.print(ts_pps_low-ts_pps_high);
    Serial.print("\t MSG high after: ");
    Serial.print(ts_msg_high-ts_pps_high);
    Serial.print("\t MSG low after: ");
    Serial.print(ts_msg_low-ts_pps_high);
    Serial.print("$GPRMC,");
    if (hh<10) Serial.print(F("0"));
    Serial.print(hh);
    if (mm<10) Serial.print(F("0"));
    Serial.print(mm);
    if (ss<10) Serial.print(F("0"));
    Serial.print(ss);
    Serial.print(".000,A,3015.");
    Serial.print(pos);
    Serial.println(",N,09749.2872,W,0.67,161.46,030913,,,A*7C");
  }
  else if (timestamp_pps>=dt_pps*i_pps+100000)
  {
    digitalWrite(11,LOW);
    if (msg==true)
    {
      msg = false; 
      ts_pps_low = micros();
    }

  }
  else if (timestamp_pps>=dt_pps*i_pps)
  {
    digitalWrite(11,HIGH);  
    if (msg==false)
    {
      msg = true;
      ts_pps_high = micros();
    }
  }
  // ================== Part for the FPS Trigger ==================
  timestamp = micros()-triggerStartTime;
  if (timestamp>= dt_trig*i_trig+2000)
  {
    digitalWrite(13,LOW);
    i_trig +=1;
    trigMsgSent = false;
  }
  else if (timestamp >=dt_trig*i_trig)
  {
    digitalWrite(13,HIGH);
    if (trigMsgSent == false)
    {
      Serial.print("FPS at: ");
      Serial.println(micros());
      trigMsgSent = true;
    }
  }

}

Update: So i implemented it with an interrupt according to this example. I just programmed the interrupt for the 50Hz signal, because that one must be accurate. The code is at the end. However, I'm still experiencing the 50Hz signal to be late when the serial is sent. It's now in the dimensions of half a millisecond, but that's not negligible in my project (Sensor fusion computer vision). The output of my post-processing-tool shows:

Trigger Nr. 25 is 646.912us late!
Trigger Nr. 28 is 434.176us late!
Trigger Nr. 75 is 653.056us late!
Trigger Nr. 78 is 429.056us late!
Trigger Nr. 125 is 646.912us late!
Trigger Nr. 128 is 434.944us late!
Trigger Nr. 175 is 646.912us late!
Trigger Nr. 178 is 439.04us late!
Trigger Nr. 225 is 646.912us late!
Trigger Nr. 228 is 440.064us late!
Trigger Nr. 275 is 647.168us late!
Trigger Nr. 278 is 434.944us late!
Trigger Nr. 325 is 611.84us late!
Trigger Nr. 328 is 462.08us late!
Trigger Nr. 375 is 612.864us late!
Trigger Nr. 378 is 475.136us late!
Trigger Nr. 425 is 619.008us late!
Trigger Nr. 428 is 473.856us late!
Trigger Nr. 475 is 613.888us late!
Trigger Nr. 478 is 467.968us late!
Trigger Nr. 525 is 612.864us late!
Trigger Nr. 528 is 474.88us late!
Trigger Nr. 575 is 613.12us late!

Does anyone know how I could improve my code?

Code:

//GPS DEFINITIONS
#include <SoftwareSerial.h>
SoftwareSerial mySerial(7, 6); //232_TX,232_

int triggerFreq = 50;
unsigned long timestamp_pps;
//unsigned long timestamp;
unsigned long triggerStartTime;
const unsigned long dt_pps = 1000000; //micros
//const unsigned long dt_trig = 1000000/triggerFreq; //micros
unsigned long ts_pps_high;
unsigned long ts_pps_low;
unsigned long ts_msg_high;
unsigned long ts_msg_low;


unsigned long i_pps = 0;
//unsigned long i_trig = 0;

int hh = 04;
int mm = 51;
int ss = 00;
unsigned int pos = 2307;
bool msg;
bool trigMsgSent;


void setup() {

  cli();//stop interrupts
  //set timer1 interrupt at 1Hz
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 1hz increments
  //OCR1A = 15624;// = (16*10^6) / (1*1024) - 1 (must be <65536)
  OCR1A = 19999; // = (16*10^6) / (100 * 8) -1 (must be <65536)
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS11 bit for 8 prescaler
  TCCR1B |= (1 << CS11);
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);
  sei();//allow interrupts

  // GPS SETUP 
  pinMode(11, OUTPUT); // PPS
  pinMode(12, OUTPUT); // Indicator of msg_sent
  pinMode(13, OUTPUT); // FPS
  Serial.begin(9600);
  mySerial.begin(9600);
  triggerStartTime = micros();

}

ISR(TIMER1_COMPA_vect){  //change the 0 to 1 for timer1 and 2 for timer2
   //interrupt commands here
   digitalWrite(13,!digitalRead(13));
}

void loop() {
  // ================== Part for the GPS Simulation ==================
  timestamp_pps = micros()-triggerStartTime;
  if (timestamp_pps>= dt_pps*i_pps+350000)
  {
    pos += 1;
    digitalWrite(12,HIGH);
    i_pps +=1;
    if (ss == 59)
    {
      ss=0;
      if (mm==59)
      {
        mm=0;
        hh+=1;
      }
      else mm+=1;
    }
    else ss+=1;

    ts_msg_high = micros();
    mySerial.print("$GPRMC,");
    if (hh<10) mySerial.print(F("0"));
    mySerial.print(hh);
    if (mm<10) mySerial.print(F("0"));
    mySerial.print(mm);
    if (ss<10) mySerial.print(F("0"));
    mySerial.print(ss);
    mySerial.print(".000,A,3015.");
    mySerial.print(pos);
    mySerial.println(",N,09749.2872,W,0.67,161.46,030913,,,A*7C");

    // Print Debug Messages
    digitalWrite(12,LOW);
    ts_msg_low=micros();
    Serial.print("PPS at: ");
    Serial.print(ts_pps_high);
    Serial.print("\t PPS low after: ");
    Serial.print(ts_pps_low-ts_pps_high);
    Serial.print("\t MSG high after: ");
    Serial.print(ts_msg_high-ts_pps_high);
    Serial.print("\t MSG low after: ");
    Serial.print(ts_msg_low-ts_pps_high);
    Serial.print("$GPRMC,");
    if (hh<10) Serial.print(F("0"));
    Serial.print(hh);
    if (mm<10) Serial.print(F("0"));
    Serial.print(mm);
    if (ss<10) Serial.print(F("0"));
    Serial.print(ss);
    Serial.print(".000,A,3015.");
    Serial.print(pos);
    Serial.println(",N,09749.2872,W,0.67,161.46,030913,,,A*7C");
  }
  else if (timestamp_pps>=dt_pps*i_pps+100000)
  {
    digitalWrite(11,LOW);
    if (msg==true)
    {
      msg = false; 
      ts_pps_low = micros();
    }

  }
  else if (timestamp_pps>=dt_pps*i_pps)
  {
    digitalWrite(11,HIGH);  
    if (msg==false)
    {
      msg = true;
      ts_pps_high = micros();
    }
  }

}
1
  • 5
    You may try to run task2 from a timer interrupt: this way it will be able to interrupt task1. Do a web search for “Arduino timer interrupt”. Commented Jan 9, 2020 at 10:34

2 Answers 2

2

Here lies your ≈ ½ ms problem:

#include <SoftwareSerial.h>

SoftwareSerial is very time sensitive. It generates the serial stream by bit-banging, with interrupts disabled. For each byte sent, interrupts are disabled just before sending the start bit, and re-enabled just after the stop bit has been written. At 9600 bps, interrupts are thus disabled for a little more than 9 bit times on each byte, i.e. 936 µs.

Do not use SoftwareSerial if you need short interrupt latency. Use a hardware serial port instead. This can be problematic on the Uno, where you would loose the ability to send debugging info, but it is fine on the Leonardo.

Alternatively, if the only thing your ISR has to do is toggle a pin, you can instead configure the timer to generate that as PWM. This way the CPU is not involved at all in the pin toggling, and you get cycle-accurate signal timing irrespective of interrupt latency.


Edit: Answering your comment.

About the hardware serial ports: on the Uno you just have Serial, so you are out of luck if you want one port for outputting NMEA sentences and another one for debugging information. However, on the Leonardo, Serial is a virtual serial port emulated over USB. The real hardware serial port is called Serial1, and you should use it in preference to a SoftwareSerial instance.

About “more than just toggle the pin”: You may still be able to use PWM if whatever other stuff task2 has to perform is less time-critical than toggling the pin. In this case, you use PWM to toggle the pin on the TIMER1_COMPA event, and ISR(TIMER1_COMPA_vect) to do the other stuff. Then, each time the timer hits TIMER1_COMPA, it will immediately toggle the pin and, at the same time, rise an interrupt request. The CPU may take some time to service the request, but this delay could be acceptable if only the toggling needs extra-accurate timing.

2
  • What do you mean by "Use a hardware serial port instead"? How would you implement this? I will need to to more than just toggle the pin. It's just not implemented yet
    – MarayJay
    Commented Jan 9, 2020 at 16:57
  • @MarayJay: See amended answer. Commented Jan 9, 2020 at 19:09
3

Two possible solutions:

  1. Split the serial string in small pieces so it's (comfortably) less than 50 ms and send the string in 'pieces' … you could even send it until e.g. 45 ms has been reached and continue in the next round. Use the Blink Without Delay mechanism to switch between the tasks.
  2. More robust solution: Make Task 2 an interrupt... This is probably much easier, since you can keep sending the serial string and the MCU takes care of automatically starting the interrupt each 50 ms. Make sure the interrupt code is very short. When it is finished, it will automatically resume with the normal code (sending serial data). I have not checked this library, but it seems very simple to use: https://playground.arduino.cc/Code/Timer/ .
2
  • 1
    Thanks! I implemented it with an interrupt. I'm not sure where I should post my solution code? Should i edit your answer or update my question?
    – MarayJay
    Commented Jan 9, 2020 at 13:33
  • It's not mandatory but it may help others, so it's always good... I would create a new answer. Good luck with your project. Commented Jan 9, 2020 at 13:41

Your Answer

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

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