Несколько срочных задач

Я использую Arduino Leonardo, и мне нужно выполнить две срочные задачи, подобные этой:

// Задача 1 — выполняется раз в секунду (1 Гц)
// Выполнение задачи требует около 70 мс
   task1(); //Отправка сообщения через Serial

// Задача 2 — выполняется 50 раз в секунду (50 Гц)
// Для выполнения задачи требуется несколько микросекунд
   task2(); // цифровая запись

Как видите, проблема в том, что эта задача требует очень много времени для выполнения, и поэтому задача 2 выполняется слишком поздно, чтобы отправить сигнал 50 Гц. Я не знаю, как это сделать, но я думаю, что должен быть способ дать задаче 1 более высокий приоритет, чтобы прервать задачу 2?

Заранее спасибо! :)

Для завершения вот мой (не очень чистый) код:

#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; //микро
const unsigned long dt_trig = 1000000/triggerFreq; //микро
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() {
  // поместите сюда код установки для однократного запуска:
  pinMode(11, OUTPUT); // PPS
  pinMode(12, OUTPUT); // Индикатор msg_sent
  pinMode(13, OUTPUT); // кадров в секунду
  Serial.begin(9600);
  mySerial.begin(9600);
  triggerStartTime = micros();
}

void loop() {
  // ================== Деталь для моделирования GPS ==================
  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");

    // Печать отладочных сообщений
    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();
    }
  }
  // ================== Часть для триггера FPS ==================
  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;
    }
  }

}

Обновление: поэтому я реализовал его с прерыванием в соответствии с этим примером. Я просто запрограммировал прерывание для сигнала 50 Гц, потому что он должен быть точным. Код в конце. Тем не менее, я все еще испытываю задержку сигнала 50 Гц при отправке серийного номера. Теперь он составляет полмиллисекунды, но в моем проекте (компьютерное зрение слияния датчиков) это немаловажно. Вывод моего инструмента постобработки показывает:

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!

Кто-нибудь знает, как я могу улучшить свой код?

Код:

//ОПРЕДЕЛЕНИЯ GPS
#include <SoftwareSerial.h>
SoftwareSerial mySerial(7, 6); //232_TX,232_

int triggerFreq = 50;
unsigned long timestamp_pps;
// беззнаковая длинная временная метка;
unsigned long triggerStartTime;
const unsigned long dt_pps = 1000000; //микро
//const unsigned long dt_trig = 1000000/triggerFreq; //микро
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;
//беззнаковый длинный i_trig = 0;

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


void setup() {

  cli();//останавливаем прерывания
  // устанавливаем прерывание от таймера 1 на 1 Гц
  TCCR1A = 0;// устанавливаем весь регистр TCCR1A в 0
  TCCR1B = 0;// то же самое для TCCR1B
  TCNT1  = 0;//инициализировать значение счетчика до 0
  // установить регистр совпадения для сравнения с шагом 1 Гц
  //OCR1A = 15624;// = (16*10^6) / (1*1024) - 1 (должно быть < 65536)
  OCR1A = 19999; // = (16*10^6) / (100 * 8) -1 (должно быть < 65536)
  // включаем режим CTC
  TCCR1B |= (1 << WGM12);
  // Устанавливаем бит CS11 для 8 предделителя
  TCCR1B |= (1 << CS11);
  // включить прерывание сравнения таймера
  TIMSK1 |= (1 << OCIE1A);
  sei();//разрешить прерывания

  // НАСТРОЙКА GPS
  pinMode(11, OUTPUT); // PPS
  pinMode(12, OUTPUT); // Индикатор msg_sent
  pinMode(13, OUTPUT); // кадров в секунду
  Serial.begin(9600);
  mySerial.begin(9600);
  triggerStartTime = micros();

}

ISR(TIMER1_COMPA_vect){  // изменяем 0 на 1 для таймера 1 и 2 для таймера 2
   // здесь прерываем команды
   digitalWrite(13,!digitalRead(13));
}

