Сегодня я буду основываться на методе, описанном в публикации Многозадачность на Arduino и исследую несколько типов прерываний Arduino, а также покажу, каким же образом их использовать, чтобы помочь Arduino управлять еще большим числом задач. Я расскажу, как использовать прерывания таймера, чтобы все работало как часы и рассмотрю внешние прерывания, дающие нам уведомления о внешних событиях.
Что такое прерывания?
Прерывание — это сигнал, который сообщает процессору, что нужно немедленно остановить то, что он в настоящий момент делает, и произвести некоторые операции, имеющие высокий приоритет. Эта обработка с высоким приоритетом называется обработчиком прерываний (interrupt handler).
Если мы реализуем некоторую функцию и присоедим ее к прерыванию, то эта функция будет вызвана всякий раз, когда появится сигнал прерывания. По возвращении из обработчика прерывания, процессор продолжит обрабатывать то, что он делал до прерывания.
Прерывания могут быть созданы несколькими источниками:
- Одним из таймеров Arduino, формируя прерывания по таймеру;
- Изменением состояния одного из входов внешних прерываний;
- Изменение состояния одного из группы пинов.
При использовании прерываний, нет необходимости писать код в loop (), постоянно проверяющий высокоприоритетное условие прерывания. Не нужно беспокоиться о медленной реакции или пропущенных нажатиях кнопок из-за слишком долго выполняющихся подпрограмм.
Процессор будет автоматически останавливать то, что он делает в момент возникновения прерывания и вызвать ваш обработчик прерывания. Вам просто нужно написать код, отвечающий на прерывание всякий раз, когда оно происходит.
Прерывания по таймеру Arduino
В прошлой публикации про многозадачность я рассматривал, как использовать функцию millis () для управления временем. Но для того, чтобы это работало, нам нужно вызывать millis () каждый раз в цикле, чтобы увидеть, не настало ли время сделать что-то. Это, своего рода, расточительство — вызывать millis () чаще, чем один раз в миллисекунду, только чтобы узнать, что время не изменилось. Было бы неплохо, если бы мы проверяли это только один раз в миллисекунду.
Таймер и прерывания по таймеру позволяют нам сделать именно это. Мы можем установить таймер, чтобы он прерывал нас один раз в миллисекунду. Таймер будет, на самом деле, сообщать нам, что пора проверить часы!
Arduino имеет три таймера: Timer0, Timer1 и Timer2. Timer0 уже настроен для генерации миллисекундных прерываний, обновляя счетчик миллисекунд, передаваемый в millis (). Это именно то, что нам нужно!
Таймеры — это простые счетчики, которые считают с некоторой частотой, получаемой из системных 16Мгц. Мы можем сконфигурировать делитель частоты для получения требуемой частоты и различных режимов счета. Мы также можем настроить их для генерации прерываний при достижении таймером некоторых заданных значений.
Timer0 являющийся 8-битным, считает от 0 до 255 и генерирует прерывание, при переполнении (превышении значения в 255). Он использует тактовый делитель на 64 по умолчанию, чтобы дать нам частоту прерываний 976.5625 Гц (достаточно близко к 1 кГц для наших целей). Мы не будем изменять частоту в Timer0, потому что это точно нарушит работу millis ()!
Регистры сравнения
В регистрах сравнения хранятся данные, которые постоянно сравниваются с состоянием таймера/счетчика. Установим регистр сравнения (OCR0A) для генерации другого прерывания где-то в середине этого счета. Приведенный ниже код будет генерировать прерывание TIMER0_COMPA всякий раз, когда значение счетчика проходит 0xAF.
// Timer0 уже используется millis() - мы создаем прерывание где-то // в середине и вызываем ниже функцию "Compare A" OCR0A = 0xAF; TIMSK0 |= _BV(OCIE0A);
Затем определим обработчик прерывания для вектора прерывания по таймеру, называемому TIMER0_COMPA_vect. В этом обработчике прерывания будет делаться все то, что мы ранее делали в loop ().
// Прерывание вызывается один раз в миллисекунду SIGNAL(TIMER0_COMPA_vect) { unsigned long currentMillis = millis(); sweeper1.Update(currentMillis); //if(digitalRead(2) == HIGH) { sweeper2.Update(currentMillis); led1.Update(currentMillis); } led2.Update(currentMillis); led3.Update(currentMillis); }
Что оставляет нас с абсолютно пустым loop ()
void loop() { }
Здесь мы теперь можем делать все что угодно. Можно даже вернуться к использованию delay ()! Это не затронет наши мигающие светодиоды и вращающиеся сервы. Они будут по-прежнему независимо вызываться один раз в миллисекунду.
Напишем новый код, независимо управляющий миганием светодиодов и вращением сервоприводов, но уже используя таймер.
#include <Servo.h> class Flasher { // Переменные-члены класса // Устанавливаются при запуске int ledPin; // Номер пина со светодиодом long OnTime; // длительность ВКЛ в мс long OffTime; // длительность ВЫКЛ в мс // Текущее состояние int ledState; // устанавливает состояние светодиода unsigned long previousMillis; // время последнего обновления светодиода // Конструктор - создает Flasher // и инициализирует переменные-члены // и состояние public: Flasher(int pin, long on, long off) { ledPin = pin; pinMode(ledPin, OUTPUT); OnTime = on; OffTime = off; ledState = LOW; previousMillis = 0; } void Update(unsigned long currentMillis) { if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime)) { ledState = LOW; // ВЫКЛ previousMillis = currentMillis; // Запомнить время digitalWrite(ledPin, ledState); // Обновить состояние светодиода } else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime)) { ledState = HIGH; // ВКЛ previousMillis = currentMillis; // Запомнить время digitalWrite(ledPin, ledState); // Обновить состояние светодиода } } }; class Sweeper { Servo servo; // объект servo int pos; // текущее положение сервы int increment; // увеличивать перемещение на каждом интервале int updateInterval; // время между обновлениями unsigned long lastUpdate; // последнее обновление положения public: Sweeper(int interval) { updateInterval = interval; increment = 1; } void Attach(int pin) { servo.attach(pin); } void Detach() { servo.detach(); } void Update(unsigned long currentMillis) { if((currentMillis - lastUpdate) > updateInterval) // пришло время обновляться { lastUpdate = millis(); pos += increment; servo.write(pos); if ((pos >= 180) || (pos <= 0)) // конец вращения { // обратное направление increment = -increment; } } } }; Flasher led1(11, 123, 400); Flasher led2(12, 350, 350); Flasher led3(13, 200, 222); Sweeper sweeper1(25); Sweeper sweeper2(35); void setup() { sweeper1.Attach(9); sweeper2.Attach(10); // Timer0 уже используется millis() - прерываемся где-то // посередине и вызываем ниже функцию "Compare A" OCR0A = 0xAF; TIMSK0 |= _BV(OCIE0A); } // Прерывание вызывается один раз в миллисекунду, // ищет любые новые данные, и сохраняет их SIGNAL(TIMER0_COMPA_vect) { unsigned long currentMillis = millis(); sweeper1.Update(currentMillis); if(digitalRead(2) == HIGH) { sweeper2.Update(currentMillis); led1.Update(currentMillis); } led2.Update(currentMillis); led3.Update(currentMillis); } void loop() { }
Внешние прерывания
В отличие от прерываний по таймеру, внешние прерывания вызываются внешними событиями. Например, когда нажата кнопка, или же получен импульс от датчика вращения. Однако, также как и с прерываниями по таймеру, нам не нужно постоянно опрашивать выводы GPIO на изменения.
На используемой мной Arduino Mega 2560 имеется 6 пинов, обрабатывающих внешние прерывания (на Arduino UNO таких входов только 2). В этом примере я подключу кнопку к одному из них и использую ее для сброса сервоприводов.
Во-первых, давайте добавим функцию reset () в наш класс Sweeper. Функция reset () устанавливает положение в ноль и немедленно перемещает туда серву.
void reset() { pos = 0; servo.write(pos); increment = abs(increment); }
Затем, добавим вызов функции attachInterrupt (), соединяющую внешнее прерывание с обработчиком. На Arduino Mega 2560, также как и на Arduino UNO, Inerrupt 0 ассоциировано с цифровым пином 2. Мы говорим, что ждем «спада» фронта сигнала на этом выводе. При нажатии кнопки сигнал «спадает» от высокого до низкого уровня и вызывается обработчик прерывания Reset.
pinMode(2, INPUT_PULLUP); attachInterrupt(0, Reset, FALLING);
И здесь есть обработчик прерывания. Он просто вызывает функции, сбрасывающие сервы.
void Reset() { sweeper1.reset(); sweeper2.reset(); }
Теперь, при нажатии на кнопку, серва останавливает вращение и немедленно возвращается в нулевую позицию.
Схема у нас та же, что и в прошлый раз.
Полный скетч с таймерами и внешними прерываниями:
#include <Servo.h> class Flasher { // Переменные-члены класса // Устанавливаются при запуске int ledPin; // Номер пина со светодиодом long OnTime; // длительность ВКЛ в мс long OffTime; // длительность ВЫКЛ в мс // Текущее состояние int ledState; // устанавливает состояние светодиода unsigned long previousMillis; // время последнего обновления светодиода // Конструктор - создает Flasher // и инициализирует переменные-члены // и состояние public: Flasher(int pin, long on, long off) { ledPin = pin; pinMode(ledPin, OUTPUT); OnTime = on; OffTime = off; ledState = LOW; previousMillis = 0; } void Update(unsigned long currentMillis) { if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime)) { ledState = LOW; // ВЫКЛ previousMillis = currentMillis; // Запомнить время digitalWrite(ledPin, ledState); // Обновить состояние светодиода } else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime)) { ledState = HIGH; // ВКЛ previousMillis = currentMillis; // Запомнить время digitalWrite(ledPin, ledState); // Обновить состояние светодиода } } }; class Sweeper { Servo servo; // объект servo int pos; // текущее положение сервы int increment; // увеличивать перемещение на каждом интервале int updateInterval; // время между обновлениями unsigned long lastUpdate; // последнее обновление положения public: Sweeper(int interval) { updateInterval = interval; increment = 1; } void Attach(int pin) { servo.attach(pin); } void Detach() { servo.detach(); } void reset() { pos = 0; servo.write(pos); increment = abs(increment); } void Update(unsigned long currentMillis) { if((currentMillis - lastUpdate) > updateInterval) //пришло время обновляться { lastUpdate = millis(); pos += increment; servo.write(pos); if ((pos >= 180) || (pos <= 0)) // конец вращения { // обратное направление increment = -increment; } } } }; Flasher led1(11, 123, 400); Flasher led2(12, 350, 350); Flasher led3(13, 200, 222); Sweeper sweeper1(25); Sweeper sweeper2(35); void setup() { sweeper1.Attach(9); sweeper2.Attach(10); // Timer0 уже используется millis() - прерываемся где-то // посередине и вызываем ниже функцию "Compare A" OCR0A = 0xAF; TIMSK0 |= _BV(OCIE0A); pinMode(2, INPUT_PULLUP); attachInterrupt(0, Reset, FALLING); } void Reset() { sweeper1.reset(); sweeper2.reset(); } // Прерывание вызывается один раз в миллисекунду, // ищет любые новые данные, и сохраняет их SIGNAL(TIMER0_COMPA_vect) { unsigned long currentMillis = millis(); sweeper1.Update(currentMillis); // if(digitalRead(2) == HIGH) { sweeper2.Update(currentMillis); led1.Update(currentMillis); } led2.Update(currentMillis); led3.Update(currentMillis); } void loop() { }
И результат
Библиотеки для работы с прерываниями
В сети можно найти несколько библиотек для работы с таймерами. Многие просто постоянно опрашивают millis () так же, как мы делали это в прошлый раз. Но есть некоторые, которые позволяют настроить таймеры для генерации прерываний.
Отличные библиотеки TimerOne и TimerThree от Paul Stoffregan дают множество низкоуровневых возможностей для конфигурирования прерываний по таймеру. Библиотека TimerThree не работает на Arduino UNO. Ее можно использовать с Leonardo, Mega 2560 и некоторыми платами Teensy.
Arduino UNO имеет только два входа внешних прерываний. Но что делать если требуется более двух входов? К счастью Arduino UNO поддерживает прерывания «pin-change» (по изменению входа) для всех пинов.
Прерывания по изменению входа — это тоже самое что и внешние прерывания. Разница в том, что первые генерируются по изменению состояния на любом из 8 соответствующих пинов. Они немного более сложны в обработке, так как вы должны отслеживать последнее известное состояние всех 8 пинов, чтобы выяснить, какой из 8 пинов вызвал это прерывание.
Библиотека PinChangeInt реализует удобный интерфейс для прерываний по изменению входа.
Правила работы с прерываниями
Для того, чтобы все работало гладко, лучше использовать не более 10 различных прерываний.
Если все имеет высокий приоритет, значит высокого приоритета нет ни у чего
Обработчики прерываний должны использоваться для обработки только приоритетных, чувствительных ко времени событий. Помните, что прерывания отключены, пока вы находитесь в обработчике прерывания. Если вы попытаетесь сделать слишком много на уровне прерывания, вы получите худший ответ на другие прерывания.
Одно прерывание в каждый момент времени
Когда программа находится в функции обработки прерывания, то другие прерывания отключены. Это имеет два важных следствия:
- Работа, выполняемая в функции обработки прерывания должна быть короткой, чтобы не пропустить не одного прерывания.
- Код, выполняемый в функции обработки прерывания не должен вызывать ничего такого, что требовало бы, чтобы прерывания были активны (например, delay () , или что-нибудь, использующее шину I2C). Это приведет к зависанию программы.
Отложите длительную обработку в loop ()
Если вам необходимо произвести обширную обработку в ответ на прерывание, используйте обработчик прерывания для того, чтобы сделать то, что необходимо, а затем установите переменную состояния volatile, показывающую, что дальнейшая обработка не требуется. При вызове функции Update () из loop () проверьте переменную состояния, на предмет того, не требуется ли какая-либо последующая обработка.
Проверка перед переконфигурированием таймера
Таймеры являются ограниченным ресурсом. На Arduino UNO их всего 3, и они используются для многих вещей. Если вы запутались с конфигурацией таймера, некоторые вещи могут перестать работать. Например, на Arduino UNO:
- Timer0 - используется millis (), micros (), delay () и ШИМ на пинах 5 и 6
- Timer1 - используется для Servo, библиотеки WaveHC и ШИМ на пинах 9 и 10
- Timer2 - используется Tone и ШИМ на пинах 11 и 13
Безопасное совместное использование данных
Поскольку прерывание приостановит все, что процессор делает, мы должны побеспокоиться об обмене данными между обработчиками прерываний и кодом в loop ().
Иногда компилятор попытается оптимизировать свой код для увеличения производительности. Иногда, в результате этой оптимизации, копии часто используемых переменных будут сохраняться в регистре для быстрого доступа к ним. Проблема в том, что если одна из этих переменных совместно используется обработчиком прерывания и кодом в loop (), то, в конечном итоге, в ней может обнаружиться несвежая копия вместо реального значения. Маркировка переменной как voltatile сообщает компилятору, что не нужно производить эти потенциально опасные оптимизации.
Даже пометка переменной как volatile будет не достаточным в случае, если переменная больше, чем целое (например, строка, массив, структура и т.п.). Большим переменным иребуется несколько циклов инструкций для обновления, и если прерывание происходит в середине этого обновления, данные могут быть повреждены. Если у вас есть большие переменные или структуры, которые совместно используются с обработчиком прерываний, вы должны отключить прерывания при обновлении их из loop (). Замечу, что прерывания отключены в обработчике прерывания уже по умолчанию.
[add_ratings]
Очень познавательная статья.
Есть вопрос: Как будет работать программа когда переполнится и обнулится millis ()?
Хороший вы задали вопрос, Валерий! Примерно через 50 дней программа работать перестанет: счетчик millis () обнулится и условия if ((currentMillis — lastUpdate) > updateInterval), (currentMillis — previousMillis >= OnTime) и (currentMillis — previousMillis >= OffTime) возможно никогда больше и не выполнятся. Чтобы этого избежать при условии, что не сильно критичен единичный сбой раз в 50 дней в длительности обновления состояний, то можно сравнивать не разность на превышение заданных интервалов, а модуль разностей. Например, для первого условия это будет выглядеть следующим образом:
if(abs(currentMillis - lastUpdate) > updateInterval) { ... }
Если же даже сбой один раз в 50 дней в работе программы неприемлем, то необходимо отдельно обработать событие переполнения счетчика, учитывая, что переменная типа unsigned long может принимать значения от 0 до (2^32-1).
Спасибо за ответ.
Добрый день всем.
Я не волшебник я только учусь.
(железным програмированием никогда не занимался — а обычное в школе институте проходили)
вы написали ...
как одно из решений

