Парсим строки с UART
- Войдите на сайт для отправки комментариев
Вс, 10/09/2017 - 18:59
Всем добра!
История , наверное, покажется кому-нить смешной, но у меня ща башка лопнет. :))
Нужно принимать поток данных и парсить их. С приемом данных все ок, но тут я наткнулся на хню, которая поломала мое представление о мире. Скорее всего из-за недостатка фундаментальных знаний.
Код:
byte text[] = {51, 53, 49, 46, 55}; // соответствует 351.7 unsigned int result = 0; void setup() { Serial.begin(38400); } void loop() { Serial.println(); int n = -1; do { // Определяем сколько знаков содержится до точки (код 46) n++; } while (text[n] != 46); Serial.print("n = "); Serial.println(n); parse_line(n); } void parse_line(int n) { double temp = 0; //пытаемся получить значение for (char k = 0; k < n; k++) { // первое число умножаем на 100, второе на 10, третье на 1 temp += (text[k] - 48) * pow(10., (float)(n - 1 - k)); // и накапливаем это в temp Serial.println(temp); } result =(unsigned int)temp; Serial.println(result); }
в массиве записано число 351.7 , мне достаточно считать 3 , 5 и 1 и перевести это в число 351 и засунуть в переменную. И все вроде бы работает до момента передачи значения из переменной double temp в переменную unsigned int result. Вместо 351 я получаю 350...
n = 3
300.00
350.00
351.00
350
Люди добрые, че за фигня такая? Первый раз с таким сталкиваюсь... Буду признателе за конструктивное наставление на путь истинный! ;)
обьясните мне, нафига переменная temp обьявлена double, а выражение (n-k-1), в котором все части вообще байты - как float?
Не говоря уж о том, что для извлечения числа из строки есть куда более простое и менее затратное решение - функция atoi
Ну, я не знал куда уже кидаться, и поэтому явно поприводил аргументы и результат к тому, что хочет функция pow.
Взялся все писать врукопашную, видимо переоценил свои силы. Но ведь код по идее долже работать!
Думаю, это следствие беспорядка с типами переменных в вашей программе. Для проверки попробуйте написать строчку 27 вот так:
result =(unsigned
int
)temp+0.5;
Подозреваю, что после всех Ваших операций со степенями получается что-нибудь типа 350.9999999. println выводит это как 351.00 а приведение к целому округляет вниз. Получается 350.
kalapanga - поддерживаю, коллега :) См. мое сообщение #2
так не работает, а вот так
result =(unsigned
int
)(temp+0.5);
работает! И даже +0.1 достаточно.
Спасибо! Вы оказались правы. Да благословит вас компилятор! Ну или вы его! :)
Можно и я влезу?
Передатчик. Выдает пакеты формата {38 0 s L 3000}
С передатчиком проблем нет, все четко и стабильно (отслеживаю с ноутбука с RS485-USB адаптером).
Приемник:
Пакеты принимаются в том же виде, неподходящие отсеиваются.
А вот с разбором принятого пакета странности: первые минуту-две работает все корректно, а потом парсинг перестает работать.
Вот лог где это видно (строка 14): sscanf возвращает уже 4 обработанных элемента вместо 5.
Может где-то происходит выход за границы? Второй день не могу понять причину.
Tomasina - с таким обьемным вопросом лучше новую начать.
Тем более что этот вопрос я уже где-то видел... не дублируйте темы, это читерство :)
Спасибо! Вы оказались правы. Да благословит вас компилятор! Ну или вы его! :)
Это "костыль", в окончательном коде советую не оставлять. Лучше наведите порядок типами переменных - и проблема исчезнет сама.
Да не я тут развел болото с типами переменных. Неявное приведение работало точно так же. Если уж по феншую, то надо от pow отказываться, на сколько я понимаю.
Ксли уж хочется преобразовывать строку в число самостоятелно, лучше пользоваться более щадящими методаим:
Это "костыль", в окончательном коде советую не оставлять. Лучше наведите порядок типами переменных - и проблема исчезнет сама.
Но я прислушался к Вашему совету, и вместо pow, которая сожрала у меня столько времени сегодня, написал простую функцию возведения 10 в степень n
Ксли уж хочется преобразовывать строку в число самостоятелно, лучше пользоваться более щадящими методаим:
И действительно - этот вариант куда эллегантнее! :)
А по моему вопросу нет идей?
Tomasina
буфер sbuff никак не защищен от переполнения. И скорее всего. в какой-то момент программа выходит за границы массива
Но ведь в строке 34 есть контроль длины sbuff.
Контроль длины - есть, а защиты от переполнения - нет.
чой-то не понимаю. При превышении длины в 32 символа мы из этой функции вылетаем, если в Serial еще что-то есть, то заново попадаем в эту функцию, а буфер уже пустой, т.к. инициализируется заново и заполняется снова.
Как возникает переполнение?
чой-то не понимаю. При превышении длины в 32 символа мы из этой функции вылетаем
А если длина ровно 32 символа, то не вылетаем, а вместо этого прибавляем к буферу еще один = '\0'
А вот опять не выходит. Буфер теперь не превышает 31, но парсинг снова срывается.
Изменения:
Вот лог переходного момента:
Дальше sscanf всегда обрабатывает только 3 значения и завершает работу.
здесь #76 смотри, как я посимвольно принимаю и собираю в строку. *считай, что у тебя не DigiUSB, а сериал - сути не меняет.
что вижу у тебя дурного - делаи, циклы.
если // посимвольно читаем, то посимвольно читаем, а не хернёй занимаемся.
Томасина, буфер вы опять неверно обрабатываете... не везет вам с буфером... или ему с вами. Но к ошибке это отношения не имеет, так что оставляю это вам как домашнее задание.
Вопрос в другом - с чего вы взялись использовать для этой задачи функцию sscanf()? Почему не пошли простым путем типа strtok()
Это очень непростая функция. С одного из англоязычных форумов - "sscanf()
sscanf()
is as simple for a beginner as diving at 1000 meters deep for someone who can't swim" - " функция sscanf() примерно настолько же "легка" для новичка, как погружение на 1000 метров для того, кто не умеет плавать".В частности, sscanf() вообще-то ожидает в качестве параметров адреса переменных, а не сами переменные - как то так:
sscanf(line,"(%d,%d,%d,%c)",&x,&y,&level,&type);
if
(len > bufferSize - 1 || millis() - t > 1000)
// контролируем переполнение буфера
О_О
Томасина, последнее замечание (насчет ссылок на переменные) - снимается, не заметил сразу, что у вас все переменные - символьные массивы.
Каких-то явных недоработок в коде я не вижу. Правда, функцию sscanf() знаю довольно поверхностно, а она, как я уже сказал, с сюрпризами.
Ставлю на то, что пакет, на котором происходит сбой - особенный. Для проверки предлагаю добавить вывод в сериал содержимого буфера в HEX-кодах
Ставлю на то, что пакет, на котором происходит сбой - особенный.
ага - проклятый
Не такой уж он и простой. Строку получил, начинаю делить:
Буфер:
После разбиения получаю указатели на его фрагменты, которые красиво выводятся в Serial:
Но как теперь получить значения, которыми можно пользоваться, а не указатели? Такой код:
возвращает 0 вместо 38.
так
byte sid = atoi(ptrinput);
Пробовал. Тоже 0 получается.
Хм... а со вторым значением нормально получается. Выходит, надо у изначальной строки отрезать первый символ.
Вот так работает, но нет ли утечек памяти?
третью строку запишите так
и тогда весьма уродливая операция в первой строке будет не нужна.
А вот как раз значения state и cmd правильнее копировать из исходного буфера командой strcpy(), а не приравнивать ссылки
Изучал этот вопрос и обнаружил в библиотеке от Megunolink (доступна из установки менеджера пакетов) отличный парсер, и весь набор для работы с внешними командами. Из встроенного примера, HandleSerialCommands, все становится понятно. Надо только обратить внимание, в конце принимаемых команд, должен быть символ перевода каретки.
p.s.
Здесь есть документация по изменению числа команд, буфера, аргументов.