Содержание
- Назначение и принцип работы
- Технические характеристики и принцип работы
- Подключение к Ардуино
- Пример использования
- Выводы
- FAQ
Назначение и принцип работы
UART (RS-232) старейший, простейший, универсальнейший и потому заслуженно любимый всеми интерфейс для обмена данными. Позволяет быстро, используя всего два проводка, связать контроллер с датчиком, модемом, другим контроллером и даже компьютером.
Однако, как и все в этом мире, UART, а точнее его аппаратная реализация RS-232, не лишен недостатков. Первый: небольшое расстояние, на котором можно обеспечить уверенный прием и передачу. При крепко спаянном кабеле с солидным экранированием, длина его может достигать лишь несколько метров, далее с каждым метром надежность и скорость падают катастрофически быстро. Второй: работа по принципу точка-точка, то есть соединить можно только два устройства.
Вот был бы способ передавать данные далеко и сразу нескольким! Такой способ есть, интерфейс носит название RS-485. Не будучи идеалом во всем, он предоставляет гораздо более широкие возможности для создания связей между разделенными расстояниями и задачами устройств. Узнать о некоторых возможностях на конкретном рабочем примере попробуем и мы в этой статье.
Технические характеристики и принцип работы
Аппаратно, интерфейс RS-485 реализуется на наиболее распространенных микросхемах семейства MAX485. Разновидностей в семействе десятки, отличаются они по количеству максимально поддерживаемых устройств, максимальной скорости, расстоянию, порядку передачи дуплекс/полудуплекс и так далее. Рассмотрим самую известную микросхему из этого семейства, которая так и называется - MAX485.
Основные характеристики:
- напряжение питания - 5В,
- ток потребления - 0,3 mA,
- расстояние передачи данных - до 1200м,
- скорость передачи данных сильно зависит от расстояния, от 62 кбит/с при 1200 м, до 10000 кбит/с при 10м,
- количество трансиверов на шине до 32 (другие представители семейства поддерживают до 256),
Пользоваться устройством крайне просто: микросхема одним концом присоединяется к аппаратному или программному порту UART контроллера, а другим к шине передачи данных, по которой информация передается в другой микросхеме. Таким образом, с точки зрения конечного устройства, передача данных ничем не отличается от привычной UART.
Соединение точка-точка:
Соединение нескольких устройств:
Шина данных представляет собой два провода, присоединяемых к одноименным точкам трансивера А или В. Суть технологии в том, что на два этих провода сигналы подаются синхронно но с инверсией, то есть когда на одном 1, на другом 0 и наоборот. Таким нехитрым образом обеспечивается увеличение разности потенциалов и высокая устойчивость к синхфазным помехам, которые воздействуют на оба провода одновременно, при этом не меняя разность потенциалов. Чтобы помехи были как можно более одновременными, провода следует размещать максимально близко друг к другу, то есть свивать в жгут.
Сама микросхема для работы требует несложной обвязки, состоящей из нескольких резисторов, кроме того, конечные трансмиттеры должны содержать гасящий отражения терминальный резистор 120 Ом, в народе известный также как “терминатор”. Для упрощения жизни DIY-мастеров китайские друзья заботливо придумали готовый модуль связи на базе MAX485, он уже содержит все необходимое и готов к работе как есть.
Именно такие модули мы будем использовать для нашего примера.
Подключение к Ардуино
Со стороны устройства на модуле имеются следующие пины:
● DI - передача данных, подключается к TX,
● RO - прием данных, подключается к RX,
● DE и RE - переключают направление передачи данных, на прием или на передачу, соединяются перемычкой и срабатывают одновременно, 1 - передача, 0 - прием.
Со стороны шины данных:
● GND - земля,
● VCC - питание +5 В,
● A и B, каналы на шину данных.
Упрощенно, принцип организации работы можно описать так: каждое устройство в сети имеет свой уникальный номер, по которому оно определяется и идентифицируется. В сети одно устройство главное, оно же ведущее, остальные сателлиты, они же ведомые. Ведомые изначально молчат и “слушают” сеть. Ведущее, в зависимости от задачи, дает команду ведомым либо (периодически) по таймеру, либо по какому-либо событию. Команду ведущего на шине видят все устройства, но откликаются только те, к кому он обращается по уникальному адресу. Если требуется ответ, ведущее переключается на прослушку, а ведомое на отправку данных, после чего управление снова передается главному. Таким образом обеспечивается последовательная коммуникация со всеми устройствами в сети без пересечений и коллизий. Существуют более сложные технологии взаимоотношений, например, передача статуса ведомого от устройства к устройству, многоуровневые сети и так далее, на что хватит фантазии разработчиков. В этой статье мы рассмотрим пример классической простой сети, состоящей из одного ведущего и двух ведомых устройств с обратной связью.
Рекомендации по разводке сети:
- чем меньше расстояния, тем лучше,
- топология сети, преимущественно, должна быть линейной,
- в качестве проводов рекомендуются витые пары,
- располагать шину, по возможности, подальше от источников электромагнитных помех в виде мощных потребителей, источников излучения и прочих.
Пример использования
Создадим простейшую рабочую сеть из трех элементов. Роль главного, на правах более внушительных размеров, достается UNO, роли остальных двух равноценных сателлитов исполнят NANO. У каждого из ведомых по одной кнопке и одному светодиоду, у главного две кнопки и два светодиода.
Задача: нажимая на кнопки главного устройства мы должны дистанционно активировать включение светодиодов на соответствующих ведомых. Нажимая же кнопки на ведомых, зажигаем соответствующий светодиод на главном. Логические связи можно изобразить так:
Верхний ряд - светодиоды, нижний - кнопки.
На физическом уровне связи между компонентами выглядят чуть запутаннее, но тоже очень логично.
Три контроллера с тремя модулями RS-485, соединёнными в простейшую сеть.
В реальной жизни схема, собранная мной на макетке, выглядит примерно так:
Заливаем программу в главный:
byte BUT[2] = {9, 8}; // пины кнопок byte LED[2] = {13, 12}; // пины светодиодов #define IN_OUT 6 // переключатель прием-отправка byte MSG_IN[4]; byte MSG_OUT[4] = {0x10, 0, 0, 0x20}; // шаблон сообщения: 0 - начальный, 1 - адрес сателлита, 2 - команда, 3 - конечный void setup() { for (byte i = 0; i < 2; i++) { pinMode(BUT[i], INPUT_PULLUP); pinMode(LED[i], OUTPUT); } pinMode(IN_OUT, OUTPUT); digitalWrite(IN_OUT, LOW); Serial.begin(9600); } void loop() { post(); // периодическая отправка команд на ведомые if (Serial.available()) { // прием команд от ведомых // delay(1); for (byte i = 0; i < 3; i++) MSG_IN[i] = MSG_IN[i + 1]; MSG_IN[3] = Serial.read(); if (MSG_IN[0] == 0x10 && MSG_IN[3] == 0x20) { // пришло полное сообщение digitalWrite(LED[MSG_IN[1]], !MSG_IN[2]); // выполняем, передаем на светодиод соответствующий адресу состояние пришедшее в команде } } } void post() { static unsigned long timerT; static byte msg_num = 0; if (timerT > millis() - 100) return; // отправляем последовательно каждые 100 мс MSG_OUT[1] = msg_num; MSG_OUT[2] = digitalRead(BUT[msg_num]); msg_num = !msg_num; digitalWrite(IN_OUT, HIGH); // включаем отправку for (byte i = 0; i < 4; i++) { // отправляем 4 байта сообщения Serial.write(MSG_OUT[i]); } delay(5); // ожидаем пока уйдет команда до того, как переключить отправку на прием digitalWrite(IN_OUT, LOW); // переключаемся на прием timerT = millis(); }
Основные моменты работы программ описаны в комментариях. Сюжет описан выше, технические подробности построчно.
byte BUT = 2; // пин кнопки byte LED = 13; // пин светодиода #define IN_OUT 6 // переключатель прием-отправка #define ADR 0 // адрес ведомого (для первого ведомого 0, для второго 1)! byte MSG_IN[4]; byte MSG_OUT[4] = {0x10, ADR, 0, 0x20}; // шаблон сообщения: 0 - начальный, 1 - адрес сателлита, 2 - команда, 3 - конечный void setup() { pinMode(BUT, INPUT_PULLUP); pinMode(LED, OUTPUT); pinMode(IN_OUT, OUTPUT); digitalWrite(IN_OUT, LOW); Serial.begin(9600); } void loop() { if (Serial.available()) { // delay(1); for (byte i = 0; i < 3; i++) MSG_IN[i] = MSG_IN[i + 1]; MSG_IN[3] = Serial.read(); if (MSG_IN[0] == 0x10 && MSG_IN[1] == ADR && MSG_IN[3] == 0x20) { // пришло полное сообщение именно нам! digitalWrite(LED, !MSG_IN[2]); // выполняем, устанавливаем в светодиод состояние пришедшее почтой MSG_OUT[2] = digitalRead(BUT); delay(5); // ждем пока главный переключится на прием digitalWrite(IN_OUT, HIGH); // включаем передачу for (byte i = 0; i < 4; i++) { // передаем Serial.write(MSG_OUT[i]); } delay(5); // ждем пока уйдет сообщение digitalWrite(IN_OUT, LOW); // переключаем на прием } } }
Заливаем на второй ведомый:
byte BUT = 2; // пин кнопки byte LED = 13; // пин светодиода #define IN_OUT 6 // переключатель прием-отправка #define ADR 1 // адрес ведомого (для первого ведомого 0, для второго 1)! byte MSG_IN[4]; byte MSG_OUT[4] = {0x10, ADR, 0, 0x20}; // шаблон сообщения: 0 - начальный, 1 - адрес сателлита, 2 - команда, 3 - конечный void setup() { pinMode(BUT, INPUT_PULLUP); pinMode(LED, OUTPUT); pinMode(IN_OUT, OUTPUT); digitalWrite(IN_OUT, LOW); Serial.begin(9600); } void loop() { if (Serial.available()) { // delay(1); for (byte i = 0; i < 3; i++) MSG_IN[i] = MSG_IN[i + 1]; MSG_IN[3] = Serial.read(); if (MSG_IN[0] == 0x10 && MSG_IN[1] == ADR && MSG_IN[3] == 0x20) { // пришло полное сообщение именно нам! digitalWrite(LED, !MSG_IN[2]); // выполняем, устанавливаем в светодиод состояние пришедшее почтой MSG_OUT[2] = digitalRead(BUT); delay(5); // ждем пока главный переключится на прием digitalWrite(IN_OUT, HIGH); // включаем передачу for (byte i = 0; i < 4; i++) { // передаем Serial.write(MSG_OUT[i]); } delay(5); // ждем пока уйдет сообщение digitalWrite(IN_OUT, LOW); // переключаем на прием } } }
Примечание: на время заливки программ следует отключить сеть RS-485, иначе одна программа зальется одновременно на все три устройства, так как аппаратный UART у них теперь объединен в единое целое.
Внимательный читатель заметит, что разница между программами сателлитов в одной единственной цифре. А именно - адресе устройства, остальное абсолютно идентично. У главного устройства адреса нет, потому что в данном случае он и не требуется.
Включаем, смотрим результат.
Замечательно, как и задумывалось! Посмотрим как выглядит сигнал со стороны UNO.
Верхняя линия - отправка данных с ведущего, вторая - ответ ведомых, нижняя - переключатель направления. Приблизим и расшифруем один сеанс обмена данными.
Если не обращать внимания на огрызки, принимаемые дешифратором за ошибки, которые ни на что не влияют, видно, что оба сообщения в точности соответствуют тому, что прописано в программе. От ведущего: 0x10 - стартовый байт, 0x00 - адрес обращения, первый ведомый, 0x01 - команда, в данном случае “погасить светодиод”, 0x20 - стоповый байт. Ответ от ведомого: 0x10 - старт, 0x00 - ответ от первого ведомого (теоретически это избыточная информация, т.к. ответ следует сразу после запроса, а запрос мы знаем для кого, но практически это значительно уменьшает вероятность ошибок), 0x00 - зажечь светодиод, 0x20 - стоп. Подобным же образом выглядит следующий за этим обмен со вторым ведомым.
Система работает правильно, но нам всегда есть к чему придраться и куда совершенствоваться. Во-первых, следует избавиться от delay(), даже от таких коротких. Для этого стоит перебрать функцию отправки данных, разбив ее на две части: собственно отправка, запуск таймера на переключение и переключение на прием. Во-вторых, для надежности в пакет данных можно добавить контрольную сумму, еще один байт, величина которого вычисляется из комбинации остальных, а потом сравнивается принимающей стороной. Для данного примера это несущественно, но если требуется доставка команды с гарантией, например на запуск ядерной ракеты, то лучше контрольную сумму добавить. В-третьих, в примере считывание данных о состоянии кнопок происходит прямо в момент отправки данных. В реальных же изделиях такое подходит не всегда, события должны отслеживаться сами по себе, а отправка-прием сообщений сами по себе. Принятая команда тоже не обязана выполняться моментально при приеме, а вдруг это длительная процедура и пока она длится, вся сеть будет висеть в ожидании. Для решения этих задач создается два буфера, один на прием, другой на передачу, данные из которых используются разными частями программы по мере необходимости.
Выводы
Связать несколько устройств в одну сеть для совместной работы не такая уж сложная задача, как может показаться на первый взгляд. Для этого смело пользуйтесь RS-485 удобным, простым, надежным и недорогим аппаратным дополнением к UART. Протокол несложно создать самому под конкретную задачу, а можно использовать готовый, например Modbus, если так привычнее или производится связь со стандартным устройством. RS-485 - еще одна хорошая возможность расширить границы собственного творчества!
FAQ
1. Можно ли использовать RS-485 для связи на расстояние более 1200м?Одним сегментом нельзя, но можно создать ретрансляторы (устройства, дублирующие сигналы с одной сети в другую и наоборот). Таким образом можно удлинять сеть почти бесконечно.
2. Как быть, если потребуется связать более 32 устройств?Вариантов несколько. Можно воспользоваться другими микросхемами из семейства, например MAX1487 (поддерживает до 128 устройств) или MAX1486 (до 256 устройств). Второй вариант - создать многоуровневую сеть, то есть разбить ее на сегменты, связанные в некоторых точках. Трудно сказать, что проще и что выбрать, вопрос решается в каждом случае индивидуально.
3. Какую максимальную скорость передачи данных можно устанавливать в сети RS-485?Стандартные рекомендации таковы:
- 62,5 кбит/с 1200 м (одна витая пара),
- 375 кбит/с 500 м (одна витая пара),
- 500 кбит/с 300 м,
- 1000 кбит/с 200 м,
- 2400 кбит/с 100 м (две витых пары),
- 10 000 кбит/с 10 м.
Однако подобрать оптимальные значения и убедиться в устойчивости связи можно лишь опытным путем в каждом конкретном проекте.
4. Аппаратный или программный UART предпочтительней использовать?Аппаратный использовать предпочтительней всегда, но если такой возможности нет, подключайте к программному. Но помните, что у него есть ограничения по максимальной скорости.
5. “Голая” микросхема MAX485 или модуль на ее основе?Для быстрого прототипирования удобнее и проще использовать готовый модуль, для более серьезных изделий, особенно с большим количеством устройств, лучше разработать схему на MAX. Обратите внимание, “терминатор” нужен только на концевых точках сети, а при использовании модуля он везде, что повышает нагрузку и снижает разность потенциалов с каждым дополнительным устройством.
6. Можно ли “перепрошивать” контроллер при помощи RS-485?Да, при правильной разводке, наличии на борту правильного бутоадера и предусмотренной в программе команды на перезагрузку. Таким образом можно осуществлять легкую смену прошивки в устройствах к которым трудно физически подобраться.