Как я делал себе АВР для генератора

в 18:53, , рубрики: Atmega, diy или сделай сам, программирование микроконтроллеров

Как я делал себе АВР для генератора - 1

Несколько лет назад делал себе АВР (автоматический ввод резерва) для работы на даче от генератора. Сейчас многие ИТ-шники переходят на удалёнку, работают с дач, где качество электропитания может оставлять лучшего. Поэтому решил написать о своем опыте самодельного АВР на микроконтроллере ATmega8A. Если тема интересна, добро пожаловать под кат, будет много букв и кода.

О заземлении

Прежде чем что-либо делать с электричеством, нужно позаботиться о наличии хорошего заземления в вашем доме. Просто так взять и подключить обычный бытовой бензиновый/дизельный/газовый генератор к электросети дома не получится. Нужно соблюдать меры предосторожности. Первая из них – ваш генератор должен быть хорошо заземлен. Тогда у вас есть хорошие шансы не получить удар током, когда статика от вашего любимого свитера пробъёт изоляцию обмотки генератора. Вообще, к работающему генератору не стоит без нужды прикасаться.

Стоит помнить, что в сети не всегда 220В. Коммутация на линиях, грозовые разряды вдалеке, статические разряды дают такие наводки, что в сети нередки короткие импульсы в несколько киловольт. С этим борются установкой разрядников и УЗИП на вводе в дом, но это очень редкая практика в РФ. Так что пусть искра в землю уходит, и не через вас – сделайте по всему дому хорошее заземление. Без этого делать что-либо дальше просто нельзя!

О генераторах

К слову, у многих бытовых бензиновых генераторов обмотки никак не соединены с землёй. И это вполне нормально, когда вы питаете от генератора один электроинструмент. Но когда вам надо подключить генератор к дому, нужно сделать нулевой провод (N) и провод фазы (L). Для этого один из выводов генератора заземляется и из этой точки заземления уже независимо нужно вести в дом два провода – один будет нейтралью N, а второй – защитным заземлением (PE). При выборе генератора нужно обратить внимание, можно ли заземлять его выход, порой это запрещено в инструкции к генератору, тогда такой генератор вам не подойдёт.

Часто в Сети можно увидеть схемы подключения генератора без заземления и разделения линий N и PE. Не делайте так, дольше проживёте. Такие схемы хорошо работают до первого неудачного стечения обстоятельств. В типичных блоках питания современных электронных приборов стоят конденсаторы с линий L, N на землю. Если N не заземлить у генератора, то за счёт этих конденсаторов на линии N будет, если повезёт, 110 вольт относительно земли. Кстати, многие газовые котлы в таком режиме вообще перестают работать. Про влияние статики без присутствия заземления я уже писал выше.

О схемах АВР

Есть несколько разных схем реализации АВР. Дальше я буду писать о наиболее безопасной с моей точки зрения схеме однофазного АВР. Я не советую экономно делать АВР на одном контакторе или же с коммутацией только одного фазного провода. Только вместе с нейтралью.

Как я делал себе АВР для генератора - 2

На приведенной схеме питание от сети и от генератора подаётся через вводы 1 и 2. Они защищены спаренными автоматами. Через дополнительные автоматы питаются схемы коммутации и индикации. Видно, что катушки реле взаимно блокируются электрически. За включение того или иного ввода отвечает для упрощения не показанный на схеме микроконтроллер, который замыкает цепи в точке коммутации ТК1 или ТК2.

Принципиальным моментом является наличие в АВР 2х схем блокировок – взаимной механической блокировки коммутирующих вводы контакторов и взаимной электрической блокировки контакторов. Самодельщики ради экономии, бывает, в своих конструкциях пренебрегают этими блокировками, а зря. Схема без блокировок может проработать некоторое время, но в какой-то момент контакты пригорят, возвратные пружины ослабнут и случится КЗ между вводами. Во-первых, это грозит большим бабахом, если обе линии окажутся под напряжением, но это не самая большая проблема. Гораздо важнее, что ваш генератор неожиданно для ремонтирующих проводку электриков может выдать в общую сеть напряжение – при неблагоприятном стечении обстоятельств ремонтирующие линию электрики могут погибнуть. Для вас это уже уголовная статья.

О контакторах

Таким образом, использование обычных реле для нас отпадает, подойдут только специализированные контакторы. Для больших мощностей есть ещё вариант с моторизованными приводами, но это дорого и для типичного домашнего применения избыточно.

Чтобы сделать механическую блокировку, нужно выбрать контакторы, которые могут работать в паре. Обычно взаимная блокировка достигается установкой одинаковых контакторов рядом друг с другом и установкой дополнительной опции – механического блокиратора. Он продаётся отдельно от контакторов и стоит копейки.

Взаимная электрическая блокировка возможна, если на контакторе есть дополнительные сигнальные контакты, работающие на размыкание. Иногда они сразу встроены в контактор, иногда их можно докупить и установить как опцию.

Ведущие производители контакторов имеют в своих линейках такое оборудование. Так что найти и купить комплект не представляет особого труда. Правда цены на брендовые контакторы на порядок выше наших/китайских. Поскольку количество циклов коммутации не ожидается большим, то выбор китайских контакторов вполне оправдан. К недостаткам можно отнести только то, что катушки контактора во время работы довольно сильно гудят.

Еще по поводу коммутируемой мощности. Контакты контактора должны выдерживать максимальную мощность, которую вам разрешено потреблять в доме. У меня это 10 кВт, поэтому контакторы я выбирал на допустимый ток через один контакт примерно в 50 ампер. Стоит отметить, что по какой-то причине коммутируемая мощность для типичного трехфазного контактора указывается в паспорте суммарная для всех трёх фаз, поэтому надо внимательно смотреть, какой допустимый ток именно через один контакт.

О схеме управления

Когда я занимался созданием АВР у меня было несколько особых требований к его работе:

  • У меня не так часто отключают электричество, поэтому я решил, что мне не нужен автозапуск генератора, а вот от автоматической остановки генератора я решил не отказываться: когда сеть восстанавливается, генератор сам затихает и сразу понятно, что теперь с питанием всё хорошо, да и бензин экономится
  • После старта генератора ему надо дать время прогреться и только после прогрева давать ему нагрузку. Т.е. мне нужен был таймер включения АВР после подачи напряжения от генератора
  • После восстановления напряжения в сети часто происходили повторные отключения через короткий промежуток времени, поэтому мне нужен был таймер, который бы выждал перед переходом с генератора на сеть некоторое время и не глушил сразу генератор
  • Генератору, говорят, полезно перед выключением немного поработать без нагрузки. И для этого мне тоже нужен был таймер

Таким образом вырисовывалась картина, что мне нужен контроллер с несколькими таймерами. В те времена я увлекался кодингом на AVR, поэтому решил сделать такой контроллер на Atmega 8a.

Хорошо бы, чтоб контроллер работал долго и надёжно. Кроме того, чтобы сделать полную гальваническую развязку и снабдить контроллер сторожевым таймером я ничего более не придумал. Ну и сделать схему и программу максимально простыми. Поскольку делалось всё для себя, то все настройки и калибровки решил оставить в коде — весь UI свелся к одному светодиоду )

Основная задача контроллера – мониторить напряжение на вводах и, при необходимости, переключать вводы. При этом приоритетным является ввод от деревенской сети.

Тут стоит отметить, что качество сети таково, что колебания от 150 в до 250 в вполне обычное явление. Поэтому понятие что есть хорошее питание от сети очень размыто. Через какое-то время я решил эту проблему, когда поставил на весь дом один мощный тиристорный стаблизатор напряжения на 11 кВт. Но, важно, стабилизатор можно ставить только до АВР, а не после! Включать стабилизатор для генератора категорически не рекомендуется. Есть опасность, что при определенной комбинации нагрузок, особенно всяких мощных насосов, система из генератора и стабилизатора станет неустойчивой и войдет в автоколебания.

После некоторых раздумий нарисовал такую схему в Eagle.

Как я делал себе АВР для генератора - 3

В схеме есть два идентичных трансформаторных источника питания, при наличии напряжения на любом из вводов схема обеспечена питанием. Между вводами возможно напряжение в 600в, поэтому изоляция трансформаторов должна быть хорошей. Питание берется после пакетников QF3 и QF4 соответственно.

У каждого источника есть резистивный делитель напряжения, защищенный от перенапряжения стабилитроном – с него производится путём нехитрых расчётов измерение напряжение сети с помощью АЦП микроконтроллера.

Для коммутации катушек контакторов применяется стандартная схема из даташита для управления семисторами. 2 штуки ). Катушки — это индуктивная нагрузка, поэтому цепи снаббера на выходе из резистора и конденсатора обязательны.

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

Из особенностей еще в качестве генератора опорного напряжения использован TL431. В остальном всё включено стандартно для Atmega 8. Есть светодиоды для индикации наличия напряжения питания на вводах и один светодиод статуса устройства. Тактируется схема с помощью внешнего кварца на 16 МГц.

Eagle мне породил вот такую печатную плату. Никаких SMD, симисторы и стабилизатор с легкими радиаторами.

Как я делал себе АВР для генератора - 4

Два тороидальных трансформатора установлены прямо на плате. Плату изготовил традиционным радиолюбительским способом с помощью фоторезиста. После монтажа покрыл тремя слоями акрилового лака. Надеюсь не пробьет его высокое напряжение.

О программе управления

Код программы довольно длинный, извините.

Код программы

/*
 * ABP - программа управления блоком "Автоматического ввода резерва"
 * В блоке управления есть два ввода напряжения от сети и генератора и три выхода-
 * один выход для управления контактором включения сети, второй - контактором
 * включения генератора и третий - реле запуска стартера генератора.
 * В блоке управления есть выход RS232 для отладочной информации, порт SPI для 
 * программирования микроконтроллера и 3 светодиода. Два зеленых светодиода 
 * показывают наличие напряжения питания на входах сети и генератора. Красный 
 * светодиод показывает состояние контроллера количеством вспышек.
 *
 * для нормальной печати напряжений нужно линковать большие библиотеки printf
 * дополнительные опции в линкере -Wl,-u,vfprintf -lprintf_flt
 */
 #ifndef F_CPU
 #  define F_CPU 16000000UL
 #endif
#define BAUD 9600

#include <stdio.h>
#include <stdlib.h>
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <avr/sleep.h>

// переменные для сохранения состояния контроллера после запуска
// используются только для отладки
uint8_t mcusr_mirror __attribute__ ((section (".noinit")));

void get_mcusr(void) 
__attribute__((naked)) 
__attribute__((section(".init3")));
void get_mcusr(void)
{
   mcusr_mirror = MCUSR;
   MCUSR = 0;
   wdt_disable();
}

//настройка UART для отладочной печати в порт RS232
void uart_init( void )
{
/* //настройка скорости обмена
   UBRRH = 0;
   UBRRL = 103; //9600 при кварце 16 МГц */
    #include <util/setbaud.h>
    UBRRH = UBRRH_VALUE;
    UBRRL = UBRRL_VALUE;   
    #if USE_2X
      UCSRA |= (1 << U2X);
    #else
      UCSRA &= ~(1 << U2X);
   #endif
   //8 бит данных, 1 стоп бит, без контроля четности
   UCSRC = ( 1 << URSEL ) | ( 1 << UCSZ1 ) | ( 1 << UCSZ0 );
   //разрешить прием и передачу данных
   UCSRB = ( 1 << TXEN ) | ( 1 <<RXEN );
}

int uart_putc(  char c, FILE *file )
{
   //ждем окончания передачи предыдущего байта
   while( ( UCSRA & ( 1 << UDRE ) ) == 0 );
   UDR = c;
   wdt_reset();
   return 0;
}

FILE uart_stream = FDEV_SETUP_STREAM( uart_putc, NULL, _FDEV_SETUP_WRITE );

// настройка счетчика 1 для счета секунд - главный таймер в программе
void timer1_init( void )
{
   TCCR1A = 0; // регистр настройки таймера 1 - ничего интересного
   /* 16000000 / 1024 = 15625 Гц, режим СТС со сбросом 15625 должен давать прерывания раз в 1 сек */
   // режим CTC, ICP1 interrupt sense (falling)(not used) + prescale /1024 + без подавления шума (not used) 
   TCCR1B = (0 << WGM13) | (1 << WGM12) | (0 << ICES1) | ((1 << CS12) | (0 << CS11) | (1 << CS10)) | (0 << ICNC1); 
   OCR1A = 15625;  
   
   // прерывание 
   TIMSK |= (1 << OCIE1A);
}

// описание состояния контакторов
typedef enum _ABP_RLY_STATES {
   RLY_OFF = 0, // контактор выключен
   RLY_ON // контактор включен
} ABP_RLY_STATES;

// перечень используемых в блоке реле
typedef enum _ABP_RLY {
   RLY_220N = 0,
   RLY_220G,
   RLY_GEN
} ABP_RLY;

volatile ABP_RLY_STATES contactors[RLY_GEN+1]; // расчетные состояния контакторов

// описание состяния софтовых таймеров
typedef enum _ABP_TMR_STATES {
   TMR_OFF = 0, // таймер выключен
   TMR_ON // таймер включен
} ABP_TMR_STATES;

// структура описывающая софтовый таймер
typedef struct {
   ABP_TMR_STATES state; // состояние включения таймера
   unsigned char passed_secs; // сколько секунд прошло (ограничение до 255!!!)
   unsigned char set_secs; // установка срабатывания таймера в секундах
} TMR_INSTANCE;

// перечень используемых софтовых таймеров
typedef enum _ABP_TMRS {
   TMR_220N_ON = 0,  // задержка включения контактора после появления 220 от сети
   TMR_220G_ON, // задержка включения контактора после появления 220 от генератора
   TMR_220N_OFF, // задержка выключения контактора после пропадания 220 в сети
   TMR_220G_OFF, // задержка выключения контактора после пропадания 220 от генератора 
   TMR_PRINT, // задержка отладочной печати в последовательный порт
   TMR_GEN_OFF // задержка выключения реле стартера генератора после появления 220 от сети
} ABP_TMRS;

volatile TMR_INSTANCE abp_timers[TMR_GEN_OFF+1]; // таймеры по перечню ABP_TMRS

void abp_timers_init( void ) {
   // время срабатывания таймеров в секундах
   abp_timers[TMR_220N_ON].set_secs = 10; // ожидание после включения сетевого напряжения
   abp_timers[TMR_220G_ON].set_secs = 60; // ожидание для переключения на генератор для прогрева генератора
   abp_timers[TMR_220N_OFF].set_secs = 5; // ожидание после пропадания сетевого напряжения
   abp_timers[TMR_220G_OFF].set_secs = 5; // ожидание после пропадания напряжения генератора
   abp_timers[TMR_GEN_OFF].set_secs = 60; // ожидание для охлаждения генератора перед остановом
   abp_timers[TMR_PRINT].set_secs = 2; // задержка печати
   
   unsigned char i;
   for(i=TMR_220N_ON; i<=TMR_GEN_OFF; i++ ) {
      abp_timers[i].state = TMR_OFF;
      abp_timers[i].passed_secs = 0;
   }
   for(i=RLY_220N; i<=RLY_GEN; i++ ) {
      contactors[i] = RLY_OFF;
   }
}

// запуск таймера 
void abp_timer_start( ABP_TMRS tmr ) {
   abp_timers[tmr].passed_secs = 0;
   abp_timers[tmr].state = TMR_ON;    
}

// остановка таймера 
void abp_timer_stop( ABP_TMRS tmr ) {
   abp_timers[tmr].state = TMR_OFF;
   abp_timers[tmr].passed_secs = 0;
}

// проверка срабатывания таймера
unsigned char abp_timer_check( ABP_TMRS tmr ) {
   if (abp_timers[tmr].passed_secs >= abp_timers[tmr].set_secs) {
      return 1; 
   } else {
      return 0;
   }
}

// прерывание для подсчета секунд в таймерах
ISR(TIMER1_COMPA_vect)
{
   // сюда надо добавлять переменные счетчиков таймеров включения/выключения
   unsigned char i;
   for(i=TMR_220N_ON; i<=TMR_GEN_OFF; i++ ) {
      if (abp_timers[i].state) {
         abp_timers[i].passed_secs++;   
      }
   }
}


//настройка COUNTER2 для управления светодиодом через переменную led_State
void counter2_init( void )
{
   ASSR = 0;  /* AS0 = 0 */  /* disable asynchronous mode */
   while (ASSR); /*EMPTY*/

   OCR2 = 223;             /* 70 Гц на выходе */

   TCCR2 |= (1 << CS22) | (1 << CS21) | (1 << CS20);  /* prescale /1024 */
   
   TCCR2 |= (1 << WGM21);                /* mode CTC */
   TCCR2 &= ~(1 << WGM20);
   
   TCCR2 &= ~(1 << COM21);              /* не выводить на OC2 */
   TCCR2 &= ~(1 << COM20);
   
   TIMSK |= (1 << OCIE2);          /* enable compare interrupt */
}

typedef enum _ABP_LED_STATES {
   ABP_UNDEF = 0, // режим неопределен
   ABP_1RELAY, // включено 1 реле
   ABP_2RELAY  // включено 2 реле
} ABP_LED_STATES;
ABP_LED_STATES led_State = ABP_UNDEF;

const unsigned char led_pattern[3][10] =
{ { 1,0,1,0,1,0,1,0,1,0 }, // статус не определен
  { 1,0,0,0,0,0,0,0,0,0 }, // включено 1 реле
  { 1,0,1,0,0,0,0,0,0,0 } }; // включено 2 реле

volatile unsigned char timer2_count = 0;         
volatile unsigned char led_cycle = 0; // от 0 до 9

ISR(TIMER2_COMP_vect)  // должно вызываться примерно 70 раз в секунду
{
   if (++timer2_count > 6)       // типа примерно через 0.1 сек. нужно сменить режим светодиода
   {
      timer2_count = 0;           // сбрасываем счетчик
      if (led_pattern[led_State][led_cycle]) {
         PORTB &= ~(1 << PB0); // включаем        
      } else {
         PORTB |=  (1 << PB0); // выключаем
      }
      if (++led_cycle >= 10)
         led_cycle = 0;
   }  
}

// количество семплов для усреднения значения датчиков напряжения
#define SAMPLES 2500
// используемое опорное напряжение TL431
#define REFERENCEV 2.479
// экспериментальные коэффициенты пересчета для делителей напряжения
#define DIVIDER1 (12.3/2.13)
#define DIVIDER2 (12.4/2.03)

double realV1 = 0; // здесь итоговое зхначение измерения V0
double realV2 = 0; // здесь итоговое зхначение измерения V1

volatile int sampleCount = 0;
volatile unsigned long tempVoltage1 = 0; // переменные для накопления суммы
volatile unsigned long tempVoltage2 = 0;
volatile unsigned long sumVoltage1 = 0; // переменные для передачи суммы семплов в основной цикл 
volatile unsigned long sumVoltage2 = 0;

void ADC_init() // ADC1,0
{
   // внешний ИОН 2,5В, 10 bit преобразование
   ADMUX = (0 << REFS0) | (0 << REFS1) | (0 << ADLAR) |
   (0 << MUX3) | (0 << MUX2) | (0 << MUX1) | (0 << MUX0); // ADC0
   // включить, free running, с прерываниями
   ADCSRA = (1 << ADEN) | (1 << ADFR) | (1 << ADIE) |
   (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // делитель 128
   
   ADCSRA |= (1 << ADSC);             // Start ADC Conversion
}

ISR(ADC_vect) // должен накапливать измерения по 2500 семплам по каждому каналу
{
   if ((ADMUX & (1 << MUX0))) { // если работаем с ADC1
      if (sampleCount++) // пропускаем первое измерение
         tempVoltage1 += ADC;
      if (sampleCount >= SAMPLES) {
         sampleCount = 0;
         sumVoltage1 = tempVoltage1;
         tempVoltage2 = 0;
         tempVoltage1 = 0;
         ADMUX &= ~(1 << MUX0); // переключаем на ADC0
      }        
   } else { // если работаем с ADC0
      if (sampleCount++) // пропускаем первое измерение
         tempVoltage2 += ADC;
      if (sampleCount >= SAMPLES) {
         sampleCount = 0;
         sumVoltage2 = tempVoltage2;
         tempVoltage2 = 0;
         tempVoltage1 = 0;
         ADMUX |= (1 << MUX0); // переключаем на ADC1
      }  
   }
   ADCSRA |=(1 << ADIF);              // Acknowledge the ADC Interrupt Flag
}

// валидность напряжения на входах блока АВР
typedef enum _ABP_U_STATES {
   U_INVALID = 0, // напряжение не в норме
   U_VALID // напряжение в норме
} ABP_U_STATES;

ABP_U_STATES u220n, u220g; // расчетная валидность напряжения на входах

// допустимый диапазон напряжений питания в В на выходе выпрямителей
// напряжения меняются не только от изменения сетевого напряжения, но и 
// плавают под нагрузкой (реле стартера), поэтому диапазон широкий
#define MAX_V 14.0
#define MIN_V 7.5

void ports_init() {
   // настройка порта светодиода индикации
   PORTB &= ~(1 << PB0);
   DDRB  |=  (1 << PB0); // output
   PORTB &= ~(1 << PB0); // включаем
   
   // настройка порта Контактора 1
   PORTD |=  (1 << PD4);
   DDRD  |=  (1 << PD4); // output
   PORTD |=  (1 << PD4); // высокий уровень - выключаем
   // настройка порта Контактора 2
   PORTD |=  (1 << PD3);
   DDRD  |=  (1 << PD3); // output
   PORTD |=  (1 << PD3); // высокий уровень - выключаем
   // настройка порта Реле запуска генератора
   PORTD &= ~(1 << PD2);
   DDRD  |=  (1 << PD2); // output
   PORTD &= ~(1 << PD2); // низкий уровень - выключаем
}

void validate220() {
   // логика валидации напряжения питания сети и генератора
   realV1 = DIVIDER1 * ((sumVoltage1 * REFERENCEV) / 1024) / SAMPLES;
   realV2 = DIVIDER2 * ((sumVoltage2 * REFERENCEV) / 1024) / SAMPLES;
   
   if( realV1 > MAX_V || realV1 < MIN_V ) { // проверка напряжения от генератора
      u220g = U_INVALID;
      } else {
      u220g = U_VALID;
   }
   if( realV2 > MAX_V || realV2 < MIN_V ) { // проверка напряжения от сети
      u220n = U_INVALID;
      } else {
      u220n = U_VALID;
   }
}

void validate_contactors() {
      // проверка валидности включения контактора сети 220 в
      // на выходе RLY_220N = RLY_ON или RLY_OFF
      if( u220n == U_VALID && contactors[RLY_220N] == RLY_OFF
      && abp_timers[TMR_220N_ON].state == TMR_OFF) { // есть напряжение, реле пока выкл, и таймер не вкл
         abp_timer_stop( TMR_220N_OFF ); // остановка таймера выключения 220
         abp_timer_start( TMR_220N_ON ); // запуск таймера включения 220
      }
      if( u220n == U_VALID && contactors[RLY_220N] == RLY_OFF
      && abp_timers[TMR_220N_ON].state == TMR_ON && abp_timer_check(TMR_220N_ON)) { // есть напряжение, реле пока выкл, таймер сработал
         abp_timer_stop( TMR_220N_OFF ); // остановка таймера выключения 220
         abp_timer_stop( TMR_220N_ON ); // остановка таймера включения 220
         contactors[RLY_220N] = RLY_ON; // ставим флаг включения 220
      }
      if( u220n == U_VALID && contactors[RLY_220N] == RLY_ON
      && abp_timers[TMR_220N_OFF].state == TMR_ON) { // есть напряжение, реле вкл, и таймер выкл включен
         abp_timer_stop( TMR_220N_OFF ); // остановка таймера выключения 220
      }
      if( u220n == U_INVALID && contactors[RLY_220N] == RLY_ON
      && abp_timers[TMR_220N_OFF].state == TMR_OFF) { // нет напряжение, реле пока выкл, и таймер не вкл
         abp_timer_stop( TMR_220N_ON ); // остановка таймера включения 220
         abp_timer_start( TMR_220N_OFF ); // запуск таймера выключения 220
      }
      if( u220n == U_INVALID && contactors[RLY_220N] == RLY_ON
      && abp_timers[TMR_220N_OFF].state == TMR_ON && abp_timer_check(TMR_220N_OFF)) { // нет напряжения, реле пока вкл, таймер сработал
         abp_timer_stop( TMR_220N_OFF ); // остановка таймера выключения 220
         abp_timer_stop( TMR_220N_ON ); // остановка таймера включения 220
         contactors[RLY_220N] = RLY_OFF; // ставим флаг выключения 220
      }
      if( u220n == U_INVALID && contactors[RLY_220N] == RLY_OFF
      && abp_timers[TMR_220N_ON].state == TMR_ON) { // нет напряжения, реле выкл, и таймер вкл включен
         abp_timer_stop( TMR_220N_ON ); // остановка таймера включения 220
      }
      
      // проверка валидности включения контактора генератора
      // на выходе RLY_220G = RLY_ON или RLY_OFF
      if( u220g == U_VALID && contactors[RLY_220G] == RLY_OFF
      && abp_timers[TMR_220G_ON].state == TMR_OFF) { // есть напряжение, реле пока выкл, и таймер не вкл
         abp_timer_stop( TMR_220G_OFF ); // остановка таймера выключения ген
         abp_timer_start( TMR_220G_ON ); // запуск таймера включения ген
      }
      if( u220g == U_VALID && contactors[RLY_220G] == RLY_OFF
      && abp_timers[TMR_220G_ON].state == TMR_ON && abp_timer_check(TMR_220G_ON)) { // есть напряжение, реле пока выкл, таймер сработал
         abp_timer_stop( TMR_220G_OFF ); // остановка таймера выключения ген
         abp_timer_stop( TMR_220G_ON ); // остановка таймера включения ген
         contactors[RLY_220G] = RLY_ON; // ставим флаг включения ген
      }
      if( u220g == U_VALID && contactors[RLY_220G] == RLY_ON
      && abp_timers[TMR_220G_OFF].state == TMR_ON) { // есть напряжение, реле вкл, и таймер выкл включен
         abp_timer_stop( TMR_220G_OFF ); // остановка таймера выключения ген
      }
      if( u220g == U_INVALID && contactors[RLY_220G] == RLY_ON
      && abp_timers[TMR_220G_OFF].state == TMR_OFF) { // нет напряжение, реле пока выкл, и таймер не вкл
         abp_timer_stop( TMR_220G_ON ); // остановка таймера включения ген
         abp_timer_start( TMR_220G_OFF ); // запуск таймера выключения ген
      }
      if( u220g == U_INVALID && contactors[RLY_220G] == RLY_ON
      && abp_timers[TMR_220G_OFF].state == TMR_ON && abp_timer_check(TMR_220G_OFF)) { // нет напряжения, реле пока вкл, таймер сработал
         abp_timer_stop( TMR_220G_OFF ); // остановка таймера выключения ген
         abp_timer_stop( TMR_220G_ON ); // остановка таймера включения ген
         contactors[RLY_220G] = RLY_OFF; // ставим флаг выключения ген
      }
      if( u220g == U_INVALID && contactors[RLY_220G] == RLY_OFF
      && abp_timers[TMR_220G_ON].state == TMR_ON) { // нет напряжения, реле выкл, и таймер вкл включен
         abp_timer_stop( TMR_220G_ON ); // остановка таймера включения ген
      }
      
      // запуск и останов генератора с таймером
      if( contactors[RLY_220N] == RLY_OFF && contactors[RLY_GEN] == RLY_OFF) { // нет сети, стартуем ген
         abp_timer_stop( TMR_GEN_OFF ); // остановка таймера останова генератора
         contactors[RLY_GEN] = RLY_ON; // ставим флаг запуска генератора
      }
      if( contactors[RLY_220N] == RLY_ON && abp_timers[TMR_GEN_OFF].state == TMR_OFF 
      && u220g == U_VALID) { // есть 220 в сети и есть от генератора
         abp_timer_start( TMR_GEN_OFF ); // запуск таймера останова генератора
      }
      if( contactors[RLY_220N] == RLY_ON && abp_timers[TMR_GEN_OFF].state == TMR_OFF
      && u220g == U_INVALID) { // есть 220 в сети и нет от генератора
         abp_timer_stop( TMR_GEN_OFF ); // остановка таймера останова генератора
         contactors[RLY_GEN] = RLY_OFF; // ставим флаг останова генератора
      }
      if( contactors[RLY_220N] == RLY_ON && abp_timers[TMR_GEN_OFF].state == TMR_ON
      &&  abp_timer_check(TMR_GEN_OFF) ) { // есть 220 в сети и истекло время таймера
         abp_timer_stop( TMR_GEN_OFF ); // остановка таймера останова генератора
         contactors[RLY_GEN] = RLY_OFF; // ставим флаг останова генератора
      }
}

void switch_contactors() {
   // логика переключения контакторов
   if( contactors[RLY_220N] == RLY_ON ) {
      PORTD |=  (1 << PD4); // высокий уровень - выключаем контактор 1 ген
       _delay_ms(50); //даем возможность отключиться контактору генератора  
      PORTD &= ~(1 << PD3); // низкий уровень - включаем контактор 2 220
      led_State = ABP_2RELAY;
   } else {
      if( contactors[RLY_220G] == RLY_ON ) {
         PORTD |=  (1 << PD3); // высокий уровень - выключаем контактор 2 220
         PORTD &= ~(1 << PD4); // низкий уровень - включаем контактор 1 ген
         led_State = ABP_1RELAY;
      } else {
         PORTD |=  (1 << PD3); // высокий уровень - выключаем контактор 2 220
         PORTD |=  (1 << PD4); // высокий уровень - выключаем контактор 1 ген
         led_State = ABP_UNDEF;
      }
   }
   if( contactors[RLY_GEN] != RLY_ON ) { // v2 реле работает на время работы генератора - не дает напряжения на стоппер
      PORTD &= ~(1 << PD2); // низкий уровень - выключаем реле - возврат напряги на стоппер - стоп гены
   } else {
      PORTD |= (1 << PD2); // высокий уровень - включаем реле - держим режим работы гены
   }
}


int main(void)
{
   ports_init(); // настройка портов светодиода, реле и контакторов
   
   uart_init(); //настройка uart
   stdout =  &uart_stream;
   
   counter2_init(); // настройка таймера мигания светодиода
   ADC_init(); // настройка АЦП измерения 220
   abp_timers_init(); // настройка таймеров задержек включения и выключения
   timer1_init(); // настройка секундного таймера на аппаратном счетчике 1

    set_sleep_mode(SLEEP_MODE_IDLE);  // разрешаем сон в режиме IDLE
    sleep_enable();

    wdt_enable(WDTO_2S); // Сторожевой таймер настроен на таймаут в 2 секунды

   sei(); // запускаем работу прерываний
   
   _delay_ms(1000); // ждем первых результатов ЦАП, отличных от 0
   wdt_reset();
   printf( "Start flag after reset = %urn", mcusr_mirror );
   abp_timer_start(TMR_PRINT);
   
    while(1)
    {
      wdt_reset(); // сбрасываем сторожевой таймер
      
      validate220(); // проверка качества 220 от сети и генератора
   
      validate_contactors(); // валидация возможности включения контакторов с таймерами
      
      switch_contactors(); // переключение контакторов по схеме
      
      // отладочная печать раз в 2 секунды
      if (abp_timer_check(TMR_PRINT)) {
         printf( "V220 = %4.2f VG = %4.2frn", realV2, realV1 );
         printf( "valid = %u %u rn", u220n, u220g );
         printf( "rly = %u %u %urn", contactors[RLY_220N], contactors[RLY_220G], contactors[RLY_GEN] );
         abp_timer_start(TMR_PRINT);
      }
      
        sleep_cpu(); // заснуть до следующего пррывания по таймерам
    }
}

Программа разработана с помощью бесплатного AVR Studio и использует стандартные библиотеки AVR.

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

Для контроля зависаний предусмотрен сторожевой таймер.

Все циклы измерений сделаны на прерываниях и с использованием аппаратных таймеров. Счетчик секунд сделан на таймере 1. По прерыванию таймера 1 обновляются программные таймеры, отвечающие за задержки включения и отключения контакторов и реле генератора.
Второй таймер используется для создания эффекта мигания светодиода статуса. Предусмотрено три паттерна мигания. Значения из паттерна мигания берутся в прерывании таймера 2. По миганию можно судить о состоянии контроллера.

Два АЦП также работают по таймерам и усредняют по 2500 сэмплов измерений напряжения. Для перевода измерений в реальные вольты предусмотрены калибровочные константы. Их значения надо исправить в ходе настройки АВР.

Кроме того, есть еще ряд констант, которые нужно определить в ходе наладки.

abp_timers[TMR_220N_ON].set_secs = 10; // ожидание после включения сетевого напряжения
abp_timers[TMR_220G_ON].set_secs = 60; // ожидание для переключения на генератор для прогрева генератора
abp_timers[TMR_220N_OFF].set_secs = 5; // ожидание после пропадания сетевого напряжения
abp_timers[TMR_220G_OFF].set_secs = 5; // ожидание после пропадания напряжения генератора
abp_timers[TMR_GEN_OFF].set_secs = 60; // ожидание для охлаждения генератора перед остановом

Реле останова генератора при работе от генератора держится включенным, блокируя поступление напряжения на цепь останова генератора. После завершения работы таймера работы генератора на холостом ходу, реле выключается и на цепь останова генератора через это реле начинает поступать ток. На самом генераторе стоит специальный блок, который после появления напряжения с некоторой задержкой замыкает цепь зажигания на массу, что приводит к останову генератора. Этот же блок содержит цепь подзаряда аккумулятора генератора. Если кому интересны детали, напишите в комментах, я сделаю отдельный пост об этом блоке. В нём нет кода, всё аппаратно.

Если кто-то надумает повторить АВР, то стоит подкорректировать значения настроек. Готовую прошивку не публикую, так как программу всё равно надо править в ходе настройки АВР.

Надо сказать, что мой АВР работает уже 4 года без проблем, так что схема можно считать проверенная как и код.

Автор: lesha108

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js