void loop() {
  // ================== Деталь для моделирования GPS ==================
  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");

    // Печать отладочных сообщений
    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();
    }
  }

}

, 👍3

Обсуждение

Вы можете попробовать запустить задачу2 из прерывания по таймеру: таким образом она сможет прервать задачу1. Выполните поиск в Интернете по запросу «прерывание таймера Arduino»., @Edgar Bonet


2 ответа


Лучший ответ:

2

Вот ваша проблема с ≈ ½ мс:

#include <SoftwareSerial.h>

SoftwareSerial очень чувствителен ко времени. Он генерирует последовательный поток побитовое, с отключенными прерываниями. Для каждого отправленного байта прерывания отключаются непосредственно перед отправкой стартового бита и снова включаются только после записи стопового бита. При скорости 9600 бит/с прерывания таким образом отключается чуть более чем на 9 бит в каждом байте, т.е. 936 мкс.

Не используйте SoftwareSerial, если вам нужна короткая задержка прерывания. Использовать аппаратный последовательный порт вместо этого. Это может быть проблематично на Uno, где вы потеряете возможность отправлять отладочную информацию, но это нормально на Леонардо.

В качестве альтернативы, если единственное действие вашего ISR — это переключение вывода, вместо этого вы можете настроить таймер для генерации его как PWM. Сюда процессор вообще не участвует в переключении контактов, и вы получаете синхронизация сигнала с точностью до цикла независимо от задержки прерывания.


Обновление: ответ на ваш комментарий.

Об аппаратных последовательных портах: на Uno у вас есть только Serial, поэтому вам не повезло, если вы хотите один порт для вывода предложений NMEA и еще один для отладочной информации. Однако на Леонардо, Serial – это виртуальный последовательный порт, эмулируемый через USB. Реальное оборудование последовательный порт называется Serial1, и вы должны использовать его вместо экземпляр SoftwareSerial.

О «большем, чем просто переключение вывода»: возможно, вы все еще сможете использовать ШИМ если какие-либо другие вещи, которые должна выполнять задача2, менее критичны по времени, чем переключение штифта. В этом случае вы используете PWM для переключения контакта на TIMER1_COMPA и ISR(TIMER1_COMPA_vect) для другого вещи. Затем каждый раз, когда таймер достигает TIMER1_COMPA, он немедленно переключать вывод и одновременно поднимать прерывание запрос. Процессору может потребоваться некоторое время для обслуживания запроса, но это задержка может быть приемлемой, если только переключение требует сверхточности время.

,

Что вы подразумеваете под «Использовать аппаратный последовательный порт вместо этого»? Как бы вы это реализовали? Мне нужно будет больше, чем просто переключить пин. просто это еще не реализовано, @MarayJay

@MarayJay: см. ответ с поправками., @Edgar Bonet


3

Два возможных решения:

  1. Разделите последовательную строку на небольшие части, чтобы она (удобно) была меньше 50 мс, и отправьте строку «кусками»… вы даже можете отправлять ее, пока, например, не будет достигнуто 45 мс, и продолжить в следующем раунде. Используйте механизм Blink Without Delay для переключения между задачами.
  2. Более надежное решение: сделать задачу 2 прерыванием... Это, вероятно, намного проще, поскольку вы можете продолжать посылать последовательную строку, а MCU позаботится об автоматическом запуске прерывания каждые 50 мс. Убедитесь, что код прерывания очень короткий. Когда он будет завершен, он автоматически возобновится с обычным кодом (отправка последовательных данных). Я не проверял эту библиотеку, но она кажется очень простой в использовании: https://playground.arduino.cc/Code/Timer/ .
,

Спасибо! Я реализовал это с помощью прерывания. Я не уверен, где я должен опубликовать свой код решения? Должен ли я отредактировать ваш ответ или обновить свой вопрос?, @MarayJay

Это не обязательно, но может помочь другим, так что это всегда хорошо... Я бы создал новый ответ. Удачи с вашим проектом., @Michel Keijzers