아두이노간 i2c 통신 - adu-inogan i2c tongsin


  • 온라인 가상시뮬레이터 : https://www.tinkercad.com
  • 공개회로도 : https://www.tinkercad.com/things/bPDpkJ3K8K6

오늘은 아두이노 간 통신을 I2C 방식을 이용하여 실험을 할까 합니다. 예전에 시리얼통신을 할 때 한꺼번에 소개 했어야 했는데 미뤄졌네요. 지난 시간에 시리얼 통신으로 다수 명령 처리 방법에 대한 post를 하다 보니 다수 아두이노 통신을 통해 명령을 처리해보고 싶어져서 다수 통신 실험을 위해 I2C 방식을 소개하면서 다뤄보고 싶어 이렇게 post를 쓰게 되었습니다. 통신에 대한 복습차원으로 좋을 것 같아서 겸사겸사 이야기를 이여 갑니다.

이제부터, I2C 방식으로 어떻게 통신이 이루어지는지 살펴 보겠습니다.

1. I2C 통신을 위한 Wire 라이브러리


  • 참고 출처 : https://www.arduino.cc/en/Reference/Wire

위 참고 출처에 가시면 아두이노 공식 홈페이지에 Wire 라이브러리에 대해 자세히 나와 있습니다. 그리고, Master Reader/Slave Writer, Master Writer/Slave receiver 예제가 있는데 이 예제를 통해서 아두이노 간 통신을 배우시면 되겠습니다.

아두이노 간 통신을 하기 위해서 Wire 라이브러리를 이용하여 I2C 통신을 할 수 있는데 아두이노우노의 A4(SDA), A5(SCL)핀에 아두이노들 간의 공유선을 이용하여 통신이 이루어 집니다. 만약, 다른 보드를 이용할 경우 해당 보드의 SDA, SCL핀이 몇번 핀인지 확인하시고 해당핀을 공유선에 연결하시면 됩니다.

Boardpins
Uno A4 (SDA), A5 (SCL)
Mega2560 20 (SDA), 21 (SCL)
Leonardo, Micro 2 (SDA), 3 (SCL)
Due 20 (SDA), 21 (SCL), SDA1, SCL1

이제 아두이노홈페이지에 나와 있는 Master Reader/Slave Writer, Master Writer/Slave receiver 예제로 기반으로 간단히 설명을 드리겠습니다.

1) 아두이노 주소 지정


아두이노 간의 식별은 주소로 구별합니다. 주소를 지정하는 방식은 다음과 같습니다.

Master : Wire.begin()
Slave : Wire.begin(address)

위 함수를 보면 아무것도 안적혀 있으면 Master가 되고 address를 지정해 주면 그 address가 해당 아두이노의 슬레이브 주소(7bit)가 됩니다. 이렇게 주소를 지정하면 상대 아두이노에서 해당 주소을 통해서 데이터를 보낼 수 있고 받을 수 있습니다.

2) 데이터 전송


Wire.beginTransmission(1); // 슬레이브주소 1번 전송시작
Wire.write("good\n");        // 문자열 전송
Wire.endTransmission();    // 전송 중단

데이터를 보낼 때 beginTransmission(address)와 endTransmission()함수가 쌍을 이루고 write()함수로 데이터를 해당 주소지로 보내게 됩니다. write()함수가 쓰기 불편하시면 print() println()함수로 쓰셔도 됩니다. 참고로 Wire.send()함수와 같이 봐주시기 바랍니다.

3) 데이터 수신


setup()함수에 한번만 표현하고 데이터가 수신이 되면 receiveEvent 함수가 호출이 됩니다.

Wire.onReceive(receiveEvent); //데이터 수신 시  receiveEvent()함수 호출

호출된 함수 내부의 코딩은 시리얼통신 때 배웠던 코딩과 동일합니다.

void receiveEvent(int howMany) { //전송 데이터 처리 명령
  while (Wire.available()) { 
    char ch = Wire.read(); 
    Serial.print(ch);         
  }  
}

