Forum: Mikrocontroller und Digitale Elektronik Drehzahlregelung in c


von Dirk D. (sureai)


Angehängte Dateien:

Lesenswert?

Hallo!

Ich versuche momentan eine Drehzahlregelung zu implementieren und leider 
komme ich nicht zu zufriedenstellenden Ergebnissen.

Ich würde mich freuen, wenn ihr mir sagen könnt, ob dass was ich vorhabe 
mit meinem Hardware Setup nicht möglich ist und wenn ja, was ich 
upgraden sollte oder ob in meiner kurzen aber schlechten Software ein 
gravierender Fehler ist.

Mein System:
Eine rotierende Scheibe mit vier Magneten in gleichem Abstand und einem 
Hall-Sensor der viertel Umdrehungen erfasst. Einem Timer, der die Zeit 
zwischen zwei Abständen misst und einem 12V Gleichstrommotor, der die 
Scheibe antreibt.

Mein Problem:
Erst nach ca. 160 Viertelumdrehungen oder 40 Umdrehungen schafft es mein 
P-Regler zum Sollwert +-10%, das ist für meine Anforderung viel zu 
langsam und zu ungenau. Bisher habe ich nur einen P-Regler, weil alle 
anderen erst im Feintuning angegangen werden sollen.

Mein Code:
1
#include <stdint.h>
2
#include <avr/io.h>
3
#include "seriel.h"
4
#include <stdio.h>
5
#include <avr/interrupt.h>
6
static int uart_putchar(char c, FILE *stream);
7
static int uart_getchar(FILE *stream);
8
static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL,_FDEV_SETUP_WRITE);
9
static FILE mystdin = FDEV_SETUP_STREAM(NULL, uart_getchar,_FDEV_SETUP_READ);
10
11
//Interrupts:
12
volatile unsigned int counterUmdrehung = 0;
13
volatile unsigned int umdrehungProSekunde = 0;
14
volatile uint8_t flagUmdrehung = 0;
15
volatile uint8_t flagUmdrehung2 = 0;
16
//volatile unsigned int counterMillisekunden = 0;
17
18
ISR(INT0_vect){
19
  counterUmdrehung ++; //Eine Umdrehung wurde registriert
20
  flagUmdrehung2 = 1;
21
}
22
23
int ms2int(int regelgroesse){
24
  //Momentan nicht verwendet
25
  float m = -5.0495;
26
  float b = 324.53;
27
  float result = regelgroesse * m + b;// g(x) = m*x+b m=-2.164, b=414
28
  if (result > 255.0) {
29
    result = 255.0;
30
  }
31
  if (result < 0.0) {
32
    result = 0.0;
33
  }
34
  return (int)result;
35
}
36
37
38
volatile uint8_t flagUmdrehung3 = 0;
39
volatile unsigned int counterUmdrehung3 = 0;
40
ISR(INT1_vect){
41
  //Externer Interrupt 2
42
    counterUmdrehung3 ++; //Eine Umdrehung wurde registriert
43
    flagUmdrehung3 = 1;
44
}
45
46
int timerStarten(){
47
  // Timer0 konfigurieren
48
  TIMSK |= (1<<TOIE0);  // den Overflow Interrupt des Timers freigeben
49
  TCNT0 = 40;    //40 bei Prescaler = 8 und swTeiler = 8
50
  TCCR0 = (1<<CS01);// Vorteiler 8, jetzt zählt der Timer bereits
51
  return 0;  
52
}
53
54
//Überlauf Timer0 8-Bit
55
volatile unsigned int counterUeberlaeufe = 0;
56
volatile unsigned int millisekunden = 0;
57
volatile unsigned int sekunden = 0;
58
volatile unsigned int counterMillisekunden = 0;
59
60
ISR( TIMER0_OVF_vect )       // Overflow Interrupt Vector Timer0
61
{
62
  static uint8_t swTeiler = 0;
63
  
64
  swTeiler++;
65
  if( swTeiler == 8 ) { 
66
    swTeiler = 0;
67
    millisekunden++;
68
    counterMillisekunden++;
69
    if (millisekunden == 1000){
70
      counterUeberlaeufe++;
71
      millisekunden = 0;
72
      sekunden++;
73
    } 
74
  }
75
}
76
77
//Überlauf Timer2 8-Bit
78
volatile unsigned int counterUeberlaeufe2 = 0;
79
volatile unsigned int millisekunden2 = 0;
80
volatile unsigned int sekunden2 = 0;
81
volatile unsigned int counterMillisekunden2 = 0;
82
ISR( TIMER2_OVF_vect ) {    // Overflow Interrupt Vector Timer2
83
84
  static uint8_t swTeiler2 = 0;
85
  
86
  swTeiler2++;
87
  if( swTeiler2 == 8 ) {
88
    swTeiler2 = 0;
89
    millisekunden2++;
90
    counterMillisekunden2++;
91
    if (millisekunden2 == 1000){
92
      counterUeberlaeufe2++;
93
      millisekunden2 = 0;
94
      sekunden2++;
95
    }
96
  }
97
}
98
99
100
void ADC_init(void){
101
  ADCSRA |= (1<<ADEN);//ADC aktivieren
102
  ADCSRA |= (1<<ADPS2);//Teilungsfaktor auf 64 stellen
103
  ADCSRA |= (1<<ADPS1);
104
}
105
106
uint16_t ADC_read(uint8_t kanal){
107
  ADMUX = kanal;//Kanal des Multiplexers wählen
108
  ADCSRA |= (1<<ADSC);//Messung starten
109
  while(bit_is_set(ADCSRA,ADSC));
110
  return ADCW;
111
}
112
113
114
int main(void){
115
   
116
  da();
117
    int i ,j,a=0;
118
    int sollwert;
119
  int messsignal;
120
  int regelgroesse;
121
  int Te;
122
  int Kr;
123
  int Tn;
124
  int Tv;
125
  int error;
126
127
  USART_Init(16);
128
129
  stdout = &mystdout;
130
  stdin  = &mystdin;                     // Anschluss der low-level Routinen????
131
132
  printf("P-Regler gestartet");
133
  TCCR1B |= (1<<CS12);
134
  TCCR1A |= (1<<WGM10);
135
  TCCR1B |= (1<<WGM12);
136
  TCCR1A |= (1<<COM1A1) | (1<<COM1B1);
137
  OCR1A =  0;
138
  DDRD |= (1<<PD4) | (1<< PD5);
139
  #define MotorLinks OCR1A
140
  //MotorLinks = 255;
141
  counterMillisekunden = 0;
142
  counterUmdrehung = 0;
143
  int zeit = 0;
144
  DDRD &= ~(1<<2); //PD2 als Eingang
145
  PORTD|= (1<<2); //Pullup aktivieren
146
  GICR|=(1<<INT0); //INT0 (INTNULL) enable im General Interupt Control Register
147
  MCUCR|=(1<<ISC01)|(1<<ISC00); //Steigende Flanke löst aus
148
  sei();
149
  sollwert = 30;
150
  //sollwert = ms2int(15);
151
  //if (MotorLinks == 0 && sollwert > 0) {
152
  //  MotorLinks = ms2int(sollwert);
153
  //}
154
  MotorLinks = 255/5; //Motor benötigt Initialgeschwindigkeit um Istgeschwindigkeit messen zu können
155
  int ealt = 0;
156
  while(1){
157
    if (flagUmdrehung2 == 1) {
158
      flagUmdrehung2 = 0;
159
      TIMSK |= (1<<TOIE0);  
160
      TCNT0 = 40;    
161
      TCCR0 = (1<<CS01);
162
      messsignal =counterMillisekunden;
163
      printf("%d\n",counterMillisekunden);
164
      counterMillisekunden = 0;
165
      MotorLinks = MotorLinks + messsignal - sollwert;
166
    }
167
    
168
  }
169
}
170
171
    static int uart_putchar(char c, FILE *stream)
