본문 바로가기
MCU/AVR

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

by linuxgo 2025. 9. 4.
반응형

개요

본 보고서는 Microchip의 AVR128DA64/48/32/28 시리즈 마이크로컨트롤러에 내장된 16비트 Timer/Counter Type B(TCB) 기능을 분석하고, 이를 활용할 수 있는 범용 드라이버를 설계 및 구현한 내용을 다룹니다. AVR128DA 시리즈는 최대 5개의 TCB 모듈을 제공하며, 주기적 인터럽트, 타임아웃 체크, 입력 캡처(이벤트, 주파수, 펄스폭, 주파수+펄스폭, 32비트 캡처), 단일 샷, 8비트 PWM, 노이즈 캔슬러, TCA 동기화 기능을 지원합니다. 본 드라이버는 이벤트 시스템(EVSYS)과 연동하여 효율적인 타이머 동작을 구현하며, 콜백 기반 인터럽트 처리, 상태 확인, 모드별 설정을 제공합니다. AVR128DA64/48/32/28 전 제품군에 호환되도록 설계되었으며, Microchip Studio 및 AVR-GCC 환경에서 손쉽게 활용 가능합니다.

AVR128DA TCB block diagram

사양

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

  • 모듈 수:
    • AVR128DA64: 최대 5개 (TCB0~4).
    • AVR128DA48/32: 최대 4개 (TCB0~3).
    • AVR128DA28: 최대 3개 (TCB0~2).
  • 핀 할당: PORTMUX로 WO(파형 출력) 핀 선택 가능 (예: TCB0 기본: PB0(WO)).
  • 전압 범위: 1.8V ~ 5.5V (VDD).
  • 기능:
    • 주기적 인터럽트: OVF 인터럽트.
    • 타임아웃 체크: CAPT 인터럽트.
    • 입력 캡처: 이벤트, 주파수, 펄스폭, 주파수+펄스폭, 32비트 캡처(TCBn+TCBn+1).
    • 단일 샷: 단일 펄스 출력.
    • 8비트 PWM: PWM 출력 (WO 핀).
    • 노이즈 캔슬러: 4주기 필터.
    • TCA 동기화: TCAn 클럭 동기화.
  • 클럭 의존성: Peripheral Clock (CLK_PER, 최대 24MHz), TCA0/1, 이벤트 입력.
  • 리셋 상태: TCB 비활성화, 핀은 GPIO 입력 모드.
  • 전력 최적화: ENABLE=0으로 전력 절감.
  • 인터럽트:
    • OVF (오버플로우).
    • CAPT (캡처/타임아웃).
    • 벡터: TCB0(0x18), TCB1(0x1A), TCB2(0x3C), TCB3(0x52), TCB4(0x78).
  • EVSYS 연동:
    • 이벤트 생성기: TCBn_CAPT (0xA0+2n), TCBn_OVF (0xA1+2n).
    • 이벤트 사용자: TCBn_CAPT (0x1F+2n), TCBn_COUNT (0x20+2n).
    • 소프트웨어 이벤트: SWEVENTA/B.

레지스터 설정 상세

TCB는 TCBn 레지스터를 통해 제어됩니다. 데이터시트의 Register Summary에 따라 주요 레지스터와 비트필드는 다음과 같습니다.

  • TCBn.CTRLA (0x00):
    • 비트 [7]: Reserved.
    • 비트 [6]: RUNSTDBY (Standby 동작, 1=활성화).
    • 비트 [5]: CASCADE (32비트 모드, 1=TCBn+TCBn+1 결합).
    • 비트 [4]: SYNCUPD (TCA 동기화, 1=TCAn).
    • 비트 [3:1]: CLKSEL[2:0] (클럭 선택: 0x0=CLK_PER, 0x1=CLK_PER/2, 0x2=TCA0, 0x3=TCA1, 0x7=이벤트).
    • 비트 [0]: ENABLE (TCB 활성화, 1=활성화).
    • 리셋: 0x00, R/W.
  • TCBn.CTRLB (0x01):
    • 비트 [5]: CCMPINIT (WO 초기 출력: 0=LOW, 1=HIGH).
    • 비트 [4]: CCMPEN (파형 출력: 1=WO 활성화).
    • 비트 [3]: ASYNC (비동기 출력: 1=비동기, 단일 샷).
    • 비트 [2:0]: CNTMODE[2:0] (모드: 0x0=주기적 인터럽트, 0x1=타임아웃, 0x2=입력 캡처, 0x3=주파수, 0x4=펄스폭, 0x5=주파수+펄스폭, 0x6=단일 샷, 0x7=8비트 PWM).
    • 리셋: 0x00, R/W.
  • TCBn.EVCTRL (0x04):
    • 비트 [6]: FILTER (노이즈 캔슬러: 1=4주기 필터).
    • 비트 [3]: EDGE (이벤트 에지: 0=상승, 1=하강).
    • 비트 [0]: CAPTEI (캡처 이벤트 입력: 1=활성화).
    • 리셋: 0x00, R/W.
  • TCBn.INTCTRL (0x05):
    • 비트 [1]: OVF (오버플로우 인터럽트 활성화).
    • 비트 [0]: CAPT (캡처 인터럽트 활성화).
    • 리셋: 0x00, R/W.
  • TCBn.INTFLAGS (0x06):
    • 비트 [1]: OVF (오버플로우 플래그, 1 작성으로 클리어).
    • 비트 [0]: CAPT (캡처 플래그, 1 작성으로 클리어).
    • 리셋: 0x00, R/W.
  • TCBn.CNT (0x08~0x09):
    • 비트 [15:0]: CNT[15:0] (16비트 카운터).
    • 리셋: 0x0000, R/W.
  • TCBn.CCMP (0x0A~0x0B):
    • 비트 [15:8]: CCMPH (듀티(PWM), TOP(기타)).
    • 비트 [7:0]: CCMPL (주기(PWM), 비교 값(타임아웃)).
    • 리셋: 0x0000, R/W.
  • EVSYS 레지스터:
    • CHANNELn (0x00+n): 이벤트 생성기 (예: TCB0_CAPT=0xA0).
    • USERx (0x10+x): 이벤트 사용자 (예: TCB0_CAPT=0x1F).
    • SWEVENTA/B (0x08/0x09): 소프트웨어 이벤트 트리거.

TCB 설정 절차

