Максимальная частота таймера 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 кГц), как показано на рисунке ниже:

Picture of the screen of an oscilloscope showing that the frequency at which the digital pin toggles is close to 410kHz

Я использовал Timer0, но я получаю ту же частоту с Timer1 и Timer2.

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

, 👍2

Обсуждение

Я понимаю, что это мелочи по сравнению с более серьезными проблемами, которые изложены в ответе Майенко, но если вы хотите переключить PB1, вы можете PINB = 1 << 1; вместо PORTB ^= 1 << 1 , чтобы сэкономить пару циклов. Конструкция может показаться странной, но они разработали аппаратное обеспечение AVR GPIO таким образом, чтобы вы могли переключать вывод, записывая 1 бит в их разрядные позиции в регистрах PINx; запись 0 бит не имеет никакого эффекта. Если вы предпримете *запись в замкнутом цикле вне предложения о прерывании*, это может оказать некоторое влияние., @timemage

Спасибо, я не знал, что вы могли бы сделать это более эффективно таким образом; это действительно оказало бы влияние в некоторых ситуациях., @Ramanewbie


1 ответ


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

3

Как можно объяснить, что с помощью этого кода я получаю эту частоту переключения на цифровом выводе ?

Выполнение кода требует времени. Ваш 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