Arduino Ses Sentezleme

Raphers

Adanmış Üye
31 Ağu 2014
7,286
5
System.Net
Arduino bütün projeler için harika bir platform fakat ses üretmeye gelince birçok kullanıcı basit bir bip sesi elde etmek ile yetiniyor ve dahası için zorlanıyor. Eğer donanımı derinlemesine anlarsanız Arduino ile hayalet ettiğiniz her türlü ses dalgasını oluşturabilir ve gerçek zamanlı olarak üretebilirsiniz. Size bu bölümümüz de ileri düzeyde Arduino ile ses sentezleme nasıl yapılır onu göstereceğiz.

Basit Ses Üretme

“Bit banging” (bit vurma) Arduino’da ses üretmek için bilinen en temel yöntemdir. Bu yöntemde Arduino’nun dijital çıkışına bir speaker (hoparlör) bağlanır ve daha sonra hızlı ve devamlı olarak pin’in HIGH ve LOW (Bkz:Link ) arasında değişmesi sağlanır. Bu Arduina’daki tone() komutunun çalışma mantığıdır. Speaker’ın bir bacağına 8 ohm’luk bir direnç bağlanır ve bu bacak çıkış pin’ine bağlanır. Diğer bacak ise Ground ’a bağlanır.

8gR4mr.png


Bir kere LOWdan HIGH’a geçip tekrar geri gelerek bir kare dalga (Şekil-A) oluşturulmuş olur. HIGH durumunda harcanan zaman mark time ve LOW da harcanan zaman space time olarak ifade edilmiş. Mark ve space time arasındaki değişim oranı, duty cycle ile dalga frekansını değiştirmeden sesin tınısını ve kalitesini değiştirebilirsiniz.

Arduino’nun analogWrite() komutu ile 490Hz’lik kare dalga üretme imkanınız vardır. Speaker’ınızı D9’a bağlayıp (Şekil-B) aşağıdaki kod satırlarını çalıştırınız:

RYz7oR.png


VVWqav.png


Duty cycle ile kare dalganın uyum içinde çalışması ile PWM dalga üretilebilir. Duty Cycle’ı değiştirerek belki çok basit bir ses üretebilirsiniz fakat daha kompleks çıkış için daha detaylı incelememiz lazım.

Digital den Analog’a

PWM dalgalardaki high ve low kesinlikle dijitaldir. Analog dalgalarda ise gerilim seviyesini bu iki aşırı uç arasında yaymamız gerekiyor. Bu işi dijital analog çevirici dediğimiz DAC’ler ile yaparız.

Birçok DAC çeşidi bulunmaktadır bunların en basiti şüphesiz R-2R merdivenidir (Şekil-C). Bu örnekte 4 adet dijital girişimiz D0-D3 olarak adlandırılmıştır. D0 en önemsiz bit ve D3 en önemli bit’i ifade etmekte.

Eğer D0’ı high yaparsak, akım büyük bir direnç olan 2R+R+R+R=5R üzerinden geçecektir ve çıkışa ulaşacaktır. Akımın bir kısmı düşük direnç olan 2R üzerinden ground’a akacaktır. Bu nedenle D3 teki yüksek gerilime göre D0 küçük bir çıkış gerilimi üretecektir. Çünkü D3 output (çıkış)’a ulaşmak için sadece 2R olan düşük direnç ile karşılaşacaktır ve 5R’lik kısımda ground’a akacaktır. ( Bkz: Link )

2gzXnE.png


D0-D3 girişlerini binary olarak 0000 dan 1111 (0-15 decimal) e kadar ayarlarsak ve tekrar geriye 0000 a düşürürsek üçgen dalga (Şekil-D) çıkış elde etmiş oluruz. Diğer dalga türlerini elde etmek için D0-D3 için doğru binary sıralamasını doğru değerlerde yapmamız lazım.

qYOojZ.png


Maalesef R-2R DAC yi kullanmanın dezavantajları da bulunmakta. En önemlisi dalganın biçiminin değiştirilmesi için kullanılacak ekleme çıkarmalar için seçilen direnç değerlerinin çok dikkatli seçilmiş olması gerekir. ****lik uyumsuz seslerden kurtulmak için keskin basamaklar low-pass filter (LPF) kullanılarak yumuşatılmalıdır. Son olarak R-2R DAC kesin ihtiyacın dışında daha fazla çıkışa sahip olmalı.

Anlaması biraz zor olabilir. Akıcı “1-bit” DAC üretmek için tek çıkışa filtre olarak bir direnç ile kondansatör bağlanır. Bu Arduino ya sesi çalarken başka şeyler yapma imkanı da sunar.

1-Bit DAC Teorisi