if (abs (currentMillis — lastUpdate) > updateInterval)
{
...
}
то есть, если я правильно понял — один раз в 50 дней доходим до максимального значения МИЛИСЕК-мах -потом сбрасывание в ноль будет «пропуск» одного интервала? ( если я правильно понял).
а что если написать
long interval = 1000;
.
.
if (millis () % interval == 0 )
{
...
}
все будет работать также ??
Николай, проблема тут вот в чем. Ход мыслей у вас правильный, но операция взятия отстатка от деления работает только с числами типа int (оба операнда должны быть типа int). Функция millis возвращает значение типа unsigned long. В комментарии к другой статейке, также посвященной многозадачности, я предложил одно из возможных вариантов отслеживания сброса счетчика.
Спасибо
Странно. У меня отлично работает операция взятия остатка (%), в случае, когда первый операнд типа unsigned long, а второй константа.
Например:
// Счетчики смены состояний unsigned long switchsCount; // Счетчик смены состояний unsigned long circlesCount; // Счетчик полных оборотов ... this->switchsCount++; if ((this->switchsCount % 4) == 0) { this->circlesCount++; }
Оба счетчика выводится на дисплей, поэтому результат можно контролировать.
Андрей, не вводите людей в заблуждение!
1) Все арифметические операции (сложение, вычитание, умножение, деление, деление по модулю) над всеми числами разрешены и поддерживаются. Это МК не поддерживает ряд операций, в частности деление и деление по модулю, а также операции с плавающей точкой, но Вы же пишите на Си/Си++, а здесь вся арифметика выполняется даже если она аппаратно не поддерживается (попросту эмулируется, точнее реализуется программно).
2) выражение
if(currentMillis - lastUpdate) > updateInterval))
работает всегда, даже когда «millis переполняется» т.к. currentMillis и lastUpdate объявлены как unsigned т.е. беззнаковые, они по определению не могут быть отрицательными. В результате операции (currentMillis — lastUpdate) у вас получится положительное число (это магия переполнения 🙂 другое дело, что у вас происходит сравнение знакового и беззнакового, это не хорошо (наврерняка компилятор указал вам на это), ну и в теории результат вычитания может быть больше разрядной сетки updateInterval (это же int), т.е. максимальный интервал ~32секунды.
Андрей, спасибо за невероятно познавательные и полезные статьи и ответы!
Скажите. правильно ли я понимаю, что millis переполняется только при месяце непрерывной работы, без выключения питания? Или речь о месяце вообще, при обычном режиме включения-выключения?
Да, правильно. Переполнение происходит через месяц непрерывной работы. Если питание отключить/включить, то счетчик при включении обнуляется.
Есть такая функция
void useInterrupt(boolean v) { if (v) { OCR0A = 0xAF; TIMSK0 |= _BV(OCIE0A); } else { // do not call the interrupt function COMPA anymore TIMSK0 &= ~_BV(OCIE0A); } }
Как её переделать для Arduino DUE? Там ведь таймеры 32-бит...
Для Arduino DUE работа с таймерами и прерываниями значительно отличается от описанного выше и выходит за рамки этой статьи. Могу порекомендовать вам ознакомится с осуждением работы с таймерами Arduino DUE на форуме arduino.cc. Пример кода взятый оттуда:
volatile boolean l; //TC1 ch 0 void TC3_Handler() { TC_GetStatus(TC1, 0); digitalWrite(13, l = !l); } void startTimer(Tc *tc, uint32_t channel, IRQn_Type irq, uint32_t frequency) { pmc_set_writeprotect(false); pmc_enable_periph_clk((uint32_t)irq); TC_Configure(tc, channel, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK4); uint32_t rc = VARIANT_MCK/128/frequency; //128 because we selected TIMER_CLOCK4 above TC_SetRA(tc, channel, rc/2); //50% high, 50% low TC_SetRC(tc, channel, rc); TC_Start(tc, channel); tc->TC_CHANNEL[channel].TC_IER=TC_IER_CPCS; tc->TC_CHANNEL[channel].TC_IDR=~TC_IER_CPCS; NVIC_EnableIRQ(irq); } void setup() { pinMode(13,OUTPUT); startTimer(TC1, 0, TC3_IRQn, 4); //TC1 channel 0, the IRQ for that channel and the desired frequency } void loop(){}
Здравствуйте! Подскажите пожалуйста почему неадекватно работает функция analogRead () во внешнем прерывании int0?
analogRead запрещает прерывания и любой другой код не может выполнится во время работы analogRead. Подробнее в статье Аналоговые измерения с Arduino.
особенно полезно последние предупреждения 🙂
я почти что решил перейти на прерывания вместо отслеживания через millis ()
> Помните, что прерывания отключены, пока вы находитесь в обработчике прерывания. Если вы попытаетесь сделать слишком много на уровне прерывания, вы получите худший ответ на другие прерывания.
Это ошибочное утверждение.
Позволю процитировать уважаемого DI HALT:
что будет если во время обработки одного прерывания придет другое? Оно потеряется?
Нет, не потеряется — у него взведется флаг и как только мы завершим первый обработчик автоматом произойдет переход к отложенному прерыванию
(easyelectronics.ru/avr-uc... -si-chast-3.html)
В случае с прерываниями микроконтроллер выполняет только одну программу, а прерывания нужны не для многозадачности, а работы с интерфейсными устройствами микроконтроллера.
Для организации меню достаточно одну кнопку завести на прерывание это будет вход в меню. Однако при такой реализации во время хождения по меню все инструкции основного цикла выполняться не будут.
Опечатка:
Маркировка переменной как volTAtile сообщает компилятору, что не нужно производить эти потенциально опасные оптимизации.
При нажатие на кнопку, получается следующиее, когда сервы не вернулись на в позицию «ноль» то светодиоды не работают, вернее не много сбиваются с ритма?
Добрый день! Подскажите новичку, пожалуйста!
Пишу код для определения скорости вращения. Датчик — оптрон с обвязкой, покупной. Этот код:
#include OLED myOLED(SDA, SCL, 8); extern uint8_t SmallFont[]; extern uint8_t BigNumbers[]; volatile unsigned long oldTime = 0; volatile unsigned long newTime = 0; volatile byte newData = 0; float i = 0; float s = 0; void pinChange () { oldTime = newTime; newTime = millis(); newData = 1; } void setup() { Serial.begin(115200); myOLED.begin(); myOLED.setFont(BigNumbers); attachInterrupt (0, pinChange, RISING); // подключаем обработчик прерывания } void loop() { if (newData == 1) { i = newTime - oldTime; s = 1000 / i; Serial.print (newTime); Serial.print (" - "); Serial.print (oldTime); Serial.print (" = "); Serial.println (newTime - oldTime); Serial.println (i); Serial.println (s); Serial.println (); myOLED.clrScr(); myOLED.setFont(BigNumbers); myOLED.printNumF(s, 2, CENTER, 16); myOLED.setFont(SmallFont); myOLED.printNumF(i, 2, CENTER, 0); myOLED.printNumI(newTime, CENTER, 48); myOLED.update(); newData = 0; } }
почему-то не работает, в сериал выдает следующие значения:
248508 - 248508 = 0 0.00 inf 249473 - 249474 = 0 0.00 inf 249521 - 249521 = 0 0.00 inf
Особенно интересно второе значение, 249473 — 249474 = 0 КАК?
Откуда берутся inf в результатах и как заставить код работать?
i = newTime - oldTime;
надо бы привести к нужному типуi = (float)newTime - (float)oldTime;
илиi = (float)(newTime - oldTime);
Вообще Ваш код должен в первый раз выдать значение newTime, ведь oldTime будет равно нулю.
Константин,
249473 — 249474 = 0 КАК?
это не ошибка, это тонкость, которую нужно знать, корни ее лежат в атомарности операции (точнее в данном случае в ее отсутсвии).У вас переменные oldTime и newTime объявлены как unsigned long, т.е. занимают 4 байта и они используются как в «фоне» (loop), так и в прерывании (pinChange). Не знаю с какой частотой у вас идут прерывания, но прерывания (вызов pinChange может произойти в любой момент выполнения любой команды в loop), и получается что пока выводилась первая строчка, значения переменной(ых) узменились... Но чтобы такого не происходило, есть один способ:
void loop(){ if (newData == 1) { //запрещаем прерывания на момент сохранения значений переменных noInterrupts(); unsigned long new_t = newTime; unsigned long old_t = oldTime; //разрешаем прерывания interrupts(); // и дальше обращаться уже не непосредственно к // oldTime и newTime, а уже к их копиям ... }
А что за датчик? Название, ссылка?
Константин,
это результат вычислений:
i = newTime — oldTime; (равно нулю)
s = 1000 / i; (равно бесконечности, т.к. вообще то на ноль делить нельзя...) поэтому выводится «inf» вместо бесконечности
хотя, тут скорее ошибка с вашей стороны, «s» это же наверное секунды?
может все таки s = i / 1000; ???
если все верно то вероятно нужно обрабатывать этот случай (деление на ноль) по особому...
TIMSK0 |= _BV(OCIE0A);
А как входить в прерывание не каждую миллисекунду, а пореже, например, каждые 8 или 16 мс?