PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Wieder Motorsteuerung...



Charly_cs
11.07.2008, 12:48
Hallo!

Dieses Thema wurde hier schon x-mal besprochen, nur habe ich noch keine Lösung für mein Problem gefunden. Vielleicht weiß ja jemand von euch Rat.
Am Ende soll das eine Motorregelung mit dem ATMega8 werden, doch zunächst möchte ich nur eine Steuerung aufbauen, bei der dem µC vom PC aus ein 10bit Pwmwert geschickt wird, er ihn einstellt und die Umdrehungen im 100ms Takt zählt. Aus einem alten Beitrag habe ich geschlossen, dass es möglich ist Timer, externe Interrupts und die PWM gleichzeitig zu verwenden.

Den Impulsgeber habe ich an den Int0 angeschlossen.
Zum Impulsgeber: 256 Impulse/Umdrehung und der Motor kann bis zu 12k U/min drehen.

Zum Programmablauf:
Im Hauptprogramm wird nur geschaut ob neue Zeichen am UART anliegen um diese dann auszuwerten. Den Timer0 verwende ich für den 100ms-Takt zum Auslesen des Zählers, der mit jedem Int0-Aufruf erhöht wird. Den Zählerstand schreibe ich in eine externe Variable, die bei Bedarf vom Hauptprogramm ausgelesen und an einen externen PC über UART geschickt wird.

Folgendes Problem besteht:
Der Controller startet sich besonders oft bei niedrigen Pwm-Werten neu, jedoch zeitlich nicht vorherzusagen. Wenn ich die Interrupts Timer0 und Int0 ausschalte, funktioniert die Pwmeinstellung wunderbar ohne das sich der µC aufhängt.
Woran kann das liegen? Habe ich bei den Interrupteinstellungen etwas falsch gemacht?

Hier ist mein Programm:

#define F_CPU 16000000
#define UART_BAUD_RATE 19200

#include <stdlib.h>
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "uart.h"
#include <string.h>

void Pwmstatus(char *);
unsigned long zaehler=0;
unsigned int timer=0;
unsigned long Ergebnis;

int main(void)
{

uart_init( UART_BAUD_SELECT(UART_BAUD_RATE,F_CPU) );
sei();

DDRB |= (1<<PB1) | (1<<PB2); // PWM für Enable1 und Enable2
DDRD |= (1<<PD6) | (1<<PD7); // Für Drehrichtung
PORTD |= (1<<PD6);
PORTD &= ~(1<<PD7);

//Pwm
TCCR1A = (1<<WGM10) | (1<<WGM11) | (1<<COM1A1);
TCCR1B = (1<<CS11) | (1<<CS10);
OCR1A = 0;

//Interrupt0
GIMSK |= (1<<INT0); //INT0 aktiviert
MCUCR |= (1<<ISC01) | (1<<ISC00); //Scharf gestellt auf steigende Flanke

//Timer0
TCCR0 |= (1<<CS01) | (1<<CS00); //Precsaler auf 64
TIMSK |= (1<<TOIE0); //Timer0 aktivieren
TCNT0 = 6; //Timerwert vorstellen auf 6 für 1ms

unsigned int c;
char Eingabe[10];
char Ausgabe[10] = "1001";
int i, asciiwert,Count;

for(;;)
{
c = uart_getc();
if ( c & UART_NO_DATA )
{
for(i=0;i<10;i++) //Dieser Delay von 100ms ist sehr wichtig!!
{
_delay_ms(10);
}

if(strlen(Eingabe)==5)
{
asciiwert = Eingabe[0];
switch (asciiwert)
{

case 'l':

PORTD |= (1<<PD6);
PORTD &= ~(1<<PD7);

Pwmstatus(Eingabe);
Eingabe[0] = '\0';
break;

case 'r':

PORTD |= (1<<PD7);
PORTD &= ~(1<<PD6);

Pwmstatus(Eingabe);
Eingabe[0] = '\0';
break;

case 'g':
itoa(Ergebnis,Ausgabe,10);
i = strlen(Ausgabe);
Ausgabe[i] = '!';
Ausgabe[i+1] = '\0';
uart_puts(Ausgabe);
Eingabe[0] = '\0';
Ausgabe[0] = '\0';
break;

}


}

}
else
{
Count = 0;
while (c != '!')
{
Eingabe[Count++] = c;
c = uart_getc();

}
Eingabe[Count] = '\0';

}

}

return 0;
}

