С TIMER1 в режиме CTC не может подняться выше ~ 200 кГц в Arduino UNO, работающем на частоте 16 МГц без предварительного вызова?

Я получил следующий код, и, согласно таблице данных, Freq должен быть clk / 2 * Prescaller * (1 + OCR1A), поэтому, если я установлю OCR1A на 0, я должен получить что-то близкое к 8 МГц, но максимум, который я могу получить, составляет ~200 кГц. Может ли кто-нибудь помочь мне понять, почему?

В РЕЖИМЕ БЫСТРОЙ ШИМ я могу достичь таких скоростей, просто хотел знать, почему я не могу в РЕЖИМЕ CTC.

    void setup() {
  
        DDRB |= (1 << PINB1) ;
        TCCR1A = 0;
        TCCR1B = 0;
        OCR1A = 0;
        TCCR1B |= (1<<CS10) | (1<<WGM12);

        TIMSK1 |= (1<<OCIE1A);
        sei();
    }

    void loop() {
 
    }

    ISR (TIMER1_COMPA_vect){
        PORTB ^= 1 << PINB1;
    }

, 👍0

Обсуждение

Вы не можете отправить ISR и вернуться достаточно быстро, чтобы получить частоту около 8 МГц в CTC. Но вы также не можете сделать это и для режима Fast PWM. Так что я не знаю, что делать с вашим вопросом. Вы имели в виду использование реальной функциональности ШИМ и просто поместили ISR в свой код как ошибку? С другой стороны, если бы вы смогли получить 8 МГц, делая то же самое с режимами ISR и Fast PWM, мне было бы интересно узнать, как это сделать., @timemage

Здравствуйте, спасибо за ваш ответ. Нет, я могу получить частоту 8 МГц, используя быстрый ШИМ, без прерываний (да на ваш вопрос об использовании функций ШИМ), просто используя регистры ICR и OCR1A и установив для них значение 0. Я предполагал, что это связано со временем, затрачиваемым на вызовы прерываний, но не мог определить время, необходимое для перехода к ISR., @Nuno Santos

Я даже зашел в IDE Microchip и эмулировал Atmega, но он не очень хорошо реализует ISR (либо так, либо я не знаю как), потому что я мог вызвать вызов ISR только при «воспроизведении» отладки. и не смог запустить его, используя пошаговую инструкцию, но мне удалось добиться того, что для тактовой частоты 8 МГц ISR запускался каждые 5 мкс или что-то в этом роде, я думаю, это было 38 командных циклов или около того, но я не смог выясните, откуда взялись эти инструкции., @Nuno Santos

Я думаю, что меня сбило с толку это в таблице данных Atmega: сгенерированный сигнал будет иметь максимальную частоту. fOC1A = fclk_I/O/2, когда OCR1A установлен в ноль (0x0000). Частота сигнала определяется следующим уравнением: Поэтому я ожидал импульс ~ 8 МГц., @Nuno Santos


1 ответ


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

2

Я дизассемблировал вашу программу. Ниже приведен код ISR. Рядом с каждым инструкции, в качестве комментария я написал количество циклов процессора:

; Prologue: save some registers to the stack.
push r1         ; 2
push r0         ; 2
in   r0, 0x3f   ; 1
push r0         ; 2
clr  r1         ; 1
push r24        ; 2
push r25        ; 2

; ISR body: PORTB ^= 0x02;
in   r24, 0x05  ; 1
ldi  r25, 0x02  ; 1
eor  r24, r25   ; 1
out  0x05, r24  ; 1

; Epilogue: restore registers and return.
pop  r25        ; 2
pop  r24        ; 2
pop  r0         ; 2
out  0x3f, r0   ; 1
pop  r0         ; 2
pop  r1         ; 2
reti            ; 4

Я насчитал в общей сложности 31 цикл процессора, большая часть из которых тратится на пролог и эпилог. Кроме того, есть некоторые накладные расходы:

  • ЦП всегда выполняет хотя бы одну инструкцию основной программы. между двумя прерываниями. Учитывая, что основная программа находится в жестком цикле содержащий только две инструкции двойного цикла, которые потребуют двух процессоров циклы.

  • ЦП требуется четыре цикла, чтобы войти в режим прерывания: предотвратите вложенные прерываний, сохраните счетчик программ и загрузите адрес вектор прерывания.

  • Вектор прерывания — это инструкция jmp, которая переходит к ISR. Это занимает три цикла.

Минимальный период прерывания определяется следующей суммой:

 2  one loop instruction
 4  enter interrupt mode
 3  jump to ISR
31  execute ISR
────────────────────────
40  total cycles

Это занимает 2,5 мкс, что дает частоту прерываний 400 кГц. Учитывая, что для полного цикла вывода нужно два прерывания сигнала, максимальная частота, которую вы можете увидеть на выходе, составляет 200 кГц.

