Giao thức truyền thông Thực tiễn và mô hình tốt nhất


19

Mỗi lần tôi thiết kế một giao thức nối tiếp được sử dụng giữa hai arduinos, tôi cảm thấy hơi giống như tôi đang phát minh lại một bánh xe. Tôi tự hỏi nếu có bất kỳ thực hành hay mô hình tốt nhất mà mọi người làm theo. Câu hỏi này ít về mã thực tế, nhưng nhiều hơn về định dạng của các tin nhắn.

Ví dụ, nếu tôi muốn bảo một arduino phát sáng thì đó là đèn LED đầu tiên 3 lần tôi có thể gửi:

^L1,F3\n
  • '^': bắt đầu một lệnh mới
  • 'L': Xác định lệnh, (L: nhắm mục tiêu lệnh này vào đèn LED)
  • '1': Nhắm mục tiêu đèn LED đầu tiên
  • ',': Dấu tách dòng lệnh, giá trị mới trong thông báo này để theo dõi
  • 'F': Lệnh phụ flash
  • '3': 3 lần (Đèn flash LED ba lần)
  • '\ n': Kết thúc lệnh

Suy nghĩ? Làm thế nào để bạn thường tiếp cận việc viết một giao thức nối tiếp mới? Nếu tôi muốn gửi một truy vấn từ arduino 1 đến arduino 2 thì sao?

Câu trả lời:


13

Có rất nhiều cách để viết một giao thức nối tiếp tùy thuộc vào chức năng bạn có thể muốn và mức độ kiểm tra lỗi bạn cần.

Một số điều phổ biến bạn thấy trong các giao thức điểm tới điểm là:

Kết thúc tin nhắn

Các giao thức ASCII đơn giản nhất chỉ có một kết thúc chuỗi ký tự thông báo, thường \rhoặc \nvì đây là những gì được in khi nhấn phím enter. Các giao thức nhị phân có thể sử dụng 0x03hoặc một số byte phổ biến khác.

Bắt đầu tin nhắn

Vấn đề với việc chỉ có phần cuối của tin nhắn là bạn không biết những byte nào đã được nhận khi bạn gửi tin nhắn của mình. Các byte này sau đó sẽ được thêm tiền tố vào thông báo và khiến nó bị hiểu sai. Ví dụ, nếu Arduino vừa ngủ dậy, có thể có một số rác trong bộ đệm nối tiếp. Để giải quyết vấn đề này, bạn phải bắt đầu trình tự tin nhắn. Trong ví dụ của bạn ^, trong các giao thức nhị phân thường0x02

Kiểm tra lỗi

Nếu tin nhắn có thể bị hỏng, chúng tôi cần kiểm tra lỗi. Đây có thể là tổng kiểm tra hoặc lỗi CRC hoặc một cái gì đó khác.

Nhân vật thoát

Có thể là tổng kiểm tra thêm vào một ký tự điều khiển, chẳng hạn như 'bắt đầu tin nhắn' hoặc 'cuối tin nhắn' hoặc tin nhắn chứa một giá trị bằng với một ký tự điều khiển. Giải pháp là giới thiệu một nhân vật thoát hiểm. Ký tự thoát được đặt trước ký tự điều khiển đã sửa đổi để không có ký tự điều khiển thực tế. Ví dụ: nếu một ký tự bắt đầu là 0x02, sử dụng ký tự thoát 0x10, chúng ta có thể gửi giá trị 0x02 trong tin nhắn dưới dạng cặp byte 0x10 0x12 (ký tự điều khiển XOR byte)

Số gói

Nếu một tin nhắn bị hỏng, chúng tôi có thể yêu cầu gửi lại bằng tin nhắn hoặc thử lại, nhưng nếu nhiều tin nhắn đã được gửi thì chỉ có thể gửi lại tin nhắn mới nhất. Thay vào đó, gói có thể được cung cấp một số cuộn qua sau một số lượng tin nhắn nhất định. Ví dụ: nếu số này là 16, thiết bị truyền có thể lưu 16 tin nhắn cuối cùng gửi và nếu có bị hỏng, thiết bị nhận có thể yêu cầu gửi lại bằng cách sử dụng số gói.

Chiều dài

Thông thường trong các giao thức nhị phân, bạn thấy một byte độ dài cho biết thiết bị nhận có bao nhiêu ký tự trong tin nhắn. Điều này thêm một mức kiểm tra lỗi khác như thể không nhận được số byte chính xác thì có lỗi.

Arduino cụ thể

Khi đưa ra một giao thức cho Arduino, việc xem xét đầu tiên là độ tin cậy của kênh truyền thông. Nếu bạn đang gửi qua hầu hết các phương tiện không dây, XBee, WiFi, v.v., đã có sẵn trong kiểm tra lỗi và thử lại và do đó không có điểm nào đưa các giao thức này vào giao thức của bạn. Nếu bạn đang gửi qua RS422 trong một vài km thì điều đó là cần thiết. Những điều tôi sẽ bao gồm là bắt đầu tin nhắn và kết thúc các ký tự tin nhắn, như bạn có. Triển khai điển hình của tôi trông giống như:

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

Phân định các phần dữ liệu bằng dấu phẩy cho phép phân tích cú pháp dễ dàng và tin nhắn được gửi bằng ASCII. Các giao thức ASCII rất tuyệt vì bạn có thể nhập tin nhắn vào màn hình nối tiếp.

Nếu bạn muốn một giao thức nhị phân, có thể rút ngắn kích thước thông báo, bạn sẽ phải thực hiện thoát nếu một byte dữ liệu có thể giống như một byte điều khiển. Các ký tự điều khiển nhị phân tốt hơn cho các hệ thống trong đó toàn bộ kiểm tra lỗi và thử lại là mong muốn. Tải trọng vẫn có thể là ASCII nếu muốn.


Không phải rác thải trước khi bắt đầu thực sự của mã tin nhắn có thể chứa một sự khởi đầu của mã kiểm soát tin nhắn sao? Làm thế nào bạn sẽ đối phó với điều này?
CMCDragonkai

@CMCDragonkai Có, đây là một khả năng, đặc biệt đối với các mã điều khiển byte đơn. Tuy nhiên, nếu bạn gặp mã kiểm soát bắt đầu giữa chừng khi phân tích cú pháp một tin nhắn, tin nhắn sẽ bị loại bỏ và phân tích lại khởi động lại.
geometrikal

9

Tôi không có bất kỳ kiến ​​thức chuyên môn chính thức nào về các giao thức nối tiếp, nhưng tôi đã sử dụng chúng khá nhiều lần và ít nhiều giải quyết theo sơ đồ này:

(Tiêu đề gói) (byte ID) (dữ liệu) (tổng kiểm tra fletcher16) (Chân trang gói)

Tôi thường tạo tiêu đề 2 byte và Footer 1 byte. Trình phân tích cú pháp của tôi sẽ kết xuất mọi thứ khi nó nhìn thấy một tiêu đề gói mới và cố gắng phân tích thông điệp nếu nó nhìn thấy một chân trang. Nếu tổng kiểm tra thất bại, nó sẽ không bỏ qua tin nhắn, nhưng tiếp tục thêm cho đến khi tìm thấy ký tự chân trang và tổng kiểm tra thành công. Theo cách đó, chân trang chỉ cần là một byte vì các va chạm không làm gián đoạn thông báo.

ID là tùy ý, đôi khi với độ dài của phần dữ liệu là nibble dưới cùng (4 bit). Một bit độ dài thứ hai có thể được sử dụng nhưng tôi thường không bận tâm vì độ dài không cần phải phân tích chính xác, do đó, việc nhìn đúng độ dài thông qua cho một ID nhất định chỉ xác nhận thêm rằng thông báo là chính xác.

Tổng kiểm tra fletcher16 là tổng kiểm tra 2 byte với chất lượng gần như tương đương với CRC nhưng dễ thực hiện hơn nhiều. một số chi tiết ở đây . Mã có thể đơn giản như thế này:

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

Tôi cũng đã sử dụng hệ thống gọi và trả lời cho các tin nhắn quan trọng, trong đó PC sẽ gửi tin nhắn sau mỗi 500ms hoặc lâu hơn cho đến khi nhận được tin nhắn OK với tổng kiểm tra toàn bộ tin nhắn gốc dưới dạng dữ liệu (bao gồm cả tổng kiểm tra gốc).

Tất nhiên, lược đồ này không phù hợp để nhập vào thiết bị đầu cuối như ví dụ của bạn. Giao thức của bạn có vẻ khá tốt vì bị giới hạn ở ASCII và tôi chắc chắn sẽ dễ dàng hơn cho một dự án nhanh mà bạn muốn có thể trực tiếp đọc và gửi tin nhắn. Đối với các dự án lớn hơn, thật tuyệt khi có mật độ của giao thức nhị phân và bảo mật của tổng kiểm tra.


Vì trình phân tích cú pháp "[your] sẽ kết xuất mọi thứ khi nó nhìn thấy một tiêu đề gói mới" Tôi tự hỏi liệu điều này không tạo ra vấn đề nếu tình cờ tiêu đề gặp phải trong dữ liệu?
nhân

@humanityANDpeace Lý do bỏ nó là khi một gói bị cắt nó sẽ không bao giờ phân tích chính xác, vậy khi nào bạn quyết định rác của nó và tiếp tục? Cách dễ nhất và theo kinh nghiệm của tôi đủ tốt, giải pháp là bỏ một gói xấu ngay khi tiêu đề tiếp theo xuất hiện. Tôi đã sử dụng tiêu đề 16 bit mà không gặp vấn đề gì, nhưng bạn có thể làm cho nó dài hơn nếu sự chắc chắn quan trọng hơn băng thông.
BrettAM

