Arduino Serial: Описание команд Serial

Последовательная связь Arduino включает множество команд, доступных через библиотеку Serial. Хотя справочник перечисляет множество функций — begin(), end(), if(Serial), print(), println(), write(), availableForWrite(), read(), readBytes(), readBytesUntil(), available(), setTimeout(), find(), findUntil(), parseFloat(), parseInt(), peek(), flush() и serialEvent() — большинство практических скетчей используют лишь около пяти из этих команд.

begin(baudRate) / begin(baudRate, settings)

Функция begin() инициализирует последовательную связь. Без неё Serial не будет работать, даже если код скомпилируется успешно.

Serial.begin(9600);

Скорость передачи (Baud Rate): скорость передачи в битах в секунду. Распространённые скорости: 1200, 2400, 4800, 19200, 38400, 57600 и 115200. Оба конца связи должны использовать одинаковые скорости.

Настройки Serial: по умолчанию Arduino использует формат 8N1 (8 бит данных, без чётности, 1 стоп-бит).

Serial.begin(9600, SERIAL_8N1);

Пакеты данных содержат:

  • Биты данных (Data Bits): фактическая передаваемая информация

  • Чётность (Parity): механизм проверки ошибок (N=Нет, E=Чётная, O=Нечётная)

  • Стоп-биты (Stop Bits): разделители пакетов (обычно 1)

Когда настройки не указаны, Arduino использует формат по умолчанию 8N1, что делает Serial.begin(9600) эквивалентным Serial.begin(9600, SERIAL_8N1).

end()

Закрывает или отключает последовательную связь. Редко нужна для аппаратного Serial, но полезна при использовании программного Serial, где существует несколько каналов, но только один работает одновременно.

Serial.end();

if (Serial)

Проверяет доступность последовательной связи. Возвращает TRUE на большинстве плат Arduino независимо от статуса инициализации. Надёжно работает только на платах Leonardo и Micro Pro с USB CDC подключением.

Leonardo требует ожидания доступности Serial:

while (!Serial) { ; }

availableForWrite()

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

int numChars = Serial.availableForWrite();

Поскольку аппаратный Serial работает в фоновом режиме, выполнение кода продолжается во время передачи данных. Эта функция помогает синхронизировать операции с завершением передачи.

read()

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

if ( Serial.available() )
{
    c = Serial.read();
}

Функция Serial.available() возвращает количество байт в буфере. Любое значение больше 0 оценивается как TRUE, гарантируя наличие хотя бы одного символа перед чтением.

readBytes(buffer, length)

Аналогична read(), но считывает несколько байт. Требует два параметра: буфер (массив char или byte) для хранения данных и количество байт для чтения.

int length = 10;
char buffer [11];
int numberOfBytes = Serial.readBytes(buffer, length)
// if numberOfBytes >0 we have data.
// if numberOfBytes =10 we have all the data.

Возвращает фактическое количество скопированных байт. Убедитесь, что буфер достаточно большой, чтобы предотвратить повреждение данных.

readBytesUntil(termChar, buffer, length)

Расширяет readBytes() за счёт принятия символа-терминатора. Чтение останавливается при обнаружении терминатора, который не копируется в буфер.

int length = 10;
char buffer [11];
char termChar = 13;
int numberOfBytes = Serial.readBytesUntil(termChar, buffer, length)
// if numberOfBytes >0 we have data.

Таймаут по умолчанию составляет 1 секунду (1000 мс) как для readBytes(), так и для readBytesUntil(). Это блокирует выполнение скетча при ожидании данных. Автор предпочитает использовать read() в пользовательских циклах для неблокирующей работы, позволяя выполнять другие задачи при отсутствии данных.

setTimeout()

Настраивает длительность таймаута для readBytes() и readBytesUntil() в миллисекундах. Значения имеют тип данных long.

Serial.setTimeout(500);  // установить на полсекунды

find(searchString)

Ищет указанную строку во входном буфере Serial. Возвращает TRUE, если найдена, FALSE — если не найдена или истёк таймаут.

Важные особенности поведения:

  • Удаляет содержимое буфера в процессе поиска

  • Блокирует выполнение скетча во время поиска

Функция последовательно удаляет символы, пока не найдёт искомую строку или не опустошит буфер. Если искать «world» в «hello world», функция вернёт TRUE, но опустошит буфер, и в нём останутся только данные после «world».

void setup()
{
    Serial.begin(9600);

    delay(4000);

    char searchString[] = "world";
    boolean found = Serial.find(searchString);

    if(found) { Serial.println("The world has been found"); }
    else      { Serial.println("The world has been lost"); }
}

void loop()
{
}

Автор считает эту функцию ограниченно полезной. Копирование данных Serial в пользовательский буфер и последующий поиск в нём обеспечивает лучшую гибкость, особенно при контроле передаваемых и получаемых данных.

findUntil(searchString, terminatingString)

Аналогична find(), но принимает строку-терминатор. Ищет строку поиска, пока не будет найдена искомая строка, строка-терминатор, или не истечёт таймаут.

