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 ;-)
>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
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.
Beitrag #5158618 wurde vom Autor gelöscht.
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 Abschnitt triggerst du den Interrupt unabhängig vom Timer, quasi per Software. Das würde ich mal rausnehmen und wie es derjaeger beschrieben hat schauen ob das Teil jemals im Handler landet. http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/Cihjjifh.html
1 | LDR r0, = NVIC_ISPR0; |
2 | LDR r1, = 0x10000000 |
3 | STR r1, [r0] |
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.
1 | B loop |
2 | |
3 | TIM2_IRQHandler |
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.
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.