Всем привет!
В данной статье мы рассмотрим основные методики симуляции и тестирования прошивок для микроконтроллеров AVR.
Для чего нужно тестирование?
Любое тестирование программного обеспечения нужно в первую очередь для того, чтобы убедиться в работоспособности программы. Также тестирование обеспечивает проверку программы в целом и/или ее отдельных частей определённым требованиям. Тем не менее, пройденные тесты не гарантируют полного отсутствия ошибок в проекте, они лишь повышают вероятность их обнаружения на ранних этапах разработки.
Ошибка во время компиляции ничего не стоит. На этапе первичного тестирования она стоит времени разработчика. На стадии альфа- или бета-тестирования цена ошибки возрастает. Стоимость исправления ошибок, найденных после запуска продукта в серию, может перекрыть стоимость всего проекта. В лучшем случае придётся выпустить хотфикс (в hardware-проектах это не так-то просто), в худшем надо будет откатывать всю партию. Основной задачей тестирования является экономия средств и времени.
Сравнение тестирования в программных и аппаратных проектах
Программное тестирование проводится на стандартизированном «железе», зачастую на высоком уровне абстракции. Стандартное окружение и готовые фреймворки для тестирования позволяют добиться однозначных результатов тестирования вне зависимости от «железа», на котором продукт запускается.
В случае с тестированием микроконтроллеров (и «железных» проектов вообще) дела обстоят проще, но есть и свои сложности. Тестировать проще из-за того, что микроконтроллеры (МК) в основном предназначены для решения простых задач. Например, это может быть мигание светодиодами, опрос датчиков, несложная обработка данных, вывод на дисплей, коммуникация с периферийными модулями. Основная сложность в том, что окружение, где работает программа, зависит от модели и производителя контроллера. Микропрограмма работает на низком уровне. С учетом жестких требований к быстродействию, тестирование становится непростой задачей.
Какой способ тестирования выбрать в зависимости от задачи?
Существует три варианта запуска тестов для встраиваемых систем. Перечислим все:
- Запуск локально (на хостовой машине).
Преимущества: тесты быстро запускаются и отрабатывают, доступ к устройству не нужен.
Недостатки: ограниченная область тестирования, работать с внешней периферией не получится. Хорошим примером использования данного способа является тестирование не зависящего от платформы вычислительного алгоритма использующего данные с датчиков. Тестировать такой алгоритм с реальным источником данных было бы очень неудобно. Гораздо лучше подготовить набор данных заранее. Это можно сделать, используя небольшую программу-логгер на не обработанных данных с датчиков. - Запуск на симуляторе.
Преимущества: нет обязательных требований наличия самого устройства, возможность симуляции всего списка необходимой периферии.
Недостатки: ограниченная точность симуляции железа и окружающей среды, трудность создания и конфигурации такого симулятора. - Запуск на самом МК.
Преимущества: можно работать с периферией МК, прошивка будет отрабатывать так же, как в продакшне.
Недостатки: обязательный доступ к самому устройству, плюс цикл тестирования будет долгим из-за необходимости постоянно перепрошивать МК.
Следствие: очень тяжело добиться автоматизации тестирования.
В данной статье мы разберем первые два способа как наиболее перспективные для автоматизации.
Тестирование локально
Локальные тесты очень хорошо подходят для тестирования частей прошивки, не зависящих от окружения. Примерами могут быть любые вычислительные алгоритмы, различные контейнеры, списки, очереди, деревья, высокоуровневые протоколы обмена, конечные автоматы и др.
Рассмотрим пример проекта для микроконтроллеров AVR с возможностью локального тестирования платформо-независимых частей прошивки.
Тестирование прошивки, совместимой с МК AVR
Для МК AVR наиболее удобной и производительной средой разработки является Atmel Studio. К сожалению, данная среда не кроссплатформенна и доступна только для Windows.
Для большей наглядности я предпочел написать это проект вручную.
Пишу в VSCode на Ubuntu, для компиляции и линковки прошивки использую AVR GNU Toolchain, тесты и симулятор компилирую при помощи gcc. Процесс сборки проекта (компиляция, линковка, прошивка) осуществляется через утилиту make. Данный подход позволяет автоматизировать выполнение тестов и загрузку прошивки в целевую систему.
Для примера давайте рассмотрим прошивку для микроконтроллера Atmega1284, реализующую функционал простого термометра.
Замер температуры происходит считыванием напряжения на делителе (в состав которого входит терморезистор), преобразования значения АЦП в температурные показатели и отображения на обычном 1602 (hd44780) ЖК-экране.
Такой простой функционал общения с внутренней и внешней периферией сводится к оперированию регистрами микроконтроллера. Тем не менее, в прошивке имеется функционал, который необходимо протестировать (преобразование значений АЦП в температуру и вывод на дисплей).
Рассмотрим пример нативного тестирования функционала прошивки.
Репозиторий проекта: https://github.com/maddevsio/AVR_Testing
Для компиляции вам потребуется AVR GNU Toolchain.
Структура проекта следующая:
Создать проект с тестами нужно так, чтобы его код можно было компилировать как под целевую систему, так и для кроссплатформенного использования — задача не простая. Необходимо использовать два компилятора, делая одни модули платформозависимыми, а другие — нет. Остановимся на main.c подробнее для наглядности.
При локальном тестировании в main.c создается 2 секции: для тестирования функционала и для реализации основного цикла программы.
Рассмотрим тестирование преобразования значений АЦП в температуру для последующего использования (файл src/tests/test.c):
В данном примере нам достаточно указать заголовочный файл исходников тестируемого модуля и реализовать проверку операции конвертирования значений АЦП в температуру с заданной погрешностью.
Помимо написания тест-кейсов для Unity и указания пути к фреймворку, необходимо также добавить специальные директивы компилятора.
В нашем случае используется сравнение чисел с плавающей точкой повышенной точности. По умолчанию в фреймворке отключено тестирование типов double, для включения нужно добавить директивы:
После выполнения make tests, Ruby сгенерирует исполняемые файлы тестов, и произойдет их запуск.
Тестирование на симуляторе
В большинстве аппаратных проектов микроконтроллер используется для взаимодействия с внешней и внутренней периферией, поведение которой очень сложно а иногда и невозможно имитировать во время тестов. В решении данной проблемы очень помогают симуляторы целевых систем.
Для микроконтроллеров с архитектурой AVR существует несколько систем симуляции:
Atmel Studio — среда разработки под AVR-микроконтроллеры, о которой упоминалось ранее. (Сделана только под Windows.) Она содержит в себе встроенный симулятор почти всех моделей микроконтроллеров и удобный инструментарий для создания как внутренней, так и внешней периферии и управления ей. Недостатком является отсутствие кроссплатфоменности и громоздкость — автоматизировать проведение симуляции тяжело.
Simulavr — открытый кроссплатформенный проект на C/C++, предлагающий свои системы симуляции микроконтроллеров AVR. Присутствует несколько моделей микроконтроллеров, возможность писать скрипты отладки на Tcl/Tk и Python. Из недостатков стоит отметить сложный механизм добавления новых моделей МК в симулятор, скудную документацию и отсутствие поддержки проекта на протяжении нескольких лет.
simavr — относительно новый (и при этом довольно гибкий) кроссплатформенный проект для написания симуляторов МК AVR на C/C++. Из-за схожего названия его можно спутать с предыдущим инструментом, тем не менее, это совершенно разные проекты. В simavr есть большое количество моделей МК AVR, архитектура позволяет с легкостью добавлять новые устройства и модели. Присутствует интеграция с PlatformIO (расширение vscode для разработки встраиваемых систем), а также приведены понятные примеры использования инструмента с различными видами периферии. Недостатки: отсутствие описания сборки проектов, довольно не очевидная документация.
Я выбрал simavr как наиболее перспективный вариант.
Для начала использования simavr достаточно склонить репозиторий и собрать проект.
После успешной сборки можно проверить работоспособность симулятора, прогнав тесты или запустив примеры работы. Тесты находятся в директории tests, примеры работы с симулятором лежат в examples. Для запуска исполняемых файлов необходимо создать символьную ссылку на исходники в директории с прошивкой. К примеру, в директории tests:
создание символьной ссылки в папке simvar/tests и запуск теста:
В директории examples расположены папки с исходниками отладочных стендов (далее просто “стенды”) и файлами распространенных видов периферии микроконтроллеров. Для большего эффекта погружения можно запустить примеры с графикой — такие, как board_hd44780, board_ssd1306.
Simavr предоставляет широкий инструментарий для видов задач, которые перечислены ниже.
- Разработка собственных виртуальных отладочных стендов с микроконтроллерами и периферией.
- Создание виртуальных электронных компонентов.
- Управление поведением симуляции на временных отрезках вплоть до такта микроконтроллера.
- Подключение отладчика avr-gdb.
Полное описание возможностей симулятора можно найти в репозитории проекта.
Структурно любой симулятор на simavr представляет из себя исходники стенда, периферии, скомпилированную прошивку для микроконтроллера (кстати, в симулятор можно загружать ту же самую прошивку, что и в реальное устройство, изменения не требуются).
Для примера тестирования давайте рассмотрим тесты того же самого термометра, только с использованием симулятора (ветка simavr-testing).
Структура директории src/tests/sim:
Принципиальная схема виртуального стенда следующая:
Simavr имеет очень простую, хоть и не совсем очевидную структуру проектов.
Рассмотрим наиболее важные моменты имплементации отладочного стенда:
main_wrapper.c — обертка точки входа для прошивки. Позволяет предоставить компилятору и симулятору дополнительную информацию о прошивке, напряжении питания и других параметрах (полное описание всех параметров можно найти в simavr/simavr/sim/avr/avr_mcu_section.h)
adcToLcd.c — исходники самого стенда с периферией, описывающие манипуляции с портами передачи данных и оперирование временными интервалами между действиями периферии.
При инициализации прошивки стенд ищет указанный в firmware elf-файл, затем происходит создание МК и заливка прошивки.
Для инициализации и работы с дисплеем в директории parts располагаются исходники ЖК-экрана на контроллере hd44780 (для проекта я просто взял его из simavr/examples/parts и немного переписал функцию вывода данных с экрана на парсинг и возвращение double-значения).
Далее применяется функция подключения частей периферии к контроллеру setConnections, которая использует такие методы как:
— возвращает указатель на уникальный идентификатор PIN порта ввода-вывода, принимая при этом идентификатор контроллера, порт, PIN.
(Подробное описание см. в simavr/simavr/sim/sim_io.h)
— функция подключения одного PIN ввода-вывода(периферия или микроконтроллер) к другому.
пример использования:
— подключение PIN 4 на порту D микроконтроллера к PIN RS дисплея
Принудительно отправлять сигналы на порты ввода-вывода можно при помощи следующей функции:
— принимает собственно идентификатор порта и требуемое значение. value может принимать 1 либо 0 для цифровых входов, либо значение в милливольтах для аналоговых входов.
В simavr реализован удобный механизм установки времени на совершение внешних воздействий посредством регистрации таймеров, по срабатыванию которых выполняется привязанный обратный вызов.
Для регистрации таймера существует функция:
— на вход таймера принимается идентификатор контроллера, на котором требуется отсчитывать время в микросекундах (либо циклы), собственно время, через которое сработает таймер, обратный вызов, который при этом сработает, и его опциональные параметры.
В данном контексте обратный вызов выполняет функцию считывания вывода экрана, переключения на вывод АЦП следующего значения напряжения и регистрацию нового таймера. Для адекватного считывания информации с экрана посредством таймеров реализована задержка между считыванием — время ожидания до заполнения дисплея новыми данными.
Запуск каждого такта микроконтроллера выполняется функцией:
— такой механизм позволяет приостанавливать симуляцию на некоторое время для верификации данных или проведения вычислений. Он также служит механизмом запуска симулятора в отдельном потоке.
В нашем примере используется простая реализация с запуском симуляции в одном потоке, сбором результатов симуляции и проведением тестов собранных данных.
Чтобы не загромождать код при двух методиках тестирования, было принято решение вынести тест-кейс в отдельный файл adc-temp_test.c, который подключается к проекту как при компиляции тестов на Unity, так и при использовании симулятора.
Для компиляции и запуска симуляции достаточно внести в src/tests/sim/Makefile, в переменную $(SIMAVR) абсолютный путь к simavr и выполнить в директории src:
В отличие от модульных тестов, тесты с использованием симулятора (по сути это интеграционные тесты) позволяют полностью оценить правильность работы прошивки.
Ярким примером тестирования в нашем проекте является симуляция преобразования АЦП. Из-за ограничений разрядности АЦП она дает намного большую погрешность вычислении низких значений, чем выясняется во время модульных тестов.
При желании для больших проектов можно легко интегрировать симулятор с фреймворком Unity и автоматизировать процесс тестирования через CI/CD.
Запуск тестов на «железе»
Запуск тестов на «железе» в основном происходит в ручном режиме, с использованием систем внутрисхемной отладки — таких, как JTAG.
Для автоматизации тестов на «железе» приходится эмулировать поведение внешней периферии при помощи другого микроконтроллера. Появляются вопросы к адекватности поведения эмулированной периферии. При этом написание тестов часто перекрывает по затратам написание основной прошивки. В любом случае, такой подход обеспечивает максимальную точность и приближенность работы системы к реальным условиям. Описание методик подготовки тестов заслуживает отдельной статьи, и мы затронем эту тему в следующих публикациях.
Заключение
О тестировании прошивок можно рассказать ещё очень многое. Стандартной методики для тестирования прошивок встраиваемых систем не существует. Тем не менее, общие рекомендации при написании тестов и симуляторов для конкретных моделей микроконтроллеров AVR позволяют полностью автоматизировать процесс тестирования прошивок. Надеюсь, что данная статья помогла вам приобрести базовые знания о тестировании микроконтроллеров AVR.
Ранее статья была опубликована тут.