172
    {
173
174
      if (c == '\n')
175
        uart_putchar('\r', stream);
176
      loop_until_bit_is_set(UCSRA, UDRE);
177
      UDR = c;
178
      return 0;
179
    }
180
181
static int uart_getchar(FILE *stream)
182
183
{
184
   char c;
185
   loop_until_bit_is_set(UCSRA, RXC);
186
    c = UDR;
187
    uart_putchar(c, stdout);
188
   
189
 if (c == '\r')
190
    return '\n';
191
192
    return(c);
193
}

Meine Überlegungen:
-Eine schnelle Regelung sollte auch mit einem einfachem P-Regler 
erreicht werden können. Der P wert von 1 gibt auch das bis jetzt beste 
(Ergebnis siehe Bild). Eine Optimierung der Regelparameter könnte das 
Problem verbessern aber nicht drastisch.
-Vier Messpunkte pro Umdrehung sind nicht genau genug: wahrscheinlich 
sind mehr immer besser, allerdings gebe ich im Code eine 
Sollgeschwindigkeit vor und ändere den Sollwert (noch) nicht innerhalb 
kürzester Zeit, also sollten die vier Magneten erstmal reichen
-Der Motor verhält sich nicht linear zur Spannung. Bei niedrigen 
Spannungen von 0 bis ca. 1V reagiert der Motor so gut wie gar nicht mit 
einer Umdrehung. Ein besserer Motor oder die Zwischenschaltung eines 
Getriebes könnte das Problem verbessern.
-Der Code ist mies :D


