Асинхронные вызовы функций в скетче ардуино

Есть ли в скетче Arduino способ выполнять асинхронные вызовы функций внутри цикла? Например, слушать запросы через http-сервер и обрабатывать их неблокирующим способом.

, 👍24

Обсуждение

Если вы не новичок в C++, то вы можете взглянуть на библиотеку Cosa (https://github.com/mikaelpatel/Cosa). Это полная переработка библиотеки Arduino, чтобы сделать ее лучше: ООП и, как правило, управляемой прерываниями. Я думаю, что они поддерживают HTTP, но я еще не проверял эту часть., @jfpoilpret


3 ответа


16

И да, и нет. Вы не совсем ясно представляете, что хотите сделать. Я разделил это на несколько разных разделов (в основном посвященных считыванию данных датчиков... это применимо ко всему, но я использую именно этот контекст):

Обсуждения

Насколько я знаю, все Arduino имеют только одно ядро (они могут делать одно дело одновременно). Для большинства плат Arduino аппаратная многопоточность не поддерживается. Однако есть способы реализации многопоточности в программном обеспечении. Подход AsheeshR не будет хорошо работать для функций, выполнение которых занимает много времени (т. е. что-то в библиотеке, что требует времени или задержки), потому что они будут забиты этими инструкциями, но он будет хорошо работать для коротких такие функции, как pinMode(). Перечисленная там библиотека Protothreads может быть лучше, но я точно не знаю.

Было бы сложно организовать это с помощью HTTP, тем более, что вам нужно сделать

Задержки

Распространенный способ остановки скетча – использование задержки. Эту проблему можно решить, используя в основном цикле оператор if и millis. () функция, которая возвращает время (не часы, а время с момента запуска Arduino). Вы также можете сделать цикл внутри цикла для опроса данных датчиков.

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

Прерывания

Прерывания — отличный способ сохранить почти асинхронность. Они запускают короткий фрагмент кода (который вы укажете) каждый раз, когда изменяется состояние вывода. Он отрывается от loop() и возвращается к тому месту, где остановился после запуска "ISR". У меня не так много времени, чтобы объяснять, как это сделать, но быстрый поиск в Google даст много результатов.


Судя по вашему примеру, раздел потоков будет наиболее подходящим. Эта тема довольно расплывчата, поэтому вам придется кое-что поэкспериментировать и найти то, что работает.

,

3

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

Процессор Arduino по своей сути является однопоточным и не может выполнять несколько задач одновременно. Как уже упоминалось, существуют способы создать иллюзию многозадачности. Annonomus Penguin очень хорошо их уловил.

Также проверьте TimerOne (наверное, он лучше ;)

,

TimerOne — плохая идея, потому что он работает только с Arduino на основе avr. Например мой Wemos D1 R1 не работает, @Vyachaslav Gerchicov


2

Вы определенно можете создавать асинхронный код на устройствах только с одним потоком. Вот что такое "статический" ключевое слово for в C. Оно помогает вам настроить функции как "конечный автомат" чтобы они могли работать одновременно с другими функциями.

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

char read_bit_sync()
{
    while ( digitalRead(PIN_CLOCK) ) {}
    while ( !digitalRead(PIN_CLOCK) ) {}
    return digitalRead(PIN_DATA);
}

void loop()
{
    char c = read_bit_sync();
    Serial.println(c);
}

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

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

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

int read_bit_async()
{
    static int state = 0;
    int ret;

    switch (state)
    {
        case 0:
            if ( digitalRead(PIN_CLOCK) )
            {
                return -1;
            }
            state++;
            return -1;
        case 1:
            if ( !digitalRead(PIN_CLOCK) )
            {
                return -1;
            }
            state++;
            return -1;
        default:
            ret = digitalRead(PIN_DATA);
            state = 0;
            return ret;
    }
}

void loop()
{
    int c = read_bit_async();
    if (c != -1)
    {
        Serial.println(c);
    }
}

В приведенном выше примере функция начинается с символа "0" состояние, в котором он будет оставаться в этом состоянии до тех пор, пока не прочитает, что на тактовом выводе высокий уровень. Затем он переходит в "1"; состояние, в котором он будет оставаться до тех пор, пока на тактовом выводе не появится низкий уровень. Затем он переходит на "2"; состояние, которое в следующий раз, когда вы его прочитаете, вернет прочитанный бит, а затем сбросит свое внутреннее состояние на «0». состояние.

Теперь в основном цикле мы можем вызывать read_bit_async() на каждой итерации цикла, и он будет возвращать -1, когда он все еще находится в состоянии "ожидания" бит, но когда он получил бит, мы получаем фактическое значение бита. Основной цикл может продолжать работать и обрабатывать другой код, ожидая получения бита.

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

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

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

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

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

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

,