RS485 hakkında uzun bir gönderi yaptım.
İlk olarak, Megas kullanımınız, eğer zaten elinizde yoksa, abartılı görünüyor. Bir Uno veya daha küçük form faktörü panolarından biri, birkaç anahtarı izlemek ve birkaç ışığı açmak için mükemmel şekilde yeterli görünebilir.
Rpi bile gereksiz görünüyor. Başka bir Uno, RS485 hatlarınızı kolayca izleyebilir ve Ethernet (bir Ethernet kalkanı) yoluyla evinizin geri kalanına veya yaptığınız her ne yapıyorsanız bağlanabilir.
... provizyon yok iletişim kurarken "çarpışmalardan" kaçınmak için ...
Pekala, onu inşa edersiniz. Her Mega'ya bir adres verirsiniz (örn. EEPROM'da kayıtlı) ve sonra istiyorum ve sonra bir yanıt bekleyin. Örneğin, yukarıdaki sayfamdaki kodda:
Ana
#include "RS485_protocol .h "#include <SoftwareSerial.h>const baytı ENABLE_PIN = 4; const bayt LED_PIN = 13; SoftwareSerial rs485 (2, 3); // pin al, pin gönder // geri çağırma rutinleri void fWrite (const byte ne) {rs485.write (ne); } int fAvailable () {return rs485.available (); } int fRead () {dönüş rs485.read (); } geçersiz kurulum () {rs485.begin (28800); pinMode (ENABLE_PIN, OUTPUT); // sürücü çıkışı etkinleştirin pinMode (LED_PIN, OUTPUT); // yerleşik LED} // kurulum baytı sonu old_level = 0; void loop () {// potansiyometre bayt seviyesi oku = analogRead (0) / 4; // değişiklik yok? (level == old_level) dönerse unutun; // mesaj baytını birleştirin msg [] = {1, // cihaz 1 2, // ışığı hangi seviyede açın // hangi seviyeye getirin}; // slave digitalWrite'a gönder (ENABLE_PIN, HIGH); // sendMsg göndermeyi etkinleştir (fWrite, msg, sizeof msg); digitalWrite (ENABLE_PIN, LOW); // göndermeyi devre dışı bırak // yanıt baytı al buf [10]; alınan bayt = recvMsg (fAvailable, fRead, buf, sizeof buf); digitalWrite (LED_PIN, alınan == 0); // hata durumunda LED'i aç
// (alındı) old_level = level;} // döngünün sonu
RS485 alıcı-vericisini göndermek veya almak için yapılandırırsanız // başarılı değişiklik başına yalnızca bir kez gönderin. Normalde alma modundadır ve bir veri "paketi" göndermek için gönderme moduna geçersiniz. (Yukarıdaki "göndermeyi etkinleştir" bölümüne bakın).
Adreslenen cihaz artık alıcı vericisini "gönderme" moduna ve yanıtlara dönüştürüyor. Yazdığım kütüphanedeki kodun bir zaman aşımı var, bu nedenle o belirli köle ölmüşse, alma zaman aşımına uğradı. Bunu usta sonunda hatırlayabilir ve onunla daha az iletişim kurmaya çalışabilirsiniz. Ya da zaman aşımının kısa olması umrunda olmayabilir.
Slave
#include <SoftwareSerial.h> # include "RS485_protocol.h" SoftwareSerial rs485 (2, 3); // pin al, pinconst baytını ilet ENABLE_PIN = 4; void fWrite (const bayt ne) {rs485.write (ne); } int fAvailable () {return rs485.available (); } int fRead () {dönüş rs485.read (); } geçersiz kurulum () {rs485.begin (28800); pinMode (ENABLE_PIN, OUTPUT); // sürücü çıktısı etkinleştirme} void döngü () {bayt buf [10]; alınan bayt = recvMsg (fAvailable, fRead, buf, sizeof (buf)); eğer (alındı) {if (buf [0]! = 1) iade; // benim cihazım değil if (buf [1]! = 2) return; // bilinmeyen komut baytı msg [] = {0, // cihaz 0 (ana) 3, // alınan komuta ışığı aç}; gecikme (1); // yöneticiye digitalWrite (ENABLE_PIN, HIGH) 'ı almaya hazırlanmak için bir dakika verin; // sendMsg göndermeyi etkinleştir (fWrite, msg, sizeof msg); digitalWrite (ENABLE_PIN, LOW); // analogWrite (11, buf [2]) göndermeyi devre dışı bırakın; // ışık düzeyini ayarla} // bir şey alınırsa bitir} // döngünün sonu
Not : Bağlı sayfamdaki örnek kod adresi okumuyor EEPROM'dan, ancak bunun uygulanması önemsizdir.
Köleleri neden bu kadar sık sorgulayamadığınızla ilgili belirli bir neden göremiyorum. Usta başka ne yapacaktı? Ana sunucuyu bir HTTP sunucusu olarak da kurabilirsiniz, böylece onunla dizüstü bilgisayarınızdan veya evin başka bir yerinden konuşabilirsiniz.
Kablolarım:
Fikri göstermek için, yaklaşık 8 m çan teli ile bağlanan iki Uno kurdum (bükülmüş çift bile değil ve kesinlikle korumalı).
Bir Uno standart ASCII tablo taslağını çalıştırıyordu (9600 baud'da), Tx pini LTC1480'e girdi, ve sonra A / B pinleri çan kablosuna gitti.
Diğer Uno bir USB arayüzü olarak bağlandı (toprağa sıfırlandı) ve Tx pinine gelen her şeyi USB'ye yansıtıyordu.
Görebildiğim kadarıyla mükemmel çalıştı.
Yaklaşımınızda bir " tek ana / çoklu bağımlı "bağlamı ... ancak" hareketli "bir ana birim ile. Bu doğru mu?
Yukarıdaki cevabım kölelerin başarısız olma olasılığının efendiden daha yüksek olduğunu varsaydı (bunun neden olacağını tam olarak düşünemiyorum, ama belki de efendilerden daha fazla köle ve efendi ışıklar gibi şeyleri kontrol etmez).
Arduino'larımı basit şeyler yaparken son derece güvenilir buluyorum (RFID kartı sunulduğunda bir kapının kilidini açmak gibi).
Muhtemelen kölelere karşı bir geri çekilme pozisyonu oluşturabilirsiniz. Sonuçta, eğer her saniye yoklanırlarsa ve sonra hiçbir anket gelmezse, çatışmaları önlemek için, muhtemelen artan cihaz numarası sırasına göre ana olarak devralmaya çalışabilirler. "Yeni usta" tarafından yapılan bu anketin bir kısmı, görevine devam etmeye hazır olup olmadığını görmek için "orijinal ustayı" kontrol etmek olabilir.
Bağlantılı sayfamda anlattığım kitaplıkta hata denetimi yerleşiktir; paketteki bir CRC'yi kontrol ederek bir paketin yarısına gelmemenizi ve içindeki verileri yanlış yorumlamanızı sağlamanızdır. .
Ayrıca, iki köle aynı anda efendi olmaya çalıştıysa, bir çıkmaza son vermek için bazı rasgele anket süreleri de oluşturabilirsiniz. Bir köle başarısız olursa, başka bir köleye bunu yapma şansı vermek için tekrar denemeden önce rastgele (ve artan) bir süre bekleyebilirdi.
Sadece şansı not etmek istedim paket çarpışmalarının oranı oldukça düşüktür. Paketleri yalnızca bir anahtara basıldığında veya bir ışığın yanması gerektiğinde gönderirsiniz.
Gerben haklı, ancak bir anahtarla ilgili bir bildirimin fark edilmemiş olmasından endişelenirdim. Buradaki olası bir çözüm, bağımlıların bir sorguya durum değişiklikleri ile yanıt vermesinden ziyade, mevcut durumla yanıt vermeleridir. Öyleyse şöyle olabilir:
Master: Bağımlı 3, durumunuz nedir? Bağımlı 3: Işıklar 1 ve 4 açık, ışıklar 2 ve 3 söner.
Herhangi bir ankete ihtiyacım olmayacak, çünkü sizin yaklaşımınızla "tek-ana / çok-köle" bağlamı uygulayabilirim ... ancak "hareketli" bir usta. Doğru mu?
Bunu biraz düşünüyordum ve bence artık temelde ustasız bir sistem yapabilirsiniz. Şu şekilde çalışabilir:
-
Her cihazın EEPROM'dan (veya DIP anahtarlarından) aldığı kendi adresi vardır. Örneğin. 1, 2, 3, 4, 5 ...
-
Kullanacağınız bir adres aralığı seçersiniz (örn. Maksimum 10)
-
Cihaz açıldığında, ilk olarak veri yolunda "konuşan" diğer cihazları dinler. Umarım en az bir tane daha duyacaktır (değilse, aşağıya bakınız).
-
Sabit bir "mesaj paketi", örneğin adres, CRC vb. Dahil 50 baytlık bir karar veriyoruz. . 9600 baud'da gönderilmesi 52 ms sürer.
-
Her cihaz bir "zaman aralığı" alır ve otobüsle konuşma sırasının gelmesini bekler.
-
Zaman dilimi geldiğinde çıkış moduna geçer kendi adresini içeren paketini yayınlar. Bu nedenle, diğer tüm cihazlar artık durumunu okuyabilir (ve gerekirse buna göre hareket edebilir). Örneğin. cihaz 1, anahtar 3'ün kapalı olduğunu bildirebilir, bu da cihaz 2'nin ışığı yakması gerektiği anlamına gelir.
-
İdeal olarak, zaman aralığınızın geldiğini biliyorsunuz çünkü cihaz adresiniz az önce dinlediğiniz paket. Örneğin. Cihaz 3'sünüz. Cihaz 2'nin durumunu duyurduğunu duydunuz. Şimdi senin sıran. Elbette maksimum sayıya ulaşırsınız, bu nedenle 10. cihazdan sonra 1. cihaza geri dönersiniz.
-
Bir cihaz eksikse ve yanıt vermiyorsa, onu verirsiniz ( Diyelim ki) yarım zaman dilimi yanıt vermek için ve sonra öldüğünü varsayın ve veriyolu üzerindeki her aygıt şimdi bir sonraki zaman diliminin başladığını varsayar. (örn. Cihaz 2'yi duydunuz, cihaz 3 yanıt vermelidir, 25 ms'lik hareketsizlikten sonra cihaz 4 artık yanıt verebilir). Bu kural, bir aygıta yanıt vermesi için 25 ms verir; bu, bir kesintiye veya benzeri bir şeye hizmet ediyor olsa bile çok olmalıdır.
-
Sırayla birden fazla cihaz eksikse, bir Sıra size gelene kadar her kayıp cihaz için 25 ms'lik boşluk.
-
En az bir yanıt aldığınızda, zamanlama yeniden senkronize edilir, böylece saatlerdeki herhangi bir sapma iptal edilir.
Buradaki tek zorluk, ilk çalıştırmada (binanın gücü kesilirse ve ardından eski haline gelirse eşzamanlı olarak gerçekleşebilir) şu anda durumunu yayınlayan hiçbir cihazın olmamasıdır. ve dolayısıyla senkronize edilecek hiçbir şey yok.
Bu durumda:
-
Tüm cihazların duyulması için yeterince uzun süre dinledikten sonra (ör. 250 ms) ve hiçbir şey duymadığında, cihaz geçici olarak ilk olduğunu varsayar ve bir yayın yapar. Ancak muhtemelen iki cihaz bunu aynı anda yapacak ve dolayısıyla birbirlerini asla duymayacaktır.
-
Bir cihaz başka bir cihazdan haber almadıysa, yayınlar arasındaki zamanı rasgele sıralar (belki de tüm cihazların yayınları "rastgele" aynı miktarda şaşırtmasını önlemek için rastgele sayı oluşturucuyu cihaz numarasından başlatır. ).
-
Ek süre ile bu rastgele sersemletme önemli olmayacak, çünkü zaten dinleyen kimse yok.
-
Er ya da geç bir cihaz, veri yolunun özel kullanımına sahip olacak ve diğerleri daha sonra normal şekilde onunla senkronize edilebilecek.
İletişim girişimleri arasındaki bu rastgele boşluk birden fazla cihaz tek bir koaksiyel kabloyu paylaştığında Ethernet'in yaptığına benzer.
Ana içermeyen sistem demosu
Bu ilginç bir zorluktu, bu yüzden bir araya getirdim Yukarıda açıklandığı gibi, belirli bir ana makineye sahip olmadan bunu yapmanın bir demosu.
Öncelikle EEPROM'da mevcut cihaz adresini ve cihaz sayısını ayarlamanız gerekir, bu nedenle bu çizimi çalıştırın ve myAddress her biri için ch Arduino:
#include <EEPROM.h>const byte myAddress = 3; const byte numberOfDevices = 4; void setup () {if (EEPROM.read ( 0)! = MyAddress) EEPROM.write (0, myAddress); eğer (EEPROM.read (1)! = numberOfDevices) EEPROM.write (1, numberOfDevices); } // setupvoid döngüsünün sonu () {}
Şimdi bunu her cihaza yükleyin:
/ * Multi-drop RS485 cihaz kontrol demosu. Nick Gammon tarafından tasarlanmış ve yazılmıştır. Tarih: 7 Eylül 2015 Sürüm: 1.0 Lisans: Genel kullanım için yayınlandı. RS485_non_blocking kitaplığı için bkz: http://www.gammon.com.au/forum/?id=11428 JKISS32 için bkz: http://forum.arduino.cc/index.php?topic=263849.0*/#include <RS485_non_blocking. h> # include <SoftwareSerial.h> # include <EEPROM.h> // birbirimize yayınladığımız veriler devicestruct {bayt adresi; bayt anahtarları [10];
int durumu; } mesaj; const unsigned long BAUD_RATE = 9600; const float TIME_PER_BYTE = 1.0 / (BAUD_RATE / 10.0); // bir baytlık işaretsiz uzun gönderme başına saniye PACKET_LENGTH = ((sizeof (mesaj) * 2) + 6); // Yük baytı başına 2 bayt artı STX / ETC / CRCconst işaretsiz uzun PACKET_TIME = TIME_PER_BYTE * PACKET_LENGTH * 1000000; // mikrosaniye // yazılım seri pinsconst bayt RX_PIN = 2; const bayt TX_PIN = 3; // etkinleştirme baytını ilet XMIT_ENABLE_PIN = 4; // hata ayıklama pinsconst baytı OK_PIN = 6; const bayt TIMEOUT_PIN = 7; sabit bayt SEND_PIN = 8; const bayt SEARCHING_PIN = 9; const bayt ERROR_PIN = 10; // eylem pimleri (demo) sabit bayt LED_PIN = 13; const bayt SWITCH_PIN = A0; // kez mikrosaniye cinsinden işaretsiz uzun TIME_BETWEEN_MESSAGES = 3000; işaretsiz uzun noMessagesTimeout; byte nextAddress; unsigned long lastMessageTime; unsigned long lastCommsTime; unsigned long randomTime; SoftwareSerial rs485 (RX_PIN, TX_PIN); // pin al, pini ilet // hangi durumdayız biz inenum {STATE_NO_DEVICES, STATE_RECENT_RESPONSE, STATE_TIMED_OUT,} durum; // engellemeyen RS485 librarysize_t fWrite (const byte what) {rs485.write (ne); } int fAvailable () {return rs485.available (); } int fRead () {lastCommsTime = micros (); rs485.read () döndür; } // RS485 kütüphanesi örneğiRS485 myChannel (fRead, fAvailable, fWrite, 20); // EEPROMbyte myAddress'den; // numberOfDevices olarak kim olduğumuzu; // veriyolundaki maksimum aygıtlar // JKISS32static unsigned long için ilk tohum x = 123456789, y = 234567891, z = 345678912, w = 456789123, c = 0; // Simple Random Number Generatorunsigned long JKISS32 () {long t; y ^ = y << 5; y ^ = y >> 7; y ^ = y << 22; t = z + w + c; z = w; c = t < 0; w = t & 2147483647; x + = 1411392427; dönüş x + y + w;
} // JKISS32'nin sonu void Seed_JKISS32 (const unsigned long newseed) {if (newseed! = 0) {x = 123456789; y = yeni tohum; z = 345678912; w = 456789123; c = 0; }} // Seed_JKISS32'nin sonu void setup () {// hata ayıklama, Serial.begin (115200) yazdırır; // diğer cihazlarla konuşmak için yazılım serisi rs485.begin (BAUD_RATE); // RS485 kitaplığını başlatın myChannel.begin (); // hata ayıklama yazdırır Serial.println (); Serial.println (F ("Başlıyor")); myAddress = EEPROM.read (0); Serial.print (F ("Adresim")); Serial.println (int (myAddress)); numberOfDevices = EEPROM.read (1); Serial.print (F ("Azami adres")); Serial.println (int (numberOfDevices)); if (myAddress > = numberOfDevices) Serial.print (F ("** UYARI ** - cihaz numarası aralık dışı, algılanmayacak.")); Serial.print (F ("Paket uzunluğu =")); Seri.baskı (PACKET_LENGTH); Serial.println (F ("bayt")); Serial.print (F ("Paket süresi =")); Serial.print (PACKET_TIME); Serial.println (F ("mikrosaniye")); // hiçbir şeyin yanıt vermediğini ne kadar süre varsayacağını hesapla noMessagesTimeout = (PACKET_TIME + TIME_BETWEEN_MESSAGES) * numberOfDevices * 2; Serial.print (F ("Mesaj yokken zaman aşımı =")); Serial.print (noMessagesTimeout); Serial.println (F ("mikrosaniye")); // çeşitli pinler kurun pinMode (XMIT_ENABLE_PIN, OUTPUT); // demo eylem pinleri pinMode (SWITCH_PIN, INPUT_PULLUP); pinMode (LED_PIN, OUTPUT); // pinlerde hata ayıklama pinMode (OK_PIN, OUTPUT); pinMode (TIMEOUT_PIN, OUTPUT); pinMode (SEND_PIN, OUTPUT); pinMode (SEARCHING_PIN, OUTPUT); pinMode (ERROR_PIN, OUTPUT); // PRNG Seed_JKISS32'yi (myAddress + 1000) tohumlayın; durum = STATE_NO_DEVICES; nextAddress = 0; randomTime = JKISS32 ()% 500000; // mikrosaniye} // kurulumun sonu // bir sonraki beklenen adresi ayarlayın, maximumvoid setNextAddress (sabit bayt akımı) {nextAddress = current; eğer (nextAddress > = numberOfDevices)
nextAddress = 0; } // setNextAddress'in sonu // Gelen bir mesajı işlemek için burada processMessage () {// kendimizden bir mesaj alamayız // eğer (message.address == myAddress) {digitalWrite ( ERROR_PIN, HIGH); while (true) {} // vazgeç} // digitalWrite adresimizi alamıyoruz (OK_PIN, HIGH); // Kimden geldiğine ve içindeki verilere bağlı olarak gelen mesajı işleyin // LED'imizi önceki cihazın anahtarıyla sırayla eşleştirin (mesaj.adresi == (myAddress - 1)) digitalWrite (LED_PIN, mesaj . anahtarlar [0]); digitalWrite (OK_PIN, DÜŞÜK); } // processMessage'ın sonu // Burada kendi mesajımızı göndermek için void sendMessage () {digitalWrite (SEND_PIN, HIGH); memset (&message, 0, sizeof mesaj); message.address = myAddress; // diğer bilgileri buraya doldurun (örn. anahtar konumları, analog okumalar, vb.) message.switches [0] = digitalRead (SWITCH_PIN); // şimdi ona digitalWrite (XMIT_ENABLE_PIN, HIGH) gönderin; // myChannel.sendMsg ((bayt *) &message, sizeof message) göndermeyi etkinleştir; digitalWrite (XMIT_ENABLE_PIN, DÜŞÜK); // setNextAddress (myAddress + 1) göndermeyi devre dışı bırakın; digitalWrite (SEND_PIN, DÜŞÜK); lastCommsTime = micros (); // kendi gönderimizi randomTime etkinliği olarak sayıyoruz = JKISS32 ()% 500000; // mikrosaniye} // sendMessage void döngüsünün sonu () {// gelen mesaj? if (myChannel.update ()) {memset (&message, 0, sizeof mesaj); int len = myChannel.getLength (); eğer (len > sizeof mesaj) len = sizeof mesaj; memcpy (&message, myChannel.getData (), len); lastMessageTime = micros (); setNextAddress (mesaj.adresi + 1); processMessage (); durum = STATE_RECENT_RESPONSE; } // mesajın sonu tamamen alındı // mesajlar arasında çok uzun bir boşluk olup olmadığını belirtir if (micros () - lastMessageTime > noMessagesTimeout)
durum = STATE_NO_DEVICES; else if (micros () - lastCommsTime > PACKET_TIME) state = STATE_TIMED_OUT; switch (state) {// uzun süredir hiçbir şey duyulmadı mı? STATE_NO_DEVICES durumunu devralacağız: if (micros () - lastCommsTime > = (noMessagesTimeout + randomTime)) {Serial.println (F ("Cihaz yok.")); digitalWrite (SEARCHING_PIN, HIGH); mesaj gönder (); digitalWrite (SEARCHING_PIN, DÜŞÜK); } mola; // yakın zamanda başka bir cihazdan haber aldık // sıra bizde ise yanıt vakası STATE_RECENT_RESPONSE: // küçük bir boşluğa izin veriyoruz ve sıra bizde ise mesajımızı gönderiyoruz if (micros () - lastCommsTime > = TIME_BETWEEN_MESSAGES && myAddress == nextAddress) sendMessage (); kırmak; // bir aygıt yuva zamanında yanıt vermedi, bir sonraki duruma geç STATE_TIMED_OUT: digitalWrite (TIMEOUT_PIN, HIGH); setNextAddress (nextAddress + 1); lastCommsTime + = PACKET_TIME; digitalWrite (TIMEOUT_PIN, LOW); durum = STATE_RECENT_RESPONSE; // eksik yanıt aralığını bulduğumuzu varsayalım; } // açma durumunun sonu} // döngünün sonu
Şu anda olduğu gibi, A0 üzerindeki bir anahtarı kapatırsanız (toprağa kısa devre) bir LED'i (pim 13) sırayla bir sonraki en yüksek cihazda. Bu, cihazların birbirleriyle konuştuğunu kanıtlıyor. Elbette pratikte, yayınlanmakta olan pakette daha karmaşık bir şeye sahip olacaksınız.
Testlerde LED'in anında açılıp kapandığını gördüm.
Tüm cihazların bağlantısı kesildiğinde ilk bağlanan cihaz diğer cihazları "arar". Eğer benim yaptığım gibi bağlı hata ayıklama LED'leri varsa, paketini rastgele değişen aralıklarla yayınlarken "arama" LED'inin rastgele aralıklarla yandığını görebilirsiniz. İkinci bir bağlantı kurduğunuzda, yerleşirler ve sadece bilgi alışverişinde bulunurlar. Aynı anda üç bağlantılı test yaptım.
Muhtemelen HardwareSerial ile daha güvenilir olurdu - Hata ayıklamaya yardımcı olması için SoftwareSerial'ı kullandım. Birkaç küçük değişiklik bunu başarabilir.
Değiştirilmiş şematik
Ekran -kodun eylem halindeki fotoğrafları
Bu resimler, kodun çalıştığını gösterir. İlk olarak, yalnızca bir cihaz bağlıyken:
Oradaki darbelerden, cihazın verilerini rastgele değişen oranlarda yayınladığını görebilirsiniz. aynı anda çalışan başka bir cihazla çatışmaya devam etmekten kaçınmak için aralıklarla.
Artık bloklar görüyoruz Ortada aşağı yukarı aynı boyutta boşluklar bulunan iki cihazdan gelen veriler. Dört cihaz için yapılandırdım, ancak yalnızca ikisi mevcut, bu nedenle iki veri bloğu ve iki boşluk görüyoruz.
Artık çevrimiçi üç cihazla, eksik cihaz atlanırken üç veri bloğu ve bir boşluk görüyoruz.
Rakamları kontrol ediyorsanız, bunlar baud hızı bir test olarak iki katına çıktı ve 19200 baud oldu.
Kablo çalıştırma testi
Uygun bir donanım testi için cihazları şirket içi UTP'ime bağladım kablolama. Çeşitli odalardan merkezi bir prize giden cat-5 kablom var. Evin bir ucundan diğerine giderken (makul bir uzunlukta koşu) hala iyi çalışıyor. Başlangıç için Arduino ile duvar prizi arasında 5 m kablo bulunmaktadır. Artı diğer ucunda 5 m kablo daha. Ardından, odalardan anahtar odasına yaklaşık 2 x 15 m'lik geçişler var ve bunların içinde bunları birbirine bağlamak için kısa bir köprü kablosu var.
Bu, hala 19200 baud'da çalışacak şekilde programlanmış anakartlarla oldu.