Генерация квадратного сигнала 1 кГц с помощью Arduino Mega

Я хочу сгенерировать квадратный сигнал 1 кГц с помощью Arduino Mega.

Я читал, что MCU содержит несколько таймеров. То, что я хочу сделать, это настроить один из этих таймеров.

Мой первый вопрос: есть ли способ автоматически управлять выходным выводом таймера, не выполняя ни одной строки кода в моей программе arduino ? Посмотрите на мой код: мне нужно написать строку в ISR(TIMER1_OVF_vect). Некоторые MCU могут это сделать, но я не нашел никакой информации об этом в документации. (Некоторые контакты подключены к выходам таймера по аппаратной концепции).

Мой второй вопрос: что не так с моими расчетами в моем коде ? Я хочу 1 кГц и получаю 990 Гц

Вот что я сделал:

  • Arduino Mega работает на частоте 16 МГц = 16000 кГц
  • Я установил прескалер a /8, поэтому частота таймера составляет 8000 кГц
  • Частота должна быть разделена на два, потому что у меня есть 2 состояния (ВЫСОКОЕ и НИЗКОЕ): 4000 кГц
  • Interupt срабатывает, когда счетчик достигает 65536. Поэтому, если я загружу 65536-1000 в TCNT1, таймер должен отсчитать 1000 раз, чтобы запустить interupt. Итак, моя частота-4000 кГц/1000 = 1 кГц.

Что случилось ?

Большое спасибо

void setup() {
  pinMode(14,OUTPUT);
  
  noInterrupts();
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 65536 - 1000; 
  TCCR1B |= (1 << CS11);    // Prescaler clock / 8
  TIMSK1 |= (1 << TOIE1);
  interrupts();
}

ISR(TIMER1_OVF_vect)
{
  TCNT1 = 65536 - 1000;
  digitalWrite(14, digitalRead(14) ^ 1);
}

void loop() {

}

, 👍3

Обсуждение

Это на самом деле не отвечает на ваш вопрос, но на самом деле действительно есть способ позволить таймеру автоматически управлять выходным контактом. Вы можете использовать таймер в режиме сравнения выходных данных. Вы записываете значение в соответствующий OCR (регистр) и позволяете таймеру работать от 0 до этого значения. При достижении этого значения соответствующий контакт автоматически переключается, и таймер сбрасывается. Таким образом, вы можете сделать это намного точнее, @chrisl

Вы не должны устанавливать TCNT1, но вам нужно установить ВЕРХНЕЕ значение. Это зависит от того, какой режим ШИМ/таймера вы используете (см. Таблицу в таблице данных). Я знаю, что в UNO это "ICR1" или "OCR1A". Не уверен насчет Мега., @Gerben


2 ответа


Лучший ответ:

3

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

Если вы хотите запустить таймер в обычном режиме и использовать его для планирования прерываний в период, который не является собственным периодом таймера, это возможно, но вы не должны использовать прерывание переполнения для этой цели. Вместо этого вы должны использовать любое из прерываний “сравнить совпадение” и настроить регистр сравнения в IRS, чтобы запланировать следующее прерывание. Например:

void setup() {
    pinMode(14,OUTPUT);

    TCR1A = 0; // нормальный режим
    TCR1B = _BV(CS11); // часы @ F_CPU/8
    TCNT1 = 0; // очистить таймер
    OCR1A = 1000; // первое совпадение COMPA за 1000 циклов
    TIFR1 = _BV(OCF1A); // очистить флаг прерывания
    TIMSK1 = _BV(OCIE1A); // включить прерывание TIMER1_COMPA
}

ISR(TIMER1_COMPA_vect) {
    OCR1A += 1000; // запланировать следующее прерывание
    PINJ = _BV(PJ1); // переключатель PJ1 = цифровой 14
}

Обратите внимание, что строка OCR1A += 1000; может вызвать обертывание, но это не проблема, так как сложение обертывается точно так же, как и сам таймер.

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

Тем не менее, если вам не нужен таймер ни для чего другого , кроме планирования одной периодической задачи (или до четырех задач, разделяющих один и тот же период), то лучшим решением будет позволить таймеру обрабатывать этот период самостоятельно. Настройка таймера в режиме 12 или 14 (CTC или fast ШИМ, с TOP = ICR1), и установить период с ICR1 = 999;. Затем ISR сводится к:

ISR(TIMER1_COMPA_vect) { PINJ = _BV(PJ1); }

и вы экономите несколько циклов. Это было бы моим предпочтительным решением, если бы мне абсолютно необходимо было выводить сигнал на вывод 14.

Обратите внимание, что это решение должно дать вам правильную среднюю частоту, но у вас все равно будет некоторое дрожание, потому что прерывание может иногда задерживаться другими прерываниями. Если вы вольны выбирать выходной вывод, то лучшим решением будет позволить таймеру самостоятельно управлять генерацией ШИМ. Установите таймер в любой режим ШИМ ( самый простой-быстрый ШИМ) и используйте один из генераторов сигналов для генерации ШИМ-сигнала. Таким образом, можно использовать только ШИМ-контакты (помеченные символом “~”). Например, таймер 1 может выводиться на контакты 11 (OC1A), 12 (OC1B) и 13 (OC1C).

Edit отвечая на вопрос:

какова цель TIFR1 = _BV(OCF1A); // очистить флаг прерывания?

Всякий раз, когда значение таймера совпадает с содержимым регистра сравнения OCR1A, флаг прерывания OCF1A (флаг сравнения вывода таймера 1, канал A) поднят. Этот флаг управляет запросом прерывания, когда установлен бит OCIE1A (Output Compare Interrupt Enable 1A). Флаг автоматически очищается при запуске ISR и может быть очищен вручную , записав в него логику 1 (да, это как бы в обратном направлении).

Вполне вероятно, что флаг поднимается в начале процесса запуска, в то время как библиотека Arduino инициализируется. Если флаг поднят при включении прерывания, то немедленно запускается запрос на прерывание. Чтобы избежать этого, общепринятой практикой является снятие флага перед включением прерывания.

,

Спасибо, но какова цель "TIFR1 = _BV(OCF1A); // очистить флаг прерывания" ?, @Bob5421

Вывод 14-это пример, я могу сделать это на любом выводе, @Bob5421

@Bob5421: См.Измененный ответ., @Edgar Bonet

О вашем ответе на "TIFR1 = _BV(OCF1A)": Необходимо ли ставить noInterrupts() / interrupts() в мою функцию настройки ? Большое спасибо, @Bob5421

@Bob5421: Да. noInterrupts() не предотвращает запуск запроса прерывания. Затем прерывание сработает, как только вы вызовете функцию interrupts ()., @Edgar Bonet

Это OCR1A = -1000; интересно-почему отрицательный? Разве это не было бы эквивалентно "OCR1A = 65536 -1000;" или " Первое совпадение COMPA за 64536 циклов`?, @Dave X

@DaveX: OCR1A = -1000; действительно эквивалентно OCR1A = 65536-1000;. Это была моя ошибка. Я смешивал это с идеей, что TCNT1 = -1000; гарантирует, что переполнение срабатывает через 1000 циклов., @Edgar Bonet


4

Мой первый вопрос: есть ли способ автоматически управлять выходным выводом таймера, не выполняя ни одной строки кода в моей программе arduino ?

ДА. Это называется ШИМ с рабочим циклом 50%.

Вам нужно прочитать о 19. Выход Сравните модулятор (OCM1C0A) в таблице данных.

Мой второй вопрос: что не так с моими расчетами в моем коде ? Я хочу 1 кГц и получаю 990 Гц

Вы должны принять во внимание количество циклов процессора, необходимое для начала выполнения вашего ISR. Перед запуском вашего кода есть короткий фрагмент кода, который сохраняет все используемые регистры в стеке и выделяет любое место, необходимое для локальных переменных. Это требует времени и должно быть рассчитано с учетом вашего фактора времени.

,

19. Функция Output Compare Modulator (OCM1C0A) кажется излишней для прямоугольной волны 1 кГц. Это модулирует таймер 1 и таймер 2 вместе на основе OCR1C OCR0A и выводит вывод на B7. Немодулированная квадратная волна 1 кГц может быть выполнена без накладных расходов с помощью таймера 1 в одном из режимов WGM с TOP as ICR1 или OCR1A., @Dave X