Memo. AVR Buzic


Esensi


Saya telah membuat sejumlah perangkat elektronik hobi yang berbeda, dan saya memiliki fitur aneh: jika ada emitor piezoelektrik suara (buzzer) di papan, saya, setelah menyelesaikan pekerjaan utama pada proyek, mulai menderita omong kosong dan membuatnya memainkan berbagai melodi (sebanyak mungkin) ) Sangat berguna untuk memasukkan melodi di akhir proses yang panjang untuk menarik perhatian. Sebagai contoh, saya menggunakannya ketika saya membangun kamera eksposur darurat untuk menerangi photoresist, dll.

Tetapi ketika saya mulai mencari contoh-contoh pembangkit frekuensi untuk AVR di jaringan, untuk beberapa alasan saya menemukan proyek-proyek yang mengerikan atau tidak cukup ringkas yang mengimplementasikan pembangkitan frekuensi suara dengan cara yang murni terprogram. Dan kemudian saya memutuskan untuk mencari tahu sendiri ...

Penyimpangan liris


Hobi saya mencakup penciptaan berbagai perangkat pada mikrokontroler, karena ini tidak bersinggungan dengan prof saya. aktivitas (pengembangan perangkat lunak), saya menganggap diri saya otodidak, dan dalam elektronik tidak terlalu kuat. Sebenarnya, saya lebih suka mikrokontroler PIC, tetapi kebetulan saya telah mengumpulkan sejumlah mikrokontroler Atmel AVR (sekarang sudah Microchip). Segera buat reservasi yang saya tidak pernah miliki AVR di tangan saya, yaitu Ini adalah proyek pertama saya di Atmel MCU, yaitu Atmega48pa. Proyek itu sendiri melakukan beberapa payload, tetapi di sini saya akan menjelaskan hanya sebagian yang terkait dengan generasi frekuensi suara. Tes untuk menghasilkan frekuensi yang saya sebut "buzic", sebagai singkatan untuk musik buzzer. Ya, saya hampir lupa: pada Habr ada pengguna dengan nama panggilan buzic , saya ingin segera memperingatkan bahwa memo ini tidak berlaku baginya, dan untuk berjaga-jaga, saya segera meminta maaf karena menggunakan kombinasi huruf "Buzic".

Jadi ayo pergi


Saya berkenalan dengan sejumlah besar contoh dari jaringan - semuanya dibangun baik pada siklus paling sederhana di bagian utama firmware, atau dalam gangguan timer. Tetapi mereka semua menggunakan pendekatan yang sama untuk menghasilkan frekuensi:

  1. beri makan tingkat tinggi ke kaki mikrokontroler
  2. membuat penundaan
  3. umpan rendah ke kaki mikrokontroler

Mengubah pengaturan waktu tunda dan timer - sesuaikan frekuensi.

Pendekatan ini tidak cocok untuk saya, karena Saya tidak punya keinginan untuk menulis kode untuk kontrol manual kaki mikrokontroler. Saya ingin "batu" untuk menghasilkan frekuensi suara untuk saya, dan saya hanya mengatur nilai register tertentu, sehingga mengubahnya (frekuensi).

Saat mempelajari datasheet (selanjutnya disebut sebagai DS), saya masih menemukan mode timer yang saya butuhkan - dan mode ini, seperti yang Anda duga, adalah mode CTC (Clear Timer on Compare Match). Karena fungsi memainkan musik, secara sederhana, bukan fungsi utama, saya lebih suka memilih timer 2 untuk itu (paragraf 22 SD).

Semua orang tahu bahwa hampir semua mikrokontroler memiliki mode pembuatan sinyal PWM yang diterapkan pada timer dan sepenuhnya perangkat keras. Tetapi dalam tugas ini, PWM tidak cocok karena hanya satu frekuensi yang akan dihasilkan dalam perangkat keras. Oleh karena itu, kita memerlukan PFM (modulasi frekuensi pulsa). Beberapa kesamaan PFM adalah mode timer CTC (klausa 22.7.2 LH).

Mode CTC


