Forum: Mikrocontroller und Digitale Elektronik 8051 sdcc & serielle Schnittstelle


von Christoph M. (chris2002)


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
1
#include <stdio.h>
2
#include <8051.h>
3
4
char x;
5
char sollwerte[3];
6
7
void Timer_1_ISR(void) interrupt 1
8
{....}
9
10
void serial_IT(void) interrupt 4
11
{
12
  if(RI)
13
  {
14
     RI=0;
15
     sollwerte[x]=SBUF 
16
     x++;
17
     if(x==2) x=0;
18
  }
19
}
20
void main(void)
21
{  
22
  EA=1; //alle Interrupts freigeben
23
24
  //Timer0 initialisieren
25
  TMOD=0x01; //Timer0 im 16Bit-Betrieb (Modus1)  
26
  TL0=0x9B;
27
  TH0=0xFF; 
28
  TR0=1; //Timer0 einschalten
29
  ET0=1; //Timer0 Interrupt freigeben
30
31
  //serielle Schnittstelle
32
  SCON = 0x50;
33
  ES=1; //Uart Interrupt freigeben  
34
35
  //Timer2 initalisieren für serielle Schnittstelle
36
  T2CON = 0x30;
37
  RCAP2L = 0xd9;
38
  RCAP2H = 0xff; //12MHz Quarz, 9600Baud
39
  TL2 = 0xd9;
40
  TH2 = 0xff;
41
  TR2 = 1;
42
  C_T2 = 0;
43
44
  while(1);
45
}

von Andreas W. (andreasw) Benutzerseite


Lesenswert?

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

von Peter D. (peda)


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

von Matthias (Gast)


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!

