SoftwareSerial. Чтение из прерывания. Потеря данных

Привет. Изучаю взаимодействие микроконтроллера с блютузом через SoftwareSerial. Для теста написала метод, который читает буфер до символа новой строки и просто выводит эту часть. И так пока буфер не иссякнет. Столкнулась с поведением, понять которое не хватает мозгов.

Итак, сам метод

void isr() {
  static char bt_command[64] = "";
  
  while (mySerial.available()) {
    char c = mySerial.read();
    
    if (c == '\r') continue;

    if (c != '\n' && c != '*') {
      int len = strlen(bt_command);
      bt_command[len + 1] = '\0';
      bt_command[len] = c;
    }
    else if (strlen(bt_command) > 0) {         
      Serial.write("Command: ");
      Serial.write(bt_command); 
      Serial.write('\n');

      bt_command[0] = '\0';
    }
  }
}

Если я вызываю этот метод из loop(), то всё чётко. Такой вывод:

11:11:02.759 → Started
11:11:08.933 → Command: +CONNECTING<<AC:D6:18:13:49:0F
11:11:08.968 → Command: CONNECTED
11:11:30.652 → Command: +DISC:SUCCESS

Если я привязываю метод к прерываю, то данные искажаются, причем строго в одном месте во время коннекта с блютузом. Лог:

11:13:09.360 → Started
11:13:17.101 → Command: +CONNECTING<<AC:D6:18:13:49:0F
11:13:17.137 → Command: ��ʪQ5
11:13:20.676 → Command: +DISC:SUCCESS

Помогите разобраться почему так. Хочется работать через прерывание

Насколько мне известно - Сериал, и СофтСериал - сами используют прерывания для чтения порта - поэтому их нельзя вызывать из другого прерывания.

1 лайк

Не нашла как редактировать тему. Стоило весь код прописать


#include <SoftwareSerial.h>
SoftwareSerial mySerial(3, 2); // RX, TX

void setup() {
  Serial.begin(9600);
  mySerial.begin(9600);
  Serial.println("Started");

  attachInterrupt(1, isr, CHANGE);
}

void loop() {
//isr();
}

void isr() {
  static char bt_command[64] = "";
  
  while (mySerial.available()) {
    char c = mySerial.read();
    
    if (c == '\r') continue;

    if (c != '\n' && c != '*') {
      int len = strlen(bt_command);
      bt_command[len + 1] = '\0';
      bt_command[len] = c;
    }
    else if (strlen(bt_command) > 0) {         
      Serial.write("Command: ");
      Serial.write(bt_command); 
      Serial.write('\n');

      bt_command[0] = '\0';
    }
  }
}

перехочется :slight_smile:

как-то не хочется перехачиваться)

У вас в коде написана жуткая чушь, простите.
Вот смотрите - вот этой строкой вы привязываете свое прерывание к ЛЮБОМУ изменению уровня на пине RX:

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

Это однозначно не будет работать.

Еще раз - зачем все это? Почему не позволить классу Сериал делать это самостоятельно?

Спасибо за пояснение. Я не просто так пишу в разделе для новичков.
Что я хочу? Я хочу реагировать на входящие данные мгновенно. В цикле loop могут быть свои задержки и наверняка будут. Хочется обойти.

Почему не позволить классу Сериал делать это самостоятельно?

Что вы имеете ввиду?

Сериал и так умеет в принимать данные и складывать их в буфер, пока работает основная программа. Для того чтобы реагировать на входящие данные быстро - почаще проверяйте наличие в буфере Сериал новых символов да и все.

Serial - да, на хардварном уровне имеет поддержку приема.
Software serial - таковой не имеет, все делает МК в рамках выполнения пользовательских инструкций. Смотрит на пин, складывает биты в байты, байты в буфер и т.д.
Пока МК чухается в монопольно занявшем его ISR, всё летит мимо софтсериала.
Идеальный метод применения этого костыля - метнуть в него запрос и ждать ответа, крутясь во while(). Остальные способы будут привносить “помехи” в поток данных разнообразной степени серьёзности.

2 лайка

я только чуть уточню - СофтСериал тоже делает это асинзронно, хотя и не на аппаратном уровне, но в прерывании.

а смысл? С таким же успехом можно крутится в while(), проверяя mySerial.available() - без всей этой жути в прерывании.
У меня все тот же вопрос - зачем делать за СофтСериал то, что он прекрасно умеет и сам?

Т.е. теряются биты из-за того, что чтение из прерывания забирает на себя весь ресурс? Но почему эти помехи всегда в одном и том же месте возникают?

мне не хватило мозгов подружить rx tx и tx rx сериала. Казалось бы просто соединить и всё, но даже тестовый скетч не запустился у меня. И так и эдак и я забила. Пока

Но даже если представить, что у меня всё завелось на сериале, то я ведь всё равно тем же способом буду данные собирать. И на прерывание не повесить это дело. Всё сводится к loop. Так что вся суть вашего беспокойства в том, что я потеряла пару пинов?)

Вы путаете теплое с мягким. Так, как вы написали через прерывание - одинаково не будет работать ни с Сериал на стандартных пинах, ни с СофтСериал на других пинах.

А пины вы не потеряли, использовать на одних и тех же пинах и Монитор порта и Блютуз все одно сложно, так что неудивительно, что у вас не получилось.

Какая ж Вы молодец! Блин … сколько мы тут усилий тратим, чтобы из новичка код выжать! Оленька, я Ваш поклонник!

image

По сути дела, сейчас разберёмся, дайте немножко времени (пообедайте пока) :slight_smile:

Спасибо)

В целом, нормально объяснили, почему идут искажения. Придется от прерываний отказываться для ресивера. Жаль, что всё же не прояснилось, почему именно в одном и том же месте эти искажения идут.

Отосплюсь и буду думать как по новому логику выстраивать

взять МК с двумя аппаратными UART, обработывать каждый приходящий байт.

“Жуть с прерыванием” - это не ко мне, надеюсь?

Давно не залезал в исходники софтварсериала, но подозреваю, что с прерываниями он дружит только при посадке на INTn пины, т.е. надеяться на работу по ISR нужно крайне осмотрительно.

нет, по PCINT тоже может.
А вот без прерываний совсем - никак не может, поэтому например на Меге СофтСериал работает только на пинах с прерываниями.

Обрабатывать в loop? Если так, то что это меняет?

а вы каждый бит хотите отрабатывать :slight_smile: