전자회로 입문 — 센서 입력 검증 · 앨리어싱과 필터의 불가능한 요구 · 실습
온실 팬의 진동이 다른 이름으로 나타난다
목표
온실 팬의875Hz 진동이125Hz로 보이는 상황을 재현하고, 아날로그 필터와 관측률을 바꾸어 구별 가능한 범위를 보고합니다.
왜 중요한가
앞의 RC·ADC 획득 시간 단원과 Python 함수·list·dict·예외·파일 처리, 복소수·삼각함수를 알고 진행하세요. 실습은 예상120분입니다. 기본60분 세션이 끝나기 전에 +시간으로 연장하세요. 최대180분이며 종료하면 파일이 사라집니다. 코드와 보고서를 별도로 보관하세요. 실제 장비 연결이나 전압 인가는 하지 않습니다.
환경과 산출물
작성할 파일은 /root/aliasing/analyze.py 하나입니다. Python3·ngspice42는 이미지에 있고 외부 다운로드·추가 권한은 필요 없습니다. /opt/lab/fixtures/aliasing/aliaswave.py를 sys.path에 해당 폴더를 추가한 뒤 import하세요. 이 도우미의 capture(cfg,새폴더)는 실제 회로를 실행해 새폴더/trace.tsv 경로를 반환합니다. input.cir·config.json·solver.log도 함께 남깁니다. 부모 폴더는 이미 있어야 하며 기존 폴더는 덮지 않습니다.
cfg는 정확한3키 dict입니다. input_hz는125·375·625·875 중 하나, cutoff_hz는None·300·80 중 하나, dt_s는2e-6·1e-6 중 하나입니다. 수치의 bool·문자열·NaN·무한대는 거절합니다. 회로는 1V 코사인 입력과1kΩ 직렬 저항, C=1/(2π1000fc)의 접지 커패시터입니다. None이면 C 대신10¹²Ω 부하입니다. 전체90ms를 계산하며 입력·출력을 함께 기록합니다. 이 회로에 앞 단원의 획득 스위치 모델은 들어 있지 않습니다.
제공 load_trace(path)는 최대16MiB의 파형을 검증해 시간·입력·출력3열 tuple의 list를 반환합니다. 헤더는 time v(in) v(out), 데이터는2~200000행입니다. 시간은0이상·엄격히 증가하고 첫 시간≤1µs, 마지막 시간은90ms에서1e-12초 이내여야 합니다. 모든 값은 유한하고 전압 절댓값≤1e6V입니다. 손상은 ValueError, OS 파일 오류는 그대로 전달됩니다. resample은 이 검증을 통과한 rows를 받으므로 매 호출마다 파일 검사를 반복할 필요 없습니다.
함수 수치 계약
명시한 수치는 int 또는 float만 허용하고 bool·문자열·NaN·무한대·float 변환 범위 초과는 ValueError입니다. 원 입력과 원 파일은 수정하지 않습니다. 반환 dict의 키는 아래와 같고 불필요한 필드를 추가하지 않습니다.
| 함수 | 입력 범위와 반환 |
| --- | --- |
| alias_hz | 0≤freq≤1e9, 0<fs≤1e9, 반환 min(freq%fs,fs−freq%fs) |
| resample | 100≤fs≤8000, start≥0, count는 실제int2~256; 시각 start+i/fs의 출력 전압 목록 |
| spectrum | values는 정확한list, 짝수 길이2~256, 각 전압 절댓값≤1e6; 100≤fs≤8000 |
| rc_gain | 0≤freq≤1e9, cutoff=None이면1, 아니면1e-6≤cutoff≤1e9 |
| rc_window | 1e-6≤wanted<nuisance≤1e9; min_gain·max_gain은1e-6~1−1e-6 포함 |
spectrum의 각 항목은 bin(0부터N/2), hz=k*fs/N, amplitude_v입니다. Z=(1/N)Σvalues[i]*exp(−2jπki/N), 진폭은 k=0 또는N/2이면abs(Z), 나머지는2*abs(Z)입니다. 동률 피크는 첫 번째 빈을 선택합니다.
analyze는 fs=1000 또는4000만 받습니다. 기본 시작0.024초·64표본으로 분석하며 반환 키는 fs_hz, start_s, n, samples_v, spectrum, peak_hz, peak_amplitude_v입니다. peak_hz는 최대 진폭의 빈 주파수이지만 그 진폭≤1e-6V이면None입니다. peak_amplitude_v는 그 경우에도 실제 최댓값을 남깁니다. 파일 읽기 오류를 임의의 무신호로 바꾸지 않습니다.
compare_reports는 정상 analyze 보고를 받습니다. fs_hz·start_s·n 중 하나라도 다르거나 samples_v 길이가 n과 다르거나 비어 있으면 ValueError입니다. 반환 max_sample_difference_v는 표본끼리 절대 차이의 최대, indistinguishable은 그 값≤1e-4V의 bool입니다. 진폭 스펙트럼만 비교하지 않습니다.
rc_gain은 1/sqrt(1+(freq/cutoff)2)입니다. rc_window의 반환 min_cutoff_hz=wanted/sqrt(1/min_gain2−1), max_cutoff_hz=nuisance/sqrt(1/max_gain**2−1), feasible=(하한≤상한)입니다. feasible=False도 정상 결과이며 요구를 변경해서True로 만들지 않습니다.
assess_filter는 정상 analyze 보고 두 개를 받고 각각 peak_amplitude_v를0이상의 유한 수치로 확인합니다. 반환 키는 wanted_amplitude_v, nuisance_amplitude_v, passband_ok(첫 진폭≥.9), stopband_ok(둘째≤.1), all_pass(두 조건 AND)입니다. 이 판정은1V 입력·정수 주기 관측이라는 이번 실험의 계약입니다.
종합 보고와 CLI 계약
campaign(inputs,runner)의 inputs는 정확한list이며 [125,875] 또는 [375,625]만 받습니다. 원소는 위 수치 규칙을 따릅니다. 순서상 앞이 원하는 입력이고 뒤가 방해입니다. 입력 전체를 먼저 검증한 다음 runner(cfg)를 호출합니다. runner는 파형 경로를 반환하며 예외는 호출자에게 전달합니다. 원 입력은 보존합니다.
실행은 cutoff None→300→80, 각 cutoff에서 입력 쌍의 순서, 각 입력에서 dt2e-6→1e-6 순서입니다. 총12번이며 파형마다 analyze(path,1000)와 analyze(path,4000)를 실행합니다. 각6조건의 trials 항목은 다음과 같습니다.
- input_hz, cutoff_hz: 실제 회로 조건
- coarse, fine: 각각 기본·세밀 파형의 보고를 low(1kHz)·high(4kHz) 두 키에 저장한 dict
- difference_v: coarse와fine을 같은 관측률끼리 compare_reports한 차이 중 최댓값
- converged: difference_v≤1e-4V
- model_consistent: 네 분석 모두 peak_hz가 alias_hz(input_hz,fs_hz)와 같고, 진폭이 rc_gain(input_hz,cutoff_hz)에서1e-4V 이내일 때만True
filter_results는 cutoff 순서로3개입니다. 각 항목의 키는 cutoff_hz, assessment(두 입력 coarse.low의 assess_filter), fine_assessment(두 입력 fine.low의 assess_filter), valid(두 입력 모두 수렴하고 모델 일치), eligible(valid이고 두 assessment의 all_pass도 모두True)입니다.
최상위 반환 키는 schema=1, trials, filter_results, single_rc_window=rc_window(*inputs), collision(무필터 두 입력의 coarse.low를 compare_reports), scope='ideal-rc-and-uniform-sampling-only'입니다. 단일 RC 후보가 없으면 eligible을False로 그대로 남깁니다. 오류를 빈 trials로 숨기지 않습니다.
main(argv)는 입력 쌍 JSON 파일과 아직 없는 출력 폴더 경로의 두 인자를 받습니다. JSON과 입력 쌍을 먼저 검증하고 출력 폴더를 만듭니다. 부모는 이미 있어야 합니다. runner 호출 순서대로 그 아래 run-00..run-11의 새 폴더에 제공 aliaswave.capture를 호출합니다. campaign 결과를 stdout에 JSON한 줄로 출력하고0을 반환합니다. 인자·설정·파일·실행 Exception은 {"error":"analysis_failed"} 한 줄과2를 반환하고 traceback을 출력하지 않습니다. 이미 만든 새 증거는 실패해도 지우지 않습니다. import 시에는 실행하지 않고 직접 실행일 때만 main의 반환값으로 종료합니다.
제공 입력 예제는 /opt/lab/fixtures/aliasing/inputs.json입니다. 최종 단계에서 직접 실행하려면 새 출력 이름을 고릅니다. 보고서 리다이렉트도 기존 자료를 덮지 않도록 주의하세요.
python3 /root/aliasing/analyze.py /opt/lab/fixtures/aliasing/inputs.json /root/aliasing/trial-01 > /root/aliasing/report-01.json단계
1. 빠른 신호가 얻는 다른 이름: /root/aliasing/analyze.py에 alias_hz(freq,fs)를 작성합니다. f mod fs와 첫 나이퀴스트 구간의 대칭으로 접힌 주파수를 반환합니다. 여러 구간·DC·경계를 처리하며 아래 수치 계약을 따릅니다.
2. 행 번호 대신 관측 시각을 쓴다: 같은 파일에 resample(rows,fs,start=.024,count=64)를 추가합니다. 검증된 파형에서 start+i/fs의 출력 전압을 선형 보간한 list를 반환합니다. 끝점은 포함하고 범위 밖 관측은 ValueError로 거절합니다.
3. 진폭이 두 배가 되는 빈을 구별한다: spectrum(values,fs)를 추가합니다. 1/N으로 정규화한 DFT에서 k=0..N/2의 bin·hz·amplitude_v를 담은 dict 목록을 만듭니다. 내부 빈만2배, DC와 나이퀴스트는1배입니다.
4. 두 관측률로 같은 파형을 다시 읽는다: analyze(path,fs)를 추가합니다. 제공 load_trace로 파일을 읽고 관측·DFT를 연결하여 아래 보고 계약을 반환합니다. fs는1000 또는4000이며 실제 표본 시각도 바뀌어야 합니다.
5. 같은 스펙트럼과 같은 표본을 구분한다: compare_reports(a,b)를 추가합니다. 관측 시계3필드와 표본 수를 확인한 뒤 최대 표본 차이와100µV 이내 여부를 반환합니다. 다른 시계나 빈 표본은 ValueError입니다.
6. 불가능한 필터 요구를 계산한다: rc_gain(freq,cutoff)와 rc_window(wanted,nuisance,min_gain=.9,max_gain=.1)를 추가합니다. 원 입력 주파수에서 이득을 계산하고 두 대역 요구의 fc 허용 구간과 교집합 존재를 반환합니다.
7. 원하는 신호도 지키는지 묻는다: assess_filter(wanted,nuisance)를 추가합니다. 두 analyze 보고의 최대 진폭으로 원하는 신호≥0.9V, 방해≤0.1V를 각각 판정하고 두 조건의 AND를 반환합니다.
8. 회로·시계·수렴을 증거로 묶는다: campaign(inputs,runner), main(argv), CLI 진입점을 추가합니다. 아래 호출 순서대로12회로를 실행하고24분석을 연결합니다. 수렴·모델·요구를 구별하며 실제 파형과 JSON 보고를 보존합니다.
참고
채점은 단계별 함수와 원 자료 보존을 확인합니다. 마지막에는 임시 폴더에서 실제 ngspice·별도 CLI도 실행하고 저장한 파형과 보고서를 대조합니다. 검사 전용 폴더만 정리하며 학생 파일은 바꾸지 않습니다. 도우미의 입력 제약은 실험 범위를 명확히 하기 위한 것이지 모든 ADC·필터의 모델이 아닙니다. 실제 보드의 잡음·지터·양자화·온도별 신뢰성은 검증하지 않습니다.
단계 8개
- 빠른 신호가 얻는 다른 이름
- 행 번호 대신 관측 시각을 쓴다
- 진폭이 두 배가 되는 빈을 구별한다
- 두 관측률로 같은 파형을 다시 읽는다
- 같은 스펙트럼과 같은 표본을 구분한다
- 불가능한 필터 요구를 계산한다
- 원하는 신호도 지키는지 묻는다
- 회로·시계·수렴을 증거로 묶는다