MSP430 Sound und Musik erzeugen (Tracker)

msp430_music_trackerAnfang 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).

msp430_music_tracker_schematic

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.

timer_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

8 thoughts on “MSP430 Sound und Musik erzeugen (Tracker)

  1. Benjo

    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.

    Reply
  2. declis Post author

    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?

    Reply
  3. Benjo

    Vielen Dank für die Antwort.

    Ja unter den Projekteinstellungen habe ich den 2553 ausgewählt.

    Ne als IDE benutze ich den IAR.

    Reply
  4. declis Post author

    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?

    Reply
  5. Benjo

    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 )

    Reply
  6. declis Post author

    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).

    Reply
  7. Benjo

    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?

    Reply
  8. declis Post author

    Perfekt! Liegt dann wohl an IAR, da muss man dann die Header-Datei mit einbinden. Bei CCS wird es wohl automatisch eingebunden.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

This site uses Akismet to reduce spam. Learn how your comment data is processed.