Vì vậy, những gì bạn tham khảo như là một tiêu đề là một phần của sự kết hợp Magic 16bit. tức là 010101001 10101010, phải không? Tôi đồng ý rằng nó chỉ thay đổi 1/ 256 * 256, nhưng nó cũng vô hiệu hóa việc sử dụng 16 bit này trong dữ liệu của bạn, nếu không nó bị hiểu sai là một tiêu đề và bạn loại bỏ thông báo, phải không?
nhân

@humanityANDpeace Tôi biết đó là một năm sau, nhưng bạn cần giới thiệu một chuỗi thoát. Trước khi gửi, máy chủ kiểm tra tải trọng cho bất kỳ byte đặc biệt nào, sau đó thoát chúng bằng một byte đặc biệt khác. Phía khách hàng, bạn phải đặt tải trọng ban đầu trở lại với nhau. Điều này không có nghĩa là bạn không thể gửi các gói có độ dài cố định và không làm phức tạp việc thực hiện. Có nhiều tiêu chuẩn giao thức nối tiếp để lựa chọn mà tất cả đều giải quyết điều này. Đây là một đọc rất tốt về chủ đề này .
RubberDuck

1

Nếu bạn đang ở trong các tiêu chuẩn, thì bạn có thể xem mã hóa ASN.1 / BER TLV. ASN.1 là ngôn ngữ được sử dụng để mô tả các cấu trúc dữ liệu, được tạo riêng cho giao tiếp. BER là phương pháp TLV mã hóa dữ liệu có cấu trúc bằng ASN.1. Vấn đề là mã hóa ASN.1 có thể khó khăn nhất. Tạo một trình biên dịch ASN.1 chính thức đầy đủ là một dự án (và một dự án đặc biệt khó khăn ở đó, nghĩ theo tháng ).


Có lẽ tốt hơn là chỉ giữ cấu trúc TLV. TLV về cơ bản bao gồm ba yếu tố: thẻ, chiều dài và trường giá trị. Thẻ xác định loại dữ liệu (chuỗi văn bản, chuỗi octet, số nguyên, v.v.) và độ dài của giá trị .

Trong BER, T cũng biểu thị nếu giá trị là một tập hợp các cấu trúc TLV (một nút được xây dựng) hoặc trực tiếp một giá trị (một nút nguyên thủy). Bằng cách đó, bạn có thể tạo một cây ở dạng nhị phân, giống như XML (nhưng không có chi phí XML).

Thí dụ:

TT LL VV
02 01 FF

là một số nguyên (thẻ 02) có độ dài của giá trị 1 (chiều dài 01) và giá trị -1 (giá trị FF). Trong ASN.1 / BER, các số nguyên được ký số lớn về cuối, nhưng tất nhiên bạn có thể sử dụng định dạng của riêng mình.

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

là một chuỗi (một danh sách) có độ dài 7 chứa hai số nguyên, một có giá trị -1 và một có giá trị 255. Hai mã hóa số nguyên với nhau tạo thành giá trị của chuỗi.

Bạn chỉ đơn giản có thể ném cái này vào một bộ giải mã trực tuyến, không phải là tốt sao?


Bạn cũng có thể sử dụng độ dài không xác định trong BER sẽ cho phép bạn truyền dữ liệu. Trong trường hợp đó, bạn cần phải phân tích chính xác cây của bạn. Tôi coi đó là một chủ đề nâng cao, bạn cần biết về phân tích đầu tiên và phân tích sâu trước, cho một.


Sử dụng sơ đồ TLV về cơ bản cho phép bạn nghĩ về bất kỳ loại cấu trúc dữ liệu nào và mã hóa nó. ASN.1 còn đi xa hơn thế, mang đến cho bạn các số nhận dạng duy nhất (OID), các lựa chọn (rất giống với các hiệp hội C), bao gồm các cấu trúc ASN.1 khác, v.v. nhưng điều đó có thể quá mức cho dự án của bạn. Có lẽ các cấu trúc được xác định ASN.1 nổi tiếng nhất hiện nay là các chứng chỉ được sử dụng bởi trình duyệt của bạn.


0

Nếu không, bạn đã có những điều cơ bản được bảo hiểm. Các lệnh của bạn có thể được tạo và đọc bởi cả người và máy, đây là một điểm cộng lớn. Bạn có thể thêm một tổng kiểm tra để phát hiện một lệnh hình thành mal hoặc một lệnh bị hỏng trong quá trình vận chuyển, đặc biệt nếu kênh của bạn bao gồm cáp dài hoặc liên kết vô tuyến.

Nếu bạn cần sức mạnh công nghiệp (thiết bị của bạn không được gây ra hoặc cho phép ai đó bị thương hoặc chết; bạn cần tốc độ dữ liệu cao, phục hồi lỗi, phát hiện gói bị thiếu, v.v.), sau đó tìm đến một số giao thức chuẩn và thực hành thiết kế.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.