Максимальная частота таймера Arduino Uno с помощью прерывания сравнения таймера, а не выходных контактов таймера (например, OC0A)
Я хочу добиться генерации пользовательского двоичного сигнала по цифровому контакту на максимально возможной частоте на Arduino Uno.
Используя выходные контакты таймера (например, OC0A), можно получить вывод для переключения на максимальной частоте 16 МГц (что соответствует квадратной волне частоты 8 МГц), как описано здесь: Максимальная частота цифрового сигнала в Arduino Uno?
Однако, насколько я понимаю, выходные контакты таймера полезны только в том случае, если нужно генерировать повторяющийся сигнал, такой как ШИМ или прямоугольная волна, поэтому он не может служить моей цели, требующей генерации пользовательского двоичного сигнала.
Используя следующий код, я ожидал, что цифровой вывод 9 тоже будет переключаться на частоте, близкой к 16 МГц, так как я не использую прескалер и устанавливаю значение регистра Сравнения совпадений равным 0:
void setup()
{
DDRB = 1 << 1; // Set D9 as OUTPUT
cli();
TCNT0 = 0; TCCR0A = 0; TCCR0B = 0;
TCCR0A |= (1 << WGM01); // CTC mode
TCCR0B |= (1 << CS00); // no prescaling
OCR0A = 0; // Максимально возможная частота переполнения таймера
TIMSK0 |= (1 << OCIE0A);
sei();
}
ISR(TIMER0_COMPA_vect)
{
PORTB ^= 1 << 1; // Toggle D9; this allows to measure the toggle frequency, however this could be replaced by some code to generate a custom binary signal
}
void loop(){}
Однако на самом деле он переключается на частоте всего лишь приблизительно 410 кГц (что соответствует квадратной волне частоты 205 кГц), как показано на рисунке ниже:
Я использовал Timer0, но я получаю ту же частоту с Timer1 и Timer2.
- Как можно объяснить, что с помощью этого кода я получаю эту частоту переключения на цифровом выводе ?
- Как я могу сгенерировать пользовательский двоичный сигнал по цифровому контакту на максимально возможной частоте ?
@Ramanewbie, 👍2
Обсуждение1 ответ
Лучший ответ:
Как можно объяснить, что с помощью этого кода я получаю эту частоту переключения на цифровом выводе ?
Выполнение кода требует времени. Ваш ISR, когда он скомпилирован, выглядит следующим образом:
ISR(TIMER0_COMPA_vect)
{
124: 1f 92 push r1
126: 0f 92 push r0
128: 0f b6 in r0, 0x3f ; 63
12a: 0f 92 push r0
12c: 11 24 eor r1, r1
12e: 8f 93 push r24
130: 9f 93 push r25
PORTB ^= 1 << 1; // Toggle D9; this allows to measure the toggle frequency, however this could be replaced by some code to generate a custom binary signal
132: 85 b1 in r24, 0x05 ; 5
134: 92 e0 ldi r25, 0x02 ; 2
136: 89 27 eor r24, r25
138: 85 b9 out 0x05, r24 ; 5
}
13a: 9f 91 pop r25
13c: 8f 91 pop r24
13e: 0f 90 pop r0
140: 0f be out 0x3f, r0 ; 63
142: 0f 90 pop r0
144: 1f 90 pop r1
146: 18 95 reti
Это 18 инструкций по сборке, для выполнения каждой из которых требуется от 1 до 4 тактовых циклов. Обратившись к таблице данных, вы можете подсчитать точные необходимые циклы и суммировать их, получив 31 такт.
Переключаясь на максимальной скорости, это будет 16 МГц / 31 = 516 кГц.
И тогда у вас есть остальная часть кода, работающая между каждым вызовом прерывания, так что это происходит медленнее, чем это.
Как я могу сгенерировать пользовательский двоичный сигнал по цифровому контакту на максимально возможной частоте ?
Не используя прерываний и написав плотный цикл на ассемблере.
Как вам удалось получить точную сборку? Можно ли это сделать с помощью Arduino IDE или вам пришлось использовать Atmel IDE?, @Tri
Это на самом деле довольно хорошо решает этот вопрос. Мое понимание происходящего таково: происходит первое прерывание сравнения таймеров. Перед завершением ISR происходит второе прерывание сравнения по таймеру, но оно ставится в очередь, поскольку предыдущий ISR не завершил выполнение. Как только первый ISR завершит работу, второму ISR будет разрешено работать. И это продолжается и продолжается таким образом, что ISR работает с частотой, равной (или, по крайней мере, близкой к) обратной его продолжительности. Что вы скажете об этой интерпретации?, @Ramanewbie
Как я могу использовать язык ассемблера для достижения более высокой частоты переключения, чем при прерывании сравнения таймера ? На самом деле, как я могу использовать язык ассемблера для достижения своей цели без использования прерывания таймера (которое, как вы показали, работает медленно)?, @Ramanewbie
@Tri Я использовал avr-objdump -S <filename.elf>
в скомпилированном файле elf из IDE Arduino (на самом деле "arduino-cli", поскольку я ненавижу IDE)., @Majenko
@Ramanewbie Это хорошая интерпретация, да. Написание языка ассемблера может быть чем-то вроде черной магии. Вам нужно будет закодировать циклы задержки, чтобы получить нужное время, а также переключить вывод ввода-вывода и просмотреть последовательность данных. Не очень веселая задача. Другим вариантом может быть потоковая передача данных с использованием порта SPI, где скорость будет соответствовать разрешению, которое вы хотите, - максимум 8 Мбит/с (квадратная волна 4 МГц), @Majenko
"Вам нужно будет закодировать циклы задержки, чтобы получить нужное время, а также переключить вывод ввода-вывода и просмотреть последовательность данных" > При таком подходе вам нужно будет подсчитать количество тактов, которые требуются для выполнения ваших задач ввода-вывода, чтобы путем вычитания вывести продолжительность, необходимую для цикла задержки, чтобы получить желаемую частоту операций, верно ?, @Ramanewbie
@Ramanewbie Это верно, да., @Majenko
Давайте [продолжим это обсуждение в чате](https://chat.stackexchange.com/rooms/122809/discussion-between-ramanewbie-and-majenko)., @Ramanewbie
- Использование millis() и micros() внутри процедуры прерывания
- Arduino непрерывно считывает значение АЦП с помощью прерывания
- Использование TIMER0_COMPB_vect
- 4-битный счетчик вверх и вниз
- Включить и отключить отдельные прерывания
- ATtiny85 AC Phase Control для регулировки яркости лампочки
- Присоедините функцию Arduino ISR к члену класса
- Быстрее TimerOne с Teensy 4.0 (600 МГц)
Я понимаю, что это мелочи по сравнению с более серьезными проблемами, которые изложены в ответе Майенко, но если вы хотите переключить PB1, вы можете
PINB = 1 << 1;
вместоPORTB ^= 1 << 1
, чтобы сэкономить пару циклов. Конструкция может показаться странной, но они разработали аппаратное обеспечение AVR GPIO таким образом, чтобы вы могли переключать вывод, записывая 1 бит в их разрядные позиции в регистрах PINx; запись 0 бит не имеет никакого эффекта. Если вы предпримете *запись в замкнутом цикле вне предложения о прерывании*, это может оказать некоторое влияние., @timemageСпасибо, я не знал, что вы могли бы сделать это более эффективно таким образом; это действительно оказало бы влияние в некоторых ситуациях., @Ramanewbie