Очередной "Умный дом", на этот раз модульная система...

alex_r61
alex_r61 аватар
Offline
Зарегистрирован: 20.06.2012

Через Дуню лучше ничего не запитывать, А 12 вольт на VCC - смерть для Дуни. На RAW надо подавать питание, да и 12 вольт много для неё. Я приблизительно накидал схему питания у себя, там всем рулить будет отдельный МК.

vde69
Offline
Зарегистрирован: 10.01.2016

я перепутал, разумеется 12 вольт на VIN а не на VCC... можно не 12 а 10, но блок питания на 12 проще найти...

6 вольт на шину - явно мало, во первых ампер потребуется много (и следовательно кабель толстый), во вторых будут просадки на длине..

MaksVV
Offline
Зарегистрирован: 06.08.2015
alex_r61
alex_r61 аватар
Offline
Зарегистрирован: 20.06.2012

6 вольт это шина для МК, большинство МК у меня будут работать на 3.3 вольт. Так что даже 9 вольт уже много, не говоря про 12. Тем более что на Pro Mini  ставят маломощные стабилизаторы. И какие там будут амперы.

vde69
Offline
Зарегистрирован: 10.01.2016

ModBus мне чего-то не покатил, решить строить свой велосипед...

наваял простой класс для передачи по RS485 пакетов, немного похоже на многие протоколы сразу, но как мне кажется в понимании будет попроще, имеет всего одну процедуру send и одну подключаемую процедуру на получение валидного пакета.

Критика приветствуется :)


#include <busrs485.h>


BusRS485 Bus(0, 0, 2); // this is master and RS-232 or USB-FTDI

void setup() {
  Bus.begin( 19200 );                      // baud-rate at 19200
  Bus.attachInPackInterrupt(BusInPack);    // подключим процедуру для обработки полученых пакетов
}

void loop() {
  Bus.poll(); // вызов процедур чтения порта
}

// процедура вызывается при получении валидного пакета
void BusInPack (){
  // тут делаем, что нам надо при получении пакета
  // например перешлем его другому адресату
  if (Bus.Id_Destination() == 0) {
    if (Bus.send(22,             // ID получателя
                 Bus.Type(),     // тип пакета
                 Bus.Func(),     // номер функции
                 Bus.SizeDate(), // размер дополнительных данных
                 Bus.Date()))    // указатель на буфер дополнительных данных 
    {;}      // удачно отпрвили
    else {;} // ошибка при отправки  
  }
  
}

а это BusRS485.h

">

/*  !!!!!!!!!!!!!!!!!!!!!!!!!! ВНИМАНИЕ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
использует расширение команды Serial.peek(), для этого нужно добавить

в HardwareSerial.h
-----------------------------------------------------------
virtual int peek(uint8_t); // надстройка для BusRS485
-----------------------------------------------------------

в HardwareSerial.cpp
-----------------------------------------------------------
int HardwareSerial::peek(uint8_t c) // надстройка для BusRS485
{
  int i = this->available();
  // проверяем, что индекс в рабочем диапазоне
  if (c < i) {
    return _rx_buffer->buffer[(SERIAL_BUFFER_SIZE + c + _rx_buffer->tail) % SERIAL_BUFFER_SIZE];
  } else {
    return -1;
  }
}
-----------------------------------------------------------
*/


#include <inttypes.h>
#include <arduino.h>	
#include <print.h>
#include <wiring_private.h>


// буфер Serial может хранить 64 байта или 16, заголовок пакета 7 байт, по этому размер буфера дополнительных данных 
// 57 или 9 байт, такой малый размер буфера надо учитывать !!! 
// данная библиотека не будет работать с малым размером буфера для шины RS485, пин управления будет отключен !!!
#if (RAMEND < 1000)
  #define MAX_BUFFER 9
#else
  #define MAX_BUFFER 57
#endif

// описание пакета, данные передаются и принимаются именно в такой последовательности
typedef struct {
  uint8_t Id_Destination;   // ID получателя пакета
  uint8_t Id_Source;        // ID отправителя пакета
  uint8_t Type;             // Тип пакета
  uint8_t Func;             // Номер команды определяет тип дополнительных данных и правила их обработки 
  uint8_t SizeDate;         // Размер дополнительных данных пакета
  uint8_t xor_Data;         // xor (контроль четности) дополнительных данных пакета
  uint8_t xor_Head;         // xor (контроль четности) заголовка, служит для определения корректности заголовка и выделения кадра из потока данных
  uint8_t Data[MAX_BUFFER]; // дополнительные данные
} 
BusRS485_Pack;

volatile static voidFuncPtr _InPack;

class BusRS485 {
private:
  HardwareSerial *port;       // указатель на Serial class 
  uint8_t Id;                 // 0=master, 1..246=slave, 247=резерв
  uint8_t ControlPin;         // номер пина контроля RS-485 
  BusRS485_Pack InPack;       // входящий пакет
  uint8_t FInPack;            // флаг входящего пакета, 0-ждем заголовок, 1-ждем дополнительные данные, 2-пакет загружен валиден, 3-пакет загружен, но не валиден
  
  void init(uint8_t Id, uint8_t PortNum, uint8_t ControlPin);

public:
  BusRS485(uint8_t Id, uint8_t PortNum, uint8_t ControlPin);
  
  uint8_t Id_Destination (void);
  uint8_t Id_Source (void);
  uint8_t Type (void);
  uint8_t Func (void);
  uint8_t SizeDate (void);
  uint8_t *Date (void);
  
  void begin(long Speed);
  void attachInPackInterrupt(void (*)());  
  void poll();         // процедура запуска обработки получения данных
  void on();           // включение шина + COM порт
  void off();          // выключение шины (только COM порт)
  
  // возвращает true если данные отправлены, и false если ошибка
  boolean send(int8_t,     // ID получателя
               int8_t,     // тип пакета
               int8_t,     // номер функции
               int8_t,     // размер дополнительных данных
               uint8_t *); // указатель на буфер дополнительных данных 
};

/* _____PUBLIC FUNCTIONS_____________________________________________________ */

BusRS485::BusRS485(uint8_t Id, uint8_t PortNum, uint8_t ControlPin) {
  init(Id, PortNum, ControlPin);
}


uint8_t BusRS485::Id_Destination () {
  return InPack.Id_Destination;
}

uint8_t BusRS485::Id_Source (){
  return InPack.Id_Source;
}

uint8_t BusRS485::Type (){
  return InPack.Type;
}

uint8_t BusRS485::Func (){
  return InPack.Func;
}

uint8_t BusRS485::SizeDate (){
  return InPack.SizeDate;
}

uint8_t * BusRS485::Date (){
  return &(InPack.Data[0]);
}


boolean BusRS485::send(int8_t Id,          // ID получателя
                    int8_t Type,        // тип пакета
                    int8_t Func,        // номер функции
                    int8_t SizeDate,    // размер дополнительных данных
                    uint8_t *Data) {    // указатель на буфер дополнительных данных 

  if (MAX_BUFFER >= SizeDate) {   // посылаем только если доп данные влезут в буфер
  
    uint8_t xor_Data = 0;         // xor (контроль четности) дополнительных данных пакета
    uint8_t xor_Head = 0;         // xor (контроль четности) заголовка, служит для определения корректности заголовка и выделения кадра из потока данных
    
    for (int i=0; i < SizeDate; i++){ 
      xor_Data ^= port->peek(i);
    } 
  
    xor_Head =  Id
               ^this->Id
               ^Type
               ^Func
               ^SizeDate
               ^xor_Data;
  
    port->write(Id);
    port->write(this->Id);
    port->write(Type);
    port->write(Func);
    port->write(SizeDate);
    port->write(xor_Data);
    port->write(xor_Head);
    
    for (int i=0; i < SizeDate; i++){ 
      port->write(Data[i]);
    } 
    return true;
  } 
  else {
    return false;
  }
}

