Làm thế nào không an toàn là \ n \ r là byte dừng?


8

Trong giao tiếp UART của tôi, tôi cần biết byte bắt đầu và byte dừng của tin nhắn được gửi. Byte bắt đầu dễ dàng nhưng byte dừng, không quá nhiều. Tôi đã triển khai hai byte dừng ở cuối tin nhắn của mình, đó là \ n và \ r (10 và 13 thập phân). UART chỉ hoạt động trên các giá trị byte 0-255, vậy làm thế nào là không an toàn? Tôi có thể tưởng tượng, mặc dù xác suất thấp, thông điệp của tôi có thể chứa các giá trị "10 và 13" sau khi chúng không phải là byte dừng.

Có cách nào tốt hơn để thực hiện điều này?


7
Để gửi dữ liệu tùy ý, bạn phải sử dụng các gói hoặc nhồi byte. Trong trường hợp của bạn, xác suất của mẫu xuất hiện ở một vị trí nhất định là 1/65536. Sẽ là 1 nếu bạn có một luồng dữ liệu ngẫu nhiên đủ dài.
Oldfart

4
Bạn có thể cung cấp bối cảnh xin vui lòng. Dừng bit là một phần của giao tiếp UART nhưng dừng byte? Điều này nghe có vẻ như là một vấn đề phần mềm thuần túy và phụ thuộc vào những gì đã được người gửi và người nhận đồng ý.
Warren Hill

2
@MariusGulbrandsen nếu dữ liệu của bạn thực sự độc đoán và không đúng văn bản (nghĩ ASCII) thì chấm dứt null sẽ không hoạt động; bạn sẽ phải thực hiện một gói.
RamblinRose

4
BTW: Thực tế phổ biến đó là đặt vận chuyển trở lại trước nguồn cấp dữ liệu : "\x0D\x0A".
Adrian McCarthy

3
@AdrianMcCarthy Tôi nghĩ rằng điểm của việc đảo ngược nó là để giảm thiểu tỷ lệ cược của nó là một chuỗi hợp lệ. Điều đó nói rằng, hai kết thúc dòng Windows liên tiếp sẽ cung cấp cho bạn \r\n\r\ntrong đó có \n\rtrình tự ở giữa ...
Mike Caron

Câu trả lời:


14

Có nhiều cách khác nhau để ngăn chặn điều này:

  • Hãy chắc chắn rằng bạn không bao giờ gửi kết hợp 10/13 trong các tin nhắn thông thường của bạn (vì vậy chỉ là các byte dừng). Ví dụ: gửi 20 21 22 23 24 25:

