Измерение времени прерывания
- Войдите на сайт для отправки комментариев
Имеется такой вопрос: как посчитать время, в течении которого программа выполняет внешние прерывание, в микросекундах?
Имеется основной код, в котором в зависимости от входных условий наступает внешнее прерывание, длительность которого будет примерно несколько тысяч микросекунд. Соответственно по выходу из прерывания счётчик Arduino nano (Atmega 328) переполняется несколько раз (вычитал, что переполнение происходит при 1024 мксек). Поэтому время, в течении которого длилось прерывание, получается некорректным (считаем разность между временем, когда наступило прерывание и когда оно закончилось).
Может быть имеется возможность записывать значения счётчика по мере его переполнения, не выходя из прерывания?
Буду благодарен если посоветуйте как можно реализовать данную задачу или подскажите литературу (в интернете пока ничего толкового не нашёл).
Правильнее не делать в прерывании ничего такого. что может потребовать несколько миллисекунд (нескольких тысяч мкс). Общепринятый подход таков - если нужно по прерыванию делать что-то длинное - в прерывании только взводим флаг (операция занимает всего несколько тактов МК) и выходим из прерывания, а собственно всю работу делает основной цикл программы.
В этом и суть, что хотелось бы отойти от общепринятого и сделать как я описал выше
О каком счётчике речь? Давайте код.
В этом и суть, что хотелось бы отойти от общепринятого и сделать как я описал выше
если ваше прерывание будет занимать систему больше определенного времени - нарушится работа системных прерываний и последствия будут непредсказуемы
Да собственно данный код писался Вами, чем Вы в своё время мне очень помогли
Только на тот момент я измерял миллисекунды, а теперь надо микро
Вставляете текст программы, которая в прерывании должна выполняться , в основной поток и обрамляете микрос() до и после. Разность даёт время. А то что она не в прерывании значения не имеет, практически.
А где micros в коде?
И я так и не понял о каком счётчие Вы говорите.
Если Вы говорите о micros, так он переполняется совсем не так часто, как Вы думаете - раз в 4 294 967 295 / 1000000 секунд, т.е. реже, чем раз в час.
Пробовал отрабатывать данный код на millis и на micros, на millis результат похож на правду, но подучается с большой погрешностью (т.е. выводится целое число, например 6 миллисекунд, а время сигнала на осциллографе примерно 6350 микросекунд), попробовал заменить на micros, с надеждой, что получу тот же результат, только более точный (например 6354 микросекунд), но после замены на микрос получил бредовый результат.
Потом начитался примерно таких выдержек "ISR должны быть как можно более короткими и быстрыми. Если в вашем скетче используется несколько ISR, одновременно можно запустить лишь одну, а другие будут выполнятся после — в зависимости от имеющегося приоритета. Счетчик в функции millis() полагается на прерывания, поэтому внутри ISR работать не будет. Поскольку функции delay() для работы тоже требуются прерывания, внутри ISR она тоже работать не будет. Функция micros() первое время будет работать нормально, но спустя 1-2 миллисекунды начнутся перебои. Функции delayMicroseconds() счетчик не требуется, поэтому она будет работать в нормальном режиме." Из чего сделал вывод о переполнении
Точность micros, если я не ошибаюсь 4 микросекунды....
С точностью до 4х микросекунд вполне устроит
Нет, дело там не в переполнении. А не работает она в ISR именно потому, что запрещены прерывания.
Вам правильно сказали, что сидеть в ISR целые миллисекунды - плохая идея, но если уж никак - просто разрешите другие прерывания, т.е. поставьте в начале Вашей ISR вызов sei(); и микрос начнёт нромально работать. Но Вы должны понимать, что прерывание от таймера будет каждые 4 мкс ненадолго прерывать Вашу ISR.
Правильно поставил? 26 и 33 строки
Да пофигу где вы что измеряете и для чего. Купите себе копеечный логанализатор, дрыгните любой свободной ногой МК при входе прерывания и перед выходом.
Вроде ничего, только обратите внимание на строку 53. Она ведь задаёт максимальное время измерения (т.е. никей таймаут), если я првильно понял. Так вот этот таймаут у Вас сейчас всего одна микросекунда.
Попробовал на железе, не работает
Вот результат:
Result is: 164 microseconds
Result is: 160 microseconds
Result is: 160 microseconds
Result is: 160 microseconds
Вот код:
При этом вместо 160, он может выдавать другие значения: 204, 404 и тд
А если поменять на millis, то:
Нет, дело там не в переполнении. А не работает она в ISR именно потому, что запрещены прерывания.
Вам правильно сказали, что сидеть в ISR целые миллисекунды - плохая идея, но если уж никак - просто разрешите другие прерывания, т.е. поставьте в начале Вашей ISR вызов sei(); и микрос начнёт нромально работать. Но Вы должны понимать, что прерывание от таймера будет каждые 4 мкс ненадолго прерывать Вашу ISR.
Ставить в прерывании sei(); бесполезно. В 328 нет возможности вызвыть прерывание из прерывания, поэтому прерывание блокикует вызов других прерываний. Если во время прерывания вызывается другое, то только выставляется флаг и оно будет вызвано только после завершения текущего. Если флагов несколько, то после выхода из прерывания будет вызвано первым прерывание с большим приоритетом. Именно поэтому любая обработка в нутри прерывания длинее 8 микросекунд нарушает счёт micros. У процессора нет информации, что прерывание вызвано повторно. Именно поэтому требуетмый алгоритм подсчета времени прерывания не будет работать. На stm32 работать будет. Там можно прерывание превать прерыванием с большим приоритетом.
а в принципе эту задачу реально выполнить на atmega328? используя например assembler?
DmitryR, а зачем вы вообще иземеряете время в прерывании? Для точных измерений внешнего сигнала существуют другие методики.
Да думаю надо внешний таймер воткнуть, да посылать на него с МК сигнал старт и стоп, и считывать результат. Единственное что, послать сигнал после выхода из прерывания не проблема, а вот можно ли послать сигнал непосредственно при наступлении прерываня?
DmitryR, в МК есть свой 16-битный таймер, нафига внешний? У таймера свой вход, он так-же может вызывать прерывание по любому фронту входящего сигнала, и хранить длительность в своих регистрах.
я так понимаю это уже на assembler надо программировать? Подключать таймер, обращаться к регистрам ....
А на си это все типа сделать низя ?
Ставить в прерывании sei(); бесполезно. В 328 нет возможности вызвыть прерывание из прерывания, поэтому прерывание блокикует вызов других прерываний.
Да, что Вы, Господь с Вами. Давайте сделаем так: заведём два таймера. Один срабатывает почти сразу, зажигает светодиод и погружается в мёртвый цикл пока истинна некая переменная. Когда (и если) он выйдет из цикла, гасит светодиод. А второй срабатывает через несколько секунд и делает ту самую переменную цикла false.
По Вашей логике, поскольку цикл находится внутри ISR, он никогда не закончится, т.к. второе прерывание не произойдёт и переменную цикла никто не сделает false.
Пробуем:
Как видите, светодиод благополучно гаснет, значит, прерывание срабатывает.
А теперь закомментируйте строку 37 и убедитесь, что на этот раз светодиод не гаснет, т.к. второе прерывание действительно не срабатывает.
Вы правильно говорите, что новое прерывание запрещено пока обрабатывается предыдущее, только Вы смысл sei не поняли - она как раз сбрасывает флаг, говорящий о том, что идёт обработка прерывания. Ппосле неё новое прерывание вполне себе может обрабатываться.
DmitryR, у Вас путаница в логике программы. Проверяйте логику. Не в таймерах счастье. Попытйтесь разобраться, чему у Вас равен в endSignalTime строке 53 в том случае, если строка 27 ещё не отработала, а чему равен, если уже отработала. Ну, и что Вы там считаете?
Если до субботы не разберёстесь, смогу помочь. Буду онлайн примерно часов с 10 до 12.
Хорошо, попробую разобраться, потом ещё отработать на железе
В примере второй таймер заводится не точно на 16 мс, а приблизительно, правильно?
Да, конечно, там просто полный цикл 8-битного таймера - 16 384 микросекунды.
Да. Согласен. Вызывается. Буду знать.
Остался только вопрос. А повторно прерванное после sei если вызовется что будет?
Вы имеете в виду на ту же ISR если прилетит? Ничего страшного. Отработает если только ISR написана так, что не ломается от повторного входа. Дело в том, что локальные переменные (если есть) у второго входа будут свои - не те же самые, что у первого, а вот глобальные - те же самые и за них они могут подраться.
nik182, есть ещё штатная опция запуска неблокирующего прерывания ISR(имя, ISR_NOBLOCK);
Попробовал ещё раз разобраться, появились вопросы
1) Задали константы
01
#define SIGNAL_PIN 3
02
03
#define CONTROL_PERIOD 500ul
2) Создаём 3 константы - состояния устройства
07
enum
METER_STATES: unsigned
long
{
08
WAITING_FOR_SIGNAL = 0,
09
MEASURING,
10
READY
11
};
3) Создаём статические переменные для запоминания времени срабатывания прерывания и времени выхода из него
16
static
volatile METER_STATES meterState = WAITING_FOR_SIGNAL;
17
18
20
static
volatile unsigned
long
startSignalTime = 0, endSignalTime = 0;
4) Прерывание - измерение
25
void
measureISR(
void
) {
26
sei();
27
endSignalTime = micros();
5) Прерывание, в котором при первом спадающем фронте начинается измерение времени
void
waitingISR(
void
) {
33
sei();
34
startSignalTime = endSignalTime = micros();
35
detachInterrupt(SIGNAL_PIN - 2);
36
pinMode(SIGNAL_PIN, INPUT);
37
attachInterrupt(SIGNAL_PIN - 2, measureISR, FALLING);
38
meterState = MEASURING;
39
}
6) Код, выполняемый при запуске, т.е. ожидание начала сигнала, и запуск внешнего прерывания waitingISR
41
void
setup
(
void
) {
42
Serial
.begin(115200);
43
pinMode(SIGNAL_PIN, INPUT);
44
attachInterrupt(SIGNAL_PIN - 2, waitingISR, RISING);
45
}
7) Основной код, выполняемый в цикле, задача которого отслеживание пропадания сигнала
47
void
loop
(
void
) {
48
if
(meterState == MEASURING) {
49
//
50
// Проверяем, что с момент последнего HIGH
51
// прошло >= CONTROL_PERIOD микросекунд
52
//
53
if
(micros() - endSignalTime >= CONTROL_PERIOD) {
54
//
55
// Если прошло, то заканчиваем измерение
56
//
57
detachInterrupt(SIGNAL_PIN - 2);
58
meterState = READY;
// Закончили
59
Serial
.print(
"Result is: "
);
60
Serial
.print(endSignalTime - startSignalTime);
61
Serial
.println(
" microseconds"
);
62
}
63
}
64
}
Вопросы:
- с 1 по 18 строки всё понятно,
- в в 20 я так понимаю делаем, что-то типа обнуления переменных?
- с 7 по 11 не совсем понятен смысл этого перечесления состояний
- с 25 по 27 похоже на прерывание, которое задаёт начало измерению, непонятна строка endSignalTime = micros();
- с 32 по 39 прерывание - ожидание; вызывает прерывание по спаду фронта сигнала, начинающее измерение, переводит измеритель в состояние MEASURING
Почему используется 2 прерывания? зачем необходимо прерывание waitingISR?
- строка 44: attachInterrupt(SIGNAL_PIN - 2, waitingISR, RISING); - прерывание, вызывающее waitingISR, по возрастания фронта?
- 53: Проверка условия того, что время с последнего HIGH >= CONTROL_PERIOD
micros() - endSignalTime : что отражает endSignalTime ?
Почему используется 2 прерывания? зачем необходимо прерывание waitingISR?
А вот это я как раз у Вас хотел спросить. Нафига оно Вам? Если Вы про то, что я когда когда-то писал (я толком не помню), так скорее всего, что Вы просили, то я и писал не особо зная задачу.
Давайте так, начните с чистого листа. Просто выпишете на бумаге что нужно делать. В итоге код получится примерно водвое короче.
Вот смотрите, прибилзительно рассуждайте так. К трём состояниям "ждем", "измеряем" и "закончили", добавим четвёртое - "аминь".
1. Устанваливаем состояние "ждём".
2. В loop проверяем
ЕСЛИ состояние=="измеряем" И истек таймаут ТО {
говорим, что не дожались;
устанавливаем endTime на текущее время
переходим в состояние "закончили"
ИНАЧЕ ЕСЛИ состояние == "закончили"
печатаем endTime-startTime
переходим в состояние "аминь"
(Все - в лупе больше не делаем ни хрена!!!)
3. ISR устрена так
ЕСЛИ состояние == "ждём" ТО
запоминаем текущее время в startTime
переходим в состояние "измеряем"
ИНАЧЕ ЕСЛИ состояние == "измеряем" ТО
запоминаем текущее время в endTime
переходим в состояние "закончили"
Вот сосбственно и всё, что нужно написать (если я правильно понимаю задачу).
Проверьте на правильность понимани и напишите.
В том коде, что у Вас сейчас логические ошибки, он переусложжнён.
На всякий случай напомню задачу:
CONTROL_RERIOD - должен быть больше длительности последнего пика (сиреневый сигнал), после которого устанавливается HIGH
Код, который получился:
Первый 12 строк я думаю работают нормально
Далее идёт такая логика:
строка 31 - устанавливаем состояние "ожидание сигнала"
строка 32 - ждём наступления прерывания measure_startISR которое вызывается спадом сигнала с HIGH на LOW
По наступлению данного условия наступает прерывание measure_startISR - строка 14
При этом запонимается время начала startSignalTime = micros();
При нарастании сигнала с LOW на HIGH - строка 18, наступает прерывание measure_endISR - строка 21
Которая запоминает время endSignalTime = micros();
и переводит состояние в MEASURING_END
Далее должно провериться условие
(endSignalTime - startSignalTime >= CONTROL_PERIOD)
Если условие выполняется, то READY и выводим результат
Но получается, что control period определяется по периоду при LOW, а надо при HIGH
Уже давно забыл как на ваших мегах всё так плохо, но картиночка прям дежавю. Где-то я её уже видел при данном вопросе. Пишу относительно СТМ32, но может вы сумеете транспонировать решение под себя, исходя их ваших реалий.
1. Мерять каждый период, потом посчитать их сумму. Для СТМ32 это совсем не трудно, там есть такой режим таймера. Т.к. последний период не полный, на дополнительном канале сравнения выставить защитный интервал чуть больше периода. Период же, насколько я понял, +- известен. По факту защитного интервала суммируем периоды + длительность последнего отрицательного импульса.
ssss, вы ему уже советовали stm :) Когда он вставил картинку с затухающей синусоидой, я сразу вспомнил его первую тему . Оказывается уж пятый месяц товарищ бьётся, никак добротность у индуктивности не измерит.. За это время можно было и СИ и STM изучить, и за полчаса всё написать..
DmitryR,
я тут собирался быть с 10, но простотите, обстоятельства изменились, буду с 18.
ssss, вы ему уже советовали stm :) Когда он вставил картинку с затухающей синусоидой, я сразу вспомнил его первую тему . Оказывается уж пятый месяц товарищ бьётся, никак добротность у индуктивности не измерит.. За это время можно было и СИ и STM изучить, и за полчаса всё написать..
+100500 !
Даже добавить нечего.
Про пятый месяц: сугубо по датам, да. А по факту нет.
Эта задача не является у меня основной, просто желательно сделать, от этого будет польза, что-то типа спортивного интереса.
Про СИ, STM и тд: учу по мере возможности, но не получается на это выделять достаточное количество времени
Алгоритм мне представляется совсем простым и даже без прерываний.
Стоим ждём LOW. Запоминаем время и сохроняем его как начало,
ждем HIGH, обзываем концом, ждем LOW.
Если LOW раньше timeout, опять ждем HIGH
Иначе время = начало-конец.
Время можно из microseconds() брать или таймер 16 битный запустить. С ним всяко время двухбайтное быстрее счититься будет, чем четырёхбайтное.
По этой теме оцень много советов было, возможных алгоритмов, вариации использования микроконтроллеров и тд. Про это можно бесконечно говорить, да в принципе я и сам могу накидать несколько алгоритмов, позволяющих выполнить задачу, суть в другом: это конкретный программный код на конкретном языке с грамматикой и прочим. Вот именно в этом и состоит загвоздка, так как не знаю толком ни одного языка программирования
Приведеннный пример должен работать. Если нет - увеличте CONTROL_PERIOD до 1000 и даже до 2000. Если все равно не работает, ошибки в схеме.
Если есть возможность, выложите осцилограмму выхода компаратора, вот прям с ноги ардуинки, на которую этот сигнал подаете.
в 15м сообщении осциллограмма прям с ноги arduino nano
OK! Тогда увеличь интервал и замени в строке37 сообщения 15 FALLING на CHANGE. Должно теперь работать.
......
Проверяй, Чего ждешь? :)
У меня всё железо не дома) теперь только в понедельник могу
А в чём суть замены на change? там же по спаду
суть в том, что при очень коротом фронте прерывание может быть пропущено. А считать нам все равно от первого до последнего изменения входа.
А когда Вы говорили, что приведеннный пример должен работать, это речь о коде в 15м сообщении или в 34м?
Они ничем не отличаются, кроме длительности интервала. Еще раз - поставьте не 50 и не 500 а 1000 или 2000.
Давайте "госДуму" закроем? Ставьте чейндж и 2000, сообщите о результате. А радость человеческого общения - перенесем к друзьям и пиву, ОК?