GPS парсер.
- Войдите на сайт для отправки комментариев
Приобрел я себе ЖПС приемник BN-880 с тем, чтоб получить 5 Гц апдейт рейт координат в моем роботе.
Уперся я в проблему, что с Tinygps библиотекой работать модуль не хочет, т.к. бодрейт должен быть не менее 38400, да и места она занимает много.
Пришлось писать парсер самому. И тут я уперся в проблему синхронизации работы моего блока управления с этими самыми пятью герцами. И, как человек действия, сначала делаю потом думаю, сделал устройство общения с ЖПС приемником. Это отдельное утройство на аруино мини, которое считывает строки NMEA, парсит их, полученные координаты в виде 19байтной пачки скидывает на блок управленияпо запросу по i2c.
Все работает ОК, но принимая во внимание , что я тот еще программист, прошу сообщество глянуть в мой код, и подвергнуть его жесткой критике. Цель - сделать код лучше, а меня - продвинутее :) Добрый глум в сочетании с дельными советами - приветствуется! Всем бобра!
#include <arduino.h> #include <Wire.h> //#include <TinyGPS.h> #include <SoftwareSerial.h> #define SLAVE_ADDRESS 4 #define DATA_LENGTH 19 #define DELAY 1 //TinyGPS gps; SoftwareSerial ss(2, 4); byte buffer[DATA_LENGTH]; //сюда запихиваем переменные для отправки по i2c float flat, flon, current_course, speed; unsigned int altitude; bool fixed_data = 0; //4 переменные float + 1 unsigned int + 1 bool = 19байт byte text[100]; //сюда считываем NMEA строку для последующего парсинга bool new_data = 0; void setup() { Serial.begin(38400); ss.begin(38400); //GPS сконфигурирован под 38400, выдает только RMC и GGA Wire.begin(SLAVE_ADDRESS); Wire.onRequest(slaveTX); } void loop() { if (ss.available()) listen_uart(); if(new_data)data_to_buffer(); } void slaveTX() { //передаем buffer на мастер по i2c Wire.write(buffer, DATA_LENGTH); } void data_to_buffer() { //копируем переменные в buffer byte *ptr_flat = (byte *)(&flat); byte *ptr_flon = (byte *)(&flon); byte *ptr_speed = (byte *)(&speed); byte *ptr_current_course = (byte *)(¤t_course); byte *ptr_altitude = (byte *)(&altitude); for (char i = 0; i < 4; ++i) { buffer[i] = *(ptr_flat + i); buffer[i + 4] = *(ptr_flon + i); buffer[i + 8] = *(ptr_speed + i); buffer[i + 12] = *(ptr_current_course + i); } buffer[16] = *ptr_altitude; buffer[17] = *(ptr_altitude + 1); buffer[18] = fixed_data; new_data = 0; } void listen_uart() { if (ss.available() > 100) { while (ss.available())ss.read(); return; }//если больше 100байт - очищаем все int i = 0; byte buff; do { buff = ss.read(); } while (buff != 36); //читаем все до знака $ delay(1); do { if(i>99)return; text[i++] = ss.read(); delayMicroseconds(100);//ожидание прихода следующего байта } while (text[i - 1] != 42); //заполняем text[]прока не встретим * ss.read(); ss.read();//ложим болт на контрольную сумму //for ( int k = 0; k < (i - 1); k++) { //Serial.print((char)text[k]); // Serial.print(" "); } Serial.println(); new_data = 1; if (text[4] == 67) parse_rmc(); if (text[4] == 65) parse_gga(); } void parse_rmc() { byte count = 0; byte i = 0; do { if (text[i++] == 44) count++; } while (count < 2); //перебираем ячейки text до второй запятой включительно text[i] == 65 ? fixed_data = 1 : fixed_data = 0; Serial.print("fixed_data = "); Serial.println(fixed_data); do { if (text[i++] == 44) count++; } while (count < 3); //перебираем ячейки text до третьей запятой включительно flat = parse_coords(i); Serial.print("flat = "); Serial.println(flat, 6); do { if (text[i++] == 44) count++; } while (count < 5); flon = parse_coords(i + 1); Serial.print("flon = "); Serial.println(flon, 6); do { if (text[i++] == 44) count++; } while (count < 7); speed = parse_speed(i); speed = speed * 1.852; Serial.print("speed = "); Serial.println(speed); do { if (text[i++] == 44) count++; } while (count < 8); current_course = parse_speed(i); Serial.print("current_course = "); Serial.println(current_course); } void parse_gga() { byte count = 0; byte i = 0; do { if (text[i++] == 44) count++; } while (count < 9); int n = -1; do { n++; } while (text[i++] != 46); i = i - n - 1; altitude = parse_value(i, n); //for( byte l = 0; l<n; l++)altitude+=(text[l+i]-48)*(unsigned int)(pow(10, n-l-1)+0.5); Serial.println(altitude); } long parse_value(byte i, byte n) { long value = 0; for (byte j = 0; j < n; j++) value = value * 10 + (text[i + j] - '0'); return value; } float parse_coords( byte i) { float coord = 0; coord = parse_value(i, 2); float minutes = parse_value(i + 2, 2); float after_dot = parse_value(i + 5, 4); minutes = minutes + after_dot / 10000.; coord = coord + minutes / 60.; return coord; } float parse_speed(byte i) { if (text[i] == 44)return 0; int n = -1; float res = 0; byte j = i; do { n++; } while (text[j++] != 46); res = parse_value(i, n); res = res + (text[i + n + 1] - '0') / 10.; return res; }
Прошу не обижаться., но текст - классический "антимотиватор". :) Если у вас и так все работает, нафига мне тратить время на улучшение вашего кода? :) тем более что вы даже приблизительно не обозначили, в какую сторону двигаться? - уменьшить размер скетча? освободить память переменных?увеличить скорость работы?
Код открывать не стал.
С уважением к автору.
Если у вас и так все работает, нафига мне тратить время на улучшение вашего кода? :)
С уважением.
В первую очередь размер интересен, т.к. одной из причин выноса парсера на отдельное устройство стал дифицит памяти. Но если еще попутно и скорость возрастет, то вау! :)
Какой памяти, сколько надо... Не инициализируйте Serial - вот вам уже память. Избавьтесь от float - вот вам и память и скорость.
А "делать красиво" - это как перед костром сидеть. До бесконечности можно. Ну, или пока с голоду набок не завалишься.
ну что, нормальный код . Сойдет для сельской местности...^)
Вот вам первое улучшение - так покороче будет.
А дальше пусть еще кто-нить выскажется
А вообще правильнее было бы обьединить все ваши данные - flat, flon, altitude и тд - в структуру и работать с ней как единым блоком. Тогда не надо было бы ничего копировать из одного места памяти в другое, буфер был бы не нужен, отправляли бы их по i2c непосредственно, а на другой стороне не надо было бы парсить отдельные байты
прошу сообщество глянуть в мой код, и подвергнуть его жесткой критике
Ну, давайте начнём, помолясь. Только … это … Вы сами просили :)
1. С буфером для передачи данных так работать нельзя
Вот смотрите. Функция slaveX вызывается по прерыванию, т.е. она может быть вызвана в любой момент. Теперь преставьте, что у Вас работает функция data_to_buffer. Часть данных она уже успела скопировать, а часть нет. Более того, она находится в процессе копирования конкретного числа (т.е. часть числа скопировалось, а часть ещё нет) и в этот самый момент прилетает прерывание. Что происходит? А ничего. Функция slaveTX радостно передаёт своему мастеру «полускопированные» данные. Мастер их получает этот сивокобылий бред и долго удивляется что курит Ваш скетч и какая зараза ему это продала. Проблема понятна?
2. Ну, кто так копирует данные в буфер?
Что это за функция data_to_buffer такая в полскетча? Вам достаточно А) удалить строку 13; Б) вместо строк 15-18 описать структуру типа такой
далее В) в строке 40 прописать
далее Г) везде, где Вы используете переменные flat, flon, current_course, speed, altitude и fixed_data заменить их на buffer.flat, buffer.flon, buffer.current_course, buffer.speed, buffer.altitude и buffer.fixed_data соответственно. Эти переменные даже не надо выискивать по тексту, просто запустите компиляцию, и компилятор радостно начнёт тыкать Вас в них носом, т.к. после нашего действия (Б) они окажутся неописанными.
Когда Вы всё это проделаете, Ваша функция data_to_buffer перестала быть нужной вовсе. Можете выбрасывать и её саму и её вызов, но не всё так гладко, см. ниже.
Дело в том, что мы решили проблему №2 – убрали ужасную функцию и сократили размер и скорость работы программы, но мы не решили проблему №1. По-прежнему прерывание может прилететь в момент, когда наш новый буфер в раздрае со всеми вытекающими.
Решать её можно 100500 способами. Самый простой, это снова завести буфер для передачи. Описать его надо после описания структуры и выглядеть он может так:
Тогда нынешнюю строку 40 запишем как
и вернём на место вызов функции data_to_buffer. Сама же функция data_to_buffer теперь будет вовсе не такой ужасной, как была у Вас, а всего лишь:
Вот и все дела. Скобки cli() - sei() предохраняют нас то того, чтобы отдать мастеру полуготовый буфер. Проблема №1 теперь тоже решена.
---------------
От 63-её строки и ниже я не смотрел. Поправьте пока то, о чём я написал, тогда и посмотрим.
ЕвгенийП - идеи те же, только лень было расписывать так подробно :)
Дополнительный плюс от структуры - если на приемной стороне описать такую же, можно будет весь этот зоопарк данных и принимать одним движением, как и отправляем...
Это само собой - на той стороне также. Но здесь более критична проблема №1, а она от структуры не зависит. А сломать может всю систему на раз.
Други, спасибо, уже ушел курить структуры и прочие неизвестные мне доселе memcpy, cli(), sei()
1. С буфером для передачи данных так работать нельзя
Вот смотрите. Функция slaveX вызывается по прерыванию, т.е. она может быть вызвана в любой момент. Теперь преставьте, что у Вас работает функция data_to_buffer. Часть данных она уже успела скопировать, а часть нет. Более того, она находится в процессе копирования конкретного числа (т.е. часть числа скопировалось, а часть ещё нет) и в этот самый момент прилетает прерывание. Что происходит? А ничего. Функция slaveTX радостно передаёт своему мастеру «полускопированные» данные. Мастер их получает этот сивокобылий бред и долго удивляется что курит Ваш скетч и какая зараза ему это продала. Проблема понятна?
Я думал над этим. Увы лучшее , что я придумал, Вы увидили в моем коде. Ничего ужастного не происходит, проверено. Но сейчас не об этом. Мне нужна феншуйность , Вы мне показали куда идти. Спасибо, ушел. :)
Но I`ll be back! :)
Ничего ужастного не происходит, проверено.
Нет, не проверено. Для того, чтобы что-то ужасное произошло, нужно чтобы точно совпало время прерывания и работы с буффером. Это может происходить раз в неделю или в месяц, но происходить обязательно будет.
Нет, не проверено. Для того, чтобы что-то ужасное произошло, нужно чтобы точно совпало время прерывания и работы с буффером. Это может происходить раз в неделю или в месяц, но происходить обязательно будет.
пусть в худшем случае у нас функция slaveTX вызовется прямо во время копирования данных в buffer. Тогда часть буфера отправится с обновленными данными, а часть - со старыми. В силу небольшой скорости моего робота, изменение его координат за 0,2 секунды большим не будет, проэтому просохатить одно обновление координат ( одной или нескольких) вполне допустимо без печальных последствий, ДАЖЕ если прерывание произошло посередине копирования переменной. Возможно ли прерывание посередине копирования одного байта , что повалило бы целостность переменнойб я не знаю.
Возможно, мои размышления были ошибочными. Всегда рад исправиться!
Это само собой - на той стороне также. Но здесь более критична проблема №1, а она от структуры не зависит. А сломать может всю систему на раз.
К стыду. я проблему №1 даже не заметил. Для меня это дело новое, я с неатомарными операциями имел дело только в базах данных.
Не очень понимаю, как прерывание может помешать отправке константного (в масштабе времени передачи) буфера? Каазалось бы - ну прилетит в момент отправки буфера прерывание, отправка прервется на десяток микросекунд, а потом возобновится снова, разве не так? Вот если бы буфер за это время изменился - возникла бы проблема, а так не понимаю, в чем опасность.
пусть в худшем случае у нас функция slaveTX вызовется прямо во время копирования данных в buffer.
5N62V, это предположение абсолютно фантастическое и потому дальше можно не читать. В вашей программе функция slaveTX и функция заполнения буфера вызываются в одном потоке, то есть строго последовательно и совпасть никак не могут.
5N62V, это предположение абсолютно фантастическое и потому дальше можно не читать. В вашей программе функция slaveTX и функция заполнения буфера вызываются в одном потоке, то есть строго последовательно и совпасть никак не могут.
Не очень понимаю, как прерывание может помешать отправке константного (в масштабе времени передачи) буфера? Каазалось бы - ну прилетит в момент отправки буфера прерывание, отправка прервется на десяток микросекунд, а потом возобновится снова, разве не так? Вот если бы буфер за это время изменился - возникла бы проблема, а так не понимаю, в чем опасность.
От 63-её строки и ниже я не смотрел. Поправьте пока то, о чём я написал, тогда и посмотрим.
Код теперь занимает на 118 байт меньше. Все работает! :))
Только я в объявлении структуры bool fixed_data = 0; заменил на byte.
Избавьтесь от float - вот вам и память и скорость.
Обязательно этим займусь, но позже. Просто на главном блоке управления тоже работала TinyGPS.h , пока я не стал менять приемник ЖПС. Теперь я ее выкинул, выгрызя из нее две интересующие меня функции : distanceBetween и directionTo . А они сами float, и аргументы у них 4 штуки float, вобщем надо будет засесть за математику, когда соберусь с духом.
Не очень понимаю, как прерывание может помешать отправке константного (в масштабе времени передачи) буфера? Каазалось бы - ну прилетит в момент отправки буфера прерывание, отправка прервется на десяток микросекунд, а потом возобновится снова, разве не так? Вот если бы буфер за это время изменился - возникла бы проблема, а так не понимаю, в чем опасность.
Беда не в том, что прерывание прилетит во время отправки. Там все наоборот - отправка делается по прерыванию.
теперь представим себе было у нас честное целое число 255 (b0000000011111111). Теперь оно возросло на 1 и стало 256 (b0000000100000000). пошла перезапись буфера. Млаший байт успел перезаписаться, а старший не успел - прилетело прерывание и отправило буфер (с этим числом) мастеру. Что получит мастер? Совместим младший байт нового число и старший байт старого - правильно - 0 он получит. Нехилый такой скачок получился.
Ну, Вам виднее к чему критичны Ваши алгоритмы, а к чему - нет. Посмотрите пост #17, я там привёл реалистичный пример большого скачка за раз.
Ну, Вам виднее к чему критичны Ваши алгоритмы, а к чему - нет. Посмотрите пост #17, я там привёл реалистичный пример большого скачка за раз.
Пожалуй, что работу с UART я бы изобразил как-то так (пишу по памяти, у ЕвгенияП наверняка кровь из глаз пойдет):
Справитесь с неблокирующим чтением порта - можно будет перейти к парсингу с использованием меньшего буфера. Ну и так, итерационно, пока от килограмма кода у вас не останется сияющая бриллиантовая однобайтовая инструкция, которая будет делать ВСЁ за один такт МК.
Беда не в том, что прерывание прилетит во время отправки. Там все наоборот - отправка делается по прерыванию.
Понятно. Спасибо за обьяснения.
Решение на поверхности - мастером должен быть передатчик :), тогда никакие половинки буфера не полетят :)
Дополнительный плюс от структуры - если на приемной стороне описать такую же, можно будет весь этот зоопарк данных и принимать одним движением, как и отправляем...
for (char i = 0; i<sizeof(coords); i++) *(ptr_coords+i) = Wire.read();
Вопрос: что вы наловите в coords и куда поедет ваш боевой человекоподобный робот, eсли i2c-передатчик зависнет или отвлечется в процессе передачи (представьте, что в буфер Wire успела попасть только треть посылки).
Вопрос в каком контексте? Реализован ли у меня резервный канал приема координат, или знаком ли я с регулярным отвлечением передатчика i2c от выполнения своих обязанностей? По второму - нет , не знаком. Буду благодарен, если просветите. Вообще-то у меня блок управления общается ещё с пятью устройствами по этой же шине. Пока траблов не замечалось, хотя, как выясняется, организовываю я обмен далеко не наилучшим образом, мягко говоря. Касательно первого- да, дублирующий канал есть, пока только в железе, общаться будут по uart.
А, я понял, Вы про то, что в цикле тупо считывается 19 байт без проверки их наличия. Ну да, надо вставить проверку наличия именно 19 байт, если в течении какого-то времени их нет, то не принимаем и очищаем буфер. Спасибо!
Вопрос в контексте доверия к принятым данным. А точнее - способа реализации приема при помощи for(). Загляните в Wire::read() - там несложно, представьте что будет, если в буфер Wire не успели попасть данные целиком, а вы уже их начали запрашивать этой функцией.