Как использовать прерывание в Arduino для получения данных с последовательного входа

В настоящее время у меня есть проект, который заставил меня написать программу, которая получает несколько данных от последовательного ввода с использованием прерываний в Arduino. Я использую Arduino в качестве получателя и ESP32 в качестве отправителя через аппаратный последовательный порт. Программа, которую я написал для Arduino, аналогична опубликованной на странице Основы последовательного ввода с некоторыми изменениями. как написано ниже:

const byte numChars = 32;
char receivedChars[numChars];
char tempChars[numChars];        // временный массив для использования при разборе

// переменные для хранения анализируемых данных
char messageFromPC[numChars] = {0};
unsigned long sendDataPrevMillis = 0;
int controlCondition = 0;
int droneCondition = 0;
int gasValue = 0;
int pitchValue = 0;
int rollValue = 0;
int yawValue = 0;

boolean newData = false;

void setup() {
  attachInterrupt(digitalPinToInterrupt(1), handleInterrupt, RISING);
  Serial.begin(57600);
}

void loop() {
  
  Serial.print("Control Condition :");
  Serial.print(controlCondition);
  Serial.print(" Drone Condition :");
  Serial.print(droneCondition);
  Serial.print(" Roll:");
  if(rollValue < 1500)Serial.print("<<<");
  else if(rollValue > 1500)Serial.print(">>>");
  else Serial.print("-+-");
  Serial.print(rollValue);
  Serial.print("  Pitch:");
  if(pitchValue > 1500)Serial.print("^^^");
  else if(pitchValue < 1500)Serial.print("vvv");
  else Serial.print("-+-");
  Serial.print(pitchValue);
  
  Serial.print("  Gas:");
  if(gasValue < 1500)Serial.print("vvv");
  else if(gasValue > 1500)Serial.print("^^^");
  else Serial.print("-+-");
  Serial.print(gasValue);
  
  Serial.print("  Yaw:");
  if(yawValue < 1500)Serial.print("<<<");
  else if(yawValue > 1500)Serial.print(">>>");
  else Serial.print("-+-");
  Serial.println(yawValue);
}

void handleInterrupt(){
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '<';
    char endMarker = '>';
    char rc;

    while (Serial.available() && 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'; // завершаем строку
                recvInProgress = false;
                ndx = 0;
                newData = true;
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
        }
    }
    if (newData == true) {

    strcpy(tempChars, receivedChars);
    char * strtokIndx; // это используется strtok() как индекс

    strtokIndx = strtok(tempChars, ",");
    controlCondition = atoi(strtokIndx);     

    strtokIndx = strtok(NULL, ",");
    droneCondition = atoi(strtokIndx);
    
    strtokIndx = strtok(NULL, ",");
    gasValue = atoi(strtokIndx);

    strtokIndx = strtok(NULL, ",");
    pitchValue = atoi(strtokIndx);     

    strtokIndx = strtok(NULL, ",");
    rollValue = atoi(strtokIndx);     

    strtokIndx = strtok(NULL, ",");
    yawValue = atoi(strtokIndx);     

    newData = false;
    }
}

Данные будут отправлены из ESP32 для изменения значений controlCondition, droneCondition, gasValue, pitchValue , rollValue и yawValue.

Я попробовал, но прерывание не сработало. Прерывание должно вызываться всякий раз, когда controlCondition, droneCondition, gasValue, pitchValue, rollValue или yawValue измените его значение. Есть идеи, что мне делать? или что-то не так с моим кодом?

Обратите внимание, что код, который я показал в void цикле(), — это всего лишь пример кода. Реальный код более сложен и требует большого количества вычислений. Переменные, которые я упомянул, являются важными переменными для расчетов.

, 👍2

Обсуждение

attachInterrupt(digitalPinToInterrupt(1), handleInterrupt, RISING) : это Arduino, потому что контакт 1, говорит, что Uno не настроен для внешних прерываний, запускаемых по фронту. Обычный метод обработки серийного номера — это цикл() с использованием Serial.available() для проверки наличия каких-либо данных для обработки. Почему вы пытаетесь сделать это в процедуре обслуживания прерываний? Вы планируете блокировать код в цикле() или что?, @6v6gt

https://stackoverflow.com/questions/53104440/how-can-i-implement-interrupt-for-serial-usart-communication-for-atmega328p-ardu#53105966, @jsotola

while (Serial.available()) ... если бы скорость Arduino была масштабирована до скорости человека, то серийный символ приходил бы раз в неделю, @jsotola

@jsotola Честно говоря, я не понимаю, к чему ты клонишь., @Nick Gammon

@NickGammon Я тоже нет ... Я, наверное, думал, что ISR ожидает несколько байтов, @jsotola

Было бы неплохо иметь возможность заставить прерывание Serial Rx вызывать функцию, которая прослушивает последовательный порт только тогда, когда мы получаем данные. Текущий метод, заключающийся в проверке в бесконечном цикле доступности данных в «программном буфере Rx», не очень пригоден для использования. В случае, если у нас есть функция, которая занимает несколько секунд в основном цикле, мы можем пропустить или задержать важное сообщение из последовательного порта. Библиотека HardwareSerial перехватывает прерывание Rx, но она также должна позволять вызывать функцию, которую мы могли бы переопределить с помощью нашего собственного кода. В настоящее время мы теряем все преимущества использования IRQ., @ManWithNoName


2 ответа


2

