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 OCR0A
seperti TOP
yang 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 A
dan Output Compare B
memotong 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 OCR0B
nilai dari 0
menjadi OCR0A
. Inilah pemahaman saya tentang apa yang harus terjadi:
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
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
}
sumber
OCR0A
digunakan oleh kode IR jadi saya hanya punyaOCR0B
. Saya mencoba menggunakannya untuk menghasilkan perangkat lunak PWM pada 3 pin non-PWM.Jawaban:
PWM perangkat lunak minimal bisa terlihat seperti ini:
Program Anda diatur
dutyCycle
ke nilai yang diinginkan dan ISR mengeluarkan sinyal PWM yang sesuai.dutyCycle
adalahuint16_t
untuk memungkinkan nilai antara 0 dan 256 inklusif; 256 lebih besar dari nilai yang memungkinkancurrentPwmCount
dan karenanya memberikan siklus tugas 100% penuh.Jika Anda tidak membutuhkan 0% (atau 100%), Anda dapat mencukur habis beberapa siklus dengan menggunakan
uint8_t
sehingga0
menghasilkan siklus tugas 1/256 dan255
100% atau0
0% dan255
merupakan 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,
PORT
Anda 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:
Kode ini memetakan siklus tugas ke
1
output logis pada pin; jika LED Anda memiliki 'logika negatif' (LED pada saat pin rendah ), Anda dapat membalikkan polaritas sinyal PWM dengan hanya mengubahif (cnt < dutyCycle...)
keif (cnt >= dutyCycle...)
.sumber
if
dalam 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?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.
sumber