본문 바로가기
카테고리 없음

[TMS320F28388D] CPU1, CPU2, CM 멀티코어 사용법: DriverLib API로 멀티코어 설정 및 코드

by linuxgo 2025. 8. 17.
반응형

이 문서에서는 Texas Instruments의 TMS320F28388D 마이크로컨트롤러에서 CPU1, CPU2, 그리고 Connectivity Manager(CM) 코어를 활용하여 작업을 분산 처리하는 방법을 DriverLib API를 사용하여 상세히 설명합니다. TMS320F28388D는 C2000 시리즈의 고성능 32비트 마이크로컨트롤러로, 두 개의 C28x CPU 코어(CPU1, CPU2)와 하나의 ARM Cortex-M4 기반 CM 코어를 제공하여 병렬 처리를 지원합니다. 이 문서를 통해 CPU1, CPU2, CM 간의 통신, 자원 공유, 그리고 멀티코어 애플리케이션 구현 방법을 배우고, Code Composer Studio(CCS) 환경에서 실행 가능한 예제 코드를 통해 실제 구현 방법을 익힐 수 있습니다. 각 코드에는 상세한 주석이 포함되어 있습니다.

1. TMS320F28388D 멀티코어 개요

TMS320F28388D는 두 개의 독립적인 32비트 C28x CPU 코어(CPU1, CPU2)와 하나의 ARM Cortex-M4 기반 CM 코어를 포함하며, 각 코어는 다음과 같은 특성을 가집니다:

  • CPU1, CPU2: 각각 최대 200MHz로 동작하는 C28x 코어로, 실시간 제어 작업에 최적화.
  • CM: 최대 125MHz로 동작하는 ARM Cortex-M4 코어로, 네트워크 및 통신 프로토콜 처리에 적합.

멀티코어 아키텍처의 주요 특징

  • 독립적인 코어: CPU1, CPU2는 C28x 아키텍처, CM은 Cortex-M4 아키텍처로 각각 독립적인 프로그램 메모리, 데이터 메모리, 주변 장치를 가짐.
  • 공유 메모리: Global Shared RAM(GSRAM)과 Message RAM을 통해 CPU1, CPU2, CM 간 데이터 교환 가능.
  • 인터프로세서 통신(IPC): IPC 모듈을 통해 CPU1, CPU2, CM 간 통신 및 동기화 지원.
  • 주변 장치 접근:
    • CPU1: 모든 주변 장치 접근 가능.
    • CPU2: 제한된 주변 장치 접근(예: 일부 ePWM, ADC).
    • CM: 이더넷, CAN, USB 등 통신 관련 주변 장치 접근 가능.
  • 클럭: CPU1, CPU2는 시스템 클럭(최대 200MHz), CM은 별도 클럭(최대 125MHz) 사용.
  • 인터럽트: 각 코어는 독립적인 인터럽트 컨트롤러(PIE for C28x, NVIC for CM)를 가짐.

DriverLib API는 C28x 및 CM 코어에 대해 각각 제공되며, IPC 및 공유 메모리 관련 함수를 통해 멀티코어 통신을 간소화합니다.

2. 주요 멀티코어 DriverLib API 함수

2.1. IPC 관련 함수 (C28x: CPU1, CPU2)

  • IPC_sendCommand(base, command, mask, data): CPU1 또는 CPU2에서 상대 코어(CPU2 또는 CM)로 명령 전송.
  • IPC_readCommand(base, command, mask, data): 수신된 IPC 명령 읽기.
  • IPC_setFlag(base, flag): IPC 플래그 설정(예: 인터럽트 요청).
  • IPC_clearFlag(base, flag): IPC 플래그 지우기.
  • IPC_registerInterrupt(base, intNumber, handler): IPC 인터럽트 서비스 루틴(ISR) 등록.

2.2. IPC 관련 함수 (CM)

  • IPC_CM_L_C28x_R_sendMessage(base, message): CM에서 C28x(CPU1 또는 CPU2)로 메시지 전송.
  • IPC_CM_L_C28x_R_readMessage(base, message): CM에서 C28x로부터 메시지 읽기.
  • IPC_CM_setFlag(base, flag): CM에서 IPC 플래그 설정.
  • IPC_CM_clearFlag(base, flag): CM에서 IPC 플래그 지우기.
  • IPC_CM_registerInterrupt(base, intNumber, handler): CM에서 IPC 인터럽트 등록.