Timer 2 dalam mikrokontroler Atmega48pa adalah 8-bit, yaitu, "kutu" dari 0 hingga 255 dan kemudian berjalan dalam lingkaran. Omong-omong, timer dapat bergerak ke arah yang berbeda, tetapi tidak dalam kasus kami. Komponen yang diperlukan berikutnya adalah Unit Bandingkan. Berbicara dengan sangat kasar, modul ini adalah inisiator dari setiap peristiwa yang berkaitan dengan timer. Peristiwa dapat berbeda - seperti interupsi, perubahan tingkat kaki tertentu mikrokontroler, dll. (Jelas, kami tertarik pada yang kedua). Seperti yang Anda tebak, modul perbandingan tidak hanya bernama - itu membandingkan nilai tertentu yang dipilih oleh pengembang firmware dengan nilai timer saat ini. Jika nilai waktu mencapai nilai yang kita atur, suatu peristiwa terjadi. Peristiwa juga dapat terjadi ketika timer meluap atau selama reset. Ok, kami sampai pada kesimpulan bahwa nyaman bagi kami pada waktu-waktu tertentu bahwa timer, bersama dengan modul perbandingan, secara independen mengubah level kaki mikrokontroler ke arah yang berlawanan - sehingga menghasilkan pulsa.

Tugas kedua adalah mengatur interval antara pulsa-pulsa ini - mis. mengontrol frekuensi generasi. Seluruh keunikan mode CTC terletak pada kenyataan bahwa dalam mode ini timer tidak menuju ke akhir (255), tetapi diatur ulang ketika nilai yang ditetapkan tercapai. Dengan demikian, dengan mengubah nilai ini, kita sebenarnya dapat mengontrol frekuensi. Sebagai contoh, jika kita mengatur nilai modul perbandingan ke 10, maka perubahan level pada kaki mikrokontroler akan terjadi 20 kali lebih sering daripada jika kita mengaturnya (nilai modul perbandingan) menjadi 200. Sekarang kita dapat mengontrol frekuensi!



Besi



Pinout mikrokontroler menunjukkan bahwa kita perlu menghubungkan buzzer ke kaki PB3 (OC2A) atau kaki PD3 (OC2B), karena OC2A dan OC2B berarti persis bahwa pada kaki ini, timer 2 dapat menghasilkan sinyal.

Skema yang biasanya saya gunakan untuk menghubungkan buzzer adalah:


Jadi kami merakit perangkat.

Daftar


Pada paragraf sebelumnya, kami memutuskan pilihan kaki - ini adalah PB3 (OC2A), kami akan bekerja dengannya. Jika Anda membutuhkan PD3, maka baginya semuanya akan sama, yang akan terlihat jelas dari cerita.

Kami akan mengkonfigurasi timer 2 kami dengan mengubah 3 register:
  1. TCCR2A - pengaturan mode dan pemilihan perilaku
  2. TCCR2B - pengaturan mode dan pembagi frekuensi timer (juga FOC bit - kami tidak menggunakannya)
  3. OCR2A (OCR2B untuk kasus kaki PD3) - nilai modul perbandingan


Pertimbangkan dulu register TCCR2A dan TCCR2B

Seperti yang Anda lihat, kami memiliki 3 kelompok bit yang signifikan bagi kami - ini adalah bit dari seri COM2xx, WGM2x dan CS2x
Hal pertama yang perlu kita ubah adalah WGM2x - ini adalah hal utama untuk memilih mode pembuatan - bit ini digunakan untuk memilih mode CTC kami.


Catatan: jelas di LH kesalahan ketik dalam "Pembaruan OCR0x di" harus OCR2x

Yaitu kodenya akan seperti ini:
TCCR2A = _BV(WGM21) ; 

Seperti yang Anda lihat, TCCR2B belum digunakan. WGM22 harus nol, tetapi sudah nol.

Langkah selanjutnya adalah mengkonfigurasi bit COM2xx, lebih tepatnya COM2Ax - karena kami bekerja dengan kaki PB3 (untuk COM3Bx PD3 digunakan dengan cara yang sama). Apa yang akan terjadi pada kaki PB3 kita tergantung pada mereka.

Bit COM2xx bergantung pada mode yang kami pilih dengan bit WGM2x, jadi kami harus menemukan bagian yang sesuai di LH. Karena kami memiliki mode CTC, mis. bukan PWM, maka kami mencari piring "Bandingkan Mode Output, non-PWM", ini dia:

Di sini Anda perlu memilih "Toggle" - sehingga level pada kaki berubah ke kebalikan ketika timer mencapai nilai yang ditetapkan. Level konstan berubah dan mengimplementasikan generasi frekuensi yang kita butuhkan.

