Нежелательное переключение на OC1A
Приведенный ниже код для ATmega328P (Arduino Pro Mini 5 В @ 16 МГц) создает периодическую последовательность на выводах таймера 1 OC1A и OC1B:
#include "Arduino.h"
// Для Arduino Pro Mini 5 В @ 16 МГц
// Предделитель 1 для наивысшего разрешения (1 тактовый импульс = 62,5 нс)
// Для достижения задержек до 4,096 мс требуется таймер 1 (16 бит)
const uint8_t OC1A_PIN = 9;
const uint8_t OC1B_PIN = 10;
const uint16_t TIME_B_US = 1000; // 1 миллисекунда для импульса сброса на OC1B
const uint16_t TIME_A_US = 1000; // длительность импульса 1 миллисекунда на OC1A
const uint16_t REPETITION_PERIOD_US = 10000; // период повторения последовательности в микросекундах
bool inverse_polarity_A = false; // При значении true OC1A включает принудительное включение OC1B при запуске!!???
uint32_t _period_start_timestamp_us;
uint8_t _flip_A_cnt;
uint8_t FLIP_A_CNT_MAX = 3;
void setup() {
pinMode(OC1A_PIN, OUTPUT);
digitalWrite(OC1A_PIN, inverse_polarity_A); //устанавливаем сигнальный вывод OC1A в исходное состояние
pinMode(OC1B_PIN, OUTPUT);
digitalWrite(OC1B_PIN, false); //устанавливаем вывод сигнала SYNC в исходное состояние (выкл)
cli();
TIFR1 = (1 << OCF1A); // очистить ожидающее прерывание канала A
TIFR1 |= (1 << OCF1B); // очистить ожидающее прерывание канала B
TIMSK1 = (1 << OCIE1A); // Включить прерывание сравнения выходов для сигнала на OC1A
TCCR1A = 0;
TCCR1B = (1 << WGM12); // включить режим CTC и остановить таймер 1
TCNT1 = 0;
sei();
}
void loop() {
uint32_t now_us = micros();
if (now_us - _period_start_timestamp_us >= REPETITION_PERIOD_US) {
_period_start_timestamp_us = now_us;
send_sequence(); // Использует таймер 1
}
}
void send_sequence() {
_flip_A_cnt = 0;
TCCR1A = (1 << COM1B0); // Включить переключение OC1B при сравнении совпадений
TCCR1A |= (1 << COM1A0); // Включить переключение OC1A при сравнении совпадений
OCR1B = TIME_B_US << 4; // * 16, чтобы получить время в микросекундах (16 * 62,5 нс = 1 мкс)
OCR1A = TIME_B_US << 4;
TCNT1 = 0;
TCCR1C = (1 << FOC1B); // Устанавливаем OC1B на высокий уровень.
// TCCR1C |= (1 << FOC1A); // Добавление этого предотвращает неправильное переключение OC1A в самой первой последовательности
// когда inverse_polarity_A == true, но тогда ошибка возникает во всех последующих последовательностях!!
TCCR1B |= (1 << CS10); // запустить таймер с предделителем 1, чтобы получить самое высокое разрешение (1 тактовый цикл = 62,5 нс)
}
// Для сигнала на OC1A
ISR(TIMER1_COMPA_vect) {
TCCR1A &= ~(1 << COM1B0); // Отключить переключатель Compare Match для канала B
if (_flip_A_cnt < FLIP_A_CNT_MAX) {
OCR1A = TIME_A_US << 4;
_flip_A_cnt++;
}
else {
_flip_A_cnt = 0; // сброс
TCCR1B &= ~(1 << CS10); // Очищаем бит CS10, чтобы остановить таймер 1
}
}
Это отлично работает для случая «inverse_polarity_A = false», как показано на рисунке ниже: верхний синий сигнал — это выход OC1B, нижний желтый сигнал — это выход OC1A.

При установке «inverse_polarity_A = true» желтый сигнал на OC1A должен стать инвертированным, ожидаемый сигнал должен выглядеть следующим образом:

Однако сигнал на осциллографе показывает дополнительное переключение на OC1A, когда OC1B переходит в высокий уровень, но только для самой первой последовательности, что портит сигнал:

Я снова и снова читаю множество других вопросов и ответов о таймерах Arduino, а также раздел технических описаний таймеров, но так и не могу понять, в чем причина.
Как это исправить?
@Jan Poppeliers, 👍1
Обсуждение2 ответа
Лучший ответ:
Есть два способа управления выводом OC1A: либо как обычный GPIO
контакт, или через генератор сигналов таймера 1. Эти два способа
независимы, и только один из них активен в любой момент времени. Когда вы
установите inverse_polarity_A в значение true и setup() выдаст ошибку
digitalWrite(OC1A_PIN, inverse_polarity_A);
Это устанавливает вывод в состояние HIGH, не влияя на таймер каким-либо образом.
Генератор сигналов по-прежнему считает, что на контакте должен быть НИЗКИЙ уровень. Затем,
начало последовательности импульсов, как выдает send_sequence()
TCCR1A |= (1 << COM1A0);
генератор сигналов берет на себя управление выводом, и поэтому вывод
НИЗКИЙ. Это паразитический переход, который вы наблюдаете.
Схема выходного блока сравнения поясняет, как
управление выводами осуществляется из обычного регистра PORT в
Генератор сигналов: в этой схеме это реализовано через мультиплексор.
Чтобы это исправить, я предлагаю вам настроить таймер.
Полностью в setup(), включая биты COM1x0. Теперь пин
Сначала под контролем генератора сигналов. Затем, всё ещё в
setup(), вы устанавливаете начальный уровень с помощью стробоскопа FOC1A.
Огромное спасибо! Теперь всё работает. Полный код ниже., @Jan Poppeliers
@Эдгар, ваш ответ проясняет причину странного поведения! Теперь всё работает отлично, большое спасибо!
Похоже, я не совсем понял: «Функция порта общего ввода-вывода переопределяется выходным сравнением (OC1x) генератора сигналов, если любой из «Установлены биты COM1x1:0» в техническом описании.
Рабочий код:
#include "Arduino.h"
// Для Arduino Pro Mini 5 В @ 16 МГц
// Предделитель 1 для наивысшего разрешения (1 тактовый импульс = 62,5 нс)
// Для достижения задержек до 4,096 мс требуется таймер 1 (16 бит)
const uint8_t OC1A_PIN = 9;
const uint8_t OC1B_PIN = 10;
const uint16_t TIME_B_US = 1000; // 1 миллисекунда для импульса сброса на OC1B
const uint16_t TIME_A_US = 1000; // длительность импульса 1 миллисекунда на OC1A
const uint16_t REPETITION_PERIOD_US = 10000; // период повторения последовательности в микросекундах
bool inverse_polarity_A = true;
uint32_t _period_start_timestamp_us;
volatile uint8_t _flip_A_cnt;
const uint8_t FLIP_A_CNT_MAX = 3;
void setup() {
pinMode(OC1A_PIN, OUTPUT);
pinMode(OC1B_PIN, OUTPUT);
cli();
TIFR1 = (1 << OCF1A); // очистить ожидающее прерывание канала A
TIFR1 |= (1 << OCF1B); // очистить ожидающее прерывание канала B
TIMSK1 = (1 << OCIE1A); // Включить прерывание сравнения выходов для сигнала PPM на OC1A
TCCR1A = (1 << COM1B0); // Включить переключение OC1B при сравнении совпадений
TCCR1A |= (1 << COM1A0); // Включить переключение OC1A при сравнении совпадений
if (inverse_polarity_A) {
TCCR1C |= (1 << FOC1A); // Принудительно переводим OC1A в высокий уровень
}
TCCR1B = (1 << WGM12); // включить режим CTC и остановить таймер 1
TCNT1 = 0;
sei();
}
void loop() {
uint32_t now_us = micros();
if (now_us - _period_start_timestamp_us >= REPETITION_PERIOD_US) {
_period_start_timestamp_us = now_us;
send_sequence(); // Использует таймер 1
}
}
void send_sequence() {
_flip_A_cnt = 0; // сбросить счетчик
TCCR1A |= (1 << COM1B0); // Повторно включить переключатель OC1B при совпадении сравнения после того, как этот бит был очищен ISR(TIMER1_COMPA)
OCR1B = TIME_B_US << 4; // * 16, чтобы получить время в микросекундах (16 * 62,5 нс = 1 мкс)
OCR1A = TIME_B_US << 4;
TCNT1 = 0;
TCCR1C |= (1 << FOC1B); // Принудительно переводим OC1B в высокое положение.
TCCR1B |= (1 << CS10); // запустить таймер с предделителем 1, чтобы получить самое высокое разрешение (1 тактовый цикл = 62,5 нс)
}
// Для сигнала PPM
ISR(TIMER1_COMPA_vect) {
TCCR1A &= ~(1 << COM1B0); // Отключить переключатель при сравнении совпадений для канала B; для этой последовательности нужен только 1 переключатель
if (_flip_A_cnt < FLIP_A_CNT_MAX) {
OCR1A = TIME_A_US << 4;
_flip_A_cnt++;
}
else {
TCCR1B &= ~(1 << CS10); // Очищаем бит CS10, чтобы остановить таймер 1
}
}
Вот правильный вывод:

- Генерация стабильной частоты
- Таймер 2 «Очистить OC2B при сравнении совпадений» не работает в режиме CTC
- Генерация импульса 200 кГц на Arduino Uno в обычном режиме
- Прерывание сравнения Timer2 не работает должным образом
- Изменчивая переменная не обновляется с таймера ISR
- Точность синхронизации Arduino nano
- ATmega328P - проблема с использованием таймера 2 для генерации тона
- Настройка таймера ATMega328p (Arduino)
Было бы полезно кратко объяснить, что делает ваш код. При первом прочтении я не до конца понял, да и порты навскидку не помню, но возможно ли, что проблема в строке *TCCR1C = (1 << FOC1B);*? Мне кажется, это перезапишет бит FOC1A и установит OC1A в низкий уровень, хотя вы хотите изменить только OC1B. Использование |= для установки бита FOC1B может помочь., @InBedded16
вывести короткий импульс на другой вывод ввода-вывода... запустить осциллограф по этому импульсу... вставить его в различные места кода, чтобы определить, какая строка генерирует начальный нарастающий фронт на желтой трассе, @jsotola
Когда обработчик прерываний (ISR) и основная программа используют общую переменную
_flip_A_cnt, крайне важно объявить_flip_A_cnt_volatile_. Возможно, это не имеет отношения к вашей проблеме, но это очевидные правила и запреты при реализации ISR., @hcheung