Таймер 1 «Установить на сравнение совпадений» в обычном режиме — не работает

Я использую аппаратные таймеры модели 328 для управления фазовым углом симистора с обнаружением пересечения нуля. Я столкнулся с каким-то странным поведением при использовании "Set on Compare Match" функция не работает должным образом на Timer1. Я пытаюсь воспроизвести красные "ворота" трассировка здесь: https://playground.arduino.cc/Main/ACPhaseControl/, но с моей собственной реализацией в коде .

Прежде чем построить симистор и схему обнаружения пересечения нуля, я использую другой таймер (Timer0) для имитации прямоугольной волны переменного тока с частотой 120 Гц (удвоенная частота 60 Гц, поскольку импульс с двойным пересечением будет поступать дважды за цикл).

Вывод синтезированной прямоугольной волны переменного тока подается на контакт внешнего прерывания (INT0) с помощью перемычки. Правильный функционал программы — короткий импульс для срабатывания симистора с некоторой фазовой задержкой относительно события прерывания перехода через ноль (другой пример: https://www.homemade-circuits.com/wp-content/uploads/2020/07/half-phase-control.jpg)

Я настраиваю Таймер 1 в обычном режиме и определяю уровни сравнения для OCR1A и OCR1B. OCR1A представляет собой «фазовую задержку». от пересечения нуля, при котором включается симистор, а OCR1B устанавливает длительность импульса, используемого для срабатывания симистора. В моем коде метод 1 работает и дает ожидаемый результат, а метод 2 не работает (выход всегда включен). Для краткости я подчеркиваю различия между двумя приведенными ниже методами, а остальная часть кода такая же.

Способ 1:

  • Задайте для OC1A ничего не делать при сравнении (нормальная работа порта) TCCR1A=0;
  • "Вручную" включите контакт внутри обработчика прерывания для сравнения вывода A. Позже он будет отключен в обработчике прерывания для сравнения вывода B: PORTB=(1<<PORTB1);

Это приводит к следующему (правильному) выводу. На снимке экрана осциллографа желтая кривая представляет собой смоделированную прямоугольную волну переменного тока, а зеленая кривая — напряжение затвора на симисторе. https://i.stack.imgur.com/tpSAw.png

Способ 2:

  • Включите параметр OC1A "Установить при совпадении при сравнении" бит в регистре TCCR1A: TCCR1A |= (1 << COM1A1) | (1 << COM1A0);
  • Теоретически это означает, что мне не нужно вручную включать вывод внутри обработчика прерываний для сравнения A, поскольку он уже включен для нас блоком вывода сравнения совпадений.

https://i.stack.imgur.com/45tFA.png

Обратите внимание, что в обоих методах мне по-прежнему нужно "вручную" отключите вывод внутри обработчика прерывания сравнения вывода B, потому что OC1A и OC1B не являются одним и тем же физическим выводом: PORTB=0;

В методе 2 вывод удерживается во включенном состоянии без каких-либо указаний на то, что обработчик прерывания для сравнения вывода B что-то делает. Моя мысленная модель порта заключается в том, что пока бит в регистре направления данных установлен для этого порта, и блок сравнения вывода, и ЦП могут записывать на вывод. Похоже, что контакт всегда остается включенным, даже после того, как он выключен процессором. Блокирует ли блок сравнения/фиксирует штифт до следующего тактового цикла, или я что-то упустил? Мы будем очень признательны за некоторые советы.

Я могу доказать, что выход действительно подключен к OC1A, потому что OC1A — это PB1, который подключен к осциллографу и переключается в методе 1. Я масштабировал OC1A по вертикали (зеленый) для удобства чтения по сравнению с OC0A.

Я ссылался на раздел 13 https://www.sparkfun.com/datasheets/Components/ SMD/ATMega328.pdf для большей части этой задачи.

Мой код ниже для метода 1 представлен. Чтобы получить метод 2, переключите комментарии в указанных строках.

# определить F_CPU 16000000L

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

/*
Для справки:
OCR1A = уровень_включения_таймера
OCR1B = уровень_таймера_включения + длительность_импульса

*/

интервал timer0_ac_sim_val = 64; // Измеряется в тактах часов с предварительной шкалой = 1024
int turn_on_timer_level = 500; // Измеряется в тактах с prescale=64
интервал_импульса_ширина = 200; // Измеряется в тактах с prescale=64

ISR(INT0_vect){
// ISR при сравнении выхода A таймера 0 включает таймер 1 - имитирует то, что происходит, когда обнаруживается передний фронт пересечения нуля на синусоидальном сигнале переменного тока 120 Гц
TCCR1B |= (1<<CS11) | (1<<CS10);
ТЦНТ1 = 0;
}

ISR(TIMER1_COMPA_vect){
// ISR на выходе сравнения A таймера 1 включает вывод
ПОРТВ = (1<<ПОРТB1); // Включаем пин КОММЕНТАРИЙ К СПОСОБУ 2
}

ISR(TIMER1_COMPB_vect){
TCCR1B = 0; // Выключить таймер
ПОРТВ = 0; // Отключаем пин
}

интервал основной () {

// Настройка таймера 0 для прямоугольной волны 120 Гц на OC0A
DDRD |= (1<<PD6);

TCCR0A = 0x0;
TCCR0A |= (1<<COM0A0) | (1<<WGM01); // Установить вывод для переключения при сравнении

TCCR0B = 0x0;
TCCR0B |= (1<<CS02) | (1<<CS00); // Настроить с предмасштабом 1024 и запустить часы

OCR0A = timer0_ac_sim_val; // Рассчитайте это значение для прямоугольной волны 120 Гц, не изменится.

// Разрешить запуск внешнего прерывания 0 по переднему фронту — установить регистр управления внешним прерыванием
EICRA |= (1<<ISC01) | (1<<ISC00);
EIMSK |= (1<<INT0);

// Глобальное разрешение прерывания
сэй();

// Настройка таймера 1
DDRB |= (1<<PORTB1);

TCCR1A = 0x0; // Нет действия вывода при сравнении вывода
// TCCR1A |= (1 << COM1A1) | (1<<COM1A0); // Устанавливаем OC1A при сравнении совпадений УДАЛИТЬ КОММЕНТАРИЙ ДЛЯ СПОСОБА 2

TCCR1B = 0x0; // Отключить часы

OCR1A = уровень_включения_таймера; // Задержка между подъемом OC0A и срабатыванием OCR1A
OCR1B = уровень_таймера_включения + длительность_импульса; // Задержка между подъемом OC0A и срабатыванием OCR1B

// Разрешить прерывания от таймера 1
TIMSK1 |= (1<< OCIE1A) | (1<<OCIE1B);

в то время как (1) {
};
}

, 👍1


2 ответа


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

0

Моя мысленная модель порта заключается в том, что пока бит в регистре направления данных установлен для этого порта, и блок сравнения вывода, и ЦП могут записывать на вывод.

Ваше предположение неверно. См. раздел 13.11.1 таблицы данных, на которую вы ссылаетесь.

Если один или оба бита COM1A1:0 записываются в единицу, выход OC1A переопределяет нормальную функциональность порта ввода-вывода, к которому он подключен.

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

,

Идеальный! Я пропустил это в таблице данных, когда я читал. Спасибо., @saustinp


0

Для тех, кому это может быть интересно, после устранения проблемы я решил реализовать ту же функциональность, используя режим Fast PWM вместо режима CTC. Пытаясь отладить свою первоначальную проблему, я наткнулся на аналогичный пост, на который один из ответов был:

https://arduino.stackexchange.com/a/30529/89096

Режимы ШИМ управляют контактами на обоих концах цикла.

Суть режимов CTC в том, что они управляют выводом только на одном конце >count, но позволяют явно управлять другим концом.

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

До сих пор я не совсем понимал, что это значит, но это щелкнуло. В режиме PWM аппаратный таймер позаботится о переключении контактов с обеих сторон сигнала. Мы бы использовали режимы Normal или CTC, если бы хотели по-разному управлять одной или обеими сторонами сигнала. Но мое приложение идеально подходит для категории Fast PWM, поэтому я решил реализовать и его. Это экономит ISR, так как ЦП не задействуется "вручную". каждый раз включать и выключать вывод. Единственное отличие состоит в том, что теперь вывод идет на OC1B (PORTB2), а не на OC1A (PORTB1).

#define F_CPU 16000000L

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

int timer0_ac_sim_val = 64;    // Измеряется в тактах часов с предварительной шкалой = 1024
int turn_on_timer_level = 400;    // Измеряется в тактах с prescale=64
int pulse_width = 100;    // Измеряется в тактах с prescale=64

ISR(INT0_vect){
    // ISR при сравнении выхода A таймера 0 включает таймер 1 - имитирует то, что происходит, когда импульс поступает от детектора пересечения нуля
    TCCR1B |= (1<<CS11) | (1<<CS10) | (1<<WGM13) | (1<<WGM12);
    TCNT1 = 0;
}

ISR(TIMER1_COMPA_vect){
    // ISR на выходе сравнения A таймера 1 включает вывод
    TCCR1B = 0; // Выключить таймер
}

int main(){
    // Настройка таймера 0 для прямоугольной волны 120 Гц на OC0A
    DDRD |= (1<<PD6);

    TCCR0A = 0x0;
    TCCR0A |= (1<<COM0A0) | (1<<WGM01);  // Режим переключения - не ШИМ

    TCCR0B = 0x0;
    TCCR0B |= (1<<CS02) | (1<<CS00);     // Настроить с предмасштабом 1024, запустить часы

    OCR0A = timer0_ac_sim_val;

    // Разрешить запуск внешнего прерывания 0 по переднему фронту — установить регистр управления внешним прерыванием
    EICRA |= (1<<ISC01) | (1<<ISC00);
    EIMSK |= (1<<INT0);

    // Разрешить прерывания
    sei();

    // Настройка таймера 1 — разрешение 16 бит с предмасштабом 8 дает максимальный период 1/(16e6/8/65536) = 32,78 мс
    DDRB |= (1<<PORTB2);

    // Установите WGM12 (CTC) в 1 и запустите таймер с предварительным делителем 1024 -> Это запускает таймер в режиме CTC.
    TCCR1A = (1<<COM1B1) | (1<<COM1B0) | (1<<WGM11) | (1<<WGM10);  // Установите COM1B1 и COM1B0 в 1 для инвертирующего режима. Для быстрой ШИМ установите WGM11 и WGM10
    TCCR1B = 0x0;   // Начать с выключенными часами

    OCR1A = turn_on_timer_level + pulse_width;
    OCR1B = turn_on_timer_level;

    // Разрешить прерывания от таймера 1
    TIMSK1 |= (1<<OCIE1A);

    while(1){

    }
,