- 3D-Druck Einstieg und Tipps         
Seite 1 von 2 12 LetzteLetzte
Ergebnis 1 bis 10 von 14

Thema: Sound-Funktion

  1. #1
    Erfahrener Benutzer Fleißiges Mitglied
    Registriert seit
    26.02.2006
    Ort
    München
    Alter
    35
    Beiträge
    161

    Sound-Funktion

    Anzeige

    Praxistest und DIY Projekte
    Hallo!
    Ich bin grad am überlegen, wie ich meinen kleinen Piezo-Speaker ansprechen kann. Das funktioniert schon ganz gut, wenn ich nen Port in Endlosschleife mit delays immer ein- und ausschalte. Jetzt würde ich das ganze nur noch gerne in eine elegantere Funktion schreiben, der ich nur noch ne Frequenz und ne Tonlänge übergebe. In etwa so:
    Code:
    sound( 440, 1000 ); //erzeugt ein a (440Hz), das 1000ms andauert
    Der Piezo hängt bei meinem Mega32 an PortD.6 mit PWM ist also nix zu machen. Rechenzeit spielt erstmal keine Rolle, die Frequenz kann also auch mit Delays erzeugt werden. Timer o.Ä. wären natürlich eleganter.
    Achja, wäre schön, wenn die Funktion so einfach wie möglich gehalten würde, damit sie ein Anfänger auch verstehen kann

    Vielen Dank, Manni

  2. #2
    Erfahrener Benutzer Robotik Einstein Avatar von SprinterSB
    Registriert seit
    09.06.2005
    Ort
    An der Saar
    Beiträge
    2.802
    Am besten geht das in einem Timer.

    Hier mal eine Routine zum Initialisieren, aus dem Wiki zusammengeschnitzt:
    Code:
    #include <avr/io.h>
    #include <avr/interrupt.h>
    #include <avr/signal.h>
    
    // Werte zur Berechnung der Interrupt-Rate bei AVR-Fuses,
    // die auf 1MHz eingestellt sind (Werkseinstellung für internen RC-Oszillator) 
    #define F_CPU                 1000000   // 1 MHz
    
    // Prototypen der Funktion(en)
    void timer1_init (uint16_t);
    
    // Initialisiert Timer1, um mit einer Frequenz von freq IRQs auszulösen
    void timer1_init (uint16_t freq)
    {
        // ATmega: Mode #4 für Timer1 und voller MCU-Takt (Prescale=1)
        TCCR1A = 0;
        TCCR1B = (1 << WGM12) | (1 << CS10);
    
        // OutputCompare1A Register setzen
        OCR1A = (uint16_t) ((uint32_t) F_CPU / freq -1);
    
        // OutputCompare1A Interrupt aktivieren
        TIMSK |= (1 << OCIE1A);
    }
    
    // Die Interrupt Service Routine (ISR) wird mit 
    // der Frequenz freq ausgeführt. 
    SIGNAL(SIG_OUTPUT_COMPARE1A)
    {
        // mach was
    }
    
    void main()
    {
       timer1_init (1000);
       sei();
    
       while (1);
    }
    Für klassische AVRs wie AT90S2313 muss die Initialisierung etwas abgeändert werden, weil sich Bit-Bezeichner unterscheiden:
    Code:
    // AVR Classic:
    // Timer1 läuft mit vollem Takt
    // CTC: Clear Timer on CompareMatch
    // Timer1 ist Zähler
        TCCR1A = 0;
        TCCR1B = (1 << CS10) | (1 << CTC1);
    Disclaimer: none. Sue me.

  3. #3
    Erfahrener Benutzer Fleißiges Mitglied
    Registriert seit
    26.02.2006
    Ort
    München
    Alter
    35
    Beiträge
    161
    Vielen Dank schonmal!
    Code:
    #include <avr/io.h>
    #include <avr/interrupt.h>
    #include <avr/signal.h>
    
    
    // Initialisiert Timer1, um mit einer Frequenz von freq IRQs auszulösen
    void timer1_init( uint16_t freq )
    {
    	// ATmega: Mode #4 für Timer1 und voller MCU-Takt (Prescale=1)
    	TCCR1A = 0;
    	TCCR1B = ( 1 << WGM12 ) | ( 1 << CS10 );
    	
    	// OutputCompare1A Register setzen
    	OCR1A = ( uint16_t ) ( ( uint32_t ) 1000000 / freq -1 ); // konstante 1000000, weil F_CPU auf meinen 16MHz-Quarz gesetzt ist
    	
    	// OutputCompare1A Interrupt aktivieren
    	TIMSK |= ( 1 << OCIE1A );
    }
    
    // Die Interrupt Service Routine (ISR) wird mit
    // der Frequenz freq ausgeführt.
    SIGNAL(SIG_OUTPUT_COMPARE1A)
    {
    	// mach was
    	PORTD ^= ( 1 << PD6 );
    }
    
    int main( void )
    {
    	DDRD |= ( 1 << PD6 );
    	
    	timer1_init( 264 );
    	sei();
    
    	while (1);
    }
    Damit kann ich zumindest schonmal die Frequenz(en) erzeugen. Das funktioniert auch schon sehr zuverlässig (hab mehrere Töne mit unserem Klavier verglichen).
    Aber wie bringe ich den AVR dann dazu, den Ton nur ne bestimmte Zeit zu spielen? Nehm ich da 'ne Zählvariable, die den Ton nur "rauslässt", wenn der Ist-Wert noch unter dem Soll-Wert liegt?

  4. #4
    Erfahrener Benutzer Robotik Einstein Avatar von SprinterSB
    Registriert seit
    09.06.2005
    Ort
    An der Saar
    Beiträge
    2.802
    Du hast duch bestimmt noch mehr Zähler. Nimm einen als Zeitbasis, der zB einen Grundtakt von 1ms oder 10ms macht.

    Im Hauptprog kannst du dann die Frequenz setzen und tönen/schweigen, so lang wie es sein soll.

    Die ANpassung von F_CPU machst du besser im Makro, und nicht an allen Stellen der Quelle, wo es auftaucht. Da übersieht du sonst leicht eine Stelle, wenn du mal nen anderen Quarz dranhängt.

    Die Töne brauchst du übrigens nicht zur Laufzeit immer wieder auszurechnen (die Teilung F_CPU/freq), sondern man kann die OCR-Werte speichern anstatt die Frequenzen. Ausrechnen überlässt man GCC.

    Könnte etwa so aussehen mit den Daten. Hier ein "Happy Birthday"

    Code:
    #define BASE 440
    
    #define C0 		1.0
    #define D0 		1.122
    #define E0	 	1.26
    #define FIS0 	1.414
    #define G0 		1.498
    #define A0 		1.682
    #define H0 		1.888
    #define C1 		2*1.0
    #define D1 		2*1.122
    
    #define FREQ(x) (F_CPU/(2*(x)-1))
    #define TON(a,b,c) { FREQ(BASE*a), DAUER(b), PAUSE(c) }
    #define DAUER(x)   12*x
    #define PAUSE(x)   5*x
    
    typedef struct
    {
    	uint16_t freq;
    	uint8_t   dauer;
    	uint8_t   pause;
    } ton_t;
    
    const ton_t morse_ton[] PROGMEM =
    {
    	TON (D0,   1, 1),
    	TON (D0,   1, 1),
    	TON (E0,   2, 1),
    	TON (D0,   2, 1),
    	TON (G0,   2, 1),
    	TON (FIS0, 4, 2),
    	
    	TON (D0,   1, 1),
    	TON (D0,   1, 1),
    	TON (E0,   2, 1),
    	TON (D0,   2, 1),
    	TON (A0,   2, 1),
    	TON (G0,   4, 2),
    	
    	TON (D0,   1, 1),
    	TON (D0,   1, 1),
    	TON (D1,   2, 1),
    	TON (H0,   2, 1),
    	TON (G0,   1, 1),
    	TON (G0,   1, 1),
    	TON (FIS0, 2, 1),
    	TON (E0,   2, 2),
    	
    	TON (C1,   1, 1),
    	TON (C1,   1, 1),
    	TON (H0,   2, 1),
    	TON (G0,   2, 1),
    	TON (A0,   2, 1),
    	TON (G0,   4, 1),
    	{}
    };
    Disclaimer: none. Sue me.

  5. #5
    Erfahrener Benutzer Fleißiges Mitglied
    Registriert seit
    26.02.2006
    Ort
    München
    Alter
    35
    Beiträge
    161
    Hmmm, ich bin grad nen bisschen gefrustet, weil ich aus deinem letzten Beitrag eigentlich nix verstanden hab.
    Dann habe ich das "Blinky-Tutorial" mal angeschaut - funktioniert soweit auch (auch wenn's 10mal so schnell blinkt; liegt wohl am externen Quarz. btw, wie passt man das auf die tatsächliche Taktfrequenz ändern? ändern von F_CPU hat nix gebracht) - aber was dadrin passiert und wofür die ganzen Register da sind hab ich nicht kapiert. Dann habe ich mir das AVR-GCC-Tut angeschaut und im Timer-Kapitel ebenso nix verstanden...

    Kann mir vll jemand erklären, was bei dem Blinky überhaupt passiert? Wäre schön, wenn das jemand so ausführlich wie möglich kommentieren könnte.

    Vielen Dank, Manni

  6. #6
    Benutzer Stammmitglied
    Registriert seit
    30.03.2006
    Ort
    Lübeck
    Alter
    36
    Beiträge
    60
    nette idee musik mitm µC zu machen... ich hab vor langer zeit mal unter dos mal mit som programm musik über den pc-piepser gemacht, leider weiss ich nicht mehr wie das programm hiess, war noch auf disketten.

    @sprinter: 440Hz hat das A und nicht das C... aber bei dieser anwendung ist das ja eigentlich egal.

  7. #7
    Erfahrener Benutzer Robotik Einstein Avatar von SprinterSB
    Registriert seit
    09.06.2005
    Ort
    An der Saar
    Beiträge
    2.802
    @lionking: Oje, ich wollte mir eben nicht den Wolf transponieren


    In dem Blinky-Beispiel muss man F_CPU anpassen und danach neu übersetzen. Bei manchen Makefile-Generatoren wird das F_CPU schon im Makefile gesetzt bzw. als Parameter an avr-gcc übergeben.

    Am einfachsten verständlich ist das Beispiel blinky-all.c, das alles in einer Quelle vereint. Die Quelle ist auch kommentiert, um zu sagen, was jeweils gemacht wird. Allerdings wird nicht das ganze AVR-Handbuch wiederholt, das wäre *etwas* viel. Hast du mal den Abschnitt zu Timer1 in deinem Handbuch gelesen? Da stehen die Erklärungen der einselnen SFR-Bits, und wozu die gut sind.
    Disclaimer: none. Sue me.

  8. #8
    Benutzer Stammmitglied
    Registriert seit
    30.03.2006
    Ort
    Lübeck
    Alter
    36
    Beiträge
    60
    okok, das c' hat 264Hz

  9. #9
    Erfahrener Benutzer Fleißiges Mitglied
    Registriert seit
    26.02.2006
    Ort
    München
    Alter
    35
    Beiträge
    161
    Melde mich nun mit dem nächsten Problem zurück
    Timer1 habe ich jetzt glaub ich verstanden. Jetzt habe ich folgenden Code ausprobiert:
    Code:
    #include <avr/io.h>
    #include <avr/signal.h>
    #include <avr/interrupt.h>
    #include <avr/delay.h>
    
    #define INTERRUPTS_PER_SECOND 1000
    
    int laenge, sound_on;
    
    // Wird durch jeden IRQ eins hochgezählt
    static volatile uint16_t irq_count = 0;
    
    // Wert für das OutputCompare-Register (OCR1A)
    #define OCR_VAL          (F_CPU / INTERRUPTS_PER_SECOND -1)
    
    // Initialisierung der Hardware
    void ioinit( void )
    {
    	DDRB |= ( 1 << PB3 );
    	DDRD |= ( 1 << PD6 );
    	
    	// Initialisiert Timer1, um jede Sekunde 1000 IRQs auszulösen
    	// ATmega: Mode #4 für Timer1 und voller MCU-Takt (Prescale=1)
    	TCCR1A = 0;
    	TCCR1B = ( 1 << WGM12 ) | ( 1 << CS10 );
    	
    	// OutputCompare1A Register setzen
    	OCR1A = ( uint16_t ) ( ( uint32_t ) OCR_VAL );
    	
    	// OutputCompare1A Interrupt aktivieren
    	TIMSK |= ( 1 << OCIE1A );
    }
    
    // Die Interrupt Service Routine (ISR) wird INTERRUPTS_PER_SECOND mal 
    // pro Sekunde ausgeführt. irq_count zählt die Aufrufe und blinkt die LED
    // wenn 1 Sekunde vergangen ist.
    SIGNAL( SIG_OUTPUT_COMPARE1A )
    {
    	uint16_t count;
    
    	// irq_count um 1 erhöhen und
    	count = 1+irq_count;
    	
    	if( count >= laenge )
    	{
    		count = 0;
    		
    		PORTB ^= ( 1 << PB3 );
    		
    		sound_on = 0; //Sound soll durch die while-Schleife in sound(...) abgeschaltet werden
    	}
    	
    	irq_count = count;
    }
    
    void sound( int length )
    {
    	laenge = length;
    	sound_on = 1;
    	
    	while( sound_on != 0 ) //während sound_on = 1 ist, soll irgend eine Frequenz erzeugt werden. sobald sound_on = = ist, soll die Schleife unterbrochen werden.
    	{
    		PORTD ^= ( 1 << PD6 );
    		_delay_ms( 100 );
    		
    		sei();
    	}
    }
    
    // Das Hauptprogramm (Einsprungpunkt)
    int main( void )
    {
    	//Peripherie initialisieren
    	ioinit();
    	
    	PORTB |= ( 1 << PB3 );
    	
    	sound( 1000 );
    	
    	while(1);
    }
    An PortB.3 hängt eine LED, welche ganz brav wie im Tutorial im Abstand von einer Sekunde ein- und ausgeknipst wird. Das funktioniert auch ->Der Timer funktioniert an sich. Allerdings will ich die sound(...)-Funktion auch nur eine Sekunde ausführen. Das soll nach einer Sekunde durch das Unwahr-Machen der while-Schleife passieren. Warum funktioniert das nicht? Das Programm soll einfach nur einmal nach einer Sekunde die while-Schleife (und damit auch die sound-Funktion) beenden und die LED an PortB.3 weiterblinken lassen.

    Eigentlich müsste das doch von der Idee her funktionieren? Tut es aber nicht...

    u.A.w.g. , Manni

  10. #10
    Erfahrener Benutzer Robotik Einstein Avatar von SprinterSB
    Registriert seit
    09.06.2005
    Ort
    An der Saar
    Beiträge
    2.802
    Nö, so nicht sound_on ist einmal global, einmal lokal. Es bezieht sich also auf unterschiedliche Objekte...

    Das globale sound_on ist zudem volatile (flüchtig). Die Deklaration des lokalen sound_on fliegt in die Tonne!
    Disclaimer: none. Sue me.

Seite 1 von 2 12 LetzteLetzte

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •  

Solar Speicher und Akkus Tests