<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>nwave hardware</title>
    <link>https://nwave.tistory.com/</link>
    <description>전기전자공학도의 실전 하드웨어 기록</description>
    <language>ko</language>
    <pubDate>Thu, 3 Sep 2026 22:35:18 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>nanowave</managingEditor>
    <item>
      <title>DWM1001 두 대로 UWB 거리 측정하기 - MATLAB 로깅과 정적 시험</title>
      <link>https://nwave.tistory.com/4</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;UAM 프로젝트에서 보조항법용 거리 센서로 UWB를 사용하기로 하면서 DWM1001-DEV를 처음 만져봤다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;최종 구성에서는 여러 앵커의 거리값을 동시에 받아 위치를 계산해야 한다. 하지만 처음부터 측위 시스템 전체를 구성하면 문제가 생겼을 때 원인을 나누기 어렵다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이번에는 DWM1001-DEV 두 대 사이에서 거리값이 계속 나오는지, 그리고 그 값을 PC로 받아 저장할 수 있는지부터 확인했다. 두 보드의 역할과 UART 출력을 확인한 뒤 MATLAB 로거를 구성했고, 브링업이 끝난 뒤에는 0.5 m, 1.0 m, 1.5 m에서 간단한 정적 측정도 진행했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K0arh/dJMcadi0uAR/7LIWGYwsdE84oRKrKcXCA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K0arh/dJMcadi0uAR/7LIWGYwsdE84oRKrKcXCA1/img.png&quot; data-alt=&quot;DWM1001-DEV 두 대와 줄자를 이용한 실내 바닥 LoS 측정 배치이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K0arh/dJMcadi0uAR/7LIWGYwsdE84oRKrKcXCA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK0arh%2FdJMcadi0uAR%2F7LIWGYwsdE84oRKrKcXCA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;602&quot; height=&quot;339&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;788&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;DWM1001-DEV 두 대와 줄자를 이용한 실내 바닥 LoS 측정 배치이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;UWB와 DWM1001&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;UWB(Ultra-Wideband)는 넓은 대역폭의 무선 신호를 사용하는 기술이다. 위치 측정에서는 수신 신호 세기만 비교하기보다, 전파가 장치 사이를 오가는 데 걸린 시간을 이용해 거리를 추정할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 사용한 TWR(Two-Way Ranging)은 두 장치가 신호를 주고받은 왕복 시간을 바탕으로 거리를 계산하는 방식이다. 한쪽 장치가 측정을 시작하는 initiator, 다른 장치가 응답하는 responder 역할을 맡는다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;DWM1001은 DW1000 UWB 송수신기와 nRF52832 마이크로컨트롤러, UWB&amp;middot;Bluetooth 안테나 등을 하나의 모듈에 넣은 제품이다. 이번에 사용한 DWM1001-DEV는 여기에 USB 연결과 온보드 J-Link 디버거 등을 추가한 개발보드라서 PC에 연결해 실험하기 편한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 사용한 두 보드에는 이미 initiator와 responder용 펌웨어가 올라가 있었다. 따라서 이번 글에서는 펌웨어를 빌드하고 플래시하는 과정이 아니라, 현재 보드의 역할을 확인한 뒤 UART로 출력되는 거리값을 MATLAB에 저장하는 과정부터 다룬다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;DWM1001 두 대로 TWR 거리값 받기&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;처음 구성은 단순했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;COM5: initiator, 거리 계산과 UART 출력&lt;/li&gt;
&lt;li&gt;COM4: responder, initiator 요청에 응답&lt;/li&gt;
&lt;li&gt;PC: MATLAB으로 COM5의 거리값 수집&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;COM 번호는 보드에 고정된 값이 아니라 당시 제 PC에서 할당된 번호이다. 다른 환경에서는 실제로 잡힌 포트를 다시 확인해야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 단계에서는 TWR 원리를 직접 구현하지 않았다. 두 포트의 출력을 비교해 보드 역할을 구분하고, COM5 쪽에서 거리값이 계속 갱신되는지를 먼저 확인했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;COM5에서는 아래와 같은 문자열이 반복해서 들어왔다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;Distance A : 0.578477
Transmission # : 204
Transmission # : 205
Reception # : 95
Distance A : 0.406498
Transmission # : 206
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;송신 횟수가 증가하고 응답을 받은 뒤 Distance A가 계산됐다. 반면 responder인 COM4에서는 같은 형태의 거리 출력이 계속 나타나지 않았다. 한쪽 포트가 조용하다고 바로 통신 실패로 판단하면 안 됐던 이유이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기까지 확인하면 적어도 아래 구간은 연결됐다고 볼 수 있었다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;DWM1001-DEV initiator &amp;harr; DWM1001-DEV responder
                    &amp;darr;
                 거리 계산
                    &amp;darr;
               COM5 UART 출력
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;MATLAB에서는 raw UART부터 확인했다&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;터미널에서 숫자가 나온다고 끝은 아니었다. 나중에 정확도와 흔들림을 비교하려면 화면을 보고 값을 적는 대신 원시 데이터(raw data)가 남아 있어야 했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;처음부터 CSV 저장과 통계 계산을 한꺼번에 붙이면 문제가 생겼을 때 포트 연결이 잘못된 것인지, 문자열 파싱이 잘못된 것인지 구분하기 어렵다. 그래서 MATLAB에서는 세 단계로 나눠 확인했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;1. 15초 동안 원문 그대로 읽기&lt;/p&gt;
&lt;pre class=&quot;lisp&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;uwb = serialport(&quot;COM5&quot;, 115200);
configureTerminator(uwb, &quot;LF&quot;);
flush(uwb);

t0 = tic;
while toc(t0) &amp;lt; 15
    line = readline(uwb);
    disp(line);
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서는 COM 포트, 115200 baud, LF 줄바꿈이 맞는지만 봤다. Transmission, Reception, Distance A가 MATLAB Command Window에 그대로 나오면 PC와 보드 사이의 serial 연결은 된 상태이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;2. Distance A만 숫자로 바꾸기&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;raw 문자열이 들어오는 것을 확인한 다음에는 Distance A가 포함된 줄만 골랐다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;s = serialport(&quot;COM5&quot;, 115200, &quot;Timeout&quot;, 2);
configureTerminator(s, &quot;LF&quot;);
flush(s);

while true
    line = readline(s);

    if contains(line, &quot;Distance A&quot;)
        parts = split(line, &quot;:&quot;);
        distance_m = str2double(strtrim(parts(2)));
        fprintf(&quot;거리: %.3f m\n&quot;, distance_m);
    end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 Distance A : 0.406498가 들어오면 콜론 뒤의 숫자만 꺼내 0.406498 m로 저장하는 방식이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;3. 정적 시험용 로거로 확장하기&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 이 수신 코드를 uwb_static_test.m으로 확장했다. 정적 시험용 스크립트는 ChatGPT로 초안을 작성했고, 실제 DWM1001 UART 출력 형식과 시험 조건을 반영해 실행&amp;middot;검증했다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;DWM1001 TWR
    &amp;darr;
UART 거리 출력
    &amp;darr;
MATLAB 실시간 수신
    &amp;darr;
time_s와 기준거리 기록
    &amp;darr;
측정 로그 CSV 저장
    &amp;darr;
통계 계산과 그래프 저장
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 단계까지 만들어두니 이후에는 기준거리와 시험 이름만 바꿔 같은 형식으로 데이터를 모을 수 있었다. 센서가 숫자를 한 번 출력하게 만드는 데서 끝내지 않고, 같은 조건으로 반복 저장할 수 있는 형태까지 연결한 셈이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사용한 전체 MATLAB 코드는 아래 파일에 첨부했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/brWP14/dJMcaikcvwp/wekhcvBTDLhI8DNKb21ya1/uwb_static_test.m?attach=1&amp;amp;knm=tfile.m&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;uwb_static_test.m&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.01MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;측정 파일은 어떻게 남겼나&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;한 번 측정하면 MATLAB Current Folder에 같은 시험 이름과 timestamp를 공유하는 세 종류의 결과가 생성됐다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;[MATLAB Current Folder]
├─ uwb_static_test.m
├─ LOS_1m_run1_20260805_231554.csv
├─ LOS_1m_run1_summary_20260805_231554.csv
└─ LOS_1m_run1_graph_20260805_231554.png
&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style3&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;파일&lt;/td&gt;
&lt;td&gt;저장 내용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;측정 로그 CSV&lt;/td&gt;
&lt;td&gt;샘플별 시간, 기준거리, 측정거리와 오차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;summary CSV&lt;/td&gt;
&lt;td&gt;표본 수, 평균, Bias, 표준편차, RMSE, P95, 갱신률과 측정 간격&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;graph PNG&lt;/td&gt;
&lt;td&gt;시간에 따른 측정거리, 실제 거리와 측정 평균&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;측정 로그 CSV의 열은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;test_name
time_s
true_distance_m
measured_distance_m
error_m
absolute_error_m
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;파일명 앞부분에는 LOS_거리_run번호, 뒤에는 YYYYMMDD_HHMMSS 형식의 저장 시각을 붙였다. 같은 test_name으로 다시 실행해도 이전 결과를 덮어쓰지 않고 별도 파일로 남길 수 있었다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;0.5 m / 1.0 m / 1.5 m에서 직접 재봤다&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;통신과 로깅까지 확인한 뒤 간단한 정적 거리 측정을 진행했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두 보드를 실내 바닥에 놓고 LoS(Line of Sight), 즉 두 장치 사이를 사람이 가리지 않는 조건에서 측정했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기준거리: 0.5 m / 1.0 m / 1.5 m&lt;/li&gt;
&lt;li&gt;반복 횟수: 거리마다 run1, run2&lt;/li&gt;
&lt;li&gt;측정 시간: 한 run당 약 30초&lt;/li&gt;
&lt;li&gt;기준점: 두 보드의 같은 안테나 위치&lt;/li&gt;
&lt;li&gt;측정 중 보드 고정&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;줄자의 같은 눈금을 두 보드의 안테나 기준점에 맞췄다. 이 실험은 DWM1001의 공식 성능을 검증하려는 목적이 아니라, 현재 장비와 현재 환경에서 어느 정도 값이 나오는지 확인하는 초기 측정에 가까웠다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;측정 결과&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두 번의 유효 측정 데이터를 거리별로 합쳐 계산한 결과이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Bias는 측정값이 기준거리에서 어느 방향으로 얼마나 치우쳤는지 보여주는 평균 오차이고, 표준편차는 정지 상태에서 값이 얼마나 흔들렸는지를 나타낸다. RMSE는 전체 오차 크기를, 95% 절대오차는 전체 샘플의 95%가 어느 오차 범위 안에 들어왔는지를 보기 위해 함께 계산했다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 72px;&quot; border=&quot;1&quot; data-ke-style=&quot;style3&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;기준거리&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;샘플 수&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;평균 측정거리&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;Bias&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;표준편차&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;RMSE&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;95% 절대오차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;0.5 m&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;126&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;0.6112 m&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;+11.12 cm&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;4.51 cm&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;12.00 cm&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;18.79 cm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;1.0 m&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;127&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;1.0146 m&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;+1.46 cm&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;2.49 cm&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;2.88 cm&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;6.31 cm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;1.5 m&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;125&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;1.5167 m&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;+1.67 cm&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;3.45 cm&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;3.82 cm&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;7.91 cm&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실제로 들어온 데이터의 평균 갱신률(update rate)은 약 2.1 Hz였다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 52px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 10px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 10px;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;641&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oBaee/dJMcaa7FpaY/KJYEdGt2YCdhkiXFRjheTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oBaee/dJMcaa7FpaY/KJYEdGt2YCdhkiXFRjheTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oBaee/dJMcaa7FpaY/KJYEdGt2YCdhkiXFRjheTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoBaee%2FdJMcaa7FpaY%2FKJYEdGt2YCdhkiXFRjheTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;773&quot; height=&quot;641&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;641&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 10px;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;641&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5Oyqb/dJMcaa7Fpbq/nQRilFIZWUjuAc7K7KusK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5Oyqb/dJMcaa7Fpbq/nQRilFIZWUjuAc7K7KusK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5Oyqb/dJMcaa7Fpbq/nQRilFIZWUjuAc7K7KusK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5Oyqb%2FdJMcaa7Fpbq%2FnQRilFIZWUjuAc7K7KusK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;773&quot; height=&quot;641&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;641&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 10px;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;641&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBmp8r/dJMcaa7Fpb1/WcpXCntiwh2djoRzGc0rSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBmp8r/dJMcaa7Fpb1/WcpXCntiwh2djoRzGc0rSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBmp8r/dJMcaa7Fpb1/WcpXCntiwh2djoRzGc0rSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBmp8r%2FdJMcaa7Fpb1%2FWcpXCntiwh2djoRzGc0rSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;773&quot; height=&quot;641&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;641&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 42px;&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;0.5 m run1. 실제 거리는 0.5 m였지만 평균 측정거리는 약 0.595 m였다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 42px;&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;1.0 m run1. 평균 측정거리는 약 1.015 m였다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 42px;&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;1.5 m run2 재측정본. 평균 측정거리는 약 1.524 m였다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1.0 m와 1.5 m에서는 평균값이 기준거리보다 약 1&amp;ndash;2 cm 크게 나왔다. 반면 0.5 m에서는 평균 측정거리가 0.6112 m로, 두 번의 측정 모두 같은 방향으로 크게 벗어났다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;0.5 m 값은 바로 보정하지 않았다&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;처음 결과를 봤을 때는 일정한 offset을 하나 정해서 빼면 되지 않을까 생각했다. 하지만 아직은 그렇게 처리하지 않았다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 측정은 보드를 실내 바닥에 바로 놓은 상태였다. 바닥과 주변 물체에 의한 다중경로(multipath), 근거리 측정 특성, 안테나 방향, USB 케이블의 영향이 섞였을 수 있다. 현재 데이터만으로는 어느 원인이 지배적이었는지 구분할 수 없다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;무엇보다 1.0 m와 1.5 m의 Bias는 약 1&amp;ndash;2 cm인데 0.5 m에 맞춰 전체 거리에서 10 cm를 빼면 나머지 구간이 오히려 나빠진다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이번 결과는 0.5 m 바닥 배치 조건에서 큰 양의 바이어스가 반복해서 관측됐다는 수준으로만 기록했다. 보정값을 정하기 전에 보드를 바닥에서 띄운 조건과 비교하는 편이 먼저라고 판단했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;두 번 측정했을 때 얼마나 달랐나&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;같은 거리에서 run1과 run2의 평균 측정거리 차이도 비교했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0.5 m: 약 3.18 cm&lt;/li&gt;
&lt;li&gt;1.0 m: 약 0.12 cm&lt;/li&gt;
&lt;li&gt;1.5 m: 약 1.38 cm&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1.0 m에서는 두 번 측정한 평균이 거의 같았다. 반대로 0.5 m는 평균 Bias도 가장 컸고 두 run 사이 차이도 상대적으로 컸다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아직 측정 횟수가 두 번뿐이라 이것만으로 DWM1001의 반복성을 평가하기에는 부족한다. 다만 다음 실험에서 0.5 m 조건을 따로 확인해야 할 이유는 생겼다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;멀쩡히 저장된 데이터도 한 번 버렸다&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1.5 m의 첫 번째 run2를 측정할 때 USB 케이블 장력이 보드를 뒤로 당기면서 보드 자세가 살짝 달라졌다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CSV에는 거리값이 정상적으로 저장됐다. 하지만 run1과 비교하려고 만든 실험에서 장치 배치 조건이 달라졌기 때문에 그대로 사용하기는 어려웠다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 23:18:50에 측정한 첫 번째 run2는 분석에서 제외하고, 보드 위치와 자세를 다시 맞춘 뒤 23:19:56에 측정한 두 번째 run2를 최종 결과에 사용했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나중에 숫자만 보면 첫 번째 파일도 정상 데이터처럼 보였을 것이다. 값이 마음에 들지 않아 제외한 것이 아니라, 실험 조건이 깨졌기 때문에 제외했다는 점을 따로 기록했다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;약 2.1 Hz도 같이 남긴 이유&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정상적인 run의 평균 측정 간격은 약 0.47초였고, 거리값은 평균 약 2.1 Hz로 들어왔다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1.5 m run2에서는 약 1.03초의 공백이 한 번 보였다. 이후 필터나 센서 융합에 연결할 때 UWB 측정값이 항상 같은 주기로 들어온다고 가정하면 안 된다는 뜻이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 측정 로그 CSV에 각 샘플의 time_s를 함께 저장했다. 새로운 거리값이 실제로 들어온 시점을 기준으로 처리해야 같은 값을 여러 번 사용하거나 누락된 샘플을 정상 주기로 착각하는 일을 피할 수 있다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;이번 브링업에서 확인한 범위&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 작업으로 DWM1001-DEV 두 대의 1:1 TWR이 동작하는 것을 확인했다. 거리값을 PC에서 실시간으로 받아 MATLAB으로 저장했고, 0.5 m / 1.0 m / 1.5 m에서 반복 측정할 수 있는 기본 파이프라인도 만들었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다만 여기서 나온 수치를 그대로 DWM1001의 정확도라고 보기는 어렵다. 현재 측정은 아래 조건에서 진행한 초기 실험이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실내&lt;/li&gt;
&lt;li&gt;바닥 배치&lt;/li&gt;
&lt;li&gt;LoS&lt;/li&gt;
&lt;li&gt;1:1 TWR&lt;/li&gt;
&lt;li&gt;각 거리 2회&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;야외나 NLoS에서는 아직 측정하지 않았고, 바닥에서 띄웠을 때 값이 어떻게 달라지는지도 확인하지 않았다. 여러 앵커의 거리값을 동시에 받는 환경 역시 다음 단계에서 별도로 확인해야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;현재 단계에서는 센서 성능을 결론내리기보다 실제 센서 &amp;rarr; 거리 출력 &amp;rarr; 샘플별 측정 로그 &amp;rarr; 분석까지 한 번 연결했다는 데 의미를 두고 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다음에는 바닥 반사의 영향을 줄인 조건에서 다시 측정하고, 여러 거리값을 동시에 받는 쪽으로 넘어갈 예정이다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;참고자료&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.qorvo.com/products/d/da007950&quot;&gt;Qorvo DWM1001 Module Data Sheet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.qorvo.com/products/d/da007952&quot;&gt;Qorvo DWM1001-DEV Development Board Data Sheet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.qorvo.com/products/d/da007972&quot;&gt;Qorvo DWM1001 Firmware User Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://github.com/Decawave/dwm1001-examples&quot;&gt;Decawave DWM1001 simple TWR examples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Measurement &amp;amp; Debug</category>
      <category>Decawave</category>
      <category>DWM1001</category>
      <category>DWM1001-DEV</category>
      <category>MATLAB</category>
      <category>TWR</category>
      <category>UART</category>
      <category>UWB</category>
      <category>UWB거리측정</category>
      <category>시리얼통신</category>
      <category>하드웨어브링업</category>
      <author>nanowave</author>
      <guid isPermaLink="true">https://nwave.tistory.com/4</guid>
      <comments>https://nwave.tistory.com/4#entry4comment</comments>
      <pubDate>Sun, 30 Aug 2026 10:30:02 +0900</pubDate>
    </item>
    <item>
      <title>FPGA Verilog 디버깅 - Claude 답변을 Waveform으로 검증한 과정</title>
      <link>https://nwave.tistory.com/2</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Claude의 RTL 디버깅 활용&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1학기 디지털시스템설계 수업 중 진행한 term project에서 SRCNN 8&amp;times;4 Streamline 가속기 과제를 담당하였습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그 과정에서 Claude에게 RTL 질문을 꽤 많이 하게 되었습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Line Buffer를 BRAM으로 바꿔야 하는지, valid는 왜 어긋나는지, PU2를 둘로 나누는 편이 나은지 같은 문제들이었습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;답변은 빠르게 나왔습니다. 그런데 실제 Vivado 설정을 보지 못한 상태에서 latency를 단정하거나, target FPGA와 맞지 않는 자원 추정을 내놓는 경우도 있었죠.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 답변을 바로 코드에 반영하지는 않았습니다. 확인할 후보를 먼저 뽑고, waveform과 Vivado report에서 하나씩 지워가는 식으로 사용했습니다.&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SRCNN 8&amp;times;4 Streamline 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8&amp;times;4 구조는 PU1(1&amp;rarr;8), PU2(8&amp;rarr;4), PU3(4&amp;rarr;1)를 파이프라인으로 연결합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞 PU의 output valid가 다음 PU의 입력 조건이 되기 때문에, 앞단에서 timing이 조금만 틀어져도 뒤 레이어까지 그대로 이어집니다. Controller부터 전체 코드를 다시 보는 것보다, data와 valid가 어디까지 맞는지 먼저 확인하는 편이 훨씬 빨랐습니다.&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Claude 활용 사례&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Line Buffer의 BRAM 전환 검토&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 받은 제안 중 하나는 Line Buffer를 BRAM으로 옮기는 것이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8&amp;times;4 구조에는 Line Buffer가 여러 개 들어가니 LUTRAM 사용량이 커질 수 있다는 얘기였습니다. 충분히 그럴듯했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 Synthesis 결과를 확인해 보니 현재 구조도 목표 보드의 자원 안에 들어왔습니다. BRAM으로 바꾸면 포트 수와 동시 접근 방식까지 다시 손봐야 했고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 구조를 바꿀 만큼 이득이 있는 상황은 아니었습니다. 그래서 Line Buffer는 그대로 두고, Claude의 자원 계산은 초기 추정치로만 참고했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. PU1 output valid의 3클럭 오차&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 오래 붙잡고 있었던 건 valid timing 문제였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PU1만 따로 시뮬레이션했을 때 o_output_valid가 실제 o_dout보다 3클럭 앞서 있었습니다. 처음에는 Controller가 잘못됐는지, Line Buffer 문제인지, post-process에서 delay가 빠진 건지 구분하기 어려웠습니다. Multiplier IP의 latency도 후보에 들어갔고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude가 가능한 원인을 정리해 주기는 했지만, 목록만 봐서는 어느 쪽이 진짜 원인인지 알 수 없습니다. 그래서 신호를 다음 순서로 내려가며 확인했습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;PU1 output data와 valid&lt;/li&gt;
&lt;li&gt;Line Buffer window와 window valid&lt;/li&gt;
&lt;li&gt;PE multiplier 입력&amp;middot;출력과 valid pipeline&lt;/li&gt;
&lt;li&gt;Vivado Multiplier IP의 pipeline 설정&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 확인하고 나서야 Multiplier IP의 실제 데이터 지연과 RTL이 가정한 valid 지연이 다르다는 것을 찾았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IP 설정과 valid pipeline을 맞춘 뒤 PU1 단독 테스트를 다시 돌렸고, 마지막으로 전체 SRCNN에서도 결과가 맞는지 확인했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 분할 PU2와 단일 PU2 비교&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PU2는 버그를 찾는 문제라기보다 구조를 고르는 문제였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 중간에는 placement와 routing을 분산해 보려고 PU2를 두 개의 8&amp;rarr;2 모듈로 나눴습니다. 반대로 하나의 8&amp;rarr;4 PU2로 합치면 Line Buffer를 공유할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드만 보면 둘 다 나름의 이유가 있습니다. 어느 쪽이 낫다고 말로 정하기 어려워서 두 버전을 직접 구현하고 같은 종류의 Vivado report를 비교했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 이미지는 그때 작업 기록에 남긴 checkpoint입니다. 최종 보고서와 측정 시점이 다르기 때문에, 최종 성능 수치라기보다는 당시 구조를 고른 근거로 보는 편이 맞습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Resource utilization&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 202px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;기존 분할 PU2&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;단일 PU2&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 181px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 181px;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;823&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMaoLj/dJMcadiWX5b/ojVjahgcCqwWmAF8eFQH51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMaoLj/dJMcadiWX5b/ojVjahgcCqwWmAF8eFQH51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMaoLj/dJMcadiWX5b/ojVjahgcCqwWmAF8eFQH51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMaoLj%2FdJMcadiWX5b%2FojVjahgcCqwWmAF8eFQH51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;823&quot; height=&quot;318&quot; data-origin-width=&quot;823&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 181px;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ejMx4e/dJMcag7GWWW/r1sxh3c55ck8DfjrKw7E90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ejMx4e/dJMcag7GWWW/r1sxh3c55ck8DfjrKw7E90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ejMx4e/dJMcag7GWWW/r1sxh3c55ck8DfjrKw7E90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FejMx4e%2FdJMcag7GWWW%2Fr1sxh3c55ck8DfjrKw7E90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;874&quot; height=&quot;376&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;376&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Timing&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;기존 분할 PU2&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;단일 PU2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1551&quot; data-origin-height=&quot;907&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bipROm/dJMcahr7tlm/57Rbnl3w44PvDxdYjIJHWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bipROm/dJMcahr7tlm/57Rbnl3w44PvDxdYjIJHWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bipROm/dJMcahr7tlm/57Rbnl3w44PvDxdYjIJHWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbipROm%2FdJMcahr7tlm%2F57Rbnl3w44PvDxdYjIJHWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1551&quot; height=&quot;907&quot; data-origin-width=&quot;1551&quot; data-origin-height=&quot;907&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1533&quot; data-origin-height=&quot;891&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2KApw/dJMcahr7tls/NBSep67ouG2EeiSbgKA6Vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2KApw/dJMcahr7tls/NBSep67ouG2EeiSbgKA6Vk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2KApw/dJMcahr7tls/NBSep67ouG2EeiSbgKA6Vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2KApw%2FdJMcahr7tls%2FNBSep67ouG2EeiSbgKA6Vk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1533&quot; height=&quot;891&quot; data-origin-width=&quot;1533&quot; data-origin-height=&quot;891&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 106px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;분할 PU2&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;단일 PU2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;LUT&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;20%&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;18%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;LUTRAM&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;9%&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;FF&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;9%&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;WNS&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;2.471 ns&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;2.770 ns&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 checkpoint에서는 단일 PU2 쪽의 LUT&amp;middot;LUTRAM&amp;middot;FF 사용량이 줄었고 WNS도 2.471 ns에서 2.770 ns로 늘었습니다. 구조도 더 단순해 최종안은 단일 PU2로 정했습니다.&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RTL 디버깅 및 검증 순서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 과정을 몇 번 반복하니 Claude에게 무엇부터 물어봐야 하는지도 조금씩 정리됐습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;증상을 숫자로 작성:&lt;/b&gt; 어느 모듈인지, 몇 cycle 차이인지, 기대값과 실제값이 무엇인지 먼저 적었습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;답변을 수정안이 아닌 가설로 설정:&lt;/b&gt; waveform, IP 설정, report 중 어디에서 확인할 수 있는지도 함께 정했습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정상인 구간부터 따라가기:&lt;/b&gt; Input &amp;rarr; Line Buffer &amp;rarr; PE &amp;rarr; post-process &amp;rarr; PU output 순서로 보면 오류가 처음 생기는 지점을 찾기 쉽습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;한 번에 한 군데만 변경:&lt;/b&gt; 단독 시뮬레이션이 통과한 뒤 통합 테스트, reference 비교, Implementation으로 범위를 넓혔습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller와 Line Buffer, PE를 한꺼번에 건드리면 무엇 때문에 결과가 바뀌었는지 알기 어렵습니다. 질문할 때도 이 세 종류의 문제를 섞지 않는 편이 나았습니다.&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Claude 활용 범위&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Claude가 도운 일&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;직접 확인한 일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;원인 후보 정리&lt;/td&gt;
&lt;td&gt;waveform 관찰과 오류 경계 확정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;볼 신호의 순서 제안&lt;/td&gt;
&lt;td&gt;Vivado IP 설정 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BRAM 전환&amp;middot;PU2 분할 같은 대안 제시&lt;/td&gt;
&lt;td&gt;RTL 수정, 단독&amp;middot;통합 시뮬레이션&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검증 항목과 글의 구조 정리&lt;/td&gt;
&lt;td&gt;Synthesis&amp;middot;Implementation 및 최종 구조 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표로 놓고 보면 Claude에게 맡긴 범위는 생각보다 좁습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 처음부터 모든 RTL을 뒤지는 대신, 다음에 볼 신호를 정하는 데는 도움이 됐습니다. 반대로 target FPGA의 자원이나 IP latency처럼 설정에 따라 달라지는 값은 Claude의 설명만으로 결정하지 않았습니다.&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에는 Claude에게 &amp;ldquo;어디를 고치면 될까?&amp;rdquo;라고 바로 묻기보다, &amp;ldquo;3클럭 차이가 생길 수 있는 지점은 어디이고 어떤 신호를 보면 구분할 수 있을까?&amp;rdquo;라고 묻게 됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대화에서 얻고 싶었던 것은 완성된 수정 코드가 아니라 다음 측정 지점이었습니다. 실제 수정과 구조 선택은 waveform, Synthesis, Implementation 결과를 확인한 뒤에 진행했습니다.&lt;/p&gt;</description>
      <category>Measurement &amp;amp; Debug</category>
      <category>AI가속기</category>
      <category>Claude</category>
      <category>FPGA</category>
      <category>RTL디버깅</category>
      <category>rtl설계</category>
      <category>SRCNN</category>
      <category>verilog</category>
      <category>Vivado</category>
      <category>waveform</category>
      <category>생성형ai활용</category>
      <author>nanowave</author>
      <guid isPermaLink="true">https://nwave.tistory.com/2</guid>
      <comments>https://nwave.tistory.com/2#entry2comment</comments>
      <pubDate>Sat, 22 Aug 2026 22:28:18 +0900</pubDate>
    </item>
    <item>
      <title>[수강신청 꿀팁] 수강신청 서버 타이밍을 분석해보자</title>
      <link>https://nwave.tistory.com/1</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;수강신청에서 정말 중요한 시간은 무엇일까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 수강신청 전에 네이비즘 같은 서버시계를 켜놓고 10:00:00.000을 기다린다. 처음에는 나도 &amp;ldquo;누가 더 정확한 시계를 보고 더 빨리 클릭하느냐&amp;rdquo;의 문제라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 구조를 뜯어보면 실제 문제는 조금 다르다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;내가 버튼에서 손가락을 뗌
        &amp;darr;
브라우저가 click 이벤트 처리
        &amp;darr;
수강신청 요청 전송
        &amp;darr;
네트워크 이동
        &amp;darr;
서버가 신청 가능 시간인지 판정
        &amp;darr;
성공 또는 실패 응답
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 서버가 보는 것은 &lt;b&gt;내 눈에 보인 10시 정각&lt;/b&gt;이 아니라, &lt;b&gt;내 요청이 서버의 시간 판정 로직에 도달한 순간&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 단순화하면 다음처럼 생각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;583&quot; data-origin-height=&quot;76&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwiErv/dJMcadv9MPp/rmyOISsHiyHFrYQap8sXXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwiErv/dJMcadv9MPp/rmyOISsHiyHFrYQap8sXXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwiErv/dJMcadv9MPp/rmyOISsHiyHFrYQap8sXXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdwiErv%2FdJMcadv9MPp%2FrmyOISsHiyHFrYQap8sXXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;392&quot; height=&quot;51&quot; data-origin-width=&quot;583&quot; data-origin-height=&quot;76&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &amp;theta;는 PC/보조시계와 신청 서버 clock의 차이, &amp;delta;는 마우스를 뗀 뒤 요청이 서버 판정 지점까지 가는 데 드는 지연이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 목표는 사실 &amp;ldquo;10시에 클릭한다&amp;rdquo;가 아니라 &lt;b&gt;서버가 OPEN이 된 직후 내 요청을 판정하도록 만드는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;네이비즘 서버시계는 정확히 무엇을 보여주는가&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWeW52/dJMcadJI4ky/Cqg6JgARK4myCUbXDkR8cK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWeW52/dJMcadJI4ky/Cqg6JgARK4myCUbXDkR8cK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWeW52/dJMcadJI4ky/Cqg6JgARK4myCUbXDkR8cK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWeW52%2FdJMcadJI4ky%2FCqg6JgARK4myCUbXDkR8cK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1122&quot; height=&quot;232&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이비즘은 매우 편리하다. 화면에서 밀리초 단위로 시간이 계속 흐르고, 수강신청이나 티켓팅 직전에 누구나 쉽게 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 중요한 점이 하나 있다. 네이비즘이 보여주는 시간은 &lt;b&gt;실제 수강신청 애플리케이션 내부의 Gate clock을 직접 화면에 연결한 것&lt;/b&gt;이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹에서 어떤 서버의 시간을 추정할 때는 대략 다음 요소들이 개입할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;대상 서버 또는 앞단 프록시/CDN의 시계
        &amp;darr;
시간을 얻는 방식
        &amp;darr;
네트워크 왕복 지연과 jitter
        &amp;darr;
보정 로직
        &amp;darr;
내 브라우저에서 흐르는 표시 시계
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 화면에 10:00:00.123이 보인다고 해서 그 123 ms가 곧바로 &amp;ldquo;수강신청 백엔드가 현재 123 ms라고 판단하고 있다&amp;rdquo;는 뜻은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 네이비즘이 부정확하다는 뜻은 아니다. &lt;b&gt;네이비즘은 연속적인 실시간 기준시계로 매우 유용하지만, 실제 신청 판정 clock과 동일하다고 가정하면 안 된다는 뜻&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서울과기대 수강신청 서버에서 직접 확인한 것&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chrome DevTools의 Network 탭을 켜고 수강신청 페이지에서 저장 버튼을 눌러봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장을 누르는 순간 첫 번째 JSONMain XHR 요청이 발생했고, Payload에는 다음과 같은 값이 포함되어 있었다.&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;sp_action: MainAction
sp_cmd: requestSuganggBasket
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신청 가능 시간이 아닐 때 이 요청의 서버 응답에는 다음과 같은 정보가 실제로 포함되어 있었다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;ErrorMsg&quot;: &quot;... 서버 시간 : 2026-08-11 14:53:33&quot;,
  &quot;ErrorCode&quot;: &quot;7&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 점이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 경고창에 표시되는 시간은 브라우저가 임의로 만든 숫자가 아니라 &lt;b&gt;실제 requestSuganggBasket 요청에 대한 서버 응답 안에 포함된 값&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 네이비즘처럼 외부에서 서버시간을 추정한 값보다, 이 시간은 &lt;b&gt;실제 신청 처리 흐름에 훨씬 가까운 clock proxy&lt;/b&gt;라고 볼 근거가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;⚠️&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 &amp;ldquo;ErrorMsg에 출력하는 시계 = 실제 OPEN/CLOSED 조건문에서 사용하는 시계&amp;rdquo;라고 100% 증명된 것은 아니다. 같은 애플리케이션 흐름 안에서 나온 값이라 매우 좋은 단서이지만, 내부 구현까지 확인한 것은 아니다. 따라서 &lt;b&gt;&amp;lsquo;정확한 절대시계&amp;rsquo;라기보다 실제 신청 로직에 더 가까운 기준&lt;/b&gt;이라고 표현하는 것이 맞다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그럼 서버시간을 보면 네이비즘보다 무조건 더 좋은가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지는 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 응답은 14:53:33처럼 &lt;b&gt;초 단위&lt;/b&gt;까지만 알려준다. 한 번의 응답만 보면 실제 판정 시점이 33.001인지 33.950인지 알 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 네이비즘은 밀리초가 계속 흐르기 때문에 &lt;b&gt;실전에서 타이밍을 맞추기에는 훨씬 보기 좋다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 둘은 경쟁 관계라기보다 역할이 다르다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 42px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;도구&lt;/td&gt;
&lt;td&gt;잘하는 것&lt;/td&gt;
&lt;td&gt;한계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;네이비즘&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;실시간으로 계속 흐르는 밀리초 기준시계&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;실제 신청 Gate clock과 동일한지 알 수 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;수강신청 ErrorMsg 서버시간&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;실제 신청 요청 처리 흐름에 가까운 서버 clock 확인&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;초 단위라 한 번만 봐서는 ms offset을 알 수 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 가장 합리적인 방식은 &lt;b&gt;네이비즘을 실전 기준시계로 사용하고, 신청 서버의 ErrorMsg 시간을 교정&amp;middot;검증용으로 사용하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 연습 중 네이비즘을 보고 특정 시점에 저장을 눌렀을 때 서버가 반복해서 같은 초를 반환한다면 둘 사이에 큰 오차는 없다고 볼 수 있다. 반대로 네이비즘에서는 이미 다음 초로 넘어갔다고 보이는데 서버 응답은 계속 이전 초라면, 두 clock 사이에 의미 있는 offset이 있을 가능성을 의심할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번의 샘플보다 &lt;b&gt;여러 번 같은 방향의 차이가 반복되는지&lt;/b&gt;가 더 중요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;네트워크가 빠르면 끝일까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DevTools에서 실제 저장 요청 하나를 측정했을 때 전체 시간은 약 20 ms 수준이었고, 대부분은 Waiting for server response 구간이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 값을 단순히 반으로 나눠서 &amp;ldquo;서버까지 10 ms&amp;rdquo;라고 보면 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 시간에는 대략 다음이 섞여 있다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;내 PC &amp;rarr; 서버 전송
+ 서버 처리
+ 서버 &amp;rarr; 내 PC 응답이 돌아오기 시작하는 시간
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 인터넷 경로는 반드시 대칭이 아니다. 가는 길이 5 ms, 오는 길이 15 ms일 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 수강신청에서는 &lt;b&gt;최저 ping 한 번&lt;/b&gt;보다도 &lt;b&gt;지연이 얼마나 일정한가&lt;/b&gt;가 더 중요할 수 있다. 평균 15 ms인데 매번 14~16 ms라면 예측하기 쉽지만, 평균 10 ms인데 5~50 ms로 출렁이면 타이밍은 오히려 불안정하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;의외로 중요한 것: 이 페이지는 &amp;lsquo;누르는 순간&amp;rsquo;보다 &amp;lsquo;떼는 순간&amp;rsquo;이 중요했다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 여러 번 확인했을 때 저장 버튼은 마우스를 누르는 순간에 바로 요청이 나가는 것이 아니라, &lt;b&gt;마우스를 뗄 때 최종 click이 발생하면서 요청이 시작되는 동작&lt;/b&gt;을 보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹의 일반적인 클릭 흐름도 대략 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;mousedown
   &amp;darr;
mouseup
   &amp;darr;
click
   &amp;darr;
JavaScript handler
   &amp;darr;