Eğer speaker’ınızın bir LED ile vurması için yerleştirdiğinizde, duty cycle 0 dan 100% e yükselince LED’in parlaklığı da artacaktır. Aslında iki uç nokta arasında (High, Low) LED 490Hz ile flash yapacaktır fakat çok hızlı olduğundan siz bunu sürekli parlak göreceksiniz.

pgJZGn.png


QAOrpg.png


Gelen PWM dalganın mark time’ı Vout dan tesbit edir. Örneğin, mark/space’i 50:50 olarak gelen bir sinyalin %50 si high gerilimdir. 75:25 sinyalinin ise %75’i dir. Bu nedenle Arduino’da dijital pin 5V ürettiği düşünülürse %50 duty cycle da 2,5V olarak Vout elde edilecektir.

En kaliteli sessi elde etmek için PWM sinyalinizin olabildiğince hızlı olması gerekir ve Arduino bize 62.5KHz’lik bir PWM üretebilmektedir.

Arduino’da 1-Bit DAC

Arduino Nano da ATmega328 çip bulunmaktadır ve bu 3 tane donanımsal zamanlayıcıya sahiptir. Her zamanlayıcı bir sayıcı içerir. Bu her bir saat tıkın da sayımı artırır. Taşma durumunda otomatik olarak sıfırlanır. Bu sayıcı TCNTn olarak adlandırılmıştır. n ise zamanlayıcının numarasındır.

Timer0 ve Timer2 8 bit’lik zamanlayıcılardır. Bu nedenle TCNT0 ve TCNT2 defalarca 0 dan 255 e sayacaktır. Timer1 16 bit’lik bir zamanlayıcı bu nedenle TCNT1 0 dan 65535 e kadar saymakta ayrıca 8-bit mod’unda da çalışabilmektedir. Aslında her bir zamanlayıcının mod’ları biraz farklıdır. “Fast PWM” diye adlandırdığımız sadece Timer1 de gerçekleşebiliyor.

Bu mod’ta TCNT1 ne zaman 0 a taştığında çıkış diğer cycle’ın çalışması için mark’ı high’lar. Timer1 da OCR1A diye bir kaydedici bulunmaktadır bu mark time’ı kurmamızı sağlar. TCNT1, OCR1A da saklanan değere ulaşınca çıkış low olur. Cycle’ın mark time’ı biter ve space time başlar. Artış TCNT1 taşana kadar devam eder ve sonra başa dönerek işlem tekrarlanır. Bu işlem Şekil-F de grafiksel olarak gösterilmiştir.

vEW16m.png


Temel Dalga Oluşturma

XvRQMj.png


Yukarıdaki gördüğünüz gibi bir lookup table, fast PWM mode ve 1-bit DAC ile sinüs dalga üretmek için gereki sketch bulunmaktadır.

İlk olarak dalga şeklini hesaplar ve bir array dizisi içinde byte serisi olarak saklarız. Bunlar direk olarak OCR1A a yüklenir. Sonra fast PWM üretmek için timer1 i başlatırız. Biz bunu 8-bit olarak kurmamız lazım çünkü timer1 16-bit tir normal durumda.

Timer2, CPU’yu düzenli olarak keser ve bir sonraki dalgayı OCR1A ya yüklemek için özel bir fonksiyon çağırır. Bu fonksiyon interrupt service routine (ISR) olarak adlandırılmış ve ne zaman TCNT2 OCR2A ya eşit olursa timer2 tarafından çağırılır. ISR diğer fonksiyonlar gibi yazılır tek farkı geriye değer döndürmez.

Arduino Nano’nun sistem saati 16MHz de çalışır bu timer2’nin çok hızlı bir şekilde çağırılmasını sağlar. “prescaler” donanımla uyumu için bizim bunu yavaşlatmamız lazım. Prescaler’i 8’e bölersek TCNT2’yi 2MHz e ayarlamış oluruz.

OCR2A’yı üretilen dalganın frekansının kontrolü için kurduk. Sonucun hesaplanması TCNT2 değerinin (2MHz olarak belirlemiştik) OCR2A değeri ile lookup table uzunluğunun çarpımına bölümü ile yapılır. Eğer OCR2A’yı 128 olarak ayarlarsak;


5d65qA.png


ISR çalışmak için zaman ister biz bunu TCNT2 yi 0 a dönmeden 6 ya ayarlayarak telafi edebiliriz. Zamanı daha da sıkıştırmak için asm(“NOP;NOP”) ’ı ekledik.

RYz78o.png


Şekil-G deki gibi direnç ve kondansatörü bağlayıp sketch’i çalıştıralım. Vout’u Osiloskop’a bağladığınızda pürüzsüz bir sinüs dalga göreceksiniz. Sesi duymak isterseniz Şekil-H’deki gibi bir transistör ile speaker bağlamanız yeterli.

Basit Dalga Programlama

Bir kere lookup table dan dalga oluşturmayı öğrendiğinizde dalga oluşturmak ve ses üretmek çok kolay olacaktır. Sizi sınırlayan tek şey Arduino’nun hızının yavaşlığı ve hafıza kapasitesi olacaktır.

Listing-2waveform() içerisine daha önceden hazırlanmış dalga formlarının tablolarını ( SQUARE,SINE,TRIANGLE,RAMP ve RANDOM) yükleyerek aşağıdaki dalga şekillerini elde edebilirsiniz.

62WAbN.png


RANDOM fonksiyonu tabloyu pseudorandom integer tabanlı seed değerler ile doldurur. randomSeed()fonksiyonu ile farklı pseudorandom diziler üretmemize olanak sağlar. Bu sesler ilginç olmakla birlikte gürültülüdür. Bizim kompleks dalgaları şekillendirmek için daha iyi bir yola ihtiyacımız var.

Katkı Sentezi

19. yüzyılda Joseph Fourier bize genlikleri ve frekansları farklı yeterli sinüs dalgalar ile herhangi bir dalga formunun tekrar üretilip sentezlenebileceğini gösterdi. Bu sinüs dalgalar kısmi veya harmonik olarak adlandırıldı. Alçak frekans harmonik ilk harmonic veya fundamental (temel olarak) adlandırıldı. Harmoniklerin birleştirilip, işlenip yeniden bir dalga formunun oluşturulmasına Katkı Sentezi adı verildi.

Verilen karmaşık bir dalgayı küçük sayıdaki harmonikler ile kombine ederek kabaca sentezleyebiliriz. Ne kadar fazla harmonik kullanırsak o kadar fazla doğru sentezleme yapmış oluruz.
Profesyonel katkı sentezleyicileri 100 den fazla harmoniği bu yöntem ile birleştirebilmekte ve etkileyici tını değişiklikleri oluşturmak için gerçek zamanlı olarak genliklerini ayarlamayabilmektedir. Bu Arduino’nun gücünün ötesinde fakat ilginç seslerle dalga tablosunu yükleyebilmemiz için yeterli.

5d65XM.png


Şekil-J’de üç tane harmonikin eklenmesi bir dalga formu oluşturmuş. Bu nekadar yuvarlanmış olsa da belirgin bir biçimde kare görünümlü bir ses.

Listing-1 deki loop’u sinüs dalga oluşturuyor olarak düşünebilirsiniz. Bu fundamental’i çağırır. 1/4 genlikte (Şekil-J) bir üçüncü harmonik eklemek için aşağıdaki basamakları ekleriz:

VVWqnr.png


Bu yeni adımda üçüncü harmoniği üretmek için loop sayacını çokluyoruz ve genliği azaltmak için dördüncünün zayıflama faktörü ile böleriz.

Listing-3 te bunun genel fonksiyonu bulunmaktadır. Bu, birleştirmek istediğimiz harmonikleri ve bunların zayıflama faktörlerini 2 array ile listelemekte.

a5rOr5.png


Şekil-K da ilk 8 tuhaf harmonik’in eklenmesi bize kare dalgaya yakın bir sonuç vermiş durumda. Bağımsız sinüs dalgaların biraz dalgalandığı görülür. Daha fazla partial’ın eklenmesi bu dalgalanmayı azaltabilir.

Arama tablosundaki (lookup table) yüklenen ses tınısını değiştirmek için 2 array daki değerleri değişmemiz yeter. Sıfır zayıflama, ilgili harmonik göz ardı edildi anlamına gelir. Listing-3 teki array (diziler) iyi bir kare dalga (Şekil-K) üretmek için yazılmıştır. Dizileri deneyip nasıl bir ses verdiğini gözlemleyebilirsiniz.

Morphing Dalga Şekilleri

Profesyonel sentezleyiciler özel efektler için elektronik devreler yada programlar ile sesi filtrelemektedir. Örneğin, çoğu LPF (low-pass filter) a sahiptir ve bu, başlarken “waa” biterken de “yeow” sesi vermektedir. Temel olarak bir LPF yüksek partial’ları kademeli olarak filtreler. Sayısal olarak, düzgün bir filtreleme Arduino için çok fazladır fakat ses çalarken benzer efektler elde etmek için yapabileceğimiz şeyler bulunmakta.

Listing-4 deki bir fonksiyonla her dalga değerini ikinci bir filtreleme tablosu ile karşılaştırabiliriz. Değerler farklı olduğunda fonksiyon dalga değerini filtre değerine iteler. Sanki çalıyor gibi yavaş yavaş ses geçişi olur.

Bir sinüs dalganın filtreleme için kullanılması, gerçek LPF ye yakındır. Harmonik kademeli olarak kaldırılır ve sonuna “oww” eklenir. Eğer diğer tarafa geçersek, dalga tablosunu bir sinüs dalga ile ve filtre tablosunu da karmaşık bir dalga ile yüklersek, başlamak için bir “waa” ekleriz. İstediğiniz herhangi bir dalga ile 2 tabloyu yükleyebilirsiniz.

Nota Oluşturmak

Peki ya gerçek bir enstrümandan çıkan bir tını gibi ses elde etmek istersek ne yapacağız?

Listing-5 deki fonksiyon sürekli dalga tablosu değerleri geri düz hat seviyesine itelenerek sesi, sessizliğe doğru bozar. Bu durum tablodan her bir değer karşılaştırılır. Eğer 127 den fazla ise bu azaltılır. Eğer az ise artırılır. Bozulma değeri delay() fonksiyonu ile kontrol edilir. Her karşılaştırma taramasının sonunda çağırılır.

Bir kere dalga ezildiğinde CPU bağlanarak ISR çalışır. cli() fonksiyonu setup’ta sei() fonksiyonunun kurulması ile kesme bayrağını temizler ve onu kapatır.

Program Hafızasını Kullanma

Arduino’nun Atmel işlemcisi Harvard tipi bir yapıya sahiptir. Bu da program hafızasını değişken hafızasında ayırır. Değişken hafızası da uçucu ve uçucu olmayan alanlara bölünür. Arduino Nano 2KB’lık bir değişken alanına sahiptir. Fakat 30KB büyük bir alan program alanı için ayırılmıştır.

Ses dalgasının verilerini burada saklama imkânımız var bu sayede çalınabilir ses repertuvarımızı büyük ölçüde genişletmiş oluruz. Veri sadece okunabilir program alanında saklanır fakat biz RAM içine yükleyerek çalma sırasında bunları kullanabiliriz.

Listing-6 bu yöntemi göstermektedir. Bir sinüs dalgayı bir dizi içinde program alanındaki dalga tablosu içine yükledik ve bunun için pgmspace.h kütüphanemiz olması lazım. PROGMEM anahtar kelimesi kullanıyoruz dizimizi beyan etmek için.

prog_char ref[256] PROGMEM = {128,131,134,…};

Prog_char ,pgmspace.h kütüphanesi içinde tanımlanmış ve byte veri türüne benzemektedir. Eğer ref[] dizisine ulaşmak istersek program değişken alanına bakacaktır. Pgm_read_byte dahili fonksiyonunu kullanırız bunun için.

Eğer birden fazla dalga şeklini bu şekilde saklamak istiyorsanız pgm_read_byte içindeki diziyi normal iki boyutlu dizi gibi yapabilirsiniz. Dizi isminden önce & işaretini koymayı unutmayın.

Gelecekte

Sesli geri bildirim bir çok projede hataları bildirmede yada bir tuşuna basıldığında geri bildirim yapmasında kullanılabilir. Geçen yıl bir arkadaş bana bir kafenin merdivenlerini piyano sesi yapmak istediğini ve her basamakta bir notayı çalmak istediğini söylemişti. Bu projeyi eğer uygular ve anlarsanız arkadaşın o projesinin çözümünü bulmuş olursunuz.

Kodların Zip Hali: Link
Hazır Dalga Tablosu: Link
 
Üst

Turkhackteam.org internet sitesi 5651 sayılı kanun’un 2. maddesinin 1. fıkrasının m) bendi ile aynı kanunun 5. maddesi kapsamında "Yer Sağlayıcı" konumundadır. İçerikler ön onay olmaksızın tamamen kullanıcılar tarafından oluşturulmaktadır. Turkhackteam.org; Yer sağlayıcı olarak, kullanıcılar tarafından oluşturulan içeriği ya da hukuka aykırı paylaşımı kontrol etmekle ya da araştırmakla yükümlü değildir. Türkhackteam saldırı timleri Türk sitelerine hiçbir zararlı faaliyette bulunmaz. Türkhackteam üyelerinin yaptığı bireysel hack faaliyetlerinden Türkhackteam sorumlu değildir. Sitelerinize Türkhackteam ismi kullanılarak hack faaliyetinde bulunulursa, site-sunucu erişim loglarından bu faaliyeti gerçekleştiren ip adresini tespit edip diğer kanıtlarla birlikte savcılığa suç duyurusunda bulununuz.