Управление двигателем через таймер и прерывания с синхронизацией по задаче - как?

Тема в разделе "Arduino & Shields", создана пользователем Yoda, 10 июн 2013.

  1. Yoda

    Yoda Нерд

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

    Встала задача по синхронизации оборотов двигателя с задачей.
    Вводные задачи:
    1. Есть выходной пин, управляемый по таймеру (1), который управляет частотой вращения двигателя. В теории 1 импульс (+5V) c ардуинки равен одному обороту двигателя. Реально не так (там на валу стоит 6 гранная призма, значит обороты будут в 6 раз меньше), но это пока не важно.
    2. Есть оптопара, фиксирующая оборот, сигнал (+5V) заведен на цифровой вход ардуинки. Вызывает прерывание и назначение текущего времени в переменную для вычисления времени требуемого на оборот (фактическое). Если быть точным - оптопара ловит шесть сработок за оборот (6 углов призмы).
    3. Есть некий синхросигнал, который подается на один из пинов Ардуинки. Вызывает прерывание и назначение текущего времени в переменную для вычисления времени требуемого на оборот (задача).
    Нужно подстроить фактические обороты двигателя по входному синхросигналу. Пока хрен с ним, с законами регулирования, мне бы пока в простом виде - регулирование по отклонению "один в один" сделать. (один синхросигнал должен соответствовать одному углу на призме)
    Вот код (упростил и постарался прокомментировать):
    Код (C):
    #include <interrupt.h>
    #define HI(x) ((x)>>8)
    #define LO(x) ((x)& 0xFF)

    //не все переменные использую - наброски на будущее
    volatile int DV1T1=0; //переменная TIME1 для синхронизации двигателя 1
    volatile int DV1T2=0; //переменная TIME2 для синхронизации двигателя 1
    volatile int DV1S1=0; //переменная TIME1 для синхронизации сигнала частоты 1
    volatile int DV1S2=0; //переменная TIME2 для синхронизации сигнала частоты 1
    int DV1NEV1 = 0; //невязка 1 управления двигателя 1 с задачей
    int DV1NEV2 = 0; //невязка 2 управления двигателя 1 с задачей
    int DV1REG1 = 0; //задача 1 управления двигателя 1  
    int DV1REG2 = 0; //задача 2 управления двигателя 1
     
    ISR(TIMER1_COMPA_vect)//обработчик прерывания по совпадению А таймера1
    {
      TCNT1H=0;//обнуляем регистр TCNT1
      TCNT1L=0;
    }

    // Обработчик прерываний (сработки датчиков оптопар, синхросигналы)
    ISR(PCINT2_vect) {
      //если пришел сигнал на пин digital2 - ловим задающий синхросигнал
      if (PIND & 0x11) {
          //если пришел синхросигнал 1  задачи
          DV1S1 = DV1S2;
          DV1S2 = micros();//раз в 70 часов будет скидываться и механизм синхронизации будет давать ошибку - придумать что-нибудь
        }
        else  if (PIND & 0x12) {
          //если сработало прерывание по оптопаре двигателя 1 (пин digital3), рассчитываем новое значение регулировки двигателя 1
          DV1T1 = DV1T2;
          DV1T2 = micros();//раз в 70 часов будет скидываться и механизм синхронизации будет давать ошибку - придумать что-нибудь
        }
    }

    // функция начальной установки - работает один раз - или постоянно по кругу выполняется????
    void setup() {             
      cli(); //Запрещаем прерывания  
        //таймеры
        DDRB = 0x02;//настраиваем OC1A как выход  - PB1 ????? волшебные числа
        PORTB = 0x00;
        TCCR1A = 0x40;//при совпадении уровень OC1A меняется на противоположный   - COM1A0/COM1B0 ????? волшебные числа
        //для 60 герц считаем:  16 000 000 (частота камня) / 64 (пределитель) / 60 (герц на выходе) * 6 (зеркал на призме) / 2 (установка 0 и 1) = 12500
          //мне было бы оптимальнее использовать пределитель 256, но пока для отладки оставлю так...
        TCCR1B = (1<<CS11) | (1<<CS12); // CS12 - 256 / CS11|CS12 - 64  ????? CS10|CS11 - 64
        TCNT1 = 5;
        OCR1AH = HI(12500);//12500 60hz
        OCR1AL = LO(12500);//12500 60hz
        TIMSK1 = 0x12;//разрешаем прерывание по совпадению  - OCIE1B OCIE1A  ????? волшебные числа
      sei();//разрешаем прерывания глобально
      delay (2000); //ждем пока раскрутится основной движок
      //теперь добавим прерывания от датчиков и синхросигналов
      cli(); //Запрещаем прерывания  
        //interrupts - выставим прерывания (сработки датчиков и приход сихросигналов)
        PCMSK2 |= _BV(PCINT18);  //DigitalOut 2 на плате Ардуино - для синхросигнала 1  
        PCMSK2 |= _BV(PCINT19);  //DigitalOut 3 на плате Ардуино - для сигнала двигателя 1 - оптопара
        PCICR  |= _BV(PCIE2);
        PCIFR |= _BV(PCIF2);
      sei();//разрешаем прерывания глобально
    }


    // основная программа - должна постоянно выполняться в цикле (по идее)
    void loop() {

      TIMSK1 = 0x00; //Запрещаем прерывания по таймеру
    //далее формула приведена только для наглядности. в реалии у неё вид DV1NEV2 = (DV1S2 - DV1S1) / ХХ (ХХ- вычисленная константа)
        DV1NEV2 = (DV1S2 - DV1S1) * 16000 / 64 * 6 / 2; //пока хотя бы так. В идеале тут должна вычисляться невязка с оптопарой - но даже так не работает!!!!
        OCR1AH = HI(DV1NEV2);
        OCR1AL = LO(DV1NEV2);
      TIMSK1 = 0x12;//разрешаем прерывания по таймеру
    }
    Пока работает ХРЕНОВО. Обороты прыгают хаотично при постоянной частоте задачи. При подаче другой частоты - непонятно, выполняется подстройка или нет из-за этих дурных "прыжков" оборотов. Такое ощущение, что либо коряво отрабатывают прерывания, либо таймер постоянно переопределяется, либо программа выполняется по циклу (функция setup похоже отрабатывает многократно) Может кто ошибку увидит в программе? Ваши предложения?
     
  2. Yoda

    Yoda Нерд

    Да, еще забыл. У меня в реальности будет два двигателя (то есть использовать буду таймер1 и таймер2), и по каждому будет по 2 прерываниия - всего 4 прерывания (то есть EXT1 и EXT2 не могу использовать - двух прерываний не хватает, пришлось использовать прерывания PinChange).
    Дребезг (с оптопары и с сихросигналов) в схеме вроде исключал RC цепочкой и микросхемой-триггером, но поведение регулируемого движка не изменилось. И даже при исчезновении синхросигналов, движок все равно "колбасит".
     
  3. Yoda

    Yoda Нерд

    Не понял каким образом, но мне пришел ответ
    "Исходя из описания, я понял Вы хотите смастерить шаговый двигатель? так купите готовый и не нужно так извращаться.." от пользователя Mitrandir. - какой-то глюк форума (я не вижу тут ответа)
    Так вот - отвечаю. Я не мастерю шаговый двигатель. Я знаю что такое шаговый двигатель, их характеристики. Мне ШД не поможет (даже по характеристикам не подойдет - 60 герц это пока для пристрелки, будут килогерцы). Спросите "для чего нужна оптопара и внешняя синхронизация" - отвечу.
    Оптопара нужна для учета РЕАЛЬНЫХ оборотов двигателя (трение, просадка напруги и прочая, прочая). А мне нужно ТОЧНО синхронизироваться с входящим синхросигналом (это другое внешнее устройство). В идеале у меня потом будет реализована оптимальная функция по управлению (ТАУ - я тебя ненавижу), но сейчас хотя-бы по элементарной разнице синхронизироваться.
     
  4. Mitrandir

    Mitrandir Гуру

    я его уже удалил, так как предугадал такой ответ, и понял что был не прав
     
  5. Yoda

    Yoda Нерд

    :) спасибо за участие.
    А по программе будут какие-то мысли?
    Функция SETUP выполняется в цикле или однократно?
     
  6. warman

    warman Гик

    думаю стоит делать сравнение частоты прихода синхро импульсов под нагрузкой с частотой импульсов без нагрузки. у любого двигателя по идее должна быть кривая частоты вращения от приложенного тока/напряжения.
     
  7. Yoda

    Yoda Нерд

    А для чего мне эта кривая? Я вроде тут факт пытаюсь поймать...
    Нет, конечно сейчас для обкатки механики я пока из программы выкину вообще всю синхронизацию, и управление оборотами сделаю по резистору на аналоговом входе. Но это ОЧЕНЬ плохо. У меня будет убегать - как я точно обороты не задам. Мне надо обеспечить хотя-бы два часа идеального попадания в синхроимпульсы....
    При ручном регулировании этого не достичь.
     
  8. warman

    warman Гик

    Зная напряжение на моторе, по кривой определяем сколько об/сек ему соответствует. Раз у вас 6-гранная призма, умножаем полученное значение на 6. Получим, например, для 1000 об/сек = 6000Гц. А с оптопары приходит, например, 5998Гц, что говорит о том, что двигатель вращается медленнее на 2Гц, то есть на 2/6=0,33 об/сек и для восстановления режима работы надо подать увеличенное напряжение, которое берем с кривой для 1000,33 об/сек.
     
  9. Yoda

    Yoda Нерд

    Нет, мне ответ КАК УПРАВЛЯТЬ ДВИГАТЕЛЕМ - известен. В моем случае - это меняется параметр в таймере-счетчике - я заставляю контроллер подавать больше или меньше тактирующих импульсов на управляющую плату двигателя. Двигатель - бесколлекторный, с готовой платой управления. Управлять им я умею.
    Я не могу увязать обороты движка с входящими синхроимпульсами. Я дал свою прогу, как я это реализовал сейчас. Но работает неправильно. Может у вас есть другой вариант реализации данной задачи.
    Я специально не связывался с DIGITALWRITE и DIGITALREAD (кажется так), мне в этой задаче дорого процессорное время (вроде работает не оптимально, да и прозевать импульс могу). До АСМа я еще не дорос, Си должно хватить.
     
  10. warman

    warman Гик

    тогда ставьте на вал двигателя датчик положения вала. будете знать, на сколько отстал двигатель и потом внесете поправку, увеличив/уменьшив на непродолжительное время обороты, пока вал не догонит/притормозится до совпадения с синхроимпульсами.
     
  11. Yoda

    Yoda Нерд

    Именно для этого УЖЕ стоит оптопара(и уже обрабатывается отдельным прерыванием). Я могу получить разницу. У меня не работает программа, в которой я это УЖЕ предусмотрел.
    Перечитал первый пост - небольшое уточнение пункта 2 условий. Оптопара ловит каждый угол грани(а не полный оборот, как можно подумать). То есть шесть граней, шесть сработок оптопары, шесть синхросигналов к которым нужно привязаться. (откорректировал свой первый пост, чтобы не вводить в заблуждение последующих потенциальных помощников)
     
  12. warman

    warman Гик

    Код (C):

    while(optopin==0)
    {
      пока не пришел импульс с оптодатчика считаем миллисекунды
      если(время > половины интервала синхроимпульсов)
      {
        притормаживаем двигатель
      }
      иначе
      {
        прибавляем ненадолго оборотов
      }
    }
    обнуляем счетчик времени
     
     
  13. Yoda

    Yoda Нерд

    Это хорошо крутиться в одном цикле, НО:
    1. У меня есть задающий сихросигнал, который нельзя "проспать", если программа находится не в том месте
    2. У меня есть сигнал с оптопары, который нельзя "проспать", если программа находится не в том месте
    3. У меня БУДЕТ задающий сихросигнал для двигателя2, который нельзя "проспать", если программа находится не в том месте(синхро с другой частотой)
    4. У меня БУДЕТ сигнал с оптопары двигателя2, который нельзя "проспать", если программа находится не в том месте

    Вы уверены, что цикл while в процедуре loop успеет всё это обработать, когда у меня одна из частот - килогерцы (это при теоретических 16 мегагерцах на камне, а в реальности и того наверное меньше) ???

    Скетч в первом сообщении - мой целиком. Я его если и утащил по кусочкам из инета, то целиком его понимаю. И он у меня работает. Только плохо. "Многозадачность" на микроконтроллере IMHO можно только прерываниями достичь...
     
  14. Unixon

    Unixon Оракул Модератор

    Код (C):

    DV1NEV2 = (DV1S2 - DV1S1) * 16000 / 64 * 6 / 2;
     
    Это же АД :confused: Так нельзя делать. Во-первых, вам не хватит разрядности. Во-вторых, вы утонете в ошибках округления. Тип int здесь это всего 16 бит. Оптимизируйте формулу так, чтобы число всегда, после каждой арифметической операции, было не более MAXINT=32767. Если не получится - переходите на long int (32 bit). Ну и приоритет одноранговых операций скобками вручную, а то непонятно что за формула в итоге вычисляется.
     
  15. Yoda

    Yoda Нерд

    Согласен. У меня была такая мысль. В одном из вариантов, я упрощал формулу до вида DV1NEV2 = (DV1S2 - DV1S1) / ХХ (ХХ- вычисленная константа). (здесь я оставил как есть для наглядности - надо коммент в скетч вставить) Но у меня один фиг не меняло общего поведения регулируемого движка. Про конечное число - подумаю, посчитаю, сколько у меня там получается. Сегодня вечером буду проверять...
    Я повторю свой вопрос: Функция SETUP выполняется в цикле или однократно?
     
  16. warman

    warman Гик

    однократно при запуске МК
     
  17. Yoda

    Yoda Нерд

    Тогда - я в панике, кэп! У меня движок ведет себя так:
    1. Инициализация, движок выходит на рабочие обороты.
    2. Потом начинается ахинея. Движок может крутиться, может не крутиться - его несистемно колбасит. Обороты могут упасть до нуля. Могут подняться до рабочей частоты. Причем это происходит непериодично и мною необъяснимо.

    Слава богу, я поставил RC фильтр и триггер Шмитта на оптопару и на синхросигнал, а то у меня еще при 60 герцах он взлететь пытался (визуально до 5 тысяч оборотов раскручивался)
     
  18. warman

    warman Гик

    вариант 1 - просадка напряжения в цепях датчиков, либо наводимая помеха на них.
    вариант 2 - нестабильный источник питания силовой части.
    организуйте в программе простейший дебаг - в последовательный порт скидывайте интервалы срабатывания таймера. где то там собака зарыта
     
  19. Unixon

    Unixon Оракул Модератор

    А что говорит отладочный вывод?
     
  20. roggedhorse

    roggedhorse Гик

    вы учитываете, что обработчик PCINT вызывается дважды ? Поскольку это Pin Change Interrupt