Как стать автором
Обновить

Комментарии 43

А теперь нужно взять паяльный фен и жидкий азот с системой подачи и посмотреть куда всё уплывёт при нагреве до максимальной (по datasheet’у) и охлаждении до минимальной температур, а также при разных номиналах питания. Судя по datasheet’у, мало того, что на заводе калибруют с точностью до 10 % (пункт 18.4.1) — до 2 % можете закалиброваться сами. Так ещё и частота сильно плывёт в зависимости от температуры, на те же 10 % примерно (пункт 19.12). И, к тому же, datasheet может и врать, иногда сильно.

Да, этот предмет споров и не дает спокойствия противникам. Могу Вас заверить, такие тесты проводились мной в сторону нагрева. И действительно частота плывет, и правда, что обмен «сыпется», но не на 19200, и даже не на 57600, а там, где стартовый бит почти совпадает с окончанием пролога. У таких скоростей, при такой частоте чипа просто нет шансов остаться в окне передачи. При этом на низких скоростях передачи, отклонение даже на 30% не вызовет серьезных проблем, ввиду допусков самого UART. И скорей всего, Вы не заметили слово — самосинхронизация. Последняя написанная мной версия кода (не входящая в планы), делает этот процесс прямо во время передачи, на границах бит 0->1 и 1->0.

Так про тестирование при температуре хорошо бы в статье написать. И там слово «самосинхронизация», хоть и есть, но не поясняется. Где она в коде я не вижу.

Согласен, объективное замечание. Я много чего хотел написать, в том числе и про испытания. Но 3 часа ночи сыграли против меня в этот раз :)
Поэтому не буду раздувать статью дальше, пусть она останется стихотворением, написанным водой на горячем асфальте, а Вам отвечу здесь:
Грел строительным феном прямо на мини-борде почти до 100 градусов. Больше не рискнул, побоявшись оплавления ABS (температура стеклования 120-140).
При такой температуре передача на 57600 работает без ошибок, 250к — сыпется. Промежуточные не измерял из-за затрудненного процесса прошивки (MiniPro 866A), нужно передергивать чип из борды в программатор и обратно. В перспективе если откопаю USBAsp, который забросил в дальний угол, организую стендик и выполню все покрытие диапазона. Отпишусь.
На счет самосинхронизации: при задержке в стоповом бите, тестируется линия на появление низкого уровня. Если событие произошло — безусловный переход на приём следующего символа, в результате чего, программа оказывается на старте уже через два такта после начала низкого уровня. Дальнейший приём идет с запасом по дрейфу, почти двух-кратным.

Понятно. Я эту часть видел, но от явления, названного «самосинхронизации», я ожидал либо чего‐то вроде изменения UART_DELAY (которая у вас сейчас константа) в зависимости от значения счётчика или же чего‐то вроде изменения счётчика задержки на половину от UART_DELAY — оба варианта когда значение на RX меняется во время задержки.

Ну так эффект в точности такой, только корректируется не длительность а фаза.
Спасибо за статью. Хотелось бы, как минимум, видеть наречие 'насколько' слитно. Спасибо ещё раз.
Спасибо, что вы заметили только опечатку, и не обратили внимание на ошибки. Исправил.
Да ладно. Он «чтобы» правильно пишет!!! За такое всё можно простить.
Я правильно понимаю, что вы написали бессмысленный и беспощадный софтовый UART, который сжирает 100% ядра при приёме или передаче? Я, если что, не осуждаю, принцип «хотел, мог, сделал» — мощная движущая сила в области DIY.
Нет, Вы всё поняли неправильно. Я сделал инструмент, а каким образом им пользоваться решает каждый для себя сам. Менеджер отправки может написать любой, кому эта тема интересна и нужна.

Да, там busy-loop как в коде отправки, так и приёма. А приём сделан в прерывании, так что когда оно сработает, отправка будет приостановлена. В результате полного дуплекса не получается, и во время отправки или получения данных полезную работу можно делать разве что в обработчиках других прерываний. Обычный программный уарт, в общем. Разве что объём кода минимальный.

Приём во время отправки запрещен, в остальном все верно, полнодуплекса нет, да и не нужен он тиньке. Ну и не стоит так категорично — «разве что...», минимальный объем кода, меньше мешает при отладке, например. Понятное дело, что это не для «продакшена», лично мне такая либа сэкономила бы уйму времени однажды. А чего уж говорить о новичках, которые только-только слезли с ардуинки, с целью минимизировать свои проекты. В общем я не претендую на роль первопроходца, я это сказал вначале. Повторюсь: это удобный инструмент, не более того. А цель разработки — спортивная.

