Вопрос по серийному номеру Arduino
Просто небольшой вопрос о странности, которую я заметил, играя с последовательным портом на своем Arduino.
Я использую последовательный монитор для тестирования интерфейса, который планирую позже адаптировать к Bluetooth. За исключением того, что я заметил, что если я читаю только фиксированные размеры с помощью readBytes, я могу вводить данные только этой точной длины в байтах, иначе мое последовательное соединение будет разорвано.
Пример псевдокода:
Serial.write(Serial.available());
if(Serial.available() >= NUM_BYTES){
Serial.readBytes(buffer,NUM_BYTES);
/* Делаем что-то */
}
Если я ввожу что-то длиной NUM_BYTES + 1, оно считывает первое NUM_BYTES. Но тогда я никогда не смогу отправить больше данных через последовательный монитор. Почему это? Это часть серийной реализации? (После этого запись всегда будет равна 1, даже если я попытаюсь отправить больше.)
У меня было решение: просто очистить буфер, вызывая Serial.read(), пока ничего не станет доступным. И из любопытства... Если кто-нибудь хочет ответить или дать мне ссылку на что-нибудь для чтения - есть ли способ проверить, прежде чем я напишу в последовательный порт, чтобы убедиться, что на Arduino есть буферное пространство для моего сообщения? Я читал, что у них есть 64-байтовый буфер для данных, но я не уверен, как протокол обеспечивает отправку больших наборов данных, если буфер когда-либо переполняется. Возможно, последовательная библиотека использует какую-то форму подтверждения?
@Raishin, 👍1
Обсуждение2 ответа
Целью буфера последовательного ввода является предотвращение потери данных между вызовами Serial.read() вашей программы. Если вы действительно хотите, чтобы в нем накапливались длинные сообщения, вот одна из многих статей, в которых описывает, как можно увеличить размер буфера в коде ядра Arduino. Но это общесистемное решение проблемы проектирования одной программы — оно приводит к увеличению затрат памяти на этот больший буфер для всех программ, независимо от того, нужно им это или нет, или вам придется определить новый тип платы в файле досок (методика в статье выше) и не забудьте использовать его для программ, которым нужен буфер большего размера.
Лучший подход — создать собственную буферизацию, определив в своей программе буфер, достаточно большой для самого большого сообщения (плюс нулевой терминатор), собирать байты из буфера последовательного ввода в свой собственный буфер достаточно часто, чтобы не допустить переполнение последовательного буфера; затем определите, когда ваш собственный буфер содержит полное сообщение, прежде чем вы "/* Делаете что-то */ ".
Похоже, вы спрашиваете о ситуации, в которой вы заранее будете знать длину следующего сообщения; в этом случае вы можете считать байты, пока не соберете все сообщение.
В других ситуациях сообщение может иметь признак завершения, например символ новой строки, или сообщение может содержать длину в пределах первых нескольких байтов. В этом случае вам придется дождаться этих байтов, преобразовать их в целое число и используйте этот номер, чтобы определить конец сообщения.
Обновление:
Но мне это не объясняет, почему я не могу отправить ему больше байтов. после этого, пока я не прочитаю весь входной буфер.
Я не могу здесь оказать большую помощь; У меня нет опыта использования .readbytes(); Я просто читаю байты по мере их поступления, пока не получу то, что хотел. Но в моих заметках говорится, что он «Читает символы в буфер. Завершается, когда прочитано len байтов, или истекает время ожидания» (я думаю, 1 секунда; может быть и призыв изменить это). Я бы также рассмотрел возможность переполнения 64-байтового последовательного буфера; звучит не так, но странные вещи могут вызывать странные вещи....
"но я редко работаю так близко к оборудованию" В этом и радость, и сложность встроенных систем!
Спасибо, полезная информация. И да, я пытался получить сообщения фиксированного размера, но из-за лени я использовал последовательный монитор для отправки их в Arduino. Я просто все еще не понимаю, почему я не могу отправить еще одно сообщение после первого, если я превышу "NUM_BYTES". Конечно, я ожидал бы, что один байт останется после чтения NUM_BYTES. Но это не объясняет мне, почему я не могу отправить ему больше байтов, пока я не прочитаю весь входной буфер., @Raishin
Также просто в качестве примечания, это просто для забавного светодиодного проекта. У меня пока не работает Android IDE на моем рабочем столе, но у меня есть Bluetooth LE, который я планирую использовать с приложением для телефона, чтобы управлять кучей светодиодов WS2812B. Но для этого мне понадобится ICD. (И я, вероятно, буду более навороченным и добавлю несколько контрольных сумм и все такое.) Я зарабатываю на жизнь разработчиком программного обеспечения, но я редко работаю так близко к аппаратному/коммуникационному уровню. Обычно у меня есть только TCP/UDP-коммуникации, которые уже обрабатываются самодельными библиотеками, поэтому все, что мне остается сделать, это определить структуру/перечисления и проанализировать данные., @Raishin
Я полагаю, что вы, возможно, неправильно поняли, как работают некоторые функции последовательного порта. Serial.available не гарантированно соответствует количеству байтов, которые можно прочитать, поскольку данные могут находиться в нескольких разных частях конвейера, и учитывается только один. Кроме того, доступная сумма может измениться после того, как вы ее проверите. Более того, оно может больше не измениться, если после его установки поступит больше данных. И поведение может отличаться в зависимости от того, как настроен порт.
В вашем случае похоже, что у вас часто остается один лишний байт, поэтому ваш тестовый if(Serial.available() >= NUM_BYTES)
остается установленным на 1.
Один из способов добиться желаемого — установить время ожидания последовательного чтения равным нулю, затем попытаться прочитать максимальное количество байтов, которое может хранить последовательный буфер, а затем проверить, сколько байтов вы на самом деле получили. Если вы получили частичное чтение, либо выполните циклическое чтение и продолжайте проверку, пока не прибудет больше, либо буферизируйте частичное чтение где-нибудь и снова пройдите основной цикл, периодически проверяя наличие дополнительных данных. Именно этот подход я обычно использую для получения больших объемов данных.
Другой способ — просто читать по одному байту (символу) за раз, пока значение Serial.available истинно (не ноль). После прочтения каждого байта вы проверяете, выполнено ли ваше условие. Если это так, вы возвращаете полученные байты, а если нет, то продолжаете чтение до тех пор, пока Serial.available() не станет ложным (доступно ноль байтов), после чего вы оставляете полученные байты в буфере и возвращаете пустую строку ( или что-то еще).
С помощью подобных методов вы можете накапливать входящие байты до тех пор, пока не будет выполнено любое необходимое вам условие. Никакие данные не будут потеряны, если только вы не проведете чтение в течение значительного периода времени и не будет реализовано квитирование). Данные, которые вы не читать после того, как ваше условие будет выполнено, все равно будет храниться в последовательном буфере и будет прочитано на следующем проходе.
Вы можете применить любые условия, чтобы остановить накопление и вернуть результат. Если вы получаете двоичные данные в кодировке COBS, вы продолжаете идти до тех пор, пока не увидите байт 0x00 (в кодировке COBS это указывает на конец одного блока и начало другого). Если вы получаете строки текста, вы продолжаете накапливать их, пока не увидите новую строку. Если вы получаете блоки фиксированного размера, вы накапливаете их до тех пор, пока не получите достаточно байтов. Конечно, если вы получаете двоичный файл, вы не используете строку для его накопления :)
Ниже приведен код, который я использую для получения сообщений с символом новой строки, используя второй упомянутый мной подход. Я вызываю эту процедуру при каждом проходе через loop(), и если она возвращает что-либо, кроме пустой строки, мой основной цикл воздействует на нее. Я использую это для получения текстовых команд ascii с разделителями-новой строкой от хоста (при потоковой передаче двоичных данных с устройства на хост (со скоростью 4 Мбит/с) через тот же последовательный порт). Я извлек этот код из версии, которая работала уже несколько лет, и никаких потерь символов обнаружено не было.
// Все последовательные чтения неблокируются, поскольку таймаут установлен в ноль.
// Это необходимо сделать до вызова Serial.begin()
// Эта процедура предназначена для получения случайных команд, завершающихся новой строкой.
// Читаем порт до тех пор, пока не будет получена новая строка или пока не закончатся символы в буфере
// Возвращает одно сообщение, завершающееся новой строкой, или возвращает "".
//
String inputString_Host = "";
String s = "";
String ReadFromHostSerialPort()
{
s = "";
while (HOST_SERIAL_PORT.available())
{
char inChar = (char)HOST_SERIAL_PORT.read();
inputString_Host += inChar;
if (inChar == '\n')
{
s = inputString_Host;
inputString_Host = "";
return s;
}
}
return "";
}
Ниже приведен еще один пример, показывающий, как использовать readBytes для выполнения аналогичной задачи с использованием чтения многобайтовых блоков. Этот код используется для чтения двоичных данных, поступающих с оксиметра. Я просто беру все, что доступно (только чтобы убрать это из последовательного порта) и помещаю в собственный кольцевой буфер, из которого позже декодирую и анализирую данные. В зависимости от интерфейса этот подход позволяет легко получать выборки данных с плавающей запятой на частоте 100 кГц без потери данных, оставляя достаточно времени для обработки выборок.
// Все последовательные чтения неблокируются, поскольку таймаут установлен в ноль.
// Это важно & должно быть выполнено до вызова Serial.begin().
// Запросите 1500 байт и посмотрите на numBytes, чтобы увидеть, что мы действительно получили.
// Обратите внимание, что spO2Buffer — это кольцевой буфер, реализованный и обслуживаемый где-то еще
// OXIMETRY_SERIAL_PORT подключен к датчику Nonin SPO2.
// Вывод Tx в настоящее время вообще не подключен.
// Чтение порта в 3-секундный циклический буфер
void OximetryClass::ReadOximeter()
{
if (OXIMETRY_SERIAL_PORT.available())
{
uint8_t inOximeterBytes[1500] = { 0 };
int numBytes = OXIMETRY_SERIAL_PORT.readBytes(inOximeterBytes, 1500);
spO2Buffer.WriteBytes(inOximeterBytes, numBytes);
}
}
Надеюсь, это поможет!
- Как разделить входящую строку?
- Как вывести несколько переменных в строке?
- В чем разница между Serial.write и Serial.print? И когда они используются?
- Загрузка Arduino Nano дает ошибку: avrdude: stk500_recv(): programmer is not responding
- Программы построения последовательных данных
- Как узнать частоту дискретизации?
- Что такое Serial.begin(9600)?
- Очистить существующий массив при получении новой последовательной команды
Можете ли вы показать нам ваш реальный код? И откуда вы знаете, что вы "не можете отправить больше данных" через последовательный порт? Какое поведение вы видите?, @chrisl