Протокол для обмена по блютус

Smith2007
Offline
Зарегистрирован: 30.10.2017

Всем добрый вечер.

Продолжаю изучение возможностей arguing . На очереди обмен данными по Блютус. Посмотрел различные примеры . В основном все сводятся к вкл/выключи светодиода.

Задумался над вопросом: множество людей используют Блютус модули для связи с телефонами но не могу поверить, что так и не разработали библиотеку для этих целей. Подскажите пожалуйста названия библиотек которые при помощи которых можно организовать обмен данными и командами между ардуино и андроид телефоном.

Если таковых нет то где можно почитать о подходящем для таких целей протоколе? Понимаю, что можно и самому придумать свой протокол обмена, но очень не хочется велосипед изобретать.

Smith2007
Offline
Зарегистрирован: 30.10.2017

Извиняюсь за ошибки. С телефона пишу.

Pablos
Pablos аватар
Offline
Зарегистрирован: 18.07.2017

Добрый!

Догадываюсь, что речь не столько про библиотеки Ардуино (там Блютус он и есть блютус), а про ответную часть на телефоне.
Таких штук много разных. От больших и красивых (типа Blynk - там и библиотеки имеются и прикладуха для телефона) до простых и убогих (типа KvushCo). Все можно поискать в ГуглПлей.

Какая цель? Что будет в результате?

 

П.

Smith2007
Offline
Зарегистрирован: 30.10.2017

В приложение на телефоне нужно считать 20 переменных разного типа. Затем с телефона нужно записать новые значения переменных на ардуино. Запись и чтение должны идти не зависимо. Как бы в параллельных потоках. Мне это представляется примерно так:

< стартовая сигнатура> <код посылки> < данные> < конец посылки>

Или похожее. Т.е. Протокол прикладного уровня. Можно самому придумать, но думаю что уже изобрели этот велосипед.

sadman41
Offline
Зарегистрирован: 19.10.2016

Конечно изобрели давно:

<header><length><payload><CRC>

или

<header><fixed length payload><CRC>

или

<STX><payload><CRC><ETX>

или

...

Payload - это RAW-представление структуры данных с вашими переменными.
Smith2007
Offline
Зарегистрирован: 30.10.2017

Да, я именно об этом и спрашиваю. Есть ли готовые библиотеки на данную тему? 

Обмен данными и командами по Блютус используется широко. Вот и думаю, что уже разработаны для этого библиотеки. Приём и передача данных - асинхронная.

Smith2007
Offline
Зарегистрирован: 30.10.2017

Подумал, что придется сделать свой простенький протокол  типа:

STRT_HEAD Command Registr D3 D2 D1 D0 CRC
byte byte byte byte byte byte byte byte

В 4 байта данных войдет byte, int, int long

Float смысла нет передавать так как с контроллера в любом случае получаем int с аналоговых входов. Преобразование к физическому значению можно сделать и на стороне компьютера/смартфона

Command
0x01 - Чтение uint8_t   D0
0x02 - Чтение uint16_t  D1, D0
0x03 - Чтение uint32_t  D3, D2, D1, D0
0x11 - Запись uint8_t  D0
0x12 - Запись uint16_t  D1, D0
0x13 - Запись uint32_t  D3, D2, D1, D0
 
В поле Registr можно указывать порядковый номер переменной (номер регистра = переменная)
 
 
Будет работать такая перегрузка?
// Передача байта
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]);
  }
}

 

 
 
sadman41
Offline
Зарегистрирован: 19.10.2016

Smith2007 пишет:

Подумал, что придется сделать свой простенький протокол  типа:

STRT_HEAD Command Registr D3 D2 D1 D0 CRC
byte byte byte byte byte byte byte byte

Преамбулу (STRT_HEAD) я бы сделал из нескольких байт. Мало ли - приемник будет по прерыванию просыпаться - не успеет схватить начало. В идеале, конечно, преамбула должна иметь значение, которое не встречается в пакете далее.