TCB 설정은 다음 순서로 진행합니다:

  1. 클럭 설정: CLKCTRL로 CLK_PER 활성화 (예: 24MHz OSCHF).
  2. 포트 설정: PORTMUX로 WO 핀 선택, GPIO 방향 설정 (WO: 출력).
  3. 모드 설정: CTRLB로 CNTMODE 설정 (주기적 인터럽트, 타임아웃, 캡처 등).
  4. 클럭 및 TOP 설정: CTRLA로 CLKSEL 설정, CCMP에 TOP/비교 값 설정.
  5. 이벤트 설정: EVCTRL로 노이즈 캔슬러, 이벤트 에지 설정.
  6. 인터럽트 설정: INTCTRL로 OVF, CAPT 인터럽트 설정, INTFLAGS 클리어.
  7. EVSYS 설정 (옵션): CHANNELn에 이벤트 생성기, USERx에 사용자 연결.
  8. 활성화: CTRLA.ENABLE=1, sei().
  9. 테스트: 콜백으로 인터럽트 확인, tcb_count/tcb_capture로 상태 확인, EVSYS로 이벤트 트리거.

PORTMUX를 통한 핀 테이블

PORTMUX 설정을 통해 WO 핀을 변경할 수 있습니다. 아래는 데이터시트(Table 22-1)에 기반한 주요 TCB 모듈의 핀 매핑 테이블입니다.

TCB 모듈 PORTMUX 설정 WO 핀 설명
TCB0 DEFAULT (0x0) PB0 기본 핀 설정
TCB0 ALT1 (0x1) PB2 대체 핀 1
TCB1 DEFAULT (0x0) PC0 기본 핀 설정
TCB1 ALT1 (0x1) PC2 대체 핀 1
TCB2 DEFAULT (0x0) PF0 기본 핀 설정
TCB2 ALT1 (0x1) PF2 대체 핀 1
TCB3 DEFAULT (0x0) PB4 기본 핀 설정 (AVR128DA48 이상)
TCB3 ALT1 (0x1) PB5 대체 핀 1
TCB4 DEFAULT (0x0) PD0 기본 핀 설정 (AVR128DA64 전용)
TCB4 ALT1 (0x1) PD2 대체 핀 1

설정 예시 (TCB0을 ALT1으로 설정):

PORTMUX.TCBROUTEA |= (1 << 0); // TCB0을 ALT1로 설정 (PB2:WO)
PORTB.DIRSET = (1 << 2); // PB2(WO) 출력
    

주의사항: PORTMUX 변경 시 SPI, TWI, USART 등 다른 주변 장치와의 핀 충돌 확인 필수.

TCB 설정 고려사항

  • 클럭 선택: CLKSEL로 CLK_PER, TCA0/1, 이벤트 선택. CLK_PER=24MHz에서 주기 계산: T = (CCMP + 1) / F_PER.
  • 인터럽트: OVF, CAPT 플래그 클리어 필수. 다중 TCB 사용 시 ISR_ALIAS 활용.
  • EVSYS: TCBn_CAPT/OVF를 CHANNELn에 연결, USERx로 TCBn_COUNT/CAPT 트리거.
  • 32비트 모드: TCBn과 TCBn+1 결합(CASCADE=1), 캡처 데이터는 uint32_t로 처리.
  • 노이즈 캔슬러: FILTER=1로 4주기 필터 활성화, 입력 캡처 시 신호 안정화.
  • 포트 설정: PORTMUX로 대체 핀 선택 (위 테이블 참조).
  • 전력 최적화: 사용하지 않는 TCB는 ENABLE=0.
  • 호환성: AVR128DA28은 TCB0~2만 지원. <avr/io.h>로 모델별 정의 제공.
  • CCP 보호: CLKCTRL는 _PROTECTED_WRITE 사용.
  • 콜백 처리: 인터럽트에서 사용자 콜백 호출로 유연성 확보.

드라이버 구현 내용

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

  • 구조체: TCB_Instance로 TCB 모듈, 캡처 값, 인터럽트 플래그, 콜백 관리.
  • 초기화: tcb_init으로 모드, 클럭, TOP, PORTMUX 설정.
  • 인터럽트: tcb_enable_interrupts로 OVF, CAPT 활성화, 콜백 지원.
  • 상태 확인: tcb_count, tcb_capture, tcb_check_interrupts로 실시간 상태 제공.
  • EVSYS: tcb_set_event_generator, tcb_set_event_user, tcb_trigger_software_event로 이벤트 연동.
  • 특수 모드: tcb_enable_noise_canceler, tcb_sync_with_tca, tcb_set_32bit_mode로 노이즈 캔슬러, TCA 동기화, 32비트 캡처 지원.
  • 호환성: <avr/io.h>로 모델별 TCB 정의 지원, ISR_ALIAS로 다중 TCB 지원.
  • 클럭 설정: main.c에서 24MHz OSCHF, Auto-tune 활성화.

사용 방법

TCB 드라이버를 사용하는 방법은 다음과 같습니다:

  1. 헤더 파일 포함: 프로젝트에 tcb_driver.h를 포함하고, <avr/io.h>, <stdio.h>, <util/delay.h>, <avr/interrupt.h>를 추가합니다.
  2. 클럭 설정: main.cclock_init_24mhz를 호출하여 24MHz OSCHF 클럭을 설정합니다.
  3. TCB 인스턴스 생성: TCB_Instance 구조체를 선언하고, tcb_init으로 초기화합니다. 예:
    TCB_Instance tcb0_instance;
    tcb_init(&tcb0_instance, &TCB0, TCB_MODE_INT, TCB_CLKSEL_DIV1, 24000, TCB_PORTMUX_DEFAULT);
                
  4. 인터럽트 설정: tcb_enable_interrupts로 OVF, CAPT 인터럽트 활성화, 콜백 설정.
  5. EVSYS 설정 (옵션): tcb_set_event_generator, tcb_set_event_user로 이벤트 연결.
  6. 활성화: tcb_enable으로 TCB 활성화, sei()로 전역 인터럽트 활성화.
  7. 상태 확인: tcb_count, tcb_capture, tcb_check_interrupts로 카운터, 캡처 값, 플래그 확인.
  8. 테스트:
    • 주기적 인터럽트: 콜백으로 OVF 확인.
    • 입력 캡처: EVSYS로 이벤트 트리거, 캡처 값 확인.
    • PWM: WO 핀 출력 확인.
    • 32비트 모드: 두 TCB 인스턴스로 캡처 값 조합.
  9. 다중 TCB 사용: 각 TCB(0~4)에 대해 별도의 TCB_Instance를 생성하고 초기화합니다. 인터럽트 핸들러는 ISR_ALIASOF로 자동 처리됩니다.

