• 27.04.2024, 18:49
  • Registrieren
  • Anmelden
  • Sie sind nicht angemeldet.

 

Lieber Besucher, herzlich willkommen bei: Aqua Computer Forum. Falls dies Ihr erster Besuch auf dieser Seite ist, lesen Sie sich bitte die Hilfe durch. Dort wird Ihnen die Bedienung dieser Seite näher erläutert. Darüber hinaus sollten Sie sich registrieren, um alle Funktionen dieser Seite nutzen zu können. Benutzen Sie das Registrierungsformular, um sich zu registrieren oder informieren Sie sich ausführlich über den Registrierungsvorgang. Falls Sie sich bereits zu einem früheren Zeitpunkt registriert haben, können Sie sich hier anmelden.

AVR Assembler - Stack einrichten?

Samstag, 9. Oktober 2010, 18:18

Hallo,

genau genommen geht's ums AVR-Studio. In (fast?) allen Tutorials liest man immer, daß der Stackpionter initialisiert werden muß (Anfängerfehler etc bla bla). Warum? Das setzen von SPH und SPL (auf high(ramend) bzw low(ramend)) kostet mich immerhin 4 Takte und 4 words program-flash. Aber im Datenblatt (des Mega88 zB) steht auf Seite 12 bei 6.6.1, daß die initial values bereits RAMEND sind, oder habe ich da was falsch verstanden?