Ну вообще да, для atTiny вполне. Там каждая нога и каждый байт памяти на счету. А тут можно на любую ногу повесить отправку и приём, удобно.

Проблема дрейфа обычно решается калибровкой по известным данным. Получаете байт вроде 0x55 или 0xAA, измеряете длину бита в тактах и пользуетесь. Уплыла частота, получаем бред? На уровне протокола приостанавливаем обмен, ждем пакета калибровочных данных, продолжаем обмен.
Писал когда-то программный UART с таким функционалом, получилось около 140 байт флеша, 3 регистра и 1 байт памяти. Функции инициализации, отправки\получения байта и автокалибровки. Без таймеров\прерываний, само собой, на задержках. Отправка\получение побайтовые, но для строк приспособить будет несложно:
github.com/sirgal/AVR-ASM-Software-UART/blob/master/software_uart.asm (использование в корыстных целях приветствуется, лицензия MIT)
Вы лишний раз подтвердили, что мой код меньше, почти в три раза. Спасибо.

Когда я предложил использовать у себя «калибровку по известным данным» вроде 0x55 мне в ответ предложили гораздо более простую вещь: просто смотрите какова минимальная длина нуля. Учитывая, что UART передаёт нулевой бит, затем наименее значимый, вероятность поймать правильный результат уже на первом бите первого байта произвольной посылки примерно 50 %.

Это неверно! Я акцентировал проблему пролога прерывания: пока обрабатывается вход, мы уже в середине стартового бита. А если следом за ним передается нулевой бит, то мы никогда не поймем где граница. HurrTheDurr в этом отношении прав, однако подобные синхронизации требуют дополнительного кода как для клиента, так и для сервера. Выше я так же акцентировал, что синхронизацию можно производить по переходным состояниям линии на границах следования бит (0->1 или 1->0), выходя из цикла задержки заранее. И тогда, даже на высоких скоростях дрейф частоты не окажет влияния (в допустимых нормах).

В вашем случае для получение длины нуля нужна будет любая последовательность «101» в середине или «10stop» в конце посылки, что тоже не является невероятным, но конкретно у меня FPGA и тактируемый цикл (в LabVIEW — цикл, гарантированно исполняющияся за один такт), поэтому вашей проблемы с началом посылки нет.


В любом случае, по переходным состояниям можно только компенсировать дрейф, но во время приёма и даже без потери посылки. По калибровочному байту можно получить значение скорости, имея смутное представление о том, каким оно должно быть, но требуется как‐то этот байт послать вместо чего‐то более полезного (я когда предлагал вариант с калибровочным байтом просто предполагал просто начинать с него каждую посылку). А по длине нуля можно калиброваться на произвольных посылках, опять же, имея только смутное представление о скорости — но что‐то получать в это время можно только если вы именно компенсируете так дрейф.

Да, так и делают. В Ethernet каждый фрейм начинается с 7 байтов 10101010 подряд. Код это не особо раздувает, надо просто 7 раз подряд вызвать функцию и посчитать среднее.
Какое-то отклонение от темы, и сравнение «теплого» с «мягким».
Понятное дело, что ПЛИС позволяет многие вещи делать совсем иначе, но это никак не связанно с циклами (не понял что Вы под этим подразумеваете, если — такт, то в AVR они так же — гарантированы), как и вход в прерывание клиента, с началом посылки — сервера. Начало посылки в моём случае выполняется сразу (не считая нескольких тактов на инициализацию).
Касаемо калибровочных байт Вы, вероятно видите задачу связи между микроконтроллерами, хотя я настойчиво утверждаю и в статье, и в комментариях к ней, что функции приема и передачи написаны с учетом стандарта.
Моя задача: читать и принимать данные любым терминалом. А это уже не позволяет использовать калибровочные данные.
Если бы я реализовывал связь между микроконтроллерами, я бы точно отказался от UART, в пользу параллельных шин, на нестандартных частотах, с потоковым сжатием, коррекцией и проверкой валидности.
Но это другая задача, и для других контроллеров.

