팁스비전에서 제공해준 예시 C++ 코드

//M_AUX_IO4에 대한 사용 설정
MdigControl(m_MilDigitizer, M_IO_MODE + M_AUX_IO4, M_OUTPUT);
MdigControl(m_MilDigitizer, M_IO_INTERRUPT_STATE + M_AUX_IO4, M_ENABLE);
MdigControl(m_MilDigitizer, M_IO_INTERRUPT_ACTIVATION + M_AUX_IO4, M_EDGE_RISING);

//M_AUX_IO5에 대한 사용 설정
MdigControl(m_MilDigitizer, M_IO_MODE + M_AUX_IO5, M_OUTPUT);
MdigControl(m_MilDigitizer, M_IO_INTERRUPT_STATE + M_AUX_IO5, M_ENABLE);
MdigControl(m_MilDigitizer, M_IO_INTERRUPT_ACTIVATION + M_AUX_IO5, M_EDGE_RISING);

//M_AUX_IO4 ON 상태 변경 OK
MdigControl(m_MilDigitizer, M_USER_BIT_STATE + M_USER_BIT4, M_ON);
//M_AUX_IO5 ON 상태 변경 NG
MdigControl(m_MilDigitizer, M_USER_BIT_STATE + M_USER_BIT5, M_ON);

//아래는 M_AUX_IO4, M_AUX_IO5 상태 읽어 오는 코드
MIL_INT UserBitState4;
MIL_INT UserBitState5;
MdigInquire(m_MilDigitizer, M_USER_BIT_STATE + M_USER_BIT5, &UserBitState4);
MdigInquire(m_MilDigitizer, M_USER_BIT_STATE + M_USER_BIT5, &UserBitState5);
if (UserBitState4 == M_OFF)// Specifies that the specified bit is set to off.
{
      MdigControl(m_MilDigitizer, M_USER_BIT_STATE + M_USER_BIT4, M_OFF);
}
else if (UserBitState4 == M_ON)// Specifies that the specified bit is set to on.
{
      MdigControl(m_MilDigitizer, M_USER_BIT_STATE + M_USER_BIT4, M_OFF);

}

if (UserBitState5 == M_OFF)// Specifies that the specified bit is set to off.
{
      MdigControl(m_MilDigitizer, M_USER_BIT_STATE + M_USER_BIT5, M_OFF);
}
else if (UserBitState5 == M_ON)// Specifies that the specified bit is set to on.
{
      MdigControl(m_MilDigitizer, M_USER_BIT_STATE + M_USER_BIT5, M_OFF);

}

그런데 여기서 우리는 실제로는 OK를 default state으로 사용할 거라서 IO4는 사용할 필요 없이 IO5만 사용하면 된다고 함. 그 대신 edge rising에 반응하기 때문에, 다시 신호를 낮춰주는 작업이 필요하긴 한듯

M_AUX_IO5만 남긴 버전

//M_AUX_IO5에 대한 사용 설정
MdigControl(m_MilDigitizer, M_IO_MODE + M_AUX_IO5, M_OUTPUT);
MdigControl(m_MilDigitizer, M_IO_INTERRUPT_STATE + M_AUX_IO5, M_ENABLE);
MdigControl(m_MilDigitizer, M_IO_INTERRUPT_ACTIVATION + M_AUX_IO5, M_EDGE_RISING);

//M_AUX_IO5 ON 상태 변경 NG
MdigControl(m_MilDigitizer, M_USER_BIT_STATE + M_USER_BIT5, M_ON);

//아래는 M_AUX_IO5 상태 읽어 오는 코드
MIL_INT UserBitState5;
if (UserBitState5 == M_OFF)// Specifies that the specified bit is set to off.
{
      MdigControl(m_MilDigitizer, M_USER_BIT_STATE + M_USER_BIT5, M_OFF);
}
else if (UserBitState5 == M_ON)// Specifies that the specified bit is set to on.
{
      MdigControl(m_MilDigitizer, M_USER_BIT_STATE + M_USER_BIT5, M_OFF);

}

저장 코드와 합친 버전

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Rapixo CXP + 라인스캔 카메라 (고정 N_LINES + 연속 저장 버전) + Aux IO 5 Toggle

- Aux IO 0: 입력 (Trigger) -> 카메라 촬영
- Aux IO 5: 출력 (Output) -> 이미지 저장 시마다 상태 토글 (ON/OFF 반복)
- 펄스당 1라인씩 스캔되고, 총 N_LINES 라인이 쌓이면 2D 이미지(한 프레임)로 저장
- 이 동작을 무한 반복하면서 파일을 계속 저장 (Ctrl+C 로 중단)
"""

import os
import sys
import time
import mil as MIL

# ============================
# 1. 사용자 설정값
# ============================

N_LINES = 1600               # 이번 스캔에서 쌓을 라인 수 (최종 이미지의 높이)
BITS_PER_PIXEL = 24           # 일반적인 단색 카메라면 8
BITS_PER_CHANNEL = 8
DCF_NAME_STR = "CoaxPress_currentstate.dcf"   # 사용할 DCF 이름

# 기본 출력 파일 이름 (프레임 번호 붙여서 저장됨)
OUTPUT_PATH = "images/linescan_result.tif"

AUX_INDEX_TRIGGER = 0  # 트리거 입력용 Aux IO 인덱스 (0 -> M_AUX_IO0)
AUX_INDEX_OUTPUT  = 5  # 상태 출력용 Aux IO 인덱스 (5 -> M_AUX_IO5)

# 카메라 GenICam 트리거 파라미터
TRIGGER_SELECTOR_VALUE = "LineStart"
TRIGGER_MODE_VALUE     = "On"
TRIGGER_SOURCE_VALUE   = "LinkTrigger0"

ENABLE_AUX_POLL_DEBUG = False

# MIL.MappControl(MIL.M_DEFAULT, MIL.M_ERROR, MIL.M_PRINT_DISABLE) # 에러 메시지 제거하기 위함이었으나 오히려 이게 에러 발생

# ============================
# 2. 유틸 함수들
# ============================
def poll_aux_io(MilDig, aux_index: int = 0, duration_sec: int = 5):
    aux_const = MIL.M_AUX_IO0 + aux_index
    last_state = None
    t0 = time.time()
    print(f"[INFO] Polling AUX IO{aux_index} status for {duration_sec} sec...")
    while time.time() - t0 < duration_sec:
        state = MIL.MdigInquire(MilDig, MIL.M_IO_STATUS + aux_const, None)
        if state != last_state:
            print(f"[DBG] AUX IO{aux_index} state changed: {last_state} -> {state}")
            last_state = state
        time.sleep(0.001)  # 1ms

def configure_camera_trigger_genicam(MilDig):
    print("[INFO] Configuring camera GenICam trigger features...")

    def set_str_feature(name: str, value: str):
        try:
            MIL.MdigControlFeature(
                MilDig,
                MIL.M_FEATURE_VALUE,          # ControlType
                MIL.MIL_TEXT(name),           # FeatureName
                MIL.M_TYPE_STRING,            # UserVarType
                MIL.MIL_TEXT(value),          # UserVarPtr
            )
            print(f"  - {name} = {value}")
        except Exception as e:
            print(f"[WARN] Failed to set feature {name} -> {value}: {e}")

    set_str_feature("AcquisitionMode", "Continuous")
    set_str_feature("TriggerSelector", TRIGGER_SELECTOR_VALUE)
    set_str_feature("TriggerMode",     TRIGGER_MODE_VALUE)
    set_str_feature("TriggerSource",   TRIGGER_SOURCE_VALUE)

def configure_digitizer_tl_trigger(MilDig, aux_index: int = 0):
    """
    Capture Works 'Digital I/O control' 설정을 코드로 옮긴 것.
    """
    print(f"[INFO] Configuring digitizer TL Trigger from Aux IO{aux_index}...")

    aux_const = MIL.M_AUX_IO0 + aux_index

    # 0) Grab 모드를 비동기/트리거 기반으로 설정
    try:
        MIL.MdigControl(MilDig, MIL.M_GRAB_MODE, MIL.M_SYNCHRONOUS)
        print("  - M_GRAB_MODE = M_SYNCHRONOUS")
    except Exception as e:
        print("[WARN] M_GRAB_MODE 설정 실패(무시 가능):", e)

    # 1) Aux IO n을 입력 모드로 설정
    try:
        MIL.MdigControl(
            MilDig,
            MIL.M_IO_MODE + aux_const,
            MIL.M_INPUT,
        )
        print(f"  - IO mode: Aux IO{aux_index} -> INPUT")
    except Exception as e:
        print(f"[WARN] Failed to set IO mode of Aux IO{aux_index} to INPUT: {e}")

    # 2) TL Trigger source를 Aux IO n으로 설정
    try:
        tl_trigger_const = MIL.M_TL_TRIGGER
        MIL.MdigControl(
            MilDig,
            MIL.M_IO_SOURCE + tl_trigger_const,
            aux_const,
        )
        print(f"  - IO source: TL Trigger <- Aux IO{aux_index}")
    except Exception as e:
        print(f"[WARN] Failed to set IO source of TL Trigger from Aux IO{aux_index}: {e}")
        tl_trigger_const = MIL.M_TL_TRIGGER

    # 3) Interrupt activation: Any edge
    try:
        MIL.MdigControl(
            MilDig,
            MIL.M_IO_INTERRUPT_ACTIVATION + tl_trigger_const,
            MIL.M_ANY_EDGE,
        )
        print("  - IO interrupt activation: Any edge")
    except Exception as e:
        print(f"[WARN] Failed to set IO interrupt activation (Any edge): {e}")

    # 4) IO interrupt state: Disable
    try:
        MIL.MdigControl(
            MilDig,
            MIL.M_IO_INTERRUPT_STATE + tl_trigger_const,
            MIL.M_DISABLE,
        )
        print("  - IO interrupt state: DISABLE")
    except Exception as e:
        print(f"[WARN] Failed to set IO interrupt state (DISABLE): {e}")

    try:
        MIL.MdigControl(
            MilDig,
            MIL.M_SOURCE_SIZE_Y,
            N_LINES,
        )
        print(f"  - Set frame height {N_LINES}")
    except Exception as e:
        print(f"[WARN] Failed to set frame height: {e}")

def configure_aux_io5_output(MilDig):
    """
    Aux IO 5번을 Output 모드로 설정하고, 요청하신 인터럽트 설정을 적용합니다.
    """
    print(f"[INFO] Configuring Aux IO{AUX_INDEX_OUTPUT} as OUTPUT...")
    
    aux_out_const = MIL.M_AUX_IO0 + AUX_INDEX_OUTPUT

    try:
        # 1. Mode 설정: Output
        MIL.MdigControl(MilDig, MIL.M_IO_MODE + aux_out_const, MIL.M_OUTPUT)
        print(f"  - Aux IO{AUX_INDEX_OUTPUT} Mode: OUTPUT")

        # 2. Interrupt State: Enable (요청사항)
        MIL.MdigControl(MilDig, MIL.M_IO_INTERRUPT_STATE + aux_out_const, MIL.M_ENABLE)
        print(f"  - Aux IO{AUX_INDEX_OUTPUT} Interrupt State: ENABLE")

        # 3. Interrupt Activation: Rising Edge (요청사항)
        MIL.MdigControl(MilDig, MIL.M_IO_INTERRUPT_ACTIVATION + aux_out_const, MIL.M_EDGE_RISING)
        print(f"  - Aux IO{AUX_INDEX_OUTPUT} Interrupt Activation: RISING EDGE")

        # 4. 초기 상태: OFF
        MIL.MdigControl(MilDig, MIL.M_IO_STATE + aux_out_const, MIL.M_OFF)
        print(f"  - Aux IO{AUX_INDEX_OUTPUT} Initial State: OFF")

    except Exception as e:
        print(f"[ERROR] Failed to configure Aux IO{AUX_INDEX_OUTPUT}: {e}")

# ============================
# 3. 메인 함수
# ============================

def main():
    print("[INFO] Line-scan capture start (continuous mode with IO toggle)")

    MilApp  = MIL.M_NULL
    MilSys  = MIL.M_NULL
    MilDig  = MIL.M_NULL
    MilDest = MIL.M_NULL

    try:
        # ----------------------------
        # 3-1) MIL Application / System / Digitizer 할당
        # ----------------------------
        MilApp = MIL.MappAlloc(MIL.MIL_TEXT("M_DEFAULT"), MIL.M_DEFAULT, None)
        MilSys = MIL.MsysAlloc(MilApp, MIL.M_SYSTEM_DEFAULT, MIL.M_DEFAULT, MIL.M_DEFAULT, None)
        
        DCF_NAME = MIL.MIL_TEXT(DCF_NAME_STR)
        MilDig = MIL.MdigAlloc(MilSys, MIL.M_DEFAULT, DCF_NAME, MIL.M_DEFAULT, None)
        print(f"[INFO] Digitizer allocated with DCF = {DCF_NAME_STR!r}")

        # (옵션) Grab timeout 설정 (10초)
        if hasattr(MIL, "M_GRAB_TIMEOUT"):
            MIL.MdigControl(MilDig, MIL.M_GRAB_TIMEOUT, 10000)

        # ----------------------------
        # 3-2) 트리거 및 IO 설정
        # ----------------------------
        # A. 카메라 및 입력 트리거(Aux 0) 설정
        configure_camera_trigger_genicam(MilDig)
        configure_digitizer_tl_trigger(MilDig, AUX_INDEX_TRIGGER)

        # B. 출력 IO(Aux 5) 설정 (추가된 기능)
        configure_aux_io5_output(MilDig)

        if ENABLE_AUX_POLL_DEBUG:
            poll_aux_io(MilDig, AUX_INDEX_TRIGGER, duration_sec=5)

        # ----------------------------
        # 3-3) 이미지 사이즈 확인 및 버퍼 할당
        # ----------------------------
        line_width   = MIL.MdigInquire(MilDig, MIL.M_SIZE_X, None)
        frame_height = MIL.MdigInquire(MilDig, MIL.M_SIZE_Y, None)
        num_bands    = MIL.MdigInquire(MilDig, MIL.M_SIZE_BAND, None)
        print(f"[INFO] SIZE_X = {line_width}, SIZE_Y = {frame_height}")

        MilDest = MIL.MbufAllocColor(
                MilSys,
                num_bands,      
                line_width,     
                frame_height,   
                BITS_PER_CHANNEL + MIL.M_UNSIGNED,
                MIL.M_IMAGE | MIL.M_GRAB,
                None
            )
        
        # ----------------------------
        # 3-4) 연속 Grab 루프
        # ----------------------------
        root, ext = os.path.splitext(OUTPUT_PATH)
        if not ext:
            ext = ".tif"

        try:
            MIL.MdigControl(MilDig, MIL.M_GRAB_STOP, MIL.M_DEFAULT)
            time.sleep(0.1)
        except:
            pass

        print("[INFO] Ready to grab frames continuously.")
        print(f"[INFO] Trigger: Aux IO{AUX_INDEX_TRIGGER}, Toggle Output: Aux IO{AUX_INDEX_OUTPUT}")
        print("[INFO] Press Ctrl+C to stop.\\n")

        frame_idx = 0
        
        # Aux IO 5번의 현재 상태 추적용 변수 (초기값 OFF)
        current_io5_state = MIL.M_OFF 

        while True:
            frame_idx += 1
            print(f"[INFO] ===== Frame {frame_idx} =====")
            print(f"[INFO] Waiting for triggers on Aux IO{AUX_INDEX_TRIGGER}...")

            # 1. 이미지 그랩 (Aux 0 입력 대기)
            try:
                MIL.MdigGrab(MilDig, MilDest)
            except Exception as e:
                print(f"[ERROR] MdigGrab failed at frame {frame_idx}: {e}")
                break

            # 2. 이미지 저장
            out_path = f"{root}_{frame_idx:05d}{ext}"
            try:
                if os.path.exists(out_path):
                    os.remove(out_path)
                MIL.MbufExport(MIL.MIL_TEXT(out_path), MIL.M_TIFF, MilDest)
                print(f"[INFO] Saved: {out_path}")
                
            except Exception as e:
                print(f"[ERROR] Failed to save frame {frame_idx}: {e}")
                break
                
            # 3. Aux IO 5 상태 토글 (ON <-> OFF)
            try:
                # 현재 상태의 반대값 계산
                next_state = MIL.M_ON if current_io5_state == MIL.M_OFF else MIL.M_OFF
                
                # IO 상태 적용 (M_IO_STATE를 사용하여 출력 제어)
                MIL.MdigControl(MilDig, MIL.M_IO_STATE + MIL.M_AUX_IO5, next_state)
                
                # 상태 변수 업데이트 및 로그 출력
                current_io5_state = next_state
                state_str = "ON" if current_io5_state == MIL.M_ON else "OFF"
                print(f"[INFO] Aux IO{AUX_INDEX_OUTPUT} Toggled -> {state_str}")

            except Exception as e:
                print(f"[ERROR] Failed to toggle Aux IO{AUX_INDEX_OUTPUT}: {e}")
                # IO 에러가 나더라도 루프를 계속 돌지 말지 결정 (여기선 로그만 찍고 계속 진행)

    except KeyboardInterrupt:
        print("\\n[INFO] KeyboardInterrupt received. Stopping...")

    except Exception as e:
        print("[ERROR] Exception:", e)

    finally:
        print("[INFO] Freeing MIL resources...")

        # 진행 중인 그랩 중단 (혹시 모를 비동기 그랩)
        try:
            if MilDig not in (None, MIL.M_NULL):
                MIL.MdigHalt(MilDig)
                print("[INFO] MdigHalt called.")
        except Exception as e:
            print(f"[WARN] MdigHalt failed or not needed: {e}")

        # Digitizer 먼저 free
        try:
            if MilDig not in (None, MIL.M_NULL):
                MIL.MdigFree(MilDig)
                print("[INFO] Freed MilDig.")
                MilDig = MIL.M_NULL
        except Exception as e:
            print(f"[WARN] Failed to free MilDig: {e}")

        # 그 다음 이미지 버퍼 free
        try:
            if MilDest not in (None, MIL.M_NULL):
                MIL.MbufFree(MilDest)
                print("[INFO] Freed MilDest.")
                MilDest = MIL.M_NULL
        except Exception as e:
            print(f"[WARN] Failed to free MilDest: {e}")

        # System / App free
        try:
            if MilSys not in (None, MIL.M_NULL):
                MIL.MsysFree(MilSys)
                print("[INFO] Freed MilSys.")
                MilSys = MIL.M_NULL
        except Exception as e:
            print(f"[WARN] Failed to free MilSys: {e}")

        try:
            if MilApp not in (None, MIL.M_NULL):
                MIL.MappFree(MilApp)
                print("[INFO] Freed MilApp.")
                MilApp = MIL.M_NULL
        except Exception as e:
            print(f"[WARN] Failed to free MilApp: {e}")

        print("[INFO] Done.")

if __name__ == "__main__":
    if len(sys.argv) == 2:
        try:
            N_LINES = int(sys.argv[1])
            print(f"[INFO] N_LINES overridden: {N_LINES}")
        except:
            pass
    main()