본문 바로가기
MCU/AVR

AVR128DA64/48/32/28 TCD 드라이버 설계 및 구현

by linuxgo 2025. 9. 5.
반응형

개요

본 문서는 Microchip의 AVR128DA64/48/32/28 시리즈 마이크로컨트롤러에 내장된 12-Bit Timer/Counter Type D (TCD) 기능을 분석하고, 이를 활용할 수 있는 범용 드라이버를 설계 및 구현한 내용을 다룹니다. TCD는 고성능 타이머/카운터로, PWM(펄스 폭 변조), 타이머, 카운터 조작, 모터 제어, 정밀 타이밍 제어 등에 적합합니다. 본 드라이버는 다양한 파형 생성 모드(One Ramp, Two Ramp, Four Ramp, Dual Slope), 데드타임 삽입, 다양한 클럭 소스(OSCHF, PLL, EXTCLK, CLK_PER), 이벤트 시스템(EVSYS) 통합, 주기적 타이머 기능, 카운터 값 읽기/설정을 지원하며, 링버퍼를 제외하고 표준 ISR을 사용하여 인터럽트 기반 동작을 구현합니다. TCD0는 이벤트 생성자로 CMPBCLR, CMPASET, CMPBSET, PROGEV를 제공하고, 이벤트 사용자로 INPUTA, INPUTB를 통해 이벤트를 수신합니다. 인터럽트 벡터는 TCD0_OVF (벡터 14, 0x1C)와 TCD0_TRIG (벡터 15, 0x1E)를 지원하며, I/O Multiplexing을 통해 유연한 핀 매핑이 가능합니다. Microchip Studio 및 AVR-GCC 환경에서 모든 AVR128DA 모델에 호환됩니다.

AVR128DA TCD block diagram

사양

AVR128DA 시리즈의 TCD 사양은 다음과 같습니다:

  • 모듈 수: 단일 TCD(TCD0).
  • 해상도: 12비트.
  • 클럭 소스:
    • OSCHF: 고주파 오실레이터 (최대 24MHz).
    • PLL: 위상 동기 루프 기반 클럭.
    • EXTCLK: 외부 클럭 입력.
    • CLK_PER: 주변 장치 클럭 (최대 24MHz).

  • 프리스케일러: 1, 2, 4, 8 분주 지원 (CNTPRE[3:0]).
  • 파형 생성 모드:
    • One Ramp: 주기당 한 번 리셋.
    • Two Ramp: 주기당 두 번 리셋.
    • Four Ramp: 주기당 네 번 리셋.
    • Dual Slope: 주기 내에서 상향 및 하향 카운팅.
  • 출력:
    • 기본 출력: WOA, WOB.
    • 추가 출력: WOC, WOD (하프 브리지/풀 브리지 애플리케이션).
  • 데드타임 삽입: 하프/풀 브리지 출력에서 Low-side (DTL) 및 High-side (DTH) 데드타임 설정 지원 (TCDn.DT).
  • 타이머 기능: 주기적 오버플로우 인터럽트(TCD0_OVF)를 활용한 마이크로초 단위 타이밍 지원.
  • 카운터 기능: TCDn.CNT를 통한 12비트 카운터 값 읽기/설정/리셋.
  • 입력 채널: Input A, Input B (이벤트 또는 소프트웨어 기반 캡처).
  • 인터럽트:
    • TCD0_OVF: 오버플로우 인터럽트 (벡터 14, 0x1C, PWM 주기 또는 타이머 주기).
    • TCD0_TRIG: 트리거 인터럽트 (TRIGA, TRIGB, 벡터 15, 0x1E).
  • 이벤트 시스템:
    • 이벤트 생성자: CMPBCLR, CMPASET, CMPBSET, PROGEV (비동기, CLK_TCD 기반 펄스 이벤트).
    • 이벤트 사용자: INPUTA, INPUTB (비동기, 레벨 또는 에지 감지로 폴트 또는 캡처).
    • 최대 10개 병렬 이벤트 채널 지원.
    • 비동기 이벤트는 Standby 모드에서도 동작 가능.
  • 기능:
    • 비동기 동작: 주변 클럭과 독립적인 클럭 소스.
    • 더블 버퍼링: CMPxSET/CLR 레지스터로 글리치 없는 업데이트.
    • 입력 블랭킹: 노이즈 필터링.
    • 폴트 처리: 비상 정지, 입력 블랭킹, 오버로드 보호.
    • 디더링: 주파수 미세 조정 (Dual Slope 제외).
  • 전압 범위: 1.8V ~ 5.5V (VDD).
  • 전력 최적화: ENABLE=0으로 전력 절감.
  • 리셋 상태: TCD 비활성화, 핀은 GPIO 입력 모드.
  • 디버그 모드: DBGRUN=1로 디버그 중 동작 유지 가능.

레지스터 설정 상세

TCD는 TCDn 레지스터를 통해 제어됩니다. 주요 레지스터와 비트필드는 다음과 같습니다:

  • TCDn.CNT (0x10~0x11):
    • 비트 [11:0]: 현재 카운터 값 (12비트, 0x000~0xFFF).
    • 리셋: 0x0000, R/W.
  • TCDn.CTRLA (0x00):
    • 비트 [7]: ENABLE (타이머 활성화).
    • 비트 [6:4]: CLKSEL[2:0] (클럭 소스: 0x0=CLK_PER, 0x1=OSCHF, 0x2=EXTCLK, 0x3=PLL).
    • 비트 [3:0]: CNTPRE[3:0] (프리스케일러: 0x0=1, 0x1=2, 0x2=4, 0x3=8).
    • 리셋: 0x00, R/W.
  • TCDn.CTRLB (0x01):
    • 비트 [3:0]: WGMODE[3:0] (파형 생성 모드: 0x0=One Ramp, 0x1=Two Ramp, 0x2=Four Ramp, 0x3=Dual Slope).
    • 리셋: 0x00, R/W.
  • TCDn.DT (0x0E):
    • 비트 [15:8]: DTH[7:0] (High-side 데드타임, 클럭 사이클 단위).
    • 비트 [7:0]: DTL[7:0] (Low-side 데드타임, 클럭 사이클 단위).
    • 리셋: 0x0000, R/W, _PROTECTED_WRITE 필요.
  • TCDn.EVCTRLA (0x05):
    • 비트 [7]: FILTER (입력 필터 활성화).
    • 비트 [6]: ASYNC (비동기 입력 활성화).
    • 비트 [5:4]: EDGE[1:0] (입력 에지 선택).
    • 비트 [3:0]: ACTION[3:0] (입력 동작: 폴트, 캡처 등).
    • 리셋: 0x00, R/W.
  • TCDn.EVCTRLB (0x06):
    • Input B에 대한 설정 (EVCTRLA와 동일).
    • 리셋: 0x00, R/W.
  • TCDn.FAULTCTRL (0x0A):
    • 비트 [7:6]: CMPA/B (폴트 동작 설정).
    • 비트 [5]: BLANK (입력 블랭킹 활성화).
    • 비트 [4]: OVERLOAD (오버로드 보호 활성화).
    • 리셋: 0x00, R/W, _PROTECTED_WRITE 필요.
  • TCDn.INTCTRL (0x07):
    • 비트 [7]: OVF (오버플로우 인터럽트 활성화, 벡터 14).
    • 비트 [1:0]: TRIGB, TRIGA (입력 이벤트 인터럽트 활성화, 벡터 15).
    • 리셋: 0x00, R/W.
  • TCDn.INTFLAGS (0x08):
    • 비트 [7]: OVF (오버플로우 플래그, 1 작성으로 클리어).
    • 비트 [1:0]: TRIGB, TRIGA (입력 이벤트 플래그, 1 작성으로 클리어).
    • 리셋: 0x00, R/W.
  • TCDn.CAPTUREA/B (0x12~0x15):
    • 비트 [11:0]: 캡처된 카운터 값.
    • 리셋: 0x0000, R.
  • TCDn.CMPASET/CMPBSET/CMPACLR/CMPBCLR (0x16~0x1D):
    • 비트 [11:0]: 비교 값 설정 (SET: 출력 활성화, CLR: 출력 비활성화).
    • 리셋: 0x0000, R/W.
  • 이벤트 시스템 관련 레지스터:
    •  EVSYS.CHANNELn (0x10 + n):
      •   비트 [7:0]: CHANNELn[7:0] (이벤트 생성자 선택: TCD0는 0xB0=CMPBCLR, 0xB1=CMPASET, 0xB2=CMPBSET, 0xB3=PROGEV).
      •   리셋: 0x00, R/W.
    •  EVSYS.USERn (0x20 + n):
      •   비트 [7:0]: USER[7:0] (이벤트 사용자 선택: TCD0는 0x29=INPUTA, 0x2A=INPUTB).
      •   리셋: 0x00, R/W.
    •  EVSYS.SWEVENTA/B:
      •   비트 [7:0]: SWEVENTx[7:0] (소프트웨어 이벤트 생성, 채널 0~7은 SWEVENTA, 8~9는 SWEVENTB).
      •   리셋: 0x00, W.