4) 요청/응답


위에서는 단순히 일방적으로 데이터를 보내고 받는 방식이라면 여기에서는 데이터를 특정 슬레이브 아두이노에게 데이터를 요청하고 해당 슬레이브 아두이노는 요청을 확인 한 후 데이터를 보내게 됩니다.

마스터 아두이노에서 요청은 다음과 같습니다.

Wire.requestFrom(1, 4); //슬레이브(1)에 6byte 요청

while (Wire.available()) { //보내온 데이터가 있을 시 데이터 읽기
  char c = Wire.read(); 
  Serial.print(c);        
}  

슬레이브 아두이노에서 응답은 다음과 같습니다.

Wire.onRequest(requestEvent); //요청할 때마다 호출

위 함수는 setup()함수에 한번만 표현하고 데이터 요청이 올 때 requestEvent()함수가 호출되게 됩니다.

호출된 함수 내부의 코딩은 간단한 메세지 전송만 보내게 표현해 보겠습니다.

void requestEvent() { //요청 시 수행 함수
  Wire.write("ok!\n");   
}

정리를 하면, Wire.requestFrom(1, 4) 함수은 마스터 아두이노에서 슬레이브(1) 아두이노에게 요청을 하고 4byte을 읽겠다는 의미이고, Wire.onRequest(requestEvent) 함수는: 슬레이브(1) 아두이노는 요청을 받게 되면 requestEvent()함수를 호출하고 그 안에 있는 write()함수를 통해 마스터 아두이노에 데이터를 보내게 됩니다.

2. I2C 아두이노 간 통신 회로도


준비물 : 아두이노우노 2개
내용 : 아두이노우노의 A4(SDA), A5(SCL)핀을 서로 공유한다.


아두이노간 i2c 통신 - adu-inogan i2c tongsin

회로도의 선 연결은 어렵지 않죠. A4, A5 선만 잘 연결하시면 됩니다. 참고로 실제로 실험하실 때는 여러분들이 사용하시는 보드에 따라 선 연결이 다릅니다. 여러분들이 사용하는 보드의 SDA, SCL 핀이 몇번인지 확인하시고 연결하셔야 합니다. 아두이노우노의 경우는 A4, A5로 연결하면 됩니다.

3. 코딩


  • 참고 출처 : https://www.arduino.cc/en/Reference/Wire

사전 학습으로 위 아두이노공식 홈페이지에 가시면 Master Reader/Slave Writer, Master Writer/Slave receiver 예제가 있는데 있는데 위 회로도에다가 코딩만 복사하셔어 가상시뮬레이터에서 돌려 보세요.

Master Reader/Slave Writer, Master Writer/Slave receiver 예제를 합쳐진 코딩으로 실험을 해보겠습니다.

위 공식홀페이지의 예제에서는 마스터 아두이노에서 x값을 순차적으로 슬레이브 아두이노에 보내는 동작과 마스터 아두이노에서 데이터 요청하고 슬레이브 아두이노에서 응답하는 동작을 하는 예제입니다.

실험은 마스터에서 x값을 순차적으로 슬레이브로 보내면서 슬레이브 아두이노에게 데이터 요청을 동시에 수행하고 슬레이브 아두이노는 데이터는 수신하면서 요청이 들어오면 응답을 수행하도록 해보겠습니다.

1) 마스터 아두이노 코딩


설계 :

  • 데이터 전송
  • 슬레이브 아두이노에 데이터 요청과 수신

먼저, Wire 라이브러를 함수를 사용하기 때문에 아래와 같인 선언 해주셔야 합니다.

#include <Wire.h>

그리고, 마스터 아두이노이기 때문에 주소지정은 다음과 같습니다.

void setup() {
  Wire.begin();
    
    Serial.begin(9600);           
}

한번만 수행되면 되니깐 Setup()함수 내에서 표현합니다.

데이터 전송하기