void BusRS485::begin(long Speed) {
  port->begin(Speed);
  port->flush();
  FInPack = 0;
}


void BusRS485::attachInPackInterrupt(void (*_userFunc)(void)) 
{ 
   _InPack = _userFunc; 
} 

void BusRS485::on() {
  if (ControlPin > 1) { 
     // с малым размера памяти устройства - оно выключено...
     #if (RAMEND < 1000)
       digitalWrite(ControlPin, LOW);
     #else
       digitalWrite(ControlPin, HIGH);
     #endif
       port->flush();
  }
} 

void BusRS485::off() {
  if (ControlPin > 1) { 
    digitalWrite(ControlPin, LOW);
  }
} 

void BusRS485::poll() {
  uint8_t i = 0;
  uint8_t i1 = 0;
  int ii;
  boolean f = true;

  while (f) {
    if (FInPack > 3) {FInPack = 0;}  // не понятный флаг, значит будем ждать заголовок
    else if (FInPack == 2) {break;}  // пакет загружен и не обработан
    else if (FInPack == 3) {break;}  // пакет загружен и не валиден
    
    ii = port->available();
    
    // 0-ждем заголовок, 1-ждем дополнительные данные
    if (FInPack == 0 && ii < 7) {
      break; // ждем заголовка, но данных мало
    } 
    
    if (ii >= 7) { // возможно это заголовок пакета, посчитаем контрольный символ
      i == (   port->peek(0)
            ^  port->peek(1)
            ^  port->peek(2)
            ^  port->peek(3)
            ^  port->peek(4)
            ^  port->peek(5));
      if (i == port->peek(6)) {
        // да это заголовок пакета, 
        
        // проверим предыдущий заголовок
        if (FInPack == 1) {
            // !!!! ошибочный пакет, пока просто выбросим !!!!
            FInPack = 0;
        }
        
        // загрузим пакет
        InPack.Id_Destination = port->read();
        InPack.Id_Source      = port->read();
        InPack.Type           = port->read(); 
        InPack.Func           = port->read(); 
        InPack.SizeDate       = port->read(); 
        InPack.xor_Data       = port->read(); 
        InPack.xor_Head       = port->read(); 
        
        if (InPack.SizeDate == 0) {
          // пакет не содержит дополнительных данных, только номер и команду
          FInPack = 2;
          break;
        }
        else { 
          // заголовок загружен, нужно пытатся загрузить данные
          FInPack = 1; 
          ii = port->available();
          continue;
        }
      }
    }
    
    if (FInPack == 0) {
      // ждем заголовок, но его нет, значит есть мусор
      port->read();
    } 
    else if (ii < InPack.SizeDate) {
      break; // ждем данных, но их мало
    } 
    else {
      // Возможно это дополнительные данные
      i == 0;
      for (int i1=0; i1 < InPack.SizeDate; i1++){ 
        i ^= port->peek(i1);
      } 
      if (i == InPack.xor_Data) {
        // это правильные дополнительные данные
        for (int i1=0; i1 < InPack.SizeDate; i1++){ 
          InPack.Data[i1] == port->read();
        } 
        FInPack = 2;
        break;
      }
      else {
        // контроль доп данных не сошелся.... идем дальше
        port->read();
      }
    }
  }
      
  if (FInPack == 2) { 
    // запускаем подключеную обработку
    _InPack(); 
    FInPack = 0;
  }  
  if (FInPack == 3) { 
    // !!!! ошибочный пакет, пока просто выбросим !!!!
    FInPack = 0;
  }  
}


/* _____PRIVATE FUNCTIONS_____________________________________________________ */


// конструктор
void BusRS485::init(uint8_t Id, uint8_t PortNum, uint8_t ControlPin) {
  this->Id = Id; 
  switch( PortNum ) {
  #if defined(UBRR1H)
  case 1:
    port = &Serial1;
    break;
  #endif

  #if defined(UBRR2H)
  case 2:
    port = &Serial2;
    break;
  #endif

  #if defined(UBRR3H)
  case 3:
    port = &Serial3;
    break;
  #endif
  case 0:
  default:
    port = &Serial;
    break;
  }
  
  this->ControlPin = ControlPin;
  if (ControlPin > 1) { 
    pinMode(ControlPin, OUTPUT);
  }
  this->on();
}
AndrF
Offline
Зарегистрирован: 10.04.2016

Не вижу контрольной суммы посылки

vde69
Offline
Зарегистрирован: 10.01.2016

AndrF пишет:

Не вижу контрольной суммы посылки

есть 2 байта XOR ов, один на шапку и второй на доп данные...

vde69
Offline
Зарегистрирован: 10.01.2016

полевые испытания показали некоторые проблемы в классе доработал немного, добавил маркер начала кадра и перевел контрольные биты с XOR на XOR+циклический сдвиг, стало вполне надежно...

а это BusRS485.h




/*  !!!!!!!!!!!!!!!!!!!!!!!!!! ВНИМАНИЕ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
использует расширение команды Serial.peek(), для этого нужно добавить

в HardwareSerial.h
-----------------------------------------------------------
virtual int peek(uint8_t); // надстройка для BusRS485
-----------------------------------------------------------

в HardwareSerial.cpp
-----------------------------------------------------------
int HardwareSerial::peek(uint8_t c) // надстройка для BusRS485
{
  int i = this->available();
  // проверяем, что индекс в рабочем диапазоне
  if (c < i) {
    return _rx_buffer->buffer[(SERIAL_BUFFER_SIZE + c + _rx_buffer->tail) % SERIAL_BUFFER_SIZE];
  } else {
    return -1;
  }
}
-----------------------------------------------------------
*/


#include 
#include 	
#include 
#include 


// буфер Serial может хранить 64 байта или 16, заголовок пакета 7 байт, по этому размер буфера дополнительных данных 
// 57 или 9 байт, такой малый размер буфера надо учитывать !!! 
// Рекомендуемый размер дополнительных данных не более 24 байта (2 полных кадра в буфере)
// данная библиотека не будет работать с малым размером буфера для шины RS485, пин управления будет отключен !!!
#if (RAMEND < 1000)
  #define MAX_BUFFER 8
#else
  #define MAX_BUFFER 56
#endif

// описание пакета, данные передаются и принимаются именно в такой последовательности
typedef struct {
  uint8_t Mark;             // маркер начала кадра (введен для стабильности распознования кадров), всегда $AA, 10101010h, 170  
  uint8_t Id_Destination;   // ID получателя пакета
  uint8_t Id_Source;        // ID отправителя пакета
  uint8_t Type;             // Тип пакета
  uint8_t Func;             // Номер команды определяет тип дополнительных данных и правила их обработки 
  uint8_t SizeDate;         // Размер дополнительных данных пакета
  uint8_t CRC_Data;         // CRC дополнительных данных пакета
  uint8_t CRC_Head;         // CRC заголовка, служит для определения корректности заголовка и выделения кадра из потока данных
  uint8_t Data[MAX_BUFFER]; // дополнительные данные, рекомендуемый размер не более 24 байт, максимальный размер 56 байт
} 
BusRS485_Pack;

