개요
본 문서는 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 사양은 다음과 같습니다:
- 모듈 수: 단일 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.
- 비트 [7:0]: SWEVENTx[7:0] (소프트웨어 이벤트 생성, 채널 0~
- EVSYS.CHANNELn (0x10 + n):
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 핀이 제한적이며, 대체 핀은 주로 PA4
PA7, 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.
- 설정 순서:
- tcd_init_simple으로 기본 초기화.
- tcd_configure_portmux로 핀 매핑 (필요 시).
- tcd_set_pwm_freq_duty 또는 tcd_set_timer_us로 PWM/타이머 설정.
- tcd_set_deadtime_us로 데드타임 설정 (PWM 필요 시).
- tcd_configure_event로 이벤트 설정 (필요 시).
- tcd_configure_fault로 폴트 설정 (필요 시).
- tcd_set_count 또는 tcd_reset_count로 카운터 초기화 (필요 시).
- 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 처리.
사용 방법
- 헤더 파일 포함: tcd_driver.h, <avr/io.h>, <util/delay.h> 추가.
- 클럭 설정: clock_init_24mhz로 24MHz OSCHF 클럭 설정.
- TCD 간소화 초기화:
TCD_Instance tcd0_instance; tcd_init_simple(&tcd0_instance, &TCD0, 24000000UL); // 24MHz 클럭
- PWM 설정 (직관적):
tcd_set_pwm_freq_duty(&tcd0_instance, 75000, 50.0); // 75kHz, 50% 듀티 tcd_set_deadtime_us(&tcd0_instance, 1.0, 1.0); // 1us 데드타임
- 타이머 설정 (직관적):
tcd_set_timer_us(&tcd0_instance, 100); // 100us 타이머
- 카운터 설정:
tcd_reset_count(&tcd0_instance); // 카운터 리셋 uint16_t count = tcd_get_count(&tcd0_instance); // 현재 카운터 값 읽기 tcd_set_count(&tcd0_instance, 1000); // 카운터를 1000으로 설정
- 포트 설정 (필요 시):
tcd_configure_portmux(&tcd0_instance, TCD_PORTMUX_ALT1); // 대체 핀 (PB0~PB3)
- 이벤트 설정 (필요 시):
tcd_configure_event(&tcd0_instance, 0xB0, 0x00, 0x29, 0x01); // CMPBCLR, 채널 0, INPUTA, 채널 1
- 폴트 설정 (필요 시):
tcd_configure_fault(&tcd0_instance, TCD_FAULT_ACTION_HALT, 1, 1); // 정지, 블랭킹 및 오버로드 활성화
- TCD 활성화:
tcd_enable(&tcd0_instance);
- 캡처 데이터 처리: tcd_capture로 단일 캡처 데이터 처리.
- 인터럽트 설정: 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에서 빌드 및 디버깅하여 동작을 확인하세요.
'MCU > AVR' 카테고리의 다른 글
AVR32DA/64DA/128DA NVMCTRL 드라이버 설계 및 구현 (0) | 2025.09.05 |
---|---|
AVR128DA64/48/32/28 Sleep Controller 드라이버 설계 및 구현 (0) | 2025.09.05 |
AVR128DA64/48/32/28 클럭 드라이버 설계 및 구현 (0) | 2025.09.05 |
AVR128DA64/48/32/28 RTC 드라이버 설계 및 구현 (0) | 2025.09.05 |
AVR128DA64/48/32/28 TCB 드라이버 설계 및 구현 (0) | 2025.09.04 |
AVR128DA64/48/32/28 AC 드라이버 설계 및 구현 (0) | 2025.09.04 |
AVR128DA64/48/32/28 DAC 드라이버 설계 및 구현 (0) | 2025.09.04 |
AVR128DA64/48/32/28 ADC 드라이버 설계 및 구현 (0) | 2025.09.04 |