-
        

Seite 1 von 2 12 LetzteLetzte
Ergebnis 1 bis 10 von 13

Thema: probleme mit funktionsgenerator-programm

  1. #1
    Benutzer Stammmitglied
    Registriert seit
    28.12.2007
    Ort
    Wien
    Alter
    22
    Beiträge
    96

    probleme mit funktionsgenerator-programm

    Anzeige

    Hi!

    ich möchte mir einen Funktionsgenerator mit einem ATmega8 und einem R2R-Netzwerk bauen.

    Hier ist der Code, hoffentlich halbwegs verständlich kommentiert:

    Code:
    #include <avr\io.h>
    #include <avr\interrupt.h>
    #include <math.h>
    
    #define F_CPU 8000000
    
    #define round(x)    ((unsigned) ((x) + .5))
    
    
    uint16_t frequenz;						//frequenz in Hz
    uint8_t signalform;						//gewünschte signalform 0=sinus, 1=dreieck, 2=sägezahn, 3=rechteck
    
    uint16_t interruptspersecond;			//anzahl der interrupts pro sekunde
    uint8_t ausgang;						//Ausgangsport
    float periodendauer;					//in S
    uint16_t interruptsproperiode;			//anzahl der interrupts pro periode
    double spg=0;							//Berechneter Spannungswert des Ausganges
    uint16_t x;
    double x2;
    double x3;
    
    
    void init()
    {
    	TIMSK=(1<<TOV0);					//Timer Overflow Interrupt einschalten
    	TCNT0=0x00;							//Zähler-Startwert setzen
    	TCCR0=(1<<CS00);					//vorteiler: 1
    	sei();								//Interrupts einschalten
    }
    
    
    
    ISR(TIMER0_OVF_vect) 					//Interrupt-Routine
    {
    	interruptspersecond = F_CPU/256;
    	periodendauer=1/frequenz;
    	interruptsproperiode=periodendauer/interruptspersecond;
    	
    	if(x<=interruptsproperiode)
    	{
    		x++;							//x nimmt werte von 0-interruptsproperiode an
    	}
    	else
    	{
    		x=0;
    	}
    
    	x2=x/interruptsproperiode*2*M_PI;	//x2 nimmt werte von 0-2Pi an
    	x3=x/interruptsproperiode;			//x3 nimmt werte von 0-1 an
    
    	if(signalform==0)					//signalform=sinus	
    	{
    		spg=sin(x2);					//spannungsberechnung für sinus-signal mit sinus funktion
    	}
    
    	if(signalform==1)					//signalform=dreieck
    	{
    		if((0<=x3)&(x3<=0.25))
    		{
    			spg=x3*2+0.5;					//spannungsberechnung für dreieck-signal mit dreieck funktion
    		}
    		
    		if((0.25<x3) &(x3<0.75))
    		{
    			spg=1-(x3-0.25)*2;
    		}
    		
    		if((0.75<=x3)&(x3<=1))
    		{
    			spg=(x3-0.75)*2;
    		}
    	}
    
    	if(signalform==2)					//signalform=sägezahn
    	{
    		spg=x3;							//spannungsberechnung für sägezahn-signal mit sägezahn funktion
    	}
    
    	if(signalform==3)					//signalform=rechteck
    	{
    		if(x3<0.5)						//spannungsberechnung für rechteck-signal mit rechteck funktion
    		{
    			spg=0;
    		}
    		else
    		{
    			spg=1;
    		}
    		//verstellbarer dutycicle wäre praktisch
    	}
    
    	spg=spg*255;						//vorher spg: 0-1, nacher spg: 0-255
    	ausgang=round(spg);					//runden
    	PORTD=ausgang;						//gerundeten wert auf port D (r2r-netzwerk angeschlossen) legen
    }
    
    
    
    int main(void) 
    {
    
    	DDRD=0b11111111;
    
    	init();
    
    	while (1)
    	{
    		frequenz=1000;
    		//frequenz einstellen
    		signalform=0;
    		//signalform einstellen
    	}
    
    }
    Der Code ist noch nicht optimiert!

    Folgende Probleme habe ich jetzt:

    1. Am Port D kommt nix an. nur 0V, an allen 8 Pins.
    2.Die HEX-Datei ist 16kB groß, also eigentlich zu groß für den ATMega8. Wieso?
    3. Der Compiler erstellt auch noch eine .eep-Datei. Warum? Hängt das mit dem zu großen Programm zusammen?

    Ich hoffe Ihr könnt mir helfen.

    Mit freundlichen Grüßen

  2. #2
    Moderator Robotik Visionär Avatar von radbruch
    Registriert seit
    27.12.2006
    Ort
    Stuttgart
    Alter
    54
    Beiträge
    5.782
    Blog-Einträge
    8
    Hallo

    Nur keine Panik! In der Hex-Datei ist jedes Byte des Programms mit zwei Zeichen hexdezimal codiert, zusätzlich noch die Zieladressen und die Checksummen. Du kannst sie mal im Editor öffnen. Die ersten 8 Zeichen pro Zeile sind die Zieladresse im Speicher des Mega8, dann kommen 16 Bytes Daten und ein Byte Checksumme, jeweils als Hex-Wert.

    Wenn ich dein Progamm kompiliere, komme ich auf 2226 Bytes Programmlänge, die Hex-Datei wird mit 7kb angezeigt. Meine Optimierung ist auf "Size" eingestellt.

    Ich habe dein Programm nicht genauer untersucht, denn es scheint mir auf den ersten Blick etwas kompliziert. Möglicherweise bringt dies eine Funktion:

    volatile uint16_t frequenz; //frequenz in Hz
    volatile uint8_t signalform; //gewünschte signalform 0=sinus, 1=dreieck, 2=sägezahn, 3=rechteck

    volatile zwingt den Kompilier, für die Variable einen Speicherplatz anzulegen. Dadurch können sowohl das Hauptprogramm wie auch die ISR auf den Wert der Variable zugreifen. (Nicht wissenschaftlich und von mir nur nachgeplappert;) Mit dieser Änderung sieht dein Programm dann so aus:

    Code:
    #include <avr\io.h>
    #include <avr\interrupt.h>
    #include <math.h>
    
    #define F_CPU 8000000
    
    #define round(x)    ((unsigned) ((x) + .5))
    
    volatile uint16_t frequenz;		//frequenz in Hz
    volatile uint8_t signalform;		//gewünschte signalform 0=sinus, 1=dreieck, 2=sägezahn, 3=rechteck
    
    uint16_t interruptspersecond;		//anzahl der interrupts pro sekunde
    uint8_t ausgang;						//Ausgangsport
    float periodendauer;					//in S
    uint16_t interruptsproperiode;	//anzahl der interrupts pro periode
    double spg=0;							//Berechneter Spannungswert des Ausganges
    uint16_t x;
    double x2;
    double x3;
    
    void init(void)
    {
       TIMSK=(1<<TOV0);					//Timer Overflow Interrupt einschalten
       TCNT0=0x00;							//Zähler-Startwert setzen
       TCCR0=(1<<CS00);					//vorteiler: 1
       sei();								//Interrupts einschalten
    }
    
    ISR(TIMER0_OVF_vect)					//Interrupt-Routine
    {
       interruptspersecond = F_CPU/256;
       periodendauer=1/frequenz;
       interruptsproperiode=periodendauer/interruptspersecond;
    
       if(x<=interruptsproperiode)
       {
          x++;								//x nimmt werte von 0-interruptsproperiode an
       }
       else
       {
          x=0;
       }
    
       x2=x/interruptsproperiode*2*M_PI;	//x2 nimmt werte von 0-2Pi an
       x3=x/interruptsproperiode;				//x3 nimmt werte von 0-1 an
    
       if(signalform==0)					//signalform=sinus
       {
          spg=sin(x2);					//spannungsberechnung für sinus-signal mit sinus funktion
       }
    
       if(signalform==1)					//signalform=dreieck
       {
          if((0<=x3)&(x3<=0.25))
          {
             spg=x3*2+0.5;				//spannungsberechnung für dreieck-signal mit dreieck funktion
          }
    
          if((0.25<x3) &(x3<0.75))
          {
             spg=1-(x3-0.25)*2;
          }
    
          if((0.75<=x3)&(x3<=1))
          {
             spg=(x3-0.75)*2;
          }
       }
    
       if(signalform==2)					//signalform=sägezahn
       {
          spg=x3;							//spannungsberechnung für sägezahn-signal mit sägezahn funktion
       }
    
       if(signalform==3)					//signalform=rechteck
       {
          if(x3<0.5)						//spannungsberechnung für rechteck-signal mit rechteck funktion
          {
             spg=0;
          }
          else
          {
             spg=1;
          }
          //verstellbarer dutycycle wäre praktisch
       }
    
       spg=spg*255;						//vorher spg: 0-1, nacher spg: 0-255
       ausgang=round(spg);				//runden
       PORTD=ausgang;						//gerundeten wert auf port D (r2r-netzwerk angeschlossen) legen
    }
    
    int main(void)
    {
       DDRD=0b11111111;
    
       init();
    
       while (1)
       {
          frequenz=1000;
          //frequenz einstellen
          signalform=0;
          //signalform einstellen
       }
    
    }
    Allerdings, so aus dem Bauchgefühl heraus, könnte mindestens bei Kurvenform 1 ein Überlauf der ISR auftreten.

    Gruß

    mic

    Atmel’s products are not intended, authorized, or warranted for use
    as components in applications intended to support or sustain life!

  3. #3
    Erfahrener Benutzer Begeisterter Techniker
    Registriert seit
    19.05.2005
    Ort
    Berlin
    Beiträge
    316
    Ich hab mir deinen Code mal angeguckt.
    Ein paar Anmerkungen:
    1. Deine Variablenbezeichnungen sind nicht gerade leserfreundlich. Guck dir mal die "Ungarische Notation" an und/oder fange am besten jedes neue Wort in einer Variablen mit einem Großbuchstaben an. (interruptspersecond => interruptsPerSecond...)
    2. interruptspersecond ist eine Konstante, die du vom Präprozessor ausrechnen lassen kannst. Das muss nicht bei jedem Timerüberlauf geschehen.
    3. Die Signalformabfrage würde ich in ein "case" packen, das ist übersichtlicher.
    4. Es wäre besser, du verzichtest auf Kommazahlen und rechnest nur mit Unsigned Integers. Das spart viel Rechenzeit (vor allem in einer ISR wichtig!).
    Ich könnte mir vorstellen, dass der Compiler für die Sinusfunktion eine Lookup-Table anlegt (im EEPROM, deswegen wahrscheinlich die eep-Datei) und deshalb so viele Daten anfallen.
    Ich würde die Sinusfunktion erstmal rauslassen, gucken wie groß der Code ist und mir dann selbst eine Lookup-Table erstellen und diese im FLASH speichern. So kannst du die Mathe-Bibliothek ganz aussen vor lassen.

    Zu deinem Ausgabeproblem: Setze doch Testhalber einen Pin an einem anderen Port, wenn die ISR aufgerufen wird. So hast du schonmal Gewissheit, dass der entsprechende Code überhaupt ausgeführt wird.
    Stimmt sonst alles mit deiner Verdrahtung? Kann es sein, dass du einen Kurzschluss hast, und deswegen keine High-Pegel abzugreifen sind?

    Mfg Loki

    *edit* ups, radbruch war schneller */edit*

  4. #4
    Benutzer Stammmitglied
    Registriert seit
    28.12.2007
    Ort
    Wien
    Alter
    22
    Beiträge
    96
    Hi!

    Danke für die schnellen Antworten!

    Ich habe mal alle eure Tipps befolgt, bis auf das ohne Sinus, das mach ich morgen, und jetzt sieht der Code so aus:

    Code:
    #include <avr\io.h>
    #include <avr\interrupt.h>
    #include <math.h>
    
    #define F_CPU 8000000
    
    #define   interruptsProSekunde (F_CPU/256)
    #define   periodendauer (1/frequenz)
    #define   interruptsProPeriode (periodendauer/interruptsProSekunde)
    
    #define round(x)    ((unsigned) ((x) + .5))
    
    volatile uint16_t frequenz;      //frequenz in Hz
    volatile uint8_t signalform;      //gewünschte signalform 0=sinus, 1=dreieck, 2=sägezahn, 3=rechteck
    
    
    uint8_t ausgang;                  //Ausgangsport
    double spg=0;                     //Berechneter Spannungswert des Ausganges
    uint16_t x;
    double x2;
    double x3;
    
    
    void init(void)
    {
       TIMSK=(1<<TOV0);               //Timer Overflow Interrupt einschalten
       TCNT0=0x00;                     //Zähler-Startwert setzen
       TCCR0=(1<<CS00);               //vorteiler: 1
       sei();                        //Interrupts einschalten
    }
    
    
    ISR(TIMER0_OVF_vect)               //Interrupt-Routine
    {
       if(x<=interruptsProPeriode)
    	{
    		x++;                        //x nimmt werte von 0-interruptsproperiode an
    	}
       else
    	{
    		x=0;
    	}
    
    	x2=x/interruptsProPeriode*2*M_PI;   //x2 nimmt werte von 0-2Pi an
    	x3=x/interruptsProPeriode;            //x3 nimmt werte von 0-1 an
    
    	switch(signalform)
    	{
    	 	case 0:               			//signalform=sinus
        		spg=sin(x2);               		//spannungsberechnung für sinus-signal mit sinus funktion
       		break;
    
    		case 1:               			//signalform=dreieck
        		if((0<=x3)&(x3<=0.25))
          		{
          		   spg=x3*2+0.5;            //spannungsberechnung für dreieck-signal mit dreieck funktion
          		}
    
          		if((0.25<x3) &(x3<0.75))
          		{
          		   spg=1-(x3-0.25)*2;
          		}
    
          		if((0.75<=x3)&(x3<=1))
          		{
           			spg=(x3-0.75)*2;
          		}
       		break;
    
    	 	case 2:               			//signalform=sägezahn
        		spg=x3;                     //spannungsberechnung für sägezahn-signal mit sägezahn funktion
       		break;
    
    	 	case 3:               			//signalform=rechteck
        		if(x3<0.5)                  //spannungsberechnung für rechteck-signal mit rechteck funktion
          		{
             		spg=0;
          		}
          		else
          		{
             		spg=1;
          		}
          		//verstellbarer dutycycle wäre praktisch
       		break;
    	}
    
    
    
       spg=spg*255;                  //vorher spg: 0-1, nacher spg: 0-255
       ausgang=round(spg);            //runden
       PORTD=ausgang;                  //gerundeten wert auf port D (r2r-netzwerk angeschlossen) legen
    
    
       if(PORTB==0b00000001)
       {
    		PORTB=0b00000000;
       }
       else
       {
    		PORTB=0b00000001;
       }
    }
    
    
    int main(void)
    {
    	DDRD=0b11111111;
    	DDRB=0b11111111;
    	init();
    
       while (1)
       {
          frequenz=1000;
          //frequenz einstellen
          signalform=0;
          //signalform einstellen
       }
    
    }
    Die LED an PB0 Blinkt mit einer Periodendauer von 1,3ms, das entspricht 770Hz, also viel zu langsam. wahrscheinlich dauert die Interruptroutine zu lange. Was passiert eigentlich, wenn während der Ausführung der Interruptroutine ein Interrupt ausgelöst wird? Bricht sie dann ab und Beginnt von vorne, oder macht sie einfach weiter?

    EDIT: was ich noch vergessen habe: PD0 und PD1 sind jetzt dauerHigh, der restliche PortD ist Low

  5. #5
    Erfahrener Benutzer Begeisterter Techniker
    Registriert seit
    19.05.2005
    Ort
    Berlin
    Beiträge
    316
    Du rechnest immer noch mit double-Werten. Das ist sehr aufwendig und kostet viel Zeit. Versuch das Ganze mal "Controllerfreundlicher" zu implementieren. Nur mit Ganzzahlwerten.

    Zum debuggen würde ich mich auch erstmal einer einfacheren Signalform widmen.

    Womit misst du denn an den Ports? Multimeter oder Oszi? Logikanalysator?

  6. #6
    Benutzer Stammmitglied
    Registriert seit
    28.12.2007
    Ort
    Wien
    Alter
    22
    Beiträge
    96
    Ok, ich werde es heute mal mit Ganzzahlwerten machen, und die anderensignalform testen.

    Ich messe mitneinemn Oszi

    EDIT:

    Die anderen Signalformen gehen auch nicht.

    Ich hab mal die ganze Berechnung in die Main-Funktion rein getan, damit die Inerruptroutine schneller ist:

    Code:
    #include <avr\io.h>
    #include <avr\interrupt.h>
    #include <math.h>
    
    
    #define F_CPU 8000000
    
    #define   interruptsProSekunde (F_CPU/256)
    #define   periodendauer (1/frequenz)
    #define   interruptsProPeriode (periodendauer/interruptsProSekunde)
    
    #define round(x)    ((unsigned) ((x) + .5))
    
    
    
    
    //######################################################################################################
    //Variablendeklaration
    //######################################################################################################
    
    
    volatile uint16_t frequenz;      //frequenz in Hz
    volatile uint8_t signalform;      //gewünschte signalform 0=sinus, 1=dreieck, 2=sägezahn, 3=rechteck
    
    uint8_t ausgang;                  //Ausgangsport
    uint16_t x;
    
    
    
    //######################################################################################################
    //Timer-Initialisierung
    //######################################################################################################
    
    
    void init(void)
    {
    	TIMSK=(1<<TOV0);               //Timer Overflow Interrupt einschalten
    	TCNT0=0x00;                     //Zähler-Startwert setzen
    	TCCR0=(1<<CS00);               //vorteiler: 1
    	sei();                        //Interrupts einschalten
    }
    
    
    
    //######################################################################################################
    //Interrupt-Routine
    //######################################################################################################
    
    
    ISR(TIMER0_OVF_vect)               //Interrupt-Routine
    {
    	if(x<interruptsProPeriode)
       	{
          	x++;                        //x nimmt werte von 0-interruptsproperiode an
       	}
       	else							//Periode zu Ende
       	{
       	   	x=0;						//von vorne beginnen
       	}
    
    
       if(PORTB==0b00000001)			//zum testen
       {
          PORTB=0b00000000;
       }
       else
       {
          PORTB=0b00000001;
       }
    }
    
    
    
    //######################################################################################################
    //Main-Funktion
    //######################################################################################################
    
    
    int main(void)
    {
       DDRD=0b11111111;
       DDRB=0b11111111;
       init();
    
       while (1)
       {
          frequenz=1000;
          //frequenz einstellen
          signalform=3;
          //signalform einstellen
    
    
    		switch(signalform)
    		{
           		case 0:                        //signalform=sinus
          	  		ausgang=round((sin(x/interruptsProPeriode*2*M_PI))*255);             //spannungsberechnung für sinus-signal mit sinus funktion
            	break;
    
          		case 1:                        //signalform=dreieck
              		if((0<=(x/interruptsProPeriode))&((x/interruptsProPeriode)<=0.25))
                	{
                   		ausgang=round((x/interruptsProPeriode*2+0.5)*255);            //spannungsberechnung für dreieck-signal mit dreieck funktion
                	}
    
                	if((0.25<(x/interruptsProPeriode)) &((x/interruptsProPeriode)<0.75))
                	{
                   		ausgang=round((1-(x/interruptsProPeriode-0.25)*2)*255);
                	}
    
                	if((0.75<=(x/interruptsProPeriode))&((x/interruptsProPeriode)<=1))
                	{
                	    ausgang=round(((x/interruptsProPeriode-0.75)*2)*255);
               	 	}
            	break;
    
           		case 2:                        //signalform=sägezahn
              		ausgang=round(x/interruptsProPeriode*255);                     //spannungsberechnung für sägezahn-signal mit sägezahn funktion
            	break;
    
           		case 3:                        //signalform=rechteck
              		if((x/interruptsProPeriode)<0.5)                  //spannungsberechnung für rechteck-signal mit rechteck funktion
                	{
                   		ausgang=0;
                	}
                	else
                	{
                   		ausgang=255;
                	}
                	//verstellbarer dutycycle wäre praktisch
            	break;
       		}
    
    
       		PORTD=ausgang;                  //gerundeten wert auf port D (r2r-netzwerk angeschlossen) legen
       }
    
    }
    Und genügt es, wenn ich jetzt keine double sondern nur noch uint8_t und uint16_t habe, oder dürfen in den Zwischenschritten der Berechnungen auch nur Ganzzahlen raus kommen?

  7. #7
    Erfahrener Benutzer Robotik Visionär
    Registriert seit
    26.11.2005
    Ort
    bei Uelzen (Niedersachsen)
    Beiträge
    7.942
    Damit es schnell wird, sollte in den häufoger durchlaufennen Schleifen keine Berechnung mehr mit double sein. Für seltene Fälle wie die Anzeige der die Umrechnung einer Eingabe ist double noch OK, aber halt nicht für jede Signalperiode oder gar jeden Punkt der Kurve. Vor allem die Sinusfunktion muss man vermeiden. Ich hab jetzt keine genauen Werte, aber so mit rund 100 µs muss man schon für die Sinus Funktion rechnen. Die Werte der Sinusfunktion kann man z.B. in einer Tabelle ablegen.

    Ein Tip wäre es sich mal mit dem DDS- verfahren zu beschäftigen.

  8. #8
    Benutzer Stammmitglied
    Registriert seit
    28.12.2007
    Ort
    Wien
    Alter
    22
    Beiträge
    96
    Ok, ich habe mit mal das DDS-Verfahren angeschaut.

    Ich hoffe ich habe es Richtig verstanden:

    Bei jedem Takt (bei mir dann Interrupt) wird der nächste Wert aus der Lookup-Tabelle auf den Ausgang gelegt. Durch das Verstellen der Taktfrequenz (Interruptfrequenz) kann man die Frequenz des Signales ändern.

    Hoffentlich stimmt das so.

    Und wie kann man am besten eine Lookup-Tabelle speichern/abfragen und wo?
    Einfach als Array im Programm, oder im EEPROM?

    EDIT: Und wie genau sollte die Tabelle sein? 8Bit?

  9. #9
    Benutzer Stammmitglied
    Registriert seit
    28.12.2007
    Ort
    Wien
    Alter
    22
    Beiträge
    96
    So, mit 8-Bit-Sinustabelle und geändertem Programm haben alle Signalformen funktioniert \/

    jetzt versuche ich alles auf den timer2 im ctc Modus umzuschreiben, aber irgendwas stimmt nicht. Die Interrruptschleife wird nicht aufgerufen.

    Hier die Codeabschnitte:

    Timer-Init:

    Code:
    void init(void)	
    {
    	TIMSK=(1<<TOIE0);               //Timer Overflow Interrupt einschalten
    	TIMSK=(1<<OCIE1A);               //Compare Interrupt einschalten
    	TCCR1B=(1<<CS10);               //vorteiler: 1
    	TCCR1B=(1<<WGM12);               //CTC-Modus	
        TCNT1H=0;  						//  Timer Daten Register Timer1 High auf 0 Setzen
        TCNT1L=0;  						//  Timer Daten Register Timer1 Low auf 0 Setzen
    	sei();     	                   //Interrupts einschalten
    }
    Interruptroutine (normalerweist steht da was drin):

    Code:
    ISR(TIMER1_COMPA_vect)               //Interrupt-Routine
    {
    }
    Einstellen der Interruptfrequenz:

    Code:
    OCR1A=timerwert;
    der timerwert ist noch fix eingestellt auf 300, das entspricht ca. einem 100Hz-Signal

    ps: ich verwende einen atmega 8

  10. #10
    Erfahrener Benutzer Roboter Genie
    Registriert seit
    09.09.2006
    Alter
    28
    Beiträge
    841
    Blog-Einträge
    1
    bei dds änderst du nicht die taktfrequenz sondern die schrittweite deines zählers

Seite 1 von 2 12 LetzteLetzte

Berechtigungen

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