전자회로 실무 / 입력 회로·인터럽트

버튼을 I²C(Inter-Integrated Circuit) 확장기에 모을 때는 입력 핀 수와 INT 신호를 함께 확인합니다

버튼이 많아지면 마이크로컨트롤러의 입력 핀이 금세 부족해집니다. 이때 입출력 확장기를 I²C 버스에 연결하면 여러 버튼의 상태를 몇 개의 통신선으로 읽을 수 있습니다. 다만 칩 이름에 ‘8비트’라고 적혀 있어도 버튼 입력에 실제로 쓸 수 있는 핀 수를 먼저 확인해야 합니다. 버튼이 바뀐 순간에만 읽고 싶다면 별도의 인터럽트 출력도 배선·설정해야 합니다.

I²C 통신선과 버튼 입력선은 역할이 다릅니다

GPIO(General-Purpose Input/Output, 범용 입출력) 확장기는 제어기 바깥에 입력·출력 핀을 더하는 칩입니다. I²C(Inter-Integrated Circuit)는 데이터선 SDA(Serial Data)와 클록선 SCL(Serial Clock)을 여러 장치가 공유하는 통신 방식입니다. 예를 들어 MCP23008은 I²C로 확장기의 GPIO 상태를 읽고 설정하는 칩입니다. 제어기는 해당 칩에서 입력으로 지원하는 핀에 연결한 버튼의 상태를 I²C로 읽을 수 있습니다.

버튼이 연결된 입력과 SDA·SCL은 같은 선이 아닙니다. SDA·SCL에는 선을 놓았을 때 높은 전압으로 돌려주는 풀업 저항(pull-up resistor)이 필요합니다. 확장기의 버튼 입력에도 버튼을 놓았을 때의 전압을 정하는 회로가 필요하지만, 이 저항과 I²C 버스의 풀업 저항은 서로 다른 역할입니다. 사용하려는 칩의 내부 입력 풀업 기능과 실제 버튼 결선을 대조하세요.

MCP23008의 보장된 입력 GP0~GP6에 버튼 일곱 개를 연결하고, 여덟 번째 버튼은 MCU 입력으로 보내는 예시입니다. SDA와 SCL은 I²C 통신선이고, INT는 별도의 선택 신호입니다. 실제 전원과 저항값은 사용 부품에 맞춰 확인합니다.
MCP23008의 보장된 입력 GP0~GP6에 버튼 일곱 개를 연결하고, 여덟 번째 버튼은 MCU 입력으로 보내는 예시입니다. SDA와 SCL은 I²C 통신선이고, INT는 별도의 선택 신호입니다. 실제 전원과 저항값은 사용 부품에 맞춰 확인합니다. · 클릭하면 확대됩니다.

‘8비트 확장기’가 곧 ‘버튼 입력 8개’는 아닙니다

MCP23008을 선택할 때는 특히 GP7을 확인해야 합니다. 현재 제조사 데이터시트는 MCP23008의 GP7을 출력 전용으로 표시하며, 입력으로 쓸 GP0~GP6과 구분합니다. 따라서 ‘8비트 확장기’라는 제품 설명만 보고 GP0~GP7에 버튼 여덟 개를 모두 배정하지 마세요. 사용하려는 부품의 실제 개정과 해당 데이터시트·에라타를 확인하고 입력으로 보장된 핀만 사용합니다. 버튼 여덟 개가 필요하다면 한 개는 제어기 입력에 직접 연결하거나, 여덟 입력을 지원하는 다른 부품을 고릅니다.

리셋(reset) 버튼은 다른 조작 버튼과 목적이 다릅니다. 제어기가 멈췄을 때도 리셋이 되어야 한다면, 제어기의 리셋 입력으로 이어지는 경로를 따로 설계합니다. 확장기에서 버튼 상태를 읽을 수 있어야만 리셋이 가능한 구조로 만들지 않습니다.

INT는 ‘어느 버튼이 바뀌었다’는 알림이며 기록 저장소는 아닙니다

버튼을 계속 읽는 폴링(polling) 대신 입력 변화가 있을 때만 알림을 받고 싶다면 확장기의 INT(interrupt output) 단자를 제어기의 인터럽트 가능한 입력에 연결할 수 있습니다. 이 선은 SDA·SCL에 자동으로 포함되지 않습니다. 회로에서 전압·출력 방식과 제어기 입력 조건을 맞추고, 프로그램에서도 어떤 입력 변화가 INT를 만들지 설정해야 합니다.