[quote=Smith2007]

0x13 - Запись uint32_t  D3, D2, D1, D0

В поле Registr можно указывать порядковый номер переменной (номер регистра = переменная)

sadman41
Offline
Зарегистрирован: 19.10.2016

Smith2007 пишет:
Подумал, что придется сделать свой простенький протокол типа: STRT_HEAD Command Registr D3 D2 D1 D0 CRC byte byte byte byte byte byte byte byte
Преамбулу (STRT_HEAD) я бы сделал из нескольких байт. Мало ли - приемник будет по прерыванию просыпаться - не успеет схватить начало. В идеале, конечно, преамбула должна иметь значение, которое не встречается в пакете далее.
Smith2007 пишет:
0x13 - Запись uint32_t D3, D2, D1, D0 В поле Registr можно указывать порядковый номер переменной (номер регистра = переменная)
На формат пакета Modbus посмотрите.
Smith2007 пишет:
Будет работать такая перегрузка?
Заведите struct my_packet , заполните и передавайте как байтовый массив, начиная с (uint8_t*) &my_packet на размер sizeof(my_packet). На приемнике складывайте принятые байты в (uint8_t*) &my_packet, а потом читайте, как структуру: my_packet.CRC, например.

Smith2007
Offline
Зарегистрирован: 30.10.2017

Удлинять посылку не хочется. От переменной длины пакета тоже решил отказаться учитывая, что максимальная размерность 4 байта. Делать длинной стартовую сигнатуру мне кажется нет смысла. И то, что она совпадёт с данными мне кажется не критичным. Я думаю, что при чтении буду тупо ждать пока в буфере не наберется 8 байт. Если сигнатуры в первом байте не найду или CFC не совпадёт — просто сбрасываю весь пакет и жду новый. Передающая сторона делает паузы между посылками пакетов не менее 10 см.

кстати вопрос: сколько миллисекунд нужно, чтобы отправить один байт в uart?

serial.write(byte)

Smith2007
Offline
Зарегистрирован: 30.10.2017

Стоит ли городить огород с подтверждением доставки пакета. 

 

Modbus я смотрел. За основу его пакет и взял. Но сам сам протокол будет избыточен и тяжеловат для простой задачи. Постоянные опросы и жесткие тайминг.... при работе на смартфоне боюсь много граблей с ним будет. Проще что—то легкое сделать

по структуре и чтении как массив — хорошая идея. У меня только пока знаний маловато. Учусь. Но саму идею понял. Я ещё думал массив и, byte, int, long int в union включить и тогда можно просто обращаться к разным типам сразу. Но не соображу как массив указать. Или массив в union не включать, а просто обращаться по ссылке как к массиву по указанному Вами примеру

 

можно ли в структуру включить union? 

Union  xx {

  Byte bI;

  uint w;

  uint long l;

}

 

struct sss {

  byte bbb

  xxx ccc

}

 

 

 

 

 

 

sadman41
Offline
Зарегистрирован: 19.10.2016

Smith2007 пишет:

Удлинять посылку не хочется. От переменной длины пакета тоже решил отказаться учитывая, что максимальная размерность 4 байта.

В данном случае вы обмениваете надёжность на ёмкость. Хотите более надёжный старт - увеличьте хидер. Экономите на спичках и уверены в том, что всегда будете получать пакет с самого начала - уменьшайте. Хотя, мне кажется, что +два-три байта при паузе в 10см вообще никакой погоды не сделают.

Smith2007 пишет:

кстати вопрос: сколько миллисекунд нужно, чтобы отправить один байт в uart?

serial.write(byte)

А сколько надо, чтобы добраться до городу Парижу? 

На сколько канал инициализируете, с такой скоростью и будете передавать: https://ru.wikipedia.org/wiki/%D0%91%D0%B8%D1%82_%D0%B2_%D1%81%D0%B5%D0%...

