※ 결론보다 문제점 설명이 더 길기에 미리 쓰는 해결방법 : 풀 다운 저항


아두이노의 디지털 핀 확장을 위해 많은 분들께서 74HC595나 74HC165 IC 등을 주로 사용하곤 합니다.
제 경우에는 업무상 필요에 의해 디지털 입력확장 보드를 제작할 필요가 있었는데, 회사에 4051 아날로그 MUX가 많더군요.

이 아날로그 MUX를 디지털 MUX로 사용할 수 있는지 알아봤는데 딱히 이렇게 쓰면 된다 하는 글을 못찾겠더군요.
제 검색 실력이 형편없었을 수도 있고, 그냥 74HC595/165를 쓰는게 효율적이기에 제가 찾는 글이 없었을 수도 있지요.

허나, 업무상 한정적인 자원으로 급하게 진행해야 하는 제 경우엔 또 새로운 IC를 적용해서 테스트를 하고 PCB를 설계할
시간적 여유가 없었기에 결국 4051 아날로그 MUX를 디지털 MUX로 사용하게 되었습니다.




4051 IC는 기본적으로 아날로그 멀티플렉서이며, 9~11번 핀의 A, B, C 컨트롤을 High/Low로 변경하며, 이를 이진법으로 0번에서 7번까지의 채널I/O핀을 제어해 COM I/O핀으로 데이터를 읽던가 보내던가 할 수 있습니다.


다음 그림은 4051 IC를 사용하는 간단한 예제입니다.




# 아두이노 핀 맵핑

- 22 : Ctrl A ( IC 11번핀 )

- 24 : Ctrl B ( IC 10번핀 )

- 26 : Ctrl C ( IC 9번핀 )

- A0 : 0ch I/O ( IC 13번핀 )

- A1 : 1ch I/O ( IC 14번핀 )

- A2 : 2ch I/O ( IC 15번핀 )

- A3 : 3ch I/O ( IC 12번핀 )

- A4 : 4ch I/O ( IC 1번핀 )

- A5 : 5ch I/O ( IC 5번핀 )

- A6 : 6ch I/O ( IC 2번핀 )

- A7 : 7ch I/O ( IC 4번핀 )

- A15 : COM I/O ( IC 3번핀)


위와 같이 회로를 구성하면, 0~7Ch I/O(아두이노 A0~A7핀)중 하나에 analogWrite() 함수로 값을 쓰고, Ctrl A~C(아두이노 22, 24, 26핀)을 사용하여 특정 채널에 써진 값을 COM_I/O(아두이노 A15핀)로 읽어올 수 있고, 반대로 COM_I/O핀에 analogWrite() 함수로 값을 쓰고 Ctrl A~C를 제어하여 특정 채널 I/O에 값을 보낼 수도 있습니다.


예를 들어 A3에 200의 값을 쓰고, Ctrl A(22)에 Low, Ctrl B(24)에 High, Ctrl C(26)에 High를 주면 COM_I/O핀에 200의 값이 전달됩니다. int value = analogRead(A3); 한 후 시리얼 모니터에 찍어보면 200이란 값이 나오겠지요.


여기서 analogRead 대신 digitalRead를 사용하면 High가 나올까요 Low가 나올까요.


결과는 '장담 못한다.' 입니다. 저는 루즈하게 analogRead/Write의 값 범위가 0~1023 이니까 적당히 512를 기준으로 낮으면 Low, 높으면 High가 나오겠지하고 생각했으나, 그렇지 않았습니다.

아날로그 값과 디지털 값을 다 읽어보았으나 어쩔땐 700~800이 나와도 Low일 때가 있고, 어쩔땐 50~100이 나와도 High일 때가 있더군요. 너무 안일했습니다.


제가 제작한 회로의 경우, 4051의 각 채널I/O 핀에 스위치를 통해 5v 전압이 걸리면 High로 인식하여 COM_IO로 HIGH를 가져와야 했으나, 5v 전압을 걸어도 High가 제대로 걸리지 않고, 인접한 다른핀의 5v 인가에도 영향을 받아 High-Low가 지멋대로 바뀌더군요.


이런 저런 시도끝에 4051의 0~7ch 핀에 각 10K옴 풀다운 저항을 걸어주니 값을 정확히 High/Low로 받을 수 있었습니다.


제 회로는 (하나의 4051 IC의 각 채널 I/O를 8개의 또다른 4051 IC의 COM_I/O로 연결) x 2 하여 최종적으로는 A0, A1 두개의 아두이노 핀과 컨트롤신호핀 6개, 총 8개의 아두이노 핀을 사용하여 128개의 디지털 인풋을 입력받을 수 있도록 확장했습니다.

