Проблема со скетчем ATTiny85 для запуска и остановки камеры по ШИМ-сигналу от радиоприемника

Я пытаюсь запустить камеру с фиксированным интервалом по длинному сервоимпульсу, остановить её по следующему длинному импульсу, снова запустить по следующему длинному импульсу и т. д. Я использую прерывание, чтобы определить, достаточно ли длительности импульса для запуска или остановки камеры, и пытаюсь переключить логическое значение для управления запуском и остановкой. С помощью кода ниже камера начинает срабатывать по первому длинному импульсу, но я не могу остановить её.

Для тестирования я зажигаю светодиод в loop(), а не вызываю fireCamera().

Я буду признателен за любой совет, который вы можете дать.

/*
   Контроллер камеры ATTiny85 для многороторных вертолетов AIS

      ___   ____
      RST 1|o   |8 Vcc  +5V
  Out PB3 2|    |7 PB2  PWM Input
  Out PB4 3|    |6 PB1  NU
      GND 4|____|5 PB0  LED

*/

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

#define LED_PIN PB0                   // PB0 - контакт 5

const int intervalBase = 1000;              // Интервал срабатывания по умолчанию в миллисекундах
const int Shutter = 4;                      // Контакт 4 (PB4) для управления камерой через проводной пульт дистанционного управления
const int Focus = 3;                        // Контакт 3 (PB5) для фокусировки камеры через проводной пульт дистанционного управления
const int pin = 2;                          // Контакт прерывания

unsigned long previousTime = 0;
unsigned long startPeriod = 0;        // Используется в подпрограмме int.
unsigned long pulseWidth = 1100;      // Ширина импульса сигнала на выводе прерывания. Используется в подпрограмме int.

boolean toggle = false;               // Флаг для запуска/остановки срабатывания камеры.
volatile boolean newPulse = false;    // Флаг, который устанавливается, если ширина импульса больше нейтрального значения. Устанавливается в подпрограмме int и считывается в цикле.

void setup() {
  pinMode(LED_PIN, OUTPUT);
  pinMode(pin, INPUT_PULLUP);
  pinMode(Shutter, OUTPUT);
  pinMode(Focus, OUTPUT);
  attachInterrupt(0, calcPulse, HIGH);  // Вызвать calcSwitch, когда вывод прерывания переключается на высокий уровень, чтобы рассчитать длительность импульса
  sei();
}

void loop() {
  if (newPulse) {
    toggle = !toggle;
  }
  if (toggle) {
    digitalWrite(LED_PIN, HIGH);
    delay(1000);
    digitalWrite(LED_PIN, LOW);
    delay(1000);
// fireCamera();
  }
}

void calcPulse() {                    // процедура прерывания
  newPulse = false;
  startPeriod = micros();
  while (digitalRead(pin) == HIGH);
  pulseWidth = (unsigned long)(micros() - startPeriod);
  if (pulseWidth > 1500) {
    newPulse = true;
}
}

void fireCamera() {
  digitalWrite(LED_PIN, HIGH);
  digitalWrite(Focus, HIGH);     // Фокус на
  delay(200);                    // Изменено с 200 для поддержки более коротких интервалов
  digitalWrite(Shutter, HIGH);   // Затвор включен
  delay(200);
  digitalWrite(Shutter, LOW);    // Затвор выключен
  digitalWrite(Focus, LOW);      // Фокус отключен
  digitalWrite(LED_PIN, LOW);
  previousTime = millis();
  while ((unsigned long)(millis() - previousTime) < intervalBase);  //Ждать истечения частоты кадров
}

Обновление:

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

Немного предыстории:

У меня есть скетч Pro Mini, использующий тот же обработчик прерываний (ISR), с которого я изначально начинал (не совсем тот, который я опубликовал) на ATTiny85, и он работает безупречно. Однако он управляется трёхпозиционным переключателем, который задаёт интервал, с которым камера должна срабатывать (выкл., интервал один, интервал два). Он срабатывает при ИЗМЕНЕНИИ. Пытаясь сократить время выполнения ISR, я изменил функцию attachInterrupt() так, чтобы она срабатывала по ВЫСОКОМУ уровню (как мне казалось), чтобы пропустить проверку и проверить, был ли он высоким при входе. Это не помогло, но всё равно работало. То есть, он обнаруживал длительность импульсов > 1500 мкс.

