본문 바로가기

IT/ROS

[ROS] 14. Jetson Nano 와 Arduino 의 I2C 통신

반응형

☞ 메인보드 : Jetson Nano Developer Kit
☞ 운영 체제 : Ubuntu 18.04 - JetPack 4.4.1
☞ ROS 버전 : Melodic
☞ IDE : Visual Studio Code
☞ 언어 : Python




목차

1. I2C 란?
2. Jetson Nano(Master)
3. Arduino(Slave)
4. 실행 결과









메인보드로 Jeston Nano를 사용하면서 아두이노와의 통신 글을 올리곤 했는데, 아두이노 이외 MCU 중에서 그렇게 유명하다는 STM32나 인텔 8051은 써볼 엄두도 나지 않는다. 어셈블리어나 C에 익숙하지 않은 내가 갑자기 AVR을 공부하겠다며 AVR Studio 4를 설치했다가 한 달 동안 건드리지 않고 결국 삭제했던 기억이 있기 때문이다.

Jetson이 튼튼한 회로 설계를 했는지 모르겠으나 작업을 하면서 라즈베리파이를 3개 터뜨렸고... PWM이라는 녀석을 아주 쉽게 제어할 수 있는 아두이노는 내게 최적화된 MCU였다. ROS 포스팅에 통신 글이 무지 많은데 이쯤이면 통신 덕후라고 생각할 것 같기도 하다. (절대 그렇진 않다. 요청이 있어서 올릴 뿐...) 통신 관련 소스는 대부분 라이브러리로 정리되어있어 일반인들이 사용하기 쉬운데 가만 보면 꼭 삽질하게 만들 때가 있어 어중간하게 알고 있으면 더욱 어려운 작업 중 하나라고 생각한다.

아두이노를 다뤄본 사람이면 알겠지만 아두이노에서 사용할 수 있는 통신 방법은 꽤 다양하다. 대표적으로 직렬 통신(UART)으로 불리는 시리얼 통신이 있는데 송수신 데이터 값을 모니터링할 수 있는 시리얼 모니터와 시리얼 함수가 있어 간단하게 사용할 수 있다는 장점이 있다. 다만 시리얼 통신은 한쪽 방향으로만 전송이 가능한 단방향 통신(Simplex)으로 동시 데이터 처리가 필요한 경우에는 다른 통신 방법을 채택해야 한다는 단점이 있다. 이때 떠오르는 통신 방법으로는 보통 SPI, I2C, CAN 등이 있는데, 오늘은 I2C에 대해서 포스팅을 진행해보려 한다.

 

 

① I2C 란?

I2C(Inter-Integrated Circuit)는 IIC, I 스퀘어 C 또는 TWI(Two Wire Interface)라고 불리는데 접두사 Inter가 상호 또는 ~사이에 라는 의미를 가지고 있고, integrated Circuit의 집적 회로라는 의미를 더해 '집적회로 간의 연결'이라는 의미 정도로 볼 수 있겠다. 아두이노는 우노(UNO) 보드 기준으로 아날로그 4번 핀 SDA(Serial DAta) 핀과 아날로그 5번 핀인 SCL(Serial CLock) 핀이 I2C 통신에 사용되는데, 양방향 통신(Full-duplex) 흉내가 가능하나 I2C 통신은 반이중 통신(Half-duplex)으로 분류되는 이유가 동시에 양쪽 방향에서 데이터를 전달할 수는 없기 때문에 송수신하는 데이터 량이 많고 빈도가 잦으면 속도가 저하된다는 점 때문이다.


 

 

※ 통신에 필요한 부품이 있는가?

Jetson 보드와 아두이노 보드 모두 I2C 통신 버스가 있어 따로 쉴드나 인터페이스가 필요하진 않다. 단지 양쪽 보드를 연결할 점퍼 케이블 2가닥(SDA, SCL)이 필요하다. 그리고 Jetson에서는 GitHub에서 쉽게 접할 수 있는 오픈소스 libi2c나 WiringPi 패키지를, 아두이노에서는 Wire 라이브러리를 이용하면 코딩이 매우 간편해진다.


 

 

※ 혹시 라즈베리파이 써보셨나요?