Нужно ли подтверждение - вопрос как философский, так и прикладной. Даже в IP-сетях есть два вида пакетов - с подтверждением и без оного. Нужна гарантия доставки - делаете подтверждение. Не нужна или алгоритм предполагает получение ответа на запрос в течении N sec с перепосылкой в случае необходимости - не делаете подтверждения.

С массивами и юнионами тот же подход: данные допускают передачу с нелимитируемым разрывом по времени - делаете юнион и мелкие пакеты. Необходимо принимать всё пачкой - длинный пакет и структура без юнионов.  Впрочем, это, с моей сторны, как вы понимаете, размышления об абстрактных данных.

Smith2007
Offline
Зарегистрирован: 30.10.2017

Спасибо Вам за консультацию.

По времени выполнения serial.write(byte) ....

Я понимаю, что uart будет передавать с той скоростью, что укажу. Но это же не значит, что время выполнения процедуры serial.write(byte) такое же. Там же есть буферизация. Или я чего путаю?

Я предполагал, что в канал (в провода) пакет пойдет бит за битом с указанной при инициализации скорости (9600, 115200 и т.д.)

Но для записи данных в uart - другая скорость.

Вопросы возникают потому, что хочу вникнуть как прием-передачу организовать учитывая таймауты и прочие временные критерии.

 

 

зы. На первых порах постараюсь реализовать обмен без подтверждения доставки. Это должно быть проще. А учитывая мой скромный опыт в программировании - это еще и необходимо :)

sadman41
Offline
Зарегистрирован: 19.10.2016

Smith2007 пишет:

Я понимаю, что uart будет передавать с той скоростью, что укажу. Но это же не значит, что время выполнения процедуры serial.write(byte) такое же. Там же есть буферизация. Или я чего путаю?

Я предполагал, что в канал (в провода) пакет пойдет бит за битом с указанной при инициализации скорости (9600, 115200 и т.д.)

Да, указанная скорость - это скорость самого нижнего, физического, уровня, считайте - наивысшая. Далее будут накручиваться уже верхние программные слои, дающие дополнительную задержку. Но если вы копировать-конвертировать туда-сюда байты по массивам-переменным перед передачей не станете, а прямо во write() подадите указатель на байтовый массив (структуру), то оверхед будет минимальный. Можно сказать - незаметный.

Вас же интересует скорость появления данных с другой стороны трубы, а не записи в память МК, так? Потому что МК на фоне не будет ничего отправлять, он однопоточный. Хотя, конечно, что там в конкретном БТ-модуле накручено - непонятно. 

PS:http://www.radiokot.ru/forum/viewtopic.php?p=1298712#p1298712

Smith2007
Offline
Зарегистрирован: 30.10.2017

Поправьте меня пожалуйста если я ошибся....

при скорости 9600 бод , 1 бит информации передаётся за 1/9600 = 104 мкС

т.е на передачу байта необходимо 833 мкс . Тогда на передачу 8 байт потребуется 6,4 мс. С учетом накладных расходов это время возможно достигнет 7 мс

 

при скорости 115200 на передачу 8 байт потребуется около 500 МКС или 0,5 мс

значит uart должен в фоне все отправлять ( аппаратно) иначе слишком долго ждать надо будет

 

sadman41
Offline
Зарегистрирован: 19.10.2016

Никому UART ничего не должен и в фоне МК отправлять ничего не будет. К тому же - почитайте по ссылке пост.

Так что, очевидно, вам придется откорректировать свои планы или переходить на уровень программирования "Бог".

Smith2007
Offline
Зарегистрирован: 30.10.2017

Ну мои планы не такие уж амбициозные. Обеспечить передачу данных по Блютус интерфейсу. Надеюсь для этой цели не нужен уровень Бог ;)

получается нет ни каких буферов в uart и модуле hc 05.

Smith2007
Offline
Зарегистрирован: 30.10.2017

