Можно с уверенностью сказать, что с момента публикации первой версии стандарта RS‑232 в мае 1960 года и по настоящее время, было написано приблизительно 109 независимых реализаций UART на всём, чём угодно. Однако, подобно «Hello world» в мире прикладного ПО, а также мигания светодиодом — «Hello world» в мире цифровой электроники (сигнализирующий об успешной настройке оборудования и среды разработки) — процесс написания UART способен проиллюстрировать особенности языка или платформы, демонстрируя применение тех или иных синтаксических конструкций для решения практических, насущных и понятных проблем.
В данном цикле статей будет рассказано про написание модуля UART на SystemVerilog, про синтез данного модуля на различных платформах и про некоторые другие аспекты применения UART в ПЛИС. Но прежде, чем писать код, поговорим про сам протокол и про особенности аппаратной части вне контекста ПЛИС.
RS-232
Представим, что у нас есть необходимость передавать данные в одну сторону по одному проводу при помощи логических уровней напряжения. Мы можем просто пустить поток бит, договорившись об одинаковой скорости передачи и приёма данных. Но принимающее устройство не будет знать, где этот поток начался, где он закончится, идёт ли он вообще, как биты распределены по байтам и так далее.
Для начала, нам потребуется заранее оговоренное состояние, которое будет считаться отсутствием передачи данных, то есть паузами между отдельными байтами. Допустим, это будет логическая единица.
Правда, без дополнительных условий будет невозможно отправить байт, состоящий из одних единиц, либо отличить последовательность 11010100 от 01010011.
Можно условиться, что к каждому передаваемому байту мы припишем дополнительный служебный бит (старт-бит), заведомо содержащий логический ноль. Тогда после паузы в передаче данных, приёмник при получении нисходящего фронта будет готов к приёму оговоренного количества бит, содержащих осмысленные данные.
Остаётся проблема: что, если будет передаваться длинная последовательность бит, содержащих нулевые значения без пауз между отдельными байтами? Тогда, рано или поздно, накопленная рассинхронизация тактовых генераторов источника и приёмника превысит половину длительности одного бита. И приёмник либо дважды прочитает один и тот же передаваемый бит, либо наоборот, пропустит один из передаваемых битов.
Нисходящий фронт старт-бита мог бы позволить синхронизироваться источнику и приёмнику каждый раз при начале передачи очередного символа, обнуляя накопившееся рассогласование. Но это возможно, только если перед старт-битом линия каждый раз будет оказываться в состоянии логической единицы. То есть если между байтами имеются хоть небольшие, но паузы. Такие обязательные паузы называются стоп-биты.
Договорившись о наличие старт-бита, стоп-битов, количестве битов данных в рамках одной передачи («символа» в терминологии UART) от старт-бита до стоп-бита и частоте следования бит можно организовать устойчивую однонаправленную передачу данных по одному проводу.
Стандарт RS-232 задаёт значение выходного напряжения логической единицы от -5В до -15В, а логического нуля — от +5В до +15В (именно в таком порядке). Данные уровни напряжения не поддерживаются микросхемами ПЛИС напрямую и существует несколько вариантов соединения ПЛИС с компьютером через RS‑232.
Если у компьютера предусмотрен com-порт, то соединение возможно при помощи пассивного кабеля и транслятора уровней. Зачастую трансляторы уровней для RS‑232 (к примеру, ADM3232E) изготавливают с встроенным умножителем (удвоителем) напряжения на основе переключаемых конденсаторов. Он преобразует +3,3 вольта в приблизительно ±6,6 вольт, которые используются в качестве уровней выходного сигнала на линии TX. А вход RX делают устойчивым к напряжениям вплоть до ±15 вольт.
Если у компьютера отсутствует встроенный com-порт, то соединение возможно при помощи внешнего преобразователя USB-RS232 (к примеру, UPort1150).
В этом случае иногда происходит некоторая путаница с разъёмами. Если разъём DB-9 на материнской плате — всегда «папа» (штыри), то разъём на оборудовании почти всегда «папа». Исключение — прецизионные источники питания компании Keithley (которую купил Tektronix). У них разъём «мама» (гнёзда). Разъём же на преобразователях USB-RS232 почти всегда, как и на материнских платах — «папа». Соответственно, он может быть подключен к Keithley непосредственно. Но для большинства оборудования дополнительно потребуется кабель «мама-мама». Разъём DB-9 предусматривает наличие стягивающих винтов. И ввиду того, что их не расположить внутри корпуса оборудования, то ими оснащается именно кабель. А на разъёмах оборудования делают гайки, куда эти винты закручиваются.
И, казалось бы, если на преобразователях USB-RS232 стоит разъём «папа» (для соединения с «мамой» кабеля, оснащённого винтами), то на нём должны быть гайки. Однако, данный элемент с вероятностью 50/50 будет также винтом! Зато к Keithley подходит идеально 🙂 В общем, если вы решили соединять оборудование с компьютером и хотите чтобы всё собралось с первого раза, не полагайтесь на то, что «всё стандартизовано, тут не ошибёшься» и обратите внимание:
-
на разъём в оборудовании и преобразователе (штыри/гнёзда)
-
на крепления в оборудовании и преобразователе (винты/гайки)
Электронная промышленность предоставляет широкую номенклатуру мостов USB-UART. Благодаря им, при создании устройства можно использовать все аппаратные плюсы интерфейса USB и при этом сохранить относительную простоту программного обеспечения, характерную для RS-232. В дальнейшем, мы будем рассматривать именно этот вариант соединения, хотя с точки зрения кода для ПЛИС, разницы между предыдущими тремя примерами нет.
Есть, правда, весьма важный момент. Сам стандарт TIA/EIA-232-F (RS-232) содержит лишь электрические характеристики и размеры разъёмов. Типичные же скорости передачи данных, количество бит данных в символе, а также наличие/отсутствие дополнительного бита контрольной суммы (бита чётности) этим стандартом не оговаривается. Иногда можно встретить утверждение, что перечисленные выше аспекты оговорены в UART («Universal Asyncronous Receiver-Transmitter»), но это общее название некоторого свода обычаев передачи данных. Этому своду не соответствует какой-либо один-единственный написанный и утверждённый стандарт.
Неким подобием стандарта может являться структура DCB, применяемая в функции SetCommState в Windows API и предназначенная для инициализации com-порта.
Так, в описании данной структуры говорится, что в символе UART может быть 1/1,5/2 стоп-бита. Однако, стандарт «ISO/IEC 7816-3», похожий на «обычный» UART и регламентирующий обмен данными со смарт-картами предусматривает наличие 0,5 стоп-бита. И, к примеру, микроконтроллер STM32F103 способен через конфигурационные биты «STOP» ([13:12]) регистра «USART_CR2» задать режим работы модуля UART как с этим самым половинным стоп-битом, так и с более распространёнными количествами стоп-битов. А мост FT232R не только не способен поддерживать половинный стоп-бит, но также не способен поддерживать полуторный стоп-бит: только один или два стоп-бита.
Или в описании структуры DCB говорится, что значащих бит в одном символе UART может быть от 5 до 8 штук (в это количество не входит бит чётности). А уже упомянутый мост FT232R способен работать только с 7 или 8 битами. Однако, другой похожий на UART стандарт — MDP («Multi-Drop Bus») — содержит, по сути, 9 значащих бит (8 бит данных, плюс бит режима/направления). И микроконтроллер STM32F103 способен работать как с MDP, так и с 8-битным UART.
Теперь, если мы возьмём материнскую плату или преобразователь USB-RS232, вроде «UPort 1115», мы сможем сказать «где-то там есть RS-232». Однако, если мы возьмём микросхему-мост USB-UART с уровнями на линиях RX/TX равными +3,3/0 вольт, сложится парадоксальная ситуация: описываемые в TIA/EIA-232-F нигде напряжения не соблюдаются, а протокол, реализуемый этим мостом не описан в самом стандарте. То есть RS-232, упоминаемый в подобном контексте, приобретает некоторые постмодернистские черты симулякра — символа, у которого нет оригинала 🙂
Возможно также соединить линии USB напрямую к ПЛИС и наделить ПЛИС функционалом, позволяющим ей опознаваться компьютером как USB-устройство класса CDC («Communication Device Class») — то есть как com-порт. Однако, тратить (и в весьма большом количестве!) логические элементы ПЛИС на столь обыденную задачу, реализованную в готовых микросхемах USB-UART имеет смысл только в рамках учебных задач, связанных с изучением протокола USB.
Работа над ошибками
Рассматривая аспекты, связанные с ошибками возникающими при передаче данных через UART, можно выделить три группы:
-
нестабильности электрических параметров сигнала
-
нестабильности временных параметров сигнала
-
нарушение в логике приёма/передачи
Обсудим ряд аспектов, связанных с каждой из этих групп.
Мажорирование
Когда речь заходит про аппаратное исправление нестабильности электрических параметров, регулярно произносится слово «мажорирование». То есть многократное (обычно — троекратное) снятие показаний в пределах одного принимаемого бита и последующий выбор в качестве истинного значения принимаемого бита того, которое чаще других встретилось в ходе этих замеров.
На первый взгляд, подобный механизм может показаться «серебряной пулей», сводящей вероятность ошибок практически к нулю. Попробуем, однако, оценить количественно степень влияния мажорирования на уровень ошибок.
Немного формул
Предположим, что ошибки в ходе замеров являются независимыми друг от друга и появляются с вероятностью «p». Выпишем в таблицу все 8 возможных комбинаций 3-битного числа. Но вместо единиц в ячейках таблицы запишем вероятность ошибки в конкретном замере («p»), а вместо нулей — вероятность верного считывания («1-p»). Вероятность появления каждой комбинации будет равна произведению содержимого ячеек соответствующего столбца. Поэтому отметим те комбинации, которые всё же приведут к ошибочному считыванию бита при мажорировании, определим вероятности появления этих комбинаций и сложим их всех.
Как видно, вероятность ошибки при мажорировании будет равна:
Или, если раскрыть скобки и упростить выражение, то:
Пока что влияние мажорирования может просматриваться не слишком явно. Для более чёткой картины возьмём десятичный логарифм «pmaj»:
Преобразуем это выражение. Для начала вынесем за скобки логарифмируемого выражения квадрат «p»:
Затем заменим произведение логарифмируемого выражение на сумму логарифмов:
С уменьшением «p», второе слагаемое (второй логарифм) достаточно быстро будет стремиться к десятичному логарифму трёх, который равен 0,477 (так, при «p» равном 10-2 (одна ошибка на сто бит) это слагаемое уже будет равно 0,474).
Иными словами, если вероятность ошибки равна:
…то вероятность ошибки с использованием мажорирования по трём замерам приблизительно равна:
Здесь «x» по идее должен принимать значения от «0» до «-∞», но при приближении к нулю (то есть при приближении «p» к единице) начнут сказываться допущения. Однако уже при «x», равном «-1» (то есть одна ошибка на десять бит), приближённые значения будут отличаться от точных значений всего на 2%.
Представим, что происходит передача данных по UART с конфигурацией «старт‑бит/8 бит данных/стоп‑бит» на скорости 9600бит/с и «p» равно 10-6. Это означает, что одна ошибка происходит в среднем один раз в две минуты (130 секунд, если точнее). Поиск причины ошибки, появление которой видится квази-случайным и происходит раз в несколько минут, является достаточно неприятным и трудозатратным занятием. Если же мы применим мажорирование, то ошибка, в теории, начнёт возникать с вероятностью 10-11,523. Или приблизительно один раз в 16,5 месяцев. Вроде бы отличный результат.
Однако, что если «p» равно 10-4 ? Что если ошибка происходит один раз в секунду (1,3 секунды, если точнее), но инженер принял решение не кропотливо настраивать различные условия захвата в цифровом осциллографе с целью выявления причин ошибки, а применить мажорирование, и оно сработало идеальным образом?
Тогда «pmaj» будет равно 10-7,523. Или приблизительно один раз в 72 минуты — как раз достаточно, чтобы в случае претензий сказать: «Ну и где? Где эта ошибка? Вот, всё же работает! А вы говорите!» и убыть в закат 🙂
Кроме того, предположение о том, что ошибки в различных замерах одного бита являются независимыми не совсем верно. Предположим, что ошибка при считывание очередного бита появляется из-за наводки, скачка в цепи питания, либо чего-то подобного. Если мажорирование не применяется, вероятность ошибки «p» тогда будет равна произведению вероятности возникновения помехи «pnoise» на отношения длительности помехи «tnoise» к длительности бита «T»:
Одинаковую вероятность ошибки может создать как длительная, но редкая помеха, так и короткая, но часто повторяющаяся помеха. Однако, часто повторяющуюся помеху («pnoise» которой много больше, чем собственно «p») будет легче обнаружить, проанализировать и выявить её источник. А редко повторяющаяся помеха, та, от поиска которой, по идее, и должно защитить мажорирование, будет длительной помехой, способной в ряде случаев «накрыть» сразу несколько замеров.
В общем, методы коррекции ошибок, применяемые бездумно, способны как решить проблему, так и в отдельных случаях оказаться заметанием мусора под ковёр или вообще не сработать.
Оверсемплинг
Представим, к примеру, ардуиновский микроконтроллер ATmega328P. В нём битрейт модуля USART задаётся при помощи делителя «UBRR» (он записывается в регистры «UBRRnL» и «UBRRnH») и равен:
Допустим, частота fOSC равна 10МГц, а нам требуется получить стандартные 9600бит/с. Тогда наиболее подходящим значением UBRR будет «64». Благодаря нему удастся задать биртейт равный 9615,4бит/с. Погрешность в 0,16% кажется незначительной, однако она накапливаться с каждым битом. И для последнего фронта в символе составит 1,44%.
Точность внутреннего RC-резонатора для того же микроконтроллера ATmega328P при фиксированной температуре в 25°C и напряжении 3,0 вольта заявлена ±2%. Во всём диапазоне рабочих температур и напряжений заявленная точность резонатора падает до крайне грубых ±14%. Но ниже мы будем отталкиваться от первой цифры.
Нестабильность резонатора создаёт дополнительную погрешность, которая хоть и не накапливается с каждым битом, но в конце символа способна сложиться с одним и тем же знаком с погрешностью от деления частоты. При этом, суммарная погрешность передатчика может удлинить передаваемые им биты, а суммарная погрешность приёмника — укоротить считываемые им биты. То есть, по сути, погрешности сложатся по модулю.
Если считывание производится непосредственно с частотой битрейта, статистически, обязательно рано или поздно возникнет ситуация, что принимающее устройство зафиксирует появление старт-бита не в его середине, а в самом его начале (сразу после появления нисходящего фронта) или же в самом конце (перед первым битом данных). Причём сколь угодно близко к фронту. И к последнему биту символа приёмник гарантированно рассогласуется с передатчиком.
Решением этой проблемы является многократное считывание логического состояния на линии RX — оверсемплинг. После первого считывания, результат которого может быть принят за старт-бит, приёмник ожидает половину битового периода и только начиная с этого момента приступает к считыванию бит через равные промежутки времени. При четырёхкратном чтении, ошибки передачи будут отсутствовать при общей временно́й погрешности, доходящей до 25%!.
На этом месте может возникнуть пара вопросов:
-
Какова временная погрешность приёмопередатчиков UART в реальных устройствах?
-
Если приёмник всё равно производит многократное чтение одного и того же бита, возможно ли получаемые данные эффективно использовать также для мажорирования?
Отвечая на первый вопрос, можно обратиться к документу «Determining Clock Accuracy Requirements for UART Communications», выпущенный компанией «Maxim Integrated» (на текущий момент она поглощена компанией «Analog Devices»), который говорит следующее:
It is difficult to quantitatively assess a worst-case acceptable sampling range across a bit’s period. EIA/TIA-232-F does specify a 4% of bit-period maximum slew time for a transmission, but this is difficult to achieve for long runs at 192kbps. But for the purpose of this application note, let us define two data path scenarios. Consider a «nasty» scenario, which can only be sampled reliably within the middle 50% of the bit time. This could equate to a long capacitive RS-232 run. The «normal» scenario can be sampled within the middle 75% of the bit time.
То есть инженеры «Maxim Integrated» предполагают, что невозможность корректно считать значения на линии RX в течении 25% от длительности бита — это вполне нормальное явление.
Если же проводить тест по маске, к примеру, для платы производства «WaveShare» с микросхемой FT232RL (с внутренним тактовым генератором) при битрейте 9600бит/с, то из допуска ±2% от длительности бита последний фронт символа (восходящий фронт стоп-бита) будет «вываливаться» с вероятностью 0,55%. А из допуска ±4% с вероятностью 0,5х10-5 %. Можно предположить, что допуск в 10-12% (±5…6%) будет достаточно широк для того, чтобы считать вероятность выхода из него несущественной величиной. Если происходит обмен данными между двумя устройствами с допуском в 10…12% у каждого, то имеет смысл говорить об общей погрешности как раз в 20…24%.
Для ответа на второй вопрос представим себе, что у нас есть 8-кратный оверсемплинг и 25% общей погрешности, связанной с временными характеристиками. Два семпла окажутся в зоне 25%. Ещё на один семпл попадает «игла», которую мы пытаемся устранить при помощи мажорирования (ведь исправлять помехи имеет смысл только когда они есть, а словосочетание «есть помеха» означает, что помехи попадают по семплам). Итого мы получим 5 годных для мажорирования семплов к которым добавлено 3 гарантированно ненадёжных семпла.
Подобная конструкция не представляется такой уж надёжной. Поэтому, к примеру, в STM32F103 коррекция ошибок (мажорирование по восьмому, девятому и десятому семплам бита), дополнена механизмом сигнализация об их наличии.
То есть если три семпла, взятые посередине бита не равны все нулю или все единице, то такое значение, вообще говоря, не считается действительным.
Ошибки логики
Является ли ошибкой отсутствия стоп-бита, то есть если был передан старт-бит, затем оговоренные N бит данных, а после этого стоп-бит не появился (линия вместо стоп-бита оказалась в состоянии логического нуля)? Не всегда. Так, стандарт «ANSI E1.11», описывающий протокол обмена данными светотехнического оборудования DMX512, похожий на классический UART, использует при передаче данных аналогичное действие. При номинальной длительности бита DMX512 равной 4мкс, переход в логический ноль и последующее удержание этого состояния в течение 92…176мкс (то есть 23…44 нулевых бита подряд) называется «break» и маркирует начало очередного пакета данных.
Однако, в общем случае, если передаваемый символ состоял не только из нулей, отсутствие стоп-бита будет, пожалуй, именно ошибкой — «framing error».
Является ли ошибкой отсутствия второго стоп-бита, если на стороне компьютера был выбран режим с двумя стоп-битами, а коммуницирующее устройство было настроено на работу с одним стоп-битом?.
Пожалуй, нет. Так, в документации на ардуиновский микроконтроллер ATmega328P написано:
The Receiver and Transmitter use the same setting. Note that changing the setting of any of these bits will corrupt all ongoing communication for both the Receiver and Transmitter. An FE (Frame Error) will only be detected in cases where the first stop bit is zero.
Ещё более подчёркнуто это написано в документации на упоминавшиеся микроконтроллеры STM32F103:
2 stop bits: Sampling for 2 stop bits is done on the 8th, 9th and 10th samples of the first stop bit. If a framing error is detected during the first stop bit the framing error flag will be set. The second stop bit is not checked for framing error.
Относительно современная микросхема Super-I/O NCT6776D, реализующая UART на материнских платах, косвенно ссылается в своей документации на ставшую де-факто стандартом, классическую микросхему PC16550D:
NSER (No Stop Bit Error). This bit is set to logical 1 to indicate that the received data have no stop bit. In 16550 mode, it indicates the same condition for the data on the top of the FIFO. When the CPU reads USR, it sets this bit to logical 0.
А в документации на микросхему PC16550D написано:
If bit 2 is a logic 0, one Stop bit is generated in the transmitted data. If bit 2 is a logic 1 when a 5-bit word length is selected via bits 0 and 1, one and a half Stop bits are generated. If bit 2 is a logic 1 when either a 6-, 7-, or 8-bit word length is selected, two Stop bits are generated. The Receiver checks the first Stop bit only, regardless of the number of Stop bits selected.
Заключение
Типичная схема применения UART — это старт-бит, 8 бит данных и один-единственный стоп-бит. Типичный битрейт — 9600 бит/с (то есть длительность любого бита будет равна 104,17мкс). В типичном случае «break frame» (большое количество нулевых бит) не применяется и будет просто ошибкой передачи. Так что базово UART относительно прост.
А упомянутые в статье особые случаи являются исключениями. О существовании которых, однако, имеет смысл знать. Также имеет смысл знать о практике применения данного протокола в индустрии. К примеру, посмотреть, какие возможности предоставляют различные микросхемы мостов USB-UART. Их детальный обзор — в следующей статье.