라즈베리파이의 경우 I2C 통신을 할 때 bcm2708이라는 Low Level 드라이버와 i2c-dev 패키지를 사용해서 통신을 제어할 수 있다. 디지털 신호에서 HIGH 상태도 아니고 LOW 상태도 아닌 애매한 상태를 Floating(플로팅) 현상이라고 하는데 이렇게 이도 저도 아닌 신호의 흐름을 HIGH, LOW 상태로 다시 밀어줘 올바른 흐름으로 보내는 것을 풀업, 풀다운 저항이라고 한다.

라즈베리파이는 입출력 모두 3.3V - 0V로 지정해 사용할 수 있는데, 앞서 말했듯 Floating 현상을 방지하기 위해 고정적으로 내부에 풀업 저항을 달아준 GPIO 핀이 있다. (Jetson GPIO 핀도 마찬가지다.) 라즈베리파이 3b+ 보드 기준으로 GPIO 02번, 03번 핀이 그렇다. 이 핀은 아두이노 우노 보드의 디지털 0, 1번 핀처럼 보통은 사용하지 않는다. 이 두 핀으로 데이터, 클럭 값을 처리한다. [입출력 3.3V - 0V 허용 걸린 녀석한테 냅다 5V를 꽂아버리면 바로 기절해버리는 연약한 파이를 볼 수 있다.]

 

 

 


Jetson Nano(Master)

 