XHR 요청
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 서울과기대 해당 페이지에서는 실전 동작을 &amp;lsquo;언제 눌러야 하나&amp;rsquo;보다 &lt;b&gt;&amp;lsquo;언제 손가락을 떼야 하나&amp;rsquo;&lt;/b&gt;로 생각하는 편이 더 정확하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 점은 인간 반응시간 측면에서도 의미가 있다. 정각에 새로 버튼을 누르는 것보다, 버튼 위에서 미리 마우스를 누르고 안정적으로 유지한 다음 원하는 순간에 손가락 힘을 풀어 mouseup을 만드는 방식이 반복 동작의 편차를 줄이는 데 도움이 될 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결국 사람이 줄여야 하는 것은 &amp;lsquo;반응속도&amp;rsquo;보다 &amp;lsquo;편차&amp;rsquo;다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람의 시각 반응시간은 수십 ms가 아니라 보통 그보다 훨씬 큰 단위로 움직인다. 하지만 수강신청에서 더 문제인 것은 매번 정확히 같은 타이밍으로 움직이기 어렵다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 어떤 사람이 평균적으로 150 ms 늦더라도 매번 거의 145~155 ms 범위라면 보정하기 쉽다. 반면 평균은 100 ms인데 어떤 때는 60 ms, 어떤 때는 190 ms라면 훨씬 예측하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 실전에서 의미 있는 연습은 &amp;ldquo;최대한 빨리 클릭&amp;rdquo;이 아니라 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 화면 배치&lt;/li&gt;
&lt;li&gt;같은 손가락 자세&lt;/li&gt;
&lt;li&gt;같은 버튼 위치&lt;/li&gt;
&lt;li&gt;같은 mousedown &amp;rarr; hold &amp;rarr; mouseup 동작&lt;/li&gt;
&lt;li&gt;네이비즘의 같은 시각을 보고 반복 release&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;속도를 최대화하기보다 동작의 jitter를 줄이는 것&lt;/b&gt;이 목표다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제로는 이렇게 세팅하면 된다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 복잡한 계측을 하지 않고도 따라 할 수 있는 현실적인 절차다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. PC방에 도착하면&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;가능하면 유선 연결 PC를 사용한다.&lt;/li&gt;
&lt;li&gt;다운로드, 게임 런처 업데이트, 스트리밍 등 불필요한 네트워크 사용을 끈다.&lt;/li&gt;
&lt;li&gt;Chrome에서 수강신청 사이트에 미리 로그인한다.&lt;/li&gt;
&lt;li&gt;수강신청 화면과 네이비즘이 동시에 보이도록 창을 배치한다.&lt;/li&gt;
&lt;li&gt;신청 버튼의 위치를 확정하고 이후 창 크기&amp;middot;배율&amp;middot;스크롤을 되도록 바꾸지 않는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 신청 10~15분 전: 서버시간과 네이비즘 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;F12 &amp;rarr; Network를 켠 뒤 저장 버튼을 정상적으로 몇 차례 눌러본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 JSONMain 요청에서 다음을 확인한다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;Payload
sp_cmd = requestSuganggBasket

Response
ErrorCode = 7
서버 시간 = YYYY-MM-DD HH:mm:ss
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 네이비즘의 초와 ErrorMsg의 서버 초가 &lt;b&gt;대체로 같은 방향으로 맞는지&lt;/b&gt; 본다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbLjzA/dJMcadiOfTU/GioBvDXugF371AtL9CmkD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbLjzA/dJMcadiOfTU/GioBvDXugF371AtL9CmkD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbLjzA/dJMcadiOfTU/GioBvDXugF371AtL9CmkD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbLjzA%2FdJMcadiOfTU%2FGioBvDXugF371AtL9CmkD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;620&quot; height=&quot;244&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;301&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표는 밀리초를 정확히 계산하는 것이 아니다. 신청 서버가 네이비즘보다 눈에 띄게 한쪽으로 어긋나 있는지 확인하는 정도면 충분하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. mouseup 연습&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마우스 커서를 신청 버튼 위에 올린다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;버튼을 미리 누름
       &amp;darr;
손가락을 누른 상태로 고정
       &amp;darr;
네이비즘의 목표 시각 관찰
       &amp;darr;
목표 순간에 손가락 release
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 번 반복하면서 &lt;b&gt;같은 시각을 보고 비슷한 순간에 떼는 감각&lt;/b&gt;을 만드는 것이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정각에 갑자기 버튼을 새로 누르는 것보다 이 방식이 동작을 하나 줄여준다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 신청 1~2분 전&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때부터는 실험을 그만두는 편이 낫다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네이비즘을 마지막으로 새로고침한다.&lt;/li&gt;
&lt;li&gt;한두번 정도만 더 타이밍 확인을 해보고 그대로 둔다.&lt;/li&gt;
&lt;li&gt;DevTools 조작을 멈춘다.&lt;/li&gt;
&lt;li&gt;페이지 새로고침을 불필요하게 하지 않는다.&lt;/li&gt;
&lt;li&gt;신청할 과목과 버튼 위치를 다시 확인한다.&lt;/li&gt;
&lt;li&gt;마우스를 버튼 위에 고정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직전까지 서버 요청을 반복해서 날리거나 화면을 바꾸다가 오히려 실전 상태를 망치는 것이 더 위험하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 실제 신청 순간&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서울과기대 해당 페이지에서 확인한 동작을 기준으로 하면:&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;정각 직전
&amp;rarr; 버튼을 누른 상태로 유지

목표 시각
&amp;rarr; mouseup
&amp;rarr; click
&amp;rarr; requestSuganggBasket
&amp;rarr; 서버 판정
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 지나치게 &amp;ldquo;몇 ms 먼저 보내야 한다&amp;rdquo;는 계산에 집착할 필요는 없다. 네트워크와 서버 처리에는 순간적인 jitter가 있고, 너무 일찍 도착하면 신청시간 전으로 판정되어 바로 실패할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 실전에서는 &lt;b&gt;네이비즘을 안정적인 연속시계로 보고, 서버 ErrorMsg로 큰 offset이 없는지 미리 확인하고, 반복 연습한 mouseup 타이밍을 그대로 실행하는 것&lt;/b&gt;이 가장 단순하고 재현성 있는 전략이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;세 줄 요약&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;네이비즘은 밀리초 단위 실전 기준시계로 쓰되, 실제 신청 서버의 Gate clock과 동일하다고 가정하지 않는다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;서울과기대에서는 실제 신청 요청 requestSuganggBasket의 응답에 서버시간이 포함되어 있어, 이를 네이비즘의 교정&amp;middot;검증 기준으로 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;해당 페이지의 실제 입력 기준은 mouseup &amp;rarr; click 흐름이므로, 버튼을 미리 누르고 목표 순간에 release하는 연습이 실전에서 더 일관될 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Measurement &amp;amp; Debug</category>
      <category>서버</category>
      <category>서울과학기술대학교</category>
      <category>수강신청</category>
      <author>nanowave</author>
      <guid isPermaLink="true">https://nwave.tistory.com/1</guid>
      <comments>https://nwave.tistory.com/1#entry1comment</comments>
      <pubDate>Tue, 11 Aug 2026 15:55:48 +0900</pubDate>
    </item>
  </channel>
</rss>