I2C
- Inter-Integrated Circuit의 약자, 아이투씨 또는 아이스퀘어씨라고 읽음
- Data선(SDA)과 Clock선(SCL) 2개로만 통신이 가능
- 통신속도는 표준모드에서 100KHz, Fast모드에서 400KHz로 비교적 저속이라 GPIO로도 구현가능하다. (내가 사용하는 보드에는 3개의 I2C컨트롤러가 내장되어 있다. 하지만 GPIO로 굳이 구현한다면 마스터는 GPIO output, 슬레이브는 GPIO input으로 설정하면 된다.)
아래 사진과 같이 SDA, SCL 라인에 여러 디바이스가 연결될 수 있다.
SDA, SCL 라인은 각각 풀업저항으로 연결되어있어 통신을 하지 않을 때는 HIGH 상태를 유지하고 하나의 마스터가 통신을 시작하면 LOW레벨로 떨어진다.
버스에 물려있는 누구나 마스터 또는 슬레이브역할을 수행 가능하며 하나가 마스터 역할을 하면 나머지는 모두 슬레이브가 되어 동작하는 프로토콜이다.
마스터는 여러개의 슬레이브 중 I2C 슬레이브 어드레스를 사용하여 하나의 슬레이브와 통신하게 된다. 한 순간에 한 마스터와 한 슬레이브간 통신할 수 있다.
Start조건과 Stop조건
I2C 버스에는 여러 디바이스가 물려있기 때문에 마스터가 알려줄 수 있는 Start와 Stop조건이 필요하다.
Start 조건 : 마스터가 SDA를 LOW로 하고 약간의 시간차를 두고 SCL을 LOW로 한다.
이 Start 신호를 모든 디바이스들이 지켜보고 Slave Address가 들어왔을 때 자기한테 주는 신호인지를 판단하게 된다.
Stop 조건 : SCL라인을 먼저 HIGH로 올리고 SDA라인 마저 풀어서 HIGH가 유지된다.
데이터 전송 포맷
마스터가 슬레이브에게 주로 8비트 단위로 데이터를 전송하게 되는데 처음엔 Slave Address를 뜻하는 7bit를 보내고 8번째 bit는 R/W 신호가 된다.
8bit의 데이터를 전송할 땐 MSB부터 LSB 까지 순차적으로 보낸다.
R/W bit가 0이면 마스터가 Write 하겠다는 신호이고 1이면 Read 하겠다는 뜻이다.
클락라인인 SCL이 라이징 엣지일 때 SDA 신호를 가져가게 된다.
마지막 9번째 bit는 해당 주소의 슬레이브가 마스터에게 값을 잘 받았는지 알리는 Ack/Nack 신호로 0이면 Ack, 1이면 Nack를 뜻한다. 8bit 단위로 데이터 전송할 때마다 계속 Ack/Nack신호가 뒤따라서 수신성공여부를 알리고 마지막 Stop Condition으로 통신이 끝나게 된다.
I2C Slave Address
I2C 버스에는 여러 슬레이브들이 물려있기 때문에 그들을 구별할 수 있는 고유의 주소값이 필요하다.
I2C 칩들은 공장 출하때부터 고유의 주소를 가지며 datasheet를 확인하면 알 수 있다.
마스터가 7bit의 주소값과 함께 R/W신호를 보내고 그 주소값을 가진 슬레이브가 같은 버스에 물려있다면 9번째 bit를 LOW로 떨어뜨려 Ack 신호를 보내고 그때부터 stop condition이 나오기 전까지 계속 통신이 진행되게 된다.
오실로스코프 작동법
일단 SCL과 SDA를 동시에 보기 위해 2채널 모두 사용해야 한다.
먼저 Verticla과 Horizontal의 Scale을 조절해서 전압레벨과 시간 단위를 적당하게 맞춰준다.
Trigger 메뉴를 누르고 종류는 에지, I2C는 디폴트 HIGH상태이다가 통신이 시작하면 LOW로 떨어지니까 경사는 하강 선택, 소스는 CH1/CH2 각각 선택한 뒤 Trigger Level을 돌려 적당하게 맞춰준다. 트리거 모드는 보통으로 선택한다. 자동으로 하면 트리거를 잡고 멈추는게 아니라 계속 돌아가서 다시 잡으니까 눈으로 확인하기 어렵다.
소스코드에 브레이크 포인트를 걸고 트리거되면 돋보기를 클릭해 보기 좋게 줌 스케일과 위치를 설정해준다. 혹은 그냥 시간축(Horizontal)의 Position 과 Scale을 조정해줘도 된다.
CubeMX 설정 및 소스코드
예제코드 출처 : https://github.com/afiskon/stm32-i2c-lcd-1602
/* USER CODE BEGIN 2 */
HAL_StatusTypeDef res;
for(uint16_t i = 0; i < 128; i++) {
res = HAL_I2C_IsDeviceReady(&hi2c1, i << 1, 1, 10);
if(res == HAL_OK) {
char msg[64];
snprintf(msg, sizeof(msg), "0x%02X", i);
HAL_UART_Transmit(&huart3, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);
} else {
HAL_UART_Transmit(&huart3, (uint8_t*)".", 1, HAL_MAX_DELAY);
}
}
/* USER CODE END 2 */
HAL_I2C_IsDeviceReady 함수를 통해 연결된 디바이스를 찾는다.
슬레이브가 가질수 있는 주소는 7비트라 2^7 개니까 0에서 127까지 검색한 후 찾으면 UART로 해당 주소값을 출력한다.
HAL_StatusTypeDef 는 enum 자료형으로 이렇게 생겼다.
참고로 주소를 찾을 때 1비트 왼쪽으로 이동 연산하는 이유는 이전글에서 설명한적 있다.
내가 사용한 LCD모듈에 붙어있는 I2C expander 칩의 datasheet를 확인하면 공장출고 기본 주소값이 아래와 같은데 이거를 R/W비트 제외하고 7비트만 읽어서 0x27 이라고 하는사람도 있고 R/W비트 포함 읽어서 Write기준 0x4E 라고 하는 사람도 있다.
일단 HAL Driver 사용할 땐 R/W비트 포함 8비트 기준으로 사용해야하는 모양인데 진짜 slave address인 7비트 기준으로 쓰고 싶다면 항상 저렇게 왼쪽으로 한칸 시프트 연산 해주면 된다.
그럼 for문에서 i가 decimal 값으로 39일때 슬레이브를 찾고 슬레이브가 9번째 bit를 low로 떨어뜨려 Ack 신호를 보낼 것이다.
코드의 적당한 위치에 breakpoint를 걸고 resume하면서 확인한다.
영상 15초 쯤
현재 I2C버스에 LCD 모듈만 연결해둔 상태이기 때문에,
슬레이브 주소 7비트가 0x27일 때 9번째 비트가 LOW로 떨어지며 ACK 신호가 뜬 것을 확인할 수 있다.
아래 함수로 데이터 전송을 했을 때의 파형
HAL_I2C_Master_Transmit(&hi2c1, lcd_addr, data_arr, sizeof(data_arr), HAL_MAX_DELAY);
'임베디드 개발 > STM32 (ARM Cortex-M)' 카테고리의 다른 글
STM32 , printf 디버깅에 사용 & 변수 값 그래프로 출력하기 2편 ( SWV / ITM ) (3) | 2022.06.06 |
---|---|
STM32 , printf 디버깅에 사용하기 1편 ( UART 통신 ) (0) | 2022.06.06 |
STM32CubeIDE , 프로젝트 복사 붙여넣기 하는법 / clone or duplicate a project for reuse (2) | 2022.06.04 |
STM32 , UART 통신으로 4 digit 7 segment FND 실시간 제어하기 1편 ( 2가지 방법 ) (0) | 2022.06.03 |
STM32 , 74HC595 시프트 레지스터로 FND 제어하기 , 카운터 / 시계 ( SysTick 타이머 사용) (0) | 2022.06.02 |