ATtiny13A - Tidak dapat menghasilkan PWM perangkat lunak dengan mode CTC

8

Saya mencoba membuat remote control RGB LED menggunakan ATtiny13A.

Saya tahu ATtiny85 lebih cocok untuk tujuan ini, dan saya tahu saya mungkin pada akhirnya tidak dapat memenuhi seluruh kode, tetapi untuk saat ini perhatian utama saya adalah untuk menghasilkan PWM perangkat lunak menggunakan interupsi dalam mode CTC.

Saya tidak dapat beroperasi dalam mode lain (kecuali untuk PWM cepat dengan OCR0Aseperti TOPyang pada dasarnya adalah hal yang sama) karena kode penerima IR Saya menggunakan membutuhkan frekuensi 38 kHz yang menghasilkan menggunakan CTC dan OCR0A=122.

Jadi saya mencoba (dan saya telah melihat orang-orang menyebutkan ini di Internet) menggunakan Output Compare Adan Output Compare Bmemotong untuk menghasilkan PWM perangkat lunak.

OCR0A, yang juga digunakan oleh kode IR, menentukan frekuensi, yang tidak saya pedulikan. Dan OCR0B, menentukan siklus tugas PWM yang akan saya gunakan untuk mengubah warna LED.

Saya berharap bisa mendapatkan PWM dengan siklus tugas 0-100% dengan mengubah OCR0Bnilai dari 0menjadi OCR0A. Inilah pemahaman saya tentang apa yang harus terjadi:

Bentuk gelombang

Tapi yang sebenarnya terjadi adalah ini (ini dari simulasi Proteus ISIS):

Seperti yang Anda lihat di bawah, saya bisa mendapatkan sekitar 25% -75% siklus tugas tetapi untuk ~ 0-25% dan ~ 75-100% bentuk gelombang hanya macet dan tidak berubah.

Baris KUNING: Perangkat Keras PWM

Garis merah: Perangkat lunak PWM dengan siklus tugas tetap

Garis HIJAU: Perangkat lunak PWM dengan berbagai siklus tugas

Hasil osiloskop

Dan ini kode saya:

#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
}
Pouria P
sumber
Bolehkah saya bertanya mengapa Anda tidak dapat menggunakan PWM perangkat keras? Alasan Anda memberi tidak masuk akal. Satu-satunya alasan untuk tidak menggunakan perangkat keras adalah jika Anda memerlukan antarmuka SPI atau interupsi eksternal.
Maple
@Maple Saya mencoba mengendalikan LED RGB jadi saya perlu 3 sinyal PWM, satu untuk setiap warna. OCR0Adigunakan oleh kode IR jadi saya hanya punya OCR0B. Saya mencoba menggunakannya untuk menghasilkan perangkat lunak PWM pada 3 pin non-PWM.
Pouria P
Perangkat lunak 38kHz, PWM tidak akan berfungsi. Itu terlalu cepat untuk MCU.
JimmyB
1
Anda dapat (dan telah melakukannya) menjalankan ISR @ 38kHz. Tetapi untuk setiap siklus tugas selain 50% Anda akan membutuhkan frekuensi yang lebih tinggi. Contoh: Untuk 25% @ 38kHz Anda harus dapat menangani dua interupsi berturut-turut dalam 38kHz / 25% = 152kHz kerangka waktu. Yang tersisa hanya sekitar 63 siklus clock CPU (9600kHz / 152kHz) untuk ISR. Pada siklus kerja 10% Anda memiliki satu 25 jam CPU tersisa untuk ISR.
JimmyB
3
Anda tidak menentukan frekuensi PWM yang diinginkan. Untuk kontrol kecerahan Anda tidak perlu berada di dekat 38kHz. 100Hz mungkin cukup. Saya sarankan Anda menggunakan frekuensi 38kHz (IR) sebagai siklus tugas terendah untuk PWM perangkat lunak Anda dan mengimplementasikan PWM sebagai beberapa kelipatan dari itu, misalnya 256, sehingga siklus tugas terendah adalah 1/256 (satu periode clock 38kHz) dan tertinggi (di bawah 100%) adalah (255/256), sama dengan 255 clock clock 38kHz. Ini memberi Anda PWM 8-bit di (38000/256) ~ 148Hz.
JimmyB

Jawaban:

8

PWM perangkat lunak minimal bisa terlihat seperti ini:

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
  }
}

Program Anda diatur dutyCycleke nilai yang diinginkan dan ISR mengeluarkan sinyal PWM yang sesuai. dutyCycleadalah uint16_tuntuk memungkinkan nilai antara 0 dan 256 inklusif; 256 lebih besar dari nilai yang memungkinkan currentPwmCountdan karenanya memberikan siklus tugas 100% penuh.

Jika Anda tidak membutuhkan 0% (atau 100%), Anda dapat mencukur habis beberapa siklus dengan menggunakan uint8_tsehingga 0menghasilkan siklus tugas 1/256 dan 255100% atau 00% dan 255merupakan siklus tugas 255 / 256.

Anda masih tidak punya banyak waktu dalam ISR 38kHz; menggunakan assembler inline kecil Anda mungkin dapat memotong jumlah siklus ISR dengan 1/3 ke 1/2. Alternatif: Jalankan kode PWM Anda hanya setiap kali timer meluap, mengurangi separuh frekuensi PWM.

Jika Anda memiliki beberapa saluran PWM dan pin yang Anda gunakan untuk PMW sama, PORTAnda juga dapat mengumpulkan semua status pin dalam sebuah variabel dan akhirnya mengeluarkannya ke port dalam satu langkah yang kemudian hanya perlu dibaca-dari- port, dan-dengan-mask, atau-dengan-status-baru, tulis-ke-port sekali alih-alih sekali per pin / saluran .

Contoh:

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;
}

Kode ini memetakan siklus tugas ke 1output logis pada pin; jika LED Anda memiliki 'logika negatif' (LED pada saat pin rendah ), Anda dapat membalikkan polaritas sinyal PWM dengan hanya mengubah if (cnt < dutyCycle...)ke if (cnt >= dutyCycle...).

JimmyB
sumber
Wow, kamu luar biasa. Saya bertanya-tanya apakah pemahaman saya tentang apa yang Anda katakan kepada saya benar dan sekarang ada jawaban yang sangat informatif dengan contoh dan semuanya. Terima kasih lagi.
Pouria P
Hanya satu hal lagi, apakah saya mengerti ini dengan benar: Jika saya melakukan PWM setiap kali timer meluap saya akan menempatkan ifdalam rutinitas interupsi untuk hanya menjalankan kode PWM setiap waktu. Dengan melakukan ini jika kode PWM saya terlalu lama dan interupsi melimpah berikutnya terlewatkan maka program saya akan baik-baik saja karena interupsi berikutnya tidak akan melakukan apa-apa. Apakah itu yang kamu maksud?
Pouria P
Ya, ini yang saya maksud, maaf karena terlalu singkat tentang hal itu. ISR harus cukup cepat untuk tidak melewatkan interupsi di tempat pertama, tetapi bahkan ketika itu, menghabiskan 90% dari waktu CPU dalam satu ISR mungkin juga tidak baik, sehingga Anda dapat memotong hampir setengahnya dengan melewatkan ' logika kompleks 'setiap interupsi lainnya menyisakan lebih banyak waktu untuk tugas-tugas lain.
JimmyB
2

Sebagai @JimmyB berkomentar frekuensi PWM terlalu tinggi.

Tampaknya interupsi memiliki latensi total seperempat dari siklus PWM.

Ketika tumpang tindih, siklus tugas diperbaiki diberikan oleh total latensi, karena interupsi kedua antri dan dieksekusi setelah yang pertama keluar.

Siklus minimum PWM diberikan oleh persentase latensi interupsi total dalam periode PWM. Logika yang sama berlaku untuk siklus tugas PWM maksimum.

Melihat grafik, siklus kerja minimum adalah sekitar 25%, dan kemudian total latensi harus ~ 1 / (38000 * 4) = 6,7 μs.

Sebagai konsekuensinya, periode PWM minimum adalah 256 * 6,7 µs = 1715 µs dan frekuensi maksimum 583 Hz.

Beberapa penjelasan lebih lanjut tentang kemungkinan tambalan pada frekuensi tinggi:

Interrupt memiliki dua jendela buta ketika tidak ada yang bisa dilakukan, memasuki akhirnya keluar dari interupsi ketika konteks disimpan dan dipulihkan. Karena kode Anda cukup sederhana, saya menduga bahwa ini membutuhkan porsi yang baik dari latensi.

Solusi untuk melewati nilai rendah masih akan memiliki latensi setidaknya keluar dari interupsi dan memasuki interupsi berikutnya sehingga siklus tugas minimum tidak akan seperti yang diharapkan.

Selama ini tidak kurang dari langkah PWM, siklus tugas PWM akan dimulai pada nilai yang lebih tinggi. Hanya sedikit perbaikan dari apa yang Anda miliki sekarang.

Saya melihat Anda sudah menggunakan 25% dari waktu prosesor dalam interupsi, jadi mengapa Anda tidak menggunakan 50% atau lebih dari itu, biarkan interupsi kedua dan hanya mengumpulkan untuk bendera membandingkan. Jika Anda menggunakan nilai hanya hingga 128, Anda hanya akan memiliki siklus tugas hingga 50%, tetapi dengan latensi dua instruksi yang dapat dioptimalkan dalam assembler.

Dorian
sumber