I/O Multiplexing and Considerations

I/O Multiplexing

TCD는 PORTMUX 설정을 통해 출력 핀(WOA, WOB, WOC, WOD)을 유연하게 매핑할 수 있습니다. 이벤트 시스템은 EVOUTx 핀(예: EVOUTA, EVOUTC 등)을 통해 TCD 이벤트를 외부로 출력할 수 있습니다. 아래는 TCD 관련 핀 매핑 테이블입니다:

TQFP64  /VQFN64 VQFN48/TQFP48  VQFN32/TQFP32 S SPDIP28/SOIC28/SSOP28  Pin Name  TCDn  EVSYS  설명
62 44 30 22 PA0 WO0 - 기본 TCD 출력 (WOA)
63 45 31 23 PA1 WO1 - 기본 TCD 출력 (WOB)
64 46 32 24 PA2 WO2 EVOUTA 기본 TCD 출력 (WOC), 이벤트 출력 A
1 47 1 25 PA3 WO3 - 기본 TCD 출력 (WOD)
2 48 2 26 PA4 WOA - 대체 TCD 출력
3 1 3 27 PA5 WOB - 대체 TCD 출력
4 2 4 28 PA6 WOC - 대체 TCD 출력
5 3 5 1 PA7 WOD EVOUTA(3) 대체 TCD 출력, 이벤트 출력 A
8 4 - - PB0 WO0(3) - 대체 TCD 출력 (WOA)
9 5 - - PB1 WO1(3) - 대체 TCD 출력 (WOB)
10 6 - - PB2 WO2(3) EVOUTB 대체 TCD 출력 (WOC), 이벤트 출력 B
11 7 - - PB3 WO3(3) - 대체 TCD 출력 (WOD)
12 8 - - PB4 WOA(3) - 대체 TCD 출력
13 9 - - PB5 WOB(3) - 대체 TCD 출력
16 10 6 2 PC0 WO0(3) - 대체 TCD 출력 (WOA)
17 11 7 3 PC1 WO1(3) - 대체 TCD 출력 (WOB)
18 12 8 4 PC2 WO2(3) EVOUTC 대체 TCD 출력 (WOC), 이벤트 출력 C
19 13 9 5 PC3 WO3(3) - 대체 TCD 출력 (WOD)
22 16 - - PC4 WO0(3) - 대체 TCD 출력 (WOA)
23 17 - - PC5 WO1(3) - 대체 TCD 출력 (WOB)
26 20 10 6 PD0 WO0(3) - 대체 TCD 출력 (WOA)
27 21 11 7 PD1 WO1(3) - 대체 TCD 출력 (WOB)
28 22 12 8 PD2 WO2(3) EVOUTD 대체 TCD 출력 (WOC), 이벤트 출력 D
29 23 13 9 PD3 WO3(3) - 대체 TCD 출력 (WOD)
36 30 - - PE0 WO0(3) - 대체 TCD 출력 (WOA)
37 31 - - PE1 WO1(3) - 대체 TCD 출력 (WOB)
38 32 - - PE2 WO2(3) EVOUTE 대체 TCD 출력 (WOC), 이벤트 출력 E
39 33 - - PE3 WO3(3) EVOUTE(3) 대체 TCD 출력 (WOD), 이벤트 출력 E
44 34 20 16 PF0 WOA(3) - 대체 TCD 출력
45 35 21 17 PF1 WOB(3) - 대체 TCD 출력
46 36 22 - PF2 WOC(3) EVOUTF 대체 TCD 출력, 이벤트 출력 F
47 37 23 - PF3 WOD(3) - 대체 TCD 출력

설정 예시 (TCD0을 PA4~PA7에 매핑):

tcd_configure_portmux(&tcd0_instance, TCD_PORTMUX_DEFAULT); // 기본 핀 (PA4:WOA, PA5:WOB, PA6:WOC, PA7:WOD)

Considerations

  • 핀 충돌 방지: TCD 출력 핀 및 EVOUTx 핀은 USART, SPI, TWI 등과 공유되므로, PORTMUX 설정 시 데이터시트 확인 필수.
  • 패키지 호환성: AVR128DA28은 핀 수가 적어 TCD 출력 및 EVOUT 핀이 제한적이며, 대체 핀은 주로 PA4PA7, PC0PC3 사용.
  • 전력 최적화: 사용하지 않는 TCD 출력 또는 EVOUT 핀은 GPIO 입력 모드로 설정.
  • 데드타임: 하프/풀 브리지 모드에서 TCDn.DT 레지스터로 DTL/DTH 설정, 마이크로초 단위로 설정 가능.
  • 클럭 소스: PLL 사용 시 CLKCTRL.PLLCTRLA 설정 확인. EXTCLK는 외부 핀 입력 확인.
  • 타이머: TCD0_OVF를 활용한 주기적 타이밍, 마이크로초 단위 설정 지원.
  • 카운터: TCDn.CNT로 카운터 값 읽기/설정/리셋, 동작 중 쓰기는 비활성화 상태에서만 안전.

Interrupt Vector Mapping

TCD 관련 인터럽트 벡터는 다음과 같습니다:

Vector
Number
 Program
Address
(word) 
Peripheral
Source
 Description  28-Pin  32-Pin  48-Pin  64-Pin
14 0x1C TCD0_OVF Timer/Counter Type D
Overflow Interrupt
(PWM 주기 또는 타이머 주기)
X X X X
15 0x1E TCD0_TRIG Timer/Counter Type D
Trigger Interrupt (TRIGA, TRIGB)
X X X X
  • TCD0_OVF: 오버플로우 발생 시 호출. PWM 주기 끝 또는 타이머 주기 처리에 사용.
  • TCD0_TRIG: 입력 이벤트(TRIGA, TRIGB) 발생 시 호출. 캡처 기능에 사용.
  • 설정 예시:
    TCD0.INTCTRL |= TCD_OVF_bm; // 오버플로우 인터럽트 활성화
    TCD0.INTCTRL |= (TCD_TRIGA_bm | TCD_TRIGB_bm); // 트리거 인터럽트 활성화
    

Event System Integration

TCD는 이벤트 시스템(EVSYS)을 통해 주변 장치 간 직접 통신을 지원합니다. TCD는 이벤트 생성자 및 사용자로 동작하며, 비동기 이벤트 처리가 가능하여 Standby 모드에서도 동작합니다.

TCD as Event Generator

TCD0는 다음과 같은 이벤트를 생성합니다 (모두 비동기, CLK_TCD 기반 펄스 이벤트):

  • CMPBCLR (0xB0): 카운터가 CMPBCLR과 일치할 때 발생.
  • CMPASET (0xB1): 카운터가 CMPASET과 일치할 때 발생.
  • CMPBSET (0xB2): 카운터가 CMPBSET과 일치할 때 발생.
  • PROGEV (0xB3): 프로그래머블 이벤트 출력.

설정 예시 (TCD0 CMPBCLR 이벤트를 채널 0에 연결, ADC0 시작 트리거로 사용):

tcd_configure_event(&tcd0_instance, 0xB0, 0x00, 0x00, 0x00); // CMPBCLR, 채널 0
EVSYS.USERADC0START = 0x01; // ADC0 START를 채널 0에 연결

TCD as Event User