Karena bit COM2xx juga ada dalam register TCCR2A - hanya itu yang berubah:
 TCCR2A = _BV(COM2A0) | _BV(WGM21) ; 

Tentu, Anda juga perlu memilih pembagi frekuensi dengan bit CS2x, dan tentu saja, mengatur kaki PB3 ke output ... tapi kami tidak akan melakukannya sehingga ketika kami menghidupkan MK, kami tidak akan mendapatkan pekikan tajam pada frekuensi yang tidak dapat dipahami, tetapi ketika kami melakukan semua pengaturan lain dan hidupkan kaki untuk keluar - akan dijelaskan di bawah ini.

Jadi mari kita lihat inisialisasi kita:
 #include <avr/io.h> //set bit - using bitwise OR operator #define sbi(x,y) x |= _BV(y) //clear bit - using bitwise AND operator #define cbi(x,y) x &= ~(_BV(y)) #define BUZ_PIN PB3 void timer2_buzzer_init() { // PB3 cbi(PORTB, BUZ_PIN); // PB3  ,    cbi(DDRB, BUZ_PIN); //  TCCR2A = _BV(COM2A0) | _BV(WGM21) ; //   (      ) OCR2A = 0; } 

Saya menggunakan makro cbi dan sbi (memata-matai suatu tempat di jaringan) untuk mengatur bit individual, dan membiarkannya seperti itu. Makro ini, tentu saja, telah ditempatkan di file header, tetapi untuk kejelasan, saya taruh di sini.

Perhitungan frekuensi dan durasi catatan


Sekarang kita sampai pada inti permasalahannya. Beberapa waktu yang lalu, kenalan musisi mencoba untuk mengarahkan sejumlah informasi tentang staf musik ke otak programmer saya, otak saya hampir mendidih, tetapi saya masih mengeluarkan sebutir berguna dari percakapan ini.
Saya memperingatkan Anda segera - ketidakakuratan besar mungkin terjadi.
  1. masing-masing ukuran terdiri dari 4 kuartal
  2. Setiap melodi memiliki tempo - yaitu jumlah tempat seperti itu per menit
  3. Setiap not dapat dimainkan sebagai seluruh ukuran keseluruhan, serta bagiannya 1/2, 1/3, 1/4, dll.
  4. Setiap nada, tentu saja, memiliki frekuensi tertentu

Kami memeriksa kasus yang paling umum, pada kenyataannya, semuanya lebih rumit di sana, setidaknya bagi saya, jadi saya tidak akan membahas topik ini dalam kerangka cerita ini.

Baiklah, kita akan bekerja dengan apa yang kita miliki. Hal yang paling penting bagi kami adalah pada akhirnya mendapatkan frekuensi catatan (pada kenyataannya, nilai register OCR2A) dan durasinya, misalnya, dalam milidetik. Oleh karena itu, perlu untuk membuat perhitungan.

Karena kita berada dalam kerangka bahasa pemrograman, melodi paling mudah untuk disimpan dalam sebuah array. Cara paling logis untuk mengatur setiap elemen array dalam format adalah catatan + durasi. Penting untuk menghitung ukuran elemen dalam byte, karena kami menulis di bawah mikrokontroler dan dengan sumber daya di sini adalah ketat - itu berarti ukuran elemen dalam byte harus memadai.

Frekuensi


Mari kita mulai dengan frekuensinya. Karena kami memiliki timer 8-bit 2, register perbandingan OCR2A juga 8-bit. Artinya, elemen kami dari array melodi sudah akan setidaknya 2 byte, karena Anda masih perlu menyimpan durasinya. Bahkan, 2 byte adalah batas untuk jenis kerajinan ini. Kami masih belum mendapatkan suara yang bagus, untuk membuatnya lebih halus, dan menghabiskan lebih banyak byte tidak masuk akal. Jadi, kami berhenti pada 2 byte.

Saat menghitung frekuensi, sebenarnya, masalah besar lain muncul.
Jika Anda melihat frekuensi not, kita akan melihat bahwa not dibagi menjadi oktaf.

Untuk sebagian besar melodi sederhana, 3 oktaf sudah cukup, tetapi saya memutuskan untuk mengelak dan menerapkan 6: besar, kecil dan 4 berikutnya.

