Прерывания по таймеру

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

Добрый день, корифеи

Освоившись с отказом от 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 сек)
  }
}

 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Для того, что является "слишком", а что - нет, должны быть какие-то критерии.

В принципе, критерии могут быть такими:

1. Интервал между пользовательскими прерываниями должен быть строго больше, чем длительность прерывания. Т.е. чтобы следующее прерывание не наезжало на предыдущее.

2. Длительность пользовательского прерывания желательно, чтобы была меньше, чем интервал между системными прерываниями.

Системнве прерывания выполняются примерно 1 раз в мс. Значит, желавтельно, чтобы пользовательское прерывангие было короче 1 мс.

Лично я в setup() перед тем, как включать прерывание вызываю его как функцию, измеряю время его работы, вывожу это время на печать, и только потом включаю прерывание. Даже если контроллер намертво зависнет, в консорли у меня будет цифра, облегчающая диагностику.

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

Спасибо за критерии оценки - подозревал что они должны быть, вот теперь появились количественные ориентиры. По проверке длительности прерывания тоже все понял, еще раз спасибо.

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Здравствуйте, это упрощенный пример, чтобы многие поняли суть вопроса Прерывание настроено на таймере 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;
        }
    }  
}

 

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Возможно функцию LCD_update_time() потребуется вызывать как это сделано в конструкции с millis() и не нужно запрещать прерывание а завести переменные либо отсчитывать время в обработчике по разрешению и завести флаг

if (millis() - previousMillis > 1000) {
       previousMillis = millis();
       ...
}

Пока в уме только это

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018
Зря похоже обращался
#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;
        }
    }   
    }
}