MCP23008의 경우 각 핀을 입력으로 설정하는 IODIR(I/O Direction), 변화 감지를 켜는 GPINTEN(GPIO Interrupt Enable), 비교 방식을 정하는 INTCON(Interrupt Control) 등을 구성합니다. 버튼을 놓았을 때 입력이 뜨지 않도록 GPPU(GPIO Pull-Up) 또는 적절한 외부 저항도 확인합니다. INT가 발생했을 때 INTCAP(Interrupt Capture)을 읽으면 처음 변화가 감지된 순간의 포트 상태를 확인할 수 있습니다. GPIO 레지스터를 읽으면 현재 핀 상태를 확인합니다. 두 레지스터를 읽는 동작은 인터럽트 해제 조건과 관계되므로 사용 중인 데이터시트에 맞춰 순서를 정합니다.

INT 한 가닥은 버튼마다 별도 펄스를 여덟 개 저장해 주지 않습니다. 첫 변화가 기록된 뒤 인터럽트를 처리하기 전까지 이어진 여러 변화의 순서가 모두 보존된다고 가정하면 안 됩니다. 기계식 버튼은 한 번 눌러도 접점이 여러 번 튀는 바운스(bounce)가 있으므로, 눌림 횟수가 필요하다면 소프트웨어나 회로의 디바운싱(debouncing, 접점 바운스를 한 번의 입력으로 정리하는 과정)도 설계해야 합니다.

버튼을 누르고 있는 동안 동작을 한 번만 실행하려면, 각 입력 비트에서 디바운싱을 마친 유효 상태의 직전 값을 저장하고 놓임에서 눌림으로 바뀐 순간에만 실행합니다. 같은 눌림 상태가 계속 읽히면 다시 실행하지 않고, 놓인 상태를 확인한 뒤 다음 눌림을 받을 준비를 합니다. 이 방법은 폴링으로도 쓸 수 있지만, 짧은 눌림이 읽기 간격 사이에 끝나면 놓칠 수 있습니다. 높은 전압(HIGH)과 낮은 전압(LOW) 중 어느 쪽이 눌림인지는 실제 결선과 출력 설정을 먼저 확인합니다. 상태 변화 감지와 디바운싱은 별도의 처리 단계입니다.

확장기의 GPIO 값을 읽지 못한 때에는 이전에 저장한 값을 새 측정값으로 취급하지 마세요. 이전 값이 계속 눌림으로 보이면 누르거나 놓는 순간을 잘못 판단할 수 있습니다. I²C 읽기 성공 여부를 상태값과 따로 기록하고, 실패한 주기에는 눌림 잠금 상태를 바꾸지 않은 채 통신을 복구한 다음 다시 읽습니다. 통신 장애 동안 끝난 짧은 눌림·놓임은 복구 후 현재 상태만 읽어 재구성할 수 없으므로, 복구 후 상태 재동기화와 동작 재허용 조건을 따로 정합니다. 버튼 하나를 길게 누르기, 두 개를 함께 누르기, 짧게 눌렀다 놓기, 통신이 한 번 실패하기를 각각 시험해 동작 횟수와 복구 뒤 상태를 확인합니다.

핀·배선·프로그램을 한 버튼씩 확인합니다

  1. 버튼 수를 세고 칩의 입력 가능 핀 수를 데이터시트에서 확인합니다. 각 버튼의 핀과 놓였을 때의 논리 상태를 표로 만듭니다. 전원과 접지는 확장기와 제어기의 허용 전압에 맞추고, I²C 주소 핀도 떠 있지 않게 정합니다.
  2. SDA와 SCL 각각에 실제 연결된 풀업 저항을 확인합니다. 다른 I²C 모듈에도 저항이 있으면 병렬 합성값을 계산하고, 신호가 지정된 시간 안에 올라오는지 확인합니다. 버튼 입력용 풀업과 혼동하지 않습니다.
  3. 한 버튼씩 누르고 놓으며 GPIO 읽기 값이 예상 비트에서만 바뀌는지 확인합니다. GP7을 입력으로 쓰지 않는 MCP23008 구성에서는 여덟 번째 버튼을 별도 입력으로 시험합니다.
  4. INT를 사용할 때는 감지할 입력, 비교 방식과 INT 출력 극성을 설정한 뒤 한 버튼·두 버튼 동시 입력·빠른 연속 입력을 차례로 시험합니다. 제어기의 인터럽트 처리 함수에서는 알림 표시만 남기고 곧바로 돌아옵니다. 메인 루프에서 INTCAP과 현재 GPIO를 읽어 버튼 상태를 처리하면, 인터럽트 안에서 I²C 전송을 기다리는 일을 피할 수 있습니다.
  5. 버튼을 누른 상태로 유지하거나 전원을 껐다 켰을 때도 상태가 의도대로 읽히는지 봅니다. INT가 울리지 않으면 먼저 폴링으로 현재 GPIO를 읽어 버튼 배선과 통신 자체가 정상인지 구분합니다.

입력 핀을 아낀다고 해서 각 버튼의 사건을 놓치지 않는 것은 아니므로, 둘은 따로 설계합니다. 핀 수가 맞는 칩을 골랐는지, I²C 통신이 되는지, 변화 알림을 처리할 수 있는지를 차례로 확인하세요.