Генерация синуса 50 Гц на AVR

КдПВ
Я недавно в форуме чисто теоретически поднимал вопрос о бесперебойнике с чистым синусом.

А тут еще стабилизатор сгорел, от которого котел газовый запитан был, да с такими спецэффектами сгорел, что до сих пор запах горелой изоляции в кухне. Слава Богу, УЗО отработало, проводка выдержала, в общем, обошлось.

Так что теория постепенно начала превращаться в практику, но пока (на уровне идеи) с использованием железного трансформатора от сгоревшего UPS, коих у меня накопилось уже много.

UPD: Добавил пару картинок

Вообще-то идей по поводу бесперебойника у меня много, начиная с непрерывного преобразования 12->220 и заканчивая синхронизацией с сетью и переключением байпасса на симисторах при переходе через ноль. Но первым этапом будет генерация синуса.

Использовать будем Timer1 микроконтроллера ATMega168. Подойдет и ATTiny2313, у него такой же таймер1.

Имеющийся у меня трансформатор (как и большинство дешевых UPS) имеет отвод от средней точки низковольтной обмотки. Поэтому нужно сделать генерацию раздельных полуволн с двух выходов сравнения таймера.

Кроме того, для синхронизации с сетью нужно предусмотреть подстройку частоты, для чего максимальное значение таймера будем задавать в регистре ICR1. На первом этапе это будет #define. Когда дойдет дело до синхронизации с сетью, изменение этого значения в соответствии с импульсами синхронизации позволит создать некое подобие ФАПЧ, синхронизирующей периоды сети и UPS, чтобы избежать импульсов при переключении.

Для обеспечения диапазона подстройки частоты вниз, максимальное значение заполнения ШИМ не доходит до значения регистра ICR1. Запас я взял 10%, хотя, думаю, можно его и уменьшить.

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

Таблицу синуса я использовал для простоты, но, пользуясь информацией из интернета, можно генерировать вполне приемлемый синус на лету. Я проверил, результат получается очень близкий. Или подготовить таблицу при старте контроллера. К сожалению, для достижения необходимой точности этот метод требует умножения чисел с несколькими цифрами после запятой или каких-то трюков, поэтому я пока от него отказался.

Таблица содержит только четверть периода синуса, остальное повторяется в разном порядке, ну, вы поняли.
Количество импульсов ШИМ на период синуса фиксированное, оно расчитывается из исходных данных в таблице.

/*
 * PWM_Mega168.c
 *
 * Created: 08.03.2015 19:03:07
 *  Author: antonluba
 */ 

#include <avr/io.h>
#include <avr/interrupt.h>

#define PULSE_PER_QUADRANT	88
#define TOP_	456

const uint16_t sinus[PULSE_PER_QUADRANT] =
{
0	,
7	,
15	,
22	,
29	,
36	,
44	,
51	,
58	,
65	,
73	,
80	,
87	,
94	,
101	,
108	,
115	,
122	,
129	,
136	,
143	,
150	,
157	,
163	,
170	,
177	,
183	,
190	,
196	,
202	,
209	,
215	,
221	,
227	,
233	,
239	,
245	,
251	,
257	,
262	,
268	,
273	,
279	,
284	,
289	,
294	,
299	,
304	,
309	,
314	,
319	,
323	,
327	,
332	,
336	,
340	,
344	,
348	,
352	,
355	,
359	,
362	,
366	,
369	,
372	,
375	,
378	,
381	,
383	,
386	,
388	,
390	,
393	,
395	,
396	,
398	,
400	,
401	,
403	,
404	,
405	,
406	,
407	,
407	,
408	,
409	,
409	,
409

};

uint8_t quadrant;
uint8_t pulse_counter;
uint8_t sin_index;


ISR(TIMER1_OVF_vect)
{
	switch (quadrant)
	{
	case 1:
		OCR1A = sinus[pulse_counter];		
		pulse_counter++;
		if (pulse_counter==PULSE_PER_QUADRANT-1)
			{
				quadrant = 2;
			} 
		break;
	case 2:
		OCR1A = sinus[pulse_counter];
		pulse_counter--;
		if (pulse_counter == 0)
		{
			quadrant = 3;
			OCR1A = 0;
		}
		break;
	case 3:
		OCR1B = sinus[pulse_counter];		
		pulse_counter++;
		if (pulse_counter==PULSE_PER_QUADRANT-1)
		{
			quadrant = 4;
		}
		break;
	case 4:
		OCR1B = sinus[pulse_counter];
		pulse_counter--;
		if (pulse_counter== 0)
		{
			quadrant = 1;
			OCR1B = 0;
		}
		break;
	}	
}

int main(void)
{	
	pulse_counter = 0;
	quadrant = 1;
	
	// ports init
	PORTB = 0;
	DDRB = (1 << PB1)|(1 << PB2);
	
	// timer init
	TCCR1A = (1 << COM1A1)|(0 << COM1A0)|(1<< COM1B1)|(0 << COM1B0)|(0 << WGM11)|(0 << WGM10);
	TCCR1B = (1 << WGM13)|(0 << WGM12)|(0 << CS12)|(0 << CS11)|(1 << CS10);
	ICR1 = TOP_;
	TIMSK1 = (1 << TOIE1);
	
	OCR1A = 0;
	OCR1B = 0;
	
	//global init
	sei();
	
    while(1)
    {
        //TODO:: Please write your application code 
    }
}


Схема в протеусе выглядит так:



Сигнал на осциллографе протеуса:



Параметры трансформатора, конечно, с потолка, только для получения формы сигнала на выходе. Работает и просто с RC-цепочками на выходах МК.

Вот еще картинки:





Здесь синусоида отстает немного, так как фильтрацию сделал на индуктивностях.

Модель для протеуса цепляю к статье.

Продолжение здесь

Комментарии (22)

RSS свернуть / развернуть
Подобного рода осциллограммы в Proteus проще и лучше делать в «Graph Mode», для смешанных сигналов лучше использовать Mixed graph. Кроме увеличения скорости анализа, можно будет легко масштабировать, использовать курсоры. Осциллограф в Proteus работает медленно, да и кривовато.
0
  • avatar
  • kvm
  • 09 марта 2015, 23:35
Я бы порекомендовал использовать, что нибудь на ARM (например STM32), в которых возможности таймеров значительно расширены.
0
Синус-то из таблицы можно один раз вычислять в начале обработчика, и значение положить во временную переменную. И оттуда брать, тогда не надо будет 4 раза индексы считать.
0
При условии, что оперативной памяти достаточно
0
Индекс вообще считается автоматически, один раз за прерывание, а в зависимости от четверти меняется направление счета и выход, на который подается сигнал.
0
Про автоматически это интересно, конечно)) Но Вы листинг все же взгляните что там в начале каждого case. Не начинается ли каждый case с высчисление адреса по индексу массива? Возможно, узнаете много нового про компилятор. Удачи!
0
Автоматически имел в виду в прерывании, один раз. На каждом следующем прерывании новое значение. Что вы предлагаете, не понимаю?
0
Что ж там не понять? Откройте листинг с ассемблерными инструкциями. Посмотрите начало каждого case. Что там делается? Не вычисляется ли в КАЖДЫЙ раз по индексу адрес загружаемого данного? Видимо, вычисляется)) Это штук 7 ASM-инстукций, если не больше. От этого можно избавиться, если при входе в обработчик ОДИН раз взять из памяти значение и НЕ ВЫЧИСЛЯТЬ его адреса в каждом case.
0
В смысле, делать инкремент и декремент указателя, а не индекса?
0
Каждый раз при вызове прерывание отрабатывает одно из ветвлений case, и будет происходить одно обращение к таблице один раз на прерывание. Конечно, можно вынести чтение из массива перед case, но в данном случае ничего не изменится. Если вы переживаете за размер кода – оптимизатор сам вполне справляется с подобными вещами.

Меня больше смущает, что переменные
uint8_t quadrant;
uint8_t pulse_counter;


не объявлены как volatile (потенциально оптимизатор может поломать код)
0
Меня больше смущает, что переменные
uint8_t quadrant;
uint8_t pulse_counter;

не объявлены как volatile (потенциально оптимизатор может поломать код)
Спасибо за подсказку, когда буду отлаживать, проверю
0
Спасибо за подсказку, когда буду отлаживать, проверю

Я бы рекомендовал в подобных случаях всегда явно делать запрет оптимизации (через volatile или барьер компиляции), не надеясь на отладку. Дело в том, что оптимизатор – вещь тонкая, в таком виде (предположим) он оставит инициализацию этих переменных в коде, потом вы немного код перепишите – он эту инициализацию уберет. Или Вы перейдете на другую версию AVRGCC, в которой оптимизатор будет более «умным»…
+1
Ну, ИАР не делает одно обращение никогда. Он в лучшем случае (при макс. оптимизации) в начале каждого case сделает call на общую подпрограмму извлечения слова из массива. А это — лишние такты внутри обработчика. Некультурно))
0
переменная
const uint16_t sinus[PULSE_PER_QUADRANT]

будет храниться в памяти RAM.
Нужно отчетливо понимать, что мы имеем дело с гарвардской архитектурой. Это значит, что область ОЗУ и область программ имеют свою адресацию. Поскольку компилятор не обладает искусственным интеллектом, ему требуется подсказать, что читать нужно из другой области памяти.
нужно использовать функции чтения из памяти pgmspace.h
а вначале ставить макрос PROGMEM.
Здесь на этом же сайте уже писалось об этом. Почитайте.
0
переменная
const uint16_t sinus[PULSE_PER_QUADRANT]

будет храниться в памяти RAM

Да, это я знаю, спасибо. Но для проверки идеи, тем более на модели, не стал усложнять.
0
по схеме: Конечно, одно из самых больших преимуществ данной схемы — простота, однако есть серьезный недостаток: Транзисторы будут сильно греться, причем не в прямом, а в обратном режиме при протекании тока через защитный диод. Так как на диодах есть сравнительное большое падение напряжения, выделяемая мощность будет произведением тока и этого напряжения. Решайте сами.
0
Транзисторы будут сильно греться
Поясню: это только иллюстрация идеи, не дальше чем моделирование.

В прототипе будут другой контроллер, другие драйверы, другие транзисторы, другие резисторы, защитные диоды, обратные диоды в затворе, блокировочные конденсаторы, накопительные конденсаторы, предохранители, другой трансформатор, провода потолще :-)))…
0
значит соответственно и другая программа. А что мы обсуждаем тогда вообще? Сама идея синуса на МК тоже не нова.
-1
значит соответственно и другая программа
Программа будет дополняться другими функциями: синхронизация с сетью, раннее определение отключения сети, синхронное переключение на симисторах, стабилизация напряжения на выходе, контроль параметров аккумулятора, индикация режимов.

Для меня новое в этой программе — возможность подстройки частоты в определенных пределах без изменения количества отсчетов за период. К слову, не все таймеры на AVR имеют такую возможность, например, ATTiny85, который я сначала хотел использовать, не имеет регистра ICR.
0
А честный синус сейчас мало где нужен вообще.
Например, если БП сделан по схеме Диодный мост->конденсатор->ШИМ, то ему меандр (е ащё лучше — постоянка) — самое то.
Чистый синус нужен для повышения КПД низкочастотных трансформаторов и двигателей. Однако, низкочастотные трансформаторы это странно, а двигатели надо пускать через частотники, а там
Диодный мост->конденсатор->ШИМ
0
Двигатели много где есть. В котлах часто трансформаторы используются. И при питании от UPS c «модифицированной синусоидой» котел у меня противно зудел.
0
для движков (подозреваю, трёхфазных) советую MC3PHAC.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.