TCD0는 INPUTA 및 INPUTB를 통해 이벤트를 수신하며, 비동기 레벨 또는 에지 감지로 폴트 또는 캡처 동작을 수행합니다:

  • INPUTA (USER #0x29): 폴트 또는 캡처 이벤트 입력.
  • INPUTB (USER #0x2A): 폴트 또는 캡처 이벤트 입력.

설정 예시 (PORTA PIN0의 핀 레벨 이벤트를 TCD0 INPUTA에 연결):

tcd_configure_event(&tcd0_instance, 0x40, 0x00, 0x29, 0x01); // PORTA PIN0, 채널 0, INPUTA, 채널 1
TCD0.EVCTRLA = TCD_ACTION_CAPTURE_bm | TCD_EDGE_RISING_bm; // INPUTA에서 상승 에지 캡처

Software Event

소프트웨어 이벤트를 통해 TCD 동작을 트리거할 수 있습니다:

EVSYS.SWEVENTA = (1 << 0); // 채널 0에 소프트웨어 이벤트 생성

Considerations

  • 비동기 동작: TCD 이벤트는 CLK_TCD 기반으로 비동기 처리되며, Standby 모드에서 동작 가능.
  • 채널 제한: 최대 10개 채널 사용 가능. TCD 이벤트는 모든 채널에 연결 가능.
  • 동기화 지연: 비동기 이벤트를 동기 사용자(예: TCA0)가 수신할 경우 2~3 CLK_PER 지연 발생.
  • EVOUT 핀: EVOUTA~EVOUTF 핀을 통해 TCD 이벤트를 외부로 출력 가능 (예: PA2, PB2, PC2 등).

TCD 설정 고려사항

  • PWM 주파수 계산 (One Ramp 기준):
    • 공식: fPWM = fCLK_TCD_CNT / (Prescaler * (CMPBCLR + 1))
    • 역으로: CMPBCLR = (fCLK_TCD_CNT / (Prescaler * fPWM)) - 1
    • 예: 24MHz 클럭, 프리스케일러=1, 75kHz PWM → CMPBCLR = (24,000,000 / (1 * 75,000)) - 1 = 319.
  • 듀티 사이클 계산:
    • 공식: CMPASET = CMPBCLR * (Duty / 100)
    • 예: CMPBCLR=319, 50% 듀티 → CMPASET = 319 * (50 / 100) = 160.
  • 데드타임 계산:
    • 공식: DTL/DTH = fCLK_TCD_CNT * DeadTime_us / 1,000,000
    • 예: 24MHz 클럭, 1us 데드타임 → DTL = 24,000,000 * 1 / 1,000,000 = 24.
  • 타이머 주기 계산 (One Ramp 기준):
    • 공식: Timer_us = (Prescaler * (CMPBCLR + 1)) * 1,000,000 / fCLK_TCD_CNT
    • 역으로: CMPBCLR = (fCLK_TCD_CNT * Timer_us / 1,000,000) / Prescaler - 1
    • 예: 24MHz 클럭, 프리스케일러=1, 100us 타이머 → CMPBCLR = (24,000,000 * 100 / 1,000,000) / 1 - 1 = 2399.
  • 카운터 값 계산:
    • 현재 시간: Time_us = (Prescaler * TCDn.CNT) * 1,000,000 / fCLK_TCD_CNT
    • 예: 24MHz 클럭, 프리스케일러=1, TCDn.CNT=1200 → Time_us = (1 * 1200) * 1,000,000 / 24,000,000 = 50us.
  • 초기화 기본값:
    • 클럭 소스: OSCHF (24MHz).
    • 프리스케일러: 1.
    • 파형 모드: One Ramp.
    • PORTMUX: 기본 핀 (PA4:WOA, PA5:WOB, PA6:WOC, PA7:WOD).
    • 이벤트 시스템: 비활성화.
    • 폴트: 비활성화.
    • 비교 값: CMPBCLR=0, CMPASET=0.
    • 데드타임: DTL=0, DTH=0.
    • 카운터: TCDn.CNT=0.
  • 설정 순서:
    1. tcd_init_simple으로 기본 초기화.
    2. tcd_configure_portmux로 핀 매핑 (필요 시).
    3. tcd_set_pwm_freq_duty 또는 tcd_set_timer_us로 PWM/타이머 설정.
    4. tcd_set_deadtime_us로 데드타임 설정 (PWM 필요 시).
    5. tcd_configure_event로 이벤트 설정 (필요 시).
    6. tcd_configure_fault로 폴트 설정 (필요 시).
    7. tcd_set_count 또는 tcd_reset_count로 카운터 초기화 (필요 시).
    8. tcd_enable으로 활성화.
  • 카운터 주의사항:
    • TCDn.CNT 쓰기는 TCD 비활성화(ENABLE=0) 상태에서만 안전.
    • 동작 중 TCDn.CNT 읽기는 가능하나, 더블 버퍼링 없으므로 타이밍 주의.
    • tcd_reset_count는 TCDn.CNT=0 설정, PWM/타이머 동기화 확인.
  • 인터럽트: 표준 ISR 사용. TCD0_OVF로 타이머 주기 처리, TCD0_TRIG로 캡처 처리, 플래그 클리어 필수.
  • 포트 설정: PORTMUX 및 EVOUT 핀 설정 시 다른 주변 장치와의 충돌 방지.
  • 전력 최적화: 사용하지 않는 TCD는 ENABLE=0으로 비활성화.
  • 호환성: 모든 AVR128DA 모델에서 단일 TCD(TCD0) 지원.
  • CCP 보호: FAULTCTRL, DT는 _PROTECTED_WRITE 사용.
  • 에러 처리: 입력 이벤트 오류는 STATUS 및 INTFLAGS로 확인.

드라이버 구현 내용

드라이버는 AVR128DA64/48/32/28 호환으로 설계되었으며, TCD 및 이벤트 시스템 기능을 추상화합니다. 주요 구현은 다음과 같습니다:

  • tcd_init_simple: 간소화된 TCD 초기화 (클럭 주파수, 기본 설정).
  • tcd_init: 상세 TCD 초기화 (기존 함수 유지).
  • tcd_enable/disable: TCD 활성화/비활성화.
  • tcd_configure_portmux: PORTMUX 설정.
  • tcd_get_count: 현재 카운터 값 읽기.
  • tcd_set_count: 카운터 값 설정.
  • tcd_reset_count: 카운터 리셋.
  • tcd_set_pwm: PWM 듀티 및 주기 설정 (레지스터 값 직접 설정).
  • tcd_set_pwm_freq_duty: PWM 주파수(Hz) 및 듀티(%) 설정.
  • tcd_configure_deadtime: 데드타임 설정 (클럭 사이클 단위).
  • tcd_set_deadtime_us: 데드타임 설정 (마이크로초 단위).
  • tcd_set_timer_us: 타이머 주기 설정 (마이크로초 단위).
  • tcd_capture: 단일 캡처 데이터 처리.
  • tcd_configure_event: 이벤트 생성자 및 사용자 설정.
  • tcd_configure_fault: 폴트 동작, 블랭킹, 오버로드 보호 설정.
  • tcd_check_faults: 폴트 상태 확인.
  • 클럭 설정: main.c에서 24MHz OSCHF, Auto-tune 활성화.
  • 인터럽트: 표준 ISR로 TCD0_OVF, TCD0_TRIG 처리.

사용 방법

  1. 헤더 파일 포함: tcd_driver.h, <avr/io.h>, <util/delay.h> 추가.
  2. 클럭 설정: clock_init_24mhz로 24MHz OSCHF 클럭 설정.
  3. TCD 간소화 초기화:
    TCD_Instance tcd0_instance;
    tcd_init_simple(&tcd0_instance, &TCD0, 24000000UL); // 24MHz 클럭
    
  4. PWM 설정 (직관적):
    tcd_set_pwm_freq_duty(&tcd0_instance, 75000, 50.0); // 75kHz, 50% 듀티
    tcd_set_deadtime_us(&tcd0_instance, 1.0, 1.0); // 1us 데드타임
    
  5. 타이머 설정 (직관적):
    tcd_set_timer_us(&tcd0_instance, 100); // 100us 타이머
    
  6. 카운터 설정:
    tcd_reset_count(&tcd0_instance); // 카운터 리셋
    uint16_t count = tcd_get_count(&tcd0_instance); // 현재 카운터 값 읽기
    tcd_set_count(&tcd0_instance, 1000); // 카운터를 1000으로 설정
    
  7. 포트 설정 (필요 시):
    tcd_configure_portmux(&tcd0_instance, TCD_PORTMUX_ALT1); // 대체 핀 (PB0~PB3)
    
  8. 이벤트 설정 (필요 시):
    tcd_configure_event(&tcd0_instance, 0xB0, 0x00, 0x29, 0x01); // CMPBCLR, 채널 0, INPUTA, 채널 1
    
  9. 폴트 설정 (필요 시):
    tcd_configure_fault(&tcd0_instance, TCD_FAULT_ACTION_HALT, 1, 1); // 정지, 블랭킹 및 오버로드 활성화
    
  10. TCD 활성화:
    tcd_enable(&tcd0_instance);
    
  11. 캡처 데이터 처리: tcd_capture로 단일 캡처 데이터 처리.
  12. 인터럽트 설정: tcd_capture_it_init로 TCD0_TRIG 활성화, TCD0.INTCTRL로 TCD0_OVF 활성화.

코드 구현

tcd_driver.h

/**
 * @file tcd_driver.h
 * @brief AVR128DA64/48/32/28 TCD 드라이버 헤더 파일
 * @details AVR128DA 시리즈의 Timer/Counter Type D (TCD) 모듈을 제어하는 드라이버.
 *          PWM, 타이머, 카운터 조작, 캡처, 이벤트 시스템, 폴트 처리, 데드타임 삽입,
 *          직관적 초기화 및 설정을 지원하며, 모든 AVR128DA 모델(64/48/32/28)에 호환.
 * @author 작성자
 * @date 2025-09-05
 */

#ifndef TCD_DRIVER_H
#define TCD_DRIVER_H

#include <avr/io.h>    // AVR 레지스터 정의를 위한 헤더
#include <stdint.h>    // 표준 정수형 정의를 위한 헤더

/**
 * @brief TCD 클럭 소스 정의
 * @details TCD 클럭 소스를 선택하는 매크로. TCDn.CTRLA.CLKSEL에 설정.
 */
#define TCD_CLKSEL_CLKPER  0x0 // Peripheral Clock (CLK_PER, 기본 시스템 클럭)
#define TCD_CLKSEL_OSCHF   0x1 // High-Frequency Oscillator (최대 24MHz)
#define TCD_CLKSEL_EXTCLK  0x2 // External Clock (외부 핀 입력)
#define TCD_CLKSEL_PLL     0x3 // PLL Clock (위상 동기 루프 기반)

/**
 * @brief TCD 프리스케일러 정의
 * @details TCD 클럭 분주 설정. TCDn.CTRLA.CNTPRE에 설정.
 */
#define TCD_CNTPRE_DIV1   0x0 // 분주 없음 (1:1)
#define TCD_CNTPRE_DIV2   0x1 // 2분주
#define TCD_CNTPRE_DIV4   0x2 // 4분주
#define TCD_CNTPRE_DIV8   0x3 // 8분주

/**
 * @brief TCD 파형 생성 모드 정의
 * @details TCD의 파형 생성 모드. TCDn.CTRLB.WGMODE에 설정.
 */
#define TCD_WGMODE_ONERAMP   0x0 // One Ramp: 주기당 한 번 리셋
#define TCD_WGMODE_TWORAMP   0x1 // Two Ramp: 주기당 두 번 리셋
#define TCD_WGMODE_FOURRAMP  0x2 // Four Ramp: 주기당 네 번 리셋
#define TCD_WGMODE_DUALSLOPE 0x3 // Dual Slope: 상향 및 하향 카운팅

/**
 * @brief TCD PORTMUX 설정 정의
 * @details TCD 출력 핀 매핑 설정. PORTMUX.TCDROUTEA에 설정.
 */
#define TCD_PORTMUX_DEFAULT 0x0 // 기본 핀 (PA4:WOA, PA5:WOB, PA6:WOC, PA7:WOD)
#define TCD_PORTMUX_ALT1    0x1 // 대체 핀 (PB0:WOA, PB1:WOB, PB2:WOC, PB3:WOD 등)

/**
 * @brief TCD 폴트 동작 정의
 * @details 폴트 이벤트 발생 시 TCD 동작. TCDn.FAULTCTRL.ACTIONx에 설정.
 */
#define TCD_FAULT_ACTION_OFF      0x0 // 폴트 동작 없음
#define TCD_FAULT_ACTION_HALT     0x1 // 카운터 정지
#define TCD_FAULT_ACTION_RESTART  0x2 // 카운터 재시작
#define TCD_FAULT_ACTION_CAPTURE  0x3 // 폴트 시 캡처

/**
 * @brief TCD 인스턴스 구조체
 * @details TCD 모듈의 상태와 설정을 저장하는 구조체.
 */
typedef struct {
    TCD_t *tcd;                      // TCD 레지스터 포인터 (예: &TCD0)
    volatile uint16_t capture_data;   // 단일 캡처 데이터 (TCDn.CAPTUREA/B에서 읽은 값)
    uint32_t clk_freq;               // TCD 클럭 주파수 (Hz, 예: 24MHz)
    uint8_t prescaler;               // 프리스케일러 값 (1, 2, 4, 8)
} TCD_Instance;

/**
 * @brief TCD 간소화 초기화
 * @param instance TCD 인스턴스 포인터
 * @param tcd TCD 레지스터 포인터 (예: &TCD0)
 * @param clk_freq TCD 클럭 주파수 (Hz, 예: 24000000 for 24MHz)
 * @details 기본 설정(24MHz OSCHF, 프리스케일러 1, One Ramp, 기본 핀)으로 TCD 초기화.
 */
void tcd_init_simple(TCD_Instance *instance, TCD_t *tcd, uint32_t clk_freq);

/**
 * @brief TCD 상세 초기화
 * @param instance TCD 인스턴스 포인터
 * @param tcd TCD 레지스터 포인터 (예: &TCD0)
 * @param clksel 클럭 소스 (예: TCD_CLKSEL_OSCHF)
 * @param cntpre 프리스케일러 (예: TCD_CNTPRE_DIV1)
 * @param wgmode 파형 생성 모드 (예: TCD_WGMODE_ONERAMP)
 * @param clk_freq TCD 클럭 주파수 (Hz, 예: 24000000 for 24MHz)
 * @param cmpbclr CMPBCLR 값 (PWM 주기 설정, 0x000~0xFFF)
 * @param cmpaset CMPASET 값 (PWM 듀티 설정, 0x000~0xFFF)
 * @param portmux PORTMUX 설정 (TCD_PORTMUX_DEFAULT, TCD_PORTMUX_ALT1)
 * @param ev_generator 이벤트 생성자 (예: 0xB0=CMPBCLR)
 * @param ev_channel 이벤트 채널 (0x00~0x09)
 * @param ev_user 이벤트 사용자 (예: 0x29=INPUTA)
 * @param ev_user_channel 사용자 채널 (0x00~0x09)
 * @param filter 입력 필터 활성화 (0: 비활성화, 1: 활성화)
 * @param dtl Low-side 데드타임 (클럭 사이클 단위, 0x00~0xFF)
 * @param dth High-side 데드타임 (클럭 사이클 단위, 0x00~0xFF)
 * @details TCD의 모든 설정을 사용자 지정으로 초기화.
 */
void tcd_init(TCD_Instance *instance, TCD_t *tcd, uint8_t clksel, uint8_t cntpre, uint8_t wgmode, uint32_t clk_freq, uint16_t cmpbclr, uint16_t cmpaset, uint8_t portmux, uint8_t ev_generator, uint8_t ev_channel, uint8_t ev_user, uint8_t ev_user_channel, uint8_t filter, uint8_t dtl, uint8_t dth);

/**
 * @brief TCD 활성화
 * @param instance TCD 인스턴스 포인터
 * @details TCDn.CTRLA.ENABLE=1로 설정하여 TCD 동작 시작.
 */
void tcd_enable(TCD_Instance *instance);

/**
 * @brief TCD 비활성화
 * @param instance TCD 인스턴스 포인터
 * @details TCDn.CTRLA.ENABLE=0으로 설정하여 TCD 동작 중지.
 */
void tcd_disable(TCD_Instance *instance);

/**
 * @brief PORTMUX 설정
 * @param instance TCD 인스턴스 포인터
 * @param portmux PORTMUX 설정 (TCD_PORTMUX_DEFAULT, TCD_PORTMUX_ALT1)
 * @details TCD 출력 핀을 기본 또는 대체 핀으로 매핑.
 */
void tcd_configure_portmux(TCD_Instance *instance, uint8_t portmux);

/**
 * @brief 현재 카운터 값 읽기
 * @param instance TCD 인스턴스 포인터
 * @return 현재 TCDn.CNT 값 (12비트, 0x000~0xFFF)
 * @details TCD의 12비트 카운터 값을 읽음. 동작 중에도 가능.
 */
uint16_t tcd_get_count(TCD_Instance *instance);

/**
 * @brief 카운터 값 설정
 * @param instance TCD 인스턴스 포인터
 * @param count 설정할 카운터 값 (12비트, 0x000~0xFFF)
 * @details TCD 비활성화 상태에서 TCDn.CNT에 값을 설정.
 */
void tcd_set_count(TCD_Instance *instance, uint16_t count);

/**
 * @brief 카운터 리셋
 * @param instance TCD 인스턴스 포인터
 * @details TCD 비활성화 상태에서 TCDn.CNT를 0으로 리셋.
 */
void tcd_reset_count(TCD_Instance *instance);

/**
 * @brief PWM 주파수 및 듀티 설정
 * @param instance TCD 인스턴스 포인터
 * @param freq_hz PWM 주파수 (Hz)
 * @param duty_percent 듀티 사이클 (%)
 * @details 주파수와 듀티를 기반으로 CMPBCLR, CMPASET 계산 및 설정.
 */
void tcd_set_pwm_freq_duty(TCD_Instance *instance, uint32_t freq_hz, float duty_percent);

/**
 * @brief 데드타임 설정 (마이크로초 단위)
 * @param instance TCD 인스턴스 포인터
 * @param dtl_us Low-side 데드타임 (us)
 * @param dth_us High-side 데드타임 (us)
 * @details 마이크로초 단위 데드타임을 클럭 사이클로 변환하여 TCDn.DT 설정.
 */
void tcd_set_deadtime_us(TCD_Instance *instance, float dtl_us, float dth_us);

/**
 * @brief 타이머 주기 설정 (마이크로초 단위)
 * @param instance TCD 인스턴스 포인터
 * @param period_us 타이머 주기 (us)
 * @details 마이크로초 단위 주기를 CMPBCLR로 변환, TCD0_OVF 인터럽트 활성화.
 */
void tcd_set_timer_us(TCD_Instance *instance, uint32_t period_us);

/**
 * @brief PWM 설정 (레지스터 값 직접 설정)
 * @param instance TCD 인스턴스 포인터
 * @param cmpaset CMPASET 값 (듀티, 0x000~0xFFF)
 * @param cmpbclr CMPBCLR 값 (주기, 0x000~0xFFF)
 * @details CMPASET, CMPBCLR을 직접 설정하여 PWM 생성.
 */
void tcd_set_pwm(TCD_Instance *instance, uint16_t cmpaset, uint16_t cmpbclr);

/**
 * @brief 데드타임 설정 (클럭 사이클 단위)
 * @param instance TCD 인스턴스 포인터
 * @param dtl Low-side 데드타임 (클럭 사이클 단위, 0x00~0xFF)
 * @param dth High-side 데드타임 (클럭 사이클 단위, 0x00~0xFF)
 * @details TCDn.DT 레지스터에 데드타임 설정, _PROTECTED_WRITE 사용.
 */
void tcd_configure_deadtime(TCD_Instance *instance, uint8_t dtl, uint8_t dth);

/**
 * @brief 단일 캡처
 * @param instance TCD 인스턴스 포인터
 * @param channel 캡처 채널 (0: Input A, 1: Input B)
 * @return 캡처된 데이터 (TCDn.CAPTUREA/B, 12비트)
 * @details 입력 이벤트 발생 시 TCDn.CAPTUREA/B에서 값을 읽고 플래그 클리어.
 */
uint16_t tcd_capture(TCD_Instance *instance, uint8_t channel);

/**
 * @brief 이벤트 설정
 * @param instance TCD 인스턴스 포인터
 * @param ev_generator 이벤트 생성자 (예: 0xB0=CMPBCLR)
 * @param ev_channel 이벤트 채널 (0x00~0x09)
 * @param ev_user 이벤트 사용자 (예: 0x29=INPUTA)
 * @param ev_user_channel 사용자 채널 (0x00~0x09)
 * @details 이벤트 시스템(EVSYS)을 통해 TCD 이벤트 생성/사용 설정.
 */
void tcd_configure_event(TCD_Instance *instance, uint8_t ev_generator, uint8_t ev_channel, uint8_t ev_user, uint8_t ev_user_channel);

/**
 * @brief 폴트 설정
 * @param instance TCD 인스턴스 포인터
 * @param fault_action 폴트 동작 (예: TCD_FAULT_ACTION_HALT)
 * @param blanking 입력 블랭킹 활성화 (0: 비활성화, 1: 활성화)
 * @param overload 오버로드 보호 활성화 (0: 비활성화, 1: 활성화)
 * @details TCDn.FAULTCTRL 설정, _PROTECTED_WRITE 사용.
 */
void tcd_configure_fault(TCD_Instance *instance, uint8_t fault_action, uint8_t blanking, uint8_t overload);

/**
 * @brief 인터럽트 기반 캡처 초기화
 * @param instance TCD 인스턴스 포인터
 * @param filter 입력 필터 활성화 (0: 비활성화, 1: 활성화)
 * @details TCD0_TRIG 인터럽트와 캡처 동작 설정.
 */
void tcd_capture_it_init(TCD_Instance *instance, uint8_t filter);

/**
 * @brief 폴트 상태 확인
 * @param instance TCD 인스턴스 포인터
 * @return 폴트 플래그 (TCDn.STATUS의 FAULTA/FAULTB 비트)
 * @details 폴트 발생 여부를 확인.
 */
uint8_t tcd_check_faults(TCD_Instance *instance);

#endif // TCD_DRIVER_H

tcd_driver.c

/**
 * @file tcd_driver.c
 * @brief AVR128DA64/48/32/28 TCD 드라이버 구현
 * @details TCD 레지스터를 사용하여 PWM, 타이머, 카운터, 캡처, 이벤트 시스템,
 *          폴트 처리, 데드타임, 직관적 초기화를 지원. 표준 ISR로 인터럽트 처리.
 * @author 작성자
 * @date 2025-09-05
 */

#include "tcd_driver.h"
#include <avr/interrupt.h> // 인터럽트 핸들러 정의를 위한 헤더

/**
 * @brief TCD 인스턴스 포인터 (ISR에서 사용)
 * @details 전역 포인터로, ISR에서 현재 TCD 인스턴스에 접근.
 */
static TCD_Instance *tcd_instance = NULL;

/**
 * @brief TCD 간소화 초기화
 * @param instance TCD 인스턴스 포인터
 * @param tcd TCD 레지스터 포인터 (예: &TCD0)
 * @param clk_freq TCD 클럭 주파수 (Hz, 예: 24000000 for 24MHz)
 * @details 기본 설정(OSCHF 24MHz, 프리스케일러 1, One Ramp, 기본 핀,
 *          캡처/이벤트/폴트 비활성화, CMPBCLR=0, CMPASET=0, 데드타임=0,
 *          카운터=0)으로 TCD 초기화.
 */
void tcd_init_simple(TCD_Instance *instance, TCD_t *tcd, uint32_t clk_freq) {
    // 기본값 정의
    uint8_t clksel = TCD_CLKSEL_OSCHF; // 클럭 소스: OSCHF (24MHz)
    uint8_t cntpre = TCD_CNTPRE_DIV1;  // 프리스케일러: 1
    uint8_t wgmode = TCD_WGMODE_ONERAMP; // 파형 모드: One Ramp
    uint8_t portmux = TCD_PORTMUX_DEFAULT; // PORTMUX: 기본 핀
    uint16_t cmpbclr = 0; // PWM 주기 초기값
    uint16_t cmpaset = 0; // PWM 듀티 초기값
    uint8_t filter = 0;   // 입력 필터 비활성화
    uint8_t dtl = 0;      // Low-side 데드타임 0
    uint8_t dth = 0;      // High-side 데드타임 0

    // PORTMUX 설정 및 GPIO 출력 방향 설정
    PORTMUX.TCDROUTEA = (PORTMUX.TCDROUTEA & ~(0x1 << 0)) | (portmux << 0); // TCD 출력 핀 선택
    if (portmux == TCD_PORTMUX_DEFAULT) {
        // 기본 핀: PA4(WOA), PA5(WOB), PA6(WOC), PA7(WOD)
        PORTA.DIRSET = (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7);
    } else if (portmux == TCD_PORTMUX_ALT1) {
        // 대체 핀: PB0(WOA), PB1(WOB), PB2(WOC), PB3(WOD)
        PORTB.DIRSET = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3);
    }

    // 클럭 소스 및 프리스케일러 설정
    tcd->CTRLA = (clksel << 4) | (cntpre << 0); // TCDn.CTRLA: CLKSEL, CNTPRE 설정
    tcd->CTRLB = (wgmode << 0); // TCDn.CTRLB: WGMODE 설정

    // 입력 필터 설정
    tcd->EVCTRLA = filter ? TCD_FILTER_bm : 0; // Input A 필터 설정
    tcd->EVCTRLB = filter ? TCD_FILTER_bm : 0; // Input B 필터 설정

    // 비교 레지스터 설정 (PWM 주기 및 듀티)
    tcd->CMPASET = cmpaset; // 듀티 설정
    tcd->CMPBCLR = cmpbclr; // 주기 설정
    tcd->CMPACLR = cmpbclr; // 출력 비활성화 시점
    tcd->CMPBSET = cmpaset; // 출력 활성화 시점

    // 데드타임 설정
    _PROTECTED_WRITE(tcd->DT, ((uint16_t)dth << 8) | dtl); // TCDn.DT: DTH, DTL 설정

    // 카운터 초기화
    tcd->CNT = 0; // TCDn.CNT를 0으로 리셋

    // 인스턴스 멤버 초기화
    instance->tcd = tcd; // TCD 레지스터 포인터 저장
    instance->capture_data = 0; // 캡처 데이터 초기화
    instance->clk_freq = clk_freq; // 클럭 주파수 저장
    instance->prescaler = 1; // 프리스케일러 기본값 (DIV1)
    tcd_instance = instance; // 전역 인스턴스 포인터 설정 (ISR용)
}

/**
 * @brief TCD 상세 초기화
 * @param instance TCD 인스턴스 포인터
 * @param tcd TCD 레지스터 포인터 (예: &TCD0)
 * @param clksel 클럭 소스 (예: TCD_CLKSEL_OSCHF)
 * @param cntpre 프리스케일러 (예: TCD_CNTPRE_DIV1)
 * @param wgmode 파형 생성 모드 (예: TCD_WGMODE_ONERAMP)
 * @param clk_freq TCD 클럭 주파수 (Hz, 예: 24000000 for 24MHz)
 * @param cmpbclr CMPBCLR 값 (PWM 주기, 0x000~0xFFF)
 * @param cmpaset CMPASET 값 (PWM 듀티, 0x000~0xFFF)
 * @param portmux PORTMUX 설정 (TCD_PORTMUX_DEFAULT, TCD_PORTMUX_ALT1)
 * @param ev_generator 이벤트 생성자 (예: 0xB0=CMPBCLR)
 * @param ev_channel 이벤트 채널 (0x00~0x09)
 * @param ev_user 이벤트 사용자 (예: 0x29=INPUTA)
 * @param ev_user_channel 사용자 채널 (0x00~0x09)
 * @param filter 입력 필터 활성화 (0: 비활성화, 1: 활성화)
 * @param dtl Low-side 데드타임 (클럭 사이클 단위, 0x00~0xFF)
 * @param dth High-side 데드타임 (클럭 사이클 단위, 0x00~0xFF)
 * @details 사용자 지정 설정으로 TCD 초기화, 이벤트 시스템 포함.
 */
void tcd_init(TCD_Instance *instance, TCD_t *tcd, uint8_t clksel, uint8_t cntpre, uint8_t wgmode, uint32_t clk_freq, uint16_t cmpbclr, uint16_t cmpaset, uint8_t portmux, uint8_t ev_generator, uint8_t ev_channel, uint8_t ev_user, uint8_t ev_user_channel, uint8_t filter, uint8_t dtl, uint8_t dth) {
    // PORTMUX 설정 및 GPIO 출력 방향 설정
    PORTMUX.TCDROUTEA = (PORTMUX.TCDROUTEA & ~(0x1 << 0)) | (portmux << 0); // TCD 출력 핀 선택
    if (portmux == TCD_PORTMUX_DEFAULT) {
        // 기본 핀: PA4(WOA), PA5(WOB), PA6(WOC), PA7(WOD)
        PORTA.DIRSET = (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7);
    } else if (portmux == TCD_PORTMUX_ALT1) {
        // 대체 핀: PB0(WOA), PB1(WOB), PB2(WOC), PB3(WOD)
        PORTB.DIRSET = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3);
    }

    // 이벤트 시스템 설정 (EVSYS.CHANNELn 및 EVSYS.USERn)
    if (ev_channel <= 9) {
        // 이벤트 채널(0~9)에 생성자 연결
        volatile register8_t *channel_reg = (volatile register8_t *)(&EVSYS.CHANNEL0 + ev_channel);
        *channel_reg = ev_generator;
    }
    if (ev_user_channel <= 9) {
        // 이벤트 사용자(INPUTA/B)에 채널 연결
        volatile register8_t *user_reg = (volatile register8_t *)(&EVSYS.USERCCLLUT0A + ev_user);
        *user_reg = ev_user_channel;
    }

    // 클럭 소스 및 프리스케일러 설정
    tcd->CTRLA = (clksel << 4) | (cntpre << 0); // TCDn.CTRLA: CLKSEL, CNTPRE
    tcd->CTRLB = (wgmode << 0); // TCDn.CTRLB: WGMODE

    // 입력 필터 설정
    tcd->EVCTRLA = filter ? TCD_FILTER_bm : 0; // Input A 필터
    tcd->EVCTRLB = filter ? TCD_FILTER_bm : 0; // Input B 필터

    // 비교 레지스터 설정
    tcd->CMPASET = cmpaset; // 듀티 설정
    tcd->CMPBCLR = cmpbclr; // 주기 설정
    tcd->CMPACLR = cmpbclr; // 출력 비활성화 시점
    tcd->CMPBSET = cmpaset; // 출력 활성화 시점

    // 데드타임 설정
    _PROTECTED_WRITE(tcd->DT, ((uint16_t)dth << 8) | dtl); // TCDn.DT: DTH, DTL

    // 카운터 초기화
    tcd->CNT = 0; // TCDn.CNT를 0으로 리셋

    // 인스턴스 멤버 초기화
    instance->tcd = tcd; // TCD 레지스터 포인터
    instance->capture_data = 0; // 캡처 데이터 초기화
    instance->clk_freq = clk_freq; // 클럭 주파수 저장
    // 프리스케일러 값 계산 (1, 2, 4, 8)
    instance->prescaler = (cntpre == TCD_CNTPRE_DIV1) ? 1 :
                          (cntpre == TCD_CNTPRE_DIV2) ? 2 :
                          (cntpre == TCD_CNTPRE_DIV4) ? 4 : 8;
    tcd_instance = instance; // 전역 인스턴스 포인터 설정
}

/**
 * @brief TCD 활성화
 * @param instance TCD 인스턴스 포인터
 * @details TCDn.STATUS.ENRDY 확인 후 TCDn.CTRLA.ENABLE=1로 설정.
 */
void tcd_enable(TCD_Instance *instance) {
    while (!(instance->tcd->STATUS & TCD_ENRDY_bm)); // 활성화 준비 완료 대기
    instance->tcd->CTRLA |= TCD_ENABLE_bm; // TCD 활성화
}

/**
 * @brief TCD 비활성화
 * @param instance TCD 인스턴스 포인터
 * @details TCDn.CTRLA.ENABLE=0으로 설정하여 TCD 동작 중지.
 */
void tcd_disable(TCD_Instance *instance) {
    instance->tcd->CTRLA &= ~TCD_ENABLE_bm; // TCD 비활성화
}

/**
 * @brief PORTMUX 설정
 * @param instance TCD 인스턴스 포인터
 * @param portmux PORTMUX 설정 (TCD_PORTMUX_DEFAULT, TCD_PORTMUX_ALT1)
 * @details TCD 출력 핀을 기본 또는 대체 핀으로 설정하고 GPIO 방향 설정.
 */
void tcd_configure_portmux(TCD_Instance *instance, uint8_t portmux) {
    PORTMUX.TCDROUTEA = (PORTMUX.TCDROUTEA & ~(0x1 << 0)) | (portmux << 0); // TCD 출력 핀 선택
    if (portmux == TCD_PORTMUX_DEFAULT) {
        // 기본 핀: PA4(WOA), PA5(WOB), PA6(WOC), PA7(WOD)
        PORTA.DIRSET = (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7);
    } else if (portmux == TCD_PORTMUX_ALT1) {
        // 대체 핀: PB0(WOA), PB1(WOB), PB2(WOC), PB3(WOD)
        PORTB.DIRSET = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3);
    }
}

/**
 * @brief 현재 카운터 값 읽기
 * @param instance TCD 인스턴스 포인터
 * @return 현재 TCDn.CNT 값 (12비트, 0x000~0xFFF)
 * @details TCDn.CNT에서 현재 카운터 값을 읽음. 동작 중에도 가능.
 */
uint16_t tcd_get_count(TCD_Instance *instance) {
    return instance->tcd->CNT; // 12비트 카운터 값 반환
}

/**
 * @brief 카운터 값 설정
 * @param instance TCD 인스턴스 포인터
 * @param count 설정할 카운터 값 (12비트, 0x000~0xFFF)
 * @details TCD 비활성화 상태에서 TCDn.CNT에 값을 설정. 동작 중 설정은 위험.
 */
void tcd_set_count(TCD_Instance *instance, uint16_t count) {
    if (!(instance->tcd->CTRLA & TCD_ENABLE_bm)) { // TCD 비활성화 확인
        instance->tcd->CNT = (count & 0xFFF); // 12비트 제한 적용
    }
}

/**
 * @brief 카운터 리셋
 * @param instance TCD 인스턴스 포인터
 * @details TCD 비활성화 상태에서 TCDn.CNT를 0으로 리셋.
 */
void tcd_reset_count(TCD_Instance *instance) {
    if (!(instance->tcd->CTRLA & TCD_ENABLE_bm)) { // TCD 비활성화 확인
        instance->tcd->CNT = 0; // 카운터를 0으로 설정
    }
}

