serial.available() вор!!!

Тема в разделе "Arduino & Shields", создана пользователем lazerrain, 16 ноя 2016.

  1. rkit

    rkit Гуру

    Можно буфер пожирнее сделать
    #defile SERIAL_RX_BUFFER_SIZE 128
     
  2. rkit

    rkit Гуру

    Хотя если в скетче написать, то ничего не получится. Надо где-то при сборке.
     
  3. ostrov

    ostrov Гуру

    Вот на Atmega328 примерно так. Задумка в том, чтобы буфер приема переносить в программный буфер быстрее чем первый переполнится. Таким образом ничего пропасть не должно. Для скоростей UART выше 9600 таймеры нужно подкрутить.
    Код (C++):
    String inputString = "";        
    boolean stringComplete = false;

    void setup() {
      Serial.begin(9600);
      pinMode(13, OUTPUT);
      inputString.reserve(200);
      TCCR1A = 0b00000000;        // настройка таймера 1, канала А
      TCCR1B = 0b00001100;        // предделитель CLK/256;
      OCR1A = 0x0C34;             // прерывание раз в 50ms
      TIMSK1 = 0b00000010;        // запуск таймера по совпадению 1А
    }

    void loop() {}


    ISR(TIMER1_COMPA_vect) {      // каждые 50мс перетаскиваем содержимое в программный буфер
      while (Serial.available()) {
        char inChar = (char)Serial.read();
        inputString += inChar;
        if (inChar == '0') {          // 0 - символ окончания приема
          Serial.print(inputString);
          inputString = "";
        }
      }
    }
     
    ИгорьК нравится это.
  4. ИгорьК

    ИгорьК Гуру

    Коллеги! Все, конечно, круты, но код ТС никто не видел. Видимо, по умолчанию, он безошибочен...
     
  5. rkit

    rkit Гуру

    Да понятно уже, что код ошибочен.
     
  6. AlexU

    AlexU Гуру

    Да не вор он (этот serial.available()).
    Просто класс Serial немного кривовато реализован. Буфер вроде бы и равен SERIAL_RX_BUFFER_SIZE, но заполнить его полностью реализация не даст. Т.е., если с приёмного буфера ничего не считывать, то он заполнится только на SERIAL_RX_BUFFER_SIZE - 1 байт.
    Вот такая вот фигня....

    PS: честно говоря, пока не задали этот вопрос, сам был уверен, что буфер заполняется полностью...
     
    Alex19 нравится это.
  7. Alex19

    Alex19 Гуру

    Там используется кольцевой буфер, есть "голова" и есть "хвост" при получении байта мы увеличиваем "голову" на один, а "хвост" остается нулем. Вот добавление в "голову" - https://github.com/arduino/Arduino/...no/avr/cores/arduino/HardwareSerial_private.h нас интересует void HardwareSerial::_rx_complete_irq(void).

    Посмотрим int HardwareSerial::available(void), кстати вот он - https://github.com/arduino/Arduino/.../arduino/avr/cores/arduino/HardwareSerial.cpp.
    Код (C++):
    int HardwareSerial::available(void)
    {
      return ((unsigned int)(SERIAL_RX_BUFFER_SIZE + _rx_buffer_head - _rx_buffer_tail)) % SERIAL_RX_BUFFER_SIZE;
    }
    Кол-во данных в буфере определяется прибавляем длины буфера к "голове" и отнимаем "хвост", после чего получаем остаток от деления. Не совсем понимаю, зачем они прибавляют длину буфера, но это мелочи.

    Просто такая реализация, тут ни чего не поделаешь, поэтому он будет принимать лишь 63 байта. Но это не проблема, так как мы оперативно вынимаем данные из буфера, а если нет, Вам вряд ли что поможет:).

    Более правильно, просто опрашивать в цикле, нормально работает и на скоростях 115200, конечно надо следить за временем выполнения loop и избегать блокировок в виде while, delay. Так как это прерывание, есть вероятность заблокировать обработку прерывания на прием и пропустить байты.

    Вы можете изменить #defile SERIAL_RX_BUFFER_SIZE 128 в файле HardwareSerial.h он находится в arduino\hardware\arduino\avr\cores\arduino.
     
    Последнее редактирование: 17 ноя 2016
  8. ostrov

    ostrov Гуру

    У меня, наверное, скопившихся в ожидании байт по пальцам оной руки можно пересчитать. Всегда стараюсь организовать так, чтобы заведомо не доводить по греха переполнения. Чего и ТС желаю.
     
  9. lazerrain

    lazerrain Нуб

    провёл серию экспериментов. поскольку мне ошибочно казалось, что размер буфера - это то количество байтов, которые он может принять, то я и ошибочно считал, что при приёме 64 байт сериалэвент срабатывает лишь 1 раз. похоже, что всякая попытка занести данные в последний байт буфера приводит к переносу этого последнего байта (или байтов, если их больше 63) в следующую итерацию сериалэвента. а раз всё так, то видимо для случая, когда количество посылаемых байт всегда известно, serial.available() вообще не нужен;
    то есть код из 1 строки просто правильно заполняет массив, какого размера бы он ни был (в примере 256 байт)
    Код (C++):
    void serialEvent() {
        Serial.readBytes(bb, 256);
    }
    всем оч большое спасибо!
     
  10. lazerrain

    lazerrain Нуб

    кстати с дилеями тоже поэкспериментировал. при передаче фиксированного блока информации ничего не пропадает даже при задержках в секунд 10. терпеливо ждёт следующего сериалэвента. это мне понравилось
     
  11. Alex19

    Alex19 Гуру

    Говорил о прерывании и его конкретной реализации, delay это немного другое.

    При выполнении прерывания программа останавливается, другие прерывания не выполняются выполняется только это прерывание. Поэтому, есть вероятность пропустить байт. Это зависит от множества факторов, скорости, потока передачи (пакет за пакетом или с паузами), приоритетов прерываний и времени обработки прерывания.

    Если говорить о delay-ях, то они тоже могут внести неприятные сюрпризы, в том числе и с Serial. Поставьте высокую скорость и шлите пакет за пакетом, после чего выставьте delay. Поясню, Вы отправляете пакеты данных по 32 байта, каждую секунду. Ваш delay на 10 секунд, приведет к потере 8 пакетов, да и последний будет не полным, так как буфер 63 байта.

    Сами данные Ардуина получит, так как получение данных идет через прерывание, как и отправка, но вот кольцевой буфер может быть перезаписан, если Вы не успеете забрать все данные.

    Что касается serialEvent, то это не прерывание, а функция, которая вызывается после каждого loop, Вы можете посмотреть - https://github.com/arduino/Arduino/...3/hardware/arduino/avr/cores/arduino/main.cpp.
     
    Последнее редактирование: 17 ноя 2016
  12. ostrov

    ostrov Гуру

    Если итерация loop происходит быстрее чем передача одного байта от источника, то данные теряться не будут. Если же медленнее, то через какое то время буфер переполнится и начнутся потери. То есть ваш способ годится лишь для частного случая.
     
  13. AlexU

    AlexU Гуру

    Это делается, чтобы не городить лишние проверки. Если "голова" окажется меньше "хвоста", то операция "по модулю" ('%') вернёт не то результат, который ожидается. А при таком подходе гарантируется, что разность между "головой" и "хвостом" будет в пределах от '0' до 'SERIAL_RX_BUFFER_SIZE' * 2 и операция "по модулю" выдаст правильный результат в любых сочетаниях "головы" и "хвоста".
    Приёмный буфер не перезаписывается, если места нет, то новые данные просто отбрасываются.
    Есть сомнения, что использование функции 'serialEvent' хорошее решение. Пользы от неё мало, а код немного запутывает. Да и вызывается эта функция после отработки 'loop()', т.е. данные переданные через последовательный порт будут доступны только на следующей итерации. Криво как то...
     
    Alex19 нравится это.
  14. ИгорьК

    ИгорьК Гуру

    Мысль хорошая, но ИМХО, функция в прерывании тяжеловата.
    Если мне память не изменяет, то по приходу байта в буфер UART есть прерывание, и вопрос работы с ним за пределами ардуинофан - вещь стандартная и давно решенная: http://easyelectronics.ru/avr-uchebnyj-kurs-rabota-na-preryvaniyax.html
     
  15. Alex19

    Alex19 Гуру

    Спасибо за пояснение.

    Да верно, не внимательно посмотрел. Кому интересно посмотрите void HardwareSerial::_rx_complete_irq(void), в этом файле - https://github.com/arduino/Arduino/...no/avr/cores/arduino/HardwareSerial_private.h.

    Все немного не так, если во время выполнения loop мы получим даже 4 байта (при стандартном буфере), то ничего не произойдет. Мы просто прочтем их в следующем loop. Так как получение данных и перенос в буфер сделано на прерываниях, как и отправка данных. Поясню подробно, увы сейчас нет времени, ближе к вечеру.
     
  16. Unixon

    Unixon Оракул Модератор

    Это лишнее. Накопление данных в буфере и так делается по прерыванию, а вычитывать из буфера UART в какой-то еще другой буфер, когда обработка невозможна, это ненужное усложнение, тогда проще сам буфер UART больше сделать.
     
  17. ostrov

    ostrov Гуру

    Что же они тогда плачут что байты теряются? Я вот стараюсь недопускать чтобы приходило больше чем я их успеваю считывать. А если будет наоборот, например работа застряла на какой то функции больше обычного, а данные прут, не пропадет ли ничего?
     
  18. Onkel

    Onkel Гуру

    видел хитрецы делают функцию прерывания по изменению состояния пина rx (D0 вроде?), и в этой функции считывают из буфера, но эта попытка сварить суп из топора приводит к еще большим проблемам. Пожалуй единственный способ вывернуться - писать код на С в отличном от ардуино Ide.
     
  19. ostrov

    ostrov Гуру

    Компилятор у IDE тот же самый, что у официальной AVRS с оптимизацией -0s. Если не пользоваться digitalRead() и прочим костылями, то код получается абсолютно одинаковый до байта. Ничто и никто не мешает писать на "честном С" под Arduino IDE.
     
  20. Onkel

    Onkel Гуру

    ну так напишите программу обработки прерывания по поступлению байта в регистр приемника Uart и потом вместе посмеемся над утверджением