Прерывания по таймеру
- Войдите на сайт для отправки комментариев
Добрый день, корифеи
Освоившись с отказом от delay и переходом на millis сваял скетч (все работало как надо, ничего не напрягало, благо задачи не ресурсоемкие), но попался на глаза материал про прерывания по таймеру, и понеслось ))) Прошу глянуть код - не слишком ли тяжелым для прерывания получился код? С одной стороны прерывание вызывается раз в 4 секунды и выполняя расчеты раз в 4 секунды, а не при каждом проходе loop процессор разгружается для выполнения других задач (конечно в моем случае для простоя он разгружается), плюс отказался от нескольких unsigned long переменных, что также должно положительно сказаться на быстродействии. А с другой стороны - не слишком ли долго процессор теперь засиживается в прерывании? Визуально все работает так же, как и в варианте с millis, но гложет удав сомнения...
ISR (TIMER1_COMPA_vect) // функция вызываемая таймером-счетчиком T1
{
// дезинфекция
if ((PumpWaterState) && UvLampTime > 0 ) { // если включена помпа и установлено время работы УФ лампы
timerPumpUV = (timerPumpUV + 4); // увеличиваем время таймера (+4)
if (timerPumpUV > PumpWaterWorkTime && (!UvLampState)) { // если время работы помпы превысило пороговое значение и УФ лампа выключена
uvStart(); // включаем УФ лампу
}
if (timerPumpUV > (PumpWaterWorkTime + UvLampTime) && (UvLampState)) { // если время работы УФ лампы превысило пороговое значение и УФ лампа включена
uvStop(); // включаем УФ лампу
timerPumpUV = 0; // сбрасываем переменную
}
}
// таймер выключения
if (TimerSet > 0) { // если время таймера не истекло
TimerSet = (TimerSet - 4); // уменьшаем время таймера (-4 сек)
if (TimerSet <= 0) { // если время таймера истекло
PowerOnTask = 0; // выдаем задание на выключение питания
TimerSet = 0; // сбрасываем таймер, а то мало ли - может в минут натикал
if (DEBUG == 1) {
beep();
Serial.println("таймер кончился");
}
}
else { // если время таймера не истекло
if (DEBUG == 1) {
TimerCoundownNew = ceil(TimerSet / 10.0); // получаем в десятках секунд время до отключения таймера (для индикации)
if (TimerCoundownNew != TimerCoundownOld) { // если требуется обновить индикацию таймера
coundownLed(3, TimerCoundownNew); // передаем в функцию номер светодида, который нужно зажечь
TimerCoundownOld = TimerCoundownNew; // запоминаем значение
}
} else {
TimerCoundownNew = ceil(TimerSet / 3600); // получаем в часах время до отключения таймера
if (TimerCoundownNew != TimerCoundownOld) { // если требуется обновить индикацию таймера
coundownLed(3, TimerCoundownNew); // передаем в функцию номер светодида, который нужно зажечь
TimerCoundownOld = TimerCoundownNew; // запоминаем значение
}
}
}
}
// таймер опроса DHT
timerDHT = (timerDHT + 4); // увеличиваем время таймера (+4)
// таймер расчета скорости вентилятора
timerFan = (timerFan + 4); // увеличиваем время таймера (+4)
// таймер отображения целевой влажности
if (timerHum > 0) { // если время таймера не истекло
timerHum = (timerHum - 2); // уменьшаем время таймера (-0.5 сек)
}
}
Для того, что является "слишком", а что - нет, должны быть какие-то критерии.
В принципе, критерии могут быть такими:
1. Интервал между пользовательскими прерываниями должен быть строго больше, чем длительность прерывания. Т.е. чтобы следующее прерывание не наезжало на предыдущее.
2. Длительность пользовательского прерывания желательно, чтобы была меньше, чем интервал между системными прерываниями.
Системнве прерывания выполняются примерно 1 раз в мс. Значит, желавтельно, чтобы пользовательское прерывангие было короче 1 мс.
Лично я в setup() перед тем, как включать прерывание вызываю его как функцию, измеряю время его работы, вывожу это время на печать, и только потом включаю прерывание. Даже если контроллер намертво зависнет, в консорли у меня будет цифра, облегчающая диагностику.
Спасибо за критерии оценки - подозревал что они должны быть, вот теперь появились количественные ориентиры. По проверке длительности прерывания тоже все понял, еще раз спасибо.
Здравствуйте, это упрощенный пример, чтобы многие поняли суть вопроса Прерывание настроено на таймере 1 с интервалом 1 секунду. Мне нужно, чтобы по нажатию кнопки запускался таймер и начинался отсчет времени именно в момент нажатия а не по ходу программы как в данном случае. Догадываюсь что нужно запретить прерывание пока находимся в теле функции loop() и разрешить в момент нажатия таким образом чтобы не затронуть работу millis() и других завязанных на timer 0. Может есть способ по лучше.
#include <Wire.h> #include <LiquidCrystal_I2C.h> #include <avr/io.h> #include <avr/interrupt.h> // Set the LCD address to 0x27 for a 16 chars and 2 line display LiquidCrystal_I2C lcd(0x27, 16, 2); volatile unsigned char second = 0; volatile unsigned char minute = 0; boolean flag = 0; static void init_timer1(void); void LCD_update_time(void); void setup() { cli(); // запрет прерываний глобально // initialize Timer1 TCCR1A = 0; // set entire TCCR1A register to 0 TCCR1B = 0; // same for TCCR1B pinMode(11, INPUT_PULLUP); init_timer1(); sei(); // enable global interrupts lcd.begin(); lcd.backlight(); } void loop() { static unsigned long previousMillis = 0; if (digitalRead(11) == LOW && (millis() - previousMillis > 100)) { previousMillis = millis(); flag = 1; } if (flag) LCD_update_time(); } static void init_timer1(void) //set timer1 interrupt at 1Hz { TCCR1A = 0;// set entire TCCR1A register to 0 TCCR1B = 0;// same for TCCR1B TCNT1 = 0;//initialize counter value to 0 // set compare match register for 1hz increments OCR1A = 15624;// = (16*10^6) / (1*1024) - 1 (must be <65536) // Установить СТС режим и делитель частоты 1024 TCCR1B |= (1 << WGM12)|(1 << CS12) | (1 << CS10); // Разрешаем прерывание по сравнению с OCR1A TIMSK1 |= (1 << OCIE1A); } void LCD_update_time() { lcd.setCursor(1, 0); //выводим значение минут lcd.print(minute/10); //количество минут lcd.print(minute%10); //количество минут lcd.print(F(":")); //выводим символ ":"между минутами и секундами lcd.print(second/10); //количество секунд lcd.print(second%10); //количество минут } ISR(TIMER1_COMPA_vect) { second++; if (second == 60) { second = 0; minute++; if (minute == 60) { minute = 0; } } }Возможно функцию LCD_update_time() потребуется вызывать как это сделано в конструкции с millis() и не нужно запрещать прерывание а завести переменные либо отсчитывать время в обработчике по разрешению и завести флаг
if (millis() - previousMillis > 1000) { previousMillis = millis(); ... }Пока в уме только это
Зря похоже обращался #include <Wire.h> #include <LiquidCrystal_I2C.h> #include <avr/io.h> #include <avr/interrupt.h> // Set the LCD address to 0x27 for a 16 chars and 2 line display LiquidCrystal_I2C lcd(0x27, 16, 2); volatile unsigned char second = 0; volatile unsigned char minute = 0; volatile boolean flag = 0; static void init_timer1(void); void LCD_update_time(void); void setup() { cli(); // запрет прерываний глобально // initialize Timer1 TCCR1A = 0; // set entire TCCR1A register to 0 TCCR1B = 0; // same for TCCR1B pinMode(11, INPUT_PULLUP); init_timer1(); sei(); // enable global interrupts lcd.begin(); lcd.backlight(); } void loop() { static unsigned long previousMillis = 0; if (digitalRead(11) == LOW && (millis() - previousMillis > 100)) { previousMillis = millis(); flag = 1; } if (flag) LCD_update_time(); } static void init_timer1(void) //set timer1 interrupt at 1Hz { TCCR1A = 0;// set entire TCCR1A register to 0 TCCR1B = 0;// same for TCCR1B TCNT1 = 0;//initialize counter value to 0 // set compare match register for 1hz increments OCR1A = 15624;// = (16*10^6) / (1*1024) - 1 (must be <65536) // Установить СТС режим и делитель частоты 1024 TCCR1B |= (1 << WGM12)|(1 << CS12) | (1 << CS10); // Разрешаем прерывание по сравнению с OCR1A TIMSK1 |= (1 << OCIE1A); } void LCD_update_time() { lcd.setCursor(1, 0); //выводим значение минут lcd.print(minute/10); //количество минут lcd.print(minute%10); //количество минут lcd.print(F(":")); //выводим символ ":"между минутами и секундами lcd.print(second/10); //количество секунд lcd.print(second%10); //количество минут } ISR(TIMER1_COMPA_vect) { if (flag) { second++; if (second == 60) { second = 0; minute++; if (minute == 60) { minute = 0; } } } }