جستجو
برای جستجو متن مورد نظر وارد کنید و Enter بزنید برای بستن Esc بزنید.
در این آموزش قصد شبکه کردن چند میکروکنترلر از طریق پروتکل 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 شما طبق نیاز خود می توانید پروتکل را شخصی سازی کنید.
در ادامه می توانید برنامه ها و فایل شبیه سازی را دانلود نمایید: