์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ

์ „๋ ฅ๋ฐ˜๋„์ฒด ์‹ ๋ขฐ์„ฑ ์‹œํ—˜ ์ž๋™ํ™”: PyVISA์™€ PyQt6 ๊ธฐ๋ฐ˜ SMU ๋น„๋™๊ธฐ ์ง€์—ฐ ํ•ด๊ฒฐ ๋ฐ SCPI ์Šค์ผ€์ค„๋ง ํ ์ตœ์ ํ™”

๊ฒŒ์ž„๊ต์ˆ˜ 2026. 9. 18. 22:01
๋ฐ˜์‘ํ˜•

๐ŸŒ English Abstract

This article addresses critical technical bottlenecks in building automated power semiconductor reliability test systems using Python, PyVISA, and PyQt6. It explores the root causes of VISA timeout errors (VI_ERROR_TMO) and UI thread-blocking issues encountered when controlling multi-channel Source Measure Units (SMUs) asynchronously. Furthermore, a robust engineering solution incorporating thread-safe SCPI scheduling queues, dynamic VISA timeout management, and PyQt6 worker thread architecture is presented with production-ready code examples.

์ฐจ์„ธ๋Œ€ ์ „๋ ฅ๋ฐ˜๋„์ฒด(Power Semiconductor)์ธ SiC(์ „๋ ฅ์šฉ ํƒ„ํ™”๊ทœ์†Œ) ๋ฐ GaN(์งˆํ™”๊ฐˆ๋ฅจ) ์†Œ์ž์˜ ๊ฐœ๋ฐœ์ด ๊ฐ€์†ํ™”๋จ์— ๋”ฐ๋ผ, ๊ณ ์ „์••ยท๊ณ ์ „๋ฅ˜ ํ™˜๊ฒฝ์—์„œ์˜ ์˜ค๋žœ ์‹ ๋ขฐ์„ฑ์„ ๊ฒ€์ฆํ•˜๋Š” ์‹ ๋ขฐ์„ฑ ์‹œํ—˜ ์ž๋™ํ™” ์‹œ์Šคํ…œ(Reliability Test Automation System)์˜ ์ค‘์š”์„ฑ์ด ๋”์šฑ ์ปค์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ HTRB(High Temperature Reverse Bias), HTGB(High Temperature Gate Bias) ๋ฐ ์›จ์ดํผ ๋ ˆ๋ฒจ ์‹ ๋ขฐ์„ฑ(WLR) ์‹œํ—˜์—์„œ๋Š” ์ˆ˜์‹ญ์—์„œ ์ˆ˜๋ฐฑ ์ฑ„๋„์˜ SMU(Source Measure Unit)๋ฅผ ๋™์‹œ์— ์ œ์–ดํ•˜๊ณ  ์ „์•• ๋ฐ ๋ˆ„์„ค ์ „๋ฅ˜(Leakage Current)๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ธก์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ํŒŒ์ด์ฌ(Python)์˜ PyVISA ํ†ต์‹  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ PyQt6 GUI ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์กฐํ•ฉํ•˜์—ฌ ๋‹ค์ค‘ ์ฑ„๋„ ํ™˜๊ฒฝ์„ ๊ตฌ์ถ•ํ•  ๋•Œ, ์ธก์ • ์žฅ๋น„์˜ ์‘๋‹ต ์ง€์—ฐ์œผ๋กœ ์ธํ•œ ํƒ€์ž„์•„์›ƒ ์—๋Ÿฌ(VisaIOError: VI_ERROR_TMO)์™€ ๋ฉ”์ธ UI ์Šค๋ ˆ๋“œ ๋ฉˆ์ถค(Freezing) ํ˜„์ƒ์ด ๋นˆ๋ฒˆํ•˜๊ฒŒ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๋ณธ ํฌ์ŠคํŒ…์—์„œ๋Š” ์ด๋Ÿฌํ•œ ํ˜„์ƒ์˜ ์›์ธ์„ ๊นŠ์ด ์žˆ๊ฒŒ ๋ถ„์„ํ•˜๊ณ , ์Šค๋ ˆ๋“œ ์•ˆ์ •์„ฑ์„ ๋ณด์žฅํ•˜๋Š” SCPI ์Šค์ผ€์ค„๋ง ํ ๋ฐ ๋น„๋™๊ธฐ ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์กฐ๋ฅผ ํ†ตํ•ด ์‹œ์Šคํ…œ ์•ˆ์ •์„ฑ์„ ๊ทน๋Œ€ํ™”ํ•œ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… ๊ฒฝํ—˜์„ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.



1. ๋ฌธ์ œ ํ˜„์ƒ ๋ฐ ์›์ธ ๋ถ„์„ (Root Cause Analysis)

๋‹ค์ค‘ ์ฑ„๋„ SMU ์ œ์–ด ์‹œ์Šคํ…œ ๊ฐœ๋ฐœ ์ค‘ ๋ฐœ์ƒํ•œ ํ•ต์‹ฌ ๋ฌธ์ œ๋Š” ํฌ๊ฒŒ ์„ธ ๊ฐ€์ง€๋กœ ์š”์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ€. SMU ์ธก์ • ์ ๋ถ„ ์‹œ๊ฐ„(NPLC)๊ณผ VISA ํƒ€์ž„์•„์›ƒ์˜ ๋ถˆ์ผ์น˜

์ „๋ ฅ๋ฐ˜๋„์ฒด์˜ pA(ํ”ผ์ฝ”์•”ํŽ˜์–ด) ์ˆ˜์ค€ ๋ˆ„์„ค ์ „๋ฅ˜๋ฅผ ์ธก์ •ํ•˜๋ ค๋ฉด SMU์˜ NPLC(Number of Power Line Cycles) ์„ค์ •์„ ๋†’์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค(์˜ˆ: NPLC 5 ๋˜๋Š” 10). NPLC๊ฐ€ ๋†’์•„์ง€๋ฉด ์žฅ๋น„ ๋‚ด๋ถ€์˜ A/D ๋ณ€ํ™˜๊ธฐ(ADC) ์ ๋ถ„ ์‹œ๊ฐ„์ด ๊ธธ์–ด์ง€๋ฉฐ, ๋‹จ์ผ ์ฑ„๋„ ์ธก์ • ์‹œ ์ˆ˜๋ฐฑ ๋ฐ€๋ฆฌ์ดˆ์—์„œ ์ˆ˜ ์ดˆ์˜ ๋ฌผ๋ฆฌ์  ์‹œ๊ฐ„์ด ์†Œ์š”๋ฉ๋‹ˆ๋‹ค. ์ด๋•Œ PyVISA ์„ธ์…˜์˜ ๊ธฐ๋ณธ ํƒ€์ž„์•„์›ƒ(Timeout) ์„ค์ •๊ฐ’์ด ์žฅ๋น„์˜ ์‹ค์ œ ์ธก์ • ์‹œ๊ฐ„๋ณด๋‹ค ์งง์œผ๋ฉด VISA ๋ฒ„์Šค ๋ ˆ๋ฒจ์—์„œ VI_ERROR_TMO (-1073807339) ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

๋‚˜. SCPI ๋ช…๋ น์–ด ํ์˜ ๋ณ‘๋ชฉ๊ณผ ๋ณ‘๋ ฌ ์ œ์–ด์˜ ํ•œ๊ณ„

๋Œ€๋ถ€๋ถ„์˜ SMU ์žฅ๋น„(Keithley 2600 ์‹œ๋ฆฌ์ฆˆ, Keysight B2900 ์‹œ๋ฆฌ์ฆˆ ๋“ฑ)๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ๋‹จ์ผ SCPI(Standard Commands for Programmable Instruments) ํŒŒ์„œ(Parser) ๋ฐ ์ปค๋งจ๋“œ ํ(Command Queue)๋ฅผ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค. ์ˆ˜์‹ญ ๊ฐœ์˜ ์ฑ„๋„์— ๋™์‹œ์— ์ธก์ • ๋ช…๋ น(READ?, MEAS:CURR?)์„ ๋ฐœ์†กํ•˜๋ฉด ์žฅ๋น„ ๋‚ด๋ถ€ ๋ฒ„ํผ๊ฐ€ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ๋˜๊ฑฐ๋‚˜, ์ด์ „ ๋ช…๋ น์ด ์ฒ˜๋ฆฌ๋˜๊ธฐ ์ „์— ์ƒˆ๋กœ์šด ๋ช…๋ น์ด ์ˆ˜์‹ ๋˜์–ด ํ†ต์‹  ์‘๋‹ต ์ˆœ์„œ๊ฐ€ ๋’ค์–ฝํžˆ๋Š” ๋น„๋™๊ธฐ ์‘๋‹ต ์ง€์—ฐ(Asynchronous Response Latency) ํ˜„์ƒ์ด ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.

๋‹ค. PyQt6 ์ด๋ฒคํŠธ ๋ฃจํ”„์™€ GIL(Global Interpreter Lock) ๋ธ”๋กœํ‚น

PyVISA์˜ ๋™๊ธฐ์‹(Synchronous) I/O ํ˜ธ์ถœ(query(), read())์„ PyQt6์˜ ๋ฉ”์ธ GUI ์Šค๋ ˆ๋“œ์—์„œ ์ง์ ‘ ์ˆ˜ํ–‰ํ•˜๋ฉด ํ†ต์‹  ๋Œ€๊ธฐ ์‹œ๊ฐ„ ๋™์•ˆ ์ด๋ฒคํŠธ ๋ฃจํ”„๊ฐ€ ์ฐจ๋‹จ๋˜์–ด ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์‘๋‹ต ์—†์Œ ์ƒํƒœ์— ๋น ์ง‘๋‹ˆ๋‹ค. ๋‹จ์ˆœํ•œ QThread ์ ์šฉ ์‹œ์—๋„ ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์ผํ•œ VISA ์ˆ˜์‹ ๊ธฐ ์„ธ์…˜(GPIB, Ethernet/VXI-11, USB-TMC)์— ๋™์‹œ ์ ‘๊ทผํ•  ๊ฒฝ์šฐ ํŒŒ์ด์ฌ์˜ GIL ๋ฐ VISA C-DLL ๋‚ด๋ถ€ ๋™๊ธฐํ™” ๋ฌธ์ œ๋กœ ํฌ๋ž˜์‹œ(Crash)๊ฐ€ ์ผ์–ด๋‚ฉ๋‹ˆ๋‹ค.

๋ฌธ์ œ ์š”์†Œ ์›์ธ (Root Cause) ์‹œ์Šคํ…œ์— ๋ฏธ์น˜๋Š” ์˜ํ–ฅ
VI_ERROR_TMO ๋†’์€ NPLC ์„ค์ •์— ๋”ฐ๋ฅธ ์žฅ๋น„ ์‘๋‹ต ์ง€์—ฐ > PyVISA Timeout ์ธก์ • ์‹œํ€€์Šค ์ค‘๋‹จ ๋ฐ ๋ฐ์ดํ„ฐ ์œ ์‹ค
UI Freezing PyQt6 Main Thread ๋‚ด Blocking I/O ์‹คํ–‰ ์ œ์–ด ๋ถˆ๋Šฅ, ์‹ค์‹œ๊ฐ„ ๊ทธ๋ž˜ํ”„ ์—…๋ฐ์ดํŠธ ์ค‘๋‹จ
Bus Contention ๋‹ค์ค‘ ์Šค๋ ˆ๋“œ์˜ ๋‹จ์ผ VISA ์„ธ์…˜ ๊ต์ฐฉ ์ ‘๊ทผ SCPI ๋ช…๋ น์–ด ๋’ค์„ž์ž„, ์žฅ๋น„ ์ปค๋งจ๋“œ ์—๋Ÿฌ (-100)

2. ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„ ๋ฐ ํ•ด๊ฒฐ ๋ฐฉ์•ˆ (System Architecture)

์ด ๋ฌธ์ œ๋ฅผ ๊ทผ๋ณธ์ ์œผ๋กœ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ƒ์‚ฐ์ž-์†Œ๋น„์ž ํŒจํ„ด(Producer-Consumer Pattern) ๊ธฐ๋ฐ˜์˜ SCPI ์Šค์ผ€์ค„๋ง ํ์™€ PyQt6์˜ ๋น„๋™๊ธฐ Worker ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋„์ž…ํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ์„ค๊ณ„ ๋ชฉํ‘œ:
1. ํ†ต์‹  I/O์™€ UI ๋ ˆ์ด์–ด์˜ ์™„๋ฒฝํ•œ ๋ถ„๋ฆฌ (PyQt6 QThread + pyqtSignal ์‚ฌ์šฉ)
2. ์Šค๋ ˆ๋“œ ์„ธ์ดํ”„(Thread-safe) ์šฐ์„ ์ˆœ์œ„ SCPI ์Šค์ผ€์ค„๋ง ํ ๊ตฌ์ถ•
3. ๋™์  VISA ํƒ€์ž„์•„์›ƒ ํŠœ๋‹ ๋ฐ OPC(Operation Complete) ํด๋ง ๋ฐฉ์‹์„ ํ†ตํ•œ ์•ˆ์ „ํ•œ ๋™๊ธฐํ™”

๋ชจ๋“  GUI ์ œ์–ด ์š”์ฒญ(์ „์•• ์„ค์ •, ๋™์  Sweep ์‹œ์ž‘, ๋น„์ƒ ์ •์ง€ ๋“ฑ)๊ณผ ์ฃผ๊ธฐ์  ๋™์‹œ ์ธก์ • ์š”์ฒญ์€ ์ง์ ‘ VISA ๋ช…๋ น์„ ๋ฐœ์†กํ•˜์ง€ ์•Š๊ณ , ์šฐ์„ ์ˆœ์œ„ ํ(Priority Queue)์— ํƒœ์Šคํฌ ํ˜•ํƒœ๋กœ ์—”ํ(Enqueue)๋ฉ๋‹ˆ๋‹ค. ์ „์šฉ ํ†ต์‹  Worker ์Šค๋ ˆ๋“œ๊ฐ€ ํ์—์„œ ํƒœ์Šคํฌ๋ฅผ ํ•˜๋‚˜์”ฉ ๋ฐํ(Dequeue)ํ•˜์—ฌ ์žฅ๋น„์™€ 1:1๋กœ ํ†ต์‹ ํ•จ์œผ๋กœ์จ ๋ฒ„์Šค ์ถฉ๋Œ ๋ฐ ์‘๋‹ต ๊ผฌ์ž„ ํ˜„์ƒ์„ ์›์ฒœ ์ฐจ๋‹จํ•ฉ๋‹ˆ๋‹ค.



3. ํ•ต์‹ฌ ์†Œ์Šค ์ฝ”๋“œ ๊ตฌํ˜„ (Implementation)

์•„๋ž˜ ์ฝ”๋“œ๋Š” ์Šค๋ ˆ๋“œ ์„ธ์ดํ”„ํ•œ PyVISA ์žฅ๋น„ ํ•ธ๋“ค๋Ÿฌ ๋ฐ PyQt6 ๊ธฐ๋ฐ˜ SCPI ์Šค์ผ€์ค„๋ง ํ์˜ ํ•ต์‹ฌ ๊ตฌํ˜„ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค.

๊ฐ€. ์šฐ์„ ์ˆœ์œ„ ๊ธฐ๋ฐ˜ SCPI Task ๋ฐ ์Šค์ผ€์ค„๋Ÿฌ ์Šค๋ ˆ๋“œ

import time
import queue
import pyvisa
from dataclasses import dataclass, field
from typing import Any, Callable
from PyQt6.QtCore import QThread, pyqtSignal, pyqtSlot

# ์šฐ์„ ์ˆœ์œ„ ์ •์˜ (์ˆซ์ž๊ฐ€ ๋‚ฎ์„์ˆ˜๋ก ๋†’์€ ์šฐ์„ ์ˆœ์œ„)
PRIORITY_EMERGENCY = 0   # ๋น„์ƒ ์ •์ง€, Output Off
PRIORITY_CONTROL = 1     # ์ „์••/์ „๋ฅ˜ ์„ค์ • ๋ณ€๊ฒฝ
PRIORITY_MEASURE = 2     # ์ •๊ธฐ ์Šค์บ” ๋ฐ ์ธก์ •

@dataclass(order=True)
class SCPITask:
    priority: int
    command: str = field(compare=False)
    is_query: bool = field(compare=False, default=False)
    callback: Callable[[Any], None] = field(compare=False, default=None)
    timeout_ms: int = field(compare=False, default=5000)

class VisaDeviceWorker(QThread):
    """PyVISA ํ†ต์‹ ์„ ์ „๋‹ด ์ฒ˜๋ฆฌํ•˜๋Š” Worker ์Šค๋ ˆ๋“œ"""
    data_received = pyqtSignal(str, object)  # (command, result)
    error_occurred = pyqtSignal(str)

    def __init__(self, resource_name: str, parent=None):
        super().__init__(parent)
        self.resource_name = resource_name
        self.task_queue = queue.PriorityQueue()
        self._is_running = True
        self.rm = None
        self.instrument = None

    def run(self):
        # VISA ๋ฆฌ์†Œ์Šค ์—ฐ๊ฒฐ
        try:
            self.rm = pyvisa.ResourceManager()
            self.instrument = self.rm.open_resource(self.resource_name)
            self.instrument.write_termination = '\n'
            self.instrument.read_termination = '\n'
            # ๊ธฐ๋ณธ ํƒ€์ž„์•„์›ƒ ์„ค์ • (5์ดˆ)
            self.instrument.timeout = 5000
        except pyvisa.VisaIOError as e:
            self.error_occurred.emit(f"์žฅ๋น„ ์—ฐ๊ฒฐ ์‹คํŒจ ({self.resource_name}): {str(e)}")
            return

        while self._is_running:
            try:
                # 100ms ๋Œ€๊ธฐํ•˜๋ฉฐ ํ์—์„œ ์ž‘์—… ์ธ์ถœ
                task: SCPITask = self.task_queue.get(timeout=0.1)
            except queue.Empty:
                continue

            self._execute_task(task)
            self.task_queue.task_done()

        # ์Šค๋ ˆ๋“œ ์ข…๋ฃŒ ์‹œ ๋ฆฌ์†Œ์Šค ํ•ด์ œ
        self._cleanup()

    def _execute_task(self, task: SCPITask):
        if not self.instrument:
            return

        try:
            # ๋™์  ํƒ€์ž„์•„์›ƒ ์ ์šฉ (์ธก์ • ๊ธฐ๋ฒ•/NPLC์— ๋”ฐ๋ผ ์œ ์—ฐํ•˜๊ฒŒ ๋ณ€๊ฒฝ)
            original_timeout = self.instrument.timeout
            if task.timeout_ms != original_timeout:
                self.instrument.timeout = task.timeout_ms

            if task.is_query:
                # Query ์‹คํ–‰ (Write + Read)
                result = self.instrument.query(task.command)
                self.data_received.emit(task.command, result)
                if task.callback:
                    task.callback(result)
            else:
                # Write ์ „์šฉ ์‹คํ–‰
                self.instrument.write(task.command)
                # ์ค‘์š”ํ•œ ์ œ์–ด ๋ช…๋ น ํ›„ *OPC? (Operation Complete) ๋™๊ธฐํ™”
                if task.priority == PRIORITY_CONTROL:
                    opc_res = self.instrument.query("*OPC?")
                    
            # ํƒ€์ž„์•„์›ƒ ๋ณต์›
            self.instrument.timeout = original_timeout

        except pyvisa.VisaIOError as e:
            err_msg = f"VISA Error [{task.command}]: {e.strerror} (Code: {e.error_code})"
            self.error_occurred.emit(err_msg)
            self._handle_timeout_recovery()

    def _handle_timeout_recovery(self):
        """VISA ํƒ€์ž„์•„์›ƒ ๋ฐœ์ƒ ์‹œ ๋ฒ„์Šค ํด๋ฆฌ์–ด ๋ฐ ํ ์ •์ œ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…"""
        try:
            # Device Clear๋ฅผ ํ†ตํ•ด ์žฅ๋น„ I/O ๋ฒ„ํผ ์ดˆ๊ธฐํ™”
            self.instrument.clear()
            time.sleep(0.2)
            # ์žฅ๋น„ ์—๋Ÿฌ ํ ํ™•์ธ
            err_code = self.instrument.query("SYST:ERR?")
            self.error_occurred.emit(f"Buffer Cleared. Device Status: {err_code}")
        except Exception as e:
            self.error_occurred.emit(f"Recovery Failed: {str(e)}")

    def add_task(self, task: SCPITask):
        """์™ธ๋ถ€ ์Šค๋ ˆ๋“œ์—์„œ ์•ˆ์ „ํ•˜๊ฒŒ ํ˜ธ์ถœํ•˜๋Š” Task ์ถ”๊ฐ€ ํ•จ์ˆ˜"""
        self.task_queue.put(task)

    def stop(self):
        self._is_running = False
        self.wait()

    def _cleanup(self):
        if self.instrument:
            try:
                self.instrument.close()
            except:
                pass
        if self.rm:
            try:
                self.rm.close()
            except:
                pass

๋‚˜. Dynamic Timeout ๋ฐ NPLC ์ž๋™ ๊ณ„์‚ฐ ๋กœ์ง

SMU ํŒŒ๋ผ๋ฏธํ„ฐ ๋ณ€๊ฒฝ ์‹œ VISA Timeout ๊ฐ’์„ ๋™์ ์œผ๋กœ ๊ณ„์‚ฐํ•˜์—ฌ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด ํ•ต์‹ฌ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… ํฌ์ธํŠธ์ž…๋‹ˆ๋‹ค.

def calculate_safe_timeout(nplc: float, power_line_freq: int = 60, channels: int = 4) -> int:
    """
    NPLC ๋ฐ ์ฑ„๋„ ์ˆ˜์— ๋”ฐ๋ฅธ ์•ˆ์ „ํ•œ VISA Timeout(ms) ๊ณ„์‚ฐ
    ์˜ˆ: 60Hz ๊ธฐ์ค€ 1 NPLC = 16.67ms
    """
    single_integration_time_sec = nplc / power_line_freq
    # ๋งˆ์ง„(Overhead) 300% ๋ฐ ํ†ต์‹  ์˜ค๋ฒ„ํ—ค๋“œ + 2000ms ์ถ”๊ฐ€
    total_estimated_ms = int((single_integration_time_sec * channels * 1000 * 3) + 2000)
    return max(total_estimated_ms, 5000) # ์ตœ์†Œ 5000ms ๋ณด์žฅ

# ์‚ฌ์šฉ ์˜ˆ์‹œ
nplc_setting = 10.0
calculated_timeout = calculate_safe_timeout(nplc=nplc_setting, channels=2)

# ๊ณ ์ •๋ฐ€ ์ธก์ • Task ๋ฐœํ–‰
measure_task = SCPITask(
    priority=PRIORITY_MEASURE,
    command="READ?",
    is_query=True,
    timeout_ms=calculated_timeout
)
worker.add_task(measure_task)

4. ๊ฒ€์ฆ ๋ฐ ์„ฑ๋Šฅ ๊ฐœ์„  ๊ฒฐ๊ณผ (Results & Verification)

ํ•ด๋‹น ๋น„๋™๊ธฐ ์Šค์ผ€์ค„๋ง ํ ๊ตฌ์กฐ์™€ ๋™์  ํƒ€์ž„์•„์›ƒ ๊ด€๋ฆฌ ๊ธฐ๋ฒ•์„ 32์ฑ„๋„ SiC MOSFET HTRB ์‹ค์‹œ๊ฐ„ ์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€ ์‹œ์Šคํ…œ์— ์ ์šฉํ•œ ๊ฒฐ๊ณผ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ช…ํ™•ํ•œ ๊ฐœ์„  ์„ฑ๊ณผ๋ฅผ ๊ฑฐ๋‘์—ˆ์Šต๋‹ˆ๋‹ค.

  • VISA Timeout ์—๋Ÿฌ ์™„๋ฒฝ ํ•ด๊ฒฐ: 100์‹œ๊ฐ„ ์—ฐ์† ๊ณ ์˜จ ์—ญ๋ฐฉํ–ฅ ๋ฐ”์ด์–ด์Šค ์‹œํ—˜ ์ค‘ VI_ERROR_TMO ๋ฐœ์ƒ ํšŸ์ˆ˜ 0๊ฑด ๋‹ฌ์„ฑ (๊ธฐ์กด: ์ฑ„๋„ ์ „ํ™˜ ์‹œ ์ˆ˜์‹œ ๋ฐœ์ƒ).
  • UI ๋ฐ˜์‘์„ฑ 100% ์œ ์ง€: ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๊ณ ๋ฐ€๋„ ์ „๋ฅ˜ ์ธก์ •์ด ์ˆ˜ํ–‰๋˜๋Š” ๋™์•ˆ์—๋„ PyQt6 ๋ฉ”์ธ UI์˜ ์ฐจํŠธ ๋ Œ๋”๋ง ๋ฐ ์ œ์–ด ๋ฒ„ํŠผ ๋ฐ˜์‘ ํ”„๋ ˆ์ž„์ด 60 FPS ์ˆ˜์ค€์œผ๋กœ ์•ˆ์ •ํ™”.
  • ๋ช…๋ น์–ด ์ถฉ๋Œ ์ฐจ๋‹จ: ๋น„์ƒ ์ •์ง€(Emergency Stop) ๋ฐœ์ƒ ์‹œ ์šฐ์„ ์ˆœ์œ„ ํ(Priority 0)์— ์˜ํ•ด ๊ธฐ์กด ๋Œ€๊ธฐ ์ค‘์ธ ๋‹จ์ˆœ ์ธก์ • ๋ช…๋ น์„ ์Šคํ‚ตํ•˜๊ณ  ์ฆ‰์‹œ :OUTP OFF ๋ช…๋ น์„ ์ˆ˜ํ–‰ํ•˜์—ฌ ์†Œ์ž ํŒŒ๊ดด ์˜ˆ๋ฐฉ.


5. ํ˜„์—… ์—”์ง€๋‹ˆ์–ด๋ฅผ ์œ„ํ•œ ์ถ”๊ฐ€ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… ํŒ (Practical Tips)

๊ฐ€. PyVISA Backend ์„ ํƒ์˜ ์ค‘์š”์„ฑ

