Управление углом отпирания симистора на AT89C2051

Постановка задачи

Микроконтроллер: AT89C2051
Язык: C
Требуется реализовать управление углом отпирания симистора.

Ограничения:

Предполагается сеть 50 Гц.


Идея решения

Главная идея: единственное аппаратное прерывание используется для детектора перехода через ноль.

Таймер при этом работает без собственного прерывания. Его флаг переполнения TF0 опрашивается из основного цикла программы.

Таким образом:


Расчёт дискретизации

Для сети 50 Гц:

T = 1 / 50 = 20 мс

Один полупериод:

T_half = 20 мс / 2 = 10 мс

Необходимо 100 шагов:

10 мс / 100 = 100 мкс

Следовательно, один шаг регулирования соответствует:

180° / 100 = 1.8°

То есть разрешение по углу составляет примерно 1,8°.


Алгоритм

переход сети через ноль
        ↓
       INT0
        ↓
сброс счётчика угла
        ↓
синхронизация Timer 0
        ↓
каждые 100 мкс выставляется TF0
        ↓
main() обнаруживает TF0
        ↓
увеличивает phase_counter
        ↓
phase_counter == заданному углу?
        ↓
формируется импульс на затвор симистора

Симистор после отпирания остаётся открыт до следующего перехода тока через ноль.


Пример реализации для Keil C51

Пример рассчитан на кварц 12 МГц.

Для классического 8051:

частота таймера = Fosc / 12

При кварце 12 МГц:

12 МГц / 12 = 1 МГц

Один тик таймера:

1 мкс

Для периода 100 мкс в режиме 8-битного autoreload:

256 - 100 = 156

Поэтому:

#define T0_RELOAD 156

Код

#include <REG51.H>

typedef unsigned char u8;

/*
 * P3.2 = INT0 = детектор перехода через ноль
 * P1.2 = управление оптосимистором
 */
sbit TRIAC = P1^2;

/*
 * Для Fosc = 12 MHz:
 *
 * timer clock = 12 MHz / 12 = 1 MHz
 * один тик = 1 us
 *
 * Нужно переполнение каждые 100 us:
 * 256 - 100 = 156
 */
#define T0_RELOAD 156

/*
 * phase:
 *
 *   0   -> открыть сразу после нуля
 *   50  -> открыть через 5 ms = 90 градусов
 *   99  -> открыть через 9.9 ms
 *   100 -> не открывать вообще
 */
volatile u8 phase = 50;

volatile u8 phase_counter = 0;
volatile u8 armed = 0;
volatile u8 gate_ticks = 0;


/*
 * Единственное прерывание программы.
 * INT0 — переход сети через ноль.
 */
void zero_cross_isr(void) interrupt 0
{
    /*
     * Снять предыдущий импульс управления.
     */
    TRIAC = 0;
    gate_ticks = 0;

    phase_counter = 0;

    /*
     * Синхронизируем сетку 100 us
     * с переходом через ноль.
     */
    TR0 = 0;

    TL0 = T0_RELOAD;
    TF0 = 0;

    TR0 = 1;

    if (phase == 0)
    {
        /*
         * Практически полная мощность:
         * открыть сразу после перехода через ноль.
         */
        TRIAC = 1;
        gate_ticks = 1;
        armed = 0;
    }
    else if (phase < 100)
    {
        armed = 1;
    }
    else
    {
        /*
         * phase == 100:
         * пропускаем весь полупериод.
         */
        armed = 0;
    }
}


void timer0_init(void)
{
    /*
     * Timer 0, Mode 2:
     * 8-bit auto reload.
     */
    TMOD &= 0xF0;
    TMOD |= 0x02;

    TH0 = T0_RELOAD;
    TL0 = T0_RELOAD;

    TF0 = 0;

    /*
     * Прерывание Timer 0 запрещено.
     */
    ET0 = 0;

    TR0 = 1;
}


void int0_init(void)
{
    /*
     * INT0 по спадающему фронту.
     *
     * Конкретная полярность зависит
     * от схемы детектора перехода через ноль.
     */
    IT0 = 1;

    EX0 = 1;
    EA  = 1;
}


