본문 바로가기
MCU/C2000

TMS320F280049C 완전 정복 -1강 아키텍처 개요 & 레지스터 접근 두 가지 방식

by linuxgo 2026. 9. 30.

대상 디바이스: TMS320F280049C (F28004x 계열) / 실습 보드: LAUNCHXL-F280049C 대상 독자: MCU 개발 경험이 있는 중·고급 엔지니어

서론

이번 강좌는 F28004x 계열 완전 정복 시리즈의 첫 강의다. 앞으로 이어질 모든 강의는 하나의 규칙을 지킨다. 모든 페리페럴을 레지스터(비트필드) 직접 접근 방식과 DriverLib API 방식 두 가지로 나란히 구현하고, 두 방식을 코드 크기·가독성·실행 방식 관점에서 비교한다. 이번 1강은 그 규칙이 왜 필요한지, 그리고 그 규칙을 실제로 어떻게 적용하는지를 F28004x의 전체 아키텍처를 통해 보여준다.

애플리케이션 영역(모터 제어, FOC, 전력변환 등)은 이 시리즈에서 다루지 않는다. 대신 칩 자체를 페리페럴 단위로 완전히 해부하는 데 집중한다.

1. 이론 배경

1.1 F28004x 계열의 포지셔닝

F28004x는 TI C2000 제품군 중 Piccolo급에 속하는 실시간 제어 특화 32비트 MCU 계열이다. F280049, F280049C, F280048, F280048C, F280045, F280041, F280041C, F280040, F280040C 등의 디바이스로 구성되며, 이 시리즈는 그중 CLB(Configurable Logic Block)와 InstaSPIN-FOC ROM 라이브러리를 내장한 최상위 모델인 F280049C를 기준으로 진행한다.

F280049C의 핵심 사양은 다음과 같다.

항목 사양
CPU C28x, 최대 100 MHz
부동소수점 FPU32 (IEEE 754 단정밀도)
수학 가속기 TMU (Trigonometric Math Unit), VCU-I (Viterbi/Complex Math Unit)
제어 가속 코어 CLA (Control Law Accelerator), 독립 32비트 부동소수점 코어, 최대 100 MHz
DMA 6채널
Flash 최대 256 KB (128 KB × 2뱅크, Read-While-Write 지원)
RAM 최대 100 KB (전용 RAM + 로컬/글로벌 공유 RAM 블록으로 구성)
디버그 ERAD (Error Reduction and Analysis Debug) 하드웨어 실시간 분석 모듈
로직 CLB (Configurable Logic Block, "C" 접미사 모델 전용)
모터 제어 ROM InstaSPIN-FOC ("C" 접미사 모델 전용)

같은 계열 안에서도 "C" 접미사가 붙은 모델(F280049C, F280048C 등)만 CLB와 InstaSPIN-FOC를 갖는다는 점이 실무에서 자주 혼동되는 부분이다. 정확한 디바이스별 차이는 TI 데이터시트의 Device Comparison 표를 반드시 확인해야 한다.

1.2 C28x + 가속기 하버드 아키텍처

C28x는 수정된 하버드 구조(Modified Harvard Architecture)를 사용한다. 프로그램 페치와 데이터 페치가 물리적으로 분리된 버스를 통해 병렬로 이루어지기 때문에, 분기 예측이 실패하지 않는 한 대부분의 명령이 단일 사이클에 처리된다. F28004x는 이 C28x 코어에 다음 네 가지 가속 유닛을 결합한다.

  • FPU32: 단정밀도 부동소수점 덧셈/곱셈/나눗셈을 하드웨어로 처리한다. 이 덕분에 제어 알고리즘을 float 타입으로 그대로 작성해도 실질적인 실시간 제어 주기를 만족시킬 수 있다.
  • TMU (Type 0): sin, cos, atan, 나눗셈, 제곱근 등 삼각함수·비선형 연산을 하드웨어 명령어로 가속한다. 소프트웨어 룩업 테이블 대비 연산 사이클을 크게 줄인다.
  • VCU-I: 복소수 연산과 비터비(Viterbi) 알고리즘 관련 연산을 가속하는 유닛이다.
  • CLA (Control Law Accelerator): 메인 C28x CPU와 완전히 독립적으로 동작하는 32비트 부동소수점 코어다. 자체 프로그램/데이터 메모리 접근 경로를 가지며, ADC 인터럽트 같은 특정 트리거에 반응해 메인 CPU와 병렬로 제어 루프를 실행할 수 있다. ANSI C의 부분집합을 지원하며, 별도의 CLA 태스크로 컴파일된다.

이 구조를 정리하면 아래와 같다.

칩 전체 기능 블록도와 정확한 핀아웃은 TI 공식 데이터시트(SPRSP53, TMS320F280049C)의 Functional Block Diagram 항목을 참고할 것. https://www.ti.com/lit/ds/symlink/tms320f280049c.pdf

1.3 클럭 트리와 PLL 수식

F28004x의 클럭 소스는 세 가지다.

  • INTOSC1: 내부 10 MHz 오실레이터. 백업 클럭 역할을 하며 Missing Clock Detect(MCD) 회로에 항상 공급된다.
  • INTOSC2: 내부 10 MHz 오실레이터. 리셋 직후 기본 OSCCLK 소스로 선택되어 있다.
  • XTAL (X1/X2): 외부 크리스탈 또는 외부 클럭 입력.

선택된 OSCCLK은 SYSPLL(System PLL)을 거쳐 SYSCLK을 만든다. F28004x의 SYSPLL은 정수 배수(IMULT)와 소수 배수(FMULT)를 동시에 지원하며, PLL 출력 뒤에 한 단계의 시스템 클럭 분주기(SYSCLKDIV)가 존재한다. 이를 수식으로 표현하면 다음과 같다.

$$ f_{PLLSYSCLK} = f_{OSCCLK} \times \frac{(IMULT + FMULT)}{SYSCLKDIV} $$

여기서 $FMULT$는 0, 0.25, 0.5, 0.75 중 하나의 값을 가지는 소수 배수이고, $SYSCLKDIV$는 PLL 출력을 다시 나누는 정수 분주값이다. 예를 들어 INTOSC2(10 MHz)를 소스로 사용하고 $IMULT=10$, $FMULT=0$, $SYSCLKDIV=1$로 설정하면,

$$ f_{PLLSYSCLK} = 10,\text{MHz} \times \frac{10 + 0}{1} = 100,\text{MHz} $$

이 되어 F280049C의 최대 동작 주파수인 100 MHz에 도달한다. 이 강의의 실습 코드는 이 조합을 그대로 사용한다.

SYSCLK 아래로는 저속 주변장치 클럭(LSPCLK)이 한 단계 더 분주되어 파생된다.

$$ f_{LSPCLK} = \frac{f_{SYSCLK}}{LSPCLKDIV} $$

주의: $IMULT$, $FMULT$, $SYSCLKDIV$ 필드의 정확한 비트 위치와 유효 범위, 그리고 PLL Lock에 필요한 대기 시간은 디바이스별로 다르며 세대에 따라 REFDIV·ODIV 필드가 추가되는 경우도 있다(F28004x에는 REFDIV/ODIV가 없다). 실제 설계에서는 반드시 F28004x TRM(SPRUI33) 5장 System Control과 3.7.11절 Clock Configuration Examples를 함께 확인해야 한다.

1.4 메모리 아키텍처 개요

F28004x는 C28x 특유의 통합 메모리 공간(프로그램/데이터 공간이 논리적으로 통합된 어드레싱)을 사용한다. F280049C 기준 총 256 KB Flash가 128 KB짜리 두 개 뱅크로 나뉘어 있어, 한 뱅크를 실행하면서 다른 뱅크를 재프로그래밍하는 Read-While-Write 갱신이 가능하다. RAM은 총 100 KB이며, CPU 전용(M0/M1) RAM 블록과 CLA를 포함한 여러 마스터가 공유하는 Local/Global Shared RAM(LSx/GSx) 블록들로 구성된다. 각 RAM 블록의 정확한 시작 주소와 용량, 그리고 어떤 마스터(CPU, CLA, DMA)가 어떤 블록에 접근 가능한지는 TRM의 Memory Map 장을 통해 확인해야 하며, 이 시리즈에서는 메모리맵과 링커 커맨드 파일을 별도 강의에서 전용으로 다룬다.

2. 레지스터 맵 해설

2.1 레지스터 접근 방식은 왜 두 가지인가

C2000Ware SDK는 공식적으로 레지스터에 접근하는 세 가지 계층을 제공한다.

  1. Direct register access: #define으로 선언된 절대 주소 포인터에 직접 값을 대입하는 가장 낮은 계층
  2. Bitfield (구조체) 접근: 레지스터를 C 구조체의 비트필드로 매핑해, PeripheralRegs.REGNAME.bit.FIELD = value; 형태로 접근하는 계층
  3. DriverLib: 위 두 계층을 함수 뒤에 완전히 숨기고, Peripheral_setXxx() 형태의 API로 제공하는 최상위 계층

실무에서 1번(순수 포인터 접근)을 직접 쓰는 경우는 거의 없다. 대부분의 프로젝트는 2번(Bitfield)과 3번(DriverLib) 중 하나를 프로젝트 전체의 기준으로 선택한다. 그래서 이 강의를 포함한 시리즈 전체는 "레지스터 접근 두 가지 방식"이라는 표현을 Bitfield 방식과 DriverLib 방식을 가리키는 말로 사용한다.

두 방식은 같은 C2000Ware SDK 안에서 물리적으로 다른 폴더에 존재한다.

2.2 EALLOW / EDIS 보호 메커니즘

C28x는 ARM 계열처럼 하드웨어 특권 레벨(privilege level)을 두지 않는다. 대신 노이즈나 폭주한 코드에 의해 값이 바뀌면 시스템 오동작으로 직결되는 설정용 레지스터 그룹을 EALLOW 보호 레지스터로 묶어, EALLOW 어셈블리 명령으로 상태 비트를 세우지 않으면 쓰기가 무시되도록 설계했다. 클럭, GPIO MUX/DIR, PWM 트립존 설정 등이 대표적인 보호 대상이며, GPIO의 GPxDAT/GPxSET/GPxCLEAR/GPxTOGGLE 같은 순수 데이터 레지스터는 보호 대상이 아니다. Bitfield 방식 코드에서는 이 EALLOW/EDIS 쌍을 개발자가 직접 관리해야 하며, DriverLib 함수들은 내부적으로 이를 자동으로 처리한다.

2.3 이번 강의에서 다루는 레지스터 그룹

레지스터 그룹(구조체) 소속 주요 필드 EALLOW 보호
WdRegs Watchdog WDCR (워치독 제어) O
ClkCfgRegs System Control – Clock Config CLKSRCCTL1, SYSPLLMULT, SYSPLLCTL1, SYSPLLSTS, SYSCLKDIVSEL, LOSPCP, MCDCR O
GpioCtrlRegs GPIO 제어 GPxMUX1/2, GPxDIR, GPxPUD, GPxQSEL1/2 O
GpioDataRegs GPIO 데이터 GPxDAT, GPxSET, GPxCLEAR, GPxTOGGLE X

각 필드의 정확한 비트 위치·폭은 디바이스마다 미묘하게 다를 수 있으므로 이 표에는 싣지 않는다. 실습 전 반드시 F28004x TRM의 System Control 레지스터 표(5장)와 GPIO 레지스터 표(GPIO 장)를 직접 대조하기 바란다.

2.4 Bitfield ↔ DriverLib 1:1 매핑표

기능 Bitfield 방식 DriverLib 방식
워치독 비활성화 WdRegs.WDCR.all = 0x0068; SysCtl_disableWatchdog();
오실레이터 소스 선택 ClkCfgRegs.CLKSRCCTL1.bit.OSCCLKSRCSEL SYSCTL_OSCSRC_OSC2 (매크로, SysCtl_setClock() 인자)
PLL 정수/소수 배수 ClkCfgRegs.SYSPLLMULT.bit.IMULT/FMULT SYSCTL_IMULT(x), SYSCTL_FMULT_x
PLL 활성화 ClkCfgRegs.SYSPLLCTL1.bit.PLLEN SYSCTL_PLL_ENABLE
PLL Lock 대기 while (ClkCfgRegs.SYSPLLSTS.bit.LOCKS != 1) { } SysCtl_setClock() 내부에서 자동 polling
SYSCLK 분주 ClkCfgRegs.SYSCLKDIVSEL.bit.PLLSYSCLKDIV SYSCTL_SYSDIV(x)
GPIO 핀먹스 GpioCtrlRegs.GPAMUX1.bit.GPIOn GPIO_setPinConfig(GPIO_n_GPIOn)
GPIO 방향 GpioCtrlRegs.GPADIR.bit.GPIOn GPIO_setDirectionMode()
GPIO 출력 제어 GpioDataRegs.GPASET/CLEAR/TOGGLE.bit.GPIOn GPIO_writePin() / GPIO_togglePin()

WDCR = 0x0068의 근거는 다음과 같다. TI DriverLib 소스에 정의된 SYSCTL_WD_CHKBITS는 0x0028인데, 이는 WDCR의 워치독 체크 비트 필드에 반드시 101b 패턴을 써야 함을 의미한다. 여기에 워치독 비활성화 비트(WDDIS, 0x0040)를 더하면 0x0028 | 0x0040 = 0x0068이 된다. 이 값은 여러 세대의 C2000 디바이스에서 동일하게 통용되는 값이지만, 정확한 비트 정의는 TRM Watchdog 장에서 재확인해야 한다.

3. CCS 프로젝트를 구성하여 코드 작성

3.1 CCS 프로젝트 구성 순서

두 방식은 별도의 CCS 프로젝트로 나누어 구성하는 것이 원칙이다. 한 프로젝트 안에 device_support 헤더와 driverlib 헤더를 동시에 include하면 이름 충돌이 발생하기 쉽다(5장 트러블슈팅 참고).

프로젝트 A — Bitfield 방식

  1. File → New → CCS Project
  2. Family: C2000, Variant: TMS320F280049C, Connection: 사용 중인 디버그 프로브(XDS110 등) 선택
  3. Project templates and examples에서 Empty Project(또는 device_support 기반 예제) 선택
  4. Project Properties → Build → C2000 Compiler → Include Options에 다음 경로 추가
    •   C2000Ware/device_support/f28004x/headers/include
    •   C2000Ware/device_support/f28004x/common/include
  5. main.c에서 #include "F28004x_Device.h" 포함

프로젝트 B — DriverLib 방식

  1. 동일하게 New CCS Project에서 Family/Variant까지 설정
  2. Project templates and examples에서 driverlib_empty_cpu1 또는 Empty Driverlib Project 계열 템플릿 선택 (SDK 버전에 따라 명칭이 다를 수 있음)
  3. driverlib/f28004x/driverlib와 device_support/f28004x/common 경로가 프로젝트에 자동 포함되는지 확인
  4. main.c에서 #include "driverlib.h"와 #include "device.h" 포함

3.2 코드 A — Bitfield(레지스터 직접 접근) 방식

/*============================================================================
 * File   : main_bitfield.c
 * Device : TMS320F280049C (F28004x)
 * Lecture: Season1-1강 - Architecture Overview & Register Access (Bitfield)
 *
 * 목적(Purpose):
 *   내부 오실레이터 INTOSC2(10MHz) -> PLL -> SYSCLK 100MHz 로 클럭을 구성하고,
 *   GPIO0을 출력으로 설정하여 일정 주기로 토글한다.
 *   (Configure SYSCLK to 100MHz from internal INTOSC2 via PLL, then toggle
 *    GPIO0 as a general-purpose digital output at a fixed interval.)
 *
 *   이 예제는 device_support(F28004x_Device.h)의 구조체/비트필드를 통해
 *   레지스터에 "직접" 접근하는 방식을 보여준다.
 *   (This example demonstrates direct register access via the bitfield
 *    structures provided in the device_support headers.)
 *==========================================================================*/
#include "F28004x_Device.h"   // 디바이스 레지스터 구조체 정의 (device register structures)

// 함수 프로토타입 (function prototypes)
void WatchDog_Disable_Bitfield(void);
void SysClk_Init_Bitfield(void);
void Gpio_Led_Init_Bitfield(void);
void Gpio_Led_Toggle_Bitfield(void);
void Delay_Loop(Uint32 count);

