Как написать неблокирующий код, для опроса датчика на 100 Гц
Я использую этот фрагмент кода, чтобы попытаться опросить датчик IMU на частоте 100 Гц (для библиотеки AHRS sensor fusion library).
void loop(void)
{
// неблокирующие переменные кода
static uint32_t last_ms;
uint32_t ms;
// задержка между выборками
ms = millis();
if (ms - last_ms < 10) return;
last_ms = ms;
sensor_data (); //...
Мой 1-й вопрос заключается в том, должен ли я объявлять last_ms
как глобальную переменную, а не статическую?
Кроме того, должен ли этот код быть в начале или в конце цикла? Насколько я понимаю, мы в основном проверяем, сколько времени занял предыдущий цикл. И если это заняло менее 10 мс (100 Гц), мы ничего не делаем. Но разве это не означает, что мы все еще можем получить отклонение от 100 Гц из-за времени, необходимого для завершения цикла (около 2-5 мс в моем случае), так что фактически это 10 мс + время завершения цикла?
Извините, если это глупый вопрос.
2 ответа
Лучший ответ:
должен ли я объявлять
last_ms
как глобальную переменную, а не статическую?
Локальная статика лучше, так как она ограничивает область действия переменной именно
там, где это необходимо. Подумайте о том, чтобы сделать его глобальным, только если код является частью
учебника, предназначенного для начинающих, которые могут быть смущены ключевым словом
статика
.
должен ли этот код быть в начале или в конце цикла?
Условное раннее возвращение имеет смысл только в том случае, если loop()
больше нечего делать после обработки данных датчика, поэтому это должно произойти в
самом конце loop()
. Однако, если loop()
делает больше, чем просто обрабатывает
эти данные, я думаю, что было бы чище избежать раннего возврата: либо
поместите это в отдельную функцию (которая может вернуться рано), либо обусловьте
обработку датчика ms - last_ms >= 10
.
Насколько я понимаю, мы в основном проверяем, сколько времени занял предыдущий цикл.
Нет. Тест смотрит, сколько времени прошло
с момента предыдущего
обновления last_ms, что может быть много итераций цикла()
назад.
Но разве это не означает, что мы все еще можем получить отклонение от 100 Гц из-за времени, необходимого для завершения цикла (около 2-5 мс в моем случае), так что фактически это 10 мс + время завершения цикла?
Действительно. При такой логике минимальное время между вызовами
функции sensor_data () - 10 мс
. Если вы хотите обеспечить точное среднее
время опроса, вам следует обновить last_ms
, добавив 10. Однако у вас все равно будет
единственное дрожание.
Небольшое замечание о millis()
: поскольку значение обновляется каждый раз
1.024 мс, иногда ему приходится прыгать сразу на две единицы, и
поэтому он имеет 1 мс дрожания. В течение 10 мс я
бы предпочел использовать micros()
. Например:
const uint32_t POLLING_PERIOD = 10000; // 1e4 us = 10 мс
// Вызовите это из loop()
void handle_sensor()
{
static uint32_t last_us;
if (micros() - last_us < POLLING_PERIOD) return;
last_us += POLLING_PERIOD;
sensor_data();
// ...
}
Edit: О том, почему last_us
должен быть увеличен на постоянную
величину, рассмотрим эту альтернативу:
void handle_sensor()
{
static uint32_t last_us;
uint32_t now = micros()
if (now - last_us < POLLING_PERIOD) return;
last_us = now;
sensor_data();
// ...
}
Код, следующий за if ... return;
строка будет выполнена, когда
тестируемое условие равно false, т. е. когда now - last_us >= POLLING_PERIOD
.
Может случиться так, что мы иногда имеем строгое равенство (now - last_us == POLLING_PERIOD
), но, поскольку выполнение любого кода требует времени, иногда
теперь - last_us
будет строго больше, чем POLLING_PERIOD
. Каждый
раз, когда это происходит, last_us
будет увеличиваться более чем на
ОПРОС_ПЕРИОД
. Это означает, что небольшие ошибки синхронизации накапливаются.
Если вместо этого
last_us обновляется путем добавления постоянной суммы, у нас все еще
есть ошибки синхронизации (из-за времени, необходимого для выполнения любого кода), но
они не являются кумулятивными. Другими словами, у нас есть джиттер, но
средняя частота опроса правильная.
Большое вам спасибо за подробный ответ Эдгара. Приятно видеть, что ты все еще помогаешь новичкам. Я попробую обновленный код с помощью micros. Чтобы проверить, что я понял, мы используем micros, чтобы избежать дрожания. Все еще немного смущает разница между добавлением периода опроса в нашу переменную сравнения вместо сравнения Миллиса с миллисом. Я чувствую, что что-то упускаю, потому что мне кажется, что они оба достигают одного и того же: ждут, пока пройдет 10 мс, и только потом начинают обрабатывать данные.. Хотя вы подразумеваете, что есть разница., @Zhelyazko Grudov
Спасибо за редактирование, теперь я понимаю., @Zhelyazko Grudov
Диапазон времени выполнения кода датчика составляет от 10 мс до 10 мс + время выполнения первой части цикла (до возврата) и время выполнения вне цикла ()
. Эти дополнительные времена не всегда применимы, но в худшем случае вы можете иметь до этой дополнительной задержки (потому что это означает, что вы проверяете значение millis()
только так часто).
Упомянутая дополнительная задержка довольно коротка (область микросекунд). Ваш код датчика выполняется только по достижении заданного времени. До тех пор, пока вы не отключите прерывания, прерывание Timer0 будет продолжать отсчитываться для millis ()
, поэтому его значение будет увеличиваться независимо от времени выполнения кода датчика.
Это до тех пор, пока код датчика также не займет больше 10 мс (или немного меньше, учитывая некоторые накладные расходы). Поскольку ваш код датчика занимает всего до 5 мс (в соответствии с вашим вопросом), вы должны быть в безопасности здесь.
Если millis()
достаточно точен для вас, я не могу сказать. Это зависит от ваших точных требований. Для более точного отсчета времени лучше использовать собственное прерывание таймера, хотя это также ограничивает вас в том, что вы можете сделать. Внутри прерывания вы не можете выполнять код, который полагается на прерывания (например, связь по последовательному или I2C). В зависимости от ваших датчиков использование приведенного выше кода уже достаточно и должно работать хорошо.
должен ли я объявлять last_ms как глобальную переменную, а не статическую?
Окончательного ответа на этот вопрос нет, но, как правило, полезно ограничить область действия переменных до необходимого минимума (чтобы избежать возможных конфликтов). last_ms
больше нигде не нужен, поэтому создание его локально и статично-хороший способ сделать это. С точки зрения памяти это на самом деле не имеет значения.
Привет, Крис, спасибо за еще один подробный ответ., @Zhelyazko Grudov
- Как установить таймеры, используя миллисекунды на 3 датчиках PIR?
- Область действия объекта RtcDateTime при объявлении вне функции
- Работа с PulseIn() и Millis().
- Как написать скрипт, если датчик воды контактирует с водой более 2 минут и загорается светодиод
- Игнорирование первого высокого выходного сигнала датчика
- Объявление глобальных переменных в отдельном файле: конфликт компилятора
- Зачем использовать несколько фильтров, выводящих тысячи значений, использовать один или два фильтра, которые могут выводить нормальное значение?
- Как игнорировать датчик, срабатывающий в первый раз, и начать запись в миллисекундах при втором срабатывании
см.Пример BlinkWithoutDelay. Я думаю, что его легко изменить с 1 Гц на 100 Гц, @Juraj
Если вы хотите запустить что-то на частоте 100 Гц, вы, вероятно, захотите использовать прерывание таймера, а не возиться со значениями задержки., @Dave Newton
Я буду экспериментировать с этим спасибо, @Zhelyazko Grudov
1. статический/глобальный; мех, на самом деле то же самое. 2. это должно быть 100 Гц, так как вы перезапускаете часы перед запуском задачи, так что он не будет дрожать, если задача займет больше 10 мс, но если это так, то он все равно никогда не будет работать., @dandavis
Можете ли вы настроить это конкретное устройство так, чтобы оно использовало прерывания, а не опрос?, @Persistence
это BMX160 от Bosch, и я так думаю. Я буду экспериментировать с этим, но хотел бы понять и логику этого., @Zhelyazko Grudov