Помогите с отправкой команд по UART
- Войдите на сайт для отправки комментариев
Здравствуйте уважаемые. Надеюсь на вашу помощь ибо я дал себе правило, обращаться сюда на крайний случай. Для меня он настал. И не исключено что я просто где-то подтупливаю.
Есть 3 платы. Первая - пульт управления, передает команды второй. Вторая плата передает команды третей и отчитывается первой.
Все используют программный и аппаратный UART в следующей связке: [DEBUG_PC]-HARDWARE-><-HARDWARE-[1]-SOFTWARE-><-HARDWARE-[2]-SOFTWARE-><-SOFTWARE-[3]
Ранее использовал конкретно аппаратный uart но из-за проблем с отладкой и перекрестным подключением двух устройств, я подключил программный. Но вот кажется сам загнал себя в угол теперь думаю что делать.
Команды управления передаются в следующем виде TX0000XT где TX-XT - метки начала-конца команд. Где 0000 - команда.
Время от времени порт "хавает" первые два, а то и три символа! И я уже устал бороться с этой проблемой. Я собираю с помощью Serial.avaible и read данные из аппаратного буфера. в общую строковую переменную. Из длинной строки вырезаю 8 символов точно зная что там есть команда, а уж потом сверяю есть ли там метки. Если они есть и на месте, запускаю исполнение команды. И так пока строковая переменная - буфер не очиститься "откусыванием" по 8 смволов за каждый цикл. Этим я избавился от слипшихся команд. Но стоит потеряться хоть одному символу в начале - все! идет сдвиг и вся очередь пошла с рельс в речку.
Позже после смены кода (из-за которго поменялась скорость работы устройства) началась другая беда. Отпадали концы команд. Я понял что проверять буфер каждый цикл не есть правильно и сделал проверку в каждую секунду, надеясь что за секунду успеет дойти все что нужно. Обрадовался. Но не надолго. Через время выявил что команды все равно бьются. В основном в начале. Редко в конце.
Я не прошу вас придумывать мне код. Я хочу попросить вас подсказать мне как поступить чтобы избежать потерь на приемо-передаче. Алгоритм, на словах.
Прерывание по UART - пишу сразу не вариант.
До этого пробовал:
Поднять baud до 115200 и опустить до 2400. Установить сигнал приема-передачи чтобы программа остановилась и слушала (типа прерывание). Замедление работы программы. Делать аккумулирующие символы пробелов в начале и конце чтобы не достало до команды. Все-равно режет и еще хуже.
Ни один из способов не подошел.
И serialEvent пробовал. Терялись целые команды.
Свой код приведите, плз. Потому как я ни разу не замечал, чтобы Atmega как-то самопроизвольно чего-то там кусала или откусывала: если байт пришёл в аппаратный UART и успел отработаться в обработчике прерывания - то он не растворяется в небытие. Тем более, что искаропки в ардуино есть буфер ажно на целых 64 байта для UART. Так что если что-то куда-то пропадает - то скорее всего, что проблемы надо искать прежде всего в написанном коде, а уже потом - думать дальше.
В общем, код - в студию, пожалуйста.
Из длинной строки вырезаю 8 символов точно зная что там есть команда, а уж потом сверяю есть ли там метки. Если они есть и на месте, запускаю исполнение команды.
А если нет? Надо начинать выбирать по 1 символу в поиске стартовой метки. Плохо что она совпадает с финишной. Все уже украдено до нас, выбирайте из стандартных https://ru.wikipedia.org/wiki/Управляющие_символы
Но стоит потеряться хоть одному символу в начале - все! идет сдвиг и вся очередь пошла с рельс в речку.
Ну да. Рассинхронизация протокола.
Еще CRC штука полезная. У вас в каналах похоже весело.
Код 400 строк. Поэтому я приведу функцию которая отвечает за то самое чтение. Вызывалась ранее каждый цикл. Теперь каждую секунду с посмощью millis()
Да я вот уже подумал слать по ответу ОК. Тоесть если мк отправил он ждет подтверждения. Если пришло что-то непонятное или error то переслать команду до тех пор пока не прийдет OK. Это пока единственное что пришло мне в голову сегодня.
Проблема в том что если команды сильно будут биться, то канал просто загрузнет в ошибках передачи как и в случае с crc
То не проблема. То обнаружится и полечится. Проблема если команда "поворот налево" исказится и восприймется как "уничтожить цивилизацию" ;))
Ну в принципе подтверждение получения - вариант. Но CRC тоже. В общем добро пожаловать в чарующий мир протоколов )))
Спасибо. И Спасибо что уделили время.
Мысль вот вот пришла. А как вот вы представляете обмен сообщениями с вычислением crc если его тоже передавать. Тем же самым каналом. Ох...
Ну я обычно делаю типа такого: <стартовый байт> <сообщение> <crc>. Если сообщение прерменной длины то <стартовый байт> <длина> <сообщение> <crc включая длину>.
Алгоритм приема так.
1.Если не в синхронизации, например при запуске - прием по байту в ожидании стартового. Полезно изредка чегото отвечать, типа WAIT. Чтоб понимать что на том конце вобще еще шото живо.
2.После появления стартового - прием всего сообщения + crc по фиксированой длинне или прием длины а затем прием всего сообщения + crc .
3. Проверка crc. Если совпало - считаем что теперь протокол в синхронизации и отвечаем OK, иначе отвечаем ERROR_CRC и на п.1
4. Прием стартовый байт+сообщение+crc или для переменной длины стартовый байт+длинна, а затем сообщение+crc. Стартовый байт проверяем сразу, если не совпал - ответить типа ERROR_SYN и п.1
5. на п.3
Если передатчик получил в ответ: ОК - считаем что команда прошла, если ERROR_CRC- не прошла и надо отправить повторно, если ERROR_SYN - все плохо, сразу отправляем пустые команды до получения ОК.
То, что CRC идет по ненадежному каналу - естественно и приемлемо. Возможна конечно ситуация сообщение верное а ошибка в самой CRC. Ну ниче, повторно отправит.
Впринципе хороше если стартовый байт уникален (не встречается больше нигде в канале ), но не обязательно, алгоритм справляется с проблемой
Заинтриговали вы меня с контрольной суммой.
Буду пробовать на свежую голову. Посмотрим сколько коррекций будет на канале за час работы))
Еще раз Спасибо.
В аналогичной ситуации, устав бороться с протоколами обмена на RS232 я попробовал MODBUS. Сейчас перевёл на него все обмены. Даже мелкие. И на RS485. Рекомендую.
А что происходит с остатками в буфере? (Навскидку не понял )) Остатком может быть начало следующей команды, а если его просто отбросить, то эта команда будет битая. По идее надо считывать "целое" число команд, а остаток сохранять в буфере и к нему приплюсовывать следующие считанные байты.
зы. у меня засада была в том, что помехи тупо вырубали USB и обмен с компом останавливался.
Остаток - я уверен на 60% что он битый. Я сделал очистку сразу как подумал об этом методе обработке с буферизацией команд. Вот в чем дело:
Устройство [3] является управляющим, а устройство [2] командующим.
[2] Выполняет свою программу и решает что когда переключить отдавая команды в [3].
Он шлет все попакетно. Отдельно. К примеру TX3231XT (прошло 2 секунды) TX3221XT (прошло пол секунды) TX3251XT (прошло 0,8 секунды) TX3211XT (Прошло 10 секунд) TX3331XT И так далее. Устройсво [2] замеряет время на контактных датчиках и делавет вывод когда и что переключить по получению сигнала а работает все в проверке через for. Поэтому паузы между командами вальируются от 0,5 до 20 секунд.
Теоритически если в буфере осталось TX32 после извлечения из всего потока. То команда никак не дополниться. Позже я подвердил свою теорию. И начиналось нечто вроде
TX3333XTTX22TX3211XT
^^^^
Мне важно не терять пакеты даже если отгрызать символы до следующей метки. Ибо есть спец команда которая если не учтется - начнет рушить все вокруг)))
Попытался переписать алгоритм приема-передачи. После отправки команды ожидается цифра длины полученной строки. Если она не равна восьми то переслать ее заново пока не будет 8. Проблема не заставила себя ждать. Я сделал цикл ожидания ответа в функции отправки команды. И получил бесконечное количество ошибок с переполнением буфера.
Кусок отвечающий за передачу:
И то что принимает:
Немного "быдлокодно" но оптимизация чуть позже.
И вот как дальше, думать еще километров 10 пешком)))
Про ModBus посмотрел, почитал. Не в ник что-то. Понял что даные передаются регистрами которые как я понял и есть содержимое. Данные нужно преобразовывать. В общем для меня стало сложно и я пока отложил этот вопрос.
Вычислять crc пока не буду.
Пришла мысль что команды кусает именно while. Мол МК замялся в передаче, сделал передышку, а тот принял за конец передачи. Если бы постоянно снимать данные с uart тогда стало бы что-то ясно. Но пока не придумаю как именно постоянно опрашивать буфер uart не тормозя сильно основную программу.
Ну то есть команды передаются в виде TX0000XT, по одной команде за раз. Минимальный интервал 0.5 сек.
loop видимо крутится быстрее, чем 0.5 сек. Поэтому в loop 1 раз делается проверка буфера порта. Если в буфере что-то есть - то начинается чтение буфера до победного конца, то есть пока не будет считана целиком последовательность TX0000XT.
Поэтому условие должно быть не while (softSerial.available()) , а
if (softSerial.available()>0)
{
while (softSerial.available() < 8 байт)
{ delay 100; } // ждем пока в буфере не накопится 8 байт
Потом считываем из буфера порта 8 байт и проверяем, что последние 2 - это "XT".
}
ну как-то так. А то действительно байты в порт поступают медленнее, чем считываются и оно вываливается из while (softSerial.available()) не дождавшись конца передачи.
diakin, Благодарю за подсказку. Я чего-то не додумался дожидаться накопления команды в буфере.
Я как-то выводил значение Serial.available и там было то 1 то 2. Двойка чаще. такое ощущение что оно так кусочками и переадает. Получается байты разбирались как горячие пирожки и работало все чисто на совпадении времени.
Попробую вернуть на место что было, а то не получается нормально в голове сложить модель с корекцией ошибок. И перед тем как попробовать запихнуть чтение буфера из loop попробую снова потестировать канал чтобы понять что именно грызет пакеты. Программа или железо.
Помниться когда замедлял передатчик [2] то команды шли исправно. Когда я понял что он торопиться сделал буфер. А потом начали откусываться куски.
Т.е. приемник просто недожидался поступления всех байт команды?! Все оказалось так просто )))
Пишите в лопе сразу без циклов и ожиданий
if (softSerial.available()>=8)
{
//считываем из буфера порта 8 байт и запускаем проверки и т.д.
.....
}
// делаем остальные дела
Хрень какая-то.
Погонял тесты. Сначала в отдельной функции через интервал, потом в loop пнапрямую. Получих такое:
Пока в ступоре. Одно ясно - ОНО не успеватет или торопиться.
Попытался сделать такое: откусывать полезные команды стараясь не кусать важное.
А получил почемуто
Я где-то явно туплю((
А буфер действительно не успевает. Сделал разгон по времени начиная от 1 секунды и уменьшая по 5 мс.
Подцепил два шнурка на софт и на хард. После интервала менее 100мс данные льються рекой, а на входе [3] ничего нет!
Но когда 1000-600мс данные идут стройным шагом. Дальше есть подозрение что теряються полными пакетами. Добавил счетчик и как-то оно странно себя ведет.
Еще чуток погоняю, до стабильности и я стану чуточку счастливее =)
Так неправильно!
В этом случае, как только в буфере останется меньше 8 байт - чтение прекращается. Правильно так (как писали выше)
Чтение прекращается. Ну никудаже ничего не девается. По идее должно просто дописываться. Я когда мониторил софтсериал там поток был без единой ошибки.
Хотя попробую. Я понял суть.
Сегодня я попробовал сделать так:
Интересно то что сообщение delayed - которое мне рассказало бы о том что была задержка при поступлении в буфер, не было отображено. Но все исправно приходило. Однако досконально не проверял так как пакеты однотипные отправлял. И почему-то между командами появился знак апострофа. Откуда - не знаю. Строки выглядели так: TX1234XT'TX1234XT'TX1234XT
Но меня это не смутило, просто вопрос откуда он появился? А увидел я его как добавил здержку при получении чего-то в буфер.
А вот цикл for с числом 7... просто я мониторил softSerial.available и там были (на этот раз) цифры больше 8-ми. Максимум 13. Я предположил что это управляющие символы... хотя разве они учитываются функцией при счете. Не знаю.
Спасибо diakin, попробую твой вариант. Может доведу до ума и буду вспоминать это, как страшненький сон.
По коду из 17 поста. А
rxCheck
если softSerial.available()>=8 не выполнено очищаете? Или оно к rxData добавляется при каждом проходе снова и снова? У вас слишком много буферов, что способствует путанице в логике.Там суть такая, мы считываем все что есть в "аппаратном" буфере в rxCheck а потом, теоритически там должна быть одна команда или кусок команды который потом должен прийти. Все разом они собираються в rxData. А потом проверяется если в rxData у нас собралось более двух команд, мы дополнительно смотрим есть ли метка XT (конечная метка) (вообще надо было сделать >= 16) Если метка есть, значит все целое и мы чистим rxCheck.
Позже проверяем первая команда начинается с начально метки?! Тогда отрезаем наш кусочек в netDatax и исполняем.
Вот перечитываю код, и вижу что запарился. В теории я предполагал что в rxCheck могут быть разные данные, в смысле недопереданные куски, но все собирается в rxData в надежде что там кусочки найдут друг друга и когда там целая команда, есть конечная метка и там длина больше двух команд. Рано или поздно там прийдет XT, то отчистить rxCheck от лишнего чтобы избежать склеивания не по местам как и сдвигов. А уже от теоретически целой строки вырезать целые команды и исполнять.
С буферами я разберусь. Просто сейчас чтобы мне было ясно как все работает в отладке, с создаю уровневые "мертвые переменные" которые мониторю через порт.
У меня две странности, может вы подскажите почему так.
1 - не пойму откуда в порте на приеме появляется знак одиночной кавычки.
У меня на входе такая строка выходит:
'TX1237XTTX1238XT'TX1239XTTX1240XT'TX1241XTTX1242XT'TX1243XTTX1244XT'TX1245XTTX1246XT'
А вот что в начале
Получается эта ЗАкавычка остается вконце. Зачем, почему откуда?
Я-то могу их отфильровать. Но откуда они? передача идет строго по тому что я делал (мониторил) Потерь кстати не заметно.
2 - В мониторе видно когда работает задержка на прием (50мс) но она отрабатывает 13 раз. Я ранее писал что почему-то при передачи 8-ми символьной команды в serial.available () фигурируют числа больше 8-ми. было 10-11-12и13 максимум.
Но у нас 8 символов. Почему 13 раз отрабатывает задержка на прием?!
Алгоритм приема и разбора такой:
закоменти это
if
(currentMillis - previousMillis >= interval) {
if
(softSerial.available() >= 8){
должно происходить максимально часто, т.е. крутиться в лупе постоянно.и это delay(50); - зачем это и кому это нужно?
//если что-то есть в буфере, даем время быть принятым. - если в буфере что-то есть, то нужно максимально быстро принять и обработать потому, что в буфере сериала уже что-то есть и ждёт обработки.
кавычка ' может быть числом 39 http://book.itep.ru/10/ascii.htm
>= 8 почему 8-мь, а не иное число?
8 потому что длина команды 8 символов и нам надо не меньше
Вот почему именно этот символ прорывается так и не ясно.
Но. Убрал посекундную проверку. По вашему совету воступил в общем убрал задержку.
И вернулся к тому с чего начал. Тестированием интервала отправки понял что от 1000 до 50 мс паузы достаточно для быстрой передачи. НО если меньше, идут страшные потери. Так что 50мс - минимальная задержка для успешной передачи.
ВОт только теперь думаю, Этот код в модуле [3] и он только принимает команды. А вот как быть с модулем [2] если он выполняет достаточно сложную программу (считыает входы с мультиплексора, контролирует время и отправляет команды управления) ему помимо этого тоже слушать надо порт от пульта управления [1] успеет ли он?!
А то получается работал над чем-то и пришел к тому с чего начинал. Да, я понял что проблема был в том что МК не успевал хватать.
Получился шум на ровном месте?!
Сейчас сделаю все на рабочий лад, и посмотрим что сейчас будет между всеми тремя платками.
ХА! Я в начале функции чтения и разбора команд вычищал все переменные в том числе и rxCheck думал от глюков избавлюсь, а получается сам себе гемор создал. Вычищать rxCheck надо когда в нем полноценная команда. Отследил монитором с выполнением основной команды:
Видно же что толко на четвертом цикле дописался конец, а когда есть конец XT мы можем очистить переменную переведя содержимое rxCheck в другую надежную строковую переменную где ничего не побито.
Так что 50мс - минимальная задержка для успешной передачи.
слушай, ну ты же тестируешь код не передачи, а приёмки - о какой неуспешной передаче ты постоянно пишешь?
ты не можешь принять, а не передать.
Ну да. С терминами запарился)
Правильнее написать: 50мс - минимальное время интервала между командами для успешного приема. (учитывая размер команды)
ок. тогда измени алгоритм приёма - собирай с сериала по одному символу в строку и по достижению условия:чего там у тебя - длина строки, метка начала нужной тебе инфы, метка конца конфы/строки, что то делай со строкой.
по сути тебе не нужно тормозить код - наоборот, скорость обработки принимаемой инфы является залогом успеха.
посмотри, как здесь сделано #76 - там digiusb, но суть сериала - когда в буфере появляется символ, с ним что-то делается - собирается в строку, а строка, затем сравнивается для выполнения нужной команды, затем строка обнуляется и начинает строиться с начала.
Минимально время между приемом команд - 50мс, дальше программа тупо игнорит входящие данняе, пока не знаю почему.
Все остльные мелочи вроде страннных символов в приеме, я создал себе сам по невнимательности.
----
Благодарю всех кто отозвался и подсказал как стоит сделать.