Помогите разобраться с работой прерываниями и таймерами

Dmti
Dmti аватар
Offline
Зарегистрирован: 13.10.2013

Понадобился пид регулятор, быстренько скидал из валявшихся ардуинки мини и какого-то термосопротивления от чайника-термоса. Все работало, пока работал блок питания 12в 25А. а когда блок крякнул, решил обойтись без него, а с помощью симистора и фазного вместо шим регулятора. Потому, как всегда хотелось попробовать, да и с прерываниями, когда-то надо начинать разбираться, вот и начал пробовать. Но столкнулся с непонятной мне проблемой: даже не запуская прерывание, перестаёт работать ком порт. При запуске монитора порта пишет одну кракозюльку и молчит зараза. Подскажите, куда рыть или ткните носом в ошибки. Спасибо.


  /*После импульса запуска  на PD0(INT0) срабатывает прерывание, запоминает время и запускает 
 timer2 на tcnt циклов. При достижении этого времени вектор ISR(TIMER1_OVF_vect)выдает импульс
 на ноге tir (PB7)длительностью 30 миллисекунд и останавливает таймер до следующего прерывания INT0.*/

#include <Arduino.h>
  byte tcntValue = 10;
  unsigned int tcnt, pause = 30;
  #define synh PD0  //синхро импульс перехода через 0
  #define tir  PB7  //импульс управление симистором
//------------------------------------------------------------------ 
//------------------------------------------------------------------Срабатывает при синхро импульсе
ISR(INT0_vect)           // Функция обработки прерывания INT0
{
  TCCR1B = (0<<CS12)|(1<<CS11)|(1<<CS10); // настраиваем делитель
   tcnt = TCNT1;                          //выставляем начальное значение TCNT1
   TCNT1 = tcnt + tcntValue;
}

//------------------------------------------------------------------ 
//------------------------------------------------------------------Через нужное время включает тиристор
ISR( TIMER1_OVF_vect )
{
  PORTB |= ( 1 << tir );         //Передний фронт отпирающего импульса симистора
   //delayMicroseconds(pause);   //Длительность отпирающего импульса 
   PORTB &= ~( 1 << tir );       //Задний фронт отпирающего импульса симистора
  
 TCCR1B = TCNT1 = 0;             //остановка и обнуление таймера 0
}

//------------------------------------------------------------------ 
//------------------------------------------------------------------ 
void setup() {
    Serial.begin(9600);
    Serial.println("1");
  //---------------------------//настраиваем порты для управления симистора
  DDRB = ( 1 << tir );           // настраиваем tir на выход
  PORTB &= ~( 1 << tir );        //гасим светодиод
  DDRD &= ~_BV(synh);            //вход детектора нуля
  PORTD |= _BV(synh);            //с подтягивающим резистором для питания транзистора оптрона
    Serial.println("2");
  //------------------------------------------------------------------ 
  //---------------------------//настраиваем внешнее прерывание
  MCUCR |= _BV(ISC00);                       //внешнее прерывание 0 по любому изменению уровня
  EIMSK &= ~ (1 << INT0);                    // Запретить прерывание (так как следующая команда устанавливает режим INT0_SENSE_LOW_LEVEL)
  EICRA &= ~ (1 << ISC00) | (1 << ISC01);    // Обнуляем биты ISC00 и ISC01 в регистре EICRA
  EICRA |= 1;
  EIMSK |= (1 << INT0);                      //разрешаем внешнее прерывание 0
    Serial.println("3");
  //------------------------------------------------------------------ 
  //---------------------------//настраиваем прерывание на таймер 
  TCCR2B = (0<<CS12)|(1<<CS11)|(1<<CS10); // настраиваем делитель
  //SREG|=(1<<7);                         // Глобальное разрешение прерываний.
  TIMSK2 |= (1<<TOIE2);                   // разрешаем прерывание по переполнению таймера
  tcnt = TCNT2;                           //выставляем начальное значение TCNT2
  TCNT2 = tcntValue + tcnt;              // выставляем начальное значение TCNT2
  // разрешение прерываний INT0 и INT1
    Serial.println("4");
  EIMSK  =  (1<<INT2);                 // выставляем бит общего разрешения прерываний
//*/

  Serial.println("Hi");
  //Настраиваем ноги камня
  DDRC = 255; 
  DDRB = 255; 
}

void loop() {
    Serial.println("Press button");
}

 

Arhat109
Offline
Зарегистрирован: 26.05.2015

не понятно. В процедуре обработчика у вас пользуется 1-й таймер, а в коде setup() - 2-й. И ещё нигде не увидел в какой режим ставятся оба счетчика (TCCR1A, TCCR2A) ... так надо?

Dmti
Dmti аватар
Offline
Зарегистрирован: 13.10.2013

Извините, этот пример выдирал из уже написанной программы, а так как начал использовать в начале timer1, а потом решил перейти на

timer2,  то правил уже прямо в коментах на сайте, ну и как всегда проглядел. А режим таймера - это мой косяк, исправил, но ничего не изменилось.

 /*После импульса запуска  на PD0(INT0) срабатывает прерывание, запоминает время и запускает 
 timer2 на tcnt циклов. При достижении этого времени вектор ISR(TIMER1_OVF_vect)выдает импульс
 на ноге tir (PB7)длительностью 30 миллисекунд и останавливает таймер до следующего прерывания INT0.*/

#include <Arduino.h>
  byte tcntValue = 10;
  unsigned int tcnt, pause = 30;
  #define synh PD0  //синхро импульс перехода через 0
  #define tir  PB7  //импульс упровление симистором
//------------------------------------------------------------------ 
//------------------------------------------------------------------Срабатывает при синхро импульсе
ISR(INT0_vect)           // Функция обработки прерывания INT0
{
  TCCR2B = (0<<CS12)|(1<<CS11)|(1<<CS10); // настраиваем делитель
   tcnt = TCNT2; //выставляем начальное значение TCNT2
   TCNT1 = tcnt + tcntValue;
}

//------------------------------------------------------------------ 
/*/------------------------------------------------------------------Через нужное время включает тиристор
ISR( TIMER2_OVF_vect )
{
  PORTB |= ( 1 << tir );         //Передний фронт отпирающего импульса симистора
   //delayMicroseconds(pause);     //Длительность отпирающего импульса 
   PORTB &= ~( 1 << tir );       //Задний фронт отпирающего импульса симистора
  
 TCCR2B = TCNT2 = 0;             //остановка и обнуление таймера 0
}//*/

//------------------------------------------------------------------ 
//------------------------------------------------------------------ 
void setup() {
    Serial.begin(9600);
    Serial.println("1");
  //---------------------------//настраиваем порты для управлением симистора
  DDRB = ( 1 << tir );  // настраиваем tir на выход
  PORTB &= ~( 1 << tir ); //гасим светодиод
  DDRD &= ~_BV(synh);     //вход детектора нуля
  PORTD |= _BV(synh);     //с подтягивающим резистором для питания транзистора оптрона
    Serial.println("2");
  //------------------------------------------------------------------ 
  //---------------------------//настраиваем внешнее прерывание
  MCUCR |= _BV(ISC00);                       //внешнее прерывание 0 по любому изменению уровня
  EIMSK &= ~ (1 << INT0);                    // Запретить прерывание (так как следующая команда устанавливает режим INT0_SENSE_LOW_LEVEL)
  EICRA &= ~ (1 << ISC00) | (1 << ISC01);    // Обнуляем биты ISC00 и ISC01 в регистре EICRA
  EICRA |= 1;
  EIMSK |= (1 << INT0);                      //разрешаем внешнее прерывание 0
    Serial.println("3");
  //------------------------------------------------------------------ 
  //---------------------------//настраиваем прерывание на таймер 
  TCCR2B = (0<<CS12)|(1<<CS11)|(1<<CS10); // настраиваем делитель
  TCCR2A = (0<<7)|(1<<6)|(0<<5)|(1<<4);   //срабатывает при переключении
  TIMSK2 |= (1<<TOIE2);   // разрешаем прерывание по переполнению таймера
  tcnt = TCNT2; //выставляем начальное значение TCNT2
  TCNT2 = tcntValue + tcnt;       // выставляем начальное значение TCNT2
  // разрешение прерываний INT0 и INT1
    Serial.println("4");
  EIMSK  =  (1<<INT2); // выставляем бит общего разрешения прерываний
//*/

  Serial.println("Hi");
  //Настраиваем ноги камня
  DDRC = 255; 
  DDRB = 255; 
}

void loop() {
    Serial.println("Press button");
  // put your main code here, to run repeatedly:

}

 

maksim
Offline
Зарегистрирован: 12.02.2012

Какой у вас контроллер?

Dmti
Dmti аватар
Offline
Зарегистрирован: 13.10.2013

Было все собранно на ардуино мини, а отладку делаю (чтобы не разбирать) на мега2560.

Dmti
Dmti аватар
Offline
Зарегистрирован: 13.10.2013

Читаю описание меги 2560 на этом-же сайте:

Внешнее прерывание: 2 (прерывание 0), 3 (прерывание 1), 18 (прерывание 5), 19 (прерывание 4), 20 (прерывание 3), и 21 (прерывание 2)

Что-то всё это меня совсем в заблуждение ввело. Если ипользую interrupt  то INT0, а если через регистры то INT2. Или как всё это понимать, и к какой наге прикручивать synh ? Во путаница, кто знает, как правильно?

Puhlyaviy
Puhlyaviy аватар
Offline
Зарегистрирован: 22.05.2013

Эт вы круто... отладка на одном камне а работать будет на другом? О.о

Dmti
Dmti аватар
Offline
Зарегистрирован: 13.10.2013

не в первой, постоянно так делаю, а чего тут такого. мега 2560 одна, а мини по необходимости сам делаю.

Puhlyaviy
Puhlyaviy аватар
Offline
Зарегистрирован: 22.05.2013

Ну даже не знаю. Как бы атмега328 и атмега2560 это конечно avr. Но как бы это разные камни.

Dmti
Dmti аватар
Offline
Зарегистрирован: 13.10.2013

Давай на этом заморачиваться не будем, это моя проблема (вообще не проблема). А сделаем на меги2560, чтобы всё работало.  Лучше с кодом помогите, 3-й день голову грею. 

Dmti
Dmti аватар
Offline
Зарегистрирован: 13.10.2013

И вообще не мега328, а мега8 :))

Dmti
Dmti аватар
Offline
Зарегистрирован: 13.10.2013

Если закоментировать 58 строку, то Serial.print начинает работать как надо. Какая связь прерывания и порта?????

maksim
Offline
Зарегистрирован: 12.02.2012

 

EIMSK |= (1<<INT2);

 

Dmti
Dmti аватар
Offline
Зарегистрирован: 13.10.2013

Да эту строку. В порт посыл сразу идёт как надо, но вся моя фазная регулировка не работает, а с разкомментированной строкой вроде (смотрю осциллографом) как работает. Или или, а хочу, чтобы всё работало.

maksim
Offline
Зарегистрирован: 12.02.2012

Внимательно посмотрите на свою 58 строку и на строку из #12 сообщения.

Arhat109
Offline
Зарегистрирован: 26.05.2015

Ну и зачем такое советовать? Что 58-я строка, что строка в сообщении №12 - абсолютно идентичны. :)

Может посоветовать сравнить пару 44, 47 ... с одной 58 строкой? :)

Dmti
Dmti аватар
Offline
Зарегистрирован: 13.10.2013

Не, дело не в этом, комментирую эту строку, получаю сообщения в порт как надо, а нет - только единичку.

= и |= менял, бестолку.

Dmti
Dmti аватар
Offline
Зарегистрирован: 13.10.2013

Логически: этот регистр разрешает прерывания, я разрешаю только INT2. Как прирывание INT2 может влиять на работу ком порта? Или если судить из сообщения №5, это не INT2, a INT0. И ещё, Serial какое прерывание использует (если использует)?

Arhat109
Offline
Зарегистрирован: 26.05.2015

Посмотрел в даташит. Ответ - "легко". Wiring, инициализируя Serial на меге, активирует ВСЕ 4 USART. посмотрите внимательно на тексты при компиляции скетчей: и легко обнаружите компиляцию файлов Serial0, ..1, ..2, ..3. Только вот как раз ваше INT2 - это одна из ног одного из USART.

Как думаю, что активируя прерывание "ручками" скорее всего надо ещё прописывать ноги в правильные режимы в setup() после отработки стандартного init()... или @выбросить" wiring.c (init() - лежит в ней и смотреть чего там происходит с ногами USART-ов надо в нем)...

Puhlyaviy
Puhlyaviy аватар
Offline
Зарегистрирован: 22.05.2013

Набрал попкорна и уселся смотреть как ща тут великий архат разрулит.
Сейчас видимо побежал на другой форум консультироваться. :)

maksim
Offline
Зарегистрирован: 12.02.2012

Arhat109 пишет:

Ну и зачем такое советовать? Что 58-я строка, что строка в сообщении №12 - абсолютно идентичны. :)

Может посоветовать сравнить пару 44, 47 ... с одной 58 строкой? :)

Для особо недалеких показываю разницу в картинках:

EIMSK = (1 << INT2);

и

EIMSK |= (1 << INT2);

при это выше делается следующее

EIMSK |= (1 << INT0);
maksim
Offline
Зарегистрирован: 12.02.2012

Dmti пишет:

Не, дело не в этом, комментирую эту строку, получаю сообщения в порт как надо, а нет - только единичку.

= и |= менял, бестолку.

И, кстати, а зачем вообще там 58 строка, ведь вы же выше настраиваете INT0, а не INT2.

maksim
Offline
Зарегистрирован: 12.02.2012

Писалось это когда то для атмеги328

#define OPT 3
#define TRIAC 0
uint16_t cur_dim = 350;

void DimmerInit()
{
  cli();
  PORTD |= 1<<OPT;
  EICRA = 1<<ISC11; 
  EIMSK = 1<<INT1;

  DDRB |= 1<<TRIAC;
  PORTB &= ~(1<<TRIAC);
  TCCR1A = 0;
  TCCR1B = 4; // 256
  OCR1B = 386; 
  sei();
}


ISR(INT1_vect)
{
  TCNT1 = 0;
  EIMSK = 0;
  TIMSK1 = (1<<OCIE1A)|(1<<OCIE1B);
}

ISR(TIMER1_COMPA_vect)
{
  PORTB |= 1<<TRIAC;
}

ISR(TIMER1_COMPB_vect)
{
  PORTB &= ~(1<<TRIAC);
  OCR1A = cur_dim;
  TIMSK1 = 0;
  EIMSK = 1<<INT1;
}

и даже работало. 

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

Dmti
Dmti аватар
Offline
Зарегистрирован: 13.10.2013

maksim Спасибо за пример. Если вы не против, взял его за основу. Дополнил коментами для понимания, если не трудно, прочитайте - правильно я понимаю ваш код. Serial работает, а остальное узнаю, когда переделаю на mega8 и вставлю в свою прогу.

/* было для меги 328
#define OPT 3
#define TRIAC 0
*/
//для меги 2560
#define OPT PD0
#define TRIAC 0
uint16_t cur_dim = 350;
 
void setup()
{
  Serial.begin(9600);
    Serial.println("1");

  cli();                //запретили прерывания
//-----------------------порты
  PORTD |= 1<<OPT;      // вход sinh
  DDRB |= 1<<TRIAC;     // выход tir
  PORTB &= ~(1<<TRIAC); // подтягиваем резистором
  Serial.println("2");
  
//-----------------------Прерывание
  EICRA = 1<<ISC11;     // срабатывает при любом изменении сигнала
  EIMSK = 1<<INT1;      // включаем прерывание INT1
 Serial.println("3");

//-----------------------Таймер
  TCCR1A = 0;           // обнулили режим работы
  TCCR1B = 4; // 256    // настраиваем делитель таймера тик=f/256
  OCR1B = 386;          // количество тиков до выключения симистора
  Serial.println("4");
//-----------------------
  sei();                //разрешили глобально прерывания
  Serial.println("5");
}
 
 
ISR(INT1_vect)
{
  TCNT1 = 0;                        //таймер начинает считать с нуля
  EIMSK = 0;                        //на всякий случай отключили прерывание
  TIMSK1 = (1<<OCIE1A)|(1<<OCIE1B); //запускаем таймер
}
 
ISR(TIMER1_COMPA_vect) //сработает при TCNT1 == cur_dim
{
  PORTB |= 1<<TRIAC;   //выключили симистор 
}
 
ISR(TIMER1_COMPB_vect) //при достижении заданного времени
{
  PORTB &= ~(1<<TRIAC);//включили симистор
  OCR1A = cur_dim;     //время, через которое включится симистор после старта
  TIMSK1 = 0;          //отключили таймер
  EIMSK = 1<<INT1;     //включили прерывание
}
void loop(){
//Serial.println("3");
}

 

maksim
Offline
Зарегистрирован: 12.02.2012

Не, немного не так, включение перепутано с выключением. Прерывание срабатывает по заднему фронту (falling).

Дело в том, что сисмистор нельзя закрыть в любой момент, а открыть можно. У него есть такая характеристика как "ток залипания", то есть при определенном токе симистор просто не закрывается. Поэтому регулирование происходит так, что срезается передняя часть синусоиды, а не задняя. Ловим 0, сначала держим симистор закрытым, открываем и закрываем при переходе через 0.

То есть:
1. Симистор закрыт;
2. Поймали фронт перехода через ноль, а точнее задний фронт с оптрона, наступающий чуть позже, чем сам переход;
3. Отсчитали нужное время ,по совпадению 
COMPA открыли симистор;
4. 
Закрыли симистор при переходе через 0 по совпадению COMPB.

И так по кругу.

Dmti
Dmti аватар
Offline
Зарегистрирован: 13.10.2013

Я так понимаю, здесь в порт запишется 0 PORTB |= 1<<TRIAC;   //выключили симистор

а здесь - единица   PORTB &= ~(1<<TRIAC);//включили симистор

а как железо работает, я знаю. Мне бы только программки писать научиться :)) 

maksim
Offline
Зарегистрирован: 12.02.2012

Нет, не правильно понимаете, все наоборот. Как оно работает написано выше.

Вот эта t (без учета того, что 0 детектируется чуть позже) и есть COMPA (cur_dim), и именно по истечению этой t симстор открывается (включается) PORTB |= 1<<TRIAC; 

Dmti
Dmti аватар
Offline
Зарегистрирован: 13.10.2013

Такие диаграммы я могу нарисовать и для 3-х фазного, и даже для 6 фазного регулирования, и понимаю, что для чего, просто выражение PORTB |= 1<<0; 

мне кажется, что в порт запишет 0, или у вас на опто паре, которая управляет симистором, светодиод к +5в подвешан?

Arhat109
Offline
Зарегистрирован: 26.05.2015

Ровно наоборот, выражение:

PORTB |= 1<<const;

читается как 1 сдвинуть влево на const бит, и добавить к значению в порту. То бишь, "установить 1 в бите const не меняя остальных битов порта"

:)

Jeka_M
Jeka_M аватар
Offline
Зарегистрирован: 06.07.2014

Dmti пишет:

просто выражение PORTB |= 1<<0; мне кажется, что в порт запишет 0, или у вас на опто паре, которая управляет симистором, светодиод к +5в подвешан?

Нет, запишет в PB0 единицу. Чтобы было понятней, можно записать так: 

PORTB |= 1<<PB0; (установить PB0 в единицу)

PORTB &= ~(1<<PB0); (сбросить PB0 в ноль)

Если совсем всё непонятно - читайте про битовые операции и сдвиг.

 

Dmti
Dmti аватар
Offline
Зарегистрирован: 13.10.2013

Извините, точно сдвигаем на ноль и выставляем 1, а я по запарке читал - сдвигаем на 1 и выставляем 0.

Dmti
Dmti аватар
Offline
Зарегистрирован: 13.10.2013

Для проверки и настройки, если вход с выходом соединить и изменить немного прогу, будет работать?

ISR(INT1_vect)
{
  TCNT1 = 0;                        //таймер начинает считать с нуля
  EIMSK = 0;                        //навсякий случай отключили прерывание
  PORTB |= 1<<2; //   <-------------   для проверки
  TIMSK1 = (1<<OCIE1A)|(1<<OCIE1B); //запускаем таймер
}
 
ISR(TIMER1_COMPA_vect) //сработает при TCNT1 == cur_dim
{
    PORTB &= ~(1<<2);  //   <-------------   для проверки
  PORTB |= 1<<TRIAC;   //включили симистор 
}
 
ISR(TIMER1_COMPB_vect) //при достижении заданного времени
{
                       //вЫключили симистор ---------отсюда -------------------->|
  OCR1A = cur_dim;     //время через которое включится симистор после старта     |
  TIMSK1 = 0;          //отключили таймер                                        |
  EIMSK = 1<<INT1;     //включили прерывание                                     |
  PORTB &= ~(1<<TRIAC);//вЫключили симистор <--перенёс сюда----------------------/
}

у меня чего-то не работает.

maksim
Offline
Зарегистрирован: 12.02.2012

Ну вы бы тогда хотябы схему с номиналами показали.

Dmti
Dmti аватар
Offline
Зарегистрирован: 13.10.2013

А при таком раскладе схемки никакой нет, есть кусок провода с PB0 до PD1. надо только вручную запустить кандюком кратковременно на +5в, срабатывает прерывание, запускает таймер, таймер через заданное время моргает портом, и тем самым снова запускает прерывание. Но если к проводу подцепить осциллограф, то все временные циклы можно подглядеть, если все работает.

Dmti
Dmti аватар
Offline
Зарегистрирован: 13.10.2013

А работает всё это странно. ||||__||||__|||| Это по умолчанию, если уменьшаем cur_dim, увеличивается количество импульсов. Если изменяем TCNT1, то всё пропадает и не запускается, и всегда показывает частоту 161 герц. Делителем частота меняется. Я так понимаю, изменяя cur_dim, должно плавно меняться расстояние между импульсами, и не должно быть пауз. Или не так?

maksim
Offline
Зарегистрирован: 12.02.2012

Это вы уже херней какой то занимаетесь, не будет так оно работать. Тогда уж если хотите проверить как оно работает, запустите на другом таймере или другой дуине генератор импульсов на 100 Гц и смотрите осцилографом что будет идти на симистор.

Dmti
Dmti аватар
Offline
Зарегистрирован: 13.10.2013

Можно и генератор, а в чем разница? И так работает.