Программа почему-то не работает корректно

Тема в разделе "Arduino & Shields", создана пользователем Altozavr, 11 июн 2016.

  1. Altozavr

    Altozavr Нуб

    Всем доброго времени суток. Мне понадобилось сделать на базе ардуино устройство для радиоуправляемой модели, которое бы в случае выключения питания передатчика выставляло бы одну серву в определенное положение. Ниже я привел код. Проблема такая - при достижении переменной counter значения 1000 серва не выставляется в требуемую позицию. Земли приемника, ардуино и сервы соединены. Не могу понять, в чем причина? Помогите пожалуйста найти ошибку.


    [​IMG]
    Диод я поставил на всякий случай, для защиты приемника

    Код (C++):
    [code]
    //если выключается пульт, управляющие импульсы с приемника пропадают,
    // счетчик достигает требуемого значения и серва должна выставиться в нужную позицию
    //...но этого почему-то не происходит
    #include <Servo.h>

    Servo bservo;

    int ServoPin = 7;//шим-вход сервы на 7 пине
    unsigned int counter;//счетчик

    void setup() {
      Serial.begin(9600);
      bservo.attach(ServoPin);
      pinMode(ServoPin,  OUTPUT);
      attachInterrupt(0, get_change, CHANGE);

    }

    void get_change()
    {
      counter = 0;
      bservo.detach();
    }

    void loop() {
      counter++;
      delay(1);

      if (counter > 1000)
        bservo.write(135);

      Serial.println(counter);

    }
     
    [/code]
     
  2. Alex19

    Alex19 Гуру

    По коду.

    1. Не правильное объявление unsigned int counter; //счетчик.
    Вы используете его в прерывании, поэтому должны использовать такое объявление.
    Код (C++):
    volatile unsigned int counter; //счетчик
    Подробнее тут - http://arduino.ua/ru/prog/Volatile.
    2. В прерывании void get_change() не стоит вызывать bservo.detach(), лучше создать переменную к примеру. Вообще прерывания должны обрабатываться максимально быстро, не стоит туда вставлять лишнего.
    Код (C++):
    volatile byte needDetach = 0;
    В прерывании выставляем needDetach = 1, а в loop добавляем
    Код (C++):
    if (needDetach)
    {
       bservo.detach();
       needDetach = 0;
    }
    3. У Вас прерывание настроено на изменение, получается, что при любом переходе импульса, Вы отключаете серву. Если у Вас обычный пульт, то он возвращает не ШИМ, а PDM и первое изменение будет, через 20 миллисекунд и серва отключается, а в loop мы ждем 1сек, за это время Ваше прерывание будет вызвано 50 раз. Как оно действительно должно работать?
    4. counter++;
    Заменить на
    Код (C++):

    unsigned char _sreg = SREG; cli();  // сохраняем регистр SREG, отключаем прерывания.
    counter++;
    SREG = _sreg; // восстанавливаем регистр SREG.
     
    Атомарный доступ, подробнее тут - http://chipenable.ru/index.php/programming-avr/item/16-atomarnyy-dostup-k-peremennym.html.

    Это основные замечания.
    По мелочи, удалите pinMode(ServoPin, OUTPUT), он не имеет смысла, так как в bservo.attach(ServoPin), сам устанавливает режим пина.
    Так же надо стремиться уходить от delay в loop, хорошая привычка, но об этом писали уже не раз.

    Ответьте на вопрос 3, чтобы можно было понять, что Вы делаете.
     
    Altozavr нравится это.
  3. Altozavr

    Altozavr Нуб

    Спасибо за подробный ответ. Опять забыл volatile :) . По поводу пункта 3, да, если передатчик работает, то счетчик не успевает досчитать до 1000, постоянно обнуляясь в прерывании. Принцип таков - если по какой-то причине пропала связь (передатчик выключили или просто перестал доходить сигнал), то приемник перестает выдавать импульсы, и через секунду ардуино должна сама повернуть серво.

    Прочитал про атомарный доступ, спасибо. Но не понял, зачем в моем случае сохранять sreg и запрещать прерывания перед инкрементированием счетчика? Если сигнал есть, то несчастный счетчик постоянно обнуляется и даже если прерывание помешает записи в счетчик, то ему же все равно считать с нуля.

    delay я применил т.к. не знаю как ардуино использует таймер1, а так бы можно было применить его для задержки.

    P.S: убрал у себя один баг в схеме - ножку с прерыванием нужно подключать за диодом, чтобы она не реагировала на генерируемый самим контроллером pdm/
     
    Последнее редактирование: 12 июн 2016
  4. Altozavr

    Altozavr Нуб

    Переделал код, учитывая ваши замечания, теперь вылез другой баг - серва нормально управляется с передатчика, через секунду после включения управление пропадает, при этом серва как бы висит неподключенной, можно прокрутить вал пальцами. Ардуино ее тоже в позицию не выставляет, хотя в мониторе видно что счетчик уже дотикал до 1000 и больше.
    Если включить ардуину при выключенном передатчике то серва как будто хочет стать в позицию, но немного не докручивает до нужного положения и остается "висеть". Если включить передатчик то серва не управляется ни им, ни ардуино (все так же "висит"). Ничего не понимаю...
     
  5. Alex19

    Alex19 Гуру

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

    Тогда не вижу смысла подключать ее в setup и отключать ее в прерывании. Просто после 1 сек. в if перед тем как отправить серву на определенный угол, подключаем ее bservo.attach(ServoPin);

    Лучше изначально делать все правильно, чтобы потом не отлавливать ошибки, которые отловить очень сложно.

    Для таких вещей не требуется таймер 1, достаточно millis, вот пример - https://www.arduino.cc/en/Tutorial/BlinkWithoutDelay. Таймеры для отсчета, лучше использовать там где требуется очень точный отсчет.

    Сработал bservo.detach();.

    Если включить передатчик сработает bservo.detach();. Что касается о не докручивании, а что будет если просто отправить серву на угол из примера - https://www.arduino.cc/en/Tutorial/Sweep? В loop убираем все, добавляем эту строку myservo.write(135);

    Как написал бы, код сам
    Код (C++):

    // если выключается пульт, управляющие импульсы с приемника пропадают,
    // счетчик достигает требуемого значения и серва должна выставиться в нужную позицию
    // ...но этого почему-то не происходит
    #include <Servo.h>

    #define ServoPin 7 // шим-вход сервы на 7 пине

    #ifndef MC_CRITICAL_SECTION_START
    #define MC_CRITICAL_SECTION_START  unsigned char _sreg = SREG; cli();
    #endif

    #ifndef MC_CRITICAL_SECTION_END
    #define MC_CRITICAL_SECTION_END    SREG = _sreg;
    #endif

    Servo bservo; // переменная сервы

    unsigned long previousMillis = 0;
    const long interval = 1;

    volatile unsigned int counter; //счетчик

    byte finishWork = 0;

    void setup()
    {
      Serial.begin(9600);
      attachInterrupt(0, get_change, CHANGE);
    }

    void get_change()
    {
      counter = 0;
    }

    void loop()
    {
      MC_CRITICAL_SECTION_START
      counter++;
      MC_CRITICAL_SECTION_END

      unsigned long currentMillis = millis();
      if (currentMillis - previousMillis >= interval)
      {
        previousMillis = currentMillis;

        if (counter > 1000 && !finishWork)
        {
          bservo.attach(ServoPin);
          bservo.write(135);
          finishWork = 1;
        }

        Serial.println(counter);
      }
    }
    UPD. Хотя в if лучше ввести переменную, чтобы не дергать attach и write. Код подправил.

    UPD2. А там как хотите, можно сделать весь loop вставить в if с проверкой finishWork или в начале loop поставить if с return или в прерывании finishWork=0 и по кругу, вариантов масса.
     
    Последнее редактирование: 12 июн 2016