코드 구현

tcb_driver.h

/**
 * @file tcb_driver.h
 * @brief AVR128DA64/48/32/28 TCB 드라이버 헤더 파일
 * @details Microchip AVR128DA 시리즈의 Timer/Counter Type B(TCB)를 위한 드라이버.
 *          주기적 인터럽트, 타임아웃 체크, 입력 캡처(이벤트, 주파수, 펄스폭, 
 *          주파수+펄스폭, 32비트 캡처), 단일 샷, 8비트 PWM, 노이즈 캔슬러, 
 *          TCA 동기화, 이벤트 시스템(EVSYS) 연동을 지원.
 *          AVR128DA64/48/32/28 전 모델 호환.
 * @author 작성자
 * @date 2025-09-04
 */
#ifndef TCB_DRIVER_H
#define TCB_DRIVER_H

#include <avr/io.h>
#include <stdint.h>

/**
 * @brief TCB 모듈 수 정의
 * @details AVR128DA 모델별로 지원되는 TCB 모듈 수를 정의.
 *          AVR128DA28: 3개(TCB0~2), AVR128DA32/48: 4개(TCB0~3), 
 *          AVR128DA64: 5개(TCB0~4).
 */
#if defined(__AVR_AVR128DA28__)
#define TCB_COUNT 3
#elif defined(__AVR_AVR128DA32__) || defined(__AVR_AVR128DA48__)
#define TCB_COUNT 4
#else
#define TCB_COUNT 5
#endif

/**
 * @brief 유효성 검사 매크로
 * @details PORTMUX, 이벤트 타입, 채널 번호의 유효성을 확인.
 */
#define TCB_PORTMUX_VALID(x) ((x) == TCB_PORTMUX_DEFAULT || (x) == TCB_PORTMUX_ALT1)
#define TCB_EVENT_VALID(x) ((x) == TCB_EVENT_CAPT || (x) == TCB_EVENT_OVF)
#define TCB_CHANNEL_VALID(x) ((x) < 10)

/**
 * @brief TCB 클럭 선택 정의
 * @details TCB 클럭 소스 선택 옵션.
 *          - TCB_CLKSEL_DIV1: CLK_PER (기본 시스템 클럭, 예: 24MHz).
 *          - TCB_CLKSEL_DIV2: CLK_PER/2.
 *          - TCB_CLKSEL_TCA0: TCA0 클럭.
 *          - TCB_CLKSEL_TCA1: TCA1 클럭.
 *          - TCB_CLKSEL_EVENT: 이벤트 입력.
 */
#define TCB_CLKSEL_DIV1   0x0 // CLK_PER
#define TCB_CLKSEL_DIV2   0x1 // CLK_PER/2
#define TCB_CLKSEL_TCA0   0x2 // TCA0
#define TCB_CLKSEL_TCA1   0x3 // TCA1
#define TCB_CLKSEL_EVENT  0x7 // 이벤트 입력

/**
 * @brief TCB 모드 정의
 * @details TCB 동작 모드.
 *          - TCB_MODE_INT: 주기적 인터럽트 (OVF).
 *          - TCB_MODE_TIMEOUT: 타임아웃 체크 (CAPT).
 *          - TCB_MODE_CAPT: 입력 캡처 (이벤트).
 *          - TCB_MODE_FRQ: 주파수 측정.
 *          - TCB_MODE_PW: 펄스폭 측정.
 *          - TCB_MODE_FRQPW: 주파수+펄스폭 측정.
 *          - TCB_MODE_SINGLE: 단일 샷.
 *          - TCB_MODE_PWM8: 8비트 PWM.
 */
#define TCB_MODE_INT      0x0 // 주기적 인터럽트
#define TCB_MODE_TIMEOUT  0x1 // 타임아웃 체크
#define TCB_MODE_CAPT     0x2 // 입력 캡처
#define TCB_MODE_FRQ      0x3 // 주파수 측정
#define TCB_MODE_PW       0x4 // 펄스폭 측정
#define TCB_MODE_FRQPW    0x5 // 주파수+펄스폭
#define TCB_MODE_SINGLE   0x6 // 단일 샷
#define TCB_MODE_PWM8     0x7 // 8비트 PWM

/**
 * @brief TCB 인터럽트 플래그 정의
 * @details 인터럽트 플래그 비트.
 *          - TCB_INT_OVF: 오버플로우 인터럽트.
 *          - TCB_INT_CAPT: 캡처/타임아웃 인터럽트.
 */
#define TCB_INT_OVF   TCB_OVF_bm  // 오버플로우
#define TCB_INT_CAPT  TCB_CAPT_bm // 캡처/타임아웃

/**
 * @brief PORTMUX 설정 정의
 * @details WO(파형 출력) 핀 선택.
 *          - TCB_PORTMUX_DEFAULT: 기본 핀 (예: TCB0=PB0).
 *          - TCB_PORTMUX_ALT1: 대체 핀 (예: TCB0=PB2).
 */
#define TCB_PORTMUX_DEFAULT 0x0 // 기본 핀
#define TCB_PORTMUX_ALT1    0x1 // 대체 핀 1

/**
 * @brief 이벤트 타입 정의
 * @details EVSYS에서 사용되는 이벤트 타입.
 *          - TCB_EVENT_CAPT: 캡처 이벤트.
 *          - TCB_EVENT_OVF: 오버플로우 이벤트.
 */
#define TCB_EVENT_CAPT 0x0 // 캡처 이벤트
#define TCB_EVENT_OVF  0x1 // 오버플로우 이벤트
#define TCB_EVENT_COUNT 0x1 // 카운트 이벤트 (사용자)

/**
 * @brief TCB 인스턴스 구조체
 * @details TCB 모듈, 캡처 값, 인터럽트 플래그, 콜백 함수를 관리.
 * @param tcb TCB 레지스터 포인터 (예: &TCB0).
 * @param capture 최신 캡처 값 (16비트).
 * @param int_flags 인터럽트 플래그 (OVF, CAPT).
 * @param callback 인터럽트 발생 시 호출되는 사용자 콜백 함수.
 */
