Forum: Mikrocontroller und Digitale Elektronik PID-Regler für Atmega8


von Bastian (Gast)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,
ich habe heute einen PID Regler in Assembler realisiert. Wenn ich die 
Funktion aber aufrufe läuft alles mögliche schief, was mit der Funktion 
nichts zu tun hat. Z.B. ist der UART dauerhaft am Senden. Die Funktion 
wird zwar bis zum Ende hin ausgeführt, dannach fängt sie aber sofort 
wieder von vorne an, anstatt im Hauptprogramm weiter zu machen.
Das ganze läuft auf einem Atmega8 auf dem ASURO.
Bevor ich jetzt einzelne Codeschnipsel poste, lade ich einfach die 
relevanten Dateien hoch.
Ich hab mit Assembler leider noch nicht so viel Erfahrung und finde den 
Fehler nicht.
Ist bestimmt was total triviales...
Kann bitte mal jemand drüber schauen? Ich komm einfach nicht drauf.

Vielen Dank im Vorraus

von Purzel H. (hacky)


Lesenswert?

Ohne den Code jetzt angeschaut zu haben... Eine Programm Entwicklung 
muss mit Fehlersuche als Konzept mit eingebaut begonnen werden. Ein 
Profi weiss schon welche Fehler zu erwarten sind und hat daher schon 
Testpunkte auf der Hardware eingebaut.

Ob man das nun hat oder auch nicht hat, muss man ein Mimimum zum Laufen 
kriegen. zB ein UART. Oder einen Pin. Ich verwende beides. Bei schnellen 
Systemen, wo das UART nicht mehr reicht, kann man auch auch einen DAC 
aufstecken und mit einem Scope den Ablauf der Werte anschauen.

Aeh. Ja. Viel Glueck. Ich mag grad nichts entzippen.

von Bastian (Gast)


Lesenswert?

Siebzehn und Fuenfzehn schrieb:
> Ob man das nun hat oder auch nicht hat, muss man ein Mimimum zum Laufen
> kriegen. zB ein UART. Oder einen Pin.

Das Minimum läuft alles. Es ist auch nicht so das ich Probleme habe den 
UART zu verwenden oder ähnliches. Auch habe ich nicht einfach die 
Fehlersuche aus der Hand gegeben, sondern selbst getestet, wie weit er 
im Quellcode kommt und ähnliches.

Zum Problem UART. Wenn ich den Funktionsaufruf auskommentiere läuft er 
einwandfrei. In dem Fall nämlich garnicht. Verwende ich die Funktion 
dreht er am Rad, OBWOHL!  er eigntlich auch hier nicht verwendet wird. 
Er bekommt von mir nie an irgendeiner Stelle gesagt, dass er senden 
soll.

Siebzehn und Fuenfzehn schrieb:
> Ich mag grad nichts entzippen.

Auch dafür gibt es eine Lösung ;) Sieht nach viel aus, ist aber mehr 
oder weniger genau das Selbe 4x.
1
calculate_PID_controller:
2
;yk=y1+y2+y3+y4
3
   push R16
4
   push R17
5
   push R18
6
   push R19
7
   push R20
8
   push R21
9
   push R22
10
   push YL
11
   push YH
12
13
14
 get_Y1:  ;=y(k-1)
15
   ldi YL, low(Yk_add)
16
   ldi YH, high(Yk_add)
17
   ld R16, Y+ ;this loads Y(k), which is now Y(k-1)
18
   ld R17, Y+ ;because the address for Y1 is direkt after Yk no new address needs to be loaded
19
   ld R18, Y+
20
   ;now the pointer is on Y1_add
21
   st Y+, R16 ;1byte is low
22
   st Y+, R17 ;2byte is 
23
   st Y, R18
24
   
25
 get_Y2: ;=Ymult1*e(k)
26
   ldi YL,low(Y_Mult1_add) ;get Y_mult1
27
   ldi YH,high(Y_Mult1_add)
28
   ld R16, Y+ ;now the pointer is on y_mult1_add ;r16= low(y_mul1)
29
   ld R17, Y+ ; ;r17= high(y_mul1)
30
   ; pointer is on ek_add; r18=e(k)
31
   ld R18, Y+ 
32
   andi MSREG, ~(1<<N_SIGN) ;clear the sign if the result is negative or positive N_SEG=1 ->negative
33
   TST R18
34
   BRMI ek_neg ;if N is set e is negativ so the whole high byte needs to be ones