(Das pushen (2x) bei Eintritt in eine ISR läßt sich nicht vermeiden, oder? (nach der Initialisierung der Hardware ist das "Hauptprogramm" 'ne leere Schleife - alles andere in ISRs. Wenn ich statt des RETI das globale interrupt flag setze (SEI), und danach ein RJMP auf sich selbst (Endlosschleife), brauch ich theoretisch den Stack nicht - hätte also den kompletten SRAM frei für Meßwerte... (gesammte Verarbeitung läuft in den Rechenregistern) ok, so sinds dann halt 2 weniger)

Samstag, 9. Oktober 2010, 20:35

du brauchst den stack nur wenn du push und pop operationen für die register benötigst.
wenn du also ne leere main schleife hast und alles in den irqs erledigst, brauchst du keine register zu sichern.
normalerweise sicher man alle arbeitsregister beim eintritt in den irq und stellt die nach dem eintritt wieder her.

der stackpointer kann nach einen reset überall stehen. deshalb sollte man den per hand neu initialieren. wenn das ohne ginge würde es nicht die probleme geben die viele beschreiben die es nicht machen.
naja und für deine main must du so etwas wie
while(1){
}

programmieren. oder

main:
rjmp main

wenn du so eine speicherkritische geschichte machen willst, nimm doch einen controller mit mehr speicher.

Samstag, 9. Oktober 2010, 22:19

ok, das mit dem Stackpointer ist jetzt klar:



Zitat




This Stack space in the data SRAM must be defined by the program before

any subroutine calls are executed or interrupts are enabled.

hatte mich nur gewundert, daß es in der Tabelle darunter als "initial value = RAMEND" angegeben wird - wenn ich den selbst setzen muß, ist initial was ICH vorgebe (ok, muß über 0100hex liegen). egal...

und daß ich den Stack dann nicht brauche ist klar, ABER mMn pusht der Interrupt den ProgrammCounter (=2 byte) auf den Stack, eben um später wieder zurück zu kommen (Reti). Wenn er das nicht täte (also wenn ich's unterbinden könnte), müßte sich eben in jeder ISR am Ende eine Endlosschleife befinden, direkt vorher halt die Interrupts global wider zugelassen.

So aber muß ich eh dafür sorgen, daß der Stack wieder gepoppt(2x) wird, also kann das klassisch mit dem RETI erfolgen -> zurück in die leere

Zitat

main:
rjmp main




Ich selbst muß trotz meiner Interrupts keine Register sichern. Warum?

-Alle (von mir verwendeten) Rechenregister sind für genau eine Funktion reserviert

-im SREG kann es nur zu Kollisionen kommen, wenn irgendwo ein Interrupt reinhaut. Im "Hauptprogramm" gibt es aber keine Operationen, und Interrupts können sich nicht gegenseitig unterbrechen (zumindest nicht, solange man in der ISR Interrupts global nicht wieder freigibt ;) )

Edit: Speicherkritisch ist das nicht wirklich, aber der Speicher ist dazu da genutzt zu werden. Sinnig genutzt. Werde mich wohl auf 1020 samples festlegen - dann liegen ja noch 2 Bytes brach :P



Danke

Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von »LotadaC« (9. Oktober 2010, 22:25)

Samstag, 9. Oktober 2010, 23:18

wegen 2 byte so ein unfug treiben und ein totel unsicheres system ohne sichern von registern oder ähnliches zu betreiben?
ich weis ja nicht.
Ja bei eine call oder isr wird der SP um 2 erhöht um die Rücksprungadresse zu sichern.
Wenn das programm eher simpel ist mag das ja noch gehen, aber bei steigender kmplexität bricht dir das später das genick.

Und um auf den stack zu verzichten kannst du auch alle ISRs deaktivieren, die flags per Software pollen und alles per direkten jump machen. Da wird dann auch nichts auf dem stack gesichert.

Mittwoch, 13. Oktober 2010, 13:28

Ne ne, die 1020 bytes reichen mir... ging mir eigentlich mehr um das Verständnis.

Aber jetzt ist mir grad aufgefallen, daß ich den ADC (u.a.) durch den OutputCompareB des Timers (1) autotriggern lassen kann (der Timer läuft im CTC-mode, also bis OC1A, bisher hatte ich da (IRQ) den ADC-Channel umsetzen lassen, und den neu ADC getriggert) - jetzt muß ich das in der ADCcomplete-ISR machen lassen... aber ich brauche auch die Timer ISR nicht mehr (ok, das OutputCompareMatch-flag muß ich dann selbst in der ADC-ISR löschen lassen - der wird nämlich bei steigender Flanke des flags getriggert, und das flag selbst wird ja automatisch nur in der dazu gehörenden ISR gelöscht... Und ich muß beide CompareMatches verwenden - A für den CTC-mode, und B für die ADC-triggerung...

Aber eine generelle Frage hab ich noch:

Trotz der, im DB angegebenen "initial values" des Stackpointers muß man den ja vor Gebrauch "Initialisieren". Wie ist das mit den "initial values" der verwendeten "Hardwareregister"? (also zB UCSR0C für die serielle Schnittstelle - initial value ist 0b00000110 für die asynchron, keine Parität, 1 Stopbit, und 8 Datenbits (das sind die beiden 1-bits). Genau diese Einstellung nehme ich ja - muß ich das auch nochmal initialisieren? Wenn ja, wozu sind dann überhaupt "initial" values angegeben? Wenn nicht, was ist hier der Unterschied zum Stackpointer? )

P.S.: wie sollte man die Initialisierung denn dann eigentlich machen? Für jedes verwendete Register jedesmal:

Quellcode

1
2
ldi tempregister, (1<<bitname)|(1<<bitname)...
sts HWregistername, tempregister


wegen der Lesbarkeit und einfachererer Anpassung für andere µCs etc... , oder

Quellcode

1
2
3
4
5
6
ldi tempregister, konstante  ;Konstante ist der Wert für alle folgenden Register
sts HWregistername1, tempregister
sts HWregistername2, tempregister
sts HWregistername3, tempregister
sts HWregistername4, tempregister
...


wegen effizienterem code - man spart ja für jedes weitere Register eine LDI-Instruktion.)

Mittwoch, 13. Oktober 2010, 13:57

Nicht verwendete HW braucht nicht angefasst werden, da diese in einem zustand (meinst aus) ist.
HW die du benötigst, also UART, Timer, SPI... muss voll initialsiert werden.
Es dei denn du steht auf fehler die sporadisch auftreten.

Mittwoch, 13. Oktober 2010, 16:09

Also auch die Register schreiben, die bereits "initial" sind, sofern sie die verwendete Hardware betreffen. Ok, das ist dann einleuchtend und konsequent (->Stack), aber wozu dann die "initial values" im Datenblatt? Das hätte ich mit "Anfangswert" oder so ähnlich übersetzt...

Danke nochmal :)

P.S.: dann muß man auch Flagregister leeren, wenn man nicht riskieren will, daß der entsprechende Interrupt (sofern freigegeben) bei "SEI" losgeht, oder? (Oh... bei meiner flankenbasierten Autotriggerung könnte das Flag dann ja auch beim Start schon gesetzt sein -> nei eine steigende Flanke -> nie der ADCC -> nie das Flag gelöscht... = sporadische Fehlfunktion beim anschalten/reset...

edit: och nöö... was denn nun?

Datenblatt des Mega88 auf Seite 44

Zitat


During reset, all I/O Registers are set to their initial values, and the program starts execution from the Reset Vector. For the ATmega168, the instruction placed at the Reset Vector must be a JMP – Absolute Jump – instruction to the reset handling routine. For the ATmega48 and ATmega88, the instruction placed at the Reset Vector must be an RJMP...

Das Anschalten ist doch auch ein (power on) Reset... also brauch ich Register, die ich wie inital verwenden will, auch nicht anzufassen, oder wie jetzt?

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »LotadaC« (13. Oktober 2010, 16:15)

Mittwoch, 13. Oktober 2010, 16:22

ja, man löscht vor aktivieren der die IRQ flags in den Status Registern der einzelnen peripherie und die globalen Flags.
Sonst kann es passieren das einfach so beim starten des controllers ein INT auslöst.
Zb. wenn du einen Brown Out Reset bekommst und deine UART einen undefinierten status hat und irgendwo hängt.

Eigentlich sollte der richtige weg folgender sein:

Alle IRQS global aus:

IO Ports Init,
Peripherie Init;

Beispiel für Timer:
timer compare register setzen,
timer value register (counter), (wenn erforderlich)
timer control register setzten,
löschen der timer irq flags,
aktivieren des timer irq

IRQs Global wieder an
Watchdog an


-> in die Main schleife

Mittwoch, 13. Oktober 2010, 17:54

Wenn Du das so alles selbst umsetzt, geht das - sicher(!). Aber was ich jetzt nicht verstehe, ist der Widerspruch dazu im Datenblatt.

(I): Aus meinem Zitat über Deinem Beitrag geht hervor, daß nach einem Reset alle(!) I/O-Register auf den initial-wert geladen werden. Danach startet der code mit dem Resetvektor. (S.44 - 10.1)

(II): Die I/O-Register befinden sich im Data Memory von 0x0020 - 0x00FF (0x0020 - 0x005F (I/O) und 0x0060 - 0x00FF (Extended I/O) (S.18 - 7.3)

(III): Über diese Register wird auch die HW gesteuert/ausgelesen

(IV): Der Stackpointer muß vor der Verwendung initialisiert werden (Zitat in Beitrag Nr3, DB S.12 - 6.6)

(V): Initial Value des Stackpointers ist "Ramend" (beide Register) - (S.13 - 6.6.1)

(VI): Der Stackpointer liegt mit 0x3E und 0x3D im I/O-Memory (siehe V)



das widerspricht sich doch... oder wo hab ich jetzt was falsch verstanden?

Mittwoch, 13. Oktober 2010, 19:19

Das mache ich immer so,.. weil sonst kann es immer wieder undefinierte probleme geben.
Das machen zb. auch alle IDEs wie Codewarrior oder die von IAR automatisch (wenn man in C schreibt).
Das Problem sind eben dann doch die undefinierten zustände wenn der Reset eben mal nicht sauber durchgelaufen ist. und das ist gar nicht mal so selten wie man denken mag.
Normalerweise gehört auch ein sauberes neu initialsieren der Ram bereiche dazu.
Das Problem ist einfach das genau solche fälle von nicht richtig initialsierter HW eben sporadische fehlfunktionen auslösen kann. soetwas fällt aber meist erst auf wenn man mal mehr als 100 Geräte im einsatz hat die 24/7 laufen.

Da gehe ich zumnidest immer auf nummer sicher und Inititalisiere alle HW die ich verwende richtig. Das spart viel mehr arbeit und zeit als wenn man dann irgendwelche probleme suchen muss die sehr schwer reproduzierbar sind - gerade wenn man sich nie sicher sein kann ob es nun aus der HW oder der Firmware kommt.

Mittwoch, 13. Oktober 2010, 19:31

wenn der Reset eben mal nicht sauber durchgelaufen ist. und das ist gar nicht mal so selten wie man denken mag.



Du meinst damit, daß der "unvollständige Reset" eben NICHT alle Register auf initial setzt (etc), danach aber TROTZDEM der Resetvektor angesprungen wird? Hätte ich jetzt nicht erwartet - aber ok. Soviel Zeit und Platz (flash) ist ja auch noch drinn...

Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von »LotadaC« (13. Oktober 2010, 19:32)

Mittwoch, 13. Oktober 2010, 19:39

Ich habe mal in den Datenblättern geschaut.
Scheinbar Macht Atmel jetzt bei neueren Versionen etwas anders und initialsiert nun alle Daten mit einem Reset (nicht alle Controller).
Aber die haben nicht das komplette Datenblatt überarbeitet, deshalb machen die wiedersprüchliche aussagen.
Ich gehe bei solchen sachen immer auf nummer sicher und Initialisiere richtig, da es eigentlich auch nix kostet ausser 5 min Zeit.
Auch bei Atmel sollte man die Datenblätter immer mit vorsicht genießen.

Wir haben zb. bei Freescale und und deren C Compiler auch schon böse Bugs gefunden die wir dann anhand von Code Samples beweisen konnten.
Aber ehe man soetwas gefunden hat sucht man natürlich auch erst mal lange warum der eigene Code nicht funktioniert.
Und da ist es am sichersten wenn man auch so einfache sachen wie die HW die eigentlich voll initialsiert sein sollte doch einfach richtig initialisiert.

Donnerstag, 14. Oktober 2010, 23:52

Erzeugen diese "Hochsprachencompiler" Assemblercode (der dann durch den AVR-Assembler umgesetzt wird), oder direkt die entsprechenden Opcodes (Maschinencode)? Und haben die Programmierer dieser Compiler andere Quellen als die Datenblätter zu den µCs und dem Instruction Set? (außer der von Dir beschriebenen "Fehlersuche")

Ich habe mich jetzt entschieden, für den Assembler 'ne "Safe" Konstante einzubauen, und alle Register, die für mich durch den Reset bereits fertig gesetzt sein sollten(!) in einem/mehreren ".if safe then ... .end if" Block zu initialisieren. Die werden dann ja nicht mit übersetzt, bei Problemen (die das als Ursache haben) reicht es dann, die Konstante umzudefienieren und neu zu assemblieren / flashen.

Freitag, 15. Oktober 2010, 07:46

normalerweise (so ich das) erzeugen die C Compiler für µController als zwischenschritt ein assebler listing. dh. der komplette C code wird mit Kommentaren des C Codes in ein Assembler Listing übersetzt. Und dann erst zu den opcodes übersetzt.
Wenn dann wirklich mal was nicht funktioniert kann man zumindest mal im ASM Listing den fehler suchen.
Zumindest macht das IAR, Metrowerks und der GCC das so.

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
//***********************************************************************
		//********** I2C SLAVE muss Daten Senden ********************************
		case TWI_STX_ADR_ACK:									// Own SLA+R has been received; ACK has been returned
			//TWI_bufPtr   = 0;                      	//pointer to first data location
			pRoDataRead= (char*)&aquastreamI2C;  	   
	16a4:	86 e4   		ldi	r24, 0x46	; 70
	16a6:	92 e0   		ldi	r25, 0x02	; 2
	16a8:	90 93 e7 00 	sts	0x00E7, r25
	16ac:	80 93 e6 00 	sts	0x00E6, r24
			internalData.i2cDataTrigger |= (1<<I2CTRIGGER_CPY);
	16b0:	80 91 bb 00 	lds	r24, 0x00BB
	16b4:	81 60   		ori	r24, 0x01	; 1
	16b6:	80 93 bb 00 	sts	0x00BB, r24

Ähnliche Themen