Непрерывное чтение двух последовательных периодов импульса
Я контролирую скорость двигателя/вентилятора постоянного тока с помощью ШИМ с помощью переменного аналогового входа. Использование Uno или Nano.
Цикл выглядит следующим образом:
void loop()
{
val = analogRead(analogPin0); // прочитать пин аналогового входа
analogWrite(pwmPin9, map(val, 0, 1024, 0 , 255));
}
Вышеуказанное значение AnalogPin0 изменяется на потенциометр.
Но я также хочу выводить скорость вращения на ЖК-дисплей. Для этого мне нужно измерить период между двумя последовательными импульсами, как показано ниже:
Как я могу непрерывно измерять "период между двумя последовательными импульсами"? Должен ли я использовать прерывание? У кого-нибудь есть опыт?
@user1245, 👍0
Обсуждение2 ответа
Лучший ответ:
Я вижу два пути:
Вы можете прикрепить к выводу простое прерывание по переднему фронту с помощью
attachInterrupt()
и подсчитать нарастающие фронты с помощью переменной. Если ощущается третий нарастающий фронт, у вас есть 1 период. Чтобы измерить время, вы можете сохранить время начала первого переднего фронта в переменной с помощьюmicros()
(что дает вам время с момента запуска в микросекундах). По третьему переднему фронту сохраните разницу во времени между началом и концом в другую переменную (объявленную как volatile):micros()-start_time
. Затем эту переменную можно отобразить в вашей функцииloop()
. Обязательно скопируйте переменную только в локальную переменную в этой части и отключите прерывания во время этой операции с помощьюnoInterrupts()
(после этого повторно активируйте прерывания с помощьюinterrupts()
. Также вы следует использовать атомарную переменную флага (атомарное значение, что запись или чтение этой переменной не может быть прервана. Например, если вы используетеbyte
или любую другую 1-байтовую переменную), чтобы показатьloop()
, что есть новые данные для отображения. Только в этом случае код в функцииloop()
будет обрабатывать данные.Если вы хотите проявить фантазию или хотите измерять более высокие скорости (более низкие периоды), вы можете использовать оборудование Timer1 в качестве счетчика. Вы даете Timer1 свой сигнал в качестве тактового входа. На каждом фронте значение таймера увеличивается на 1. Таким образом, когда значение таймера равно 5, прошел один период. Вы можете использовать таймер в режиме сравнения совпадений, чтобы он генерировал прерывание, если в регистре значения таймера находится специальное значение (здесь 5). В этой ISR (подпрограмме обслуживания прерываний) вы должны записать текущее время с помощью
micros()
. При совпадении сравнения таймер генерирует прерывание, снова устанавливает значение на ноль (если настроено правильно) и снова начинает считать. Таким образом, период будет временем между двумя последовательными измерениями времени. Отображение в функцииloop()
можно обрабатывать по принципу, описанному выше.
Кроме того, было бы неплохо напрямую взять среднее значение за период (чтобы отфильтровать шум в периоде) путем подсчета, пока не пройдет несколько периодов. В первом случае вы могли сосчитать до 11, чтобы получить 5 периодов. Затем разделите разницу во времени на 5, чтобы получить 1 период. Во втором случае вы можете установить регистр соответствия Timer1 на 21, чтобы получить 5 периодов. Здесь также разделите время на 5, чтобы получить 1 период.
Поскольку вы кажетесь новичком, я бы предложил первый способ: использование attachInterrupt()
. Второй способ больше подходит, если вы хотите покопаться в регистрах микроконтроллера и описаниях в даташите.
Первый способ может выглядеть примерно так:
const int pin = 2;
volatile byte counter = 0;
volatile unsigned long start_time;
volatile unsigned long period;
volatile byte data_flag = 0;
void speed_ISR(){
counter++;
if(counter==3){
unsigned long now = micros();
period = now - start_time;
data_flag = 1;
start_time = now;
counter = 1;
}
}
void setup(){
pinMode(pin, INPUT);
attachInterrupt(digitalPinToInterrupt(pin), speed_ISR, RISING);
}
void loop(){
if(data_flag){
noInterrupts();
unsigned long local_period = period;
data_flag = 0;
interrupts();
// Теперь отображаем период на ЖК-дисплее
} else {
// Вывести ноль на ЖК-дисплей
}
}
Код дает вам возможность записывать ноль на ЖК-дисплей, когда нет импульсного входа (следовательно, нет данных --> data_flag должен быть равен нулю). Это может быть неудобно для чтения на низких частотах импульсов, если ЖК-дисплею не требуется больше времени для отображения результатов, поскольку будет измерен следующий импульс, поскольку он может отображать ноль и следующее измерение в быстрой последовательности. Вы можете либо использовать millis()
, чтобы установить data_flag
в ноль через указанное время (вместо того, чтобы сбрасывать его рядом с кодом ЖК-дисплея), либо вы можете ограничить скорость отображения, как мерцание выполняется в примере BlinkWithoutDelay
, который поставляется с Arduino IDE.
Обратите внимание, что этот код не тестировался.
Я получаю сообщение об ошибке: ожидаемое первичное выражение перед токеном "==". это в строке if(byte==3){, @user1245
будет ли байт счетчиком?, @user1245
А также наличие «NoInterrupts» не было объявлено в этой области «, @user1245
Хорошо, опечатки. Да, оператор if в ISR должен быть со счетчиком. Также это noInterrupts()
, а не notInterrupts
. Извините, я изменил свой ответ, @chrisl
И в ISR не хватает 2 точек с запятой. Я надеюсь, что теперь это структурно правильно, @chrisl
Какие контакты вместо контакта 2 я могу использовать?, @user1245
Я изменил вызов attachInterrupt, чтобы он был действительно правильным, как в документации. Вы можете посмотреть [здесь][https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/] таблицу, какие выводы можно использовать. В документации сказано, что на Uno вы можете использовать контакты 2 или 3., @chrisl
micros()
следует вызывать только один раз в ISR: unsigned long now = micros(); период = сейчас - время_начала; время_начала = сейчас; ...
Обратите внимание, что только period
и data_flag
должны быть volatile
. counter
и start_time
могут быть статическими
локальными переменными для speed_ISR()
., @Edgar Bonet
Насчет volatile переменных: Да, вы правы. О micros()
: почему нельзя вызывать micros()
несколько раз? Значение не изменится во время ISR, так как это зависит от прерываний., @chrisl
@chris Не могли бы вы обновить свой код? Будет ли эта часть находиться за пределами ISR unsigned long now = micros(); период = сейчас - время_начала; время_начала = сейчас; ?, @user1245
Вызов micros() занимает нетривиальное количество циклов. И его значение _будет_ изменяться во время ISR, так как [оно зависит от таймера](https://github.com/arduino/ArduinoCore-avr/blob/1.6.23/cores/arduino/wiring.c#L86)., @Edgar Bonet
Моя другая проблема: я не мог изменить программу для вывода нуля, когда нет импульсного входа., @user1245
if(!data_flag) сделает это, я думаю?, @user1245
@atmnt Я снова изменил код, так как Эдгар прав насчет micros()
. В отличие от millis()
, micros()
также будет изменяться во время ISR, так как он считывается непосредственно из регистра таймера. Также я добавил запрошенный функционал, описание подводных камней и как их избежать., @chrisl
Прерывание одного из таймеров кажется самым простым способом. Чтобы получить точность измерения, скажем, 3% при 4 мс, означает выборку каждые (4000 мкс * 0,03) = 120 мкс (8,333 кГц). Если вашему коду больше нечего делать, вам может сойти с рук наблюдение за часами — micros()
отсчитывает каждые 4 мкс, — но вам нужно будет сохранять все остальное, что вы делаете между чтениями micros ()
до менее чем 120 us, и половина этого может быть хорошим целевым значением.
В этом ответе я описал общую неблокирующую процедуру для своевременного управления несколькими задачами. Если вы выбрали такой дизайн, одна задача может управлять потенциометром и аналоговым выходом, другая — измерением оборотов, а третья — ЖК-дисплеем. ISR ничего не сделает, кроме как установит флаг.
Доход на тысячу показов & Задачи ЖК-дисплея могут выполняться с относительно низкой скоростью, поскольку они предназначены только для потребления человеком. Однако им, возможно, придется работать по частям, чтобы уложиться в ограничения по времени.
- Какова работа pulseIn?
- Функция Pulsein() блокирует одновременное выполнение других задач
- Как использовать SPI на Arduino?
- Как решить проблему «avrdude: stk500_recv(): programmer is not responding»?
- Как создать несколько запущенных потоков?
- Как подключиться к Arduino с помощью WiFi?
- avrdude ser_open() can't set com-state
- Как узнать частоту дискретизации?
О каком временном диапазоне здесь идет речь?, @chrisl
Длительность одного импульса от минимум 4 мс до 100 мс, @user1245
зачем нужно измерять сигнал? ... вы точно знаете значение параметра, который вы передаете в функцию AnalogWrite(), @jsotola
Я не знаю точных оборотов, только рабочий цикл, @user1245
вы не забыли рассказать нам о датчике оборотов? ...непонятно как вы рассчитываете определить обороты вентилятора по ширине импульса напряжения питания, @jsotola