Провел некоторые исследования

       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(), а данные по проводу (физический уровень) все еще передаются

    115200 9600
Процедура Символы Время, мкс Время, мкс
Serial.print 1 16 16
Serial.print 12 16 16
Serial.print 123 16 16
Serial.print 1234 16 16
Serial.print 12345 20 20
Serial.print 123456 20 20
Serial.print 1234567 20 20
Serial.print 12345678 20 20
Serial.print 123456789 24 24
Serial.print 1234567890 24 24
Serial.print 12345678901 24 24
Serial.print 123456789012 24 24
Serial.print 1234567890123 24 24
Serial.print 12345678901234 28 28
Serial.print 123456789012345 28 28

 

Smith2007
Offline
Зарегистрирован: 30.10.2017

Вопрос: Если в скетче зарядили два прерывания по двум таймерам. 

Оба прерывания разрешены.

Если произошло прерывание 1, мы вошли в процедуру обработки прерывания 1 и тут происходит прерывание 2. Будет прервана процедура 1 и выполнится прерывание 2? Или закончится выполнение процедуры 1 и сразу же будет запущена процедура 2?

OlegK
OlegK аватар
Offline
Зарегистрирован: 26.11.2014

Smith2007 пишет:
Вопрос: Если в скетче зарядили два прерывания по двум таймерам.

Ответ
таблица векторов прерываний

Если не хотите, что бы прерывание прерывалось прерыванием (сорри за тавтологию), то после входа в прерывание запрещайте их, после отработки - разрешайте.

Smith2007
Offline
Зарегистрирован: 30.10.2017

Спасибо за инфу. Если поступит одновременно два прерывания по таймерам, то сработает сначала то у которого меньше адрес, а затем второе.

А если первым сработало второе прерывание (с большим адреосм вектора) и во время выполнения процедуры второго прерывания происходит прерывание по первому вектору. 

Завершится обработка второго прерывания и следом начнется исполнение первого прерывания?

Или прервется второе прерывание и обработается первое, а затем завершится второе?

OlegK
OlegK аватар
Offline
Зарегистрирован: 26.11.2014

Smith2007 пишет:
Или прервется второе прерывание и обработается первое, а затем завершится второе?

Угу.
Если я не прав, пусть "старшие" товарищи поправят ))

sadman41
Offline
Зарегистрирован: 19.10.2016

По вхождению в обработчик прерывания запрещаются. Поэтому, если извращениями не заниматься, то никаких вторых прерываний не будет обрабатываться до выхода из обработчика. От того и millis() в прерывании стоит.

OlegK
OlegK аватар
Offline
Зарегистрирован: 26.11.2014

Бумзнать, спасибо.
Получается, что приоритет важен лишь на момент выбора одного прерывания из нескольких одновременных.

ЗЫ. Такой нюанс ещё "вычитался":
"Есть еще одна тонкость: если в данный момент обрабатывается прерывание, а другой запрос уже ожидает своей очереди, то после завершения текущего обработчика производится возврат в прерванную программу, выполняется одна инструкция, и лишь затем запускается новый обработчик. Эту задержку следует учитывать, если время реакции на прерывание очень критично."

Smith2007
Offline
Зарегистрирован: 30.10.2017

sadman41 пишет:

По вхождению в обработчик прерывания запрещаются. Поэтому, если извращениями не заниматься, то никаких вторых прерываний не будет обрабатываться до выхода из обработчика. От того и millis() в прерывании стоит.

Я должен сам позаботится (указать в обработчике прерывания) "запретить прерывания" или это произойдет без моего вмешательства?

 

А что если вошли в обработчик второго прерывания, запретили прерывания и пока выполняем код обработчика срабатывает условие (таймер) первого прерывания. Но мы запретили прерывания в начале второго обработчика.

Обработчик второй завершился, завершаем его и разрешаем прерывания.

Выполнится ли после этого первое прерывание, которое должно было произойти во время выполнения второго обработчика? Или забудем уже пр то, что не случилось? Но в последнем случае можно потерять важную информацию.

