آموزش پروتکل RS485 در آردوینو

در این آموزش قصد شبکه کردن چند میکروکنترلر از طریق پروتکل RS485 را داریم.ابتدا باید تعریف مشخصی از پروتکل RS485 داشته باشیم , RS485 یک پروتکل نرم افزاری در بستر اینترفیس  UART(پورت سریال در میکروکنترلر) می باشد.

بر خلاف پروتکلهای I2C و SPI که قابلیت شبکه کردن چند Device وجود دارد در پروتکل UART که یک پروتکل نظیر به نظیر می باشد (اتصال دو Device بهم) قابلیت اتصال چند میکروکنترلر به یکدیگر روی یک باس وجود ندارد.

برای حل این مشکل راه حل RS485 به وجود امده است.در ساختار RS485 یک Master تعریف می شود و مابقی میکروکنترلرها در حالت Slave (و همگی در حالت گیرنده) قرار می گیرند.این Master  است که مشخص می کند کدام Slave باید اطلاعات را دریافت کند یا بفرستد.

مشکلی که در ساختار بالا وجود دارد این است که در صورت مشکل در یکی از Slave ها (ولتاژ متفاوت در خطهای سریال) باعث اختلال در کل شبکه می شود.

برای حل این مشکل از ای سی های واسطی بنام مبدل RS485 (برای مثال می توان به تراشه MAX487 اشاره نمود) باید استفاده کرد.

مزایای استفاده از مبدل RS485 :

امکان محافظت میکروکنترلرها در صورت خرابی (اتصال کوتاه و …) روی باس

افزایش برد سیگنال سریال برای کار در مسافت طولانی تر کابل به دلیل ارسال ولتاژ تفاضلی

کم کردن اثر نویز پذیری

امکان استفاده از سرعت بیشتر

امکان شبکه سازی تا 32 میکروکنترلر

مبدلهای RS485 بصورت نیمه دوطرفه طراحی شده اند و همزمان نمی توانند عمل ارسال و دریافت دیتا را انجا دهند بنابراین توسط دو پایه RE و DE می توان جهت ان را مشخص کرد.از انجا که این دو پایه با سطح منطقی متفاوتی راه اندازی می شوند می توانیم این دو پایه را بهم وصل کنیم تا در سمت میکروکنترلر پایه کمتری استفاده شود.

در صورت یک کردن پایه فعال ساز(انتخاب و فعال کردن واحد فرستنده یا گیرنده) مبدل در حالت فرستنده قرار می گیرد و ما می توانیم اطلاعات را از طریق پایه TX سریال به پایه DI ارسال کنیم و در صورت صفر کردن پایه مبدل در حالت گیرنده قرار گرفته و اطلاعات از پایه RO به پایه RX میکروکنترلر ارسال می شود.

بنابراین برای هر node در شبکه RS485 نیاز به یک مبدل RS485 خواهیم داشت.همین طور که قبلا به ان اشاره شد در ساختار RS485  یک Master بعنوان node اصلی در شبکه وجود دارد وMaster بعنوان برد مرکزی وظیفه دریافت اطلاعات از node های Slave و یا دادن فرامین به انها را برعهده دارد.

برای برقراری ارتباط درست در تمامی node ها , همه ی انها باید دارای یک بادریت مشترک در پورت سریال خود باشند وهمچنین

برای اینکه گره های Slave توسط Master قابل شناسایی باشند باید یک ادرس اختصاصی داشته باشند.این ادرس در اولین بایت هر پکت ارسالی توسط Master مشخص می شود.و گیرنده بعد از دریافت پکت ابتدا بایت ادرس را با ادرس خود مقایسه می کند در صورت برابری شروع به پردازش مابقی اطلاعات پکت می کند و در غیر اینصورت ان را نادیده می گیرد.

همچنین Slave ها بعد از دریافت اطلاعات خود پکت ارسالی خود را برای Master می فرستند و در اولین بایت پکت ادرس خود را قرار می دهند تا Master اطمینان پیدا کند اطلاعات دریافت شده مربوط به Slave مورد نظر است.

در تصویر نحوه اتصالات مدار را مشاهده می کنید:

مقاومتهای 120 اهم مقاومت های Termination باس هستند و در فواصل طولانی کابل کشی حتما باید قرار بگیرند و همچنین زمین همه node باید بهم وصل شود, مقاومت 10 کیلو اهم در پایه Rx میکروکنترلرها برای این است تا در صورت قطع یا خرابی مبدل RS485 گیرنده سریال میکروکنترلر در وضعیت نامعلوم قرار نگیرد.

 

ابتدا برنامه  Masterرا شرح می دهیم :

#define Direction 2

تعیین پایه فعال ساز مبدل به اردوینو

#define packet_size 4

اندازه پکت ارسالی (دیتا به همراه ادرس بایت)که شما می توانید هر سایز دلخواهی را قرار بدهید.

#define Slave1_Address  0x45

مشخص کردن ادرس slave  اول و دوم

#define Slave2_Address  0x54

#define debugSerial 13

چون اردوینو uno یک پورت سریال دارد و ان درگیر RS485 می باشد ما برای نمایش و پرینت اطلاعات دریافتی از سریال مجازی روی پورت 13 میکروکنترلر استفاده نموده ایم.

#define timeout 1500

تنظیم مدت زمان timeout در Master برای دریافت اطلاعات

uint8_t getDatas[packet_size];
uint8_t counter = 0;


void SenderState(){

این تابع مبدل را در حالت فرستنده تنظیم می کند

digitalWrite(Direction,HIGH);
}

void RecieverState(){

این تابع مبدل را در حالت گیرنده تنظیم می کند

digitalWrite(Direction,LOW);
}




//9600 baudrate    no-parity    stopbit : 1

تابع پرینت اطلاعات توسط پورت سریال مجازی

void virtualSerial(String str){

   for(unsigned int z = 0; z < str.length(); z++){

      digitalWrite(debugSerial,LOW);//start bit
      delayMicroseconds(104);

      byte temp = str.charAt(z);

      byte shift = 1;

      for(byte x = 0; x < 8; x++){
      
          if((temp & shift) != 0){
            digitalWrite(debugSerial,HIGH); 
          }
          else{
            digitalWrite(debugSerial,LOW);
          }

          shift = shift << 1;
          delayMicroseconds(104);
      }

      digitalWrite(debugSerial,HIGH);//stop bit
      delayMicroseconds(104);
    
   }
}



bool  getData = false;

void setup() {

  pinMode(Direction,OUTPUT);

  pinMode(debugSerial,OUTPUT);

  digitalWrite(debugSerial,HIGH);
  
  SenderState();
  
  Serial.begin(9600);

  virtualSerial("Master ...");
  virtualSerial("\r\n");
  virtualSerial("\r\n");

  delay(100);

}


void printData(){

  virtualSerial("Get Data");
  virtualSerial("\r\n");

  if(getDatas[0] == Slave1_Address){

بایت اول دریافتی در ارایه مربوط به ادرس slave است که خود slave ادرس خود را در پکت قرار می دهد تا Master اطمینان پیدا کند کدام slave اطلاعات را ارسال کرده است.

     virtualSerial("it is Slave 1");
     virtualSerial("\r\n");
  }

  if(getDatas[0] == Slave2_Address){
    
     virtualSerial("it is Slave 2");
     virtualSerial("\r\n");
  }
  
  for(uint8_t z = 1; z < packet_size; z++){

نمایش مابقی اطلاعات دریافتی درون ارایه

virtualSerial(String(getDatas[z]));

      virtualSerial("\r\n");
    
  }

   virtualSerial("\r\n");
}



uint8_t _switch = 0;

