Не понятное поведение таймеров-счетчиков

//#include <avr/wdt.h>
#include <avr/delay.h>

void frenzy_start(long frenzy_need)
  {
    //8MHz
    int registr=4000000/frenzy_need;
    cli();
      OCR1A=registr;
      TCCR1B=0b00001001; //Делитель 1
    sei();
  }

ISR(TIMER1_COMPA_vect)
{
  digitalWrite(31, digitalRead(31) ^ 1);
}
ISR(INT4_vect)  
{
  digitalWrite(32, digitalRead(32) ^ 1);
}
//Чтение Serial порта
ISR(TIMER0_COMP_vect)
  {
    digitalWrite(33, digitalRead(33) ^ 1); 
    //SerialRead();
  }
void setup() {
  pinMode(31, OUTPUT);
  pinMode(32, OUTPUT);
  pinMode(33, OUTPUT);
  pinMode(13, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(3,OUTPUT);
  pinMode(4,INPUT);
  //Serial1.begin(38400);
  //Serial.begin(38400);
  cli();

  TIMSK =0;
  ETIMSK =0;
  EICRB = (1<<ISC41);  // Setting it equal is good here *IF* you want to ensure all other bits are clear, in this test code we do
  EIFR |= (1<<INTF4);  // Write a 1 to clear any possible pending interrupt
  EIMSK = (1<<INT4);
  //
  TCCR3A =0;
  TCCR3B=0;
  ETIMSK |= (1 << OCIE3A);
  TCNT3 =0;
  TCCR3A = 0b01000000;          
  OCR3A   = 1562;         
  TCCR3B =0b00001100;
  //
  TCCR0 =0;
  TCCR0=0;
  TCNT0 =0;
  TIMSK |= (1 << OCIE0);
  OCR0 = 78;
  TCCR0=0b00011100;
  //
  TCCR1A =0;
  TCCR1B=0;
  TCNT1 =0;  
  TIMSK |= (1 << OCIE1A);
  TCCR1A = 0b01000000;
  OCR1A = 78;
  //TCCR1B=0b00001011;
  TCCR1B=0b00001000;

  sei();
  frenzy_start(10000);
}
void loop() {}

Всем привет, сразу привожу код, строчки мигания светодиодами вставлены просто для того чтобы осциллографом постоянно не ширять по портам. Ситуация такая, таймеры запускаются и работают, строкой 71 я запускаю на лапку выход 10кГц, все работает, но если я пытаюсь подать больше, например 30кГц, то перестает работать таймер 0, точно не могу сказать с какой частоты перестает работать, но поведение странное не могу понять в чем причина, спасибо за любую помощь.

причина в том, что у контроллера только одно ядро… Дальше продолжать?

Настройте таймер так, что бы он вообще не дергал процессор. Пусть сам преключает пины.

1 лайк

@3d_killer
Прерывания - медленная вещь. Если вам нужны сколь-заметно высокие частоты - не генерите их в прерываниях.
А вы, к тому же, запихнули в прерывание ардуиновские digitalRead() и Write(). Если уж управляете таймерами через регистры. могли бы и пины напрямую переключать, хоть немного быстрее было бы

4000000/30000=133 такта. Управляя регистрами успеть можно. Но, как указали выше, ардуиновские функции всё портят. Не помню, но там как раз сотни тактов выполняются ДиджиталВрайт и Рид.

Во-во! +100! Он отлично умеет это делать. И никаких прерываний не нужно.

Вы видимо не поняли, DigitalWrite я написал просто для визуальной проверки, если посмотреть как стартуются таймеры у них сразу лапы подключены на выход и осциллографом на них есть частота, INT 4 это внешнее прерывание раз в секунду на него можно не обращать внимание, с осциллографом такая же тема (строки DigitalWrite при этом закомментированы), то есть когда я повышаю частоту на первом таймере до определенного значения все хорошо, а далее например 30, 40,50 кГц он выдает эти частоты, но при этом отваливается таймер 0 и перестает работать c Serial портом, на лапе 12 (Atmega128a) импульсы просто пропадают, это происходит с какой-то частоты и все что выше

это вы не поняли. Не важно, зачем вы это написали, но если время исполнения для вас критично - не надо писать ардуиновские функции в прерываниях.

Прочтите еще раз мой самый первый комментарий и подумайте над ним. Прерывания не исполняются где-то отдельно “над остальным кодом”, как иногда думают новички. Они исполняются на том же ядре, которое у атмеги128 ОДНО. И если вы напихали в каждый таймер кучу прерываний и запустили их на высокой частоте - начиная с какой-то частоты они начнут сталкиватся друг с другом и в итоге займут все время контроллера, не давая ему делать ничего другого.

Вообще идея управлять пинами, используя прерывания на таймерах - плохая. А вы, похоже, используете прерывания в хвост и в гриву, так ведь?
Послушайте Командира - у каждого таймера есть выделенные пины, которыми он может управлять, не задействуя ядро. Используйте их.

Так я же вам говорю, когда управление портами закомментированы (как вобщем-то в коде и должно быть) и осциллографом я проверяю лапы (OC0) - 12, (OC1A) - 13, (OC3A) - 3, при этом этот код из программы удален:

ISR(TIMER1_COMPA_vect)
{
  digitalWrite(31, digitalRead(31) ^ 1);
}
ISR(INT4_vect)  
{
  digitalWrite(32, digitalRead(32) ^ 1);
}
ISR(TIMER0_COMP_vect)
  {
    digitalWrite(33, digitalRead(33) ^ 1); 
  }

Происходит точно такая же ситуация, поднимаю до определенной частоты и отваливается таймер (OC0) - 12, импульсы на лапе пропадают и если в векторе прерывания вставить чтение Serial порта оно тоже не работает (хоть с ним хоть без него импульсы пропадают)

простите, кроме этого кода вы в первом сообщении почти ничего и не выложили. Как можно что-то обсуждать без кода?

Без кода мы сейчас опять свалимся в срач, как во всех ваших предыдущих ветках.

Сериал сам использует прерывания, поэтому в общем случае его в векторе прерывания использовать нельзя

Значит Вы привели нам не тот код, который исполнялся и хотите, чтобы мы чего-то угадывали по левому коду, не имеющему отношения к задаче?

Давайте я дам Вам ссылку на статью - держите. Возможно, это не совсем то, чего Вы ищете, но там разберётесь.

Так это весь код на котором опыты произвожу

#include <avr/wdt.h>
#include <avr/delay.h>
void frenzy_start(long frenzy_need)
    int registr=4000000/frenzy_need;
    cli();
      OCR1A=registr;
      TCCR1B=0b00001001; //Делитель 1
    sei();
  }