void main(void)
{
    //------------------------------------------------------------------
    // 1. 워치독 비활성화 (Disable the watchdog timer)
    //------------------------------------------------------------------
    WatchDog_Disable_Bitfield();

    //------------------------------------------------------------------
    // 2. 시스템 클럭 초기화: INTOSC2(10MHz) -> PLL -> SYSCLK(100MHz)
    //    (System clock init: INTOSC2 10MHz -> PLL -> SYSCLK 100MHz)
    //------------------------------------------------------------------
    SysClk_Init_Bitfield();

    //------------------------------------------------------------------
    // 3. GPIO0을 LED 출력으로 초기화 (Initialize GPIO0 as an LED output)
    //    주의: GPIO0은 예시 핀이며, 실제 보드의 LED 핀 번호로 교체할 것.
    //    (Note: GPIO0 is a placeholder pin - replace with the actual
    //     LED GPIO number of the board in use.)
    //------------------------------------------------------------------
    Gpio_Led_Init_Bitfield();

    //------------------------------------------------------------------
    // 4. 메인 루프 (main loop): 일정 주기로 GPIO0 토글
    //------------------------------------------------------------------
    for (;;)
    {
        Gpio_Led_Toggle_Bitfield();
        Delay_Loop(2000000UL);   // 대략적인 지연 - 정밀 타이밍은 CPU 타이머로 별도 구현
    }
}

/*----------------------------------------------------------------------------
 * WatchDog_Disable_Bitfield
 * WDCR 레지스터에 0x0068을 기록하여 워치독을 비활성화한다.
 * 0x0068 = WDCHK(0b101, 체크 비트 패턴, 0x28) | WDDIS(0x40)
 * (Write 0x0068 to WDCR to disable the watchdog: WDCHK check-bit pattern
 *  0x28 OR'd with the WDDIS disable bit 0x40.)
 *--------------------------------------------------------------------------*/
void WatchDog_Disable_Bitfield(void)
{
    EALLOW;                        // 보호 레지스터 쓰기 허용 (unlock protected regs)
    WdRegs.WDCR.all = 0x0068;      // 워치독 비활성화 (disable watchdog)
    EDIS;                          // 보호 레지스터 쓰기 종료 (lock protected regs)
}

/*----------------------------------------------------------------------------
 * SysClk_Init_Bitfield
 * INTOSC2(10MHz)를 OSCCLK 소스로 선택하고 PLL을 통해 100MHz SYSCLK을 만든다.
 * (Select INTOSC2 as OSCCLK, then configure the PLL to produce a 100MHz
 *  SYSCLK.)
 *
 *   f_PLLSYSCLK = f_OSCCLK x (IMULT + FMULT) / SYSCLKDIV
 *               = 10MHz   x (10 + 0)         / 1        = 100MHz
 *--------------------------------------------------------------------------*/
void SysClk_Init_Bitfield(void)
{
    EALLOW;

    // 1) Missing Clock Detect(MCD) 임시 비활성화
    //    (Temporarily disable Missing Clock Detect during clock switching)
    ClkCfgRegs.MCDCR.bit.MCLKOFF = 1;

    // 2) 오실레이터 소스 선택: INTOSC2 (리셋 기본값과 동일하지만 명시적으로 설정)
    //    (Select oscillator source: INTOSC2 - explicit for clarity,
    //     even though it matches the reset default)
    ClkCfgRegs.CLKSRCCTL1.bit.OSCCLKSRCSEL = 0;   // 0 = INTOSC2

    // 3) 배수 변경 전 PLL 비활성화 (Disable PLL before changing multiplier)
    ClkCfgRegs.SYSPLLCTL1.bit.PLLEN = 0;

    // 4) PLL 배수 설정: 정수 배수 10, 소수 배수 없음
    //    (Set PLL multiplier: integer x10, no fractional multiplier)
    ClkCfgRegs.SYSPLLMULT.bit.IMULT = 10;
    ClkCfgRegs.SYSPLLMULT.bit.FMULT = 0;

    // 5) PLL 활성화 (Enable the PLL)
    ClkCfgRegs.SYSPLLCTL1.bit.PLLEN = 1;

    // 6) PLL Lock 대기 - Lock 전에 SYSCLK을 전환하면 시스템이 불안정해진다.
    //    (Wait for PLL lock - switching SYSCLK before lock causes instability)
    while (ClkCfgRegs.SYSPLLSTS.bit.LOCKS != 1)
    {
        // 인터럭트 등에 의해 무한 대기하지 않도록 실제 프로젝트에서는
        // 타임아웃 카운터를 추가하는 것을 권장한다.
        // (In production code, add a timeout counter to avoid an
        //  unbounded wait here.)
    }

    // 7) SYSCLK 분주 없음 -> SYSCLK = PLL 출력 그대로 (100MHz)
    //    (No post-PLL divide -> SYSCLK equals PLL output, 100MHz)
    ClkCfgRegs.SYSCLKDIVSEL.bit.PLLSYSCLKDIV = 0;

    // 8) MCD 재활성화 (Re-enable Missing Clock Detect)
    ClkCfgRegs.MCDCR.bit.MCLKOFF = 0;

    EDIS;
}