Тактируемые циклы — это особенность программирования ПЛИС на LabVIEW, их тело запускается в начале такта и один запуск тела цикла гарантированно длится не более одного такта. На VHDL я не пишу, но знаю, что первая часть (запуск в начале такта) будет выглядеть примерно как process (clk) begin if rising_edge(clk) then {body} end if; end process;, а вторая часть вроде выглядит как TIMESPEC TS_CLK = PERIOD "clk" 100 MHz HIGH 50%; и идёт в отдельный файл (не VHDL), что в совокупности совершенно не выглядит как цикл (и потому я и написал уточнение).


По поводу калибровочных байт/длины нуля: я вижу их применимость в любом случае, когда осуществляется связь между двумя устройствами, частота приёма/передачи хотя бы одного из которых может изменяться во время работы. Вариант с длиной нуля специально придуман именно для случая, когда нельзя использовать калибровочные данные. У меня там действительно связь между ПЛИС и микроконтроллером, причём шлёт данные в основном второй, а подстраивается под него первая. В вашей ситуации приём с нулём будет выглядеть примерно так:


  1. Микроконтроллер определил, что ему нужно закалиброваться — например, по тому что он получает какой‐то бред. Или его только что включили, а создатель устройства решил не указывать конкретную частоту при создании прошивки. Важно, чтобы команды ему продолжали/начали слать, даже если другая сторона не понимает ответы микроконтроллера так же, как микроконтроллер не понимает команды.
  2. Он входит в калибровочный режим и начинает подсчитывать, насколько длинные нули ему приходят, запоминая только минимальное значение.
  3. Через некоторое время он выходит из калибровочного режима, вычисляет UART_DELAY на основе полученных данных (т.е. умножает длину нуля на коэффициент, предварительно проверив данные на адекватность) и далее использует его.

Требования к посылкам при этом минимальные: если во время «калибровочного режима» нужно, чтобы работал какой‐то ещё код, то «наличие последовательности 101 в посылке, в т.ч. подойдёт 10 и стоповый бит». Если не нужно, то ещё подойдёт посылка с единицей в наименее значимом бите: тогда можно не использовать прерывания. (Кстати, строка «стандарт» на момент написания комментария на всей странице встречается ровно два раза — все в том комментарии, на который я сейчас отвечаю.)


Конечно, «калибровочный режим» не нужен, если вы абсолютно уверены в том, что можете одной константой обеспечить приём и передачу на одной из стандартных скоростей в течении всего времени работы устройства и свободная память вам пригодится больше дополнительного запас прочности.


Про подстройку на основе длины нуля во время приёма: я подумал, как это будет выглядеть с программным UART и понял, что иметь такое в цикле задержки мне бы не понравилось. В целом примерно то же самое, что и с калибровочным режимом, только длина считается не во время калибровочного режима, а во время нормальной работы, а задержка вычисляется между приёмами.


PS: Чтобы исключить возможное недопонимание, привожу как я видел компенсацию дрейфа во время приёма по переходным состояниям: вместо nop’ов получение значения с порта, сравнение с сохранённым предыдущим, brne на команду вперёд и установка счётчика в UART_DELAY/2 − C (поправка на дополнительные затраты времени на установку счётчика).

Спасибо за столь развернутый ответ, это многое прояснило.
Стандартный протокол UART, не подразумевает автокалибровку по дополнительным входным последовательностям и работает на заданных частотах, поэтому я неправильно интерпретировал Ваши «нули».
Касаемо же этой подстройки по длительности периода бод на неизвестной частоте, идея хорошая и нужная, но опять же — жирно использовать ее для микроконтроллера с 1кб флеша и 64 байтами SRAM. В случае же режима корректировки фазы, код не сильно увеличивается, теряется возможность автоопределения скорости, но повышается точность приёма при дрейфе частоты. И этот вариант вполне самодостаточен для программной реализации UART, используемого для «ОТЛАДКИ» микроконтроллера.
Ситуация с подсчетами еще усложняется тем, что разные команды имеют разное количество циклов, в этой ситуации требуется унификация задержки, которую я в свою очередь развернул, чтобы сократить размер кода и получить возможность более точной подстройки.
Поэтому любая команда, будь то условный переход, или чтение линии порта, будет выполняться дольше NOP, и в случае получения значения с порта, сравнения, и условного перехода, это уже в ПЯТЬ раз больше, и получение неустранимого джитера в приёмнике.
Спасибо за Ваш интерес к теме и описанные варианты обработки, любые решения, даже неудачные позволяют приблизиться к одному — идеальному.
Эксперименты не заканчиваются, и как я сказал выше — я написал другой приемник, с автокоррекцией фазы, но меня не устраивают некоторые нюансы в нём. Если я устраню эти нюансы и удастся оптимизировать код, я форкну проект на гитхабе и отпишусь об изменениях.
А пока, будем считать это решение законченным и самодостаточным.
Спасибо еще раз!