Если я уберу newPulse = false; из обработчика прерываний (ISR), камера будет срабатывать один раз при каждом нажатии кнопки, а не непрерывно. Моя цель — сделать так, чтобы она срабатывала непрерывно до обнаружения следующего newPulse. Тем не менее, тот факт, что она срабатывает при каждом нажатии кнопки, говорит о том, что обработчик прерываний (ISR) работает должным образом.

Я вернулся к своей оригинальной версии ISR. Разницы в поведении камеры между ней и укороченной версией нет.

Проблема, похоже, связана с переключением cameraEnable (ранее toggle) для остановки камеры на втором newPulse.

Я в растерянности.

Вот текущий код:

/*
   Контроллер камеры ATTiny85 для многороторных вертолетов AIS

      ___   ____
      RST 1|o   |8 Vcc  +5V
  Out PB3 2|    |7 PB2  NU
  Out PB4 3|    |6 PB1/Int0/PCInt2 (my PWM Input)
      GND 4|____|5 PB0  LED

*/

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

#define LED_PIN PB0                   // PB0 - Pin 5

const int intervalBase = 1000;              // Default firing interval in milliseconds
const int Shutter = 4;                      // Pin 4 (PB4) to fire camera via wired remote
const int Focus = 3;                        // Pin 3 (PB5) to focus camera via wired remote
const int pulsePin = 2;                     // Interrupt pin

unsigned long previousTime = 0;
unsigned long startPeriod = 0;        // Used in int routine.
unsigned long pulseWidth = 1100;      // Width of pulse on pulsePin. Used in int routine.

boolean cameraEnable = false;               // Flag to start/stop camera firing.
volatile boolean newPulse = false;    // Flag that is set if pulse width is > than neutral.  Set in int routine and read in loop.
void setup() {
  pinMode(LED_PIN, OUTPUT);
  pinMode(pulsePin, INPUT_PULLUP);
  pinMode(Shutter, OUTPUT);
  pinMode(Focus, OUTPUT);
  attachInterrupt(0, calcPulse, CHANGE);  // Call calcPulse to calculate the pulse length when pulsePin toggles high
}

void loop() {
  if (newPulse) {
    cameraEnable = !cameraEnable;
  }
  if (cameraEnable) {
   fireCamera();
  }
}

void calcPulse() {                    // interrupt routine
  newPulse = false;
  if(digitalRead(pulsePin) == HIGH){                //starts the PW measurement 
    startPeriod = micros();
  }
  else{
    if(startPeriod && (newPulse == false)){ //ends the PW measurement
      pulseWidth = (unsigned long)(micros() - startPeriod);
      startPeriod = 0;
    }
  }
  if(pulseWidth > 1500){
    newPulse = true;
  }
}  


void fireCamera() {
    digitalWrite(LED_PIN, HIGH);
    delay(500);
    digitalWrite(LED_PIN, LOW);
    delay(500);
/*  digitalWrite(LED_PIN, HIGH);
  digitalWrite(Focus, HIGH);     // Focus on
  delay(200);                    // Changed from 200 to accomodate shorter intervals
  digitalWrite(Shutter, HIGH);   // Shutter on
  delay(200);
  digitalWrite(Shutter, LOW);    // Shutter off
  digitalWrite(Focus, LOW);      // Focus off
  digitalWrite(LED_PIN, LOW);
  previousTime = millis();
  while ((unsigned long)(millis() - previousTime) < intervalBase);  //Wait for frame rate to elapse
  */
}

**Еще одно обновление** Я пока не придумал, как правильно ответить на ваши предложения. Если это не так, пожалуйста, дайте мне знать.

Вот где я был до ваших последних предложений:

void loop() {
  if (cameraEnable) {
   fireCamera();
  }
}

void calcPulse() {                                           // ISR срабатывает при изменении контакта
  newPulse = false;
  if(digitalRead(pulsePin) == HIGH){                         // если изменение было от низкого к высокому
    startPeriod = micros();                                  // начинается измерение, затем ISR заканчивается
  }
  else{                                                      // Изменение было от высокого к низкому
    if(startPeriod && (newPulse == false)){                  // поэтому измерение заканчивается,
      pulseWidth = (unsigned long)(micros() - startPeriod);  // ширина импульса зафиксирована,
      startPeriod = 0;                                       // startPeriod сбрасывается,
      newPulse = true;
      if(pulseWidth > 1500){
        cameraEnable = !cameraEnable;
      }
    }
  }
}