typedef struct {
    TCB_t *tcb;                     // TCB 레지스터 포인터
    volatile uint16_t capture;       // 캡처 값
    volatile uint8_t int_flags;     // 인터럽트 플래그
    void (*callback)(uint8_t flags, uint16_t capture); // 콜백 함수
} TCB_Instance;

/**
 * @brief TCB 초기화 함수
 * @details TCB 모듈을 지정된 모드, 클럭, TOP 값, PORTMUX로 설정.
 * @param instance TCB 인스턴스 포인터.
 * @param tcb TCB 레지스터 포인터 (예: &TCB0).
 * @param mode TCB 모드 (예: TCB_MODE_INT).
 * @param clksel 클럭 선택 (예: TCB_CLKSEL_DIV1).
 * @param top CCMP 값 (TOP 또는 비교 값).
 * @param portmux PORTMUX 설정 (TCB_PORTMUX_DEFAULT, TCB_PORTMUX_ALT1).
 */
void tcb_init(TCB_Instance *instance, TCB_t *tcb, uint8_t mode, uint8_t clksel, uint16_t top, uint8_t portmux);

/**
 * @brief TCB 활성화 함수
 * @details TCB 모듈을 활성화 (ENABLE=1).
 * @param instance TCB 인스턴스 포인터.
 */
void tcb_enable(TCB_Instance *instance);

/**
 * @brief TCB 비활성화 함수
 * @details TCB 모듈을 비활성화 (ENABLE=0).
 * @param instance TCB 인스턴스 포인터.
 */
void tcb_disable(TCB_Instance *instance);

/**
 * @brief TCB 인터럽트 활성화 함수
 * @details OVF 및 CAPT 인터럽트를 활성화.
 * @param instance TCB 인스턴스 포인터.
 * @param ovf OVF 인터럽트 활성화 (TCB_INT_OVF).
 * @param capt CAPT 인터럽트 활성화 (TCB_INT_CAPT).
 */
void tcb_enable_interrupts(TCB_Instance *instance, uint8_t ovf, uint8_t capt);

/**
 * @brief 인터럽트 플래그 확인 함수
 * @details 인터럽트 플래그를 읽고 클리어.
 * @param instance TCB 인스턴스 포인터.
 * @return 인터럽트 플래그 (TCB_INT_OVF | TCB_INT_CAPT).
 */
uint8_t tcb_check_interrupts(TCB_Instance *instance);

/**
 * @brief 현재 카운터 값 읽기 함수
 * @details TCB의 CNT 레지스터 값을 반환.
 * @param instance TCB 인스턴스 포인터.
 * @return 16비트 카운터 값.
 */
uint16_t tcb_count(TCB_Instance *instance);

/**
 * @brief 캡처 값 읽기 함수
 * @details TCB의 CCMP 레지스터 값을 반환.
 * @param instance TCB 인스턴스 포인터.
 * @return 16비트 캡처 값.
 */
uint16_t tcb_capture(TCB_Instance *instance);

/**
 * @brief 노이즈 캔슬러 활성화 함수
 * @details 4주기 노이즈 필터를 활성화 (FILTER=1).
 * @param instance TCB 인스턴스 포인터.
 */
void tcb_enable_noise_canceler(TCB_Instance *instance);

/**
 * @brief TCA 동기화 활성화 함수
 * @details TCA 클럭 동기화를 활성화 (SYNCUPD=1).
 * @param instance TCB 인스턴스 포인터.
 */
void tcb_sync_with_tca(TCB_Instance *instance);

/**
 * @brief TCB 동작 상태 확인 함수
 * @details TCB가 동작 중인지 확인.
 * @param instance TCB 인스턴스 포인터.
 * @return 1=동작 중, 0=정지.
 */
uint8_t tcb_is_running(TCB_Instance *instance);

/**
 * @brief 이벤트 생성기 설정 함수
 * @details TCB 이벤트를 EVSYS 채널에 연결.
 * @param instance TCB 인스턴스 포인터.
 * @param channel EVSYS 채널 번호 (0~9).
 * @param event_type 이벤트 타입 (TCB_EVENT_CAPT, TCB_EVENT_OVF).
 */
void tcb_set_event_generator(TCB_Instance *instance, uint8_t channel, uint8_t event_type);

/**
 * @brief 이벤트 사용자 설정 함수
 * @details TCB를 EVSYS 채널의 사용자로 설정.
 * @param instance TCB 인스턴스 포인터.
 * @param channel EVSYS 채널 번호 (0~9).
 * @param event_type 이벤트 타입 (TCB_EVENT_CAPT, TCB_EVENT_COUNT).
 */
void tcb_set_event_user(TCB_Instance *instance, uint8_t channel, uint8_t event_type);

/**
 * @brief 소프트웨어 이벤트 트리거 함수
 * @details 소프트웨어 이벤트를 트리거 (SWEVENTA 또는 SWEVENTB).
 * @param event SWEVENTA(0) 또는 SWEVENTB(1).
 */
void tcb_trigger_software_event(uint8_t event);

/**
 * @brief 32비트 모드 설정 함수
 * @details 두 TCB 모듈(TCBn, TCBn+1)을 결합하여 32비트 모드 설정.
 * @param instance1 첫 번째 TCB 인스턴스 (TCBn).
 * @param instance2 두 번째 TCB 인스턴스 (TCBn+1).
 */
void tcb_set_32bit_mode(TCB_Instance *instance1, TCB_Instance *instance2);

/**
 * @brief 32비트 캡처 값 읽기 함수
 * @details 두 TCB 모듈의 CCMP 값을 결합하여 32비트 캡처 값을 반환.
 * @param instance1 첫 번째 TCB 인스턴스 (TCBn).
 * @param instance2 두 번째 TCB 인스턴스 (TCBn+1).
 * @return 32비트 캡처 값.
 */
uint32_t tcb_capture_32bit(TCB_Instance *instance1, TCB_Instance *instance2);

#endif // TCB_DRIVER_H
    

tcb_driver.c

/**
 * @file tcb_driver.c
 * @brief AVR128DA64/48/32/28 TCB 드라이버 구현
 * @details TCBn 레지스터를 통해 TCB 기능을 제어. 주기적 인터럽트, 타임아웃 체크,
 *          입력 캡처(이벤트, 주파수, 펄스폭, 주파수+펄스폭, 32비트 캡처),
 *          단일 샷, 8비트 PWM, 노이즈 캔슬러, TCA 동기화, EVSYS 연동 지원.
 *          표준 ISR을 사용하여 인터럽트 처리.
 * @author 작성자
 * @date 2025-09-04
 */
