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
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.
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
|
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 ?
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 |
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?
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.
@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?
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
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.
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.
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.