반응형
목적: 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);
반응형