---
title: Управление углом отпирания симистора на AT89C2051
url: https://share.jotbird.com/breezy-vibrant-sunbeam
updated_at: 2026-09-30T20:51:05.931804+00:00
---

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

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

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

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

- дискретизация — **100 уровней на полупериод сети**;
- разрешено использовать только **один таймер**;
- разрешено использовать только **одно прерывание**;
- единственное прерывание — **переход сетевого напряжения через ноль**.

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

---

## Идея решения

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

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

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

- `INT0` — единственное прерывание;
- `Timer 0` — единственный используемый таймер;
- прерывание `Timer 0` запрещено;
- каждые 100 мкс программа проверяет `TF0`;
- после нужного количества шагов формируется импульс управления симистором.

---

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

Для сети 50 Гц:

```text
T = 1 / 50 = 20 мс
```

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

```text
T_half = 20 мс / 2 = 10 мс
```

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

```text
10 мс / 100 = 100 мкс
```

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

```text
180° / 100 = 1.8°
```

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

---

## Алгоритм

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

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

---

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

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

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

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

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

```text
12 МГц / 12 = 1 МГц
```

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

```text
1 мкс
```

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

```text
256 - 100 = 156
```

Поэтому:

```c
#define T0_RELOAD 156
```

---

## Код

```c
#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.
         */
    }
}
```

---

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

Переменная:

```c
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:

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

    phase = 100 - power;
}
```

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

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

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

---

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

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

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

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

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

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

```text
power -> phase
```

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

---

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

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

```text
1 таймер:
    Timer 0

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

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

```c
ET0 = 0;
```

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

```c
if (TF0)
{
    TF0 = 0;

    ...
}
```

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

---

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

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

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

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

```text
200...500 мкс
```

Например:

```c
gate_ticks = 3;
```

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

---

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

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

```text
MOC3020
MOC3021
MOC3022
MOC3023
```

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

---

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

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

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

---

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

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

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

```c
delay_ms(10);
```

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

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

---

## Итог

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

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

Получаем:

- один таймер;
- одно прерывание;
- 100 положений угла на полупериод;
- разрешение около 1,8°;
- простую реализацию без второго ISR.