volatile static voidFuncPtr _InPack;

class BusRS485 {
private:
  HardwareSerial *port;       // указатель на Serial class 
  uint8_t Id;                 // 0=master, 1..246=slave, 247=резерв
  uint8_t ControlPin;         // номер пина контроля RS-485 
  BusRS485_Pack InPack;       // входящий пакет
  uint8_t FInPack;            // флаг входящего пакета, 0-ждем заголовок, 1-ждем дополнительные данные, 2-пакет загружен валиден, 3-пакет загружен, но не валиден
  
  void init(uint8_t, uint8_t, uint8_t);
  uint8_t CRC(uint8_t, uint8_t);

public:
  BusRS485(uint8_t Id, uint8_t PortNum, uint8_t ControlPin);
  
  uint8_t Id_Destination (void);
  uint8_t Id_Source (void);
  uint8_t Type (void);
  uint8_t Func (void);
  uint8_t SizeDate (void);
  uint8_t *Date (void);
  
  void begin(long Speed);
  void attachInPackInterrupt(void (*)());  
  void poll();         // процедура запуска обработки получения данных
  void on();           // включение шина + COM порт
  void off();          // выключение шины (только COM порт)
  
  // возвращает true если данные отправлены, и false если ошибка
  boolean send(int8_t,     // ID получателя
               int8_t,     // тип пакета
               int8_t,     // номер функции
               int8_t,     // размер дополнительных данных
               uint8_t *); // указатель на буфер дополнительных данных 
};

/* _____PUBLIC FUNCTIONS_____________________________________________________ */

BusRS485::BusRS485(uint8_t Id, uint8_t PortNum, uint8_t ControlPin) {
  init(Id, PortNum, ControlPin);
}


uint8_t BusRS485::Id_Destination () {
  return InPack.Id_Destination;
}

uint8_t BusRS485::Id_Source (){
  return InPack.Id_Source;
}

uint8_t BusRS485::Type (){
  return InPack.Type;
}

uint8_t BusRS485::Func (){
  return InPack.Func;
}

uint8_t BusRS485::SizeDate (){
  return InPack.SizeDate;
}

uint8_t * BusRS485::Date (){
  return &(InPack.Data[0]);
}


boolean BusRS485::send(int8_t Id,          // ID получателя
                    int8_t Type,        // тип пакета
                    int8_t Func,        // номер функции
                    int8_t SizeDate,    // размер дополнительных данных
                    uint8_t *Data) {    // указатель на буфер дополнительных данных 

  if (MAX_BUFFER >= SizeDate) {   // посылаем только если доп данные влезут в буфер
  
    uint8_t CRC_Data = 0;         // CRC дополнительных данных пакета
    uint8_t CRC_Head = 0;         // CRC заголовка, служит для определения корректности заголовка и выделения кадра из потока данных
    
    for (int i=0; i < SizeDate; i++){ 
      CRC_Data = CRC(port->peek(i), CRC_Data);
    } 
  
    CRC_Head =  CRC(170, CRC_Head);
    CRC_Head =  CRC(Id, CRC_Head);
    CRC_Head =  CRC(this->Id, CRC_Head);
    CRC_Head =  CRC(Type, CRC_Head);
    CRC_Head =  CRC(Func, CRC_Head);
    CRC_Head =  CRC(SizeDate, CRC_Head);
    CRC_Head =  CRC(CRC_Data, CRC_Head);

  
    port->write(170);
    port->write(Id);
    port->write(this->Id);
    port->write(Type);
    port->write(Func);
    port->write(SizeDate);
    port->write(CRC_Data);
    port->write(CRC_Head);
    
    for (int i=0; i < SizeDate; i++){ 
      port->write(Data[i]);
    } 
    return true;
  } 
  else {
    return false;
  }
}

void BusRS485::begin(long Speed) {
  port->begin(Speed);
  port->flush();
  FInPack = 0;
}


void BusRS485::attachInPackInterrupt(void (*_userFunc)(void)) 
{ 
   _InPack = _userFunc; 
} 

void BusRS485::on() {
  if (ControlPin > 1) { 
     // с малым размера памяти устройства - оно выключено...
     #if (RAMEND < 1000)
       digitalWrite(ControlPin, LOW);
     #else
       digitalWrite(ControlPin, HIGH);
     #endif
       port->flush();
  }
} 

void BusRS485::off() {
  if (ControlPin > 1) { 
    digitalWrite(ControlPin, LOW);
  }
} 

void BusRS485::poll() {
  uint8_t mCRC = 0;
  uint8_t i1 = 0;
  int ii;
  boolean f = true;

  while (f) {
    if (FInPack > 3) {FInPack = 0;}  // не понятный флаг, значит будем ждать заголовок
    else if (FInPack == 2) {break;}  // пакет загружен и не обработан
    else if (FInPack == 3) {break;}  // пакет загружен и не валиден
    
    ii = port->available();
    
    // 0-ждем заголовок, 1-ждем дополнительные данные
    if (FInPack == 0 && ii < 8) {
      break; // ждем заголовка, но данных мало
    } 
    
    if (ii >= 8 && port->peek(0) == 170) { // возможно это заголовок пакета, посчитаем контрольный символ
      mCRC =  CRC(170, mCRC);
      mCRC =  CRC(port->peek(1), mCRC);
      mCRC =  CRC(port->peek(2), mCRC);
      mCRC =  CRC(port->peek(3), mCRC);
      mCRC =  CRC(port->peek(4), mCRC);
      mCRC =  CRC(port->peek(5), mCRC);
      mCRC =  CRC(port->peek(6), mCRC);

      if (mCRC == port->peek(7)) {
        // да это заголовок пакета, 
        
        // проверим предыдущий заголовок
        if (FInPack == 1) {
            // !!!! ошибочный пакет, пока просто выбросим !!!!
            FInPack = 0;
        }
        
        // загрузим пакет
        InPack.Mark           = port->read();
        InPack.Id_Destination = port->read();
        InPack.Id_Source      = port->read();
        InPack.Type           = port->read(); 
        InPack.Func           = port->read(); 
        InPack.SizeDate       = port->read(); 
        InPack.CRC_Data       = port->read(); 
        InPack.CRC_Head       = port->read(); 
        
        if (InPack.SizeDate == 0) {
          // пакет не содержит дополнительных данных, только номер и команду
          FInPack = 2;
          break;
        }
        else { 
          // заголовок загружен, нужно пытатся загрузить данные
          FInPack = 1; 
          ii = port->available();
          continue;
        }
      }
    }
    
    if (FInPack == 0) {
      // ждем заголовок, но его нет, значит есть мусор
      port->read();
    } 
    else if (ii < InPack.SizeDate) {
      break; // ждем данных, но их мало
    } 
    else {
      // Возможно это дополнительные данные
      mCRC == 0;
      for (int i1=0; i1 < InPack.SizeDate; i1++){ 
        mCRC =  CRC(port->peek(i1), mCRC);
      } 
      if (mCRC == InPack.CRC_Data) {
        // это правильные дополнительные данные
        for (int i1=0; i1 < InPack.SizeDate; i1++){ 
          InPack.Data[i1] == port->read();
        } 
        FInPack = 2;
        break;
      }
      else {
        // контроль доп данных не сошелся.... идем дальше
        port->read();
      }
    }
  }
      
  if (FInPack == 2) { 
    // запускаем подключеную обработку
    _InPack(); 
    FInPack = 0;
  }  
  if (FInPack == 3) { 
    // !!!! ошибочный пакет, пока просто выбросим !!!!
    FInPack = 0;
  }  
}


