Связь двух ардуин по rs-485

trojan-tj
Offline
Зарегистрирован: 11.04.2017

Доброго времени суток! Прошу сильно не пинать новичка. Проектирую сейчас свой "умный дом".

Вкратце расскажу суть. В каждую комнату проложена витая пара и в каждой организован небольшой щиток, в которой будет распологаться "комнатный" контроллер (пока arduino nano). Далее все "комнатные" контроллеры сводятся в один щиток к главному контроллеру - шлюзу mqtt (это будет arduino mega 2560). Далее Мега будет через Ethetnet-шилд общаться с mqtt-сервером и все данные будут выводиться на отдельный сервер визуализации. 

Теперь к проблеме. Мне нужно организовать простую и надежную связь между комнатными контроллерами и главным. Я долго перебирал варианты и остановился на rs-485 (с помощью RS-485 - TTL конвертеров), благо провода уложены уже. Взял ардуино нано (прототип комн контрллера), уно (прототип главного). Пытаюсь реализовать задачу:

1. К нано подключена кнопка, при ее нажатии включается светодиод и в rs-485 канал отправляется сообщение, сигнализирующее, что светодиод включен. Уно принимает это сообщение, расшифровывает и отправляет на mqtt-сервер в соответствующий топик сообщение, что светодиод включен. 

2. И в обратном порядке. mqtt генерит сообщение, что нужно выключить светодиод. Уно получает его, отправляет соответствующее сообщение по rs-485. Нано получает его, расшифровывает и гасит светодиод.

Вот мои скетчи:

//MASTER
//Подключаем библиотеки
#include <SoftwareSerial.h> 
#include <SPI.h> 
#include <Ethernet2.h>
#include <PubSubClient.h>
//Настройки пинов
#define DIR 2  // переключатель прием\передача - DE-RE на конвертере TTL-RS-485
//Настройки RS-485
const byte ID = 1;  // номер ардуины
SoftwareSerial RS485 (7, 8);  // назначаем пины RX, TX ардуины
//Настройки сети
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
IPAddress ip(192, 168, 31, 200);
IPAddress mqttserver(192, 168, 31, 238);
//Объекты, переменные, функции Ethernet и MQTT
void callback(char* topic, byte* payload, unsigned int length);  // шапка функции Callback для объявления и инициализации PubSubClient
EthernetClient ethClient;  // Инициализируем Ethernet клиент
PubSubClient client(mqttserver, 1883, callback, ethClient);  // Инициализируем MQTT клиент
unsigned long lastReconnectAttempt = 0;  // переменная определяет, сколько миллисекунд назад была попытка подключения к MQTT-серверу
byte myArray[6] = {5, 6, 0, ID, 0, 0};  // создадим шаблон байтового массива в котором [0 и 1]-стандартное начало, [2]кому будет отсылаться, [3]ID- от кого, [4] порядковый номер сенсора (кнопка, датчик и т.д.), [5] произошедшее на сенсоре событие

//Функция обработки входящих соединений - прием данных по подписке
void callback(char* topic, byte* payload, unsigned int length) {
 //преобразуем тему(topic) и значение (payload) в строку
  payload[length] = '\0';
  String strTopic = String(topic);
  String strPayload = String((char*)payload);
  //Исследуем что "прилетело" от сервера по подписке:
  if (strTopic == "/myhome/sw1") {
    if (strPayload == "off" || strPayload == "0" || strPayload == "false") {
      Serial.println ("switch off light");
      digitalWrite(DIR, HIGH); // включаем передачу RS-485
      myArray[2] = 2;  // вносим изменения в массив. (2) - номер ардуины, которой отправляем задание
      myArray[4] = 1;  // (1) - номер выключателя, состояние которого нужно изменить
      myArray[5] = 0;  // (0) - как изменить состояние? -выкл.
      for (int i = 0; i < 6; i++) {  // перебираем массив и передаем его по RS-485
        Serial.println(myArray[i]);
        RS485.write(myArray[i]);
       }
    }
    if (strPayload == "on" || strPayload == "1" || strPayload == "true") {
      Serial.println ("switch on light bedroom");
      digitalWrite(DIR, HIGH); // включаем передачу RS-485
      myArray[2] = 2;  // вносим изменения в массив 
      myArray[4] = 1;
      myArray[5] = 1;
      for (int i = 0; i < 6; i++) {  // перебираем массив и передаем его по RS-485
        Serial.println(myArray[i]);
        RS485.write(myArray[i]);
        }
    }
  }
   delay(10);
   digitalWrite(DIR, LOW); // включаем прием RS-485
}