Ich hoffe ihr könnt mir ein paar Tips geben, was ich verbessern kann. 
Wenn ich mein Ziel zufriedenstellend erreichen sollte würde ich auch 
etwas zurück geben und meine komplette Dokumentation als Anleitung 
wieder zur Verfügung stellen.

Gruß Sureai

von Kai S. (kai1986)


Lesenswert?

Hallo,

deine bleibende Regelabweichung kommt vom reinen P-Anteil, dieser kann 
die nämlich nicht kompensieren. Regelabweichung kompensieren benötigst 
du einen I-Anteil. Damit kommst du recht weit, wenn du dann noch 
schneller werden willst kannst du noch einen D-Anteil hinzunhemen.

Gruß Kai

von Dirk D. (sureai)


Lesenswert?

Hey,
danke für deine Antwort. Die bleibende Regelabweichung und ihr Ursache 
ist mir bewusst. Nur Braucht der Motor von Stand auf Maximale 
Geschwindigkeit eine Umdrehung. Deswegen verstehe ich nicht, wieso ich 
erst nach 40 Umdrehungen auf Sollwert +- Regelabweichung lande.
Ich wollte eine Einschwingzeit von ungefähr einer Umdrehung erreichen.
Aber dann probiere ich doch mal einen PID Regler um das zu 
beschleunigen.
Gruß Sureai

: Bearbeitet durch User
von lol (Gast)


Lesenswert?

Ich bin mir nicht sicher, ob man soo genaue Werte in C überhaupt 
auswerten kann. Wäre reiner Assembler da nicht geeigneter?

von Frank (Gast)


Lesenswert?

Mach mal mehr Magnete drauf.
Du musst definitiv die Auflösung deines Gebers erhöhen bzw die Totzeit 
verringern.
Dann bekommst du auch das Einschwingen schneller runter.
Bzw. Gerade bei langsamen Umdrehungen oder wenigen Strichen solltest du 
eher die Zeit zwischen zwei Flanken auswerten und nicht die Anzahl pro 
Zeit!

Ein Freilaufender Zähler. In den Interrupts der IOs speicherst Du die 
Timestamps ab und setzt ein flag.
Danach kannst du auch sauber die Zeitdifferenz berechnen!

von Dirk D. (sureai)


Lesenswert?

Hallo Frank,
ich bin mir gerade nicht ganz sicher, ob ich dich richtig verstehe. Ich 
werte doch die Zeit zwischen zwei Flanken aus, oder nicht? Wenn eine 
auslösende Flanke registriert wird, wird die if-Bedingung der main loop 
ausgelöst.
Verringert sich die Totzeit auch mit mehr Magneten? Ich dachte die um 
die Totzeit zu "regeln" verwendet man einen Smith-Prediktor. Ich dachte 
mein System hat eine relativ geringe Totzeit von einstelligen ms.
Ich werde die doppelte Anzahl an Magneten testen und überprüfen ob sich 
die Einschwingzeit reduziert.
Gruß Sureai

: Bearbeitet durch User
von Bastian W. (jackfrost)


Lesenswert?

Dirk D. schrieb:
> MotorLinks = MotorLinks + messsignal - sollwert;

Der Code oben ist kein P Regler. Du ziehst deinen Sollwert ( 30 von 
deinem Startwert 255/5) ab und addierst das Messsignal, wenn dies bei 
dem ersten Durchlauf noch 0 ist dann startet der Motor mit 20.

