본문 바로가기
MCU/STM32

[STM32G474] 를 이용한 동일 어드레스를 갖는 64채널 I2C 디바이스 제어

by linuxgo 2025. 8. 12.
반응형

 

목적: STM32G474 마이크로컨트롤러와 TCA9548A I2C 멀티플렉서를 사용하여 동일한 I2C 주소를 가진 64개 디바이스를 제어하는 방법을 구현하고 설명한다.

1. 개요

본 내용은 STM32G474를 사용하여 동일한 I2C 주소를 가진 64개 디바이스를 제어하기 위해 TCA9548A I2C 멀티플렉서를 활용한 구현 방법을 기술한다. TCA9548A의 A0~A2 핀은 하드웨어적으로 고정되어 있으며, 단일 채널 번호(0~63)를 입력받아 TCA 인덱스와 내부 채널을 계산하는 간소화된 함수를 제공한다. 코드는 STM32CubeIDE와 HAL 라이브러리를 기반으로 작성되었으며, 데이터 읽기/쓰기 기능을 지원한다.

2. 시스템 구성

2.1 하드웨어 구성

  • 마이크로컨트롤러: STM32G474 (I2C1 사용, SCL=PB6, SDA=PB7)
  • I2C 멀티플렉서: TCA9548A 8개
    • A0~A2 핀: 하드웨어적으로 VCC(3.3V) 또는 GND에 고정
    • 주소: 0x70~0x77 (예: TCA #0=A0/A1/A2=GND → 0x70)
    • SCL, SDA: I2C1에 연결
    • RESET: VCC에 연결
  • 디바이스: 동일한 I2C 주소 (예: 0x20)를 가진 64개 디바이스
    • 연결: TCA9548A의 출력 채널 (SC0/SD0 ~ SC7/SD7)
    • 예: 채널 0 (TCA #0, SC0/SD0), ..., 채널 63 (TCA #7, SC7/SD7)
  • 풀업 저항: SCL, SDA에 4.7kΩ 저항

2.2 TCA9548A 주소 설정

TCA 번호 A0 A1 A2 I2C 주소
TCA #0 GND GND GND 0x70
TCA #1 VCC GND GND 0x71
TCA #2 GND VCC GND 0x72
TCA #3 VCC VCC GND 0x73
TCA #4 GND GND VCC 0x74
TCA #5 VCC GND VCC 0x75
TCA #6 GND VCC VCC 0x76
TCA #7 VCC VCC VCC 0x77

3. 소프트웨어 구현

3.1 코드 개요

코드는 STM32CubeIDE에서 HAL 라이브러리를 사용하여 작성되었다. 주요 특징은 다음과 같다:

  • 채널 계산: 단일 채널 번호(0~63)를 입력받아 TCA 인덱스(`tca_index = global_channel / 8`)와 내부 채널(`channel = global_channel % 8`)을 계산.
  • 함수: 채널 선택(`I2C_SelectChannel`), 데이터 쓰기(`I2C_WriteDevice`), 데이터 읽기(`I2C_ReadDevice`)로 구성.
  • I2C 설정: 100kHz, 필요 시 400kHz로 변경 가능.

3.2 소스 코드


/* Includes */
/* STM32 HAL 라이브러리와 기본 헤더 포함 */
#include "main.h"
#include "stm32g4xx_hal.h"

/* Private variables */
/* I2C1 핸들: STM32G474의 I2C1 모듈(SCL=PB6, SDA=PB7) */
I2C_HandleTypeDef hi2c1;

/* TCA9548A의 고정된 I2C 주소 배열 (A0~A2 하드웨어 고정) */
/* 예: TCA #0=0x70 (A0/A1/A2=GND), TCA #1=0x71 (A0=VCC,A1/A2=GND) */
const uint8_t tca_addresses[8] = {0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77};

/* Private function prototypes */
/* 시스템 클록 설정: 80MHz HSI+PLL */
void SystemClock_Config(void);
/* GPIO 초기화: A0~A2 고정으로 설정 불필요 */
static void MX_GPIO_Init(void);
/* I2C1 초기화: 100kHz, PB6=SCL, PB7=SDA */
static void MX_I2C1_Init(void);
/* TCA9548A 채널 선택: 글로벌 채널 번호(0~63)로 TCA와 내부 채널 선택 */
HAL_StatusTypeDef I2C_SelectChannel(uint8_t global_channel);
/* 디바이스 데이터 쓰기: 글로벌 채널로 디바이스에 1바이트 전송 */
HAL_StatusTypeDef I2C_WriteDevice(uint8_t global_channel, uint8_t device_addr, uint8_t data);
/* 디바이스 데이터 읽기: 글로벌 채널로 디바이스에서 1바이트 읽기 */
HAL_StatusTypeDef I2C_ReadDevice(uint8_t global_channel, uint8_t device_addr, uint8_t* data);

/* Main function */
int main(void) {
  /* MCU 초기화: HAL 라이브러리 초기화 */
  HAL_Init();
  /* 시스템 클록 설정: 80MHz */
  SystemClock_Config();
  /* GPIO 초기화: I2C 핀만 사용 */
  MX_GPIO_Init();
  /* I2C1 초기화: 100kHz */
  MX_I2C1_Init();

  /* 테스트 데이터: 쓰기 및 읽기용 */
  uint8_t write_data = 0xFF; // 예시 데이터 (0xFF 쓰기)
  uint8_t read_data = 0;     // 읽은 데이터 저장

  /* 메인 루프: 64개 디바이스 순회 */
  while (1) {
    for (uint8_t channel = 0; channel < 64; channel++) { // 채널 0~63
      /* 데이터 쓰기: 채널과 디바이스 주소(0x20)에 0xFF 전송 */
      I2C_WriteDevice(channel, 0x20, write_data);
      /* 데이터 읽기: 채널과 디바이스 주소(0x20)에서 1바이트 읽기 */
      I2C_ReadDevice(channel, 0x20, &read_data);
      /* 디바이스 응답 대기: 디바이스 사양에 따라 조정 */
      HAL_Delay(10);
    }
    /* 전체 루프 대기: 시스템 부하 조절 */
    HAL_Delay(100);
  }
}

/* TCA9548A 채널 선택 함수 */
HAL_StatusTypeDef I2C_SelectChannel(uint8_t global_channel) {
  /* 글로벌 채널(0~63)에서 TCA 인덱스와 내부 채널 계산 */
  /* TCA 인덱스: global_channel / 8 (예: 채널 10 → TCA #1) */
  uint8_t tca_index = global_channel / 8;
  /* 내부 채널: global_channel % 8 (예: 채널 10 → 채널 2) */
  uint8_t channel = global_channel % 8;
  /* TCA9548A 주소 선택 (0x70~0x77) */
  uint8_t tca_addr = tca_addresses[tca_index];
  /* 채널 활성화 비트 설정 (예: channel=2 → 0b00000100) */
  uint8_t data = 1 << channel;
  /* I2C로 채널 선택 명령 전송, 타임아웃 100ms */
  return HAL_I2C_Master_Transmit(&hi2c1, tca_addr << 1, &data, 1, 100);
}

/* 디바이스 데이터 쓰기 함수 */
HAL_StatusTypeDef I2C_WriteDevice(uint8_t global_channel, uint8_t device_addr, uint8_t data) {
  /* 채널 선택: 실패 시 에러 반환 */
  if (I2C_SelectChannel(global_channel) != HAL_OK) {
    return HAL_ERROR;
  }
  /* 디바이스에 1바이트 데이터 전송, 타임아웃 100ms */
  return HAL_I2C_Master_Transmit(&hi2c1, device_addr << 1, &data, 1, 100);
}

/* 디바이스 데이터 읽기 함수 */
HAL_StatusTypeDef I2C_ReadDevice(uint8_t global_channel, uint8_t device_addr, uint8_t* data) {
  /* 채널 선택: 실패 시 에러 반환 */
  if (I2C_SelectChannel(global_channel) != HAL_OK) {
    return HAL_ERROR;
  }
  /* 디바이스에서 1바이트 읽기, 타임아웃 100ms */
  return HAL_I2C_Master_Receive(&hi2c1, device_addr << 1, data, 1, 100);
}

/* 시스템 클록 설정: 80MHz HSI+PLL */
void SystemClock_Config(void) {
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /* HSI 발진기 활성화 및 PLL 설정 */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLM = 1;
  RCC_OscInitStruct.PLL.PLLN = 10;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  HAL_RCC_OscConfig(&RCC_OscInitStruct);

  /* 시스템, AHB, APB 클럭 설정 */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
                              | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
  HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4);
}

/* GPIO 초기화: A0~A2는 하드웨어 고정, I2C 핀만 설정 */
static void MX_GPIO_Init(void) {
  /* GPIOB 클럭 활성화: PB6=SCL, PB7=SDA */
  __HAL_RCC_GPIOB_CLK_ENABLE();
  /* 필요 시 추가 GPIO 설정 가능 */
}

/* I2C1 초기화: PB6=SCL, PB7=SDA, 100kHz */
static void MX_I2C1_Init(void) {
  hi2c1.Instance = I2C1;
  hi2c1.Init.Timing = 0x10909CEC; // 100kHz (STM32CubeMX에서 생성)
  hi2c1.Init.OwnAddress1 = 0;
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c1.Init.OwnAddress2 = 0;
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  HAL_I2C_Init(&hi2c1);
}
        

4. 사용 방법

코드는 단일 채널 번호(0~63)를 사용하여 디바이스를 제어한다. 주요 함수 사용법은 다음과 같다:

  • 채널 선택:
    I2C_SelectChannel(10); // 채널 10 선택 (TCA #1, 내부 채널 2)
  • 데이터 쓰기:
    I2C_WriteDevice(10, 0x20, 0xFF); // 채널 10, 디바이스 0x20에 0xFF 쓰기
  • 데이터 읽기:
    uint8_t data;
    I2C_ReadDevice(10, 0x20, &data); // 채널 10, 디바이스 0x20에서 1바이트 읽기

5. STM32CubeIDE 설정

  • I2C 설정:
    • STM32CubeMX에서 I2C1 활성화 (SCL=PB6, SDA=PB7).
    • Timing: 100kHz (0x10909CEC), 400kHz 필요 시 재설정.
  • GPIO 설정:
    • A0~A2는 하드웨어 고정, GPIO 설정 불필요.
    • I2C 핀(PB6, PB7)만 설정.
  • 클럭: 80MHz HSI+PLL (기본 설정).

6. 디버깅 및 최적화

6.1 디버깅 방법

  • I2C 스캐너: TCA9548A 주소(0x70~0x77) 확인.
    for (uint8_t addr = 0x70; addr <= 0x77; addr++) {
        if (HAL_I2C_IsDeviceReady(&hi2c1, addr << 1, 1, 100) == HAL_OK) {
            // UART로 주소 출력
        }
    }
  • 로직 애널라이저: SCL, SDA 신호 확인.
  • UART 디버깅: 읽기 데이터(`read_data`)를 UART로 출력.
  • 타이밍: 디바이스 응답 느리면 `HAL_Delay(10)` 조정.

6.2 최적화

  • I2C 속도: 디바이스 사양에 따라 400kHz로 변경 (Timing 값 재설정).
  • 다중 바이트: 필요 시 `I2C_WriteDevice`와 `I2C_ReadDevice`에서 데이터 배열 수정.
    uint8_t data[] = {reg_addr, value};
    HAL_I2C_Master_Transmit(&hi2c1, device_addr << 1, data, 2, 100);
 

 

반응형