/*----------------------------------------------------------------------------
 * Gpio_Led_Init_Bitfield
 * GPIO0을 범용 디지털 출력으로 설정한다.
 * (Configure GPIO0 as a general-purpose digital output.)
 *--------------------------------------------------------------------------*/
void Gpio_Led_Init_Bitfield(void)
{
    EALLOW;
    GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 0;    // 0 = GPIO 기능 선택 (select GPIO function, not peripheral)
    GpioCtrlRegs.GPADIR.bit.GPIO0  = 1;    // 1 = 출력 방향 (output direction)
    GpioCtrlRegs.GPAPUD.bit.GPIO0  = 1;    // 1 = 내부 풀업 비활성화 (disable internal pull-up)
    EDIS;

    GpioDataRegs.GPACLEAR.bit.GPIO0 = 1;   // 초기값 Low (initial state: low)
}

/*----------------------------------------------------------------------------
 * Gpio_Led_Toggle_Bitfield
 * GPIO0 출력을 토글한다. 데이터 레지스터는 EALLOW 보호 대상이 아니다.
 * (Toggle GPIO0 output. Data registers are NOT EALLOW-protected.)
 *--------------------------------------------------------------------------*/
void Gpio_Led_Toggle_Bitfield(void)
{
    GpioDataRegs.GPATOGGLE.bit.GPIO0 = 1;
}

void Delay_Loop(Uint32 count)
{
    volatile Uint32 i;
    for (i = 0; i < count; i++) { }
}

3.3 코드 B — DriverLib 방식

/*============================================================================
 * File   : main_driverlib.c
 * Device : TMS320F280049C (F28004x)
 * Lecture: Season1-1강 - Architecture Overview & Register Access (DriverLib)
 *
 * 목적(Purpose): main_bitfield.c와 동일한 동작을 DriverLib API로 구현한다.
 *   (Implements the exact same behavior as main_bitfield.c, using the
 *    DriverLib abstraction layer instead of direct register access.)
 *==========================================================================*/
#include "driverlib.h"
#include "device.h"

// INTOSC2(10MHz) 기준 PLL 설정 매크로
// (PLL configuration macro based on INTOSC2, 10MHz)
//   f_PLLSYSCLK = 10MHz(OSC2) x 10(IMULT) / 1(SYSDIV) = 100MHz
#define MY_CLOCK_CFG  (SYSCTL_OSCSRC_OSC2 | SYSCTL_IMULT(10) | \
                        SYSCTL_FMULT_NONE | SYSCTL_SYSDIV(1) | \
                        SYSCTL_PLL_ENABLE)

void main(void)
{
    //------------------------------------------------------------------
    // 1. 워치독 비활성화 (Disable the watchdog timer)
    //    - 내부적으로 WDCR에 체크비트 패턴 + WDDIS를 기록한다.
    //------------------------------------------------------------------
    SysCtl_disableWatchdog();

    //------------------------------------------------------------------
    // 2. 시스템 클럭 초기화 - 함수 내부에서 PLL Lock까지 자동 대기한다.
    //    (Initialize system clock - internally polls for PLL lock)
    //------------------------------------------------------------------
    SysCtl_setClock(MY_CLOCK_CFG);

    //------------------------------------------------------------------
    // 3. GPIO0을 LED 출력으로 초기화
    //    (Initialize GPIO0 as an LED output)
    //    주의: GPIO0은 예시 핀이며, 실제 보드의 LED 핀 번호로 교체할 것.
    //------------------------------------------------------------------
    GPIO_setPinConfig(GPIO_0_GPIO0);              // 핀먹스: GPIO 기능 선택 (pin mux -> GPIO function)
    GPIO_setPadConfig(0, GPIO_PIN_TYPE_STD);       // 풀업 없음, 표준 출력 (no pull-up, standard pad)
    GPIO_setDirectionMode(0, GPIO_DIR_MODE_OUT);   // 출력 방향 (output direction)
    GPIO_writePin(0, 0);                           // 초기값 Low (initial state: low)

    //------------------------------------------------------------------
    // 4. 메인 루프 (main loop)
    //------------------------------------------------------------------
    for (;;)
    {
        GPIO_togglePin(0);
        DEVICE_DELAY_US(500000);   // 약 0.5초 지연 (~0.5s busy-wait delay, driverlib 제공 매크로)
    }
}