Ein P-Regler arbeite mit der Regelabweichung ( Soll - Ist ) und 
multipliziert dies mit dem Proportionalwert. Das Ergebnis ist dann der 
Stellgrad 0 - 100 % bzw. bei dir 0 - 255.



Dirk D. schrieb:
> y,
> danke für deine Antwort. Die bleibende Regelabweichung und ihr Ursache
> ist mir bewusst. Nur Braucht der Motor von Stand auf Maximale
> Geschwindigkeit eine Umdrehung. Deswegen verstehe ich nicht, wieso ich
> erst nach 40 Umdrehungen auf Sollwert +- Regelabweichung lande.
> Ich wollte eine Einschwingzeit von ungefähr einer Umdrehung erreichen.
> Aber dann probiere ich doch mal einen PID Regler um das zu
> beschleunigen.
> Gruß Sureai

Wenn du das brauchst , dann wird dir das kaum ein Regler schaffen. Du 
musst dann schon mit der Frequenz den Motor anfahren den er direkt 
braucht. Ein Regler schafft das nicht aus dem Stand ( 0 % ) das 
anzufahren.
Allein von der Massenträgkeit wird das schon schwer das der Motor das 
mit der ersten Umdrehung schaffen soll.

Der Nadelimpuls für einen D-Anteil wird nicht einfach. Versuch erstmal 
einen PI Regler. Die Dämpfung über den D-Anteil kannst du dann immer 
noch rein bauen das das System Schwinungsämer wird. Mach dir vorallem 
Gedanken um das Anti-Windup für den I-Anteil. So das dieser nicht weiter 
integriert
wenn der Istwert nicht steigt.

Für den I-Anteil brauchst du nen zweiten Timer. Einfach mal ins blaue 
geschossen:


t_abtast ist die Durchlaufzeit deines Timers nach der jedesmal die 
Stellgradänderung berechnet wird.

Ist den Istwert zu niedrig wird das Ergebnis addiert, wenn nicht dann 
abgezogen. Zum Stellgrad vom I-Anteil noch den P-Anteil addieren und die 
Summe gibt dann den Stellgrad für den Ausgang. Den Stellgrad für den 
I-Anteil bis zur Summe der beiden Anteile musst du getrennt vom P-Anteil 
führen. Du brauchst also zwei Variablen. Und im I-Zweig muss bereits das 
Anti-Windup drinnen sein ( Begrenzung auf 0 und 255 )

Gruss JackFrost

von Frank (Gast)


Lesenswert?

Dirk D. schrieb:
> ich bin mir gerade nicht ganz sicher, ob ich dich richtig verstehe. Ich
> werte doch die Zeit zwischen zwei Flanken aus, oder nicht?

Schon, aber warum machst Du diese eine (zeitkritische) Subtraktion nicht 
in der ISR? Du hast definitiv Fehler in der Zeitmessung sobald noch ein 
Interrupt dazwischen kommt.
Ich würde einfach einen Zähler frei laufen lassen. Relativ schnell. Im 
externen Interrupt machst Du dann nur noch sowas ind er Art:
1
ISR(INT0_vect)
2
{
3
 timeOld = timeNew; //Alten Wert sichern
4
 //Aktueller 32Bit Zeitstempel aus Overflow + aktuellem 16Bit Counter
5
 timeNew = (((uint32)OverflowCounterTimer <<16 )+ aktueller Timerwert);
6
 timeDiff = timeNew - timeOld;
7
}

Dirk D. schrieb:
> Verringert sich die Totzeit auch mit mehr Magneten?

Bei gleicher Drehzahl zumindest die Aktualisierungsrate Deiner 
Geschwindigkeit. Du kannst ja nur mit RPM/4 Geschwindigkeiten rechnen 
(es sei denn Du fängst noch mit Prädiktoren an).

Wenn Du 4 Mal mehr Magnete hast, bekommst Du 4 Mal so oft eine aktuelle 
Geschwindigkeit. Wenn Du darüber wieder einen Filter legst (z.B. 
gleitenden Mittelwert über 4 Werte) hast Du genauso oft wie vorher einen 
neuen Wert, aber mit deutlich weniger Drehzahlrauschen. Was Deiner 
Regelung mit Sicherheit wieder zugute kommt.

Grüße
Frank

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.