Как использовать прерывание в 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 цикле()
, — это всего лишь пример кода. Реальный код более сложен и требует большого количества вычислений. Переменные, которые я упомянул, являются важными переменными для расчетов.
@Zero, 👍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
Не делайте ничего в вашей функции loop()
, кроме вызова других функций, каждая из которых решает — для одной части вашего процесса — нужно ли это пора сделать эту часть. Если да, сделайте эту часть; если нет, немедленно вернитесь к функции цикла.
Прочитайте мой ответ на аналогичный вопрос, в котором больше рассказывается о том, как это сделать. Этот метод называется неблокирующим программированием. Ваш код должен быстро опрашивать все, на что ему может потребоваться отреагировать, но не должен ждать, пока что-то из этого произойдет. Если вы это сделаете, вам, скорее всего, не понадобится ответ, требующий прерываний.
Кроме того, операторы печати в вашем loop()
замедляют (блокируют) выполнение и могут помешать вашему PID адекватно реагировать. Если они нужны вам для отладки вычислений, вы можете обернуть их в операторы #if DEBUG
... #endif
, чтобы можно было использовать один #define DEBUG
. легко включать их, когда вам нужно (зная, что это повлияет на управление вашим устройством) и удалять их для эксплуатационных испытаний. Если вам необходимо поддерживать работу некоторых операторов печати, запускайте одновременно только необходимое количество операторов, обязательно используйте максимально возможную скорость передачи данных и делайте их как можно более краткими.
- устаревшее преобразование из строковой константы в 'char*'
- Количество элементов в массиве char
- Ошибка Cast from 'char*' to 'uint8_t {aka unsigned char}' loses precision [-fpermissive]
- Длина константного массива uint8_t*
- RtcDateTime' не называет тип
- Какие Arduino поддерживают ATOMIC_BLOCK?
- Прерывание переполнения таймера AVR не работает
- Как объединить два значения в строку?
attachInterrupt(digitalPinToInterrupt(1), handleInterrupt, RISING)
: это Arduino, потому что контакт 1, говорит, что Uno не настроен для внешних прерываний, запускаемых по фронту. Обычный метод обработки серийного номера — это цикл() с использованием Serial.available() для проверки наличия каких-либо данных для обработки. Почему вы пытаетесь сделать это в процедуре обслуживания прерываний? Вы планируете блокировать код в цикле() или что?, @6v6gthttps://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