/* _____PRIVATE FUNCTIONS_____________________________________________________ */


// конструктор
void BusRS485::init(uint8_t Id, uint8_t PortNum, uint8_t ControlPin) {
  this->Id = Id; 
  switch( PortNum ) {
  #if defined(UBRR1H)
  case 1:
    port = &Serial1;
    break;
  #endif

  #if defined(UBRR2H)
  case 2:
    port = &Serial2;
    break;
  #endif

  #if defined(UBRR3H)
  case 3:
    port = &Serial3;
    break;
  #endif
  case 0:
  default:
    port = &Serial;
    break;
  }
  
  this->ControlPin = ControlPin;
  if (ControlPin > 1) { 
    pinMode(ControlPin, OUTPUT);
  }
  this->on();
}


// функция делает XOR и после циклический сдвиг
uint8_t BusRS485::CRC(uint8_t a, uint8_t b) {
  uint8_t c;
  c = a ^ b;
  return c << 1 | c >> 7;
}



}

 

AndrF
Offline
Зарегистрирован: 10.04.2016

vde69 пишет:

скорость допустим 9600 бит в сек

А если допустить скорость хотя бы 115200? Зачем на 9600-то, если интерфейс позволяет гораздо большую скорость?

vde69
Offline
Зарегистрирован: 10.01.2016

AndrF пишет:

vde69 пишет:

скорость допустим 9600 бит в сек

А если допустить скорость хотя бы 115200? Зачем на 9600-то, если интерфейс позволяет гораздо большую скорость?

скорость определяется двумя фактами

1. физическими характеристиками трассы (длинна, волновое сопротивление, затухание, помехи и т.д.)

2. используемыми интерфейсами и сопряжениями.

 

да скорость можно разгонять значительно выше, но классически рекомендумая - 19200, а для длинных линий (например 1 км) еще ниже, это связано с физическими своствами кабеля...

AndrF
Offline
Зарегистрирован: 10.04.2016

vde69 пишет:

да скорость можно разгонять значительно выше, но классически рекомендумая - 19200, а для длинных линий (например 1 км) еще ниже, это связано с физическими своствами кабеля...

IMHO - на ваших расстояниях (домашне-огородных) скорость 485 интерфейса 115200 - вполне нормальна, причем с запасом.

При лучшем кабеле можно было бы сделать и мегабит. Но это уже вряд ли стоит.

Да просто попробуйте, взяв бухту кабеля...

vde69
Offline
Зарегистрирован: 10.01.2016

AndrF пишет:

vde69 пишет:

да скорость можно разгонять значительно выше, но классически рекомендумая - 19200, а для длинных линий (например 1 км) еще ниже, это связано с физическими своствами кабеля...

IMHO - на ваших расстояниях (домашне-огородных) скорость 485 интерфейса 115200 - вполне нормальна, причем с запасом.

При лучшем кабеле можно было бы сделать и мегабит. Но это уже вряд ли стоит.

Да просто попробуйте, взяв бухту кабеля...

обязательно попробую, но позже... сейчас кроме кабеля для меня имеет значение скорость обработки даных на ардуинке, если скорость передачи будет выше чем скорость обработки - будут потери, по этому пока не будет в более менее полной мере понятны обьемы несчастя, скорость выбрать лучше меньше чем больше а потом пробовать ее поднимать....

кроме того сейчас UART работает паралеьно и на rs485 и на USB к компу (в режиме эмуляции COM), то есть еще тут могут быть заморочки....

кстати попутный вопрос:

использую "альтернативный" монитор порта https://sites.google.com/site/terminalbpp/ вроде все устраивает кроме одного, при подключении ардуинка уходит в ребут, а мне очень хочется подключатся "на живую" без ребута ардуинки...

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

vde69 пишет:

использую "альтернативный" монитор порта https://sites.google.com/site/terminalbpp/ вроде все устраивает кроме одного, при подключении ардуинка уходит в ребут, а мне очень хочется подключатся "на живую" без ребута ардуинки...

Читал где-то, что это из-за линии DTR, которая на дуинах соединена с reset для удобства загрузки скетчей. Вариантов, помнится, предлагалось несколько: не юзать DTR, перерезать дорожку на плате, ещё чего-то там.

Вот что сходу нашёл:

http://playground.arduino.cc/Main/DisablingAutoResetOnSerialConnection

http://atroshin.ru/ru/content/avtomaticheskaya-perezagruzka-arduino-pri-...

 

 

Mr.Privet
Mr.Privet аватар
Offline
Зарегистрирован: 17.11.2015

привет, интересная концепция, думаю что не новая но интересная. Тоже умный дом мастерю, пока есть только управление светом... как то у вас обсуждение зачахло. Если что нибудь наработается готов присоединиться к разработке, в силу своих скудных познаний)))

vde69
Offline
Зарегистрирован: 10.01.2016

сейчас пишу свой протокол и библиотеки поддержки шины, я тут выкладывал промежуточный вариант но уже далеко ушел, получается очень даже интересно.

Протокол ориентирован на системы с малым ОЗУ, один пакет максимум 8 байт заголовок и до 24 байт данных, в заголовок встроено 4 битовых флага, одна команда может содержать до 8 пакетов данных (192 байта), всего команд - 16 из которых часть (наверно 10) будет свободно для пользовательского использования.

Шина будет уметь

1. находить и устранять конфликты ID

2. выбирать максимальную скорость обмена

3. планирую несколько ID зарезервировать на управление с компа и дебугер, по этому шина будет поддерживать слейвы с 1 по 240 ID.

из железа почти собрал один модуль, он будет мастером и в нем будет встроено SD хранилище + часы реального времени... не выкладываю все это по причине недоделок... осталось кнопки навесить и светодиоды.

Mr.Privet
Mr.Privet аватар
Offline
Зарегистрирован: 17.11.2015

то есть получается если мастер ек, все слейвы переходят в режим автономности и не обмениваются данными...

я тут думал над такой концепцией. если слейвы шлют в шину свои данные(на пример с датчиков температуры или движения), но не постоянно а только когда происходят значительные изменения (изменения которые достаточны для того, чтобы что то сделать, на пример изменение температуры на 3 градуса, или наличие движения, или резко запахло газом...) то есть в большинстве своем шина простаивает в ожидании важных команд. Когда слейв крикнул что то другие его слушают, и если информация им нужна, запоминают ее. (на пример контроллер освещения получил информацию об освещенности и движении и в зависимости от времени и режима работы (автоуправление по движению, охрана) принял решение о включении такой-то лампы). Другие же слейвы не связанные с освещенностью (на пример обогрев) эту информацию игнорируют и не учитывают в своей работе. а мастер ее принимает и пишет в лог.

