ATtiny13A - Не удается сгенерировать программный ШИМ в режиме CTC


8

Я пытаюсь сделать пульт дистанционного управления RGB LED подсветкой с помощью ATtiny13A.

Я знаю, что ATtiny85 лучше подходит для этой цели, и я знаю, что в конечном итоге я не смогу уместить весь код, но сейчас моя главная задача - сгенерировать программный ШИМ с использованием прерываний в режиме CTC.

Я не могу работать в любом другом режиме (для быстрой ШИМ с исключением , OCR0Aкак , TOPтак как код ИК - приемник Я использую потребности частоту 38 кГц , который он генерирует , используя КТК и который является в основном то же самое) OCR0A=122.

Так что я пытаюсь (и я видел , как люди упоминают об этом в Интернете) использовать Output Compare Aи Output Compare Bпрерывание для создания программного обеспечения PWM.

OCR0A, который также используется ИК-кодом, определяет частоту, которая меня не волнует. И OCR0Bопределяет рабочий цикл ШИМ, который я буду использовать для изменения цвета светодиодов.

Я ожидаю, что смогу получить ШИМ с рабочим циклом 0-100%, изменив OCR0Bзначение с 0на OCR0A. Это мое понимание того, что должно произойти:

Форма волны

Но на самом деле происходит следующее (это из симуляции Proteus ISIS):

Как вы можете видеть ниже, я могу получить около 25% -75% рабочего цикла, но для ~ 0-25% и ~ 75-100% форма волны просто застревает и не меняется.

ЖЕЛТАЯ линия: Аппаратный ШИМ

RED line: программный ШИМ с фиксированным рабочим циклом

ЗЕЛЕНАЯ линия: Программное обеспечение ШИМ с переменным рабочим циклом

Результаты осциллографа

И вот мой код:

#ifndef        F_CPU
    #define        F_CPU        (9600000UL) // 9.6 MHz
#endif

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

int main(void)
{
    cli();

    TCCR0A = 0x00;                        // Init to zero
    TCCR0B = 0x00;

    TCCR0A |= (1<<WGM01);                 // CTC mode
    TCCR0A |= (1<<COM0A0);                // Toggle OC0A on compare match (50% PWM on PINB0)
                                          // => YELLOW line on oscilloscope

    TIMSK0 |= (1<<OCIE0A) | (1<<OCIE0B);  // Compare match A and compare match B interrupt enabled

    TCCR0B |= (1<<CS00);                  // Prescalar 1

    sei();

    DDRB = 0xFF;                          // All ports output


    while (1)
    {
        OCR0A = 122;                      // This is the value I'll be using in my main program
        for(int i=0; i<OCR0A; i++)
        {
            OCR0B = i;                    // Should change the duty cycle
            _delay_ms(2);
        }
    }
}


ISR(TIM0_COMPA_vect){
    PORTB ^= (1<<PINB3);                  // Toggle PINB3 on compare match (50% <SOFTWARE> PWM on PINB3)
                                          // =>RED line on oscilloscope
    PORTB &= ~(1<<PINB4);                 // PINB4 LOW
                                          // =>GREEN line on oscilloscope
}

ISR(TIM0_COMPB_vect){
    PORTB |= (1<<PINB4);                  // PINB4 HIGH
}

Могу ли я спросить, почему вы не можете использовать аппаратный ШИМ? Причина, которую вы приводите, не имеет никакого смысла. Единственная причина не использовать аппаратное обеспечение - если вам нужен интерфейс SPI или внешнее прерывание.
Клен

@Maple Я пытаюсь управлять светодиодом RGB, поэтому мне нужно 3 сигнала ШИМ, по одному на каждый цвет. OCR0Aиспользуется ИК-код, поэтому у меня есть только OCR0B. Я пытаюсь использовать его для генерации программного ШИМ на 3 выводах без ШИМ.
Pouria P

Программный ШИМ 38 кГц не будет работать. Это слишком быстро для MCU.
JimmyB

1
Вы можете (и сделали это) запустить ISR @ 38 кГц. Но для любого рабочего цикла, кроме 50%, вам потребуется более высокая частота. Пример: для 25% при 38 кГц необходимо иметь возможность обрабатывать два последовательных прерывания в пределах временного интервала 38 кГц / 25% = 152 кГц. Это оставляет только около 63 тактов процессора (9600 кГц / 152 кГц) для ISR. При коэффициенте заполнения 10% у вас осталось 25 тактовых частот процессора для ISR.
JimmyB

3
Вы не указали желаемую частоту ШИМ. Для управления яркостью вам не нужно быть где-то около 38 кГц. 100 Гц может быть достаточно. Я предлагаю вам использовать частоту 38 кГц (ИК) в качестве наименьшего рабочего цикла для вашего программного ШИМ и использовать ШИМ, кратное некоторому, например 256, чтобы наименьший рабочий цикл составлял 1/256 (один тактовый интервал 38 кГц), а самый высокий (ниже 100%) - (255/256), равный 255 тактовым периодам 38 кГц. Это дает вам 8-битный ШИМ с частотой (38000/256) ~ 148 Гц.
JimmyB

Ответы:


8

Минимальный программный ШИМ может выглядеть так:

volatile uint16_t dutyCycle;


uint8_t currentPwmCount;

ISR(TIM0_COMPA_vect){
  const uint8_t cnt = currentPwmCount + 1; // will overflow from 255 to 0
  currentPwmCount = cnt;
  if ( cnt <= dutyCyle ) {
    // Output 0 to pin
  } else {
    // Output 1 to pin
  }
}

Ваша программа устанавливает dutyCycleжелаемое значение, и ISR выводит соответствующий сигнал ШИМ. dutyCycleэто uint16_tзначение для значений от 0 до 256 включительно; 256 больше любого возможного значения currentPwmCountи, таким образом, обеспечивает полный 100% рабочий цикл.

Если вам не нужны 0% (или 100%), вы можете сбрить некоторые циклы, используя uint8_tтак, что либо 0приводит к рабочему циклу 1/256 и 255составляет 100%, либо к 00%, а 255рабочий цикл равен 255 /. 256.

У вас все еще мало времени на 38 кГц ISR; используя небольшой встроенный ассемблер, вы можете сократить количество циклов ISR на 1/3 - 1/2. Альтернатива: запускайте свой код ШИМ только при каждом переполнении таймера, вдвое уменьшая частоту ШИМ.

Если у вас несколько каналов PWM и все контакты, которые вы используете в PMW, одинаковы, PORTвы также можете собрать все состояния контактов в переменной и, наконец, вывести их на порт за один шаг, который затем требует только чтения из порт и-с-маской или-с-новым состоянием, запись в порт один раз, а не один раз на вывод / канал .

Пример:

volatile uint8_t dutyCycleRed;
volatile uint8_t dutyCycleGreen;
volatile uint8_t dutyCycleBlue;

#define PIN_RED (0) // Example: Red on Pin 0
#define PIN_GREEN (4) // Green on pin 4
#define PIN_BLUE (7) // Blue on pin 7

#define BIT_RED (1<<PIN_RED)
#define BIT_GREEN (1<<PIN_GREEN)
#define BIT_BLUE (1<<PIN_BLUE)

#define RGB_PORT_MASK ((uint8_t)(~(BIT_RED | BIT_GREEN | BIT_BLUE)))

uint8_t currentPwmCount;

ISR(TIM0_COMPA_vect){
  uint8_t cnt = currentPwmCount + 1;
  if ( cnt > 254 ) {
    /* Let the counter overflow from 254 -> 0, so that 255 is never reached
       -> duty cycle 255 = 100% */
    cnt = 0;
  }
  currentPwmCount = cnt;
  uint8_t output = 0;
  if ( cnt < dutyCycleRed ) {
    output |= BIT_RED;
  }
  if ( cnt < dutyCycleGreen ) {
    output |= BIT_GREEN;
  }
  if ( cnt < dutyCycleBlue ) {
    output |= BIT_BLUE;
  }

  PORTx = (PORTx & RGB_PORT_MASK) | output;
}

Этот код отображает рабочий цикл на логический 1вывод на выводах; если ваши светодиоды имеют «отрицательную логику» (светодиод включается при низком выводе ), вы можете инвертировать полярность сигнала ШИМ, просто переключившись if (cnt < dutyCycle...)на if (cnt >= dutyCycle...).


Ух ты, ты классный. Мне было интересно, если мое понимание того, что вы сказали мне делать, было правильным, и теперь есть этот очень информативный ответ с примерами и всем этим. Еще раз спасибо.
Pouria P

Еще одна вещь, правильно ли я понял: если бы я выполнял ШИМ при каждом переполнении таймера, я бы вставил ifв подпрограмму прерывания только выполнение кода ШИМ через раз. Делая это, если мой ШИМ-код занимает слишком много времени и пропускается следующее прерывание переполнения, тогда с моей программой все будет в порядке, потому что следующее прерывание ничего не будет делать. Это то, что вы имели в виду?
Pouria P

Да, это то, что я имел в виду, извините за краткость. ISR должен быть достаточно быстрым, чтобы в первую очередь не пропустить ни одного прерывания, но даже если это так, тратить 90% процессорного времени на один ISR тоже может нехорошо, так что вы можете сократить его почти вдвое, пропустив ' сложная логика каждого другого прерывания, оставляя больше времени для других задач.
JimmyB

2

Как заметил @JimmyB, частота ШИМ слишком высока.

Кажется, что прерывания имеют общую задержку в одну четверть цикла ШИМ.

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

Минимальный рабочий цикл ШИМ определяется как процент задержки общего прерывания в период ШИМ. Та же логика применима к максимальному рабочему циклу ШИМ.

Глядя на графики, минимальный рабочий цикл составляет около 25%, а затем общая задержка должна составлять ~ 1 / (38000 * 4) = 6,7 мкс.

Как следствие, минимальный период ШИМ составляет 256 * 6,7 мкс = 1715 мкс и максимальная частота 583 Гц.

Еще несколько объяснений о возможных патчах на высокой частоте:

Прерывание имеет два скрытых окна, когда ничего нельзя сделать, вводя конец, выходя из прерывания, когда контекст сохраняется и восстанавливается. Поскольку ваш код довольно прост, я подозреваю, что это занимает значительную часть времени ожидания.

Решение пропустить низкие значения будет по-прежнему иметь задержку, по крайней мере, при выходе из прерывания и при вводе следующего прерывания, поэтому минимальный рабочий цикл будет не таким, как ожидалось.

Пока это не меньше шага ШИМ, рабочий цикл ШИМ начнется с более высокого значения. Просто небольшое улучшение по сравнению с тем, что есть сейчас.

Я вижу, что вы уже используете 25% процессорного времени в прерываниях, так почему бы вам не использовать 50% или более его, оставьте второе прерывание и просто используйте пул для флага сравнения. Если вы используете значения только до 128, у вас будет только до 50% рабочего цикла, но с задержкой двух команд, которые могут быть оптимизированы в ассемблере.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.