sadman41
Offline
Зарегистрирован: 19.10.2016

Smith2007 пишет:

Я должен сам позаботится (указать в обработчике прерывания) "запретить прерывания" или это произойдет без моего вмешательства?

Оно само.

На другие вопросы ответит ваш коллега c другого ресурса: http://radiokot.ru/forum/viewtopic.php?p=1348441&sid=e3063d5aa969af0721361431eaec1d82#p1348441

 

Smith2007
Offline
Зарегистрирован: 30.10.2017

Спасибо. Теперь некоторое понимание обработки прерываний пришло.

Остается реализовать протокол обмена. 

Пока застрял на протоколе обмена. Если в порт поступают байты я должен выделить пакет. (Под пакетом подразумеваю весь кадр <Сигнатура><Команда><Регистр><Данные 4 байта><CRC>)

Если запускать обработчик с периодом 1 мс и ждать когда Serial.available() вернет значение 8 (кол-во байт в пакете) то при выполнении равенства попытаться разложить в структуру (проверить стартовый байт, CRC)

Но пока разбирали буфер приема из 8-ми байт, могут поступить новые байты. А если принимаем данные постоянно то выделить нужную последовательность оказыывается очень сложно.

Вероятно тут нужно как-то отделять пакеты паузами в передаче, а не просто по сигнатуре вычислять. Например:

<Время в течении которого не принимались ни какие байты  Более 5 мс><START_SIGN>

 

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Smith2007 пишет:

А если принимаем данные постоянно то выделить нужную последовательность оказыывается очень сложно.

Чой-то? Допустим, у вас заголовок из пары байт: завели буфер из пары байт, читаем в него побайтово, каждый раз проверяем - заголовок ли? Если нет - сдвигаем на один байт в голову, продолжаем до посинения, пока не встретим заголовок. Как только встретили - читаем данные в буфер для данных. 

При таком раскладе неважно, когда включили дуню - она всё равно выделит начало пакета в потоке данных, главное, чтобы это начало пакета было уникальным.

 

Smith2007
Offline
Зарегистрирован: 30.10.2017

Всегда будет вероятность принять данные за сигнатуру

если только сигнатуру не сделать большой. Например 6 байт

но это как.то коряво выглядит. Или я ошибаюсь?

DenSyo
Offline
Зарегистрирован: 13.01.2017

Писал для себя протокол сериал-порта, была мысль дать роутеру экран и датчики, и... пока все забросил. Возможно какие-то идеи вам пригодятся.

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"? – тест пакета, ответ ардуины принятыми данными. рекомендую эту опцию для изучения протокола

Smith2007
Offline
Зарегистрирован: 30.10.2017

Спасибо. Буду изучать.
По описанию, Вы серьезный пртокол продумали.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Smith2007 пишет:

Поправьте меня пожалуйста если я ошибся....

при скорости 9600 бод , 1 бит информации передаётся за 1/9600 = 104 мкС

Угу.

Цитата:

т.е на передачу байта необходимо 833 мкс .

Нет.

Как приеммник может понять, что передача началась? Что она закончилась? Что прошла без ошибок?

Нужны служебные биты, минимум - одни стартовый и один стоповый. Т.е. для передачи байта нужно передать минимум 10 бит. Бывает, к ним добавляется бит четности и второй (полуторный) стоповый (строго говоря, стоповый бит может быть в 1, 1.5 или 2 раза длиннее информационного).

Smith2007
Offline
Зарегистрирован: 30.10.2017

andriano пишет:

Smith2007 пишет:

Поправьте меня пожалуйста если я ошибся....

при скорости 9600 бод , 1 бит информации передаётся за 1/9600 = 104 мкС

Угу.

Цитата:

т.е на передачу байта необходимо 833 мкс .

Нет.

Как приеммник может понять, что передача началась? Что она закончилась? Что прошла без ошибок?

