Was sind
Fallen?
Die
Taktraten-Falle
Die
Interrupt-Falle
Die
Interrupt-Falle 2
Die
Analog-Falle
(Port A)
Die
Arbeitsspeicher-Falle
Die
Daten-Tabellen-Falle
Die
I2C-Falle
Die
in/out-Falle
Die
RA4-Falle
Die
I/O-Speed-Falle
Die
INTOSC&MCLR-Falle
Die
LVP_ON-Falle
Die Quarz-Falle
Die "ICD/ICSP Port Enable bit"-Falle
Der Timer
lässt
sich übrigens auch nur mit maximal 1/4 des externen
Prozessortaktes
füttern,
wenn man den internen Takt verwendet.
Bei den
PIC24/dsPIC30/dsPIC33 ist ein Zyklus nur 2 Takte lang.
Für einen 16F84
sollte die Interruptbehandlungsroutine stets mit dem folgenden Code
beginnen:
movwf w_temp
swapf STATUS,w
movwf
status_temp
und wie folgt enden:
swapf
status_temp,w
movwf STATUS
swapf w_temp,f
swapf w_temp,w
retfie
Für einen 16F62x
oder einen 12F6xx sollte die Interruptbehandlungsroutine stets
mit
dem folgenden Code beginnen:
movwf w_temp
swapf STATUS,w
bcf
STATUS, RP0 ; status_temp in Bank 0
movwf
status_temp
und wie folgt enden:
swapf
status_temp,w
movwf STATUS
swapf w_temp,f
swapf w_temp,w
retfie
Für einen 16F87x
sollte die Interruptbehandlungsroutine stets mit dem folgenden Code
beginnen:
MOVWF W_TEMP
SWAPF STATUS,W
CLRF
STATUS
MOVWF
STATUS_TEMP
MOVF
PCLATH, W
MOVWF
PCLATH_TEMP
CLRF
PCLATH
und wie folgt enden:
MOVF
PCLATH_TEMP, W
MOVWF PCLATH
SWAPF
STATUS_TEMP,W
MOVWF STATUS
SWAPF W_TEMP,F
SWAPF W_TEMP,W
retfie
Das
Register
'status_temp' muss in der Bank 0 definiert
sein.
Da
man nicht weiß, welche Registerbank gerade ausgewählt ist,
während
der Interrupt ausgelöst wird, muss das Register
'w_temp'
ein Register sein, das in jeder Bank existiert. Beim 16F84 und beim
12F6xx
ist das immer garantiert, beim 16F62x dürfen für
'w_temp'
Register mit einer Adresse unter 0x70 nur verwendet werden, wenn im
Programm
niemals in die Bänke 2 oder 3 geschaltet wird.
Beispiel:
wird
'w_temp' für die Adresse 0x20 definiert (w_temp equ 0x20), und
befindet
sich der Prozessor im Augenblick des Interrupts in der Bank 3, dann
versucht
die erste Zeile der Interruptserviceroutine, den Inhalt von w in 0x20
abzuspeichern
(movwf w_temp). Da der Prozessor in der Bank 3 ist, wird anstelle der
Adresse
0x20 aber die Adresse 0x1A0 verwendet. Der 16F62x besitzt auf dieser
Adresse
aber keine Register. Der Wert von w verschwindet im Nirwana, und der
Absturz
ist vorprogrammiert.
Natürlich
muss
sich
die
Interruptroutine
(nach dem Retten von W und STATUS)
sich selbst die Bank einstellen, die sie benötigt. Am Ende der
Interruptroutine
wird mit dem Rück-Retten von STATUS die alte Bank-Einstellung
automatisch
wieder hergestellt.
; 16F876: alle ADC-Eingänge auf
digital
I/O umschalten
BSF STATUS,
RP0
; auf Bank 1 umschalten
MOVLW
0x06
; PCFG3..0 = '0110'
MOVWF ADCON1
BCF STATUS,
RP0
; auf Bank 0 zurückschalten
Die Analog-Falle betrifft übrigens auch den PIC16F628. Hier liegen die Komparatoreingänge nach Reset an den Pins, die eigentlich dem Port A zugedacht sind.
; 16F628 alle Comparatoreingänge auf
Digital umschalten
; alles in der Bank 0
BSF CMCON, CM0
BSF CMCON, CM1
BSF CMCON, CM2
Auch die kleinen 12F6xx sind nach dem Reset auf analog eingestellt. Beim 12F629 liegt der Komparator an den Pins GP0 und GP1. Beim 12F675 ist zusätzlich noch der ADC an die Ports GP0, GP1, GP2 und GP4 zugeschaltet.
; 12F629 alle Comparatoreingänge auf
Digital umschalten
; alles in der Bank 0
BSF CMCON, CM0
BSF CMCON, CM1
BSF CMCON, CM2
; 12F675 alle
Comparator-/ADC-Eingänge
auf Digital umschalten
; beginnt in der Bank 0
BSF CMCON,
CM0
; GP0,1 von Comparator auf digital
BSF CMCON, CM1
BSF CMCON, CM2
BSF STATUS,
RP0
; Bank 1
CLRF
ANSEL
;
GP0,1,2,4
von
ADC
auf digital
BCF STATUS,
RP0
; Bank0
Um z.B. die Ziffer 4 darzustellen, wird "w" mit dem Wert "4" geladen, und dann die Zeile "addwf PCL,f" mit einem Call-Befehl "call Segmente" angesprungen. Der addwf-Befehl erhöht den Programmcounter zusätzlich um den Wert 4 und bewirkt dadurch einen Sprung in die Zeile "retlw B'11010100' ; 4". Hier wird der Wert B'11010100' nach "w" geladen und ein Return ausgelöst, der zur rufenden Routine zurückführt.
;
7-Segment-Tabelle
Segmente
addwf PCL,
f
retlw
B'00011000'
; 0
retlw
B'11011110'
; 1
retlw
B'00110010'
; 2
retlw
B'01010010'
; 3
retlw
B'11010100'
; 4
retlw
B'01010001'
; 5
retlw
B'00010001'
; 6
retlw
B'11011010'
; 7
retlw
B'00010000'
; 8
retlw
B'01010000'
; 9
Das ist trickreich und funktioniert meistens.
Bei der Verwendung solcher Tabellen muss man aber darauf achten, dass der addwf-Befehl nur 8-bittig arbeitet, der Programmcounter aber viel länger ist. Innerhalb der Tabelle, darf keine Befehlsadresse der Form xxxFF auftauchen. Der addwf-Befehl kann solche Adressen nicht überspringen, sondern springt stattdessen zu einer Adresse 256 Byte vor dem gewünschten Ziel. Die Adressen muss man deshalb im *.lst-file prüfen und evtl. durch "org"-Befehle und Sprünge manipulieren.
Sobald man die Sprungberechnungs-Routine bei einer Adresse höher 0x00FF hat, darf nicht vergessen werden, PCLATCH anzupassen.
Da die beiden Pins des PIC, die das I2C-Port darstellen, keine internen Hochziehwiderstände besitzen, muss man diese extern anschließen. Ansonsten interpretiert der PIC den externen Low-Pegel als aktiven I2C-Bus, den gerade ein anderer I2C-Master benutzt. Solange jemand anders auf dem Bus zu senden scheint, übernimmt der PIC aber nicht die Kontrolle über den Bus.
Die externen Hochziehwiderstände präsentieren dem PIC einen sauberen High-Pegel, und der I2C-Master übernimmt dann gern die Buskontrolle
Beim anfänglichen Lesen des Ports liest der Prozessor nicht etwa das Portregister (z.B. PORTA oder PORTB) aus, sondern liest die momentanen Pegel an den Pins des Ports ein. Bei Pins, die auf Output eingestellt sind macht das keinen Unterschied, wohl aber für Port-Pins, die auf Input eingestellt sind. Deren Bits im Port-Register können dadurch unbeabsichtigter Weise verändert werden. Nach dem Umschalten auf Output, haben diese Pins dann einen anderen Pegel als beabsichtigt.
Beispiel:
An einem PIC sei RB0
über
einen 10kOhm Widerstand mit Vss (Masse) verbunden, während RB1 mit
einem weiteren 10kOhm-Widerstand an Vdd (+5V) liegt.
An allen Pins wird low
ausgegeben:
; 16Fxx RB0..7 auf Output einstellen
BSF STATUS,
RP0
; auf Bank 1 umschalten
CLRF
TRISB
; PORTB auf Output
BCF STATUS,
RP0
; auf Bank 0 zurückschalten
CLRF
PORTB
; low an allen PortB-Pins
Nun liegt an allen Pins
low
Pegel.
Nun werden RB0 und RB1 auf
Input geschaltet, und anschließend RB2 auf high gesetzt.
; 16Fxx RB0 und RB1 auf Input einstellen
BSF STATUS,
RP0
; auf Bank 1 umschalten
BSF TRISB,
0
; RB0 auf input
BSF TRISB,
1
; RB1 auf input
BCF STATUS,
RP0
; auf Bank 0 zurückschalten
; RB2 auf high setzen
BSF PORTB,
2
; RB2 auf high setzen
Nachdem RB0 und RB1 auf
input
umgeschaltet wurden, nehmen ihre Pins die Pegel an, die ihnen von
außen
durch die Widerstände als Eingangssignale aufgezwungen werden. Am
Pin RB0 liegt nun low aber am Pin RB1 liegt high. Die zugehörigen
Bits im PORTB-Register sind aber nach wie vor beide 0.
Beim Setzen von RB2 auf
high liest der PIC zunächst alle Port-Pins ein. Dabei liest er am
Pin von RB1 nun den high-Pegel des externen Widerstandes. Der PIC liest
also:
00000010
Anschließend setzt
er das Bit 2
00000110
und schreibt das Ergebnis
in das Register PORTB. Dadurch verändert sich nun nicht nur das
Bit
2 sonden auch das Bit 1 von PORTB von low auf high. Da RB1 aber auf
input
steht, hat das noch keine Konsequenzen.
Nun schalten wir RB0 und
RB1 wieder auf output.
; 16Fxx RB0..7 auf Output einstellen
BSF STATUS,
RP0
; auf Bank 1 umschalten
CLRF
TRISB
; PORTB auf Output
BCF STATUS,
RP0
; auf Bank 0 zurückschalten
Nun wird der
veränderte
Wert von RB1 ausgegeben. Während RB0 wieder low ausgibt, und RB2
(wie
gewollt) high ausgibt, liegt nun plötzlich auch RB1 auf high,
obwohl
das im Programm gar nicht explizit so programmiert wurde.
Bei der Entwicklung der
PIC18Fxxxx-Typen
hat man dem Problem Rechnung getragen. Wenn man das Port auslesen will,
so kann man das jetzt auf zwei verschiedene Weisen machen. Ein Lesen
von
PORTx (z.B. PORTA oder PORTB) liest den Pegel der Port-Pins, wie es
auch
bisher üblich war. Man kann aber nun auch von LATx (z.B. LATA oder
LATB) lesen. Ein Zugriff auf LATx ist ein Zugriff auf das Portregister,
und nicht auf die Pins. Wenn man auf LATx zugreift, dann gibt es keine
in/out-Falle. Auf PORTx sollte nur zugegriffen werden, wenn man
wirklich den Spannungspegel an input-Pins einlesen will.
Für RA4 gilt aber eine gesteigerte Version der oben beschriebenen in/out-Falle. Diese Falle ist deshalb besonders gemein, da RA4 nicht auf input umgeschaltet werden muss, um durch Manipulation anderer PORTA-Pins versehentlich auf low umgeschaltet zu werden.
Beispiel:
RA4 und RA3 werden
für
die Emulation eines I2C-Busses benutzt. Dabei treibt RA3 die
Taktleitung,
und RA4 die Datenleitung. Die Datenleitung kann auch von anderen Chips
(I2C-Slaves) mit open-drain-Treibern nach low gezogen werden.
Momentan ist RA4 auf
high gesetzt, um anderen Geräten die Komunikation über
die
Datenleitung zu ermöglichen. Ein anderes Gerät zieht nun
die
Datenbleitung (und damit Pin RA4) auf low. Ein beliebiger
schreibender
Zugriff auf PORTA (z.B. das Setzen von RA3 mit 'bsf PORTA, 3' )
löst
nun folgendes aus: Der PIC liest alle Port-Pins ein. Dabei liest er
am Pin RA4 low, weil ein anderes Gerät die Datenleitung auf
low
zieht. Danach verändert der PIC das eingelesenen Byte entsprechend
dem Befehl (z.B. wird das Bit 3 gesetzt bei 'bsf PORTA, 3' ). Das
veränderte
Byte wird nun nach PORTA geschrieben. In diesem Moment wird RA4 mit '0'
beschrieben, und RA4 beginnt die Datenleitung aktiv nach low zu
ziehen.
Damit unterbindet RA4 natürlich jegliche Komunikation auf dieser
Leitung.
Man beachte, dass in diesem Fall keine Manipulation von TRISA nötig ist, um in diese Falle zu tappen
Beispiel:
Ein PIC wird mit 20 MHz
getaktet. Port B ist auf output eingestellt. In PORTB steht
zu Beginn der Wert 0xFF:
CLRF
PORTB
; Alle Pins von Port B auf low setzen
MOVFW
PORTB
; Port B einlesen
Welcher Wert steht nun in w ??
Man sollte annehmen, dass 00000000 eingelesen wurde. Das wird auch oft der Fall sein, garantieren kann man es aber nicht.
Bei 20 MHz ist ein Befehlszyklus 0,2 µs lang. Zwischen dem Schreiben auf Port B und dem Auslesen desselben liegen höchstens 0,05 µs = 50 ns. Es hängt von der Kapazität der an die Pins angeschlossenen Schaltung ab, ob die Pintreiber in der Lage sind, innerhalb dieser kurzen Zeit die Ausgänge wirklich auf low zu ziehen. Bei einer kapazitiven Last von 1nF sind dazu immerhin schon 80 mA nötig.
Sicherer ist es,
zwischen
die beiden Befehle ein 'NOP' einzufügen.
Wenn ein Brenner auf einen PIC zugreifen will, dann schaltet er normalerweise seine 5V-Betriebsspannung ein, legt MCLR auf Vss (um einen Reset auszulösen) und legt dann MCLR schlagartig auf Vpp (+12V), so dass der PIC nach dem Reset nicht dazu kommt, auch nur einen einzigen Befehl abzuarbeiten. Damit ist der PIC im Programmiermodus, und durch den Reset steht der interne Programcounter des PIC auf der Adresse 0x00. Dort kann man dann anfangen zu brennen.
Wenn bei einem PIC aber beim vorigen Brennen der interne Oszillator aktiviert wurde und MCLR zu einem I/O-Pin gemacht wurde, dann ignoriert der PIC das Vss-Signal an MCLR und beginnt mit der Abarbeitung seines Programms, sobald die Betriebsspannung Vdd angelegt wird.. Beim dann folgenden Anlegen von Vpp an MCLR stoppt der PIC seine Arbeit, aber der Programcounter steht nicht mehr auf 0x00, und das anschließende Brennen eines neuen Programms erfolgt auf zufälligen Adressen des Programmspeichers.
Solche PICs lassen sich
nur
problemlos brennen, wenn zuerst Vpp (12V) und danach Vdd (5V)
angeschaltet
wird. Das ist nicht mit allen Brennern möglich. Die Brenner
1, 2, 3 und 5 können das mit der Software PBrenner ab V3.4.
(Ältere Versionen nur mit einigen Typen)
PIC18Fxxx/xxxx
Das gleiche Problem gilt im Prinzip auch für die PIC18F-Typen mit internem Oszillatorblock (nanoWatt). Auch hier lässt sich MCLR per Config zum Input-Port RE3 machen und gleichzeitig INTOSC oder INTOSCIO als Taktquelle einstellen. In den Datenblättern fand ich keine Lösung, insbesondere wird die Einschaltreihenfolge VDD->Vpp explizit verlangt.
Der Hersteller warnt davor, LVP (low voltage programming) zu erlauben, wenn MCLR zum I/O-Pin gemacht wird. Dann wäre es beim nächsten Programmieren nämlich nötig, das PGM-Pin (RB5 o.ä.) auf low zu legen, um den Programmiermode zu erreichen. Die meisten Programmiergeräte tun das aber nicht, da ihre Entwickler (auch ich) LVP für nicht notwendig erachten.
Falls das nicht beachtet wird, und PGM auf High wandert (das passiert sehr leicht, wenn das Pin frei gelassen wird), dann schaltet der PIC in den Programmiermodus. Die Warscheinlichkeit, das Programm im Flash-Speicher zu beschädigen ist verschwindend gering, aber der PIC unterbricht die Programmabarbeitung. Der PIC bleibt "stehen" oder er arbeitet nur "stotternd".
Die PIC-Typen, die LVP beherrschen, werden vom Hersteller oft mit aktiviertem LVP_ON ausgeliefert. Wer kein LVP benötigt sollte es beim ersten Brennen des PIC deaktivieren.