2.3. 공유 메모리 관련 함수

  • SysCtl_mapSharedRAM(owner, ramSection): 특정 공유 RAM 섹션의 소유권 설정(CPU1, CPU2, 또는 CM).
  • SysCtl_enableSharedRAMAccess(ramSection): 공유 RAM 접근 활성화.
  • SysCtl_disableSharedRAMAccess(ramSection): 공유 RAM 접근 비활성화.

2.4. CM 부팅 및 제어 (CPU1에서 호출)

  • SysCtl_bootCM(bootMode): CM을 부팅하고 실행 시작 (예: SYSCTL_BOOT_FLASH).
  • SysCtl_resetCM(): CM 리셋.
  • SysCtl_haltCM(): CM 실행 중지.

2.5. 인터럽트 및 주변 장치 설정

  • Interrupt_initModule() (C28x): PIE 초기화.
  • NVIC_EnableIRQ(irqNumber) (CM): Cortex-M4 NVIC 인터럽트 활성화.
  • SysCtl_enablePeripheral(periph): CPU1, CPU2, CM에서 특정 주변 장치 활성화.
  • GPIO_setPinConfig(pinConfig): GPIO 멀티플렉싱 설정(각 코어에서 독립적).

3. 멀티코어 설정 및 동작 원리

  1. CPU1 초기화: 시스템 클럭, GPIO, 인터럽트 설정, CPU2 및 CM 부팅 명령.
  2. 공유 메모리 설정: GSRAM 소유권 및 접근 설정.
  3. IPC 설정: CPU1, CPU2, CM 간 통신 채널 구성, 인터럽트 활성화.
  4. CPU2 초기화: CPU2에서 독립적인 주변 장치 및 작업 설정.
  5. CM 초기화: CM에서 통신 관련 작업(예: 이더넷 패킷 처리) 설정.
  6. 데이터 교환 및 동기화: IPC 또는 공유 메모리를 통해 데이터 및 제어 신호 교환.

4. 멀티코어 예제 코드

아래는 CPU1, CPU2, CM을 활용한 예제 코드로, C2000Ware의 DriverLib를 기반으로 작성되었습니다. CPU1은 ePWM 제어, CPU2는 ADC 측정, CM은 이더넷 패킷 전송을 담당하며, IPC와 공유 메모리로 동기화합니다. 코드에는 상세한 주석이 포함되어 있습니다.

4.1. 예제: CPU1(ePWM), CPU2(ADC), CM(Ethernet) 협력

CPU1은 ePWM1으로 PWM 신호를 생성, CPU2는 ADC로 아날로그 입력을 측정, CM은 ADC 데이터를 이더넷으로 전송합니다.

CPU1 코드 (ePWM 제어)

#include "driverlib.h"
#include "device.h"

// 디버깅용 상태 변수 (0: 초기화 완료, 1: 동작 중, 0xFFFF: 오류)
volatile uint32_t cpu1Status = 0;

// 공유 메모리 정의 (GS0 RAM, PWM 듀티 사이클 저장)
volatile uint32_t *pwmDuty = (uint32_t *)0x0003F800;

// GPIO 초기화 함수
void initGPIO(void)
{
    // GPIO0을 ePWM1A로 설정 (PWM 출력)
    GPIO_setPinConfig(GPIO_0_EPWM1A);
    // GPIO0을 출력 모드로 설정
    GPIO_setDirectionMode(0, GPIO_DIR_MODE_OUT);
    // GPIO0에 풀업 저항 활성화
    GPIO_setPadConfig(0, GPIO_PULL_UP);
    
    // GPIO31을 일반 GPIO로 설정 (디버깅용 LED)
    GPIO_setPinConfig(GPIO_31_GPIO31);
    // GPIO31을 출력 모드로 설정
    GPIO_setDirectionMode(31, GPIO_DIR_MODE_OUT);
    // GPIO31에 풀업 저항 활성화
    GPIO_setPadConfig(31, GPIO_PULL_UP);
    // 초기 LED 상태: OFF
    GPIO_writePin(31, 0);
}

// ePWM1 초기화 함수
void initEPWM(void)
{
    // ePWM1 클럭 활성화
    SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_EPWM1);
    
    // 주기 설정: 100kHz PWM (200MHz / 2000 = 100kHz)
    EPWM_setTimeBasePeriod(EPWM1_BASE, 2000);
    // 카운터 모드: Up 카운터
    EPWM_setCounterMode(EPWM1_BASE, EPWM_COUNTER_MODE_UP);
    
    // PWM_A 동작 설정: 카운터=0일 때 HIGH, CMPA에서 LOW
    EPWM_setActionQualifierAction(EPWM1_BASE, EPWM_AQ_OUTPUT_A, 
                                 EPWM_AQ_OUTPUT_ON_TIMEBASE_ZERO, EPWM_AQ_OUTPUT_HIGH);
    EPWM_setActionQualifierAction(EPWM1_BASE, EPWM_AQ_OUTPUT_A, 
                                 EPWM_AQ_OUTPUT_ON_TIMEBASE_CMPA_UP, EPWM_AQ_OUTPUT_LOW);
    
    // 초기 듀티 사이클 50% 설정 (CMPA = 1000)
    EPWM_setCompareA(EPWM1_BASE, 1000);
    
    // ADC SOCA 트리거 설정: 카운터=0일 때 발생
    EPWM_enableADCTrigger(EPWM1_BASE, EPWM_SOC_A);
    EPWM_setADCTriggerSource(EPWM1_BASE, EPWM_SOC_A, EPWM_SOC_TBCTR_ZERO);
    EPWM_setADCTriggerEventPrescale(EPWM1_BASE, EPWM_SOC_A, 1);
    
    // ePWM 모듈 활성화
    EPWM_enableModule(EPWM1_BASE);
}

// CPU2 및 CM 부팅 함수
void bootCores(void)
{
    // CPU2를 플래시에서 부팅
    SysCtl_bootCPU2(SYSCTL_BOOT_FLASH);
    // CPU2 부팅 완료 대기
    while (!SysCtl_isCPU2BootComplete())
    {
        DEVICE_DELAY_US(1000);
    }
    
    // CM을 플래시에서 부팅
    SysCtl_bootCM(SYSCTL_BOOT_FLASH);
    // CM 부팅 완료 대기
    while (!SysCtl_isCMBootComplete())
    {
        DEVICE_DELAY_US(1000);
    }
}

void main(void)
{
    // 시스템 클럭, PLL, Watchdog, 주변 장치 초기화
    Device_init();
    
    // 기본 GPIO 초기화 (C2000Ware 제공)
    Device_initGPIO();
    // 사용자 정의 GPIO 설정
    initGPIO();
    
    // 인터럽트 컨트롤러(PIE) 및 벡터 테이블 초기화
    Interrupt_initModule();
    Interrupt_initVectorTable();
    
    // 공유 메모리 소유권을 CPU1로 설정 (GS0 RAM)
    SysCtl_mapSharedRAM(SYSCTL_ACCESS_CPU1, SYSCTL_GS0);
    // 공유 메모리 접근 활성화
    SysCtl_enableSharedRAMAccess(SYSCTL_GS0);
    
    // 초기 듀티 사이클 저장 (50%, CMPA=1000)
    *pwmDuty = 1000;
    
    // 시스템 클럭 확인 (200MHz 예상)
    uint32_t sysClockHz = SysCtl_getClock(DEVICE_OSCSRC_FREQ);
    if (sysClockHz != 200000000)
    {
        // 클럭 오류 시 상태 업데이트
        cpu1Status = 0xFFFF;
        // LED OFF로 오류 표시
        GPIO_writePin(31, 0);
        // 디버깅용 정지
        ESTOP0;
    }
    
    // ePWM1 초기화
    initEPWM();
    
    // CPU2 및 CM 부팅
    bootCores();
    
    // 상태 업데이트: 정상 동작
    cpu1Status = 1;
    
    // 글로벌 인터럽트 활성화
    EINT;
    
    // Watchdog 활성화
    SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_WD);
    SysCtl_serviceWatchdog();
    
    // 무한 루프
    for(;;)
    {
        // 디버깅용: 상태에 따라 LED 제어
        GPIO_writePin(31, (cpu1Status == 1) ? 1 : 0);
        
        // 듀티 사이클 변경: 25%(500) ↔ 75%(1500) 순환
        *pwmDuty = (*pwmDuty == 1000) ? 1500 : 500;
        EPWM_setCompareA(EPWM1_BASE, *pwmDuty);
        // CPU2로 듀티 사이클 전송
        IPC_sendCommand(IPC_CPU1_L_CPU2_R, IPC_FLAG0, IPC_ADDR_CORRECTION_ENABLE, *pwmDuty);
        // CM으로 듀티 사이클 전송
        IPC_sendCommand(IPC_CPU1_L_CM_R, IPC_FLAG0, IPC_ADDR_CORRECTION_ENABLE, *pwmDuty);
        
        // 1초 대기
        DEVICE_DELAY_US(1000000);
    }
}

CPU2 코드 (ADC 제어)

#include "driverlib.h"
#include "device.h"

// 디버깅용 상태 변수 (0: 초기화 완료, 1: 동작 중, 0xFFFF: 오류)
volatile uint32_t cpu2Status = 0;

// 공유 메모리 정의 (GS0 RAM: PWM 듀티 사이클, GS1 RAM: ADC 결과)
volatile uint32_t *pwmDuty = (uint32_t *)0x0003F800;
volatile uint16_t *adcResults = (uint16_t *)0x0003FC00;

// ADC 결과 저장 버퍼 (256개 샘플)
#define RESULTS_BUFFER_SIZE 256
volatile uint16_t index = 0;

// ADC 인터럽트 서비스 루틴
__interrupt void adcA1ISR(void)
{
    // ADC 변환 결과 읽기 (ADCIN0 채널)
    adcResults[index] = ADC_readResult(ADCA_BASE, ADC_SOC_NUMBER0);
    
    // 버퍼 인덱스 증가 및 오버플로우 방지
    index = (index + 1) % RESULTS_BUFFER_SIZE;
    
    // ADC 인터럽트 플래그 지우기
    ADC_clearInterruptStatus(ADCA_BASE, ADC_INT_NUMBER1);
    // PIE 인터럽트 그룹 ACK
    Interrupt_clearACKGroup(INTERRUPT_ACK_GROUP1);
    
    // 상태 업데이트: 정상 동작
    cpu2Status = 1;
    
    // CM으로 ADC 데이터 전송 요청
    IPC_sendCommand(IPC_CPU2_L_CM_R, IPC_FLAG1, IPC_ADDR_CORRECTION_ENABLE, index);
}

// GPIO 초기화 함수
void initGPIO(void)
{
    // GPIO34를 일반 GPIO로 설정 (디버깅용 LED)
    GPIO_setPinConfig(GPIO_34_GPIO34);
    // GPIO34를 출력 모드로 설정
    GPIO_setDirectionMode(34, GPIO_DIR_MODE_OUT);
    // GPIO34에 풀업 저항 활성화
    GPIO_setPadConfig(34, GPIO_PULL_UP);
    // 초기 LED 상태: OFF
    GPIO_writePin(34, 0);
}

void main(void)
{
    // CPU2 시스템 초기화 (클럭, PLL, Watchdog 등)
    Device_init();
    
    // 기본 GPIO 초기화 (C2000Ware 제공)
    Device_initGPIO();
    // 사용자 정의 GPIO 설정
    initGPIO();
    
    // 인터럽트 컨트롤러(PIE) 및 벡터 테이블 초기화
    Interrupt_initModule();
    Interrupt_initVectorTable();
    
    // ADC 설정: 50MHz 클럭, 12비트 해상도, ePWM1_SOCA 트리거
    ADC_setPrescaler(ADCA_BASE, ADC_CLK_DIV_4_0);
    // 내부 3.3V 기준 전압 설정
    ADC_setVREF(ADCA_BASE, ADC_REFERENCE_INTERNAL, ADC_REFERENCE_3_3V);
    // 12비트 단일 종단 모드 설정
    ADC_setMode(ADCA_BASE, ADC_RESOLUTION_12BIT, ADC_MODE_SINGLE_ENDED);
    // SOC0 설정: ePWM1_SOCA 트리거, ADCIN0 채널, 샘플링 15 사이클
    ADC_setupSOC(ADCA_BASE, ADC_SOC_NUMBER0, ADC_TRIGGER_EPWM1_SOCA, ADC_CH_ADCIN0, 15);
    // SOC0에서 ADC_INT1 트리거 설정
    ADC_setInterruptSOCTrigger(ADCA_BASE, ADC_SOC_NUMBER0, ADC_INT_NUMBER1);
    // ADC 인터럽트 활성화
    ADC_enableInterrupt(ADCA_BASE, ADC_INT_NUMBER1);
    // ADC 변환기 활성화
    ADC_enableConverter(ADCA_BASE);
    
    // ADC 안정화를 위한 1ms 지연
    DEVICE_DELAY_US(1000);
    
    // 공유 메모리 접근 활성화 (GS0, GS1 RAM)
    SysCtl_enableSharedRAMAccess(SYSCTL_GS0);
    SysCtl_enableSharedRAMAccess(SYSCTL_GS1);
    
    // 글로벌 인터럽트 활성화
    EINT;
    
    // Watchdog 활성화
    SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_WD);
    SysCtl_serviceWatchdog();
    
    // 무한 루프
    for(;;)
    {
        // 디버깅용: 상태에 따라 LED 제어
        GPIO_writePin(34, (cpu2Status == 1) ? 1 : 0);
        // 1초 대기
        DEVICE_DELAY_US(1000000);
    }
}

