Управление углом отпирания симистора на AT89C2051 ¶
Постановка задачи ¶
Микроконтроллер: AT89C2051
Язык: C
Требуется реализовать управление углом отпирания симистора.
Ограничения:
- дискретизация — 100 уровней на полупериод сети;
- разрешено использовать только один таймер;
- разрешено использовать только одно прерывание;
- единственное прерывание — переход сетевого напряжения через ноль.
Предполагается сеть 50 Гц.
Идея решения ¶
Главная идея: единственное аппаратное прерывание используется для детектора перехода через ноль.
Таймер при этом работает без собственного прерывания. Его флаг переполнения TF0 опрашивается из основного цикла программы.
Таким образом:
INT0— единственное прерывание;Timer 0— единственный используемый таймер;- прерывание
Timer 0запрещено; - каждые 100 мкс программа проверяет
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
импульс затвора симистора
Получаем:
- один таймер;
- одно прерывание;
- 100 положений угла на полупериод;
- разрешение около 1,8°;
- простую реализацию без второго ISR.