von Christoph M. (chris2002)


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?
1
#include <stdio.h>
2
#include <8051.h>
3
4
char RS232Buffer[24];
5
int RS232Index=0;
6
int sollwerte[3]={0,0,0};
7
8
void main(void)
9
{  
10
  //Timer0 initialisieren
11
  TMOD=0x01; //Timer0 im 16Bit-Betrieb (Modus1)  
12
  TL0=0x9B;
13
  TH0=0xFF; //Startwert=65435
14
15
  //serielle Schnittstelle
16
  SCON = 0x50;
17
18
  //Timer1
19
  TMOD = (TMOD & 0x0F) | 0x20;  
20
  PCON= 0x00;
21
  TL1=0xFD;
22
  TH1=0xFD; // 256-12MHz/(384*9600Baud)
23
  REN = 1;
24
  PS = 1;
25
  TI=0;
26
  RI=0;
27
  
28
  ES=1; //RS232 Interrupt freigeben
29
30
  TF1=0; //Timer0 Flag löschen
31
  TR1=1; //Timer1 einschalten
32
  TR0=1; //Timer0 einschalten
33
  ET0=1; //Timer0 Interrupt freigeben
34
35
  EA=1; //alle Interrupts freigeben     
36
  
37
  while(1)
38
  {
39
    if(RS232Index==24) //24 Bytes empfangen (pro Motor 4 Bytes)
40
    {
41
      RS232Index=0;
42
      Schrittmotorenkarte1
43
      sollwerte[0]+=1000*RS232Buffer[0];
44
      sollwerte[0]+=100*RS232Buffer[1];
45
      sollwerte[0]+=10*RS232Buffer[2];
46
      ....
47
    }  
48
  }
49
50
void serial_IT(void) interrupt 4  using 2
51
{
52
  if(TI)
53
    TI=0;
54
  if(RI)
55
  {
56
    P3_7=1;
57
    RI=0;
58
    RS232Buffer[RS232Index]=ChartoInt(SBUF);
59
    RS232Index++;
60
  }
61
}
62
63
void Timer_0_ISR(void) interrupt 1 //1 ISR Adresse 0x000B
64
{
65
  
66
  ...//Hier drin werden die Schrittmotoren gesteuert
67
}

von Christoph M. (chris2002)


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.

1
void Timer_0_ISR(void) interrupt 1 //1 ISR Adresse 0x000B
2
{
3
  
4
  TF0=0;//Überlaufflag löschen
5
  
6
  TR0=0;
7
  TL0=0x9B;
8
  TH0=0xFF; 
9
  TR0=1;
10
  
11
  //alle Clock Ausgänge (wieder) 0
12
  P2_2=0;
13
  P2_5=0;
14
  P4_0=0;
15
  
16
  //ein Schritt weiter bewegen?
17
  for(x=0;x<=2;x++)
18
  {  
19
    if(Antriebsdrehzahlneu[x]>=0)  
20
      Antriebflanke[x] = Antriebflanke[x] - Antriebsdrehzahlneu[x];
21
    else
22
      Antriebflanke[x] = Antriebflanke[x] + Antriebsdrehzahlneu[x];
23
      
24
    if (Antriebflanke[x]<=0)
25
    {
26
      Antriebflanke[x]=10000;
27
      
28
      //Drehrichtung und Istwerte neuberechnen
29
      if(Antriebsdrehzahlneu[x]>=0)
30
      {
31
        switch(x)
32
        {
33
          case 0: P2_1=1; break;  //P2_1 Direction1
34
          case 1: P2_4=1; break;  //P2_4 Direction2
35
          case 2: P2_7=1; break;  //P2_7 Direction3
36
        }
37
      }
38
      else
39
      {
40
        switch(x)
41
        {
42
          case 0: P2_1=0; break;  //P2_1 Direction1
43
          case 1: P2_4=0; break;  //P2_4 Direction2
44
          case 2: P2_7=0; break;  //P2_7 Direction3
45
        }
46
      }  
47
      
48
      if(Antriebsdrehzahlneu[x]>=0) 
49
        if(istwerte[x]!=sollwerte[x]) istwerte[x]++;
50
51
      if(Antriebsdrehzahlneu[x]<0)
52
        if(istwerte[x]!=sollwerte[x]) istwerte[x]--;
53
      
54
      //Flanke erzeugen    
55
      switch(x)
56
      {
57
        case 0: if(istwerte[x]!=sollwerte[x]) P2_2=1; break;  
58
        case 1: if(istwerte[x]!=sollwerte[x]) P2_5=1; break;  
59
        case 2: if(istwerte[x]!=sollwerte[x]) P4_0=1; break;  
60
      }
61
    }
62
  }
63
}

von Andreas W. (andreasw) Benutzerseite


Lesenswert?

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

von R. W. (quakeman)


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

von Christoph M. (chris2002)


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?

von Ralf (Gast)


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

von rossi75 (Gast)


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...

von rossi75 (Gast)


Lesenswert?

sorry, meinte ntürlich TF0 statt TI...

von Joe (Gast)


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.
1
void uart_isr (void) interrupt 4    {
2
  if (RI)    {             // Prüfe auf Receive Interrupt Flag
3
    RI = 0;                // Zeichen empfangen, lösche RI Flag
4
    SBUF = SBUF;           // local echo zum Terminal
5
  }
6
  while (!TI);             // Transmit ready/busy ?
7
  TI = 0;                  // clear Transmit Interrupt Flag
8
}

von Christoph M. (chris2002)


Angehängte Dateien:

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

von Christoph M. (chris2002)


Lesenswert?

1
#include <stdio.h>
2
//#include <math.h> 
3
#include <8051.h>
4
5
int RS232Buffer[25];
6
volatile int RS232Index=0;
7
int sollwerte[3]={0,0,0};
8
int istwerte[3]={0,0,0};
9
int streckediff[3]; //-32768..+32767 
10
int LimitSchritte=20; 
11
int Antriebsdrehzahlneu[3];
12
int Antriebflanke[3]={2000,2000,2000}; 
13
volatile unsigned char x=0;
14
unsigned int ergebnis = 0;
15
16
void Drehzahlberechnen(void)
17
{
18
  for(x=0;x<=2;x++)
19
  {  
20
    //einfache Berechnen ohne Rampe
21
    streckediff[x]=sollwerte[x]-istwerte[x]; 
22
      
23
    //Limitierung
24
    if(streckediff[x]>LimitSchritte) 
25
      streckediff[x]=LimitSchritte;
26
    
27
    if(streckediff[x]<-LimitSchritte) 
28
      streckediff[x]=-LimitSchritte;
29
    
30
    Antriebsdrehzahlneu[x]=10*streckediff[x]; 
31
  }  
32
}
33
34
int atoi(char s)
35
{
36
  switch(s)
37
  {
38
    case '0': ergebnis = 0; break;
39
    case '1': ergebnis = 1; break;
40
    case '2': ergebnis = 2; break;
41
    case '3': ergebnis = 3; break;
42
    case '4': ergebnis = 4; break;
43
    case '5': ergebnis = 5; break;
44
    case '6': ergebnis = 6; break;
45
    case '7': ergebnis = 7; break;
46
    case '8': ergebnis = 8; break;
47
    case '9': ergebnis = 9; break;
48
  }
49
  return (ergebnis);
50
}
51
52
void main(void)
53
{  
54
  //Timer0 initialisieren
55
  TMOD=0x01; //Timer0 im 16Bit-Betrieb (Modus1)  
56
  TL0=0x0B;//9B;
57
  TH0=0xFE;//FF; //Startwert=65435, d.h. der Timer braucht bis zum Überlauf 100 Zählschritte (100Mikrosekunden=0,1ms)
58
59
  //serielle Schnittstelle
60
  SCON = 0x50;
61
62
  //Timer1
63
  TMOD = (TMOD & 0x0F) | 0x20;  
64
  PCON= 0x00;
65
  TL1=0xFD;
66
  TH1=0xFD;
67
  REN = 1;
68
  PS = 1;
69
  TI=0;
70
  RI=0;
71
  
72
  ES=1; //RS232 Interrupt freigeben
73
  
74
  //alle Motoren einschalten
75
  P2_3=1; //P2_3 Enabled Motor1
76
  P2_6=1; //P2_6 Enabled Motor2
77
  P4_1=1; //P4_1 Enabled Motor3
78
    
79
  TF1=0; //Timer0 Flag löschen
80
  TR1=1; //Timer1 einschalten
81
  TR0=1; //Timer0 einschalten
82
  ET0=1; //Timer0 Interrupt freigeben
83
  
84
  EA=1; //alle Interrupts freigeben
85
  
86
  while(1);      
87
}
88
void serial_IT(void) interrupt 4  using 2
89
{
90
  if(TI)
91
    TI=0;
92
  if(RI)
93
  {
94
    RI=0;
95
    if(SBUF=='a')
96
    {
97
      RS232Buffer[0]=5;
98
      RS232Index=1;
99
    }
100
    if(SBUF!='a')
101
    {
102
      if(RS232Buffer[0]==5)
103
      {
104
        RS232Buffer[RS232Index]=atoi(SBUF);        
105
        RS232Index++;
106
      }
107
    }
108
    if(RS232Index==13) 
109
    {  
110
      int temp;
111
      
112
      temp=1000*RS232Buffer[1] + 100*RS232Buffer[2] + 10*RS232Buffer[3] + RS232Buffer[4];
113
      if(sollwerte[0]!=temp) sollwerte[0]=temp;
114
      temp=1000*RS232Buffer[5] + 100*RS232Buffer[6] + 10*RS232Buffer[7] + RS232Buffer[8];
115
      if(sollwerte[1]!=temp) sollwerte[1]=temp;
116
      temp=1000*RS232Buffer[9] + 100*RS232Buffer[10] + 10*RS232Buffer[11] + RS232Buffer[12];
117
      if(sollwerte[2]!=temp) sollwerte[2]=temp;
118
      
119
      Drehzahlberechnen();  
120
      
121
      RS232Buffer[0]=0;
122
      RS232Index=0;
123
    }
124
  }
125
}
126
void Timer_0_ISR(void) interrupt 1 //1 ISR Adresse 0x000B
127
{
128
  //alle Clock Ausgänge (wieder) 0
129
  P2_2=0;
130
  P2_5=0;
131
  P4_0=0;
132
  
133
  //ein Schritt weiter bewegen?
134
  for(x=0;x<=2;x++)
135
  {  
136
    if(Antriebsdrehzahlneu[x]>=0)  
137
      Antriebflanke[x] = Antriebflanke[x] - Antriebsdrehzahlneu[x];
138
    else
139
      Antriebflanke[x] = Antriebflanke[x] + Antriebsdrehzahlneu[x];
140
      
141
    if (Antriebflanke[x]<=0)
142
    {
143
      Antriebflanke[x]=2000;
144
      
145
      //Drehrichtung und Istwerte neuberechnen
146
      if(Antriebsdrehzahlneu[x]>=0)
147
      {
148
        switch(x)
149
        {
150
          case 0: P2_1=1; break;  //P2_1 Direction1
151
          case 1: P2_4=1; break;  //P2_4 Direction2
152
          case 2: P2_7=1; break;  //P2_7 Direction3
153
        }
154
      }
155
      else
156
      {
157
        switch(x)
158
        {
159
          case 0: P2_1=0; break;  //P2_1 Direction1
160
          case 1: P2_4=0; break;  //P2_4 Direction2
161
          case 2: P2_7=0; break;  //P2_7 Direction3
162
        }
163
      }  
164
      
165
      if(Antriebsdrehzahlneu[x]>=0) 
166
        if(istwerte[x]!=sollwerte[x]) istwerte[x]++;
167
168
      if(Antriebsdrehzahlneu[x]<0)
169
        if(istwerte[x]!=sollwerte[x]) istwerte[x]--;
170
      
171
      //Flanke erzeugen    
172
      switch(x)
173
      {
174
        case 0: if(istwerte[x]!=sollwerte[x]) P2_2=1; break;  
175
        case 1: if(istwerte[x]!=sollwerte[x]) P2_5=1; break;  
176
        case 2: if(istwerte[x]!=sollwerte[x]) P4_0=1; break;  
177
      }
178
    }
179
  }
180
  TF0=0;//Überlaufflag löschen
181
  
182
  TR0=0;
183
  TL0=0x0B;//9B;
184
  TH0=0xFE;//FF; 
185
  TR0=1;
186
}

von Ralf (Gast)


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

von Ralf (Gast)


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

von Matthias (Gast)


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.

von R. W. (quakeman)


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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.