Посты

Переменное количество аргументов функции в Си

Некоторые стандартные функции в Си принимают любые аргументы, которые вы только им подсунете. Самый известный пример такой функции — всевозможные *\*printf и **scanf. Вы задумывались, как это сделано? Или даже хотите сделать так сами?

Ответ на ваши вопросы — макросы variable arguments из заголовочного файла stdargs.h. Они предоставляют вам набор аргументов функции как непрерывный список, из которого можно последовательно считывать все переданные аргументы.

Вызов функции в ассемблере

Но для начала вспомним, как устроен вызов функции и передача ей аргументов. Любой вызов функции типа void some_func(param1, param2, param3) будет скомпилирован в такой ассемблерный код:

push param3
push param2
push param1
call some_func

Инструкция push помещает аргумент на вершину стека и продвигает указатель назад по памяти. После выполнения трёх push-ей в стеке аргументы будут лежать в «естественном» порядке: param1, param2, param3. В отдельном регистре процессора будет лежать указатель на первый параметр.

Инструкция call вызывает функцию some_func — переносит туда указатель выполнения. Функция начинает читать свои аргументы, используя инструкцию pop, которая поочерёдно вытащит из стека все параметры, начиная с первого (конечно, нужно учесть длину каждого аргумента).

Отладочная консоль через Telnet

Самый удобный способ для вывода отладочной информации — это Telnet. Конечно, он обязательно требует наличия интерфейса Ethernet, но если он в девайсе уже есть, то телнетом просто грех не воспользоваться: его очень просто добавить к проекту. Если сети нету — воспользуйтесь примером из статьи консоль через UART.

Сначала пару слов о протоколе. Первые описания интерфейса появились ещё в 1968 году в RFC под номером 15 — редко встретишь упоминания настолько раннего RFC, это однозначно говорит о том что протокол очень прост и был крайне востребован ещё на заре развития IT. Потом, в 1983 вышел уже RFC 854, благодаря которому протокол стал утверждён и вошёл в перечень стандартов STD 8, один из первых стандартов IETF.

Это поистине простейший из всех протоколов передачи данных, он представляет собой простой текст, передающийся по порту 23 (TCP). Конечно, связь дуплексная (одновременно могут передавать и принимать оба участника), причём на низком уровне нет различия между сервером и клиентом, протокол симметричен.

Другая особенность — telnet это просто «труба для данных», никаких изменений в данные он не вносит (за исключением управляющих последовательностей).

На самом деле, единственное что нужно помнить про telnet — перенос строки делается обязательно двумя символами: CRLF или /r/n или #10#13. Первый символ переводит каретку к началу строки, второй передвигает каретку на следующую строку.

Профайлер

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

Грамотная оптимизация невозможна без профилировки — ведь, не зная точного времени работы всех модулей,  вы можете взяться за оптимизацию какой-то одной функции (занимающей 10% времени), а окажется что вся проблема в другой (занимающей 80%). Оптимизировав первую функцию даже в два раза — вы выиграете всего 5% времени работы приложения, в то время как двукратная оптимизация второй функции даст целых 40%.

Модуль, выполняющий эти замеры, называется профайлер (profiler). Типичные профайлеры имеют гораздо больше функций, чем я назвал: постройка дерева вызовов, слежение за кэшем и памятью, автоматическое определение наиболее долгих функций, и наоборот, функций которые никогда не получают управление. Однако для микроконтроллеров реализовать всё перечисленное достаточно тяжело, да и у нас нет такой задачи.

Напишем свою компактную (но обязательно удобную!) реализацию профайлера для STM32 на Си. Конечно, это подойдёт и для любого другого однопоточного окружения.

Преобразователи уровня логических сигналов, часть 1. Однонаправленная передача

У меня очень часто возникает задача преобразования уровня логических сигналов. К примеру, в недавнем проекте мне потребовалось как-то ввести в плату снаружи высоковольтный UART. Это был не обычный RS-232 с инвертированием и уровнями +12 (лог. 0) .. -12В (лог. 1), а именно что UART с уровнями 0 (лог. 0) .. 12В (лог. 1). Такой нестандартный протокол, кстати, раньше иногда встречался в ноутбуках.

