Таймер 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) { }; }
@saustinp, 👍1
2 ответа
Лучший ответ:
Моя мысленная модель порта заключается в том, что пока бит в регистре направления данных установлен для этого порта, и блок сравнения вывода, и ЦП могут записывать на вывод.
Ваше предположение неверно. См. раздел 13.11.1 таблицы данных, на которую вы ссылаетесь.
Если один или оба бита COM1A1:0 записываются в единицу, выход OC1A переопределяет нормальную функциональность порта ввода-вывода, к которому он подключен.
Таким образом, "нормальный" переопределяемая функциональность не позволяет вам самостоятельно изменить вывод. Вам нужно будет установить эти биты в ноль в ISR, чтобы восстановить использование порта, и установить их обратно в единицу, когда это необходимо.
Для тех, кому это может быть интересно, после устранения проблемы я решил реализовать ту же функциональность, используя режим 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){
}
- Заставить TCNT оставаться ниже OCRxA на ATmega328P
- Генерация стабильной частоты
- Заменить предохранители Arduino Uno (может ли Arduino Uno заменить свои собственные предохранители?)
- Использование Arduino Nano для программирования (как ISP) автономного 328p
- Как работает стирание EEPROM?
- Запустить Timer1 в ATmega2560 со сборкой
- Изменчивая переменная не обновляется с таймера ISR
- ATmega328P - проблема с использованием таймера 2 для генерации тона
Идеальный! Я пропустил это в таблице данных, когда я читал. Спасибо., @saustinp