PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : C Bitte um Hilfe bei C-Code aus unbekannter Quelle



oberallgeier
17.12.2021, 10:52
Hallo Programmierkundige - in C.

Vor Jahren war ich über eine Waitroutine gestolpert die ich nicht selten in meinen Quellen benutze besondes dann, wenn Warten angesagt ist ohne dass Timer, insbes. Interrupts erwünscht/zugelassen sind. Das übliche "delay" aus den Codesammlungen hatte ich eigentlich nie benutzt, weil ich der Hoffnung war, irgendwann in die Geheimnisse der erwähnten Routine einzdringen. Is nicht! Leider. Statt C klingt die mir immer noch wie Kantonesisch, Mandarin, Spanisch oder so - mit zusätzlichen Defekten in der Tastatur. Daher meine Bitte mir zum Verständnis des Ablaufes zu helfen.
Bei der Anwendung ist mir klar, dass die Routine ein Behelf ist - interuptgetriebene Timer wären da meist sinnvoller, die verwende ich auch. Nichts desto trotz würde ich gerne die Geheimnisse dieses Codes verstehen ;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void wms ( uint16_t ms ) // 128 kHz-wms; Pausenwert ist nur experimentell !
// M E I S T keine Millisekunde ! ! !
// - - - - - - - - - - - - - - - -
{ // Weiter Messwerte in L3_tmr10.c und früher (siehe dort)
for(; ms>0; ms--)
{ //
uint16_t __c = 26; // Ergibt am 14Dez21,17h34 1,001 sec bei wms=1000
// => bei wms ( 100) ist der (periphere) Einfluss
// des Programmablaufs deutlich messbar !!
__asm__ volatile ( //
"1: sbiw %0,1" "\n\t" // schwankt >> mit Temperatur ! ! !
"brne 1b"
: "=w" (__c) : "0" (__c)
);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Meine Hoffung auf ein besseres Verständnis meinerseits durch Auflösen der Klammern erfüllten sich nie - das wird dadurch eher noch kryptischer.

// - - - - - - - - - - - - - - - -
{ //
for(; ms>0; ms--)
{ //
uint16_t __c = 26; // Dieser Wert dient zur Anpassung an ControllerClock etc.
__asm__ volatile ( "1: sbiw %0,1" "\n\t" "brne 1b" : "=w" (__c) : "0" (__c) );
}
}
// - - - - - - - - - - - - - - - -

Auch das Studium des entsprechenden Abschnittes der *.lls-Datei half mir nie. Daher die Bitte um Hilfe.

Ausschnitt "*.lls"

00000032 <wms>:
for(; ms>0; ms--)
{ //
uint16_t __c = 26; // Ergibt am 14Dez21,17h34 1,001 sec bei wms=1000
// => bei wms ( 100) ist der (periphere) Einfluss
// des Programmablaufs deutlich messbar !!
__asm__ volatile ( //
32: 2a e1 ldi r18, 0x1A ; 26
34: 30 e0 ldi r19, 0x00 ; 0
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void wms ( uint16_t ms ) // 128 kHz-wms; Pausenwert ist nur experimentell !
// M E I S T keine Millisekunde ! ! !
// - - - - - - - - - - - - - - - -
{ // Weiter Messwerte in L3_tmr10.c und früher
for(; ms>0; ms--)
36: 04 c0 rjmp .+8 ; 0x40 <__SREG__+0x1>
{ //
uint16_t __c = 26; // Ergibt am 14Dez21,17h34 1,001 sec bei wms=1000
// => bei wms ( 100) ist der (periphere) Einfluss
// des Programmablaufs deutlich messbar !!
__asm__ volatile ( //
38: f9 01 movw r30, r18
3a: 31 97 sbiw r30, 0x01 ; 1
3c: f1 f7 brne .-4 ; 0x3a <__CCP__+0x6>
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void wms ( uint16_t ms ) // 128 kHz-wms; Pausenwert ist nur experimentell !
// M E I S T keine Millisekunde ! ! !
// - - - - - - - - - - - - - - - -
{ // Weiter Messwerte in L3_tmr10.c und früher
for(; ms>0; ms--)
3e: 01 97 sbiw r24, 0x01 ; 1
40: 00 97 sbiw r24, 0x00 ; 0
42: d1 f7 brne .-12 ; 0x38 <__CCP__+0x4>
"1: sbiw %0,1" "\n\t" // schwankt >> mit Temperatur ! ! !
"brne 1b"
: "=w" (__c) : "0" (__c)
);
}
}
44: 08 95 ret

00000046 <main>:
// ================================================== =========================== =

Danke im Voraus für Eure Mühe.

Searcher
17.12.2021, 14:13
Hallo Joe,
ich versuche mich mal am Assembler Teil:

Da wird in C mit Inline Assembler in einer Schleife eine Millisekunde versucht zu
erzeugen. Bei 128kHz ist ein Maschinenzyklus 1/128000Hz = 7,8125µs lang. Die Sequenz
3a: 31 97 sbiw r30 , 0x01
3c: f1 f7 brne .-4 ; 0x3a
subtrahiert von dem, in der Routine eingestellten Wert 26 (uint16_t __c = 26 ) den Wert eins solange, bis dieser 0 ist. 25 Subtraktionen und Sprünge auf Adresse 3a brauchen je 4 Zyklen. 4 * 25 * 7,8125 sind 781,25µs + 23,1375 = 804,6875µs. 3 * 7,8125µs = 23,1375µs für den letzten Schleifendurchlauf ohne Sprung.

Wenn 26 auf 0 runtergezählt ist, wird vom übergebenen Millisekundenwert eine Millisekunde abgezogen, und wenn nicht Null, dann Sprung auf 0x38 und wieder eine knappe Millisekunde (0,804..ms) Wartezeit erzeugt. Dafür wird bei Adresse 0x38 zunächst wieder der Wert 26 mit movw herausgekramt und dann wieder die Warteschleife durchlaufen. Jedes sbiw verschlingt 2 Maschinenzyklen. Der nachfolgende Vergleich mit Sprung auf 3x38 auch nochmal 2 Zyklen. Um zu ergründen, warum da mal eine 0 abgezogen wird, müßte man tiefer in asm einsteigen. Möglicherweise richten des Carry Flags.

Justierung der genauen Millisekunde also am Wert von uint16_t __c unter Berücksichtigung des Maschinentaktes.


00000032 <wms>:
for(; ms>0; ms--)
{ //
uint16_t __c = 26; // Ergibt am 14Dez21,17h34 1,001 sec bei wms=1000
// => bei wms ( 100) ist der (periphere) Einfluss
// des Programmablaufs deutlich messbar !!
__asm__ volatile ( //
** der Wert 26 wird als 2 Byte in die Prozessorregister 18, 19 geladen.
32: 2a e1 ldi r18, 0x1A ; 26
34: 30 e0 ldi r19, 0x00 ; 0
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void wms ( uint16_t ms ) // 128 kHz-wms; Pausenwert ist nur experimentell !
// M E I S T keine Millisekunde ! ! !
// - - - - - - - - - - - - - - - -
{ // Weiter Messwerte in L3_tmr10.c und früher
for(; ms>0; ms--)
36: 04 c0 rjmp .+8 ; 0x40 <__SREG__+0x1>
{ //
uint16_t __c = 26; // Ergibt am 14Dez21,17h34 1,001 sec bei wms=1000
// => bei wms ( 100) ist der (periphere) Einfluss
// des Programmablaufs deutlich messbar !!
__asm__ volatile ( //
** der Wert 26 wird aus r18, r19 nach r30, r31 übertragen
38: f9 01 movw r30, r18
** Beginn der Schleife für eine ms
3a: 31 97 sbiw r30, 0x01 ; 1
** Sprung nach 3a wenn noch nicht Null
3c: f1 f7 brne .-4 ; 0x3a <__CCP__+0x6>
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void wms ( uint16_t ms ) // 128 kHz-wms; Pausenwert ist nur experimentell !
// M E I S T keine Millisekunde ! ! !
// - - - - - - - - - - - - - - - -
{ // Weiter Messwerte in L3_tmr10.c und früher
for(; ms>0; ms--)
** Abzug von eins vom übergebenen Millisekundenwert, der sich anscheinend in r24, r25 befindet.
3e: 01 97 sbiw r24, 0x01 ; 1
40: 00 97 sbiw r24, 0x00 ; 0
** Wenn übergebener ms Wert noch nicht Null, dann nochmal Sprung zur 1ms Laufschleife
42: d1 f7 brne .-12 ; 0x38 <__CCP__+0x4>
"1: sbiw %0,1" "\n\t" // schwankt >> mit Temperatur ! ! !
"brne 1b"
: "=w" (__c) : "0" (__c)
);
}
}
** Rücksprung zu Aufrufort
44: 08 95 ret

00000046 <main>:
// ================================================== ===========================


Gruß
Searcher

oberallgeier
18.12.2021, 17:18
.. ich versuche mich mal am Assembler Teil: .. Danke für Deine Mühe, das hilft mir schon mal etwas weiter. Die eigentlichen Assemblerbefehle hatte ich schon aus dem Studio4 rausgelesen; die hatte ich zwar nicht parat, aber in meinen AVR-Anfangszeiten hatte ich wenige, einfache Aufgaben (z.B. Servotester mit Potiabfrage etc) in Assembler programmiert. Daher sagten mir zB sbiw oder brne schon etwas. (Es gibt auch noch Rudimente zu 8086- oder Z80-Assembler . . . aus der Studienzeit).


;Auszug aus Servotester; beispielhaft
mc_init: ;=== Initialisiere Mikrocontroller
; PB0 (servo1) + PB3 (servo2) = Ausgang, PB4 (poti1) = ADC-Eingang
ldi r16,0b00001000 ;Ausgang (1) auf PB3, Eingang (0) auf PB4
out ddrb,r16 ;Datenrichtungsbits setzen, Port ist Ausgang
ldi r16,0b11100111 ;Datenrichtungsbits setzen
out portb,r16 ; und initialisieren
ret ;=====----->>>>>

Unverständlich sind mir noch Zahlendarstellung dieser Art : rjmp .+8 , brne .-4 , brne .-12.

Ebenso unverständlich ist mir noch der Zusammenhang zwischen Assemblercode und Kommentar wie hier :
........rjmp .+8 ; 0x40 <__SREG__+0x1>
........brne .-4 ; 0x3a <__CCP__+0x6>
........brne .-12 ; 0x38 <__CCP__+0x4>

SREG hatte ich in der Studio4-Assemblerhilfe gefunden, ist klar. CCP dagegen gibts weder im Instruction Set Summary der Controllerdokumentation noch in der Studio4-Hilfe.

Lücken über Lücken - auch die Zeilen
........sbiw %0,1
........\n\t
........brne 1b
........: "=w" (__c) : "0" (__c)
sind geheimnisvoll.

Ich sehe, dass ich sehr umfassende Wissenslücken zu Assembler habe. Daher sollte ich die Titelfrage ändern in: Wo kann man diese Fragen - und alle sonstigen Feinheiten - zu Assembler lernen? Buch? (www-)Tutorial? oder . . .

Danke für die Aufmerksamkeit

Searcher
18.12.2021, 19:20
Hallo,



Unverständlich sind mir noch Zahlendarstellung dieser Art : rjmp .+8 , brne .-4 , brne .-12.

Ich benutze immer das Instruction set
http://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf

Da sieht man, daß das Sprungziel bei den meisten Sprünge wie auch die Sprünge bei Verzweigungen wie bei brne relativ mit einer Konstanten k angegeben werden. Der Assembler/Compiler rechnet die Konstante anhand des Sprunglabels aus und trägt es in den Befehl ein. Die .+8 bzw .-4 ist die relative Sprungweite. Dahinter schreibt der Assembler nochmal die Programmcounter Adresse als absoluten Wert wie 0x3A oder so ein. Die relative Sprungweite scheint zu groß zu sein, hat aber vermutlich damit zu tun, daß der Programmcounter schon auf den nächsten Befehl zeigt.




Ebenso unverständlich ist mir noch der Zusammenhang zwischen Assemblercode und Kommentar wie hier :
........rjmp .+8 ; 0x40 <__SREG__+0x1>
........brne .-4 ; 0x3a <__CCP__+0x6>
........brne .-12 ; 0x38 <__CCP__+0x4>

SREG hatte ich in der Studio4-Assemblerhilfe gefunden, ist klar. CCP dagegen gibts weder im Instruction Set Summary der Controllerdokumentation noch in der Studio4-Hilfe.


Der Compiler erzeugt auch eine .lst Datei. Dort findet man die Zuordnung der Namen zu Adressen unter DEFINED SYMBOLS ganz unten in der Datei.
Die 0x38 beim letzen brne liegt 0x4 Adressen über der Adresse von __CCP__ (0x3a liegt 0x6 über __CCP__). __CCP__ war bei mir das MCUSR Register (Schulterzuck :-) )
Das gleiche mit __SREG__ . Wozu die Angabe gut ist? Keine Ahnung.




Lücken über Lücken - auch die Zeilen
........sbiw %0,1
........\n\t
........brne 1b
........: "=w" (__c) : "0" (__c)
sind geheimnisvoll.


Das sind die Inline Assembler Geheimnisse/Syntax von C. Aufschluß könnte der Artikel im RN-Wissen geben:
https://rn-wissen.de/wiki/index.php/Inline-Assembler_in_avr-gcc



Ich sehe, dass ich sehr umfassende Wissenslücken zu Assembler habe. Daher sollte ich die Titelfrage ändern in: Wo kann man diese Fragen - und alle sonstigen Feinheiten - zu Assembler lernen? Buch? (www-)Tutorial? oder . . .


Da habe ich leider keine spezielle Empfehlung. Ich hatte meine ersten Assembler Erfahrungen mit dem C64. Jetzt versuche ich andere Beispielcodes zu verstehen bzw wie andere Authoren bestimmte Probleme lösen und nutze für meine asm-Routinen hauptsächlich das o.g. Instruction Set Manual.

Gruß
Searcher

oberallgeier
18.12.2021, 19:52
.. Ich benutze immer das Instruction set ..Danke, prima, das AVR-Instruction-Set-Manual hatte ich noch nicht. Nie gesehen. DAS ist ja wirklich sehr informativ - viel Lesestoff für diesen Winter *gg*. Dann noch der RN-Wissen-Abschnitt. Ja, so wirds wohl durchsichtig.

Erstmal vielen, vielen Dank!