Это работало, но спорадически.

По совету 6v6gt я удалил newPulse. Он тоже иногда работал.

Затем я использовал последнее предложение Эдгара для ISR, и оно тоже сработало спорадически.

Эти наброски должны были сработать, поэтому я заподозрил, что с импульсами, исходящими от дрона, что-то не так.

Я написал простой скетч Nano для замены сигнала сервопривода дрона и подключил его к плате ATTiny с последней версией скетча. Всё работает, как заявлено! Сравнив два сигнала, я обнаружил, что длительность отправки импульсов дрона составляет около 0,250 секунды, в то время как длительность, которую я произвольно использовал в скетче сервопривода, составляла 0,750 секунды. Я поэкспериментировал с длительностью в скетче сервопривода и обнаружил, что всё, что меньше 0,50 секунды, приводило к нестабильной работе платы ATTiny. Вот и всё.

Я ценю время, которое вы потратили, помогая мне решить эту проблему, и все ваши полезные советы. Спасибо ещё раз!

-Дон

, 👍1

Обсуждение

Итак. Итак, система должна дождаться длинного импульса, после чего камера должна непрерывно срабатывать, выполняя последовательность действий до следующего длинного импульса. В этот момент срабатывание камеры должно прекратиться, и цикл повторится. Из первого описания это было не совсем понятно. Если вы имитируете такой короткий период (1500 мкс в коде, но в тексте указано 1500 мс) нажатием кнопки, то дребезг должен быть хорошим. Я бы предпочёл решение на основе конечного автомата., @6v6gt

Вы выбрали лишь некоторые из моих советов, и ни один из них, похоже, не решил вашу проблему. Это ожидаемо. Пожалуйста, попробуйте применить их **все вместе** и сообщите нам, что получилось., @Edgar Bonet

Извините, 6v6gt, это 1500 мкс. Я не имитирую импульс, а получаю его от контроллера полёта дрона при нажатии кнопки на пульте управления. Импульсы имеют длительность 1100 мкс с частотой 50 Гц до нажатия кнопки, затем один импульс 1700 мкс, а затем снова 1100 мкс до повторного нажатия кнопки. Импульсы чёткие и стабильные., @Don

Спасибо, Эдгар. Думаю, второй скетч, который я опубликовал, учитывает все ваши предложения, за исключением установки значения newPulse в false в цикле, из-за чего камера срабатывала только один раз при каждом новом импульсе. Думаю, что возвращение calcPulse к изначальному варианту (я изменил его в первом скетче, который я отправил), позволяет захватить нарастающий фронт, уйти и вернуться по спадающему фронту. Как я уже упоминал, это хорошо подходит для измерения длительности импульса на Pro Mini. Я попробую свой скетч на Pro Mini, чтобы можно было использовать serial.print() для отслеживания происходящего., @Don

И ещё раз спасибо, 6v6gt. Кстати, я помню ваше имя пользователя. Я реставрирую радиоприёмники 20-х, 30-х и 40-х годов. Вы любитель радиоприёмников или усилителей?, @Don

Хорошо. Это было важно. Если за длинным импульсом следует серия коротких импульсов частотой 50 Гц, то каждый из чередующихся длинных импульсов будет потерян, поскольку newPulse будет установлен в false в calcPulse() через 20 мс, к моменту поступления следующего короткого импульса, пока активны операторы delay() для мигания светодиода. Усилители: https://forum.arduino.cc/t/are-there-any-young-arduino-enthusiasts/1026108/13?u=6v6gt, @6v6gt


1 ответ


1