/**
 * @brief PWM 주파수 및 듀티 설정
 * @param instance TCD 인스턴스 포인터
 * @param freq_hz PWM 주파수 (Hz)
 * @param duty_percent 듀티 사이클 (%)
 * @details 주파수와 듀티를 기반으로 CMPBCLR, CMPASET 계산 및 설정.
 *          공식: CMPBCLR = (fCLK / (Prescaler * fPWM)) - 1, CMPASET = CMPBCLR * (Duty / 100)
 */
void tcd_set_pwm_freq_duty(TCD_Instance *instance, uint32_t freq_hz, float duty_percent) {
    // CMPBCLR 계산: PWM 주기
    uint32_t cmpbclr = (instance->clk_freq / (instance->prescaler * freq_hz)) - 1;
    if (cmpbclr > 0xFFF) cmpbclr = 0xFFF; // 12비트 제한
    // CMPASET 계산: PWM 듀티
    uint16_t cmpaset = (uint16_t)(cmpbclr * (duty_percent / 100.0));
    if (cmpaset > cmpbclr) cmpaset = cmpbclr; // 듀티가 주기를 초과하지 않도록
    while (!(instance->tcd->STATUS & TCD_CMDRDY_bm)); // 비교 레지스터 준비 대기
    instance->tcd->CMPASET = cmpaset; // 듀티 설정
    instance->tcd->CMPBCLR = cmpbclr; // 주기 설정
    instance->tcd->CMPACLR = cmpbclr; // 출력 비활성화 시점
    instance->tcd->CMPBSET = cmpaset; // 출력 활성화 시점
    instance->tcd->CTRLE = TCD_SYNCEOC_bm; // 주기 끝 동기화
}

