Hallo liebe Mikrocontroller Gemeinde.
Ich habe ein kleines Projekt für mich um den STM32F103C8 besser kennen
zulernen. Ich möchte eine LED blinken lassen und zwar im Takt von einer
Sekunde. Ich möchte den Takt über Timer 2 realisieren.
Das ganze soll in asm mit keil realisiert werden. LED und PORT alles
schon richtig Initialisiert der Timer läuft auch schon, aber jetzt kommt
der Interrupt. Leider habe ich dort keine Idee wie das mit ASM
realisieren kann.
Ich bin Anfänger in ASM, aber lernwillig. Mir würde es schon reichen
wenn ich das mit dem NVIC verstehen würde, welche Register für TIM2
zuständig sind im NVIC.
Ich habe mal mein ASM file mit angehangen. Ich bin auch gerne für
Verbesserungsvorschläge im Code wie man ihn besser strukturieren kann
usw.. sehr dankbar.
Hallo,
also erstmal empfehle ich dir das:
www.st.com/resource/en/programming_manual/cd00228163.pdf
Im Reference Manual steht das der "TIM2 global Interrupt" die ID 28 hat.
Für die Interruptkonfiguration der Interrupts von 0 bis 31 (dein TIM2
ist 28) sind die folgenden Register zuständig:
ISER0 (Set-enable): Interrupt aktivieren
ICER0 (Clear-enable): Interrupt deaktivieren
ISPR0 (Set-pending): Interrupt ausloesen
ICPR0 (Clear-pending): Interrupt Flag loeschen
Du musst das Bit 28 (gezaehlt von 0) ansprechen in ALLEN Registern
(immer 1 schreiben).
Willst du das TIM2 Interrupt aktivieren dann mach folgendes
(Pseudocode):
ISER0 |= BIT28
Du musst dann noch die Interrupt global aktivieren per Assemblerbefehl:
"CPSIE I"
So beginnst du deinen Interrupthandler:
.global TIM2_IRQHandler
TIM2_IRQHandler:
Für deinen TIM2 Konfiguration musst du noch folgende Register
konfigurieren:
TIM2_DIER Bit UIE
Am besten vor diesem Befehl im Register
TIM2_SR das Bit UIF auf 0 setzen, was du auch am Anfang deines TIM2 IRQ
Handlers machen solltest.
Ich bin mir nicht sicher, aber es scheint ausreichend nur dieses Flag
zurücksetzen bei einem Interrupt, dass Register ICPR0 musst du nicht
ansteuern. Vielleicht mal ausprobieren ob du ein Unterschied zwischen
ICPR0 und TIM2_SR findest, vielleicht sind diese miteinander verbunden.
Noch ein Tipp:
Folgende PDF Dateien solltest du immer offen haben für deine
Lernerfolge:
-Datasheet
-Reference Manual
-Programming Manual
Die Informationen sind verteilt über diese 3 Sachen. z.B. habe ich im
Reference Manual geschaut welche ID dein gewünschter Interrupt hat und
habe dann im Programming Manual die notwendigen Register identifiziert
Ich hab etwas recherchiert:
Wenn der Interrupt aktiviert wird, dann führt der Prozessor automatisch
ICPR0 aus, aber du musst dich selbst um TIM2_SR kümmern.
Für Interrupt solltest du dir merken:
Es reicht nicht aus die Interrupts für die Peripherie nur zu aktivieren,
sondern du musst anschließend noch die NVIC Bits dementsprechend setzen
(meistens Register ISER0)
Vielen dank für dein ausführliche Hilfe. Genau die drei Sachen habe ich
auch immer auf. Auf meinem Laptop leider nicht ganz bequem aber so ist
das halt.
Ich werde das ganze mal ausprobieren. Ich bedanke mich nochmal für die
Hilfe. Du hast genau das erklärt was ich nicht ganz verstanden hab.
Vielen dank.
So gestern noch ein wenig getestet und wie erwartet gibt es noch einige
Problem. Einige Sachen die ich nicht verstanden habe, habe ich im Code
kommentiert.
Ich Arbeite auch das erste mal mit "Pusch" und "Pull" befehlen. Die
scheinen ganz gut zu funktionieren aber muss ich noch einen Stack
einrichten?
Ich probiere auch gerade eine "If" "Else" Anweisung mit asm
hinzubekommen.
Bin ich da mit "CMD" auf den richtigen weg?
Mir ist beim lesen von anderen asm Dokumenten aufgefallen das manche
"befehle" bei mir nicht erkannt werden. Ich nenne mal als Bsp.
.equ geht nicht bei mir aber "Test EQU 0x00000000" wird erkannt. Kann es
sein das es an .Thump oder ASM liegt und wenn ja welche von beiden
benutze ich eigentlich?
Ich bedanke mich schon mal.
Motze schrieb:> .equ
Das kann meines Wissens nach nur der GAS. Keil mag nur EQU.
Motze schrieb:> Ich probiere auch gerade eine "If" "Else" Anweisung mit asm> hinzubekommen.> Bin ich da mit "CMD" auf den richtigen weg?
CMD kenne ich nicht aber CMN und CMP kannst du für Vergleiche nutzen um
dann darauf folgenden Instruktionen per "condition code" nur unter der
bestimmten Bedingung auszuführen. Für Sprunganweisungen kannst du CBZ
("compare and branch if zero") oder CBNZ ("... if non-zero") nutzen.
Wenn du Interrupts nutzen willst benötigst du auch noch eine
Vektortabelle. Wie diese aussehen kann bzw. sollte, schaust du dir am
besten im Startup-Code von Keil bzw. ST an, der typischerweise von denen
für deinen Controller gedacht ist.
Motze schrieb:> aber muss ich noch einen Stack einrichten?
Die ersten 4 Byte im Flash sind die Stackadresse. Praktisch gesehen
kannst Du ein uint32_t-Array hernehmen:
uint32_t my_stack[N];
und die Adresse, die dann im Flash am Anfang stehen muß, lautet einfach
&my_stack[N].
Das ist kein Array-Overflow, weil es sich um einen fully descendent
stack handelt, d.h. von dem Wert wird vor der Benutzung ERSTMAL 4
abgezogen und DANN was reingeschrieben, so daß der erste Schreibvorgang
bei my_stack[N-1] landet, was der letzte Eintrag des Arrays ist.
In Assembler halt entsprechend mit der Reservierung eines entsprechend
großen Speicherbereiches. Optimal ist, wenn die Adresse &my_stack[N]
durch 8 teilbar ist.
Danke die Vektortabelle hat Keil schon im Start up fiel erzeugt. Was das
"EXPORT TIM2_IRQHandler" in meinen Code überflüssig macht. Danke für die
Info.
Jetzt kommt was ganz banales wie kann ich den Inhalt eines Speichers in
ein Register einlesen?
z.B.
LDR r5, = GPIOC_ODR ;Das liest aber nur die Adresse ein
CMP r5, #0x2000 ;hier möchte ich dann rausfinden ob das Bit
gesetzt
;ist
Lg Motze
Hallo,
was du meinst ist eine indirekte Adressierung (Pointer): Du gibst die
Adresse in ein Register ein (das ist deine erste Zeile) und dann der
nachfolgende Befehl:
LDR r6, [r5] ; Schaue was an der Adresse in R5 steht und lade es in r6
Viele Grüße
derjaeger
derjaeger schrieb:> Hallo,>> was du meinst ist eine indirekte Adressierung (Pointer): Du gibst die> Adresse in ein Register ein (das ist deine erste Zeile) und dann der> nachfolgende Befehl:>> LDR r6, [r5] ; Schaue was an der Adresse in R5 steht und lade es in r6>>>> Viele Grüße> derjaeger
Und bitte nur die Basisadresse eines Periepherieblocks (z.B.Timer2) in
r5 laden und die einzelnen Periepherie-Register per
LDR r5,=Timer2_Base ; eigentlich LDR r5,[pc+n] mit n Offset zur
Konstante
LDR r6,[r5+16] ; Timer2_SR Laden
ansprechen.
Dazu hat Sophie Wilson extra Base-Offset-Adressierung (neben diversem
Anderem) von den IBM-Mainframes abgeschaut. Das sollte man dann auch
nutzen.
C-Compiler können das auch, und Hand-Codiert will doch auf keinen Falk
schlechter sein ;-)
So schleife läuft keine Ahnung bei mir musste das anstatt CBZ u. CBNZ
eben BEQ und BNE heißen. Danke für den Hinweis mit LDR r6, [r5] hat mir
viel weitergeholfen.
So jetzt habe ich nur noch ein Problem mit dem Interrupt,
wenn ich Debugge springt er einmal in den Interrupt und dann nie wieder
und wenn ich den MCU einfach so laufen lasse scheint der Interrupt aber
nicht auszulösen.
Ich glaube das ich NVIC noch nicht richtig Konfiguriert habe.
Jetzt muss ich noch lernen wie das ganze hübscher wird und die
Kommentare was die einzelnen schritte machen, werden immer wichtiger
habe ich gemerkt.
derjaeger schrieb:>>C-Compiler können das auch>> Ich hab mir die Defines von der SPL angeschaut und da werden die> Register so auch definiert: Immer Basisadresse + Offset
Nur ist "so auch" nicht das gleiche. Das [rn+ofs] muß schon in einem
Assembler-Befehl stehen und nicht im "Text-Ersetzungs-Prozessor".
Hallo,
der ARM-Mikrocontroller hat ein paar tolle Sachen auf Lager. Eins davon
sind "conditional executions": Du kannst bei jedem Befehl anhängen WANN
er eigentlich ausgeführt werden soll.
Dann kannst du dir diese -if-else mit Labels und Jumps sparen:
LDR r5, = GPIOC_ODR
LDR r6, [r5]
CMP r6, #0x2000
;Nur einer von beiden nachfolgenden Befehlen wird ausgefuehrt abhaengig
vom Ergebnis des CMP
STREQ r1, [r0];beachte das 'EQ'
STRNE r2, [r0];beachte das 'NE'
POP {r1,r2,r3,r4,r5,r6}
BX LR
Super so ein ARM, oder?
Hier ist eine Liste von Befehlen:
http://infocenter.arm.com/help/topic/com.arm.doc.qrc0001m/QRC0001_UAL.pdf
Seite 6, rechte Tabelle: Diese Codes kannst du hinter (fast?) jeden
Befehl kleben und brauchst gar keine if-else mehr zu machen
Das 'CPSIE i' gehoert kurz vor deine loop, aber erst NACH dem
Aktivieren des NVIC Kanals !
Woran dein Interrupt scheitert weiß ich noch nicht.
Ich empfehle dir folgendes zum Suchen:
Ersetze dein Code im Interrupthandler einfach durch folgende Zeile (ohne
Interruptflag löschen, Push und Pop usw:
LDR r5, =GPIOC_BSRR ;1 im Register schreiben setzt den Pin
LDR r6, =0x2000
STR r6, [r5]
forever: b forever
Wenn die LED nicht angeht, dann wird dein Interrupt noch nicht
ausgelöst. Wenn das schonmal klappt, dann funktioniert dein NVIC und der
Rest sollte einfacher sein
Zum Testen würd ich dir empfehlen den obengezeigten Code erstmal in der
main zu probieren, ob er auch wirklich ausgeführt wird. Dann kannst du
Sachen wie falsche Portkonfiguration ausschließen und dem Interrupt die
Schuld in die Schuhe schieben.
Danke für den Hinweis und den sehr guten Link. Die LEDs blinken wie
gewollt aber leider nur im Debugger.
Der Interrupt wird erreich aber wahrscheinlich nur einmal. Bin mir aber
nicht sicher. Ich habe das ganze mit deinem Code getestet ohne
rücksetzen des Interrupt auch leider keine Änderung.
Ich kann im Interrupt die LED ein oder ausschalten, aber nur einmal.
Entweder ist der Timer 2 nicht richtig Initialisiert so das es ewig
brauch um ein neuen Interrupt auszulösen oder irgendwas anderes ist es.
Hier ist der aktuelle Code.
Ja und ich finde den ARM super. Ich kratze zwar nur die Oberfläche von
dem was der alles kann, aber ich bin begeistert. Hatte den vorher immer
nur in C Programmiert und wollte einfach mehr verstehen was dort vor
sich geht. Ich merke wie manche Sachen auch Stück für Stück logischer
erscheinen. Das meine ich in C und ASM.
Liegt es vielleicht am Auto reload? Ich habe bei mir damals diese
Einstellung immer aktiviert für meine Timer:
Register TIM2_CR1:
Bit 7 ARPE: Auto-reload preload enable
0: TIMx_ARR register is not buffered
1: TIMx_ARR register is buffered
Wenn es auch nicht klappt, würde ich dir empfehlen zu schauen ob dein
Timer sich nicht im Interrupt deaktiviert/stoppt und vielleicht die Bits
nochmal zu lesen insbesondere das Bit "CEN" und "UIE".
Motze schrieb:> Ich kann im Interrupt die LED ein oder ausschalten, aber nur einmal.
Wenn ich das richtig sehe liegt das an deiner Toggle-Funktion im
Handler.
Schau dir nochmal das BSRR im Handbuch an. Das ist kein Toggle-Register.
Set- und Reset-Bit sind um 16 Bit verschoben und du musst schon das
jeweils richtige schreiben.
Du musst übrigens nicht alle Register im Handler manuell sichern. R0-R3,
R12, xPSR, PC und LR landen automatisch auf dem Stack. Darum kümmert
sich der Prozessor automatisch. Siehe auch
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0337e/Babedgea.html
>Schau dir nochmal das BSRR im Handbuch an. Das ist kein Toggle-Register.
Er verwendet das Register "ODR" im Interrupthandler (hat er vorher in
der main ins Register geladen):
This is a 16-bit read/write register. Each bit represents the output
value on a corresponding pin. Writing a '0' in bit 8 of this GPIOC _ODR
register indicates that the voltage on PC8 is driven by the micro to 0V
(GND). While writing a '1' in bit 8 of this GPIOC _ODR register
indicates that the voltage on PC8 is driven by the micro to 3.3V (VDD).
Was ich im Interrupthandler besser machen würde: Vertraue nicht darauf,
dass in r0, r1, r2 bereits das richtige steht (hast du ja in main
initialisiert) sondern lade es im Interrupthandler nochmal rein. Das
sieht auch viel lesbarer aus.
derjaeger schrieb:> Er verwendet das Register "ODR" im Interrupthandler (hat er vorher in> der main ins Register geladen):
Ich hatte nicht gesehen, dass er die vorher korrekt beladen hatte. Der
von dir vorgeschlagene Code mit BSRR toggelt aber nur einmal, was du ja
auch gesagt hattest. Ich hatte mich auf das hier bezogen:
> Ich habe das ganze mit deinem Code getestet ohne> rücksetzen des Interrupt auch leider keine Änderung.> Ich kann im Interrupt die LED ein oder ausschalten, aber nur einmal.
Beim nochmaligen durchlesen verstehe ich aber selber nicht so ganz, was
da beim TO jetzt geht und was nicht.
derjaeger schrieb:> Was ich im Interrupthandler besser machen würde: Vertraue nicht darauf,> dass in r0, r1, r2 bereits das richtige steht (hast du ja in main> initialisiert) sondern lade es im Interrupthandler nochmal rein. Das> sieht auch viel lesbarer aus.
Das würde ich definitiv auch machen. Wie oben schon geschrieben werden
r0-r3, etc. ohnehin automatisch gesichert. Die kann man also bedenkenlos
mit neuen Sachen vollschreiben. Lediglich das Link Register darf man
nicht (etwa durch ein BL) überschreiben ohne es vorher zu sichern, weil
da der spezielle Wert "exception return" drin stehen muss. Sonst führt
ein BX LR logischerweise nicht zum verlassen des Handlers.
Hallo,
so habe den Code jetzt ein wenig umgeschrieben und euren Tipps
angepasst. Die Funktion hat das leider nicht verändert. Es wird nur
einmal in die Interrupt Rutine gesprungen und dann verbleibt das ganze
in der main Schleife.
Bit 7 ARPE: Auto-reload preload enable <- Habe ich auch umgesetzt hat
aber leider nicht viel geholfen. Ich habe es aber drin gelassen.
Geh nach folgendem Prinzip vor:
Erstmal machst du dein LED blinken mit Timer 2, aber ohne einen
Interrupt Handler (d.h. UIE = 0), sondern nur in der main nach folgendem
Schema:
1. GPIO konfigurieren
2. Timer konfigurieren (ohne Interrupt einschalten, UIE = 0)
3. Timer 2 starten (CEN = 1)
4. In einer Schleife das Interrupt Flag von Timer 2 (UIF) abfragen auf
HIGH
5. Das Interrupt Flag dann löschen wenn UIF = 1
6. Den Pin togglen
7. Springe zurück nach [4] und warte bis der Timer den nächsten
Interrupt raushaut
Damit testest du ob dein Timer nach einem Interruptauslösung (die du
aber nicht in einem Interrupthandler bearbeitest sondern in main) weiter
macht oder keine weiteren Interrupts auslöst nach dem ersten Mal.
Im folgenden würde ich unter "B loop" mal ein ENDP packen und ein PROC
hinter "TIM2_IRQHandler". Es kann sein, das der Linker den Handler an
eine gerade Adresse packt und dann führt der Sprung in den Handler zum
Crash. Was sagt eigentlich der Debugger? Für so etwas wurden die doch
ursprünglich mal entwickelt.
Christopher J. schrieb:> Es kann sein, das der Linker den Handler an> eine gerade Adresse packt
Tut er sowieso, weil das LSB keine Adreßbedeutung hat, sondern thumb/arm
auswählt. Bei Cortex-M also immer thumb.
Nop schrieb:> Christopher J. schrieb:>> Es kann sein, das der Linker den Handler an>> eine gerade Adresse packt>> Tut er sowieso, weil das LSB keine Adreßbedeutung hat, sondern thumb/arm> auswählt. Bei Cortex-M also immer thumb.
Ja, für den Cortex-M macht alles außer Thumb-Code keinen Sinn aber das
heißt nicht, dass er das LSB ignoriert. Auf ein BX zu einer geraden
Adresse reagiert der Cortex-M allergisch. Das gleiche gilt auch für die
Sprungadressen in der Vektortabelle. Das hatten wir letztens erst:
Beitrag "STM32F401 C-ISR durch Assembler-ISR ersetzen"
Vielen Dank an alle Unterstützer.
Jetzt Funktioniert alles und der Timer2 löst im Sekundentakt sein
Interrupt aus.
LDR r0, = TIM2_DIER
MOV r1, #1
STR r1, [r0]
Das hat gefehlt. Manchmal kann so ein paar Zeilen Code mehrere Stunden
Arbeit bedeuten. So jetzt kommt mein nächstes Projekt und das soll eine
7 Segment Anzeige in Sekundentakt hochzählen lassen.
Ich bedanke mich nochmal an die Unterstützer und habe den vollständigen
Code angehangen.
Christopher J. schrieb:> Auf ein BX zu einer geraden> Adresse reagiert der Cortex-M allergisch.
Ja, weil "gerade Adresse" soviel bedeutet wie "mach am Ziel mal mit
ARM-Code weiter", und Cortex-M kann nur Thumb. Logisch stürzt das ab.
Trotzdem ist die Funktion physikalisch an einer geraden Adresse richtig,
auch wenn in der Vektortabelle die Sprunganweisung zu einer ungeraden
geht. Das heißt soviel wie "spring zu der geraden Adresse und ignoriere
das LSB dabei, aber mach mit Thumb weiter".
Um das überhaupt machen zu können, muß das LSB für die Anzeige des
Codemodus der Zielfunktion reserviert sein, und das bedeutet, daß die
Funktionen physikalisch immer an geraden Adressen liegen, also was den
Linker angeht. Der linkt das nicht an ungerade Adressen, sondern das
geht andersrum, daß zu den Sprunganweisungen 1 draufaddiert wird.
STM32 ASM schrieb im Beitrag #5157176:
> Leider habe ich dort keine Idee wie das mit ASM realisieren kann.> Ich bin Anfänger in ASM, aber lernwillig.
würde ich bleiben lassen ... Assembler macht auf solchen Geschossen
keinen Sinn mehr ... und der Compiler optimiert eh besser, als es jemand
per Hand machen könnte :)
Mampf unterwegs schrieb:> STM32 ASM schrieb im Beitrag #5157176:>> Leider habe ich dort keine Idee wie das mit ASM realisieren kann.>> Ich bin Anfänger in ASM, aber lernwillig.>> würde ich bleiben lassen ... Assembler macht auf solchen Geschossen> keinen Sinn mehr ... und der Compiler optimiert eh besser, als es jemand> per Hand machen könnte :)
Besonders wenn man sieht mit welchem Aufwand beim Togglen das XOR
realisiert ist. Conditional Move ist "cool", aber nicht immer
angebracht.
Nop schrieb:> Trotzdem ist die Funktion physikalisch an einer geraden Adresse richtig,> auch wenn in der Vektortabelle die Sprunganweisung zu einer ungeraden> geht. Das heißt soviel wie "spring zu der geraden Adresse und ignoriere> das LSB dabei, aber mach mit Thumb weiter".
Da hast du natürlich vollkommen Recht. Ich meinte auch die Adresse in
der Vektortabelle aber habe mich falsch ausgedrückt.
Motze schrieb:> LDR r0, = TIM2_DIER> MOV r1, #1> STR r1, [r0]
Das hattest du vorher auch schon mit drin und jetzt eben zweimal. Ich
glaube nicht das es daran lag.
Ich konnte auch nichts großartiges unterschiedliches feststellen außer
das nach dem loop in der main ein "ENDP" hinzugefügt wurde. Und
natürlich das in der IRQ das Flag am Anfang zurückgesetzt wird.
1
2
;Timer 2 bei NVIC
3
LDR r0, = NVIC_ISPR0;
4
LDR r1, = 0x10000000
5
STR r1, [r0]
Diese Stelle ist immer noch unnötig im Code, damit löst du einen
Interrupt selbst aus bzw. diese Codestelle hat dort nichts zu suchen.