boolean reconnect() {  // функция переподключения к mqtt серверу
  if (client.connect("arduinoClient")) {  // попытка подключения к mqtt-серверу 
    // Если удалось подключиться, то сразу подписаться
    client.subscribe("/myhome/#");
  }
  return client.connected(); //возвращает true если подключение к mqtt есть, false- если его нет
}

void setup() {
  Serial.begin(9600);  // Запускаем отправку данных в Serial
  RS485.begin(9600);  // Запускаем RS-485
  pinMode(DIR, OUTPUT);
  digitalWrite(DIR, LOW);  // включаем прием RS-485
  Ethernet.begin(mac, ip);  // устанавливаем Ethernet-соединение
  delay(1000);
  if (client.connect("arduinoClient")) {  // соединяемся с mqtt сервером и если успешно то
    client.subscribe("/myhome/#");  // подписываемся на топик
    Serial.println("MQTT connected");  // и выводим сообщение
  }
  else Serial.println("MQTT not connected");  // если неуспешно то выводим сообщение
}

void loop() {
  // Восстановление MQTT-соединения при обрывах
  if (!client.connected()) { // если MQTT-соединение разорвалось, каждые 5с пробуем его восстанавливать
    Serial.println("MQTT disconnected.Attemp to connect");
    unsigned long now = millis();
    if (now - lastReconnectAttempt > 5000) {
      lastReconnectAttempt = now;
      if (reconnect()) {  // попытка восстановления mqtt-соединения
        lastReconnectAttempt = 0;
      }
    }
  }
  // Обработка входящих сообщений по RS-485
  if (RS485.available() >= 6) { // если пришел пакет по RS-485 длиной 6 и более 
    byte kod1 = RS485.read(); // читаем первый пришедший байт и пишем его в переменную kod1
    byte kod2 = RS485.read(); // читаем второй пришедший байт и пишем его в переменную kod2
    byte id = RS485.read(); // читаем третий пришедший байт и пишем его в переменную id
    if (kod1 == 5 && kod2 == 6 && id == ID) { // если два первых байта - 5 и 6 и ID адресата совпадает с нашим, то читаем пришедшие данные дальше
      switch (RS485.read()) { // проверяем от какой ардуины пришел пакет - читаем четвертый байт и выполняем нужный case
        case 2:
          Serial.println("Starting task2...");
          task_2();
          break;
        }
    }
    else RS485.flush(); // если пришел пакет не нам - очищаем буфер RS-485


  }
    client.loop();
}

void task_2() { // обрабатываем пакет, пришедший с ардуины №2
  switch (RS485.read()) { // проверяем по какому выключателю произошли изменения и далее считываем код изменения
    case 1: if (RS485.read() == 1) {
        // формируем mqtt пакет на условие, что на сенсоре 1 произошло изменение на TRUE
        client.publish("/myhome/sw1", "1");
      }
      else {
        // формируем mqtt пакет на условие, что на сенсоре 1 произошло изменение на FALSE
        client.publish("/myhome/sw1", "0");
      }
      break;
    }
}
 
 
//SLAVE
#include <SoftwareSerial.h>
#include "OneButton.h"

#define DIR 12 // переключатель прием\передача - DE-RE на конвертере TTL-RS-485
#define BUTTON_1 2 // указываем на каком пине находится первая кнопка
#define LED_1 13
int ledValue_1 = LOW; //зададим начальное состояние светодиода
const byte ID = 2; // номер ардуины
byte myArray[6] = {5, 6, 1, ID, 0, 0}; //создадим шаблон байтового массив в котором [0 и 1]-стандартное начало, [2]кому будет отсылаться, [3]ID- от кого, [4] порядковый номер сенсора (кнопка, датчик и т.д.), [5] произошедшее на сенсоре событие

// true значит что кнопка подтянута к плюсу
OneButton button1(BUTTON_1, true);

SoftwareSerial RS485 (7, 8); // назначаем пины RX, TX ардуины

void setup() {
  Serial.begin(9600); // Запускаем отправку данных в Serial
  RS485.begin(9600); // Запускаем RS-485
  pinMode(DIR, OUTPUT);
  pinMode(LED_1, OUTPUT);
  button1.attachClick(click1); // перечисляем функции первой кнопки
  digitalWrite(DIR, LOW); // включаем прием RS-485
  digitalWrite(LED_1, LOW);
}

