www.mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik 8051 sdcc & serielle Schnittstelle


Autor: Christoph Müller (chris2002)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi,
ich habe ein uC Programm für einen AT89C51AC3, das mir 3 Schrittmotoren 
ansteuert und regelt. Funktioniert auch einwandfrei. Dazu müssen aber 
erstmal die drei Sollwerte vom PC zum uC per serieller Schnittstelle 
gesendet werden.
Damit habe ich irgendwie beträchtliche Probleme:
Programmiert ist alles in c und mit sdcc kompiliert (Editor ist M-IDE).
Ich wäre Euch wirklich dankbar, falls jemand mal drüber sehen könnte, 
denn ich bin langsam am verzweifeln.
Chris
#include <stdio.h>
#include <8051.h>

char x;
char sollwerte[3];

void Timer_1_ISR(void) interrupt 1
{....}

void serial_IT(void) interrupt 4
{
  if(RI)
  {
     RI=0;
     sollwerte[x]=SBUF 
     x++;
     if(x==2) x=0;
  }
}
void main(void)
{  
  EA=1; //alle Interrupts freigeben

  //Timer0 initialisieren
  TMOD=0x01; //Timer0 im 16Bit-Betrieb (Modus1)  
  TL0=0x9B;
  TH0=0xFF; 
  TR0=1; //Timer0 einschalten
  ET0=1; //Timer0 Interrupt freigeben

  //serielle Schnittstelle
  SCON = 0x50;
  ES=1; //Uart Interrupt freigeben  

  //Timer2 initalisieren für serielle Schnittstelle
  T2CON = 0x30;
  RCAP2L = 0xd9;
  RCAP2H = 0xff; //12MHz Quarz, 9600Baud
  TL2 = 0xd9;
  TH2 = 0xff;
  TR2 = 1;
  C_T2 = 0;

  while(1);
}

Autor: Andreas Watterott (andreasw) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Was funktioniert denn nicht? Senden schon mal probiert?
Der Code oben macht ja erstmal nichts.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Deine Routine speichert immer 2 Byte in sollwerte[].

Aber woher soll der MC wissen, welches Byte was bedeutet?

Du brauchst ein Protokoll, damit sich der MC auf den PC synchronisieren 
kann.

Für ein Binärprotokoll brauchst Du ein Terminalprogramm, was Binärwerte 
senden kann.

Daher werden oftmals Textprotokolle verwendet.
Ein Kommando endet mit Zeilenende.
Zahlenwerte werden per sscanf oder atoi nach binär umgewandelt.


Peter

Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
x wird nicht initialisiert, damit überschreibst Du möglicherweise andere 
Variablen im interen RAM, also:
x = 0; // noch vor der Interruptfreigabe machen

EA = 1;
Würde ich erst am Ende machen, wenn alles korrekt initialisiert ist und 
nicht gleich als ersten Befehl!

Autor: Christoph Müller (chris2002)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi,
ich habe nun mal alles etwas umgeschrieben und muss das morgen nach der 
Vorlesung nochmal ausprobieren.
Das Problem mit dem oberen Code ist, dass er nicht in die ISR der 
seriellen Schnittstelle springt und das empfangene Zeichen in dem Puffer 
abspeichert.
Den Code im Timer2 sowie das "Protokoll" habe ich der Übersichthalber 
weggelassen.

Warum wird eigentlich durch Einschalten von Timer0 das TI-Flag gesetzt?
#include <stdio.h>
#include <8051.h>

char RS232Buffer[24];
int RS232Index=0;
int sollwerte[3]={0,0,0};

