I2C 통신

- 데이터를 전송하는 SDA, Clock주기를 전송하는 SCL 두개의 버스로 디바이스끼리 데이터를 송/수신할 수 있는 통신 방식

- 동기식 통신 방식

 

[동기 vs 비동기]

동기 : 요청을 보냈을 때 응답이 돌아와야 다음 동작 수행(데이터가 전달된다는 것을 알려주는 용도로 Clock 사용)

비동기 : 요청을 보냈을 때 응답상태와 상관없이 다음 동작 수행

 

하나의 마스터에 다수의 슬레이브가 연결되어있습니다.

마스터에서 Clock을 생성하고 이 Clock에 맞춰서 SDA를 송/수신합니다. 그러므로 송/수신이 동시에 될 수 없습니다.

각 슬레이브에 주소가 있기때문에 원하는 슬레이브의 주소에서 해당 데이터를 읽어오거나 쓸 수 있습니다.

슬레이브의 주소를 마스터가 알고있어야합니다.

 

MCU 1대와 I2C통신을 지원하는 여러개의 IC칩을 연결하는 경우입니다.

 

 

[마스터에서 슬레이브로 1byte데이터를 쓸 때 데이터 규격]

 

# 통신 순서

  • 마스터가 시작 신호 전송
  • MCU와 연결된 IC의 RAM주소를 전송
  • 마지막 비트는 0으로 줘서 데이터를 쓰겠다는 것을 알림
  • Slave가 나 데이터 받았다는 뜻으로 ACK신호 전송(LOW신호 전송)
  • Master가 데이터를 쓸 곳의 주소를 전송
  • 잘 받았으면 Slave가 ACK 전송
  • Master가 쓸 데이터 전송
  • ACK
  • 끝났으면 STOP

# ACK 신호 보내는 경우

  • 데이터를 잘 받았을 때

 

[마스터에서 슬레이브로부터 1byte데이터를 읽을 때 데이터 규격]

# 통신 순서

  • 마스터가 시작 신호 전송
  • MCU와 연결된 IC의 RAM주소를 전송
  • 일단 쓴다는 뜻으로 마지막 비트는 0 전송
  • ACK
  • Master가 데이터를 읽을 곳의 주소를 전송(그냥 가리키기는 용도)
  • ACK
  • 이제 읽을꺼니까 다시 시작한다는 신호 전송
  • 읽을 IC의 RAM 주소 전송
  • 마지막 비트는 읽는다는 뜻으로 1 전송
  • ACK
  • Slave가 해당하는 데이터 전송
  • 데이터를 다 읽었다는 의미로 NACK신호 전송(HIGH신호 전송)
  • STOP

# NACK 신호 보내는 경우

  • 수신측에서 뭔가 다른 작업을 하고있거나 통신을 시작할 준비가 안되었을 때
  • 전송 중 수신측에서 이해하지 못하는 데이터나 명령을 받을 때
  • 전송 중 수신측에서 더 이상 데이터 바이트를 수신할 수 없을 때
  • READ 동작시 Master가 수신측으로 작동하면서 예상한 만큼 데이터를 읽었을 때 더 이상 안 보내도 된다고 Slave에 알릴 때

 

[신호의 시작과 끝]

[데이터를 읽을 때 Repeated START부분]

[비트 전송 과정]

SCL이 HIGH인데 SDA가 HIGH에서 LOW가 되면 시작 → 주소 7비트를 보낸다 → 읽을지 쓸지 1비트 전송 → ACK → 데이터 8비트 전송 → ACK → SCL이 HIGH인데 SDA가 LOW에서 HIGH가 되면 끝

데이터 교환전에는 SDA, SCL모두 1을 유지합니다.

ACK전에 0이면 데이터 쓰는중, 1이면 데이터 읽는중을 의미합니다.

 

SDA와 SCL에 풀업저항을 달아서 전압을 공급합니다.

그러므로 신호가 없을 때는 HIGH를 유지하고 신호가 들어오면 LOW로 시작을 알립니다.

SDA는 SCL의 rising edge와 falling edge 사이에서(SCL=1인 동안) 읽어 들입니다.

 

신호를 읽는 방법이 잘 이해가 안가서 저는 이런 방식으로 읽는데요...

SDA 쿵 (전송 시작) -> SCL 쿵 하나 둘 … (하나에 SDA에서 주소1비트 전송 ...)

 

** SCL은 맞지않으니 SDA만 참고하시기 바랍니다. **

아두이노를 이용해 I2C통신을 구현해 봤습니다.

작성한 코드는 github에 올려뒀습니다.

 

출처

https://velog.io/@d3fau1t/I2C-%ED%86%B5%EC%8B%A0-%EC%9D%B4%ED%95%B4-with-MCP23008

https://enidanny.github.io/iot/i2c-protocol/

https://wowon.tistory.com/225

임베디드 소프트웨어를 짜다보면 PC랑 연결해서 디버깅 해야할수도있습니다.

 

저는 UART통신으로 PC랑 연결해서 디버깅하려고 합니다.

임베디드 코드상에서 printf사용하면 그 내용들이 PC로 전달돼서 시리얼 통신 프로그램에 뜨도록 할 수 있습니다.

이때 printf와 비슷한 효과를 내는 fun이라는 함수를 만들어보려고 합니다.

내장함수를 사용하지않고 필요한 함수들은 만들었습니다.

 

일반 printf함수의 경우 가변인자로 매개변수의 개수 제한이 없습니다.

하지만 임베디드의 경우 CPU종속적이기때문에 인수의 개수는 10개로 제한하려고합니다.

 

# 가변인자란?
인수의 개수와 타입이 미리 정해져있지않다는 것

[조건]

1. 함수 형태는 fun(”Hello %d”, &a) 이고 a값이 123일 때 출력은 Hello 123

2. 출력 형식은 %d, %s이런거 대신 %1, %2, %4로 설정

  • 1은 tU08에서 Char로
  • 2는 tU16에서 Char로
  • 4는 tU32에서 Char로

unsigned char은 tU08
unsigned int는 tU16
unsigned long은 tU32 로 정의했습니다.

 

# 함수의 매개변수가 메모리에 저장되는 방식

: stack에 little endian방식으로 저장됩니다.
함수를 호출할 경우 매개변수가 stack에 저장되고 함수가 종료될 경우 stack에서 소멸됩니다.

 

출력 함수 형태 : fun ("asdfas %1", arg )

문자열과 arg를 받아오는 함수이고 %1자리에 arg를 대입해야합니다.

배열로 하나씩 검사해서 %를 만나면 그 뒷자리가 1,2,4중 어떤 것인지 switch로 구분하고 각 case마다 형변환을 합니다.

 

숫자를 대입했을 때 문자로 바꾸는 방식 : 아스키 코드 사용

[숫자 3221을 문자 3221로 출력하기]
3221 / 1000 +0x30 = 0x33 
221 / 100 +0X30 = 0x32
21 /10 +0X30 = 0x32
1 /1 +0X30 = 0x31
=> 0x33 0x32 0x32 0x31을 문자로 출력해보면 3221로 출력되는 것을 확인할 수 있습니다.

 

작성한 코드는 github에 올려뒀습니다.

 

! 코드 오류 발생

1. error: conflicting types for '함수이름' : 함수의 위치가 잘못돼서 발생한 오류입니다.

2. invalid operands to binary % : %, / 연산자는 integer형태일 때만 가능합니다. (int)를 붙여 형변환을 시켜주면 됩니다.

3. invalid use of void expression : 함수가 void로 정의되어있는데 return을 char로 하려면 char로 맞춰줘야합니다.

 

# 코드 작성중 발생한 이슈

1. argbuffer에는 받아온 변수값의 주소를 저장하는데 계산해야하는 것은 해당주소의 값인데

   이를 어떻게 받아올수있을까

   - 해결 전 : tU08 h = argbuffer[argindex]

   - 해결 후 : tU08 h = *(tU08*)argbuffer[argindex]

2. 전역변수로 선언한 format 배열을 0으로 초기화했는데 함수에서 어떻게 입력된 값으로 format을 초기화하지

    - 함수에서 사용되는 format배열은 지역변수이기때문에 같은 이름을 사용할 경우 전역변수로 값을 대입할 수 없습니다. 

      그러므로 전역변수는 format으로 두고 input_fomat이라는 이름으로 값을 받아와서 for문으로 하나씩 format에

      대입했습니다.

 

*(tU08*) 의미

tU08 a1=100;
tU08* b1=&a1;
tU08 c1=*(tU08*)b1;

printf("%d\n",a1); // 100
printf("%d\n",b1); // 6422014
printf("%d\n",c1); // 100

b1 : a1의 주소를 저장

c1 : b1의 값이 a1의 주소이므로 그 주소의 값을 저장(a1값 저장)

본 코드에서 argbuffer에는 입력받은 값의 주소들이 저장되어있습니다.

그러므로 그 주소의 값을 불러와야합니다. 그래서 *(tU08*)을 붙여서 원하는 값을 불러오도록 설계했습니다. 

 

'임베디드' 카테고리의 다른 글

I2C 통신으로 칩 펌웨어 업데이트 방법  (0) 2025.02.06
MCU I2C 통신 데이터 읽는 방법  (0) 2025.02.04
I2C 통신이란?  (0) 2022.11.17

 

$ mkdir bagfiles
$ cd bagfiles
$ rosbag record -a -O [file name]

 

roslaunch 파일을 실행시켰을 때 topic list

launch을 실행시킨 후  rosbag을 한 결과 발생한 오류

위 오류를 없애기 위해 제가 사용할 토픽만 기록이 되도록 했습니다.

 

$ rosbag record -a -x "[토픽이름1]|[토픽이름2]|....|.....|...."

 

모든 토픽을 저장

 

몇개의 토픽을 제거하고 필요한 토픽만 저장

 

방법

터미널 1: roscore

터미널 2: rosrun cvbridge_tutorials marker_detection. y 

터미널 3: rqt_image_view
터미널 4: rosbag play [bag file]

 

$ rosbag record "/camera/color/image_raw"

코드에서 사용하는 토픽이 카메라 이미지 토픽하나이기때문에 위 토픽만 기록하도록 했습니다.

 

위 방법을 이용하여 어두울때 마커를 인식한 결과 데이터들을 저장하였습니다.

 

아래와 같이 rosbag파일을 실행시킨 후 다른 터미널에서 실행시킬 파일을 launch하면 됩니다.

 

 

https://velog.io/@legendre13/rosbag

외부 라이다를 이용해 로봇의 위치를 파악하기

RPLiDAR S1

 

SLAMTEC RPLIDAR S1, 알피라이다 S1 360도 레이저 스캐너40M

How to build rplidar ros package

  • Clone this project to your catkin's workspace src folder
  • Running catkin_make to build rplidarNode and rplidarNodeClient

How to run rplidar ros package

  • Check the authority of rplidar's serial-port :
  • ls -l /dev |grep ttyUSB
  • Add the authority of write: (such as /dev/ttyUSB0)
  • sudo chmod 666 /dev/ttyUSB0
  • There're two ways to run rplidar ros package

 

Run rplidar node and view in the rviz

roslaunch rplidar_ros view_rplidar_s1.launch

 

 

모터 시스템에 의해 구동될 때 범위 스캐너 코어는 시계 방향으로 회전합니다.
현재 환경에 대한 360도 스캔을 수행합니다.

 

output

Distance : mm

Heading : degree --> Current heading angle of the measurement

rviz axis

lidar raw data

 

같은 물체로 판단되는 것을 같은 색으로 표현하는 clustering을 한 결과입니다.

 

clustering and position

제 책상위에서 라이다를 실행시켜봤는데 위 이미지에서 볼수있듯이 어떤 물체가 인식된건지 알수가 없었습니다. 그래서 아무 장애물이 없는 곳에 작은 물체를 하나 놓고 제대로 인식이 되는지 확인해보려고 합니다.

먼저 최대한 장애물이 없는 도서관 스터디룸에서 큰 상자를 두고 라이다를 인식시켜봤습니다.

 

*  c++ 코드를 수정했다면 catkin_make를 해줘야 바뀐코드가 적용됩니다.

* open source를 사용했고 제 환경에 맞게 frame_id 와 world_frame은 둘다 laser로 바꾸고 rviz에 text를 띄우기 위해 코드를 더 추가했습니다.  또한 rplidar파일과 함께 실행시키기위해 datmo launch file에 rplidar launch 실행파일을 추가했습니다.

 

아래와 같은 error가 발생한다면 pc에 usb port가 할당되지 않았다는 것을 의미합니다.

터미널에 다음 명령을 입력하고 다시 코드를 실행시키면 오류가 발생하지 않을 것입니다.

$ sudo chmod 777 /dev/ttyUSB0

 

/etc/udev/rules.d위치에 usb port위치를 넣으면 chmod를 계속 하지않아도 됩니다.

lsusb 명령어를 이용하여 usb port 숫자를 알수있습니다.

 

아래와 같은 error가 발생한다면 log file disk의 저장 용량을 초과했다는 의미이므로

$ rosclean purge 명령을 이용하여 에러를 제거할수있습니다.

 

원래 상자 크기가 너비 40cm에 길이 50cm 인데 라이다를 이용해 길이를 재본 결과 35cm, 48cm로 오차가 있는것을 확인하였습니다. 2,3 cm정도의 오차가 발생하므로 5cm level에서 정확도를 가집니다.

 

이는 인식한 크기만큼 네모를 그리도록 한 것입니다. 상자의 위치는 주황색 네모입니다.

 

저는 상자의 크기를 알기때문에 width가 0.3~0.45 안에 속하고 length가 0.45~0.55안에 속하는 크기만 보이도록 했습니다.

 

 

하지만 문제점은 상자가 비스듬하게 위치해있다면 width 와 length가 모두 측정되어 정확히 상자크기 조건을 줄수있지만 한쪽길이만 측정이된다면 원하는 상자이외에 다른 물체까지 네모박스를 그리게 됩니다. 이는 어떻게 해결할수 있을까요?

 

제가 생각한 방법은 로봇이 움직일 거니까 다른  raw data들은 가만히 있기때문에 네모 상자를 그리더라도 알아서 배제를 시킬수있습니다. 그래서 저는 L1 또는 L2길이가 0.4~0.5이내로 들어오게 된다면 로봇의 원래 길이인 L1=0.48, L2=0.49를 대입했습니다.

 

그럼 여러개의 네모 상자가 만들어질텐데 이 중 움직이는 것이 로봇(축의 약간 왼쪽에 위치)이라고 생각하는 것입니다.

로봇의 경우 프레임사이 빈 공간때문에 라이다로 측정하기 힘들어 사이사이 폼보드를 부착하여 진행했습니다.

 

 

ros 명령어

1. 파일 이동

mv file1 dir1/

(앞에 파일이 오고 뒤에 디렉토리가 오는 경우)

file1 파일을 dir1 디렉토리로 이동합니다.

mv file1 file2 dir1/

여러개의 파일을 한번에 이동시킬 수 있습니다.

 

연구실에서 로봇의 위치를 측정하였고 로봇의 방향이 나오도록 해봤습니다.

마커아래에 라이다가 있습니다.

 

그 결과 아래와 같이 많은 사각형이 발견되었습니다.

길이 조건을 더 상세하게 주어 로봇만을 감지하도록 하였습니다.

 

blog

https://github.com/robopeak/rplidar_ros

https://github.com/hhayoung/object_detection_gl_ros

https://github.com/LucasWEIchen/lidar_tracking

https://github.com/kostaskonkk/datmo

 

+ Recent posts