Работа с последовательным портом UART (serial) на Arduino
Последовательный интерфейс (serial) предназначен передачи данных через универсальный асинхронный порт UART. Порт UART очень часто используется для передачи данных с Ардуино на компьютер, и обратно, а также для связи нескольких плат ардуин между собой.
Для многопортовых DUE/MEGA см. здесь.
Основные функций для работы с последовательным портом (Serial)
Serial.begin(rate) — Открывает последовательный порт и задаёт скорость для последовательной передачи данных. Типичная скорость обмена для компьютерной коммуникации — 9600.
Очевидно, когда задействован последовательный порт, выводы 0 (RX) и 1 (TX) не могут использоваться для других целей.
Serial.println(data) — Передаёт данные в последовательный порт, сопровождая автоматическим возвратом каретки и переходом на новую строку.
Serial.print(data) — тоже самое без возврата каретки и перехода на новую строку.
Serial.begin(скорость_передачи); — Инициализация порта. Задает скорость передачи в битах в секунду. Нормированные скорости: 300, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 57600, или 115200.
count = Serial.available(); — Принимаемые по последовательному порту байты попадают в буфер микроконтроллера, откуда Ваша программа может их считать. Функция возвращает количество накопленных в буфере байт. Последовательный буфер может хранить до 128 байт.
if (Serial.available() > 0) < // Если в буфере есть данные // здесь должен быть прием и обработка данных >
char = Serial.read(); — Считывает следующий байт из буфера последовательного порта. возвращает -1 если нет входящих данных
Serial.flush(); — Ожидает окончания передачи исходящих данных (до версии Arduino 1.0 функция очищала буфер последовательного соединения)..
Разные варианты функции print:
Serial.print(b, DEC); — выводит ASCII-строку — десятичное представление числа b.
Serial.print(b, BYTE) — выводит младший байт числа b.
(аналогично HEX, OCT, BIN).
Serial.print(str) // если str — строка или массив символов, побайтно передает str на COM-порт.
Serial.println(); — отличие заключается в том, что после данных дополнительно выводятся два символа – символ возврата каретки (ASCII 13, или ‘\r’) и символ новой линии (ASCII 10, или ‘\n’).
Функция write:
Serial.write(uint8_t c); — Записывает данные в последовательный порт. Данные посылаются как байт или последовательность байт.
Serial.write(val); // где val — переменная для передачи, как единственный байт
Serial.write(str); // где str — строка для передачи, как последовательность байт
Serial.write(buf, len); // где buf — массив для передачи, как последовательность байт; len — длина массива.
Пример 1. Передача данных по Serial-порту с Arduino на компьютер
Инициализация порта со скоростью 9600 бот и передача данных (от Arduino на внешние устройства, например на компьютер):
void setup() < Serial.begin(9600); // открывает порт и устанавливает скорость 9600 бит/c Serial.println("Hello Computer"); >void loop() <>
Пример 2. Передача данных по Serial-порту с компьютера на Arduino
serialEvent() — функция вызывается автоматически, когда поступают данные.
String inputString = ""; void serialEvent() < // Далем что то с поступившими данными while (Serial.available()) < char inChar = (char)Serial.read(); // get the new byte inputString += inChar; if (inChar == '\n') < stringComplete = true; >>
Serial.setTimeout() — задает максимальное время (в мс) для работы Serial.readBytesUntil();
Возможные проблемы
1) auto-reboot DTR : возможна автоперезагрузка МК при передаче на него данных по serial-пору. Чтобы отключить это, надо поставить конденсатор 10мкФ между RES и GND. Я ставил электролитический кондер (естественно, + на RES).
Как соединить две ардуины по UART (serial) порту
Схема соединения двух ардуин:
Длина провода и скорость: RS-232 (англ. Recommended Standard 232) — стандарт физического уровня для асинхронного интерфейса (UART).
Расстояние связи по RS232 максимум 15 метров.
Но всё зависит от скорости.
Работа Arduino MEGA/DUE с несколькими последовательными serial портами
Многопортовые ардуино.
Как вы уже заметили, на ардуиновских платах Mega и Due имеется по 4 последовательных порта, а именно:
Serial — выводы 0 (RX) and 1 (TX);
Serial1 — выводы 19 (RX) and 18 (TX);
Serial2 — выводы 17 (RX) and 16 (TX);
Serial3 — выводы 15 (RX) and 14 (TX).
Естественно, что на Due используется напряжение 3.3 В (на MEGA как обычно 5 В).
Как с ними работать?
Здесь синим цветом выделены собственно имена объектов ( Serial , Serial1 , Serial2 , Serial3 ), которые используются в коде программы для работы с их методами. Всё просто! Например,
// Пример работы с несколькими serial-портами: void setup() < // инициализируем те порты, котор: Serial.begin(9600); Serial1.begin(9600); Serial2.begin(9600); Serial3.begin(9600); >// и так же с ними работаем: void loop() < // выводим строку в порт-2: Serial2.print("Hello World!"); // читаем данные из порта-1 и пишем в порт-3: if (Serial1.available()) < int inByte = Serial1.read(); Serial3.write(inByte); >>
Пример вывода на дисплей LCD1602 через последовательный порт UART Arduino из-под Linux средствами языка Python
Короче говоря, есть комп с линуксом, к которому подключена Arduino через USB, а к арудине подключен дисплей LCD1602, и мы хотим на него выводить инфу.
Сначала привожу полный код программы для Arduino UNO, к которой подключен дисплей LCD1602:
#include < LiquidCrystal_I2C.h > LiquidCrystal_I2C lcd(0x27,16,2); /* или 0x3F. */ const char TERM_SYMBOL = '#'; String inputString = ""; void serialEvent() < while (Serial.available()) < char inChar = (char)Serial.read(); // get the new byte Serial.print(inChar); if (inChar != TERM_SYMBOL) < inputString += inChar; >else < lcd.setCursor(0, 0); lcd.print(inputString); // Выводим текст inputString = ""; >> > void setup() < Serial.begin(9600); Serial.println("Hi Computer"); lcd.init(); // Инициализация lcd.backlight(); // Включаем подсветку lcd.print("Ready to work"); // Выводим текст >void loop() // цикл
Я сделал здесь решетку ‘#’ в качестве символа завершения передачи пакета данных. Как только в потоке данных встречается символ #, данные выводятся на дисплей, и буфер обнуляется, при этом сам символ ‘#’ не выводится. Конечно, можно было бы использовать ‘\n’ или др.
Далее мы напишем скрипт на Python, который будет выводить инфу на дисплей. Я выбрал Python, потому что это прикладной язык и он лучше всего подходит для подобных задач. С такими языками как C++/C# и т.п. больше возни с библиотеками, а здесь всё просто, особенно если это дело происходит под линуксом.
Первым делом устанавливаем необходимые библиотеки (для 2-ой или 3-ей версии python)
$sudo apt-get install python-serial
$sudo apt-get install python3-serial
Далее в интерпретаторе python пишем:
import serial ser = serial.Serial("/dev/ttyUSB0") ser.baudrate = 9600 ser.write(' #') # очищаем строку ser.write("123; It's work#") # выводим какие-либо данные
Здесь ардуина у меня подключена к порту /dev/ttyUSB0 — это я узнавал из Arduino IDE. Обычно она всегда на этом порту сидит, если других устройств на последовательный порт не подключено.
Как вы уже догадались, и в первой, и во второй программы должна быть указано одинаковая скорость в бодах. В моем случае это 9600 — стандартная, хотя и маленькая скрость. Может быть и больше (читай выше).
Платы ARDUINO по низкой цене
Нажми на изображение, чтобы заказать в интернет-магазине:
Now 30.04.24 20:19:50, Your IP: 95.214.216.82; arduino.zl3p.com/basic/serial
ePN
Интерфейс i2c Ардуино, сканер i2c шины (scl sda)
I2C Arduino (i2c — Inter-Integrated Circuits) — это протокол последовательной связи по двум линиям связи. Эти линии также называются: шина SDA (последовательные данные) и шина SCL (последовательный тактовый генератор). Порты SDA и SCL Ардуино подтягиваются к шине питания микроконтроллера через резисторы. Рассмотрим, что такое шина i2c Arduino Nano и как подключить к плате Ардуино несколько устройств по интерфейсу i2c.
Необходимые компоненты:
- Arduino Uno / Arduino Nano / Arduino Mega
- lcd 1602 i2c дисплей
- модуль rtc ds1307 i2c
- коннекторы
- библиотека LiquidCrystal_I2C.h и iarduino_RTC.h
- Подключение устройств по шине SPI к Arduino
- Порты коммуникации на Arduino Uno, Nano, Mega
- Язык программирования Ардуино C++
Микроконтроллеры Arduino используют два контакта для работы i2c. В Arduino Uno и Nano линия SDA соответствует аналоговому порту A4, а SCL — аналоговому порту A5. В Ardunio Mega линия SDA — это 20 вывод, а линия SCL — 21 вывод. Чтобы облегчить работу с шиной i2c и обмен данными между устройствами, для IDE Arduino написана стандартная библиотека Wire (скачивать и устанавливать библиотеку не требуется).
Протокол интерфейс i2c Ардуино: описание
Каждому устройству при подключении к микроконтроллеру по протоколу i2c присваивается уникальный адрес (можно подключить до 127 устройств). Изменить адрес устройства на шине невозможно, так как он встроен в микросхему. Часто производители модулей предлагают возможность изменения адреса устройства в небольшом диапазоне, что позволяет подключить к шине iic Arduino несколько одинаковых устройств одновременно.
I2C scanner Arduino (сканер шины i2c)
LCD 1602 i2c | Arduino Uno | Arduino Nano | Arduino Mega |
GND | GND | GND | GND |
VCC | 5V | 5V | 5V |
SDA | A4 | A4 | 20 |
SCL | A5 | A5 | 21 |
Следующая программа позволяет узнать адреса всех устройств, подключенных к шине i2c. Если устройство не было подключено или подключено неправильно, на мониторе порта появится сообщение о том, что устройство не найдено. Вместо дисплея можно подключить любое устройство, поддерживающее связь с микроконтроллером по протоколу iic — модуль часов реального времени DS1302, датчик атмосферного давления BMP180 и т.д.
Скетч для сканирования шины I2C Arduino
#include "Wire.h" void setup() < Wire.begin(); Serial.begin(9600); >void loop() < byte error, address; int nDevices; Serial.println("Scanning. "); nDevices = 0; for(address = 8; address < 127; address++ )< Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0)< Serial.print("I2C device found at address 0x"); if (address < 16) Serial.print("0"); Serial.print(address,HEX); Serial.println(" !"); nDevices++; >else if (error == 4) < Serial.print("Unknow error at address 0x"); if (address < 16) Serial.print("0"); Serial.println(address,HEX); >> if (nDevices == 0) Serial.println("No I2C devices found\n"); else Serial.println("done\n"); delay(5000); >
Подключение несколько устройств к шине i2c Ардуино
DS1307 i2c | Arduino Uno | Arduino Nano | Arduino Mega |
GND | GND | GND | GND |
VCC | 5V | 5V | 5V |
SDA | A4 | A4 | 20 |
SCL | A5 | A5 | 21 |
В следующем примере два устройства: дисплей 1602 и модуль часов времени будут подключены к шине IIC. После сборки схемы можно провести предварительное сканирование шины, используя программу, размещенную выше. Адреса всех устройств различны, и можно изменить только iic адрес жк дисплея 1602 i2c. Для остальных устройств адреса «жестко привязаны» и используются в библиотеках по умолчанию.
Скетч несколько устройств на i2c, scanner i2c шины
#include "Wire.h" #include "LiquidCrystal_I2C.h" LiquidCrystal_I2C LCD(0x27,16,2); #include "iarduino_RTC.h" iarduino_RTC time (RTC_DS1307); // DS1307 i2C // iarduino_RTC time (RTC_DS3231); // DS3231 i2C void setup() < Serial.begin(9600); LCD.init(); LCD.backlight(); time.begin(); // 0 sec, 30 min, 18 hour, 2 date, january, 2022, wed time.settime(0, 30, 18, 2, 1, 22, 0); >void loop() < // выводим время 1 раз в секунду if (millis() % 1000 == 0) < Serial.println(time.gettime("d-m-Y, H:i:s, D")); LCD.setCursor(0,0); LCD.print(time.gettime("d M Y, D")); LCD.setCursor(4,1); LCD.print(time.gettime("H:i:s")); >>
Заключение. Мы исследовали возможность подключения нескольких i2c устройств к плате Ардуино, что будет полезно знать при разработке различных DIY проектов. Например, метеостанции на Ардуино с дисплеем и датчиком давления. Если у вас остались вопросы о сканере шины i2c или о подключении нескольких периферийных устройств по этому протоколу, не стесняйтесь оставлять их в комментариях к этой статье.
Урок 1. Двусторонняя связь между двумя Arduino с использованием I2C.
При разработке проектов на Arduino часто возникает потребность в увеличении возможности, а пинов не достаточно. Также бывает необходимость объединить несколько работающих узлов для обмена данными, т.е. реализовать двухстороннюю связь между двумя Arduino. Для реализации двусторонней связи между двумя Arduino отлично подойдёт шина I2C.
Интерфейс I 2 C (или по-другому IIC) — это достаточно широко распространённый сетевой последовательный интерфейс, придуманный фирмой Philips и завоевавший популярность относительно высокой скоростью передачи данных, дешевизной и простотой реализации.
Шина I2C синхронная, состоит из двух линий: данных (SDA) и тактов (SCL). При проектировании есть 2 типа устройств: ведущий (master) и ведомый (slave). Инициатором обмена всегда выступает ведущий, обмен между двумя ведомыми невозможен. Всего на одной двухпроводной шине может быть до 127 устройств.
Такты на линии SCL генерирует ведущий (master). Линией SDA могут управлять как мастер, так и ведомый (slave), в зависимости от направления передачи. Единицей обмена информации является пакет, обрамленный уникальными условиями на шине, именуемыми стартовым и стоповым условиями. Мастер в начале каждого пакета передает один байт, где указывает адрес ведомого и направление передачи последующих данных. Данные передаются 8-битными словами. После каждого слова передается один бит подтверждения приема приемной стороной.
Ведущее устройство инициирует связь с ведомым устройством. Для начала разговора требуется адрес ведомого устройства. Подчиненное устройство реагирует на ведущее устройство, когда к нему обращается ведущее устройство.
I 2 C используется во многих приложениях, таких как чтение RTC (часы реального времени), доступ к внешней памяти EEPROM. Он также используется в сенсорных модулях, таких как гироскоп, магнитометр и т. д.
Контакты Arduino I2C.
Выводы I2C расположены следующим образом на плате Arduino Uno.
Для других моделей плат соответствие выводов такое:
Плата
Пин SDA
Пин SCL
Связь нескольких Arduino по проводу [GyverBus]
Не нашёл нормальных способов связать несколько Ардуинок в проводную сеть, решил сделать свой. Да, существуют библиотеки на базе Wire (например EasyTransfer), но там всё очень криво, по двум проводам и на слишком высокой частоте, т.е. на большом расстояние отправить не получится. В рассмотренной библиотеке предлагается использовать программный аналог интерфейса UART с двухсторонней связью по одному проводу, встроенным протоколом передачи, адресацией, упаковщиком и распаковщиком данных, контролем целостности и прочими приколами. Скорость интерфейса невысокая, что позволяет отправлять данные по проводам на очень большие расстояния и с высокой надёжностью, а лёгкая реализация вмещается даже в ATtiny13 на приём и на отправку. Расскажу немного о том, как работает интерфейс.
Подключение производится по одному пину, земля (GND) должна быть общая. В сети могут находиться любые Ардуино – совместимые платы и МК, подключение – к любому GPIO (цифровому пину):
Сам интерфейс имеет такую же суть, как UART:
Чтение производится в ожидаемых серединах фреймов, начало отсчёта – стартовый LOW:
В начальный момент времени все устройства держат пин в подтянутом к питанию состоянии ( INPUT_PULLUP ). Передавать данные может только одно устройство. Все устройства в сети принимают данные одновременно, но в пакет входит адрес приёмника, поэтому приёмник поймёт, что данные предназначены ему. Остальные устройства проигнорируют принятый пакет.
Применение
Зачем нужен данный интерфейс? Можно собрать устройство с центральной базой (например на Arduino Nano) и несколькими удалёнными, например на копеечных ATtiny13. База будет общаться с удалёнными устройствами и отправлять им команды на исполнение (включить/выключить нагрузку, увеличить/уменьшить тепловую мощность, яркость и т.д.), а также может запрашивать данные с их датчиков. Связь по медленному цифровому протоколу получится очень надёжной даже на большом расстоянии (сотни метров), что избавляет от применения дополнительных конвертеров интерфейсов. Та же тинька 13 может отправлять на базу данные с датчиков температуры/влажности/освещённости и так далее.
Как ещё один вариант: индивидуальное управление яркостью освещения для цепи светильников в саду или на складе: в каждом светильнике стоит тинька и управляет яркостью своей лампы по команде с базы. Преимущество в том, что все светильники связаны между собой и вместе с базой всего двумя проводами.
А нужен ли GBUS?
Для передачи между двумя платами через Serial (обычный и software) можно использовать полностью стандартный способ: методы write() и readBytes(). Функции принимают байтовые массивы, но на ничего не мешает их обмануть и передавать что угодно, даже структуры:
// Пример отправки и приёма структуры через Serial // ОТПРАВИТЕЛЬ // Ардуины соединены так: // отправитель D11 -> приёмник D10 #include < SoftwareSerial.h>SoftwareSerial mySerial(10, 11); // RX, TX struct Str < byte val_b; int val_i; long val_l; float val_f; >; void setup() < Serial.begin(9600); mySerial.begin(4000); >void loop() < // буфер на отправку Str buf; // заполняем buf.val_b = 123; buf.val_i = 12345; buf.val_l = 123456; buf.val_f = 123.456; // отправляем родным write() // указываем ему буфер-структуру, но приводим тип к byte* // размер можно указать через sizeof() mySerial.write((byte*)&buf, sizeof(buf)); delay(2000); >
// Пример отправки и приёма структуры через Serial // ПРИЁМНИК // Ардуины соединены так: // приёмник D10 -> отправитель D11 #include < SoftwareSerial.h>SoftwareSerial mySerial(10, 11); // RX, TX // структура для приёма // должна соответствовать отпраляемой struct Str < byte val_b; int val_i; long val_l; float val_f; >; // создаём саму структуру Str buf; void setup() < Serial.begin(9600); mySerial.begin(4000); >void loop() < // читаем родным методом readBytes() // указываем ему буфер-структуру, но приводим тип к byte* // размер можно указать через sizeof() if (mySerial.readBytes((byte*)&buf, sizeof(buf))) < Serial.println(buf.val_b); Serial.println(buf.val_i); Serial.println(buf.val_l); Serial.println(buf.val_f); >>
О других способах читайте в уроке общение по Serial.
GBUS по сути делает то же самое, но предлагает дополнительные возможности:
- GBUS использует свои способы отправки и чтения байтов (на базе read, write и available), которые не блокируют выполнение остального кода на время чтения и отправки, в отличие от стандартных. На медленных скоростях это может быть критично.
- В GBUS встроена проверка целостности данных, в десятки раз увеличивающая надёжность передачи.
- GBUS превращает интерфейс в шину, то есть общаться могут не два устройства, а сколько угодно, так как имеют адресацию.
- GBUS даёт расширенную отладку ошибок по приёму данных.
- В GBUS есть встроенные функции, позволяющие “достучаться” до приёмника, то есть отправить ему запрос и дождаться корректного ответа.
БИБЛИОТЕКА
GyverBus v2.4
- Очень простой, надёжный, устойчивый к помехам и задержкам, но медленный интерфейс связи на базе UART
- Двухсторонняя связь по одному проводу
- Асинхронная отправка и чтение (на базе millis())
- Двухсторонняя совместимость с аппаратным UART
- Возможность принимать и отправлять данные внутри сети Ардуинок
- Адресация до 254 устройств в сети (от 1 до 255)
- Всеядная функция отправки и приёма (ест переменные, структуры, массивы)
- Встроенная проверка CRC (контроль целостности) пакета данных
- Возможность отправки и чтения короткого “запроса”
- Сама библиотека предоставляет возможности по отладке (коды ошибок)
- В примерах есть компактные варианты чтения и отправки данных, влезет даже в ATtiny
Поддерживаемые платформы: все Arduino (используются стандартные Wiring-функции)
УСТАНОВКА
- Библиотеку можно найти и установить через менеджер библиотек по названию GyverBus в:
- Arduino IDE (Инструменты/Управлять библиотеками)
- Arduino IDE v2 (вкладка “Library Manager”)
- PlatformIO (PIO Home, вкладка “Libraries”)
ДОКУМЕНТАЦИЯ
Общая информация
Особенности новой версии
В новой версии библиотеки протокол GBUS был полностью отделён от интерфейса: сейчас GBUS — это протокол связи, то есть порядок байтов в посылке (адрес, CRC и прочее). Общаться по GBUS можно при помощи любой Serial библиотеки, а точнее — любого объекта класса Stream (родная библиотека аппаратного Serial, включая Serial1, Serial2 и прочие на Arduino Mega, SoftwareSerial на любой Ардуино-совместимой платформе, а также встроенный в GyverBus однопроводной softUART). Это позволяет удобно, надёжно и не блокируя выполнение остального кода передавать наборы данных любых типов между Ардуино-совместимыми платами (Arduino, esp8266, esp32) при помощи программного или аппаратного UART’a.
Отправка широковещательных сообщений (new!)
В версии 2.3 добавлена возможность отправки сообщений «всем» по адресу 255: любой приёмник получает сообщение, отправленное на его личный адрес и на адрес 255.
Особенности библиотеки
Библиотека содержит в себе несколько наборов инструментов:
- GyverBus.h — базовый набор инструментов для работы с протоколом GBUS: функции для упаковки, распаковки и проверки байтовых массивов для передачи и приёма данных. Функции можно использовать отдельно от всего остального для упаковки-распаковки и проверки данных для передачи по любому интерфейсу связи, просто передавая любые данные как байтовый массив.
- GBUS.h — класс GBUS, который позволяет удобно общаться по протоколу GBUS через любой «Serial». В отличие от родных инструментов класса Stream не блокирует выполнение кода на приём и отправку, что очень критично на невысоких скоростях.
- softUART.h — однопроводной UART, работающий на приём и отправку, не блокирующий выполнение кода. Может использоваться для библиотеки GBUS, так как наследует класс Stream.
- GBUSmini.h — набор отдельных функций для общения по GBUS. Все функции блокируют выполнение кода на время приёма/отправки (сделаны на базе delay() ), но реализованы максимально легко и позволяют работать с шиной даже на ATtiny13. Для работы нужно подключить библиотеку и просто использовать нужные функции из доступных.
Подключение для softUART
Платы/микроконтроллеры объединяются в шину одним дата-проводом и должны иметь общую землю GND. Пин шины — любой обычный GPIO (цифровой пин входа-выхода). Пин должен быть подтянут к питанию внутренним (режим INPUT_PULLUP ) или внешним резистором (5-100 кОм). При использовании GyverBus.h внутренняя подтяжка включается автоматически, а для GBUSmini.h её нужно прописать самому (см. примеры). Схема подключения — на самой первой картинке этой страницы сайта.
Для удалённого подключения рекомендуется использовать экранированный провод, экран нужно подключить на GND. Если подключение производится двумя обычными проводами (GND и дата) — провода рекомендуется скрутить в косичку для защиты от наводок.
Подключение для Serial
Для одностороннего общения по классическому Serial достаточно соединить TX передатчика и RX приёмника. Для двухсторонней связи нужно будет соединить ещё и RX передатчика и TX приёмника. Например для использования функций «достукивания» до приёмника, чтобы удостовериться в том, что он получил данные.
Общение по шине
Шина двухсторонняя, любое из устройств в сети может быть отправителем и получателем, но в один момент времени может осуществляться только одна передача: один передаёт — все слушают. Такая логика работы позволяет всем устройствам общаться по очереди между собой без ограничений.
Совместимость softUART
softUART имеет такие же контрольные биты и тайминги, как у UART (в ардуиновской среде — Serial), и полностью с ним совместим, то есть можно отправлять данные по Serial (с ноги TX) и принимать на softUART, либо отправлять c softUART и принимать по Serial (на ногу RX). Скорость Serial нужно выставлять выше 250 бод, softUART соответственно тоже.
Скорость
- softUART — скорость задаётся при создании объекта.
- GBUS — скорость задаётся у самого интерфейса, например у «Serial’ов» это begin.
- GBUSmini — скорость задаётся в GBUSmini.h, параметром GBUS_DEFAULT_SPEED, либо дефайном GBUS_SPEED в скетче перед подключением библиотеки.
Скорость задаётся в бодах (baud rate) и совпадает с бодрейтом UART’а. Время передачи одного байта равно 10 / baud секунд, соответственно скорость передачи составляет baud / 10 байт в секунду (например при скорости 300 бод — 30 байт в секунду).
Примечание: чем ниже скорость, там надёжнее передача и тем меньше влияют помехи, индуктивность линии и несовпадение частот тактирования у передатчика и приёмника. Скорость должна быть одинаковой для всей шины, т.е. всех подключенных к ней устройств!
Максимальная скорость
Максимальная скорость ограничена прежде всего соединением: длиной проводов, наличием экрана, наличием источников помех и прочим. В идеальных условиях скорость будет такая:
При использовании в качестве интерфейса аппаратного Serial скорости могут быть очень большие, вплоть до 1000000 бод.
При использовании в качестве интерфейса программного Serial читайте описание к нему. Ардуиновский софтсериал обещает стабильную передачу на скоростях вплоть до 115200. В то же время он блокирует код и запрещает прерывания, что может быть очень плохо.
При использовании в качестве интерфейса softUART очень важен частый вызов tick(): рекомендуется делать это не реже, чем каждые 1 000 000 / baud / 4 микросекунд. При наличии в остальном коде задержек или блокирующих выполнение участков на время, превышающее четверть времени бита, передача на высоких скоростях может происходить с ошибками. Можно вызывать tick() по прерыванию таймера. Ещё можно сделать так:
void loop() < if (rx.tick() != RECEIVING) < // Потенциально "тяжёлый" код. // Не выполняем, пока идёт приём! >// "лёгкий" код >
При использовании библиотеки GBUSmini.h максимальная скорость ограничена частотой опроса читающих функций (вызывать не реже, чем каждые 1 000 000 / baud / 4 микросекунд) и точностью настройки коррекции: в GBUSmini.h в секции настроек есть параметры GBUS_DEFAULT_WRITE и GBUS_DEFAULT_READ . Они отвечают за коррекцию задержки в микросекундах на отправку и чтение одного бита. Это значение зависит от частоты тактирования МК ( пропорционально! ), модели самого МК, версии «ядра» Arduino и подбирается вручную. Также значение коррекции может меняться в зависимости от скорости шины! Привожу некоторые известные:
МК Ядро GBUS_DEFAULT_WRITE GBUS_DEFAULT_READ ATmega328p (Arduino Nano) Стандартное версии 1.8.3 8 (при 16 MHz) 5 (при 16 MHz) Максимальная скорость с учётом «пустого» скетча и откалиброванными значениями коррекции задержки, платы соединены на бредборде проводами длиной 10см:
Отправитель — приёмник Макс. скорость softUART — softUART 25’000 GBUSmini — GBUSmini 10’000 softUART — GBUSmini 15’000 GBUSmini — GBUSmini 10’000 Улучшение связи
Предложено Саней Чёрным. Есть предложение по улучшению помехоустойчивости работы на дальние дистанции. К примеру имеем 30 устройств на линии. У каждого подтяжка к + внешняя по 100К. В сумме считаем это как параллельное соединение резисторов и при передаче LOW(0-ка) нужно пересилить 3.45кОм это 1.45мА на вывод. Все вроде как хорошо, но если в этом же кабеле( к примеру ПВС4*1 — четырехжильный кабель с сечением 1мм^2) рядом идет 220VAC, то на линию GND Arduin-ы будет идти нехилая наводка, которая на длине в 100м(а это всего 50м от передатчика, т.к. 50м провод данных и 50м провод земли) приведет к целому ряду ложных приемов пакетов. Поэтому предлагаю схему, которая позволит каждому устройству на линии при получении LOW дублировать его. Это влечет за собой уменьшение скорости передачи данных. Т.к. каждая оптопара имеет задержку в 4мкс. А на 30 последовательно соединенных устройствах это 0.12мс.
Контроль целостности
В библиотеку встроен контроль целостности данных (включен по умолчанию): последним байтом в пакете передаётся crc (очевидно 8-ми битный). Приёмник считает CRCвсего пакета, и если он совпадает — приём считается успешным. CRC повышает надёжность передачи данных: даже если один бит будет передан неправильно — приёмник отбракует посылку.
Примечание: у прошивок всех устройств в сети CRC должен быть или включен, или выключен. Устройство, настроенное на приём данных с CRC не сможет принять пакет без CRC.
Проверку CRC можно отключить и в основной библиотеке, и в мини-версии. Зачем отключать? Позволит сэкономить чутка памяти. Лучше не отключать CRC, надёжность передачи данных сильно снизится.
Протокол связи
Протоколом здесь назван порядок и значение байтов в пакете:
Тип посылки Байт 1 Байт 2 Байт 3 Байт 4 Байт 4+n Байт 4+n+1 Данные Количество байт Адрес получателя Адрес отправителя Байт даты 1 Байт даты n CRC Тип посылки Байт 1 Байт 2 Байт 3 Байт 4 Запрос (request) 0 Адрес получателя Адрес отправителя CRC Тип посылки Байт 1 Байт 2 Байт 3 Байт 4 Ответ (ack) 1 Адрес получателя Адрес отправителя CRC Таким образом например пакет [5, 3, 8, 123, 456, 12] содержит 6 байт, предназначен для устройства с адресом 3 и отправлен с адреса 8. Байты данных имеют значение 123 и 456, а CRC — 12.
Оба способа взаимодействия с GBUS работают по этому протоколу и совместимы между собой. Если стоит задача сформировать пакет вручную, отправить и принять его через функции из библиотеки — пакет должен быть сформирован согласно протоколу.
Адресация
В GBUS используется 8-ми битная адресация, но адрес 0 зарезервирован как код ошибки чтения в GBUSmini.h. Таким образом адресовать устройства можно с 1 по 255 адрес.
Если не работает
Самая нестабильная передача — между softUART и GBUSmini, а также между устройствами на базе разных микроконтроллеров и/или с разной частотой тактирования. Первым делом стоит попробовать чуть увеличить или чуть уменьшить значения коррекции задержки.
Совместимость
- Вариант с GBUS поверх родного Serial или софтсериал совместим абсолютно между всеми платформами.
- softUART лично мне не удалось завести на esp8266, возможно виной тому кривая распиновка и неудачно выбранные пины
- А так по сути Serial он везде Serial, то есть общаться можно между разными МК и платформами
Инициализация
GBUS объект(обработчик, адрес, размер буфера) , где:
- обработчик — адрес объекта-обработчика интерфейса (например &Serial , &mySerial )
- адрес — адрес этого устройства в сети (1-255)
- размер буфера — размер внутреннего буфера в количестве байт (больше или равен отправляемому объёму данных)
Метод tick()
Метод tick() занимается отправкой и приёмом данных по шине, то есть измеряет время и слушает/дёргает пин, что обеспечивает «асинхронную» работу интерфейса. Вызывать тик нужно как можно чаще, желательно как минимум в 4 раза чаще, чем 1’000’000 / скорость микросекунд. То есть например для скорости 300 это будет 1000000/300/4 ~ 830 микросекунд. Можно оставить его в loop() и обеспечить его «прозрачное» выполнение без задержек, либо дополнительно положить в прерывание таймера с таким периодом. Логика работы tick() автоматически переключается в зависимости от текущего режима: во всё время кроме отправки мы «слушаем» шину.
Также tick() возвращает текущее состояние шины.
Коды статусов и ошибок
Помимо тика, текущий статус можно узнать из функции getStatus() , она возвращает то же самое, что тик:
Код Название Описание 0 GBUS_IDLE Ожидание 1 TRANSMITTING Передача 2 TX_OVERFLOW Буфер переполнен 3 TX_COMPLETE Передача завершена 4 RECEIVING Приём 5 RX_ERROR Ошибка приёма 6 RX_ABORT Ошибка. Приём прерван 7 RX_OVERFLOW Ошибка. Буфер или пакет переполнен 8 RX_ADDRESS_ERROR Ошибка. Не наш адрес 9 RX_CRC_ERROR Ошибка. Не совпадает CRC 10 RX_REQUEST Успешное получение запроса 11 RX_COMPLETE Успешный приём данных 12 RX_ACK Успешное получение подтверждения Статус можно опрашивать как по коду, так и по имени (константа подсветится синим).
В версии 2.5 появилась функция statusChanged() , возвращающая true при изменении статуса. Использовать можно так:
bus.tick(); // тикаем if (bus.statusChanged()) < // если статус изменился Serial.println(bus.getStatus()); // выводим код >
Отправка данных
Для отправки данных нужно взывать sendData(адрес, дата) , где адрес — адрес принимающего устройства, а дата — любой тип данных (переменная любого типа, массив переменных любого типа, включая char array, структура). Данные будут автоматически разбиты на байты и отправлены внутри tick() .
Пример отправки структуры:
struct myStruct < byte val_b; int val_i; float val_f; >; myStruct tx_data; void setup() < // отправляем на адрес 2 tx.sendData(2, tx_data); >void loop()
Приём данных
На принимающей стороне опрашиваем функцию gotData() , она однократно вернёт true при получении корректного пакета данных (внутри tick() автоматически проводится проверка ошибок передачи, переполнения, совпадения адреса и контроль целостности данных, если он включен).
Данные читаются при помощи readData(дата) , где дата — переменная с таким же типом данных, какой был отправлен. Функция сама запишет данные в указанную переменную, то есть соберёт из встроенного буфера. Пример:
struct myStruct < byte val_b; int val_i; float val_f; >; myStruct rx_data; void setup() < >void loop() < rx.tick(); if (rx.gotData()) < rx.readData(rx_data); >>
Адрес отправителя запоминается при получении, его можно прочитать из getTXaddress() .
Отправка и приём запроса
В библиотеке также реализована отправка короткого «запроса» при помощи sendRequest(адрес) . При получении такого запроса на текущее устройство метод gotRequest() вернёт true .
Структура запроса для работы с шиной вручную: [0, адрес получателя, адрес отправителя]
Отправка и приём подтверждения
В библиотеке также есть отправка и приём «подтверждения» — ack. Отправить можно при помощи sendAck(адрес) и принять в gotAck() , логика такая же как у запроса.
Структура подтверждения для работы с шиной вручную: [1, адрес получателя, адрес отправителя]
Отправка запроса с подтверждением
В библиотеке реализован блокирующий метод sendRequestAck(адрес, кол-во попыток, таймаут) , который отправляет запрос по указанному адресу и ждёт подтверждения приёма (ack) или отправленные в ответ данные. Если в течение указанного таймаута данные или ack не получены — запрос будет отправлен ещё раз, и так до тех пор, пока количество попыток не достигнет указанного. Это позволяет «достучаться» до приёмника и понять, получил ли он запрос, или дождаться от него данные в ответ на запрос. Смотри пример call_response_ack.
Сам метод sendRequestAck() возвращает статус:
Код Название Описание 2 ACK_ERROR Ответ не получен 3 ACK_ONLY Получено подтверждение 4 ACK_DATA Получены данные Ожидание ответа (v1.1)
Метод waitAck(адрес, кол-во попыток, таймаут) работает по такой же логике, как предыдущий, но не блокирует выполнение кода. Логика такая: вручную отправляем реквест sendRequest(адрес) и при помощи waitAck() можем дождаться ответа и попытаться достучаться до приёмника, автоматически отправляя новые запросы через таймаут миллисекунд. Количество попыток ограничено заданным. Метод возвращает статусы:
Код Название Описание 0 ACK_IDLE Ничего не делаем 1 ACK_WAIT Ждём ответа 2 ACK_ERROR Ответ не получен 3 ACK_ONLY Получено подтверждение 4 ACK_DATA Получены данные Примеры находятся в examples/GBUS/wait_ack. В примере wait_ack_rx можно закомментировать отправку ответа и в мониторе порта увидеть, как передатчик отправляет несколько запросов перед тем, как выдать ошибку.
Широковещательная отправка (v2.4)
Если отправлять данные или запросы на адрес 255, то их получат все устройства на линии. Вместо цифры можно использовать константу GBUS_BROADCAST .
Отправка и приём сырых данных
Сырые данные (просто байтовый массив) можно отправить по шине при помощи sendRaw(байтовый массив, размер) , где размер можно передать как sizeof(массив) . Метод gotRaw() вернёт true , если приёмник корректно принял какие-то данные. Размер принятого пакета можно узнать в rawSize() , а прочитать его — обратившись напрямую к члену класса buffer , как к массиву.
Инициализация
- пин — любой GPIO пин. Автоматически будет настроен в INPUT_PULLUP. Если встроенной подтяжки нет — тянуть вручную резистором.
- режим — режим работы объекта:
- GBUS_FULL — двухсторонняя связь (активен по умолчанию, можно не указывать)
- GBUS_TX — только отправка (экономит память)
- GBUS_RX — только приём (экономит память)
Использование с GBUS
Для общения по GBUS через однопроводной UART достаточно передать его в GBUS при создании:
// подключаем софт юарт #include "softUART.h" softUART myUART(1000); // пин 4, скорость 1000 // подключаем GBUS #include "GBUS.h" GBUS bus(&myUART, 5, 20); // обработчик UART, адрес 5, буфер 20 байт
Буфер
softUART имеет свой программный буфер на отправку, его размер можно настроить в файле softUART.h, параметр SOFTUART_BUF_SIZE
Особенности
GBUSmini.h содержит набор функций для работы с шиной, все функции (за исключением GBUS_is_busy(пин) ) — блокирующие, то есть блокируют выполнение кода на время отправки или чтения пакета. Функции чтения должны работать в таких же условиях, как и tick() : вызываться как можно чаще, чтобы не пропустить начало передачи. Все функции работают только с массивом байтов (в описании ниже — дата), также в функции передаётся его размер (например через sizeof(data) ). Для работы с другими типами данных используй упаковщик и распаковщик данных из «Утилиты«.
Выключение CRC
CRC включается и выключается в файле GBUSmini.h, параметр GBUS_CRC в секции настроек
Отправка и чтение данных
- GBUS_send_raw(пин, дата, размер)
- GBUS_read_raw(пин, дата, размер) — возвращает количество принятых байт при успешном завершении приёма
- GBUS_send(пин, адрес получателя, адрес отправителя, дата, размер)
- GBUS_read(пин, наш адрес, дата, размер) — возвращает адрес отправителя при успешном завершении приёма. При ошибке возвращает 0
Отправка и чтение запроса
- GBUS_send_request(пин, адрес получателя, адрес отправителя)
- GBUS_read_request(пин, наш адрес) — возвращает адрес отправителя при успешном завершении приёма. При ошибке возвращает 0
Отправка и чтение подтверждения
- GBUS_send_ack(пин, адрес получателя, адрес отправителя)
- GBUS_read_ack(пин, наш адрес) — возвращает адрес отправителя при успешном завершении приёма. При ошибке возвращает 0
Запрос с ожиданием
GBUS_send_request_ack(пин, адрес получателя, адрес отправителя, кол-во попыток, таймаут между попытками) — отправить запрос и ждать подтверждения приёма, т.е. пытаться «достучаться» до приёмника. Возвращает 0 при таймауте, 1 при успехе (получили ack). Смотри примеры call response_ack
// =========== GBUS =========== // проверить статус принятых данных (буфер, его размер, кол-во принятых байтов, наш адрес) // вернёт статус GBUSstatus GBUSstatus checkGBUS(uint8_t* buffer, byte bufSize, byte amount, byte addr); // ====== УПАКОВЩИК GBUS ====== // запаковать данные для отправки (буфер, его размер, дата, адрес получателя, адрес отправителя) // вернёт количество упакованных байт // запакует согласно протоколу [суммарное количество байт, адрес получателя, адрес отправителя, . байты даты. CRC] template byte packGBUSdata(uint8_t* buffer, byte bufSize, T &data, byte to, byte from); // распаковать данные, минуя служебные байты (буфер, его размер, дата) // при успехе вернёт true. Вернёт false, если буфер слишком мал для даты template bool unpackGBUSdata(uint8_t* buffer, byte bufSize, T &data); // запаковать команду в буфер (буфер, команда, кому, от кого) // команды: запрос (0), ответ (1) // запакует согласно протоколу [команда, адрес получателя, адрес отправителя, CRC] byte packGBUScmd(uint8_t* buffer, byte cmd, byte to, byte from); // ====== УПАКОВЩИК БАЙТОВ ====== // пакуем любой тип данных в байтовый буфер (буфер, дата) template void packDataBytes(byte *buffer, T &data); // распаковываем из байтового буфера обратно (буфер, дата) template void unpackDataBytes(byte *buffer, T &data); // ============= CRC ============= // обновить CRC байта (crc, байт) void GBUS_crc_update(uint8_t &crc, uint8_t data); // расчёт crc для буфера (буфер, количество байт для проверки) byte GBUS_crc_bytes(byte *data, byte size);
ПРИМЕРЫ
Остальные примеры смотри в папке examples библиотеки, также примеры можно открыть из Arduino IDE/Файл/Примеры
В библиотеке очень много примеров, смотри их в папке examples =) Онлайн-версия на GitHub
ПОДДЕРЖАТЬ
Вы можете поддержать меня за создание доступных проектов с открытым исходным кодом, полный список реквизитов есть вот здесь .