void Pwmstatus(char *buffer)
{
char Buffer[5];
int i;
Buffer[0] = buffer[1];
Buffer[1] = buffer[2];
Buffer[2] = buffer[3];
Buffer[3] = buffer[4];
Buffer[4] = '\0';
i = atoi(Buffer);
OCR1A = i;

}

//Externe Interrupt0 Routine
ISR(INT0_vect)
{
zaehler++;
}
//Timer0 Interruptroutine
SIGNAL (SIG_OVERFLOW0)
{
timer++;
TCNT0=6; //Für 1ms
if(timer == 100)
{
GIMSK &= ~(1<<INT0);
Ergebnis = zaehler;
timer = 0;
zaehler = 0;
GIMSK |= (1<<INT0);
}

}




Vielen Dank!

Grüsse
Charly

sternst
12.07.2008, 02:40
Ich habe mir den Sourcecode mal angesehen:

1) Die Variablen zaehler, Ergebnis und timer:
Bei 256 Impulsen/U und max 12000 U/min hast du in 100 ms max 5120 Impulse. Dafür brauchst du keine Long-Variablen, das passt auch locker in Ints (also zaehler und Ergebnis in unsigned int ändern). timer zählt von 0 bis 100, da reicht ein unsigned char. Durch die unnötig großen Variablen werden die ISRs nur unnötig verlangsamt.

2) Die Variable Ergebnis im speziellen:
Diese Variable muss als volatile deklariert werden. Außerdem muss der Zugriff auf Ergebnis in main vor Interrupts geschützt werde. Damit die Interrupts nur so kurz wie möglich blockiert werden, empfehle ich die Verwendung einer zusätzlichen Variable, also z.B. so:

volatile unsigned int Ergebnis;
...
...
unsigned int tmp;
cli();
tmp = Ergebnis;
sei();
itoa(tmp,Ausgabe,10);

3) SIGNAL (SIG_OVERFLOW0):
Nimm die beiden GIMSK-Zeilen raus und mache daraus eine "normale" ISR. Wenn du erlaubst, dass dieser Interrupt durch den anderen unterbrochen werden kann, handelst du dir nur Scherereien ein, und es bringt dir hier keinen wirklichen Nutzen.

4)
Achte darauf, dass du die Optimierungen des Compilers aktiviert hast, schließlich ist das Timing ja nicht ganz unkritisch.

5)
Dieser Delay von 100ms ist sehr wichtig!!
Wenn im normalen Programmablauf Delays nötig sind, damit es funktioniert, ist das immer extrem verdächtig. Da liegt dann eigentlich immer etwas im Argen. Bei dir ist es folgender Code:

while (c != '!')
{
Eingabe[Count++] = c;
c = uart_getc();
}
Du versuchst hier alle Zeichen der Eingabe direkt hintereinander über uart_getc einzulesen. Das funktioniert aber nur, wenn auch schon alle Zeichen im AVR eingetrudelt sind. Wenn dieser Code mitten in der Übertragung ausgeführt wird, kracht es (und zwar richtig). Und trotz des Delays kann das jederzeit passieren. Das muss unbedingt geändert werden. Ich finde die Struktur der main überhaupt etwas verquer.

Morgen mache ich mal einen Alternativvorschlag für die main. Im Augenblick will ich eigentlich nur noch ins Bett. ;-)

Charly_cs
12.07.2008, 11:02
Hi!

wow. Vielen Dank für die Tips! Werde sie heute nachmittag umsetzen und gucken ob es besser läuft.

Gruß
Charly

Charly_cs
12.07.2008, 13:57
Hallo!

Hab das Resetproblem gelöst. Beim Durchmessen der Vs und GND Leitungen ist mir das starke Rauschen aufgefallen. Ein 100nF Kondensator am Mega8 angelötet und siehe da, ruhe ist eingekehrt... Er resettet nicht mehr und alles funzt wunderbar!\:D/

@sternst: Mich würde trotzdem interessieren wie man das Readout vom Uart besser hinbekommt. In BASCOM gabs ja extra ein Interrupt, der ausgeführt wurde, wenn ein Byte angekommen ist. Kann ich das in C auch irgendwie verwirklichen ohne auf die Uartbibliothek von Peter Fleury zu verzichten?

Danke!

Gruß
Charly