void loop() {
  button1.tick();  // функция постоянно следит за нажатием кнопки 1
  delay(10);
  // Обработка входящих сообщений по RS-485. Принимаем сообщения только от Arduino-Master (ID=1), остальные отбрасываем
  if (RS485.available()) { // если пришел пакет по RS-485
    byte kod1 = RS485.read(); // читаем первый пришедший байт
    byte kod2 = RS485.read(); // читаем второй пришедший байт
    byte id = RS485.read(); // читаем третий пришедший байт
    byte from = RS485.read(); // читаем четвертый пришедший байт
    if (kod1 == 5 && kod2 == 6 && id == ID && from == 1) { // если два первых байта - 5 и 6, ID адресата совпадает с нашим и данные пришли от Arduino-Master (ID=1), то читаем пришедшие данные дальше
      switch (RS485.read()) { // проверяем по какому выключателю пришел пакет - читаем пятый байт и выполняем нужный case
        case 1:
          if (RS485.read() == 1) {
            Serial.println("LED_1 vkl...");
            digitalWrite(LED_1, HIGH);
          }
          else {
            Serial.println("LED_1 vikl...");
            digitalWrite(LED_1, LOW);
          }
          break;
          }
    }
    else RS485.flush(); // если пришел пакет не нам - очищаем буфер RS-485
  }
}

void click1() {
  Serial.println("Button 1 click.");
  //если свет был выключен, включаем его
  if ( ledValue_1 == LOW ) {
    Serial.println("turn on...");
    ledValue_1 = HIGH;
    digitalWrite(DIR, HIGH); // включаем передачу RS-485
    myArray[4] = 1; //вносим изменения в массив согласно изменения состояния 
    myArray[5] = 1;
    for (int i = 0; i < 6; i++) { //перебираем массив и передаем его мастеру по RS-485
      Serial.println(myArray[i]);
      RS485.write(myArray[i]);
    }

  }
  //если свет был включен, будем выключать
  else {
    Serial.println("turn off...");
    ledValue_1 = LOW;
    digitalWrite(DIR, HIGH); // включаем передачу RS-485
    myArray[4] = 1; //вносим изменения в массив согласно изменения состояния
    myArray[5] = 0;
    for (int i = 0; i < 6; i++) { //перебираем массив и передаем его мастеру по RS-485
      Serial.println(myArray[i]);
      RS485.write(myArray[i]);
    }
  }
  digitalWrite(LED_1, ledValue_1); //записываем значение вкл/выкл на пин со светодиодом
  delay(10);
  digitalWrite(DIR, LOW); // включаем прием RS-485

}

Теперь что имеем.Жму кнопку на нано. Светодиод загорается, на уно приходит 5 6 1 2 1 1. Уно все разбирает и отправляет в mqtt сообщение "1". Здесь все гладко. А вот если я шлет сообщение mqtt-сервер, то тут проблема. УНО принимает его, формирует команду для нано и отправляет. Но ничего не просходит. Вставлял Serial print,смотрел что видит нано в порту который принимает rs-485. Там должно быть 5 6 2 1 1 0 (когда команда на выкл), но вместо этого я вижу 5 255 255 255 255 255 255 6 255 255 255 255 255 2 1 255 255 255 1 0 255 255 255 . Что то вроде того. То есть данные то пришли, но они разбавлены мусором. Причем это приходит не разом, а частями. Это я заметил, когда вставил Serial print. Выглядит примерно так : 

"новое сообщение" 

5 255 255 255 255 255 255

"новое сообщение" 

6 255 255 255 255 255

"новое сообщение" 

2 1 255 255 255

"новое сообщение" 

1 0 255 255 255
 
Вместо 5 6 2 1 1 0. Помогите пожалуйста, уже неделю не сплю ночами, воюю с этим, не могу понять в чем дело. Контакты проверял, модули менял, ардуинки менял. Все что мог перебрал
 
kasper007
Offline
Зарегистрирован: 23.05.2016

сто пудов вы сильно быстро читаете из порта, у вас скорость отправки 9600 - это гораздо медленней, чем скорость чтения. Поэтому при чтении вы из буфера выбираете истинные занчения, а потом когда они заканчиваются начинаете брать оттуда неизвестно что. Поставьте в строчках 94-96 вашего мастера delay перед каждым чтением и посмотрите на результат (ни у по аналогии сделайте это в коде slave)

trojan-tj
Offline
Зарегистрирован: 11.04.2017

Спасибо, я попробую, как доберусь и отпишусь тут о результате. Вы пишите, что скорость отправки низкая. А может увеличить ее? Если да, то до скольки?