35
   rjmp get_Y2_continue1;else e is positive and the high byte needs to be zeros
36
  ek_neg:
37
   neg R18 ;2compl e(k)
38
   ldi R22,(1<<N_SIGN);remember you negated n18!
39
   EOR MSREG, R22 ;negate the N_SIGN bit in mystatusregister
40
  get_Y2_continue1:
41
   NEG_16bit R17, R16 ;negate Y_Mult1
42
   ldi R22,(1<<N_SIGN)
43
   EOR MSREG, R22 ;Y_Mult1 is negative
44
   rcall mul_16bitx8bit ;multiply to get Y2
45
   SBRC MSREG, N_SIGN ;skip if the result should be positive
46
   NEG_24bit R21,R20,R19 ;switch the sign of Y2 to negative
47
   ;pointer is now on Y2_add
48
   st Y+,R19 ;the pointer is on Y2_add so it is on the low address
49
   st Y+,R20 ;store middle
50
   st Y,R21 ;store high
51
52
 get_Y3: ;=ymul2*e(k-1)
53
   ldi YL,low(Y_Mult2_add) ;get Y_mult2
54
   ldi YH,high(Y_Mult2_add)
55
   ld R16, Y+ ;now the pointer is on y_mult2_add ;r16= low(y_mul2)
56
   ld R17, Y+ ; ;r17= high(y_mul2)
57
   ;pointer is now on e(k-1)
58
   ld R18, Y+ ; pointer is on ek_1_add; r18=e(k-1)
59
   andi MSREG, ~(1<<N_SIGN) ;clear the sign if the result is negative or positive N_SEG=1 ->negative
60
   TST R18
61
   BRMI ek_1_neg ;if N is set e is negativ so the whole high byte needs to be ones
62
   rjmp get_Y3_continue1;else e is positive and the high byte needs to be zeros
63
  ek_1_neg:
64
   neg R18
65
   ldi R22,(1<<N_SIGN)
66
   EOR MSREG, R22 ;negate the N_SIGN bit in mystatusregister
67
  get_Y3_continue1:
68
   ;mult2 is positive
69
   rcall mul_16bitx8bit ;multiply to get Y2
70
   SBRC MSREG, N_SIGN ;skip if the result should be positive
71
   NEG_24bit R21,R20,R19 ;switch the sign of Y2 to negative
72
   ;pointer is now on Y3_add
73
   st Y+,R19 ;the pointer is on Y2_add so it is on the low address
74
   st Y+,R20 ;store middle
75
   st Y,R21 ;store high
76
77
78
 get_Y4: ;=Kd*e(k-2)
79
   ldi YL,low(Kd_add) ;get Kd
80
   ldi YH,high(Kd_add)
81
   ld R16, Y+ ;now the pointer is on Kd_add ;r16= low(y_mul1)
82
   ld R17, Y+ ; ;r17= high(y_mul1)
83
   ;pointer is now on ek_2_add
84
   ld R18, Y+ ; pointer is on ek_2_add; r18=e(k-2)
85
   andi MSREG, ~(1<<N_SIGN) ;clear the sign if the result is negative or positive N_SEG=1 ->negative
86
   TST R18
87
   BRMI ek_2_neg ;if N is set e is negativ so the whole high byte needs to be ones
88
   rjmp get_Y4_continue1;else e is positive and the high byte needs to be zeros
89
  ek_2_neg:
90
   neg R18
91
   ldi R22,(1<<N_SIGN)
92
   EOR MSREG, R22 ;negate the N_SIGN bit in mystatusregister
93
  get_Y4_continue1:
94
   NEG_16bit R17, R16 ;negate Kd
95
   ldi R22,(1<<N_SIGN)
96
   EOR MSREG, R22 ;Kd is negative so neg statusbit
97
   rcall mul_16bitx8bit ;multiply to get Y2
98
   SBRC MSREG, N_SIGN ;skip if the result should be positive
99
   NEG_24bit R21,R20,R19 ;switch the sign of Y2 to negative
100
   st Y+,R19 ;the pointer is on Y2_add so it is on the low address
101
   st Y+,R20 ;store middle
102
   st Y+,R21 ;store high
103
104
 get_Yk: ;Y1+Y2+Y3+Y4
105
   ldi YH, high(Y1_add)
106
   ldi YL, low(Y1_add)
107
   ld R16, Y+
108
   ld R17, Y+
109
   ld R18, Y+ ;load Y1
110
   mov R19,R16
111
   mov R20, R17
