준비물

1. I2C 신호를 USB로 변환하는 보드(제가 사용한 보드는 CH341A 입니다.)

2. 점퍼선, 점퍼(2pin 사용)

3. Flash loader 프로그램(1번에 사용하는 보드와 호환되는 프로그램 필요)

- 펌웨어를 업데이트 할 칩 회사에 문의하면 받을 수 있습니다.

 

업데이트 순서

1. 칩 회사에서 펌웨어 입수

2. 변환 보드의 I2C 핀과 펌웨어를 넣고자 하는 보드의 I2C 핀을 점퍼선으로 연결

- 위 변환 보드의 경우 Output voltage를 3.3v 또는 5v로 선택할 수 있습니다

- 펌웨어를 넣고자 하는 보드의 전압을 도면으로 확인하고 점퍼를 끼워 사용할 전압을 선택해야합니다.

3. Flash loader 프로그램을 실행 후 받은 펌웨어 업로드

- 프로그램 메뉴얼도 칩 회사에서 받을 수 있습니다.

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

MCU I2C 통신 데이터 읽는 방법  (0) 2025.02.04
I2C 통신이란?  (0) 2022.11.17
UART 디버깅용 print함수 만들기  (0) 2022.11.10

I2C 통신으로 0110을 전송한다고 예시를 들어보자.

SCL이 상승엣지일 떄 값을 읽는다고 가정

▶ 데이터는 SCL이 low일때 변경돼야합니다.

 

값 읽는 순서

1. Start : SCL이 high일때 SDA가 low로 변하는 경우, 데이터 전송 시작

2. 0을 전송하므로 SCL이 low일때 0을 유지

3. SCL이 high가 되는 시점에 0인 SDA값을 읽는다.

4. SCL이 low일때 SDA는 1로 변경된다.(파란선 시점에서 데이터 변경)

5. SDA가 1을 유지하고있으면 SCL이 high가 되는 시점에 1을 읽는다.(데이터가 바뀐 후 빨간 선에서 데이터 읽기)

6. 다음 전송할 값이 1이므로 1을 계속 유지하고있으면 SCL의 다음 high 시점에서 또 1을 읽는다.

7. SCL이 low일 때 SDA값이 0으로 변경

8. SCL이 high일때 SDA 0을 읽는다

9. 그럼 0110 이 제대로 전송된 것을 확인할 수 있다

10. Stop: SCL이 high일때 SDA가 high로 변하는 경우, 데이터 전송 끝

 

제가 공부한 부분을 기록한 것이므로 오류가 있을 수 있습니다.

틀린 부분을 알려주시면 수정하겠습니다.

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

I2C 통신으로 칩 펌웨어 업데이트 방법  (0) 2025.02.06
I2C 통신이란?  (0) 2022.11.17
UART 디버깅용 print함수 만들기  (0) 2022.11.10

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

+ Recent posts