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

Пример очень утрированный, но надеюсь он поможет понять общую суть. Пофантазируем и представим себе обычный водопроводный кран, который может крутиться влево или вправо, в зависимости от этого будет литься горячая и холодная вода. Когда кран посередине (в нуле) вода не течет. Что будет, если крутануть кран влево и вправо по синусоидальному закону?
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 комментария: Управление симистором. Еще раз про таймеры.

  • Я понимаю, что внешним прерыванием Вы отлавливаете переход через ноль, и отсчитываете таймером время после этого перехода — и если это время совпадает с регистром сравнения, то подаёте импульс открытия симистора — и сразу его закрываете, обнуляете таймер и выключаете его, и так далеее….
    а вот мне интересно как менять значение в регистре OCR — тупо присвоить переменную и менять эту переменную.? А получится?

  • что мешает вам напрямую писать в OCR?

  • Отличная статья -спасибо большое! Админ но вопрос такой у Вас показан оптрончик — как раз который не подойдёт для этой схемы 3041М (Zero-Cross Optoisolators Triac Driver Output)
    надо как у Вас в принципиальной указан 3021?

  • Подскажите пожалуйста как реализовать, так чтобы симистор открывался только на положительных полуволнах синусоиды +310 и отдельно, на отрицательных -310. Допустим в нужный момент времени открыть его и подать несколько отрицательных или положительных полуволн и закрыть.

  • P.S. хочу использовать для этой цели симистор AQH3223 для гальванической развязки. Подойдёт ли он для этого? В даташите написано
    «random» Вроде как без детектора нуля.

  • поставить детектор нуля, и через таймауты открывать симистор

  • admin Спасибо за статью, очень помогла, всё получилось.
    Сделал опрос логического уровня PD1 INT0 в прерывании и по нему управление

  • Основная образовательная идея статьи заслуживает только похвалы. За это — плюс. НО!!! За использование задержек в прерываниях надо руки отбивать… вместе с ушами.. (а потом как говорится у них спутники падают)… Чему учите подрастающее поколение? За это ОГРОМНЫЙ МИНУС.

  • простите, я больше так не буду :mrgreen:

  • Вариант решения — у таймера2 есть 2 регистра сравнения, используем их

    void SetTimer2 (void)
    {
    TCCR2A |= _BV(WGM21); //Normal port operation, OC2A disconnected, OC2B disconnected , M0de 2 :CTC, TOP=OCR1A
    TIMSK2 |= _BV (OCIE2A) | _BV (OCIE2B);
    }

    ISR (INT1_vect) //External Interrupt Request 0 - Takt - внешнее тактирование
    {
    // Запускаем таймер 2 на заданную задержку
    GTCCR |= _BV (PSRASY); //Сбрасываем прескалер таймера 2
    TCNT2 = 0;
    OCR2B = PhaseTimerCounter;
    OCR2A = PhaseTimerCounter+2;
    TCCR2B = 0x07; //Start Timer2 - clkT2s/1024 (From prescaler)
    };

    ISR (TIMER2_COMPA_vect) //Timer/Counter2, Output Compare A Match Interrupt
    {
    TCCR2B = 0x00; //Timer2 -Stop
    PORTD.7 = 0;
    };

    ISR (TIMER2_COMPB_vect) //Timer/Counter2, Output Compare B Match Interrupt
    {
    PORTD.7 = 1;
    };

  • Второй вариант еще проще (но не лучше) — просто по таймеру выставлять в 1 выход управления тиристором, а сбрасывать в прерывании по детекту нуля.

  • Отлично

  • Что-то я не совсем понял. Вы написали, что нужны Non-Zero Crossing. Однако приведенный оптрон MOC3041 как раз-таки имеет внутреннюю схему Zero Crossing.

  • Можно ли управлять симистором не при помощи кнопок, а потенциометром? То есть имеется 5 вольт, на вход АЦП подаём от 0 до 5, при этом меняется процент открытия симистора, например от 4-8 % до 94-95%. Как вывести пропорцию и вписать её в прерывание АЦП? Проблему 10 читал, но кодвижн к сожалению от простых русских слов не компилирует, я пробовал.

  • Управлять можно. Не компилировать может из-за 100500 причин, вопрос слишком абстрактный.

  • А вот такой вопрос: Если мы используем для управления симистором оптрон со встроенной детекцией нуля, управлять мощностью нагрузки мы не сможем, верно? А что мы, в таком случае, должны делать, чтобы просто постоянно держать симистор открытом н-нное кол-во. времени? Достаточно будет просто подать логическую единицу с выхода МК и через н-нное время подать логический ноль? Симистор при этом будет сам открываться с каждым переходом через ноль? То есть, если регулировка мощности не нужна, то и городить детектор нуля на входе МК тоже не нужно, я всё правильно понимаю?

  • А для чего таймер отключать? Не словлю суть.

  • если не выключать то будет срабатывать прерывание

  • А как сделать чтобы было циклическая мощность 1сек 30% 2сек 90%

  • Как сделать чтобы было циклическая мощность 1сек 20% потом 2сек 70%

  • Помогите написать программу для импульсного режима работы. Т.е. 0.1 сек 70% потом 0.5сек 10% и чтобы эти параметры настраивались.

  • Если не трудно, скинь программу в Atmel Studio 🙄

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

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

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