chore: upload project files
This commit is contained in:
23
magnifier.sln
Normal file
23
magnifier.sln
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.14.36705.20 d17.14
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "magnifier", "magnifier\magnifier.pyproj", "{F619A247-964C-4302-AFCD-31ACD6D5E1E6}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{F619A247-964C-4302-AFCD-31ACD6D5E1E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F619A247-964C-4302-AFCD-31ACD6D5E1E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {194D84A8-E21B-4F8D-8C26-7C946E91B65B}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
184
magnifier/demos.py
Normal file
184
magnifier/demos.py
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
class Demos:
|
||||||
|
def get_tasks(self):
|
||||||
|
return {
|
||||||
|
"Demo 1: Pojedyncza LED": self.get_demo1_code,
|
||||||
|
"Demo 2: Sekwencja 8 LED": self.get_demo2_code,
|
||||||
|
"Demo 3: Switche i LEDy": self.get_demo3_code,
|
||||||
|
"Demo 4: Sumuj liczby na switchach": self.get_demo4_code
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_demo1_code(self):
|
||||||
|
return """
|
||||||
|
; Demo 1: Migająca dioda LED0
|
||||||
|
; Rekomendowana prędkość: 20
|
||||||
|
|
||||||
|
udata 0x20
|
||||||
|
CNT res 1
|
||||||
|
|
||||||
|
TIMEOUT EQU 0xFF ; maksymalnie 255 (8 bitów)
|
||||||
|
|
||||||
|
START:
|
||||||
|
; Zapal bit zerowy
|
||||||
|
BSF PORTB, 0
|
||||||
|
CALL DELAY
|
||||||
|
; Zgaś bit zerowy
|
||||||
|
BCF PORTB, 0
|
||||||
|
CALL DELAY
|
||||||
|
|
||||||
|
GOTO START
|
||||||
|
|
||||||
|
DELAY:
|
||||||
|
; Wczytaj opóźnienie
|
||||||
|
MOVLW TIMEOUT
|
||||||
|
MOVWF CNT
|
||||||
|
LOOP:
|
||||||
|
; Dekrementuj CNT,
|
||||||
|
DECFSZ CNT, F
|
||||||
|
GOTO LOOP
|
||||||
|
; tak długo jak nie jest zerem
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_demo2_code(self):
|
||||||
|
return """
|
||||||
|
; Demo 2: Sekwencja 8 LED
|
||||||
|
; Rekomendowana prędkość: 60
|
||||||
|
|
||||||
|
udata 0x20
|
||||||
|
CNT res 1
|
||||||
|
MASK res 1
|
||||||
|
|
||||||
|
TIMEOUT EQU 0xFF ; maksymalnie 255 (8 bitów)
|
||||||
|
|
||||||
|
; Wczytaj 1 jako maskę.
|
||||||
|
; Ta jedynka będzie przesuwana
|
||||||
|
; instrukcją RLF.
|
||||||
|
RESET:
|
||||||
|
MOVLW 1
|
||||||
|
MOVWF MASK
|
||||||
|
; Wyczyść także bit Carry
|
||||||
|
; specjalnego rejestru STATUS,
|
||||||
|
; aby przesunięcie wykonało
|
||||||
|
; się bez dodatkowego bitu.
|
||||||
|
BCF STATUS, C ; = BCF STATUS 0
|
||||||
|
|
||||||
|
START:
|
||||||
|
; Wczytaj maskę do akumulatora.
|
||||||
|
MOVF MASK, W
|
||||||
|
; Wypisz na PORTB
|
||||||
|
MOVWF PORTB
|
||||||
|
; (Opóźnienie)
|
||||||
|
CALL DELAY
|
||||||
|
|
||||||
|
; Przesuń maskę
|
||||||
|
RLF MASK, F
|
||||||
|
|
||||||
|
; Tak długo, jak RLF
|
||||||
|
; nie wywołało zapalenia bitu
|
||||||
|
; Carry, wykonuj START.
|
||||||
|
BTFSS STATUS, C ; BTFSS STATUS, 0
|
||||||
|
GOTO START
|
||||||
|
|
||||||
|
; W przeciwnym razie:
|
||||||
|
; zacznij program od początku.
|
||||||
|
GOTO RESET
|
||||||
|
|
||||||
|
DELAY:
|
||||||
|
; Wczytaj opóźnienie
|
||||||
|
MOVLW TIMEOUT
|
||||||
|
MOVWF CNT
|
||||||
|
LOOP:
|
||||||
|
; Dekrementuj CNT,
|
||||||
|
DECFSZ CNT, F
|
||||||
|
GOTO LOOP
|
||||||
|
; tak długo jak nie jest zerem
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_demo3_code(self):
|
||||||
|
return """
|
||||||
|
; Demo 3: Switche i LEDy
|
||||||
|
; Rekomendowana prędkość: 1
|
||||||
|
|
||||||
|
; Demonstracja mająca na celu pokazać
|
||||||
|
; jak działa odczytywanie danych ze switchy (PORT A/C)
|
||||||
|
; oraz wypisywanie wartości do LEDów (PORT B/D).
|
||||||
|
START:
|
||||||
|
; Wczytaj PORTA, PORTC (switche)
|
||||||
|
; i wyprowadź na PORTB, PORTD (diody)
|
||||||
|
MOVF PORTA, W
|
||||||
|
MOVWF PORTB
|
||||||
|
|
||||||
|
MOVF PORTC, W
|
||||||
|
MOVWF PORTD
|
||||||
|
|
||||||
|
; Opcjonalnie:
|
||||||
|
; Użyj
|
||||||
|
; @PRINTLN [@TIME] Akumulator -> @W
|
||||||
|
; lub
|
||||||
|
; @PRINTF W
|
||||||
|
; aby wydrukować zawartość akumulatora
|
||||||
|
; do standardowego wyjścia (linii poleceń).
|
||||||
|
|
||||||
|
GOTO START
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_demo4_code(self):
|
||||||
|
return """
|
||||||
|
; Demo 4: Sumuj liczby na switchach
|
||||||
|
; Rekomendowana prędkość: 1+
|
||||||
|
|
||||||
|
; Opcjonalnie:
|
||||||
|
; Zapisz wartości z PORTA, PORTC
|
||||||
|
; w zmiennych LICZBA1, LICZBA2.
|
||||||
|
udata 0x20
|
||||||
|
LICZBA1 RES 1
|
||||||
|
LICZBA2 RES 1
|
||||||
|
|
||||||
|
|
||||||
|
START:
|
||||||
|
|
||||||
|
; Wczytaj pierwszą liczbę
|
||||||
|
; (Switche 0-7)
|
||||||
|
MOVF PORTA, W
|
||||||
|
MOVWF LICZBA1
|
||||||
|
@PRINTLN (@ @PC) LICZBA1 = @W
|
||||||
|
|
||||||
|
; Wczytaj drugą liczbę
|
||||||
|
; (Switche 8-15)
|
||||||
|
MOVF PORTC, W
|
||||||
|
MOVWF LICZBA2
|
||||||
|
@PRINTLN (@ @PC) LICZBA2 = @W
|
||||||
|
|
||||||
|
; Wyczyść akumulator
|
||||||
|
CLRW
|
||||||
|
|
||||||
|
; Dodaj liczby do siebie
|
||||||
|
ADDWF LICZBA1, W
|
||||||
|
ADDWF LICZBA2, W
|
||||||
|
|
||||||
|
; Sprawdź Carry
|
||||||
|
CLRF PORTD
|
||||||
|
BTFSC STATUS, C
|
||||||
|
CALL USTAW_CARRY
|
||||||
|
|
||||||
|
; Wypisz wynik na PORTB (LEDy)
|
||||||
|
MOVWF PORTB
|
||||||
|
; Opcjonalnie też na STDOUT
|
||||||
|
@PRINTLN (@@PC) LICZBA1 + LICZBA2 = @W
|
||||||
|
@PRINTLN
|
||||||
|
|
||||||
|
GOTO START
|
||||||
|
|
||||||
|
USTAW_CARRY:
|
||||||
|
; Jeśli Carry jest zapalone,
|
||||||
|
; nastąpiło przepełnienie,
|
||||||
|
; zatem zapalamy 8. LED (PORTD<0>)
|
||||||
|
@PRINTLN Wykryto przepełnienie, zapalam Carry!
|
||||||
|
BSF PORTD, 0
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
"""
|
||||||
682
magnifier/gui.py
Normal file
682
magnifier/gui.py
Normal file
@@ -0,0 +1,682 @@
|
|||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk, scrolledtext, messagebox
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Im większa maksymalna prędkość,
|
||||||
|
# tym bardziej niestabilna symulacja.
|
||||||
|
MAX_SIMULATION_SPEED = 5_000
|
||||||
|
|
||||||
|
# Import własnych modułów:
|
||||||
|
# pic_core - zawiera logikę procesora (rejestry, wykonywanie instrukcji)
|
||||||
|
# tasks - biblioteka gotowych kodów (zadań) do wczytania
|
||||||
|
from pic_core import PIC16F87XA
|
||||||
|
from tasks import TaskLibrary
|
||||||
|
|
||||||
|
class PICSimulatorGUI:
|
||||||
|
"""
|
||||||
|
Inicjalizacja głównego okna i stanu symulatora.
|
||||||
|
"""
|
||||||
|
def __init__(self, root):
|
||||||
|
self.root = root
|
||||||
|
self.root.title("magnifier-debug (PIC16F87XA)")
|
||||||
|
self.root.geometry("1600x950")
|
||||||
|
|
||||||
|
self.pic = PIC16F87XA()
|
||||||
|
self.task_lib = TaskLibrary()
|
||||||
|
self.tasks = self.task_lib.tasks
|
||||||
|
self.switches = [0] * 16 # Tablica przechowująca stan fizycznych przełączników (0 lub 1)
|
||||||
|
|
||||||
|
# Zmienne dla debuggera
|
||||||
|
self.breakpoints = set() # Przechowuje numery linii z breakpointami (1-based)
|
||||||
|
self.pc_to_line = {} # Mapowanie PC -> numer linii w edytorze
|
||||||
|
|
||||||
|
self.btns = {} # Przechowalnia przycisków kontrolnych (Start, Stop itp.)
|
||||||
|
|
||||||
|
self.create_widgets()
|
||||||
|
|
||||||
|
"""
|
||||||
|
Tworzy i układa wszystkie elementy GUI (przyciski, ekrany, pamięć).
|
||||||
|
"""
|
||||||
|
def create_widgets(self):
|
||||||
|
hw_frame = ttk.LabelFrame(self.root, text="Hardware", padding="5")
|
||||||
|
hw_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5)
|
||||||
|
|
||||||
|
# LEDy
|
||||||
|
led_f = ttk.Frame(hw_frame); led_f.pack(pady=5)
|
||||||
|
ttk.Label(led_f, text="LEDy (LD15-LD0)", font=("Arial",10,"bold")).pack()
|
||||||
|
c_f = ttk.Frame(led_f); c_f.pack()
|
||||||
|
self.led_canvas = []
|
||||||
|
for i in range(16):
|
||||||
|
f=ttk.Frame(c_f); f.pack(side=tk.LEFT, padx=2)
|
||||||
|
c=tk.Canvas(f,width=30,height=30,bg="white",highlightthickness=1); c.pack()
|
||||||
|
o=c.create_oval(5,5,25,25,fill="gray",outline="black")
|
||||||
|
self.led_canvas.append((c,o))
|
||||||
|
ttk.Label(f,text=f"{15-i}",font=("Arial",7)).pack()
|
||||||
|
|
||||||
|
# Switche
|
||||||
|
sw_f = ttk.Frame(hw_frame); sw_f.pack(pady=5)
|
||||||
|
ttk.Label(sw_f, text="Switche (SW15-SW0)", font=("Arial",10,"bold")).pack()
|
||||||
|
sw_b_f = ttk.Frame(sw_f); sw_b_f.pack()
|
||||||
|
self.switch_buttons = []
|
||||||
|
for i in range(16):
|
||||||
|
b = tk.Button(sw_b_f, text=f"SW{15-i}\nOFF", width=5, bg="lightgray", command=lambda x=i: self.toggle_switch(x))
|
||||||
|
b.pack(side=tk.LEFT, padx=2)
|
||||||
|
self.switch_buttons.append(b)
|
||||||
|
|
||||||
|
# Panel główny
|
||||||
|
pane = ttk.PanedWindow(self.root, orient=tk.HORIZONTAL)
|
||||||
|
pane.pack(fill=tk.BOTH, expand=True, padx=5)
|
||||||
|
|
||||||
|
# Panel kodu (linie + tekst)
|
||||||
|
prog_f = ttk.LabelFrame(pane, text="Program (ASM)", padding="0"); pane.add(prog_f, weight=2)
|
||||||
|
|
||||||
|
# Kontener dla edytora
|
||||||
|
editor_frame = ttk.Frame(prog_f)
|
||||||
|
editor_frame.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# Scrollbar
|
||||||
|
self.text_scroll = ttk.Scrollbar(editor_frame)
|
||||||
|
self.text_scroll.pack(side=tk.RIGHT, fill=tk.Y)
|
||||||
|
|
||||||
|
# Płótno (canvas) na numery linii i breakpointy
|
||||||
|
self.linenumbers = tk.Canvas(editor_frame, width=40, bg="#f0f0f0")
|
||||||
|
self.linenumbers.pack(side=tk.LEFT, fill=tk.Y)
|
||||||
|
|
||||||
|
# Główny edytor tekstu
|
||||||
|
self.program_text = tk.Text(editor_frame, font=("Courier", 10), undo=True, yscrollcommand=self.text_scroll.set)
|
||||||
|
self.program_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||||
|
self.text_scroll.config(command=self.program_text.yview)
|
||||||
|
|
||||||
|
# Konfiguracja tagów dla debuggera
|
||||||
|
self.program_text.tag_config("current_line", background="#ffee33") # Żółte tło
|
||||||
|
self.program_text.tag_config("error_line", background="#ffcccc")
|
||||||
|
|
||||||
|
# Wydarzenia (eventy) dla edytora
|
||||||
|
self.program_text.bind("<<Change>>", self._on_content_changed)
|
||||||
|
self.program_text.bind("<KeyRelease>", self._on_content_changed)
|
||||||
|
self.program_text.bind("<MouseWheel>", self._on_scroll)
|
||||||
|
self.program_text.bind("<Button-4>", self._on_scroll)
|
||||||
|
self.program_text.bind("<Button-5>", self._on_scroll)
|
||||||
|
|
||||||
|
# Kliknięcie w pasek numerów (obsługa breakpointów)
|
||||||
|
self.linenumbers.bind("<Button-1>", self.toggle_breakpoint)
|
||||||
|
|
||||||
|
# --- ROZBUDOWANY PANEL PAMIĘCI ---
|
||||||
|
mem_container = ttk.Frame(pane); pane.add(mem_container, weight=2)
|
||||||
|
|
||||||
|
# Rejestry statusowe
|
||||||
|
status_f = ttk.LabelFrame(mem_container, text="Rejestry kluczowe", padding="5")
|
||||||
|
status_f.pack(fill=tk.X, padx=2, pady=2)
|
||||||
|
|
||||||
|
self.status_vars = {}
|
||||||
|
status_grid = ttk.Frame(status_f)
|
||||||
|
status_grid.pack(fill=tk.X)
|
||||||
|
|
||||||
|
regs = [
|
||||||
|
("W", "Akumulator"),
|
||||||
|
("STATUS", "Flagi"),
|
||||||
|
("PC", "Program Counter"),
|
||||||
|
("FSR", "File Select Reg"),
|
||||||
|
("PORTA", "Port A (SW 0-7)"),
|
||||||
|
("PORTB", "Port B (LED 0-7)"),
|
||||||
|
("PORTC", "Port C (SW 8-15)"),
|
||||||
|
("PORTD", "Port D (LED 8-15)"),
|
||||||
|
("TMR1L", "Timer1 Low"),
|
||||||
|
("TMR1H", "Timer1 High")
|
||||||
|
]
|
||||||
|
|
||||||
|
# Generowanie siatki z rejestrami
|
||||||
|
for idx, (name, desc) in enumerate(regs):
|
||||||
|
row = idx // 2
|
||||||
|
col = (idx % 2) * 3
|
||||||
|
|
||||||
|
ttk.Label(status_grid, text=f"{name}:", font=("Arial", 9, "bold"), width=8).grid(row=row, column=col, sticky=tk.W, padx=2, pady=1)
|
||||||
|
v = tk.StringVar(value="00")
|
||||||
|
ttk.Label(status_grid, textvariable=v, font=("Courier", 10, "bold"), foreground="blue", width=6).grid(row=row, column=col+1, sticky=tk.W, padx=2)
|
||||||
|
ttk.Label(status_grid, text=desc, font=("Arial", 8), foreground="gray").grid(row=row, column=col+2, sticky=tk.W, padx=5)
|
||||||
|
self.status_vars[name] = v
|
||||||
|
|
||||||
|
# Notebook z zakładkami dla różnych widoków pamięci
|
||||||
|
mem_notebook = ttk.Notebook(mem_container)
|
||||||
|
mem_notebook.pack(fill=tk.BOTH, expand=True, padx=2, pady=2)
|
||||||
|
|
||||||
|
# Zakładka 1: Pełny RAM (0x00-0xFF)
|
||||||
|
ram_frame = ttk.Frame(mem_notebook)
|
||||||
|
mem_notebook.add(ram_frame, text="RAM (0x00-0xFF)")
|
||||||
|
|
||||||
|
self.ram_text = scrolledtext.ScrolledText(ram_frame, font=("Courier", 9), width=60)
|
||||||
|
self.ram_text.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# Zakładka 2: Zmienne użytkownika (0x20-0x7F)
|
||||||
|
user_frame = ttk.Frame(mem_notebook)
|
||||||
|
mem_notebook.add(user_frame, text="Zmienne (0x20-0x7F)")
|
||||||
|
|
||||||
|
self.user_text = scrolledtext.ScrolledText(user_frame, font=("Courier", 9), width=60)
|
||||||
|
self.user_text.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# Zakładka 3: Rejestry SFR (0x00-0x1F + wybrane)
|
||||||
|
sfr_frame = ttk.Frame(mem_notebook)
|
||||||
|
mem_notebook.add(sfr_frame, text="SFR (systemowe)")
|
||||||
|
|
||||||
|
self.sfr_text = scrolledtext.ScrolledText(sfr_frame, font=("Courier", 9), width=60)
|
||||||
|
self.sfr_text.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# Dół ekranu
|
||||||
|
btm = ttk.Frame(self.root); btm.pack(side=tk.BOTTOM, fill=tk.X, padx=5, pady=5)
|
||||||
|
log_f = ttk.LabelFrame(btm, text="Log"); log_f.pack(side=tk.BOTTOM, fill=tk.X)
|
||||||
|
self.output_text = scrolledtext.ScrolledText(log_f, height=4); self.output_text.pack(fill=tk.X)
|
||||||
|
|
||||||
|
# Zadania (dema)
|
||||||
|
task_f = ttk.LabelFrame(btm, text="Wybierz zadanie (kod)", padding="5")
|
||||||
|
task_f.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
self.task_var = tk.StringVar()
|
||||||
|
self.task_combo = ttk.Combobox(task_f, textvariable=self.task_var, state="readonly", width=40)
|
||||||
|
self.task_combo['values'] = list(self.tasks.keys())
|
||||||
|
self.task_combo.current(0)
|
||||||
|
self.task_combo.pack(side=tk.LEFT, padx=5, pady=5)
|
||||||
|
|
||||||
|
ttk.Button(task_f, text="Wczytaj zadanie", command=self.load_selected_task).pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
|
# Panel kontrolny
|
||||||
|
ctrl_f = ttk.LabelFrame(btm, text="Panel kontrolny"); ctrl_f.pack(side=tk.RIGHT)
|
||||||
|
self.btns["reset_btn"] = ttk.Button(ctrl_f, text= "Reset", command=self.reset_sim)
|
||||||
|
self.btns[ "load_btn"] = ttk.Button(ctrl_f, text="Wczytaj", command=self.load_program)
|
||||||
|
self.btns[ "run_btn"] = ttk.Button(ctrl_f, text="Uruchom", command=self.run)
|
||||||
|
self.btns[ "stop_btn"] = ttk.Button(ctrl_f, text= "Stop", command=self.stop)
|
||||||
|
self.btns[ "step_btn"] = ttk.Button(ctrl_f, text= "Krok", command=self.step)
|
||||||
|
|
||||||
|
for btn in self.btns:
|
||||||
|
self.btns[btn].pack(side=tk.LEFT, padx=2)
|
||||||
|
|
||||||
|
sep = ttk.Separator(ctrl_f, orient=tk.VERTICAL)
|
||||||
|
sep.pack(side=tk.LEFT, fill=tk.Y, padx=5)
|
||||||
|
|
||||||
|
# Licznik prędkości (spinbox)
|
||||||
|
ttk.Label(ctrl_f, text="Szybkość:").pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
# Zmienna przechowująca wartość
|
||||||
|
self.speed_var = tk.IntVar(value=100)
|
||||||
|
|
||||||
|
# Spinbox: pozwala wpisać liczbę lub klikać strzałkami
|
||||||
|
# from_/to określa zakres, increment o ile skacze przy kliknięciu
|
||||||
|
self.speed_spin = tk.Spinbox(ctrl_f, from_=1, to=MAX_SIMULATION_SPEED, increment=10,
|
||||||
|
textvariable=self.speed_var, width=8)
|
||||||
|
self.speed_spin.pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
# Inicjalne rysowanie linii
|
||||||
|
self._redraw_lines()
|
||||||
|
self.load_selected_task()
|
||||||
|
|
||||||
|
# --- OBSŁUGA GUI I LINE NUMBERS ---
|
||||||
|
"""
|
||||||
|
Synchronizuje przewijanie tekstu z paskiem numerów linii.
|
||||||
|
"""
|
||||||
|
def _on_scroll(self, event):
|
||||||
|
self.program_text.yview_scroll(int(-1*(event.delta/120)), "units")
|
||||||
|
self._redraw_lines()
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
"""
|
||||||
|
Odświeża numery linii, gdy użytkownik wpisuje kod.
|
||||||
|
"""
|
||||||
|
def _on_content_changed(self, event=None):
|
||||||
|
self._redraw_lines()
|
||||||
|
|
||||||
|
"""
|
||||||
|
Rysuje numery linii i kropki breakpointów na lewym pasku Canvas.
|
||||||
|
"""
|
||||||
|
def _redraw_lines(self):
|
||||||
|
self.linenumbers.delete("all")
|
||||||
|
i = self.program_text.index("@0,0")
|
||||||
|
while True:
|
||||||
|
dline = self.program_text.dlineinfo(i)
|
||||||
|
if dline is None: break
|
||||||
|
y = dline[1]
|
||||||
|
linenum = str(i).split(".")[0]
|
||||||
|
|
||||||
|
# Rysuj numer linii
|
||||||
|
self.linenumbers.create_text(25, y, anchor="ne", text=linenum, font=("Courier", 10), fill="#555")
|
||||||
|
|
||||||
|
# Rysuj breakpoint jeśli istnieje
|
||||||
|
if int(linenum) in self.breakpoints:
|
||||||
|
self.linenumbers.create_oval(5, y+2, 17, y+14, fill="red", outline="darkred")
|
||||||
|
|
||||||
|
i = self.program_text.index(f"{i}+1line")
|
||||||
|
|
||||||
|
def toggle_breakpoint(self, event):
|
||||||
|
# Pobierz linię na podstawie kliknięcia myszką Y
|
||||||
|
y = event.y
|
||||||
|
# Znajdź linię tekstu najbliższą tej współrzędnej Y
|
||||||
|
idx = self.program_text.index(f"@0,{y}")
|
||||||
|
linenum = int(idx.split(".")[0])
|
||||||
|
|
||||||
|
if linenum in self.breakpoints:
|
||||||
|
self.breakpoints.remove(linenum)
|
||||||
|
else:
|
||||||
|
self.breakpoints.add(linenum)
|
||||||
|
|
||||||
|
self._redraw_lines()
|
||||||
|
|
||||||
|
def highlight_execution_line(self):
|
||||||
|
# Usuń stare podświetlenie
|
||||||
|
self.program_text.tag_remove("current_line", "1.0", tk.END)
|
||||||
|
|
||||||
|
# Pobierz aktualny PC i znajdź odpowiadającą linię kodu źródłowego
|
||||||
|
current_pc = self.pic.PC
|
||||||
|
source_line = self.pc_to_line.get(current_pc)
|
||||||
|
|
||||||
|
if source_line:
|
||||||
|
# Podświetl linię (source_line to int, format tagu to "line.0")
|
||||||
|
self.program_text.tag_add("current_line", f"{source_line}.0", f"{source_line}.end")
|
||||||
|
self.program_text.see(f"{source_line}.0") # Scrolluj do linii
|
||||||
|
|
||||||
|
# --- LOGIKA SYMULATORA ---
|
||||||
|
"""
|
||||||
|
Obsługa kliknięcia przycisku Switch (Hardware).
|
||||||
|
"""
|
||||||
|
def toggle_switch(self, i):
|
||||||
|
|
||||||
|
# 15-i ponieważ staramy się rysować przyciski/ledy
|
||||||
|
# w układzie jak na laboratorium: 15, 14, ... 1, 0
|
||||||
|
self.switches[15-i] = 1 - self.switches[15-i]
|
||||||
|
|
||||||
|
# Aktualizacja wyglądu przycisku
|
||||||
|
self.switch_buttons[i].config(text=f"SW{15-i}\n{'ON' if self.switches[15-i] else 'OFF'}", bg="lightgreen" if self.switches[15-i] else "lightgray")
|
||||||
|
|
||||||
|
# Budowanie bajtów dla PORTA i PORTC na podstawie switchy
|
||||||
|
pa, pc = 0, 0
|
||||||
|
for x in range(8):
|
||||||
|
if self.switches[x]: pa |= (1<<x)
|
||||||
|
if self.switches[x+8]: pc |= (1<<x)
|
||||||
|
|
||||||
|
# Wymuszenie wartości wejściowych w symulatorze
|
||||||
|
# 5 = adres PORTA, 7 = adres PORTC w pamięci PIC
|
||||||
|
self.pic.input_overrides[5] = pa
|
||||||
|
self.pic.input_overrides[7] = pc
|
||||||
|
|
||||||
|
self.pic.file_registers[5] = pa
|
||||||
|
self.pic.file_registers[7] = pc
|
||||||
|
|
||||||
|
self.update_display()
|
||||||
|
|
||||||
|
def update_leds(self):
|
||||||
|
# PORTB i PORTD zawierają bity, które należy odwrócić, aby prezentować LEDy od 15. do 0.
|
||||||
|
# https://stackoverflow.com/a/12682003
|
||||||
|
pb = int('{:08b}'.format(self.pic.file_registers[8])[::-1], 2) # sic!
|
||||||
|
pd = int('{:08b}'.format(self.pic.file_registers[6])[::-1], 2)
|
||||||
|
for i in range(8):
|
||||||
|
# Zaktualizuj jednocześnie PORTB i PORTD, dwie pieczenie na jednym ogniu.
|
||||||
|
self.led_canvas[i ][0].itemconfig(self.led_canvas[i ][1], fill="red" if (pb&(1<<i)) else "gray")
|
||||||
|
self.led_canvas[i+8][0].itemconfig(self.led_canvas[i+8][1], fill="red" if (pd&(1<<i)) else "gray")
|
||||||
|
|
||||||
|
def update_display(self):
|
||||||
|
self.pic.read_status_to_cache()
|
||||||
|
|
||||||
|
reg_map = {
|
||||||
|
'W': self.pic.W,
|
||||||
|
'STATUS': self.pic.file_registers[3],
|
||||||
|
'PC': self.pic.PC,
|
||||||
|
'FSR': self.pic.file_registers[4],
|
||||||
|
'PORTA': self.pic.file_registers[5],
|
||||||
|
'PORTB': self.pic.file_registers[6],
|
||||||
|
'PORTC': self.pic.file_registers[7],
|
||||||
|
'PORTD': self.pic.file_registers[8],
|
||||||
|
'TMR1L': self.pic.file_registers[14],
|
||||||
|
'TMR1H': self.pic.file_registers[15]
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, val in reg_map.items():
|
||||||
|
self.status_vars[k].set(f"0x{val:02X}")
|
||||||
|
|
||||||
|
self.update_leds()
|
||||||
|
|
||||||
|
# --- RAM (Widok po 8 bajtów) ---
|
||||||
|
self.ram_text.delete("1.0", tk.END)
|
||||||
|
# Zmieniony nagłówek na 8 kolumn
|
||||||
|
self.ram_text.insert(tk.END, "Adres | +0 +1 +2 +3 +4 +5 +6 +7 | ASCII\n")
|
||||||
|
self.ram_text.insert(tk.END, "-------+-------------------------+---------\n")
|
||||||
|
|
||||||
|
# Krok pętli zmieniony na 8
|
||||||
|
for i in range(0, len(self.pic.file_registers), 8):
|
||||||
|
chunk = self.pic.file_registers[i:i+8]
|
||||||
|
|
||||||
|
# HEX (8 bajtów w jednej linii)
|
||||||
|
hex_vals = " ".join(f"{b:02X}" for b in chunk)
|
||||||
|
|
||||||
|
# ASCII (kropka zamiast niewydrukowalnych)
|
||||||
|
ascii_vals = "".join(chr(b) if 32 <= b < 127 else "." for b in chunk)
|
||||||
|
|
||||||
|
# Wyrównanie hexa (8 bajtów * 3 znaki - 1 spacja = 23 znaki szerokości)
|
||||||
|
hex_vals = hex_vals.ljust(23)
|
||||||
|
|
||||||
|
self.ram_text.insert(
|
||||||
|
tk.END,
|
||||||
|
f"0x{i:03X}: | {hex_vals} | {ascii_vals}\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Zmienne użytkownika (variables) ---
|
||||||
|
self.user_text.delete("1.0", tk.END)
|
||||||
|
# Nagłówek tabeli
|
||||||
|
self.user_text.insert(tk.END, "Adres | Hex | Dec | Bin | Char | Nazwa zmiennej\n")
|
||||||
|
self.user_text.insert(tk.END, "------+-----+-----+----------+------+---------------\n")
|
||||||
|
|
||||||
|
# 1. Budujemy mapę: Adres -> Nazwa zmiennej
|
||||||
|
# Pobieramy to bezpośrednio z rdzenia PIC, który przeanalizował kod (udata/res)
|
||||||
|
addr_map = {}
|
||||||
|
for name, addr in self.pic.SYMBOLS.items():
|
||||||
|
# Interesują nas tylko zmienne w obszarze RAM użytkownika (0x20 - 0x7F)
|
||||||
|
# Ignorujemy rejestry systemowe (np. STATUS, PORTA) i etykiety kodu
|
||||||
|
if 0x20 <= addr <= 0x7F:
|
||||||
|
# Jeśli pod jednym adresem jest kilka nazw, łączymy je
|
||||||
|
#
|
||||||
|
# Aktualizacja:
|
||||||
|
# To powinno być naprawione i było problemem, ponieważ
|
||||||
|
# etykiety były traktowane jako symbole. Przez to, symulator
|
||||||
|
# pokazywał etykiety w miejscu przeznaczonym dla zmiennych użytkownika.
|
||||||
|
if addr in addr_map:
|
||||||
|
addr_map[addr] += f" / {name}"
|
||||||
|
else:
|
||||||
|
addr_map[addr] = name
|
||||||
|
|
||||||
|
# 2. Iterujemy przez pamięć RAM użytkownika (0x20 do 0x7F)
|
||||||
|
for i in range(0x20, 0x80):
|
||||||
|
val = self.pic.file_registers[i]
|
||||||
|
|
||||||
|
# Sprawdzamy, czy pod tym adresem jest nazwa w naszej mapie
|
||||||
|
var_name = addr_map.get(i, "")
|
||||||
|
|
||||||
|
# Znak ASCII (kropka dla znaków niedrukowalnych)
|
||||||
|
ascii_char = chr(val) if 32 <= val < 127 else "."
|
||||||
|
|
||||||
|
# Formatowanie wiersza
|
||||||
|
# 0x20: | FF | 255 | 11111111 | . | <- CNT1
|
||||||
|
line = f"0x{i:02X}: | {val:02X} | {val:3d} | {val:08b} | {ascii_char} "
|
||||||
|
|
||||||
|
# Jeśli zmienna ma nazwę, dodajemy ją
|
||||||
|
if var_name:
|
||||||
|
line += f"| <- {var_name}"
|
||||||
|
|
||||||
|
self.user_text.insert(tk.END, line + "\n")
|
||||||
|
|
||||||
|
# --- Podświetlanie ---
|
||||||
|
# Obliczamy numer linii w polu tekstowym (2 linie nagłówka + index)
|
||||||
|
line_num = i - 0x20 + 3
|
||||||
|
|
||||||
|
if var_name:
|
||||||
|
# Jeśli to zmienna użytkownika (zadeklarowana w kodzie) -> Zielone tło
|
||||||
|
self.user_text.tag_add("defined_var", f"{line_num}.0", f"{line_num}.end")
|
||||||
|
elif val != 0:
|
||||||
|
# Jeśli to tylko śmieci w pamięci (wartość > 0, ale bez nazwy) -> Szare tło
|
||||||
|
self.user_text.tag_add("nonzero", f"{line_num}.0", f"{line_num}.end")
|
||||||
|
|
||||||
|
# Konfiguracja kolorów podświetlenia
|
||||||
|
self.user_text.tag_config("defined_var", background="#ccffcc") # Jasna zieleń dla zmiennych użytkownika
|
||||||
|
self.user_text.tag_config("nonzero", background="#f0f0f0") # Szary dla niezerowych wartości bez nazwy
|
||||||
|
|
||||||
|
# --- SFR ---
|
||||||
|
self.sfr_text.delete("1.0", tk.END)
|
||||||
|
self.sfr_text.insert(tk.END, "Rejestr | Adr | Hex | Bin | Opis\n")
|
||||||
|
self.sfr_text.insert(tk.END, "---------------+-----+-----+----------+-------------\n")
|
||||||
|
|
||||||
|
sfr_list = [
|
||||||
|
(0x00, "INDF", "Indirect addr"),
|
||||||
|
(0x01, "TMR0", "Timer0"),
|
||||||
|
(0x02, "PCL", "PC Low byte"),
|
||||||
|
(0x03, "STATUS", "Status flags"),
|
||||||
|
(0x04, "FSR", "File Select"),
|
||||||
|
(0x05, "PORTA", "Port A I/O"),
|
||||||
|
(0x06, "PORTB", "Port B I/O"),
|
||||||
|
(0x07, "PORTC", "Port C I/O"),
|
||||||
|
(0x08, "PORTD", "Port D I/O"),
|
||||||
|
# (0x09, "PORTE", "Port E I/O"),
|
||||||
|
(0x0A, "PCLATH", "PC High"),
|
||||||
|
(0x0B, "INTCON", "Interrupt Ctrl"),
|
||||||
|
(0x0C, "PIR1", "Periph Int 1"),
|
||||||
|
(0x0D, "PIR2", "Periph Int 2"),
|
||||||
|
(0x0E, "TMR1L", "Timer1 Low"),
|
||||||
|
(0x0F, "TMR1H", "Timer1 High"),
|
||||||
|
(0x10, "T1CON", "Timer1 Ctrl")
|
||||||
|
]
|
||||||
|
|
||||||
|
for addr, name, desc in sfr_list:
|
||||||
|
val = self.pic.file_registers[addr]
|
||||||
|
self.sfr_text.insert(tk.END, f"{name:14s} | {addr:02X} | {val:02X} | {val:08b} | {desc}\n")
|
||||||
|
|
||||||
|
# Podświetlenie linii w kodzie
|
||||||
|
self.highlight_execution_line()
|
||||||
|
|
||||||
|
"""
|
||||||
|
Funkcja: Pobiera kod, parsuje go i ładuje do procesora.
|
||||||
|
"""
|
||||||
|
def load_program(self):
|
||||||
|
# Czyścimy stare błędy
|
||||||
|
self.program_text.tag_remove("error_line", "1.0", tk.END)
|
||||||
|
self.output_text.insert(tk.END, "--- Kompilacja ---\n")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. Pobieranie kodu
|
||||||
|
raw_lines = self.program_text.get("1.0", tk.END).split('\n')
|
||||||
|
|
||||||
|
clean_code_for_core = []
|
||||||
|
self.pc_to_line = {}
|
||||||
|
executable_pc_counter = 0
|
||||||
|
|
||||||
|
# 2. Parsowanie wstępne dla GUI
|
||||||
|
for idx, line in enumerate(raw_lines):
|
||||||
|
line_num = idx + 1
|
||||||
|
clean_line = line.split(';')[0].strip()
|
||||||
|
|
||||||
|
if not clean_line: continue
|
||||||
|
|
||||||
|
parts = clean_line.split()
|
||||||
|
token = parts[0].upper()
|
||||||
|
|
||||||
|
# Lista ignorowanych przez licznik PC (musi być zgodna z Core)
|
||||||
|
NON_EXECUTABLE = ['UDATA', 'RES', 'EQU', 'ORG', 'CONFIG', '__CONFIG', 'END']
|
||||||
|
|
||||||
|
is_preprocessor = token.startswith('#')
|
||||||
|
is_label_only = token.endswith(':') and len(parts) == 1
|
||||||
|
is_directive = token in NON_EXECUTABLE
|
||||||
|
if len(parts) > 1 and parts[1].upper() in ['RES', 'EQU']: is_directive = True
|
||||||
|
|
||||||
|
# Dodajemy do listy dla Core
|
||||||
|
clean_code_for_core.append((clean_line, line_num))
|
||||||
|
|
||||||
|
# Mapowanie PC -> Linia
|
||||||
|
if not is_directive and not is_label_only and not is_preprocessor:
|
||||||
|
if token.endswith(':') and len(parts) > 1:
|
||||||
|
self.pc_to_line[executable_pc_counter] = line_num
|
||||||
|
executable_pc_counter += 1
|
||||||
|
elif not token.endswith(':'):
|
||||||
|
self.pc_to_line[executable_pc_counter] = line_num
|
||||||
|
executable_pc_counter += 1
|
||||||
|
|
||||||
|
# 3. Wysłanie do Core
|
||||||
|
self.pic.program_memory = clean_code_for_core
|
||||||
|
self.pic.PC = 0
|
||||||
|
|
||||||
|
# 4. "Kompilacja" w Core
|
||||||
|
self.pic.build_instruction_cache()
|
||||||
|
|
||||||
|
# Sprawdzenie flagi sukcesu
|
||||||
|
if hasattr(self.pic, 'compilation_success') and not self.pic.compilation_success:
|
||||||
|
raise ValueError("Błąd kompilacji (szczegóły nieznane).")
|
||||||
|
|
||||||
|
self.output_text.insert(tk.END, f"✓ Załadowano {executable_pc_counter} instrukcji.\n")
|
||||||
|
self.output_text.see(tk.END)
|
||||||
|
self.update_display()
|
||||||
|
self._redraw_lines()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = str(e)
|
||||||
|
self.output_text.insert(tk.END, f"🗙 Wystąpił błąd: {error_msg}\n")
|
||||||
|
self.output_text.see(tk.END)
|
||||||
|
self.pic.instruction_cache = [] # Czyścimy cache, żeby nie uruchomić złego kodu
|
||||||
|
|
||||||
|
|
||||||
|
# Szukamy wzorca "linii X" w komunikacie błędu (który dodaliśmy w Kroku 1)
|
||||||
|
match = re.search(r"linii (\d+)", error_msg)
|
||||||
|
if match:
|
||||||
|
error_line_num = int(match.group(1))
|
||||||
|
self._highlight_error(error_line_num)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Pomocnicza funkcja do zaznaczania błędnej linii
|
||||||
|
"""
|
||||||
|
def _highlight_error(self, line_num):
|
||||||
|
self.program_text.tag_add("error_line", f"{line_num}.0", f"{line_num}.end")
|
||||||
|
self.program_text.see(f"{line_num}.0")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Resetuje procesor i czyści GUI.
|
||||||
|
"""
|
||||||
|
def reset_sim(self):
|
||||||
|
self.pic.reset()
|
||||||
|
self.program_text.tag_remove("current_line", "1.0", tk.END)
|
||||||
|
self.output_text.insert(tk.END," Symulator zresetowany.\n")
|
||||||
|
self.output_text.see(tk.END)
|
||||||
|
self.update_display()
|
||||||
|
|
||||||
|
"""
|
||||||
|
Wykonuje jeden krok (jedną instrukcję assemblera).
|
||||||
|
"""
|
||||||
|
def step(self):
|
||||||
|
if self.pic.input_overrides: # Najpierw wgraj stany switchy do pamięci
|
||||||
|
for addr, val in self.pic.input_overrides.items():
|
||||||
|
self.pic.file_registers[addr] = val
|
||||||
|
|
||||||
|
if self.pic.PC < len(self.pic.instruction_cache): # Sprawdź czy PC nie wykracza poza kod
|
||||||
|
self.pic.step_fast() # Wykonaj instrukcję w rdzeniu
|
||||||
|
self.pic.PC += 1
|
||||||
|
self.update_display()
|
||||||
|
else:
|
||||||
|
self.running = False
|
||||||
|
self.output_text.insert(tk.END, "Koniec programu.\n")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Zatrzymuje ciągłe wykonywanie.
|
||||||
|
"""
|
||||||
|
def stop(self):
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
self.btns[ "run_btn"]["state"] = "enabled"
|
||||||
|
self.btns["stop_btn"]["state"] = "disabled"
|
||||||
|
|
||||||
|
self.output_text.insert(tk.END," Wykonanie zatrzymane.\n")
|
||||||
|
self.output_text.see(tk.END)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Rozpoczyna ciągłe wykonywanie programu.
|
||||||
|
"""
|
||||||
|
def run(self):
|
||||||
|
|
||||||
|
# Blokuj przyciski
|
||||||
|
if self.running:
|
||||||
|
self.output_text.insert(tk.END, " OSTRZEŻENIE: Symulacja już działa!")
|
||||||
|
|
||||||
|
# Zabezpieczenie przed uruchomieniem pustego lub błędnego kodu
|
||||||
|
if not self.pic.instruction_cache or (hasattr(self.pic, 'compilation_success') and not self.pic.compilation_success):
|
||||||
|
# Zamiast głośnego messagebox, wypisujemy ostrzeżenie do logu
|
||||||
|
self.output_text.insert(tk.END, " OSTRZEŻENIE: Najpierw załaduj poprawny program (przycisk \"Wczytaj\").\n")
|
||||||
|
self.output_text.see(tk.END)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.btns["stop_btn"]["state"] = "enabled"
|
||||||
|
self.btns[ "run_btn"]["state"] = "disabled"
|
||||||
|
|
||||||
|
self.running = True
|
||||||
|
self.output_text.insert(tk.END," Uruchomiono...\n")
|
||||||
|
self.output_text.see(tk.END)
|
||||||
|
|
||||||
|
# Zabezpieczenie pętli głównej
|
||||||
|
try:
|
||||||
|
self.run_loop()
|
||||||
|
except Exception as e:
|
||||||
|
self.running = False
|
||||||
|
# Zamiast głośnego messagebox, wypisujemy błąd do logu
|
||||||
|
self.output_text.insert(tk.END, f" BŁĄD WYKONANIA (Runtime error): {str(e)}\n")
|
||||||
|
self.output_text.insert(tk.END, " Symulacja zatrzymana awaryjnie.\n")
|
||||||
|
self.output_text.see(tk.END)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Pętla symulacji wykonywana cyklicznie przez Tkinter.
|
||||||
|
"""
|
||||||
|
def run_loop(self):
|
||||||
|
if not self.running: return
|
||||||
|
|
||||||
|
# Pobieranie z limitem 1-5000
|
||||||
|
try:
|
||||||
|
raw_val = int(self.speed_spin.get())
|
||||||
|
|
||||||
|
# 1. max(1, ...) -> jeśli liczba mniejsza niż 1, weź 1
|
||||||
|
# 2. min(5000, ...) -> jeśli liczba większa niż 5000, weź 5000
|
||||||
|
cycles_per_frame = max(1, min(5000, raw_val))
|
||||||
|
|
||||||
|
# Opcjonalnie: Jeśli użytkownik wpisał złą liczbę, popraw ją w okienku:
|
||||||
|
if raw_val != cycles_per_frame:
|
||||||
|
self.speed_var.set(cycles_per_frame)
|
||||||
|
|
||||||
|
except (ValueError, tk.TclError):
|
||||||
|
cycles_per_frame = 1
|
||||||
|
|
||||||
|
|
||||||
|
instr_len = len(self.pic.instruction_cache)
|
||||||
|
cache = self.pic.instruction_cache
|
||||||
|
|
||||||
|
# Synchronizacja wejść
|
||||||
|
if self.pic.input_overrides:
|
||||||
|
for addr, val in self.pic.input_overrides.items():
|
||||||
|
self.pic.file_registers[addr] = val
|
||||||
|
|
||||||
|
# Wykonaj X instrukcji w jednej klatce
|
||||||
|
for _ in range(cycles_per_frame):
|
||||||
|
if self.pic.PC >= instr_len:
|
||||||
|
self.running = False
|
||||||
|
break
|
||||||
|
|
||||||
|
# --- BREAKPOINT CHECK ---
|
||||||
|
src_line = self.pc_to_line.get(self.pic.PC)
|
||||||
|
if src_line in self.breakpoints:
|
||||||
|
self.running = False
|
||||||
|
self.btns[ "run_btn"]["state"] = "enabled"
|
||||||
|
self.btns["stop_btn"]["state"] = "disabled"
|
||||||
|
self.output_text.insert(tk.END, f" Breakpoint na linii {src_line} (PC: 0x{self.pic.PC:02X})\n")
|
||||||
|
self.output_text.see(tk.END)
|
||||||
|
break
|
||||||
|
# ------------------------
|
||||||
|
|
||||||
|
# Wywołaj Timer1 kilka razy na instrukcję
|
||||||
|
# for _ in range(1): # Symuluj 1 cykl zegara na instrukcję
|
||||||
|
# self.pic.tick_timer1()
|
||||||
|
|
||||||
|
# Pobranie i wykonanie instrukcji z cache
|
||||||
|
func, args = cache[self.pic.PC]
|
||||||
|
if func.__name__ == "DBG_WAIT":
|
||||||
|
# DBG_WAIT wymaga zmiany w zachowaniu,
|
||||||
|
# aby nie zawiesić całego programu dla
|
||||||
|
# prędkości (cycles_per_frame) > 1.
|
||||||
|
func(args[0] / cycles_per_frame)
|
||||||
|
else:
|
||||||
|
func(*args)
|
||||||
|
self.pic.PC += 1
|
||||||
|
|
||||||
|
self.update_display()
|
||||||
|
if self.running:
|
||||||
|
self.root.after(10, self.run_loop)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ładuje przykładowy kod z listy rozwijanej.
|
||||||
|
"""
|
||||||
|
def load_selected_task(self):
|
||||||
|
self.stop()
|
||||||
|
task_name = self.task_var.get()
|
||||||
|
if task_name in self.tasks:
|
||||||
|
code = self.tasks[task_name]()
|
||||||
|
self.program_text.delete("1.0", tk.END)
|
||||||
|
self.program_text.insert("1.0", code.strip())
|
||||||
|
self.load_program()
|
||||||
44
magnifier/magnififer.pyproj
Normal file
44
magnifier/magnififer.pyproj
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
<ProjectGuid>f619a247-964c-4302-afcd-31acd6d5e1e6</ProjectGuid>
|
||||||
|
<ProjectHome>.</ProjectHome>
|
||||||
|
<StartupFile>main.py</StartupFile>
|
||||||
|
<SearchPath>
|
||||||
|
</SearchPath>
|
||||||
|
<WorkingDirectory>.</WorkingDirectory>
|
||||||
|
<OutputPath>.</OutputPath>
|
||||||
|
<Name>magnifier</Name>
|
||||||
|
<RootNamespace>magnifier</RootNamespace>
|
||||||
|
<InterpreterId>Global|PythonCore|3.13</InterpreterId>
|
||||||
|
<IsWindowsApplication>False</IsWindowsApplication>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="gui.py" />
|
||||||
|
<Compile Include="demos.py" />
|
||||||
|
<Compile Include="main.py" />
|
||||||
|
<Compile Include="pic_core.py" />
|
||||||
|
<Compile Include="tasks.py" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<InterpreterReference Include="Global|PythonCore|3.13" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets" />
|
||||||
|
<!-- Uncomment the CoreCompile target to enable the Build command in
|
||||||
|
Visual Studio and specify your pre- and post-build commands in
|
||||||
|
the BeforeBuild and AfterBuild targets below. -->
|
||||||
|
<!--<Target Name="CoreCompile" />-->
|
||||||
|
<Target Name="BeforeBuild">
|
||||||
|
</Target>
|
||||||
|
<Target Name="AfterBuild">
|
||||||
|
</Target>
|
||||||
|
</Project>
|
||||||
7
magnifier/main.py
Normal file
7
magnifier/main.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import tkinter as tk
|
||||||
|
from gui import PICSimulatorGUI
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
root = tk.Tk()
|
||||||
|
app = PICSimulatorGUI(root)
|
||||||
|
root.mainloop()
|
||||||
1255
magnifier/pic_core.py
Normal file
1255
magnifier/pic_core.py
Normal file
File diff suppressed because it is too large
Load Diff
19
magnifier/tasks.py
Normal file
19
magnifier/tasks.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
class TaskLibrary:
|
||||||
|
def __init__(self):
|
||||||
|
self.tasks = {}
|
||||||
|
|
||||||
|
# --- Programy demonstracyjne ---
|
||||||
|
# Używamy try-except, żeby błąd w jednym pliku nie wywalił całego programu
|
||||||
|
try:
|
||||||
|
from demos import Demos
|
||||||
|
self.tasks["--- Demonstracyjne ---"] = self.get_empty
|
||||||
|
self.tasks.update(Demos().get_tasks())
|
||||||
|
self.tasks["--- Koniec prog. demon. ---"] = self.get_empty
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
def get_tasks(self):
|
||||||
|
return self.tasks
|
||||||
|
|
||||||
|
def get_empty(self):
|
||||||
|
return "; Wybierz zadanie."
|
||||||
Reference in New Issue
Block a user