칩의 핀이 부족해서 하나를 두 용도로 쓰고 있었다. 레지스터 설정에 따라 같은 PIN_5 가 A로도 B로도 동작하는 구조였다.
회로에서는 자연스러운 선택이다. 핀을 하나 아끼면 더 작은 칩을 쓸 수 있고 그만큼 값과 기판 넓이가 줄어든다.
그 절약이 전부 소프트웨어 쪽으로 넘어왔다.
Table of contents
Open Table of contents
전환에 시간이 든다
A로 쓰다가 B로 쓰려면 모드를 바꾸고 다시 돌려놔야 한다.
set_pin_mode(PIN_5, MODE_B);
do_b_thing();
set_pin_mode(PIN_5, MODE_A);
do_b_thing 하나를 부르려고 set_pin_mode 를 두 번 부르는 모양이 된다. 그리고 전환 자체에 시간이 든다.
데이터시트에는 모드를 바꾼 뒤 신호가 안정될 때까지 몇 클럭을 기다리라고 적혀 있었다. 한 번이면 무시할 만한 값인데 A와 B를 자주 번갈아 쓰면 그 대기가 계속 쌓인다.
호출 한 번의 비용이 아니라 호출 빈도의 비용이었다. 바꿔 쓰는 횟수를 줄이는 것이 곧 성능 문제가 됐다.
동시에 못 쓴다
더 큰 문제는 두 기능을 같은 시각에 못 쓴다는 점이었다. A가 도는 동안 B가 필요해지면 A를 멈춰야 한다.
그런데 A가 통신이라 중간에 멈추면 받던 자료가 유실된다. 그래서 아무 때나 MODE_B 로 바꿀 수 없고 타이밍을 따로 맞춰야 했다.
- A가 한가한 구간을 찾는다
- 그 구간에서만
MODE_B로 전환한다 - B를 빨리 끝내고
MODE_A로 돌아온다
소프트웨어가 하드웨어 제약을 계속 신경 쓰면서 돌게 된 것이다. B를 부르는 자리마다 지금 A가 한가한지를 먼저 따져야 했다.
원인 — 전환 구간에 끼어드는 인터럽트
A를 처리하는 중에 인터럽트가 들어오면 그 안에서도 같은 핀을 만질 수 있다.
메인: A 모드로 전환 → (인터럽트) → B로 바꿈 → 복귀 → 메인이 A라고 믿고 동작 → 실패
메인 쪽은 자기가 MODE_A 로 맞춰 뒀다고 믿는데 복귀 시점의 실제 레지스터는 MODE_B 다. 핀 모드가 코드의 가정과 어긋난 상태가 된다.
이것을 막으려고 전환 구간을 인터럽트로부터 닫았다.
disable_irq();
set_pin_mode(PIN_5, MODE_B);
do_b_thing();
set_pin_mode(PIN_5, MODE_A);
enable_irq();
disable_irq 와 enable_irq 사이에서는 ISR 이 안 돌므로 모드가 중간에 안 바뀐다. 그런데 그 구간이 길면 그동안 다른 처리가 전부 밀린다.
하나를 막으니 다른 제약이 생기는 식이었다. do_b_thing 이 오래 걸릴수록 통신 쪽이 손해를 본다.
해결 — 현재 모드를 변수로 들고 있기
지금 어느 모드인지를 암묵적으로 가정하는 코드가 여기저기 있었다. 그 가정을 없애려고 현재 모드를 변수 하나로 명시했다.
static volatile pin_mode_t current_mode;
void ensure_mode(pin_mode_t m) {
if (current_mode != m) {
set_pin_mode(PIN_5, m);
current_mode = m;
}
}
ISR 에서도 읽고 쓰므로 volatile 을 붙여 컴파일러가 그 읽기를 걷어내지 못하게 했다. ensure_mode 는 이미 그 모드면 set_pin_mode 를 안 부르고 그냥 돌아온다.
그래서 불필요한 전환이 크게 줄었다. 상태를 안 들고 있으면 안전을 위해 매번 전환하게 되고 그 대기가 전부 낭비가 된다.
변수 하나를 관리하는 비용으로 전환 횟수를 샀다. 코드는 늘었지만 대기 시간은 줄었다.
회로에서 아낀 것이 넘어온다
여기까지가 전부 핀 하나를 아끼려다 생긴 일이다. 핀이 하나만 더 있었으면 전환도 대기도 인터럽트 차단도 없었다.
비용이 사라진 것이 아니라 회로에서 소프트웨어로 옮겨 간 것이다. 회로도에서는 안 보이고 코드에서만 보이니 아낀 쪽만 눈에 남는다.
그것이 틀린 결정이라는 말은 아니다. 칩 값과 기판 크기에 제약이 있었을 테고 그 판단은 그 판단대로 맞다.
다만 그 결정이 소프트웨어에 얼마를 물리는지를 설계 단계에서 같이 봤으면 좋았을 것이다. 한쪽 비용만 재면 총합이 늘어도 줄인 것처럼 보인다.
변경 내용 — 다음 판에 반영된 것
기판 개정 논의가 열렸을 때 핀 하나 때문에 소프트웨어가 얼마나 복잡해졌는지를 적어 올렸다.
ensure_mode를 거치는 전환 코드가 몇 군데인지disable_irq로 인터럽트를 막는 구간이 새로 생겼다는 것- 그 차단 때문에 통신 쪽이 밀리는 시간이 몇 마이크로초인지
숫자로 적으니 검토 대상이 됐다. 복잡하다는 말만으로는 회로 쪽이 움직일 근거가 되지 않는다.
같은 내용을 말로만 하면 취향 문제로 들린다. 시간과 개수로 적으면 기판 값과 같은 저울에 올릴 수 있다. 결과적으로 다음 판에서는 핀이 나뉘었다.
정리
- 핀을 공유하면 전환과 대기와 동시 사용 불가가 제약이 된다
set_pin_mode뒤에는 신호가 안정될 때까지 대기가 붙는다- 비용은 호출 한 번이 아니라 바꿔 쓰는 빈도에 비례한다
- 전환 구간에
ISR이 끼어들면 모드가 코드의 가정과 어긋난다 disable_irq로 막으면 그동안 다른 처리가 밀린다volatile변수와ensure_mode로 불필요한 전환을 없앤다- 회로에서 아낀 비용이 소프트웨어로 옮겨 온다
- 개정을 요청할 때는 군데 수와 지연 시간을 숫자로 적는다