//******************************************************************
//Вектор прерывания таймера 1
//******************************************************************
ISR(TIMER1_COMPA_vect)
  {

  }
ISR(INT4_vect)  
  {
    wdt_reset();
  }
//Чтение Serial порта
ISR(TIMER0_COMP_vect)
  {
    //SerialRead();
  }
void setup() {
  wdt_enable(WDTO_2S);
  rtc.begin();
  rtc.setOutput(OUTPUT_SQW);
  rtc.setSQWRate(SQW_RATE_1);
  pinMode(31, OUTPUT);
  pinMode(32, OUTPUT);
  pinMode(33, OUTPUT);
  pinMode(13, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(3,OUTPUT);
  pinMode(4,INPUT);
  //Serial.begin(38400);
  cli();
  //******************************************************************
  //Запуск внешнего прерывания по часам
  //******************************************************************
  TIMSK =0;
  ETIMSK =0;
  EICRB = (1<<ISC41);  // Setting it equal is good here *IF* you want to ensure all other bits are clear, in this test code we do
  EIFR |= (1<<INTF4);  // Write a 1 to clear any possible pending interrupt
  EIMSK = (1<<INT4);
  //
  TCCR3A =0;
  TCCR3B=0;
  ETIMSK |= (1 << OCIE3A);
  TCNT3 =0;
  TCCR3A = 0b01000000;          
  OCR3A   = 1562;         
  TCCR3B =0b00001100;
  //
  TCCR0 =0;
  TCCR0=0;
  TCNT0 =0;
  TIMSK |= (1 << OCIE0);
  OCR0 = 78;
  TCCR0=0b00011100;
  //
  TCCR1A =0;
  TCCR1B=0;
  TCNT1 =0;  
  TIMSK |= (1 << OCIE1A);
  TCCR1A = 0b01000000;
  OCR1A = 78;
  //TCCR1B=0b00001011;
  TCCR1B=0b00001000;
  //
  //TCCR2=0;
  //TCNT2 = 0; 
  //TIMSK = (1<<OCIE2);
  //TCCR2=0b00101100;
  //OCR2 = 78;
  sei();
  frenzy_start(60000);
  
}

void loop() {}

Меняю эту строку frenzy_start(60000);
осциллографом проверяю лапы которые написал выше. 10000 работает, 30000 и выше отваливается 0 таймер

Ну так прерывания-то остались.

Тут я не вполне уверен, ассемблерщики поправят, но скорее всего оптимизатор не может выкинуть обработчик прерывания, даже если он пустой. А значит время на прерывание кода, вход в обработчик и выход из него все равно тратится. То есть все проблемы, описанные в сообщении 8 - в полный рост.

Не используйте прерывания для генерации сигналов. Это неверный подход.

2 лайка

Сейчас удалю векторы, попробую

не только векторы, еще настройки прерываний на таймерах тоже уберите, иначе у вас будет отрабатывать системный пустой вектор

Вот так попробовал, сейчас выдает на трех лапах импульсы вроде как помогло

#include <avr/wdt.h>
#include <avr/delay.h>
#include <DS3231.h>               //Часы
Time times;
DS3231  rtc(SDA, SCL);
void frenzy_start(long frenzy_need)
  {
    int registr=4000000/frenzy_need;
    cli();
      OCR1A=registr;
      TCCR1B=0b00001001; //Делитель 1
    sei();
  }
//******************************************************************
//Вектор прерывания таймера 1
//******************************************************************
//ISR(TIMER1_COMPA_vect)
//  {

//  }
ISR(INT4_vect)  
  {
    wdt_reset();
  }
//Чтение Serial порта
//ISR(TIMER0_COMP_vect)
//  {
    //SerialRead();
// }
void setup() {
  wdt_enable(WDTO_2S);
  rtc.begin();
  rtc.setOutput(OUTPUT_SQW);
  rtc.setSQWRate(SQW_RATE_1);
  pinMode(31, OUTPUT);
  pinMode(32, OUTPUT);
  pinMode(33, OUTPUT);
  pinMode(13, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(3,OUTPUT);
  pinMode(4,INPUT);
  //Serial.begin(38400);
  cli();
  //******************************************************************
  //Запуск внешнего прерывания по часам
  //******************************************************************
  TIMSK =0;
  ETIMSK =0;
  EICRB = (1<<ISC41);  // Setting it equal is good here *IF* you want to ensure all other bits are clear, in this test code we do
  EIFR |= (1<<INTF4);  // Write a 1 to clear any possible pending interrupt
  EIMSK = (1<<INT4);
  //
  TCCR3A =0;
  TCCR3B=0;
  ETIMSK |= (1 << OCIE3A);
  TCNT3 =0;
  TCCR3A = 0b01000000;          
  OCR3A   = 1562;         
  TCCR3B =0b00001100;
  //
  TCCR0 =0;
  TCCR0=0;
  TCNT0 =0;
  TIMSK |= (1 << OCIE0);
  OCR0 = 78;
  TCCR0=0b00011100;
  //
  TCCR1A =0;
  TCCR1B=0;
  TCNT1 =0;  
  TIMSK |= (1 << OCIE1A);
  TCCR1A = 0b01000000;
  OCR1A = 78;
  //TCCR1B=0b00001011;
  TCCR1B=0b00001000;
  //
  //TCCR2=0;
  //TCNT2 = 0; 
  //TIMSK = (1<<OCIE2);
  //TCCR2=0b00101100;
  //OCR2 = 78;
  sei();
  frenzy_start(600000);
  
}

void loop() {}

Так ведь все эти недочёты на TC1, а отваливается TC0. Как я понял там тоже режим CTC?
И вачдог на какое время? 2с, начит не влияет.

на всех трех CTC
вач дог 2 секунды, импульс с часов каждую секунду

Теперь другая проблемка, возникла, (для теста визуально чтобы увидеть почему команды с Serial не приходят), код поправил вот так:

#include <avr/wdt.h>
#include <avr/delay.h>
#include <DS3231.h>               //Часы
Time times;
DS3231  rtc(SDA, SCL);
void frenzy_start(long frenzy_need)
  {
    int registr=4000000/frenzy_need;
    cli();
      OCR1A=registr;
      TCCR1B=0b00001001; //Делитель 1
    sei();
  }
ISR(INT4_vect)  
  {
    wdt_reset();
  }
//Чтение Serial порта
ISR(TIMER0_COMP_vect)
  {
        Serial1.println("123");
  }
void setup() {
  wdt_enable(WDTO_2S);
  rtc.begin();
  rtc.setOutput(OUTPUT_SQW);
  rtc.setSQWRate(SQW_RATE_1);
  pinMode(31, OUTPUT);
  pinMode(32, OUTPUT);
  pinMode(33, OUTPUT);
  pinMode(13, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(3,OUTPUT);
  pinMode(4,INPUT);
  Serial1.begin(38400);
  cli();
  //******************************************************************
  //Запуск внешнего прерывания по часам
  //******************************************************************
  TIMSK =0;
  ETIMSK =0;
  EICRB = (1<<ISC41);  // Setting it equal is good here *IF* you want to ensure all other bits are clear, in this test code we do
  EIFR |= (1<<INTF4);  // Write a 1 to clear any possible pending interrupt
  EIMSK = (1<<INT4);
  //
  TCCR3A =0;
  TCCR3B=0;
  ETIMSK |= (1 << OCIE3A);
  TCNT3 =0;
  TCCR3A = 0b01000000;          
  OCR3A   = 1562;         
  TCCR3B =0b00001100;
  //
  TCCR0 =0;
  TCCR0=0;
  TCNT0 =0;
  TIMSK |= (1 << OCIE0);
  OCR0 = 78;
  TCCR0=0b00011100;
  //
  TCCR1A =0;
  TCCR1B=0;
  TCNT1 =0;  
  TIMSK |= (1 << OCIE1A);
  TCCR1A = 0b01000000;
  OCR1A = 78;
  //TCCR1B=0b00001011;
  TCCR1B=0b00001000;
  sei();
  frenzy_start(60000);
  
}

void loop() {}

Когда строка frenzy_start(60000); раскомментирована то в монитор валятся сообщения вида 123�� без перехода на следующую строку, если эту строку закомментировать то приходит 123 каждый раз с новой строки (как должно быть)