Комментарии 43
А теперь нужно взять паяльный фен и жидкий азот с системой подачи и посмотреть куда всё уплывёт при нагреве до максимальной (по datasheet’у) и охлаждении до минимальной температур, а также при разных номиналах питания. Судя по datasheet’у, мало того, что на заводе калибруют с точностью до 10 % (пункт 18.4.1) — до 2 % можете закалиброваться сами. Так ещё и частота сильно плывёт в зависимости от температуры, на те же 10 % примерно (пункт 19.12). И, к тому же, datasheet может и врать, иногда сильно.
Так про тестирование при температуре хорошо бы в статье написать. И там слово «самосинхронизация», хоть и есть, но не поясняется. Где она в коде я не вижу.
Поэтому не буду раздувать статью дальше, пусть она останется стихотворением, написанным водой на горячем асфальте, а Вам отвечу здесь:
Грел строительным феном прямо на мини-борде почти до 100 градусов. Больше не рискнул, побоявшись оплавления ABS (температура стеклования 120-140).
При такой температуре передача на 57600 работает без ошибок, 250к — сыпется. Промежуточные не измерял из-за затрудненного процесса прошивки (MiniPro 866A), нужно передергивать чип из борды в программатор и обратно. В перспективе если откопаю USBAsp, который забросил в дальний угол, организую стендик и выполню все покрытие диапазона. Отпишусь.
На счет самосинхронизации: при задержке в стоповом бите, тестируется линия на появление низкого уровня. Если событие произошло — безусловный переход на приём следующего символа, в результате чего, программа оказывается на старте уже через два такта после начала низкого уровня. Дальнейший приём идет с запасом по дрейфу, почти двух-кратным.
Понятно. Я эту часть видел, но от явления, названного «самосинхронизации», я ожидал либо чего‐то вроде изменения UART_DELAY (которая у вас сейчас константа) в зависимости от значения счётчика или же чего‐то вроде изменения счётчика задержки на половину от UART_DELAY — оба варианта когда значение на RX меняется во время задержки.
Да, там busy-loop как в коде отправки, так и приёма. А приём сделан в прерывании, так что когда оно сработает, отправка будет приостановлена. В результате полного дуплекса не получается, и во время отправки или получения данных полезную работу можно делать разве что в обработчиках других прерываний. Обычный программный уарт, в общем. Разве что объём кода минимальный.
Писал когда-то программный UART с таким функционалом, получилось около 140 байт флеша, 3 регистра и 1 байт памяти. Функции инициализации, отправки\получения байта и автокалибровки. Без таймеров\прерываний, само собой, на задержках. Отправка\получение побайтовые, но для строк приспособить будет несложно:
github.com/sirgal/AVR-ASM-Software-UART/blob/master/software_uart.asm (использование в корыстных целях приветствуется, лицензия MIT)
Когда я предложил использовать у себя «калибровку по известным данным» вроде 0x55 мне в ответ предложили гораздо более простую вещь: просто смотрите какова минимальная длина нуля. Учитывая, что UART передаёт нулевой бит, затем наименее значимый, вероятность поймать правильный результат уже на первом бите первого байта произвольной посылки примерно 50 %.
В вашем случае для получение длины нуля нужна будет любая последовательность «101» в середине или «10stop» в конце посылки, что тоже не является невероятным, но конкретно у меня FPGA и тактируемый цикл (в LabVIEW — цикл, гарантированно исполняющияся за один такт), поэтому вашей проблемы с началом посылки нет.
В любом случае, по переходным состояниям можно только компенсировать дрейф, но во время приёма и даже без потери посылки. По калибровочному байту можно получить значение скорости, имея смутное представление о том, каким оно должно быть, но требуется как‐то этот байт послать вместо чего‐то более полезного (я когда предлагал вариант с калибровочным байтом просто предполагал просто начинать с него каждую посылку). А по длине нуля можно калиброваться на произвольных посылках, опять же, имея только смутное представление о скорости — но что‐то получать в это время можно только если вы именно компенсируете так дрейф.
Понятное дело, что ПЛИС позволяет многие вещи делать совсем иначе, но это никак не связанно с циклами (не понял что Вы под этим подразумеваете, если — такт, то в 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), что в совокупности совершенно не выглядит как цикл (и потому я и написал уточнение).
По поводу калибровочных байт/длины нуля: я вижу их применимость в любом случае, когда осуществляется связь между двумя устройствами, частота приёма/передачи хотя бы одного из которых может изменяться во время работы. Вариант с длиной нуля специально придуман именно для случая, когда нельзя использовать калибровочные данные. У меня там действительно связь между ПЛИС и микроконтроллером, причём шлёт данные в основном второй, а подстраивается под него первая. В вашей ситуации приём с нулём будет выглядеть примерно так:
- Микроконтроллер определил, что ему нужно закалиброваться — например, по тому что он получает какой‐то бред. Или его только что включили, а создатель устройства решил не указывать конкретную частоту при создании прошивки. Важно, чтобы команды ему продолжали/начали слать, даже если другая сторона не понимает ответы микроконтроллера так же, как микроконтроллер не понимает команды.
- Он входит в калибровочный режим и начинает подсчитывать, насколько длинные нули ему приходят, запоминая только минимальное значение.
- Через некоторое время он выходит из калибровочного режима, вычисляет UART_DELAY на основе полученных данных (т.е. умножает длину нуля на коэффициент, предварительно проверив данные на адекватность) и далее использует его.
Требования к посылкам при этом минимальные: если во время «калибровочного режима» нужно, чтобы работал какой‐то ещё код, то «наличие последовательности 101 в посылке, в т.ч. подойдёт 10 и стоповый бит». Если не нужно, то ещё подойдёт посылка с единицей в наименее значимом бите: тогда можно не использовать прерывания. (Кстати, строка «стандарт» на момент написания комментария на всей странице встречается ровно два раза — все в том комментарии, на который я сейчас отвечаю.)
Конечно, «калибровочный режим» не нужен, если вы абсолютно уверены в том, что можете одной константой обеспечить приём и передачу на одной из стандартных скоростей в течении всего времени работы устройства и свободная память вам пригодится больше дополнительного запас прочности.
Про подстройку на основе длины нуля во время приёма: я подумал, как это будет выглядеть с программным UART и понял, что иметь такое в цикле задержки мне бы не понравилось. В целом примерно то же самое, что и с калибровочным режимом, только длина считается не во время калибровочного режима, а во время нормальной работы, а задержка вычисляется между приёмами.
PS: Чтобы исключить возможное недопонимание, привожу как я видел компенсацию дрейфа во время приёма по переходным состояниям: вместо nop’ов получение значения с порта, сравнение с сохранённым предыдущим, brne на команду вперёд и установка счётчика в UART_DELAY/2 − C (поправка на дополнительные затраты времени на установку счётчика).
Стандартный протокол UART, не подразумевает автокалибровку по дополнительным входным последовательностям и работает на заданных частотах, поэтому я неправильно интерпретировал Ваши «нули».
Касаемо же этой подстройки по длительности периода бод на неизвестной частоте, идея хорошая и нужная, но опять же — жирно использовать ее для микроконтроллера с 1кб флеша и 64 байтами SRAM. В случае же режима корректировки фазы, код не сильно увеличивается, теряется возможность автоопределения скорости, но повышается точность приёма при дрейфе частоты. И этот вариант вполне самодостаточен для программной реализации UART, используемого для «ОТЛАДКИ» микроконтроллера.
Ситуация с подсчетами еще усложняется тем, что разные команды имеют разное количество циклов, в этой ситуации требуется унификация задержки, которую я в свою очередь развернул, чтобы сократить размер кода и получить возможность более точной подстройки.
Поэтому любая команда, будь то условный переход, или чтение линии порта, будет выполняться дольше NOP, и в случае получения значения с порта, сравнения, и условного перехода, это уже в ПЯТЬ раз больше, и получение неустранимого джитера в приёмнике.
Спасибо за Ваш интерес к теме и описанные варианты обработки, любые решения, даже неудачные позволяют приблизиться к одному — идеальному.
Эксперименты не заканчиваются, и как я сказал выше — я написал другой приемник, с автокоррекцией фазы, но меня не устраивают некоторые нюансы в нём. Если я устраню эти нюансы и удастся оптимизировать код, я форкну проект на гитхабе и отпишусь об изменениях.
А пока, будем считать это решение законченным и самодостаточным.
Спасибо еще раз!
Проблемы с условным переходом по идее должны решаться константой (C в комментарии). Изменение длительности цикла ожидания в данном случае только ограничивает максимальную скорость передачи.
Спасибо за беседу, интересно пообщаться с человеком с иным взглядом на проблему: я на работе (пока?) не пишу программы для микроконтроллеров с жёсткой экономией памяти (и отсутствием отдельного UART блока), но специфика работы (тестирование микросхем на радиационную стойкость) предполагает, что внутренняя частота во время испытаний уплывёт с немалой вероятностью, иногда даже намного сильнее, чем по datasheet’у. Поэтому автокорректировка на одной из сторон видится довольно полезной вещью — и если использовать строго стандартные скорости, то этой стороной будет менее ограниченный микроконтроллер. (Правда мы, если частота действительно уплывёт за границы, указанные в datasheet’е, можем написать, что схема не прошла испытания. Впрочем, часто внутреннего генератора нет или его не предполагается использовать.)
У вас код под гну лицензией, она не запрещает использование кода в коммерческих проектах. И более того, я могу использовать ваш код в моём проекте и если кому-то понадобиться, то я могу предоставить ваш исходный код.
По поводу коммерции — бесспорно, ведь не я придумал протокол обмена, здесь я имел ввиду сам код и его «несвободность». То есть продажу кода как своего личного, любого измененного приложения — как своего, либо в составе собственного продукта в качестве библиотеки, с нарушением авторских прав. Во всех остальных случаях, с соблюдением этикета, вы можете пользоваться так, как считаете нужным, с указанием источника, автора, и исходного кода.
Сравнить помехоустойчивость программных и аппаратных реализаций можно, добавив искусственные положительные и отрицательные иголки (кратковременные переходы 1->0->1 и 0->1->0) в программный генератор UART, и подключив на прием одновременно обе реализации
Очевидно, что сэмплирование осуществляется в любой точке временного диапазона бита на конкретной частоте. Стартом (и началом отсчета) служит стартовый бит, в результате чего происходит вызов функции прерывания которая, в свою очередь выполняя пролог, учитывает текущее смещение к нулевому биту таким образом, чтобы обеспечить попадание в окно.
Не менее очевидно, что если в момент сэмплирования будет попадание в помеху, то результат будет записан безусловно. Однако для таких целей существует контроль по сумме и четности, которые в данной реализации не предусмотрены, но могут быть организованы с минимальными усилиями, силами разработчика.
Любые другие алгоритмы помехоустойчивости на столь низкой частоте МК, гарантированно ведут к снижению рабочего диапазона частот, раздувают код и количество задействованных ресурсов (речь о регистрах).
Обычно программные реализации достаточно наивны именно потому, что поддержка сложного конечного автомата в реальном времени сильно снижает скорость, и увеличивают долю ресурсов МК, а жесткие условия по помехам на столе или в макете практически не встречаются. Вот я и хотел узнать, у вас так же, или нет
Проект версии 2.0, для Atmel Studio 7.0
Хабровчане, выложите код в текстовом виде, пожалуйста, ну нет у меня пока Atmel Studio.
Как раз активно заинтересовался софтовым UART, и тут на тебе, такая удача!
«Проект для Atmel Studio» — это вообще‐то набор самых обычных исходных файлов плюс настройки studio. Если у вас нет studio просто игнорируйте настройки и создайте Makefile/CMakeLists.txt/… для своей среды. Исходный код там есть, он не зашифрован и даже не обёрнут в XML (и я не помню, чтобы так делала вообще какая‐либо IDE). Конкретно в этом случае — файл Tiny_UART/main.c.
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 массивы.
И спасибо за Ваше тестирование!
Теоретически можно прикрутить к любым AVR? Для атмега8 сейчас мешают
267 GIMSK = (1<<PCIE); // Enable External Interrupts //
268 PCMSK = (1<<RxD); // Enable accorded Interrupt (PCINT3) //
У Меги8 только два прерывания int0(PD2) и int1(PD3). Если перенастроить приемник, и переназначить пин приемника на этот порт, то других изменений не потребуется.
Но ведь у Меги8 есть аппаратный UART?
Что делают две строки, которые компилятор не скушал с мегой? (Простите за дилетантские вопросы).
Micro-UART для МикроКонтроллера (ATtiny13A)