从乱码里把波特率找回来
한국어 원문으로 표시합니다.
목표
포착된 표본 열에서 UART 프레임을 직접 해독하고, 송신 클럭이 어긋난 포착에서 실제 비트 시간을 되재어 읽히는 문장을 되살립니다. 그 과정에서 패리티와 프레이밍 오류가 각각 무엇을 잡고 무엇을 놓치는지 숫자로 셉니다.
왜 중요한가
UART 에는 클럭 선이 없습니다. 양쪽이 같은 비트 시간을 믿고 있어야 하는데 그 믿음을 확인할 방법이 프레임마다 한 번, 시작 비트뿐입니다. 그래서 클럭이 어긋나면 프레임 뒤쪽부터 무너지고, 증상은 "글자가 깨진다" 로만 올라옵니다. 이 실습은 그 신고를 몇 퍼센트 어긋났는가라는 숫자로 바꾸는 연습입니다. 값을 고치는 것이 아니라 시간 축을 고친다는 점이 핵심이고, 그 시간 축의 근거는 포착 파일 안에 이미 들어 있습니다.
단계
- 표본 열에 시간 축을 세운다 — 머리말의 표본 속도로 표본 번호를 시각으로 바꾸고 개요를 남깁니다.
- 임계값을 올리면 모서리가 움직인다 — rising_edges 를 구현하고 두 임계값의 검출 시각 차이를 잽니다.
- 프레임 해독기를 쓴다 — 시작 모서리에서 (k + 0.5) 비트 시간 떨어진 표본을 읽는 decode 를 구현합니다.
- 정상 포착을 읽는다 — 프레이밍 오류 0으로 문장을 얻습니다.
- 표본을 솎아 판독이 무너지는 곳을 찾는다 — 1·4·10·25·50 간격으로 다시 해독합니다.
- 명목 보율로 읽었을 때의 증상 — 어긋난 포착의 프레이밍 오류와 가장 짧은 펄스를 잽니다.
- 비트 시간을 되재어 문장을 되살린다 — 거친 값에서 시작해 긴 기준선으로 좁힙니다.
- 패리티가 놓친 것을 센다 — 조용히 틀린 프레임이 몇 개인지 셉니다.
참고
- 재료는
/opt/fixtures/serialbus/uart/아래 세 편입니다:hello-9600.csv(정상),drift-8n1.csv,drift-8e1.csv. 읽기 전용이며 고치지 마세요. 형식 설명은 같은 폴더 위의/opt/fixtures/serialbus/README.md에 있습니다. - 포착 파일을 읽는 함수는
capture.py같은 파일 하나에 두고 단계마다 다시 쓰지 마세요. 정답지도 그렇게 합니다. - 파일의
n열은 표본 번호입니다. 시각은n / sample_rate_hz초이고, 파일에 시각 열은 없습니다. - 흔한 실수 하나: 데이터 비트를 높은 자리부터 모으는 것. UART 는 낮은 자리부터입니다.
- 흔한 실수 둘: 비트당 표본 수를 정수로 반올림하는 것. 9600 Bd 를 500 kHz 로 받으면 52.083 이고, 반올림하면 프레임 끝에서 어긋납니다.
- 추가 설치도 인터넷도 필요 없습니다. python3 만 씁니다.
- 예상 70분이므로 기본 60분이 끝나기 전에 +시간으로 연장하세요(최대 180분). 세션이 끝나면
/root가 사라지므로 남기고 싶은 것은 따로 보관하세요.
표본 열에 시간 축을 세운다
mkdir -p /root/bus-uart 로 작업 폴더를 만들고, /opt/fixtures/serialbus/uart/hello-9600.csv 를 읽어 /root/bus-uart/survey.json 에 sample_rate_hz(머리말에서 읽은 값), sample_count(표본 행 수), duration_us(표본 수 x 1,000,000 / 표본 속도), min_v, max_v, idle_level(첫 표본이 1.65 V 이상이면 "high", 아니면 "low")을 저장하세요.
머리말은 # 로 시작하는 줄이고 sample_rate_hz 가 거기 있습니다. 그다음 줄이 열 이름이고 그 아래가 표본입니다. 시각은 파일에 없고 표본 번호와 표본 속도로 계산합니다.
임계값을 올리면 모서리가 움직인다
/root/bus-uart/edges.py 에 rising_edges(volts, threshold_v) 를 구현하세요. volts[i-1] 이 임계값 미만이고 volts[i] 가 임계값 이상인 i 들을 오름차순 목록으로 돌려줍니다(i 는 1부터). 그리고 hello-9600.csv 에 임계값 1.65 V 와 2.90 V 를 각각 적용해 /root/bus-uart/threshold.json 에 count_1v65, count_2v90, first_1v65, first_2v90, shift_samples(first_2v90 빼기 first_1v65), shift_us 를 저장하세요.
모서리가 유한한 기울기를 가지면 높은 임계값은 더 늦게 넘어섭니다. 두 임계값의 개수가 같은데 시각만 다르다는 점을 확인하세요. 채점기는 포착 파일과 무관한 짧은 표로도 이 함수를 시험합니다.
프레임 해독기를 쓴다
/root/bus-uart/uart.py 에 decode(volts, sample_rate_hz, baud, threshold_v=1.65, parity=None) 을 구현하세요. 유휴(높음)에서 시작 비트로 떨어지는 모서리 i 를 찾고, i 에서 (k + 0.5) 비트 시간 떨어진 표본을 int() 로 잘라 k번 비트로 읽습니다. 데이터는 8비트이고 낮은 자리부터입니다. 프레임 하나를 {"start": i, "value": 바이트, "framing_ok": 정지비트가 1인가, "parity_ok": 패리티가 맞는가} 로 담아 목록으로 돌려주고, 정지 비트를 읽은 표본 바로 다음부터 다음 모서리를 찾습니다. parity 는 None 또는 "even" 입니다.
비트당 표본 수는 sample_rate_hz / baud 이며 정수가 아닙니다. parity 가 None 이면 parity_ok 는 언제나 참입니다. 채점기가 손으로 만든 시험표(정상 프레임, 정지 비트가 0인 프레임, 패리티 비트만 뒤집은 프레임)로 먼저 확인합니다.
정상 포착을 읽는다
hello-9600.csv 를 9600 Bd 로 해독해 /root/bus-uart/message.json 에 frame_count, framing_errors, bytes(정수 목록), text(바이트를 그대로 글자로 이은 문자열)를 저장하세요.
프레이밍 오류가 0이어야 정상입니다. 0이 아니면 비트당 표본 수나 시작 모서리를 찾는 조건을 다시 보세요.
표본을 솎아 판독이 무너지는 곳을 찾는다
hello-9600.csv 의 표본 열을 1, 4, 10, 25, 50 간격으로 솎아(volts[::k]) 각각 표본 속도를 rate // k 로 두고 9600 Bd 로 다시 해독하세요. /root/bus-uart/sampling.json 에 cases 를 이 순서 그대로 5개 담고, 각 항목에 decimate, sample_rate_hz, samples_per_bit, text_ok(솎지 않은 결과와 문자열이 같은가)를 넣으세요. 그리고 처음으로 text_ok 가 거짓이 되는 간격을 first_failing_decimate 에 적으세요.
비트당 표본 수가 1에 가까워지면 해독기는 오류를 내지 않고 다른 글자를 냅니다. text_ok 는 불리언이어야 하고 문자열 "true" 가 아닙니다.
명목 보율로 읽었을 때의 증상
/opt/fixtures/serialbus/uart/drift-8n1.csv 를 9600 Bd 로 해독해 /root/bus-uart/drift.json 에 frame_count, framing_errors, bytes 를 저장하세요. 여기에 더해, 준위가 바뀌는 표본 번호들을 모아 이웃 간격의 최솟값을 구하고 shortest_pulse_us 로, 1,000,000 / 9600 을 nominal_bit_time_us 로, 둘의 비를 ratio 로 적으세요.
가장 짧은 간격은 비트 하나입니다. 그 값이 명목 비트 시간과 몇 퍼센트 다른지가 이 포착의 진단입니다. 프레임 수는 정상 포착과 비슷한데 프레이밍 오류만 늘어난다는 점을 확인하세요.
비트 시간을 되재어 문장을 되살린다
drift-8n1.csv 에서 비트 시간을 되재세요. 먼저 모서리 간격의 최솟값을 거친 값으로 잡고, 그다음 기준선을 늘려 가며 좁힙니다 — 첫 모서리에서 (지금 추정값 x 16) 표본 안에 드는 가장 먼 모서리를 골라 그 거리를 추정값으로 나눠 반올림한 정수로 다시 나누고, 배수를 4배씩 키워 마지막 모서리까지 되풀이합니다. 결과를 /root/bus-uart/measured.json 에 bit_time_us, baud(1,000,000 나누기 bit_time_us), 그 보율로 다시 해독한 text, framing_errors 로 저장하세요.
한 번에 전체 길이로 반올림하면 안 됩니다 — 거친 값의 오차에 비트 개수를 곱한 값이 0.5비트를 넘으면 엉뚱한 정수를 고릅니다. 거친 값만으로도 글자는 읽히지만 채점은 0.2 % 안쪽을 요구하므로 좁히는 단계를 건너뛸 수 없습니다.
패리티가 놓친 것을 센다
/opt/fixtures/serialbus/uart/drift-8e1.csv 를 짝수 패리티로 두 번 해독하세요 — 한 번은 되잰 비트 시간으로(이쪽이 참값), 한 번은 9600 Bd 로. /root/bus-uart/parity.json 에 frames, parity_errors, framing_errors, flagged(둘 중 하나라도 걸린 프레임 수), wrong_bytes(참값과 값이 다른 프레임 수), silently_wrong(값이 다른데 패리티도 프레이밍도 통과한 프레임 수), text_at_measured 를 적으세요.
패리티는 1의 개수의 홀짝만 봅니다. 짝수 개가 뒤집히면 그대로 통과하므로 flagged 가 wrong_bytes 보다 작을 수 있습니다. 두 해독 결과를 프레임 순서대로 짝지어 견주세요.