112
   mov R21, R18 ;store it
113
114
   ;add Y2
115
   ldi YH, high(Y2_add)
116
   ldi YL, low(Y2_add)
117
   ld R16, Y+
118
   ld R17, Y+
119
   ld R18, Y+ ;load y2
120
   add R19, R16
121
   adc R20, R17
122
   adc R21, R18 ;add it
123
   
124
   ;add Y3
125
   ldi YH, high(Y3_add)
126
   ldi YL, low(Y3_add)
127
   ld R16, Y+
128
   ld R17, Y+
129
   ld R18, Y+ ;load y3
130
   add R19, R16
131
   adc R20, R17
132
   adc R21, R18 ;add it
133
134
   ;add Y4
135
   ldi YH, high(Y4_add)
136
   ldi YL, low(Y4_add)
137
   ld R16, Y+
138
   ld R17, Y+
139
   ld R18, Y+ ;load y4
140
   add R19, R16
141
   adc R20, R17
142
   adc R21, R18 ;add it
143
144
   ;store Yk
145
   ldi YH, high(Yk_add)
146
   ldi YL, low(Yk_add)
147
   st Y+, R19
148
   st Y+, R20
149
   st Y+, R21;store Yk
150
151
   pop YH
152
   pop YL
153
   pop R22
154
   pop R21
155
   pop R20
156
   pop R19
157
   pop R18
158
   pop R17
159
   pop R16
160
RET

von Purzel H. (hacky)


Lesenswert?

Der Vorteil des bestehenden Codes ist dass er zeitlos und fast 
unabhangeig ist. Und man so jeden Summand einzeln bearbeiten kann. Also 
einen Wert (Byte, Word, Integer) ueber's UART reinschicken, 
durchrechnen, und ueber's UART zurueck.

Eine Idee. Es laeuft einiges ueber den Stack. Wie gros ist der Stack ?

von Bastian (Gast)


Lesenswert?

Der Stack ist ziemlich leer. Der aufruf der Funktion ist aus dem 
Hauptprogramm und an der stelle ist der Stack schön wieder abgebaut. 
Überschneidet sich irgendewas mit den von mir gewählten Adressen für 
meine Variablen?
1
;############## D E F I N I T I O N S ###########################
2
.EQU Kd = -1500
3
.EQU Kp = 40
4
.EQU Ki = 1
5
.EQU Y_Mult1 = -1460 ;=Kp+Kd
6
.EQU Y_Mult2 = 2961 ;ki-kp-2kd
7
.EQU W = -2 ;reference value = Führungsgröße für e=W-X
8
9
.DEF MSREG = R25 ;my status REG
10
.EQU Bumper = 0
11
.EQU CP_CARRY = 1
12
.EQU CP_EQU = 2
13
.EQU N_SIGN = 3
14
15
.DEF r_motorspeed_left = R2
16
.DEF r_motorspeed_right = R3
17
.EQU motorspeed = 100
18
.EQU motorspeed_change = 15;2way controller
19
20
.EQU LEFT_ON = 0x0120 ;0x0130=high, 0x0131=low
21
.EQU LEFT_OFF = 0x0121
22
.EQU LEFT = 0x0122
23
.EQU RIGHT_ON = 0x0123
24
.EQU RIGHT_OFF = 0x0124
25
.EQU RIGHT = 0x0125
26
27
.EQU Yk_add = 0x0126 ;the value the motorsspeed have to be changed ;24bit value
28
.EQU Y1_add = 0x0129 ; = Y(k-1) ;24bit value
29
30
.EQU Y_Mult1_add = 0x012C ; 16bit value
31
.EQU ek_add = 0x012E ; =e(k)=w-x 8bit
32
.EQU Y2_add = 0x012F ;=Y_mult1*e(k) 24bit value
33
34
.EQU Y_Mult2_add = 0x0132 ; 16bit value
35
.EQU ek_1_add = 0x0134 ;=e(k-1) 8bit
36
.EQU Y3_add = 0x0135 ;=Y_Mult2*e(k-1) ;24bit value
37
38
.EQU Kd_add = 0x0138 ;16bit value
39
.EQU ek_2_add = 0x013A ;=e(k-2) 8bit
40
.EQU Y4_add = 0x013B ;=Kd*e(k-2) ;24bit value

von Linüx (Gast)


Lesenswert?