이 경우 스위치와 연결되어 5v를 직접 받는 16개의 4051에는 풀 다운 저항을 달아줄 필요가 없으며, 8개의 4051 IC가 모이는 허브 역할을 하는 4051 IC의 각 채널 I/O에만 풀 다운 저항을 사용하면 인풋을 High/Low 디지털로 읽을 수 있습니다.


이하는 제가 사용한 PCB 설계도 입니다.


무료 설계툴인 Fritzing에서는 2레이어 PCB까지밖에 설계할 수가 없기에 점프 와이어를 꽤 많이 날려야 하지만, 일단 동작은 확실하게 검증하였습니다.


Ctrl A,B,C는 허브 역할을 하는 4051의 채널을 제어하며, Ctrl 1,2,3은 5v가 인가되는 말단 4051을 제어합니다.

Slot 0,1을 아두이노 A0, A1에 연결하여 디지털 High/Low를 가져오도록 되어있습니다.


이하는 위 PCB로 디지털 입력을 확장한 소스코드입니다.


 

#define DigitIn_enable 0x01
#define DigitIn_disable 0x00

// 공용 함수 및 변수
bool DebugMode = true;
int nDelay = 1;
int nSerialBaudrate = 9600;
bool breakWhile;
bool onLock ;

// Digital Input 관련 함수 및 변수
void DigitalInputProcessing();  // Digital Input 상태 변화를 감지
bool CheckChanged_DigitInput();  // Digital Input 상태가 변화한 경우 데이터 배열에 적용
void Init_DigitalPin();
void Init_ControlPin();
void Setup_DataPin();
void Setup_ControlPin();

int DigitIn_DATA_PIN[2];  // Digital Input 상태 가져올 핀
int DigitIn_CONTROL_PIN[6]; // Digital Input Board 제어할 컨트롤  핀
byte DigitIn_DATA[2][8][8];  // Digital Input 상태 저장할 배열
byte PREV_DigitIn_DATA[8][8][8]; // Digital Input 상태 변화 비교용 버퍼
int DigitIn_nCtrlSignal1 = 0; // DigitIn_nCtrlSignal1 = 말단 pin 나오는 MUX 8개 중 하나 선택
int DigitIn_nCtrlSignal2 = 0; // DigitIn_nCtrlSignal2 = 선택된 MUX중 In/Out Pin 8개 중 하나 선택
int DigitIn_nSlot = 0;        // DigitIn_nSlot = IO보드 넘버(0~7).

                                   // DigitIn_DATA_PIN[0][DigitIn_nSlot]에 각 Slot에서 들어온 값 넣음.
int CtrlA, CtrlB, CtrlC; // 허브 Mux 컨트롤 신호. 8개의 Mux 중 하나 선택.
int Ctrl1, Ctrl2, Ctrl3; // 말단 MUX 컨트롤 신호. Mux의 8개 데이터 핀 중 하나 선택.

void setup()
{
  if(DebugMode)
  {
    Serial.begin(nSerialBaudrate);
    while (!Serial) {;} // 시리얼 포트 오픈되기까지 대기
  }

  // 공용 설정
  breakWhile = false;
  onLock = false;

  // Digital Input 관련 설정
  Init_DigitalPin();
  Init_ControlPin();
  init_AnalogInputPin();
  Setup_ControlPin();
  Setup_DataPin();
 
  if(DebugMode)
    Serial.println("Setup done."); 
}

int cnt = 0;
unsigned long currentMillis = 0;
void loop()
{
  // Digital Input Process
  DigitalInputProcessing(); // Digital Input 상태변화 감지
 
  bool bIsChanged_DigitInput = CheckChanged_DigitInput(); // 상태변화가 있으면 현재상태를 저장
  if(bIsChanged_DigitInput && !onLock)
  {
    onLock = true;
    // 변화상태 전송
    // Send_MSG_0x4008(); // 상태변화 TCP 전송
    bIsChanged_DigitInput = false;
    onLock = false;
  }
  delay(1);
}

void Init_DigitalPin()
{
  // 데이터핀 하나에서 8*8=64개 인풋 받아옴.
  DigitIn_DATA_PIN[0] = A0;
  DigitIn_DATA_PIN[1] = A1;
}

void Init_ControlPin()
{
  // [0]~[2] : Control신호1. (MUX 1~8번 선택)
  // [3]~[5] : Control신호2. (Data Pin 선택)
  DigitIn_CONTROL_PIN[0] = 22;
  DigitIn_CONTROL_PIN[1] = 23;
  DigitIn_CONTROL_PIN[2] = 24;
  DigitIn_CONTROL_PIN[3] = 25;
  DigitIn_CONTROL_PIN[4] = 26;
  DigitIn_CONTROL_PIN[5] = 27;
}