Sekarang mari kita menyimpang dari musik dan terjun kembali ke dunia pemrograman mikrokontroler.
Setiap timer dalam AVR (dan sebagian besar MK lainnya) terkait dengan frekuensi MK itu sendiri. Frekuensi kuarsa di sirkuit saya adalah 16Mhz. Frekuensi yang sama ditentukan oleh F_CPU "define" sama dengan 16000000 dalam kasus saya. Dalam register TCCR2B, kita dapat memilih pembagi frekuensi sehingga timer 2 kita tidak "menandai" pada kecepatan panik 16000000 kali per detik, tetapi sedikit lebih lambat. Pembagi frekuensi dipilih oleh bit CS2x, seperti yang disebutkan di atas.


Catatan: jelas dalam LH kesalahan ketik bukan "CA2x" harus CS2x

Muncul pertanyaan - bagaimana cara mengkonfigurasi pembagi?

Untuk melakukan ini, Anda perlu memahami cara menghitung nilai untuk register OCR2A. Dan menghitungnya cukup sederhana:
OCR2A = F_CPU / (pembagi frekuensi kuarsa * 2) / frekuensi catatan
Misalnya, ambil catatan SEBELUM oktaf pertama dan pembagi 256 (CS22 = 1, CS21 = 1, CS20 = 0):
OCR2A = 16000000 / (256 * 2) / 261 = 119

Saya akan segera menjelaskan dari mana penggandaan oleh 2 berasal. Faktanya adalah bahwa kami memilih mode "Toggle" dengan register COM2Ax, yang berarti bahwa perubahan level pada kaki dari rendah ke tinggi (atau sebaliknya) dan kembali akan terjadi dalam 2 lintasan timer: pertama timer mencapai nilai OCR2A dan mengubah kaki mikrokontroler, misalnya, dari 1 ke 0, diatur ulang dan hanya pada putaran kedua berubah 0 kembali ke 1. Oleh karena itu, 2 putaran timer pergi untuk setiap gelombang penuh, masing-masing, pembagi harus dikalikan dengan 2, jika tidak kita hanya mendapatkan setengah dari frekuensi catatan kami.

Oleh karena itu kemalangan yang disebutkan di atas ...

Jika kita mengambil catatan SEBELUM oktaf besar dan meninggalkan pembagi 256:
OCR2A = 16000000 / (256 * 2) / 65 = 480 !!!
480 - jumlah ini jelas lebih dari 255 dan secara fisik tidak masuk ke dalam register OCR2A 8-bit.

Apa yang harus dilakukan Jelas mengubah pembagi, tetapi jika kita menempatkan pembagi 1024, maka dengan oktaf besar, semuanya akan baik-baik saja. Masalah akan dimulai dengan oktaf atas:
LA 4th Octave - OCR2A = 16000000 / (1024 * 2) / 3520 = 4
Oktaf keempat yang tajam - OCR2A = 16000000 / (1024 * 2) / 3729 = 4
Nilai OCR2A tidak lagi berbeda, yang berarti bahwa suara juga akan berhenti berbeda.

Hanya ada satu jalan keluar: untuk frekuensi catatan Anda perlu menyimpan tidak hanya nilai register OCR2A, tetapi juga bit pembagi frekuensi kuarsa. Karena untuk oktaf yang berbeda akan ada nilai yang berbeda dari pembagi frekuensi kuarsa, yang kita perlu atur di register TCCR2B!

Sekarang semuanya jatuh ke tempatnya - dan saya akhirnya menjelaskan mengapa kita tidak bisa langsung mengisi nilai pembagi di fungsi timer2_buzzer_init ().

Sayangnya, pembagi frekuensi adalah 3 bit lebih banyak. Dan mereka harus diambil dalam byte kedua elemen array melodi.

Hidup makro
 #define DIV_MASK (_BV(CS20) | _BV(CS21) | _BV(CS22)) #define DIV_1024 (_BV(CS20) | _BV(CS21) | _BV(CS22)) #define DIV_256 (_BV(CS21) | _BV(CS22)) #define DIV_128 (_BV(CS20) | _BV(CS22)) #define DIV_64 _BV(CS22) #define DIV_32 (_BV(CS20) | _BV(CS21)) #define NOTE_1024( x ) ((F_CPU / (1024 * 2) / x) | (DIV_1024 << 8)) #define NOTE_256( x ) ((F_CPU / (256 * 2) / x) | (DIV_256 << 8)) #define NOTE_128( x ) ((F_CPU / (128 * 2) / x) | (DIV_128 << 8)) #define NOTE_64( x ) ((F_CPU / (64 * 2) / x) | (DIV_64 << 8)) #define NOTE_32( x ) ((F_CPU / (32 * 2) / x) | (DIV_32 << 8)) //  #define DOB NOTE_1024( 65 ) #define DO_B NOTE_1024( 69 ) #define REB NOTE_1024 ( 73 ) #define RE_B NOTE_1024 ( 78 ) #define MIB NOTE_1024 ( 82 ) #define FAB NOTE_1024 ( 87 ) #define FA_B NOTE_1024 ( 93 ) #define SOLB NOTE_1024 ( 98 ) #define SOL_B NOTE_1024 ( 104 ) #define LAB NOTE_1024 ( 110 ) #define LA_B NOTE_1024 ( 116 ) #define SIB NOTE_1024 ( 123 ) //  #define DOS NOTE_256( 131 ) #define DO_S NOTE_256( 138 ) #define RES NOTE_256 ( 146 ) #define RE_S NOTE_256 ( 155 ) #define MIS NOTE_256 ( 164 ) #define FAS NOTE_256 ( 174 ) #define FA_S NOTE_256 ( 185 ) #define SOLS NOTE_256 ( 196 ) #define SOL_S NOTE_256 ( 207 ) #define LAS NOTE_256 ( 219 ) #define LA_S NOTE_256 ( 233 ) #define SIS NOTE_256 ( 246 ) //  #define DO1 NOTE_256( 261 ) #define DO_1 NOTE_256( 277 ) #define RE1 NOTE_256 ( 293 ) #define RE_1 NOTE_256 ( 310 ) #define MI1 NOTE_256 ( 329 ) #define FA1 NOTE_256 ( 348 ) #define FA_1 NOTE_256 ( 369 ) #define SOL1 NOTE_256 ( 391 ) #define SOL_1 NOTE_256 ( 414 ) #define LA1 NOTE_256 ( 439 ) #define LA_1 NOTE_256 ( 465 ) #define SI1 NOTE_256 ( 493 ) //  #define DO2 NOTE_128( 522 ) #define DO_2 NOTE_128( 553 ) #define RE2 NOTE_128 ( 586 ) #define RE_2 NOTE_128 ( 621 ) #define MI2 NOTE_128 ( 658 ) #define FA2 NOTE_128 ( 697 ) #define FA_2 NOTE_128 ( 738 ) #define SOL2 NOTE_128 ( 782 ) #define SOL_2 NOTE_128 ( 829 ) #define LA2 NOTE_128 ( 878 ) #define LA_2 NOTE_128 ( 930 ) #define SI2 NOTE_128 ( 985 ) //  #define DO3 NOTE_64( 1047 ) #define DO_3 NOTE_64( 1109 ) #define RE3 NOTE_64 ( 1175 ) #define RE_3 NOTE_64 ( 1245 ) #define MI3 NOTE_64 ( 1319 ) #define FA3 NOTE_64 ( 1397 ) #define FA_3 NOTE_64 ( 1480 ) #define SOL3 NOTE_64 ( 1568 ) #define SOL_3 NOTE_64 ( 1661 ) #define LA3 NOTE_64 ( 1760 ) #define LA_3 NOTE_64 ( 1865 ) #define SI3 NOTE_64 ( 1976 ) //  #define DO4 NOTE_32( 2093 ) #define DO_4 NOTE_32( 2217 ) #define RE4 NOTE_32 ( 2349 ) #define RE_4 NOTE_32 ( 2489 ) #define MI4 NOTE_32 ( 2637 ) #define FA4 NOTE_32 ( 2794 ) #define FA_4 NOTE_32 ( 2960 ) #define SOL4 NOTE_32 ( 3136 ) #define SOL_4 NOTE_32 ( 3322 ) #define LA4 NOTE_32 ( 3520 ) #define LA_4 NOTE_32 ( 3729 ) #define SI4 NOTE_32 ( 3951 ) 



Dan untuk durasi not, kita hanya memiliki 5 bit tersisa, jadi mari kita hitung durasinya.

Durasi


Pertama, Anda perlu menerjemahkan nilai tempo ke unit sementara (misalnya, dalam milidetik) - Saya melakukannya seperti ini:
Durasi ukuran musik dalam ms = (60.000 ms * 4 kuartal) / nilai tempo.

Dengan demikian, jika kita berbicara tentang beat parts, maka nilai ini perlu dibagi, dan pada awalnya saya berpikir bahwa shift kiri yang biasa untuk divider akan cukup. Yaitu kodenya adalah ini:

 uint16_t calc_note_delay(uint16_t precalced_tempo, uint16_t note) { return (precalced_tempo / _BV((note >> 11) & 0b00111)); } 


Yaitu Saya menggunakan 3 bit (dari 5 sisanya) dan mendapat bagian dari irama musik dari derajat 2 hingga 1/128. Tetapi ketika saya memberi seorang teman meminta saya untuk menulis semacam nada dering untuk besi saya, ada pertanyaan mengapa tidak ada 1/3 atau 1/6 dan saya mulai berpikir ...

Pada akhirnya, saya membuat sistem yang rumit untuk mendapatkan durasi seperti itu. Satu bit dari sisa 2x - saya habiskan untuk tanda multiplikasi 3 untuk pembagi jam yang diperoleh setelah shift. Dan bit terakhir adalah untuk menunjukkan apakah perlu untuk mengurangi 1. Ini sulit untuk dijelaskan, lebih mudah untuk melihat kode:
 uint16_t calc_note_delay(uint16_t precalced_tempo, uint16_t note) { note >>= 11; uint8_t divider = _BV(note & 0b00111); note >>= 3; divider *= ((note & 0b01) ? 3 : 1); divider -= (note >> 1); return (precalced_tempo / divider); } 

Lalu saya "mendefinisikan" semua catatan yang mungkin (kecuali yang kurang dari 1/128).
Inilah mereka
 #define DEL_MINUS_1 0b10000 #define DEL_MUL_3 0b01000 #define DEL_1 0 #define DEL_1N2 1 #define DEL_1N3 (2 | DEL_MINUS_1) #define DEL_1N4 2 #define DEL_1N5 (1 | DEL_MINUS_1 | DEL_MUL_3) #define DEL_1N6 (1 | DEL_MUL_3) #define DEL_1N7 (3 | DEL_MINUS_1) #define DEL_1N8 3 #define DEL_1N11 (2 | DEL_MUL_3 | DEL_MINUS_1) #define DEL_1N12 (2 | DEL_MUL_3) #define DEL_1N15 (4 | DEL_MINUS_1) #define DEL_1N16 4 #define DEL_1N23 (3 | DEL_MUL_3 | DEL_MINUS_1) #define DEL_1N24 (3 | DEL_MUL_3) #define DEL_1N31 (5 | DEL_MINUS_1) #define DEL_1N32 5 #define DEL_1N47 (4 | DEL_MUL_3 | DEL_MINUS_1) #define DEL_1N48 (4 | DEL_MUL_3) #define DEL_1N63 (6 | DEL_MINUS_1) #define DEL_1N64 6 #define DEL_1N95 (5 | DEL_MUL_3 | DEL_MINUS_1) #define DEL_1N96 (5 | DEL_MUL_3) #define DEL_1N127 (7 | DEL_MINUS_1) #define DEL_1N128 7 



Menyatukan semuanya


Total, kami memiliki format berikut untuk elemen array nada dering kami.

  • 1bit: delay divider - 1
  • 1bit: tunda pembagi * 3
  • 3bit: tunda shift pembagi
  • 3bit: pembagi jam cpu
  • 8bit: nilai OCR2A

Hanya 16 bit.

Pembaca yang budiman, jika Anda mau, Anda bisa memimpikan formatnya sendiri, mungkin sesuatu yang lebih luas daripada milik saya akan lahir.

Kami lupa menambahkan catatan kosong, mis. diam. Dan akhirnya, saya menjelaskan mengapa di awal, dalam fungsi timer2_buzzer_init (), kami secara khusus mengatur kaki PB3 ke input dan bukan output. Mengubah register DDRB, kita akan menghidupkan dan mematikan pemutaran "keheningan" atau komposisi secara keseluruhan. Karena kita tidak dapat memiliki catatan dengan nilai 0 - itu akan menjadi catatan "kosong".

Tentukan makro yang hilang dan fungsi untuk mengaktifkan pembuatan suara:
 #define EMPTY_NOTE 0 #define NOTE(delay, note) (uint16_t)((delay << 11) | note) ........ ........ ........ void play_music_note(uint16_t note) { if (note) { TCCR2B = (note >> 8) & DIV_MASK; OCR2A = note & 0xff; sbi(DDRB, BUZ_PIN); } else cbi(DDRB, BUZ_PIN); } 

Sekarang saya akan menunjukkan kepada Anda seperti apa nada dering yang ditulis menurut prinsip ini:

 const uint16_t king[] PROGMEM = { NOTE(DEL_1N4, MI3), NOTE(DEL_1N4, FA_3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N4, LA3), NOTE(DEL_1N4, SI3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N2, SI3), NOTE(DEL_1N4, LA_3), NOTE(DEL_1N4, FA_3), NOTE(DEL_1N4, LA_3), NOTE(DEL_1N4, EMPTY_NOTE), NOTE(DEL_1N4, LA3), NOTE(DEL_1N4, FA3), NOTE(DEL_1N2, LA3), NOTE(DEL_1N4, MI3), NOTE(DEL_1N4, FA_3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N4, LA3), NOTE(DEL_1N4, SI3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N4, SI3), NOTE(DEL_1N4, MI4), NOTE(DEL_1N4, RE4), NOTE(DEL_1N4, SI3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N4, SI3), NOTE(DEL_1N2, RE4), NOTE(DEL_1N2, EMPTY_NOTE), }; 


Memainkan nada dering


Kami masih memiliki satu tugas - memainkan melodi. Untuk melakukan ini, kita perlu "menjalankan" melalui array nada dering, menahan jeda yang tepat dan mengganti frekuensi catatan. Jelas, kita perlu timer lain, yang, omong-omong, dapat digunakan untuk tugas-tugas umum lainnya, seperti yang biasanya saya lakukan. Selain itu, Anda dapat beralih di antara elemen array baik dalam gangguan timer ini, atau dalam loop utama, dan menggunakan timer untuk menghitung waktu. Dalam contoh ini, saya menggunakan opsi ke-2.

Seperti yang Anda ketahui, isi dari program apa pun untuk MK termasuk loop tak terbatas:
 int main(void) { for(;;) { //   } return 0; } 

Di dalamnya kita akan "berlari" di sepanjang array kita. Tetapi kita membutuhkan fungsi yang mirip dengan GetTickCount dari WinApi, yang mengembalikan jumlah milidetik pada sistem operasi Windows. Tapi tentu saja, di dunia MK tidak ada fungsi seperti itu "di luar kotak", jadi kita harus menulis sendiri.

Timer 1


Untuk menghitung interval waktu (saya sengaja tidak menulis milidetik, nanti Anda akan mengerti alasannya) Saya menggunakan timer 1 bersamaan dengan mode CTC yang sudah dikenal. Timer 1 adalah timer 16-bit, yang berarti bahwa nilai modul perbandingan untuk itu sudah ditunjukkan oleh 2 register 8-bit OCR1AH ​​dan OCR1AL - untuk masing-masing byte tinggi dan rendah. Saya tidak ingin menjelaskan secara rinci pekerjaan dengan timer 1, karena ini tidak berlaku untuk topik utama memo ini. Karena itu, saya akan memberi tahu Anda hanya dalam 2 kata.

Kami sebenarnya membutuhkan 3 fungsi:
  • Inisialisasi pengatur waktu
  • Timer interrupt handler
  • fungsi yang mengembalikan jumlah interval waktu.

File kode C
 #include <avr/io.h> #include <avr/interrupt.h> #include <util/atomic.h> #include "timer1_ticks.h" volatile unsigned long timer1_ticks; //  ISR (TIMER1_COMPA_vect) { timer1_ticks++; } void timer1_ticks_init() { //   // CTC ,     8 TCCR1B |= (1 << WGM12) | (1 << CS11); //     OCR1AH = (uint8_t)(CTC_MATCH_OVERFLOW >> 8); OCR1AL = (uint8_t) CTC_MATCH_OVERFLOW; //    TIMSK1 |= (1 << OCIE1A); } unsigned long ticks() { unsigned long ticks_return; //  ,   ticks_return   //     ATOMIC_BLOCK(ATOMIC_FORCEON) { ticks_return = timer1_ticks; } return ticks_return; } 



Sebelum saya menunjukkan file header dengan CTC_MATCH_OVERFLOW konstan tertentu, kita perlu kembali ke waktu ke bagian "Durasi" dan menentukan makro yang paling penting untuk melodi, yang menghitung tempo melodi. Saya menunggu lama untuk menentukannya, karena langsung terhubung ke pemain, dan karenanya dengan timer 1.
Dalam perkiraan pertama, itu tampak seperti ini (lihat perhitungan di bagian "Durasi"):
 #define TEMPO( x ) (60000 * 4 / x) 

Nilai yang kita dapatkan pada output selanjutnya kita harus mengganti argumen pertama menjadi fungsi calc_note_delay . Sekarang perhatikan fungsi calc_note_delay, yaitu baris:
 return (precalced_tempo / divider); 

Kami melihat bahwa nilai yang diperoleh dengan menghitung makro TEMPO dibagi dengan pembagi tertentu. Ingat bahwa pembagi maksimum yang telah kami tetapkan adalah DEL_1N128 , mis. pembagi akan 128.

Sekarang mari kita ambil nilai tempo umum sama dengan 240 dan lakukan beberapa perhitungan sederhana:
60000 * 4/240 = 1000
Oh horor! Kami hanya mendapat 1000, karena nilai ini masih akan dibagi dengan 128, kami berisiko tergelincir ke 0, dengan harga tinggi. Ini adalah masalah durasi kedua.

Bagaimana cara mengatasinya? Jelas, untuk memperluas rentang nilai tempo, kita perlu menambah jumlah yang diperoleh dengan menghitung makro TEMPO. Ini dapat dilakukan hanya dengan satu cara - untuk menjauh dari milidetik dan menghitung waktu dalam interval waktu tertentu. Sekarang Anda mengerti mengapa selama ini saya menghindari menyebutkan "milidetik" dalam cerita. Mari kita definisikan makro lain:
  #define MS_DIVIDER 4 

Jadikan itu sebagai pembagi milidetik kita - bagilah milidetik, misalnya 4 kali (250 ΞΌs).
Maka Anda perlu mengubah makro TEMPO:
 #define TEMPO( x ) (60000 * MS_DIVIDER * 4 / x) 


Sekarang, dengan hati nurani yang bersih, saya akan memberikan file header untuk bekerja dengan timer 1:
 #ifndef TIMER1_TICKS_H_INCLUDED #define TIMER1_TICKS_H_INCLUDED #define MS_DIVIDER 4 #define CTC_MATCH_OVERFLOW ((F_CPU / 1000) / (8 * MS_DIVIDER)) void timer1_ticks_init(); unsigned long ticks(); #endif // TIMER1_TICKS_H_INCLUDED 

Sekarang kita bisa, mengubah MS_DIVIDER, menyesuaikan rentang untuk tugas kita - saya punya 4 dalam kode saya - ini sudah cukup untuk tugas saya. Perhatian: jika Anda masih memiliki tugas yang "terikat" ke timer 1, jangan lupa untuk melipatgandakan / membagi nilai kontrol waktu untuknya dengan MS_DIVIDER.

Meja putar


Sekarang mari kita tulis pemain kami. Saya pikir semuanya akan jelas dari kode dan komentar.

 int main(void) { timer1_ticks_init(); //   sei(); timer2_buzzer_init(); //    MS_DIVIDER long time_since = ticks(); //       MS_DIVIDER uint16_t note_delay = 0; //     uint16_t note_pos = 0; //  uint16_t length = sizeof(king) / sizeof(king[0]); //     uint16_t tempo = TEMPO(240); for(;;) { unsigned long time_current = ticks(); if (time_current - time_since > note_delay) { //   uint16_t note = pgm_read_word(&king[note_pos]); //   play_music_note(note); //    note_delay = calc_note_delay(tempo, note); //  if (++note_pos >= length) note_pos = 0; time_since = time_current; } } return 0; } 


Kesimpulan


Saya berharap memo ini bermanfaat bagi pembaca yang terhormat dan saya sendiri, agar tidak melupakan semua nuansa lagu, kalau-kalau saya mengambil mikrokontroler AVR lagi.

Yah, secara tradisional video dan kode sumber (saya mengembangkannya di lingkungan Blok Kode, jadi jangan takut dengan file yang tidak jelas):



Kode sumber

Source: https://habr.com/ru/post/id402329/


All Articles