Рад приветствовать вновь всех посетителей этого славного места в интернете, чтобы привнести еще один скромный вклад.
В этот раз, он касается уже довольно избитой темы, вокруг которой всегда ходят споры, а когда суть доходит до дела оказывается, что все её реализации либо заточены для конкретной задачи, конкретным человеком, в конкретных условиях, либо не работают совсем.
Речь пойдет о программной реализации UART, для микроконтроллеров AVR компании Atmel, интеллектуальной собственностью которой с некоторых пор владеет компания Microchip.
Итак, следуя традиции, присказка
Мысль в голове на тему этой статьи закралась давно, благодаря одному спору в комментариях, что, мол: программный UART для микроконтроллера, работающего от внутреннего генератора, очень нестабильный, и на нем нельзя получить высокой точности на скоростях выше 9600 бод, ввиду сильного дрейфа задающей частоты этого самого внутреннего генератора
И вот однажды, мне вновь захотелось проверить этот тезис (к сожалению я не смог найти ни статьи, где это обсуждалось, ни обсуждения. Хотя я уверен, что это происходило здесь, на Хабре), узнать:
- На сколько велика погрешность;
- На сколько велика максимальная скорость передачи;
- На сколько компактный код я смогу получить, используя все свои знания.
Таким образом, начальный смысл этой работы был скорее — спортивным интересом.
Но когда были получены первые результаты, я решил продемонстрировать начальный размер кода на канале Анархическая электроника мессенжера Telegram, и неожиданно тема начала резонировать. К обсуждению начали подключаться новые люди, и скоро спор вернулся в привычное русло, пользователь канала @RT_1_98 — очень яростно настаивал на несостоятельности этой работы (за что ему отдельное спасибо, он меня только распалил сильнее — доказать обратное ;) ) Тем временем, один из администраторов канала — @Byte_kgd, предложил не останавливаться на достигнутом, а довести работу до логического завершения: к передатчику написать еще и приёмник!
И вызов был принят…
Не мудрствуя лукаво, я выбрал простой путь.
Нет, не потому что я боюсь сложностей, а потому что я так же хотел, чтобы полученные результаты были применимы другими людьми, чтобы код легко читался, и благодаря простоте идеи, он мог поддаваться модификации под другие микроконтроллеры. Поэтому я сформировал калибровочные константы задержек под группу частот от 9600 до 57600 бод, и написал функцию передатчика, которая отправляет байт данных на линию, полностью отказавшись от прерываний таймера.
Конечно же я сел писать в своей любимой IDE — Atmel Studio. В первоначальном варианте код тела функции (на инлайновом ассемблере) занял около 90 байт (45 команд), благодаря которой я выставил константы задержек, протестировав выполнение в симуляторе Proteus.
Работоспособность меня очень вдохновила, так как я уже уложился по размеру кода, меньше любых других реализаций.
Немного поразмыслив, я переписал функцию для отправки строк, оканчивающихся нулём, что позволило отправлять заранее подготовленный буфер определенного размера директивой #define, а кроме того, удалось сократить размер кода до 76 байт (38 команд)!
В этот момент я и решился показать результаты тестов в Telegram, после чего было принято решение написать и приёмник.
Собирался с мыслями я долго...
Не давала покоя одна главная мысль: раз уж я решил отказаться от прерываний таймера в передатчике, было бы неплохо построить на этом же принципе и приёмник… но как?!
И я решился на отчаянный шаг: написать обработчик прерывания таким образом, чтобы весь приём происходил в один вызов.
Да, это плохая практика, и да — это непристойное поведение! Но во-первых, объемы памяти микроконтроллера не позволяют хранить большой буфер, это накладно для основного кода, а во-вторых, я решил написать обработчик так, чтобы он умел обрабатывать:
- Переполнение буфера (реализовано выходом из прерывания при достижении границы);
- Разрыв линии приема (тестовая эмуляция в Proteus, показала полное зависание, в результате чего было принято решение о контроле такого состояния);
- И, собственно, штатное поведение (с заполнением флага успешного приёма данных);
Когда написание кода прерывания было завершено, я провел тесты на стабильность приёма/передачи, построив такую модель в Proteus:
Один микроконтроллер выполнял передачу заранее заданного буфера с тестовым содержимым:
char Rx_Buf[Buf_Size]={'T','e','s','t','0','1','2','3','4','5',10,13,0};
А второй тем временем, приняв его, сравнивал с эталонным, и в случае несоответствия, отправлял по линии данных во второй терминал сообщение:"Error!", после чего запустил симуляцию и лег спать…
… На утро
когда я проснулся меня ждало разочарование: терминал был усыпан нежелательными сообщениями об ошибках… Но когда я их посчитал, оказалось все не так плохо: за 9 часов работы, на скорости 57600 бод было 11 сообщений. Это значит что для 3888000 байт, произошло не более 143 ошибок (в случае полного несоответствия буфера).
… Так это же успех!...
Действительно, вероятность ошибки оказалось на несколько порядков ниже, и я списал это на высокую точность симуляции Proteus.
Пересмотрев код приёмника и передатчика еще раз, я вновь сократил передатчик до 60 байт (30 команд), и оптимизировал приёмник.
После чего передал код @Byte_kgd, который пламенно захотел по-участвовать в тестировании, с небольшим отступлением: в наличие имеется только ATtiny2313.
И он мне злорадно и пафосно сообщил: "… сафсем ниработаит! ваапще!..."
Вот так фиаско, братан...
… Настал новый год, съеден весь оливье, выпито все шампанское, вот уже горят ёлки за окном.
А код все не работает… не поддается, ни в какую!
И наконец до меня доходит, что в коде нужно половину перечеркнуть, половину переписать, а третью половину нужно вывернуть наизнанку.
И тут Остапа понесло! (с) И. Ильф Е. Петров.
Во-первых код передатчика был сокращен до 54-ёх байт (26 команд), во-вторых была устранена фатальная ошибка прерывания: несброшенный флаг, в третьих и четвёртых — были устранены ошибки архитектурного характера, такие как: неправильный возврат флага, джитер, несоответствие длительностей передачи и приёма и т.д. Код приёмника был переписан с самосинхронизацией по STOP-биту, что несомненно повысило качество приёма.
Кроме того, я наконец достал логический анализатор, расчехлил программатор, UART-преобразователь, и достав драгоценную ATtiny13A из своих закромов, решил ВПЕРВЫЕ за всё это время, проанализировать работоспособность в железе!
Забегая вперед скажу: ЧУДО ПРОИЗОШЛО!
Прошитая «тинька» показала в терминале «пинг», и ответила с улыбкой: Test012345.
Использование логического анализатора показало расхождение в задержках в Proteus и на реальном железе. Благодаря чему, удалось унифицировать задержку одной константой для приёмника и для передатчика:
А так же, позволило найти теоретический предел для максимальной скорости передачи в нормальных условиях:
Я умышленно не стал выбирать другой скрин, показав ошибки приемника, который не смог работать на таких скоростях, ввиду того, что выполнение пролога и эпилога в заклинании прерывании, успевают пропустить стартовый бит. Однако в несколько измененной версии, я смог добиться уверенного приема на скорости 512 кбод, но ввиду объективных причин, решил оставить код таким как есть, с ограничением на приём/передачу в 250 кбод.
Итог
В результате проделанной, весьма изнурительной работы,
- Получился самый компактный код в мире!
- Не использующий немаскируемые (INT0 и INT1) прерывания
- Не использующий прерывания таймера, освобождая все доступные для использования каналы ШИМ
- Позволяющий гибко оперировать настройкой порта, выбирая нужные пины для линий отправки и приёма данных
- Позволяющий стабильно передавать и принимать данные на скоростях от 9600 до 250000 бод
- Передавать строки из флэш-памяти, благодаря соответствующей функции
- Конвертировать и передавать данные в двоичном, hex-, и десятичном виде (для последнего реализованы две функции для передачи байт и слов данных), благодаря соответствующим функциям
На этом изображении хорошо видно, что в демонстрационном примере использован весь доступный набор функций, при общем размере демонстрационного проекта — 920 байт занятой флэш-памяти. При правильной организации, можно использовать только нужные функции, что позволит сильно минимизировать затраты для основного проекта, позволяя при этом производить отладку микроконтроллера через UART. Код легко модифицируем под другие контроллеры (для примера — ATtiny85A), при этом позволяя увеличить размер буфера и количество используемых функций, не в ущерб производительности и работоспособности основной задачи. Код не выносился в библиотеку, для наглядности и удобства анализа и обучения. Контроль переполнения буфера было решено не реализовывать полноценно. Это значит что при переполнении флаг переполнения не устанавливается, вместо этого происходит выход из прерывания, а дальнейшее поведение определяет входящий поток. Таким образом микроконтроллер вновь уходит в прерывание и затирает принятый буфер новыми данными. Чтобы этого избежать, не нужно его кормить порциями данных, большей длины, чем определенный размер буфера. На этом было решено остановиться, после нескольких обсуждений, чтобы не раздувать код обработчика. Главное условие выполнено — микроконтроллер не зависает и продолжает работу в штатном режиме.
Еще одним отступлением от традиций в этот раз, стало отсутствие выдержек кода в виде скриншотов. В прошлой статье в первом же комментарии был на эту тему гнев :)
А раз так, то пусть код лежит на Githab'е, и как следствие, отображает всю красоту в не форматированном им виде:
Проект версии 2.0, для Atmel Studio 7.0
Это вторая версия проекта, и последняя. Все изменения которые могут быть произведены, могут касаться только калибровочных задержек. На данный момент, для всего диапазона были проведены испытания и выполнены успешно.
Желаю скорейшего наступления тепла, в ваши дома, на ваши улицы и в ваши сердца!
Всем успехов, творений, свершений!
Использование в коммерческих проектах, перепродажа исходного кода, использование с целью наживы и любых корыстных целях, запрещено. Исходные тексты распространяются бесплатно как есть, в случае использования на других сайтах, либо в других источниках, указание автора и уведомление о размещении — обязательно!
Комментариев нет:
Отправить комментарий