Возвращает TRUE, если искомая строка найдена, FALSE — в противном случае. Если встречается строка-терминатор, поиск прекращается и возвращается FALSE.

char searchString[] = "zzz";
char termString[]   = "\n";

void setup()
{
    Serial.begin(9600);

    boolean found = Serial.findUntil(searchString, termString);
    Serial.print("searchString ");
    if(found) { Serial.println("found"); }
    else { Serial.println("not found"); }
}

void loop()
{
}

Важное замечание: Arduino передаёт символы EOL в порядке «\r» «\n». Если буфер содержит «abcd\r\n» и «\n» является терминатором, функция вернёт FALSE, но оставит «\r» в буфере, что потенциально вызовет проблемы при последующем разборе.

parseInt()

Извлекает первое допустимое целое число в ASCII-формате из буфера Serial, возвращая значение типа long. Нечисловые символы пропускаются. Возвращает 0, если допустимое целое число не найдено или истёк таймаут.

Особенности поведения:

  • Удаляет нечисловые символы в процессе поиска

  • Оставляет содержимое буфера после целого числа нетронутым

  • Может вызвать проблемы с производительностью, если буфер содержит только целые числа (ожидает таймаут)

Примеры:

  • Буфер «12345» — возвращает 12345, буфер становится пустым

  • Буфер «abd1234zxy» — возвращает 1234, буфер становится «zxy»

  • Буфер «-3434\r\n» — возвращает -3434, буфер содержит «\r\n»

long parseIntValue = Serial.parseInt();

Критически важно: всегда сохраняйте возвращаемое значение в переменную типа long. Arduino int (2 байта) не может безопасно хранить все значения long (4 байта). Значения, превышающие 32 767, будут усечены некорректно. Например, значение long 2147483640 станет -8 при преобразовании в int.

parseFloat()

Извлекает первое допустимое число с плавающей точкой в ASCII-формате из буфера Serial, возвращая значение типа float. Возвращает 0, если допустимое число не найдено или истёк таймаут. Функция блокирует выполнение кода при ожидании данных.

Процесс:

  • Удаляет символы, не относящиеся к числам с плавающей точкой (не числа и не знак минуса)

  • Считывает данные числа с плавающей точкой до обнаружения символа, не относящегося к float

  • Преобразует значение в ASCII-формате в фактическое число с плавающей точкой

Пример: Буфер «abc3.1234abcdefg» — возвращает 3.1234, буфер становится «abcdefg»

peek()

Возвращает следующий символ в буфере Serial, не удаляя его. Работает как read(), но сохраняет содержимое буфера. Обращается только к первому доступному символу; последовательные вызовы peek возвращают один и тот же символ, так как указатель буфера не продвигается.

Полезна для создания пользовательских парсеров, требующих возможности «заглянуть вперёд» без нарушения данных буфера.

flush()

Часто неправильно понимаемая, эта функция не очищает буферы мгновенно. Вместо этого она приостанавливает выполнение кода до завершения передачи всех исходящих данных Serial.

Поскольку аппаратный Serial работает в фоновом режиме через прерывания, Serial.print() копирует данные в выходной буфер, а затем немедленно возвращается к основному коду, пока передача продолжается асинхронно. Иногда код должен дождаться завершения передачи перед продолжением — именно здесь применяется Serial.flush(). Возникающая задержка может быть существенной.

serialEvent()

Технически не является командой Serial, а является специальной функцией, вызываемой один раз за итерацию loop, когда входной буфер Serial содержит данные. Работает как внешняя проверка if(Serial.available()).

// Arduino Serial: Using serialEvent 01
// www.martyncurrey.com

char c  = ' ';
char buffer [21];
int pos = 0;
bool stringComplete = false;

void setup()
{
    Serial.begin(9600);
    Serial.println("START");
}

void loop()
{
 if (stringComplete)
 {
    Serial.println(buffer);
    buffer[0] = '\0';
    stringComplete = false;
    pos=0;
  }
}

void serialEvent()
{
    char c = (char)Serial.read();
    buffer[pos] = c;
    pos++;
    if (c == 10) {  stringComplete = true;  }
}

Обратите внимание: в скетче нет явного вызова serialEvent() — система вызывает её вне функции loop() автоматически.

Альтернативный подход с использованием if(Serial.available()):

// Arduino Serial: Using if (Serial.available())
// www.martyncurrey.com

char c  = ' ';
char buffer [21];
int pos = 0;
bool stringComplete = false;

void setup()
{
    Serial.begin(9600);
    Serial.println("START");
}

void loop()
{
   if(Serial.available())
   {
      char c = (char)Serial.read();
      buffer[pos] = c;
      pos++;
      if (c == 10) {  stringComplete = true;  }
   }

   if (stringComplete)
   {
      Serial.println(buffer);
      buffer[0] = '\0';
      stringComplete = false;
      pos=0;
    }
}

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

Рекомендации

Автор рекомендует избегать использования find(), findUntil(), readBytes() и readBytesUntil(), так как они блокируют выполнение скетча. Создание собственных циклов разбора с использованием read() и available() позволяет скетчу выполнять другие задачи в ожидании данных Serial.


Оригинал статьи: martyncurrey.com