22.05.2013

Pozytywka z PWM

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:

STM32F4 Speaker

STM32F4Discovery Speaker
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:

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