Anfang letzten Monat habe ich mir mal ein paar Gedanken gemacht zu dem Thema: Sound, Musik und Töne auf dem MSP430 generieren. Da es in diesem Bereich ziemlich viele verschiedene Möglichkeiten gibt, Musik zu erzeugen, habe ich ungefähr eine ganze Woche für das folgende Sound-Modul gebraucht. Der Algorithmus sollte so klein wie möglich bleiben und natürlich sollte die Musik im Hintergrund automatisch abgespielt werden, ohne manuell eine Funktion aufrufen zu müssen. Das bedeutet also: Ein Interrupt muss dafür sorgen, dass die Noten automatisch abgespielt werden. Weiterhin sollte es so einfach wie möglich aufgebaut werden. Also habe ich mich dazu entschlossen, Töne per PWM (Pulsweitenmodulation) zu erzeugen. So wird es auch beim Game Boy Advance realisiert (als grobes Beispiel). Als Grundlage habe ich den typischen Tracker-Stil (auch Rastersequenzer), benutzt bzw. habe dies als Basis genommen. Die Code-Größe bleibt somit ziemlich klein. Zum Einsatz kommt ein MSP430G2553 (wie so oft in letzter Zeit) der zwei Timer besitzt.
Video
Schaltung
Einen Lautsprecher sollte man nicht direkt an einen Port-Ausgang von einem MSP430 anschließen, da die Leistung von einem Port absolut nicht ausreichend ist. Also muss eine Verstärkerschaltung / Treiberstufe benutzt werden. Ich habe eine ganze einfache Gegentaktendstufe oder auch Komplementärendstufe benutzt. Diese ist in diesem Beispiel sehr einfach aufgebaut und reicht für kleine Spielerein. Hier kann man natürlich noch unglaublich viel tunen und modifizieren und sich auch wunderbar einen abrechnen, wenn man Lust hat. Weitere Infos unter Klangerzeugung (Mikrocontroller.net).
Tonerzeugung
Wie schon weiter oben geschrieben, kommt ein MSP430G2553 zum Einsatz. Dieser besitzt insgesamt zwei 16bit-Timer. Ein Timer erzeugt per Hardware die Noten-Frequenz (PWM) und der andere Timer ist für die BPM (Beats per Minute) und das Abspielen der Noten zuständig. Das Noten-Update erfolgt in einer Interrupt-Service-Routine (ISR). DIe ISR sollte natürlich so schnell wie möglich abgearbeitet werden um anderen Berechnungen und Abläufe nicht zu lange zu unterbrechen. Wie man in dem Video sehen kann, wird nebenbei noch ein kleiner rotierender 3D-Dot-Cube erzeugt, der auf die Noten reagiert. Wie man mit dem MSP430 eine PWM (Pulsweitenmodulation) erzeugt, habe ich vor ein paar Monaten beschrieben: MSP430 PWM.
Abgespeichert werden die Noten in einem 2D-Array. Pattern-Size ist standardmäßig auf 16 gesetzt, kann aber auch verkleinert oder vergrößert werden. Um die Patterns abzuspielen, müssen die Pattern-Positionen in einem Sequencer-Array abgespeichert werden. Die Noten in den festgelegten Patterns werden somit automatisch eingelesen und abgespielt.
Source-Code
Kompletter Source-Code: DOWNLOAD
main.c
/*******************************
* __
* /\ \
* __ _ \_\ \ __ ___
* /\ \/'\ /'_' \ /'__'\ /'___\
* \/> <//\ \ \ \/\ __//\ \__/
* /\_/\_\ \___,_\ \____\ \____\
* \//\/_/\/__,_ /\/____/\/____/
*
* Author: declis
********************************/
#include <msp430g2553.h>
#include "typedef.h"
#include "lib_sound.h"
#include "delay.h"
void main(void)
{
WDTCTL=WDTPW+WDTHOLD;
BCSCTL1=CALBC1_16MHZ; // SMCLK=~16MHz
DCOCTL=CALDCO_16MHZ;
init_sound(110,ONCE);
start_sound();
while(1);
}
lib_sound.c
/*******************************
* __
* /\ \
* __ _ \_\ \ __ ___
* /\ \/'\ /'_' \ /'__'\ /'___\
* \/> <//\ \ \ \/\ __//\ \__/
* /\_/\_\ \___,_\ \____\ \____\
* \//\/_/\/__,_ /\/____/\/____/
*
* Author: declis
********************************/
#include <msp430g2553.h>
#include "typedef.h"
#include "delay.h"
#include "lib_sound.h"
const uint snd_pattern[][PATTERN_SIZE]={
E4,E4,0,E4,0,C4,E4,0,G4,0,0,0,G3,0,0,0, // 00 Mario start
C4,0,0,G3,0,0,E3,0,0,A3,0,B3,0,As3,A3,0, // 01
G3,E4,0,G4,A4,0,F4,G4,0,E4,0,C4,D4,B3,0,0, // 02
0,0,G4,Fs4,F4,Ds4,0,E4,0,Gs3,A3,C4,0,A3,C4,D4, // 03
0,0,G4,Fs4,F4,Ds4,0,E4,0,C5,0,C5,C5,0,0,0, // 04
0,0,Ds4,0,0,D4,0,0,C4,0,0,0,0,0,0,0, // 05
C4,C4,0,C4,0,C4,D4,0,E4,C4,0,A3,G3,0,0,0, // 06
C4,C4,0,C4,0,C4,D4,E4,0,0,0,0,0,0,0,0, // 07 Mario End
C5,C5,A4,A4,C5,C5,D5,C5,0,E4,E4,E4,F4,F4,Fs4,Fs4, // 08 Bob-omb start
G4,G4,G4,0,G4,G3,G3,G4,G4,0,0,0,0,0,D5,Ds4, // 09
E5,Ds4,E5,G5,G5,A5,G5,G5,C5,C5,C5,C5,0,0,G4,Gs4, // 10
A4,Gs4,A4,C5,C5,D5,C5,C5,A4,A4,A4,A4,0,0,A4,A4, // 11
G4,E4,G4,G5,0,0,G4,E4,G4,G5,0,0,G4,E4,G4,G5, // 12
0,0,G5,G5,A5,G5,G5,D5,D5,D5,D5,D5,0,0,D4,Ds4, // 13
E4,Ds4,E4,G4,G4,A4,G4,G4,As4,As4,As4,As4,0,0,Gs4,Gs4, // 14
G4,Fs4,G4,G3,0,0,G4,Fs4,G4,G3,0,Ds4,E4,E4,E4,E4, // 15
C4,C4,C4,C4,C4,C4,C4,C4,C4,C4,C4,C4,0,0,0,0, // 16 Bob-omb end
A5,A5,A5,A5,A3,A3,A3,A3,A5,A5,A5,A5,A3,A3,A3,A3, // 17 Princess start
A5,A5,A5,A5,Fs5,Fs5,Fs5,Fs5,E5,E5,E5,E5,Ds5,Ds5,Ds5,Ds5, // 18
D5,D5,G3,G3,B3,B3,G3,G3,D5,D5,G3,G3,B3,B3,G3,G3, // 19
D5,D5,G3,G3,B3,B3,G4,B4,D5,D5,G3,G3,E5,E5,G3,G3, // 20
D5,D5,Fs3,Fs3,A3,A3,A4,A4,Fs5,Fs5,Fs3,Fs3,A5,A5,Fs3,Fs3, // 21
Gs5,Gs5,Fs3,Fs3,A5,A5,Fs3,Fs3,Gs5,A5,Gs5,Fs5,D5,D5,A4,A4, // 22
B4,B4,G3,Cs5,D5,D5,G3,E5,Fs5,F5,Fs5,G5,A5,G5,Fs5,E5, // 23
D5,Cs5,D5,A5,Cs5,C5,Cs5,A5,Cs5,C5,B4,As4,A4,Gs4,G4,E4, // 24
A4,A4,A3,A3,D4,D4,A3,A3,B4,B4,A3,A3,A4,A4,A3,A3, // 25
D4,D4,D5,D5,Cs5,Cs5,D5,D5,Fs5,Fs5,A3,A3,E5,E5,D5,D5, // 26
B4,B4,G3,G3,B4,B4,G3,G3,E5,E5,G3,G3,D5,D5,G3,G3, // 27
B4,B4,B4,B4,As4,As4,B4,B4,D5,D5,G3,G3,Cs5,Cs5,B4,B4, // 28
A4,A4,Fs3,Fs3,Fs3,Fs3,A4,A4,Fs3,Fs3,Fs3,Fs3,A5,A5,Fs3,Fs3 // 29 Princess end
};
// array for playing the patterns in snd_pattern array
const uchar snd_sequencer[]={
0,1,2,1,2,3,4,3,5,3,4,3,5,6,7,6, // Mario
0,1,2,1,2,3,4,3,5,3,4,3,5,6,7,6,
0+8,1+8,2+8,3+8,4+8,5+8,6+8,4+8,7+8,8+8, // Bob-omb
0+8,1+8,2+8,3+8,4+8,5+8,6+8,4+8,7+8,8+8,
0+17,1+17,2+17,3+17,4+17,5+17,6+17,7+17,8+17,9+17,10+17,11+17,12+17, // Princess
0+17,1+17,2+17,3+17,4+17,5+17,6+17,7+17,8+17,9+17,10+17,11+17,12+17,
0+17,1+17,2+17,3+17,4+17,5+17,6+17,7+17,8+17,9+17,10+17,11+17,12+17,
0+17
};
uint snd_ms_tick;
uchar snd_loop,snd_pattern_pos,snd_notes_pos,snd_sequencer_size,snd_cut_off;
void init_sound(uint bmp, uchar mode)
{
P1DIR|=SPEAKER; // output
P1SEL|=SPEAKER; // TA0.0 option (PWM output)
P1OUT&=~SPEAKER;
// init. ACLK (32kHz crystal)
BCSCTL3|=XCAP_3; // CL=~12.5pF
do
{
IFG1&=~OFIFG; // IFG1=Interrupt Flag Register 1
wait_ms(1); // OFIFG=Oscillator Fault Interrupt Flag
}
while(IFG1&OFIFG);
snd_cut_off=0; // var for note dead time
snd_pattern_pos=0; // pattern position
snd_notes_pos=0; // notes position (current pattern)
if(mode) snd_loop=1;// play in loop
else snd_loop=0; // play only once
snd_sequencer_size=sizeof(snd_sequencer); // get sequencer size
snd_ms_tick=60000/(TICKS_PER_BEAT*bmp); // note duration
// TimerA0 for NOTE FREQUENCY
TA0CTL|=TASSEL_2+MC_0+ID_3; // SMCLK=16MHz, Timer Div: 8, timer in stop mode
TA0CCR0=snd_pattern[snd_sequencer[snd_pattern_pos]][snd_notes_pos++]; // first note frequency
TA0CCR1=TA0CCR0>>DUTY_CYCLE;
TA0CCTL1|=OUTMOD_7; // reset/set (PWM)
// TimerA1 for NOTE DURATION (BMP)
TA1CTL|=TASSEL_1+MC_0+ID_0; // 32kHz crystal, no divider
//set duration (32768*snd_ms_tick*notes_duration[snd_notes_pos++])/1000-DEAD_MS;
TA1CCR0=((snd_ms_tick-DEAD_MS)<<5);
TA1CCTL0|=CCIE;
_EINT();
}
void start_sound(void)
{
TA0CTL|=MC_1;
TA1CTL|=MC_1;
}
void stop_sound(void)
{
TA0CTL&=~MC_1;
TA1CTL&=~MC_1;
}
#pragma INTERRUPT (update_note);
#pragma vector=TIMER1_A0_VECTOR
void update_note(void)
{
TA1CTL&=~MC_1; // stop duration timer
TA1R=0; // set count register to zero
if(!snd_cut_off) // DEAD TIME
{
TA0CCR0=0; // stop note frequency timer (a ZERO will stop the timer)
TA1CCR0=DEAD_MS<<5; // set dead time
snd_cut_off=1; // will be used after the dead time is over (load next note)
}
else // NOTE TIME
{
if(snd_pattern_pos<snd_sequencer_size)
{
// set note
TA0CCR0=snd_pattern[snd_sequencer[snd_pattern_pos]][snd_notes_pos++];
TA0CCR1=TA0CCR0>>DUTY_CYCLE;
// set duration (32768*snd_ms_tick*notes_duration[snd_notes_pos++])/1000-DEAD_MS;
//TA1CCR0=((snd_ms_tick*notes_duration[snd_notes_pos++])<<5)-DEAD_MS;
TA1CCR0=((snd_ms_tick-DEAD_MS)<<5);
if(snd_notes_pos==PATTERN_SIZE)
{
snd_pattern_pos++; // next pattern
snd_notes_pos=0; // set note position to first note in pattern
if(snd_pattern_pos==snd_sequencer_size&&snd_loop) // loop mode, start again at first pattern
snd_pattern_pos=0;
}
}
else stop_sound();
snd_cut_off=0; // for dead time, see if(!snd_cut_off)
}
TA1CTL|=MC_1; // start timer again
}
lib_sound.h
/*******************************
* __
* /\ \
* __ _ \_\ \ __ ___
* /\ \/'\ /'_' \ /'__'\ /'___\
* \/> <//\ \ \ \/\ __//\ \__/
* /\_/\_\ \___,_\ \____\ \____\
* \//\/_/\/__,_ /\/____/\/____/
*
* Author: declis
********************************/
#ifndef LIB_SOUND_H_
#define LIB_SOUND_H_
#define SPEAKER 0x0004 // P1.2
#define LOOP 1 // play in loop
#define ONCE 0 // play once
#define PATTERN_SIZE 16 // size of pattern
#define DEAD_MS 10 // dead time of note (cut off)
#define DUTY_CYCLE 3 // 2^DUTY_CYLCE ; 2^1=2 (50%); 2^2=4 (25%); 2^3=8 (12.5%)
#define TICKS_PER_BEAT 4 // 4/4
void init_sound(uint,uchar);
void start_sound(void);
void stop_sound(void);
// Precalulated notes: CPUCLK/(note_freq*TimerDiv)
// 16MHz/(note_freq*8)
#define C8 477
#define B7 506
#define As7 536
#define A7 568
#define Gs7 601
#define G7 637
#define Fs7 675
#define F7 715
#define E7 758
#define Ds7 803
#define D7 851
#define Cs7 901
#define C7 955
#define B6 1012
#define As6 1072
#define A6 1136
#define Gs6 1203
#define G6 1275
#define Fs6 1351
#define F6 1431
#define E6 1516
#define Ds6 1607
#define D6 1702
#define Cs6 1803
#define C6 1911
#define B5 2024
#define As5 2145
#define A5 2272
#define Gs5 2407
#define G5 2551
#define Fs5 2702
#define F5 2863
#define E5 3033
#define Ds5 3214
#define D5 3405
#define Cs5 3607
#define C5 3822
#define B4 4049
#define As4 4290
#define A4 4545
#define Gs4 4815
#define G4 5102
#define Fs4 5405
#define F4 5726
#define E4 6067
#define Ds4 6428
#define D4 6810
#define Cs4 7215
#define C4 7644
#define B3 8099
#define As3 8580
#define A3 9090
#define Gs3 9631
#define G3 10204
#define Fs3 10810
#define F3 11453
#define E3 12134
#define Ds3 12856
#define D3 13621
#define Cs3 14430
#define C3 15289
#define B2 16198
#define As2 17161
#define A2 18181
#define Gs2 19262
#define G2 20408
#define Fs2 21621
#define F2 22907
#define E2 24269
#define Ds2 25712
#define D2 27241
#define Cs2 28861
#define C2 30578
#define B1 32396
#define As1 34322
#define A1 36363
#define Gs1 38525
#define G1 40816
#define Fs1 43243
#define F1 45815
#define E1 48539
#define Ds1 51425
#define D1 54483
#define Cs1 57723
#define C1 61156
#define B0 64792
#endif /*LIB_SOUND_H_*/
Für dieses Beispiel habe ich einfach ein paar Melodien von einem anderen Sound-Programm benutzt: bobsomers/msp430-launchpad-music
Hallo,
vielen Dank dass du dein Projekt hier reingestellt hast.
Ich hab nun versucht, dein Code mal auf meinen Controller zu testen, bekomme leider die folgende Fehlermeldung:
Error[e46]: Undefined external “__delay_cycles” referred in delay (
C:\Users\…\Desktop\…\Debug\Obj\delay.r43 )
Hoffe du weißt da weiter.
Der Compiler kennt die Funktion __delay_cycles(value) in dem Modul delay.c nicht. Wieso auch immer. Welche Code Composer Version benutzt du? Sind die Projekteinstellungen auf den 2553 angepasst?
Vielen Dank für die Antwort.
Ja unter den Projekteinstellungen habe ich den 2553 ausgewählt.
Ne als IDE benutze ich den IAR.
Okay, IAR habe ich in Verbindung mit dem MSP430 noch nie benutzt (nur mit einem 8051). Deswegen kann ich nur raten. Könntest du in der Datei main.c und lib_sound.c die Zeile
#include msp430g2553.h
durch#include io430.h
ersetzen und nochmal kompilieren?Hat leider auch nicht funktioniert. Bekomme sogar noch eine weitere Fehlermeldung dazu:
Error[e46]: Undefined external “__delay_cycles” referred in delay
(C:\Users\…\Desktop\…\Debug\Obj\delay.r43)
Error[e46]: Undefined external “_EINT” referred in lib_sound
(C:\Users\…\Desktop\…\Debug\Obj\lib_sound.r43 )
Okay, dann wieder msp430g2553.h einbinden und mal in delay.c (ganz oben) die Datei intrinsics.h einbinden. Dort ist __delay_cycles jedenfalls vorhanden (wenn ich das gerade richtig sehe).
Ja super hat funktioniert. Vielen Dank.
Aber mir stellt sich die Frage, obwohl wir beide den gleichen Prozessor benutzen, wieso hat es bei dir ohne “intrinsics.h” funktioniert und bei mir ist es notwendig?
Kann es wirklich nur bei der IDE liegen?
Perfekt! Liegt dann wohl an IAR, da muss man dann die Header-Datei mit einbinden. Bei CCS wird es wohl automatisch eingebunden.