Нужны служебные биты, минимум - одни стартовый и один стоповый. Т.е. для передачи байта нужно передать минимум 10 бит. Бывает, к ним добавляется бит четности и второй (полуторный) стоповый (строго говоря, стоповый бит может быть в 1, 1.5 или 2 раза длиннее информационного).

Я проделал эксперимент с замером времени на реальной процедуре. То, что там в физическую линию передается избыточная информация - я понимаю. Но в результате это все уже учтено. 

Smith2007
Offline
Зарегистрирован: 30.10.2017

Проделал еще один эксперимент с 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 с, в терминал выводится строка текста.

 

 

 

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Smith2007 пишет:

Всегда будет вероятность принять данные за сигнатуру

если только сигнатуру не сделать большой. Например 6 байт

но это как.то коряво выглядит. Или я ошибаюсь?

Не, если пакет грамотный - никогда ;) Помимо заголовка неплохо было бы иметь и жопку. На примере пакета длиной 10 байт:

ЗАГОЛОВОК (2 байта) - Данные (6 байт) - ЖОПКА (2 байта).

Вычитываешь 10 байт, сравниваешь на заголовок и жопку - если совпали - зуб даю, это пакет (конечно, ещё CRC8 надо, как минимум). Если не совпали - сдвигаешь на 1 байт в голову, читаешь следующий байт. И так до тех пор, пока не поймаешь заголовок и жопку, да ещё и CRC8 сойдётся. У мну пакет из 30 байт, юзаю именно этот подход: модулю, подтыкаемому к RS-485 - пофиг, когда он включен физически: он корректно ловит запрос от мастера на отдачу данных, реализовано ровно по описанной схеме.

Smith2007
Offline
Зарегистрирован: 30.10.2017

Пришел к следующему кадру (пакету)



STRT_HEAD Adr Cmd Reg D3 D2 D1 D0 CRC
byte byte byte byte byte byte byte byte byte byte byte byte

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 

При компиляции ошибка. Я не очень разобрался с работой с указателями.

Smith2007
Offline
Зарегистрирован: 30.10.2017

Исследования продолжаются...

Формат пакета

STRT_HEAD Adr Cmd Reg D3 D2 D1 D0 CRC
byte byte byte byte byte byte byte byte byte byte byte byte

Сигнатуру начала пакета увеличил до 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)

 

 

sadman41
Offline
Зарегистрирован: 19.10.2016

На строке 64 лучше использовать millis(), тогда таймаут будет вычисляться точнее.

Встроенный в IDE терминал может отправлять и без <CR> / <LF> - снизу справа можно выбрать, что досылать и досылать ли хоть что-то.

И мне всё же непонятно - почему вы все время о таймере каком-то пишете. В чем идея-то заключается - считывать из UART buffer байт периодически, по прерыванию, а не всё скопом в loop() ?

Smith2007
Offline
Зарегистрирован: 30.10.2017

sadman41 пишет:

На строке 64 лучше использовать millis(), тогда таймаут будет вычисляться точнее.

Да, я это понимаю, но подумал, что в этом месте высокая точность не нужна, а вот скорость нужна.

Предположил, что:

tStart = millisec;  будет выполнятся быстрее чем следующая строка

tStart = millis(); Тут же целый вызов функции. Не знаю как там в ее недрах но предполагаю, что времени потребуется больше

sadman41 пишет:

Встроенный в IDE терминал может отправлять и без <CR> / <LF> - снизу справа можно выбрать, что досылать и досылать ли хоть что-то.

:)

Да, это моя невнимательность. Спасибо. 

sadman41 пишет:

И мне всё же непонятно - почему вы все время о таймере каком-то пишете. В чем идея-то заключается - считывать из UART buffer байт периодически, по прерыванию, а не всё скопом в loop() ?

Ну в loop() будет работать некоторая программа. На прерывание по таймеру думаю повесить выполнение вот этого скетча (в виде процедуры)