#include "tcb_driver.h"
#include <avr/interrupt.h>

/**
 * @brief TCB 인스턴스 배열
 * @details 최대 5개 TCB 인스턴스(TCB0~TCB4)를 저장하여 인터럽트 핸들링 관리.
 *          AVR128DA28은 3개, AVR128DA32/48은 4개, AVR128DA64는 5개 지원.
 */
static TCB_Instance *tcb_instances[TCB_COUNT] = {NULL};

/**
 * @brief TCB 인스턴스 등록 함수
 * @details TCB 인스턴스를 tcb_instances 배열에 등록하여 인터럽트에서 참조.
 * @param instance TCB 인스턴스 포인터.
 * @param tcb TCB 레지스터 포인터 (예: &TCB0).
 */
static void register_instance(TCB_Instance *instance, TCB_t *tcb) {
    if (tcb == &TCB0) tcb_instances[0] = instance;
    else if (tcb == &TCB1) tcb_instances[1] = instance;
    else if (tcb == &TCB2) tcb_instances[2] = instance;
    else if (tcb == &TCB3) tcb_instances[3] = instance;
    else if (tcb == &TCB4) tcb_instances[4] = instance;
}

/**
 * @brief TCB 초기화 함수
 * @details TCB 모듈을 설정. PORTMUX로 WO 핀 선택, 모드, 클럭, TOP 값 설정.
 *          인터럽트 플래그 초기화 및 인스턴스 등록.
 */
void tcb_init(TCB_Instance *instance, TCB_t *tcb, uint8_t mode, uint8_t clksel, uint16_t top, uint8_t portmux) {
    // PORTMUX 설정 및 GPIO 출력 방향 설정 (WO 핀)
    if (tcb == &TCB0) {
        PORTMUX.TCBROUTEA = (PORTMUX.TCBROUTEA & ~(0x1 << 0)) | (portmux << 0); // TCB0 PORTMUX 설정
        if (portmux == TCB_PORTMUX_DEFAULT) PORTB.DIRSET = (1 << 0); // PB0(WO) 출력
        else PORTB.DIRSET = (1 << 2); // PB2(WO) 출력
    } else if (tcb == &TCB1) {
        PORTMUX.TCBROUTEA = (PORTMUX.TCBROUTEA & ~(0x1 << 1)) | (portmux << 1); // TCB1 PORTMUX 설정
        if (portmux == TCB_PORTMUX_DEFAULT) PORTC.DIRSET = (1 << 0); // PC0(WO) 출력
        else PORTC.DIRSET = (1 << 2); // PC2(WO) 출력
    } else if (tcb == &TCB2) {
        PORTMUX.TCBROUTEA = (PORTMUX.TCBROUTEA & ~(0x1 << 2)) | (portmux << 2); // TCB2 PORTMUX 설정
        if (portmux == TCB_PORTMUX_DEFAULT) PORTF.DIRSET = (1 << 0); // PF0(WO) 출력
        else PORTF.DIRSET = (1 << 2); // PF2(WO) 출력
    } else if (tcb == &TCB3) {
        PORTMUX.TCBROUTEA = (PORTMUX.TCBROUTEA & ~(0x1 << 3)) | (portmux << 3); // TCB3 PORTMUX 설정
        if (portmux == TCB_PORTMUX_DEFAULT) PORTB.DIRSET = (1 << 4); // PB4(WO) 출력
        else PORTB.DIRSET = (1 << 5); // PB5(WO) 출력
    } else if (tcb == &TCB4) {
        PORTMUX.TCBROUTEA = (PORTMUX.TCBROUTEA & ~(0x1 << 4)) | (portmux << 4); // TCB4 PORTMUX 설정
        if (portmux == TCB_PORTMUX_DEFAULT) PORTD.DIRSET = (1 << 0); // PD0(WO) 출력
        else PORTD.DIRSET = (1 << 2); // PD2(WO) 출력
    }

    // TCB 비활성화
    tcb->CTRLA = 0x00;

    // 모드 및 파형 출력 설정 (PWM8, 단일 샷 모드에서 WO 활성화)
    tcb->CTRLB = (mode == TCB_MODE_PWM8 || mode == TCB_MODE_SINGLE) ? (1 << 4) | (mode & 0x07) : (mode & 0x07);

    // 클럭 선택 및 TOP 값 설정
    tcb->CTRLA = (clksel << 1);
    tcb->CCMP = top;

    // 인터럽트 플래그 초기화
    tcb->INTFLAGS = TCB_INT_OVF | TCB_INT_CAPT;

    // 인스턴스 초기화
    instance->tcb = tcb;
    instance->capture = 0;
    instance->int_flags = 0;
    instance->callback = NULL;
    register_instance(instance, tcb);
}

/**
 * @brief TCB 활성화 함수
 * @details TCB 모듈을 활성화 (CTRLA.ENABLE=1).
 */
void tcb_enable(TCB_Instance *instance) {
    instance->tcb->CTRLA |= (1 << 0); // ENABLE 비트 설정
}

/**
 * @brief TCB 비활성화 함수
 * @details TCB 모듈을 비활성화 (CTRLA.ENABLE=0).
 */
void tcb_disable(TCB_Instance *instance) {
    instance->tcb->CTRLA &= ~(1 << 0); // ENABLE 비트 클리어
}

/**
 * @brief TCB 인터럽트 활성화 함수
 * @details OVF 및 CAPT 인터럽트를 활성화 (INTCTRL 설정).
 */
void tcb_enable_interrupts(TCB_Instance *instance, uint8_t ovf, uint8_t capt) {
    instance->tcb->INTCTRL = (ovf ? TCB_INT_OVF : 0) | (capt ? TCB_INT_CAPT : 0);
}

/**
 * @brief 인터럽트 플래그 확인 함수
 * @details INTFLAGS를 읽고 클리어, 인스턴스의 플래그 업데이트.
 * @return 인터럽트 플래그 (TCB_INT_OVF | TCB_INT_CAPT).
 */
uint8_t tcb_check_interrupts(TCB_Instance *instance) {
    uint8_t flags = instance->tcb->INTFLAGS;
    instance->tcb->INTFLAGS = flags; // 플래그 클리어
    instance->int_flags |= flags;
    return flags;
}

/**
 * @brief 현재 카운터 값 읽기 함수
 * @details TCB의 CNT 레지스터 값을 반환.
 * @return 16비트 카운터 값.
 */