Проблемы с условным переходом по идее должны решаться константой (C в комментарии). Изменение длительности цикла ожидания в данном случае только ограничивает максимальную скорость передачи.


Спасибо за беседу, интересно пообщаться с человеком с иным взглядом на проблему: я на работе (пока?) не пишу программы для микроконтроллеров с жёсткой экономией памяти (и отсутствием отдельного UART блока), но специфика работы (тестирование микросхем на радиационную стойкость) предполагает, что внутренняя частота во время испытаний уплывёт с немалой вероятностью, иногда даже намного сильнее, чем по datasheet’у. Поэтому автокорректировка на одной из сторон видится довольно полезной вещью — и если использовать строго стандартные скорости, то этой стороной будет менее ограниченный микроконтроллер. (Правда мы, если частота действительно уплывёт за границы, указанные в datasheet’е, можем написать, что схема не прошла испытания. Впрочем, часто внутреннего генератора нет или его не предполагается использовать.)

Похвально. А чего код не написали просто чисто на ассемблере, а ещё прикрутили си? Там же проще было всё чистоганом сделать на асме.

У вас код под гну лицензией, она не запрещает использование кода в коммерческих проектах. И более того, я могу использовать ваш код в моём проекте и если кому-то понадобиться, то я могу предоставить ваш исходный код.
Для себя я так и делаю, после того как библиотека прошла тесты и показала полную работоспособность, а пишу так, потому что привычка с тех времен, когда еще не было окон и вкладок. Мне удобно перемещаться по листингу не переключаясь между окнами в маленьких проектах, и считаю, что новичкам так проще не запутаться.
По поводу коммерции — бесспорно, ведь не я придумал протокол обмена, здесь я имел ввиду сам код и его «несвободность». То есть продажу кода как своего личного, любого измененного приложения — как своего, либо в составе собственного продукта в качестве библиотеки, с нарушением авторских прав. Во всех остальных случаях, с соблюдением этикета, вы можете пользоваться так, как считаете нужным, с указанием источника, автора, и исходного кода.
Огромное спасибочки. Заказал тиней десяток и неплохо бы их использовать как мозг какого-нибудь простенького сетевого датчика, только без уарта его в «классическую» проводную шину втыкать накладно. А так задача сильно упростилась
Только не стоит забывать, что расстояния там не очень большие, возможно лучше использовать другие интерфейсы. Есть планы на счет реализации i2c и twi, но пока это размазано и туманно. Хотя есть заказ. В общем — думаю.
НЛО прилетело и опубликовало эту надпись здесь
А как у данной программной реализации с помехоустойчивостью? Хорошие аппаратные реализации делают несколько проверок уровня входного сигнала в ожидаемой середине битового интервала, и затем мажоритарно делают вывод о наиболее вероятном значении бита. Начальная же синхронизация посылки по старт-биту в хороших аппаратных моделях вообще делается следующим образом: в состоянии idle каждый переход 1->0 инициирует свой таймер, вызывающий серию проверок уровня сигнала в середине ожидаемого стартового бита — то есть, каждый переход по сути порождает гипотезу об истинном времени начала передачи. Подтверждается или опровергается гипотеза мажоритарным выбором через половину интервала — при этом точкой синхронизации начала посылки считается время перехода 1->0, породившего наиболее раннюю гипотезу. При подтверждении гипотезы конечный автомат переходит из idle в busy, а более поздние гипотезы отбрасываются; при неподтверждении автомат остается в состоянии idle, и отбрасываются все гипотезы, устаревшие более чем на один битовый интервал.