void main(void)
{
    TRIAC = 0;

    timer0_init();
    int0_init();

    while (1)
    {
        /*
         * Никакого прерывания таймера нет.
         * Просто опрашиваем флаг TF0.
         */
        if (TF0)
        {
            TF0 = 0;

            /*
             * Завершение управляющего импульса.
             */
            if (gate_ticks)
            {
                gate_ticks--;

                if (gate_ticks == 0)
                {
                    TRIAC = 0;
                }
            }

            /*
             * Отсчёт угла отпирания.
             */
            if (armed)
            {
                phase_counter++;

                if (phase_counter >= phase)
                {
                    TRIAC = 1;

                    /*
                     * Длительность импульса:
                     * один шаг = 100 us.
                     */
                    gate_ticks = 1;

                    /*
                     * Второй раз за текущий
                     * полупериод не открываем.
                     */
                    armed = 0;
                }
            }
        }

        /*
         * Здесь может выполняться остальная логика.
         *
         * Важно: основной цикл должен проверять TF0
         * чаще, чем раз в 100 us.
         */
    }
}

Установка угла

Переменная:

phase

задаёт задержку отпирания в шагах по 100 мкс.

Например:

phase Задержка Угол
0 0 мс ~0°
25 2,5 мс 45°
50 5 мс 90°
75 7,5 мс 135°
99 9,9 мс ~178,2°
100 весь полупериод выключено

Функция задания условной мощности

Если хочется задавать значение от 0 до 100:

void triac_set_power(u8 power)
{
    if (power > 100)
    {
        power = 100;
    }

    phase = 100 - power;
}

Использование:

triac_set_power(100);
triac_set_power(75);
triac_set_power(50);
triac_set_power(25);
triac_set_power(0);

Но это будет линейное изменение угла отпирания, а не линейное изменение электрической мощности.


Зависимость мощности от угла

Для активной нагрузки относительная мощность приблизительно определяется выражением:

P(alpha) / Pmax =
1 - alpha/pi + sin(2*alpha)/(2*pi)

где alpha — угол отпирания в радианах.

Поэтому, например, изменение phase с 50 до 25 не означает строгое изменение мощности с 50% до 75%.

Если нужны именно 100 примерно линейных уровней мощности, можно заранее рассчитать таблицу:

power -> phase

и хранить её во Flash.


Почему ограничение соблюдено

Используется:

1 таймер:
    Timer 0

1 аппаратное прерывание:
    INT0 — переход через ноль

Прерывание Timer 0 не используется:

ET0 = 0;

Программа лишь опрашивает аппаратный флаг:

if (TF0)
{
    TF0 = 0;

    ...
}

Флаг переполнения таймера работает независимо от разрешения его прерывания.


Важные практические замечания

1. Длительность импульса затвора

В примере импульс длится 100 мкс.

На практике для конкретного симистора, оптосимистора и нагрузки может оказаться полезнее импульс порядка:

200...500 мкс

Например:

gate_ticks = 3;

даст примерно 300 мкс.


2. Оптосимистор должен быть без Zero Cross

Для фазового регулирования нужен оптосимистор типа random phase, например семейства:

MOC3020
MOC3021
MOC3022
MOC3023

Оптосимисторы со встроенным zero-cross для такой задачи не подходят, поскольку они сами задерживают включение до перехода напряжения через ноль.


3. Гальваническая развязка

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

Работа с сетевым напряжением опасна.


4. Время выполнения main()

Так как TF0 опрашивается программно, нельзя надолго блокировать основной цикл.

Если программа зависнет, например:

delay_ms(10);

то моменты отпирания будут пропущены.

Основной цикл должен быть неблокирующим и возвращаться к проверке TF0 существенно чаще, чем раз в 100 мкс.


Итог

Для задачи с AT89C2051 достаточно следующей архитектуры:

INT0:
    синхронизация с переходом через ноль
          |
          v
Timer 0:
    шаг 100 мкс
          |
          v
main():
    polling TF0
          |
          v
счётчик 0...99
          |
          v
импульс затвора симистора

Получаем:

JotBird Logo
Published with JotBird