void Setup_ControlPin()
{
  for (int nControl = 0; nControl < 6; nControl++)
    pinMode(DigitIn_CONTROL_PIN[nControl], OUTPUT);
}

void Setup_DataPin()
{
  for (int nControl = 0; nControl < 2; nControl++)
    pinMode(DigitIn_DATA_PIN[nControl], INPUT);
}

bool CheckChanged_DigitInput()
{
  bool rtn = false;
 
  for(int i=0;i<8;i++)  // DigitIn_nCtrlSignal1
  {
    for(int j=0;j<8;j++)  // DigitIn_nCtrlSignal2
    {
      for(int k=0;k<2;k++)  // DigitIn_nSlot
      {
        if(DigitIn_DATA[k][i][j] != PREV_DigitIn_DATA[k][i][j])
        {
          rtn = true;
        }
        PREV_DigitIn_DATA[k][i][j] = DigitIn_DATA[k][i][j];
      }
    }
  }       
  return rtn;
}

void DigitalInputProcessing()
{
  for (DigitIn_nCtrlSignal1 = 0; DigitIn_nCtrlSignal1 < 8; DigitIn_nCtrlSignal1++)
  {
    // Control signal 1을 변경하며 MUX 선택
    CtrlA = bitRead(DigitIn_nCtrlSignal1, 0);
    CtrlB = bitRead(DigitIn_nCtrlSignal1, 1);
    CtrlC = bitRead(DigitIn_nCtrlSignal1, 2);
    digitalWrite(DigitIn_CONTROL_PIN[0], CtrlA);
    digitalWrite(DigitIn_CONTROL_PIN[1], CtrlB);
    digitalWrite(DigitIn_CONTROL_PIN[2], CtrlC);
   
    for (DigitIn_nCtrlSignal2 = 0; DigitIn_nCtrlSignal2 < 8; DigitIn_nCtrlSignal2++)
    {
      // 선택한 MUX에서 Control signal 2를 변경하며 말단 I/O 핀 선택
      Ctrl1 = bitRead(DigitIn_nCtrlSignal2, 0);
      Ctrl2 = bitRead(DigitIn_nCtrlSignal2, 1);
      Ctrl3 = bitRead(DigitIn_nCtrlSignal2, 2);
      digitalWrite(DigitIn_CONTROL_PIN[3], Ctrl1);
      digitalWrite(DigitIn_CONTROL_PIN[4], Ctrl2);
      digitalWrite(DigitIn_CONTROL_PIN[5], Ctrl3);

      // 8개 I/O보드의 1~64번 I/O 핀 상태를 동시에 가져와 데이터 배열에 저장.
      for (DigitIn_nSlot = 0; DigitIn_nSlot < 2; DigitIn_nSlot++)
      {
        if(digitalRead(DigitIn_DATA_PIN[DigitIn_nSlot]))
        {
          DigitIn_DATA[DigitIn_nSlot][DigitIn_nCtrlSignal1][DigitIn_nCtrlSignal2] = DigitIn_enable;
          //Serial.println("input[" + String(DigitIn_nCtrlSignal1) + "][" + String(DigitIn_nCtrlSignal2) + "] is " + String(digitalRead(DigitIn_DATA_PIN[DigitIn_nSlot])));
        }
        else
        {
          DigitIn_DATA[DigitIn_nSlot][DigitIn_nCtrlSignal1][DigitIn_nCtrlSignal2] = DigitIn_disable;
          //Serial.println("input[" + String(DigitIn_nCtrlSignal1) + "][" + String(DigitIn_nCtrlSignal2) + "] is " + String(digitalRead(DigitIn_DATA_PIN[DigitIn_nSlot])));
        }
      }
    }
  }
}


원래 소스코드에는 192개 LED 개별 밝기조절/점등 부분과 TCP/IP 서버 생성하여 클라이언트에 변화감지 시 메세지 날리는 부분도 함께 있었으나, 포스팅의 깔끔함을 위해 편집을 가하였습니다. 편집 과정에서 불필요한 부분이 남아있을 수 있다는 점 미리 양해를 구합니다.


아마도 저처럼 사용하실 분이 계시긴 할런지 의문이긴 한데, 혹시나 4051 아날로그 MUX를 보유중인데, 디지털 MUX가 따로 더 필요하다 하시는 분이 계시다면 풀 다운 저항을 이용하여 디지털 MUX로 유용할 수 있다는 정보를 공유하고 싶었습니다 ㅎㅎ


그럼 오늘의 포스팅은 여기까지!! 휘리릭 뿅~