При этом если слейв, которому нужна какая либо информация может получить ее как из лога мастера, так от слейва, если поймал её. Получается что система будет вполне жизнеспособна в плане связи между слейвами, даже если мастер молчит. Как то так...

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Цитата:
я тут думал над такой концепцией. если слейвы шлют в шину свои данные
Это уже token ring, несколько сложнее, чем мастер/слейв, но тоже реализуемо. Вопрос только в целесообразности.

vde69
Offline
Зарегистрирован: 10.01.2016

Mr.Privet пишет:

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

При этом если слейв, которому нужна какая либо информация может получить ее как из лога мастера, так от слейва, если поймал её. Получается что система будет вполне жизнеспособна в плане связи между слейвами, даже если мастер молчит. Как то так...

такую систему нельзя реализовать на простых железках типа RS485, такое реально реализуемо только на буферизируемых устройствах (например HUB Ethernet), это уже и сложнее и дороже...

faeton
faeton аватар
Offline
Зарегистрирован: 21.03.2016

vde69 пишет:

Mr.Privet пишет:

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

При этом если слейв, которому нужна какая либо информация может получить ее как из лога мастера, так от слейва, если поймал её. Получается что система будет вполне жизнеспособна в плане связи между слейвами, даже если мастер молчит. Как то так...

такую систему нельзя реализовать на простых железках типа RS485, такое реально реализуемо только на буферизируемых устройствах (например HUB Ethernet), это уже и сложнее и дороже...

RS-485 всего лишь физический уровень, причём, допускающий подключения нескольких устройств параллельно - шина. На нём возможно реализовать любой протол верхнего уровня, в т.ч. и токентинг, и несколько мастеров, и конкурентную среду. Например, модбас допускает несколько мастеров.

vde69
Offline
Зарегистрирован: 10.01.2016

faeton пишет:

RS-485 всего лишь физический уровень, причём, допускающий подключения нескольких устройств параллельно - шина. На нём возможно реализовать любой протол верхнего уровня, в т.ч. и токентинг, и несколько мастеров, и конкурентную среду. Например, модбас допускает несколько мастеров.

ModBus - не допускает одновременно несколько мастеров, если мы говорим о нем как о транспорте нулевого уровня. Он допускает цикличного передача мастера, но мастер всегда в сети один на один момент, если это условие не соблюдать -будет бред в шине... банально две команды от двух мастеров сложатся на физическом уровне...

alex_r61
alex_r61 аватар
Offline
Зарегистрирован: 20.06.2012

Зациклились просто с мастерами. Я уже выше давал ссылку на пример с режимом мульти мастер. Только зачем дома нужен космический корабль? Если у меня дома что то потечёт, то контроллер в ванной сразу перекроет воду. А через сколько об этом узнает мастер, через 1 или 2 секунды, какая разница. Что от этого изменится?

faeton
faeton аватар
Offline
Зарегистрирован: 21.03.2016

vde69 пишет:

faeton пишет:

RS-485 всего лишь физический уровень, причём, допускающий подключения нескольких устройств параллельно - шина. На нём возможно реализовать любой протол верхнего уровня, в т.ч. и токентинг, и несколько мастеров, и конкурентную среду. Например, модбас допускает несколько мастеров.

ModBus - не допускает одновременно несколько мастеров, если мы говорим о нем как о транспорте нулевого уровня. Он допускает цикличного передача мастера, но мастер всегда в сети один на один момент, если это условие не соблюдать -будет бред в шине... банально две команды от двух мастеров сложатся на физическом уровне...

Я разве где-то говорил об одновременной работе двух мастеров в ModBus?

alex_r61
alex_r61 аватар
Offline
Зарегистрирован: 20.06.2012

faeton пишет:

Я разве где-то говорил об одновременной работе двух мастеров в ModBus?

И я не об этом. Просто зачем всё усложнять, если в с системе будет от трёх-четырёх до десятка МК.

vde69
Offline
Зарегистрирован: 10.01.2016

на праздники начал писать отладчик (снифер+дебагер), начал с того, что написал формы разбора произвольного пакета и сборки пакета по параметрам, сейчас прикручиваю работу с COM портом.

короче получается вполне себе система, возможно и для комерческого использования пойдет, вот теперь думаю - выкладывать в нахаляву или нет :)

NE_XT
NE_XT аватар
Offline
Зарегистрирован: 22.05.2012

vde69 пишет:

 вот теперь думаю - выкладывать в нахаляву или нет :)

Пилите Шура, пилите (с)

 

alex_r61
alex_r61 аватар
Offline
Зарегистрирован: 20.06.2012

Ну что УСЁ? Все по норам и тему закрыли?

Anton_Kos87
Offline
Зарегистрирован: 07.03.2014

ну че разобрались с интерфейсом?? хотел сделать мнемо схему состояния и контроля устройств по интерфейсу вот с интерфейсом возникли проблеммы, если кому интересно пишите почту скину саму мнемо схему, 

vde69
Offline
Зарегистрирован: 10.01.2016

по железу:

почти собрал модуль мастера (состоит NANO+часы+SD), осталось поставить диоды на питание (которых пока нет) + 3 кнопки и 5 светодиодов, и лицевую панель, сейчас все собрано в корпусе и работает, но пока без кнопок...

сделал генератор трафика (на NANO)

 

практически доделал по софту:

1. классы для ардуино и общий шаблон скеча для мастера и слейвов

2. анализатор трафика на дельфи (с разбором пакетов, фильтрами, показателями мусорности и т.д.)

3. отладчик на дельфи (позволяет выделять обмен между мастером и одним слейвом, при этом умеет заставлять мастера посылать в степовом режиме, при этом вся остальная шина работает в штатном режиме)

4. отдельный канал для отладочных сообщений (приходят только отладчику)

осталось реализовать:

1. автоматическая смена ID слейвом

2. обработку широковещательных пакетов

 

после этого можно будет приступать к слейвам, первый слейв будет: NANO+давление+шина с датчиками температуры (улица и дома по зонам), тут у меня все есть и даже скетч я писал перед новым годом, останется его адаптировать. Следующий этап будет - это экранчик...

 

MaksVV
Offline
Зарегистрирован: 06.08.2015

Тут в начале говорилось об обмене модулями. Сам начал с освещения. Модуль сделал на печатке. Могу выложить инфу по модулю света, если интересно, хотя сам ниразу не программист, поэтому при вашем уровне, вам врядли понравится. 

vde69
Offline
Зарегистрирован: 10.01.2016

дошел до состояния когда нужно отлаживать одновременно несколько устройств, есть вопросы

1. можно- ли подключить несколько NANO по USB к одному компу на разные COM порты ? что для этого надо? и как их различать?

2. стоит-ли делать отдельный макетный модуль (NANO+RS485) который использовать как эталонный ответчик мастеру / генератор помех / и т.д. или использовать реальные устройства?

alex_r61
alex_r61 аватар
Offline
Зарегистрирован: 20.06.2012

1, Можно, различаются по номеру СОМ порта, у меня от одного-двух до трёх-четырёх висят.

2. Это уже как удобнее. Я связь сначала в симуляторе отлаживаю, а затен в железо переношу. И быстрее, и для МК лучше.

AndrF
Offline
Зарегистрирован: 10.04.2016

vde69 пишет:

1. можно- ли подключить несколько NANO по USB к одному компу на разные COM порты ? что для этого надо? и как их различать?

Логичней и комп подключать через 485

vde69
Offline
Зарегистрирован: 10.01.2016

AndrF пишет:

vde69 пишет:

1. можно- ли подключить несколько NANO по USB к одному компу на разные COM порты ? что для этого надо? и как их различать?

Логичней и комп подключать через 485

комп нужен не только для монитора данных но и для загрузки исправленых программ, при этом будет удобно ничего не перетыкивать а иметь открытым сразу 2 проекта и перезаливать их по мере необходимости....

vde69
Offline
Зарегистрирован: 10.01.2016
vde69
Offline
Зарегистрирован: 10.01.2016

нужен совет по удобству компоновки скетчей.... сейчас у меня есть некий общий базовый функционал, дальше я вижу 2 пути развития

1. на каждый конкретный железный модуль заводить на основании некого шаблона отдельный проект

2. делать один большой проект который будет подходить всем модулям (в зависимости от директив компилятору)

минусы первого подхода: при внесении изменений в общую часть, или при внесение взаимо зависимых данных нужно будет перелопачивать все проекты

минус второго подхода - будет очень много файлов, и куча директив которые будут существенно усложнять читабельность кода

сам пока склоняюсь ко второму подходу... кто чего посоветует?

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

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

Anton_Kos87
Offline
Зарегистрирован: 07.03.2014

посмотрите ссылочку там есть связь блоков по rs485

http://bigbarrel.ru/tag/%D1%83%D0%BC%D0%BD%D1%8B%D0%B9-%D0%B4%D0%BE%D0%BC/page/2/

vde69
Offline
Зарегистрирован: 10.01.2016

Anton_Kos87 пишет:

посмотрите ссылочку там есть связь блоков по rs485

http://bigbarrel.ru/tag/%D1%83%D0%BC%D0%BD%D1%8B%D0%B9-%D0%B4%D0%BE%D0%BC/page/2/

сейчас у меня реально работает на столе

1. мастер с RTC часами

2. слейв с 4х строчным дисплеем

3. слейв с датчиками температуры даллас на он вире

данные датчиков темппературы и часов корректно отображаются на дисплеи, после разрыва связи все само востанавливается... конфликты одного ID в сети разруливаются... количество модулей с дисплеями ограничено лишь разумным пределом.

Сейчас занимаюсь переписыванием кода на более универсальный с прицелом на расширение, жду тачскринов (буду переделывать на тачи) и еще мелочевки. Так что лично для меня вопрос протокола - не актуален уже, то что сейчас работает я пока не пробовал на больших расстояниях, но на 10 метровом кабеле вообще никаких проблемм (уже 3суток работают на стенде и комп ловит "мусор", пока ни одного ошибочного байта)

зы

выкладывать правда некуда, файлов реально много будет, нужен репозитарий како-то более менее надежный...

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

vde69 пишет:

выкладывать правда некуда, файлов реально много будет, нужен репозитарий како-то более менее надежный...

GitHub наше всё ;)

vde69
Offline
Зарегистрирован: 10.01.2016

не пойму как туда выкладывать вложеные каталоги, конечно можно zip но для меня это не подходит...

наверно можно как отдельную ветку (ибо по смыслу это подходит), может еще какие варианты?

Anton_Kos87
Offline
Зарегистрирован: 07.03.2014

меня интересует как вывести все на мнемосхему с помощью web, ethernet шилда, с помощью Ajax неплохой внешний вид получится!!!! скиньте пожалуйста свой проект очень интересно!!! у меня в проекте управление скважины (Насос включение выключение по давлению, подогрев трубы в мороз, состояния температуры в скважине и самой воды,), уличный датчик (Температура, влажность), и септик (Температура, состояние, аварийное переполнение) вот думал сделать на RS485 и поставить мегу 2560 как мастера с Ethernet шилдом с смартфона или с компьютера открываем страничку и смотрим что там происходит (мнемосхема), или задаем параметры включения и выключения например подогрева трубы в мороз!! вот ищу подоные проекты что бы осущиствить свои планы, у вас помойму есть чему поучится скиньте пожалуйста скетч на почту Anton_Kos87@mail.ru

vde69
Offline
Зарегистрирован: 10.01.2016

выложу наброски через неделю, другую сейчас очень сыро еще...

в качестве затравки кину 2 модуля сюда (они пока еще не полностью переписаны на новую идеологию, но все более менее понятно), что-бы было понятно в каком оформление это будет дальше, может кто покритикует...

// ---------------------------------------------------------
// проект "Модульный умный дом", шина RS485
//
// состав:
// Arduino NANO
// MAX485 RS-485 Module TTL to RS-485 module
//
// версия среды Arduino 1.6.9
// 
// автор vde69@mail.ru (с)
// ---------------------------------------------------------

// ------------------------------------------------------------
//                   конфигурация прошивки                  
//
// перечень прошивок, все кроме одной должны быть закоментированы 
// детальная настройка прошивок  в файле A_Config.h 
// ------------------------------------------------------------

//#define PROGRAMS_MASTER           
//#define PROGRAMS_METEOSTATION           
#define PROGRAMS_TEST
//#define PROGRAMS_DISPLAY

#include "A_Config.h"           // загрузим детальную конфигурацию прошивок

// ------------------------------------------------------------
// загрузка библиотек, желательно загрузить все библиотеки до первого оператора
// при более позденй загрузки возможны неочевидные ошибки компиляции, 
// очередность загрузки библиотек важен!!!
// до загрузки библиотек не должно быть ничего кроме директив компилятору!!!

#include <EEPROM.h>
#include <Wire.h>
#include "BusRS485.h"

#ifndef A_HOUSE_ADDON           // загрузим расширения и общие для всех определения
  #include "include/a_house_addon.h"
#endif 


#ifdef ONE_WIRE 
  #include <OneWire.h> 
#endif 
#ifdef TEMPERATURE_DS18B20_ONE_WIRE 
  #include <DallasTemperature.h>
  #include "include/ds18b20.h"
#endif 
#ifdef MODULE_RTC_I2C 
  #include <RTClib.h>
#endif 
#ifdef LCD_4X20_I2C 
  #include <LiquidCrystal_I2C.h>
#endif 

// ------------------------------------------------------------
// Общие переменные не зависящие от конфигурации устройства
BusRS485 Bus(0, 5, 4);            // создание шины 485, порт - 0, пин - 5 (Read), пин - 4 (Write), можно назначить на один пин, но не будет работать полное отключение шины
unsigned long time_loop_new = 0;  // время начала основного цикла, на это время идет синхронизация всех команд и пауз
uint8_t Status              = 0;  // битовый статус контроллера, включая затребованые команды
                                  // 1 - режим инициализации ID
                                  // 2 - <свободно>
                                  // 3 - <свободно>
                                  // 4 - <свободно>
                                  // 5 - <свободно>
                                  // 6 - <свободно>
                                  // 7 - <свободно>
                                  // 8 - <свободно>
                                  
// ------------------------------------------------------------
// определение переменных и настроек для шин ONE WIRE
#ifdef ONE_WIRE 
  OneWire One_Wire(ONE_WIRE);     // Настройка oneWire интерфейса
#endif 

// ------------------------------------------------------------
// определение переменных и настроек для RTC часов
#ifdef MODULE_RTC_I2C 
  RTC_Millis RTC;
