-
Notifications
You must be signed in to change notification settings - Fork 0
/
dokuMario.txt
468 lines (354 loc) · 16.8 KB
/
dokuMario.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
1. EINLEITUNG
#############
Das in diesem Dokument beschriebene Projekt zur Implementierung eines Simulators für den Mikrocontroller "PIC16F84",
dient dazu die Funktionsweise des im dritten Semester verwendeten Mikrocontrollers besser nachzuvollziehen. Mithilfe
des Simulators ist es möglich die Speicherinhalte der einzelnen Register einzusehen sowie zu manipulieren. Überdies kann
der Ablauf übersetzter Programme in Form von .LST-Dateien simulieren und mit Hilfe von Breakpoints genauer verfolgt werden.
Der Umsetzung des Simulators liegt zu großen Teilen das Datenblatt des "PIC16C84" von "Mikrochip Technology Inc." zugrunde.
2. PIC16C84
###########
2.1 BEFEHLSSATZ
###############
Der PIC16C84 arbeitet bis auf wenige Ausnahmen (z.B. Programmcounter) mit 8 Bit Registern.
Diese werden mit einem aus 35 Anweisungen bestehenden RISC-Befehlssatz beschrieben, welche
nur grundlegende Operationen durchführen können (vlg. CISC-Befehlssätze haben für komplexere Operationen
fertige Befehle). Dabei unterscheidet man zwischen bit- und byteorientierten Befehlen sowie Programmflussoperationen
und solche Befehle, die mit Konstanten arbeiten, wobei sich die letzten beiden teilweise überschneiden z.B.) RETLW.
Im Programmspeicher liegen die Befehle kodiert als 14-Bitwerte vor.
2.2 ARCHITEKTUR
###############
Alle Mikrocontroller der PIC-Familie sind nach der Harvard-Architektur konzipiert. Im Gegensatz zur Von-Neumann-Architektur
werden dabei Programm- und Datenspeicher von unterschiedlichen Bussen angesteuert, was die Geschwindigkeit des Systems um den
Faktor zwei steigern kann. Die zugrunde liegende Busstruktur wurde im Rahmen des Simulators jedoch nicht berücksichtig, da sie
für die korrekte Ausführung der Programme keine Rolle spielen.
Im Speicher findet die sogenannte Adressspiegelung Einsatz. Der Speicher ist in zwei Bänke unterteilt. Zu großen Teilen
verweisen die Einträge von Bank 0 und Bank 1 auf die selben Registerinhalte (z.B. General Purpose Register). Unter den
Special Function Registern gibt es doppelt belegte Adressen. Ein Beispiel heirfür sind die Register PORTA und TRISA.
SIMULATOR
#########
1. ALLGEMEINE DEFINITION
########################
Ein Simulator bzw. eine Simulation soll in erster Linie Vorgänge, Zusammenhänge und Objekte der realen Welt
abstrahieren und vereinfacht nachstellen. Somit lassen sich aus der Simulation Rückschlüsse auf das echte System
ziehen. Darüber hinaus lassen sich Szenarien ohne Risiken durchspielen und ihr Ablauf in der Realität besser abschätzen.
2. VOR- UND NACHTEILE DER SIMULATION
####################################
Wie bereits im vorigen Abschnitt erwähnt, können mittels einer Simulation Erfahrung über das zugehörige reale System
gesammelt werden. Das bedeutet im Klartext, dass Lernwillige sich im Falle des PIC16C84 die Hardware nicht kaufen müssen
und damit auch keine Gefahr besteht diese durch falsche Handhabung zu beschädigen.
Nachteilig an der Verwendung eines Simulationsprogrammes ist im konkreten Fall des PIC-Simulators mangels
identischer Taktung zum echten Mikrocontroller die Gegebenheit zeitkritische Anwendungen nicht sinnvoll nachbilden
zu können.
BEDIENUNG
#########
1. PROGRAMM LADEN
#################
Um ein Programm zu laden klickt man unten rechts auf den Button "Browse". Darauf öffnet sich ein Dialog,
über den man die gewünschte .LST-Datei auswählt. Nach einem Klick auf "Load LST" erscheint in der darüber liegenden
Tabelle der gesamte Inhalt des gewählten Dokuments. Dabei wird die Startadresse 0000 direkt grün markiert.
2. PROGRAMM STARTEN UND DEBUGGEN
################################
Um das geladene Programm zu starten genügt ein Klick auf "Go" (links unter dem angezeigten Quelltext). Wurde dieser
betätigt läuft das Programm ohne Unterbrechungen durch bis man auf "Stop" klickt. Während des Programmlaufes ist es
außerdem möglich, die Geschwindigkeit zu ändern. Dafür verändert man den Wert Verzögerung (rechts neben "Go").
Möchte man das Programm Schritt für Schritt ausführen lassen, so erreicht man dies durch den Button "Execute Step".
Auch das setzen von Breakpoints ist möglich. Durch Doppelklicken auf eine Codezeile färbt sich diese rot und das Programm stoppt
während eines aktiven "Go"-Befehls, wenn es an dieser Stelle angelangt.
Es muss sich hierbei um eine Zeile mit übersetztem Maschinencode handeln. Ein equ Befehl kann also nicht als Breapoint gewählt werden.
3. HÄNDISCHE SPEICHERMANIPULATION
#################################
Links von der Programmansicht sind zwei Fenster zur Visualisierung des Speicherinhaltes vorhanden. Der obere zeigt die gesamten Register
des RAMs, das andere listet die Special Function Register auf. Möchte man einen Wert im Speicher händisch Manipulieren, so kann dies über einen
Doppelklick auf den entsprechenden HEX-Eintrag der gewünschten Speicherzelle bewerkstelligt werden. Das ist allerdings nur in der allgemeinen
Speicheransicht möglich. Über das SFR-Widget können keine Werte geändert werden.
Auf der rechten Seite lassen sich zwei Kontrollbausteine zur Ansteuerung der beiden Ports RA und RB finden. Durch einfaches Anklicken des aktuellen
Wertes eines Pins wird selbiger zum toggeln gebracht.
4. WATCHDOG, STACK UND WEITERE ANZEIGEN
#######################################
Neben den bisher genannten GUI-Elementen, gibt es noch eine Checkbox mit der sich der Watchdog an-/abschalten lässt. Darüber wird die aktuelle
Laufzeit in µs angegeben. Damit lässt sich erkennen wie viel Zeit noch nötig ist, bis der Watchdog eingreift.
Außerdem kann die Frequenz mit der die Befehle abgearbeitet werden verändert werden. Abhängig davon wird die Dauer pro Befehlszyklus berechnet
und ebenfalls ausgegeben. Mittels des Clear-Buttons kann die momentane Gesamtlaufzeit des Programms auf 0 zurückgesetzt werden.
Oberhalb der Port-Visualisierung findet man den Stack. Dort werden die Rücksprungadressen von CALL-Befehlen abgelegt und angezeigt. Im Gegensatz
zum echten PIC gibt es hier keine Begrengzung der Stackgröße auf 8 Einträge.
Unterhalb des Programmlistings werden explizit die von den Prozessoroperationen betroffenen Flags im Status-Register und der gesamte Programmcounter
als HEX-Wert angezeigt.
BEFEHLSIMPLEMENTIERUNGEN
########################
x.1 MOVF
########
x.1.1 ECKDATEN
##############
Syntax: MOVF f,d
Operanden: 0 <= f <= 127
d € [0,1]
Operaton: (f) -> (d)
Status bits: Zero-Flag
Befehlscode: 00 1000 dfff ffff
Beschreibung: Der Inhalt des Registers f wird in ein Zielregister geschrieben,
das vom Wert des Parameters d abhängt. Bei d=0 wird der Inhalt in
das W-Register geschrieben, für d=1 zurück in das Quellregister.
Zyklen: 1
x.1.2 IMPLEMENTIERUNG
#####################
Zunächst wird der Parameter d aus dem Befehl extrahiert, um den Speicherort zu bestimmen.
Durch Maskieren des Befehlscodes wird die Speicheradresse ausgelesen und danach der Inhalt
an dieser Stelle geladen. Die Funktion read(int) berücksichtigt dabei den Wert des RP0-Bits
im Satus-Register. Kann der Inhalt nicht gelesen werden, da eine ungültige Adresse vorliegt, gibt
die Funktionen einen Wert zurück der mit 8 Bit nicht dargestellt werden kann. Nun wird mit der Methode
checkZeroFlag(int) geprüft, ob das von der Operation affektierte Zero-Flag gesetzt bzw. gelöscht werden soll.
Schlussendlich wird der "neue" Wert in das Zielregister zurückgeschrieben.
Um einen homogeneren Ablauf der einzelnen Operationen zu erhalten, wird hier auch die Funktion writeBack mit dem Parameter file
aufgerufen, auch wenn dieser im Falle von d=0 nicht benötigt wird.
QUELLCODE
void Prozessor::movf(int command)
{
bool storeInFileRegister = (CHECK_BIT(command,7));
// 00 1000 dfff ffff
// & 00 0000 0111 1111 = 0x7F
// 00 0000 0fff ffff
int file = command & 0x7F;
// Register laden
int currentValue = speicher.read(file);
if(currentValue== 0x0100) //die Speicheradresse ist nicht belegt!!
{
cycles++;
return;
}
// Operation
int newValue = currentValue;
// betroffene Flags prüfen und setzen/löschen
checkZeroFlag(newValue);
writeBack(file, newValue, storeInFileRegister);
cycles++;
}
x.2 BTFSC
#########
x.2.1 ECKDATEN
##############
Syntax: BTFSC f,b
Operanden: 0 <= f <= 127
0 <= b <= 7
Operaton: skip if (f<b>) = 0
Status bits: keine
Befehlscode: 01 10bb bfff ffff
Beschreibung: Wenn das entsprechende Bit im Register f '1' ist,
wird er nächste Befehl ausgeführt. Ansonsten wird der
nächste Befehl übersprungen und stattdessen ein NOP
ausgeführt.
Zyklen: 1(2)
x.2.2 IMPLEMENTIERUNG
#####################
Der Grundlegende unterschied zu dem MOVF-Befehl besteht darin, dass der PC verändert wird bzw. verändert werden kann.
Da dieser im Steuerwerk liegt, muss der Funktion eine Referenz darauf übergeben werden. Über diese kann der PC verändert werden.
Zunächst werden aus dem Befehlscode erneut durch Maskieren die Parameter extrahiert, welche in diesem Fall b und f sind.
Wie in MOVF wird darauf der Speicherinhalt von f gelesen. Nun wird mittels der Bedingung actualValue & (1<<bit) geprüft, ob
das Bit an der Stelle b gesetzt ist. Ist es gesetzt wird ein Befehlszyklus angerechnet, wenn nicht wird der PC zusätzlich inkrementiert
und zwei Befehlszyklen angerechnet. Um einen möglichen Timer-Inkrement zwischen den beiden Zyklen nicht zu übergehen wird zwischen den
beiden cycles++ Befehlen die checkTimer0()-Methode aufgerufen.
QUELLCODE
void Prozessor::btfsc(int command, Steuerwerk* steuerwerk)
{
int bit, file, actualValue;
// 01 01bb bfff ffff
// & 00 0011 1000 0000 = 0x380
// 00 00bb b000 0000
// >> 00 0000 0000 0bbb
bit = command & 0x380;
bit = bit >> 7;
// 01 01bb bfff ffff
// & 00 0000 0111 1111 = 0x7F
// 00 0000 0fff ffff
file = command & 0x7F;
actualValue = speicher.read(file);
if(actualValue== 0x0100) //die Speicheradresse ist nicht belegt!!
{
cycles++;
return;
}
if(actualValue & (1<<bit))
{
//Bit ist 1
cycles++;
}
else
{
//Bit ist 0 -> nächster Befehl wird übersprungen
steuerwerk->pc++;
/*
* Es muss nach jedem Befehlszyklus geprüft werden,
* ob der Timer gesetzt werden soll, da sonst Inkrements
* von TMR0 verpasst werden können.
*/
cycles++;
steuerwerk->checkTimer0();
cycles++;
}
}
x.3 SUBWF
#########
x.3.1 ECKDATEN
##############
Syntax: SUBWF f,d
Operanden: 0 <= f <= 127
d € [0,1]
Operaton: (f) -> (d)
Status bits: Carry-, Digit-Carry-, Zero-Flag
Befehlscode: 00 0010 dfff ffff
Beschreibung: Subtraktion per 2er-Komplement W-Register minus Register f.
Für d=0 wird das Ergebnis im W-Register gespeichert, für
d=1 im Register f.
Zyklen: 1
x.3.2 IMPLEMENTIERUNG
#####################
Der Mikrocontroller selbst arbeitet zwar mit der 2er-Komplement-Methode, da bei der Programmierung
in C++ die Subtraktion zur Verfügung steht, kann dieser Umweg ausgespart werden. Die Funktion gleicht
zu großen Teilen der Funktion MOVF. Unterschiede sind zu finden in der Operationszeile
int newValue = currentValue - workingRegisterValue;
und in der Prüfung der Flags, da hier mehrere betroffen sind. Für das Digit-Carry gibt es für Subtraktion
und Addition verschiedene Kriterien. Aus diesem Grund wird hier nicht die Funktion checkDigitCarry() sondern
checkDigitCarryFlagSubtraktion() aufgerufen.
QUELLCODE
void Prozessor::subwf(int command)
{
bool storeInFileRegister = (CHECK_BIT(command,7));
// 00 0010 dfff ffff
// & 00 0000 0111 1111 = 0x7F
// 00 0000 0fff ffff
int file = command & 0x7F;
// Register laden
int currentValue = speicher.read(file);
int workingRegisterValue = speicher.readW();
if(currentValue== 0x0100) //die Speicheradresse ist nicht belegt!!
{
cycles++;
return;
}
// Rechenoperation
int newValue = currentValue - workingRegisterValue;
// betroffene Flags prüfen und setzen/löschen
checkCarryFlag(newValue);
checkDigitCarryFlagSubtraktion(currentValue, workingRegisterValue);
checkZeroFlag(newValue);
writeBack(file, newValue, storeInFileRegister);
cycles++;
}
x.4 CALL
########
x.4.1 ECKDATEN
##############
Syntax: CALL k
Operanden: 0 <= f <= 127
Operaton: PC)+ 1 -> TOS,
k -> PC<10:0>,
(PCLATH<4:3>) -> PC<12:11>
Status bits: keine
Befehlscode: 10 0kkk kkkk kkkk
Beschreibung: Subroutine wird aufgerufen. Die Rücksprungadresse wird auf den Stack
gelegt. Der untere Teil des PC ergibt sich aus k (untere 11 Bit). Der
obere Teil stammt aus dem dritten und vierten Bit des PCLATH-Registers.
Zyklen: 2
x.4.2 IMPLEMENTIERUNG
#####################
Auch hier wird mittels Maske der Parameter k ausgelesen. Zusätzlich wird auch das PCLATH-Register geladen und später ebenfalls maskiert
und dementsprechend geshiftet, dass die beiden gewünschten Bits die 11. und 12. Stelle belegen.
Zuvor wird jedoch der aktuelle Programmcounter inkrementiert und auf den Stack gelegt. Nun wird mit der Zeile
vector<Codeline>::iterator subroutineAddress = steuerwerk->maschinencode.begin() + address + pclath - 1;
die Sprungadresse berechnet. Die "-1" ergibt sich daraus, dass der PC vor der Ausführung der Befehle inkrementiert wird. Dies stellt eine Abweichung
vom tatsächlichen Ablauf dar, ist aber für die Simulation irrellevant. Danach wird die neue Adresse in den PC zurückgeschrieben und die Befehlszyklen
respektive Timer0 aktualisiert.
QUELLCODE
void Prozessor::call(int command, Steuerwerk* steuerwerk)
{
// 10 0kkk kkkk kkkk
// & 00 0111 1111 1111 = 0x07FF
// 00 0000 kkkk kkkk
int address = (command & 0x7FF); //Der Programmcounter hat 13 BIT die 2 fehlenden oberen Bits werden aus dem aktuellen PC extrahiert
int pclath = speicher.read(0x0A);
//PCLATH ist jetz in Adresse integriert
// Push PC+1 to stack
vector<Codeline>::iterator stackAddress = steuerwerk->pc + 1;
steuerwerk->picStack.push(stackAddress);
// PCLATH maskieren
// xxxp pxxx
// & 0001 1000 = 0x18
// = 000p p000
pclath = pclath & 0x18;
// PCLATH shiften um 8 Bit nach links
pclath = pclath << 8;
vector<Codeline>::iterator subroutineAddress = steuerwerk->maschinencode.begin() + address + pclath - 1;//PC wird noch um inkrementiert
steuerwerk->pc = subroutineAddress;
/*
* Es muss nach jedem Befehlszyklus geprüft werden,
* ob der Timer gesetzt werden soll, da sonst Inkrements
* von TMR0 verpasst werden können.
*/
cycles++;
steuerwerk->checkTimer0();
cycles++;
}
rbValueChanged()
################
Wenn manuell ein Pin am PORTB geändert wird, regelt diese Methode die ausgelösten Aktionen wie z.B. den Interrupt.
Zunächst wird der aktuelle Wert der zu toggelnden Zelle ausgelesen und negiert (noch nicht zurückgeschrieben!).
Nun wird geprüft ob einer der Interrupts (RB0/INT oder RB7:RB4/INT) ausgelöst worden ist. Entsprechend werden dann die
Interrupt-Flags gesetzt. Um Flanken prüfen zu können wird jedes mal der aktuelle Wert von RB0 zwischengespeichert
lastRB0Value = newRB0Value;
Am Ende der Routine wird der neue Wert des Registers bestimmt
if(newValue == 0)
value &= ~(1 << bit);
else
value |= 1 << bit;
und zurück geschrieben, falls der entsprechende Pin auch im Tris-Register als Input definiert ist.
void MainWindow::slotRBValueChanged(int row, int column)
{
if(steuerwerk == NULL)
return;
if(row == 0)
return;
int currentValue = ui->tw_RB->item(row, column)->text().toInt();
//wert an der stelle toggeln
int newValue = (~currentValue) & 1;
ui->tw_RB->item(row, column)->setText(QString::number(newValue));
ui->tw_RB->setCurrentCell(-1,-1);
ui->tw_RB->clearSelection();
// RB0/INT - Interrupt
// Edge-Select auswählen
int option = steuerwerk->alu->speicher.readOnBank(1, 0x01);
bool intedg = CHECK_BIT(option, 6);
int value = steuerwerk->readForGUI(0, 0x06);
int bit = 7 - column;
bool newRB0Value = CHECK_BIT(value, 0);
if(bit == 0)
{
if(intedg) // positive Flanke
{
if(!lastRB0Value && newRB0Value)
setIntf();
}
else // negative Flanke
{
if(lastRB0Value && !newRB0Value)
setIntf();
}
}
lastRB0Value = newRB0Value;
// RB7:RB4 - Interrupt ausgelöst
int trisbValue = steuerwerk->readForGUI(1, 0x06);
if(bit >= 4 && bit <= 7)
{
bool isInput = CHECK_BIT(trisbValue, bit);
if(isInput)
setRbif();
}
if(newValue == 0)
value &= ~(1 << bit);
else
value |= 1 << bit;
// nur schreiben, wenn im TrisRegister der PIN als Input definiert ist
int trisBValue = steuerwerk->readForGUI(1, 0x06);
bool isInput = CHECK_BIT(trisBValue, bit);
if(isInput)
steuerwerk->writeRBFromGUI(value);
refreshStorageElements();
}