CM 코드 (Ethernet 전송)

#include "driverlib_cm.h"
#include "cm.h"

// 디버깅용 상태 변수 (0: 초기화 완료, 1: 동작 중, 0xFFFF: 오류)
volatile uint32_t cmStatus = 0;

// 공유 메모리 정의 (GS0 RAM: PWM 듀티 사이클, GS1 RAM: ADC 결과)
volatile uint32_t *pwmDuty = (volatile uint32_t *)0x0003F800;
volatile uint16_t *adcResults = (volatile uint16_t *)0x0003FC00;

// IPC 인터럽트 서비스 루틴 (CPU2로부터 ADC 데이터 인덱스 수신)
__interrupt void ipcISR(void)
{
    uint32_t command, data;
    
    // CPU2에서 전송된 IPC 명령 읽기
    IPC_CM_readMessage(IPC_CM_L_C28x_R, &command, &data);
    
    // 최신 ADC 데이터 인덱스 저장
    volatile uint16_t adcIndex = (uint16_t)data;
    
    // 이더넷 패킷 전송 (가정: ADC 데이터 전송)
    Ethernet_sendPacket(adcResults[adcIndex]);
    
    // IPC 플래그 지우기
    IPC_CM_clearFlag(IPC_CM_L_C28x_R, IPC_FLAG1);
    // NVIC 인터럽트 ACK
    NVIC_ClearPendingIRQ(IPC0_IRQn);
    
    // 상태 업데이트: 정상 동작
    cmStatus = 1;
}

// 이더넷 초기화 함수 (가정: 간소화된 설정)
void initEthernet(void)
{
    // 이더넷 클럭 활성화
    SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_EMAC);
    
    // 이더넷 PHY 설정 (외부 PHY에 따라 조정 필요)
    Ethernet_init();
    
    // 이더넷 인터럽트 활성화
    NVIC_EnableIRQ(EMAC0_IRQn);
}

// GPIO 초기화 함수
void initGPIO(void)
{
    // GPIO58을 일반 GPIO로 설정 (디버깅용 LED, CM 제어)
    GPIO_setPinConfig(GPIO_58_GPIO58);
    // GPIO58을 출력 모드로 설정
    GPIO_setDirectionMode(58, GPIO_DIR_MODE_OUT);
    // GPIO58에 풀업 저항 활성화
    GPIO_setPadConfig(58, GPIO_PULL_UP);
    // 초기 LED 상태: OFF
    GPIO_writePin(58, 0);
}

void main(void)
{
    // CM 시스템 초기화 (클럭, PLL 등)
    CM_init();
    
    // 기본 GPIO 초기화
    CM_initGPIO();
    // 사용자 정의 GPIO 설정
    initGPIO();
    
    // NVIC 인터럽트 컨트롤러 초기화
    NVIC_EnableIRQ(IPC0_IRQn);
    
    // IPC 인터럽트 등록
    IPC_CM_registerInterrupt(IPC_CM_L_C28x_R, IPC0_IRQn, &ipcISR);
    
    // 공유 메모리 접근 활성화 (GS0, GS1 RAM)
    SysCtl_enableSharedRAMAccess(SYSCTL_GS0);
    SysCtl_enableSharedRAMAccess(SYSCTL_GS1);
    
    // 이더넷 초기화
    initEthernet();
    
    // IPC 플래그 초기화
    IPC_CM_clearFlag(IPC_CM_L_C28x_R, IPC_FLAG_ALL);
    
    // IPC 인터럽트 활성화
    IPC_CM_enableInterrupt(IPC_CM_L_C28x_R, IPC_FLAG1);
    
    // 글로벌 인터럽트 활성화
    __enable_irq();
    
    // 무한 루프
    for(;;)
    {
        // 디버깅용: 상태에 따라 LED 제어
        GPIO_writePin(58, (cmStatus == 1) ? 1 : 0);
        // 1초 대기
        CM_delay(1000000);
    }
}

설명:

  • 기능:
    • CPU1: ePWM1으로 100kHz PWM 신호 생성, 듀티 사이클 변경 및 CPU2, CM으로 전송.
    • CPU2: ePWM1의 SOCA 트리거로 ADC 변환 수행, 결과를 공유 메모리에 저장, CM으로 전송 요청.
    • CM: CPU2로부터 ADC 데이터 인덱스를 받아 이더넷으로 전송.
  • 설정:
    • 공유 메모리: GS0 RAM(PWM 듀티 사이클), GS1 RAM(ADC 결과).
    • IPC: CPU1 → CPU2, CPU1 → CM, CPU2 → CM 통신.
    • GPIO: CPU1(GPIO0: ePWM1A, GPIO31: LED), CPU2(GPIO34: LED), CM(GPIO58: LED).
  • 결과: PWM 신호 생성, ADC 데이터 수집, 이더넷 전송이 동기화되어 수행, 정상 동작 시 각 코어의 LED ON.
  • 용도: 모터 제어 시스템에서 PWM 제어, 센서 데이터 수집, 네트워크 전송 분리.

5. 추가 고려 사항

5.1. 공유 메모리 관리

공유 메모리(Global Shared RAM, GSRAM)는 CPU1, CPU2, CM 간 데이터 교환의 핵심 요소입니다. TMS320F28388D는 여러 GSRAM 블록(예: GS0, GS1 등)을 제공하며, 각 블록은 특정 코어의 소유권으로 설정되거나 공유 접근이 가능합니다. 소유권 충돌을 방지하기 위해 다음 사항을 준수해야 합니다:

  • 소유권 설정: SysCtl_mapSharedRAM 함수를 사용하여 특정 GSRAM 블록의 소유권을 CPU1, CPU2, 또는 CM으로 명확히 지정합니다. 예를 들어, SysCtl_mapSharedRAM(SYSCTL_ACCESS_CPU1, SYSCTL_GS0)는 GS0 RAM의 소유권을 CPU1로 설정합니다. 소유 코어는 쓰기 권한을 가지며, 다른 코어는 읽기 전용 접근이 가능합니다.
  • 접근 제어: SysCtl_enableSharedRAMAccess를 호출하여 모든 코어가 특정 GSRAM 블록에 접근할 수 있도록 설정합니다. 예: SysCtl_enableSharedRAMAccess(SYSCTL_GS0). 반대로, SysCtl_disableSharedRAMAccess로 접근을 제한할 수 있습니다.
  • 데이터 일관성: 여러 코어가 동일한 GSRAM 블록에 동시에 쓰기를 시도할 경우 데이터 손상이 발생할 수 있습니다. 이를 방지하려면 IPC를 통해 동기화하거나, 특정 코어가 쓰기를 완료한 후 다른 코어가 접근하도록 설계해야 합니다.
  • 메모리 할당: 예제 코드에서는 GS0 RAM(0x0003F800)을 PWM 듀티 사이클 저장용으로, GS1 RAM(0x0003FC00)을 ADC 결과 저장용으로 사용했습니다. 메모리 주소는 TMS320F28388D의 메모리 맵을 참조하여 정확히 설정해야 합니다.
  • 성능 최적화: GSRAM은 고속 액세스를 지원하므로, 빈번한 데이터 교환 시 메모리 액세스 대기 시간을 최소화하기 위해 적절한 블록 크기와 접근 패턴을 설계해야 합니다.

5.2. IPC 동기화

인터프로세서 통신(IPC)은 CPU1, CPU2, CM 간의 동기화와 데이터 교환을 위한 핵심 메커니즘입니다. C28x와 CM 간 IPC는 별도 채널을 사용하며, 신뢰성 있는 통신을 보장하기 위해 다음 사항을 고려해야 합니다:

  • 별도 IPC 채널: CPU1과 CPU2 간 통신은 IPC_CPU1_L_CPU2_R, CPU1과 CM 간 통신은 IPC_CPU1_L_CM_R, CPU2와 CM 간 통신은 IPC_CPU2_L_CM_R 채널을 사용합니다. 각 채널은 독립적인 플래그와 인터럽트를 지원합니다.
  • 플래그 관리: IPC 플래그(예: IPC_FLAG0, IPC_FLAG1)는 코어 간 이벤트 신호를 전달합니다. 예를 들어, CPU1이 CPU2로 데이터를 전송할 때 IPC_setFlag(IPC_CPU1_L_CPU2_R, IPC_FLAG0)를 호출하여 인터럽트를 트리거하고, CPU2는 IPC_clearFlag(IPC_CPU2_L_CPU1_R, IPC_FLAG0)로 플래그를 지웁니다.
  • 인터럽트 처리: 각 코어는 IPC 인터럽트를 등록하여 데이터를 즉시 처리합니다. C28x에서는 IPC_registerInterrupt, CM에서는 IPC_CM_registerInterrupt를 사용합니다. 인터럽트 서비스 루틴(ISR)은 데이터 무결성을 보장하기 위해 빠르게 처리해야 합니다.
  • 메시지 교환: CM은 IPC_CM_L_C28x_R_sendMessage와 IPC_CM_L_C28x_R_readMessage를 사용하여 메시지를 교환합니다. 메시지 크기는 32비트로 제한되므로, 대량 데이터는 공유 메모리를 통해 전달하고 IPC로 인덱스나 플래그만 전송하는 방식이 효율적입니다.
  • 동기화 예제: 예제 코드에서 CPU1은 PWM 듀티 사이클을 CPU2와 CM에 전송하고, CPU2는 ADC 데이터 인덱스를 CM에 전송하여 동기화된 작업을 수행합니다.

5.3. CM 주변 장치

Connectivity Manager(CM)는 ARM Cortex-M4 기반으로, 통신 관련 작업에 특화된 주변 장치를 주로 제어합니다. CM의 주변 장치 사용 시 다음 사항을 고려해야 합니다:

  • 주변 장치 제한: CM은 이더넷(EMAC), CAN, USB 등 통신 관련 모듈에 접근할 수 있지만, ePWM, ADC 등 실시간 제어 장치에는 접근할 수 없습니다. 반면, CPU2는 일부 ePWM과 ADC에 접근 가능하므로, 작업 분배 시 이를 고려해야 합니다.
  • 이더넷 사용: 예제 코드에서 CM은 이더넷 모듈(EMAC)을 사용하여 ADC 데이터를 외부로 전송합니다. SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_EMAC)으로 클럭을 활성화하고, Ethernet_init()으로 PHY를 설정합니다. 실제 구현에서는 외부 PHY 칩의 설정과 네트워크 스택(LwIP 등)을 추가로 구성해야 합니다.
  • CAN 및 USB: CM은 CAN 및 USB 모듈을 지원하므로, 예를 들어 CAN 통신을 통해 데이터를 전송하거나 USB로 호스트와 통신할 수 있습니다. 이는 SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_CAN) 또는 SYSCTL_PERIPH_CLK_USB로 활성화됩니다.
  • GPIO 제어: CM은 독립적인 GPIO 핀(예: GPIO58)을 제어할 수 있습니다. 예제 코드에서는 디버깅용 LED로 사용됩니다.
  • 성능 고려: CM의 클럭은 최대 125MHz로, CPU1/CPU2(200MHz)보다 낮으므로, 복잡한 연산은 CPU1/CPU2에 위임하고 CM은 통신 처리에 집중하는 것이 효율적입니다.

5.4. 부팅 순서

CPU1은 시스템의 마스터 코어로, CPU2와 CM의 부팅을 담당합니다. 부팅 순서와 관련된 주요 고려 사항은 다음과 같습니다:

  • CPU1의 역할: CPU1은 시스템 초기화 후 SysCtl_bootCPU2(SYSCTL_BOOT_FLASH)와 SysCtl_bootCM(SYSCTL_BOOT_FLASH)를 호출하여 CPU2와 CM을 부팅합니다. 부팅 모드는 플래시(SYSCTL_BOOT_FLASH) 또는 RAM(SYSCTL_BOOT_RAM)으로 설정 가능합니다.
  • 부팅 완료 확인: CPU2와 CM의 부팅 완료는 각각 SysCtl_isCPU2BootComplete()와 SysCtl_isCMBootComplete()로 확인합니다. 부팅이 완료될 때까지 루프에서 대기하며, 일반적으로 1ms 단위로 지연(DEVICE_DELAY_US(1000))을 추가합니다.
  • 부팅 순서: 예제 코드에서는 CPU2를 먼저 부팅한 후 CM을 부팅합니다. 이는 CPU2의 ADC 설정이 CM의 이더넷 전송에 선행되어야 하기 때문입니다. 애플리케이션 요구사항에 따라 부팅 순서를 조정할 수 있습니다.
  • 리셋 및 중지: 필요 시 SysCtl_resetCPU2() 또는 SysCtl_resetCM()으로 코어를 리셋하고, SysCtl_haltCPU2() 또는 SysCtl_haltCM()으로 실행을 중지할 수 있습니다.
  • 안정성: 부팅 과정에서 클럭 안정화와 메모리 초기화를 보장하기 위해, CPU1은 부팅 전 모든 주변 장치와 메모리 설정을 완료해야 합니다.

5.5. CCS 설정

Code Composer Studio(CCS)를 사용한 멀티코어 프로젝트 설정은 다음과 같은 절차를 따릅니다:

  • 프로젝트 구조: 멀티코어 프로젝트는 CPU1, CPU2, CM용으로 별도의 소스 파일을 구성합니다. 예제 코드에서는 각각 cpu1_main.c, cpu2_main.c, cm_main.c로 구현됩니다.
  • .syscfg 툴: CCS의 SysConfig 툴(.syscfg 파일)을 사용하여 주변 장치, GPIO, IPC 설정을 시각적으로 구성할 수 있습니다. 예를 들어, ePWM1을 CPU1에, ADC를 CPU2에, EMAC을 CM에 할당할 수 있습니다.
  • 빌드 설정: 각 코어는 별도의 타겟(C28x 또는 Cortex-M4)으로 빌드됩니다. CCS 프로젝트 설정에서 CPU1, CPU2는 C2000 컴파일러를, CM은 ARM 컴파일러를 사용합니다.
  • 링커 스크립트: 각 코어의 메모리 맵은 링커 스크립트(.cmd 파일)로 정의됩니다. GSRAM 주소(예: 0x0003F800, 0x0003FC00)는 링커 스크립트에 명시적으로 매핑해야 합니다.
  • 디버깅: CCS의 멀티코어 디버깅 기능을 활용하여 CPU1, CPU2, CM의 실행 상태를 동시에 모니터링할 수 있습니다. 디버거는 각 코어의 레지스터, 메모리, 인터럽트를 개별적으로 추적합니다.

5.6. 기타 고려 사항

  • C2000Ware: 예제 코드는 C2000Ware의 DriverLib 기반으로 작성되었습니다. C28x용 DriverLib는 C:\ti\c2000\C2000Ware_X_XX_XX_XX\driverlib에, CM용 DriverLib는 C:\ti\c2000\C2000Ware_X_XX_XX_XX\driverlib_cm에 설치됩니다.
  • 디버깅: CCS의 멀티코어 디버깅 기능을 활용하여 CPU1, CPU2, CM 상태를 동시에 모니터링해야 합니다. 디버깅 시 LED 상태(GPIO31, GPIO34, GPIO58)를 확인하여 각 코어의 동작 상태를 점검할 수 있습니다.
  • 성능 최적화: CPU1과 CPU2는 실시간 제어 작업(ePWM, ADC)에, CM은 통신 작업(이더넷, CAN)에 집중하도록 작업을 분배하여 전체 시스템 성능을 최적화합니다.
  • 확장성: 추가적인 GSRAM 블록이나 IPC 채널을 활용하여 더 복잡한 멀티코어 애플리케이션을 구현할 수 있습니다. 예를 들어, CPU2에서 추가 ADC 채널을 처리하거나, CM에서 CAN과 USB를 동시에 사용할 수 있습니다.

키워드: TMS320F28388D, 멀티코어, CPU1, CPU2, CM, IPC, 공유 메모리, DriverLib, C2000, Code Composer Studio, 병렬 처리, 동기화, ePWM, ADC, Ethernet

반응형