Другой, даже более частый случай — согласование 3.3-вольтовых и 5-вольтовых микросхем. К примеру, такая необходимость практически всегда возникает при стыковке современных микроконтроллеров и каких-нибудь древних микросхем интерфейсов или силовых мостов. Ещё один пример это использование цветных ЖК-дисплеев (они часто требуют 1.8В) и соединение с ПЛИС, напряжение на выходе которых может доходить до 1.0-1.2В.

Ну и наконец, третий случай — использование низковольтных низкопотребляющих микросхем. Один из ярких примеров это магнитометр в составе датчика LSM303DLHC, который требует питания 1.8В. У него вроде как есть отдельный вход для питания ног I2C-интерфейса, но напряжение там не может превышать напряжения питания, и возникает задача стыковки 1.8 и 3.3В.

Назовём уровень напряжения низковольтной части Vcc_L, а высоковольтной — Vcc_H.

Чаще всего модули с разным уровнем напряжения питания всё-таки имеют общую землю, и я буду это неявно подозревать во всех следующих схемах. Таким образом, уровень лог. 0 нас не интересует потому что он передаётся и так хорошо. Все проблемы возникают при передаче логической 1, когда к примеру на вход микросхемы, рассчитанной на 3.3В попадает 5 вольт. В этом примере Vcc_L = 3.3В, Vcc_H = 5В.

Давайте также условимся различать «источник» сигнала — это тот элемент, который генерирует сигнал, и «приёмник», который принимает этот сигнал.

Передача большее → меньшее

Диод, замыкающий сигнал на линию питания

Простейший вариант, при котором вся схема сопряжения сводится до одного резистора.

Например, мы используем микросхему которая питается от 3.3В, а снаружи приходит 5 вольт. В одном из моих недавних проектов я использовал микросхему приёмника интерфейса RS-423, у которой на выходе было напряжение 5 вольт, и мне нужно было подключить её к МК STM32, который питается от 3.3В. Минимальное напряжение питания приёмника составляло 4.5 вольта, так что я не мог запитать её от тех же 3.3В.

Внутри STM32 около каждого вывода стоят защитные «диодные вилки» — последовательно включенные диоды (часто это диоды Шоттки), которые предохраняют пин от напряжений выше Vcc и ниже GND.

protection_diodes

При попадании 5В на такой 3.3В вход, верхний диод открывается, пропуская ток с пина на Vcc, при этом на диоде падает напряжение порядка 0.3В.

Zepto.js

Есть такая фронтенд-библиотека, называется Zepto.js. Она является прямой заменой для jQuery, но отбрасывает поддержку старых браузеров ради скорости работы и сокращения размера кода. Плюс к тому, она модульная.

jQuery долго обещали что распилят код на модули, которые можно будет подключать отдельно, но это так и не было сделано. Zepto же, наоборот, сделал такую возможность: выделил отдельно ядро и отдельно дюжину подключаемых модулей: AJAX, поддержка IE, работа с формами, анимации, поддержка тач-дисплеев и прочее. Есть онлайн-сборщик для создания своей собственной сборки библиотеки.

jQuery версии 2.1.3 в минифицированном варианте весит 82.3кБ, а Zepto — 25 кБ. Если же оставить только самые часто используемые модули (ядро и ajax), она станет весить 20кБ. Разработчики утверждают что обеспечивают практически полную совместимость с jQuery, единственное место где могут возникнуть проблемы — это анимация.

По словам разработчиков, Zepto быстрее — но интересно увидеть результаты тестов. Сделаем простой тест: вставку десяти тысяч div в страницу с использованием обоих библиотек, я подготовил пример на jsFiddle.

Как видно из примера, Zepto вставляет divы за 300 миллисекунд, jQuery — за 700. При этом Zepto ещё и втрое меньше весит.