uint16_t tcb_count(TCB_Instance *instance) {
    return instance->tcb->CNT;
}

/**
 * @brief 캡처 값 읽기 함수
 * @details TCB의 CCMP 레지스터 값을 반환.
 * @return 16비트 캡처 값.
 */
uint16_t tcb_capture(TCB_Instance *instance) {
    return instance->tcb->CCMP;
}

/**
 * @brief 노이즈 캔슬러 활성화 함수
 * @details 4주기 노이즈 필터를 활성화 (EVCTRL.FILTER=1).
 */
void tcb_enable_noise_canceler(TCB_Instance *instance) {
    instance->tcb->EVCTRL |= (1 << 6); // FILTER 비트 설정
}

/**
 * @brief TCA 동기화 활성화 함수
 * @details TCA 클럭 동기화를 활성화 (CTRLA.SYNCUPD=1).
 */
void tcb_sync_with_tca(TCB_Instance *instance) {
    instance->tcb->CTRLA |= (1 << 4); // SYNCUPD 비트 설정
}

/**
 * @brief TCB 동작 상태 확인 함수
 * @details TCB가 동작 중인지 확인 (CTRLA.ENABLE).
 * @return 1=동작 중, 0=정지.
 */
uint8_t tcb_is_running(TCB_Instance *instance) {
    return (instance->tcb->CTRLA & (1 << 0)) ? 1 : 0;
}

/**
 * @brief 이벤트 생성기 설정 함수
 * @details TCB 이벤트를 EVSYS 채널에 연결 (CHANNELn 설정).
 *          예: TCB0_CAPT=0xA0, TCB0_OVF=0xA1.
 */
void tcb_set_event_generator(TCB_Instance *instance, uint8_t channel, uint8_t event_type) {
    if (!TCB_CHANNEL_VALID(channel)) return;
    uint8_t base = (instance->tcb == &TCB0) ? 0xA0 : (instance->tcb == &TCB1) ? 0xA2 :
                   (instance->tcb == &TCB2) ? 0xA4 : (instance->tcb == &TCB3) ? 0xA6 : 0xA8;
    (&EVSYS.CHANNEL0)[channel] = base + event_type;
}

/**
 * @brief 이벤트 사용자 설정 함수
 * @details TCB를 EVSYS 채널의 사용자로 설정 (USERx 설정).
 *          예: TCB0_CAPT=0x1F, TCB0_COUNT=0x20.
 */
void tcb_set_event_user(TCB_Instance *instance, uint8_t channel, uint8_t event_type) {
    if (!TCB_CHANNEL_VALID(channel)) return;
    uint8_t base = (instance->tcb == &TCB0) ? 0x1F : (instance->tcb == &TCB1) ? 0x21 :
                   (instance->tcb == &TCB2) ? 0x23 : (instance->tcb == &TCB3) ? 0x25 : 0x27;
    (&EVSYS.USER0)[channel] = base + event_type;
}

/**
 * @brief 소프트웨어 이벤트 트리거 함수
 * @details SWEVENTA 또는 SWEVENTB를 트리거.
 */
void tcb_trigger_software_event(uint8_t event) {
    if (event == 0) EVSYS.SWEVENTA = 0x01; // SWEVENTA 트리거
    else EVSYS.SWEVENTB = 0x01; // SWEVENTB 트리거
}

/**
 * @brief 32비트 모드 설정 함수
 * @details 두 TCB 모듈을 결합하여 32비트 모드로 설정 (CTRLA.CASCADE=1).
 */
void tcb_set_32bit_mode(TCB_Instance *instance1, TCB_Instance *instance2) {
    instance1->tcb->CTRLA |= (1 << 5); // 첫 번째 TCB의 CASCADE 비트 설정
    instance2->tcb->CTRLA |= (1 << 5); // 두 번째 TCB의 CASCADE 비트 설정
}

/**
 * @brief 32비트 캡처 값 읽기 함수
 * @details 두 TCB의 CCMP 값을 결합하여 32비트 값을 반환.
 * @return 32비트 캡처 값 (상위 16비트: instance1, 하위 16비트: instance2).
 */
uint32_t tcb_capture_32bit(TCB_Instance *instance1, TCB_Instance *instance2) {
    uint16_t low = instance2->tcb->CCMP; // 하위 16비트 (TCBn+1)
    uint16_t high = instance1->tcb->CCMP; // 상위 16비트 (TCBn)
    return ((uint32_t)high << 16) | low;
}

/**
 * @brief TCB 인터럽트 핸들러 함수
 * @details 인터럽트 플래그 처리 및 사용자 콜백 호출.
 * @param instance TCB 인스턴스 포인터.
 */
static void tcb_handler(TCB_Instance *instance) {
    uint8_t flags = instance->tcb->INTFLAGS; // 인터럽트 플래그 읽기
    instance->tcb->INTFLAGS = flags; // 플래그 클리어
    instance->int_flags |= flags; // 인스턴스 플래그 업데이트
    if (flags & TCB_INT_CAPT) instance->capture = instance->tcb->CCMP; // 캡처 값 저장
    if (instance->callback) instance->callback(flags, instance->capture); // 콜백 호출
}

/**
 * @brief TCB0 인터럽트 핸들러
 * @details TCB0의 OVF 및 CAPT 인터럽트를 처리.
 */
ISR(TCB0_INT_vect) {
    if (tcb_instances[0]) tcb_handler(tcb_instances[0]);
}

/**
 * @brief TCB1 인터럽트 핸들러
 * @details TCB1의 OVF 및 CAPT 인터럽트를 처리.
 */
ISR(TCB1_INT_vect) {
    if (tcb_instances[1]) tcb_handler(tcb_instances[1]);
}

/**
 * @brief TCB2 인터럽트 핸들러
 * @details TCB2의 OVF 및 CAPT 인터럽트를 처리.
 */
ISR(TCB2_INT_vect) {
    if (tcb_instances[2]) tcb_handler(tcb_instances[2]);
}

/**
 * @brief TCB3 인터럽트 핸들러
 * @details TCB3의 OVF 및 CAPT 인터럽트를 처리 (AVR128DA48 이상).
 */
ISR(TCB3_INT_vect) {
    if (tcb_instances[3]) tcb_handler(tcb_instances[3]);
}

#if defined(TCB4)
/**
 * @brief TCB4 인터럽트 핸들러
 * @details TCB4의 OVF 및 CAPT 인터럽트를 처리 (AVR128DA64 전용).
 */
ISR(TCB4_INT_vect) {
    if (tcb_instances[4]) tcb_handler(tcb_instances[4]);
}
#endif
    

main.c

/**
 * @file main.c
 * @brief AVR128DA64/48/32/28 TCB 드라이버 테스트 프로그램
 * @details 시스템 클럭을 24MHz로 설정하고, TCB0으로 PWM, TCB1으로 입력 캡처 테스트.
 *          UART0를 통해 디버깅 출력 (9600 baud).
 * @author 작성자
 * @date 2025-09-04
 */
#ifndef F_CPU
#define F_CPU 24000000UL // 시스템 클럭 주파수 24MHz 정의
#endif

#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include "tcb_driver.h"

/**
 * @brief 시스템 클럭을 24MHz로 초기화
 * @details OSCHF 클럭을 24MHz로 설정하고 Auto-tune 활성화.
 *          CLKCTRL 레지스터에 보호된 쓰기 사용.
 */
void clock_init_24mhz(void) {
    _PROTECTED_WRITE(CLKCTRL.XOSCHFCTRLA, CLKCTRL_ENABLE_bm | (0 << CLKCTRL_SEL_bp)); // OSCHF 활성화
    _PROTECTED_WRITE(CLKCTRL.OSCHCTRLA, (0x9 << CLKCTRL_FRQSEL_gp) | CLKCTRL_AUTOTUNE_bm); // 24MHz, Auto-tune
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00); // 클럭 분주 비활성화
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, 0x00); // 기본 클럭 소스 선택
    while ((CLKCTRL.MCLKSTATUS & CLKCTRL_OSCHF_bm) == 0); // 클럭 안정화 대기
}

/**
 * @brief TCB0 콜백 함수
 * @details TCB0 인터럽트에서 호출. 인터럽트 플래그와 캡처 값을 출력.
 * @param flags 인터럽트 플래그 (TCB_INT_OVF, TCB_INT_CAPT).
 * @param capture 캡처 값 (16비트).
 */
void tcb0_callback(uint8_t flags, uint16_t capture) {
    printf("TCB0: Flags=%02X, Capture=%u\r\n", flags, capture);
}

/**
 * @brief TCB1 콜백 함수
 * @details TCB1 인터럽트에서 호출. 인터럽트 플래그와 캡처 값을 출력.
 * @param flags 인터럽트 플래그 (TCB_INT_OVF, TCB_INT_CAPT).
 * @param capture 캡처 값 (16비트).
 */
void tcb1_callback(uint8_t flags, uint16_t capture) {
    printf("TCB1: Flags=%02X, Capture=%u\r\n", flags, capture);
}

/**
 * @brief 메인 함수
 * @details 클럭, UART, TCB0(PWM), TCB1(입력 캡처)을 초기화하고 테스트.
 * @return 0 (무한 루프).
 */
int main(void) {
    clock_init_24mhz(); // 24MHz 클럭 설정
    sei(); // 전역 인터럽트 활성화

    // USART0 초기화 (디버깅 출력용, 9600 baud)
    UART_Instance usart0_instance;
    uart_init(&usart0_instance, &USART0, UART_BAUD_9600, UART_DATA_8BIT, UART_STOP_1BIT, UART_PARITY_NONE, UART_MODE_ASYNC, UART_PORTMUX_DEFAULT);
    uart_enable_tx(&usart0_instance);
    uart_enable_rx(&usart0_instance);
    uart_setup_stdio(&usart0_instance);

    // TCB0 초기화: 8비트 PWM, 1kHz (24MHz / 24000 = 1kHz)
    TCB_Instance tcb0_instance;
    tcb_init(&tcb0_instance, &TCB0, TCB_MODE_PWM8, TCB_CLKSEL_DIV1, 24000, TCB_PORTMUX_DEFAULT);
    tcb0_instance.callback = tcb0_callback;
    tcb_enable_interrupts(&tcb0_instance, 1, 0); // OVF 인터럽트 활성화
    tcb_enable(&tcb0_instance); // TCB0 활성화

    // TCB1 초기화: 입력 캡처, 이벤트 입력
    TCB_Instance tcb1_instance;
    tcb_init(&tcb1_instance, &TCB1, TCB_MODE_CAPT, TCB_CLKSEL_EVENT, 0, TCB_PORTMUX_DEFAULT);
    tcb1_instance.callback = tcb1_callback;
    tcb_enable_interrupts(&tcb1_instance, 0, 1); // CAPT 인터럽트 활성화
    tcb_enable_noise_canceler(&tcb1_instance); // 노이즈 캔슬러 활성화
    tcb_set_event_user(&tcb1_instance, 0, TCB_EVENT_CAPT); // EVSYS 사용자 설정
    tcb_enable(&tcb1_instance); // TCB1 활성화

    printf("TCB Driver Test: TCB0 (PWM), TCB1 (Capture)\r\n");
    _delay_ms(1000); // 초기화 후 대기

    // 메인 루프: TCB0 카운터와 TCB1 캡처 값 주기적 출력
    while (1) {
        printf("TCB0 Count: %u, TCB1 Capture: %u\r\n", tcb_count(&tcb0_instance), tcb_capture(&tcb1_instance));
        _delay_ms(1000);
    }

    return 0;
}
    

tcb_pwm_example.c

/**
 * @file tcb_pwm_example.c
 * @brief AVR128DA TCB 드라이버 PWM 예제
 * @details TCB0을 사용하여 1kHz, 50% 듀티 PWM 출력 테스트.
 *          UART0를 통해 디버깅 출력 (9600 baud).
 * @author 작성자
 * @date 2025-09-04
 */
#ifndef F_CPU
#define F_CPU 24000000UL // 시스템 클럭 주파수 24MHz 정의
#endif

#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include "tcb_driver.h"

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

/**
 * @brief TCB0 콜백 함수
 * @details TCB0 인터럽트에서 호출. 인터럽트 플래그 출력.
 * @param flags 인터럽트 플래그 (TCB_INT_OVF, TCB_INT_CAPT).
 * @param capture 캡처 값 (16비트, PWM에서는 사용 안 함).
 */
void tcb0_callback(uint8_t flags, uint16_t capture) {
    printf("TCB0 PWM: Flags=%02X\r\n", flags);
}

/**
 * @brief 메인 함수
 * @details 클럭, UART, TCB0(PWM)을 초기화하고 1kHz PWM 출력 테스트.
 * @return 0 (무한 루프).
 */
