CRC (循環冗餘校驗) 的計算
這是一個非常關鍵的 CRC 計算細節:在進行計算前,必須根據生成多項式的最高冪次 n,在原始資料末尾補上 n 個零(稱為 Padding),這代表了預留給 CRC 餘數的空間。
我已經更新了程式碼,現在它會:
自動檢測最高冪次:從生成多項式二進位字串中找出最高位(例如
100011101最高冪是 x^8)。自動補零:在資料末尾補上對應數量的
0,並在畫面上用紅色標示這些補上的位元。正確起始計算:使用補零後的長資料進行模2除法。
在電腦科學中,這種多項式除法(Polynomial Division)通常用於 CRC (循環冗餘校驗) 的計算,且其關鍵在於 模2運算 (Modulo-2 arithmetic),也就是減法等同於 XOR (互斥或)。
為了在 Tkinter 中呈現這個過程,我將程式碼設計為:
視覺化長除法格式:模擬手寫除法的排列方式。
逐步執行:按下「下一步」會顯示當前的 XOR 運算結果與下拉項。
十六進制轉換
在 CRC 中,二進位字串 1101 代表多項式x^3 + x^2 + 1。
import tkinter as tk
from tkinter import messagebox
class ScrollableCRCDemo:
def __init__(self, root):
self.root = root
self.root.title("CRC-8 長除法演示 (支援捲動畫面)")
self.root.geometry("1100x800")
# --- 1. 固定輸入區域 (不隨捲動) ---
input_frame = tk.Frame(self.root)
input_frame.pack(pady=10, side=tk.TOP, fill=tk.X)
tk.Label(input_frame, text="資料 (Binary):").grid(row=0, column=0, padx=5)
self.ent_data = tk.Entry(input_frame, width=30)
self.ent_data.insert(0, "110001000110100")
self.ent_data.grid(row=0, column=1)
tk.Label(input_frame, text="生成多項式 (Binary):").grid(row=1, column=0, padx=5)
self.ent_poly = tk.Entry(input_frame, width=30)
self.ent_poly.insert(0, "100011101")
self.ent_poly.grid(row=1, column=1)
self.btn_calc = tk.Button(input_frame, text="開始計算", command=self.start_calculation, bg="#d9ead3")
self.btn_calc.grid(row=0, column=2, rowspan=2, padx=10)
self.btn_next = tk.Button(self.root, text="下一步 >", command=self.next_step, state="disabled")
self.btn_next.pack(pady=5)
# --- 2. 建立帶捲軸的畫布區域 ---
self.main_container = tk.Frame(self.root)
self.main_container.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)
self.canvas = tk.Canvas(self.main_container, bg="white", highlightthickness=1, highlightbackground="black")
self.scrollbar = tk.Scrollbar(self.main_container, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# 支援滑鼠滾輪
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
self.steps = []
self.current_step = 0
self.padding_size = 0
def _on_mousewheel(self, event):
self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")
def start_calculation(self):
raw_data = self.ent_data.get().strip()
poly = self.ent_poly.get().strip()
if not all(b in '01' for b in raw_data + poly) or not poly:
messagebox.showerror("錯誤", "請輸入有效的二進位字串")
return
self.padding_size = len(poly.lstrip('0')) - 1
data = raw_data + ("0" * self.padding_size)
self.steps = []
self.current_step = 0
poly_len = len(poly)
self.steps.append({"type": "init", "raw_data": raw_data, "data": data, "poly": poly})
working_bits = data[:poly_len]
ptr = poly_len
quotient = ""
while True:
is_one = working_bits[0] == '1'
q_bit = '1' if is_one else '0'
quotient += q_bit
sub_val = poly if is_one else '0' * poly_len
xor_res = "".join('0' if working_bits[i] == sub_val[i] else '1' for i in range(poly_len))[1:]
if ptr < len(data):
new_working = xor_res + data[ptr]
self.steps.append({"type": "div", "sub": sub_val, "res": new_working, "quot_bit": q_bit, "ptr": ptr})
working_bits = new_working
ptr += 1
else:
self.steps.append({"type": "final", "sub": sub_val, "res": xor_res, "quot_bit": q_bit})
break
self.btn_next.config(state="normal")
self.render_step()
def render_step(self):
self.canvas.delete("all")
if not self.steps: return
header = self.steps[0]
poly = header["poly"]
raw_data = header["raw_data"]
full_data = header["data"]
char_w = 14
canvas_w = self.canvas.winfo_width()
if canvas_w < 100: canvas_w = 1000
# 被除數置中座標計算
total_data_w = len(full_data) * char_w
x_start = (canvas_w // 2) - (total_data_w // 2)
y_base = 120
# 1. 繪製生成多項式 (左側固定)
self.canvas.create_text(50, y_base, text=f"除數: {poly}", anchor="w", font=("Courier New", 14, "bold"), fill="blue")
# 2. 繪製框架
self.canvas.create_line(x_start - 5, y_base - 25, x_start - 5, y_base + 15, width=2)
self.canvas.create_line(x_start - 5, y_base - 25, x_start + total_data_w + 10, y_base - 25, width=2)
# 3. 繪製被除數 (資料 + 紅色補零)
self.canvas.create_text(x_start, y_base, text=raw_data, anchor="nw", font=("Courier New", 14))
pad_x = x_start + len(raw_data) * char_w
self.canvas.create_text(pad_x, y_base, text="0"*self.padding_size, anchor="nw", font=("Courier New", 14, "bold"), fill="red")
# 4. 逐步繪製計算過程
y_offset = y_base
for i in range(1, self.current_step + 1):
step = self.steps[i]
shift = (i - 1) * char_w
# XOR 減項
self.canvas.create_text(x_start + shift, y_offset + 30, text=step["sub"], anchor="nw", font=("Courier New", 14), fill="gray")
self.canvas.create_line(x_start + shift, y_offset + 55, x_start + shift + len(poly)*char_w, y_offset + 55)
# XOR 結果
y_offset += 60
self.canvas.create_text(x_start + shift + char_w, y_offset, text=step["res"], anchor="nw", font=("Courier New", 14))
# 上方商數
self.canvas.create_text(x_start + shift, y_base - 50, text=step["quot_bit"], anchor="nw", font=("Courier New", 14, "bold"), fill="darkgreen")
# 顯示餘數
if self.steps[self.current_step]["type"] == "final":
final_res = self.steps[self.current_step]["res"]
hex_val = hex(int(final_res, 2)).upper().replace("X", "x")
self.canvas.create_text(x_start + total_data_w + 40, y_offset,
text=f"餘數: {final_res}\n(Hex: {hex_val})",
anchor="nw", font=("Arial", 14, "bold"), fill="red")
# --- 核心:更新捲動區域尺寸 ---
# 根據 y_offset 動態調整畫布的高度範圍
self.canvas.configure(scrollregion=(0, 0, canvas_w, y_offset + 200))
def next_step(self):
if self.current_step < len(self.steps) - 1:
self.current_step += 1
self.render_step()
# 自動捲動到底部
self.canvas.yview_moveto(1.0)
else:
self.btn_next.config(state="disabled")
if __name__ == "__main__":
root = tk.Tk()
app = ScrollableCRCDemo(root)
# 視窗縮放時重新渲染
root.bind("<Configure>", lambda e: app.render_step())
root.mainloop()
import tkinter as tk
from tkinter import messagebox
class RegisterCRCDemo:
def __init__(self, root):
self.root = root
self.root.title("CRC-8 硬體移位暫存器模擬 (含動態 XOR 閘)")
self.root.geometry("1100x700")
# --- 輸入區域 ---
input_frame = tk.Frame(self.root)
input_frame.pack(pady=10)
tk.Label(input_frame, text="原始資料 (Binary):").grid(row=0, column=0, padx=5)
self.ent_data = tk.Entry(input_frame, width=30)
self.ent_data.insert(0, "11000100")
self.ent_data.grid(row=0, column=1)
tk.Label(input_frame, text="生成多項式 (Binary):").grid(row=1, column=0, padx=5)
self.ent_poly = tk.Entry(input_frame, width=30)
self.ent_poly.insert(0, "100011101")
self.ent_poly.grid(row=1, column=1)
self.btn_init = tk.Button(input_frame, text="重設並初始化", command=self.init_sim, bg="#d9ead3")
self.btn_init.grid(row=0, column=2, rowspan=2, padx=10)
self.btn_step = tk.Button(self.root, text="移入下一個位元 (Step) >", command=self.step_sim, state="disabled")
self.btn_step.pack(pady=5)
self.canvas = tk.Canvas(self.root, bg="white", height=500)
self.canvas.pack(fill=tk.BOTH, expand=True, padx=50)
self.reset_state()
def reset_state(self):
self.data_stream = ""
self.register = []
self.poly_bits = []
self.current_bit_idx = 0
def init_sim(self):
raw_data = self.ent_data.get().strip()
poly = self.ent_poly.get().strip()
if not all(b in '01' for b in raw_data + poly) or len(poly) < 2:
messagebox.showerror("錯誤", "請輸入有效的二進位字串")
return
self.n_zeros = len(poly.lstrip('0')) - 1
self.data_stream = raw_data + ("0" * self.n_zeros)
self.poly_bits = [int(b) for b in poly]
self.register = [0] * self.n_zeros
self.current_bit_idx = 0
self.btn_step.config(state="normal")
self.draw_registers("初始化完成,⊕ 符號標示了多項式的 XOR 位置。")
def step_sim(self):
if self.current_bit_idx >= len(self.data_stream):
messagebox.showinfo("完成", f"運算結束!CRC 餘數為: {''.join(map(str, self.register))}")
self.btn_step.config(state="disabled")
return
in_bit = int(self.data_stream[self.current_bit_idx])
msb = self.register[0]
temp_reg = self.register[1:] + [in_bit]
action = f"位元 [{in_bit}] 進入"
if msb == 1:
for i in range(len(temp_reg)):
temp_reg[i] ^= self.poly_bits[i+1]
action += ",MSB 為 1 觸發回饋 XOR"
else:
action += ",MSB 為 0 僅執行移位"
self.register = temp_reg
self.current_bit_idx += 1
self.draw_registers(action, trigger_xor=(msb==1))
def draw_registers(self, action_text, trigger_xor=False):
self.canvas.delete("all")
cw = self.canvas.winfo_width()
if cw < 100: cw = 1100
y_center = 300
box_size = 60
gap = 25
reg_count = len(self.register)
total_reg_w = reg_count * box_size + (reg_count - 1) * gap
total_data_area_w = 400
reg_start_x = (cw - total_data_area_w) // 2 - 100
# --- A. 繪製右側資料輸入端 ---
data_x = reg_start_x + total_reg_w + 100
remaining = self.data_stream[self.current_bit_idx:]
processed = self.data_stream[:self.current_bit_idx]
self.canvas.create_text(data_x, y_center, text=remaining, anchor="w", font=("Courier New", 18, "bold"), fill="black")
self.canvas.create_text(data_x, y_center, text=processed, anchor="e", font=("Courier New", 18), fill="#e0e0e0")
# --- B. 繪製暫存器與 XOR 符號 ---
for i, bit in enumerate(self.register):
x = reg_start_x + i * (box_size + gap)
color = "#fff2cc" if bit == 1 else "#f3f3f3"
# 繪製 XOR 符號 ⊕ (如果多項式該位為 1)
# poly_bits[0] 是 x^8 (MSB), poly_bits[1] 對應 b7...
if i + 1 < len(self.poly_bits) and self.poly_bits[i+1] == 1:
xor_color = "red" if trigger_xor else "blue"
self.canvas.create_text(x + box_size/2, y_center - 80, text="⊕", font=("Arial", 24, "bold"), fill=xor_color)
# 繪製回饋線一小段
self.canvas.create_line(x + box_size/2, y_center - 60, x + box_size/2, y_center - 30, arrow=tk.LAST, fill=xor_color)
# 暫存器方格
self.canvas.create_rectangle(x, y_center-30, x+box_size, y_center+30, fill=color, outline="black", width=2)
self.canvas.create_text(x + box_size/2, y_center, text=str(bit), font=("Arial", 20, "bold"))
self.canvas.create_text(x + box_size/2, y_center+50, text=f"b{reg_count-1-i}", font=("Arial", 10, "italic"))
# --- C. 繪製回饋長線 (當 MSB=1 時變紅) ---
feedback_color = "red" if trigger_xor else "lightgray"
self.canvas.create_line(reg_start_x - 40, y_center, reg_start_x - 40, y_center - 120, fill=feedback_color, width=2)
self.canvas.create_line(reg_start_x - 40, y_center - 120, reg_start_x + total_reg_w - 30, y_center - 120, fill=feedback_color, width=2)
# --- D. 狀態顯示 ---
self.canvas.create_text(cw//2, 60, text=f"Step {self.current_bit_idx}: {action_text}", font=("微軟正黑體", 16, "bold"), fill="#2c3e50")
reg_str = "".join(map(str, self.register))
hex_val = "0x" + hex(int(reg_str, 2))[2:].upper().zfill(2)
self.canvas.create_text(cw//2, 450, text=f"目前 CRC 餘數: {reg_str} ({hex_val})", font=("Courier New", 18, "bold"), fill="red")
if __name__ == "__main__":
root = tk.Tk()
app = RegisterCRCDemo(root)
root.bind("<Configure>", lambda e: app.draw_registers("視窗調整中..."))
root.mainloop()




沒有留言:
張貼留言