/**
 * @brief 데드타임 설정 (마이크로초 단위)
 * @param instance TCD 인스턴스 포인터
 * @param dtl_us Low-side 데드타임 (us)
 * @param dth_us High-side 데드타임 (us)
 * @details 마이크로초를 클럭 사이클로 변환하여 TCDn.DT 설정.
 *          공식: DTL/DTH = fCLK * DeadTime_us / 1,000,000
 */
void tcd_set_deadtime_us(TCD_Instance *instance, float dtl_us, float dth_us) {
    // 데드타임 계산 (클럭 사이클 단위)
    uint8_t dtl = (uint8_t)(instance->clk_freq * dtl_us / 1000000.0);
    uint8_t dth = (uint8_t)(instance->clk_freq * dth_us / 1000000.0);
    if (dtl > 0xFF) dtl = 0xFF; // 8비트 제한
    if (dth > 0xFF) dth = 0xFF; // 8비트 제한
    _PROTECTED_WRITE(instance->tcd->DT, ((uint16_t)dth << 8) | dtl); // TCDn.DT 설정
}

/**
 * @brief 타이머 주기 설정 (마이크로초 단위)
 * @param instance TCD 인스턴스 포인터
 * @param period_us 타이머 주기 (us)
 * @details 마이크로초 단위 주기를 CMPBCLR로 변환, TCD0_OVF 인터럽트 활성화.
 *          공식: CMPBCLR = (fCLK * period_us / 1,000,000) / Prescaler - 1
 */
void tcd_set_timer_us(TCD_Instance *instance, uint32_t period_us) {
    // CMPBCLR 계산: 타이머 주기
    uint32_t cmpbclr = (instance->clk_freq * period_us / 1000000UL) / instance->prescaler - 1;
    if (cmpbclr > 0xFFF) cmpbclr = 0xFFF; // 12비트 제한
    while (!(instance->tcd->STATUS & TCD_CMDRDY_bm)); // 비교 레지스터 준비 대기
    instance->tcd->CMPBCLR = cmpbclr; // 주기 설정
    instance->tcd->CMPACLR = cmpbclr; // 출력 비활성화 시점
    instance->tcd->CTRLE = TCD_SYNCEOC_bm; // 주기 끝 동기화
    instance->tcd->INTCTRL |= TCD_OVF_bm; // 오버플로우 인터럽트 활성화
}

/**
 * @brief PWM 설정 (레지스터 값 직접 설정)
 * @param instance TCD 인스턴스 포인터
 * @param cmpaset CMPASET 값 (듀티, 0x000~0xFFF)
 * @param cmpbclr CMPBCLR 값 (주기, 0x000~0xFFF)
 * @details CMPASET, CMPBCLR을 직접 설정하여 PWM 생성.
 */
void tcd_set_pwm(TCD_Instance *instance, uint16_t cmpaset, uint16_t cmpbclr) {
    while (!(instance->tcd->STATUS & TCD_CMDRDY_bm)); // 비교 레지스터 준비 대기
    instance->tcd->CMPASET = cmpaset; // 듀티 설정
    instance->tcd->CMPBCLR = cmpbclr; // 주기 설정
    instance->tcd->CMPACLR = cmpbclr; // 출력 비활성화 시점
    instance->tcd->CMPBSET = cmpaset; // 출력 활성화 시점
    instance->tcd->CTRLE = TCD_SYNCEOC_bm; // 주기 끝 동기화
}

/**
 * @brief 데드타임 설정 (클럭 사이클 단위)
 * @param instance TCD 인스턴스 포인터
 * @param dtl Low-side 데드타임 (클럭 사이클 단위, 0x00~0xFF)
 * @param dth High-side 데드타임 (클럭 사이클 단위, 0x00~0xFF)
 * @details TCDn.DT 레지스터에 데드타임 설정, _PROTECTED_WRITE 사용.
 */
void tcd_configure_deadtime(TCD_Instance *instance, uint8_t dtl, uint8_t dth) {
    _PROTECTED_WRITE(instance->tcd->DT, ((uint16_t)dth << 8) | dtl); // TCDn.DT 설정
}

/**
 * @brief 단일 캡처
 * @param instance TCD 인스턴스 포인터
 * @param channel 캡처 채널 (0: Input A, 1: Input B)
 * @return 캡처된 데이터 (TCDn.CAPTUREA/B, 12비트)
 * @details 입력 이벤트 발생 시 TCDn.CAPTUREA/B에서 값을 읽고 플래그 클리어.
 */
uint16_t tcd_capture(TCD_Instance *instance, uint8_t channel) {
    // 입력 이벤트 플래그 대기
    while (!(instance->tcd->INTFLAGS & (channel ? TCD_TRIGB_bm : TCD_TRIGA_bm)));
    // 캡처 데이터 읽기
    uint16_t data = channel ? instance->tcd->CAPTUREB : instance->tcd->CAPTUREA;
    // 인터럽트 플래그 클리어
    instance->tcd->INTFLAGS |= (channel ? TCD_TRIGB_bm : TCD_TRIGA_bm);
    return data;
}

/**
 * @brief 이벤트 설정
 * @param instance TCD 인스턴스 포인터
 * @param ev_generator 이벤트 생성자 (예: 0xB0=CMPBCLR)
 * @param ev_channel 이벤트 채널 (0x00~0x09)
 * @param ev_user 이벤트 사용자 (예: 0x29=INPUTA)
 * @param ev_user_channel 사용자 채널 (0x00~0x09)
 * @details EVSYS.CHANNELn에 생성자, EVSYS.USERn에 사용자 설정.
 */
void tcd_configure_event(TCD_Instance *instance, uint8_t ev_generator, uint8_t ev_channel, uint8_t ev_user, uint8_t ev_user_channel) {
    if (ev_channel <= 9) {
        // 이벤트 채널(0~9)에 생성자 연결
        volatile register8_t *channel_reg = (volatile register8_t *)(&EVSYS.CHANNEL0 + ev_channel);
        *channel_reg = ev_generator;
    }
    if (ev_user_channel <= 9) {
        // 이벤트 사용자(INPUTA/B)에 채널 연결
        volatile register8_t *user_reg = (volatile register8_t *)(&EVSYS.USERCCLLUT0A + ev_user);
        *user_reg = ev_user_channel;
    }
}

/**
 * @brief 폴트 설정
 * @param instance TCD 인스턴스 포인터
 * @param fault_action 폴트 동작 (예: TCD_FAULT_ACTION_HALT)
 * @param blanking 입력 블랭킹 활성화 (0: 비활성화, 1: 활성화)
 * @param overload 오버로드 보호 활성화 (0: 비활성화, 1: 활성화)
 * @details TCDn.FAULTCTRL 설정, _PROTECTED_WRITE 사용.
 */
void tcd_configure_fault(TCD_Instance *instance, uint8_t fault_action, uint8_t blanking, uint8_t overload) {
    _PROTECTED_WRITE(instance->tcd->FAULTCTRL, 
        (fault_action << TCD_ACTIONA_bp) | // Input A 폴트 동작
        (fault_action << TCD_ACTIONB_bp) | // Input B 폴트 동작
        (blanking ? TCD_BLANK_bm : 0) |    // 입력 블랭킹
        (overload ? TCD_OVERLOAD_bm : 0)); // 오버로드 보호
}

/**
 * @brief 인터럽트 기반 캡처 초기화
 * @param instance TCD 인스턴스 포인터
 * @param filter 입력 필터 활성화 (0: 비활성화, 1: 활성화)
 * @details TCD0_TRIG 인터럽트와 상승 에지 캡처 설정.
 */
void tcd_capture_it_init(TCD_Instance *instance, uint8_t filter) {
    instance->tcd->INTCTRL |= (TCD_TRIGA_bm | TCD_TRIGB_bm); // TRIGA, TRIGB 인터럽트 활성화
    instance->tcd->EVCTRLA = TCD_ACTION_CAPTURE_bm | TCD_EDGE_RISING_bm | (filter ? TCD_FILTER_bm : 0); // Input A: 상승 에지 캡처
    instance->tcd->EVCTRLB = TCD_ACTION_CAPTURE_bm | TCD_EDGE_RISING_bm | (filter ? TCD_FILTER_bm : 0); // Input B: 상승 에지 캡처
}