void main(void)
{  
  //Timer0 initialisieren
  TMOD=0x01; //Timer0 im 16Bit-Betrieb (Modus1)  
  TL0=0x9B;
  TH0=0xFF; //Startwert=65435

  //serielle Schnittstelle
  SCON = 0x50;

  //Timer1
  TMOD = (TMOD & 0x0F) | 0x20;  
  PCON= 0x00;
  TL1=0xFD;
  TH1=0xFD; // 256-12MHz/(384*9600Baud)
  REN = 1;
  PS = 1;
  TI=0;
  RI=0;
  
  ES=1; //RS232 Interrupt freigeben

  TF1=0; //Timer0 Flag löschen
  TR1=1; //Timer1 einschalten
  TR0=1; //Timer0 einschalten
  ET0=1; //Timer0 Interrupt freigeben

  EA=1; //alle Interrupts freigeben     
  
  while(1)
  {
    if(RS232Index==24) //24 Bytes empfangen (pro Motor 4 Bytes)
    {
      RS232Index=0;
      Schrittmotorenkarte1
      sollwerte[0]+=1000*RS232Buffer[0];
      sollwerte[0]+=100*RS232Buffer[1];
      sollwerte[0]+=10*RS232Buffer[2];
      ....
    }  
  }

void serial_IT(void) interrupt 4  using 2
{
  if(TI)
    TI=0;
  if(RI)
  {
    P3_7=1;
    RI=0;
    RS232Buffer[RS232Index]=ChartoInt(SBUF);
    RS232Index++;
  }
}

void Timer_0_ISR(void) interrupt 1 //1 ISR Adresse 0x000B
{
  
  ...//Hier drin werden die Schrittmotoren gesteuert
}


Autor: Christoph Müller (chris2002)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das ist noch der Code von Timer0, mit dem die Schrittmotoren angesteuert 
werden, falls jemand auch mal ähnliches vor hat. Jetzt hab ich euch nur 
einige Variablendef., eine Funktion ChartoInt und noch die Funktion 
vorenthalten, mit der "Antriebsdrehzahlneu[x]" berechnet wird.

Ein Win-Programm habe ich bereits: Es sendet an die zwei 
Schrittmotorenkarte ständig die Positionen für alle 6 Schrittmotoren als 
Zeichenkette z.B. "100010001000100010001000" (alle 6 Motoren auf Pos 
1000).
Ich glaube ich sollte noch ein Kommando für Anfang und Ende mitsenden.

void Timer_0_ISR(void) interrupt 1 //1 ISR Adresse 0x000B
{
  
  TF0=0;//Überlaufflag löschen
  
  TR0=0;
  TL0=0x9B;
  TH0=0xFF; 
  TR0=1;
  
  //alle Clock Ausgänge (wieder) 0
  P2_2=0;
  P2_5=0;
  P4_0=0;
  
  //ein Schritt weiter bewegen?
  for(x=0;x<=2;x++)
  {  
    if(Antriebsdrehzahlneu[x]>=0)  
      Antriebflanke[x] = Antriebflanke[x] - Antriebsdrehzahlneu[x];
    else
      Antriebflanke[x] = Antriebflanke[x] + Antriebsdrehzahlneu[x];
      
    if (Antriebflanke[x]<=0)
    {
      Antriebflanke[x]=10000;
      
      //Drehrichtung und Istwerte neuberechnen
      if(Antriebsdrehzahlneu[x]>=0)
      {
        switch(x)
        {
          case 0: P2_1=1; break;  //P2_1 Direction1
          case 1: P2_4=1; break;  //P2_4 Direction2
          case 2: P2_7=1; break;  //P2_7 Direction3
        }
      }
      else
      {
        switch(x)
        {
          case 0: P2_1=0; break;  //P2_1 Direction1
          case 1: P2_4=0; break;  //P2_4 Direction2
          case 2: P2_7=0; break;  //P2_7 Direction3
        }
      }  
      
      if(Antriebsdrehzahlneu[x]>=0) 
        if(istwerte[x]!=sollwerte[x]) istwerte[x]++;

      if(Antriebsdrehzahlneu[x]<0)
        if(istwerte[x]!=sollwerte[x]) istwerte[x]--;
      
      //Flanke erzeugen    
      switch(x)
      {
        case 0: if(istwerte[x]!=sollwerte[x]) P2_2=1; break;  
        case 1: if(istwerte[x]!=sollwerte[x]) P2_5=1; break;  
        case 2: if(istwerte[x]!=sollwerte[x]) P4_0=1; break;  
      }
    }
  }
}

Autor: Andreas Watterott (andreasw) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Vielleicht ist es auch ein Hardwareproblem: RX TX vertauscht?
In dem letzten Codestück muss RS232Index noch mit volatile deklariert 
werden.

Autor: R. W. (quakeman)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Matthias
Das mit dem Initialisieren stimmt zwar, aber deine Aussage stimmt nicht 
ganz. Wenn die Variable nicht initialisiert wird ist der Startwert zwar 
unbekannt, mehr aber auch nicht.
Alleine schon durch die Definition der Variablen reserviert der Compiler 
ihr einen festen Speicherplatz. Der Compiler kennt ja alle Variablen und 
deren Lebenszeiten, weshalb durch eine fehlende Initialisierung keine 
anderen Variablen überschrieben werden könnten.

Ciao,
     Rainer

Autor: Christoph Müller (chris2002)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
RX und TX sind nicht vertauscht, denn sonst könnte ich den MC nicht auf 
der Schrittmotorenkarte programmieren.

Wenn ich aus M-IDE mit JSIM simuliere bekomme ich bei einem Reloadwert 
von 65435 (100us Abtastrate) einen stackoverflow (0x02F6, 0x02EE, 
0x033D). Könnte das Grund für die Probleme sein?
Gibt es Methoden, die Funktionen von Timer0 leistungsmäßig zu 
verbessern?

Autor: Ralf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Poste doch bitte mal den gesamten Code bzw. noch besser, pack ihn in ein 
Zip-File, so dass wir uns das gesamte Projekt mal angucken können.

Z.B. die Aussage:

> Warum wird eigentlich durch Einschalten von Timer0 das TI-Flag gesetzt?

ist so nicht erklärbar (für mich), weil TI nix mit Timer 0 zu tun hat.

Ralf

Autor: rossi75 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Kann es vielleicht sein dass Du das TI-Flag zu früh zurücksetzt?

Dein Hinweis war der Reload auf knapp unter dem Überlauf. Wenn Du nun 
den Interrupt durch TI=0 direkt wieder freigibst, wird nach ~ 100 Zyklen 
der Interrupt wieder angesprochen. Wenn man grob 2-3 Zyklen pro 
Assembler-Befehl rechnet, komme ich auf etwa 33-50 Assembler Befehle für 
den ISR - mehr nicht. Wenn in dieser Zeit aber der Interrupt wieder 
auftritt (weil TI ja schon gecleart wurde), springt er wieder da rein. 
Da aber die Abarbeitung des ersten Einsprungs noch nicht fertig ist, 
muss der Stack hochgezählt werden.

Warum machst Du nicht einen Timer x (derer du ja drei hast) in dem Mode 
wo das eine Byte den Reload-wert angibt und das andere den aktuellen 
Timer (weiss grad den Mode nicht). In die neue Timer-ISR darfst Du aber 
nur ein paar kleine Befehle reinsetzen, sonst bekommst Du wieder dein 
Lastproblem.

cu,
olly...

Autor: rossi75 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
sorry, meinte ntürlich TF0 statt TI...

Autor: Joe (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
anbei mal ein kleiner Denkanstoß. Verwende mal ein Terminal Program wie 
z.B. TeraTerm. Das Zeichen, welches der MC empfängt, wird als "echo" 
zurück gesendet.

und laß mal das "using" weg.
void uart_isr (void) interrupt 4    {
  if (RI)    {             // Prüfe auf Receive Interrupt Flag
    RI = 0;                // Zeichen empfangen, lösche RI Flag
    SBUF = SBUF;           // local echo zum Terminal
  }
  while (!TI);             // Transmit ready/busy ?
  TI = 0;                  // clear Transmit Interrupt Flag
}

Autor: Christoph Müller (chris2002)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Hi,
vielen Dank für eure großartige Hilfe. Momentan läuft das Programm (es 
bekommt von der Steuersoftware per RS232 die Sollwerte permanent 
zugesendet und regelt Lage und Drehzahl der Motoren), auch wenn das 
Programm unter programmiertechnischen Aspekten wohl unausgereift ist :). 
Dafür bin ich auch eher der c# Programmierer, wenns denn sein muss.
Im Anhang findet ihr das c Programm für meine Schrittmotorenkarte. Zum 
Glück hatte ich wenigstens mit dem Platinenlayout wenig Ärger.
Des Weiteren habe ich ein Screenshot von meiner Steuersoftware gemacht. 
Es ist zum Steuern eines Hexapoden. Der ist zum Glück auch fertig 
(endlich Zeit für Klausuren :( ).

Einziges Problem ist noch, dass ich das Programm nach jedem reset über 
Flip neuladen muss, damit der Uart richtig empfängt ("BLJB" bei Flip ist 
nicht gesetzt)

Grüße, Chris

Autor: Christoph Müller (chris2002)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
#include <stdio.h>
//#include <math.h> 
#include <8051.h>

int RS232Buffer[25];
volatile int RS232Index=0;
int sollwerte[3]={0,0,0};
int istwerte[3]={0,0,0};
int streckediff[3]; //-32768..+32767 
int LimitSchritte=20; 
int Antriebsdrehzahlneu[3];
int Antriebflanke[3]={2000,2000,2000}; 
volatile unsigned char x=0;
unsigned int ergebnis = 0;

void Drehzahlberechnen(void)
{
  for(x=0;x<=2;x++)
  {  
    //einfache Berechnen ohne Rampe
    streckediff[x]=sollwerte[x]-istwerte[x]; 
      
    //Limitierung
    if(streckediff[x]>LimitSchritte) 
      streckediff[x]=LimitSchritte;
    
    if(streckediff[x]<-LimitSchritte) 
      streckediff[x]=-LimitSchritte;
    
    Antriebsdrehzahlneu[x]=10*streckediff[x]; 
  }  
}

int atoi(char s)
{
  switch(s)
  {
    case '0': ergebnis = 0; break;
    case '1': ergebnis = 1; break;
    case '2': ergebnis = 2; break;
    case '3': ergebnis = 3; break;
    case '4': ergebnis = 4; break;
    case '5': ergebnis = 5; break;
    case '6': ergebnis = 6; break;
    case '7': ergebnis = 7; break;
    case '8': ergebnis = 8; break;
    case '9': ergebnis = 9; break;
  }
  return (ergebnis);
}

void main(void)
{  
  //Timer0 initialisieren
  TMOD=0x01; //Timer0 im 16Bit-Betrieb (Modus1)  
  TL0=0x0B;//9B;
  TH0=0xFE;//FF; //Startwert=65435, d.h. der Timer braucht bis zum Überlauf 100 Zählschritte (100Mikrosekunden=0,1ms)

  //serielle Schnittstelle
  SCON = 0x50;

  //Timer1
  TMOD = (TMOD & 0x0F) | 0x20;  
  PCON= 0x00;
  TL1=0xFD;
  TH1=0xFD;
  REN = 1;
  PS = 1;
  TI=0;
  RI=0;
  
  ES=1; //RS232 Interrupt freigeben
  
  //alle Motoren einschalten
  P2_3=1; //P2_3 Enabled Motor1
  P2_6=1; //P2_6 Enabled Motor2
  P4_1=1; //P4_1 Enabled Motor3
    
  TF1=0; //Timer0 Flag löschen
  TR1=1; //Timer1 einschalten
  TR0=1; //Timer0 einschalten
  ET0=1; //Timer0 Interrupt freigeben
  
  EA=1; //alle Interrupts freigeben
  
  while(1);      
}
void serial_IT(void) interrupt 4  using 2
{
  if(TI)
    TI=0;
  if(RI)
  {
    RI=0;
    if(SBUF=='a')
    {
      RS232Buffer[0]=5;
      RS232Index=1;
    }
    if(SBUF!='a')
    {
      if(RS232Buffer[0]==5)
      {
        RS232Buffer[RS232Index]=atoi(SBUF);        
        RS232Index++;
      }
    }
    if(RS232Index==13) 
    {  
      int temp;
      
      temp=1000*RS232Buffer[1] + 100*RS232Buffer[2] + 10*RS232Buffer[3] + RS232Buffer[4];
      if(sollwerte[0]!=temp) sollwerte[0]=temp;
      temp=1000*RS232Buffer[5] + 100*RS232Buffer[6] + 10*RS232Buffer[7] + RS232Buffer[8];
      if(sollwerte[1]!=temp) sollwerte[1]=temp;
      temp=1000*RS232Buffer[9] + 100*RS232Buffer[10] + 10*RS232Buffer[11] + RS232Buffer[12];
      if(sollwerte[2]!=temp) sollwerte[2]=temp;
      
      Drehzahlberechnen();  
      
      RS232Buffer[0]=0;
      RS232Index=0;
    }
  }
}
void Timer_0_ISR(void) interrupt 1 //1 ISR Adresse 0x000B
{
  //alle Clock Ausgänge (wieder) 0
  P2_2=0;
  P2_5=0;
  P4_0=0;
  
  //ein Schritt weiter bewegen?
  for(x=0;x<=2;x++)
  {  
    if(Antriebsdrehzahlneu[x]>=0)  
      Antriebflanke[x] = Antriebflanke[x] - Antriebsdrehzahlneu[x];
    else
      Antriebflanke[x] = Antriebflanke[x] + Antriebsdrehzahlneu[x];
      
    if (Antriebflanke[x]<=0)
    {
      Antriebflanke[x]=2000;
      
      //Drehrichtung und Istwerte neuberechnen
      if(Antriebsdrehzahlneu[x]>=0)
      {
        switch(x)
        {
          case 0: P2_1=1; break;  //P2_1 Direction1
          case 1: P2_4=1; break;  //P2_4 Direction2
          case 2: P2_7=1; break;  //P2_7 Direction3
        }
      }
      else
      {
        switch(x)
        {
          case 0: P2_1=0; break;  //P2_1 Direction1
          case 1: P2_4=0; break;  //P2_4 Direction2
          case 2: P2_7=0; break;  //P2_7 Direction3
        }
      }  
      
      if(Antriebsdrehzahlneu[x]>=0) 
        if(istwerte[x]!=sollwerte[x]) istwerte[x]++;

      if(Antriebsdrehzahlneu[x]<0)
        if(istwerte[x]!=sollwerte[x]) istwerte[x]--;
      
      //Flanke erzeugen    
      switch(x)
      {
        case 0: if(istwerte[x]!=sollwerte[x]) P2_2=1; break;  
        case 1: if(istwerte[x]!=sollwerte[x]) P2_5=1; break;  
        case 2: if(istwerte[x]!=sollwerte[x]) P4_0=1; break;  
      }
    }
  }
  TF0=0;//Überlaufflag löschen
  
  TR0=0;
  TL0=0x0B;//9B;
  TH0=0xFE;//FF; 
  TR0=1;
}


Autor: Ralf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Gibt es Methoden, die Funktionen von Timer0 leistungsmäßig zu verbessern?
Natürlich gibts die :)

Das, was du im Timer-Interrupt machst, ist MURKS. Interrupts MÜSSEN so 
kurz wie möglich gehalten werden. Du berechnest aber im Timer-Interrupt 
munter, in welche Richtung usw. Das ist der falsche Ansatz!

Überleg dir mal, was der Timer-Interrupt machen soll... Richtig, er soll 
alle 100µs (das war doch die Zeit, oder?) die Motoren ansteuern - SONST 
NIX.
Das, was du da drin hast, mit den ganzen Berechnungen usw. gehört da 
m.E. nicht rein.

Mach folgendes:

Die Berechnung findet im Hauptprogramm statt. Jedesmal, wenn die 
Berechnung fertig ist, setzt du ein Flag. Der Timer-Interrupt prüft das 
Flag. Ist es nicht gesetzt, verlässt du den Interrupt wieder. Ist das 
Flag gesetzt, holst du die neuen Ausgabewerte in eine Variable des 
Interrupts, steuerst die Motoren an, und löschst das Flag. Das 
Hauptprogramm darf erst dann neue Werte schreiben/berechnen, wenn der 
Interrupt die aktuellen Werte ausgegeben hat, sonst überspringst du 
evtl. einen Datensatz.

Das Flag brauchst du, um erst dann neue Werte im Interrupt auszugeben, 
wenn die Berechnungen abgeschlossen sind, sonst würde der Interrupt, 
wenn er mitten in einer Berechnung Daten holt, eine Mischung aus alten 
und neuen Daten ausgeben.

Ralf

Autor: Ralf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Einziges Problem ist noch, dass ich das Programm nach jedem reset über
> Flip neuladen muss, damit der Uart richtig empfängt ("BLJB" bei Flip ist
> nicht gesetzt)
Hm, ich meine mal etwas gelesen zu haben, dass das BLJB bei Flip 
invertiert dargestellt wird, könnte es das sein?

Weitere Möglichkeiten sind:

- Ist der PSEN-Pin zufällig beim Resetten auf Low? Das startet den 
Bootloader.
- Wird das BLJB-Flag zufällig in der Software gesetzt (evtl. durch einen 
unerwünschten API-Aufruf des Bootloaders)?

Ralf

Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Fox Mulder

> char sollwerte[3];
> sollwerte[x]=SBUF
> x++;
> if(x==2) x=0;

Wenn x nicht (mit 0,1 oder 2) initialisiert wird, überschreibt ein 
größerer x Wert den 'sollwerte' Buffer. Dadurch werden ggf. andere 
Variablen, die in diesem Bereich liegen, überschrieben.

Autor: R. W. (quakeman)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Matthias
Okok, du hast ja Recht. Ich habe deine Aussage dann etwas falsch 
verstanden gehabt und nicht explizit auf diesen Code bezogen gesehen.
Ich dachte du meinst damit, daß ohne Initialisierung der Variablen noch 
kein fester Speicherplatz zugewiesen werden würde.
Also damit hätten wir das Missverständnis ja aus der Welt geräumt. In 
diesem speziellen Code Beispiel muß die Variable in der Tat 
initialisiert werden, damit versehentlich keine Speicherbereice darüber 
überschrieben werden. :)

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.