«Серьезные» разработчики встраиваемых систем (читай: стмщики) время от времени любят шпынять голозадых «ардуинщиков», у которых среда разработки, помимо всего прочего, не поддерживает даже аппаратные отладчики с точками останова и просмотром значений переменных под курсором мышки или в специальной табличке в реальном времени. Что ж, обвинение вполне справедливо, окошко Монитора последовательного порта (Serial Monitor) плюс Serial.println — не самый лучший инструмент отладки. Однако грамотный ардуинщик сможет с легкостью парировать атаку и поставить зарвавшегося стмщика на место в том случае, если он (ардуинщик) использует модульные тесты.


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


Однако переход к использованию автоматических тестов в проекте требует специальной внутренней дисциплины, особого подхода к написанию кода и организации рабочего пространства проекта.


При подготовке к внедрению в проект модульных тестов следует иметь ввиду:


  • Тесты требуют дополнительного времени для написания кода (на самом деле, нет: время, потраченное на автоматические тесты, вполне сравнимо со временем, потраченным на ручную отладку того же участка, а на долгой дистанции оно еще многократно окупится), при этом код теста может превышать по размеру код тестируемого участка.
  • В покрытом тестами проекте может быть сложно проводить глобальную реорганизацию кода (рефакторинг) — особенно актуально на начальном этапе разработки, когда кодовая база и внутренний API еще не достаточно устаканились (с другой стороны, рефактор проекта, не покрытого тестами, повлечет все те же регрессии, просто вы про них не узнаете)
  • Нужно писать модули приложения так, чтобы их можно было запускать как в рамках приложения, так и внутри отдельных тестов
  • Необходимо проработать структуру и связи внутри проекта так, чтобы в нем нашлось место коду основного приложения, исполняемой прошивке основного приложения, коду тестов, исполняемой прошивке («запускальщик»/ланчер) для запуска тестов.

Я более не буду распространяться про философию модульного тестирования, а просто покажу, как технически внедрить простые модульные тесты в ваш проект на Ардуино.


Далее рассмотрим:


  • Несколько стратегий организации рабочего пространства проекта с модульными тестами с учетом особенностей платформы Ардуино.
  • Вариант «все в одном» (и код и тесты в одном файле скетча),
  • вынесение тестов в отдельный модуль в каталоге скетча,
  • вынесение тестов в отдельный проект.
  • Запуск тестов на устройстве,
  • запуск этих же тестов на настольном компьютере без загрузки на устройство, заглушки для API Ардуино

Выбор библиотеки для модульного тестирования


Нам нужен фреймворк модульного тестирования:


  • Для Си/С++
  • Должен работать на устройствах семейства Ардуино
  • Должен работать на настольных системах
  • Люблю легковесные библиотеки (моё персональное предпочтение)

Для программирования Ардуино используется язык С++ вперемешку с Си, поэтому, теоретически, пойдет любой фреймворк модульного тестирования для С++, но мы хотим запускать тесты и на настольном компьютере и на устройстве. Дело в том, что для Ардуино реализованы кое-какие вызовы стандартной библиотеки libc, но далеко не все, поэтому не каждый фреймворк, работающий с libc, скомпилируется для Ардуино. Верно и в обратную сторону: если фреймворк сделан специально для Ардуино, то он может не заработать на настольной системе с libc.


Я просмотрел несколько фреймворков и остановился на 2х:


  • ArduinoUnit: https://github.com/mmurdoch/arduinounit. В общем, он удовлетворяет ключевым исходным требованиям: работает как на Ардуино (очевидно из названия), так и на настольных системах (см раздел «En Vitro Testing» на сайте проекта), но на беглый взгляд показался тяжеловатым и я решил посмотреть другие варианты.
  • Библиотека Sput (Sput Unit Testing Framework for C/C++) https://www.use-strict.de/sput-unit-testing/. Это библиотека легкая настолько, насколько это возможно: всего один заголовочный файл, даже без пары с исходником «.cpp» (все сделано на нескольких макросах). Однако вывод сообщений идет через std::out (что совершенно естественно для libc), который на Ардуино как раз не реализован.

И все-таки мои симпатии перевесили в пользу sput, а проблему с std::out удалось решить несколькими исправлениями (заменой printf на sprintf+Serial.print).


В итоге получился проект sput-ino — порт библиотеки sput на платформу Ардуино с сохранением совместимости с настольными системами с libc


— пример однофайлового скетча с тестами
/sput-ino/examples/sput-ino-monolith/


— пример с разделением основного кода и тестов на модули
sput-ino/examples/sput-ino-modules/


— запуск тестов на настольной системе
sput-ino/example-desktop/


— пример с разделением основного кода и тестов на разные проекты — в отдельном репозитории
https://github.com/sadr0b0t/sput-ino-demo


Установим библиотеку


Просто клонируйте репозиторий git https://github.com/sadr0b0t/sput-ino.git в каталог $HOME/Arduino/libraries:


cd $HOME/Arduino/libraries/
git clone https://github.com/sadr0b0t/sput-ino.git

и перезапустите среду Ардуино IDE.


Или на странице проекта github https://github.com/sadr0b0t/sput-ino/ нажмите кнопку Клонировать или скачать > Скачать ZIP (Clone or download > Download ZIP), после этого установите архив sput-ino-master.zip через меню установки библиотек Ардуино: Скетч > Подключить библиотеку > Добавить .ZIP библиотеку....


Примеры появятся в меню Файл > Примеры > sput-ino (File > Examples > sput-ino)


Простой вариант: однофайловый скетч с кодом и тестами


image


При внедрении тестов в проект Ардуино придется учитывать некоторые особенности её сборочной системы. В простейшем случае проект (скетч) состоит из одного файла с расширением «.ino». При сборке файл «.ino» с незначительными изменениями конвертируется в «.cpp» (подключается заголовок Arduino.h и еще кое-чего по мелочи), сгенерированный файл компилируется в прошивку.


Создаем новый скетч
sput-ino/examples/sput-ino-monolith/sput-ino-monolith.ino


добавляем какой-то полезный код:


/**
 * @return a плюс b
 */
int a_plus_b(int a, int b) {
    return a + b;
}

/**
 * @return a минус b
 */
int a_minus_b(int a, int b) {
    return a - b;
}

/** 
 * Включить лампочку, если число четное
 * @param pin номер ножки лапмочки
 * @param num число
 * @return true, если число num четное
 */
bool led_on_even(int pin, int num) {
    if(num % 2 == 0) {
        digitalWrite(pin, HIGH);
    } else {
        digitalWrite(pin, LOW);
    }
    return num % 2 == 0;
}