Jetson Nano 2GB Developer Kit 핀[출처 : https://developer.nvidia.com/embedded/learn/jetson-nano-2gb-devkit-user-guide]



Jetson Nano 보드에는 2개의 I2C 버스가 있는데 이는 -- 명령어로 확인할 수 있다. GPIO 핀 배열을 보면 I2C 0번 버스가 27번, 28번 핀이고 1번 버스가 3번, 5번 핀으로 배정되어 있음을 알 수 있다. [포스트에서는 3번 5번 핀의 1번 버스로 진행한다.]

i2c-tools 패키지가 필요한데 Jetson Nano에는 이미 설치된 상태일 것이다. 만약 없다고 한다면 설치를 해야한다.

 

 

sudo apt install i2c-tools libi2c-dev python-smbus

 

 

아두이노를 사용하지 않으면 보통 PCA9685라는 드라이버 모듈을 통해 I2C 통신을 하거나 따로 인터페이스를 사용하여 통신하는 것 같다. 아래에 Jetson 개발자 JetsonHacks 사이트에 가면 I2C 이외에도 아주 많은 참고자료를 확보할 수 있으니 확인해보면 좋겠다.

 

 

 

Jetson Nano - Using I2C - JetsonHacks

Here we connect servo motors to the NVIDIA Jetson Nano over I2C via a PWM Servo Driver. The code is written in Python through the Adafruit ServoKit library.

www.jetsonhacks.com

 

 

 

※  주소 확인(수정 23.05.16)

 

터미널을 실행하고 주소 확인을 위해 먼저 모듈 리스트를 확인하자.

 

 

lsmod | grep i2c_

 

 

 

위와 같이 i2c 모듈이 제대로 마운트 된 것을 확인하고 나면 -l 옵션을 통해 i2c 버스의 리스트를 확인하자.

 

 

i2cdetect -l

 

 

지금은 젯슨 보드가 아닌 노트북에서 멀티 부팅으로 우분투를 사용하고 있는데 필자의 노트북의 경우 리스트를 확인했을 경우 아래와 같은 결과가 나온다.

 

 

 

 

위의 내용에서 확인할 수 있는 것은 SMBus 패키지와 함께할 수 있는 i2c 버스가 3개 있다는 것이다.

 

  • i2c-2 : port 0
  • i2c-4 : port 4
  • i2c-3 : port 2

 

위의 3개의 버스 번호로 주소를 알아낼 수 있다. 다음은 port 0번의 i2c 2번 버스의 주소를 알아내는 명령어이다.

 

 

i2cdetect -y 2

 

 

 

 

주소는 50, 57이 나왔다. 둘 중 하나, 이따가 소스코드에 적어줄 0x50을 기억해놓자.

 

 

 

 

 

  그룹 추가

 

// yasun95 자리에는 본인의 유저명을!
sudo usermod -aG i2c yasun95
groupadd -f -r gpio
sudo usermod -a -G gpio yasun95

 

 

중요한 작업이니 꼭 해주기 바라요. 이거 입력 안 하면 I2C를 사용할 수 없습니다. (。T ω T。)

yasun95는 필자의 유저명이니 각자 본인의 유저명을 입력해주기 바란다.

 

 




※ 코드 작성

 

#! /usr/bin/python3
import smbus as sm

address = 0x50
bus = sm.SMBus(1)

def writeNumber(value):
    bus.write_byte(address, value)
    return -1

def readNumber():
    number = bus.read_byte(address)
    return number

while True:
    data = 150

    writeNumber(data)
    number = readNumber()




 


Arduino(Slave)

 

 

아두이노 통신에 대해서는 이전에 Rosserial 통신으로 포스트를 작성했던 적이 있다. 아마 아래에 있는 포트 접근 허가에 대한 내용도 있을 것이다. 만약 아두이노 설치를 안 했거나 ROS와의 퍼블리셔-서브스크라이버에 대한 내용을 찾고 있다면 밑에 있는 포스트를 읽으면 된다.

 

 

 

[ROS] 8. ROS + Arduino 와 통신 Rosserial

☞ 메인보드 : Jetson Nano Developer Kit ☞ 운영 체제 : Ubuntu 18.04 - JetPack 4.3 ☞ ROS 버전 : Melodic ☞ 언어 : C++ 목차 ○ 1. 아두이노 ○ 2. rosserial ① 아두이노 https://www.arduino.cc/ 현재 1.8..

95mkr.tistory.com

 

 

 

※ 포트 접근 허가

 

dmesg | grep tty
sudo chmod a+rw /dev/ttyACM0

 

 

 

 

I2C는 2가닥의 점퍼선으로 연결을 하는데 포트 접근 권한을 부여하는 이유는 아두이노 소스코드를 업로드할 때 필요하기 때문이다. 보통은 ttyACM0 포트에 연결이 되었다고 나오는데 다르게 나오면 권한을 해당 포트에 줘야 하니 아래의 /dev/ttyACM0 자리에 그 포트를 입력해주면 된다.

 

 

 

 

※ 코드 작성

 

#include <Wire.h>

int LED = 7;
int ledState = 0;
const int Address = 0x50;

void setup()
{
  Serial.begin(9600);
  Wire.begin(Address);
  Wire.onReceive(receiveEvent);
  Wire.onRequest(sendData);
  
  pinMode(LED, OUTPUT);
}

void receiveEvent(int bytes) {
  ledState = Wire.read();
  Serial.print("Receive Data : ");
  Serial.println(ledState);
}

void loop()
{
  if(ledState > 125) {
    digitalWrite(LED, HIGH);
  }
  else {
    digitalWrite(LED, LOW);
  }
}

void sendData(){
    Wire.write(ledState);
}







④ 실행 결과



간단히 125 이상의 값을 받으면 LED 점등, 125 이하의 값을 받으면 LED 소등을 했다. 초기에 코드는 문제없이 돌아갈 텐데 왜 안 되는 거지 하는 시기가 있었다. JetsonHacks의 파일을 뜯어본 결과 그룹 추가 때문에 그랬다.... 는 것을 알았다. 쓸데없이 시간을 소비한 후에는 현타가 오지만 시간이 조금 지나 해결이 되면 만족스러운 삽질이었다고 혼자 위로하는 하루였다. 




왜 갑자기 Python으로 작성했니 하시는 분이 있을 텐데 아마 기존 포스트를 쭉 읽어주신 분들은 C++에 적응이 되었을 거다. C로 작성을 할지 Python으로 작성을 할지 고민을 했는데, 비교적 간단한 Python에 비해 C로 작성할 때는 C 표준 라이브러리 외에도 smbus 라이브러리와 함께 시프트도 설명하려니 글이 매우 길어질 것 같아서 기회가 되면 다음에 정리해서 올리려고 이번에는 Python으로 작성했다.

 

 

 

 

 

반응형