Windows ํ™˜๊ฒฝ์—์„œ๋Š” National Instruments์˜ NI-VISA ๋ฒค๋” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๊ฐ€์žฅ ์•ˆ์ •์ ์ž…๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด Linux ๊ธฐ๋ฐ˜ ๋‚ด์žฅํ˜• ์‹œ์Šคํ…œ ๊ตฌ์ถ• ์‹œ pure-python ๊ตฌํ˜„์ฒด์ธ pyvisa-py๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, USB-TMC ๋˜๋Š” Ethernet ์—ฐ๊ฒฐ ์‹œ socket timeout ์ฒ˜๋ฆฌ๊ฐ€ NI-VISA์™€ ๋‹ค๋ฅด๊ฒŒ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๋ฐ˜๋“œ์‹œ pyvisa-py ์ „์šฉ ์ปค์Šคํ…€ ํƒ€์ž„์•„์›ƒ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋‚˜. Status Byte(STB) ๋ ˆ์ง€์Šคํ„ฐ ํด๋ง ํ™œ์šฉ

๋ธ”๋กœํ‚น ๋ฐฉ์‹์˜ query("READ?") ๋Œ€์‹ , ์žฅ๋น„์˜ *OPC(Operation Complete) ๋น„ํŠธ๊ฐ€ Status Byte Register์— ์„ธํŒ…๋  ๋•Œ๊นŒ์ง€ read_stb()๋ฅผ ๋น„๋ธ”๋กœํ‚น ํด๋ง(Polling)ํ•˜๊ฑฐ๋‚˜ Service Request(SRQ) ์ธํ„ฐ๋ŸฝํŠธ๋ฅผ ์ˆ˜์‹ ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์„ค๊ณ„ํ•˜๋ฉด ์‹œ์Šคํ…œ CPU ์ ์œ ์œจ์„ ํš๊ธฐ์ ์œผ๋กœ ๋‚ฎ์ถœ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๋ก  (Conclusion)

Python, PyVISA, PyQt6๋ฅผ ํ™œ์šฉํ•œ ์ „๋ ฅ๋ฐ˜๋„์ฒด ์‹ ๋ขฐ์„ฑ ์‹œํ—˜ ์ž๋™ํ™”๋Š” ํ•˜๋“œ์›จ์–ด ์ œ์–ด ํŠน์„ฑ๊ณผ ์†Œํ”„ํŠธ์›จ์–ด ์ด๋ฒคํŠธ ๋ฃจํ”„์˜ ๋ช…ํ™•ํ•œ ์ดํ•ด๋ฅผ ํ•„์š”๋กœ ํ•ฉ๋‹ˆ๋‹ค. SMU ๋‹ค์ค‘ ์ฑ„๋„ ํ™˜๊ฒฝ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์‘๋‹ต ์ง€์—ฐ๊ณผ ํƒ€์ž„์•„์›ƒ ๋ฌธ์ œ๋Š” 1) NPLC ๊ธฐ๋ฐ˜ ๋™์  ํƒ€์ž„์•„์›ƒ ์„ค์ •, 2) PriorityQueue ๊ธฐ๋ฐ˜์˜ ๋‹จ์ผ ํ†ต์‹  Worker ์Šค๋ ˆ๋“œ ์„ค๊ณ„, 3) VISA Device Clear๋ฅผ ํ™œ์šฉํ•œ ์—๋Ÿฌ ๋ณต๊ตฌ ๋งค์ปค๋‹ˆ์ฆ˜์„ ํ†ตํ•ด ๊ฒฌ๊ณ ํ•˜๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ณธ ํฌ์ŠคํŒ…์—์„œ ๋‹ค๋ฃฌ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์žฅ์‹œ๊ฐ„ ์•ˆ์ •์ ์œผ๋กœ ๋™์ž‘ํ•˜๋Š” ๊ณ ํ’ˆ์งˆ ๊ณ„์ธก ์ž๋™ํ™” ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•˜์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค.

๋ฐ˜์‘ํ˜•