소개
Texas Instruments의 TMS320F28377D는 실시간 제어 애플리케이션(모터 제어, 전력 변환, 센서 데이터 처리 등)에 최적화된 C2000 시리즈 마이크로컨트롤러로, 3개의 32비트 CPU 타이머(Timer 0, 1, 2)를 제공합니다. 이 블로그에서는 CPU 타이머의 상세 동작 원리, Bitfield 방식 설정 방법, 그리고 LED 토글, ADC 트리거, 타이밍 측정, PWM 동기화 예제를 통해 실전 활용법을 다룹니다. 초보자도 따라 할 수 있는 실행 가능한 코드를 포함하며, Code Composer Studio(CCS)에서 바로 테스트 가능합니다.
1. TMS320F28377D CPU 타이머란?
TMS320F28377D의 CPU 타이머는 실시간 제어 애플리케이션에서 정밀한 타이밍 제어를 위해 설계된 32비트 카운트다운 타이머입니다. 시스템 클럭(SYSCLK, 기본 200MHz)을 기반으로 동작하며, 주기적 인터럽트 생성, 이벤트 트리거, 시간 측정 등 다양한 기능을 제공합니다. 총 3개의 타이머(Timer 0, Timer 1, Timer 2)가 있으며, 각 타이머는 독립적으로 설정 가능하지만 고유한 인터럽트 라인과 용도를 가집니다. 아래는 CPU 타이머의 상세 특징과 동작 원리를 설명합니다.
1.1. CPU 타이머 특징
- 타이머 개수: 3개 (Timer 0, Timer 1, Timer 2).
- 구조: 32비트 카운트다운 타이머로,
TIM
레지스터가PRD
값에서 0까지 감소하며 동작. - 클럭 소스: SYSCLK(기본 200MHz)를 사용하며,
TPR/TPRH
레지스터의TDDR
필드로 분주 가능. - 인터럽트:
- Timer 0: PIE(Peripheral Interrupt Expansion)를 통해 INT1 (Group 1, INTx7)에 연결. PIE를 사용하므로 유연한 인터럽트 우선순위 설정 가능.
- Timer 1: CPU INT13에 직접 연결, 높은 우선순위.
- Timer 2: CPU INT14에 연결, SYS/BIOS 사용 시 기본적으로 예약됨(예: RTOS 틱 생성).
- 용도:
- 주기적 인터럽트: 정해진 주기(예: 100μs)로 작업 스케줄링.
- ADC 트리거: 아날로그-디지털 변환을 주기적으로 시작.
- PWM 동기화: ePWM 모듈의 위상 및 주기를 정밀 제어.
- 시간 측정: 이벤트 간 경과 시간 계산(예: 버튼 누름 시간).
- 주기 계산 공식:
Period (s) = (PRD × (TDDR + 1)) / SYSCLK
예: SYSCLK = 200MHz, PRD = 20,000, TDDR = 0 → Period = 100μs (20000 / 200e6 = 100e-6초).
1.2. 동작 원리
CPU 타이머는 다음과 같은 방식으로 동작합니다:
- 초기화:
PRD
레지스터에 주기 값을 설정하고,TPR/TPRH
로 클럭 분주비(TDDR)를 지정. - 카운트다운:
TIM
레지스터가PRD
값에서 0까지 SYSCLK 주기로 감소. - 인터럽트 발생:
TIM
이 0에 도달하면TCR.TIF
플래그가 설정되고,TCR.TIE = 1
일 경우 인터럽트 발생. - 재설정:
TIM
이 0에 도달하면 자동으로PRD
값으로 재설정되어 반복 동작. - 제어:
TCR.TSS
로 타이머 시작/정지 제어,TCR.TRB
로PRD
값을TIM
에 강제 로드 가능.
1.3. 주요 레지스터
TIM
(Timer Counter Register):- 32비트, 현재 카운터 값 저장.
- 읽기 전용, 직접 수정 불가.
PRD
(Period Register):- 32비트, 타이머 주기 값 설정.
- 예: SYSCLK = 200MHz, 100μs 주기 → PRD = 200e6 × 100e-6 = 20,000.
TCR
(Timer Control Register):TSS
(bit 4): 타이머 시작(0) 또는 정지(1).TRB
(bit 5):PRD
값을TIM
에 로드.TIF
(bit 6): 인터럽트 플래그, 1로 설정하여 클리어.TIE
(bit 7): 인터럽트 활성화(1) 또는 비활성화(0).
TPR/TPRH
(Timer Prescale Registers):TDDR
(Timer Divider): SYSCLK 분주비 설정 (0 = 분주 없음).- 16비트로 나뉘어 저장(TPR: 하위 8비트, TPRH: 상위 8비트).
- 레지스터 접근: 보호된 레지스터로,
EALLOW
로 접근 허용,EDIS
로 보호 복구.
1.4. 인터럽트 메커니즘
- Timer 0: PIE를 통해 INT1에 연결. PIE는 12개의 그룹으로 인터럽트를 관리하며, Timer 0은 그룹 1의 INTx7에 할당.
PieVectTable.TINT0
에 ISR 주소를 설정하고,PieCtrlRegs.PIEIER1.bit.INTx7 = 1
로 활성화. - Timer 1: CPU INT13에 직접 연결, PIE 설정 불필요.
PieVectTable.TINT1
로 ISR 설정. - Timer 2: CPU INT14에 연결, SYS/BIOS 사용 시 주의.
PieVectTable.TINT2
로 ISR 설정. - 인터럽트 처리: ISR에서
TCR.TIF = 1
로 플래그 클리어, Timer 0은PieCtrlRegs.PIEACK
로 그룹 ACK 필요.
1.5. 실제 응용 사례
- 모터 제어: Timer 0로 PWM 신호를 동기화하여 모터 속도 제어.
- 센서 데이터 처리: Timer 1로 ADC를 주기적으로 트리거하여 센서 데이터 수집.
- 타이밍 분석: Timer 2로 외부 이벤트(예: 버튼 입력) 간 시간 측정.
- RTOS: Timer 2를 사용하여 실시간 운영체제의 틱 생성.
1.6. 주의사항
- SYSCLK: 기본 200MHz. 다른 클럭 주파수 사용 시
PRD
재계산 필요. - Timer 2 예약: SYS/BIOS 사용 시 Timer 2는 RTOS 틱으로 예약될 수 있음.
- 인터럽트 충돌: 동일 인터럽트 라인 사용 시 우선순위 확인 (INT1 > INT13 > INT14).
- 레지스터 보호:
EALLOW
/EDIS
로 보호된 레지스터 접근 관리.
2. CPU 타이머 설정 방법 (Bitfield 방식)
Bitfield 방식은 C2000 드라이버 라이브러리 없이 레지스터를 직접 조작하여 타이머를 설정합니다. 이는 하드웨어 동작을 명확히 이해하고, 코드 최적화를 원하는 개발자에게 적합합니다. 설정 단계는 다음과 같습니다:
PRD
에 주기 값 설정 (예: 100μs → 20,000 사이클 at 200MHz).TPR/TPRH
로 프리스케일러 설정 (보통 TDDR = 0, 분주 없음).TCR.TRB = 1
로 PRD 값을 TIM 레지스터에 로드.TCR.TIE = 1
로 인터럽트 활성화.TCR.TSS = 0
으로 타이머 시작.- PIE(Peripheral Interrupt Expansion)와 인터럽트 벡터 설정.
3. 실전 예제: CPU 타이머 활용
아래는 TMS320F28377D CPU 타이머를 활용한 4가지 예제입니다. 각 코드는 CCS에서 실행 가능하며, SYSCLK = 200MHz를 가정합니다. Bitfield 방식으로 작성되었으며, F28x_Project.h
헤더를 사용합니다. 모든 코드에는 상세한 주석을 추가하여 각 설정과 동작을 설명합니다.
예제 1: 100μs마다 LED 토글
목표: Timer 0를 사용하여 100μs마다 GPIO0에 연결된 LED를 토글합니다.
#include "F28x_Project.h"
// 인터럽트 서비스 루틴 선언: Timer 0 인터럽트 처리 함수
interrupt void cpu_timer0_isr(void);
// 메인 함수
void main(void)
{
// 시스템 초기화: PLL 설정, SYSCLK을 200MHz로 구성
InitSysCtrl();
// GPIO0 설정 (LED 출력)
EALLOW; // 보호된 레지스터 접근 허용
GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 0; // GPIO0을 일반 GPIO로 설정 (EPWM 등 다른 기능 비활성화)
GpioCtrlRegs.GPADIR.bit.GPIO0 = 1; // GPIO0을 출력으로 설정
GpioCtrlRegs.GPAPUD.bit.GPIO0 = 0; // 풀업 저항 비활성화 (외부 풀업/풀다운 회로 가정)
EDIS; // 보호된 레지스터 접근 종료
// 인터럽트 설정
EALLOW;
PieVectTable.TINT0 = &cpu_timer0_isr; // Timer 0 인터럽트 벡터를 ISR에 연결
EDIS;
// 타이머 0 설정: 100μs 주기
EALLOW;
CpuTimer0Regs.PRD.all = 20000; // 주기 설정: 100μs = 200MHz * 100e-6 = 20,000 사이클
CpuTimer0Regs.TPR.bit.TDDR = 0; // 프리스케일러 비활성화 (분주비 1)
CpuTimer0Regs.TPRH.bit.TDDRH = 0; // 상위 8비트 프리스케일러도 0
CpuTimer0Regs.TCR.bit.TRB = 1; // PRD 값을 TIM 레지스터에 로드
CpuTimer0Regs.TCR.bit.TIE = 1; // 타이머 인터럽트 활성화
CpuTimer0Regs.TCR.bit.TSS = 0; // 타이머 시작 (TSS = 0)
EDIS;
// 인터럽트 활성화
IER |= M_INT1; // CPU INT1 활성화 (Timer 0은 PIE 그룹 1에 연결됨)
PieCtrlRegs.PIEIER1.bit.INTx7 = 1; // PIE 그룹 1, INT7 (Timer 0) 활성화
EINT; // 글로벌 인터럽트 활성화
ERTM; // 실시간 인터럽트 활성화 (디버깅 지원)
while(1); // 무한 루프: 모든 동작은 인터럽트로 처리
}
// Timer 0 인터럽트 서비스 루틴
interrupt void cpu_timer0_isr(void)
{
GpioDataRegs.GPATOGGLE.bit.GPIO0 = 1; // GPIO0 상태 반전 (LED 토글)
CpuTimer0Regs.TCR.bit.TIF = 1; // 인터럽트 플래그 클리어 (TIF = 1로 설정)
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; // PIE 그룹 1 ACK: 다음 인터럽트 허용
}
결과: GPIO0에 연결된 LED가 100μs마다 토글됩니다. 주기가 매우 짧아 눈으로 깜빡임을 확인하기 어려울 수 있으며, 오실로스코프 사용을 권장합니다.
예제 2: 500μs마다 ADC 트리거
목표: Timer 1을 사용하여 500μs마다 ADC A0 채널을 변환하고, 결과를 배열에 저장합니다.
#include "F28x_Project.h"
// ADC 결과 저장 배열: 16개 샘플 저장
Uint16 AdcResults[16];
// 인터럽트 서비스 루틴 선언: Timer 1 인터럽트 처리
interrupt void cpu_timer1_isr(void);
void main(void)
{
// 시스템 초기화: PLL 설정, SYSCLK = 200MHz
InitSysCtrl();
// ADC 초기화
EALLOW;
AdcaRegs.ADCCTL1.bit.INTPULSEPOS = 1; // 인터럽트 펄스 위치: 변환 완료 시 발생
AdcaRegs.ADCCTL1.bit.ADCPWDNZ = 1; // ADC 모듈 전원 활성화
AdcaRegs.ADCCTL2.bit.PRESCALE = 6; // ADC 클럭 = SYSCLK/4 (50MHz, 성능 최적화)
AdcaRegs.ADCSOC0CTL.bit.CHSEL = 0; // SOC0에서 A0 채널 선택
AdcaRegs.ADCSOC0CTL.bit.ACQPS = 14; // 샘플링 윈도우: 15 SYSCLK 사이클 (안정적 샘플링)
AdcaRegs.ADCSOC0CTL.bit.TRIGSEL = 5; // SOC0 트리거 소스를 CPU Timer 1로 설정
AdcaRegs.ADCINTSEL1N2.bit.INT1SEL = 0; // ADC INT1을 SOC0 완료로 설정
AdcaRegs.ADCINTSEL1N2.bit.INT1E = 1; // ADC INT1 활성화
AdcaRegs.ADCCTL1.bit.ADCBGPWD = 1; // 밴드갭 회로 활성화 (정확한 변환)
AdcaRegs.ADCCTL1.bit.ADCREFPWD = 1; // 내부 레퍼런스 활성화
GpioCtrlRegs.AIOMUX1.bit.AIO2 = 2; // GPIO2를 ADC A0 입력으로 설정
EDIS;
// 인터럽트 설정
EALLOW;
PieVectTable.TINT1 = &cpu_timer1_isr; // Timer 1 인터럽트 벡터 설정
EDIS;
// 타이머 1 설정: 500μs 주기
EALLOW;
CpuTimer1Regs.PRD.all = 100000; // 주기: 500μs = 200MHz * 500e-6 = 100,000 사이클
CpuTimer1Regs.TPR.bit.TDDR = 0; // 프리스케일러 비활성화
CpuTimer1Regs.TPRH.bit.TDDRH = 0;
CpuTimer1Regs.TCR.bit.TRB = 1; // PRD 값을 TIM에 로드
CpuTimer1Regs.TCR.bit.TIE = 1; // 인터럽트 활성화
CpuTimer1Regs.TCR.bit.TSS = 0; // 타이머 시작
EDIS;
// 인터럽트 활성화
IER |= M_INT13; // CPU INT13 활성화 (Timer 1)
EINT; // 글로벌 인터럽트 활성화
while(1); // 무한 루프: ADC 결과는 ISR에서 처리
}
// Timer 1 인터럽트 서비스 루틴
interrupt void cpu_timer1_isr(void)
{
static Uint16 index = 0; // 순환 버퍼 인덱스
AdcResults[index] = AdcaResultRegs.ADCRESULT0; // SOC0의 ADC 변환 결과 저장
index = (index + 1) % 16; // 인덱스 증가, 16으로 순환
CpuTimer1Regs.TCR.bit.TIF = 1; // 타이머 인터럽트 플래그 클리어
}
결과: A0 채널에서 500μs마다 ADC 변환 결과가 AdcResults
배열에 저장됩니다. CCS의 Expressions 뷰 또는 그래프 뷰로 결과를 확인할 수 있습니다.
예제 3: 버튼 타이밍 측정
목표: Timer 2를 사용하여 GPIO1(버튼) 누름과 뗌 사이의 시간을 측정하고, GPIO0(LED)을 제어합니다.
#include "F28x_Project.h"
// 시간 측정 변수
volatile Uint32 startTime, elapsedTime; // 시작 시간과 경과 시간 저장
void main(void)
{
// 시스템 초기화: PLL 설정, SYSCLK = 200MHz
InitSysCtrl();
// GPIO 설정: GPIO1 (버튼 입력), GPIO0 (LED 출력)
EALLOW;
GpioCtrlRegs.GPAMUX1.bit.GPIO1 = 0; // GPIO1을 일반 GPIO로 설정
GpioCtrlRegs.GPADIR.bit.GPIO1 = 0; // GPIO1을 입력으로 설정
GpioCtrlRegs.GPAPUD.bit.GPIO1 = 0; // GPIO1 풀업 저항 활성화 (버튼 로직 안정화)
GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 0; // GPIO0을 일반 GPIO로 설정
GpioCtrlRegs.GPADIR.bit.GPIO0 = 1; // GPIO0을 출력으로 설정
GpioCtrlRegs.GPAPUD.bit.GPIO0 = 0; // GPIO0 풀업 비활성화
EDIS;
// 타이머 2 설정: 최대 주기로 연속 카운트
EALLOW;
CpuTimer2Regs.PRD.all = 0xFFFFFFFF; // 최대 주기 (32비트 최대값, 오버플로우 방지)
CpuTimer2Regs.TPR.bit.TDDR = 0; // 프리스케일러 비활성화
CpuTimer2Regs.TPRH.bit.TDDRH = 0;
CpuTimer2Regs.TCR.bit.TRB = 1; // PRD 값을 TIM에 로드
CpuTimer2Regs.TCR.bit.TSS = 0; // 타이머 시작
EDIS;
while(1)
{
// 버튼 입력 감지: GPIO1이 저전위(0)일 때 (버튼 누름)
if(GpioDataRegs.GPADAT.bit.GPIO1 == 0)
{
startTime = CpuTimer2Regs.TIM.all; // 현재 카운터 값 저장
GpioDataRegs.GPASET.bit.GPIO0 = 1; // LED 켜기 (GPIO0 = 1)
while(GpioDataRegs.GPADAT.bit.GPIO1 == 0); // 버튼 뗄 때까지 대기
elapsedTime = startTime - CpuTimer2Regs.TIM.all; // 경과 시간 계산 (단위: SYSCLK 사이클, 1 카운트 = 5ns)
GpioDataRegs.GPACLEAR.bit.GPIO0 = 1; // LED 끄기
}
}
}
결과: 버튼 누름과 뗌 사이의 시간(1 카운트 = 5ns)이 elapsedTime
에 저장됩니다. 버튼을 누르는 동안 LED가 켜집니다. CCS Expressions 뷰로 elapsedTime
을 확인하세요.
예제 4: PWM 동기화
목표: Timer 0를 사용하여 ePWM1 모듈을 10kHz로 동기화하고, 50% 듀티 사이클의 PWM 신호를 생성합니다.
#include "F28x_Project.h"
// 인터럽트 서비스 루틴 선언: Timer 0 인터럽트 처리
interrupt void cpu_timer0_isr(void);
void main(void)
{
// 시스템 초기화: PLL 설정, SYSCLK = 200MHz
InitSysCtrl();
// ePWM1 초기화: GPIO0을 EPWM1A로 설정
EALLOW;
GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 1; // GPIO0을 EPWM1A 출력으로 설정
CpuSysRegs.PCLKCR2.bit.EPWM1 = 1; // ePWM1 모듈 클럭 활성화
EPwm1Regs.TBPRD = 10000; // PWM 주기: 10kHz = 200MHz / 20,000 (TBCLK = SYSCLK)
EPwm1Regs.TBPHS.all = 0; // 위상 초기화 (동기화 기준점)
EPwm1Regs.TBCTL.bit.CTRMODE = 0; // 업 카운트 모드 (카운터가 0에서 TBPRD까지 증가)
EPwm1Regs.TBCTL.bit.SYNCOSEL = 1; // 동기화 소스: 외부 신호 (SWFSYNC)
EPwm1Regs.TBCTL.bit.PHSEN = 1; // 위상 동기화 활성화
EPwm1Regs.CMPA.bit.CMPA = 5000; // 비교 값: 50% 듀티 사이클 (TBPRD/2)
EPwm1Regs.AQCTLA.bit.ZRO = 2; // 카운터 0에서 출력 HIGH
EPwm1Regs.AQCTLA.bit.CAU = 1; // CMPA에서 출력 LOW
EPwm1Regs.TBCTL.bit.SWFSYNC = 1; // 초기 소프트웨어 동기화
EDIS;
// 인터럽트 설정
EALLOW;
PieVectTable.TINT0 = &cpu_timer0_isr; // Timer 0 인터럽트 벡터 설정
EDIS;
// 타이머 0 설정: 100μs 주기 (10kHz 동기화)
EALLOW;
CpuTimer0Regs.PRD.all = 20000; // 주기: 100μs = 200MHz * 100e-6 = 20,000 사이클
CpuTimer0Regs.TPR.bit.TDDR = 0; // 프리스케일러 비활성화
CpuTimer0Regs.TPRH.bit.TDDRH = 0;
CpuTimer0Regs.TCR.bit.TRB = 1; // PRD 값을 TIM에 로드
CpuTimer0Regs.TCR.bit.TIE = 1; // 인터럽트 활성화
CpuTimer0Regs.TCR.bit.TSS = 0; // 타이머 시작
EDIS;
// 인터럽트 활성화
IER |= M_INT1; // CPU INT1 활성화 (Timer 0)
PieCtrlRegs.PIEIER1.bit.INTx7 = 1; // PIE 그룹 1, INT7 활성화
EINT; // 글로벌 인터럽트 활성화
while(1); // 무한 루프: PWM 동기화는 ISR에서 처리
}
// Timer 0 인터럽트 서비스 루틴
interrupt void cpu_timer0_isr(void)
{
EALLOW;
EPwm1Regs.TBCTL.bit.SWFSYNC = 1; // ePWM1 동기화: 카운터를 TBPHS로 재설정
EDIS;
CpuTimer0Regs.TCR.bit.TIF = 1; // 타이머 인터럽트 플래그 클리어
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; // PIE 그룹 1 ACK
}
결과: ePWM1에서 10kHz, 50% 듀티 사이클의 PWM 신호가 생성되며, Timer 0에 의해 동기화됩니다. GPIO0(EPWM1A)을 오실로스코프로 확인하세요.
4. 실행 환경 및 디버깅
- 툴: Code Composer Studio (CCS) v10 이상, C2000Ware.
- 헤더:
F28x_Project.h
(C2000Ware:device_support/f2837xd/headers/include
). - 하드웨어: TMS320F28377D LaunchPad 또는 ControlCARD.
- 디버깅 팁:
- CCS Expressions 뷰로
AdcResults
,elapsedTime
확인. - 오실로스코프로 GPIO0(LED), GPIO0(EPWM1A), 또는 ADC 입력(A0) 확인.
TIM
,TCR.TIF
레지스터를 모니터링하여 타이머 상태 점검.
- CCS Expressions 뷰로
- 주의사항:
- SYSCLK: 200MHz 가정. 다른 클럭 사용 시
PRD
값을 조정 (예: Period(s) × SYSCLK(Hz)). - Timer 2: SYS/BIOS 사용 시 예약됨. 사용 전 확인.
- 레지스터 보호:
EALLOW
/EDIS
로 보호된 레지스터 접근 관리. - ADC 입력: A0에 아날로그 신호 연결 필요.
- 인터럽트:
EINT
와PieCtrlRegs.PIEIERx
설정 필수.
- SYSCLK: 200MHz 가정. 다른 클럭 사용 시
5. 결론
TMS320F28377D의 CPU 타이머는 실시간 제어에 필수적이며, Bitfield 방식으로 설정하면 하드웨어 동작을 깊이 이해할 수 있습니다. 위 예제는 LED 제어, ADC 트리거, 타이밍 측정, PWM 동기화를 다루며, 초보자도 CCS에서 바로 실행 가능합니다.
'MCU > C2000' 카테고리의 다른 글
TI C2000 Lockstep 완벽 정리: 기능 안전을 위한 필수 기술 (0) | 2025.08.06 |
---|---|
[TMS320F28377D] SCI 사용법: Bitfield 구조 활용 예제 코드 (0) | 2025.08.06 |
[TMS320F28377D] ADC 트리거 모드 사용: Bitfield 구조 활용 (0) | 2025.08.06 |
[TMS320F28377D] ADC 사용법 : Bitfield 구조 활용 예제 코드 (0) | 2025.08.06 |
[TMS320F28377D] CCS 프로젝트 설정 및 기본 프로그램 (0) | 2025.08.06 |
[TMS320F28377D] GPIO 사용법 : Bitfield 구조 활용 예제 코드 (0) | 2025.08.06 |
[TMS320F28377D] 하프-브릿지 PWM 설정: 비트필드 예제 (0) | 2025.08.05 |
[TMS320F28377D] PWM 출력 설정: Bitfield 구조 활용 예제 코드 (0) | 2025.08.05 |