두 코드를 나란히 놓고 보면 WatchDog_Disable_Bitfield() 8줄이 SysCtl_disableWatchdog(); 한 줄로, SysClk_Init_Bitfield()의 8단계가 SysCtl_setClock(MY_CLOCK_CFG); 한 줄로 압축된 것을 확인할 수 있다. DriverLib는 EALLOW/EDIS, PLL Lock 폴링, MCD 처리까지 함수 내부에 캡슐화한다.

4. 동작 확인 방법 (CCS)

  1. 빌드 및 다운로드: 두 프로젝트를 각각 빌드한 뒤 Run → Debug로 타깃에 다운로드한다.
  2. Expressions 창으로 레지스터 확인: View → Expressions에서 다음을 추가하고 Continuous Refresh를 켠다.
    •   ClkCfgRegs.SYSPLLSTS.bit.LOCKS — PLL Lock 여부(1이어야 정상)
    •   ClkCfgRegs.SYSPLLMULT.bit.IMULT — 설정된 배수값 확인
    •   GpioDataRegs.GPADAT.bit.GPIO0 — GPIO0 출력 레벨이 주기적으로 바뀌는지 확인
    DriverLib 프로젝트에서도 동일한 ClkCfgRegs/GpioDataRegs 심볼이 그대로 보인다. DriverLib 함수는 결국 이 레지스터들을 대신 조작할 뿐이기 때문이다.
  3. CCS Registers 뷰 활용: View → Registers에서 CPU 코어 레지스터와는 별개로, Memory Browser(View → Memory Browser)에 ClkCfgRegs의 베이스 주소를 입력하면 워드 단위 raw 값도 직접 확인할 수 있다.
  4. 실제 핀 레벨 확인: 오실로스코프나 로직 애널라이저를 GPIO0 핀에 연결해 약 0.5초 주기의 사각파가 나오는지 확인한다. LED가 연결된 핀이라면 육안으로도 토글을 확인할 수 있다.
  5. 코드 크기·명령어 수 비교: Project → Build(또는 Rebuild) 후 .map 파일(Project Properties → Build → Linker → Generate linker information/.map 파일 활성화)에서 두 프로젝트의 .text 섹션 크기를 비교하고, View → Disassembly로 SysCtl_setClock() 호출부와 SysClk_Init_Bitfield() 호출부의 실제 어셈블리 명령 수를 비교해본다. 최신 C2000 컴파일러는 DriverLib 함수의 상당 부분을 인라인·최적화하므로, 실측 결과는 최적화 옵션(-O0 ~ -O4)에 따라 달라질 수 있다. 구체적인 수치는 사용 중인 컴파일러 버전으로 직접 빌드해 확인하는 것이 정확하다.

5. 자주 하는 실수 & 트러블슈팅

5.1 Bitfield 방식

  •   EALLOW 누락: 보호 레지스터에 EALLOW 없이 값을 대입해도 컴파일 에러는 발생하지 않는다. 그러나 CPU가 해당 쓰기를 무시하기 때문에, 레지스터 값이 전혀 바뀌지 않는 채로 조용히 실패한다. 클럭이나 GPIO MUX가 "코드는 맞는데 동작이 안 되는" 경우 가장 먼저 EALLOW/EDIS 쌍부터 점검해야 한다.
  •   PLL Lock 대기 생략: Lock 완료 전에 SYSCLK을 전환하면 시스템이 불안정한 클럭으로 동작해 예측 불가능한 리셋이나 오동작이 반복된다.
  •   MCD 비활성화 없이 오실레이터 소스 전환: 클럭 소스를 바꾸는 짧은 과도 구간 동안 Missing Clock Detect 회로가 클럭 이상으로 오판하여 강제로 백업 클럭으로 전환시킬 수 있다. 클럭 소스나 PLL 설정을 바꿀 때는 관례적으로 MCD를 잠시 비활성화했다가 재활성화한다.
  •   device_support와 driverlib 헤더 혼용: TI 공식 E2E 포럼에도 driverlib 기반 프로젝트에 GpioCtrlRegs 같은 bitfield 심볼을 그대로 사용하려다 identifier ... is undefined 컴파일 에러가 발생한 사례가 여러 건 보고되어 있다. 두 방식은 서로 다른 헤더/소스 집합을 사용하므로 한 프로젝트 안에서 섞어 쓰지 않는 것이 원칙이다.

