Hallo liebe Forumteilnehmer. Ich beschäftige mich erst seit kurzem mit PICs und Assemblerprogrammierung. Um den Umgang mit Timern und Interupts zu lernen, wollte ich mit diesen gerne eine Einschaltverzögerung programmieren. Ca. 5 Sekunden nach dem Anlegen der Betriebsspannung, sollen mehrere LEDs angehen. Erstmal ein paar Worte zur Hardware. Ich benutze die Testplatine von „sprut“ mit dem PIC 16f877. Getaktet ist der mit 8MHz. Am Port D hängt eine 7- Segment- Platine mit 4 Anzeigen (meine LEDs). Am Port B sind die Steuerleitungen zur Auswahl der Anzeige angeschlossen. Es sollen alle LEDs von Anzeige 1 zum leuchten gebracht werden. Nun meine Grundsatzüberlegungen zur Programmgestaltung. Ich Benutze den Timer 0. Seine Impulse erhält er vom internen PIC- Takt. Mein PIC ist mit 8MHz getaktet. Über den bei Timer 0 obligatorischen Vorteiler, wird dieser auf ein ¼ des PIC- Taktes reduziert. Also auf 2MHz. Dann benutze ich den Vorteiler von Timer 0, mit einem eingestellten Teilverhältnis von 256:1. Dadurch kommt man noch auf 7812,5 Hz. Das bedeutet der Timer bekommt aller 128 µs einen Impuls. Bis zum Überlauf, und somit zum Überlaufinterupt, benötigt der Timer 256 Impulse. 256 x 128 µs = ~0,033 s. Um auf meine gewünschten 5s zu kommen, braucht es also ~152 Timerdurchläufe (5s / 0,033s = 151,5). Um dieses Problem zu lösen, war meine Idee eine reservierte Speicherzelle („loop“) mit 152 zu beschreiben, und in jedem Timer 0 Interupt zu decrementieren. Ich prüfe dann ob „loop“ = 0 ist. Wenn nicht dann wird ein neuer Timerdurchlauf gestartet, wenn ja dann werden die LEDs eingeschaltet. Daraufhin habe ich das im Anhang zu findende Programm geschrieben. Leider Funktioniert es aber nicht. Beim ersten Einschalten nach der Programmierung tut sich gar nichts. Beim zweiten Einschalten leuchten dann alle LEDs sofort. Kann mir vielleicht jemand weiterhelfen, wo der Fehler liegt? L.G. Micha
Hi, ich hab grad nicht mehr den Nerv, deinen ganzen Code zu durchforsten. Aber beim Thema ISR hast du was falsch verstanden. Das GIE ist beim Eintritt in die ISR disabled, und wird mit dem RETFIE wieder enabled. Außerdem darf vor dem Sichern des Kontexts und nach dessen Wiederherstellen kein kein anderer Befehl ausgeführt werden, der den Kontext verändert. Das erst mal für den Moment. Ach, und wenn du wieder mal Code postest, dann nicht als xxxx.txt sondern als xxx.asm bitte. Dann bietet das Forum eine Code-Ansicht, das liest sich besser. Grüße.
Hallo Micha. Danke für Deine Tipps. Ich habe Deine Anregung bezüglich der ISR gleich mal umgesetzt. Jetzt leuchten die LEDs nicht erst beim zweiten Einschalten sofort, sondern gleich beim ersten Einschalten nach der Programmierung. Den geänderten Code habe ich angehängt. Diesmal als xxx.asm. L.G. Micha
Hallo nochmal. Du hast den Code etwas verbastelt ... Hier mal die "Grundkonstruktion" der ISR
1 | isr
|
2 | ; Kontext sichern |
3 | movwf w_temp ; save off current W register contents |
4 | movf STATUS,w ; move status register into W register |
5 | movwf status_temp ; save off contents of STATUS register |
6 | |
7 | ; hier der eigentliche ISR-Code |
8 | |
9 | |
10 | ; Kontext wiederherstellen |
11 | movf status_temp,w ; retrieve copy of STATUS register |
12 | movwf STATUS ; restore pre-isr STATUS register contents |
13 | swapf w_temp,f |
14 | swapf w_temp,w ; restore pre-isr W register contents |
15 | |
16 | retfie
|
Das hier beschriebene Sichern und Wiederherstellen des Kontexts ist das absolute Minimum, bitte lese mal unter "Context Saving During Interrupts" im Datenblatt.
Hallo Michael R. Ich kommentiere mal Deinen Text zu den wichtigsten Auffälligkeiten welche ich beim Durchlesen so gesehen habe: Struktur des Codes: Halte Dich am Besten immer an ein einheitliches Schema im Programmaufbau. Sprut macht das sehr schön, aber auch in den mit MPLAB gelieferten Beispielfiles wird das deutlich gezeigt. Siehe dazu "MPLAB\MPASM Suite\Template\Code\16F877TEMP.ASM" ****** VARIABLE DEFINITIONS w_temp EQU 0x7E status_temp EQU 0x7F cblock 0x10 ;das ist die Adresse des Registers T1CON! loop ; solange dieses Register nicht als T1CON endc ; verwendet wird bleibt das noch ohne Auswirkungen! ; Glück gehabt! Du hast jetzt einmal die Register 7E,7F und dann mit der Variable "loop" Register 10 belegt. Sinnvoller ist es alle Variablen in einen Adressbereich, optimal am Beginn des GPR.Memory reinzupacken: CBLOCK 0x20 ;20h Beginn des General Purpose Register-Memory (GPR) w_temp status_temp loop ENDC Das dekrementieren und Auswerte des Interruptzählers "loop" - ich würde ihn im Klartext "cntOverflow" nennen - lässt sich vereinfachen: decfsz loop,f Goto Befehl1 Befehl2 vermindert bei jedem Durchlauf loop um 1. Bei Erreichen von 0 wird der folgende Befehl1 übersprungen (z.B. Flag setzen/löschen u.a. Wenn Du die ISR nicht mehr durchlaufen willst, schaltets Du einfach den aufrufenden Interrupt ab z.B. bcf INTCON,T0IE ;TMR0-Overflow-Interrupt disabled Die Interrupt-Start - und Ende-Sequenz übernimmst Du im Prinzip am Besten immer aus dem Datenblatt! Init Initialisierungen nicht in "main" sondern im Abschnitt "Init" unterbringen nachfolgend kannst Du Dir 2x banksel sparen denn alle 3 Register sind in der gleichen Bank (bank 1) banksel OPTION_REG clrf OPTIN_REG banksel TRISD clrf TRISD movlw 0x0 ; alle pins von D sollen output pins werden movwf TRISD banksel TRISB clrf TRISB movlw 0x0 ; alle pins werden als output pins geschaltet movwf TRISB Vorschlag: banksel TRISB ;wechselt zu bank 1 clrf TRISB ;bank1 clrf TRISD ;bank1 clrf OPTION_REG ;bank1 ..... In der ISR würde ich nur ein Flag setze, welches dann im Hauptprogramm "Main" ausgewertet wird ISR decfsz cntOverflow,f ;Dekrement counter bsf Flag,0 ;wenn counter=0 dann Flag,0=1 ..... Dann vereinfacht sich Main deutlich Main btfsc Flag,0 ;prüfen ob Flag,0=1 CALL LED_Einschalten ;Ja, LED einschalten ; sonstigr Code ;NEIN-dann hier weiter ; ............... GOTO Main GOTO's machen den Code unübersichtlich, Unterprogramm-CALL's sind dagegen auf den ersten Blick klar zu verstehen (vorausgesetzt ihre Labels sind auch verständlich (LED_Einschalten -> LEDswtch0 <- ???? ; LED_Einschalten movlw b'00001000' ;Wert zum setzen von Pins (0/1 LED aus/ein) movwf PORTD ;über das WREG in PORTD kopieren bcf Flag,0 ;Flag wieder zurücksetzen RETURN Das wär was mir so beim Überfliegen aufgefallen ist. mfG Ottmar
Ein PIC ist nicht so dolle für Assemblerprogrammierung. Jeder 2. Befehl muss ein banksel oder retlw sein, grausam. Du solltest auf C umstellen. Beschaffe die das "Pickit3" bzw. "pickit 3 debug express tutorial". Heutzutage macht man Fahrzeugführerschein auch auf PKW und nicht mehr auf Pferdekutschen mit Gäulen. Gruss
Nachtrag zu 09.01.2013 13:21 : Gute Seite ist hier http://www.norbertmoch.de/_elektronik/PIC/PIC.html Gruss
Erich schrieb: > Ein PIC ist nicht so dolle für Assemblerprogrammierung. > Jeder 2. Befehl muss ein banksel oder retlw sein, grausam. So ein einseitiger Quatsch! Hast wohl noch nie nen PIC in Assembler programmiert! mfG Ottmar
>Hast wohl noch nie nen PIC in Assembler programmiert!
Leider doch.
Aber das war früher, als ich noch jung und dumm war.
Gruss
Ottmar K. schrieb: > In der ISR würde ich nur ein Flag setze, welches dann im Hauptprogramm > "Main" ausgewertet wird Da reichts doch gleich nur das Interrupt-Flag im Hauptprogramm aus zu werten. Erich schrieb: > Aber das war früher, als ich noch jung und dumm war. Na ja, man könnte vermuten viel schlauer bist Du Heute auch nich, wen man deine Aussage liest :P Aber viel Tipperrei ist das schon und manchmal glaubt man auch zu hören wie sich die Gehirnwindungen verbiegen :) Ist allerdings meiner Meinung nach der vernünftigste weg sich das erste mal mit µC's zu beschäftigen.
Teo Derix schrieb: > Da reichts doch gleich nur das Interrupt-Flag im Hauptprogramm aus zu > werten. Nach RETFIE ist INTCON,GIE automatisch wieder gesetzt. Sollte INTCON,T0IE wieder aktiv sein - was bei fortlaufend aktivem TMR0-overflow-Interrupt ja wohl die Regel sein muss - wird sofort wieder die ISR aufgerufen! ???? Für den Rest Deines Kommentares hast Du meine volle Zustimmung!:-) mfG Ottmar
Der Interrupt ist natürlich gesperrt, es wird keine ISR aufgerufen. Das Flag per Hand zurücksetzen. Darum gehts ja, man muss keine ISR dafür einrichten wenn man darin eh nur ein Flag setzt. Ich dachte das sei klar.
Hallo alle zusammen. Ich habe mein Programm, unter Berücksichtigung der von euch angeratenen Änderungen, umgeschrieben. Es scheint jetzt auch zu funktionieren. :-) Vielen Dank an dieser Stelle für eure Hilfe. Ich schreibe „scheint zu funktionieren“, da nun ein Phänomen auftritt, auf das ich mir noch keinen rechten Reim machen kann. Beim ersten Einschalten nach der Programmierung, Funktioniert die Einschaltverzögerung wunderbar. Schalte ich aus und wieder ein, leuchten die LEDs sofort. Schalte ich allerdings nicht aus, sondern ziehe nur den Stecker vom Steckernetzteil ab, und stecke diesen dann wieder an, dann funktioniert die Einschaltverzögerung. Kann mir das vielleicht jemand erklären? Dann noch eine zweite Frage. Die Verzögerung funktioniert zwar, unter den geschilderten Umständen, jedoch beträgt die Verzögerungszeit ca. 8s, statt meiner gewünschten 5s. Das lässt sich in der Software einfach beheben. Allerdings weicht die tatsächliche Verzögerung, von der geplanten doch deutlich ab. Kann dies daran liegen, dass meine obige Rechnung, nicht die für die Befehlabarbeitung benötigte Zeit mit berücksichtigt? Ich habe mal das geänderte Programm als xxx.asm angehängt. Wer dazu noch Anregungen hat, kann diese gern äußern. Ich möchte ja dazu lernen. Außerdem habe ich den Schaltplan, von meinem Testboard, als PDF angehängt, weil ich mir vorstellen kann, dass die Ursache für das erst geschilderte Phänomen, auf der Hardwareseite liegt. L.G. Micha
Hallo Michael R. (elektr-hobbyist) Wenn Du MPLAB verwenden solltest, stehen Dir verschiedene Tools zur Verfügung. So ist im Menü "Debugger" der Eintrag "StopWatch" zu finden. Mit dieser Stoppuhr kannst Du zyklengenau die Verarbeitungszeiten messen. Dein Programm beendet z.B. nach ca.8.3s mit dem setzen von flag,0 die ISR.Routine. An Deiner Platine liegt es also nicht. Aber: Eine einmalige Einschaltverzögerung nach Power-ON-Reset (POR) würde ich nicht per Interrupt sondern mit einer simplen Delay-Routine erledigen: Main CALL Delay_5s ;mach dies CALL Delay_5s ;mach jenes Main_Loop ;warte auf irgendwas GOTO Main_Loop Delay_5s ; Generated by http://www.piclist.com/cgi-bin/delay.exe ; See also various delay routines at ; http://www.piclist.com/techref/microchip/delays.htm ; Delay = 5 seconds Clock frequency = 4 MHz movlw 0x2D movwf d1 movlw 0xE7 movwf d2 movlw 0x0B movwf d3 Delay_0 decfsz d1, f goto $+2 decfsz d2, f goto $+2 decfsz d3, f goto Delay_0 RETURN ; Wenn Du eine sich imemr wiederholende Aufgabe hast, z.B. Zeittakt für eine Uhr, oder andere per Interrupt wiederholt zu steuernde Vorgänge, ist eine ISR von Vorteil. Allerdings musst Du Dir klar sein, dass bis zum 1. Aufruf der ISR etwas mehr Zeit verstreicht als durch das eigentliche ISR-Intervall vorgegeben ist. Schließlich ist ja die Initialisierung durchzuführen, main ist zu durchlaufen usw. Allerdings kann man das auch -falls notwendig- mit entsprechenden Presets abgleichen. Als Anlage habe ich Dir mal so eine Routine beigefügt, welche Du ja nach eigenem Gutdünken verwerten/ändern kannst. Auf jeden Fall liefert sie auf die µs 5 Sekunden-ISR-Intervalle (5s =1 5s=0 usw) des Flags "FLAG_5s" mit dem 8MHz-Quarz. Den Abgleich habe ich mit MPLAB-Sim und der dort integrierten StopWatch vorgenommen. Noch was: versuche einen noch klareren Programmaufbau zu realisieren. Nimm Dir dazu für das Erste die Lernbeispiele von www.sprut.de als Vorbild. mfG Ottmar
Ottmar K. schrieb: > Du hast jetzt einmal die Register 7E,7F und dann mit der Variable > "loop" Register 10 belegt. Sinnvoller ist es alle Variablen in einen > Adressbereich, optimal am Beginn des GPR.Memory reinzupacken: > > CBLOCK 0x20 ;20h Beginn des General Purpose Register-Memory (GPR) > w_temp > status_temp > loop > ENDC Also die Rettungsregister in der ISR (w_temp, status_temp) müssen im Bereich ab 70h liegen. Die Adressen von 70h bis 7Fh werden in alle Bänke "gespiegelt". Und da du nie weist, in welcher Bank du dich bei Eintritt in die ISR befindest, muss sichergestellt sein, dass die Rettungsregister von allen Bänken aus erreichbar sind. Gruß Sven
Hallo Ottmar. Vielen Danke für deine Tipps. Die beiden Links sind Super. Meine Programme klarer zu strukturieren, werde ich beherzigen. > Dein Programm beendet z.B. nach ca.8.3s mit dem setzen von > flag,0 die ISR.Routine. An Deiner Platine liegt es also nicht. Die Sache mit der Hardware meinte ich, im Zusammenhang mit dem Phänomen, das nach dem ersten Einschalten, nach der Programmierung, alles nach Wunsch funktioniert, beim zweiten Einschalten aber sofort alle LEDs leuchten. Lasse ich das Board eingeschaltet, und ziehe stattdessen den Stromversorgungsstecker ab, und stecke ihn wieder an, funktioniert auch alles nach Wunsch. Schalte ich das Board aus, und ziehe den Stromversorgungsstecker, und stecke ihn wieder an, leuchten die LEDs wieder sofort. Schalte ich das Board nur mit dem Schalten aus, und drücke danach den "reset"- Taster, und schalte wieder ein, klappt auch alles. ??? L.G. Micha
Hallo Sven. > Also die Rettungsregister in der ISR (w_temp, status_temp) müssen im > Bereich ab 70h liegen. Die Adressen von 70h bis 7Fh werden in alle Bänke > "gespiegelt". Und da du nie weist, in welcher Bank du dich bei Eintritt > in die ISR befindest, muss sichergestellt sein, dass die > Rettungsregister von allen Bänken aus erreichbar sind. Danke für den Hinweis. L.G. Micha
stepp64 schrieb: > Also die Rettungsregister in der ISR (w_temp, status_temp) müssen im > Bereich ab 70h liegen. Dank Deinem Hinweis habe ich das Datenblatt zu diesem Thema genauer gelesen. Besten Dank, man lernt eben nie aus! mfg Ottmar
Hallo Michael R. Beim Programmieren von 16Fxxx- PICs mußt Du mehr auf das Banking achten. Schaue Dir doch mal an in welcher Bank die einzelnen Register liegen (S. 19 Datenblatt 16F877). Der ERSTE Eintritt in die ISR geschieht noch mit Bank 1, während aber das GPR in Bank 0 ist. decfsz cnt_overflow,1 ; cnt_overflow wird decrementiert, und das ;Ergebniss wieder in cnt_overflow diese Variable wird NICHT dekrementiert!! Ursache ist der unterlassene Bankwechsel von Bank 1 zurück zu Bank 0: main: ....... bsf STATUS,RP0 ; auf Bank 1 umschalten bcf OPTION_REG,T0CS ; TMR 0 wird von externem Takt umg ; ===================== BCF STATUS,RP0 ;ZURÜCK zu BANK 0 !!! ; ===================== nach dieser Änderung reduziert sich die Durchlaufzeit bis zur ISR, Programmzeite bsf flag,0 ; Flag 0 wird auf 1 gesetzt von 8,39s auf 4,98s! Ich möchte Dir ja nicht meinen Porgrammierstil aufschwatzen, der ist bei weitem nicht perfekt, aber wenn Du keine Übersichtlichkeit bewahrst, erzeugst Du Dir selber ohne Ende Probleme und Zeitverluste. Warum wechselst Du eigentlich vom internen Takt für TMR0 zu externen Takt? mfG Ottmar
Das ist so ein Trick. Der Timer0 lässt sich nicht abschalten. Man kann ihn aber auf extern legen, damit bekommt er keine Impulse mehr und bleibt stehen. Man sollte dann aber auch dafür sorgen, dass der externe Eingang nicht offen in der Luft hängt und eventuell Störimpulse auffängt. Also am besten auf ein definiertes Potential legen (über einen 10k R).
Ich hatte mir allerdings irgendwann angewöhnt über den Timer2 und dem PR2 Register einen 10ms Takt zu generieren welcher auch exakt 10ms lang ist. Und dann halt über mehrere Variablen die benötigten Intervalle (100ms 1s, 10s etc.) daraus abgeleitet. Hat sich eigentlich bewährt. Bsp. für 8MHz. Bank0 und Bank1 sind Makros. Find ich übersichtlicher.
1 | Bank0 |
2 | movlw B'01001111' ;Timer2: Nachteiler 10:1, Vorteiler 16:1, Timer ON |
3 | movwf T2CON |
4 | Bank1 |
5 | movlw .124 ;PR2=124 (0 zählt mit) 8MHz:4:10:16:125= 100Hz |
6 | movwf PR2 |
Ach ja, nun noch ein drittes mal: Ich hab mir auch angewöhnt, den RAM zu Löschen. Nach dem Einschalten hat dieser einen undefinierten Zustand, was zu Problemen führen kann. Hier meine Löschroutine:
1 | ;nun noch den RAM löschen |
2 | Bank0 |
3 | movlw 0x20 ;Bank 0 löschen |
4 | movwf FSR |
5 | RAMerase0 |
6 | clrf INDF |
7 | incf FSR,f |
8 | btfss FSR,7 ;0x80 erreicht (Bit7=1)? |
9 | goto RAMerase0 ;nö, dann noch mal |
10 | |
11 | Bank1 ;Bank 1 löschen |
12 | movlw 0xA0 |
13 | movwf FSR |
14 | RAMerase1 |
15 | clrf INDF |
16 | incf FSR,f |
17 | btfss STATUS,Z ;0x00 erreicht (STATUS,Z=1)? |
18 | goto RAMerase1 ;nö, dann noch mal |
Das nun schon etwas tricky. Ich spreche die RAM-Zellen indirekt über das INDF Register an. Ist eine schöne Sache. Könnte aber noch etwas zu schwierig sein als Anfänger.
Hallo Ottmar. Danke, ich habe die Änderung implementier. Ottmar schrieb: > Warum wechselst Du eigentlich vom internen Takt für TMR0 zu externen > Takt? stepp64 schrieb: > Das ist so ein Trick. Der Timer0 lässt sich nicht abschalten. Man kann > ihn aber auf extern legen, damit bekommt er keine Impulse mehr und > bleibt stehen. Genau das war mein Gedanke dahinter. Der Timer 1 würde sich wohl besser dafür eignen, da er eine Torstufe besitzt. Allerdings schafft dessen Vorteiler nur 8:1. Das empfand ich als unvorteilhaft. L.G. Micha
Hallo stepp64. Danke für deine Tipps. stepp64 schrieb: > Man sollte dann aber auch dafür sorgen, dass der externe > Eingang nicht offen in der Luft hängt und eventuell Störimpulse > auffängt. Also am besten auf ein definiertes Potential legen (über > einen 10k R). Ich habe ihn direkt auf Masse gelegt. L.G. Micha
Timer0 kann man nicht abschalten, das stimmt, aber den TMR0IE kann man ein sowie ausschalten, damit keine Interrupts mehr kommen. Dies sollte reichen. Im Interrupt sollte man schauen, ob tmr0ie eingeschaltet ist oder nicht bevor man einen TMR0IF checkt, also isr_tmr0 btfsc INTCON,TMR0IE btfss INTCON,TMR0IF goto isr_tmr0_done bcf INTCON,TMR0IF ; tmr0 isr code goes here movlw 250 -2 ; 250 clk (uS) - 2 clk subwf tmr0,f ; udate timer for 1ms interval div 4 incfsz tick ; every 0.25 ms goto isr_tmr0_done incfsz tick+1 ; every 64 ms goto isr_tmr0_done incf tick+1 ; every 16.384 sec isr_tmr0_done isr_tmr1 Weiters ist es auch gängig, die OSCCAL so zu calibrieren, daß 1024 = 1ms. Damit hat man dann einen einfachen 1ms/4ms/8ms/... Zeitbasis. Auch ist eine Möglichkeit ist, z.B. eine Variable zu haben als zusätzlichen Timer. Z.B. 10ms. Beispiel wenn man bei 0.25ms Timer 40 dazuzählt, hat man bei jedem Überlauf 10ms Interval.
Maik schrieb:
> cblock(0x10) geht nicht mein besster. Auf der 0x10 liegt T1CON!
Das wurde hier schon erörtert, und ich habe es entsprechen geändert.
Siehe Anhang "Timer_Test_V2" mein Bester.
Gruß.
Micha
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.