По приходу данных с линии будет устанавливаться та или иная переменная. 

Программа контролирует давление в рессивере. Если оно падает ниже минимального значения - включить компрессор. Достигли максимального давления - отключить компрессор.

А вот минимальное и максимальное давление задавать через приложение со смартфона (Андроид) с организацией канала по блютус.

Это для примера привел. Там еще контроль напряжения бортовой сети и еще ряд датчиков

 

зы. Установил Visual studio и нашел простой пример работы с COM портом. Подставил в него свою последовательность и запустил. ЙЙЕЕС! Заработало! Это моя первая програмка на компе. Немного модифицировал и теперь если правильную команду отправить - зажигаю светодиод pin13. Неправильну (crc) - гаснет.

sadman41
Offline
Зарегистрирован: 19.10.2016

Smith2007 пишет:

Да, я это понимаю, но подумал, что в этом месте высокая точность не нужна, а вот скорость нужна.

Теоретически - да, на практике вы этого скорее всего не заметите, а вот свинью такой способ может подложить - если у вас между забором значения из millis() и использованием этого значения будет выполнятся какой-нибудь процесс с непредсказуемой продолжительностью исполнения (типа Ethermet.maintain(), к примеру, при использовании DHCP). Вобщем, пользуйте, но с осмотрительностью.
 
 

Smith2007 пишет:

Ну в loop() будет работать некоторая программа. На прерывание по таймеру думаю повесить выполнение вот этого скетча (в виде процедуры)
...
 
Овчинка выделки не стоит, по моему мнению, если вам блюпуп нужен не в реалтайм, а исключительно для выставления лимитов. Просто поработайте над процедурами чтения датчиков - чтобы блокирующими не были. Тогда лупа вам заглаза хватит.
 
Но потренироваться можете, конечно. Узнаете, к чему ведут долгие процедуры в обработчике прерываний.
 
 
Smith2007
Offline
Зарегистрирован: 30.10.2017

sadman41 пишет:

Smith2007 пишет:

Ну в loop() будет работать некоторая программа. На прерывание по таймеру думаю повесить выполнение вот этого скетча (в виде процедуры)
...
 
Овчинка выделки не стоит, по моему мнению, если вам блюпуп нужен не в реалтайм, а исключительно для выставления лимитов. Просто поработайте над процедурами чтения датчиков - чтобы блокирующими не были. Тогда лупа вам заглаза хватит.
 
Но потренироваться можете, конечно. Узнаете, к чему ведут долгие процедуры в обработчике прерываний.
 
Спасибо за подсказки.
Я примерно представляю что произойдет если обработчик прерывания будет на долго управление забирать.
(ну или надеюсь, что пинимаю :) )
Я поэтому и время измеряю, что бы понимать на сколько эта процедура загрузит контроллер. Останется ли время на выполнение других задач. 
Если этот код включить в обработчик, то за один вызов (например с периодом 1 мс) будет получен только 1 байт. И по времени это займет максимум 40 мкс.
Т.е. если по блютус прилетит пакет - то через 13 мс он будет принят полностью.
 
А вот дальше пока не пойму как быть....
Два варианта выижу:
1. В этой же процедуре обработки прерывания при успешном приеме пакета - разобрать его сразу. Т.е. записать значение в конкретную переменную.
Это увеличит время выполнения обработчика, но пока не осознаю на сколько. В принципе это проверка на код команды и регистр. 
2. Выставлять флаг готовности данных. А в основной программе (не в обработчике прерывания) если увидели готовность данных - выполнили присвоение или другие действия.
 
Второй метод вроде как разгружает проедуру обработки прерывания, но возникают совершенно непредсказуемые колизии. Например: Выставлен флаг готовности данных. В loop() я проверил этот флаг и начал проверку на код команды и регистр и пока я бежал по строкам, произошло прерывание и например изменился регистр (принял новые данные по линии), а я ничего не подозревающий записываю не понятно какие данные.
 
