Всем доброго времени суток. Мне понадобилось сделать на базе ардуино устройство для радиоуправляемой модели, которое бы в случае выключения питания передатчика выставляло бы одну серву в определенное положение. Ниже я привел код. Проблема такая - при достижении переменной counter значения 1000 серва не выставляется в требуемую позицию. Земли приемника, ардуино и сервы соединены. Не могу понять, в чем причина? Помогите пожалуйста найти ошибку. Спойлер: Схема Диод я поставил на всякий случай, для защиты приемника Спойлер: Код Код (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]
По коду. 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, чтобы можно было понять, что Вы делаете.
Спасибо за подробный ответ. Опять забыл volatile . По поводу пункта 3, да, если передатчик работает, то счетчик не успевает досчитать до 1000, постоянно обнуляясь в прерывании. Принцип таков - если по какой-то причине пропала связь (передатчик выключили или просто перестал доходить сигнал), то приемник перестает выдавать импульсы, и через секунду ардуино должна сама повернуть серво. Прочитал про атомарный доступ, спасибо. Но не понял, зачем в моем случае сохранять sreg и запрещать прерывания перед инкрементированием счетчика? Если сигнал есть, то несчастный счетчик постоянно обнуляется и даже если прерывание помешает записи в счетчик, то ему же все равно считать с нуля. delay я применил т.к. не знаю как ардуино использует таймер1, а так бы можно было применить его для задержки. P.S: убрал у себя один баг в схеме - ножку с прерыванием нужно подключать за диодом, чтобы она не реагировала на генерируемый самим контроллером pdm/
Переделал код, учитывая ваши замечания, теперь вылез другой баг - серва нормально управляется с передатчика, через секунду после включения управление пропадает, при этом серва как бы висит неподключенной, можно прокрутить вал пальцами. Ардуино ее тоже в позицию не выставляет, хотя в мониторе видно что счетчик уже дотикал до 1000 и больше. Если включить ардуину при выключенном передатчике то серва как будто хочет стать в позицию, но немного не докручивает до нужного положения и остается "висеть". Если включить передатчик то серва не управляется ни им, ни ардуино (все так же "висит"). Ничего не понимаю...
Там в прерывании идет отключение сервы, а потом в цикле, мы попытаемся отправить ее на определенный угол. Тогда не вижу смысла подключать ее в 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 и по кругу, вариантов масса.