//----------------------------------------------------------------------------
//модуль реализации управления тиристорами
//----------------------------------------------------------------------------
//Модуль использует таймер 1. С помощью петли PLL, реализованной
//программно (software PLL или SPLL), работа таймера синхронизируется
//по частоте и фазе с сетевым напряжением.
//Период таймера равен длительности периода сетевого напряжения.
//Прескалер таймера должен быть запрограммирован так, чтобы разрядности
//таймера с учетом прескалера было достаточно для формирования такого
//интервала.
//Для синхронизации используются импульсы, формируемые детектором перехода
//через ноль (Zero Crossing Detector, ZCD) в начале каждого периода.
//Импульсы подаются на вход захвата ICP. Используется спад импульса,
//так как ZCD обычно имеет выход с открытым коллектором, и спад имеет
//меньшую длительность, чем фронт. Хотя можно использовать и фронт
//импульсов, для чего в регистре TCCR1B нужно установить бит ICES1.
//Схема захвата имеет аппаратный подавитель шумов, который включен
//с помощью бита ICNC1.
//В прерывании по захвату вычисляется текущая ошибка фазы. Она является
//входной величиной для PI-регулятора, который служит петлевым фильтром
//SPLL. Выходной сигнал PI-регулятора представляет собой текущий период
//таймера 1, который загружается в регистр OCR1A. Таймер работает в режиме
//очистки по сравнению (CTC), причем TOP = OCR1A.
//Значение регистра OCR1A является фактически длительностью периода
//сетевого напряжения, выраженной в тиках таймера. Это значение можно
//использовать для измерения частоты сети.
//SPLL позволяет получить постоянный сдвиг относительно сигнала ZCD, который
//задается константой TRIAC_SHIFT в пределах от минус периода
//(опережение) до плюс периода (запаздывание). Сдвиг задается в
//микросекундах, а не в градусах, поэтому он не зависит от сетевой частоты.
//Сдвиг позволяет скомпенсировать конечную длительность импульса ZCD, задержку
//срабатывания ZCD, а в случае необходимости - реализовать опережение
//открывания триака и одновременно защититься от паразитного открывания
//на следующем полупериоде.
//Для управления триаком служит канал B аппаратного PWM, значение которого
//загружается в регистр OCR1B, а выходная последовательность формируется на
//выводе OC1B. К этому выводу должна быть подключена схема управления
//триаком. Считается, что открытому состоянию триака соответствует
//высокий уровень на выводе OC1B, поэтому используется инверсный режим PWM.
//Поскольку данный канал PWM использует тот же таймер 1,
//который с помощью SPLL синхронизирован с сетью, выход PWM тоже будет
//синхронен сети. Загрузка регистра OCR1B должна осуществляться как
//при изменении желаемого угла открывания триака (функцией Triac_SetAngle),
//так и при подстройке периода таймера (в прерывании по захвату).
//Регистры OCR1A и OCR1B имеют double buffering, поэтому их перезагрузка
//не нарушает текущий цикл управления триаком.
//Преимуществом такого способа управления триаком является то, что оно
//реализуется аппаратно, чем достигается минимальная загрузка процессора и
//отсутствие проблем с временем выполнения обработчиков прерываний при
//очень больших и очень малых углах. Недостатком способа является то, что
//ток управления триака включен в течение всего времени проводимости.
//Хотя при управлении индуктивными нагрузками это сказывается положительно,
//так как в этом случае момент перехода тока через ноль запаздывает
//относительно импульсов ZCD, и применение длинных импульсов управления
//является обязательным, по крайней мере, на малых углах.
//Для контроля захвата SPLL при необходимости может быть использовано
//значение текущей ошибки фазы Spll_X. Критерием захвата является значение
//ошибки, меньше заданного, сохраняющееся в течение заданного времени.
//----------------------------------------------------------------------------
#include "Main.h"
#include "Triac.h"
#include "Display.h"
//----------------------------- Константы: -----------------------------------
#define F_MAINS_MIN 45 //минимальная частота сети, Hz
#define F_MAINS_NOM 50 //номинальная частота сети, Hz
#define F_MAINS_MAX 55 //максимальная частота сети, Hz
#define SPLL_PI_KP 50 //пропорциональный коэффициент PI-регулятора SPLL
#define SPLL_PI_KI 10 //интегральный коэффициент PI-регулятора SPLL
#define TRIAC_ERROR 200 //максимальная ошибка в состоянии захвата, us
#define LOCK_N 10 //число периодов для детектирования захвата
#define MAX_ANGLE 36000L //диапазон задания угла открывания триака
#define PRE 8 //значение прескалера таймера
#define ESIG 0xBABE //сигнатура EEPROM
#define TRIAC_SHIFT 150 //сдвиг сигнала управления триаком, us
#define TRIAC_ANGLE 3000 //сдвиг угла сигнала управления триаком
#define PERIOD_MIN ((int)(F_CLK * 1E6 / F_MAINS_MAX / PRE))
#define PERIOD_NOM ((int)(F_CLK * 1E6 / F_MAINS_NOM / PRE))
#define PERIOD_MAX ((int)(F_CLK * 1E6 / F_MAINS_MIN / PRE))
#define ERROR (F_CLK * TRIAC_ERROR / PRE)
//Код текущей фазы:
enum { PH_AH1, PH_AH0,
PH_BH1, PH_BH0,
PH_CH1, PH_CH0,
PH_AL1, PH_AL0,
PH_BL1, PH_BL0,
PH_CL1, PH_CL0 };
#define PULSE_US 75 //длительность импульсов, us (< 3333 us)
#define PHASE_AH (0L * MAX_ANGLE / 6)
#define PHASE_BH (1L * MAX_ANGLE / 6)
#define PHASE_CH (2L * MAX_ANGLE / 6)
#define PHASE_AL (3L * MAX_ANGLE / 6)
#define PHASE_BL (4L * MAX_ANGLE / 6)
#define PHASE_CL (5L * MAX_ANGLE / 6)
#define PULSE (F_CLK * PULSE_US / PRE)
//----------------------------- Переменные: ----------------------------------
static int Period; //текущая длительность периода
static unsigned int Period1; //загруженная длительность периода
static int Angle; //текущий угол
static int Spll_X; //текущая ошибка петли SPLL
static char Lock_Counter; //счетчик захвата петли SPLL
static bool Spll_Lock; //текущий статус петли SPLL
static char Phase; //текущая фаза
static int dT; //временной сдвиг (установленный, мкс)
static int dTt; //временной сдвиг (в тиках таймера)
static int dF; //фазовый сдвиг
static bool Cycle; //флаг начала цикла PLL
__no_init __eeprom int ESig; //сигнатура EEPROM
__no_init __eeprom int EdT; //временной сдвиг в EEPROM
__no_init __eeprom int EdF; //фазовый сдвиг в EEPROM
//-------------------------- Прототипы функций: ------------------------------
#pragma vector = TIMER1_CAPT_vect
__interrupt void Timer_Cap(void); //прерывание по захвату
#pragma vector = TIMER1_COMPB_vect
__interrupt void Timer_Comp(void); //прерывание по совпадению
#pragma vector = TIMER1_COMPA_vect
__interrupt void Timer_Ovf(void); //прерывание по совпадению
bool Get_Cycle(void); //чтение флага начала цикла
//------------------------- Инициализация модуля: ----------------------------
void Triac_Init(void)
{
//TCCR1A = (1<<WGM11) | (1<<WGM10);
TCCR1A = 0;
//CTC, CK/64 (PRE =
:
TCCR1B = (1 << ICNC1) | (1 << WGM12) | (1 << CS11);
//очистка отложенных прерываний:
TIFR = (1 << ICF1) | (1 << OCF1B) | (1 << OCF1A);
//разрешение прерывания по захвату и совпадению:
TIMSK |= (1 << TICIE1) | (1 << OCIE1B) | (1 << OCIE1A);
//загрузка начального значения периода:
Period = PERIOD_NOM;
OCR1A = Period;
Period1 = Period;
//нет захвата:
Lock_Counter = 0;
Spll_Lock = 0;
//чтение EEPROM:
if(ESig == ESIG)
{
dT = EdT;
dF = EdF;
}
else
{
dT = TRIAC_SHIFT;
EdT = dT;
dF = TRIAC_ANGLE;
EdF = dF;
ESig = ESIG;
}
dTt = ((long)(F_CLK * 1000L)) * dT / ((long)(PRE * 1000L));
//установка начального угла открывания триака:
Angle = MAX_ANGLE / 2;
Phase = PH_AH1;
OCR1B = (long)Period1 * (Angle + dF + PHASE_AH) / MAX_ANGLE;
if(OCR1B > Period1) OCR1B = OCR1B - Period1 - 1;
Cycle = 0;
}
//-------------------- Установка угла открывания триака: ---------------------
//Таблица перевода среднего значения тока 0..250 в угол 0.00..120.00°:
//Для угла f нормализованное среднее значение тока равно интегралу
//от f до pi от sin(x)dx, деленному 2.
//Или Pnorm = (1 + cos(f))/2.
const __flash int PowerTable[251] =
{
#include "Table.inc"
};
//i = 0..25000
//angle = 0..12000
void Triac_SetI(int i)
{
if(i <= MAX_I)
{
//находим индекс первого элемента таблицы:
char Index1 = i / 100;
//находим индекс второго элемента таблицы:
char Index2 = (Index1 < 250)? (Index1 + 1) : Index1;
//читаем первую точку таблицы:
int a1 = PowerTable[Index1];
//читаем вторую точку таблицы:
int a2 = PowerTable[Index2];
//вычисляем дробную часть процентов мощности:
int DeltaI = i % 100;
//линейная интерполяция по отрезку 1 A:
a1 = a1 + DeltaI * (a2 - a1) / 100;
//задаем угол:
//запрещаем прерывания для атомарности доступа к Angle:
char si = __save_interrupt();
__disable_interrupt();
Angle = MAX_ANGLE / 2 - a1;
//восстанавливаем состояние прерываний:
__restore_interrupt(si);
}
}
//---------------------- Чтение текущего угла, x0.01°: -----------------------
int Triac_GetAngle(void)
{
return(Angle - 6000);
}
//------------------------- Чтение состояния PLL: ----------------------------
bool Triac_GetPLL(void)
{
return(Spll_Lock);
}
//-------------------- Установка сдвига открытия триака: ---------------------
__monitor void Triac_SetT(int t)
{
dT = t;
dTt = ((long)(F_CLK * 1000L)) * dT / ((long)(PRE * 1000L));
if(EdT != dT) EdT = dT;
}
//----------------- Установка сдвига угла открытия триака: -------------------
__monitor void Triac_SetF(int f)
{
dF = f;
if(EdF != dF) EdF = dF;
}
//--------------------- Чтение сдвига открытия триака: -----------------------
int Triac_GetT(void)
{
return(dT);
}
//------------------- Чтение сдвига угла открытия триака: --------------------
int Triac_GetF(void)
{
return(dF);
}
//-------------------------- Обслуживание модуля: ----------------------------
bool Triac_Exe(bool t)
{
if(t)
{
static char Lt;
if(Lt) Lt--;
else
{
Lt = 50;
if(Lock_Counter) Lock_Counter--; //сброс Lock при отсутствии импульсов
else Spll_Lock = 0; //привязки (когда Capture не возникает)
}
Display_Led(LED_H, Spll_Lock); //управление светодиодом "Lock"
}
return(Get_Cycle());
}
//------------------------- Прерывание по захвату: ---------------------------
#pragma vector = TIMER1_CAPT_vect
__interrupt void Timer_Cap(void)
{
//предыдущее значение ошибки фазы:
static int Spll_Xp = 0;
//внутреннее представление воздействия (х256):
static long Spll_Y = PERIOD_NOM * 256L;
//вычисление текущей ошибки фазы:
Spll_X = ICR1 - dTt;
//сдвиг в диапазон ±Period / 2:
if(Spll_X > Period >> 1) Spll_X = Spll_X - Period - 1;
//учет пропорциональной составляющей:
Spll_Y = Spll_Y - (long)(Spll_Xp - Spll_X) * SPLL_PI_KP;
//учет интегральной составляющей:
Spll_Y = Spll_Y + (long)Spll_X * SPLL_PI_KI;
//ограничение результата:
if(Spll_Y > PERIOD_MAX * 256L) Spll_Y = PERIOD_MAX * 256L;
if(Spll_Y < PERIOD_MIN * 256L) Spll_Y = PERIOD_MIN * 256L;
//вычисление нового периода:
Period = Spll_Y >> 8;
//сохранение предыдущего значения ошибки фазы:
Spll_Xp = Spll_X;
//загрузка нового периода:
//OCR1A = Period;
//коррекция OCR1B:
//if(OCR1B > Period) OCR1B = OCR1B - Period - 1;
//проверка захвата:
if(Spll_X > -ERROR && Spll_X < ERROR)
{
if(Lock_Counter < LOCK_N) Lock_Counter++;
else Spll_Lock = 1;
}
else
{
if(Lock_Counter > 0) Lock_Counter--;
else Spll_Lock = 0;
}
}
//---------------------- Прерывание по совпадению B: -------------------------
#pragma vector = TIMER1_COMPB_vect
__interrupt void Timer_Comp(void)
{
int p;
bool n = !Spll_Lock || Angle == MAX_ANGLE / 2;
switch(Phase)
{
case PH_AH1:
if(!n)
{
Port_PH_AH_0;
Port_PH_CL_0;
}
Phase = PH_AH0;
p = OCR1B + (int)PULSE;
break;
case PH_AH0:
Port_PH_AH_1;
Port_PH_CL_1;
Phase = PH_BH1;
p = (long)Period1 * (Angle + dF + PHASE_BH) / MAX_ANGLE;
break;
case PH_BH1:
if(!n)
{
Port_PH_AH_0;
Port_PH_BH_0;
}
Phase = PH_BH0;
p = OCR1B + (int)PULSE;
break;
case PH_BH0:
Port_PH_AH_1;
Port_PH_BH_1;
Phase = PH_CH1;
p = (long)Period1 * (Angle + dF + PHASE_CH) / MAX_ANGLE;
break;
case PH_CH1:
if(!n)
{
Port_PH_CH_0;
Port_PH_BH_0;
}
Phase = PH_CH0;
p = OCR1B + (int)PULSE;
break;
case PH_CH0:
Port_PH_CH_1;
Port_PH_BH_1;
Phase = PH_AL1;
p = (long)Period1 * (Angle + dF + PHASE_AL) / MAX_ANGLE;
break;
case PH_AL1:
if(!n)
{
Port_PH_CH_0;
Port_PH_AL_0;
}
Phase = PH_AL0;
p = OCR1B + (int)PULSE;
break;
case PH_AL0:
Port_PH_CH_1;
Port_PH_AL_1;
Phase = PH_BL1;
p = (long)Period1 * (Angle + dF + PHASE_BL) / MAX_ANGLE;
break;
case PH_BL1:
if(!n)
{
Port_PH_BL_0;
Port_PH_AL_0;
}
Phase = PH_BL0;
p = OCR1B + (int)PULSE;
break;
case PH_BL0:
Port_PH_BL_1;
Port_PH_AL_1;
Phase = PH_CL1;
p = (long)Period1 * (Angle + dF + PHASE_CL) / MAX_ANGLE;
break;
case PH_CL1:
if(!n)
{
Port_PH_BL_0;
Port_PH_CL_0;
}
Phase = PH_CL0;
p = OCR1B + (int)PULSE;
break;
case PH_CL0:
Port_PH_BL_1;
Port_PH_CL_1;
Phase = PH_AH1;
p = (long)Period1 * (Angle + dF + PHASE_AH) / MAX_ANGLE;
break;
}
//if(p > Period1 / 2) { OCR1A = Period; Period1 = Period; }
if(p > Period1) p = p - Period1 - 1;
OCR1B = p;
}
//---------------------- Прерывание по совпадению A: -------------------------
#pragma vector = TIMER1_COMPA_vect
__interrupt void Timer_Ovf(void) //прерывание по совпадению
{
//Port_TEST_1;
OCR1A = Period;
Period1 = Period;
Cycle = 1;
//Port_TEST_0;
}
//---------------------- Чтение флага начала цикла: --------------------------
__monitor bool Get_Cycle(void)
{
bool cyc = Cycle;
Cycle = 0;
return(cyc);
}
//----------------------------------------------------------------------------