Сравнить помехоустойчивость программных и аппаратных реализаций можно, добавив искусственные положительные и отрицательные иголки (кратковременные переходы 1->0->1 и 0->1->0) в программный генератор UART, и подключив на прием одновременно обе реализации
Вы говорите про помехоустойчивость передачи, или же приёмника? :)
Очевидно, что сэмплирование осуществляется в любой точке временного диапазона бита на конкретной частоте. Стартом (и началом отсчета) служит стартовый бит, в результате чего происходит вызов функции прерывания которая, в свою очередь выполняя пролог, учитывает текущее смещение к нулевому биту таким образом, чтобы обеспечить попадание в окно.
Не менее очевидно, что если в момент сэмплирования будет попадание в помеху, то результат будет записан безусловно. Однако для таких целей существует контроль по сумме и четности, которые в данной реализации не предусмотрены, но могут быть организованы с минимальными усилиями, силами разработчика.
Любые другие алгоритмы помехоустойчивости на столь низкой частоте МК, гарантированно ведут к снижению рабочего диапазона частот, раздувают код и количество задействованных ресурсов (речь о регистрах).
Я говорю про помехоустойчивость программной реализации приемника относительно более-менее стандартной аппаратной реализации (тактирование автомата с периодом в 16 раз меньше периода битового интервала, проверка и мажоритарный выбор в 7-8-9 периоде внутреннего генератора).
Обычно программные реализации достаточно наивны именно потому, что поддержка сложного конечного автомата в реальном времени сильно снижает скорость, и увеличивают долю ресурсов МК, а жесткие условия по помехам на столе или в макете практически не встречаются. Вот я и хотел узнать, у вас так же, или нет
250000*(1+8+1)*16=40000000 Hz (40MHz)
Как Вы себе представляете тактирование автомата на такой частоте, в чипе с частотой 9.6МГц?
На мой взгляд, такая программная реализация — более чем наивна.
Проект версии 2.0, для Atmel Studio 7.0

Хабровчане, выложите код в текстовом виде, пожалуйста, ну нет у меня пока Atmel Studio.
Как раз активно заинтересовался софтовым UART, и тут на тебе, такая удача!

«Проект для Atmel Studio» — это вообще‐то набор самых обычных исходных файлов плюс настройки studio. Если у вас нет studio просто игнорируйте настройки и создайте Makefile/CMakeLists.txt/… для своей среды. Исходный код там есть, он не зашифрован и даже не обёрнут в XML (и я не помню, чтобы так делала вообще какая‐либо IDE). Конкретно в этом случае — файл Tiny_UART/main.c.

Спасибо! Даже Arduino-IDE переварила, всего-то 8 строк пришлось закомментировать. Вот только я сомневаюсь, что эти 8 строк лишние:
код
50 #define Send_BEEP UART_Send((char [2]){7,0}) // Beep Terminal macro (if Terminal support it) //
52 #define Send_CR UART_Send((char [3]){10,13,0}) // Next Terminal line macro //
277 Send_BEEP;
280 UART_Send_PGM( ( char* ) PGM_String0 ); Send_CR;
281 UART_Send_PGM( ( char* ) PGM_String1 ); Send_CR;
282 UART_Send_PGM( ( char* ) PGM_String2 ); Send_CR;
283 Send_CR;
297 Send_CR;

Вам нужно указать Arduino IDE, чтобы она использовала стандарт C99. Если она такого не умеет (или если вы вставили C’шный код в C++ файл), то замените эти длинные массивы в #define’ах на нормальные строковые литералы: "\007" и "\012\015" (или более читаемые"\a" и "\r\n", если вспомнить, что означают эти цифры): не знаю, зачем автор вообще сделал там C99 массивы.

Это макросы, созданные для удобства управления терминалом, CR — перевод каретки, BEEP — звуковой сигнал. Можно их не использовать ;)
И спасибо за Ваше тестирование!
Пока что это ещё не тестирование, ещё хочется прошить и попробовать.
Теоретически можно прикрутить к любым AVR? Для атмега8 сейчас мешают
267 GIMSK = (1<<PCIE); // Enable External Interrupts //
268 PCMSK = (1<<RxD); // Enable accorded Interrupt (PCINT3) //
Теоретически — да, к любым, основная сложность — отличия в периферии и регистрах.
У Меги8 только два прерывания int0(PD2) и int1(PD3). Если перенастроить приемник, и переназначить пин приемника на этот порт, то других изменений не потребуется.
Но ведь у Меги8 есть аппаратный UART?
Про мега8 вопрос скорее теоретический, просто у меня их много и прошивать проще. Дойдут руки- попробую на тиньках (Вы ведь не пробовали, как я понял?).
Что делают две строки, которые компилятор не скушал с мегой? (Простите за дилетантские вопросы).
Две строки — разрешают прерывания внешние, и маской устанавливают пин порта, какой будет реагировать на прерывания.
По поводу не пробовал: как не пробовал то? в статье же подробно все описано.
Я читал пару дней назад, запамятовал, что таки проверено в железе.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории