Преобразователь частоты для 3х фазового асинхронного двигателя на Arduino UNO

step64
Offline
Зарегистрирован: 01.04.2013

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

andryn
Offline
Зарегистрирован: 08.06.2018

step64 пишет:

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

Да, был такой косячек. При остановке не останавливались ШИМ, поэтому при нажатии на кнопу для остановки могла оставться генерация на ключах из разных фаз. Сейчас это убрал. Под нагрузкой из мотора винчестера проверил, мотор крутится, но работает кривовато, больше 70Гц не раскручивается. Еще мне не нравится механизм плавного увеличения частоты по 1Гц, наверно переделаю.

Изменения такие:

@@ -314,6 +324,13 @@
             Serial.print("Button press");
             if ((run & B00000010)) { //Надо остановить
                 TIMSK2 &= ~(1<<TOIE2); //остановка таймера
+                //Установить все PWM в 0
+                OCR0A = 0;
+                OCR0B = 0;
+                OCR1A = 0;
+                OCR1B = 0;
+                OCR2A = 0;
+                OCR2B = 0;
                 //Вывод на экран F + частота
                 disp.display(0,15);
                 disp.display(1,(dfreq_set%1000)/100);

 

andryn
Offline
Зарегистрирован: 08.06.2018

Для всех интересующихся темой: провел небольшие испытания в железе.

Описание стенда: Arduino Uno, Переменный резистор 10К(подключен к A0), Кнопка без фиксации(подключена к А1), дисплей 1602 с I2C(подключен к A4, A5), силовая часть на ir2101+2SK3569, моторчик от FDD 3''(обмотки соединены звездой). Питание драйверов 12В, питание мотора тоже 12В, питание arduino 5В.

Основной результат испытаний: работает. Покрутил моторчик частотами от 10 до 200Гц - работает стабильно.

Есть некоторые особенности, которе не проявляется на эмуляторах:

После запуска таймера с генерацией нельзя ничего перерисовать на дисплее, для этого нужно таймер остановить, перерисовать, потом таймер запустить. Это плохо сказывается на работе мотора. Может это особенность работы LiquidCrystal_I2C, но пока разбираться неохота.

Частота увеличивается по +5Гц примерно 2 раза в секунду, т.е. 50Гц набирает где-то за 5 секунд.

Кнопка старт/стоп работает. Если покрутить резистор в процессе работы мотора, то генерация останавливается принудительно.

Свежий код:

/* Main.ino
 *
 * Created:   AVS, 2018-06-06
 * Processor: Arduino Uno
 * Compiler:  Arduino AVR
 */

#include "TM1637.h"
#include "LiquidCrystal_I2C.h"


const byte PROGMEM sineP4[]  = { //Константа для первой четверти синуса. 256 значений.
   //Для реализации Dead Time при переключении с Hight pin на Low pin добавим несколько нулевых значений. 
   //Для частоты 125Гц значений должно быть 2, т.к. описана 1/4 периода. Сделаем 3 значения, этого точно хватит :)
   0,0,
   0,2,3,5,6,8,9,11,13,14,16,17,19,20,22,24,25,27,28,30,31,33,34,36,38,39,41,42,44,45,47,48,50,51,53,55,56,58,59,61,62,64,65,67,68,70,71,73,74,76,77,79,80,82,83,85,86,88,89,91,92,94,95,96,
   98,99,101,102,104,105,107,108,109,111,112,114,115,116,118,119,121,122,123,125,126,128,129,130,132,133,134,136,137,138,140,141,142,143,145,146,147,149,150,151,152,154,155,156,157,159,160,161,162,164,165,166,167,168,169,171,172,173,174,175,176,178,179,180,
   181,182,183,184,185,186,187,188,190,191,192,193,194,195,196,197,198,199,200,201,202,203,203,204,205,206,207,208,209,210,211,212,213,213,214,215,216,217,218,218,219,220,221,222,222,223,224,225,225,226,227,228,228,229,230,230,231,232,232,233,234,234,235,235,
   236,237,237,238,238,239,239,240,241,241,242,242,243,243,243,244,244,245,245,246,246,247,247,247,248,248,248,249,249,249,250,250,250,251,251,251,251,252,252,252,252,253,253,253,253,253,254,254,254,254,254,254,254,255,255,255,255,255,255,255,255,255,255,255
};

 

// const double refclk=31372.549;  // =16MHz / 510
const unsigned int refclk = 31374;      // Реальная внутренняя частота

volatile unsigned long phaccu;    // аккумулирующая переменная, 32 бит
volatile unsigned long delta;     // переменная, расчитывающаяся на основе refclk и желаемой частоты работы
volatile unsigned int incr;       // переменная для вычисления итерации(10 бит = 2 бит + 8 бит)
volatile byte dfreq;              //Текущая Частота, Гц
volatile byte dfreq_set;          //Установленная частота, Гц

byte inctmr;             // переменная для реализации таймера в loop

//Управление частотой
const int deltafreq=10;                 // deltafreq пунктов изменения аналогового входа дают изменение частоты на 2Гц. Если deltafreq=20, то изменение до 110Гц
unsigned int setfreq=1024+deltafreq;    // переменная для хранения предыдущего измерения заначения пина. По умолчанию гарантированно больше 1023 на deltafreq.

//******************************************************************
//Для регулировки частоты переменным резистором
#define ADJ_PIN A0 // Пин, к которому подключен переменный резистор, регулирующий частоту
int adj;           // переменная для чтения значения с переменного резистора

//*******************************************************************
//Кнопка старт/стоп
#define BTN_START_PIN A1 // Пин, к которому подключена кнопка старта
byte run = B00000000; //младший бит(B00000001) - нажата ли кнопка, старший(B00000010) на до ли запускать


//******************************************************************
//Индикатор TM1637
#define CLK 8
#define DIO 7
TM1637 disp(CLK, DIO);
//******************************************************************
//Дисплей LiquidCrystal_I2C
LiquidCrystal_I2C lcd(0x27,16,2);   // Задаем адрес и размерность дисплея. 

//******************************************************************
// Настройка таймеров
void Setup_timer() {

    // TIMER0
    TCCR0A |=  (1<<COM0A1); // COM0A1, COM0A0: 1 0 - None-inverted mode для OC0A, для инверсного режима ШИМ нужно 1 1
    TCCR0A &= ~(1<<COM0A0); 
    TCCR0A |=  (1<<COM0B1); // COM0B1, COM0B0: 1 0 - None-inverted mode для OC0B, для инверсного режима ШИМ нужно 1 1
    TCCR0A &= ~(1<<COM0B0); 
    TCCR0A |=  (0<<WGM01);  // WGM02, WGM01, WGM00: 0 1 1 - Fast PWM 
    TCCR0A |=  (1<<WGM00);

    TCCR0B &= ~(1<<CS02);   // CS02, CS01, CS00: 0 0 1 - тактовый генератор CLK
    TCCR0B &= ~(1<<CS01); 
    TCCR0B |=  (1<<CS00); 
    TCCR0B &= ~(1<<WGM02);

    // TIMER1
    TCCR1A |=  (1<<COM1A1); // COM1A1, COM1A0: 1 0 - None-inverted mode для OC1A, для инверсного режима ШИМ нужно 1 1
    TCCR1A &= ~(1<<COM1A0); 
    TCCR1A |=  (1<<COM1B1); // COM1B1, COM1B0: 1 0 - None-inverted mode для OC1B, для инверсного режима ШИМ нужно 1 1
    TCCR1A &= ~(1<<COM1B0); 
    TCCR1A &= ~(0<<WGM11);  // WGM13, WGM12, WGM11, WGM10: 0 1 0 1 - Fast PWM, 8bit 
    TCCR1A |=  (1<<WGM10);

    TCCR1B &= ~(1<<CS12);   // CS12, CS11, CS10: 0 0 1 - тактовый генератор CLK
    TCCR1B &= ~(1<<CS11); 
    TCCR1B |=  (1<<CS10); 
    TCCR1B &= ~(1<<WGM13);
    TCCR1B |=  (1<<WGM12);

    // TIMER2
    TCCR2A |=  (1<<COM2A1); // COM2A1, COM2A0: 1 0 - None-inverted mode для OC2A, для инверсного режима ШИМ нужно 1 1
    TCCR2A &= ~(1<<COM2A0);
    TCCR2A |=  (1<<COM2B1); // COM2B1, COM2B0: 1 0 - None-inverted mode для OC2B, для инверсного режима ШИМ нужно 1 1
    TCCR2A &= ~(1<<COM2B0);
    TCCR2A |=  (1<<WGM21);  // WGM22, WGM21, WGM20: 0 1 1 - Fast PWM
    TCCR2A |=  (1<<WGM20);

    TCCR2B &= ~(1<<CS22);   // CS22, CS21, CS20: 0 0 1 - тактовый генератор CLK
    TCCR2B &= ~(1<<CS21); 
    TCCR2B |=  (1<<CS20); 
    TCCR2B &= ~(1<<WGM22);

    //Установить все PWM в 0
    OCR0A = 0;
    OCR0B = 0;
    OCR1A = 0;
    OCR1B = 0;
    OCR2A = 0;
    OCR2B = 0;
    //Разрешить/запретить прерывания по таймерам
    TIMSK0 &= ~(1<<TOIE0); //Запретить прерывания на таймер 0
    TIMSK1 &= ~(1<<TOIE1); //Запретить прерывания на таймер 1
    TIMSK2 &= ~(1<<TOIE2); //Запретить прерывания на таймер 2

}
 
//******************************************************************
//Прерывание по таймеру 2 (TIMER2)
//Работает с частотой 
ISR(TIMER2_OVF_vect) {
    PIND |= (1 << 2); //Для замера скорости
    //Таймер 0 | выводы 5 и 6  | OC0B, OC0A
    //Таймер 1 | выводы 9 и 10 | OC1A, OC1B
    //Таймер 2 | выводы 3 и 11 | OC2B, OC2A

    //Для теста
    //ШИМ 0, pins 6, 5
    //OCR0A = 128;
    //OCR0B = 128;
    //ШИМ 1, pins 9, 10
    //OCR1A = 128;
    //OCR1B = 128;
    //ШИМ 2, pins 11, 3
    //OCR2A = 128;
    //OCR2B = 128;

    phaccu = phaccu + delta;   // Добавляем дельту(рассчитна по частоте) в аккумулирующую переменную 32 бит
    //Фаза 1
    incr = (phaccu >> 22); //10 бит. Старщие 2 бита(incr & 0x300) - номер четверти, младшие 8 бит(incr & 0xFF) - номер значения из таблицы констант

    switch (incr & 0x300) { //B1100000000=0x300
        case 0x0: //Первая четверть, выбрать константу из массива в пин A.
        {
            OCR0B = 0;
            OCR0A = pgm_read_byte_near(sineP4 + (incr & 0xFF));
            break;
        }
        case 0x100: //Вторая четверть, выбрать обратнную константу из массива в пин A
        {
            OCR0B = 0;
            OCR0A = pgm_read_byte_near(sineP4 + 255 - (incr & 0xFF));
            break;
        }
        case 0x200: //Третья четверть, выбрать константу из массива в пин B
        {
            OCR0A = 0;
            OCR0B = pgm_read_byte_near(sineP4 + (incr & 0xFF));
            break;
        }
        case 0x300: //Четвертая четверть, выбрать обратнную константу из массива в пин B
        {
            OCR0A = 0;
            OCR0B = pgm_read_byte_near(sineP4 + 255 - (incr & 0xFF));
            break;
        }
        default:
        {
            OCR0A = 0;
            OCR0B = 0;
            break;
        }
    }
    //Фаза 2. Смещена относительно фазы 1 на 120. 1/2*Pi = 512; 120 = 2/3*Pi = 512*4/3 = 683. Сдвинем фазу на 683 деления
    incr = incr + 683;
    //Уберем биты выще 10го B001111111111=0x3FF, чтобы попасть в массив констант
    incr &= 0x3FF;

    switch (incr & 0x300) { //B1100000000=0x300
        case 0x0: //Первая четверть, выбрать константу из массива в пин A.
        {
            OCR1B = 0;
            OCR1A = pgm_read_byte_near(sineP4 + (incr & 0xFF));
            break;
        }
        case 0x100: //Вторая четверть, выбрать обратнную константу из массива в пин A
        {
            OCR1B = 0;
            OCR1A = pgm_read_byte_near(sineP4 + 255 - (incr & 0xFF));
            break;
        }
        case 0x200: //Третья четверть, выбрать константу из массива в пин B
        {
            OCR1A = 0;
            OCR1B = pgm_read_byte_near(sineP4 + (incr & 0xFF));
            break;
        }
        case 0x300: //Четвертая четверть, выбрать обратнную константу из массива в пин B
        {
            OCR1A = 0;
            OCR1B = pgm_read_byte_near(sineP4 + 255 - (incr & 0xFF));
            break;
        }
        default:
        {
            OCR1A = 0;
            OCR1B = 0;
            break;
        }
    }
    //Фаза 3. Смещена относительно фазы 2 на 120. 1/2*Pi = 512; 120 = 2/3*Pi = 512*4/3 = 683. Сдвинем фазу на 683 деления
    incr = incr + 683;
    //Уберем биты выще 10го B001111111111=0x3FF, чтобы попасть в массив констант
    incr &= 0x3FF;

    switch (incr & 0x300) { //B1100000000=0x300
        case 0x0: //Первая четверть, выбрать константу из массива в пин A.
        {
            OCR2B = 0;
            OCR2A = pgm_read_byte_near(sineP4 + (incr & 0xFF));
            break;
        }
        case 0x100: //Вторая четверть, выбрать обратнную константу из массива в пин A
        {
            OCR2B = 0;
            OCR2A = pgm_read_byte_near(sineP4 + 255 - (incr & 0xFF));
            break;
        }
        case 0x200: //Третья четверть, выбрать константу из массива в пин B
        {
            OCR2A = 0;
            OCR2B = pgm_read_byte_near(sineP4 + (incr & 0xFF));
            break;
        }
        case 0x300: //Четвертая четверть, выбрать обратнную константу из массива в пин B
        {
            OCR2A = 0;
            OCR2B = pgm_read_byte_near(sineP4 + 255 - (incr & 0xFF));
            break;
        }
        default:
        {
            OCR2A = 0;
            OCR2B = 0;
            break;
        }
    }
}

void setup()
{
    Serial.begin(115200);        // connect to the serial port
    Serial.println("Test begin");

    DDRD = 0xFC;  // весь порт D - выход, кроме RX TX
    DDRB = 0xFF;  // весь порт B - выход

    //Настройка и остановка таймеров
    Setup_timer();
    
    dfreq = 9; //минимальнач частота
    //Дя регулировки частоты
    pinMode(ADJ_PIN, INPUT);

    //Кнопка старта
    pinMode(BTN_START_PIN, INPUT_PULLUP);
    //Индикатор
    disp.init();
    disp.point(false);
    disp.set(BRIGHT_TYPICAL); //яркость
    disp.clearDisplay(); //Очистить
    //LCD дисплей
    lcd.init();                            // Инициализация lcd             
    lcd.backlight();                       // Включаем подсветку
    // Курсор находится в начале 1 строки
    lcd.setCursor(0, 0);                        // Устанавливаем курсор в начало 1 строки
    lcd.print("3phase generator");            // Выводим текст
    lcd.setCursor(0, 1);                        // Устанавливаем курсор в начало 2 строки
    lcd.print("Stop            ");             // Выводим текст
}

void loop() {
    //PIND |= (1 << 2); //Для замера скорости
    //Реализация для изменения частоты переменным резистором
    if ((inctmr++) % 2048 == 0) { //Обрабатывается каждый 512-й вызов loop. Можно, наверно, увеличить.
        adj = (analogRead(ADJ_PIN)+analogRead(ADJ_PIN)+analogRead(ADJ_PIN))/3; //Усредненное значение на пине регулировки частоты
        if ( abs(adj - setfreq) >= deltafreq ){
            //Значение аналогового входа изменилось на deltafreq и более.
            //Сначала останавливаем прерывания по таймеру 2, иначе не работает
            TIMSK2 &= ~(1<<TOIE2);
            //Установить все PWM в 0
            OCR0A = 0;
            OCR0B = 0;
            OCR1A = 0;
            OCR1B = 0;
            OCR2A = 0;
            OCR2B = 0;
            
            //Переведем значение analogRead в частоту
            setfreq = adj;
            dfreq_set = 10 + 2*(setfreq/deltafreq);    // Минимальная частота 10Гц, на каждые 15 пунктов частота увеличивается на 2Гц
            
            //Заполнить значение для LCD частотой dfreq_set. Вывод: Set freq: + частота
            lcd.setCursor(0, 0);                      // Устанавливаем курсор в начало 1 строки
            lcd.print("Set freq:       ");            // Выводим текст
            lcd.setCursor(10, 0);                     // Устанавливаем курсор в 10 позицию
            lcd.print(dfreq_set);                     // Выводим текст
            lcd.setCursor(0, 1);                      // Устанавливаем курсор в начало 2 строки
            lcd.print("Stop            ");            // Выводим текст
            
            run = B00000000;       //Изменение состояния на выкл и запрет обработки кнопки
            run |= B00000001;      //Кнопку можно снова обрабатывать            
        }
    }
    if (inctmr % 256 == 0) { //Обрабатывается каждый 256-й вызов loop. Можно, наверно, увеличить.
        //Нажатие кнопки
        if ( digitalRead(BTN_START_PIN) == LOW && (run & B00000001) ){ //Нажата кнопка и нужно менять состояние
            if ((run & B00000010)) { //Надо остановить
                TIMSK2 &= ~(1<<TOIE2); //остановка таймера
                //Установить все PWM в 0
                OCR0A = 0;
                OCR0B = 0;
                OCR1A = 0;
                OCR1B = 0;
                OCR2A = 0;
                OCR2B = 0;
                //Вывод на экран F + частота
                /*disp.display(0,15);
                disp.display(1,(dfreq_set%1000)/100);
                disp.display(2,(dfreq_set%100)/10);
                disp.display(3,dfreq_set%10);
                */
                //Заполнить значение для LCD частотой dfreq_set. Вывод: Set freq: + частота
                lcd.setCursor(0, 1);          // Устанавливаем курсор в начало 2 строки
                lcd.print("Stop            ");// Выводим текст
                
                run = B00000000;       //Изменение состояния на выкл и запрет обработки кнопки
            }
            else {
                phaccu = 0;
                dfreq = 4; //Минимальная частота для увеличения до рабочей
                //Заполнить значение для LCD частотой dfreq_set. Вывод: Set freq: + частота
                lcd.setCursor(0, 1);          // Устанавливаем курсор в начало 2 строки
                lcd.print("Running         ");// Выводим текст
                run = B00000010;  //Изменение состояния на вкл и запрет обработки кнопки
            }
        }
        else if (digitalRead(BTN_START_PIN) == HIGH) { //кнопка отпущена
            run |= B00000001; //Кнопку можно снова обрабатывать
        }
    }

    
    if (dfreq < dfreq_set && (run & B00000010) && inctmr % 1048576/2 == 0) { //Плавное увеличение частоты, если нужная частота больше текущей. Резкое изменение, если меньше
        dfreq++; //Увеличение частоты

        if (dfreq % 5 == 0 || dfreq == dfreq_set) { //Увеличиваем каждые 5Гц
            delta = 0x100000000 * dfreq / refclk;  // Новое вычисление дельты
            TIMSK2 |=  (1<<TOIE2); //Запуск таймера
        }
        //delta = 0x100000000 * dfreq / refclk;  // Новое вычисление дельты
        //TIMSK2 |=  (1<<TOIE2); //Запуск таймера
    }
}

Дальнейшие планы:

1. Сделать реверс

2. Испытать на чем-то более силовом, вольт на 40. Есть идеи?

3. Сделать токовую защиту. Пока простенькую, типа датчика тока по линии питания мотора.

 

step64
Offline
Зарегистрирован: 01.04.2013

andryn пишет:

После запуска таймера с генерацией нельзя ничего перерисовать на дисплее, для этого нужно таймер остановить, перерисовать, потом таймер запустить. Это плохо сказывается на работе мотора. Может это особенность работы LiquidCrystal_I2C, но пока разбираться неохота.

Частота увеличивается по +5Гц примерно 2 раза в секунду, т.е. 50Гц набирает где-то за 5 секунд.

Кнопка старт/стоп работает. Если покрутить резистор в процессе работы мотора, то генерация останавливается принудительно.

Экран по 6 проводам пробовали подключать, результат тот же? Тоесть динамическая регулировка частоты отсутствует?

andryn
Offline
Зарегистрирован: 08.06.2018

step64 пишет:

Экран по 6 проводам пробовали подключать, результат тот же?

Не пробовал. Я всетаки склоняюсь к семисегентнику и паре светодиодов.

step64 пишет:

Тоесть динамическая регулировка частоты отсутствует?

Да, сейчас так. Не понятно как ее отобразить, если нужно останавливать таймер. Может придумаю что-то.

andryn
Offline
Зарегистрирован: 08.06.2018

Добавлена кнопка реверса на пине A2.

Код:

/* Main.ino
 *
 * Created:   AVS, 2018-06-06
 * Processor: Arduino Uno
 * Compiler:  Arduino AVR
 */

#include "TM1637.h"
#include "LiquidCrystal_I2C.h"


const byte PROGMEM sineP4[]  = { //Константа для первой четверти синуса. 256 значений.
   //Для реализации Dead Time при переключении с Hight pin на Low pin добавим несколько нулевых значений. 
   //Для частоты 125Гц значений должно быть 2, т.к. описана 1/4 периода. Сделаем 3 значения, этого точно хватит :)
   0,0,
   0,2,3,5,6,8,9,11,13,14,16,17,19,20,22,24,25,27,28,30,31,33,34,36,38,39,41,42,44,45,47,48,50,51,53,55,56,58,59,61,62,64,65,67,68,70,71,73,74,76,77,79,80,82,83,85,86,88,89,91,92,94,95,96,
   98,99,101,102,104,105,107,108,109,111,112,114,115,116,118,119,121,122,123,125,126,128,129,130,132,133,134,136,137,138,140,141,142,143,145,146,147,149,150,151,152,154,155,156,157,159,160,161,162,164,165,166,167,168,169,171,172,173,174,175,176,178,179,180,
   181,182,183,184,185,186,187,188,190,191,192,193,194,195,196,197,198,199,200,201,202,203,203,204,205,206,207,208,209,210,211,212,213,213,214,215,216,217,218,218,219,220,221,222,222,223,224,225,225,226,227,228,228,229,230,230,231,232,232,233,234,234,235,235,
   236,237,237,238,238,239,239,240,241,241,242,242,243,243,243,244,244,245,245,246,246,247,247,247,248,248,248,249,249,249,250,250,250,251,251,251,251,252,252,252,252,253,253,253,253,253,254,254,254,254,254,254,254,255,255,255,255,255,255,255,255,255,255,255
};
#define PHASE_OFFSET 683 //Смещение фазы относительно предыдущей на 120 градусов(3/4*Pi). Расчет: 1/2*Pi = 512 пунктов; 2/3*Pi = 512*4/3 = 683 пункта. 
 
// const double refclk=31372.549;  // =16MHz / 510
const unsigned int refclk = 31374;      // Реальная внутренняя частота

volatile unsigned long phaccu;     // аккумулирующая переменная, 32 бит
volatile unsigned long delta;      // переменная, расчитывающаяся на основе refclk и желаемой частоты работы
volatile unsigned int incr;        // переменная для вычисления итерации(10 бит = 2 бит + 8 бит)
volatile unsigned int phaseoffset; // переменная для смещения фазы. PHASE_OFFSET для прямого движения и -PHASE_OFFSET для реверсивного
volatile byte dfreq;               //Текущая Частота, Гц
volatile byte dfreq_set;           //Установленная частота, Гц

byte inctmr;             // переменная для реализации таймера в loop

//Управление частотой
const int deltafreq=10;                 // deltafreq пунктов изменения аналогового входа дают изменение частоты на 2Гц. Если deltafreq=20, то изменение до 110Гц
unsigned int setfreq=1024+deltafreq;    // переменная для хранения предыдущего измерения заначения пина. По умолчанию гарантированно больше 1023 на deltafreq.

//******************************************************************
//Для регулировки частоты переменным резистором
#define ADJ_PIN A0 // Пин, к которому подключен переменный резистор, регулирующий частоту
int adj;           // переменная для чтения значения с переменного резистора

//*******************************************************************
//Кнопка старт/стоп
#define BTN_START_PIN A1 // Пин, к которому подключена кнопка старта
byte run = B00000000; //младший бит(B00000001) - нажата ли кнопка, старший(B00000010) надо ли запускать

//*******************************************************************
//Кнопка реверса
#define BTN_REVERSE A2 // Пин, к которому подключена кнопка реверса
byte reverse = B00000000; //младший бит(B00000001) - нажата ли кнопка, старший(B00000010) надо ли реверсировать

//******************************************************************
//Индикатор TM1637
#define CLK 8
#define DIO 7
TM1637 disp(CLK, DIO);
//******************************************************************
//Дисплей LiquidCrystal_I2C
LiquidCrystal_I2C lcd(0x27,16,2);   // Задаем адрес и размерность дисплея. 

//******************************************************************
// Настройка таймеров
void Setup_timer() {

    // TIMER0
    TCCR0A |=  (1<<COM0A1); // COM0A1, COM0A0: 1 0 - None-inverted mode для OC0A, для инверсного режима ШИМ нужно 1 1
    TCCR0A &= ~(1<<COM0A0); 
    TCCR0A |=  (1<<COM0B1); // COM0B1, COM0B0: 1 0 - None-inverted mode для OC0B, для инверсного режима ШИМ нужно 1 1
    TCCR0A &= ~(1<<COM0B0); 
    TCCR0A |=  (0<<WGM01);  // WGM02, WGM01, WGM00: 0 1 1 - Fast PWM 
    TCCR0A |=  (1<<WGM00);

    TCCR0B &= ~(1<<CS02);   // CS02, CS01, CS00: 0 0 1 - тактовый генератор CLK
    TCCR0B &= ~(1<<CS01); 
    TCCR0B |=  (1<<CS00); 
    TCCR0B &= ~(1<<WGM02);

    // TIMER1
    TCCR1A |=  (1<<COM1A1); // COM1A1, COM1A0: 1 0 - None-inverted mode для OC1A, для инверсного режима ШИМ нужно 1 1
    TCCR1A &= ~(1<<COM1A0); 
    TCCR1A |=  (1<<COM1B1); // COM1B1, COM1B0: 1 0 - None-inverted mode для OC1B, для инверсного режима ШИМ нужно 1 1
    TCCR1A &= ~(1<<COM1B0); 
    TCCR1A &= ~(0<<WGM11);  // WGM13, WGM12, WGM11, WGM10: 0 1 0 1 - Fast PWM, 8bit 
    TCCR1A |=  (1<<WGM10);

    TCCR1B &= ~(1<<CS12);   // CS12, CS11, CS10: 0 0 1 - тактовый генератор CLK
    TCCR1B &= ~(1<<CS11); 
    TCCR1B |=  (1<<CS10); 
    TCCR1B &= ~(1<<WGM13);
    TCCR1B |=  (1<<WGM12);

    // TIMER2
    TCCR2A |=  (1<<COM2A1); // COM2A1, COM2A0: 1 0 - None-inverted mode для OC2A, для инверсного режима ШИМ нужно 1 1
    TCCR2A &= ~(1<<COM2A0);
    TCCR2A |=  (1<<COM2B1); // COM2B1, COM2B0: 1 0 - None-inverted mode для OC2B, для инверсного режима ШИМ нужно 1 1
    TCCR2A &= ~(1<<COM2B0);
    TCCR2A |=  (1<<WGM21);  // WGM22, WGM21, WGM20: 0 1 1 - Fast PWM
    TCCR2A |=  (1<<WGM20);

    TCCR2B &= ~(1<<CS22);   // CS22, CS21, CS20: 0 0 1 - тактовый генератор CLK
    TCCR2B &= ~(1<<CS21); 
    TCCR2B |=  (1<<CS20); 
    TCCR2B &= ~(1<<WGM22);

    //Установить все PWM в 0
    OCR0A = 0;
    OCR0B = 0;
    OCR1A = 0;
    OCR1B = 0;
    OCR2A = 0;
    OCR2B = 0;
    //Разрешить/запретить прерывания по таймерам
    TIMSK0 &= ~(1<<TOIE0); //Запретить прерывания на таймер 0
    TIMSK1 &= ~(1<<TOIE1); //Запретить прерывания на таймер 1
    TIMSK2 &= ~(1<<TOIE2); //Запретить прерывания на таймер 2

}
 
//******************************************************************
//Прерывание по таймеру 2 (TIMER2)
//Работает с частотой 
ISR(TIMER2_OVF_vect) {
    PIND |= (1 << 2); //Для замера скорости
    //Таймер 0 | выводы 5 и 6  | OC0B, OC0A
    //Таймер 1 | выводы 9 и 10 | OC1A, OC1B
    //Таймер 2 | выводы 3 и 11 | OC2B, OC2A

    //Для теста
    //ШИМ 0, pins 6, 5
    //OCR0A = 128;
    //OCR0B = 128;
    //ШИМ 1, pins 9, 10
    //OCR1A = 128;
    //OCR1B = 128;
    //ШИМ 2, pins 11, 3
    //OCR2A = 128;
    //OCR2B = 128;

    phaccu = phaccu + delta;   // Добавляем дельту(рассчитна по частоте) в аккумулирующую переменную 32 бит
    //Фаза 1
    incr = (phaccu >> 22); //10 бит. Старщие 2 бита(incr & 0x300) - номер четверти, младшие 8 бит(incr & 0xFF) - номер значения из таблицы констант

    switch (incr & 0x300) { //B1100000000=0x300
        case 0x0: //Первая четверть, выбрать константу из массива в пин A.
        {
            OCR0B = 0;
            OCR0A = pgm_read_byte_near(sineP4 + (incr & 0xFF));
            break;
        }
        case 0x100: //Вторая четверть, выбрать обратнную константу из массива в пин A
        {
            OCR0B = 0;
            OCR0A = pgm_read_byte_near(sineP4 + 255 - (incr & 0xFF));
            break;
        }
        case 0x200: //Третья четверть, выбрать константу из массива в пин B
        {
            OCR0A = 0;
            OCR0B = pgm_read_byte_near(sineP4 + (incr & 0xFF));
            break;
        }
        case 0x300: //Четвертая четверть, выбрать обратнную константу из массива в пин B
        {
            OCR0A = 0;
            OCR0B = pgm_read_byte_near(sineP4 + 255 - (incr & 0xFF));
            break;
        }
        default:
        {
            OCR0A = 0;
            OCR0B = 0;
            break;
        }
    }
    //Фаза 2. Смещена относительно фазы 1 на phaseoffset
    incr = incr + phaseoffset;
    //Уберем биты выще 10го B001111111111=0x3FF, чтобы попасть в массив констант
    incr &= 0x3FF;

    switch (incr & 0x300) { //B1100000000=0x300
        case 0x0: //Первая четверть, выбрать константу из массива в пин A.
        {
            OCR1B = 0;
            OCR1A = pgm_read_byte_near(sineP4 + (incr & 0xFF));
            break;
        }
        case 0x100: //Вторая четверть, выбрать обратнную константу из массива в пин A
        {
            OCR1B = 0;
            OCR1A = pgm_read_byte_near(sineP4 + 255 - (incr & 0xFF));
            break;
        }
        case 0x200: //Третья четверть, выбрать константу из массива в пин B
        {
            OCR1A = 0;
            OCR1B = pgm_read_byte_near(sineP4 + (incr & 0xFF));
            break;
        }
        case 0x300: //Четвертая четверть, выбрать обратнную константу из массива в пин B
        {
            OCR1A = 0;
            OCR1B = pgm_read_byte_near(sineP4 + 255 - (incr & 0xFF));
            break;
        }
        default:
        {
            OCR1A = 0;
            OCR1B = 0;
            break;
        }
    }
    //Фаза 2. Смещена относительно фазы 1 на phaseoffset
    incr = incr + phaseoffset;
    //Уберем биты выще 10го B001111111111=0x3FF, чтобы попасть в массив констант
    incr &= 0x3FF;

    switch (incr & 0x300) { //B1100000000=0x300
        case 0x0: //Первая четверть, выбрать константу из массива в пин A.
        {
            OCR2B = 0;
            OCR2A = pgm_read_byte_near(sineP4 + (incr & 0xFF));
            break;
        }
        case 0x100: //Вторая четверть, выбрать обратнную константу из массива в пин A
        {
            OCR2B = 0;
            OCR2A = pgm_read_byte_near(sineP4 + 255 - (incr & 0xFF));
            break;
        }
        case 0x200: //Третья четверть, выбрать константу из массива в пин B
        {
            OCR2A = 0;
            OCR2B = pgm_read_byte_near(sineP4 + (incr & 0xFF));
            break;
        }
        case 0x300: //Четвертая четверть, выбрать обратнную константу из массива в пин B
        {
            OCR2A = 0;
            OCR2B = pgm_read_byte_near(sineP4 + 255 - (incr & 0xFF));
            break;
        }
        default:
        {
            OCR2A = 0;
            OCR2B = 0;
            break;
        }
    }
}

void display_status() {
    //Отображение на дисплее текущего статуса
    //Первая строка
    //Заполнить значение для LCD частотой dfreq_set. Вывод: Set freq: + частота
    lcd.setCursor(0, 0);                      // Устанавливаем курсор в начало 1 строки
    lcd.print("Freq:           ");            // Выводим текст
    lcd.setCursor(6, 0);                      // Устанавливаем курсор в 6 позицию
    lcd.print(dfreq_set);                     // Выводим текст
    lcd.setCursor(10, 0);                     // Устанавливаем курсор в 10 позицию
    if ((reverse & B00000010)) {
        lcd.print("Revers");
    }
    else {
        lcd.print("Direct");  
    }
    lcd.setCursor(0, 1);                      // Устанавливаем курсор в начало 2 строки
    if (run & B00000010) {
        lcd.print("Run             ");            // Выводим текст
    }
    else {
        lcd.print("Stop            ");            // Выводим текст
    }
    //Вывод на экран F + частота
    /*disp.display(0,15);
    disp.display(1,(dfreq_set%1000)/100);
    disp.display(2,(dfreq_set%100)/10);
    disp.display(3,dfreq_set%10);
    */
}

void setup()
{
    Serial.begin(115200);        // connect to the serial port
    Serial.println("Test begin");

    DDRD = 0xFC;  // весь порт D - выход, кроме RX TX
    DDRB = 0xFF;  // весь порт B - выход

    //Настройка и остановка таймеров
    Setup_timer();
    
    dfreq = 9; //минимальнач частота
    //Дя регулировки частоты
    pinMode(ADJ_PIN, INPUT);

    //Кнопка старта
    pinMode(BTN_START_PIN, INPUT_PULLUP);
    //Кнопка реверса
    pinMode(BTN_REVERSE, INPUT_PULLUP);
    //Индикатор
    disp.init();
    disp.point(false);
    disp.set(BRIGHT_TYPICAL); //яркость
    disp.clearDisplay(); //Очистить
    //LCD дисплей
    lcd.init();                            // Инициализация lcd             
    lcd.backlight();                       // Включаем подсветку
    display_status();
}

void loop() {
    //PIND |= (1 << 2); //Для замера скорости
    //Реализация для изменения частоты переменным резистором
    if ((inctmr++) % 2048 == 0) { //Обрабатывается каждый 2048-й вызов loop
        adj = (analogRead(ADJ_PIN)+analogRead(ADJ_PIN)+analogRead(ADJ_PIN))/3; //Усредненное значение на пине регулировки частоты
        if ( abs(adj - setfreq) >= deltafreq ){
            //Значение аналогового входа изменилось на deltafreq и более.
            //Сначала останавливаем прерывания по таймеру 2, иначе не работает
            TIMSK2 &= ~(1<<TOIE2); //остановка таймера
            //Установить все PWM в 0
            OCR0A = 0;
            OCR0B = 0;
            OCR1A = 0;
            OCR1B = 0;
            OCR2A = 0;
            OCR2B = 0;
            
            //Переведем значение analogRead в частоту
            setfreq = adj;
            dfreq_set = 10 + 2*(setfreq/deltafreq);    // Минимальная частота 10Гц, на каждые 15 пунктов частота увеличивается на 2Гц

            display_status();
            run = B00000000;       //Изменение состояния кнопки на выкл, запрет обработки(если случайно в этот момент нажата)
        }
    }
    if (inctmr % 256 == 0) { //Обрабатывается каждый 256-й вызов loop. Можно, наверно, увеличить.
        //Нажатие кнопки реверс
        if ( digitalRead(BTN_REVERSE) == LOW && (reverse & B00000001) ){ //Нажата кнопка и нужно менять состояние
            //Останавливаем прерывания по таймеру 2
            TIMSK2 &= ~(1<<TOIE2); //остановка таймера
            //Установить все PWM в 0
            OCR0A = 0;
            OCR0B = 0;
            OCR1A = 0;
            OCR1B = 0;
            OCR2A = 0;
            OCR2B = 0;
            
            if ((reverse & B00000010)) {
                reverse = B00000000;
            }
            else {
                reverse = B00000010;
            }
            display_status();
            run = B00000000;       //Изменение состояния кнопки на выкл, запрет обработки(если случайно в этот момент нажата)
        }
        else if (digitalRead(BTN_REVERSE) == HIGH) { //кнопка отпущена
            reverse |= B00000001; //Кнопку можно снова обрабатывать
        }
        //Нажатие кнопки старт/стоп
        if ( digitalRead(BTN_START_PIN) == LOW && (run & B00000001) ){ //Нажата кнопка и нужно менять состояние
            if ((run & B00000010)) { //Надо остановить
                TIMSK2 &= ~(1<<TOIE2); //остановка таймера
                //Установить все PWM в 0
                OCR0A = 0;
                OCR0B = 0;
                OCR1A = 0;
                OCR1B = 0;
                OCR2A = 0;
                OCR2B = 0;
                //Заполнить значение для LCD частотой dfreq_set. Вывод: Set freq: + частота
                lcd.setCursor(0, 1);          // Устанавливаем курсор в начало 2 строки
                lcd.print("Stop            ");// Выводим текст
                
                run = B00000000;       //Изменение состояния на выкл и запрет обработки кнопки
            }
            else {
                phaccu = 0; //
                dfreq = 4;  //Минимальная частота для увеличения до рабочей
                if (reverse & B00000010) {
                    phaseoffset = - PHASE_OFFSET; //Смещение для фазы для реверса
                }
                else {
                    phaseoffset = PHASE_OFFSET; //Смещение для фазы для прямого вращения
                }
                run = B00000010;  //Изменение состояния на вкл и запрет обработки кнопки
                display_status();
                TIMSK2 |=  (1<<TOIE2); //Запуск таймера
            }
        }
        else if (digitalRead(BTN_START_PIN) == HIGH) { //кнопка отпущена
            run |= B00000001; //Кнопку можно снова обрабатывать
        }
    }

    
    if (dfreq < dfreq_set && (run & B00000010) && inctmr % 1048576/2 == 0) { //Плавное увеличение частоты
        dfreq++; //Увеличение частоты

        if (dfreq % 5 == 0 || dfreq == dfreq_set) { //Увеличиваем каждые 5Гц
            delta = 0x100000000 * dfreq / refclk;  // Новое вычисление дельты
        }
        
        //delta = 0x100000000 * dfreq / refclk;  // Новое вычисление дельты
    }
}

 

step64
Offline
Зарегистрирован: 01.04.2013

Не придумали как динамическую регулировку частоты сделать? На другой контроллер не планируете свой проект переводить?

AV
Offline
Зарегистрирован: 24.11.2018

andryn какая максимальная частота вами достигнута ?

andryn
Offline
Зарегистрирован: 08.06.2018

step64 пишет:

Не придумали как динамическую регулировку частоты сделать?

Проблема не в динамической регулировке частоты, ее сдеать можно. Проблема в отображении на дисплее текущей частоты. В библиотеке LiquidCrystal_I2C встречается 10 delay. В библиотеке TM1637 тоже есть delay.

Поэтому сейчас искуственно введена остановка генерации сигнала при изменении частоты.

Возможно перепишу либу TM1637, но может это и не нужно.

step64 пишет:

На другой контроллер не планируете свой проект переводить?

Нет, не планирую. На других, более мощных контроллерах, есть куча готовых решений. Сам проект сделан специально на Arduino, т.к. когда я искал готовый начитался много ерунды, типа "ардуина так не сможет".

AV пишет:

andryn какая максимальная частота вами достигнута ?

Гонял до 200Гц, но понял, что больше 100 сильно падает мощность мотора. Поэтому сейчас регулировка от 10 до 110Гц.

 

Ну и, для интересующихся, вести с полей. Испытал работу на маленьком трехфазном моторчике 220Вольт 40Вт(по-моему). Работает устойчиво.

При испытании выявил несколько косяков. Вот свежий код:

/* Main.ino
 *
 * Created:   AVS, 2018-06-06
 * Processor: Arduino Uno
 * Compiler:  Arduino AVR
 */

#include "TM1637.h"
#include "LiquidCrystal_I2C.h"


const byte PROGMEM sineP4[]  = { //Константа для первой четверти синуса. 256 значений.
   //Для реализации Dead Time при переключении с Hight pin на Low pin добавим несколько нулевых значений. 
   //Для частоты 125Гц значений должно быть 2, т.к. описана 1/4 периода. Сделаем 3 значения, этого точно хватит :)
   0,0,
   0,2,3,5,6,8,9,11,13,14,16,17,19,20,22,24,25,27,28,30,31,33,34,36,38,39,41,42,44,45,47,48,50,51,53,55,56,58,59,61,62,64,65,67,68,70,71,73,74,76,77,79,80,82,83,85,86,88,89,91,92,94,95,96,
   98,99,101,102,104,105,107,108,109,111,112,114,115,116,118,119,121,122,123,125,126,128,129,130,132,133,134,136,137,138,140,141,142,143,145,146,147,149,150,151,152,154,155,156,157,159,160,161,162,164,165,166,167,168,169,171,172,173,174,175,176,178,179,180,
   181,182,183,184,185,186,187,188,190,191,192,193,194,195,196,197,198,199,200,201,202,203,203,204,205,206,207,208,209,210,211,212,213,213,214,215,216,217,218,218,219,220,221,222,222,223,224,225,225,226,227,228,228,229,230,230,231,232,232,233,234,234,235,235,
   236,237,237,238,238,239,239,240,241,241,242,242,243,243,243,244,244,245,245,246,246,247,247,247,248,248,248,249,249,249,250,250,250,251,251,251,251,252,252,252,252,253,253,253,253,253,254,254,254,254,254,254,254,255,255,255,255,255,255,255,255,255,255,255
};
#define PHASE_OFFSET 683 //Смещение фазы относительно предыдущей на 120 градусов(3/4*Pi). Расчет: 1/2*Pi = 512 пунктов; 2/3*Pi = 512*4/3 = 683 пункта. 
 
// const double refclk=31372.549;  // =16MHz / 510
const unsigned int refclk = 31374;      // Реальная внутренняя частота

volatile unsigned long phaccu;     // аккумулирующая переменная, 32 бит
volatile unsigned long delta;      // переменная, расчитывающаяся на основе refclk и желаемой частоты работы
volatile unsigned int incr;        // переменная для вычисления итерации(10 бит = 2 бит + 8 бит)
volatile unsigned int phaseoffset; // переменная для смещения фазы. PHASE_OFFSET для прямого двидения и -PHASE_OFFSET для реверсивного
volatile byte dfreq;               //Текущая Частота, Гц
volatile byte dfreq_set;           //Установленная частота, Гц

unsigned int inctmr;             // переменная для реализации таймера в loop
unsigned int inctmr_rev;         // переменная для реализации антидребезга кнопки reverse
unsigned int inctmr_stop;        // переменная для реализации антидребезга кнопки stop

//Управление частотой
const int deltafreq=20;                 // deltafreq пунктов изменения аналогового входа дают изменение частоты на 2Гц. Если deltafreq=20, то изменение до 110Гц
unsigned int setfreq=1024+deltafreq;    // переменная для хранения предыдущего измерения заначения пина. По умолчанию гарантированно больше 1023 на deltafreq.

//******************************************************************
//Для регулировки частоты переменным резистором
#define ADJ_PIN A0 // Пин, к которому подключен переменный резистор, регулирующий частоту
int adj;           // переменная для чтения значения с переменного резистора

//*******************************************************************
//Кнопка старт/стоп
#define BTN_START_PIN A1 // Пин, к которому подключена кнопка старта
byte run = B00000000; //младший бит(B00000001) - нажата ли кнопка, старший(B00000010) надо ли запускать

//*******************************************************************
//Кнопка реверса
#define BTN_REVERSE A2 // Пин, к которому подключена кнопка реверса
byte reverse = B00000000; //младший бит(B00000001) - нажата ли кнопка, старший(B00000010) надо ли реверсировать

//******************************************************************
//Индикатор TM1637
#define CLK A5
#define DIO A4
TM1637 disp(CLK, DIO);
//******************************************************************
//Дисплей LiquidCrystal_I2C
//LiquidCrystal_I2C lcd(0x27,16,2);   // Задаем адрес и размерность дисплея. 

//******************************************************************
// Настройка таймеров
void Setup_timer() {

    // TIMER0
    TCCR0A |=  (1<<COM0A1); // COM0A1, COM0A0: 1 0 - None-inverted mode для OC0A, для инверсного режима ШИМ нужно 1 1
    TCCR0A &= ~(1<<COM0A0); 
    TCCR0A |=  (1<<COM0B1); // COM0B1, COM0B0: 1 0 - None-inverted mode для OC0B, для инверсного режима ШИМ нужно 1 1
    TCCR0A &= ~(1<<COM0B0); 
    TCCR0A |=  (0<<WGM01);  // WGM02, WGM01, WGM00: 0 1 1 - Fast PWM 
    TCCR0A |=  (1<<WGM00);

    TCCR0B &= ~(1<<CS02);   // CS02, CS01, CS00: 0 0 1 - тактовый генератор CLK
    TCCR0B &= ~(1<<CS01); 
    TCCR0B |=  (1<<CS00); 
    TCCR0B &= ~(1<<WGM02);

    // TIMER1
    TCCR1A |=  (1<<COM1A1); // COM1A1, COM1A0: 1 0 - None-inverted mode для OC1A, для инверсного режима ШИМ нужно 1 1
    TCCR1A &= ~(1<<COM1A0); 
    TCCR1A |=  (1<<COM1B1); // COM1B1, COM1B0: 1 0 - None-inverted mode для OC1B, для инверсного режима ШИМ нужно 1 1
    TCCR1A &= ~(1<<COM1B0); 
    TCCR1A &= ~(0<<WGM11);  // WGM13, WGM12, WGM11, WGM10: 0 1 0 1 - Fast PWM, 8bit 
    TCCR1A |=  (1<<WGM10);

    TCCR1B &= ~(1<<CS12);   // CS12, CS11, CS10: 0 0 1 - тактовый генератор CLK
    TCCR1B &= ~(1<<CS11); 
    TCCR1B |=  (1<<CS10); 
    TCCR1B &= ~(1<<WGM13);
    TCCR1B |=  (1<<WGM12);

    // TIMER2
    TCCR2A |=  (1<<COM2A1); // COM2A1, COM2A0: 1 0 - None-inverted mode для OC2A, для инверсного режима ШИМ нужно 1 1
    TCCR2A &= ~(1<<COM2A0);
    TCCR2A |=  (1<<COM2B1); // COM2B1, COM2B0: 1 0 - None-inverted mode для OC2B, для инверсного режима ШИМ нужно 1 1
    TCCR2A &= ~(1<<COM2B0);
    TCCR2A |=  (1<<WGM21);  // WGM22, WGM21, WGM20: 0 1 1 - Fast PWM
    TCCR2A |=  (1<<WGM20);

    TCCR2B &= ~(1<<CS22);   // CS22, CS21, CS20: 0 0 1 - тактовый генератор CLK
    TCCR2B &= ~(1<<CS21); 
    TCCR2B |=  (1<<CS20); 
    TCCR2B &= ~(1<<WGM22);

    //Установить все PWM в 0
    OCR0A = 0;
    OCR0B = 0;
    OCR1A = 0;
    OCR1B = 0;
    OCR2A = 0;
    OCR2B = 0;
    //Разрешить/запретить прерывания по таймерам
    TIMSK0 &= ~(1<<TOIE0); //Запретить прерывания на таймер 0
    TIMSK1 &= ~(1<<TOIE1); //Запретить прерывания на таймер 1
    TIMSK2 &= ~(1<<TOIE2); //Запретить прерывания на таймер 2

}
 
//******************************************************************
//Прерывание по таймеру 2 (TIMER2)
//Работает с частотой 
ISR(TIMER2_OVF_vect) {
    PIND |= (1 << 2); //Для замера скорости
    //Таймер 0 | выводы 5 и 6  | OC0B, OC0A
    //Таймер 1 | выводы 9 и 10 | OC1A, OC1B
    //Таймер 2 | выводы 3 и 11 | OC2B, OC2A

    //Для теста
    //ШИМ 0, pins 6, 5
    //OCR0A = 128;
    //OCR0B = 128;
    //ШИМ 1, pins 9, 10
    //OCR1A = 128;
    //OCR1B = 128;
    //ШИМ 2, pins 11, 3
    //OCR2A = 128;
    //OCR2B = 128;

    phaccu = phaccu + delta;   // Добавляем дельту(рассчитна по частоте) в аккумулирующую переменную 32 бит
    //Фаза 1
    incr = (phaccu >> 22); //10 бит. Старщие 2 бита(incr & 0x300) - номер четверти, младшие 8 бит(incr & 0xFF) - номер значения из таблицы констант

    switch (incr & 0x300) { //B1100000000=0x300
        case 0x0: //Первая четверть, выбрать константу из массива в пин A.
        {
            OCR0B = 0;
            OCR0A = pgm_read_byte_near(sineP4 + (incr & 0xFF));
            break;
        }
        case 0x100: //Вторая четверть, выбрать обратнную константу из массива в пин A
        {
            OCR0B = 0;
            OCR0A = pgm_read_byte_near(sineP4 + 255 - (incr & 0xFF));
            break;
        }
        case 0x200: //Третья четверть, выбрать константу из массива в пин B
        {
            OCR0A = 0;
            OCR0B = pgm_read_byte_near(sineP4 + (incr & 0xFF));
            break;
        }
        case 0x300: //Четвертая четверть, выбрать обратнную константу из массива в пин B
        {
            OCR0A = 0;
            OCR0B = pgm_read_byte_near(sineP4 + 255 - (incr & 0xFF));
            break;
        }
        default:
        {
            OCR0A = 0;
            OCR0B = 0;
            break;
        }
    }
    //Фаза 2. Смещена относительно фазы 1 на phaseoffset
    incr = incr + phaseoffset;
    //Уберем биты выще 10го B001111111111=0x3FF, чтобы попасть в массив констант
    incr &= 0x3FF;

    switch (incr & 0x300) { //B1100000000=0x300
        case 0x0: //Первая четверть, выбрать константу из массива в пин A.
        {
            OCR1B = 0;
            OCR1A = pgm_read_byte_near(sineP4 + (incr & 0xFF));
            break;
        }
        case 0x100: //Вторая четверть, выбрать обратнную константу из массива в пин A
        {
            OCR1B = 0;
            OCR1A = pgm_read_byte_near(sineP4 + 255 - (incr & 0xFF));
            break;
        }
        case 0x200: //Третья четверть, выбрать константу из массива в пин B
        {
            OCR1A = 0;
            OCR1B = pgm_read_byte_near(sineP4 + (incr & 0xFF));
            break;
        }
        case 0x300: //Четвертая четверть, выбрать обратнную константу из массива в пин B
        {
            OCR1A = 0;
            OCR1B = pgm_read_byte_near(sineP4 + 255 - (incr & 0xFF));
            break;
        }
        default:
        {
            OCR1A = 0;
            OCR1B = 0;
            break;
        }
    }
    //Фаза 2. Смещена относительно фазы 1 на phaseoffset
    incr = incr + phaseoffset;
    //Уберем биты выще 10го B001111111111=0x3FF, чтобы попасть в массив констант
    incr &= 0x3FF;

    switch (incr & 0x300) { //B1100000000=0x300
        case 0x0: //Первая четверть, выбрать константу из массива в пин A.
        {
            OCR2B = 0;
            OCR2A = pgm_read_byte_near(sineP4 + (incr & 0xFF));
            break;
        }
        case 0x100: //Вторая четверть, выбрать обратнную константу из массива в пин A
        {
            OCR2B = 0;
            OCR2A = pgm_read_byte_near(sineP4 + 255 - (incr & 0xFF));
            break;
        }
        case 0x200: //Третья четверть, выбрать константу из массива в пин B
        {
            OCR2A = 0;
            OCR2B = pgm_read_byte_near(sineP4 + (incr & 0xFF));
            break;
        }
        case 0x300: //Четвертая четверть, выбрать обратнную константу из массива в пин B
        {
            OCR2A = 0;
            OCR2B = pgm_read_byte_near(sineP4 + 255 - (incr & 0xFF));
            break;
        }
        default:
        {
            OCR2A = 0;
            OCR2B = 0;
            break;
        }
    }
}

void display_status() {
    //Отображение на дисплее текущего статуса
    //Первая строка
    //Заполнить значение для LCD частотой dfreq_set. Вывод: Set freq: + частота
    /*lcd.setCursor(0, 0);                      // Устанавливаем курсор в начало 1 строки
    lcd.print("Freq:           ");            // Выводим текст
    lcd.setCursor(6, 0);                      // Устанавливаем курсор в 6 позицию
    lcd.print(dfreq_set);                     // Выводим текст
    lcd.setCursor(10, 0);                     // Устанавливаем курсор в 10 позицию
    if ((reverse & B00000010)) {
        lcd.print("Revers");
    }
    else {
        lcd.print("Direct");  
    }
    lcd.setCursor(0, 1);                      // Устанавливаем курсор в начало 2 строки
    if (run & B00000010) {
        lcd.print("Run             ");            // Выводим текст
    }
    else {
        lcd.print("Stop            ");            // Выводим текст
    }
    */
    //Вывод на экран первого символа
    if ((reverse & B00000010) && (run & B00000010)) {
        disp.display(0,25);
    }
    else if ((reverse & B00000010) && !(run & B00000010)) {
        disp.display(0,23);      
    }
    else if (!(reverse & B00000010) && (run & B00000010)) {
        disp.display(0,24);
    }
    else {
        disp.display(0,22);
    }
    
    //Вывод на экран частоты
    disp.display(1,(dfreq_set%1000)/100);
    disp.display(2,(dfreq_set%100)/10);
    disp.display(3,dfreq_set%10);
    
}

void setup()
{
    Serial.begin(115200);        // connect to the serial port
    Serial.println("Test begin");

    DDRD = 0xFC;  // весь порт D - выход, кроме RX TX
    DDRB = 0xFF;  // весь порт B - выход

    //Настройка и остановка таймеров
    Setup_timer();
    
    dfreq = 9; //минимальнач частота
    //Дя регулировки частоты
    pinMode(ADJ_PIN, INPUT);

    //Кнопка старта
    pinMode(BTN_START_PIN, INPUT_PULLUP);
    //Кнопка реверса
    pinMode(BTN_REVERSE, INPUT_PULLUP);
    //Индикатор
    disp.init();
    disp.point(false);
    disp.set(BRIGHT_TYPICAL); //яркость
    disp.clearDisplay(); //Очистить
    //LCD дисплей
    //lcd.init();                            // Инициализация lcd             
    //lcd.backlight();                       // Включаем подсветку
    display_status();
}

void loop() {
    //PIND |= (1 << 2); //Для замера скорости
    //Реализация для изменения частоты переменным резистором
    if ((inctmr++) % 2048 == 0) { //Обрабатывается каждый 2048-й вызов loop
        adj = (analogRead(ADJ_PIN)+analogRead(ADJ_PIN)+analogRead(ADJ_PIN))/3; //Усредненное значение на пине регулировки частоты
        if ( (abs(adj - setfreq) >= deltafreq  && !(run & B00000010)) || (abs(adj - setfreq) >= deltafreq*4) ){ //Если (остановлено и есть изменение) или (работает и изменение на 4 пункта)
            //Значение аналогового входа изменилось на deltafreq и более.
            //Сначала останавливаем прерывания по таймеру 2, иначе не работает
            TIMSK2 &= ~(1<<TOIE2); //остановка таймера
            //Установить все PWM в 0
            OCR0A = 0;
            OCR0B = 0;
            OCR1A = 0;
            OCR1B = 0;
            OCR2A = 0;
            OCR2B = 0;
            
            //Переведем значение analogRead в частоту
            setfreq = adj;
            dfreq_set = 10 + 2*(setfreq/deltafreq);    // Минимальная частота 10Гц, на каждые 15 пунктов частота увеличивается на 2Гц

            display_status();
            run = B00000000;       //Изменение состояния кнопки на выкл, запрет обработки(если случайно в этот момент нажата)
        }
    }
    if (inctmr % 256 == 0) { //Обрабатывается каждый 256-й вызов loop
        //Нажатие кнопки реверс
        if ( digitalRead(BTN_REVERSE) == LOW && (reverse & B00000001) ){ //Нажата кнопка и нужно менять состояние
            //Останавливаем прерывания по таймеру 2
            TIMSK2 &= ~(1<<TOIE2); //остановка таймера
            //Установить все PWM в 0
            OCR0A = 0;
            OCR0B = 0;
            OCR1A = 0;
            OCR1B = 0;
            OCR2A = 0;
            OCR2B = 0;
            
            if ((reverse & B00000010)) {
                reverse = B00000000;
            }
            else {
                reverse = B00000010;
            }
            run = B00000000;       //Изменение состояния кнопки на выкл, запрет обработки(если случайно в этот момент нажата)
            display_status();
        }
        else if (digitalRead(BTN_REVERSE) == HIGH) { //кнопка отпущена некоторое время
            reverse |= B00000001; //Кнопку можно снова обрабатывать
        }
        //Нажатие кнопки старт/стоп
        if ( digitalRead(BTN_START_PIN) == LOW && (run & B00000001) ){ //Нажата кнопка и нужно менять состояние
            if ((run & B00000010)) { //Надо остановить
                TIMSK2 &= ~(1<<TOIE2); //остановка таймера
                //Установить все PWM в 0
                OCR0A = 0;
                OCR0B = 0;
                OCR1A = 0;
                OCR1B = 0;
                OCR2A = 0;
                OCR2B = 0;
                run = B00000000;       //Изменение состояния на выкл и запрет обработки кнопки
                display_status();
            }
            else {
                phaccu = 0; //
                dfreq = 4;  //Минимальная частота для увеличения до рабочей
                if (reverse & B00000010) {
                    phaseoffset = - PHASE_OFFSET; //Смещение для фазы для реверса
                }
                else {
                    phaseoffset = PHASE_OFFSET; //Смещение для фазы для прямого вращения
                }
                run = B00000010;  //Изменение состояния на вкл и запрет обработки кнопки
                display_status();
                TIMSK2 |=  (1<<TOIE2); //Запуск таймера
            }
        }
        else if (digitalRead(BTN_START_PIN) == HIGH) { //кнопка отпущена некоторое время
            run |= B00000001; //Кнопку можно снова обрабатывать
        }
    }

    
    if ((dfreq < dfreq_set) && (run & B00000010) && (inctmr % (1048576/2) == 0)) { //Плавное увеличение частоты
        dfreq++; //Увеличение частоты

        if (dfreq % 5 == 0 || dfreq == dfreq_set) { //Увеличиваем каждые 5Гц
            delta = 0x100000000 * dfreq / refclk;  // Новое вычисление дельты
        }
        
        //delta = 0x100000000 * dfreq / refclk;  // Новое вычисление дельты
    }
}

В библиотеку TM1637 добавил несколько символов для индикации в старшем разряде текущего состояния.

--- TM1637.cpp.orig	2018-11-10 11:47:08.181946200 +0300
+++ TM1637.cpp	2018-11-11 14:06:05.698749800 +0300
@@ -39,7 +39,8 @@
                             0x66, 0x6d, 0x7d, 0x07,
                             0x7f, 0x6f, 0x77, 0x7c,
                             0x39, 0x5e, 0x79, 0x71,
-                            0x00, 0x40}; //0~9,A,b,C,d,E,F,CLEAR,-
+                            0x00, 0x40, //0~9,A,b,C,d,E,F,CLEAR,- (1-9, 10-17)
+                            0x76, 0x30, 0x1E, 0x38, 0x54, 0x50, 0x37, 0x31}; //H(18), I(19), J(20), L(21), n(22), r(23), П(24), Г(25) -----------AVS--------------
 
 TM1637::TM1637(uint8_t clk, uint8_t data)
 {

 

step64
Offline
Зарегистрирован: 01.04.2013

andrynРад что проект развивается. Вы двигатель на 220 тестировали, силовую сразу на движок цепляли без фильтров? Вч ШИМ 62кГц может похерить изоляцию обмоток движка, да и будут большие динамические потери на таких частотах в МОЩНЫХ игбт,  не зря в пром частотниках частота шим ДО десятка кГц +/- единицы кГц. А вы не хотите как опцию тормозной резистор приделать? Команда стоп -> лог.1 на свободный порт-> ключ-> Резистор. Рекомендую простенькую но эффективную схему защиты от перегруза и кз, повторялась не однократно, есть печатка если надо.

b707
Offline
Зарегистрирован: 26.05.2017

andryn пишет:

Гонял до 200Гц, но понял, что больше 100 сильно падает мощность мотора. Поэтому сейчас регулировка от 10 до 110Гц.

При испытании выявил несколько косяков.

мощность мотора падает, потому что амплитуда генерируемого синуса должна расти с частотой, как я понимаю, а в коде этого нет.

А можно подробнее. какие косяки выявились на испытаниях? - думаю, многим будет интересно, если конечно это не простые описки..

andryn
Offline
Зарегистрирован: 08.06.2018

step64 пишет:

Вы двигатель на 220 тестировали, силовую сразу на движок цепляли без фильтров? Вч ШИМ 62кГц может похерить изоляцию обмоток движка, да и будут большие динамические потери на таких частотах в МОЩНЫХ игбт,  не зря в пром частотниках частота шим ДО десятка кГц +/- единицы кГц. 

Подключал напрямую, без фильтров. Про пробой изоляции не задумывался. Пока не понимаю физику пробоя при высокой частоте. Почитаю про это.

step64 пишет:

А вы не хотите как опцию тормозной резистор приделать? Команда стоп -> лог.1 на свободный порт-> ключ-> Резистор. Рекомендую простенькую но эффективную схему защиты от перегруза и кз, повторялась не однократно, есть печатка если надо.

Для борьбы с перегрузом и КЗ думал поставить измеритель тока после выпрямителя 220В. Тогда можно программно регулировать максимальную мощность.

b707 пишет:

мощность мотора падает, потому что амплитуда генерируемого синуса должна расти с частотой, как я понимаю, а в коде этого нет.

Амплитуда у меня постоянна и равна выпрямленному напряжению 220В. Т.е. повышать ее не куда, если не делать повышающий преобразователь вольт на 500. Можно было бы уменьшать скважность обратно пропорционально частоте, но тогда на номинальных 50Гц будет не полная мощность. В общем я считаю, что обычные 3-х фазные моторы не предназначены и не рассчитаны для вращения со скоростями в 4 раза, превышающими номинальную.

b707 пишет:

А можно подробнее. какие косяки выявились на испытаниях? - думаю, многим будет интересно, если конечно это не простые описки..

Функция генерации работает нормально, в нее исправлений не вносил. Основной косяк был в том, что при работе analogRead начинает прыгать на 25-30 пунктов, в связи с чем мотор останавливался, т.к. алгоритм считал, что повернули ручку регулировки. Еще переделал индикацию на TM1637, т.к. использовать большой экран бессмысленно, потому что не получается его динамически рефрешить. Ну и описки были, конечно :)

 

 

step64
Offline
Зарегистрирован: 01.04.2013

andryn пишет:

Для борьбы с перегрузом и КЗ думал поставить измеритель тока после выпрямителя 220В. Тогда можно программно регулировать максимальную мощность.

  Идея конечно отличная, поставить хотя бы один модуль например ACS712 20A на шину питания и смотреть/контролировать ток на дисплее, тогда и напряжение шины питания надо выводить на дисплей, что бы всё по взрослому было) Интересно, меге 328 это по силам будет всё сразу делать, да ещё успевать мгновенно среагировать на КЗ.

b707
Offline
Зарегистрирован: 26.05.2017

step64 пишет:

 Интересно, меге 328 это по силам будет всё сразу делать, да ещё успевать мгновенно среагировать на КЗ.

не, я конечно понимаю, что чисто из спортивного интереса это классно делать на атмеге328... но вообще практически любой stm32...  даже самый банальный stm32f103 - справится с этим в разы лучше и будет загружен задачей процентов на 20.

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

andryn пишет:

Подключал напрямую, без фильтров. Про пробой изоляции не задумывался. Пока не понимаю физику пробоя при высокой частоте. Почитаю про это.

тут всё просто, механическое повреждение изоляции (перетирает) )))
Вакуумная пропитка кремнийорганическим лаком с последующей сущкой спасает ситуацию

EgorDS
Offline
Зарегистрирован: 08.09.2017

1. Заглянул на данную ветку. На работе занимаюсь ремонтом (модульно) промышленных станций - частотников.  Возможно, я что то упустил, но думаю так: не имеет смысла в таблице синусов первыми выборками назначать 0-0-0. Это всего лишь искривит вашу амлитуду при переходе через 0. Дит-тайм - это пауза в шим-сигнале. Подробнее так: методом шима формируется импульс ( определенной (расчетной) длины в заданное время), после каждого импульса устанавливается Дит-тайм (для IGBT-модулей которые мы применяем 3-3,3 mks) и только после этого может включиться нижний ключ или ключ другой фазы. В промышленных установках часто используют камни TMSxxx где все это реализовано аппаратно. Как сделать это программно на атмеге пока не представляю.

2. Недавно прочитал, правильно использовать "геометрический синус" - или как то так.. Взяв из сети 220В по простой таблице синуса вы обратно не отдадите теже 220/3ф. Как он работает - проще посмотреть картину в гугле. В настоящее время на коленке пишу 3-х фазный генератор синуса (срочно попросили). Если кому нужно - выложу то что получилось...

P/S быть может.. фазный шим поможет (заполнение не должно превышать ~ 99%) . Но кто даст гарантии что все таймеры запустятся синхронно и синронность работы PWM не нарушится... И с другой стороны, в гугле попадались частотники на простеньких камнях - и схемы рабочии (но там точно были драйверы с защитой от сквозных токов). Шим в установках 2500 - 5000 Гц с L-C фильтром. 1500 Гц без фильтра что бы АД не грелся...

