Довольно часто в комментариях задаются одни и те же вопросы, суть которых сводится к отсчету времени. Обычный ответ — юзай таймер! Примеры уже есть некоторых статьях, но возможно не сделаны нужные акценты. И чтобы пример был с каким то смыслом, заодно рассмотрим принцип управления симистором.

Пример очень утрированный, но надеюсь он поможет понять общую суть. Пофантазируем и представим себе обычный водопроводный кран, который может крутиться влево или вправо, в зависимости от этого будет литься горячая и холодная вода. Когда кран посередине (в нуле) вода не течет. Что будет, если крутануть кран влево и вправо по синусоидальному закону?
simistor1

В общем то, ничего особенного 🙂 Просто в банку плюхнется некоторое количество воды. Сколько? Условимся, что этот объем будет пропорционален площади фигуры ограниченной синусом, проще говоря, если посчитать площадь красного горбика (горячей воды) и синего горбика (холодной воды), и сложить их, то получим наши литры теплой воды.

Теперь повторим еще раз тот же эксперимент, но в момент открывания крана, на долю секунды будем затыкать пальцем кран, так чтобы вода не текла.
simistor2

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

Симистор является неким аналогом водопроводного крана, а аналогом количества воды в банке за единицу времени, является мощность, которая передается на потребитель(лампочка, нагреватель и т.п.). Т.е. если мы воткнем лампочку напрямую в сеть то, это будет 100% яркости лампы, если мы воткнем лампу через симистор и будем открывать его с задержкой, то лампочка будет светить меньше. Чем больше задержка тем тусклее будет свет.

Симистор имеет три электрода, два из них обычно называются условный анод/катод и один управляющий. Главная особенность в том, что если на условном аноде и катоде не нулевое напряжение, то если подать на его управляющий электрод напряжение, он откроется, но закрыть его воздействием на управляющий электрод уже будет нельзя. Только когда напряжение на аноде и катоде станет равным нулю, тогда он закроется. Таким образом для управления нагрузкой постоянного тока, он не подходит, зато с переменный током нет проблем, так как напряжение сети проходит через ноль 100 раз за секунду.

Рассмотрим полпериода синусоиды 10мс, того самого напряжения что у нас в розетке. Представим все пройденное напряжение за 10мс, как 100% мощности. Если нам нужно зажечь лампочку на 50% мощности мы должны отловить момент, когда сетевое напряжение пересекает 0 точку, в этом случае симистор гарантированно выключен, после этого ждем 50% времени от 10мс(т.е. 5 мс все выключено) и в оставшиеся 5мс включаем симистор. Когда он дойдет до следующего нуля, то он сам выключится и можно будет повторить цикл.
simistor3

Вопрос первый как отловить пересечение нуля (zero cross)? Самый простой способ, это апнот AVR182 — подавать сетевое напряжение через мегаомный резистор на вход внешнего прерывания. Некоторые рекомендуют поставить параллельно входу кондер на 10пФ.
simistor4

Идея заключается в том, что внутренние диоды ножки ограничивают напряжение, поэтому в моменты положительной полуволны синусоиды у нас будет логическая единица, а когда отрицательная, то напряжение не опустится ниже -0.5В, что соответствует логическому нулю. Таким образом, когда включаем внешнее прерывание по любому изменению фронта, получаем ноль. По правильному, хорошо сделать развязку от сети, например через трансформатор или оптрон. Но как реализовывать дело личное.

Следующий момент, симистор отпирается отрицательным напряжением на управляющем электроде, поэтому есть ширпотребное решение использовать оптроны. Типовую схему можно найти в даташите, т.е. достаточно в моменты прохождения напряжения через ноль, подавать импульсы на Rin(VCC).
moc3041

Только нужно смотреть внимательно, вышеописанный метод называется фазовым управлением, для него нужны оптроны Non-Zero Crossing, ибо существуют со встроенной Zero Crossing схемой, у них принцип управления симистором отличается. Там используется пропуск периодов, т.е. подаешь импульс, оптрон откроется только в момент следующего прохождения через ноль.

И самое важная часть повествования, расчет работы таймера. Первое что нужно понимать, таймер зависит от частоты кварца/генератора, я обычно юзаю 8МГц. Далее идет выбор предделителя, например для меги8 таймера2, есть такие варианты 1,8,16,32,64,128,256,1024. Обратите внимание, что для разных таймеров количество делителей будет разным. Не парим мозг, открываем CodeWizard и видим все доступные частоты, т.е. это и есть выбор предделителя.
timer2_clock

При этом 1 тик таймера будет равен:
1/ 8 000 000 = 0,000000125 сек
1/ 1 000 000 = 0,000001
1/ 250 000 = 0,000004
1/ 125 000 = 0,000008
1/ 62 500 = 0,000016
1/ 31 250 = 0,000032
1/ 7 813 = 0,000128

Все эти делители включаются в регистре TCCR, как? Это тупо комбинация битов CS22, CS21, CS20.

Для делителя на 1024

TCCR2= (1<<CS22) | (1<<CS21) | (1<<CS20);

Для делителя на 1

TCCR2= (0<<CS22) | (0<<CS21) | (1<<CS20);

Для делителя на 256

TCCR2=(1<<CS22) | (1<<CS21) | (0<<CS20);

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

Допустим выбран предделитель на 128 (частота 62 500), это значит что через каждые 0,000016 секунды таймер увеличит свое значение на 1. Обычно это значение хранится в регистре TCNT1, TCNT2, TCNTx и т.п., в stm32 CNT, он называется такой регистр счетным. Теоретически мы можем руками менять значения в счетном регистре: обнулять TCNT2 = 0, начинать считать не с нуля TCNT2 = 64, прибавлять TCNT2 = TCNT2 + 10, убавлять TCNT2 = TCNT2 — 10.

До какой величины может считать таймер? Обычно это выражается битностью, если говорят что таймер восьмибитный, значит максимальное 2^8 = 256, если шестнадцатибитный 2^16 = 65 536. После достижения максимального значения таймер сбросится в ноль.

Что нам дает количество бит? Максимальный временной промежуток, который может отсчитать 8битный таймер, на частоте 62500:
256 * 0,000016 = 0,004096сек
16 битный таймер
65 536 * 0,000016 = 1,048576

Чувствуете разницу между таймерами? Но обычно 16 битных таймеров у AVR мало, а то и вообще нет, зато у Stm32 с таймерами нет проблем. Не забываем, про шаг 0,000016сек, т.е. на 0,000016 секунде TCNT будет показывать 1, на 0,000017с тоже 1, на 0,000018с тоже 1 и только на 0,000032с TCNT изменится на 2. Про этот момент все начинающие почему то постоянно забывают.

Только, что мы говорили об регистре TCNT, в котором тики увеличиваются, так что же нам сидеть и постоянно проверять чему равно текущее количиство тиков?
if(TCNT == 15) {…}

Нет, так никто не делает, у таймера есть удобная вещь, специально заточенная для этого. Называется регистр сравнения. Обычно именуется OCR у атмела и CR(compare register) у стм. Суть его такая, пишем в него нужное число и количество текущих тиков автоматически сравнивается с регистром OCR. Что произойдет, когда OCR станет равным TCNT? Обычно таймер настраивается на прерывание, т.е. в этот момент произойдет прерывание.

Грубо говоря вот этот кусок кода, где мы включаем ножку PB1

void main(void)
{
while (1)
      {
          if(TCNT == 0x15) 
          {
              PORTB.1=1;
          }
      }
}

Будет делать тоже самое что и этот

interrupt [TIM2_COMP] void timer2_comp_isr(void)
{
    PORTB.1=1;
}
 
void main(void)
{
OCR2=0x15;
 
while (1)
      {
      }
}

Но так как сделано в первом куске потребует очень большого количества ресурсов. Мы должны постоянно проверять, сколько «времени» на таймере, если мы отвлечемся на что то другое, то можем пропустить момент, когда на таймере стало больше 0x15. Во втором случае этого не случится, потому что проверка производится автоматически, не зависимо от того, какой код сейчас выполняется. В момент совпадения, основной код внутри while прекратит выполнение и выполнится то, что внутри фигурных скобок interrupt [TIM2_COMP] void timer2_comp_isr(void).

Обратите внимание, что выше приведенные куски это псевдокод, т.е. показаны самые основные вещи. Любой таймер нужно настраивать!!! Выглядеть это должно как то так:

// Timer/Counter 2 initialization
// Clock source: System Clock
// Clock value: 7,813 kHz
// Mode: CTC top=OCR2A
// OC2 output: Disconnected
// Timer Period: 2,816 ms
ASSR=0<<AS2; //асинхронный режим не используем
TCCR2=
//режимы ШИМ не используются
(0<<PWM2) | (0<<COM21) | (0<<COM20) |    
//максимальный делитель, режим прерывания по совпадению
(1<<CTC2) | (1<<CS22) | (1<<CS21) | (1<<CS20); 
//обнуляем счетный регистр
TCNT2=0x00; 
OCR2=0x15;  
 
// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=(1<<OCIE2)|  //включаем прерывание от второго таймера
//выключаем прерывания от остальных таймеров
(0<<TOIE2) | (0<<TICIE1) | (0<<OCIE1A) | (0<<OCIE1B) | (0<<TOIE1) | (0<<TOIE0); 
 
// Global enable interrupts
#asm("sei")  //разрешаем прерывания глобально

Перейдем к поставленной изначально проблеме. У нас есть симистор, момент прохождения через ноль, мы можем отловить с помощью внешнего прерывания — специальная ножка INT0. Сразу уточню, delay использовать внутри прерываний, это нехорошо и неправильно, в данном примере он используется, исключительно для того, чтобы не захламлять код настройками второго таймера. В реальном проекте используйте таймер и прерывания.

// External Interrupt 0 service routine
interrupt [EXT_INT0] void ext_int0_isr(void)
{
    //ждем 5мс или 50% времени при 50Гц
    PORTD.7 = 0;
    delay_ms(5);
 
    //открывающий импульс
    PORTD.7 = 1;
    delay_us(100);
    PORTD.7 = 0;
}

Во первых, если использовать delay_us, то нельзя использовать переменные, поэтому регулировать микросекунды не получится. Кроме того, те 5мс, которые мы просто ждем, микроконтроллер мог бы использовать во благо и выполнять другие задачи. Поэтому код должен выглядеть как то так:

// это внешнее прерывание оно произойдет тогда, когда напряжение пройдет через ноль
interrupt [EXT_INT0] void ext_int0_isr(void)
{
    TCNT2=0x00;  //обнуляем таймер 
    TIMSK=(1<<OCIE2);  //включаем таймер
    PORTD.7 = 0; //выключаем ножку
}
 
// это прерывание по совпадению, оно произойдет тогда, когда таймер дотикает до регистра совпадения OCR2
interrupt [TIM2_COMP] void timer2_comp_isr(void)
{
    //подаем импульс на симистор
    PORTD.7 = 1;
    delay_us(200);
    PORTD.7 = 0;
 
    TIMSK=(0<<OCIE2); //выключаем таймер, он включится снова по внешнему прерыванию 
}

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

Подобные задачи, например «как в зависимости от длительности нажатия кнопки, делать разные действия?»

if(PINB.0 == 0) //смотрим момент когда кнопка нажата  
{
    TCNT2=0x00;  //обнуляем таймер 
    TIMSK=(1<<OCIE2);  //включаем таймер
}
 
interrupt [TIM2_COMP] void timer2_comp_isr(void)
{   
    //через равные промежутки времени проверяем состояние кнопки 
    if(PINB.0 == 0) //если все еще нажата, увеличиваем временную переменную
    {
        temp_value++;
    }
    else //если кнопка отжата, смотрим сколько накапало во временную переменную
    {
        if((temp_value > 0) && (temp_value < 9)) //если мало, то выполняем один код
        { 
            PORTB.5 = 1;
        }
        if(temp_value > 10) //если много то другой
        {
            PORTB.6 = 1;
        }
        TIMSK=(0<<OCIE2); //выключаем таймер
    } 
}

Т.е. внутри прерывания мы «сканируем» состояние кнопки. Надеюсь стало более понятно.

Ну и напоследок собрал такую схемку в протеусе.
triak_proteus

Забабахал такой исходник. Если менять OCR2, то будет меняться фаза открытия симистора, при этом основной цикл совершенно свободен, для любых Ваших действий.

#include <mega8.h>
#include <delay.h>
 
// Timer2 output compare interrupt service routine
interrupt [TIM2_COMP] void timer2_comp_isr(void)
{
   //подаем импульс на симистор
    PORTD.7 = 1;
    delay_us(500);
    PORTD.7 = 0;         
    TCCR2 = 0;
}
// External Interrupt 0 service routine
interrupt [EXT_INT0] void ext_int0_isr(void)
{
    TCNT2=0x00;  //обнуляем таймер 
    TCCR2=(1<<CS22) | (1<<CS21) | (0<<CS20);  //включаем таймер
    PORTD.7 = 0; //выключаем ножку
}
void main(void)
{
// Port D initialization
// Function: Bit7=In Bit6=In Bit5=In Bit4=In Bit3=In Bit2=In Bit1=In Bit0=In 
DDRD=(1<<DDD7) | (0<<DDD6) | (0<<DDD5) | (0<<DDD4) | (0<<DDD3) | (0<<DDD2) | (0<<DDD1) | (0<<DDD0);
// State: Bit7=T Bit6=T Bit5=T Bit4=T Bit3=T Bit2=T Bit1=T Bit0=T 
PORTD=(0<<PORTD7) | (0<<PORTD6) | (0<<PORTD5) | (0<<PORTD4) | (0<<PORTD3) | (0<<PORTD2) | (0<<PORTD1) | (0<<PORTD0);
 
// External Interrupt(s) initialization
// INT0: On
// INT0 Mode: Any change
// INT1: Off
GICR|=(0<<INT1) | (1<<INT0);
MCUCR=(0<<ISC11) | (0<<ISC10) | (0<<ISC01) | (1<<ISC00);
GIFR=(0<<INTF1) | (1<<INTF0);
 
// Timer/Counter 2 initialization
// Clock source: System Clock
// Clock value: 31,250 kHz
// Mode: CTC top=OCR2A
// OC2 output: Disconnected
// Timer Period: 4,992 ms
ASSR=0<<AS2;
TCCR2=(0<<PWM2) | (0<<COM21) | (0<<COM20) | (1<<CTC2) | (1<<CS22) | (1<<CS21) | (0<<CS20);
TCNT2=0x00;
OCR2=0x9B;
 
// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=(1<<OCIE2) | (0<<TOIE2) | (0<<TICIE1) | (0<<OCIE1A) | (0<<OCIE1B) | (0<<TOIE1) | (0<<TOIE0);
 
// Global enable interrupts
#asm("sei")
 
while (1)
      {
      }
}

Получившиеся осциллограммы вполне похожи на ожидаемое.
triak_proteus2

Надеюсь вопросов, про то как делать несколько действий сразу, станет меньше.

62 комментария: Управление симистором. Еще раз про таймеры.

  • добрый день
    А не могли бы вы выложить исходники в CVAVR и схему в протеус.
    идея замечательная но я не заметил контроля нуля можете по этому поводу пару слов

  • 1) OCR2=0x9B; это частота обработки прерывания interrupt [TIM2_COMP] void timer2_comp_isr(void)?
    2)функция delay_us(500) это время простоя процессора

  • плиз продолжение вопроса 2) функция delay_us(500) это время простоя процессора если она указана в прерывании

  • да

  • 🙄 это ответ на два вопроса?

  • да)

  • А как там регулировать скважность симистора??
    пробовал менять OCR2 не помогло

  • в симуляторе оно коряво работает

  • можна управлят етого импулса на тиристором

  • Подскажите пожалуйста, как сделать в данном примере со старта симистор полностью открыт, по истечении к примеру 10 секунд закрывается и отключает нагрузку. Т.Е диммер наоборот. Заранее спасибо.

  • не вижу проблемы, отсчитали 10 сек и закрыли, все расчеты есть в статье

  • interrupt [TIM2_COMP] void timer2_comp_isr(void)
    {
    //подаем импульс на симистор
    if((up==0)&(OCR2=0xff)) // увеличивать пока не достигло максиума
    {
    OCR2+=1;} //увеличиваем заполнение
    if (OCR2==0xff){
    PORTD.7 = 1;
    delay_us(500);}

    if((down==0)&(pw!=0x00)){ // уменьшать пока не достигло миниума

    OCR2-=1;} //уменьшаем заполнение
    if (pw==0x00){
    PORTD.7 = 0;
    TCCR2 = 0; }
    Скажите, правильно ли так?

  • У вас все в кучу и шим и ctc. Читайте уроки по AVR с 1 по 5.

  • А как управлять плавным включением? Нужно изменять регистр OCR2?

  • Если мне нужно допустим половина мощности на выходе, в OCR2 записать 255/2 = 127? Осциллоскоп в протеусе показывает постоянно одно и то же значение, при изменении регистра OCR2.

  • Симистор управляется не ШИМом, почитайте статью выше

  • Хорошо. Почему OCR2=0x9B; выбран таким = 155? это частота обработки прерывания interrupt [TIM2_COMP] void timer2_comp_isr(void). Почему такое значение? Я немного не понимаю.

  • Я так понимаю, осцил у меня не верно показывает с вашим кодом? http://prntscr.com/944919

  • Так… У нас 8Мгц. 8000000 / 256prescaller = 31250 — выбрали предделитель. 1мс = 1000Гц, 10мс — 100Гц, 5мс — 50Гц. 100Гц (10мс)=100%, 50Гц (5мс)=50%. 1/31250 prescaller = 0.000032сек. Таймер байтовый = 255. OCR2=09b(155) = 5мс. Полная мощность 0xFF=9,9мс. Правильно? Меняя значение OCR2 в прерывании INT0 мы изменяем время подачи импульса длинной 500мкс, чем изменяем фазу открытия симистора и соответственно мощность. Правильно — ли я все понял?

  • вроде того

  • Доброго дня подскажите как сделать независимое управление 4 и более симисторов

  • также

  • я имел в виду на одном кристалле с одним таймером

  • Для особо ленивых, можно выложить проект в протеусе, если он конечно у вас сохранился.

  • Добрый день, Использую Атини 13. Взял ваш проект за основу. Скажите пожалуйста, у меня 1 таймер в распоряжении (8бит). В регистре сравнения я изменяю время отпирания симистора… Параллельно мне необходимо отсчитывать необходимые интервалы времени. Например, через 10 секунд после включения увеличивать каждые 200 мс содержимое регистра сравнения (таймер в сравнение уходит раз в 4 мс), если нет изменения OCR0A… И вот тут как-то не пойму, что нужно сделать.

  • почитайте http://avr-start.ru/?p=4500

  • Народ, кто с матаном знаком хорошо? Как разбить синусоиду на равные по площади криволинейные трапеции, получив при этом конкретные участки по времени? Так то метод работает отлично, но если просто разделить на равные промежутки времени, то получается нехилая такая погрешность по напряжению в разных точках. Статья хорошая, почерпнул для себя новую полезную инфу, спасибо! 🙂

  • Посчитай обратную функцию, т.е зависимость времени от напряжения.
    Прямая функция U(t): U(t)=U0*cos(w*t); U0=220*2^0,5; w=2*pi*F; F=50.
    Осталось приложить чуть усилий и посчитать t(U) 🙂 .

  • Здравствуйте! Как реализовать программу так, что бы вместо смещения управляющего импульса относительно полупериода, импульс просто расширялся как ШИМ от начала полуперода(а не в конце, как в симисторном управлении). Нужно реализовать управление IGBT транзисторами. Пробовал управлять функцией delay() внутри таймера-не выходит.

  • если вы спрашиваете про программную часть, то чудес не бывает, задетектил 0 — включил, подождал — выключил

  • С детектированием 0 проблем нет, через инт0 ловим переход, в прерывании инт0 включаем ногу МК и запускаем таймер, далее заставляем таймер сколько-то ждать и в прерывании отключить ногу, только как заставить его ждать пока не понял. Или лучше управлять ногой целиком в таймере минуя инт0?

  • Тут все зависит от вашей фантазии, но по логике вещей лучше delay не юзать в прерывании. поэтому сработал int0 включили ногу, запустили таймер, таймер считает, срабатывает прерывание по совпадению или переполнению, отключаете ногу, выключаете таймер.

  • Добрый день! Выбрал предделитель 31250.При OCR2=0xFF У меня задержка 8,192 мс и напряжение около 40 вольт на выходе. Чтобы получить 10 мс нужно изменить частоту кварца или предднелитель (7813)? Я делал на ATtiny2313, тактовая 4 MHz
    // Timer/Counter 0 initialization
    // Clock source: System Clock
    // Clock value: 15,625 kHz
    // Mode: Normal top=0xFF
    // OC0A output: Disconnected
    // OC0B output: Disconnected
    // Timer Period: 16,384 ms
    Что посоветуете? Спасибо!

  • Здравствуйте, можно ли управлять симистором от АЦП? Объясните немного сам принцип, как и что…,есть ли примеры.

  • http://avr-start.ru/?p=4500 проблема 10 ваш случай

  • Приветствую — а разве нельзя всё это делать внешними прерываниями по входу ( по фронту)
    один сделать по возрастающему, другой по спадающему?

  • а можно наверное — вообще по прерыванию по входу там же по обоим фронтам изменяется

  • не понятно о чем вы в принципе 🙂 в схеме и используется внешнее прерывание.

  • да я имел ввиду без сравнеия, просто отслеживание перехода нуля…….
    а вообще есть желание на тиньку13 это посадить . но там 1 внешнее прерывание — то бишь только или по возрастающему фронту или по спаду…..

  • похоже что вы не внимательно прочитали статью, для отлавливанию нуля достаточно одной ножки внешнего прерывания.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Последние комментарии
  • Загрузка...
Счетчик
Яндекс.Метрика