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.


