Обзор протокола XMODEM |
![]() |
Добавил(а) microsin | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Протокол XMODEM был разработан довольно давно, в 1977 году (автор Ward Christensen), для предоставления возможности обмениваться данными двум компьютерам через модем (обмен типа точка-точка). В протоколе применен полудуплексный принцип обмена данными, блоками по 128 байт, с кодами квитирования ACK/NAK и проверкой данных с помощью CRC. Благодаря простоте и открытой документации протокол XMODEM нашел широкое применение во многих приложениях. Фактически большинство коммуникационных пакетов, доступных для PC, имеют встроенную поддержку передачи данных через XMODEM. Из-за своей простоты XMODEM удобно использовать на встраиваемых системах [3], где всегда имеется дефицит по вычислительным ресурсам и памяти. Хорошо получаются загрузчики кода [4], позволяющие перепрошивать программу системы по последовательному каналу связи с помощью готовых утилит терминала. Полудуплексный принцип работы позволяет использовать простейшие физические каналы связи. Например, передатчик и приемник может соединять единственный сигнальный провод (либо это может быть радиоканал на общей частоте для передатчика и приемника), который переключается с приема на передачу, потому что протокол XMODEM не подразумевает одновременной передачи на обоих концах канала связи. [Немного теории] XMODEM является полудуплексным (half-duplex) протоколом обмена данными. Протокол не подходит для канала связи, где элементарная посылка состоит менее чем из 8 бит. Приемник после приема пакета либо подтвердит (acknowledge, ACK), либо не подтвердит его (not acknowledge, NAK). Улучшенный по сравнению с оригинальной реализацией XMODEM (которая использовала 1 байт CRC) подсчет контрольной суммы CRC использует более надежную 16-разрядную CRC для проверки целостности принятого блока данных. Протокол XMODEM можно считать протоколом, управляемым со стороны приемника, потому что передатчик не обязан автоматически запускать повторные передачи без участия приемника. Однако все-таки во многих реализациях протокола передатчик самостоятельно запускает передачу пакетов при отсутствии активности приемника. Кратко обмен данными можно описать следующим образом. Приемник начинает обмен предварительными приглашающими посылками символа NAK или "C" в сторону передатчика, чтобы показать свою готовность к приему данных. После этого передатчик посылает 132-байтный (если посылалось приглашение NAK) или 133-байтный пакет (если посылалось приглашение в виде символа 'C'). Затем приемник проверяет пакет и отвечает кодом ACK или NAK, и в зависимости от этого передатчик либо посылает следующий пакет (если от приемника было подтверждение ACK), либо повторно посылает последний пакет (если от приемника пришел код NAK). Этот процесс продолжается до тех пор, пока приемник не получит код EOT, после чего приемник должен подтвердить завершение приема файла посылкой кода ACK в сторону передатчика. После начальной установки связи (initial handshake) приемник управляет потоком данных с помощью отправки передатчику кодов ACK и NAK. В таблице представлены управляющие коды (символы) протокола XMODEM. Таблица 1.1. Специальные символы протокола XMODEM.
Передаваемые полезные данные (например файл) могут быть любой длины, однако они передаются блоками жесткого размера, по 128 байт, и протокол не поддерживает передачу точного размера пересылаемого блока данных (файла). Если содержимое данных не укладываются точно в 128-байтную границу по размеру, то необязательно, но желательно, чтобы остальная часть данных была заполнена так называемыми символами ctl/z (код ASCII 0x1A) конца файла (EOF, End of File). Последний передаваемый блок не отличается от всех остальных, таким образом нет "коротких блоков". Некоторые утилиты или программы пользователя не обрабатывают поддержку окончания файла без наличия символов ctrl/z. Формат пакета XMODEM с поддержкой CRC8 (оригинальный XMODEM):
Формат пакета XMODEM с поддержкой CRC16 (XmodemCRC):
Передатчик определит тип используемого протокола (с CRC8 или с CRC16) по байту приглашения, который отправляет приемник. Если приемник передает байт приглашения NAK, то используется пакет с CRC8, если же приемник передает байт приглашения в виде символа 'C', то это означает использование пакета с CRC16. Байт 1 пакета передатчика может иметь значения SOH, EOT, CAN или ETB, и никакие другие, иначе произойдет ошибка. Байты 2 и 3 вместе формируют номер пакета с проверкой, так что сумма этих двух байт друг с другом всегда должна быть равна 0xff. Имейте в виду, что номер пакета начинает отсчитываться с 1, и если количество пакетов превысит 255, то номер пакета переваливает через 0, и отсчет продолжается - таким образом пакетов может быть любое количество, большее 255. Байты 4..131 формируют пакет данных, и здесь могут быть любые двоичные данные. Байты 132 и 133 формируют 16-битную CRC. Старший байт CRC размещается в байте 132, младший в байте 133. Контрольная сумма вычисляется только от данных пакета (байты 4..131). Синхронизация между приемником и передатчиком. Как уже упоминалось, приемник начинает сессию обмена с периодических (с интервалом примерно 3 сек) посылок передатчику символа ASCII “C” (код 0x43), чем показывая передатчику, что ожидается отправка блока с поддержкой проверки алгоритмом CRC. После отправки символа “C” ждет либо истечения таймаута 3 секунды, либо появления в приемном буфере символа от передатчика. При появлении символа он записывается во внутренний буфер, и таймаут сбрасывается. Если символ от передатчика не поступил и таймаут истек, то приемник снова посылает другой символ “C” передатчику, и снова ждет 3 секунды. Этот процесс продолжается, пока приемник не примет полный пакет из 133 байт. Алгоритм работы приемника. Приемник отслеживает 10-секундный таймаут. Он посылает символ NAK каждый раз, когда интервал таймаута завершился. Первый таймаут приемника приводит к посылке NAK, что сигнализирует передатчику, что можно начать передачу. Опционально приемник может сразу отправить NAK, если передатчик готов к приему, это могло бы экономить 10 секунд времени на начальном таймауте. Однако приемник ДОЛЖЕН продолжить отсчеты таймаута каждые 10 секунд - в случае, если передатчик пока не готов. Как только приемник вошел в прием блока, для каждого принимаемого символа и контрольной суммы используется таймаут в 1 секунду. Если приемник хотел бы послать NAK по какой-то любой причине (неправильный заголовок, ошибка фрейма, истек таймаут в процессе приема данных), он должен ожидать чистой линии на входе (когда передатчик ничего не передает в течение 1 секунды). Подробнее см. ниже "Советы по программированию". Обычно (когда передатчик, приемник и канал связи исправны) в протоколе XMODEM коды NAK используются для следующих ситуаций: 1. Ошибка фрейма (Frame error) при приеме любого байта. При получении любого NAK передатчик заново отправит последний пакет. Ситуации 1 и 2 могут произойти из-за серьезных аппаратных проблем, в этом случае следует проверить соответствие настроек последовательного порта передатчика и приемника (должны быть одинаковые baud rate, количество start bit и stop bit). Ситуация 3 может произойти, когда передатчик ошибочно принял ACK (не принял его), и по этой причине не отправляет новый пакет. В этом случае приемник должен выдать ACK повторно. Также не исключена ситуация, когда передатчик высылает тот же пакет повторно. Приемник может проверить это по изменению номера пакета - если номер пакета тот же самый, то приемник должен просто выдать ACK, после чего отбросить принятые данные пакета. Ситуация 4 может произойти в условиях повышенных помех в линии связи. Последняя проблема 5 должна исправиться сама, после того как приемник передаст код NAK в сторону передатчика. В случае приема блока с ошибкой вызывается специальная процедура очистки приема (см. Советы по программированию далее). Все ошибки в канале связи исправляются 10-ю повторными попытками передачи блока. Если у принятого блока не те атрибуты, которые ожидались, то приемник посылает NAK, и увеличивает свой счетчик повторных попыток. После того, как было 10 неудачных повторов, сеанс связи считается завершенным из-за потери синхронизации. Если был принят допустимый номер блока, то это может означать следующее: 1. Этот блок ожидался, в этом случае все в порядке. Алгоритм работы передатчика. Когда передатчик ждет начала передачи, у него есть только один очень большой таймаут, скажем в 1 минуту. Однако во многих текущих реализациях протокола после начала сеанса связи у передатчика есть 10-секундный таймаут, после которого передатчик предпримет повторную передачу блока. Однако разработчик протокола Ward Christensen не рекомендует так делать, оставляя протокол на управление только со стороны приемника. Это сохранит совместимость с уже существующими программами. Когда у передатчика больше нет данных для отправки, он посылает EOT, ждет ACK, и снова посылает EOT, если ACK не получен. Опять-таки, протокол и в этом случае можно оставить на управление со стороны приемника, и сделать для передатчика единственный таймаут в 1 минуту, после которого передатчик может разорвать сеанс связи. [Пример сеанса связи] В таблице для иллюстрации принципа работы протокола XMODEM показан примерный сеанс связи с восстановлением из ситуации с ошибками. Вся передача файла в этом примере состоит из 5 блоков, некоторые из которых передаются повторно.
Некоторые версии протокола используют специальный символ CAN, код ASCII ctrl/x (см. таблицу 1.1), для отмены передачи. Эта опция не вошла в стандартную версию протокола, потому что наличие в протоколе одиночного символа обрыва передачи повышает вероятность ошибочного обрыва передачи из-за случайного превращения символов ACK, NAK или SOH в символ CAN. [Советы по программированию] Подпрограмма приема символа должна вызываться с параметром, где указывается таймаут в секундах, в течение которого можно ждать приема символа. Первый вызов этой подпрограммы должен быть сделан с временем 10 секунд, так что после истечения 10 секунд таймаута должен быть отправлен символ NAK, и так должно быть сделано 10 раз. После приема SOH (это первый символ, который передан передатчиком) приемник должен вызывать подпрограмму приема символа с таймаутом в 1 секунду, и так до всей оставшейся части сообщения, включая контрольную сумму CRC. Поскольку данные будут передаваться как непрерывный поток байт, то истечение таймаута 1 сек будет означать серьезную ошибку - скажем, принято 127 символов вместо 128. Когда приемник хочет передать NAK, он должен вызвать "очищающую" подпрограмму (PURGE), в которой будет реализован прием с 1-секундным таймаутом, и зацикливание, пока таймаут не истечет. Эта процедура в сущности заключается в ожидании "чистой" линии связи на входе, когда передатчик не передает, что будет гарантировать, что передатчик правильно примет и обработает символ NAK. Вспомните, что протокол рассчитан на обмен в полудуплексе, т. е. передатчик на время своей передачи блокирует работу своего входного канала, что гарантирует отсутствие ошибочной интерпретации помех, которые могли бы возникнуть во входном канале передатчика в процессе передачи. Желательно добавить код для обработки специальных ошибок приема, таких как ошибка фрейма UART, или переполнение входного буфера. Двухбайтная контрольная сумма (CRC16) лучше защищает данные, чем однобайтная (CRC8), потому что, к примеру, CRC8 дает для байт 0x80 и 0x80 тот же результат, что и для байт 0x00 и 0x00. [Пример кода, вычисляющего CRC8] Это простейший алгоритм, который просто считает сумму всех байт (нужно вычислять сумму от 128 байт данных пакета, байты 4..131) с отбрасыванием переполнения. Получится байт суммы, который должен совпадать с байтом 132 пакета. u8 crc8(u8 *buf, int sz) { u8 crc = 0; for (int i=0; i < sz; i++) crc += buf[i]; return crc; } [Пример кода, вычисляющего CRC16] int calcrc(char *ptr, int count) { int crc; char i; crc = 0; while (--count >= 0) { crc = crc ^ (int) *ptr++ << 8; i = 8; do { if (crc & 0x8000) crc = crc << 1 ^ 0x1021; else crc = crc << 1; } while(--i); } return (crc); } [Пример кода, вычисляющего CRC16] static const unsigned short crc16tab[256]= { 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef, 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6, 0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de, 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485, 0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d, 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4, 0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc, 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823, 0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b, 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12, 0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a, 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41, 0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49, 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70, 0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78, 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f, 0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067, 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e, 0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256, 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d, 0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405, 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c, 0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634, 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab, 0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3, 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a, 0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92, 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9, 0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1, 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8, 0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0 }; unsigned short crc16_ccitt(const void *buf, int len) { int counter; unsigned short crc = 0; u8 *pnt = (u8*)buf; for( counter = 0; counter < len; counter++) crc = (crc << 8) ^ crc16tab[((crc >> 8) ^ *(char *)pnt++)&0x00FF]; return crc; } Подпрограмма вернет 16-битное значение контрольной суммы, где старший байт числа должен быть помещен в 132 байт буфера XMODEM (если считать по индексу от 0, то в 131 байт буфера), а младший байт должен быть помещен в 133 байт буфера XMODEM (если считать по индексу от 0, то в 132 байт буфера). Пример использования программы для подсчета CRC16: #define XMODEM_PKT_SIZE (1+1+1+128+2) // 133 байта стандартного пакета XModem static unsigned char xbuff[XMODEM_PKT_SIZE]; static void xtxpacket_create (u8 *data, u32 packetcnt) { u32 idx = 0; u16 crc; //Признак заголовка: xbuff[idx++] = XSOH; //Номер пакета: xbuff[idx++] = 0xFF & packetcnt; xbuff[idx++] = 0xFF - (0xFF & packetcnt); //Запись данных: memcpy(&xbuff[idx], data, 128); //Вставка контрольной суммы: crc = crc16_ccitt(&xbuff[idx], 128); idx += 128; xbuff[idx++] = (crc >> 8); xbuff[idx++] = crc&0x00FF; } [Linux] sx, sb, sz - утилиты для отправки файла по протоколам XMODEM, YMODEM, ZMODEM, находятся в пакете lrzsz. Minicom Qodem Terminal Emulator RZSZ - пакет, который предоставляет множество команд для работы по протоколам XMODEM, YMODEM, ZMODEM. [Windows] HyperTerminal SecureCRT TeraTerm ExtraPuTTY [Android] FTDI UART Terminal. Подробнее см. [5]. [Ссылки] 1. XModem Protocol with CRC site:web.mit.edu. |