2026年1月17日 星期六

CRC (循環冗餘校驗) 的計算

  CRC (循環冗餘校驗) 的計算


這是一個非常關鍵的 CRC 計算細節:在進行計算前,必須根據生成多項式的最高冪次 n,在原始資料末尾補上 n 個零(稱為 Padding),這代表了預留給 CRC 餘數的空間。

我已經更新了程式碼,現在它會:

  1. 自動檢測最高冪次:從生成多項式二進位字串中找出最高位(例如 100011101 最高冪是 x^8)。

  2. 自動補零:在資料末尾補上對應數量的 0,並在畫面上用紅色標示這些補上的位元。

  3. 正確起始計算:使用補零後的長資料進行模2除法。

在電腦科學中,這種多項式除法(Polynomial Division)通常用於 CRC (循環冗餘校驗) 的計算,且其關鍵在於 模2運算 (Modulo-2 arithmetic),也就是減法等同於 XOR (互斥或)

為了在 Tkinter 中呈現這個過程,我將程式碼設計為:

  1. 視覺化長除法格式:模擬手寫除法的排列方式。

  2. 逐步執行:按下「下一步」會顯示當前的 XOR 運算結果與下拉項。

  3. 十六進制轉換




在 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()


沒有留言:

張貼留言

Fultter APP控制 WOKWI ESP32 RFID+LED (使用flutter_windows_3.19.1-stable Visual studio 1.108.2 )

Fultter APP控制 WOKWI ESP32 RFID+LED (使用flutter_windows_3.19.1-stable  Visual studio  1.108.2 ) 1) Android studio 環境設定 2) Visual studio  1.108...