Почему вы вообще хотите использовать прерывание? Последовательное оборудование уже генерирует прерывание, и внутри этого ISR входящий байт помещается в буфер.

Я предполагаю, что у вас есть основной цикл, в котором вы делаете другие вещи, и вы хотите реагировать на поступление интересных последовательных данных.

У меня есть пример обработки последовательных данных без блокировки на моей странице о последовательной обработке, который я воспроизвожу ниже:

р>
/*
Пример обработки входящих последовательных данных без блокировки.

Автор: Ник Гэммон
Дата: 13 ноября 2011 г.
Изменено: 31 августа 2013 г.

Выпущено для публичного использования.
*/

// сколько последовательных данных мы ожидаем перед переводом строки
const unsigned int MAX_INPUT = 50;

void setup ()
  {
  Serial.begin (115200);
  } // конец настройки

// здесь для обработки входящих последовательных данных после получения терминатора
void process_data (const char * data)
  {
  // пока просто отображаем это
  // (но вы можете сравнить его с каким-то значением, преобразовать в целое число и т. д.)
  Serial.println (data);
  }  // конец Process_data
  
void processIncomingByte (const byte inByte)
  {
  static char input_line [MAX_INPUT];
  static unsigned int input_pos = 0;

  switch (inByte)
    {

    case '\n':   // конец текста
      input_line [input_pos] = 0;  // завершающий нулевой байт
      
      // терминатор достигнут! обработать input_line здесь...
      process_data (input_line);
      
      // сбрасываем буфер для следующего раза
      input_pos = 0;  
      break;

    case '\r':   // отбрасываем возврат каретки
      break;

    default:
      // продолжаем добавлять, если не заполнено... разрешаем завершающий нулевой байт
      if (input_pos < (MAX_INPUT - 1))
        input_line [input_pos++] = inByte;
      break;

    }  // конец переключателя
   
  } // конец процессаIncomingByte

void loop()
  {
  // если серийные данные доступны, обработаем их
  while (Serial.available () > 0)
    processIncomingByte (Serial.read ());
    
  // делаем здесь другие вещи, например, проверяем цифровой ввод (нажатие кнопок) ...

  }  // конец цикла

Мне кажется, что вы ищете «маркер конца»; это когда вы хотите отреагировать на то, что получили ранее. Как и в моем примере выше, вы можете просто брать отдельные байты в основном цикле, и когда прибудет маркер конца, можно будет обработать всю строку. Я использовал новую строку для и конечного маркера r, но вы можете изменить его на '>'.


Проблемы с прерыванием, подобные вашим:

  • Прерывание сработает по первому биту (из 10), поэтому последовательных данных еще не будет, и ваша программа будет бесполезно искать последовательные данные, которых там не будет.

  • Вы вызываете функцию Serial.read(), которая ничего не возвращает, поскольку пока вы находитесь в вашем ISR, основной последовательный ISR не сможет сработать.

  • Ваш ISR делает слишком много.

  • Последовательная линия обычно имеет ВЫСОКИЙ уровень и переходит в НИЗКИЙ (начальный бит) при запуске байта, поэтому вам следует искать ПАДЕНИЕ, а не ПОДЪЕМ.


Я настоятельно рекомендую вам переделать работу, чтобы просто проверять наличие серийного номера в основном цикле, как это сделал я, и обрабатывать его при появлении конечного разделителя.

,

На самом деле мой проект заключается в создании дрона, которым можно управлять с помощью приложения для Android, поэтому упомянутые мной переменные очень важны для расчета PID для дрона. А вычисления очень большие, поэтому циклу может потребоваться много времени для чтения значений переменных. Я попробовал способ, который вы показали ранее, и он не сработал. Вы можете увидеть мой код [здесь](https://drive.google.com/file/d/1jr-Ii3wdHICppz3AHUoS3COtGQbNi-6q/view?usp=sharing). Есть идеи?, @Zero

Ну, сказать, что это «не сработало», не очень полезно. В вашем коде много последовательных отпечатков. Что вы видите на терминале? Кажется, вы вызываете showParsedData в основном цикле. Не лучше ли вызвать его сразу после анализа данных?, @Nick Gammon

Вы оставили в тесте \n как конец данных, но я думал, что в качестве разделителя данных у вас есть ">". Есть ли еще новая строка?, @Nick Gammon


0

Не делайте ничего в вашей функции loop(), кроме вызова других функций, каждая из которых решает — для одной части вашего процесса — нужно ли это пора сделать эту часть. Если да, сделайте эту часть; если нет, немедленно вернитесь к функции цикла.

Прочитайте мой ответ на аналогичный вопрос, в котором больше рассказывается о том, как это сделать. Этот метод называется неблокирующим программированием. Ваш код должен быстро опрашивать все, на что ему может потребоваться отреагировать, но не должен ждать, пока что-то из этого произойдет. Если вы это сделаете, вам, скорее всего, не понадобится ответ, требующий прерываний.

Кроме того, операторы печати в вашем loop() замедляют (блокируют) выполнение и могут помешать вашему PID адекватно реагировать. Если они нужны вам для отладки вычислений, вы можете обернуть их в операторы #if DEBUG ... #endif, чтобы можно было использовать один #define DEBUG. легко включать их, когда вам нужно (зная, что это повлияет на управление вашим устройством) и удалять их для эксплуатационных испытаний. Если вам необходимо поддерживать работу некоторых операторов печати, запускайте одновременно только необходимое количество операторов, обязательно используйте максимально возможную скорость передачи данных и делайте их как можно более краткими.

,