int x = 0;
Wire.beginTransmission(1);                
Wire.write("good\n");       
Wire.write(x);             
Wire.endTransmission();
x++;
if(x==6)x=0;  

데이터 요청과 수신

Wire.requestFrom(1, 4); //슬레이브(1)에 4byte 요청
while (Wire.available()) {
    char c = Wire.read(); 
    Serial.print(c);        
}    

2) 슬레이브 아두이노 코딩


설계 :

  • 데이터 수신 처리
  • 데이터 요청 응답

슬레이브 아두이노는 다음과 같이 코딩합니다.

#include <Wire.h>

void setup() {
  Wire.begin(1); //슬레이브 주소                
  Wire.onRequest(requestEvent); //요청시 requestEvent함수 호출
  Wire.onReceive(receiveEvent); //데이터 전송 받을 때 receiveEvent함수 호출
    
  Serial.begin(9600);           
}

마스터 아두이노와 다른 접은 Wire.begin(address) 함수의 주소를 지정해 줘야 합니다. 데이터 수신과 요청에 대한 onReceive()함수와 onRequest()함수를 setup()함수에 한번만 표현합니다.

이제 데이터 수신과 요청에 대한 이벤트 호출함수에 대한 동작만 코딩하면 됩니다.

데이터 수신하기

void receiveEvent(int howMany) { //전송 데이터 읽기
  while (Wire.available()>1) { 
    char ch = Wire.read(); 
    Serial.print(ch);         
  }
  int x = Wire.read();    
  Serial.println(x);      
}

데이터 요청 응답하기

void requestEvent() { //요청 시 수행 함수
  Wire.write("ok!\n");   
}

종합해보면,

[마스터 아두이노 소스]

#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600); 
}

byte x = 0;

void loop() {
  Wire.beginTransmission(1);                
  Wire.write("good ");       
  Wire.write(x);             
  Wire.endTransmission();    
     
  delay(500);
  
  Wire.requestFrom(1, 4); //슬레이브(1)에 4byte 요청
  while (Wire.available()) {
    char c = Wire.read(); 
    Serial.print(c);        
  }    
  x++;
  if(x==6)x=0;  
}

위 코딩을 보면 데이터 전송과 데이터 요청 사이의 delay()함수를 주었습니다. 그 이유는 서로 충돌이 일어나지 않게 일정시간 딜레이 주었는데 한번 딜레이 함수없이 가상시뮬레이터에서 실행 시켜보세요. 어떤 결과가 나오는지 확인을 하시기 바랍니다.

[슬레이브 아두이노 소스]

#include <Wire.h>

void setup() {
  Wire.begin(1); //슬레이브 주소                
  Wire.onRequest(requestEvent); //요청시 requestEvent함수 호출
  Wire.onReceive(receiveEvent); //데이터 전송 받을 때 receiveEvent함수 호출

  Serial.begin(9600);           
}

void loop() {
  delay(500);
}

void receiveEvent(int howMany) { //전송 데이터 읽기
  while (Wire.available()>1) { 
    char ch = Wire.read(); 
    Serial.print(ch);         
  }
  int x = Wire.read();    
  Serial.println(x);      
}
void requestEvent() { //요청 시 수행 함수
  Wire.write("ok!\n");   
}

여기서 available()함수에서 왜 1보다 커야 하는지 한번 0보다 크다일 때와 1보다 크다 일때는 비교해보세요. 그 차이점을 구별하시기 바랍니다.

4. 결과


결과는 마스터 아두이노는 "good " 문자열과 x값을 0~5까지의 값을 슬레이브 아두이노에 보내고 슬레이브 아두이노는 전송 된 "good 숫자"의 값을 읽고 시리얼모티너로 출력합니다. 그리고 슬레이브 아두이노는 데이터 요청이 오면 "ok!\n"문자열 값을 마스터 아두이노에 보내고 마스터 아두이노는 "ok!\n"문자열을 읽고 시리얼모니터로 요청/응답이 정상적으로 처리 되었는지 해당 메시지로 확인 할 수 있습니다.