#endif 

// ------------------------------------------------------------
// определение переменных и настроек для LCD текстового дисплея 4х40
#ifdef LCD_4X20_I2C 
  LiquidCrystal_I2C lcd(0x27, 20, 4);
#endif 

// ------------------------------------------------------------
// определение переменных и настроек для датчиков температуры DS18B20
#ifdef TEMPERATURE_DS18B20_ONE_WIRE 
  Sensors_DS18B20 sensors_DS18B20(&One_Wire, TEMPERATURE_DS18B20_COUNT); 
 // uint16_t Setup_DS18B20[TEMPERATURE_DS18B20_COUNT][5]; // массив адресов датчиков, первых два байта - зона, остальные адрес
#endif 

// ------------------------------------------------------------
// определение переменных и настроек для модулей использующих общее время
#ifdef MODULE_RTC_USE
  uint16_t Year   = 0;
  uint8_t Month   = 0;
  uint8_t Day     = 0;
  uint8_t Hour    = 0;
  uint8_t Minute  = 0;
  uint8_t Second  = 0;
#endif 


//*************************************************************************************************
// процедура инициализации контролера
//*************************************************************************************************
void setup()
{ 

  if (Get_signature() != SIGNATURE) {
    // перезапишем все сохраняемые параметры
    Set_signature(SIGNATURE);
    Set_Id(ID_DEFAULT);
    Set_Speed(SPEED_DEFAULT);
    delay(100);
  }
 
  #ifdef MODULE_MASTER
    Bus.InitMaster(Get_Speed()*100);  
  #else  
    if (Get_Id() == 0) { Bus.InitSlave(Get_Speed()*100, 241, OnSlave_All); }
    else               { Bus.InitSlave(Get_Speed()*100, Get_Id(), OnSlave_All); }
  #endif 
  
  Bus.SetEcho(ECHO);

  #ifdef MODULE_RTC_I2C 
    RTC.begin(DateTime(__DATE__, __TIME__));
    //RTC.adjust(DateTime(__DATE__, __TIME__)); //— Устанавливает текущие дату и время Вашего компьютера в Часы.
  #endif 

  #ifdef LCD_4X20_I2C 
    lcd.init();
    lcd.backlight();
    lcd.noCursor();
    LCD_4X20_I2C_poll(true, true);
  #endif 

}


//*************************************************************************************************
// главный цикл
//*************************************************************************************************
void loop() {

  time_loop_new = millis();  // получим время начала цикла
    
  Bus.poll();                // вызов процедур чтения порта
  
  #ifdef TEMPERATURE_DS18B20_ONE_WIRE 
   // ds18b20_poll(); 
  #endif 

  #ifdef MODULE_MASTER  
    Master_poll();
  #endif 

  #ifdef LCD_4X20_I2C 
    LCD_4X20_I2C_poll(false, false);
  #endif 

}

// ВНИМАНИЕ, в данном модуле использовать можно ТОЛЬКО директивы компилятору #define, #ifdef, #endif, #undef
// в случае использовании любых других операторов будут неочевидные ошибки компиляции


// ------------------------------------------------------------------
// Значения по умолчанию, можно переопределять в конфигурациях через отмену #undef
#define ECHO 0                    // эхо приема (выводит в USB)
#define ID_DEFAULT 241            // Id по умолчанию, для мастера ставим = 0, для слейва = 241 (ошибочный ID)
#define SPEED_DEFAULT 192         // скорость по умолчанию (в сотнях, 192 = 19200), в сотнях сделано для влезания в 2 байта EEPROM, менять не рекомендуется
#define TIME_OUT_COMMAND 20       // таймаут ожидания ответа на команду, подбирается опытным путем, рекомендуемый диапазон от 20 до 100
#define SIGNATURE 1               // сигнатура формата памяти EEPROM, при смене значения происходит перезапись памяти значениями по умолчанию

// ------------------------------------------------------------------
// Конфигурация мастера
//   состоит из 
//   I2C RTC DS1307 AT24C32 часы реального времени
//   Micro SD Storage TF Card Memory Shield Module Adapter SPI For Arduino IDE
#ifdef PROGRAMS_MASTER 
  #define MODULE_MASTER             // это мастер шины RS-485 
  #define MODULE_RTC_I2C            // установлен модуль RTC подключение I2C
  
  // переопределим режим эха
  #undef ECHO                    
  #define ECHO 1
  
  // переопределим ID по умолчанию
  #undef ID_DEFAULT             
  #define ID_DEFAULT 0           
#endif 

// ------------------------------------------------------------------
// Конфигурация отладочного модуля
#ifdef PROGRAMS_TEST 
  #define MODULE_RTC_USE                 // признак, что модуль использует общее время
  #define LCD_4X20_I2C                   // установлен LCD экран 4 строки, 20 символов, подключение I2C

  // -------------------------------------------
  // следующие 3 параметра связаны друг с другом
  #define TEMPERATURE_DS18B20_ONE_WIRE   // установлены датчики температуры Dallas на шину One Wire (шина должна быть активирована одельно через ONE_WIRE)
  #define ONE_WIRE 2                     // используется шина One Wire на пин D2 (варианты D2 или D3)
  #define TEMPERATURE_DS18B20_COUNT 2    // количество установленых датчиков, при изменении количества - обязательно проверяйте распределение памяти EEPROM
  // -------------------------------------------

  // переопределим режим эха
//  #undef ECHO                    
//  #define ECHO 1
#endif 

 

Baks
Baks аватар
Offline
Зарегистрирован: 11.01.2016

vde69 пишет:

выложу наброски через неделю, другую сейчас очень сыро еще...

в качестве затравки кину 2 модуля сюда (они пока еще не полностью переписаны на новую идеологию, но все более менее понятно), что-бы было понятно в каком оформление это будет дальше, может кто покритикует...

похоже автор статьи забыл выложить вкусняшку и пропал

gonzales
Offline
Зарегистрирован: 13.07.2015

Доброго времени суток!!!

Долго и внимательно читал тему, но так и не осознал ответа на свой вопрос, как бороться на rs485 с коллизиями? Уважаемый vde69 как ТС и остальная общественность, не могли бы Вы дать комментарий по следующим вопросам

1. Я наивно полагал, что если один что-то передает, то у других Serial.available>0 что говорит о том, что линия занята и надо просто  дождаться ее освобождения

bool Transfer_Message(RS485Message Message) {
  bool result = false;
  while (RS485.available() > 0) {
    RS485.read();
  }
  digitalWrite(DIR, HIGH); // включаем передачу
  delay(2);
  RS485.write(Message_to_send.reciver_id);
  RS485.write(Message_to_send.reciver_adress);
  RS485.write(Message_to_send.sender_id);
  RS485.write(Message_to_send.sender_adress);
  RS485.write(Message_to_send.command);
  RS485.write(Message_to_send.data1);
  RS485.write(Message_to_send.data2);
  RS485.write(Message_to_send.data3);
  RS485.write(Message_to_send.sign);
  RS485.flush();
  digitalWrite(DIR, LOW);
  delay(2);
  /*digitalWrite(LED1, HIGH);
    delay(100);
    digitalWrite(LED1, LOW);*/
  result = true;
  return result;
}

