Ардуино: передача данных на большие расстояния — RS485

Из предыдущих уроков мы знаем, что Ардуино имеет несколько встроенных шин для быстрой передачи данных. Например, для работы с дисплеями обычно используют шину SPI. Разного рода датчики часто подключают по шине I2C. Есть ещё популярный UART, который часто используют вместе с интерфейсом USB для обмена данным с компьютером. Но у всех этих способов есть серьёзный недостаток — они не работают на больших дистанциях!

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

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

1. Несколько фактов об RS485

RS485 — это последовательный интерфейс, предком которого является RS232. Последний получил известность из-за COM-портов старых компьютеров, которые как раз работали по интерфейсу RS232.

Максимальная длина линии при соединении по RS485 составляет 1200 метров! А если на линии будут специальные усилители, то ещё больше. Конечно, скорость передачи по такому длинному проводу будет всего около 60 кб/с, но для передачи показаний датчиков больше и не требуется.

В качестве кабеля для RS485 используется витая пара. Это два провода, сплетенные друг с другом. Такой кабель ещё используется в Ethernet линиях, так что его легко можно достать. Чтобы передавать данные на дистанции более 500 метров, потребуется экранированная витая пара.

К одному кабелю может быть подключено 32 устройства. Но в один момент времени только одно устройство может передавать данные.

2. Подключение двух Ардуино с помощью RS485

Для того, чтобы подключить две платы Ардуино по интерфейсу RS485 нам потребуется специальный модуль. Обычно такие модули используют распространённую микросхему MAX485.

RS485-TTL MAX485

Соединим две Ардуино по следующей схеме:

Схема подключения двух Ардуино через RS485

Внешний вид макета

Подключение двух Ардуино через RS485

Примечание. На рисунке провода A и B — прямые, и этого будет достаточно, если расстояние будет небольшим. В случае подключения по кабелю длиной в несколько метров нужно обязательно использовать витую пару!

3. Программа приемника и передатчика для RS485

Как мы уже отмечали, если к линии подключены несколько устройств, то в один момент времени только одно из них может передавать данные. Получается, нам нужно каким-то образом сообщить всем соседям, что мы готовы к передаче, а они при этом должны молчать и слушать. Делается это при помощи контактов 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.

В этом примере, приёмник и передатчик не меняют своей роли на всём протяжении работы. Усовершенствуем программы и сделаем так, чтобы приёмник и передатчик менялись ролями в процессе работы.

4. Программа для передачи данных по RS485 в обе стороны

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

Программа для первой платы Ардуино

#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 мс, светодиоды погаснут в той же последовательности. И так далее.

5. Заключение

Как уже говорилось ранее, RS485 применяется с конца 90-х годов во множестве промышленных систем. Там, где требуется передавать данные на большие расстояния, в условиях электромагнитных помех.

Как правило, интерфейс RS485 используют совместно со специализированными протоколами, такими как ModBus или DMX512. Однако, как мы увидели в примерах, для простых задач можно не применять сложных протоколов и дополнительных библиотек.

1+

Изменено: