Znalazłem jeszcze jedno zastosowanie PWM, dosyć rozrywkowe. Do wyjścia PWM można podłączyć głośniczek i odtwarzać proste melodie. Najpierw schemat:
Rezystor może być inny, ale nie mniejszy niż 110, 120 omów. Z wyjścia STM32F407 można pobierać tylko 25mA prądu. Biorąc pod uwagę rezystancję głośniczka mamy równanie: 3V/(8ohm + 120ohm) = 0,023A. Tak więc przy tej rezystancji zmieścimy się w zakresie.
Teraz wystarczy tylko poszukać jakie częstotliwości odpowiadają poszczególnym dźwiękom i można odtworzyć dźwięki gamy:
Teraz wystarczy tylko poszukać jakie częstotliwości odpowiadają poszczególnym dźwiękom i można odtworzyć dźwięki gamy:
//C, D, E, F, G, A, H, C var scale = new[]{261.6, 293.7, 329.2, 349.6, 391.9, 440.0, 493.9, 523.2}; var pwm4 = new PWM(Cpu.PWMChannel.PWM_4, 50, 0, false); pwm4.Start(); foreach (double note in scale) { pwm4.Frequency = note; pwm4.DutyCycle = 0.5; Thread.Sleep(1000); } pwm4.Stop();
Aby zagrać jakąś prostą melodię trzeba się nieźle nawpisywać tych częstotliwości. Z pomocą przychodzi format RTTL lub MML. Na internecie można znaleźć mnóstwo melodyjek zapisanych w tych formatach.
Konstrukcję odtwarzacza RTTL rozpoczynamy od zdefiniowania interfejsu ISpeaker. Interfejs da możliwość dowolnej implementacji urządzenia wyjściowego. Jak widać są tylko dwie metody Play i Pause. Czyli to co głośniczek najlepiej umie robić: albo milczeć albo grać.
public interface ISpeaker { void Pause(); void Play(double frequency); }
Teraz implementacja tego interfejsu na wyjściu PWM z podłączonym głośniczkiem:
public class PwmSpeaker : ISpeaker { private readonly PWM _pwm; public PwmSpeaker(PWM pwm) { _pwm = pwm; _pwm.Frequency = 50; _pwm.DutyCycle = 0; _pwm.Start(); } public void Play(double frequency) { _pwm.Frequency = frequency; _pwm.DutyCycle = 0.5; } public void Pause() { _pwm.Frequency = 50; _pwm.DutyCycle = 0; } }
Przechodzimy do odtwarzacza RTTL. Całe zadanie polega na zdekodowaniu napisu w formacie RTTL na poszczególne częstotliwości dźwięków i czas ich trwania. Później trzeba to wysłać do ISpeakera.
Do dekodowania formatu RTTL na bardziej przyjazne dane służy statyczna metoda Parse klasy Rttl:
public static Rttl Parse(string rttlData) { string[] sections = rttlData.Split(':'); string name = sections[0].Trim(); string[] tones = sections[2].Split(','); int duration = 4; int octave = 6; int bpm = 63; if (sections[1].Length > 0) { string[] controls = sections[1].Split(','); foreach (string item in controls) { string control = item.Trim(); string valueStr = control.Substring(2, control.Length - 2); int value = Int32.Parse(valueStr); switch (control[0].ToLower()) { case 'd': duration = value; break; case 'o': octave = value; break; case 'b': bpm = value; break; } } } var result = new Rttl { Name = name, Tones = tones, Duration = duration, Octave = octave, Bpm = bpm }; return result; }
Jak już mamy wszystkie potrzebne dane to trzeba znaleźć tylko odpowiednie częstotliwości. Klasa RttlPlayer ma zdefiniowane tablice z częstotliwościami poszczególnych dźwięków. Wartości wypełnione są tylko dla pierwszej oktawy. Pozostałe oktawy zostaną uzupełnione poprzez podwojenie częstotliwości z oktawy poprzedzającej w statycznym konstruktorze.
private static readonly double[][] Scales = new[] { new[] { //C, Cis, D, Dis, E, F, Fis, G, Gis, A, Ais, H 261.6, 277.2, 293.7, 311.2, 329.2, 349.6, 370, 391.9, 415.3, 440.0, 466.2, 493.9 }, new double[12], new double[12], new double[12] }; static RttlPlayer() { for (int i = 1; i < Scales.Length; i++) for (int j = 0; j < Scales[i].Length; j++) Scales[i][j] = 2*Scales[i - 1][j]; }
No teraz to wystarczy wszystko połączyć w całość w metodzie Play RttlPlayera.
public void Play(string rttlData) { const int oneBpmWholeNote = 60*4*1000; //one bpm whole note in ms Rttl rttl = Rttl.Parse(rttlData); int wholeNote = oneBpmWholeNote/rttl.Bpm; foreach (string tone in rttl.Tones) { bool specialDuration; string durationStr, noteStr, scaleStr; ParseCommand(tone, out durationStr, out noteStr, out scaleStr, out specialDuration); int duration = rttl.Duration; if (durationStr.Length > 0) duration = Int32.Parse(durationStr); duration = specialDuration ? (3*wholeNote)/(2*duration) : wholeNote/duration; int freqIndex; switch (noteStr[0].ToLower()) { case 'c': freqIndex = 0; break; case 'd': freqIndex = 2; break; case 'e': freqIndex = 4; break; case 'f': freqIndex = 5; break; case 'g': freqIndex = 7; break; case 'a': freqIndex = 9; break; case 'b': freqIndex = 11; break; default: freqIndex = -1; break; } if (noteStr.Length > 1)//# freqIndex++; int scale = rttl.Octave; if (scaleStr.Length > 0) scale = Int32.Parse(scaleStr); if (freqIndex >= 0) { double freq = Scales[scale - 4][freqIndex]; Debug.Print("Playing: (" + tone + ")" + freq + " " + duration); _speaker.Play(freq); Thread.Sleep(duration); _speaker.Pause(); } else { Debug.Print("Pausing: (" + tone + ") " + duration); _speaker.Pause(); Thread.Sleep(duration); } } }
A tak można odtwarzać melodyjki:
var pwm = new PWM(Cpu.PWMChannel.PWM_4, 50, 0, false); var player = new RttlPlayer(new PwmSpeaker(pwm)); const string song = "Indiana:d=4,o=5,b=250:e,8p,8f,8g,8p,1c6,8p.,d,8p,8e,1f,p.,g,8p,8a,8b,8p,1f6,p,a,8p,8b,2c6,2d6,2e6,e,8p,8f,8g,8p,1c6,p,d6,8p,8e6,1f.6,g,8p,8g,e.6,8p,d6,8p,8g,e.6,8p,d6,8p,8g,f.6,8p,e6,8p,8d6,2c6"; player.Play(song);
Przykład odtwarzania formatu MTL można znaleźć w projekcie .NET Micro Framework Toolbox.
Brak komentarzy:
Prześlij komentarz