07. HAL 입문기2 , I2C 통신 구현

owljun·2025년 8월 1일

학습 목표

  • I2C 통신을 위한 H/W 회로 구성방법
  • I2C 통신을 위한 주소 확인 방법
  • CubeMX, IDE를 활용하여 HAL 기반 핸들링 코드 작성

H/W 구성

  • STM32 Nucleo-F103RB 보드 (Master)
  • PCF8591P + YL-40 센서 모듈 (Slave)
  • 브레드보드
  • 5.1k옴 저항 2 (풀업 저항, 4.7k 저항이 없는관계로 대체)
  • 선 연결 (실사)
  • 선 연결 (그림)

S/W 구성

이전 포스팅과 동일 (시리즈 06. HAL 입문기1 참고)


CubeMX 세팅 (.ioc)

  • I2C1 활성화 , 핀 맵에서 PB9 , PB8 각각 SDA, SCL 할당
  • (선택사항) HSI -> HSE 모드 전환 (사용중인 보드에서 지원해야함)
  • Pinout & Configuration -> RCC -> HSE : Crystal/Ceramic Resonator 설정
  • Clock Configuration -> 중하단 PLL 인풋을 HSE 로 설정 , 배율 x9 설정 -> System Clock Mux : PLLCLK 설정

코드 작성

  • include 문 작성
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "string.h"
/* USER CODE END Includes */
  • PCF8591T 주소 설정
/* USER CODE BEGIN PD */
#define PCF8591_I2C_ADDR (0x90) // 7비트 주소 0x48 << 1 = 0x90
/* USER CODE END PD */
  • 데이터 송,수신용 변수선언
uint8_t ldr_value = 0;
uint8_t pot_value = 0;
char uart_buf[50];
  • 디버그용 UART 송신 함수 프로토타입 선언 void UART_Transmit_String(char*);
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_I2C1_Init(void);
void UART_Transmit_String(char*);
/* USER CODE BEGIN PFP */
  • main 함수 작성
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */
  /* MCU Configuration--------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* USER CODE BEGIN Init */
  /* USER CODE END Init */
  /* Configure the system clock */
  SystemClock_Config();
  /* USER CODE BEGIN SysInit */
  /* USER CODE END SysInit */
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  MX_I2C1_Init();
  /* USER CODE BEGIN 2 */
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
	  // 1. 조도 센서 값 읽기 - AIN0
	  // 컨트롤 바이트 : 0x40 (0b01000000) -> AIN0 선택, Auto-increment OFF
	  uint8_t control_byte_ldr = 0x40;
	  // 컨트롤 바이트 전송
	  HAL_I2C_Master_Transmit(&hi2c1, PCF8591_I2C_ADDR, &control_byte_ldr, 1, 100);
	  // ADC 값 수신
	  HAL_I2C_Master_Receive(&hi2c1, PCF8591_I2C_ADDR, &ldr_value, 1, 100);
	  // 2. 가변저항 (Potentiometer) 값 읽기 - AIN1
	  // 컨트롤 바이트 : 0x41 (0b01000001) -> AIN1 선택, Auto-increment OFF
	  uint8_t control_byte_pot = 0x41;
	  // 컨트롤 바이트 전송
	  HAL_I2C_Master_Transmit(&hi2c1, PCF8591_I2C_ADDR, &control_byte_pot, 1, 100);
	  // ADC 값 수신
	  HAL_I2C_Master_Receive(&hi2c1, PCF8591_I2C_ADDR, &pot_value, 1, 100);
	  // 3. TO DO HANDLE
	  sprintf(uart_buf, "LDR: %d, Pot: %d \r\n", ldr_value, pot_value);
	  UART_Transmit_String(uart_buf);
	  HAL_Delay(1000);
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
  • UART 송신함수 구현
/* USER CODE BEGIN 4 */
void UART_Transmit_String(char* str)
{
	HAL_UART_Transmit(&huart2, (uint8_t*)str, strlen(str), HAL_MAX_DELAY);
}
/* USER CODE END 4 */

결과

  • Putty 로 동작 확인

주소는 어디서 보고 쓰나요?

  • 사용하는 센서, 모듈의 주소값을 확인하고 사용해야한다, 주로 제조사에서 주소를 할당해놓기 때문에 데이터시트를 참조하면 된다.

  • 이번 예제에서 사용한 PCF8591T 를 예시로 설명하자면

  • 데이터시트를 열어보면 위와 같이 R/W 을 제외한 7bit의 주소값을 구해낼수 있다.
  • 7bit + R/W 코드를 삽입하기위해 비트쉬프트연산(<<)을 해준다.
  • 위 그림의 주소를 코드로 나타내면 아래와 같다.
#define PCF8591_I2C_ADDR (0x90) // 7비트 주소 0x48 << 1 = 0x90
  • 그럼 A2, A1, A0 는 뭔데요?
  • AIN0 , AIN1 ... 과 같은 핀을 사용해 외부 센서 등의 부품을 연결해서 사용할 수 있다.
  • 이번에 사용한 모듈은 PCF8591T + YN-40 으로서 A0, A1 핀에 내부적으로 센서와 , potentiometer 부품이 할당되어있어, 따로 외부 연결은 하지 않았다.
  • 결론은 , 위 내용이 중요한것이 아니고, A0, A1, A2 핀에 어떤 전압이 걸려있는가에 따라 값이 달라진다는 것이다.
  • GND가 인가되어있다면 0 , VCC가 인가되었다면 1을 나타낸다.
  • 예시 : A0 : GND , A1 : GND , A2 : VCC -> 1001100 + R/W [bit]
  • 이걸 통해 H/W 주소를 알아낼 수 있다.
  • 컨트롤 비트를 통해 연결된 센서 하나하나를 선택해보자.

  • 데이터 시트를 확인해보면 위와같이 컨트롤 비트 정보를 제공한다.
  • 각 비트는 위치에 할당된 기능들을 ON/OFF 할수 있게끔 설정되어있으며, 이번 제품의 경우에는 LSB기준 2칸이 채널 주소를 설정할 수 있게끔 되어있다. (4핀 -> 222^2)
  • A0 에 내부적으로 연결된 센서를 컨트롤하려면 0b01000000
    • (1 -> DAC 기능 ON) , (BOLD : 채널주소)
  • 코드로 나타내면
// 0x40 (0b01000000) -> AIN0 선택, Auto-increment OFF
uint8_t control_byte_ldr = 0x40;
// 컨트롤 바이트 전송
HAL_I2C_Master_Transmit(&hi2c1, PCF8591_I2C_ADDR, &control_byte_ldr, 1, 100);
// ADC 값 수신
HAL_I2C_Master_Receive(&hi2c1, PCF8591_I2C_ADDR, &ldr_value, 1, 100);
  • 흐름은 I2C 이론에서 설명한 것과 같이 아래처럼 이루어 진다.
    • 마스터 -> 슬레이브에게 W Flag 포함하여 송신
    • 마스터 -> 수신대기

마치며

이번 글을 통해 STM32 Nucleo 보드와 YL-40/PCF8591 모듈을 활용하여 I2C 통신을 성공적으로 구현했습니다. HAL 라이브러리를 사용함으로써 복잡한 레지스터 설정을 직접 다루지 않고도 추상화된 함수를 통해 손쉽게 통신을 제어할 수 있었으며, 추후 LL을 통해 더 저수준을 다뤄보려고 합니다.

  • 이 예제의 핵심은 다음과 같이 정리할 수 있습니다.

하드웨어 구성의 이해: I2C 통신은 SCL, SDA 두 개의 라인으로 이루어지며, 통신 신호의 안정성을 위해 풀업 저항이 필수적입니다. NUCLEO 보드와 YL-40 모듈의 핀을 정확하게 연결하는 것이 모든 프로세스 중에서 핵심입니다.

I2C 주소 및 컨트롤 비트의 이해: I2C 통신에서는 슬레이브 디바이스의 고유 주소를 정확히 파악해야 합니다. PCF8591T 모듈의 데이터시트를 참조하여 7비트 주소와 8비트 주소의 관계를 이해하고, ADC 채널을 선택하는 컨트롤 비트의 역할을 명확히 하는 것이 중요했습니다. HAL 라이브러리에서는 R/W 비트가 0인 8비트 주소를 사용하여 Transmit와 Receive 함수를 호출함으로써 마스터의 역할을 수행했습니다.

HAL 기반 핸들링 코드 작성: STM32CubeIDE의 CubeMX 기능을 활용하여 I2C 및 UART와 같은 주변 장치를 손쉽게 설정했습니다. 이후 HAL_I2C_Master_Transmit()와 HAL_I2C_Master_Receive() 함수를 순서대로 호출하는 간단한 로직으로 센서 값을 읽어올 수 있었고, sprintf()와 UART를 통해 그 결과를 Putty로 출력하며 실시간으로 동작을 확인했습니다.

이번 예제는 I2C 통신을 처음 접하는 분들에게도 이해를 도울 수 있을것 같습니다. PCF8591T 모듈 외에도 다양한 센서들이 I2C 통신을 지원하므로, 이 경험을 바탕으로 다른 센서들을 다루는 프로젝트로 확장해볼 수 있습니다. 예를 들어, MPU6050 (자이로/가속도 센서), BMP280 (온도/압력 센서) 등 많은 I2C 슬레이브 디바이스를 동일한 방식으로 제어할 수 있습니다.

profile
Embedded S/W Developer :)

0개의 댓글