Buldakov
Offline
Зарегистрирован: 17.01.2016

По поводу ШИМ для двигателя может быть еще несколько проблем.

Первая -  как уже писалось Если верхний транзистор открыт мы сначала подаем сигнал на закрытие верхнего транзистора. Ждем когда он закроется. И после этого только подаем сигнал на открытие нижнего транзистора. Так сделано в типовой схеме компьютерного блока питания на микросхеме TL494. Иначе пойдет ток короткого замыкания через 2 одновременно открытых транзистора и они сгорят. Время закрытого состояния обоих транзисторов около 4% длительности.

Второе - Допустим мы подаем идеальный ШИМ на двигатель.Но двигатель имеет неидеальные характеристики. Напимер индуктивность одной фазы на 0.0001% больше чем другой. В итоге сердечник двигателя войдет в насыщение по этой фазе и переодически ток насышения будет по этой фазе нарастать (например 1 раз в секунду для одного периода импульса) гораздо быстрее чем на остальных. Допустим частота ШИМ = 1 кгц. ток по истечении 0.5 мс с момента начала импульса ток должен быть 10 А. Но из за насыщения сердечника 1 раз в несколько секунд ток по истечении 0.5 мс   будет 100 А. После этого насыщение убирается и все повторяется заново. Так вот этот ток и палит транзисторы IGBT. Либо ставить транзисторы с импульсным током гораздо большим среднего тока. Либо как то бороться с этим. Но бороться специальными схемными решениями. Контроллер в этом не поможет. Врят ли он сумеет обработать датчик тока и закрыть транзистор. При использовании двигателей маленькой мошности средний ток около 0.5А. Импульсный ток может быть 10А. Такой режим в основном держат все мощные полевые транзисторы или IGBT. При этом проблем не будет. Проблемы будут при мощных движках. Если не делать защит то транзисторы надо будет выбирать на импульсные токи 300 - 500А.

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

SergeiNSK
Offline
Зарегистрирован: 11.03.2015

Всем привет. Ну как ? Работает схема??

SergeiNSK
Offline
Зарегистрирован: 11.03.2015

Выкладывай что получилось.. и продублируй мне на почту пожалуйста!   spgazsystem@yandex.ru 

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

ты забыл ключевое слово "ЩАЖЖЭ!!!"

SergeiNSK
Offline
Зарегистрирован: 11.03.2015

Я тама прописал.. Пажалуйста !!