Wie wäre es wenn du systematisch ans Debuggen rangehst anstatt uns 
wahllos potentielle Fehlerstellen anzubieten die wir sowieso nicht 
verstehen weil wir deinen Code nicht kennen?

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Um Himmels willen - wenn du da jemals durchblickst, gratuliere ich dir 
herzlich. Wir haben aber keine Ahnung, was du eigentlich erreichen 
willst, und welches die Stell- und Eingangsgrössen sind.

Ist Assembler hier unbedingt nötig? Es gibt so gut funktionierende und 
schnelle PID Regler für die AVRs, das ich vermutlich nicht auf die Idee 
kommen würde, so etwas in ASM zu realisieren. Die Wartbarkeit ist 
gegenüber dem Geschwindigkeitsvorteil doch meistens wichtiger.

von Thomas G. (tomatos666)


Lesenswert?

@Bastian
Ich weiß ja nicht mit welchem Programm du Schreibst.
Bei mir AVR 4.19 musste ich die .include in einem anderem Ort 
Positionieren damit mir AVR überhaupt ohne Fehler das Programm 
Simulieren lies.
Beim PID Abschnitt läuft der Stack über und das Programm läuft im 
Anschluss Amok.
Schau dir noch mal den Abschnitt PID an und ich glaube bin aber nicht 
sicher es liegt an den ST LD Befehlen.
Am Anfang Sicherst du die Register welche aber im Ablauf vom PID Teils 
Überschrieben werden.

@Matthias Sch.
Bin im Moment auch auf der Suche nach einem PID vorzugsweise in 
Assembler.
Kannst du mir aber einen Übersichtlichen und leicht Verständlichen in C 
Empfehlen?

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Thomas Gruber schrieb:
> Kannst du mir aber einen Übersichtlichen und leicht Verständlichen in C
> Empfehlen?

AVR221 beschreibt einen universellen PID Regler in C. Angewendet wird er 
z.B.auch in AVR447 (BLDC Steuerung mit Sinusmodulation).
PDF:
http://www.atmel.com/Images/doc2558.pdf
Source:
http://www.atmel.com/Images/AVR221.zip

von Purzel H. (hacky)


Lesenswert?

Man kann schon mit ASM arbeiten. Muss dann aber die Funktionen 
entkoppeln. Dann kann man die jeweilige Funktionalitaet mit dem 
Simulator testen. Wobei die Interrupt proceduren in System getestet 
werden muessen - einmal.

Dann muss man auch der Uebersichtlichkeit wegen mit einer 
Zustandsmaschine arbeiten. Dh der gesammte Code muss nicht blockierend 
geschrieben werden. Gewartet wird genau an einer Stelle im Main(). Dort 
kann man dann falls spaeter erwuenscht auch einen Sleep einwerfen.

von AVR-GCC (Gast)


Lesenswert?

Man darf das in jeder beliebigen Sprache machen, aber man sollte sein 
Werkzeug auch danach auswählen, daß man es im Griff hat. Kompliziertes 
Werkzeug nur wenn es einen Mehrwert bringt. Und sei's nur der, daß es in 
C schon verfügbar ist.

von Thomas G. (tomatos666)


Lesenswert?

Danke für den Link.
Leider hab ich um erlich zu sein mit C überhaupt keine Erfahrung.
Das restliche Programm ist in Assembler geschrieben.
Man kann ja ein Programm mit beide Sprachen schreiben nicht?
Ich verwende AVR 4.19.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Thomas Gruber schrieb:
> Leider hab ich um erlich zu sein mit C überhaupt keine Erfahrung.
> Man kann ja ein Programm mit beide Sprachen schreiben nicht?

Man kann in C Programmstückchen in Assembler einbinden. Umgekehrt ist 
das evtl. auch möglich, habe ich aber noch nie gemacht, weil ich es 
nicht brauche - denn nur ganz zeitkritische Sachen oder Mini-Programme 
mache ich heute noch in Assembler. Mixen ist für dich vermutlich zu 
kompliziert, weil du dann erstmal C pauken müsstest und dann noch die 
Feinheiten des Einbindens.
AVR-gcc produziert so schnellen Code, das ich Assembler nicht vermisse 
und die Wartbarkeit ist einfach viel besser.
Geh doch mal AVR221 Stück für Stück durch und portiere die Sachen, die 
du kapiert hast in Assembler. Du wirst ein wenig 16bit Arithmetik 
brauchen und eine universelle (und wasserdichte) 16bit 
Multiplikationsroutine.
Wenn du das modular anlegst mit definierten Übergabeparametern, hast du 
was für deine Bibliothek.

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.