Пишем тесты с библиотекой sput (подробнее документация: http://www.use-strict.de/sput-unit-testing/tutorial.html):


#include "sput.h"

/** Test a_plus_b call */
void test_a_plus_b() {
    sput_fail_unless(a_plus_b(2, 2) == 4, "2 + 2 == 4");
    sput_fail_unless(a_plus_b(-2, 2) == 0, "-2 + 2 == 0");

    // this one would pass on 32-bit controllers and would fail on AVR with 16-bit int
    sput_fail_unless(a_plus_b(34000, 34000) == 68000, "34000 + 34000 == 68000");
}

/** Test a_minus_b call */
void test_a_minus_b() {
    sput_fail_unless(a_minus_b(115, 6) == 109, "115 - 6 == 109");
    sput_fail_unless(a_minus_b(13, 17) == -4, "13 - 17 == -4");
}

/** Test test_led_on_even call */
bool test_led_on_even() {
    pinMode(13, OUTPUT);

    sput_fail_unless(led_on_even(13, 2), "num=2 => led#13 on");
    // would pass on desktop, might fail or pass on difference devices
    // (e.g.: Arduino Due - fail, ChipKIT Uno32 - pass)
    sput_fail_unless(digitalRead(13) == HIGH, "num=2 => led#13 on");

    sput_fail_unless(!led_on_even(13, 5), "num=5 => led#13 off");
    sput_fail_unless(digitalRead(13) == LOW, "num=5 => led#13 off");

    sput_fail_unless(led_on_even(13, 18), "num=18 => led#13 on");
    sput_fail_unless(digitalRead(13) == HIGH, "num=18 => led#13 on");
}

Комплектуем наборы тестов (тест-сьюты).


Все тесты в одном наборе:


/** All tests in one bundle */
int mylib_test_suite() {
    sput_start_testing();

    sput_enter_suite("a plus b");
    sput_run_test(test_a_plus_b);

    sput_enter_suite("a minus b");
    sput_run_test(test_a_minus_b);

    sput_enter_suite("led on even");
    sput_run_test(test_led_on_even);

    sput_finish_testing();
    return sput_get_return_value();
}

и по одному набору на каждый тест:


/** Test suite for a_plus_b call */
int mylib_test_suite_a_plus_b() {
    sput_start_testing();

    sput_enter_suite("a plus b");
    sput_run_test(test_a_plus_b);

    sput_finish_testing();
    return sput_get_return_value();
}

/** Test suite for a_minus_b call */
int mylib_test_suite_a_minus_b() {
    sput_start_testing();

    sput_enter_suite("a minus b");
    sput_run_test(test_a_minus_b);

    sput_finish_testing();
    return sput_get_return_value();
}

/** Test suite for led_on_even call */
int mylib_test_suite_led_on_even() {
    sput_start_testing();

    sput_enter_suite("led on even");
    sput_run_test(test_led_on_even);

    sput_finish_testing();
    return sput_get_return_value();
}

Здесь я делаю по одному набору на каждый тест плюс один набор на все тесты вместе. На устройстве ограничен ресурс флеш-памяти, все тесты могут не уместиться разом в одну прошивку, поэтому одиночные наборы можно включать/выключать, комментируя отдельные вызовы в главном скетче. Всеобщий набор удобно пускать одной строчкой на настольном компьютере (ну, и на устройстве тоже, если он там все-таки уместится).


Запускаем тесты здесь:


void run_tests() {
    Serial.println("#################### Start testing...");

    // comment out specific test suites if firmware does not
    // fit to device memory

    // Test suite for a_plus_b call
    mylib_test_suite_a_plus_b();

    // Test suite for a_minus_b call
    mylib_test_suite_a_minus_b();

    // Test suite for led_on_even call
    mylib_test_suite_led_on_even();

    // All tests in one bundle
    //mylib_test_suite();

    Serial.println("#################### Finished testing");
}

Добавляем обычные setup/loop, запускаем тесты с run_tests в setup в самом начале, предварительно инициировав последовательный порт Serial.begin, чтобы тесты могли печатать сообщения:


void setup() {
    Serial.begin(9600);
    while (!Serial);

    // run tests
    run_tests();

    // other code - kinda application business logic
    Serial.println("Just show that we call functions from tested lib, nothing useful here");

    pinMode(13, OUTPUT);

    Serial.print("14+23=");
    Serial.println(a_plus_b(14, 23));
    Serial.print("14-23=");
    Serial.println(a_minus_b(14, 23));
    Serial.print("34000+34000=");
    Serial.println(a_plus_b(34000, 34000));
}

void loop() {
    static int i = 0;
    led_on_even(13, i++);
    delay(2000);
}

Здесь основной код приложения и тесты совмещены внутри одного скетча. Если хотите отключить запуск тестов, нужно закомментировать вызов run_tests, приложение будет работать в обычном режиме.


Компилируем, загружаем на устройство, смотрим результат в окошке монитора последовательного порта (Инструменты > Монитор порта / Tools > Serial monitor)


Результат выполнения на плате ChipKIT Uno32 (клон Ардуино с 32-битным чипом PIC32):


#################### Start testing...

== Entering suite #1, "a plus b" ==

[1:1]  test_a_plus_b:#1  "2 + 2 == 4"  pass
[1:2]  test_a_plus_b:#2  "-2 + 2 == 0"  pass
[1:3]  test_a_plus_b:#3  "34000 + 34000 == 68000"  pass

--> 3 check(s), 3 ok, 0 failed (0.00%)

==> 3 check(s) in 1 suite(s) finished after 0.00 second(s),
    3 succeeded, 0 failed (0.00%)

[SUCCESS]

== Entering suite #1, "a minus b" ==

[1:1]  test_a_minus_b:#1  "115 - 6 == 109"  pass
[1:2]  test_a_minus_b:#2  "13 - 17 == -4"  pass

--> 2 check(s), 2 ok, 0 failed (0.00%)

==> 2 check(s) in 1 suite(s) finished after 0.00 second(s),
    2 succeeded, 0 failed (0.00%)

[SUCCESS]

== Entering suite #1, "led on even" ==

[1:1]  test_led_on_even:#1  "num=2 => led#13 on"  pass
[1:2]  test_led_on_even:#2  "num=2 => led#13 on"  pass
[1:3]  test_led_on_even:#3  "num=5 => led#13 off"  pass
[1:4]  test_led_on_even:#4  "num=5 => led#13 off"  pass
[1:5]  test_led_on_even:#5  "num=18 => led#13 on"  pass
[1:6]  test_led_on_even:#6  "num=18 => led#13 on"  pass

--> 6 check(s), 6 ok, 0 failed (0.00%)

==> 6 check(s) in 1 suite(s) finished after 0.00 second(s),
    6 succeeded, 0 failed (0.00%)

[SUCCESS]
#################### Finished testing
Just show that we call functions from tested lib, nothing useful here
14+23=37
14-23=-9
34000+34000=68000

запуск на обычной Arduino Uno (чип AVR 16 бит):


#################### Start testing...

== Entering suite #1, "a#################### Start testing...

== Entering suite #1, "a plus b" ==

[1:1]  test_a_plus_b:#1  "2 + 2 == 4"  pass
[1:2]  test_a_plus_b:#2  "-2 + 2 == 0"  pass
[1:3]  test_a_plus_b:#3  "34000 + 34000 == 68000"  FAIL
!    Type:      fail-unless
!    Condition: a_plus_b(34000, 34000) == 68000
!    Line:      14

--> 3 check(s), 2 ok, 1 failed (?%)

==> 3 check(s) in 1 suite(s) finished after ? second(s),
    2 succeeded, 1 failed (?%)

[FAILURE]

== Entering suite #1, "a minus b" ==

[1:1]  test_a_minus_b:#1  "115 - 6 == 109"  pass
[1:2]  test_a_minus_b:#2  "13 - 17 == -4"  pass

--> 2 check(s), 2 ok, 0 failed (?%)

==> 2 check(s) in 1 suite(s) finished after ? second(s),
    2 succeeded, 0 failed (?%)

[SUCCESS]

== Entering suite #1, "led on even" ==

[1:1]  test_led_on_even:#1  "num=2 => led#13 on"  pass
[1:2]  test_led_on_even:#2  "num=2 => led#13 on"  pass
[1:3]  test_led_on_even:#3  "num=5 => led#13 off"  pass
[1:4]  test_led_on_even:#4  "num=5 => led#13 off"  pass
[1:5]  test_led_on_even:#5  "num=18 => led#13 on"  pass
[1:6]  test_led_on_even:#6  "num=18 => led#13 on"  pass

--> 6 check(s), 6 ok, 0 failed (?%)

==> 6 check(s) in 1 suite(s) finished after ? second(s),
    6 succeeded, 0 failed (?%)

[SUCCESS]
#################### Finished testing
Just show that we call functions from tested lib, nothing useful here
14+23=37
14-23=-9
34000+34000=2464

Обратим внимание на пару моментов:


— На PIC32 все тесты завершились успешно, а на AVR один тест со сложением провалился. 34000 + 34000 == 68000 только на 32-битном контроллере PIC32, на AVR размер int = 2 байта (16 бит), максимальное число, которое можно в него положить = 2^16-1=65536-1=65535 (в беззнаковом режиме unsigned). На AVR с 16-битным int происходит переполнение, а на 32-битном PIC32 (и на 64-битном десктопе с x86_64) все ок. Такие особенности платформы стоит учитывать там, где они могут себя проявить, и добавлять в тесты.


— Тест test_led_on_even (включить лампочку, если передано четное число) успешно проходит на обоих контроллерах, но, вообще говоря, использовать чтение digitalRead для проверки успешности записи digitalWrite на реальном железе — не самая хорошая идея.


Во-первых, digitalRead (прочитать значение GPIO в режиме ввода pinMode INPUT) совершенно не обязан выдавать значение, которое было отправлено в порт GPIO с digitalWrite в режиме вывода pinMode OUTPUT: в официальной документации на digitalRead про такое использование метода ничего не говорится, хотя на железке это и срабатывает.


Во-вторых, полагаясь на то, что digitalRead вернет нужное значение после вызова digitalWrite, мы встаем на скользкую дорожку тестирования не своего, но чужого кода. Успешность прохождения теста зависит не только от тестируемого кода, но и от того, как именно реализована связка digitalWrite/digitalRead на конкретном контроллере и нет ли в ней ошибок (кстати, на Arduino UNO с AVR тест провалится, если убрать строку перевода ножки в режим вывода pinMode(13, OUTPUT), на ChipKIT Uno32 с PIC32 тест проходит в любом случае).


Здесь мы не должны проверять, что digitalWrite ЗАПИСАЛ значение в порт GPIO так, что digitalRead смог его прочитать. Здесь мы проверяем, что digitalWriite БЫЛ ВЫЗВАН с нужными нам параметрами. При запуске тестов на реальном железе мы навряд ли сможем это сделать без построения каких-то некрасивых вспомогательных конструкций, но в режиме тестирования на настольной системе это будет легко реализовано при помощи заглушек (см ниже).


Тестируемый код и тесты в отдельные модули


image


Хранить тесты и весь код в одном большом файле — не самое удобное решение, если проект начинает жить и вырастает чуть дальше чернового наброска.


Теперь мы хотим вынести тесты в отдельный модуль. Модуль тестов должен вызывать тестируемые функции, для этого он должен подключить заголовочный файл с их объявлениями, а значит весь тестируемый код тоже идет в свой отдельный модуль. Здесь мы видим, как внедрение тестов с первых шагов волей-неволей принуждает нас к красоте и порядку внутри проектного дерева.


Система сборки Ардуино позволяет дробить проект на модули: в каталоге со скетчем (.ino) можно размещать дополнительные заголовочные файлы (*.h), файлы с исходниками Си (.c) и C++ (.cpp). Заголовочные файлы будут подключаться как обычно директивой #include, файлы с исходниками C/C++ будут автоматически компилироваться и собираться в единую исполняемую прошивку. Среда разработки Arduino IDE показывает все исходные файлы проекта на вкладках.


Реорганизуем проект:
sput-ino/examples/sput-ino-modules/


Модуль с тестируемым кодом: mylib.h+mylib.cpp


Заголовочный файл — объявления функций:
sput-ino/examples/sput-ino-modules/mylib.h


#ifndef MYLIB_H
#define MYLIB_H

/**
 * @return a плюс b
 */
int a_plus_b(int a, int b);

/**
 * @return a минус b
 */
int a_minus_b(int a, int b);

/** 
 * Включить лампочку, если число четное
 * @param pin номер ножки лапмочки
 * @param num число
 * @return true, если число num четное
 */
bool led_on_even(int pin, int num);

#endif // MYLIB_TEST_H

Исходный код модуля. Если хотите здесь взаимодействовать с железом и использовать API Arduino, просто подключайте Arduino.h.


sput-ino/examples/sput-ino-modules/mylib.cpp


#include "Arduino.h"

/**
 * @return a плюс b
 */
int a_plus_b(int a, int b) {
    return a + b;
}

/**
 * @return a минус b
 */
int a_minus_b(int a, int b) {
    return a - b;
}

/** 
 * Включить лампочку, если число четное
 * @param pin номер ножки лапмочки
 * @param num число
 * @return true, если число num четное
 */
bool led_on_even(int pin, int num) {
    if(num % 2 == 0) {
        digitalWrite(pin, HIGH);
    } else {
        digitalWrite(pin, LOW);
    }
    return num % 2 == 0;
}

Модуль с тестами: mylib-test.h+mylib-test.cpp


Заголовочный файл — объявления наборов тестов (тест-сьютов), сами тесты объявлять на публику не обязательно:
sput-ino/examples/sput-ino-modules/mylib-test.h


#ifndef MYLIB_TEST_H
#define MYLIB_TEST_H

/** Test suite for a_plus_b call */
int mylib_test_suite_a_plus_b();

/** Test suite for a_minus_b call */
int mylib_test_suite_a_minus_b();

/** Test suite for led_on_even call */
int mylib_test_suite_led_on_even();

/** All tests in one bundle */
int mylib_test_suite();

#endif // MYLIB_TEST_H

Тесты и наборы тестов: все тоже без изменений, только теперь подключаем mylib.h и Arduino.h вручную.


sput-ino/examples/sput-ino-modules/mylib-test.cpp


// http://www.use-strict.de/sput-unit-testing/tutorial.html
#include "sput.h"

#include "Arduino.h"
#include "mylib.h"

/** Test a_plus_b call */
void test_a_plus_b() {
    sput_fail_unless(a_plus_b(2, 2) == 4, "2 + 2 == 4");
    sput_fail_unless(a_plus_b(-2, 2) == 0, "-2 + 2 == 0");

    // this one would pass on 32-bit controllers and desktop (libc) and would fail on AVR with 16-bit int
    sput_fail_unless(a_plus_b(34000, 34000) == 68000, "34000 + 34000 == 68000");
}

/** Test a_minus_b call */
void test_a_minus_b() {
    sput_fail_unless(a_minus_b(115, 6) == 109, "115 - 6 == 109");
    sput_fail_unless(a_minus_b(13, 17) == -4, "13 - 17 == -4");
}

/** Test test_led_on_even call */
bool test_led_on_even() {
    pinMode(13, OUTPUT);

    sput_fail_unless(led_on_even(13, 2), "num=2 => led#13 on");
    // would pass on desktop, might fail or pass on difference devices
    // (e.g.: Arduino Due - fail, ChipKIT Uno32 - pass)
    sput_fail_unless(digitalRead(13) == HIGH, "num=2 => led#13 on");

    sput_fail_unless(!led_on_even(13, 5), "num=5 => led#13 off");
    sput_fail_unless(digitalRead(13) == LOW, "num=5 => led#13 off");

    sput_fail_unless(led_on_even(13, 18), "num=18 => led#13 on");
    sput_fail_unless(digitalRead(13) == HIGH, "num=18 => led#13 on");
}

/*******************************************/
// test suites

/** Test suite for a_plus_b call */
int mylib_test_suite_a_plus_b() {
    sput_start_testing();

    sput_enter_suite("a plus b");
    sput_run_test(test_a_plus_b);

    sput_finish_testing();
    return sput_get_return_value();
}

/** Test suite for a_minus_b call */
int mylib_test_suite_a_minus_b() {
    sput_start_testing();

    sput_enter_suite("a minus b");
    sput_run_test(test_a_minus_b);

    sput_finish_testing();
    return sput_get_return_value();
}

/** Test suite for led_on_even call */
int mylib_test_suite_led_on_even() {
    sput_start_testing();

    sput_enter_suite("led on even");
    sput_run_test(test_led_on_even);

    sput_finish_testing();
    return sput_get_return_value();
}

/** All tests in one bundle */
int mylib_test_suite() {
    sput_start_testing();

    sput_enter_suite("a plus b");
    sput_run_test(test_a_plus_b);

    sput_enter_suite("a minus b");
    sput_run_test(test_a_minus_b);

    sput_enter_suite("led on even");
    sput_run_test(test_led_on_even);

    sput_finish_testing();
    return sput_get_return_value();
}

Главный скетч для исполняемой прошивки: здесь остались только обращения к модулю приложения mylib.h и модулю с тестами mylib-test.h.


sput-ino/examples/sput-ino-modules/sput-ino-modules.ino


#include "mylib.h"
#include "mylib-test.h"

/** run tests on device */
void run_tests() {
    Serial.println("#################### Start testing...");

    // comment out specific test suites if firmware does not
    // fit to device memory

    // Test suite for a_plus_b call
    mylib_test_suite_a_plus_b();

    // Test suite for a_minus_b call
    mylib_test_suite_a_minus_b();

    // Test suite for led_on_even call
    mylib_test_suite_led_on_even();

    // All tests in one bundle
    //mylib_test_suite();

    Serial.println("#################### Finished testing");
}

void setup() {
    Serial.begin(9600);
    while (!Serial);

    // run tests
    run_tests();

    // other code - kinda application business logic
    Serial.println("Just show that we call functions from tested lib, nothing useful here");

    pinMode(13, OUTPUT);

    Serial.print("14+23=");
    Serial.println(a_plus_b(14, 23));
    Serial.print("14-23=");
    Serial.println(a_minus_b(14, 23));
    Serial.print("34000+34000=");
    Serial.println(a_plus_b(34000, 34000));
}

void loop() {
    static int i = 0;
    led_on_even(13, i++);
    delay(2000);
}

Прошиваем, открываем монитор последовательного порта, результат идентичен предыдущему.


Итого, структура проекта:
sput-ino-modules/


— исполняемая прошивка для основного приложения и тестов:
sput-ino-modules/sput-ino-modules.ino


— тестируемый код:
sput-ino-modules/mylib.h
sput-ino-modules/mylib.cpp


— код тестов:
sput-ino-modules/mylib-test.h
sput-ino-modules/mylib-test.cpp


В целом, с такими установками уже можно жить вполне комфортно. Однако не всем может понравится, что тесты и исходники хранятся вперемешку в одном и том же каталоге, а так же то, что для переключения режимов тест/приложение нужно что-то комментировать/раскомментировать в одной и той же исполняемой прошивке, поэтому


Выносим тесты в отдельный проект


Чтобы понять, зачем нам нужно делать дальнейшие не совсем очевидные телодвижения, сначала стоит пояснить в общих чертах, как работает система сборки проектовав Ардуино:


  • В простейшем случае проект состоит из одного файла с расширением «.ino» (скетч), который должен храниться в каталоге с таким же именем (например: «myproj1/myproj1.ino»).
  • В этом же каталоге могут находиться другие исходники — заголовочные файлы «.h», модули на Си «.c», модули на С++ «.cpp», но не другие файлы «.ino».
  • В начале процедуры компиляции все содержимое каталога проекта копируется в другой временный каталог (что-то вроде /tmp/build2b91b1aecd83593cdd811791fcf30e97.tmp/), там файл «.ino» превращается в «.cpp», потом все файлы «.cpp» и «.c» компилятор gcc превращает в объектные файлы «.o», потом все объектные файлы «.o» линкер превращает в единый файл с исполняемой прошивкой «.hex» и (если был выбран вариант «скомпилировать и прошить») программный программатор avrdude отправляет её на устройство (совет: откройте меню Файл > Настройки, включите галочки Показывать подробный вывод для компиляции и загрузки).
  • Общие библиотеки устанавливают в каталог $HOME/Arduino/libraries/ — они будут доступны при компиляции и сборке любых проектов на этом компьютере.

Итого, имеем:


  • Один проект Ардуино может содержать только один исполняемый файл «.ino». Если мы хотим иметь два разных исполняемых файла «.ino», нам нужно сделать два разных проекта в разных каталогах файловой системы.
  • Мы можем разбивать исходный код на модули и подключать их один к другому с помощью директивы #include (например: #include "mylib.h") внутри каталога одного проекта.
  • Мы НЕ можем из одного проекта напрямую ссылаться на модули из других проектов через относительные ссылки, полагаясь на взаимное положение проектов в файловой системе (например: #include "../proj2/proj2lib.h"), т.к. перед сборкой каждый из проектов будет скопирован во временный каталог и эти связи будут нарушены.
  • Даже если мы решим подключить заголовочные файлы «.h» второго проекта не через относительные, а абсолютные ссылки (а мы это делать, конечно, не будем), система сборки все равно не подцепит исходные файлы «.cpp» и «.c», так тоже не получится.
  • Если мы хотим сделать так, чтобы модули одного нашего проекта были доступны для использования внутри другого нашего проекта, мы должны оформить первый проект в виде библиотеки Ардуино.

Значит, теперь такой план:


  • Конвертировать исходный проект в библиотеку Ардуино и разместить её в $HOME/Arduino/libraries/
  • Вынести тесты в отдельный проект, который будет обращаться к исходному проекту как к общедоступной библиотеке

Пример такого проекта (его можно использовать, как шаблон для ваших новых проектов) я вынес в отдельный репозиторий:
https://github.com/sadr0b0t/sput-ino-demo


Скачайте демо-проект себе на компьютер.


Первым делом в каталоге $HOME/Arduino/libraries нужно создать символьную ссылку на каталог проекта


cd $HOME/Arduino/libraries/
ln -s /path/to/projects/dir/sput-ino-demo

или, если ваша операционная система не умеет в символьные ссылки, просто скопировать туда весь проект и дальше вести работу прямо в библиотеках.


получаем:


$HOME/Arduino/libraries/sput-ino-demo/

Структура этого проекта — структура библиотеки Ардуино.


здесь у нас исходники библиотеки — заголовочные файлы и код Си/С++:
sput-ino-demo/src/
sput-ino-demo/src/mylib.h
sput-ino-demo/src/mylib.cpp


Мы сможем подключать заголовочные файлы этой библиотеки из любого проекта Ардуино на текущем компьютере обычным:


#include "mylib.h"

Но чтобы это работало, в корень библиотеки нужно положить еще файл с информацией о библиотеке library.properties:
sput-ino-demo/library.properties


name=sput-ino-demo
version=0.0.1
author=sadr0b0t
maintainer=sadr0b0t
sentence=Demo project for sput-ino, Sput unit testing framework for C/C++ port to Arduino
paragraph=Demo project for sput-ino. Sput is an unit testing framework for C/C++ that focuses on simplicity of use and maximum portability. It is implemented as a single ANSI C compliant header file that provides all macros needed to start unit testing in nearly no time.
category=Other
url=https://github.com/sadr0b0t/sput-ino-demo
architectures=*

(Кстати, можно обойтись без library.properties, если положить все исходники .h, .c, .cpp не в src/, а в корень библиотеки sput-ino-demo/. Они так же будут подключаться/компилироваться с прошивками ссылающихся на них проектов, но мы так делать не будем, т.к. с src/, конечно, аккуратнее.)


Главный проект — скетч, теперь опять однофайловый:
sput-ino-demo/sput-ino-demo/sput-ino-demo.ino


Кстати-2, после установки проекта-библиотеки и перезапуска среды Ардуино этот скетч появится в меню Файл > Примеры > sput-ino-demo/sput-ino-demo, но он оттуда откроется только для чтения. Чтобы открыть скетч для редактирования, воспользуйтесь обычным Файл > Открыть и найдите его в файловой системе.


Кстати-3, файлы проекта-библиотеки mylib.h и mylib.cpp теперь не будут появляться в окне среды Arduino IDE (т.к. они находятся за пределами каталога скетча sput-ino-demo/), вам придется редактировать их в вашем любимом текстовом редакторе. Придется это принять как данность, кому к сожалению, а кому и к счастью.


Кстати-4, теперь у вас в проекте может быть более одного скетча «.ino».


Итак, с библиотекой и запускаемым скетчем разобрались, теперь к тестам.


Тесты мы разместим теперь в отдельном каталоге:
sput-ino-demo/test/


Запускаемый скетч для Ардуино и сами тесты:
sput-ino-demo/test/mylib-test-arduino/
sput-ino-demo/test/mylib-test-arduino/mylib-test-arduino.ino
sput-ino-demo/test/mylib-test-arduino/mylib-test.h
sput-ino-demo/test/mylib-test-arduino/mylib-test.cpp


Для настольной системы:
sput-ino-demo/test/mylib-test-desktop/


Тесты для настольной системы обсуждаем далее.


Запуск тестов на настольном компьютере


Итак, с запуском тестов на устройстве в целом разобрались. Теперь посмотрим, получится ли запустить эти же тесты на настольном компьютере. Для чего вообще запускать тесты на настольном компьютере? Во-первых, это удобно и быстро: поменяли в исходниках пару строк, быстро пересобрали, запустили тесты, здесь же в консольке посмотрели результат; в случае с устройством одна процедура прошивки может занять больше времени, чем все описанные выше действия. Во-вторых, некоторые ситуации, которые можно легко отработать в настольной симуляции (или, точнее, на макете, mock), на железке будет воспроизвести проблематичнее (например, отработать получение значения с одного или нескольких датчиков, отследить правильность ответной реакции). Так же существует мнение, что запускать тесты на микроконтроллерах вообще не правильно, а правильно их запускать только на настольных системах.


В общем, мы хотим:


  • запускать тесты на настольной системе без прошивки в устройство,
  • это должны быть те же самые тесты и те же самые тестируемые участки приложения, которые мы запускаем на устройстве.

Для того, чтобы решить эту задачу, во-первых, у нас должна быть библиотека для модульного тестирования, которая запустится одновременно и на железке с Ардуино и на настольной системе. Как было сказано в начале статьи, библиотека sput-ino по этому условию проходит: исходная библиотека sput работает на настольных системах с libc, sput-ino — порт библиотеки sput на платформу Ардуино с полным сохранением совместимости API, а также с поддержкой обеих платформ в одной библиотеке. Короче, тесты, использующие библиотеку sput-ino, можно компилировать как для настольных систем с libc, так и для платформы Ардуино.


Далее, условно разделим исходники на две части:


  • части приложения, которые не взаимодействуют с железом, не используют API Ардуино.
  • части приложения, которые взаимодействуют с железом, используют API Ардуино.

Части приложения НЕ используют API Ардуино


В первом случае (у нас это a_plus_b и a_minis_b) всё ясно — это части приложения, написанные на чистом Си/С++. Скорее всего это какие-то математические, алгоритмические или структурные блоки. Как они компилировались и запускались на Ардуино, точно так же они скомпилируются и запустятся с тестами на настольной системе без дополнительных телодвижений. Однако даже с ними не стоит забывать о различиях между платформами (выше мы уже рассмотрели случай с тестом, провалившимся из-за переполнения 16-битного int на чипе AVR, когда на 32-битном PIC32 и 64-битном настольном Intel/AMD все проходит). Такие отличия стоит учитывать при написании тестов и время от времени гонять тесты на целевом устройстве.


Части приложения используют API Ардуино


Во втором случае (у нас это led_on_even) ситуация кажется еще интереснее. Допустим, мы хотим протестировать функцию, которая помимо других действий обращается к железу контроллера через родные ардуинные digitalRead или digitalWrite. Совершенно очевидно, что никаких digitalRead и digitalWrite в стандартных библиотеках libc на настольной системе нет, этот блок приложения просто так не скомпилируется, тем более не запустится (и где у ноутбука пины GPIO?). Что делать? Неужели искать эмулятор или симулятор плат Ардуино и каким-то образом тащить все это счастье к себе в проект? Компилировать исходники Ардуино под x86? Писать симулятор чипа AVR со всей его внутренней регистровой кухней и драйверами самому?


Примерно такие мысли пронеслись у меня в голове, когда я первый раз подумал о том, что нужно каким-то образом запустить значительную часть приложения, написанного специально для Ардуно, на обычном десктопе. Однако первые практические шаги решения почти сразу показали, что масштаб проблемы весьма преувеличен. Я бы сказал, что никакой проблемы вообще нет.


Да, для каждого используемого вызова API Ардуино мы добавляем в проект собственную заглушку: объявляем функцию с таким же именем и сигнатурой (тип возвращаемого значения и аргументы), добавляем ей собственную реализацию. Нет, реализация заглушки не будет иметь никакого отношения к дереву исходников или к железу оригинальной Ардуино. В некоторых случаях заглушка может представлять пустую функцию вообще без кода.


sput-ino/example-desktop/


Вот заголовок фейкового Arduino.h
sput-ino/example-desktop/Arduino.h


#ifndef WPROGRAM_H
#define WPROGRAM_H

#define OUTPUT 1
#define INPUT 0

#define HIGH 1
#define LOW 0

unsigned long micros();

void pinMode(int pin, int mode);

void digitalWrite(int pin, int val);

int digitalRead(int pin);

#endif // WPROGRAM_H

а вот реализация заглушек Arduino.cpp:
sput-ino/example-desktop/Arduino.cpp


// saved values for pins
static int _pin_modes[64];
static int _pin_values[64];

// from Arduino.h

/**
 * micros stub
 */
unsigned long micros() {
    return 0;
}

/**
 * Set GPIO pin mode
 */
void pinMode(int pin, int mode) {
    _pin_modes[pin] = mode;
}

/**
 * Write GPIO pin value
 */
void digitalWrite(int pin, int val) {
    _pin_values[pin] = val;
}

/**
 * Read GPIO pin value
 */
int digitalRead(int pin) {
    return _pin_values[pin];
}

Здесь далеко не все вызовы API Ардуино, мы добавили только те функции и константы, которые необходимы для компиляции и запуска приведенных выше тестов (плюс еще парочка лишних вызовов для примера). Заглушки для остальных функций можно добавлять по мере необходимости.


В общем, этого уже достаточно, чтобы скомпилировать и запустить наши тесты на настольном компьютере. Добавляем главный исполняемый файл с main:
sput-ino/example-desktop/mylib-test-main.cpp


#include "mylib-test.h"

int main() {
    return mylib_test_suite();
}

собираем
sput-ino/example-desktop/build.sh


#!/bin/sh
# simple build script, feel free to modify or convert it
# to your favourite build system config

#gcc -c c_file_stub.c
#g++ -std=c++11 -c cpp_file_stub.cpp

g++ -std=c++11 -c     -I. -I../examples/sput-ino-modules -I$HOME/Arduino/libraries/sput-ino/src     Arduino.cpp     ../examples/sput-ino-modules/mylib.cpp     ../examples/sput-ino-modules/mylib-test.cpp     mylib-test-main.cpp
g++ *.o -o test_mylib

(видим тесты из модульной версии проекта Ардуино)


запускаем


./test_mylib

здесь же в консольке:


== Entering suite #1, "a plus b" ==

[1:1]  test_a_plus_b:#1  "2 + 2 == 4"  pass
[1:2]  test_a_plus_b:#2  "-2 + 2 == 0"  pass
[1:3]  test_a_plus_b:#3  "34000 + 34000 == 68000"  pass

--> 3 check(s), 3 ok, 0 failed (0.00%)

== Entering suite #2, "a minus b" ==

[2:1]  test_a_minus_b:#1  "115 - 6 == 109"  pass
[2:2]  test_a_minus_b:#2  "13 - 17 == -4"  pass

--> 2 check(s), 2 ok, 0 failed (0.00%)

== Entering suite #3, "led on even" ==

[3:1]  test_led_on_even:#1  "num=2 => led#13 on"  pass
[3:2]  test_led_on_even:#2  "num=2 => led#13 on"  pass
[3:3]  test_led_on_even:#3  "num=5 => led#13 off"  pass
[3:4]  test_led_on_even:#4  "num=5 => led#13 off"  pass
[3:5]  test_led_on_even:#5  "num=18 => led#13 on"  pass
[3:6]  test_led_on_even:#6  "num=18 => led#13 on"  pass

--> 6 check(s), 6 ok, 0 failed (0.00%)

==> 11 check(s) in 3 suite(s) finished after 0.00 second(s),
    11 succeeded, 0 failed (0.00%)

[SUCCESS]

Саксэс, саксэс, саксэс. На этой оптимистической ноте можно было бы закончить статью, но лучше разберем еще один обещанный выше случай.


Расширение API макета; тесты, которые получится запускать только на настольной системе


Выше мы отметили, что мы не должны проверять, что digitalWrite ЗАПИСАЛ значение в порт GPIO так, что digitalRead смог его прочитать. Мы проверяем, что digitalWriite БЫЛ ВЫЗВАН с нужными нам параметрами. Другими словами, мы хотим проверить, что digitalWrite был вызван с определенными параметрами, но мы не хотим использовать для этого digitalRead. Да, если говорить конкретно про пару digitalWrite/digitalRead, еще можно как-то рассуждать о целесообразности такого желания (ведь при запуске тестов на настольной системе digitalRead все равно является заглушкой и мы можем вставлять в нее любой удовлетворяющий нас код), но мы вполне можем захотеть проверить обращения и к другим вызовам API Ардуино, у которых нет даже такой пары (например, pinMode).


Короче, давайте добавим к заглушкам API Ардуино еще несколько расширенных вызовов и посмотрим, как будут выглядеть с ними наши старые тесты.


Для порядка объявим дополнительные вызовы для макета в отдельном заголовочном файле, я назвал его _Arduino.h (в начале нижнее подчеркивание):
sput-ino/example-desktop/_Arduino.h


#ifndef _ARDUINO_H
#define _ARDUINO_H

// Additional calls to get extended info from Arduino mocks

/** Get pin mode */
int _get_pin_mode(int pin);

/** Get pin value */
int _get_pin_value(int pin);

#endif // _ARDUINO_H

добавим реализацию в Arduino.cpp:
sput-ino/example-desktop/Arduino.cpp


// From _Arduino.h
// Calls to get extended info from Arduino mocks

/** Get pin mode */
int _get_pin_mode(int pin) {
    return _pin_modes[pin];
}

/** Get pin value */
int _get_pin_value(int pin) {
    return _pin_values[pin];
}

Как видим, реализация _get_pin_value идентична заглушке для digitalRead, но _get_pin_mode уже не имеет прямого аналога в API Ардуино.


Далее пишем новую версию теста test_led_on_eventest_led_on_even_desktoponly, использующую новый вызов _get_pin_value вместо digitalRead. Этот тест уже не скомпилируется и не запустится на устройстве, поэтому мы его размещаем в отдельном модуле за пределами проекта Ардуино — в каталоге с исходными файлами для тестирования на настольном компьютере sput-ino/example-desktop/


заголовочный файл с наборами тестов:
sput-ino/example-desktop/mylib-test-desktoponly.h


#ifndef MYLIB_TEST_DESKTOPONLY_H
#define MYLIB_TEST_DESKTOPONLY_H

/** Test suite for led_on_even call */
int mylib_test_suite_led_on_even_desktoponly();

/** Desktop-only tests in one bundle */
int mylib_test_suite_desktoponly();

#endif // MYLIB_TEST_DESKTOPONLY_H

Код теста:
sput-ino/example-desktop/mylib-test-desktoponly.cpp


// http://www.use-strict.de/sput-unit-testing/tutorial.html
#include "sput.h"

#include "_Arduino.h"
#include "Arduino.h"
#include "mylib.h"

/** Test test_led_on_even call */
bool test_led_on_even_desktoponly() {
    // we do not use Arduino API calls here to get info about
    // moked chip state, use calls from _Arduino.h instead

    sput_fail_unless(led_on_even(13, 2), "num=2 => led#13 on");
    sput_fail_unless(_get_pin_value(13) == HIGH, "num=2 => led#13 on");

    sput_fail_unless(!led_on_even(13, 5), "num=5 => led#13 off");
    sput_fail_unless(_get_pin_value(13) == LOW, "num=5 => led#13 off");

    sput_fail_unless(led_on_even(13, 18), "num=18 => led#13 on");
    sput_fail_unless(_get_pin_value(13) == HIGH, "num=18 => led#13 on");
}

/*******************************************/
// test suites

/** Test suite for led_on_even call */
int mylib_test_suite_led_on_even_desktoponly() {
    sput_start_testing();

    sput_enter_suite("led on even (only desktop)");
    sput_run_test(test_led_on_even_desktoponly);

    sput_finish_testing();
    return sput_get_return_value();
}

/** All tests in one bundle */
int mylib_test_suite_desktoponly() {
    sput_start_testing();

    sput_enter_suite("led on even (only desktop)");
    sput_run_test(test_led_on_even_desktoponly);

    sput_finish_testing();
    return sput_get_return_value();
}

Немного поправим исполняемый файл — теперь у нас два набора тестов: кросс-платформенные тесты и тесты, которые запускаем только на десктопе.


sput-ino/example-desktop/mylib-test-main.cpp


#include "mylib-test.h"
#include "mylib-test-desktoponly.h"

int main() {
    return mylib_test_suite() | mylib_test_suite_desktoponly();
}

чуть правим сборочный скрипт (добавляем mylib-test-desktoponly.cpp)


#!/bin/sh
# simple build script, feel free to modify or convert it
# to your favourite build system config

#gcc -c c_file_stub.c
#g++ -std=c++11 -c cpp_file_stub.cpp

g++ -std=c++11 -c     -I. -I../examples/sput-ino-modules -I$HOME/Arduino/libraries/sput-ino/src     Arduino.cpp     ../examples/sput-ino-modules/mylib.cpp     ../examples/sput-ino-modules/mylib-test.cpp     mylib-test-desktoponly.cpp     mylib-test-main.cpp
g++ *.o -o test_mylib

собираем


./build.sh

запускаем


./test_mylib

== Entering suite #1, "a plus b" ==

[1:1]  test_a_plus_b:#1  "2 + 2 == 4"  pass
[1:2]  test_a_plus_b:#2  "-2 + 2 == 0"  pass
[1:3]  test_a_plus_b:#3  "34000 + 34000 == 68000"  pass

--> 3 check(s), 3 ok, 0 failed (0.00%)

== Entering suite #2, "a minus b" ==

[2:1]  test_a_minus_b:#1  "115 - 6 == 109"  pass
[2:2]  test_a_minus_b:#2  "13 - 17 == -4"  pass

--> 2 check(s), 2 ok, 0 failed (0.00%)

== Entering suite #3, "led on even" ==

[3:1]  test_led_on_even:#1  "num=2 => led#13 on"  pass
[3:2]  test_led_on_even:#2  "num=2 => led#13 on"  pass
[3:3]  test_led_on_even:#3  "num=5 => led#13 off"  pass
[3:4]  test_led_on_even:#4  "num=5 => led#13 off"  pass
[3:5]  test_led_on_even:#5  "num=18 => led#13 on"  pass
[3:6]  test_led_on_even:#6  "num=18 => led#13 on"  pass

--> 6 check(s), 6 ok, 0 failed (0.00%)

==> 11 check(s) in 3 suite(s) finished after 0.00 second(s),
    11 succeeded, 0 failed (0.00%)

[SUCCESS]

== Entering suite #1, "led on even (only desktop)" ==

[1:1]  test_led_on_even_desktoponly:#1  "num=2 => led#13 on"  pass
[1:2]  test_led_on_even_desktoponly:#2  "num=2 => led#13 on"  pass
[1:3]  test_led_on_even_desktoponly:#3  "num=5 => led#13 off"  pass
[1:4]  test_led_on_even_desktoponly:#4  "num=5 => led#13 off"  pass
[1:5]  test_led_on_even_desktoponly:#5  "num=18 => led#13 on"  pass
[1:6]  test_led_on_even_desktoponly:#6  "num=18 => led#13 on"  pass

--> 6 check(s), 6 ok, 0 failed (0.00%)

==> 6 check(s) in 1 suite(s) finished after 0.00 second(s),
    6 succeeded, 0 failed (0.00%)

[SUCCESS]

Ну и на десерт


Хороший пример: потестируем обработчик прерываний


Допустим, у нас есть небольшой проект с модулем управления шаговым мотором:


  • Мотор шагает на фронте HIGH > LOW,
  • модуль проверяет выход за границы с концевых датчиков и
  • программно считает сделанные шаги.

Мотор шагает в фоне по сигналам из программного обработчика прерываний от таймера, несколько тысяч (или десятков тысяч) раз в секунду. Один шаг — 3 тика таймера: тик 1 — проверяем границы (концевые датчики), тик 2 — взводим ножку STEP в HIGH, тик 3 — делаем шаг: сбрасываем STEP в LOW, увеличиваем счетчик.


Код управления мотором может выглядеть примерно так:


#define ACTION_STOP 0
#define ACTION_CHECK_BOUNDS 1
#define ACTION_GO_HIGH 2
#define ACTION_STEP 3

int step_count = 0;
int action = ACTION_STOP;

void timer_handle_interrupts() {
    // мы можем пропустить несколько вызовов,
    // чтобы двигаться с нужной скоростью
    if(!timeForStep()) return;

    // время делать шаг
    if(ACTION_CHECK_BOUNDS == action) {
        // проверяем границы - концевые датчики
        if(checkBounds()) {
            // сработал концевик - останавливаемся
            action = ACTION_STOP;
        } else {
            // все ок, на следующий тик готовим шаг
            action = ACTION_GO_HIGH;
        }
    } else if(ACTION_GO_HIGH == action) {
        // взводим ножку STEP, шаг на следующий тик таймера
        digitalWrite(STEP_PIN, HIGH);
        action = ACTION_STEP;
    } else if(ACTION_STEP == action) {
        // шагаем
        digitalWrite(STEP_PIN, LOW);
        step_count++;

        if(step_count < max_steps) {
            // готовим новый шаг
            action = ACTION_CHECK_BOUNDS;
        } else {
            // нашагались
            action = ACTION_STOP;
        }
    }
}

Вызов timer_handle_interrupts — обработчик прерывания от таймера, вызывается на каждый тик таймера определенное заранее количество раз в секунду (как запустить таймер на Ардуино: arduino-timer-api).


Теперь представьте, что код загружен на контроллер, мотор подключен, крутится, но что-то не в порядке: может вращается слишком быстро, может не докручивает часть предполагаемого пути, может что-то еще. Подключение электроники в порядке, проверено на простых тестах, проблема явно в программе. Как бы вы стали отлавливать ошибку? Допустим, у вас есть полноценный аппаратный отладчик с просмотром памяти и переменных, точками останова и красивой поддержкой в IDE. Будем ставить брейкпоинт в обработчик прерывания и проверять значения переменных все 100500 тиков? Ставить точку останова с динамическим условием в надежде поймать проблему в середине цикла? Возможно какой-то из этих или других приемов поможет отловить и исправить проблему.


Но посмотрим, как будет выглядеть процедура отладки этого участка при помощи автоматических тестов:


void test_timer_handle_interrupts() {
    // тик 1
    test_timer_handle_interrupts();
    // проверка 1
    // проверка 2
    // проверка 3

    // тик 2
    test_timer_handle_interrupts();
    // проверка 1
    // проверка 2
    // проверка 3

    // тик 3
    test_timer_handle_interrupts();
    // проверка 1
    // проверка 2
    // проверка 3

    // тик 100500
    for(long i = 0; i < 100500 - 3; i++) {
    }
    // проверка 1
    // проверка 2
    // проверка 3

    // тик 100500+1
    test_timer_handle_interrupts();
    // проверка 1
    // проверка 2
    // проверка 3

    // ...
    // и так далее
}

Прерывания от таймера мы симулируем элементарным ручным вызовом обработчика test_timer_handle_interrupts. Как видим, таким образом можно легко контролировать каждый тик: 1й, 2й, 3й, 103й, предпоследний, последний, — и после каждого тика спокойно делать любые нужные проверки.

Комментарии (171)


  1. NordicEnergy
    15.08.2018 18:56

    Вот она! «Разработка», которой мы достойны…

    Все проекты, что видел на ардуино, мизерные и достаточно сделать банальную обработку ошибок. Что-то большое в ардуино в принципе не влезет. Что-то быстродействующее на ардуино не сделать. Тогда зачем вообще так заморачиваться? Интерпрайз разработка очередной «умной» управлялки для лампочки в сортире на даче?


    1. sadr0b0t Автор
      15.08.2018 19:01

      1) Возможно, вы не в курсе, Arduino != AVR
      2) Говорить о быстродействии вне контекста задачи — беседа с малым количеством смысла


      1. NordicEnergy
        15.08.2018 19:05

        Покажите хоть одну задачу, где есть приличный ЦОС на ардуине или там управлением каким-то сервоприводом злым.

        P.S. минусы побежали, хомячки атакуют!


        1. NordicEnergy
          15.08.2018 19:19

          О, я уже и без минусов… А в прошлый раз дети таки на сливали кармы мне


    1. olartamonov
      15.08.2018 19:41
      +2

      Я тут сделал проект небольшой на ARM Mbed, и я всё меньше понимаю страдания ардуинщиков.

      Современная ОСРВ со всеми нужными сервисами и кучей сторонних. Приятный API на C++. Поддержка просто горы плат на Cortex-M* прямо из коробки, включая примерно все Nucleo. Порог входа не то что низкий — он нулевой, даже IDE ставить не надо: регистрируешься на сайте, открываешь онлайн-IDE, выбираешь плату и готовый пример. На том уровне, на котором пишут поделки для ардуины там, по-моему, вообще ничего знать не надо, всё уже есть и работает. Ну да, в пользовательских библиотеках там адок, но в ардуине он ещё хуже.

      Я в общем студентам (из 45 человек где-то 3-4 имели опыт работы с STM32 без RTOS, остальные в лучшем случае однажды щупали ардуину) показывал, как с RIOT OS между первой встречей и мигающим светодиодом лежит по сути только установка тулчейна, но блин, в Mbed даже этого не надо.

      Вот нахрена, нахрена нужна эта ардуина, ребята, опомнитесь, 2018 год скоро кончится уже?

      Чтобы накачать из инета готовых примеров и библиотек по форумам и, сильно не рефлексируя, собрать на них умную лампочку в сортир?


      1. vasimv
        15.08.2018 22:45
        +1

        Не всем и не всегда удобно делать разработки в онлайн-IDE.

        Ну и у ARM-овских микроконтроллеров (как от STM, так и от Microchip), с которыми мне приходилось дело иметь — есть какие-то подводные камни. Типа начнешь SD-ADC в F373 юзать с DMA — отваливается I2C. И каждый errata вычитывать целыми днями — не очень продуктивно.

        Не говоря уже о том, что когда ищешь какую-то готовую библиотеку под нужную фигню — постоянно сталкиваешься с тем, что или вообще нет, или написана под другой тулкит (или без него, напрямую с регистрами) и переписывать нужно все вообще. С ардуинками проще во всех случаях. Даже если производительности/периферии не хватает — берешь тот же ATSAM и вперед…


        1. olartamonov
          15.08.2018 22:56

          Не всем и не всегда удобно делать разработки в онлайн-IDE.


          Эээ… и какая конкретно религия запрещает вам в таком случае поставить IDE локально?

          Типа начнешь SD-ADC в F373 юзать с DMA — отваливается I2C


          Вы в данный момент просто беседуете с играющим в голове радио, или хотите сообщить, что на ардуине проблем с SD-ADC и DMA на F373 не испытываете?..


          1. vasimv
            15.08.2018 23:03

            А mbed локально вообще можно использовать? Не особо разбирался с ним, посмотрел как-то и пошел дальше, после того как предложило мне проекты где-то на сервере в интернете разрабатывать.

            Ну, на большинстве ардуин вообще нет SD-ADC, так что даже соблазна не возникает, да. А для тех, у которых есть что-то более сложное (типа ATSAM) — есть готовые ардуиновские библиотеки обычно. Гарантии, конечно, нет никаких, что не нарвешься на очередной ARM-овский косяк, но как-то легче обычно, на форумах люди уже все обсудили… :)


            1. olartamonov
              15.08.2018 23:11
              -1

              Очень люблю людей, высказывающих своё решительное мнение по вопросу, в котором они не разбирались.

              Да, можно.

              Кстати, форумы там тоже есть.


              1. vasimv
                15.08.2018 23:15

                Как бы не очевидно совсем. Напишите лучше статью про так как mbed локально использовать, будет всем польза.

                А количество пользователей (как и полезных советов) на их форумах — гораздо меньше ардуиновских.


                1. olartamonov
                  15.08.2018 23:18

                  Состоящую из одного скриншота?..



                  Я боюсь, если мы начнём рассматривать качество советов на ардуиновских форумах, станет совсем смешно.


                1. x893
                  16.08.2018 00:17

                  Использовать локально не проблема, только ~50КБ флэша будьте готовы отдать за mbed runtime.


                  1. olartamonov
                    16.08.2018 00:24

                    Меньше 15 КБ у него минимальный рантайм.


                    1. x893
                      16.08.2018 00:59

                      Я смотрел mbed 5 на nRF.


                      1. olartamonov
                        16.08.2018 08:16

                        Какая разница? Собираю на той же nRF52 мигалку светодиодом, получаю ~10 КБ — это и есть минимальный рантайм ОС.


      1. dlinyj
        16.08.2018 12:53

        Вы бы красивый псот накатали отдельный, со скринами, видео и фото. Глядишь постов подобного этому будет меньше, да и я сам бы ознакомился.


        1. olartamonov
          16.08.2018 13:15
          -1

          На последней встрече в Хабре я был известен в узких кругах как человек, не написавший больше всех постов — потому как с меня причитается не то что один красивый пост, а вообще-то десяток статей по основам работы с RTOS на STM32 на базе курса для МИРЭА, который я весной читал.


          1. dlinyj
            16.08.2018 15:08

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


    1. Iv38
      16.08.2018 03:03

      Arduino Mega Server недостаточно крупный проект? ESPEasy? Огромное количество RepRap
      3D-принтеров базируется на Ардуино.


      1. olartamonov
        16.08.2018 08:18
        -1

        Да, про AMS прямо на Хабре можно почитать, как его автор упорно изобретает велосипед из граблей и костылей: habr.com/post/413779


        1. smart_alex
          16.08.2018 08:46

          О, Олег, приветствую вас, вы снова на ринге? Загубник не забыли вставить? :)


          1. olartamonov
            16.08.2018 08:56

            Я просто оставлю здесь ваш код из Arduino Mega Server, чтобы бессмысленность обсуждения с вами вопросов программирования была очевидна окружающим:

            /* — Function addFN(char c)
            Add char to FileName and move old chars
            TODO: rewrite function with cycle
            — */

            void addFN(char c) {
            fn[19] = fn[18];
            fn[18] = fn[17];
            fn[17] = fn[16];
            fn[16] = fn[15];
            fn[15] = fn[14];
            fn[14] = fn[13];
            fn[13] = fn[12];
            fn[12] = fn[11];
            fn[11] = fn[10];
            fn[10] = fn[9];
            fn[9] = fn[8];
            fn[8] = fn[7];
            fn[7] = fn[6];
            fn[6] = fn[5];
            fn[5] = fn[4];
            fn[4] = fn[3];
            fn[3] = fn[2];
            fn[2] = fn[1];
            fn[1] = fn[0];
            fn[0] = c;
            }


            1. smart_alex
              16.08.2018 09:04

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


              1. olartamonov
                16.08.2018 09:06

                Я вообще ничего не говорю. И вы ничего не говорите. Этот код абсолютно прекрасен без каких-либо комментариев.


                1. dlinyj
                  16.08.2018 12:56
                  -1

                  Не вижу криминала в этом коде. Ну написал человек так, ну и что? Будто ваш код всегда идеален.


                  1. olartamonov
                    16.08.2018 13:16

                    Этот код всего лишь говорит, что человек испытывает серьёзные сложности с самостоятельным написанием цикла for в языке C.


                    1. smart_alex
                      16.08.2018 14:39

                      Ваши попытки меня уесть при помощи ковыряния в legacy коде двухгодичной давности говорят о вашей мелочности и отсутствии аргументов по существу. Изначально речь шла о том, что вы несёте чушь с трибуны технического университета.


                      1. dlinyj
                        16.08.2018 15:09

                        Камрад, вы кормите тролля.


                        1. YourBunnyWrote
                          16.08.2018 16:10
                          -2

                          Камрадов тут нет.


                          1. dlinyj
                            16.08.2018 16:15
                            +1

                            Вычёркиваем вас из списка камрадов.


                    1. dlinyj
                      16.08.2018 15:09

                      И что с того? Захотел он так написать, ему было нагляднее. А вы позоритесь мелочностью. Как по мне, вы сейчас выглядите сильно глупее человека, который написал данный код.


                  1. smart_alex
                    16.08.2018 14:45

                    Это техническая заглушка, которая давно переписана и не заменена в дистрибутиве только из-за его объёмности. Я всё это подробно объяснил в комментариях к моей статье и Олег это прекрасно знает.


                    1. dlinyj
                      16.08.2018 15:11

                      Лично для меня принципиального значения не имеет, как написано.

                      Я знаю хороших программистов, которые решают сложные задачи, но пишут код чуть ли не одним пальцем и очень долго. Вообще не имеет значения КАК решена задача, если она работает и делает это хорошо.


                    1. zloe_morkoffko
                      16.08.2018 15:14

                      А вот эта глобальная переменная из electro_pm.ino?

                      byte U = 0;

                      Тоже legacy или так и должно быть?!
                      Или например strings.ino:
                      void StrClear(char *str, char length) {
                        for (int i = 0; i < length; i++) {
                          str[i] = 0;
                        }
                      }

                      memset на Ардуино не подвезли?


                      1. smart_alex
                        16.08.2018 15:28

                        Уважаемый zloe_morkoffko перед тем как вы присоединитесь к Олегу в перекапывании кода АМС, я хочу чтобы вы обратили внимание на номер версии, а именно 0,17, то есть это глубокая альфа, если это вам о чём-то говорит. И конечно там можно накопать много чего, особенно если это делать с пристрастием.


                        1. dlinyj
                          16.08.2018 15:38

                          Уважаемый, вы слишком серьёзно относитесь к таким вещам. Вы напрасно оправдываетесь, у данных личностей грязи хватает. Например в данных комментариях они позорятся сильнее, чем какие-то огрехи кода, которых у каждого полно.


                          1. smart_alex
                            16.08.2018 16:00
                            +1

                            Да вроде хотел всем ответить по существу, но наверное вы правы. :)


                        1. zloe_morkoffko
                          16.08.2018 15:41

                          Уважаемый smart_alex, абстрактный номер версии не имеет ничего общего с качеством кода. Конечно, если слова «качество кода» вам о чем-то говорят.


                          1. smart_alex
                            16.08.2018 15:50

                            Нигде и никогда не было заявлено, что АМС 0.17 версии это образец качества кода. Об этом даже речь не идёт. АМС 0.17 версии это демонстрация концепции в альфа варианте, которую ещё нужно доделывать и приводить в порядок (о чём красноречиво говорит её номер 0.17).

                            Но есть ещё один важный момент — практика — критерий истины и этот «неправильный» код имеет подтверждённые аптаймы в месяцы беспроблемной работы.


                            1. olartamonov
                              16.08.2018 16:11
                              -2

                              Давайте говорить прямо: на момент выпуска AMC 0.17 вы не умели писать нормальный код.

                              Научитесь ли вы когда-либо в будущем — вопрос открытый. На данный момент — нет, не умеете.

                              В свете этого говорить с вами о программировании как-то немного бессмысленно.


                              1. dlinyj
                                16.08.2018 16:26
                                +1

                                Подскажите, а вы кто такой, чтобы так критично оценивать человека? Покажите свой код, покажите его всей публике, поковыряемся в ваших ошибках.
                                У человека есть результат, это важно. То что он делает какие-то глупости, но в результате работает, то это не имеет значения. Если вы имеете шикарный, красивый, но бесполезный код, то грош вам цена.


                                1. olartamonov
                                  16.08.2018 16:29
                                  -2

                                  1. dlinyj
                                    16.08.2018 16:51

                                    Какая прелесть! github.com/olegart/codefix/blob/master/gost.c ваш код

                                    		n2 ^= f(n1+key[0]);
                                    		n1 ^= f(n2+key[1]);
                                    		n2 ^= f(n1+key[2]);
                                    		n1 ^= f(n2+key[3]);
                                    		n2 ^= f(n1+key[4]);
                                    		n1 ^= f(n2+key[5]);
                                    		n2 ^= f(n1+key[6]);
                                    		n1 ^= f(n2+key[7]);
                                    
                                    		n2 ^= f(n1+key[0]);
                                    		n1 ^= f(n2+key[1]);
                                    		n2 ^= f(n1+key[2]);
                                    		n1 ^= f(n2+key[3]);
                                    		n2 ^= f(n1+key[4]);
                                    		n1 ^= f(n2+key[5]);
                                    		n2 ^= f(n1+key[6]);
                                    		n1 ^= f(n2+key[7]);
                                    


                                    А вас, что циклом for не учили пользоваться? И не говорите, что это нельзя было упихать в цикл. После этого наезжать подобным кодом на smart_alex просто не этично. Всё с вами ясно, профессор…


                                    1. olartamonov
                                      16.08.2018 17:01

                                      Вы сейчас хотите нам рассказать, что не знаете, как работает git вообще и GitHub в частности?

                                      Ну, в смысле, называя моим кодом форк проекта codefix, в котором нет ни одного коммита от меня?

                                      Сильно.

                                      P.S. Упихать это в цикл можно, но в некоторых случаях не нужно, т.к. в развёрнутом виде у этого кода выше производительность — а в системах шифрования она часто является узким местом.


                                      1. dlinyj
                                        16.08.2018 17:04

                                        Вы сейчас хотите нам рассказать, что не знаете, как работает git вообще и GitHub в частности?


                                        Не претендую на знатока git. И вообще не вижу зазорного чего-то не знать. Так же лично я не утверждал, что вселенский программист, и суперумный преподаватель тракторно-заборостроительного института. Вы ж эксперт и профессор, который позорится с каждым комментарием.

                                        А касательно форка, то вы могли бы это и поправить. Раз вы критикуете чужой код.


                                        1. olartamonov
                                          16.08.2018 17:16
                                          -2

                                          Юноша, я специально для вас в постскриптуме написал, почему именно здесь именно это править не надо, какое конкретно слово там вам непонятно?

                                          Вы спрашивайте, не стесняйтесь.


                                          1. dlinyj
                                            16.08.2018 17:23
                                            +1

                                            Не удивлюсь, дедуля, что мы ровесники :).

                                            Да нечего спрашивать. В данном случае просто выудил первое попавшееся. Я посмотрел другой ваш код, есть до чего докопаться. Но опускаться до вашего уровня я не собираюсь. Поскольку каждый имеет право писать код так как ему хочется. Да и я не святой программист.

                                            image

                                            Картинка из одного вашего же поста, который вас же и характеризует.


                                        1. sadr0b0t Автор
                                          16.08.2018 17:53

                                          здесь восхищаться, здесь не восхищаться, а здесь рыбу заворачивали, бгг :)


                                    1. YourBunnyWrote
                                      16.08.2018 17:04

                                      man git-blame


                                  1. sadr0b0t Автор
                                    16.08.2018 17:52
                                    +1

                                    github.com/unwireddevices/RIOT/commits/loralan-public

                                    О, так вы все-таки соизволили форкнуть ртос, авторов которого поливаете у себя в фб, а теперь выдаете их работу за образчик профессионализма команды Unwired Devices? Спасибо, хоть исходники не зажали, бэгэгэ


                                    1. olartamonov
                                      16.08.2018 18:54

                                      Ещё один человек, не умеющий пользоваться гитом?

                                      Вроде бы на гитхабе довольно несложно посмотреть, сколько в том форке наших коммитов, в каких частях и что они делают.

                                      Но нет.


                                      1. sadr0b0t Автор
                                        16.08.2018 19:05

                                        здесь восхищаться, здесь не восхищаться, а здесь рыбу заворачивали :)


                                      1. dlinyj
                                        16.08.2018 19:12
                                        +1

                                        Скажите, а вы умеете общаться с людьми без хамства? Или это такой преподавательский стиль? Студентам вы тоже хамите?


                              1. smart_alex
                                16.08.2018 16:28

                                Ну, перед вами дистрибутив который обеспечивает работу на 8-битном контроллере с 8-ю КБ памяти в реальном времени:

                                — Веб-сервера
                                — Сайтового движка
                                — 8-и сайтов, каждый со своим функционалом, дизайном и топологией
                                — С поддержкой честных интерактивных 3D-сцен
                                — Power Monitor-а на 14 каналов и сетевого осциллографа
                                — Поддержку nRF24 связи с датчиками и актуаторами
                                — Dash-панель работающей в реальном времени
                                — И прочими возможностями, список которых весьма велик

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


                                1. olartamonov
                                  16.08.2018 16:31
                                  -2

                                  На свете вообще много индусского кода, который как-то до какого-то момента работает.

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


                                  1. smart_alex
                                    16.08.2018 16:34
                                    +1

                                    На свете много и болтунов, способных только хаить чужую работу.


                                    1. dlinyj
                                      16.08.2018 16:53

                                      Когда на вас лает собака, не нужно вставать на четвереньки и лаять в ответ. Вы молодец!


                        1. YourBunnyWrote
                          16.08.2018 16:12
                          -1

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


                          1. smart_alex
                            16.08.2018 16:31
                            +1

                            Возможно вы недостаточно внимательно читали: у этой альфы подтверждённые аптаймы во многие месяцы работы. А также работающие в режиме 24/7 DIY проекты пользователей по контролю потребления электроэнергии, протечки воды, управления светодиодными лентами и т.д и т. п.


                            1. YourBunnyWrote
                              16.08.2018 16:37
                              -2

                              Если я «достаточно внимательно» почитаю код этого поделия — то, боюсь, надорву живот от смеха.


                              1. smart_alex
                                16.08.2018 16:39
                                +1

                                Прошу вас, не утруждайте себя.


            1. Sau
              16.08.2018 12:00

              Такой код может быть оправдан если транслируется в машинные коды как написан — если не ошибаюсь, он использует на один регистр меньше ценой объёма кода. Как push r1 pop r2 вместо mov r2,r1. Хоть я и сомневаюсь, что тут цель была именно такой.


              1. olartamonov
                16.08.2018 12:03

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

                Ну и
                а) я бы вот так сходу не решился бы спорить с современными оптимизирующими компиляторами
                б) в AMS такого индусского кода полно


        1. solarplexus
          16.08.2018 09:33

          Статью не читал, но комментарии отличные. Иногда кажется, что комменты дороже самой статьи.


          1. smart_alex
            16.08.2018 11:09

            Без статьи не было бы и комментариев, так что, как говориться, оставайтесь с нами, дальше будет ещё интереснее. :)


        1. Iv38
          16.08.2018 12:27

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


          1. olartamonov
            16.08.2018 12:35

            А они там существуют зачем?

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

            Какой в принципе смысл делать какой-то проект на этом унынии?


      1. NordicEnergy
        16.08.2018 13:01

        Ох… Крупный — это, например, контроллер ДГУ на 250 кВт или аппаратный файрвол с 10G. 3D принтер это поделка школьного уровня.


        1. n12eq3
          16.08.2018 13:08

          но ведь рулилка генератором это просто хоббийная поделка уровня начального кружка при институте, зачем приводить как пример?


          1. NordicEnergy
            16.08.2018 13:11

            Боюсь модуль синхронизации с сетью на igbt сделать школьнику не под силу. Или может у вас есть такой проект со школьных времен? С интересом бы глянул, а может быть и закупил.


            1. n12eq3
              16.08.2018 13:25
              +1

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


              1. NordicEnergy
                16.08.2018 19:51
                -1

                Все так плохо в школе нынче? Тогда понятно почему так много ардуинщиков становится — принтер перестал быть школьным уровнем (хотя самостоятельно даже драйвера никто не пишет на комп) и видимо теперь требует минимум кандидатской степени…


  1. x893
    15.08.2018 20:12

    Обсуждается Arduino IDE, Arduino Create или Arduino UNO на ATMega168?
    Или программирование тестов на C/C++?


    1. sadr0b0t Автор
      16.08.2018 01:29

      Обсуждается Arduino IDE
      Или программирование тестов на C/C++?

      и то и другое

      Arduino Create

      не знаю, если туда можно загружать сторонние библиотеки, то может быть

      Arduino UNO на ATMega168?

      в меньшей степени, как один из примеров


  1. solarplexus
    15.08.2018 20:28
    +2

    Муахаха:) Грамотный ардуинщик )
    В первую очередь, Ардуино это аппаратная платформа с низким порогом вхождения в мир микроконтроллеров, а не микроконтроллер.
    Если идет сравнение эстээмщиков и ардуинщиков, то сравниваете уж дискаверщиков с ардуинщиками.


    1. solarplexus
      15.08.2018 20:44
      +3

      Я как «эстээмщик», нейтрально отношусь к выбору людей в сторону ардуины. Это всего навсего инструмент. И как его применять — выбор самого разработчика. Я иногда могу себе позволить забить гвоздь пассатижами, т.к. идти до молотка лень.
      И нет ничего зазорного в использовании ардуины. Я и сам в некоторых проектах применяю ардуину, считая приемлемым вариантом для данных проектов.


      1. Mike_soft
        15.08.2018 21:39

        но если проект требует юнит-тестирования — наверное, он перерос Ардуину?


    1. sadr0b0t Автор
      16.08.2018 01:25

      Ардуино это аппаратная платформа с низким порогом вхождения в мир микроконтроллеров, а не микроконтроллер.

      золотые слова!


      1. olartamonov
        16.08.2018 08:19
        -1

        Устаревшая на десяток-другой лет, а так-то да.


      1. dlinyj
        16.08.2018 12:58

        Да, абсолютно согласен. Но когда я читал этот пост, то диву давался. Дичь, когда люди делают что-то серьёзное на ардуине.


    1. Bork1507
      16.08.2018 13:46

      Ардуино это аппаратная платформа с низким порогом вхождения в мир микроконтроллеров

      А еще под нее можно на Scratch писать. :)


  1. solarplexus
    15.08.2018 21:01

    Ну и, собственно, у меня встал вопрос. Если вы занимаетесь профессиональной разработкой под ардуино с написанием тестов, то какого фига вы используете убогую IDE?


    1. sadr0b0t Автор
      15.08.2018 21:43
      +2

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


      1. olartamonov
        15.08.2018 22:58

        Грамотный подход начинается с выкидывания средств разработки, застрявших где-то на уровне 1995 года.


        1. sadr0b0t Автор
          15.08.2018 23:13

          вы про gcc?


          1. olartamonov
            15.08.2018 23:14
            +1

            Я про IDE.

            gcc-то как раз в ардуине есть. И notepad.exe ещё есть — но в приличных кругах этого уже четверть века как маловато.


            1. sadr0b0t Автор
              15.08.2018 23:34

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

              >И notepad.exe ещё есть
              notepad.exe — это вин-онли проприетарная поделка, как и большая часть тулчейна для stm, который я когда-то видел. А еще в нем нет подсветки.


              1. olartamonov
                15.08.2018 23:43

                Да, подсветка — это важнейшее свойство современной IDE. Заменяющее всё остальное, включая отладчик и менеджер многофайловых проектов. Особенно для великих умов, пишущих архитектурно незабываемые проекты для ардуины (у вас там в этой ардуине великие умы хотя бы таймеры, процессы и IPC уже придумали, или надо ещё лет двадцать подождать?).

                это вин-онли проприетарная поделка, как и большая часть тулчейна для stm, который я когда-то видел


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

                Скажите, вы про существование arm-none-eabi-gcc и GNU Make в курсе?


                1. sadr0b0t Автор
                  15.08.2018 23:47

                  >Скажите, вы про существование arm-none-eabi-gcc и GNU Make в курсе?

                  Под stm завезли кросс-платформенный полностью открытый тулчейн? Искренне рад за стмщиков!


                  1. olartamonov
                    15.08.2018 23:52

                    Простите, вы последний десяток лет где-то вдали от цивилизации провели, наверное?..


                    1. sadr0b0t Автор
                      16.08.2018 00:22

                      наверное


                    1. dlinyj
                      16.08.2018 13:00

                      Нука-нука, расскажите нам пожалуйста про IDE под Linux для STM32?


                      1. zloe_morkoffko
                        16.08.2018 13:04
                        +1

                        Atollic True Studio — из коробки все есть. Ну и Eclipse для тех кто любит всё сам.


                        1. olartamonov
                          16.08.2018 13:20

                          ATS собственно на Эклипсе и сделан, как и System Workbench, и гора других условно-вендорских IDE.


                      1. NordicEnergy
                        16.08.2018 13:12
                        +1

                        Эклипс, воркбенч, труСтудио, Qt и это только вполне себе взрослые ide.


                      1. olartamonov
                        16.08.2018 13:17

                        Visual Studio Code ещё.


                      1. vvzvlad
                        16.08.2018 15:02

                        Про VS Code сказали уже, у меня все разработка в нем. Подсветка, линтер, автодополнение, быстрые переходы и так далее.
                        Да хоть Sublime с плагинами — он будет поустойчивее и чуть быстрее, чем VS Code на электроне.


                      1. Bratak
                        16.08.2018 18:58
                        +1

                        Segger Embedded Studio.Кто пробовал, больше ничего другого не станет использовать.


              1. solarplexus
                16.08.2018 06:03

                Если я вас правильно понял, для вас хороший инструмент (вижуалстудия) на последнем месте по приоритетам?
                Ваше разделение по приоритетам звучит обидно. Правильный подход, удобный стиль, хороший инструмент — залог успеха.
                А ваши высокие умы могут бесконечно долго обсуждать концепты.


                1. sadr0b0t Автор
                  16.08.2018 10:34

                  Скажем так, я знаю, что такое интегрированная среда разработки с автоформатированием, автодополнением, всплывающей документацией при наведенении мышки на вызов, переходом по клику к исходнику любой системной функции, деревьями проектов, автоматическим рефактором, отладчиком с условными точками останова, профайлером памяти в реальном времени и т.п. При разработке на Java этого добра хватает на разный вкус в не одном варианте. При этом видел разработчиков, которые, если у них выдернуть из-под ног всю эту красоту, начинают теряться, вставать в ступор и протестовать примерно, как офисные барышни, у которых в новом офисе все кнопочки не на своем месте (по моим наблюдениям это в большей степени относится к пользователям как раз МС Вижуалстудии, чем к жабщикам, возможно, потому, что жабщики привыкли от проекта к проекту прыгать между Эклипсом, Нетбинзом и Идеей, а у МС среда единственная и неповторимая на все времена).

                  Это я к тому, что если очень хочется, то можно и IDE настроить, не вижу в этом ничего плохого, а если нет под рукой IDE, то и текстовый редактор + make + gcc в консольке — нормальный инструмент, ничем не хуже, местами лучше (спросите емаксера или вимера, что он думает о редакторе вижуалстудии или эклипса). Можно обсуждать их удобства и преимущества для разных ситуаций, но никак не повод кичиться, тем более сводить целую платформу с развесистой экосистемой к убогому текстовому редактору.


                  1. olartamonov
                    16.08.2018 11:20

                    целую платформу с развесистой экосистемой к убогому текстовому редактору


                    Простите, а эта ваша целая платформа вообще из чего состоит?

                    1) простенький 8-битный микроконтроллер, давно вышедший из употребления у большинства профессиональных разработчиков

                    2) IDE, которую более-менее адекватно можно было воспринимать году эдак в 1995-м

                    3) гора библиотек и скетчей, написанных в основном любителями и, как правило, тривиальных и невысокого качества

                    Каким из этих компонентов надо восхищаться? Или я что-то забыл?


                    1. sadr0b0t Автор
                      16.08.2018 12:32

                      8-битный микроконтроллер

                      да пишите сразу 4-битный, чего уж там

                      Каким из этих компонентов надо восхищаться? Или я что-то забыл?

                      Она умеет запускать код, написанный на Си/С++, скомпилированный с gcc, для меня этого достаточно


                      1. olartamonov
                        16.08.2018 12:34
                        +1

                        Я даже стеняюсь предположить, что вы хотели сказать этим комментарием — что AVR не 8-битный или «а вот есть же ардуина на STM32!»?


                        1. dlinyj
                          16.08.2018 13:03

                          У меня такое ощущение, что вы не истину ищите, а просто решили обмазать грязью оппонента. К сожалению вас это не красит.


                          1. olartamonov
                            16.08.2018 13:25

                            Ну, может быть, вы тогда расшифруете мне тезис про «пишите сразу 4-битный»?

                            Я что-то не так сказал про то, что AVR — 8-битный контроллер?


                            1. dlinyj
                              16.08.2018 15:15

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

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


                    1. Iv38
                      16.08.2018 12:36

                      Но ведь…
                      Ардуино != Atmega.
                      Ардуино != Arduino IDE.
                      А что есть другая платформа, где есть аналогичное количество готовых библиотек и все они написаны исключительно профессионалами?


                      1. olartamonov
                        16.08.2018 12:40

                        1) Мне кажется, я перечислил все компоненты ардуино. Если я какой-то забыл, то можно добавить, но если вы из этих трёх вычтите первые два, то останется Arduino = гора пользовательских библиотек.

                        Которые написаны с расчётом на атмегу и сборку в Arduino IDE, что внезапно возвращает нас к первым двум пунктам.

                        2) В Mbed вам чего не хватает? При том, что присутствующее в Mbed и отсутствующее в Arduino можно перечислять примерно так до завтра?


                    1. n12eq3
                      16.08.2018 13:01

                      1) отлично справляющийся с поставленными задачами. необходимости замены нет.
                      2) нормально справляющаяся с тем говнокодом и с теми требованиями, которые соответствуют уровню применения ардуины
                      3) в большинстве случаев это достоинство.
                      при поливании поносом нужно быть аккуратным, т.к. после определённого момента аргументы заканчиваются и такие комменты в большей степени показывают ограниченность автора, нежели предмета обсуждения.


                      1. olartamonov
                        16.08.2018 13:23

                        Так а смысл-то в 2018 году в использовании этого всего, всё же, какой? «Деды на турбопаскале под досом работали, и мы будем»? «Хорошо не жили, нечего и начинать»?

                        Я каждый раз в развёртнутой статье про ардуину не вижу ничего, кроме мучительного изобретания вещей, которыми всё остальное человечество давно уже просто пользуется.


                        1. n12eq3
                          16.08.2018 13:33

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


                          1. olartamonov
                            16.08.2018 13:41

                            Я понимаю (хотя и не одобряю) использование готового говнокода. Я не понимаю, зачем люди так мучаются, пытаясь изобрести велосипед с квадратными прямоугольными колёсами и криво-косо прикрутить к ардуине вещи, которые на нормальных платформах являются само собой разумеющимися. То есть — продолжают множить говнокод.

                            Значительно проще в этом случае ардуину забыть как страшный сон.

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


                            1. n12eq3
                              16.08.2018 14:40
                              +1

                              а я понимаю (хоть и не одобряю) идею использования готовых фреймворков. зачем мучаться и постоянно изучать, переходить на новые прослойки с квадратными колёсами — ведь на выходе всегда получается неимоверный говнокод с просто потрясающими извращениями. значительно лучше забыть это всё как страшный сон и придумать что-нибудь одно, чтобы не видеть страниц в интернете по 10мбайт.

                              а ардуину используют по вполне конкретным экономическим соображениям, не понимаю в чём тут может быть удивление и вопрос. в цивилизованной стране человек покупает ЭТУ ардуину и ТОТ датчик. совместно с ВОН ТЕМ блоком питания он за один вечер собирает хоббийную поделку. всё работает, клац-тык и завелось. строитель не будет вникать, почему стм32 за цену в два раза большую чем атмега8 имеет начинку в 13 раз лучше. какой смысл в 16 таймерах, если на их изучение нужно потратить месяц? месяц вечеров стоит три доллара разницы, или атмега не потянет замок с Bluetooth? просто экономика, ничего личного.
                              кстати про экономику. одно из направлений фриланса в данной области — перевод хоббийных проектов или проектов выполненных непрофессионалами на более высокий уровень. когда Джонни из Техаса хочет сделать ворота с кастомным управлением то он покупает все детальки по цене своего обеда и стряпает вечерами корявую поделку на бредборде. уверяю, что это всё получается и работает. затем он заходит на апворк и вполне себе определённый Андрюша или Игорь переводит его схему в «правильный» вид. то же самое (не всегда) с софтом для этой поделки. как итог — у Билли есть ЕГО проект, выполненный на хорошем уровне и по цене половины кроссовка. ему нет нужды платить тонны зелени за местного инженера (помним, что АндрюИгорь — просто рабы) и не нужно вникать в проблемы отладки CorteX-M под фрибсд с джитаг и эклипс. а местный специалист получает работу + в той или иной степени развитие. все довольны, всем выгодно, правила экономики во всей красе.


                              1. olartamonov
                                16.08.2018 16:12

                                если на их изучение нужно потратить месяц?


                                Слушайте, может вы мне объясните, почему ардуинщики так железобетонно уверены, что весь остальный мир до сих пор Hello world на регистрах ассемблером пишет, а HAL только у них изобрели?..


                                1. n12eq3
                                  16.08.2018 17:48
                                  +1

                                  пока не видел, чтобы статьи про поделки на ардуино имели посыл вида «в wiring продуктивнее и легче писать код». и в любом случае её синтаксис проще для запоминания, даже с учётом наличия hal/spl или чего-либо ещё.
                                  получается что есть некая статья и в ней Петя, мидл программист на джаве, рассказывает как он наговнокодил свой проект. вытяжка работает, жалюзи открываются и всё замечательно. при этом железо убогое, код индусен полностью и выглядит всё как упавший на материнскую плату друшлаг макарон.
                                  затем вкатываются тру эмбеддеры и рассказывают что проще на стм, там шима больше, стоит на доллар дешевле и вообще модно сейчас.
                                  смотрелось бы нормально, если бы это был срач в сельском клубе, а тут получается что образованный, состоявшийся инженер (элита населения по сути) порет какую-то чушь, которая к теме статьи отношения не имеет. очень дико смотрится.


                                  1. olartamonov
                                    16.08.2018 18:52

                                    Простите, вы знаете, что в мире существуют операционные системы?


                              1. YourBunnyWrote
                                16.08.2018 16:49

                                > стм32 за цену в два раза большую чем атмега8 имеет начинку в 13 раз лучше

                                Поправочка — STM32F030F4P6 стоит этак в полтора-два раза дешевле AtMega8.


                                1. n12eq3
                                  16.08.2018 17:54

                                  в разовых поделках или в хоббийных проектах стоимость контроллера исчезающе мала и учитывать её как определяющий фактор очень глупо.
                                  красивая кнопка, фирменный разъём = могут стоить цену контроллера или даже больше.


                    1. dlinyj
                      16.08.2018 13:02

                      Вы знаете, я вот в 21 веке пишу в текстовом редакторе всё и компилирую в консоли. Так как мне все эти красивые IDE показлись неудобным избыточным говном. И знаете, я счастлив и мне удобно


                      1. vvzvlad
                        16.08.2018 15:06

                        Так замечательно. Но считать, что это не надо никому — ну, странно. Мне вот надо. Статический анализ на самом минимальном простом уровне предупреждает о 90% ошибок типа «обратился к переменной вне зоны видимости», прыжки к функциям экономит кучу времени на «а где эта функция, в каком файле», подсветка просто упрощает считывание кода. Из всего этого в Arduino «IDE» есть только последнее.


                        1. dlinyj
                          16.08.2018 15:16

                          Бро, я тут не говорю что это плохо. Говорю только о том, что удобно мне. Кто-то в vi вообще пишет.

                          Ну вот с твоей подачи попробую Visual Studio Code


                          1. vvzvlad
                            16.08.2018 15:18

                            Если не зайдет — попробуй саблайм. VS Code — она немного, как бы это сказать… хипстерская, в общем.


                  1. solarplexus
                    16.08.2018 19:45
                    +1

                    Это, конечно, ваше дело в чем писать, но отрицать то, что есть удобные IDE взамен ардуиновской — глупо.
                    Выдергивать разработчика из комфортной среды — так себе аргумент, т.к. речь не о их способностях, а о вас и вашего нехотеня использовать удобный инструмент. Это очень странно.
                    Например, я в поисках хорошего и удобного перепробовал много IDE и текстовых редакторов. Тем не менее, если вы заберете у меня все красявости и оставите перед голой консолькой, то я себя прекрасно буду чувствовать и в vim. Но предпочтения отдам, естественно, удобному IDE.


            1. IGR2014
              16.08.2018 11:19

              Простите конечно, но GCC-то вам лично что сделал? Отличный современны инструмент, который нормально поддерживается и прекрасно справляется с современными стандартами C/C++. Или вас работа из консоли смущает? Это может и моё оценочное суждение, но кнопочки — то для юзверей, а для разработчиков — приятные излишества без которых вполне можно жить потратив немного времени на обучение/привыкание.
              P.S. Я не ардуинщик, пишу на чистых плюсах. Просто стало обидно за принижение качеств любимого инструмента. И да, кроме GCC знаком с более современным Clang/LLVM, не отрицаю — тоже потрясающая вещь.


              1. olartamonov
                16.08.2018 11:21

                А вы из каких конкретно моих слов сделали вывод, что gcc мне что-то сделал, а работа из консоли смущает?..


        1. YourBunnyWrote
          16.08.2018 16:01

          1995? Нет, в 1995 году за текстовый редактор с криво прикрученными кнопочками Compile и Run могли разве что дать в морду. Arduino IDE даже до трупопаскаля тех лет — как до Китая раком.


          1. olartamonov
            16.08.2018 16:19
            +1

            Причём, что характерно, до того, как Atmel сгрёб под себя avr-gcc и вкрутил его в свою Студию, типовой средой разработки под AVR был WinAVR и Programmer's Notepad.

            То есть вот лет десять тому назад ещё.

            И Arduino IDE оно по удобство радикально так обходило, на голову. И кнопочек разных было много.

            P.S. Во!
            image


  1. lingvo
    15.08.2018 21:04
    +2

    Считаю, что правильные тесты должны тестировать и прошивку и железо, с ней связанное. А поэтому должны исполняться на внешней, по отношению к проверяемой плате, системе.


    1. solarplexus
      15.08.2018 21:09

      Есть в этом смысл. Не все аппаратное получится протестировать. Можно, конечно, тестовым внутренним таймером замерить частоту какого-нибудь другого таймера… В общем, много вопросов возникает.


      1. lingvo
        15.08.2018 22:18
        +1

        Есть такой раздел электроники — Design For Testability, который дает ответы на многие вопросы в этом плане.


  1. Mike_soft
    15.08.2018 21:35

    если уж доросли до юнит-тестирования — может, пора отказаться от среды Ардуино? в конце концов оставить железо от дуйни, а софт писать сразу по-человечески?


    1. olartamonov
      15.08.2018 22:58

      А железо-то зачем? Оно ж унылое, в нормальной разработке атмеги практически померли в связи со своей бессмысленостью.


      1. Karlson_rwa
        16.08.2018 00:16

        Ох, знали б вы, как заблуждаетесь. В смысле есть еще много людей с хорошим именем, у которых под капотом атмеги из китая за доллар штука.


        1. olartamonov
          16.08.2018 00:27

          В промразработке её ещё есть по историческим причинам — «чувак, мы эту стиралку двадцать лет на атмеге делаем, зачем что-то менять?».

          В железе, не обременённом таким наследием, смысла в атмеге чаще всего очень мало. Тем более, доллар — это уже вполне себе далеко не самый тощий Cortex-M0+, а для 8-битного процессора и вовсе как-то дофига.


      1. Mike_soft
        16.08.2018 07:02

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


        1. olartamonov
          16.08.2018 08:27

          Скажите, а вы в курсе, что не просто все эти датчики прекрасно работают не с ардуиной — но и если немножко от мира ардуины отойти, датчиков становится существенно лучше и больше?..

          Кроме того, проясните вопрос про серии: вот мне Aspencore в исследовании 2017 года по рынку встраиваемой электроники сообщает, что 8-битные процессоры используются в 12 % текущих проектов, а вы — что они выпускаются большими сериями. Мне кому верить?


          1. Mike_soft
            16.08.2018 09:53

            конечно же, я в курсе. просто мы говорим про разные сегменты: для ардуинщиков «разработка» — это соединить платки проводами, залить их соплеметом. этих платок тыщщи, но достаточно ограниченное количество — чтоб не связались выбором. за счёт этого массовые и дешёвые. мне как-то оказалось дешевле купить «датчик дня ардуины» и выпаять оттуда bmp805, чем покупать их поштучно.


            1. olartamonov
              16.08.2018 09:57

              Я слышал, с датчиками «для ардуины» теперь есть прекрасный новый фокус: берётся плата с надписью «BME280», на неё впаивается втрое более дешёвый BMP280, и продаётся по цене ниже, чем стоит один только голый чип настоящего BME280.

              Массово, дёшево.

              Пока там ардуинщик ещё разберётся, почему у него влажность прочитать не получается…

              P.S. Датчики физических величин не стоит лишний раз перепаивать, им от этого плохеет.


              1. Mike_soft
                16.08.2018 09:57

                и ардуины и ихние (или евойные?) датчики к встраиваемым системам никакого отношения не имеют. это нечто учебное или одноразовое.


                1. olartamonov
                  16.08.2018 09:59

                  У меня стиральная машинка (немолодая, без всех этих вайфаев и цветных экранов) на какой-то там жирной атмеге, она учебная или одноразовая?

                  Или учебность и одноразовость определяются формой печатной платы?


                  1. Mike_soft
                    16.08.2018 10:00

                    возможно. есть и приколы с лазерной перемаркировкой корпусов. это уже, увы, издержки. т.е. это не принципиальное свойство дуйни


                  1. Mike_soft
                    16.08.2018 10:05

                    дуйня-это в первую очередь не атмега, а плата с каким-то там контроллером-процессором-чипом. под которую есть некая среда разработки, под которую выпускают датчики, которые помогут подключить на форумах…


                    1. olartamonov
                      16.08.2018 10:08

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

                      Потому как «плата с чипом, под который есть среда» — это и NRF52-DK с Mbed, и SmartRF06 с TI RTOS, и многое другое. И от ардуины они качественно отличаются.


                      1. Mike_soft
                        16.08.2018 10:15

                        именно! «из Г&П!»®
                        и многим этого более чем достаточно.
                        некоторые через это приходят (начинают) в более взрослый мир. а остальным не нужно ни ардуины, ни nrf…


                        1. olartamonov
                          16.08.2018 10:16

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


                          1. Mike_soft
                            16.08.2018 10:21

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


                  1. Mike_soft
                    16.08.2018 10:10

                    учебность определяется почти нулевым порогом вхождения. одноразовость — да, «формой платы». типовая платка, на которой можно с определенными издержками сваять какое-то устройство с типовыми датчиками. управление вентилятором в сральнике от датчика освещения и закрытия двери.


  1. n12eq3
    15.08.2018 21:56

    добрый день.
    есть ли реальный опыт применения такого подхода и примеры, где это было реализовано и использовано? примеры для наглядности приведены неправильные и плохие, т.к. не учитывать размерность переменных при работе с контроллерами и одновременно писать крутой код, покрывать его тестами — нельзя. физически нельзя, такого в мире не бывает. а если это встретится то речь идёт про аутиста и это не лечится.
    пример с двигателем плох в том, что при «статической» работе, когда контроллер просто крутит двигатель — именно отладчик (или симулятор) быстро показывают где затык. также частая возможная проблема — если используются несколько прерываний и в одном из них, например, используется математика, то прерывания с более высокими приоритетами могут нарушать работу в плане нехватки времени (должно было посчитаться внутри прерывания, не успело). такое тоже отлавливается отладчиком + осциллограф, комплексный подход для живого железа.
    могут ли описанные тесты подойти для ситуации когда часть условий (внешние сигналы) влияют на ход выполнения кода и не могут быть имитированы тупым «элементарным ручным вызовом обработчика»?


    1. sadr0b0t Автор
      16.08.2018 00:19

      есть ли реальный опыт применения такого подхода и примеры, где это было реализовано и использовано?


      не претендуя на «реальность», есть более развернутый пример:
      github.com/sadr0b0t/stepper_h/blob/master/stepper_test/stepper_test.cpp

      это моя библиотечка для управления шаговыми моторами, основанная на подсчете шагов и отправке импульсов фоном из прерывания таймера. В общем, примерно то, что описано в примере, только для нескольких моторов и с более ветвистой логикой. Собственно, эти тесты я начал применять и обкатал подход именно в ней, т.к. в один момент там внутри начали происходить странные вещи, которые без потактового разбора за пределами железки было крайне сложно, если вообще возможно, отловить. Добавление тестов помогло отловить и исправить многие проблемы уровня «недосчитались шага на последнем такте, если время шага некратно частоте импульса таймера».

      могут ли описанные тесты подойти для ситуации когда часть условий (внешние сигналы) влияют на ход выполнения кода и не могут быть имитированы тупым «элементарным ручным вызовом обработчика»?

      появление внешних сигналов можно легко имитировать при помощи мокапов, ситуацию можно разложить на что-то вроде: вот пришел внешний сигнал (записали состояние в мокап), вот выполнили тестируемый участок кода (он внутри проверяет состояние сигнала), вот он дал такой результат; вот пришел другой сигнал, вот опять выполнили этот участок, вот он дал другой результат и т.п. Если речь о том, что от сигнала идет прерывание и он там меняет какие-то переменные, которые используются внутри тестируемого участка, в любой произвольный момент, то не знаю, наверное это тоже можно как-то протестировать, если разложить тестируемый кусок на более мелкие запчасти и проверить поведение в каждом из возможных промежутков. В общем, идея — свести ситуацию к множеству последовательных событий (и их разных комбинаций) в одном потоке, тогда в каждой контрольной точке можно делать предсказуемые проверки. Если прям нужно много потоков, не знаю, возможно и есть такие техники для модульных тестов, я не в курсе.


  1. E_Art
    15.08.2018 22:45
    -1

    Да, для каждого используемого вызова API Ардуино мы добавляем в проект собственную заглушку:


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


    1. sadr0b0t Автор
      15.08.2018 22:50
      +2

      Заглушки и мокапы — это не костыли, а концептуальный элемент технологии модульного тестирования


  1. GREGOR_812
    15.08.2018 23:54

    В покрытом тестами проекте может быть сложно проводить глобальную реорганизацию кода (рефакторинг)

    Это неверное утверждение, т.к. юнит-тесты во многом как раз и служат для того, чтобы безболезненно проводить рефакторинг или другие изменения кода.

    Что касается «писать модули так, чтобы можно было их использовать и в тестах, и в прошивке» — это решается выбором необходимого уровня абстракции. В C++ это, имхо, вообще не проблема (правда, я не уверен на 100%, что в ардуино-проектах можно использовать такие вещи, как виртуальные функции и интерфейсы)


    1. iCpu
      16.08.2018 07:57

      (правда, я не уверен на 100%, что в ардуино-проектах можно использовать такие вещи, как виртуальные функции и интерфейсы)
      Я просто оставлю это здесь.
      Не столько вам, сколько всем остальным.


  1. GREGOR_812
    16.08.2018 00:30

    Кстати, слово "мокап" тут неверно употребляется. Вероятно, имеется в виду "мок". Мокап — это графический макет (например, для GUI)


    1. sadr0b0t Автор
      16.08.2018 00:35

      вообще да, но по смыслу оба слова довольно близки, и то и другое можно перевести, как макет
      www.multitran.ru/c/m.exe?CL=1&s=mockup
      www.multitran.ru/c/m.exe?l1=1&l2=2&s=mock


  1. AVI-crak
    16.08.2018 03:50

    Насколько я верно понял, все обнаруженные ошибки сводятся к использованию int.
    Разный результат на разных чипах, что не удивительно.


  1. locutus
    16.08.2018 06:11
    +1

    Интересная статья, но остаётся ощущение некоторой костыльности. Для своих проектов на AVR8 и STM32 используем систему сборки Ceedling с тестовым фреймворком Unity и библиотекой генерации моков CMock. Ну и используем достаточный уровень абстракции на C, чтобы код был по большей части платформонезависимым и переносимым между указанными платформами. Плюс некоторая дисциплина написания кода и понимание стандарта C. Та же работа с GPIO может быть описана как драйвер, у которого есть несколько вариантов работы в зависимости от таргета. А пользовательский код уже вызывает функции этого драйвера, что уже тестируется путем создания моков на указанные функции. Для чувствительных библиотек, которым вот прям надо работать с железом как можно теснее, им можно скармливать коллбэки на необходимые функции. В итоге ситуация, когда требуется лезть в GDB и смотреть, что же такое не работает, возникает очень редко.


    Удобно: смотришь datasheet, errata, конструируешь архитектуру, пишешь тесты, пишешь код, пишешь приемочные тесты (для автоматического прогона на железе основных функций) — и все работает сразу.


  1. Indemsys
    16.08.2018 10:17

    Спорный вопрос в том действительно ли написание и отладка модульных тестов таких простейших функций, как в статье по времени сравнима с аппаратной отладкой.
    Тесты конечно писать приходится, здесь спору нет, но это тесты действительно сложных вещей типа файловых систем, как например, вот эти
    А функции работающие в жестком реальном времени проверять и надо в жестком реальном времени. Для этого у микроконтроллеров на ARM Cortex-M (Arduino делают и на них) есть механизм трассировки.
    Проблему отладки управления шаговым двигателем он бы решил за пару минут.
    Трассировочные вставки там тоже пришлось бы писать, но они во-первых очень короткие и могли бы остаться даже в релизном коде, во-вторых их можно написать не менее чем в 10 раз быстрее чем любые модульные тесты, и переделывать архитектуру кода этот подход не требует.


  1. smart_alex
    16.08.2018 11:06

    Спасибо, статья и идеи, освящённые в ней, весьма интересны и содержательны и вполне (в том или ином виде) могут быть применены на практике для тех или иных конкретных случаев. Ждём следующих, столь же содержательных статей.


  1. aleaksah
    16.08.2018 14:17

    >запуск на обычной Arduino Uno (чип AVR 16 бит)
    AVR 16 бит? Вы уверены?


    1. dlinyj
      16.08.2018 15:18

      Думаю имелось в виду, что он позволяет 16-ти битную арифметику.


    1. sadr0b0t Автор
      16.08.2018 16:00

      вы правы, формально битность архитектуры определяют по битности регистров, которые в AVR 8 бит, хотя адреса, команды и арифметика у нее по большей части 16-битные. Подумаю, как лучше поправить, чтобы не вводить людей в заблуждение, но и не запутать при этом


      1. dlinyj
        16.08.2018 16:08

        Прямо сказать, что говорится об арифметике.


      1. Bratak
        17.08.2018 12:53
        +1

        Ваш порыв привнести в эмбеддед-разработку что-то новое похвален, но ваши знания в области железа и архитектуры МК негативно влияют на результаты вашей работы. Вы же должны понимать, что среда выполнения на ПК (где модульное тестирование во многих случаях очень даже уместно) и среда выполнения на МК отличаются слишком сильно. В микроконтроллере нет строгой мамы-операционной системы, которая будет следить за тем, чтобы обращения по адресам ОЗУ происходили строго в заданном сегменте, не выходя за границы разрешенной области памяти. В AVR вообще разграничение оперативной памяти между пользовательской частью и ОС (если она используется) может быть реализовано только программно. Прерывания, таймеры, стек — за все этим следит только программист, а не ОС. Это в рантайме операционки все отлажено, можно считать что пользовательская программа выполняется в идеальных, лабораторных условиях. А на МК вокруг пропасти, ямы с шипами, орки и бутлупы.
        Если говорить дальше, то важный момент — методы, которые используются при программировании на МК: большинство программ представляют собой или конечные автоматы, или исключительно конкретные реализации приложений, написанные исключительно для исключительных задач. Никаких шаблонов, обобщенного программирования, даже ООП в большинстве случаев неуместно.
        Любой серьезный эмбеддед-разработчик знает используемую архитектуру досконально, ему не нужны в принципе никакие тесты для разработки драйвера для какой либо периферии, поскольку он использует в каждой новой программе десятки раз используемый код, написанный им самим и проверенный сто раз. В обычных случая есть аппаратный дебаг, который позволяет не только найти ошибку, но и глубже понять механизмы функционирования периферии или вычислительной части МК.
        Вы же вообще используете библиотеки ARDUINO, написанные неизвестно какими индусами, содержащие не один десяток ошибок, которые может и не вылезают при решении каких-то тривиальных задач, но которые поломают вам жизнь, если вы попытаетесь использовать их в какой либо более-менее серьезной разработке. К этим же библиотекам вы прикручиваете снова бог знает какие библиотеки для реализации всяких вещей с периферией, без понимания их реализации. И конечным этапом прикручиваете библиотеки для использования юнит-тестов, которые тоже в свою очередь содержат кучу багов. Да-да, даже код ядра операционной системы (молчу про окружение и все остальное) содержит сотни и тысячи больших и мелких багов, если вы зайдете на kernel.org, то можете неплохо позабавится. И это код ядра, над которым работают сотни и тысячи талантливых программистов со всего мира. И теперь представьте, сколько багов надо победить в вами любимых библиотеках, чтобы правильно начать искать собственные ошибки. Я думаю посидеть пару-тройку дней с датащитом на контроллер даст куда более позитивные результаты.


        1. Indemsys
          17.08.2018 15:45

          Автор хоть и начинает статью с модульных тестов, но в его проекте на github сделаны интеграционные тесты. Он на самом деле не использует модульных тестов в своем проекте на микроконтроллере. Идет скорее спекуляция на громком названии.

          Интеграционные тесты абсолютно необходимы если мы имеем дело с двигателями. Прежде всего для отлова проблем со сбоями и ошибками вызванными помехами или ошибками планирования ресурсов времени. Что автор старательно и выполняет, причем достаточно неэффективно, поскольку для таких тестов можно было бы и пользовательский интерфейс разработать на целевой платформе и инструментальные утилиты на PC.