Ceos
12.07.2008, 16:13
in c gibts auch nen interrupt und die passende ISR dazu

ISR(byte_received_signal){
uart_getc();
}

die register hab ich jetzt nicht parat, aber wozu gibts das datenblatt ^^

sternst
12.07.2008, 16:25
Das funktioniert aber nicht zusammen mit dem Fleury-UART-Code, der benutzt nämlich selber schon diesen Interrupt.
Man kann halt nicht beides haben. Entweder man schreibt den UART-Code selber inklusive des Interrupt-Handlings, oder man muss eben das benutzen, was die Fleury-Bibliothek anbietet.

(Code für die main folgt noch)

sternst
12.07.2008, 19:05
Er resettet nicht mehr und alles funzt wunderbar!
Aber nicht immer. Der Code aus Punkt 5 führt unweigerlich zum Crash. Vielleicht erst nach Stunden oder Tagen (hängt auch davon ab, wie viel Kommandos du an den Controller sendest), aber er kommt garantiert.

Ok, hier also mein Vorschlag für main:

Ich habe deinen Code doch richtig dahingehend interpretiert, dass eine Eingabe immer aus einem Buchstaben, 4 Ziffern und einem Ausrufezeichen besteht?
Die Funktion Pwmstatus habe ich übrigens rausgeschmissen.


int main () {

// hier die ganzen Initialisierungen einfügen

unsigned int c;
char Eingabe[6];
char Ausgabe[10];
unsigned char count = 0;

for (;;) {

// auf das nächste Zeichen warten
do
c = uart_getc();
while (c & UART_NO_DATA);

if (c != '!') {
Eingabe[count] = c;
count++;
if (count > 5) //ups
count = 0;
continue; // weiter mit Warten auf das nächste Zeichen
}

// '!' wurde empfangen, Eingabe müsste also vollständig sein

Eingabe[count] = '\0';
count = 0;
if (strlen(Eingabe) != 5) //ups
continue;

switch (Eingabe[0]) {

case 'l':
PORTD |= (1<<PD6);
PORTD &= ~(1<<PD7);
OCR1A = atoi(Eingabe+1);
break;

case 'r':
PORTD &= ~(1<<PD6);
PORTD |= (1<<PD7);
OCR1A = atoi(Eingabe+1);
break;

case 'g':
cli();
c = Ergebnis; // c wird hier als Zwischenspeicher "missbraucht"
sei();
itoa(c,Ausgabe,10);
strcat(Ausgabe,"!");
uart_puts(Ausgabe);
break;
}

}
return 0;
}


Die mit "ups" markierten Stellen sind Vorsichtsmaßnahmen, damit der Controller auch dann nicht aus dem Tritt kommt, wenn über die serielle Schnittstelle mal Müll eintrifft.

Charly_cs
12.07.2008, 21:11
Abend!

Hab alles umgeschrieben und es funktioniert auch wunderbar! Hab mal die Probe aufs Example gemacht und den µC mit lauter gültigen Commandos bombardiert. Bei meinem UART-Code hats nicht lange gedauert bis nix mehr ging. :roll:
Deinen Code konnte nichts aus der Ruhe bringen.
Vielen Dank nochmal!

Hab jetzt noch eine allerletzte Frage. Zusätzlich kommt eine Regelung hinzu mit gegebener Abtastzeit von 100ms. Kann ich die Regelung in die Timer0 ISR einbauen oder ist davon abzuraten und sie lieber in der main unterbringen oder dafür den Timer2 verwenden?

Grüsse
Charly

sternst
12.07.2008, 22:44
Kann ich die Regelung in die Timer0 ISR einbauen
Davon ist abzuraten. Grundsätzlich gilt: ISRs immer so kurz wie möglich halten.

Mein Vorschlag:

Nimm eine zusätzliche globale Variable:
volatile unsigned char TimerFlag;

Füge in die ISR ein:
TimerFlag = 1;

Und modifiziere in main die Schleife, die auf das nächste Zeichen wartet:

do {
if (TimerFlag) {
TimerFlag = 0;
regelung();
}
c = uart_getc();
} while (c & UART_NO_DATA);

Es in die "warte auf das nächste Zeichen"-Schleife einzufügen ist allerdings ein wenig Quick&Dirty. Ich persönlich würde die Struktur von main wieder ein wenig ändern, aber dazu habe ich jetzt keine Lust. ;-)