void loop() {

  SenderState();

ابتدا master را در حالت فرستنده قرار می دهیم

  if(_switch == 0){

از متغییر switch برای نوبت دادن به slave ها برای دریافت و خواندن اطلاعاتشان استفاده می کنیم

_switch = 1;

    Serial.write(Slave1_Address);

ابتدا ارسال ادرس و بعد دیتا

Serial.write(3);//data 1
    Serial.write(4);//data 2
    Serial.write(3);//data 3
    
  }
  else{
    _switch = 0;

    Serial.write(Slave2_Address);

    Serial.write(5);//data 1
    Serial.write(6);//data 2
    Serial.write(5);//data 3
  }

  

  delay(10);
  RecieverState();

قرار دادن مبدل در حالت گیرنده برای دریافت اطلاعات

unsigned long temp = millis();

  while((millis() - temp) < timeout){
    
     if(getData == true){
        break;
     }
  }

منتظر تا پایان timeout

  if(getData == true){

در صورت دریافت دیتا متغییر getData در وقفه دریافت سریال true می شود.

getData = false;
     counter = 0;

نمایش اطلاعات دریافتی

printData();
  }

  delay(1000);

}



void serialEvent() {

وقفه دریافت سریال

while (Serial.available()) {

    counter++;

    getDatas[counter - 1] = Serial.read();

    if(counter == packet_size){

اگر تعداد دیتای دریافتی برابر با packet_size شد یعنی به انتهای دریافت اطلاعات رسیدیم.

counter = 0;

       getData = true;

       Serial.flush();
    }

    
  }

}

به علت تشابه برنامه در slave ها فقط برنامه مربوط به slave1 را بررسی می کنیم.

#define Direction 2

#define packet_size 4

#define Slave1_Address  0x45

#define debugSerial 13



uint8_t getDatas[packet_size];
uint8_t counter = 0;


void SenderState(){
  digitalWrite(Direction,HIGH);
}

void RecieverState(){
  digitalWrite(Direction,LOW);
}




//9600 baudrate    no-parity    stopbit : 1


void virtualSerial(String str){

   for(unsigned int z = 0; z < str.length(); z++){

      digitalWrite(debugSerial,LOW);//start bit
      delayMicroseconds(104);

      byte temp = str.charAt(z);

      byte shift = 1;

      for(byte x = 0; x < 8; x++){
      
          if((temp & shift) != 0){
            digitalWrite(debugSerial,HIGH); 
          }
          else{
            digitalWrite(debugSerial,LOW);
          }

          shift = shift << 1;
          delayMicroseconds(104);
      }

      digitalWrite(debugSerial,HIGH);//stop bit
      delayMicroseconds(104);
    
   }
}




bool  getData = false;

void setup() {

  pinMode(Direction,OUTPUT);

  pinMode(debugSerial,OUTPUT);

  digitalWrite(debugSerial,HIGH);
  
  RecieverState();
  
  Serial.begin(9600);

  virtualSerial("Slave1 ...");
  virtualSerial("\r\n");

}


void printData(){

  virtualSerial("Get Data \r\n");
  
  for(uint8_t z = 1; z < packet_size; z++){

      virtualSerial(String(getDatas[z]));

      virtualSerial("\r\n");
    
  }

   virtualSerial("\r\n");
}



void loop() {



  if(getData == true){

اگر پکتی دریافت کردیم

     getData = false;
     counter = 0;

     if(getDatas[0] == Slave1_Address){//first byte is address

و ادرس دریافتی برابر با ادرس تنظیم شده بود

delay(50);
           SenderState();

مبدل را در حالت فرستنده قرار می دهیم

           Serial.write(Slave1_Address);

و ادرس slave خود و دیتای ان را برای Master می فرستیم

           Serial.write(30);//data1
           Serial.write(31);//data2
           Serial.write(32);//data3

           delay(10);
           RecieverState();

سپس مبدل را برای دریافت بعدی در حالت گیرنده قرار می دهیم.

           printData();
      
     }
          
  }


}



void serialEvent() {

وقفه دریافت پورت سریال

  while (Serial.available()) {

    counter++;

    getDatas[counter - 1] = Serial.read();

خواندن اطلاعات دریافتی

    if(counter == packet_size){
      
       counter = 0;

       getData = true;

       Serial.flush();
    }

    
  }

}

به علت نرم افزاری بودن پروتکل RS485 شما طبق نیاز خود می  توانید پروتکل را شخصی سازی کنید.

در ادامه می توانید برنامه ها و فایل شبیه سازی را دانلود نمایید:

دانلود فایل

دیدگاهتان را بنویسید!

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

گیل الکترونیک
سبد خرید
empty basket

هیچ محصولی در سبد خرید نیست.