Протокол для обмена по блютус
- Войдите на сайт для отправки комментариев
Втр, 28/11/2017 - 23:13
Всем добрый вечер.
Продолжаю изучение возможностей arguing . На очереди обмен данными по Блютус. Посмотрел различные примеры . В основном все сводятся к вкл/выключи светодиода.
Задумался над вопросом: множество людей используют Блютус модули для связи с телефонами но не могу поверить, что так и не разработали библиотеку для этих целей. Подскажите пожалуйста названия библиотек которые при помощи которых можно организовать обмен данными и командами между ардуино и андроид телефоном.
Если таковых нет то где можно почитать о подходящем для таких целей протоколе? Понимаю, что можно и самому придумать свой протокол обмена, но очень не хочется велосипед изобретать.
Извиняюсь за ошибки. С телефона пишу.
Добрый!
Догадываюсь, что речь не столько про библиотеки Ардуино (там Блютус он и есть блютус), а про ответную часть на телефоне.
Таких штук много разных. От больших и красивых (типа Blynk - там и библиотеки имеются и прикладуха для телефона) до простых и убогих (типа KvushCo). Все можно поискать в ГуглПлей.
Какая цель? Что будет в результате?
П.
В приложение на телефоне нужно считать 20 переменных разного типа. Затем с телефона нужно записать новые значения переменных на ардуино. Запись и чтение должны идти не зависимо. Как бы в параллельных потоках. Мне это представляется примерно так:
< стартовая сигнатура> <код посылки> < данные> < конец посылки>
Или похожее. Т.е. Протокол прикладного уровня. Можно самому придумать, но думаю что уже изобрели этот велосипед.
.
Конечно изобрели давно:
<header><length><payload><CRC>
или
<header><fixed length payload><CRC>
или
<STX><payload><CRC><ETX>
или
...
Да, я именно об этом и спрашиваю. Есть ли готовые библиотеки на данную тему?
Обмен данными и командами по Блютус используется широко. Вот и думаю, что уже разработаны для этого библиотеки. Приём и передача данных - асинхронная.
Подумал, что придется сделать свой простенький протокол типа:
В 4 байта данных войдет byte, int, int long
Float смысла нет передавать так как с контроллера в любом случае получаем int с аналоговых входов. Преобразование к физическому значению можно сделать и на стороне компьютера/смартфона
// Передача байта void Bus4::send(uint8_t reg, uint8_t value) { Bus4::sendBuf[0] = START_SIGN; Bus4::sendBuf[1] = CMD_WRITE8; Bus4::sendBuf[2] = reg; Bus4::sendBuf[3] = 0; Bus4::sendBuf[4] = 0; Bus4::sendBuf[5] = 0; Bus4::sendBuf[6] = value; Bus4::sendBuf[7] = CMD_WRITE8 ^ reg ^ value; sendLine() } // Передача uint16_t void Bus4::send(uint8_t reg, uint16_t value) { Bus4::sendBuf[0] = START_SIGN; Bus4::sendBuf[1] = CMD_WRITE16; Bus4::sendBuf[2] = reg; Bus4::sendBuf[3] = 0; Bus4::sendBuf[4] = 0; Bus4::sendBuf[5] = (value & 0xff00) >> 8; Bus4::sendBuf[6] = value & 0x00ff; Bus4::sendBuf[7] = CMD_WRITE8 ^ reg ^ Bus4::sendBuf[5] ^ Bus4::sendBuf[6]; sendLine() } // Передача uint32_t void Bus4::send(uint8_t reg, uint32_t value) { Bus4::sendBuf[0] = START_SIGN; Bus4::sendBuf[1] = CMD_WRITE32; Bus4::sendBuf[2] = reg; Bus4::sendBuf[3] = (value & 0xff000000) >> 24; Bus4::sendBuf[4] = (value & 0x00ff0000) >> 16; Bus4::sendBuf[5] = (value & 0x0000ff00) >> 8; Bus4::sendBuf[6] = value & 0x000000ff; Bus4::sendBuf[7] = CMD_WRITE8 ^ reg ^ Bus4::sendBuf[3] ^ Bus4::sendBuf[4] ^ Bus4::sendBuf[5] ^ Bus4::sendBuf[6]; sendLine() } void sendLine() { for (int i=0; i < 8; i++) { Serial.write(Bus::sendBuf[i]); } }Подумал, что придется сделать свой простенький протокол типа:
Преамбулу (STRT_HEAD) я бы сделал из нескольких байт. Мало ли - приемник будет по прерыванию просыпаться - не успеет схватить начало. В идеале, конечно, преамбула должна иметь значение, которое не встречается в пакете далее.
[quote=Smith2007]
0x13 - Запись uint32_t D3, D2, D1, D0
В поле Registr можно указывать порядковый номер переменной (номер регистра = переменная)
Удлинять посылку не хочется. От переменной длины пакета тоже решил отказаться учитывая, что максимальная размерность 4 байта. Делать длинной стартовую сигнатуру мне кажется нет смысла. И то, что она совпадёт с данными мне кажется не критичным. Я думаю, что при чтении буду тупо ждать пока в буфере не наберется 8 байт. Если сигнатуры в первом байте не найду или CFC не совпадёт — просто сбрасываю весь пакет и жду новый. Передающая сторона делает паузы между посылками пакетов не менее 10 см.
кстати вопрос: сколько миллисекунд нужно, чтобы отправить один байт в uart?
serial.write(byte)
Стоит ли городить огород с подтверждением доставки пакета.
Modbus я смотрел. За основу его пакет и взял. Но сам сам протокол будет избыточен и тяжеловат для простой задачи. Постоянные опросы и жесткие тайминг.... при работе на смартфоне боюсь много граблей с ним будет. Проще что—то легкое сделать
по структуре и чтении как массив — хорошая идея. У меня только пока знаний маловато. Учусь. Но саму идею понял. Я ещё думал массив и, byte, int, long int в union включить и тогда можно просто обращаться к разным типам сразу. Но не соображу как массив указать. Или массив в union не включать, а просто обращаться по ссылке как к массиву по указанному Вами примеру
можно ли в структуру включить union?
Union xx {
Byte bI;
uint w;
uint long l;
}
struct sss {
byte bbb
xxx ccc
}
Удлинять посылку не хочется. От переменной длины пакета тоже решил отказаться учитывая, что максимальная размерность 4 байта.
В данном случае вы обмениваете надёжность на ёмкость. Хотите более надёжный старт - увеличьте хидер. Экономите на спичках и уверены в том, что всегда будете получать пакет с самого начала - уменьшайте. Хотя, мне кажется, что +два-три байта при паузе в 10см вообще никакой погоды не сделают.
serial.write(byte)
А сколько надо, чтобы добраться до городу Парижу?
На сколько канал инициализируете, с такой скоростью и будете передавать: https://ru.wikipedia.org/wiki/%D0%91%D0%B8%D1%82_%D0%B2_%D1%81%D0%B5%D0%...
Нужно ли подтверждение - вопрос как философский, так и прикладной. Даже в IP-сетях есть два вида пакетов - с подтверждением и без оного. Нужна гарантия доставки - делаете подтверждение. Не нужна или алгоритм предполагает получение ответа на запрос в течении N sec с перепосылкой в случае необходимости - не делаете подтверждения.
С массивами и юнионами тот же подход: данные допускают передачу с нелимитируемым разрывом по времени - делаете юнион и мелкие пакеты. Необходимо принимать всё пачкой - длинный пакет и структура без юнионов. Впрочем, это, с моей сторны, как вы понимаете, размышления об абстрактных данных.
Спасибо Вам за консультацию.
По времени выполнения serial.write(byte) ....
Я понимаю, что uart будет передавать с той скоростью, что укажу. Но это же не значит, что время выполнения процедуры serial.write(byte) такое же. Там же есть буферизация. Или я чего путаю?
Я предполагал, что в канал (в провода) пакет пойдет бит за битом с указанной при инициализации скорости (9600, 115200 и т.д.)
Но для записи данных в uart - другая скорость.
Вопросы возникают потому, что хочу вникнуть как прием-передачу организовать учитывая таймауты и прочие временные критерии.
зы. На первых порах постараюсь реализовать обмен без подтверждения доставки. Это должно быть проще. А учитывая мой скромный опыт в программировании - это еще и необходимо :)
Я понимаю, что uart будет передавать с той скоростью, что укажу. Но это же не значит, что время выполнения процедуры serial.write(byte) такое же. Там же есть буферизация. Или я чего путаю?
Я предполагал, что в канал (в провода) пакет пойдет бит за битом с указанной при инициализации скорости (9600, 115200 и т.д.)
Да, указанная скорость - это скорость самого нижнего, физического, уровня, считайте - наивысшая. Далее будут накручиваться уже верхние программные слои, дающие дополнительную задержку. Но если вы копировать-конвертировать туда-сюда байты по массивам-переменным перед передачей не станете, а прямо во write() подадите указатель на байтовый массив (структуру), то оверхед будет минимальный. Можно сказать - незаметный.
Вас же интересует скорость появления данных с другой стороны трубы, а не записи в память МК, так? Потому что МК на фоне не будет ничего отправлять, он однопоточный. Хотя, конечно, что там в конкретном БТ-модуле накручено - непонятно.
PS:http://www.radiokot.ru/forum/viewtopic.php?p=1298712#p1298712
Поправьте меня пожалуйста если я ошибся....
при скорости 9600 бод , 1 бит информации передаётся за 1/9600 = 104 мкС
т.е на передачу байта необходимо 833 мкс . Тогда на передачу 8 байт потребуется 6,4 мс. С учетом накладных расходов это время возможно достигнет 7 мс
при скорости 115200 на передачу 8 байт потребуется около 500 МКС или 0,5 мс
значит uart должен в фоне все отправлять ( аппаратно) иначе слишком долго ждать надо будет
Никому UART ничего не должен и в фоне МК отправлять ничего не будет. К тому же - почитайте по ссылке пост.
Так что, очевидно, вам придется откорректировать свои планы или переходить на уровень программирования "Бог".
Ну мои планы не такие уж амбициозные. Обеспечить передачу данных по Блютус интерфейсу. Надеюсь для этой цели не нужен уровень Бог ;)
получается нет ни каких буферов в uart и модуле hc 05.
Провел некоторые исследования
uint32_t t1 = micros(); Serial.write("123456789012345"); uint32_t t2 = micros(); Serial.println(); Serial.print(" t= "); Serial.println(t2 - t1);По кнопке вызывается.
Результат: от скорости порта, передача в процедуру Serial.print/Serial.write не меняется.
Вероятно это работа буфера драйвера Serial
Таким образом я отправил кадр в Serial и он мне сразу вернул управление (через несколько микросекунд). Я продолжаю выполнять loop(), а данные по проводу (физический уровень) все еще передаются
Вопрос: Если в скетче зарядили два прерывания по двум таймерам.
Оба прерывания разрешены.
Если произошло прерывание 1, мы вошли в процедуру обработки прерывания 1 и тут происходит прерывание 2. Будет прервана процедура 1 и выполнится прерывание 2? Или закончится выполнение процедуры 1 и сразу же будет запущена процедура 2?
Ответ
таблица векторов прерываний
Если не хотите, что бы прерывание прерывалось прерыванием (сорри за тавтологию), то после входа в прерывание запрещайте их, после отработки - разрешайте.
Спасибо за инфу. Если поступит одновременно два прерывания по таймерам, то сработает сначала то у которого меньше адрес, а затем второе.
А если первым сработало второе прерывание (с большим адреосм вектора) и во время выполнения процедуры второго прерывания происходит прерывание по первому вектору.
Завершится обработка второго прерывания и следом начнется исполнение первого прерывания?
Или прервется второе прерывание и обработается первое, а затем завершится второе?
Угу.
Если я не прав, пусть "старшие" товарищи поправят ))
По вхождению в обработчик прерывания запрещаются. Поэтому, если извращениями не заниматься, то никаких вторых прерываний не будет обрабатываться до выхода из обработчика. От того и millis() в прерывании стоит.
Бумзнать, спасибо.
Получается, что приоритет важен лишь на момент выбора одного прерывания из нескольких одновременных.
ЗЫ. Такой нюанс ещё "вычитался":
"Есть еще одна тонкость: если в данный момент обрабатывается прерывание, а другой запрос уже ожидает своей очереди, то после завершения текущего обработчика производится возврат в прерванную программу, выполняется одна инструкция, и лишь затем запускается новый обработчик. Эту задержку следует учитывать, если время реакции на прерывание очень критично."
По вхождению в обработчик прерывания запрещаются. Поэтому, если извращениями не заниматься, то никаких вторых прерываний не будет обрабатываться до выхода из обработчика. От того и millis() в прерывании стоит.
Я должен сам позаботится (указать в обработчике прерывания) "запретить прерывания" или это произойдет без моего вмешательства?
А что если вошли в обработчик второго прерывания, запретили прерывания и пока выполняем код обработчика срабатывает условие (таймер) первого прерывания. Но мы запретили прерывания в начале второго обработчика.
Обработчик второй завершился, завершаем его и разрешаем прерывания.
Выполнится ли после этого первое прерывание, которое должно было произойти во время выполнения второго обработчика? Или забудем уже пр то, что не случилось? Но в последнем случае можно потерять важную информацию.
Я должен сам позаботится (указать в обработчике прерывания) "запретить прерывания" или это произойдет без моего вмешательства?
Оно само.
На другие вопросы ответит ваш коллега c другого ресурса: http://radiokot.ru/forum/viewtopic.php?p=1348441&sid=e3063d5aa969af0721361431eaec1d82#p1348441
Спасибо. Теперь некоторое понимание обработки прерываний пришло.
Остается реализовать протокол обмена.
Пока застрял на протоколе обмена. Если в порт поступают байты я должен выделить пакет. (Под пакетом подразумеваю весь кадр <Сигнатура><Команда><Регистр><Данные 4 байта><CRC>)
Если запускать обработчик с периодом 1 мс и ждать когда Serial.available() вернет значение 8 (кол-во байт в пакете) то при выполнении равенства попытаться разложить в структуру (проверить стартовый байт, CRC)
Но пока разбирали буфер приема из 8-ми байт, могут поступить новые байты. А если принимаем данные постоянно то выделить нужную последовательность оказыывается очень сложно.
Вероятно тут нужно как-то отделять пакеты паузами в передаче, а не просто по сигнатуре вычислять. Например:
<Время в течении которого не принимались ни какие байты Более 5 мс><START_SIGN>
А если принимаем данные постоянно то выделить нужную последовательность оказыывается очень сложно.
Чой-то? Допустим, у вас заголовок из пары байт: завели буфер из пары байт, читаем в него побайтово, каждый раз проверяем - заголовок ли? Если нет - сдвигаем на один байт в голову, продолжаем до посинения, пока не встретим заголовок. Как только встретили - читаем данные в буфер для данных.
При таком раскладе неважно, когда включили дуню - она всё равно выделит начало пакета в потоке данных, главное, чтобы это начало пакета было уникальным.
Всегда будет вероятность принять данные за сигнатуру
если только сигнатуру не сделать большой. Например 6 байт
но это как.то коряво выглядит. Или я ошибаюсь?
Писал для себя протокол сериал-порта, была мысль дать роутеру экран и датчики, и... пока все забросил. Возможно какие-то идеи вам пригодятся.
http://syo.su/data/documents/Mega2560_TFT32.zip
В исходнике оставил для примера подключение и использование моих устройств, для тестирования непосредственно только протокола эту часть можно убрать. Используемую в коде библиотеку UTFT_binFonts можно взять здесь https://forum.arduino.cc/index.php?topic=448864.0
Пакет в протоколе начинается либо явно заданным номером команды, либо именем команды (в моем примере передача имени команды реализована на тестовом уровне, но легко расширяется). Передача номера команды начинается с символа # и следующего сразу за ним числа в десятичной, шестнадцатеричной или двоичной системе. Символ # переданный без номера считается равным #0, в моем примере команда опроса устройства. Далее идет передача данных из которых заполняется четыре массива. С круглой скобки начинается передача двухбайтовых значений, с квадратной четырехбайтовых, с символа меньше коды массива char, с кавычек символы массива char, и символы переданные вне блоков заносятся в отдельный массив char имени и аргументов команды. По умолчанию передача пакета завершается символом перенос строки (LF), либо подача строки (CR), либо символом &. Сразу после символа & может начинаться передача нового пакета. После передачи на ардуину команды #1 символы LF и CR перестанут завершать прием пакета, принятые после них символы будут продолжать заполнение данных для незавершенного пакета. Передача в пакете символа ? приведет пакет в режим теста, ардуина выдаст ответ с данными как она их прочитала, команда выполнена не будет.
Размеры массивов для данных к сожалению в исходнике заданы не константами, будет неудобно править. Размер двухбайтового и четырехбайтового массивов 16 элементов, массив char 255, массив char с именем команды 32. Массив char может передаваться как в одинарных кавычках, так и в двойных. В случае передачи в массив символа кавычки такой же с которой начинается блок, кавычка должна быть экранирована такой же кавычкой. Завершение блока символов начатого кавычкой обязательно и должно заканчиваться такой же кавычкой. Завершение блоков с числами обязательно только при необходимости продолжить ввод массива имени и аргументов команды, новый блок может начинаться без завершения предыдущего. Числа в блоке могут быть переданы в десятичной, шестнадцатеричной или двоичной системе, разделяются символом точка с запятой, пробелы переданные в блоке значения не имеют. Знак минус может быть передан где угодно в числе, в начале или в середине (такая особенность). Число 0 не обязательно явно указывать при передаче в блоке, разделитель или символ конца блока, либо начала блока следующий за разделителем создаст элемент со значением 0. Порядок передачи данных не имеет значения, массивы будут заполняться последовательно принятыми данными. При синтактической ошибке в пакете ардуина выдаст сообщение с указанием ошибки. Все сообщения от ардуины строятся по такому же протоколу.
Для каждого массива имеется переменная хранящая количество элементов принятых в пакете. Это позволяет, например, разбить принятый массив char на несколько строк оканчивающихся символом с кодом 0. Так же в исходнике можно видеть как количество переданных двухбайтовых и четырехбайтовых значений влияет на аргументы вызываемой функции. Например, команда #137 вывод текста на экран принимает в блоке четырехбатовых значений цвет фона текста: значения отсутствуют - цвет остается установленный ранее, передано одно значение - цвет устанавливается по этому значению как двухбайтовому значению цвета, передано три значения - цвет устанавливается по этим значениям как RGB.
Примеры:
#137(20;30;;2;xFF;0xFF;b11111111[;;]"hello"<32'world'
- инициализирует переменные sp_cmd=137, sp_int[]={20;30;0;2;255;255;255}, sp_long[]={0;0;0}, sp_str[]={hello world}. флаг пакета указывает на передачу команды по номеру
#138"""abc"" "'"def"'( 1 ; 2; ; ; [1;2 [3(3(4(5[4]<32>"!"
- инициализирует переменные sp_cmd=138, sp_int[]={1;2;0;0;0;3;4;5}, sp_long[]={1;2;3;4}, sp_str[]={"abc" "def" !}. флаг пакета указывает на передачу команды по номеру
PRINT(;)"hello"<13;10>AND(x20;X20)'goodbye'<0x0D;0x0A>OK
- инициализирует переменные sp_msg[]="PRINT AND OK", sp_int[]={0;0;32;32}, sp_str[]={hello\n\rgoodbye\n\r}. флаг пакета указывает на передачу команды по имени
? – справка по синтаксису протокола
#XX? – справка по команде с номером XX (требуется заполнение справки)
#XX(XX[XX"XXXX"? – тест пакета, ответ ардуины принятыми данными. рекомендую эту опцию для изучения протокола
Спасибо. Буду изучать.
По описанию, Вы серьезный пртокол продумали.
Поправьте меня пожалуйста если я ошибся....
при скорости 9600 бод , 1 бит информации передаётся за 1/9600 = 104 мкС
Угу.
т.е на передачу байта необходимо 833 мкс .
Нет.
Как приеммник может понять, что передача началась? Что она закончилась? Что прошла без ошибок?
Нужны служебные биты, минимум - одни стартовый и один стоповый. Т.е. для передачи байта нужно передать минимум 10 бит. Бывает, к ним добавляется бит четности и второй (полуторный) стоповый (строго говоря, стоповый бит может быть в 1, 1.5 или 2 раза длиннее информационного).
Поправьте меня пожалуйста если я ошибся....
при скорости 9600 бод , 1 бит информации передаётся за 1/9600 = 104 мкС
Угу.
т.е на передачу байта необходимо 833 мкс .
Нет.
Как приеммник может понять, что передача началась? Что она закончилась? Что прошла без ошибок?
Нужны служебные биты, минимум - одни стартовый и один стоповый. Т.е. для передачи байта нужно передать минимум 10 бит. Бывает, к ним добавляется бит четности и второй (полуторный) стоповый (строго говоря, стоповый бит может быть в 1, 1.5 или 2 раза длиннее информационного).
Я проделал эксперимент с замером времени на реальной процедуре. То, что там в физическую линию передается избыточная информация - я понимаю. Но в результате это все уже учтено.
Проделал еще один эксперимент с Serial
Serial.println("Front: ON"); Serial.print("freq = "); Serial.println(freq); digitalWrite(PIN_LOCK_FRONT, blockFront); uint32_t t1 = micros(); Serial.write("123456789012345aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); uint32_t t2 = micros(); //delay(1000); Serial.println(); Serial.print(" t= "); Serial.println(t2 - t1);Выполняю этот код.
Время отработки t2-t1 = 84 мкс
Раскоментировал строку delay(1000)
Выводится значение freq, затем сразу выводится строка и пауза в 1 сек.
Что я хочу сказать этим экспериментом...
Serial.print не ждет завершения передачи данных в канал, а выполнив какие-то действия (84 мкс) сразу же передает управление на следующую строку кода. И пока мы тупо стоим и ждем 1 с, в терминал выводится строка текста.
Всегда будет вероятность принять данные за сигнатуру
если только сигнатуру не сделать большой. Например 6 байт
но это как.то коряво выглядит. Или я ошибаюсь?
Не, если пакет грамотный - никогда ;) Помимо заголовка неплохо было бы иметь и жопку. На примере пакета длиной 10 байт:
ЗАГОЛОВОК (2 байта) - Данные (6 байт) - ЖОПКА (2 байта).
Вычитываешь 10 байт, сравниваешь на заголовок и жопку - если совпали - зуб даю, это пакет (конечно, ещё CRC8 надо, как минимум). Если не совпали - сдвигаешь на 1 байт в голову, читаешь следующий байт. И так до тех пор, пока не поймаешь заголовок и жопку, да ещё и CRC8 сойдётся. У мну пакет из 30 байт, юзаю именно этот подход: модулю, подтыкаемому к RS-485 - пофиг, когда он включен физически: он корректно ловит запрос от мастера на отдачу данных, реализовано ровно по описанной схеме.
Пришел к следующему кадру (пакету)
4 байта сигнатуры можно легко обрабатывать как uint32
// *********************************************************************** const uint32_t SIGN = 0x33333333; // Сигнатура uint8_t count = 0; // Счетчик байт данных (принимаем 4 байта данных) uint8_t inChr = 0; // принятый байт uint32_t sign =0; // регистр сигнатуры с линии. Данные полученные с линии. uint8_t crc = 0; // Подсчет CRC с линии. Сигнатура не считается. XOR uint8_t mode = 0; // Режим. // 0 - ожидание сигнатуры, 1 - ожидание адреса, // 2 - ожидание команды, 3 - ожидание регистра, // 4 - ожидание данных, 5 - ожидание crc // 6 - успешное завершение, 7 - не совпало CRC struct xBuf_t { // Структура буфера uint8_t adr; uint8_t cmd; uint8_t reg; uint8_t b3; uint8_t b2; uint8_t b1; uint8_t b0; }; xBuf_t xBuf; void setup() { Serial.begin(115200); } void loop() { uint8_t n = Serial.available(); if ( n > 0) { inChr = Serial.read(); Serial.print(" inChr = "); Serial.println(inChr, HEX); Serial.print(" n = "); Serial.println(n); Serial.print(" mode = "); Serial.println(mode); switch ( mode ) { case 0: // Ожидаем сигнатуру sign |= inChr; Serial.print(" sign = "); Serial.println(sign, HEX); if (sign == SIGN) { Serial.println("SIGN - OK"); mode = 1; // Переходим на ввод адреса crc = 0; // Очистили подсчет crc count = 0; // Обнулили счетчик байт данных. (4 байта) } else { sign = sign << 8; } break; case 1: // Ввод адреса crc ^= inChr; xBuf.adr = inChr; mode = 2; // Переходим на ввод команды Serial.print(" xBuf.adr = "); Serial.println(xBuf.adr, HEX); break; case 2: // Ввод команды crc ^= inChr; xBuf.cmd = inChr; mode = 3; // Переходим на ввод регистра Serial.print(" xBuf.cmd = "); Serial.println(xBuf.cmd, HEX); break; case 3: // Ввод регистра crc ^= inChr; xBuf.reg = inChr; mode = 4; // Переходим на ввод команды Serial.print(" xBuf.reg = "); Serial.println(xBuf.reg, HEX); break; case 4: // Ввод данных 4 байта if (count < 3 ) { crc ^= inChr; (uint8_t *) (& (xBuf.b3 - count)) = inChar; count++; } else { mode = 5; // Переходим на ввод crc } break; case 5: // Ввод crc if (crc == inChr) { mode = 6; // Успешное чтение пакета Serial.println(" crc - OK!"); } else { mode = 7; Serial.println(" crc - FALSE!"); } break; case 6: // Успешно Serial.println(" Packet receved Success!"); break; case 7: // Ошибка CRC Serial.println(" Packet receved Filed!"); break; } } }В строке 82 запутался. Ошибка.
Заполняю сегмент данных в буфере
xBuf.b0 xBuf.b1 xBuf.b2 xBuf.b3
Для этого беру указатель на b3 (тип uint8_t) и записываю по его адресу значение из линии.
Затем указатель уменьшаю на count
При компиляции ошибка. Я не очень разобрался с работой с указателями.
Исследования продолжаются...
Формат пакета
Сигнатуру начала пакета увеличил до 4 байт. В тестовом варианте использую простую сигнатуру 0x33333333. В боевых условиях нужно что-то посерьезнее.
Прошу покритиковать следующий код.
#include <button.h> #define PIN_BT 5 button bt(PIN_BT, false, 50); // *********************************************************************** const uint32_t SIGN = 0x33333333; // Сигнатура начала пакета uint8_t count = 0; // Счетчик байт данных (принимаем 4 байта данных) uint8_t inChr = 0; // принятый байт uint32_t sign = 0; // регистр сигнатуры с линии. Данные полученные с линии. uint8_t crc = 0; // Подсчет CRC (XOR) с линии. Сигнатура не считается. uint8_t mode = 0; // Режим. // 0 - ожидание сигнатуры, 1 - ожидание адреса, // 2 - ожидание команды, 3 - ожидание регистра, // 4 - ожидание данных, 5 - проверка crc Если crc верно - буфер готов struct xBuf_t { // Структура буфера uint8_t adr; uint8_t cmd; uint8_t reg; uint8_t b3; uint8_t b2; uint8_t b1; uint8_t b0; }; xBuf_t xBuf; // Буфер данных с линии uint8_t *p_b0; // Указатель на xBuf.b0 uint32_t tStart = 0; uint32_t millisec = 0; const uint32_t TIME_OUT = 10000; // В реальной системе тайм аут следует снизить. Например 50 мс. // Следующие переменные для замера времени выполнения основного цикла, мкс uint32_t t1; uint32_t t2; void setup() { Serial.begin(115200); } void loop() { // Обработка кнопки (для отладки) bt.update(); if (bt.btOn || bt.btOff) { mode = 0; sign = 0; //Serial.println("TimeOut reset"); } // Обработка тайм-аута. Если на линии ничего нет более чем TIME_OUT - сбрасываем режим millisec = millis(); if ( (millisec - tStart) > TIME_OUT) { mode = 0; // Сброс режима sign = 0; // Сброс регистра сигнатуры } uint8_t n = Serial.available(); // Если буфер приема не пустой if ( n > 0) { tStart = millisec; // Время отсчета тайм-аута t1 = micros(); // Для отладки. Вычисление времени работы цикла inChr = Serial.read(); //Serial.print(" inChr = "); //Serial.println(inChr, HEX); //Serial.print(" n = "); //Serial.println(n); //Serial.print(" mode = "); //Serial.println(mode); switch ( mode ) { // Режим 0. Проверка на сигнатуру case 0: sign |= inChr; // Принятый с линии байт помещаем в младший байт 4-х байтовой переменной //Serial.print(" sign = "); //Serial.println(sign, HEX); if (sign == SIGN) { // Если сигнатура с линии совпала с заданной sign = 0; // Обнуляем сигнатуру для следующего пакета //Serial.println("SIGN - OK"); mode = 1; // Переходим на ввод адреса crc = 0; // Очистили подсчет crc count = 0; // Обнулили счетчик байт данных. (4 байта) } else { sign = sign << 8; // Сдвигаем на 8 бит влево //Serial.println("SIGN - Filed"); } break; // Ввод адреса устройства case 1: crc ^= inChr; // Считаем контрольную сумму XOR xBuf.adr = inChr; // записали адрес в буфер mode = 2; // Переходим на ввод команды //Serial.print(" xBuf.adr = "); //Serial.println(xBuf.adr, HEX); break; // Ввод команды case 2: crc ^= inChr; // Считаем контрольную сумму XOR xBuf.cmd = inChr; // Записали команду в буфер mode = 3; // Переходим на ввод регистра //Serial.print(" xBuf.cmd = "); //Serial.println(xBuf.cmd, HEX); break; // Ввод регистра case 3: crc ^= inChr; // Считаем контрольную сумму XOR xBuf.reg = inChr; // Записали регистр в буфер mode = 4; // Переходим на ввод команды p_b0 = &xBuf.b0; // Получаем адрес xBuf.b0 //Serial.print(" xBuf.reg = "); //Serial.println(xBuf.reg, HEX); break; // Ввод данных 4 байта case 4: crc ^= inChr; // Считаем контрольную сумму XOR *(p_b0 - count) = inChr; // Записываем байт по адресу xBuf.b0 скорректированному на номер байта count++; // Переходим к следующему байту данных if (count == 4) { // Если 4 байта приняты (от 0 до 3) mode = 5; // Переходим на ввод crc //Serial.print("b3 = "); //Serial.println(xBuf.b3, HEX); //Serial.print("b2 = "); //Serial.println(xBuf.b2, HEX); //Serial.print("b1 = "); //Serial.println(xBuf.b1, HEX); //Serial.print("b0 = "); //Serial.println(xBuf.b0, HEX); } //Serial.print("count = "); //Serial.println(count, HEX); break; // Ввод crc case 5: if (crc == inChr) { // Сравниваем подсчитанную crc с указанной в inChr mode = 0; // Сбрасываем режим //Serial.println(" crc - OK!"); Serial.println("OK"); // Успешное чтение пакета // Тут может быть вызов процедуры установки значений // или просто взведение флага готовности // или копирование буфера в выходные переменные } else { // Контрольная сумма не совпала mode = 0; // Сброс режима //Serial.println(" crc - FALSE!"); Serial.println("Err"); } break; } //Serial.print("crc = "); //Serial.println(crc, HEX); // Для отладки. Подсчте времени выполнения цикла. // Без отладочных вызовов Serial.print колеблется от 12 до 40 мкс // С отладочными Serial.print - время увеличивается до 800 мкс t2 = micros(); Serial.print(" T = "); Serial.println(t2-t1); } }Набор последовательных байт посылаю с терминала. Только встроенный терминал в Arduino IDE не отправляет байты пока не нажать Enter, а это в свою очередь отправляет еще и код 0x0D
Для чистоты эксперимента использовал Putty. Он отправляет только то, что нажимаем.
Если убрать все отладочные Serial.print - цикл выполняется за время от 12 до 40 мкс. На сколько я понимаю такая процедура должна вызываться по таймеру с высокой частотой (например 1 мс). Т.е. это загрузит контроллер на 4% (40/1000)
На строке 64 лучше использовать millis(), тогда таймаут будет вычисляться точнее.
Встроенный в IDE терминал может отправлять и без <CR> / <LF> - снизу справа можно выбрать, что досылать и досылать ли хоть что-то.
И мне всё же непонятно - почему вы все время о таймере каком-то пишете. В чем идея-то заключается - считывать из UART buffer байт периодически, по прерыванию, а не всё скопом в loop() ?
На строке 64 лучше использовать millis(), тогда таймаут будет вычисляться точнее.
Да, я это понимаю, но подумал, что в этом месте высокая точность не нужна, а вот скорость нужна.
Предположил, что:
tStart = millisec; будет выполнятся быстрее чем следующая строка
tStart = millis(); Тут же целый вызов функции. Не знаю как там в ее недрах но предполагаю, что времени потребуется больше
Встроенный в IDE терминал может отправлять и без <CR> / <LF> - снизу справа можно выбрать, что досылать и досылать ли хоть что-то.
:)
Да, это моя невнимательность. Спасибо.
И мне всё же непонятно - почему вы все время о таймере каком-то пишете. В чем идея-то заключается - считывать из UART buffer байт периодически, по прерыванию, а не всё скопом в loop() ?
Ну в loop() будет работать некоторая программа. На прерывание по таймеру думаю повесить выполнение вот этого скетча (в виде процедуры)
По приходу данных с линии будет устанавливаться та или иная переменная.
Программа контролирует давление в рессивере. Если оно падает ниже минимального значения - включить компрессор. Достигли максимального давления - отключить компрессор.
А вот минимальное и максимальное давление задавать через приложение со смартфона (Андроид) с организацией канала по блютус.
Это для примера привел. Там еще контроль напряжения бортовой сети и еще ряд датчиков
зы. Установил Visual studio и нашел простой пример работы с COM портом. Подставил в него свою последовательность и запустил. ЙЙЕЕС! Заработало! Это моя первая програмка на компе. Немного модифицировал и теперь если правильную команду отправить - зажигаю светодиод pin13. Неправильну (crc) - гаснет.
Да, я это понимаю, но подумал, что в этом месте высокая точность не нужна, а вот скорость нужна.
Ну, понятное дело, что всегда будет вероятность получить данные во время работы с периферией. Избежать вы этого не сможете, как не старайтесь - хоть с разбором пакета в обработчике прерывания, хоть вне его. Данные могут начать поступать даже пока вы digitalWrite() исполняете, так как обе части вашей системы работают не синхронно. Тут я вижу навскидку два пути:
1) При передаче мастер должен поднять семафор "я передаю", который остановит все процессы слейва (через внешнее прерывание, ожидая пакет "ACK" перед передачей или как-то иначе). После передачи слейв должен переинициализироваться, т.е. начать свой цикл управления с шага 0, используя вновь принятые данные.
2) Мастер шлет значение уставки в пакете, пока слейв не ответит: "да понял я, понял". При этом слейв волен сделать это в лупе, по окончании цикла управления и чтении очередного пакета из буфера UART. Конечно это не рилтайм, какое-то время пройдет до этого момента и ваша цель - максимально сократить выполнение loop(), чтобы анализ входящих пакетов был чаще.
И я вас уверяю - нормально написанный луп даже не даст вам почувствовать, что между тыканьем в экран смартфона и получением уставки слейвом прошёл ощутимый с человеческой точки зрения временной промежуток. Просто не загоняйтесь на микросекундах в подобной задаче - вы моргаете медленнее, чем луп проходит.
А как только станете махать пальцами со скоростью колибри - так и начинайте бороться за мкс. К тому времени вы уже многим фокусам успеете научиться.
P.S. В ответе слейва неплохо было бы иметь ID того пакета, на который он отвечает "понял". Это если мастеру нужно точно знать, если он лупит по 50 уставок в секунду, что там слейв понял и не надо ли ему переповторить. Вобщем, посмотрите любой протокол с гарантией доставки, хоть тот же TCP.
Вот такая мысль возникла...
В основной программе проинициализировать массив в котором в качестве индекса будет номер регистра, а в качестве элемента - ссылка на нужную переменную в программе. Массив можно заполнить в Setup()
Сколько байт занимает адрес переменной?
Не съем я всю память таким массивом?
Не понимаю смысла. Глобальную структуру/массив заведите, если память хотите поэкономить.
Заменять собой гугл я не собираюсь, поэтому учитесь добывать информацию: https://www.google.ru/search?q=avr+pointer+size
Точно!
Я должен был сам догадаться, что незачем дублировать переменные. Достаточно их сразу в одну структуру свести.
Ну что сказать.... учить и учить еще. И практики больше.
Научился с компа отправлять посылку и вкл/выкл набор светодиодов подключенных к пинам в зависимости от команды.
Данные передавать почти научился (и принимать тоже). Следующий этап - подключение блютус-модуля. Надеюсь в понедельник уже приедет.
Тогда новый виток исследований. И больше всего смущает Android Studio. С архитектурой приложений для андроид совсем не знаком. Первые познания получил по видеоурокам. На эмуляторе удалось получить некоторый результат. Но все это все далеко от результата необходимого. Понимаю, что "не боги горшки обжигают" и даже медведей на вилосипеде учат кататься :)
Терпением только запастись учить ... учить ... учить ... А как хочется уже результат пощупать! :)