/**
 * @brief 폴트 상태 확인
 * @param instance TCD 인스턴스 포인터
 * @return 폴트 플래그 (TCDn.STATUS의 FAULTA/FAULTB 비트)
 * @details 폴트 발생 여부를 확인하여 반환.
 */
uint8_t tcd_check_faults(TCD_Instance *instance) {
    return instance->tcd->STATUS & (TCD_FAULTA_bm | TCD_FAULTB_bm); // FAULTA, FAULTB 상태 반환
}

/**
 * @brief TCD 오버플로우 인터럽트 핸들러
 * @details TCD0_OVF 인터럽트 발생 시 호출. 오버플로우 플래그 클리어.
 *          사용자 정의 동작(예: LED 토글) 추가 가능.
 */
ISR(TCD0_OVF_vect) {
    if (tcd_instance) {
        tcd_instance->tcd->INTFLAGS |= TCD_OVF_bm; // 오버플로우 플래그 클리어
        // 사용자 정의 동작 (예: PORTA.OUTTGL = PIN0_bm; // PA0 토글)
    }
}

/**
 * @brief TCD 트리거 인터럽트 핸들러 (Input A)
 * @details TCD0_TRIGA 인터럽트 발생 시 호출. CAPTUREA 값을 저장하고 플래그 클리어.
 */
ISR(TCD0_TRIGA_vect) {
    if (tcd_instance) {
        tcd_instance->capture_data = tcd_instance->tcd->CAPTUREA; // 캡처 데이터 저장
        tcd_instance->tcd->INTFLAGS |= TCD_TRIGA_bm; // TRIGA 플래그 클리어
    }
}

/**
 * @brief TCD 트리거 인터럽트 핸들러 (Input B)
 * @details TCD0_TRIGB 인터럽트 발생 시 호출. CAPTUREB 값을 저장하고 플래그 클리어.
 */
ISR(TCD0_TRIGB_vect) {
    if (tcd_instance) {
        tcd_instance->capture_data = tcd_instance->tcd->CAPTUREB; // 캡처 데이터 저장
        tcd_instance->tcd->INTFLAGS |= TCD_TRIGB_bm; // TRIGB 플래그 클리어
    }
}

main.c

/**
 * @file main.c
 * @brief AVR128DA64/48/32/28 TCD 드라이버 테스트 프로그램
 * @details 24MHz 클럭에서 TCD0을 사용해 75kHz PWM, 100us 타이머, 카운터 테스트.
 *          이벤트, 폴트, 캡처, 소프트웨어 이벤트 포함.
 * @author 작성자
 * @date 2025-09-05
 */

#ifndef F_CPU
#define F_CPU 24000000UL // CPU 클럭 주파수 정의 (24MHz)
#endif

#include <avr/io.h>        // AVR 레지스터 정의
#include <util/delay.h>    // 지연 함수
#include <avr/interrupt.h> // 인터럽트 처리
#include "tcd_driver.h"    // TCD 드라이버 헤더

/**
 * @brief 24MHz OSCHF 클럭 초기화
 * @details CLKCTRL 설정으로 24MHz 고주파 오실레이터 활성화 및 Auto-tune 설정.
 */
void clock_init_24mhz(void) {
    // XOSCHF 활성화, 기본 클럭 소스 선택
    _PROTECTED_WRITE(CLKCTRL.XOSCHFCTRLA, CLKCTRL_ENABLE_bm | (0 << CLKCTRL_SEL_bp));
    // OSCHF 24MHz, Auto-tune 활성화
    _PROTECTED_WRITE(CLKCTRL.OSCHCTRLA, (0x9 << CLKCTRL_FRQSEL_gp) | CLKCTRL_AUTOTUNE_bm);
    // 클럭 분주 비활성화
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00);
    // 메인 클럭 소스 OSCHF 선택
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, 0x00);
    // OSCHF 클럭 안정화 대기
    while ((CLKCTRL.MCLKSTATUS & CLKCTRL_OSCHF_bm) == 0);
}

/**
 * @brief 메인 함수
 * @details TCD0 초기화, PWM, 타이머, 카운터, 이벤트, 폴트 설정 및 테스트.
 * @return 0 (무한 루프이므로 반환 없음)
 */
int main(void) {
    // 24MHz 클럭 초기화
    clock_init_24mhz();
    // 전역 인터럽트 활성화
    sei();

    // TCD0 인스턴스 선언
    TCD_Instance tcd0_instance;
    // TCD 간소화 초기화: 24MHz OSCHF, 기본 설정
    tcd_init_simple(&tcd0_instance, &TCD0, 24000000UL);

    // PWM 설정: 75kHz, 50% 듀티
    tcd_set_pwm_freq_duty(&tcd0_instance, 75000, 50.0);
    // 데드타임 설정: Low/High-side 1us
    tcd_set_deadtime_us(&tcd0_instance, 1.0, 1.0);

    // 타이머 설정: 100us 주기
    tcd_set_timer_us(&tcd0_instance, 100);

    // 카운터 초기화: TCDn.CNT=0
    tcd_reset_count(&tcd0_instance);

    // 대체 핀 설정: PB0~PB3
    tcd_configure_portmux(&tcd0_instance, TCD_PORTMUX_ALT1);

    // 이벤트 설정: CMPBCLR을 채널 0, INPUTA를 채널 1에 연결
    tcd_configure_event(&tcd0_instance, 0xB0, 0x00, 0x29, 0x01);

    // 폴트 설정: 정지 동작, 블랭킹 및 오버로드 활성화
    tcd_configure_fault(&tcd0_instance, TCD_FAULT_ACTION_HALT, 1, 1);

    // TCD 활성화
    tcd_enable(&tcd0_instance);

    // 인터럽트 기반 캡처 활성화: 입력 필터 사용
    tcd_capture_it_init(&tcd0_instance, 1);

    // 메인 루프
    while (1) {
        // 현재 카운터 값 읽기
        uint16_t count = tcd_get_count(&tcd0_instance);
        // count 사용 (예: 시리얼 모니터 출력)

        // 캡처 데이터 확인
        if (tcd0_instance.capture_data) {
            // 캡처 데이터 처리 (예: 시리얼 출력)
            tcd0_instance.capture_data = 0; // 캡처 데이터 클리어
        }

        // 폴트 상태 확인
        if (tcd_check_faults(&tcd0_instance)) {
            // 폴트 처리 (예: TCD 비활성화 또는 에러 표시)
        }

        // 소프트웨어 이벤트 생성: 채널 0
        EVSYS.SWEVENTA = (1 << 0);

        // 100ms 대기
        _delay_ms(100);
    }

    return 0; // 도달하지 않음
}

 

추가 팁

  • 카운터 사용 시나리오:
    • tcd_get_count: 타이머 진행 상태 확인(예: 24MHz, 프리스케일러=1, CNT=1200 → 50us 경과).
    • tcd_set_count: 특정 시점에서 카운터 조정(예: 위상 조정, 이벤트 동기화).
    • tcd_reset_count: 주기 시작점 재설정.
  • 제한:
    • TCDn.CNT 쓰기는 TCD 비활성화 상태에서만 수행, 동작 중 쓰기는 예기치 않은 동작 가능.
    • 최대 카운터 값: 12비트(0xFFF=4095), 24MHz, 프리스케일러=1 → 최대 170.7us.
  • 시간 변환:
    • 공식: Time_us = (Prescaler * TCDn.CNT) * 1,000,000 / fCLK_TCD_CNT.
    • 예: TCDn.CNT=1200, 24MHz, 프리스케일러=1 → Time_us = (1 * 1200) * 1,000,000 / 24,000,000 = 50us.
  • 디버깅: tcd_get_count로 읽은 값을 시리얼 모니터로 출력하거나, 오실로스코프로 PWM/타이머 동작과 비교.
  • 호환성: 카운터 함수는 모든 파형 모드와 AVR128DA 모델 호환, Dual Slope 모드에서는 CMPBCLR * 2 주기 고려.

결론

수정된 TCD 드라이버는 카운터 기능(tcd_get_count, tcd_set_count, tcd_reset_count)을 추가하여 TCDn.CNT 값을 직접 조작할 수 있도록 했습니다. 이는 타이머 진행 상태 확인, 특정 카운터 값 설정, 주기 리셋 등에 유용합니다. 기존 PWM(tcd_set_pwm_freq_duty), 타이머(tcd_set_timer_us), 데드타임(tcd_set_deadtime_us), 캡처, 이벤트, 폴트, 초기화(tcd_init_simple) 기능은 유지됩니다. main.c 예제를 통해 75kHz PWM, 50% 듀티, 1us 데드타임, 100us 타이머, 카운터 읽기/리셋을 테스트할 수 있습니다. Microchip Studio에서 빌드 및 디버깅하여 동작을 확인하세요.

반응형