Вы можете сэкономить 10 циклов процессора, переключив вывод с помощью:

    PINB |= 1 << PINB1;

Эта строка компилируется в одну машинную инструкцию sbi с двойным циклом. Поскольку эта инструкция не обращается ни к каким регистрам ЦП, вам не нужно сохраните и восстановите r24 и r25. На самом деле, вам не нужно сохранять и восстановите любой регистр, чтобы можно было просто удалить пролог и заменить эпилог с помощью одной инструкции reti:

ISR (TIMER1_COMPA_vect, ISR_NAKED) {
    PINB |= 1 << PINB1;
    asm("reti");  // возвращаемся из прерывания
}

Это дает ISR, который выполняется за шесть циклов, а общее количество прерываний тогда период может составлять всего 15 циклов. Но будьте осторожны, вы не следует писать голый ISR, если вы не абсолютно уверены, что код внутри него не затирает ни регистр ЦП, ни статус зарегистрируйтесь.


Уточнение. На первый взгляд инструкция

PINB |= 1 << PINB1;

похоже, означает: «Загрузите PINB, выполните побитовое ИЛИ с 0x02, затем напишите результат возвращается в PINB». Однако это не так, как компилятор интерпретирует это. Вместо этого это означает «установить бит 1 регистра». PINB в единицу». Это одна машинная инструкция, так как PINB — это регистры ввода-вывода с побитовой адресацией.

Да, компилятор avr-gcc может быть немного странным в этом отношении.

,

Прежде всего, большое спасибо за такое подробное объяснение. я попытался включить эту опцию ISR_NAKED, и она удвоила частоту. Одна вещь, которую я не понял, это то, что вы сказали, что можете переключить булавку с помощью |= ? я попробовал, и он всегда включен, я могу переключаться только с помощью оператора ^=, я делаю что-то не так?, @Nuno Santos

PINB |= 1 << PINB1; может быть просто PINB = 1 << PINB1;, @timemage

Но это не переключает вывод, нужно ли мне устанавливать какой-либо регистр?, @Nuno Santos

@NunoSantos поведение записи 1-битного значения в PINx приводит к переключению этого вывода в PORTx. Я знаю, это выглядит странно, но именно так это и работает на этих чипах., @timemage

Подожди, не уверен, что я тебя полностью понимаю, если честно, я нуб... Итак, у меня было переключение вывода с помощью оператора XOR., но когда я перехожу на ИЛИ или даже просто устанавливаю вывод со знаком равенства, я не получаю переключения на этот вывод, прочитав ваш комментарий, я перешел к таблице данных, потому что я помню, как читал, что в регистре был один бит, который делал именно это, поэтому я обновил свой код строкой TCCR1A |= (1<<COM1A0); и ТЕПЕРЬ он переключается, если я устанавливаю ПИН-код на ВЫСОКИЙ уровень внутри прерывания, не уверен, что вы говорили именно об этом., @Nuno Santos

Потому что в моей опубликованной версии кода, когда я переключал XOR на OR или просто назначение контактов, я не получал переключения на этот контакт., @Nuno Santos

@timemage: PINB = 1 << PINB1; не быстрее, чем PINB |= 1 << PINB1;. Кроме того, установка вывода с = затирает регистр ЦП, который вам придется сохранять и восстанавливать. Тут без пролога и эпилога не обойтись., @Edgar Bonet

@NunoSantos: не следует заменять XOR на OR: вам следует заменить PORTB ^= 1 << PINB1; на PINB |= 1 << PINB1;. Обратите внимание, что мы действуем с другим регистром ввода-вывода: PINB вместо PORTB., @Edgar Bonet

@EdgarBonet, что касается более быстрой части, дело было не в этом. По поводу второй части: странно., @timemage

Оооо, теперь я понимаю, вы устанавливаете PINB, а не PORTB, я только что попробовал и работает без установки бита COM1A0, почему? почему установка PIN-кода напрямую отключает пин-код? И еще раз спасибо за уделенное время и извините за нубские вопросы., @Nuno Santos

с вашим кодом я получаю 517 кГц, это более чем вдвое больше, чем у меня было раньше., @Nuno Santos

@NunoSantos: Порты ввода-вывода AVR имеют встроенную функцию переключения. Он был закреплен за регистром PIN, для которого в противном случае операция записи была бы бессмысленной. Вероятно, способ сэкономить адресное пространство ввода-вывода. См. техническое описание ATmega., @Edgar Bonet

@EdgarBonet Я читал таблицу данных, и регистр PINx помечен как регистр только R, а PORTx, DDRx и R/W, но это полезно знать, хотя даже если вы напишете в них, изменения не останутся , приятно иметь такую функциональность. Не знал этого! Спасибо., @Nuno Santos