5.2 DriverLib 방식

  •   예제의 클럭 소스 매크로를 보드에 맞게 수정하지 않음: C2000Ware의 많은 driverlib 예제는 기본적으로 외부 크리스탈(XTAL) 기준 DEVICE_SETCLOCK_CFG를 사용한다. LaunchXL-F280049C처럼 외부 크리스탈이 없는 보드에서 이 매크로를 그대로 사용하면 SysCtl_setClock()이 PLL Lock에 실패하거나 잘못된 주파수로 동작한다. SYSCTL_OSCSRC_XTAL을 SYSCTL_OSCSRC_OSC2로 바꾸고 배수를 다시 계산해야 한다.
  •   device_support와 driverlib 라이브러리를 링커에 동시 지정: 두 라이브러리에 동일한 이름의 초기화 심볼이 중복 정의되어 링크 에러가 발생할 수 있다. 프로젝트 속성의 Include/Library 경로에 한쪽만 추가되어 있는지 확인해야 한다.
  •   SysConfig(.syscfg) 자동 생성 코드와 수동 코드의 충돌: SysConfig로 클럭/GPIO를 설정한 프로젝트에 수동으로 SysCtl_setClock()을 다시 호출하면 두 초기화 경로가 서로 다른 설정을 시도해 예측 불가능한 결과가 나올 수 있다. SysConfig를 사용하는 프로젝트에서는 생성된 ti_msp_dl_config 계열 초기화 함수 하나로 통일하는 것이 안전하다.

6. 요약

비교 항목 Bitfield 방식 DriverLib 방식
코드 가독성 레지스터/필드명을 알아야 의도 파악 가능 함수명 자체가 의도를 전달
세부 제어력 레지스터 단위로 완전한 제어 가능 라이브러리가 노출하지 않는 세부 옵션은 제어 불가할 수 있음
이식성 디바이스별 헤더가 달라 이식성 낮음 유사한 API가 여러 디바이스에 공통 제공(단, 매크로 파라미터는 디바이스별로 다를 수 있음)
디버깅 편의성 Expressions/Watch 창에 레지스터명이 그대로 노출되어 직관적 함수 내부까지 단계적으로 추적해야 할 수 있음
초기 학습 난이도 TRM을 정독해야 함 API 가이드만으로도 시작 가능

F28004x는 C28x CPU에 FPU32·TMU·VCU-I·CLA라는 네 개의 가속 유닛을 결합한 구조이며, OSCCLK은 INTOSC1/INTOSC2/XTAL 중 하나를 선택해 SYSPLL을 거쳐 SYSCLK으로 만들어진다. 이 SYSCLK을 만드는 과정 하나만 보더라도 Bitfield 방식은 EALLOW·MCD·PLL Lock까지 개발자가 직접 관리해야 하는 반면, DriverLib 방식은 SysCtl_setClock() 한 줄로 그 모든 과정을 캡슐화한다는 차이가 명확히 드러난다. 이 두 방식의 대비는 앞으로 다룰 모든 페리페럴(GPIO, ePWM, ADC, eCAP, SCI/SPI 등)에서 동일한 틀로 반복된다.

다음 강의(2강)에서는 이번 강의에서 간단히 짚고 넘어간 F28004x 클럭 시스템을 심층적으로 다룬다. 오실레이터 절체, PLL 세부 레지스터, Missing Clock Detect(MCD)와 NMI 워치독까지 레지스터 레벨과 DriverLib 레벨 양쪽에서 완전히 분석할 예정이다.

참고문헌