Или как-то вводить еще флаги.... При успешном принятии пакета, если не установлен флаг что происходит обработка данных, скопировать весь буфер в новый буфер, который только и доступен вне процедуры.
Но тут тоже не понятно, не записали сейчас. А когда записать? А если пока я не записываю данные в выходной (не рабочий) буфер, не прилетят ли новые данные? Ну в общем теории мне не хватает :)
sadman41
Offline
Зарегистрирован: 19.10.2016

Ну, понятное дело, что всегда будет вероятность получить данные во время работы с периферией. Избежать вы этого не сможете, как не старайтесь - хоть с разбором пакета в обработчике прерывания, хоть вне его. Данные могут начать поступать даже пока вы digitalWrite() исполняете, так как обе части вашей системы работают не синхронно. Тут я вижу навскидку два пути:

1) При передаче мастер должен поднять семафор "я передаю", который остановит все процессы слейва (через внешнее прерывание, ожидая пакет "ACK" перед передачей или как-то иначе). После передачи слейв должен переинициализироваться, т.е. начать свой цикл управления с шага 0, используя вновь принятые данные.

2) Мастер шлет значение уставки в пакете, пока слейв не ответит: "да понял я, понял". При этом слейв волен сделать это в лупе, по окончании цикла управления и чтении очередного пакета  из буфера UART. Конечно это не рилтайм, какое-то время пройдет до этого момента и ваша цель - максимально сократить выполнение loop(), чтобы анализ входящих пакетов был чаще. 

И я вас уверяю - нормально написанный луп даже не даст вам почувствовать, что между тыканьем в экран смартфона и получением уставки слейвом прошёл ощутимый с человеческой точки зрения временной промежуток. Просто не загоняйтесь на микросекундах в подобной задаче - вы моргаете медленнее, чем луп проходит.

А как только станете махать пальцами со скоростью колибри - так и начинайте бороться за мкс. К тому времени вы уже многим фокусам успеете научиться.

P.S. В ответе слейва неплохо было бы иметь ID того пакета, на который он отвечает "понял". Это если мастеру нужно точно знать, если он лупит по 50 уставок в секунду, что там слейв понял и не надо ли ему переповторить. Вобщем, посмотрите любой протокол с гарантией доставки, хоть тот же TCP.

Smith2007
Offline
Зарегистрирован: 30.10.2017

Вот такая мысль возникла...

В основной программе проинициализировать массив в котором в качестве индекса будет номер регистра, а в качестве элемента - ссылка на нужную переменную в программе. Массив можно заполнить в Setup()

Сколько байт занимает адрес переменной?

Не съем я всю память таким массивом?

sadman41
Offline
Зарегистрирован: 19.10.2016

Не понимаю смысла. Глобальную структуру/массив заведите, если память хотите поэкономить.

Заменять собой гугл я не собираюсь, поэтому учитесь добывать информацию:  https://www.google.ru/search?q=avr+pointer+size

Smith2007
Offline
Зарегистрирован: 30.10.2017

Точно! 

Я должен был сам догадаться, что незачем дублировать переменные. Достаточно их сразу в одну структуру свести. 

Ну что сказать.... учить и учить еще. И практики больше.

 

Научился с компа отправлять посылку и вкл/выкл набор светодиодов подключенных к пинам в зависимости от команды.

Данные передавать почти научился (и принимать тоже). Следующий этап - подключение блютус-модуля. Надеюсь в понедельник уже приедет. 

Тогда новый виток исследований. И больше всего смущает Android Studio. С архитектурой приложений для андроид совсем не знаком. Первые познания получил по видеоурокам. На эмуляторе удалось получить некоторый результат. Но все это все далеко от результата необходимого. Понимаю, что "не боги горшки обжигают" и даже медведей на вилосипеде учат кататься :)

Терпением только запастись учить ... учить ... учить ...  А как хочется уже результат пощупать! :)