но на практике столкнулся с тем, что когда два устройства начинают что-то вещать у меня не просто на шине каша, у меня они перестают вообще что-либо слать.

2. Так можно или нет реализовать схему с мультимастером, и как тогда бороться с коллизиями. 

Объясню свою позицию: я согласен с звучавшими в теме мнениями, что вариант запрос-ответ не совсем здорово, потому как нажав кнопку я хочу увидеть реакцию в течении одной секунды а не ждать пока Мастер опросит "мое" устройство. То есть, я нажимаю кнопку, МК посылает данные на Мастер, получает команду и выполняет ее.  

В интеренте есть описания протоколов типа KNX, SmartBus но я не нашел готовых реализаций. 

В общем темой живо интересуюсь, есть готовые наработки, но при усложнении системы возникли непонятки с коллизиями, и главный вопрос, помему при коллизии больше до МК не достучаться до его перезагрузки. 

alex_r61
alex_r61 аватар
Offline
Зарегистрирован: 20.06.2012

Я в самом начале писал про интервально-маркерный метод для разделения устройств в моноканале. И приводил ссылку на книгу с подробным описанием этого метода.

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

gonzales пишет:
Так можно или нет реализовать схему с мультимастером, и как тогда бороться с коллизиями.
Маркерное кольцо и есть мультимастерная схема, только она в реализации несколько сложнее. RS485 не позволяет определить наличие колизии, для этого используют CAN.

gonzales пишет:
я согласен с звучавшими в теме мнениями, что вариант запрос-ответ не совсем здорово, потому как нажав кнопку я хочу увидеть реакцию в течении одной секунды а не ждать пока Мастер опросит "мое" устройство. То есть, я нажимаю кнопку, МК посылает данные на Мастер, получает команду и выполняет ее. 
Даже в маркерном кольце устройство должно дождаться, пока ему не передадут маркер, и только после этого оно становится мастером на шине. Что мешает в схеме мастер-слэйв опрашивать подчиненные устройства чаще, чем раз в 1 сек?

gonzales пишет:
и главный вопрос, помему при коллизии больше до МК не достучаться до его перезагрузки.
Кривая реализация протокола обмена.

alex_r61
alex_r61 аватар
Offline
Зарегистрирован: 20.06.2012

Можно просто дать Slave-ам разный приоритет и с более высоким опрашивать чаще, два или более раз в секунду.

gonzales
Offline
Зарегистрирован: 13.07.2015

Маркерное кольцо - это не мультимастер, по сути это тот же мастер-слейв, только вместо запроса от мастера я гоняю по сети маркер. Или я не прав?

>Что мешает в схеме мастер-слэйв опрашивать подчиненные устройства чаще, чем раз в 1 сек?

по моим подсчетам в сети будет около 100 устройств. Пакет у меня 10 байтный. При скорости 19200 это получается с небольшим запасом 0.01с на запрос-ответ. То есть мастер только и будет занят тем, что опрашивать устройства. Как только на это наложатся временные лаги на выполнение команд самим мастером будет просадка по времени отклика.

>RS485 не позволяет определить наличие колизии, для этого используют CAN

уже читаю про CAN, правда с реализациями на ардуино под can достаточно туго.

>Кривая реализация протокола обмена.

А что тут может быть кривого? Не сочтите за труд посмотрите скеч

bool SendMessage(byte Reciver_ID, byte Reciver_Adress, byte Sender_ID, byte Sender_Addres, byte Command, byte Data1, byte Data2, byte Data3) {
  Fill_Message(Reciver_ID, Reciver_Adress, Sender_ID, Sender_Addres, Command, Data1, Data2, Data3);
  Transfer_Message(Message_to_send);
}

bool Fill_Message(byte Reciver_ID, byte Reciver_Adress, byte Sender_ID, byte Sender_Adress, byte Command, byte Data1, byte Data2, byte Data3) {
  bool result = false;
  byte Sign = (Reciver_ID + Reciver_Adress + Sender_ID + Sender_Adress + Command + Data1 + Data2 + Data3) & 255;
  Message_to_send.reciver_id = Reciver_ID;
  Message_to_send.reciver_adress = Reciver_Adress;
  Message_to_send.sender_id = Sender_ID;
  Message_to_send.sender_adress = Sender_Adress;
  Message_to_send.command = Command;
  Message_to_send.data1 = Data1;
  Message_to_send.data2 = Data2;
  Message_to_send.data3 = Data3;
  Message_to_send.sign = Sign;
  result = true;
  return result;
}

bool Transfer_Message(RS485Message Message) {
  bool result = false;
  while (RS485.available() > 0) {
    RS485.read();
  }
  digitalWrite(DIR, HIGH); // включаем передачу
  delay(2);
  RS485.write(Message_to_send.reciver_id);
  RS485.write(Message_to_send.reciver_adress);
  RS485.write(Message_to_send.sender_id);
  RS485.write(Message_to_send.sender_adress);
  RS485.write(Message_to_send.command);
  RS485.write(Message_to_send.data1);
  RS485.write(Message_to_send.data2);
  RS485.write(Message_to_send.data3);
  RS485.write(Message_to_send.sign);
  RS485.flush();
  digitalWrite(DIR, LOW);
  delay(2);
  /*digitalWrite(LED1, HIGH);
    delay(100);
    digitalWrite(LED1, LOW);*/
  result = true;
  return result;
}

bool CheckRS485() {
  digitalWrite(DIR, LOW);
  delay(2);
  if (RS485.available() == 9) { // если пришло 9 байт
    Message.reciver_id = RS485.read();
    Message.reciver_adress = RS485.read();
    Message.sender_id = RS485.read();
    Message.sender_adress = RS485.read();
    Message.command = RS485.read();
    Message.data1 = RS485.read();
    Message.data2 = RS485.read();
    Message.data3 = RS485.read();
    Message.sign = RS485.read();
    /*if (RS485.available() ==0){
      digitalWrite(LED1, HIGH);
    delay(100);
    digitalWrite(LED1, LOW);
    }*/
    //DebugWrite(Message.command, 1);
    byte TestSign = (Message.reciver_id + Message.reciver_adress + Message.sender_id + Message.sender_adress + Message.command + Message.data1 + Message.data2 + Message.data3) & 255;
    if (TestSign == Message.sign) {
      if (Message.reciver_id == ID) {
        while (RS485.available() > 0) {
          RS485.read();
        }
        ParseMessage();
        return true;
      }
      else {
        DebugWrite("NOT FOR ME", 1);
        return false;
      }
    }
    else {
      DebugWrite("BAD SIGN", 1);
      return false;
    }
  }
  else {
    return false;
  }


}

 

alex_r61
alex_r61 аватар
Offline
Зарегистрирован: 20.06.2012

>А что тут может быть кривого? Не сочтите за труд посмотрите скеч

Кривая сама IDE, при такой загрузке нужен прямой доступ к ресурсам МК.

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Маркерное кольцо в каждый конкретный промежуток времени это мастер-слейв. Мастером может быть любое устройство, которому передан маркер - следовательно это мультимастерная схема.

gonzales пишет:
по моим подсчетам в сети будет около 100 устройств.
Рискну предположить, интерфейс реализован на MAX485, а он позволяет только 32 устройства на шине.

gonzales пишет:
А что тут может быть кривого?
Строка 51. Что если пришло не 9 символов, а меньше или больше?