int main(void) {
    clock_init_24mhz(); // 24MHz 클럭 설정
    sei(); // 전역 인터럽트 활성화

    // USART0 초기화 (디버깅 출력용, 9600 baud)
    UART_Instance usart0_instance;
    uart_init(&usart0_instance, &USART0, UART_BAUD_9600, UART_DATA_8BIT, UART_STOP_1BIT, UART_PARITY_NONE, UART_MODE_ASYNC, UART_PORTMUX_DEFAULT);
    uart_enable_tx(&usart0_instance);
    uart_enable_rx(&usart0_instance);
    uart_setup_stdio(&usart0_instance);

    // TCB0 초기화: 8비트 PWM, 1kHz (24MHz / 24000 = 1kHz)
    TCB_Instance tcb0_instance;
    tcb_init(&tcb0_instance, &TCB0, TCB_MODE_PWM8, TCB_CLKSEL_DIV1, 24000, TCB_PORTMUX_DEFAULT);
    tcb0_instance.callback = tcb0_callback;
    tcb_enable_interrupts(&tcb0_instance, 1, 0); // OVF 인터럽트 활성화
    tcb_enable(&tcb0_instance); // TCB0 활성화

    printf("TCB PWM Test: 1kHz, 50%% duty on TCB0\r\n");
    _delay_ms(1000); // 초기화 후 대기

    // 메인 루프: 무한 대기
    while (1) {
        _delay_ms(1000);
    }

    return 0;
}
    

tcb_capture_example.c

/**
 * @file tcb_capture_example.c
 * @brief AVR128DA TCB 드라이버 입력 캡처 예제
 * @details TCB1을 사용하여 이벤트 입력으로 펄스폭 측정 테스트.
 *          UART0를 통해 디버깅 출력 (9600 baud).
 * @author 작성자
 * @date 2025-09-04
 */
#ifndef F_CPU
#define F_CPU 24000000UL // 시스템 클럭 주파수 24MHz 정의
#endif

#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include "tcb_driver.h"

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

/**
 * @brief TCB1 콜백 함수
 * @details TCB1 인터럽트에서 호출. 인터럽트 플래그와 캡처 값 출력.
 * @param flags 인터럽트 플래그 (TCB_INT_OVF, TCB_INT_CAPT).
 * @param capture 캡처 값 (16비트).
 */
void tcb1_callback(uint8_t flags, uint16_t capture) {
    printf("TCB1 Capture: Flags=%02X, Value=%u\r\n", flags, capture);
}

/**
 * @brief 메인 함수
 * @details 클럭, UART, TCB1(입력 캡처)을 초기화하고 펄스폭 측정 테스트.
 * @return 0 (무한 루프).
 */
int main(void) {
    clock_init_24mhz(); // 24MHz 클럭 설정
    sei(); // 전역 인터럽트 활성화

    // USART0 초기화 (디버깅 출력용, 9600 baud)
    UART_Instance usart0_instance;
    uart_init(&usart0_instance, &USART0, UART_BAUD_9600, UART_DATA_8BIT, UART_STOP_1BIT, UART_PARITY_NONE, UART_MODE_ASYNC, UART_PORTMUX_DEFAULT);
    uart_enable_tx(&usart0_instance);
    uart_enable_rx(&usart0_instance);
    uart_setup_stdio(&usart0_instance);

    // TCB1 초기화: 입력 캡처, 이벤트 입력, 펄스폭 측정
    TCB_Instance tcb1_instance;
    tcb_init(&tcb1_instance, &TCB1, TCB_MODE_PW, TCB_CLKSEL_EVENT, 0, TCB_PORTMUX_DEFAULT);
    tcb1_instance.callback = tcb1_callback;
    tcb_enable_interrupts(&tcb1_instance, 0, 1); // CAPT 인터럽트 활성화
    tcb_enable_noise_canceler(&tcb1_instance); // 노이즈 캔슬러 활성화
    tcb_set_event_user(&tcb1_instance, 0, TCB_EVENT_CAPT); // EVSYS 사용자 설정
    tcb_enable(&tcb1_instance); // TCB1 활성화

    printf("TCB Capture Test: Pulse width on TCB1\r\n");
    _delay_ms(1000); // 초기화 후 대기

    // 메인 루프: 소프트웨어 이벤트 트리거로 캡처 테스트
    while (1) {
        tcb_trigger_software_event(0); // SWEVENTA 트리거
        _delay_ms(1000);
    }

    return 0;
}
    

추가 팁

  • 클럭 최적화: 높은 주파수 캡처 시 CLKSEL_DIV1 사용, 낮은 주파수 시 CLKSEL_DIV2로 클럭 분주.
  • 인터럽트 우선순위: 다중 TCB 사용 시 CPUINT로 우선순위 조정.
  • EVSYS 활용: TCBn_CAPT를 다른 모듈(TCA, ADC) 트리거로 사용.
  • 노이즈 캔슬러: 입력 신호가 불안정할 경우 FILTER=1 필수.
  • PWM 설정: 8비트 PWM에서 CCMP는 듀티(상위 8비트)와 주기(하위 8비트) 설정.
  • 32비트 모드: TCBn+TCBn+1 쌍으로 사용, CASCADE 설정 확인.
  • 디버깅: tcb_check_interruptsprintf로 플래그 및 캡처 값 모니터링.
  • 테스트 환경: Microchip Studio 시뮬레이터 또는 PuTTY로 출력 확인.

결론

본 TCB 드라이버는 AVR128DA64/48/32/28 시리즈의 TCB 기능을 완벽히 활용하도록 설계되었습니다. 주기적 인터럽트, 타임아웃, 입력 캡처, 단일 샷, 8비트 PWM, 노이즈 캔슬러, TCA 동기화, EVSYS 연동을 지원하며, 콜백 기반 인터럽트 처리와 상태 확인 기능을 제공합니다. 모든 AVR128DA 모델에 호환되며, PORTMUX와 ISR_ALIAS로 유연한 핀 설정과 다중 TCB 지원을 보장합니다. 제공된 예제 코드를 통해 PWM, 입력 캡처 등 다양한 기능을 쉽게 테스트할 수 있으며, 추가 팁을 참고하여 성능을 최적화할 수 있습니다. 이 드라이버는 임베디드 시스템에서 신뢰할 수 있는 타이머 솔루션을 제공합니다.

반응형