Генерация импульса 200 кГц на Arduino Uno в обычном режиме

Мне нужно получить 200 кГц от Arduino Uno. Я использую Timer0. Я настроил его для работы в обычном режиме, а предделитель установлен на clk/8 (тактовая частота Arduino составляет 16 МГц). TCNT0 установлен на 0xFB, а код переключает 2 контакта PORTB, когда устанавливается флаг переполнения таймера.

Вот мой код (я использую Atmel Studio 7):

#include <avr/io.h>


int main(void)
{
   DDRB = 0x03;
   PORTB |= 0x02;
   while (1) 
   {
       TCNT0 = 0xFB;
       TCCR0A = 0X00;
       TCCR0B = 0x02;
       while ( (TIFR0 & 0x01) == 0 );
       PORTB ^= 0x03;
       TCCR1A = 0x00;
       TCCR1B = 0x00;
       TIFR0 = 0X01;
  }
}

Когда я проверяю на осциллографе, он показывает, что импульс составляет 166 кГц вместо 200 кГц. Хотя я могу использовать режим Fast PWM и прерывания, мне просто интересно, почему это не работает? Я сделал какую-то ошибку?

, 👍2


1 ответ


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

3

Это достаточно просто — вы забыли, что всему нужно время.

Ваша временная шкала:

  1. Настроить таймер
  2. Установить количество
  3. Дождитесь переполнения счетчика (33 тика)
  4. Переключить штифт
  5. Сделайте что-нибудь с T1 (зачем...?)
  6. Очистить бит переполнения
  7. Вернуться к 1.

Вот как это выглядит в сборе:

90: 3b ef           ldi r19, 0xFB
92: 42 e0           ldi r20, 0x02
94: 23 e0           ldi r18, 0x03
96: 91 e0           ldi r25, 0x01

while (1) {
98: 36 bd           out 0x26, r19   ; TCNT0 = 0xFB;
9a: 14 bc           out 0x24, r1    ; TCCR0A = 0X00;
9c: 45 bd           out 0x25, r20   ; TCCR0B = 0x02;

while ( (TIFR0 & 0x01) == 0 );
9e: a8 9b           sbis    0x15, 0
a0: fe cf           rjmp    .-4         ; 0x9e <setup+0xe>

PORTB ^= 0x03;
a2: 85 b1           in  r24, 0x05   
a4: 82 27           eor r24, r18
a6: 85 b9           out 0x05, r24   

a8: 10 92 80 00     sts 0x0080, r1
ac: 10 92 81 00     sts 0x0081, r1
b0: 95 bb           out 0x15, r25   

}
b2: f2 cf           rjmp    .-28

Итак, предположим, что ожидание переполнения таймера займет 8*4+ 1=33 такта, и ссылаясь на техническое описание, сколько времени занимает каждая инструкция, мы можем сложить все в основном цикле while:

while (1) {
98: 36 bd           out 0x26, r19   ; 1
9a: 14 bc           out 0x24, r1    ; 1
9c: 45 bd           out 0x25, r20   ; 1

while ( (TIFR0 & 0x01) == 0 );
9e: a8 9b           sbis    0x15, 0 ; 2 (at end)
a0: fe cf           rjmp    .-4     ; 33 (while running)

PORTB ^= 0x03;
a2: 85 b1           in  r24, 0x05   ; 1
a4: 82 27           eor r24, r18    ; 1
a6: 85 b9           out 0x05, r24   ; 1

a8: 10 92 80 00     sts 0x0080, r1  ; 2
ac: 10 92 81 00     sts 0x0081, r1  ; 2
b0: 95 bb           out 0x15, r25   ; 1

}
b2: f2 cf           rjmp    .-28    ; 2

Вы можете видеть, что это занимает 48 тактов (нас не волнует, что находится во внутреннем while, за исключением случаев, когда речь идет о завершении, а это только инструкция SBIS). Для 200 кГц вам нужно 40 тактов. У вас есть 48. 16000000/48 = 333333. Разделите на два для переключения контактов, и вы получите 166666 Гц.

Если вы хотите использовать этот метод, вам необходимо убедиться, что таймер работает совершенно автономно, а флаг переполнения устанавливается с регулярным интервалом, а не только через 32 тика после его запуска.

Вы должны работать в режиме CTC, чтобы он считал от 0 до целевого значения, а затем сбрасывался обратно на 0, устанавливая флаг переполнения. Тогда ваш цикл будет выглядеть так:

while (1) {
    if ((TIFR0 & 0x01) == 0x01) {
        PORTB ^= 0x03;
    }
}

Вся настройка таймера выполняется до цикла, и он просто работает и работает, пока вы не укажете иное.

,