PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : probleme mit funktionsgenerator-programm



Franky55555
31.01.2011, 20:45
Hi!

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

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



#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

radbruch
31.01.2011, 21:39
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:


#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

lokirobotics
31.01.2011, 21:46
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*

Franky55555
31.01.2011, 22:20
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:



#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

lokirobotics
01.02.2011, 07:30
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?

Franky55555
01.02.2011, 10:51
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:



#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?

Besserwessi
01.02.2011, 16:39
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.

Franky55555
01.02.2011, 18:07
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?

Franky55555
02.02.2011, 14:03
So, mit 8-Bit-Sinustabelle und geändertem Programm haben alle Signalformen funktioniert \:D/

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:



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



ISR(TIMER1_COMPA_vect) //Interrupt-Routine
{
}


Einstellen der Interruptfrequenz:


OCR1A=timerwert;

der timerwert ist noch fix eingestellt auf 300, das entspricht ca. einem 100Hz-Signal

ps: ich verwende einen atmega 8

dremler
02.02.2011, 14:17
bei dds änderst du nicht die taktfrequenz sondern die schrittweite deines zählers

Franky55555
02.02.2011, 14:21
ok, stimt. is aber im prinzip egal oder (außer dass man dann bei echtem dds höhere frequenzen erreichen kann)?
hat ja mit dem timer0 schon super funktioniert, nur jetzt finde ich den fehler beim timer1 nicht.

Hubert.G
02.02.2011, 15:07
Probier das init mal so
void init(void)
{
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
}
Es ist außerdem nicht gut einen Interrupt einzuschalten und ihn dann nicht verwenden.
TIMSK=(1<<TOIE0); //Timer Overflow Interrupt einschalten
TIMSK=(1<<OCIE1A); //Compare Interrupt einschalten
Hier wird der Overflow Interrupt zwar ohnehin durch den Compare Interrupt gelöscht.
Ein Interrupt der nicht in eine Interrupt Routine führt, löst ein Reset aus.

Franky55555
02.02.2011, 15:22
Jetzt gehts, danke!