20 21 22 23 24 25 10 13 13

  • Thoát 10 và 13 (hoặc tất cả các ký tự không phải ASCII có ký tự thoát, ví dụ: để gửi 20 21 10 13 25 26 gửi: (xem nhận xét về / tín dụng cho: DanW)

20 21 1b 10 1b 13 25 26

  • Xác định một gói khi gửi tin nhắn. Ví dụ: nếu bạn muốn gửi tin nhắn 20 21 22 23 24 25 thay vì thêm số byte cần gửi, thì gói là:

<nr_of_data_bytes> <data>

Nếu tin nhắn của bạn tối đa 256 byte gửi:

06 20 21 22 23 24 25

Vì vậy, bạn biết sau khi nhận được 6 byte dữ liệu là kết thúc; bạn không phải gửi 10 13 sau đó. Và bạn có thể gửi 10 13 trong một tin nhắn. Nếu tin nhắn của bạn có thể dài hơn, bạn có thể sử dụng 2 byte cho kích thước dữ liệu.

Cập nhật 1: Một cách khác để xác định gói

Một cách khác là gửi các lệnh có độ dài cụ thể và có thể có nhiều phương sai, ví dụ:

10 20 30 (Lệnh 10 luôn có 2 byte dữ liệu)

11 30 40 50 (Lệnh 11 luôn có 3 byte dữ liệu)

12 06 10 11 12 13 14 15 (Lệnh 12 + 1 byte cho số byte dữ liệu theo sau)

13 01 02 01 02 03 ... (Lệnh 13 + 2 byte (01 02 cho 256 + 2 = 258 byte dữ liệu theo sau)

14 80 90 10 13 (Lệnh 14 được theo sau bởi chuỗi ASCII kết thúc bằng 10 13)

Cập nhật 2: Mất kết nối / mất byte

Tất cả các cách trên chỉ hoạt động khi dòng UART đang gửi byte chính xác. Nếu bạn muốn sử dụng các cách gửi đáng tin cậy hơn, cũng có nhiều khả năng. Dưới đây là một vài:

  1. Gửi một tổng kiểm tra trong gói (kiểm tra google cho CRC: Kiểm tra dự phòng chu kỳ). Nếu CRC ổn, người nhận biết tin nhắn đã được gửi ok (với xác suất cao).
  2. Nếu bạn cần gửi lại tin nhắn, hơn cơ chế xác nhận (ACK / trả lời) cần được sử dụng (ví dụ: người gửi gửi một cái gì đó, người nhận nhận dữ liệu bị hỏng, gửi NACK (không được xác nhận), người gửi có thể gửi lại.
  3. Hết thời gian: Trong trường hợp người nhận không nhận được ACK hoặc NACK kịp thời, một tin nhắn cần phải được gửi lại.

Lưu ý rằng tất cả các cơ chế trên có thể đơn giản hoặc phức tạp như bạn muốn (hoặc cần). Trong trường hợp gửi lại tin nhắn, cũng cần một cơ chế xác định tin nhắn (ví dụ: thêm số thứ tự vào gói).


1
"Hãy chắc chắn rằng bạn không bao giờ gửi kết hợp 10/13 trong các tin nhắn thông thường của bạn (vì vậy chỉ là các byte dừng)." - bạn đã không biết làm thế nào để gửi dữ liệu mà không bao gồm một sự kết hợp 10/13 - bạn cần phải thoát khỏi nó. Vì vậy, "20 10 13 23 10 13" có thể được gửi dưới dạng "20 1b 10 1b 13 23" với 1b là ký tự thoát của bạn.
Dan W

1
Lưu ý rằng việc sử dụng trường độ dài như đề xuất, bạn sẽ gặp rắc rối khi liên kết nối tiếp của bạn xấu và mất một byte đơn. Mọi thứ sẽ không đồng bộ.
Jonas Schäfer

@DanW Nếu bạn sử dụng một hoặc 2 byte đầu tiên làm số byte dữ liệu, sẽ không có vấn đề gì nếu 10 hoặc 13 là một phần của những dữ liệu đó ... Vì vậy, 20 10 13 23 10 13 có thể được gửi dưới dạng 06 20 10 13 23 10 13 trong đó 06 là số byte dữ liệu theo sau.
Michel Keijzers

@MichelKeijzers - vâng, nhưng đó là giải pháp thứ hai bạn đề cập. Giải pháp đầu tiên của bạn thiếu một lời giải thích về các chuỗi thoát để ngăn các byte dừng được truyền đi.
Dan W

Cả hai phương pháp đều hoạt động và thường được sử dụng, nhưng chúng có những ưu điểm và nhược điểm khác nhau, bạn có thể thêm vào nếu muốn, mặc dù nó vượt quá những gì OP yêu cầu.
Dan W

13

Làm thế nào không an toàn là \ n \ r là byte dừng?

Nếu bạn gửi gửi dữ liệu tùy ý -> có thể không đủ an toàn.

Một giải pháp phổ biến là sử dụng thoát:

Hãy xác định rằng các ký tự 0x02 (STX - bắt đầu khung) và 0x03 (ETX - kết thúc khung) cần phải là duy nhất trong luồng dữ liệu được truyền. Bằng cách này, phần đầu và phần cuối của tin nhắn có thể được phát hiện một cách an toàn.

Nếu một trong những ký tự này phải được gửi trong khung thông báo, nó sẽ được thay thế bằng tiền tố một ký tự thoát (ESC = 0x1b) và thêm 0x20 vào ký tự gốc.

Nhân vật gốc được thay thế bởi

0x02 -> 0x1b 0x22  
0x03 -> 0x1b 0x23  
0x1b -> 0x1b 0x3b  

Người nhận đảo ngược quá trình này: Bất cứ khi nào anh ta nhận được một nhân vật thoát, nhân vật này bị loại bỏ và nhân vật tiếp theo bị trừ đi 0x20.

Điều này chỉ thêm một số chi phí xử lý nhưng đáng tin cậy 100% (giả sử không có lỗi truyền nào xảy ra, mà bạn có thể / nên xác minh bằng cách thực hiện bổ sung cơ chế tổng kiểm tra).


1
Câu trả lời tốt đẹp. Ký tự thoát phổ biến được sử dụng cho các giao thức ASCII là '\x10'DLE (Data Link Escape). Một số trang Wikipedia cho rằng DLE thường được sử dụng theo cách ngược lại: để nói rằng byte kế tiếp là ký tự điều khiển chứ không phải là byte dữ liệu. Theo kinh nghiệm của tôi, đó thường là ý nghĩa ngược lại cho một lối thoát.
Adrian McCarthy

2
Một điều cần theo dõi của chúng tôi ở đây là kích thước bộ đệm trường hợp xấu nhất của bạn tăng gấp đôi. Nếu bộ nhớ quá chật có thể không phải là giải pháp tốt nhất.
TechnoSam

1
@Rev Lý do để thêm 0x20 vào ký tự gốc là gì? Liệu kế hoạch thoát hiểm có hoạt động mà không có điều đó không?
Nick Alexeev

1
@NickAlexeev: Việc xác định ranh giới khung thực tế sẽ dễ dàng hơn / nhanh hơn nếu bạn loại bỏ bất kỳ sự xuất hiện nào khác của các ký tự dành riêng khỏi luồng. Bằng cách đó, bạn có thể tách biệt việc tiếp nhận khung và phân tích khung (bao gồm cả việc không thoát). Điều này có thể đặc biệt có liên quan, nếu bạn có bộ điều khiển rất chậm mà không có FIFO và / hoặc tốc độ dữ liệu cao. Vì vậy, bạn chỉ có thể sao chép các byte đến (giữa STX / ETX) vào bộ đệm khung khi chúng đến, đánh dấu khung là hoàn thành và thực hiện xử lý với mức độ ưu tiên thấp hơn.
Rev1.0

@TechnoSam: Điểm tốt.
Rev1.0

5

Bạn biết đấy, ASCII đã có byte cho các hàm này.

  • 0x01: bắt đầu tiêu đề - bắt đầu byte
  • 0x02: bắt đầu văn bản - tiêu đề kết thúc, bắt đầu tải trọng
  • 0x03: kết thúc văn bản - tải trọng cuối
  • 0x04: kết thúc truyền - dừng byte
  • 0x17: kết thúc khối truyền - thông báo tiếp tục trong khối tiếp theo

Nó cũng có mã cho các mục đích sử dụng khác nhau bên trong tải trọng.

  • 0x1b: esc (thoát ký tự tiếp theo - sử dụng trong tải trọng để chỉ ra ký tự tiếp theo không phải là một trong các cấu trúc mô tả mã được sử dụng trong giao thức của bạn)
  • Lần lượt 0x1c, 0x1d, 0x1e, 0x1f: tệp, nhóm, bản ghi và dấu phân cách đơn vị - được sử dụng làm byte dừng và khởi động đồng thời cho các phần của dữ liệu phân cấp

Giao thức của bạn nên chỉ định mức độ chi tiết tốt nhất của ACK (0x06) và NAK (0x15), để dữ liệu được thừa nhận tiêu cực có thể được truyền lại. Theo mức độ chi tiết tốt nhất này, nên có trường độ dài ngay sau bất kỳ chỉ báo bắt đầu (không được giải thích) nào và (như được giải thích trong câu trả lời khác), nên tuân theo bất kỳ chỉ báo dừng (không thoát) nào với CRC.


Tôi sẽ gửi dữ liệu tùy ý, tôi đoán có thể đã gây nhầm lẫn khi sử dụng "\ n \ r" trong câu hỏi của tôi khi tôi không gửi dữ liệu ASCII. Mặc dù, tôi thích câu trả lời này, nó rất hữu ích khi gửi ASCII qua UART
CK

@MariusGulbrandsen: Miễn là giao thức của bạn thiết lập vị trí tải trọng và mã nào phải được thoát trong mỗi phần tải trọng, bạn có thể gửi bất cứ thứ gì, không chỉ là dữ liệu văn bản.
Tháp Eric

4

UART không phải là không an toàn bởi bản chất của nó - chúng ta đang nói về công nghệ những năm 1960 ở đây.

Nguyên nhân của vấn đề là UART chỉ đồng bộ hóa một lần trên 10 bit, cho phép rất nhiều lời nói vô nghĩa giữa các giai đoạn đồng bộ hóa đó. Không giống như ví dụ CAN có thể lấy mẫu mỗi bit riêng lẻ nhiều lần.

Bất kỳ lỗi bit kép nào xảy ra bên trong dữ liệu sẽ làm hỏng khung UART và vượt qua mà không bị phát hiện. Lỗi bit trong bit start / stop có thể hoặc không thể được phát hiện ở dạng lỗi tràn.

Do đó, bất kể bạn sử dụng dữ liệu thô hay gói dữ liệu nào, luôn có xác suất xảy ra lỗi bit do EMI gây ra trong dữ liệu không mong muốn.

Có tồn tại rất nhiều cách "thủ thuật UART truyền thống" để cải thiện tình hình từng chút một. Bạn có thể thêm byte đồng bộ hóa, bit đồng bộ hóa, chẵn lẻ, bit stop đôi. Bạn có thể thêm tổng kiểm tra đếm tổng của tất cả các byte (và sau đó đảo ngược nó - bởi vì tại sao không) hoặc bạn có thể đếm số lượng nhị phân là tổng kiểm tra. Tất cả điều này được sử dụng rộng rãi, cực kỳ không khoa học và có xác suất thiếu lỗi cao. Nhưng đây là những gì mọi người đã làm từ những năm 1960 đến 1990 và rất nhiều điều kỳ lạ như những cuộc sống ngày nay.

Cách chuyên nghiệp nhất để đối phó với việc truyền an toàn qua UART là kiểm tra CRC 16 bit ở cuối gói. Mọi thứ khác không an toàn và có xác suất cao bị thiếu.

Sau đó, ở cấp độ phần cứng, bạn có thể sử dụng vi sai RS-422 / RS-485 để cải thiện đáng kể độ chắc chắn của đường truyền. Đây là điều bắt buộc để truyền an toàn trên khoảng cách xa hơn. UART cấp độ TTL chỉ nên được sử dụng cho giao tiếp trên tàu. RS-232 không nên được sử dụng cho bất kỳ mục đích nào khác mà tương thích ngược với những thứ cũ.

Nhìn chung, cơ chế phát hiện lỗi của bạn càng gần phần cứng thì hiệu quả càng cao. Về tính hiệu quả, các tín hiệu vi sai bổ sung nhiều nhất, tiếp theo là kiểm tra các lỗi đóng khung / tràn ngập vv. CRC16 thêm một số, và sau đó "thủ thuật UART truyền thống" thêm một chút.


7
Lời khuyên này khá tiếp tuyến - bạn thực sự chưa giải quyết được câu hỏi. Cụ thể, các giải pháp được đề xuất của bạn có thể giải quyết các vấn đề khác, nhưng chúng không giải quyết được vấn đề cơ bản của câu hỏi trên trang này , đó là sự nhầm lẫn giữa việc đóng khung tạm biệt và tạm biệt tải trọng. Nhiều nhất, đề xuất của bạn sẽ từ chối dữ liệu hợp lệ nhúng một byte khung do CRC hoặc lỗi tương tự, không có cách nào để giao tiếp như vậy.
Chris Stratton

3
Trong thực tế, câu trả lời này làm cho nó tồi tệ hơn. Bản gốc chỉ có byte dữ liệu và byte dừng. Điều này thêm một thể loại thứ ba, byte CRC. Và như được trình bày ở đây, những giá trị này có thể đảm nhận bất kỳ giá trị nào, bao gồm {10,13}.
MSalters

1
@MSalters: CRC có thể được mã hóa hex ASCII để ngăn chặn vấn đề này. Một mẹo khác mà tôi đã thấy trên RS485 là đặt bit 7 trên byte start / address.
Transitor

Re "CÓ THỂ lấy mẫu mỗi bit riêng lẻ nhiều lần." : Việc lấy mẫu thực tế của giá trị bit chỉ một lần trên mỗi bit. Bạn đang đề cập đến điều gì ở đây? Một số loại kiểm tra lỗi, như của người gửi? Đồng bộ hóa đồng hồ?
Peter Mortensen

Việc đảo ngược tổng kiểm tra đã được thực hiện để tổng hợp toàn bộ khối dữ liệu sẽ có kết quả bằng 0, việc mã hóa dễ dàng hơn một chút và thực thi nhanh hơn một chút. Ngoài ra, CRC tốt hơn nhiều so với bạn nghĩ, hãy tìm nó trong Wikipedia.
toolforger

0

... Tôi có thể tưởng tượng, mặc dù xác suất thấp, thông điệp của tôi có thể chứa các giá trị "10 và 13" sau khi chúng không phải là byte dừng.

Một tình huống khi một phần dữ liệu bằng chuỗi kết thúc nên được xem xét khi thiết kế định dạng của gói dữ liệu nối tiếp. Một điều khác cần xem xét là bất kỳ nhân vật nào cũng có thể bị hỏng hoặc bị mất trong quá trình truyền tải. Ký tự bắt đầu, ký tự dừng, byte tải trọng dữ liệu, byte tổng kiểm tra hoặc byte CRC, byte sửa lỗi chuyển tiếp không tránh khỏi tham nhũng. Cơ chế đóng khung phải có khả năng phát hiện khi một gói có dữ liệu bị hỏng.

Có một số cách để tiếp cận tất cả điều này.

Tôi đang đưa ra giả định làm việc rằng các gói chỉ được đóng khung với các byte nối tiếp. Các dòng bắt tay không được sử dụng để đóng khung. Thời gian trễ không được sử dụng để đóng khung.

Gửi chiều dài gói

Gửi độ dài của gói ở đầu, thay vì [hoặc thêm vào] ký tự kết thúc ở cuối.

ưu điểm: Tải trọng được gửi ở định dạng nhị phân hiệu quả.

Nhược điểm: Cần biết chiều dài gói khi bắt đầu truyền.

Thoát khỏi những nhân vật đặc biệt

Thoát các ký tự đặc biệt khi gửi dữ liệu tải trọng. Điều này đã được giải thích trong một câu trả lời trước đó .

ưu điểm: Người gửi không cần biết chiều dài của gói khi bắt đầu truyền.

Nhược điểm: Ít hiệu quả hơn, tùy thuộc vào số lượng byte tải trọng cần được thoát.

Dữ liệu tải trọng được mã hóa sao cho không thể chứa các ký tự bắt đầu và dừng

Tải trọng của gói được mã hóa sao cho nó không thể chứa các ký tự bắt đầu hoặc dừng. Thông thường, điều này được thực hiện bằng cách gửi các số dưới dạng đại diện ASCII hoặc Hex-ASCII của chúng.

ưu điểm: Con người có thể đọc được với các chương trình thiết bị đầu cuối phổ biến. Không cần mã để xử lý thoát. Không cần biết chiều dài của gói khi bắt đầu truyền

Nhược điểm: Hiệu quả thấp hơn. Đối với một byte dữ liệu tải trọng, một vài byte được gửi.

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.