Из предыдущих уроков мы знаем, что Ардуино имеет несколько встроенных шин для быстрой передачи данных. Например, для работы с дисплеями обычно используют шину SPI. Разного рода датчики часто подключают по шине I2C. Есть ещё популярный UART, который часто используют вместе с интерфейсом USB для обмена данным с компьютером. Но у всех этих способов есть серьёзный недостаток — они не работают на больших дистанциях!
Представим себе ситуацию, когда нам нужно собирать данные с датчиков на крыше дома, и в зависимости от их показаний, переключать реле в подвале. Как быть? Нужен какой-то мощный, помехоустойчивый интерфейс. И такой интерфейс уже десятки лет используется в промышленности. Имя ему — RS485.
На этом уроке мы попробуем соединить две платы Ардуино с помощью длинного кабеля, используя тот самый 485-й интерфейс.
Список необходимых компонентов
Для выполнения всех экспериментов в этом уроке потребуются два контроллера Arduino Uno или любой их аналог, модуль RS485-TTL, немного проводов и две макетные платы.
Необходимые компоненты можно добавить в корзину прямо здесь, и затем оформить заказ в нашем интернет-магазине.
Несколько фактов о RS485
RS485 — это последовательный интерфейс, предком которого является RS232. Последний получил известность из-за COM-портов старых компьютеров, которые как раз работали по интерфейсу RS232.
Максимальная длина линии при соединении по RS485 составляет 1200 метров! А если на линии будут специальные усилители, то ещё больше. Конечно, скорость передачи по такому длинному проводу будет всего около 60 кб/с, но для передачи показаний датчиков больше и не требуется.
В качестве кабеля для RS485 используется витая пара. Это два провода, сплетенные друг с другом. Такой кабель ещё используется в Ethernet линиях, так что его легко можно достать. Чтобы передавать данные на дистанции более 500 метров, потребуется экранированная витая пара.
К одному кабелю может быть подключено 32 устройства. Но в один момент времени только одно устройство может передавать данные.
Подключение
Для того, чтобы подключить две платы Ардуино по интерфейсу RS485 нам потребуется специальный модуль. Обычно такие модули используют распространённую микросхему MAX485.
Соединим две Ардуино по следующей схеме:
Внешний вид макета
Примечание. На рисунке провода A и B — прямые, и этого будет достаточно, если расстояние будет небольшим. В случае подключения по кабелю длиной в несколько метров нужно обязательно использовать витую пару!
Программа: приёмник и передатчик
Как мы уже отмечали, если к линии подключены несколько устройств, то в один момент времени только одно из них может передавать данные. Получается, нам нужно каким-то образом сообщить всем соседям, что мы готовы к передаче, а они при этом должны молчать и слушать. Делается это при помощи контактов DE и RE. Если мы подаём высокий уровень на эти контакты, то устройство переходит в режим передатчика. Низкий уровень — приёмника.
Напишем две программы. Одна будет передавать в эфир текст «ping», каждые 500 мс. Другая будет слушать эфир и при получении текст «ping» — мигать светодиодом №13.
Программа передатчика
#define SerialTxControl 2 // контакт №2 будет переключать режим приёмник/передатчик
#define RS485Transmit HIGH
#define RS485Receive LOW
void setup(void) {
Serial.begin(9600); // настраиваем последовательный порт на скорость 9600бод
pinMode(SerialTxControl, OUTPUT);
digitalWrite(SerialTxControl, RS485Transmit); // переводим устройство в режим передатчика
}
void loop(void) {
Serial.print("ping"); // отправляем текст
delay(500);
}
Программа приёмника
#define SerialTxControl 2
#define RS485Transmit HIGH
#define RS485Receive LOW
char buffer[100];
byte state = 0;
void setup(void) {
Serial.begin(9600);
pinMode(13, OUTPUT);
pinMode(SerialTxControl, OUTPUT);
digitalWrite(SerialTxControl, RS485Receive); // переводим устройство в режим приёмника
}
void loop(void) {
int i=0;
if( Serial.available() ){ // если в порт пришли какие-то данные
delay(5); // немного ждём, чтобы вся пачка данных была принята портом
while( Serial.available() ){
buffer[i++] = Serial.read(); // считываем данные и записываем их в буфер
}
}
if(i>0){ // если в буфере что-то есть
buffer[i++]='\0'; // превращаем содержимое буфера в строку, добавляя нулевой символ
if( strcmp(buffer, "ping") ){ // если принятая строка равна тексту ping
digitalWrite(13, state); // мигаем светодиодом
state = !state;
}
}
}
Загружаем программы на обе платы и наблюдаем за происходящим. Если всё собрано верно, то после подачи питания, на второй плате начнет мигать светодиод, подключённый к выводу №13.
В этом примере, приёмник и передатчик не меняют своей роли на всём протяжении работы. Усовершенствуем программы и сделаем так, чтобы приёмник и передатчик менялись ролями в процессе работы.
Программа: передача в обе стороны
Первая Ардуино уже не будет постоянно в режиме передатчика, она будет менять режим на приёмник в момент отправки сообщения. Вторая плата будет вести себя аналогичным образом.
Программа для первой платы Ардуино
#define SerialTxControl 2
#define RS485Transmit HIGH
#define RS485Receive LOW
char buffer[100];
byte state = 0;
unsigned long ping_next, t;
unsigned int ping_to = 500;
void setup(void) {
Serial.begin(115200);
pinMode(13, OUTPUT);
pinMode(SerialTxControl, OUTPUT);
digitalWrite(SerialTxControl, RS485Receive);
}
void loop(void) {
int i=0;
if( Serial.available() ){
delay(5);
while( Serial.available() ){
buffer[i++] = Serial.read();
}
if(i>0){
buffer[i++]='\0';
if( !strcmp(buffer, "pong") ){
digitalWrite(13, state);
state = !state;
}
}
}
// каждые 500 мс отправляем текст "ping"
t = millis();
if( t>ping_next ){
ping_next = t + ping_to;
digitalWrite(SerialTxControl, RS485Transmit);
Serial.print("ping");
delay(5);
digitalWrite(SerialTxControl, RS485Receive);
}
}
Первая плата отправляет текст «ping» каждые 500 мс. Делается это не с помощью delay, а с помощью таймаутов, о которых мы подробно говорили на одном из уроков: Ардуино: параллельное выполнение задач по таймеру
Программа для второй платы Ардуино
#define SerialTxControl 2
#define RS485Transmit HIGH
#define RS485Receive LOW
char buffer[100];
byte state = 0;
void setup(void) {
Serial.begin(115200);
pinMode(13, OUTPUT);
pinMode(SerialTxControl, OUTPUT);
digitalWrite(SerialTxControl, RS485Receive);
}
void loop(void) {
int i=0;
if( Serial.available() ){
delay(5);
while( Serial.available() ){
buffer[i++] = Serial.read();
}
if(i>0){
buffer[i++]='\0';
if( !strcmp(buffer, "ping") ){
digitalWrite(13, state);
state = !state;
digitalWrite(SerialTxControl, RS485Transmit);
Serial.print("pong");
delay(10);
digitalWrite(SerialTxControl, RS485Receive);
}
}
}
}
Загружаем программы. В результате, вторая плата Ардуино будет каждые 500 миллисекунд зажигать встроенный светодиод на выводе №13. Сразу вслед за этим светодиод будет зажигаться и на первой Ардуино. Ещё через 500 мс, светодиоды погаснут в той же последовательности. И так далее.
К размышлению
Как уже говорилось ранее, RS485 применяется с конца 90-х годов во множестве промышленных систем. Там, где требуется передавать данные на большие расстояния, в условиях электромагнитных помех.
Как правило, интерфейс RS485 используют совместно со специализированными протоколами, такими как ModBus или DMX512. Однако, как мы увидели в примерах, для простых задач можно не применять сложных протоколов и дополнительных библиотек.
Светодиод не мигает, хотя pjng приходит, проверено через монитор порта, функция сравнения почему-то не работает.
ping надо в одиночные кавычки, тогда все заработало
По стандарту полагается тянуть не только линии A и B, но и общий провод, подключая его через последовательный 100-омный резистор на каждом устройстве — это необходимо для уравнивания потенциалов, без него входы могут и погореть. Жаль, что на платке клеммник не имеет такой возможности. Не смертельно, конечно — можно и внешний поставить, а этот использовать для подключения терминатора.
Кстати, интересно: организовано ли смещение линий A и B? Этих модулей у меня пока нет, куплю — проверю.