В этом коде есть несколько проблем. Я не уверен, как именно. В совокупности они формируют то поведение, которое вы испытываете. В любом случае, вот некоторые моменты, которые я предлагаю вам исправить:

  • Константа pin названа неудачно, поскольку существует три константы Идентификация номеров контактов. Возможно, PWMinput будет более явным. То же самое. для переменной toggle: cameraActive было бы понятнее.

  • Большинство переменных могут быть локальными для единственной функции, использующей их, что сделало бы код более читабельным.

  • Третий аргумент attachInterrupt() должен быть CHANGE, ПАДЕНИЕ или РАСТ. Значение ВЫСОКИЙ идентично значению ИЗМЕНЕНИЕ. что, вероятно, не то, что вам нужно.

  • Не нужно вызывать sei(): ядро Arduino уже позаботилось об этом это.

  • Как заметил 6v6gt, не полагайтесь на настройку calcPulse() newPulse на false, так как это делает вашу программу слишком зависимой от тайминги. Вместо этого установите значение false в loop(), сразу после переключения переменная переключить.

  • Функция calcPulse() блокируется на всю длительность импульса, чего никогда не следует делать в контексте прерывания: это заблокирует прерывание таймера, используемое ядром Arduino для отслеживания времени и создания millis() и delay() работают. Процедура обработки прерываний должна срабатывать на обоих фронтах импульса и устанавливать только startPeriod (по восходящему фронту edge) или newPulse (на заднем фронте) и немедленно вернуться.

На более высоком уровне мне интересно, как вы планируете послать импульс ATtiny. Если вы используете пульт дистанционного управления, сможете ли вы настроить управление выше нейтрального только на протяжении одного импульса (20 мс)? Если пульт дистанционного управления посылает несколько последовательных импульсов длительностью более 1,5 мс, ваш ATtiny может не оправдать ваших ожиданий.


Изменение: Что касается вашего обновлённого кода, я предлагаю ещё несколько изменений:

  • Как заметил 6v6gt, не полагайтесь на настройку calcPulse() newPulse на false, так как это делает вашу программу слишком зависимой от времени. Вместо этого установите значение false в loop(), сразу после переключения переменная cameraEnable (которая должна называться «cameraEnabled»). Я Возможно, я здесь повторяюсь.

  • Вычисление ширины импульса не должно зависеть от newPulse == false. Нет причин делать это: каждый импульс должен быть измерен.

  • Не используйте startPeriod в логическом контексте: значение 0 является совершенно допустимое возвращаемое значение для micros().

Вот версия calcPulse(), в которой удалено все ненужное:

void calcPulse() {
    unsigned long now = micros();
    if (digitalRead(pulsePin) == HIGH) {  // начинается импульс
        pulseStartTime = now;
    } else {                              // импульс заканчивается
        unsigned long pulseLength = now - pulseStartTime;
        if (pulseLength > 1500) {
            newPulse = true;
        }
    }
}

Я только что заметил, что переменная newPulse на самом деле не нужна: Обработчик прерываний мог бы просто позаботиться о переключении cameraEnabled. Это также следует подразумевать loop():

unsigned long pulseStartTime;
volatile bool cameraEnabled = false;  // камера снимает?

// Обработчик прерываний для CHANGE в pulsePin.
void calcPulse() {
    unsigned long now = micros();
    if (digitalRead(pulsePin) == HIGH) {  // начинается импульс
        pulseStartTime = now;
    } else {                              // импульс заканчивается
        unsigned long pulseLength = now - pulseStartTime;
        if (pulseLength > 1500) {
            cameraEnabled = !cameraEnabled;
        }
    }
}

void loop() {
    if (cameraEnabled) {
        // Пожарная камера.
        digitalWrite(LED_PIN, HIGH);
        delay(500);
        digitalWrite(LED_PIN, LOW);
        delay(500);
    }
}
,

Похоже, это хороший и всесторонний анализ скетча. Помимо попытки вытащить OP из текущей дыры, я даже не знаю, с чего начать предложения по улучшению. Кстати, раз уж вы об этом упомянули, я никогда не видел "HIGH" в качестве параметра для attachInterrupt(), но он определённо скомпилировался на ATtiny85., @6v6gt

Спасибо вам обоим за советы. Позвольте мне начать с ответа на вопрос Эдгара «более высокого уровня». Эти импульсы — действительно стандартные сигналы сервопривода, поступающие с полётного контроллера. Они запускаются нажатием кнопки на пульте управления. Полётный контроллер настроен на выдачу импульса длительностью 11 мс с частотой 50 Гц до нажатия кнопки, а затем одиночного импульса длительностью 17 мс. Я отвечу вам позже, чтобы обсудить остальное., @Don