İletişim Protokolü En İyi Uygulamaları ve Kalıpları


19

İki arduino arasında kullanılacak bir seri protokol tasarladığımda, bir tekerleği yeniden icat ediyormuşum gibi hissediyorum. İnsanların takip ettiği en iyi uygulamalar veya kalıplar olup olmadığını merak ediyorum. Bu soru gerçek kod hakkında daha az, mesajların formatı hakkında daha fazladır.

Örneğin, bir arduino'ya yanıp sönmesini söylemek istersem ilk LED'i 3 kez gönderebilirim:

^L1,F3\n
  • '^': yeni bir komut başlatır
  • 'L': Komutu tanımlar, (L: bu komutu bir LED'e hedefle)
  • '1': İlk LED'i hedefleyin
  • ',': Komut satırı ayırıcı, izlenecek bu iletideki yeni değer
  • 'F': Flash alt komutu
  • '3': 3 kez (LED'i üç kez yanıp söner)
  • '\ n': Komutu sonlandır

Düşünceler? Genellikle yeni bir seri protokol yazmaya nasıl yaklaşıyorsunuz? Arduino 1'den arduino 2'ye bir sorgu göndermek ve sonra bir yanıt almak istersem ne olur?

Yanıtlar:


13

İstediğiniz işlevselliğe ve ne kadar hata kontrolüne ihtiyacınız olduğuna bağlı olarak bir seri protokol yazmanın birçok yolu vardır.

Noktadan noktaya protokollerde sık karşılaştığınız bazı şeyler şunlardır:

Mesajın sonu

En basit ASCII protokolleri sadece mesaj sonu dizisine sahiptir, genellikle \rveya \nenter tuşuna basıldığında yazdırılan şey budur. İkili protokoller 0x03veya başka bir ortak bayt kullanabilir.

Mesajın başlangıcı

İletinin sonuna gelmeyle ilgili sorun, iletinizi gönderdiğinizde başka hangi baytların zaten alındığını bilmemenizdir. Bu baytlar daha sonra mesaja önek olarak eklenir ve yanlış yorumlanmasına neden olur. Örneğin, Arduino uykudan yeni uyandıysa, seri tamponda biraz çöp olabilir. Bu sorunu çözmek için bir mesaj dizisi başlangıcı vardır. Örneğinizde, ^ikili protokollerde sıklıkla0x02

Hata kontrolü

Mesaj bozulabiliyorsa, hata kontrolü yapmamız gerekir. Bu bir sağlama toplamı veya bir CRC hatası veya başka bir şey olabilir.

Kaçış Karakterleri

Sağlama toplamı, 'mesaj başlangıcı' veya 'mesaj sonu' baytı gibi bir kontrol karakterine eklenebilir veya mesaj bir kontrol karakterine eşit bir değer içerebilir. Çözüm bir kaçış karakteri getirmektir. Çıkış karakteri, gerçek kontrol karakterinin bulunmaması için değiştirilmiş bir kontrol karakterinin önüne yerleştirilir . Bir başlangıç ​​karakteri 0x02 ise, 0x10 çıkış karakterini kullanarak mesajdaki 0x02 değerini 0x10 0x12 bayt çifti olarak gönderebiliriz (bayt XOR kontrol karakteri)

Paket numarası

Bir mesaj bozulursa, bir yoklama veya yeniden deneme mesajı ile yeniden gönderme talebinde bulunabiliriz, ancak birden fazla mesaj gönderildiyse yalnızca en son mesaj yeniden gönderilebilir. Bunun yerine pakete belirli sayıda mesajdan sonra dönen bir sayı verilebilir. Örneğin, bu sayı 16 ise, verici cihaz gönderilen son 16 mesajı saklayabilir ve herhangi bir bozulma durumunda alıcı cihaz paket numarasını kullanarak yeniden gönderme talebinde bulunabilir.

uzunluk

Genellikle ikili protokollerde, alıcı cihaza mesajda kaç karakter olduğunu söyleyen bir uzunluk baytı görürsünüz. Bu, doğru sayıda bayt alınmamış gibi başka bir hata denetimi düzeyi ekler, ardından bir hata oluştu.

Arduino'ya özgü

Arduino için bir protokol hazırlarken, ilk düşünce iletişim kanalının ne kadar güvenilir olduğudur. Çoğu kablosuz ortam, XBee, WiFi vb. Üzerinden gönderiyorsanız, zaten hata kontrolü ve yeniden denemelerde yerleşiktir ve bu nedenle bunları protokolünüze koymanın bir anlamı yoktur. Birkaç kilometre boyunca RS422 üzerinden gönderiyorsanız, gerekli olacaktır. İçerdiğim şeyler mesajın başlangıcı ve mesaj karakterlerinin sonu. Benim tipik uygulama şöyle bir şey:

>messageType,data1,data2,…,dataN\n

Veri parçalarının virgülle sınırlandırılması kolay ayrıştırmaya olanak tanır ve mesaj ASCII kullanılarak gönderilir. ASCII protokolleri mükemmeldir çünkü seri monitöre mesaj yazabilirsiniz.

İkili bir protokol istiyorsanız, belki mesaj boyutlarını kısaltmak istiyorsanız, bir veri baytı bir kontrol baytı ile aynı olabiliyorsa kaçmayı uygulamanız gerekir. İkili kontrol karakterleri, tam hata kontrolü ve yeniden deneme spektrumunun istendiği sistemler için daha iyidir. İstenirse taşıma kapasitesi hala ASCII olabilir.


İleti kodunun gerçek başlangıcından önceki çöpün bir ileti denetim kodu başlangıcı içermesi mümkün değil mi? Bununla nasıl başa çıkardınız?
CMCDragonkai

@CMCDragonkai Evet, bu özellikle tek bayt kontrol kodları için bir olasılıktır. Ancak, bir iletiyi ayrıştırma yoluyla bir başlangıç ​​denetim kodu ile karşılaşırsanız, ileti atılır ve ayrıştırma yeniden başlar.
geometrikal

9

Seri protokoller konusunda resmi bir uzmanlığım yok, ancak bunları birkaç kez kullandım ve bu şemaya az çok yerleştim:

(Paket başlığı) (ID bayt) (veri) (fletcher16 checksum) (Paket Altbilgisi)

Genellikle üstbilgi 2 bayt ve Altbilgi 1 bayt yapıyorum. Ayrıştırıcım yeni bir paket üstbilgisi gördüğünde her şeyi dökecek ve bir altbilgi görürse iletiyi ayrıştırmaya çalışacaktır. Sağlama toplamı başarısız olursa, iletiyi kaldırmaz, ancak altbilgi karakteri bulunana ve bir sağlama toplamı başarılı olana kadar eklemeye devam eder. Bu şekilde, çarpışmalar mesajı bozmadığından altbilginin yalnızca bir bayt olması gerekir.

Kimlik, bazen veri bölümünün uzunluğunun alt kırıntı (4 bit) olması nedeniyle isteğe bağlıdır. İkinci bir uzunluk biti kullanılabilir, ancak normalde uzunluğun doğru ayrıştırılması gerekmediği için rahatsız etmiyorum, bu nedenle belirli bir kimlik için doğru uzunluğu görmek mesajın doğru olduğunu daha doğrulamaktır.

Fletcher16 sağlama toplamı, CRC ile hemen hemen aynı kaliteye sahip 2 baytlık bir sağlama toplamıdır, ancak uygulanması çok daha kolaydır. bazı detaylar burada . Kod şu kadar basit olabilir:

for(int i=0; i < bufSize; i++ ){
   sum1 = (sum1 + buffer[i]) % 255;
   sum2 = (sum2 + sum1) % 255;
}
uint16_t checksum = (((uint16_t)sum1)<<8) | sum2;

Ayrıca kritik mesajlar için bir çağrı ve yanıt sistemi kullandım, PC'nin her 500 ms'de bir mesaj göndereceği ve veri olarak orijinal mesajın toplamı (orijinal sağlama toplamı dahil) bir sağlama toplamı içeren bir Tamam mesajı alana kadar.

Bu şema, elbette, örneğiniz gibi bir terminale yazılmak için uygun değildir. Protokolünüz ASCII ile sınırlı olduğu için oldukça iyi görünüyor ve eminim ki doğrudan okumak ve mesaj göndermek istediğiniz hızlı bir proje için daha kolay. Daha büyük projeler için, ikili protokolün yoğunluğuna ve bir sağlama toplamının güvenliğine sahip olmak güzeldir.


"[Ayrıştırıcı yeni bir paket üstbilgisi gördüğünde her şeyi dökümü" beri Bu şans eseri başlığın veri içinde karşılaşılırsa sorun yaratmaz mı acaba?
insanlıkANDpeace

@ insanlık En kolay ve deneyimime göre çözüm, bir sonraki başlık gelir gelmez kötü bir paket bırakmaktır. 16 bitlik bir başlığı sorunsuz olarak kullanıyorum, ancak kesinliğin daha önemli olması daha uzun olabilir Bant genişliği.
BrettAM

Başlık olarak hak ettiğiniz şey, Magic 16bit kombinasyonudur. yani 010101001 10101010, değil mi? Vurmak sadece 1/256 * 256 değişiklik olduğunu kabul ediyorum, ama aynı zamanda bu 16bit'i verilerinizde kullanmayı da devre dışı bırakır, aksi takdirde bir başlık olarak yanlış yorumlanır ve mesajı atarsınız, değil mi?
humanityANDpeace

@humanityANDpeace Bunun bir yıl sonra olduğunu biliyorum, ama bir kaçış dizisi eklemeniz gerekiyor. Göndermeden önce, sunucu yükü herhangi bir özel bayt için kontrol eder, sonra bunları başka bir özel baytla kaçar. Müşteri tarafında, orijinal yükü tekrar bir araya getirmelisiniz. Bu, sabit uzunlukta paketler gönderemeyeceğiniz anlamına gelir ve uygulamayı zorlaştırır. Bunların hepsini ele alan birçok seri protokol standardı vardır. İşte konuyla ilgili çok iyi bir okuma .
RubberDuck

1

Standartlara giriyorsanız, ASN.1 / BER TLV kodlamasına bir göz atabilirsiniz. ASN.1, özellikle iletişim için yapılmış veri yapılarını tanımlamak için kullanılan bir dildir. BER, ASN.1 kullanılarak yapılandırılan verileri kodlamak için bir TLV yöntemidir. Sorun, ASN.1 kodlamasının en iyi ihtimalle zor olabilmesidir. Tam teşekküllü bir ASN.1 derleyicisi oluşturmak kendi başına bir projedir (ve özellikle de zor bir projedir, aylar düşünün ).


Sadece TLV yapısını korumak daha iyidir. TLV temel olarak üç öğeden oluşur: bir etiket, bir uzunluk ve bir değer alanı. Etiketi, verilerin türünü (metin dizesi, sekizli dize, tamsayı vb.) Ve değerin uzunluğunu tanımlar .

BER'de T, değerin TLV yapılarının kendisinin (yapılandırılmış bir düğüm) veya doğrudan bir değerin (ilkel bir düğüm) olup olmadığını da belirtir. Bu şekilde, XML'de olduğu gibi (ancak XML ek yükü olmadan) ikili bir ağaç oluşturabilirsiniz.

Misal:

TT LL VV
02 01 FF

021 (uzunluk 01) ve -1 (değer FF) değerinin uzunluğuna sahip bir tamsayıdır (etiket ). ASN.1 / BER'de tamsayılar büyük endian sayılarını işaretler, ancak elbette kendi biçiminizi kullanabilirsiniz.

TT LL (TT LL VV, TT LL VV VV)
30 07  02 01 FF  02 02 00 FF

içeren uzunluk 7 ile bir sekans (bir liste) olan iki tamsayı değeri, 255 iki tamsayı kodlamaları birlikte dizisinin değeri oluşturan ile değerine sahip olan 1 ve bir.

Bunu basitçe çevrimiçi bir kod çözücüye de atabilirsiniz, bu hoş değil mi?


BER'de veri akışına izin veren belirsiz uzunluk da kullanabilirsiniz. Bu durumda ağacınızı doğru şekilde ayrıştırmanız gerekir. Bunu ileri bir konu olarak görüyorum, önce genişlik ve önce ayrıştırma derinliğini bilmeniz gerekir.


Bir TLV şeması kullanmak temel olarak her türlü veri yapısını düşünmenizi ve kodlamanızı sağlar. ASN.1 bundan çok daha ileri gider ve size benzersiz tanımlayıcılar (OID'ler), seçimler (C-sendikaları gibi), diğer ASN.1 yapılarını vb. İçerir. Muhtemelen en iyi bilinen ASN.1 tanımlı yapılar bugün tarayıcınız tarafından kullanılan sertifikalardır.


0

Değilse, temelleri ele aldınız. Komutlarınız hem insanlar hem de makineler tarafından oluşturulabilir ve okunabilir, bu da büyük bir artı. Özellikle kanalınız uzun kablo veya radyo bağlantısı içeriyorsa, hatalı biçimlendirilmiş bir komutu veya taşıma sırasında hasar görmüş bir komutu tespit etmek için bir sağlama toplamı ekleyebilirsiniz.

Endüstriyel güce ihtiyacınız varsa (cihazınız kimsenin incinmesine veya ölmesine izin vermemelidir; yüksek veri hızlarına, hata kurtarma, eksik paket algılama vb. Gerekir) sonra bazı standart protokollere ve tasarım uygulamalarına bakın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.