본문 바로가기
MCU/AVR

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

by linuxgo 2025. 9. 3.
반응형

개요

본 문서는 Microchip의 AVR128DA64/48/32/28 시리즈 마이크로컨트롤러에 내장된 UART(USART) 기능을 분석하고, 이를 활용할 수 있는 범용 드라이버를 설계 및 구현한 내용을 다룹니다. AVR128DA 시리즈는 최대 6개의 USART 모듈을 제공하며, 비동기/동기 통신, 다양한 보드레이트 설정, 패리티 및 스톱 비트 지원, 인터럽트 및 폴링 기반 송수신 기능을 포함합니다. 본 드라이버는 링버퍼를 기반으로 효율적인 데이터 송수신을 지원하며, printf를 통한 표준 출력 리디렉션, 가용 바이트 확인(uart_available), 링버퍼 초기화(uart_flush)와 같은 부가 기능을 제공합니다. 또한 AVR128DA64/48/32/28 전 제품군에 호환되도록 설계되어 Microchip Studio 및 AVR-GCC 환경에서 손쉽게 활용 가능합니다.

AVR128DA USART block diagram

사양

AVR128DA 시리즈의 UART(USART) 사양은 다음과 같습니다:

  • 모듈 수:
    • AVR128DA64: 최대 6개 (USART0~5).
    • AVR128DA48: 최대 5개 (USART0~4).
    • AVR128DA32: 최대 4개 (USART0~3).
    • AVR128DA28: 최대 3개 (USART0~2).
  • 핀 할당: PORTMUX로 TxD/RxD/XCK 핀 선택 가능 (예: USART0 기본: PA0(TxD), PA1(RxD)).
  • 전압 범위: 1.8V ~ 5.5V (VDD).
  • 기능:
    • 비동기/동기 모드.
    • 보드레이트: 300~2Mbps (24MHz CLK_PER 기준).
    • 프레임: 5~9비트 데이터, 1~2 스톱 비트, 패리티(없음/짝수/홀수).
    • 인터럽트: 송신 완료(TXCIF), 데이터 레지스터 비움(DREIF), 수신 완료(RXCIF), 스타트 프레임 감지(RXSIF).
    • 링버퍼: 송수신 데이터 관리 (64바이트, 폴링 및 인터럽트 지원).
    • printf: UART를 통한 표준 출력 지원.
    • 추가: 가용 바이트 확인, 링버퍼 비우기.
  • 클럭 의존성: Peripheral Clock (CLK_PER, 최대 24MHz).
  • 리셋 상태: USART 비활성화, 핀은 GPIO 입력 모드.
  • 전력 최적화: RXEN=0, TXEN=0으로 전력 절감.

레지스터 설정 상세

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

  • USARTn.RXDATAL (0x00):
    • 비트 [7:0]: DATA[7:0] (수신 데이터, 8비트).
    • 리셋: 0x00, 읽기 전용(R).
  • USARTn.RXDATAH (0x01):
    • 비트 [7]: RXCIF (수신 완료 플래그, 1 작성으로 클리어).
    • 비트 [6]: BUFOVF (버퍼 오버플로우 에러, 1 작성으로 클리어).
    • 비트 [5]: FERR (프레임 에러, 1 작성으로 클리어).
    • 비트 [4]: PERR (패리티 에러, 1 작성으로 클리어).
    • 비트 [0]: DATA[8] (9비트 모드의 상위 비트).
    • 리셋: 0x00, R/W.
  • USARTn.TXDATAL (0x02):
    • 비트 [7:0]: DATA[7:0] (송신 데이터, 8비트).
    • 리셋: 0x00, 쓰기 전용(W).
  • USARTn.TXDATAH (0x03):
    • 비트 [0]: DATA[8] (9비트 모드의 상위 비트).
    • 리셋: 0x00, W.
  • USARTn.STATUS (0x04):
    • 비트 [7]: RXCIF (수신 완료 플래그, 1 작성으로 클리어).
    • 비트 [6]: TXCIF (송신 완료 플래그, 1 작성으로 클리어).
    • 비트 [5]: DREIF (데이터 레지스터 비움 플래그).
    • 비트 [3]: RXSIF (스타트 프레임 감지 플래그, 1 작성으로 클리어).
    • 비트 [2]: ISFIF (인프레임 스타트 플래그).
    • 비트 [1]: BDF (브레이크 감지 플래그).
    • 비트 [0]: WFB (웨이트 포 브레이크 플래그).
    • 리셋: 0x20, R/W.
  • USARTn.CTRLA (0x05):
    • 비트 [7]: RXCIE (수신 완료 인터럽트 활성).
    • 비트 [6]: TXCIE (송신 완료 인터럽트 활성).
    • 비트 [5]: DREIE (데이터 레지스터 비움 인터럽트 활성).
    • 비트 [3]: RXSIE (스타트 프레임 감지 인터럽트 활성).
    • 비트 [1]: LBME (루프백 모드 활성).
    • 비트 [0]: ABEIE (자동 보드 에러 인터럽트 활성).
    • 비트 [0]: RS485 (RS485 모드: 0=비활성, 1=활성).
    • 리셋: 0x00, R/W.
  • USARTn.CTRLB (0x06):
    • 비트 [7]: MPCM (멀티프로세서 통신 모드 활성).
    • 비트 [6]: RXEN (수신기 활성).
    • 비트 [5]: TXEN (송신기 활성).
    • 비트 [3]: SFDEN (스타트 프레임 감지 활성).
    • 비트 [2]: ODME (오픈 드레인 모드 활성).
    • 비트 [1:0]: RXMODE[1:0] (0x0=정상, 0x1=CLK2x, 0x2=일반).
    • 리셋: 0x00, R/W.
  • USARTn.CTRLC (0x07):
    • 비동기 모드:
      • 비트 [7:6]: CMODE[1:0] (0x0=비동기, 0x1=동기).
      • 비트 [5:4]: PMODE[1:0] (패리티: 0x0=없음, 0x2=짝수, 0x3=홀수).
      • 비트 [3]: SBMODE (0: 1 스톱 비트, 1: 2 스톱 비트).
      • 비트 [2:0]: CHSIZE[2:0] (캐릭터 크기: 0x0=8비트 등).
    • 동기 모드:
      • 비트 [5]: UDORD (데이터 순서: 0=MSB 먼저, 1=LSB 먼저).
      • 비트 [4]: UCPHA (클럭 위상: 0=선행 에지 샘플링, 1=후행 에지 샘플링).
    • 리셋: 0x00, R/W.
  • USARTn.BAUD (0x08~0x09):
    • 비트 [15:0]: BAUD[15:0] (보드레이트 설정, 16비트).
    • 리셋: 0x0000, R/W.
  • USARTn.CTRLD (0x0A):
    • 비트 [7:6]: ABW[1:0] (자동 보드 윈도우 설정).
    • 리셋: 0x00, R/W.
  • USARTn.DBGCTRL (0x0B):
    • 비트 [0]: DBGRUN (디버그 모드에서 동작 유지).
    • 리셋: 0x00, R/W.
  • USARTn.EVCTRL (0x0C):
    • 비트 [0]: IREI (인프레임 이벤트 인터럽트 활성).
    • 리셋: 0x00, R/W.
  • USARTn.TXPLCTRL (0x0D):
    • 비트 [7:0]: TXPL[7:0] (송신 펄스 길이).
    • 리셋: 0x00, R/W.
  • USARTn.RXPLCTRL (0x0E):
    • 비트 [6:0]: RXPL[6:0] (수신 펄스 길이).
    • 리셋: 0x00, R/W.

UART 설정 절차

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

  1. 클럭 설정: CLKCTRL로 CLK_PER 활성화 (예: 24MHz OSCHF).
  2. 포트 설정: PORTMUX로 TxD/RxD/XCK 핀 선택, GPIO 방향 설정 (TxD: 출력, RxD: 입력).
  3. 모드 설정: CTRLC로 비동기/동기, 패리티, 스톱 비트, 데이터 크기 설정.
  4. 보드레이트 설정: BAUD 레지스터로 보드레이트 계산 및 설정.
  5. 인터럽트 설정 (옵션): CTRLA로 RXCIE, TXCIE, DREIE 설정, STATUS 플래그 클리어.
  6. 송수신 활성화: CTRLB로 TXEN, RXEN 설정.
  7. 링버퍼 초기화: 송수신 링버퍼 초기화.
  8. printf 설정: uart_setup_stdioprintf 출력 리디렉션.
  9. 멀티플렉싱 확인: PORTMUX로 다른 주변 장치와 충돌 방지.
  10. 테스트: 송신/수신 데이터(폴링/인터럽트), 에러 상태(STATUS), 가용 바이트 확인, 링버퍼 비우기, printf 출력 확인.

PORTMUX를 통한 핀 테이블

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

USART 모듈 PORTMUX 설정 TxD 핀 RxD 핀 XCK 핀 설명
USART0 DEFAULT (0x0) PA0 PA1 PA2 기본 핀 설정
USART0 ALT1 (0x1) PA2 PA3 PA4 대체 핀 1
USART0 ALT2 (0x2) PC0 PC1 PC2 대체 핀 2
USART1 DEFAULT (0x0) PC0 PC1 PC2 기본 핀 설정
USART1 ALT1 (0x1) PC2 PC3 PC4 대체 핀 1
USART1 ALT2 (0x2) PA0 PA1 PA2 대체 핀 2
USART2 DEFAULT (0x0) PF0 PF1 PF2 기본 핀 설정
USART2 ALT1 (0x1) PF2 PF3 PF4 대체 핀 1
USART3 DEFAULT (0x0) PB0 PB1 PB2 기본 핀 설정
USART3 ALT1 (0x1) PB2 PB3 PB4 대체 핀 1
USART4 DEFAULT (0x0) PD0 PD1 PD2 기본 핀 설정 (AVR128DA48 이상)
USART4 ALT1 (0x1) PD2 PD3 PD4 대체 핀 1
USART5 DEFAULT (0x0) PE0 PE1 PE2 기본 핀 설정 (AVR128DA64 전용)
USART5 ALT1 (0x1) PE2 PE3 PE4 대체 핀 1

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

PORTMUX.USARTROUTEA |= (1 << 0); // USART0을 ALT1로 설정 (PA2:TxD, PA3:RxD)
PORTA.DIRSET = (1 << 2); // PA2(TxD) 출력
PORTA.DIRCLR = (1 << 3); // PA3(RxD) 입력
    

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

UART 설정 고려사항

  • 보드레이트 계산: BAUD = (64 * F_PER) / (S * BAUDRATE), S=16(정상), 8(CLK2x). 예: 24MHz에서 9600bps → BAUD=1600.
  • 링버퍼: 64바이트 순환 버퍼로 오버플로우 방지. 폴링(uart_transmit_buffered, uart_receive_buffered) 및 인터럽트(uart_transmit_it, uart_receive_it)에서 사용. uart_available로 가용 바이트 확인, uart_flush로 버퍼 비우기.
  • printf: putchar 재정의로 UART 출력. 폴링 기반으로 간단 구현.
  • 인터럽트: RXCIF, DREIF 플래그 클리어 필수. 다중 USART 지원 시 ISR_NAKED 또는 ISR_ALIAS 사용.
  • 포트 설정: PORTMUX로 대체 핀 선택 (위 테이블 참조).
  • 전력 최적화: 사용하지 않는 USART는 RXEN=0, TXEN=0.
  • 호환성: AVR128DA28은 USART0~2만 지원. <avr/io.h>로 모델별 정의 제공.
  • CCP 보호: CLKCTRL는 _PROTECTED_WRITE 사용.
  • 에러 처리: RXDATAH로 FERR, PERR, BUFOVF 확인 및 1 작성으로 클리어.

드라이버 구현 내용

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

  • 링버퍼: 64바이트 순환 버퍼로 송수신 데이터 관리. RingBuffer 구조체로 head/tail 관리. 폴링 및 인터럽트 방식 지원.
  • 폴링 기반 링버퍼: uart_transmit_buffereduart_receive_buffered로 다중 바이트 송수신.
  • 가용 바이트 확인: uart_available로 수신 링버퍼의 읽기 가능한 바이트 수 반환.
  • 링버퍼 비우기: uart_flush로 송수신 링버퍼 초기화.
  • printf 지원: uart_setup_stdioputchar 재정의, UART를 통한 표준 출력.
  • uart_init: USART 모듈 초기화 (포트, 보드레이트, 프레임 포맷, PORTMUX).
  • uart_transmit/receive: 단일 바이트 송수신 (폴링).
  • uart_transmit_it/receive_it: 인터럽트 기반 송수신 (링버퍼 사용).
  • uart_check_errors: 에러 상태 확인 (FERR, PERR, BUFOVF, 1 작성으로 클리어).
  • uart_enable/disable: 송수신 활성화/비활성화.
  • 클럭 설정: main.c에서 24MHz OSCHF, Auto-tune 활성화.
  • 다중 USART 지원: ISR_NAKED와 ISR_ALIAS로 USART0~5 인터럽트 처리.
  • 호환성: <avr/io.h>로 모델별 USART 정의 지원.

사용방법

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

  1. 헤더 파일 포함: 프로젝트에 uart_driver.h를 포함하고, <avr/io.h>, <stdio.h>, <util/delay.h>를 추가합니다.
  2. 클럭 설정: main.cclock_init_24mhz를 호출하여 24MHz OSCHF 클럭을 설정합니다.
  3. UART 인스턴스 생성: UART_Instance 구조체를 선언하고, uart_init으로 초기화합니다. 예:
    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);
                
  4. 송수신 활성화: uart_enable_txuart_enable_rx를 호출하여 송수신을 활성화합니다.
  5. printf 설정 (옵션): uart_setup_stdio로 UART를 통해 printf 출력을 설정합니다.
  6. 데이터 송수신:
    • 폴링 기반: uart_transmit_buffered로 데이터 송신, uart_receive_buffered로 데이터 수신.
    • 인터럽트 기반: uart_transmit_it_inituart_receive_it_init로 인터럽트 활성화 후, uart_transmit_ituart_receive_it로 송수신.
  7. 가용 바이트 확인: uart_available로 수신 링버퍼의 읽기 가능한 바이트 수를 확인합니다.
  8. 버퍼 비우기: uart_flush로 송수신 링버퍼를 초기화합니다.
  9. 에러 처리: uart_check_errors로 프레임 에러(FERR), 패리티 에러(PERR), 버퍼 오버플로우(BUFOVF)를 확인하고 클리어합니다.
  10. 다중 USART 사용: 각 USART(0~5)에 대해 별도의 UART_Instance를 생성하고 초기화합니다. 인터럽트 핸들러는 ISR_ALIASOF로 자동 처리됩니다.

코드 구현

  •  UART 드라이버 코드: uart_driver.h,uart_driver.c,main.c
  •   드라이버 코드를 이용한 예시코드:uart_echo_buffered_example.c, uart_monitor_example.c
/**
 * @file uart_driver.h
 * @brief AVR128DA64/48/32/28 UART 드라이버 헤더 파일
 * @details 모든 AVR128DA 시리즈 호환. 비동기/동기 통신, 링버퍼(폴링/인터럽트), 
 *          printf, 가용 바이트 확인, 버퍼 비우기, 에러 처리, 다중 USART 지원.
 * @author 작성자
 * @date 2025-09-03
 */
#ifndef UART_DRIVER_H
#define UART_DRIVER_H

#include <avr/io.h>     // AVR 레지스터 정의
#include <stdint.h>     // 표준 정수 타입
#include <stdio.h>      // printf 지원을 위한 표준 입출력

/**
 * @brief UART 보드레이트 정의
 * @details 일반적인 보드레이트 값(9600, 115200bps)을 상수로 정의
 */
#define UART_BAUD_9600  9600
#define UART_BAUD_115200 115200

/**
 * @brief UART 프레임 설정 정의
 * @details 데이터 비트, 스톱 비트, 패리티, 모드 설정을 위한 상수
 */
#define UART_DATA_5BIT  0x0 // 5비트 데이터
#define UART_DATA_6BIT  0x1 // 6비트 데이터
#define UART_DATA_7BIT  0x2 // 7비트 데이터
#define UART_DATA_8BIT  0x3 // 8비트 데이터
#define UART_DATA_9BIT  0x7 // 9비트 데이터
#define UART_STOP_1BIT  0   // 1 스톱 비트
#define UART_STOP_2BIT  1   // 2 스톱 비트
#define UART_PARITY_NONE 0x0 // 패리티 없음
#define UART_PARITY_EVEN 0x2 // 짝수 패리티
#define UART_PARITY_ODD  0x3 // 홀수 패리티
#define UART_MODE_ASYNC  0x0 // 비동기 모드
#define UART_MODE_SYNC   0x1 // 동기 모드

/**
 * @brief UART 에러 플래그 정의
 * @details 프레임 에러, 패리티 에러, 버퍼 오버플로우 플래그
 */
#define UART_ERROR_FERR   USART_FERR_bm   // 프레임 에러
#define UART_ERROR_PERR   USART_PERR_bm   // 패리티 에러
#define UART_ERROR_BUFOVF USART_BUFOVF_bm // 버퍼 오버플로우

/**
 * @brief UART 링버퍼 크기
 * @details 송수신 링버퍼 크기를 64바이트로 고정
 */
#define UART_BUFFER_SIZE 64

/**
 * @brief PORTMUX 설정 정의
 * @details 핀 매핑 선택 (기본, 대체 1, 대체 2)
 */
#define UART_PORTMUX_DEFAULT 0x0 // 기본 핀
#define UART_PORTMUX_ALT1    0x1 // 대체 핀 1
#define UART_PORTMUX_ALT2    0x2 // 대체 핀 2

/**
 * @brief 링버퍼 구조체
 * @details 송수신 데이터를 저장하는 순환 버퍼
 */
typedef struct {
    uint8_t buffer[UART_BUFFER_SIZE]; // 데이터 저장 배열
    volatile uint8_t head;           // 쓰기 인덱스
    volatile uint8_t tail;           // 읽기 인덱스
} RingBuffer;

/**
 * @brief UART 인스턴스 구조체
 * @details 각 USART 모듈의 상태와 설정 관리
 */
typedef struct {
    USART_t *usart;                  // USART 레지스터 포인터
    RingBuffer tx_buffer;            // 송신 링버퍼
    RingBuffer rx_buffer;            // 수신 링버퍼
    volatile uint8_t tx_busy;        // 송신 진행 상태 플래그
} UART_Instance;

/**
 * @brief UART 초기화
 * @param instance UART 인스턴스 포인터
 * @param usart USART 레지스터 포인터 (예: &USART0)
 * @param baud 보드레이트 (예: UART_BAUD_9600)
 * @param data_bits 데이터 비트 (예: UART_DATA_8BIT)
 * @param stop_bits 스톱 비트 (UART_STOP_1BIT, UART_STOP_2BIT)
 * @param parity 패리티 (UART_PARITY_NONE, UART_PARITY_EVEN, UART_PARITY_ODD)
 * @param mode 모드 (UART_MODE_ASYNC, UART_MODE_SYNC)
 * @param portmux PORTMUX 설정 (UART_PORTMUX_DEFAULT, UART_PORTMUX_ALT1, UART_PORTMUX_ALT2)
 * @details USART 모듈 초기화, 포트 설정, 링버퍼 초기화
 */
void uart_init(UART_Instance *instance, USART_t *usart, uint32_t baud, uint8_t data_bits, uint8_t stop_bits, uint8_t parity, uint8_t mode, uint8_t portmux);

/**
 * @brief UART 송신기 활성화
 * @param instance UART 인스턴스 포인터
 * @details CTRLB의 TXEN 비트를 설정하여 송신 활성화
 */
void uart_enable_tx(UART_Instance *instance);

/**
 * @brief UART 수신기 활성화
 * @param instance UART 인스턴스 포인터
 * @details CTRLB의 RXEN 비트를 설정하여 수신 활성화
 */
void uart_enable_rx(UART_Instance *instance);

/**
 * @brief UART 비활성화
 * @param instance UART 인스턴스 포인터
 * @details TXEN, RXEN 비트를 클리어하여 USART 비활성화
 */
void uart_disable(UART_Instance *instance);

/**
 * @brief 단일 바이트 송신 (폴링)
 * @param instance UART 인스턴스 포인터
 * @param data 송신 데이터
 * @details DREIF 플래그 확인 후 TXDATAL에 데이터 작성
 */
void uart_transmit(UART_Instance *instance, uint8_t data);

/**
 * @brief 단일 바이트 수신 (폴링)
 * @param instance UART 인스턴스 포인터
 * @return 수신 데이터
 * @details RXCIF 플래그 확인 후 RXDATAL 읽기
 */
uint8_t uart_receive(UART_Instance *instance);

/**
 * @brief 폴링 기반 링버퍼 송신
 * @param instance UART 인스턴스 포인터
 * @param data 송신 데이터
 * @param length 데이터 길이
 * @return 성공 여부 (0: 성공, 1: 버퍼 가득참)
 * @details 링버퍼에 데이터 저장 후 순차 송신
 */
uint8_t uart_transmit_buffered(UART_Instance *instance, uint8_t *data, uint8_t length);

/**
 * @brief 폴링 기반 링버퍼 수신
 * @param instance UART 인스턴스 포인터
 * @param data 수신 데이터 버퍼
 * @param length 읽을 데이터 길이
 * @return 수신된 바이트 수
 * @details 링버퍼에서 데이터 읽기
 */
uint8_t uart_receive_buffered(UART_Instance *instance, uint8_t *data, uint8_t length);

/**
 * @brief 인터럽트 기반 송신 초기화
 * @param instance UART 인스턴스 포인터
 * @details DREIE 비트를 설정하여 송신 인터럽트 활성화
 */
void uart_transmit_it_init(UART_Instance *instance);

/**
 * @brief 인터럽트 기반 수신 초기화
 * @param instance UART 인스턴스 포인터
 * @details RXCIE 비트를 설정하여 수신 인터럽트 활성화
 */
void uart_receive_it_init(UART_Instance *instance);

/**
 * @brief 인터럽트 기반 데이터 송신
 * @param instance UART 인스턴스 포인터
 * @param data 송신 데이터
 * @param length 데이터 길이
 * @return 성공 여부 (0: 성공, 1: 버퍼 가득참)
 * @details 링버퍼에 데이터 저장 후 인터럽트로 송신
 */
uint8_t uart_transmit_it(UART_Instance *instance, uint8_t *data, uint8_t length);

/**
 * @brief 인터럽트 기반 데이터 수신
 * @param instance UART 인스턴스 포인터
 * @param data 수신 데이터 버퍼
 * @param length 읽을 데이터 길이
 * @return 수신된 바이트 수
 * @details 링버퍼에서 데이터 읽기
 */
uint8_t uart_receive_it(UART_Instance *instance, uint8_t *data, uint8_t length);

/**
 * @brief UART 에러 상태 확인 및 클리어
 * @param instance UART 인스턴스 포인터
 * @return 에러 플래그 (UART_ERROR_FERR | UART_ERROR_PERR | UART_ERROR_BUFOVF)
 * @details RXDATAH의 에러 플래그 확인 및 클리어
 */
uint8_t uart_check_errors(UART_Instance *instance);

/**
 * @brief printf를 UART로 리디렉션
 * @param instance UART 인스턴스 포인터
 * @details putchar를 재정의하여 UART로 출력
 */
void uart_setup_stdio(UART_Instance *instance);

/**
 * @brief 수신 링버퍼의 가용 바이트 수 확인
 * @param instance UART 인스턴스 포인터
 * @return 읽기 가능한 바이트 수
 * @details 링버퍼의 head와 tail로 가용 바이트 계산
 */
uint8_t uart_available(UART_Instance *instance);

/**
 * @brief 송수신 링버퍼 비우기
 * @param instance UART 인스턴스 포인터
 * @details head, tail을 초기화하고 tx_busy 플래그 클리어
 */
void uart_flush(UART_Instance *instance);

#endif // UART_DRIVER_H
/**
 * @file uart_driver.c
 * @brief AVR128DA64/48/32/28 UART 드라이버 구현
 * @details USARTn 레지스터로 비동기/동기 통신 제어. 링버퍼(폴링/인터럽트), 
 *          printf, 가용 바이트 확인, 버퍼 비우기, 에러 처리, 다중 USART 지원.
 * @author 작성자
 * @date 2025-09-03
 */
#include "uart_driver.h"
#include <avr/interrupt.h> // 인터럽트 처리 헤더

/**
 * @brief UART 인스턴스 배열
 * @details 최대 6개 USART 인스턴스 저장 (AVR128DA64 기준)
 */
static UART_Instance *uart_instances[6] = {NULL}; // 초기화 안된 인스턴스 방지

/**
 * @brief printf용 USART 포인터
 * @details printf 출력에 사용할 USART 모듈
 */
static USART_t *stdio_usart;

/**
 * @brief UART 보드레이트 계산
 * @param baud 보드레이트 (예: 9600)
 * @param clk2x CLK2x 모드 (0: 비활성, 1: 활성)
 * @return BAUD 레지스터 값
 * @details BAUD = (64 * F_PER) / (S * BAUDRATE), S=16(정상), 8(CLK2x)
 */
static uint16_t calculate_baud(uint32_t baud, uint8_t clk2x) {
    uint8_t s = clk2x ? 8 : 16; // 샘플링 비율 (CLK2x 여부)
    return (uint16_t)((64.0 * F_CPU) / (s * baud)); // BAUD 계산
}

/**
 * @brief UART 인스턴스 등록
 * @param instance UART 인스턴스 포인터
 * @param usart USART 레지스터 포인터
 * @details 인터럽트 핸들러에서 인스턴스 참조를 위해 배열에 저장
 */
static void register_instance(UART_Instance *instance, USART_t *usart) {
    if (usart == &USART0) uart_instances[0] = instance;
    else if (usart == &USART1) uart_instances[1] = instance;
    else if (usart == &USART2) uart_instances[2] = instance;
    else if (usart == &USART3) uart_instances[3] = instance;
    else if (usart == &USART4) uart_instances[4] = instance;
    else if (usart == &USART5) uart_instances[5] = instance;
}

/**
 * @brief UART 초기화
 * @details PORTMUX로 핀 설정, 보드레이트 계산, 프레임 포맷 설정, 링버퍼 초기화
 */
void uart_init(UART_Instance *instance, USART_t *usart, uint32_t baud, uint8_t data_bits, uint8_t stop_bits, uint8_t parity, uint8_t mode, uint8_t portmux) {
    // PORTMUX 설정 및 GPIO 방향 설정
    if (usart == &USART0) {
        PORTMUX.USARTROUTEA = (PORTMUX.USARTROUTEA & ~(0x3 << 0)) | (portmux << 0); // USART0 PORTMUX 설정
        if (portmux == UART_PORTMUX_DEFAULT) {
            PORTA.DIRSET = (1 << 0); // PA0(TxD) 출력
            PORTA.DIRCLR = (1 << 1); // PA1(RxD) 입력
            if (mode == UART_MODE_SYNC) PORTA.DIRSET = (1 << 2); // PA2(XCK) 출력
        } else if (portmux == UART_PORTMUX_ALT1) {
            PORTA.DIRSET = (1 << 2); // PA2(TxD) 출력
            PORTA.DIRCLR = (1 << 3); // PA3(RxD) 입력
            if (mode == UART_MODE_SYNC) PORTA.DIRSET = (1 << 4); // PA4(XCK) 출력
        } else if (portmux == UART_PORTMUX_ALT2) {
            PORTC.DIRSET = (1 << 0); // PC0(TxD) 출력
            PORTC.DIRCLR = (1 << 1); // PC1(RxD) 입력
            if (mode == UART_MODE_SYNC) PORTC.DIRSET = (1 << 2); // PC2(XCK) 출력
        }
    } else if (usart == &USART1) {
        PORTMUX.USARTROUTEA = (PORTMUX.USARTROUTEA & ~(0x3 << 2)) | (portmux << 2); // USART1 PORTMUX 설정
        if (portmux == UART_PORTMUX_DEFAULT) {
            PORTC.DIRSET = (1 << 0); // PC0(TxD) 출력
            PORTC.DIRCLR = (1 << 1); // PC1(RxD) 입력
            if (mode == UART_MODE_SYNC) PORTC.DIRSET = (1 << 2); // PC2(XCK) 출력
        } else if (portmux == UART_PORTMUX_ALT1) {
            PORTC.DIRSET = (1 << 2); // PC2(TxD) 출력
            PORTC.DIRCLR = (1 << 3); // PC3(RxD) 입력
            if (mode == UART_MODE_SYNC) PORTC.DIRSET = (1 << 4); // PC4(XCK) 출력
        } else if (portmux == UART_PORTMUX_ALT2) {
            PORTA.DIRSET = (1 << 0); // PA0(TxD) 출력
            PORTA.DIRCLR = (1 << 1); // PA1(RxD) 입력
            if (mode == UART_MODE_SYNC) PORTA.DIRSET = (1 << 2); // PA2(XCK) 출력
        }
    } else if (usart == &USART2) {
        PORTMUX.USARTROUTEA = (PORTMUX.USARTROUTEA & ~(0x3 << 4)) | (portmux << 4); // USART2 PORTMUX 설정
        if (portmux == UART_PORTMUX_DEFAULT) {
            PORTF.DIRSET = (1 << 0); // PF0(TxD) 출력
            PORTF.DIRCLR = (1 << 1); // PF1(RxD) 입력
            if (mode == UART_MODE_SYNC) PORTF.DIRSET = (1 << 2); // PF2(XCK) 출력
        } else if (portmux == UART_PORTMUX_ALT1) {
            PORTF.DIRSET = (1 << 2); // PF2(TxD) 출력
            PORTF.DIRCLR = (1 << 3); // PF3(RxD) 입력
            if (mode == UART_MODE_SYNC) PORTF.DIRSET = (1 << 4); // PF4(XCK) 출력
        }
    } else if (usart == &USART3) {
        PORTMUX.USARTROUTEA = (PORTMUX.USARTROUTEA & ~(0x3 << 6)) | (portmux << 6); // USART3 PORTMUX 설정
        if (portmux == UART_PORTMUX_DEFAULT) {
            PORTB.DIRSET = (1 << 0); // PB0(TxD) 출력
            PORTB.DIRCLR = (1 << 1); // PB1(RxD) 입력
            if (mode == UART_MODE_SYNC) PORTB.DIRSET = (1 << 2); // PB2(XCK) 출력
        } else if (portmux == UART_PORTMUX_ALT1) {
            PORTB.DIRSET = (1 << 2); // PB2(TxD) 출력
            PORTB.DIRCLR = (1 << 3); // PB3(RxD) 입력
            if (mode == UART_MODE_SYNC) PORTB.DIRSET = (1 << 4); // PB4(XCK) 출력
        }
    } else if (usart == &USART4) {
        PORTMUX.USARTROUTEB = (PORTMUX.USARTROUTEB & ~(0x3 << 0)) | (portmux << 0); // USART4 PORTMUX 설정
        if (portmux == UART_PORTMUX_DEFAULT) {
            PORTD.DIRSET = (1 << 0); // PD0(TxD) 출력
            PORTD.DIRCLR = (1 << 1); // PD1(RxD) 입력
            if (mode == UART_MODE_SYNC) PORTD.DIRSET = (1 << 2); // PD2(XCK) 출력
        } else if (portmux == UART_PORTMUX_ALT1) {
            PORTD.DIRSET = (1 << 2); // PD2(TxD) 출력
            PORTD.DIRCLR = (1 << 3); // PD3(RxD) 입력
            if (mode == UART_MODE_SYNC) PORTD.DIRSET = (1 << 4); // PD4(XCK) 출력
        }
    } else if (usart == &USART5) {
        PORTMUX.USARTROUTEB = (PORTMUX.USARTROUTEB & ~(0x3 << 2)) | (portmux << 2); // USART5 PORTMUX 설정
        if (portmux == UART_PORTMUX_DEFAULT) {
            PORTE.DIRSET = (1 << 0); // PE0(TxD) 출력
            PORTE.DIRCLR = (1 << 1); // PE1(RxD) 입력
            if (mode == UART_MODE_SYNC) PORTE.DIRSET = (1 << 2); // PE2(XCK) 출력
        } else if (portmux == UART_PORTMUX_ALT1) {
            PORTE.DIRSET = (1 << 2); // PE2(TxD) 출력
            PORTE.DIRCLR = (1 << 3); // PE3(RxD) 입력
            if (mode == UART_MODE_SYNC) PORTE.DIRSET = (1 << 4); // PE4(XCK) 출력
        }
    }

    // 보드레이트 설정
    uint8_t clk2x = (mode == UART_MODE_ASYNC && (usart->CTRLB & (1 << 1))); // CLK2x 모드 확인
    usart->BAUD = calculate_baud(baud, clk2x); // BAUD 레지스터 설정

    // 프레임 포맷 설정
    if (mode == UART_MODE_ASYNC) {
        usart->CTRLC = (mode << 7) | (parity << 4) | (stop_bits << 3) | (data_bits << 0); // 비동기 모드 설정
    } else if (mode == UART_MODE_SYNC) {
        uint8_t udord = 0; // MSB 먼저 (기본값)
        uint8_t ucpha = 0; // 선행 에지 샘플링 (기본값)
        usart->CTRLC = (mode << 7) | (udord << 5) | (ucpha << 4) | (data_bits << 0); // 동기 모드 설정
    }

    // 송수신 비활성화 (초기화 전)
    usart->CTRLB = 0x00;

    // 에러 플래그 초기화
    if (usart->RXDATAH & UART_ERROR_FERR) usart->RXDATAH |= UART_ERROR_FERR; // 프레임 에러 클리어
    if (usart->RXDATAH & UART_ERROR_PERR) usart->RXDATAH |= UART_ERROR_PERR; // 패리티 에러 클리어
    if (usart->RXDATAH & UART_ERROR_BUFOVF) usart->RXDATAH |= UART_ERROR_BUFOVF; // 버퍼 오버플로우 클리어

    // 링버퍼 및 인스턴스 초기화
    instance->tx_buffer.head = instance->tx_buffer.tail = 0; // 송신 링버퍼 초기화
    instance->rx_buffer.head = instance->rx_buffer.tail = 0; // 수신 링버퍼 초기화
    instance->tx_busy = 0; // 송신 진행 상태 초기화
    instance->usart = usart; // USART 포인터 저장
    stdio_usart = usart; // printf용 USART 설정
    register_instance(instance, usart); // 인스턴스 등록
}

/**
 * @brief UART 송신기 활성화
 * @details CTRLB의 TXEN 비트를 설정
 */
void uart_enable_tx(UART_Instance *instance) {
    instance->usart->CTRLB |= (1 << 5); // TXEN=1
}

/**
 * @brief UART 수신기 활성화
 * @details CTRLB의 RXEN 비트를 설정
 */
void uart_enable_rx(UART_Instance *instance) {
    instance->usart->CTRLB |= (1 << 6); // RXEN=1
}

/**
 * @brief UART 비활성화
 * @details TXEN, RXEN 비트를 클리어
 */
void uart_disable(UART_Instance *instance) {
    instance->usart->CTRLB = 0x00; // TXEN=0, RXEN=0
}

/**
 * @brief 단일 바이트 송신 (폴링)
 * @details DREIF 플래그 확인 후 TXDATAL에 데이터 작성
 */
void uart_transmit(UART_Instance *instance, uint8_t data) {
    while (!(instance->usart->STATUS & USART_DREIF_bm)); // 데이터 레지스터 비움 대기
    instance->usart->TXDATAL = data; // 데이터 송신
}

/**
 * @brief 단일 바이트 수신 (폴링)
 * @details RXCIF 플래그 확인 후 RXDATAL 읽기
 */
uint8_t uart_receive(UART_Instance *instance) {
    while (!(instance->usart->STATUS & USART_RXCIF_bm)); // 수신 완료 대기
    return instance->usart->RXDATAL; // 데이터 읽기
}

/**
 * @brief 폴링 기반 링버퍼 송신
 * @details 데이터를 링버퍼에 저장 후 순차적으로 송신
 */
uint8_t uart_transmit_buffered(UART_Instance *instance, uint8_t *data, uint8_t length) {
    uint8_t i;
    for (i = 0; i < length; i++) {
        uint8_t next_head = (instance->tx_buffer.head + 1) % UART_BUFFER_SIZE; // 다음 head 계산
        if (next_head == instance->tx_buffer.tail) return 1; // 버퍼 가득참
        instance->tx_buffer.buffer[instance->tx_buffer.head] = data[i]; // 데이터 저장
        instance->tx_buffer.head = next_head; // head 갱신
    }
    // 링버퍼에서 데이터 송신
    while (instance->tx_buffer.head != instance->tx_buffer.tail) {
        while (!(instance->usart->STATUS & USART_DREIF_bm)); // 데이터 레지스터 비움 대기
        instance->usart->TXDATAL = instance->tx_buffer.buffer[instance->tx_buffer.tail]; // 데이터 송신
        instance->tx_buffer.tail = (instance->tx_buffer.tail + 1) % UART_BUFFER_SIZE; // tail 갱신
    }
    return 0; // 성공
}

/**
 * @brief 폴링 기반 링버퍼 수신
 * @details 하드웨어 수신 데이터를 링버퍼에 저장 후 읽기
 */
uint8_t uart_receive_buffered(UART_Instance *instance, uint8_t *data, uint8_t length) {
    uint8_t count = 0;
    // 하드웨어에서 수신 데이터 링버퍼에 저장
    while (count < length && (instance->usart->STATUS & USART_RXCIF_bm)) {
        uint8_t next_head = (instance->rx_buffer.head + 1) % UART_BUFFER_SIZE; // 다음 head 계산
        if (next_head != instance->rx_buffer.tail) { // 버퍼 가득차지 않음
            instance->rx_buffer.buffer[instance->rx_buffer.head] = instance->usart->RXDATAL; // 데이터 저장
            instance->rx_buffer.head = next_head; // head 갱신
        } else {
            (void)instance->usart->RXDATAL; // 버퍼 오버플로우 방지
            break;
        }
    }
    // 링버퍼에서 데이터 읽기
    count = 0;
    while (instance->rx_buffer.head != instance->rx_buffer.tail && count < length) {
        data[count++] = instance->rx_buffer.buffer[instance->rx_buffer.tail]; // 데이터 읽기
        instance->rx_buffer.tail = (instance->rx_buffer.tail + 1) % UART_BUFFER_SIZE; // tail 갱신
    }
    return count; // 수신된 바이트 수 반환
}

/**
 * @brief 인터럽트 기반 송신 초기화
 * @details DREIE 비트를 설정하여 송신 인터럽트 활성화
 */
void uart_transmit_it_init(UART_Instance *instance) {
    instance->usart->CTRLA |= (1 << 5); // DREIE 활성화
}

/**
 * @brief 인터럽트 기반 수신 초기화
 * @details RXCIE 비트를 설정하여 수신 인터럽트 활성화
 */
void uart_receive_it_init(UART_Instance *instance) {
    instance->usart->CTRLA |= (1 << 7); // RXCIE 활성화
}

/**
 * @brief 인터럽트 기반 데이터 송신
 * @details 링버퍼에 데이터 저장 후 인터럽트로 송신 시작
 */
uint8_t uart_transmit_it(UART_Instance *instance, uint8_t *data, uint8_t length) {
    uint8_t i;
    for (i = 0; i < length; i++) {
        uint8_t next_head = (instance->tx_buffer.head + 1) % UART_BUFFER_SIZE; // 다음 head 계산
        if (next_head == instance->tx_buffer.tail) return 1; // 버퍼 가득참
        instance->tx_buffer.buffer[instance->tx_buffer.head] = data[i]; // 데이터 저장
        instance->tx_buffer.head = next_head; // head 갱신
    }
    if (!instance->tx_busy) {
        instance->tx_busy = 1; // 송신 시작
        instance->usart->TXDATAL = instance->tx_buffer.buffer[instance->tx_buffer.tail]; // 첫 데이터 송신
        instance->tx_buffer.tail = (instance->tx_buffer.tail + 1) % UART_BUFFER_SIZE; // tail 갱신
    }
    return 0; // 성공
}

/**
 * @brief 인터럽트 기반 데이터 수신
 * @details 링버퍼에서 데이터 읽기
 */
uint8_t uart_receive_it(UART_Instance *instance, uint8_t *data, uint8_t length) {
    uint8_t count = 0;
    while (instance->rx_buffer.head != instance->rx_buffer.tail && count < length) {
        data[count++] = instance->rx_buffer.buffer[instance->rx_buffer.tail]; // 데이터 읽기
        instance->rx_buffer.tail = (instance->rx_buffer.tail + 1) % UART_BUFFER_SIZE; // tail 갱신
    }
    return count; // 수신된 바이트 수 반환
}

/**
 * @brief UART 에러 상태 확인 및 클리어
 * @details RXDATAH의 에러 플래그 확인 및 클리어
 */
uint8_t uart_check_errors(UART_Instance *instance) {
    uint8_t errors = instance->usart->RXDATAH & (UART_ERROR_FERR | UART_ERROR_PERR | UART_ERROR_BUFOVF); // 에러 플래그 읽기
    if (errors & UART_ERROR_FERR) instance->usart->RXDATAH |= UART_ERROR_FERR; // FERR 클리어
    if (errors & UART_ERROR_PERR) instance->usart->RXDATAH |= UART_ERROR_PERR; // PERR 클리어
    if (errors & UART_ERROR_BUFOVF) instance->usart->RXDATAH |= UART_ERROR_BUFOVF; // BUFOVF 클리어
    return errors; // 에러 플래그 반환
}

/**
 * @brief printf를 UART로 리디렉션
 * @details putchar를 재정의하여 UART로 출력
 */
void uart_setup_stdio(UART_Instance *instance) {
    stdio_usart = instance->usart; // printf용 USART 설정
}

/**
 * @brief 수신 링버퍼의 가용 바이트 수 확인
 * @details head와 tail의 차이로 가용 바이트 계산
 */
uint8_t uart_available(UART_Instance *instance) {
    return (UART_BUFFER_SIZE + instance->rx_buffer.head - instance->rx_buffer.tail) % UART_BUFFER_SIZE; // 가용 바이트 수 반환
}

/**
 * @brief 송수신 링버퍼 비우기
 * @details head, tail 초기화 및 tx_busy 클리어
 */
void uart_flush(UART_Instance *instance) {
    instance->tx_buffer.head = instance->tx_buffer.tail = 0; // 송신 링버퍼 초기화
    instance->rx_buffer.head = instance->rx_buffer.tail = 0; // 수신 링버퍼 초기화
    instance->tx_busy = 0; // 송신 진행 상태 초기화
    instance->usart->CTRLA &= ~(1 << 5); // DREIE 비활성화
}

/**
 * @brief putchar 재정의로 UART 출력
 * @details printf가 UART로 출력되도록 구현
 */
int putchar(int c) {
    uart_transmit(&((UART_Instance){.usart = stdio_usart}), (uint8_t)c); // 문자 송신
    return c; // 송신된 문자 반환
}

/**
 * @brief 범용 송신 데이터 레지스터 비움 인터럽트 핸들러
 * @details 링버퍼에서 데이터 송신, 버퍼 비우면 인터럽트 비활성화
 */
static void uart_dre_handler(UART_Instance *instance) {
    if (instance->tx_buffer.head != instance->tx_buffer.tail) {
        instance->usart->TXDATAL = instance->tx_buffer.buffer[instance->tx_buffer.tail]; // 다음 데이터 송신
        instance->tx_buffer.tail = (instance->tx_buffer.tail + 1) % UART_BUFFER_SIZE; // tail 갱신
    } else {
        instance->tx_busy = 0; // 송신 완료
        instance->usart->CTRLA &= ~(1 << 5); // DREIE 비활성화
    }
}

/**
 * @brief 범용 수신 완료 인터럽트 핸들러
 * @details 수신 데이터를 링버퍼에 저장
 */
static void uart_rxc_handler(UART_Instance *instance) {
    uint8_t next_head = (instance->rx_buffer.head + 1) % UART_BUFFER_SIZE; // 다음 head 계산
    if (next_head != instance->rx_buffer.tail) { // 버퍼 가득차지 않음
        instance->rx_buffer.buffer[instance->rx_buffer.head] = instance->usart->RXDATAL; // 데이터 저장
        instance->rx_buffer.head = next_head; // head 갱신
    } else {
        (void)instance->usart->RXDATAL; // 버퍼 오버플로우 방지
    }
}

/**
 * @brief USART 인터럽트 핸들러 정의
 * @details ISR_NAKED와 ISR_ALIASOF로 다중 USART 지원
 */
ISR(USART0_DRE_vect, ISR_NAKED) {
    if (uart_instances[0]) uart_dre_handler(uart_instances[0]); // USART0 송신 인터럽트 처리
    reti(); // 인터럽트 종료
}
ISR(USART0_RXC_vect, ISR_NAKED) {
    if (uart_instances[0]) uart_rxc_handler(uart_instances[0]); // USART0 수신 인터럽트 처리
    reti(); // 인터럽트 종료
}
ISR(USART1_DRE_vect, ISR_ALIASOF(USART0_DRE_vect)); // USART1 송신 인터럽트 (USART0 재사용)
ISR(USART1_RXC_vect, ISR_ALIASOF(USART0_RXC_vect)); // USART1 수신 인터럽트 (USART0 재사용)
ISR(USART2_DRE_vect, ISR_ALIASOF(USART0_DRE_vect)); // USART2 송신 인터럽트 (USART0 재사용)
ISR(USART2_RXC_vect, ISR_ALIASOF(USART0_RXC_vect)); // USART2 수신 인터럽트 (USART0 재사용)
ISR(USART3_DRE_vect, ISR_ALIASOF(USART0_DRE_vect)); // USART3 송신 인터럽트 (USART0 재사용)
ISR(USART3_RXC_vect, ISR_ALIASOF(USART0_RXC_vect)); // USART3 수신 인터럽트 (USART0 재사용)
#if defined(USART4)
ISR(USART4_DRE_vect, ISR_ALIASOF(USART0_DRE_vect)); // USART4 송신 인터럽트 (USART0 재사용)
ISR(USART4_RXC_vect, ISR_ALIASOF(USART0_RXC_vect)); // USART4 수신 인터럽트 (USART0 재사용)
#endif
#if defined(USART5)
ISR(USART5_DRE_vect, ISR_ALIASOF(USART0_DRE_vect)); // USART5 송신 인터럽트 (USART0 재사용)
ISR(USART5_RXC_vect, ISR_ALIASOF(USART0_RXC_vect)); // USART5 수신 인터럽트 (USART0 재사용)
#endif
/**
 * @file main.c
 * @brief AVR128DA64/48/32/28 UART 드라이버 테스트 프로그램
 * @details 24MHz 클럭, USART0/1으로 printf, 폴링 기반 링버퍼, 
 *          가용 바이트 확인, 버퍼 비우기, 인터럽트 기반 테스트
 * @author 작성자
 * @date 2025-09-03
 */
#ifndef F_CPU
#define F_CPU 24000000UL // 시스템 클럭 주파수 24MHz 정의
#endif

#include <avr/io.h>      // AVR 레지스터 정의
#include <util/delay.h>  // 지연 함수
#include <stdio.h>       // printf 지원
#include <avr/interrupt.h> // 인터럽트 활성화
#include "uart_driver.h" // UART 드라이버 헤더

/**
 * @brief 시스템 클럭을 24MHz OSCHF로 설정하고 Auto-tune 활성화
 * @details 데이터시트(DS40002183A) 기반:
 *          - XOSCHFCTRLA: 32.768kHz 크리스털 활성 (ENABLE=1, SEL=0, RUNSTDBY=0).
 *          - OSCHCTRLA: FRQSEL=0x9(24MHz), AUTOTUNE=1.
 *          - MCLKCTRLB: 프리스케일러 비활성 (PEN=0).
 *          - MCLKCTRLA: OSCHF 선택 (CLKSEL=0x0).
 *          - _PROTECTED_WRITE로 CCP 보호.
 * @note TOSC1/TOSC2(PF6/PF7)에 32.768kHz 크리스털 연결 필수
 */
void clock_init_24mhz(void) {
    _PROTECTED_WRITE(CLKCTRL.XOSCHFCTRLA, CLKCTRL_ENABLE_bm | (0 << CLKCTRL_SEL_bp)); // 32.768kHz 크리스털 활성
    _PROTECTED_WRITE(CLKCTRL.OSCHCTRLA, (0x9 << CLKCTRL_FRQSEL_gp) | CLKCTRL_AUTOTUNE_bm); // 24MHz, Auto-tune
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00); // 프리스케일러 비활성
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, 0x00); // OSCHF 선택
    while ((CLKCTRL.MCLKSTATUS & CLKCTRL_OSCHF_bm) == 0); // 안정화 대기
}

/**
 * @brief 메인 함수
 * @details USART0/1으로 9600bps, 8-N-1 설정 후 폴링 및 인터럽트 기반 링버퍼 테스트
 */
int main(void) {
    clock_init_24mhz(); // 클럭 설정
    
    // 전역 인터럽트 활성화
    sei();

    // USART0 초기화: 9600bps, 8비트, 1 스톱 비트, 패리티 없음, 비동기, ALT1
    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_ALT1);
    uart_enable_tx(&usart0_instance); // 송신 활성화
    uart_enable_rx(&usart0_instance); // 수신 활성화
    uart_setup_stdio(&usart0_instance); // printf 설정

    // USART1 초기화: 인터럽트 기반, 9600bps, 8비트, 1 스톱 비트, 패리티 없음, 비동기, DEFAULT
    UART_Instance usart1_instance;
    uart_init(&usart1_instance, &USART1, UART_BAUD_9600, UART_DATA_8BIT, UART_STOP_1BIT, UART_PARITY_NONE, UART_MODE_ASYNC, UART_PORTMUX_DEFAULT);
    uart_enable_tx(&usart1_instance); // 송신 활성화
    uart_enable_rx(&usart1_instance); // 수신 활성화
    uart_transmit_it_init(&usart1_instance); // 송신 인터럽트 활성화
    uart_receive_it_init(&usart1_instance); // 수신 인터럽트 활성화

    // 테스트 메시지
    printf("UART Driver Test: USART0 (Polling) and USART1 (Interrupt)\r\n");
    _delay_ms(1000);

    // 폴링 기반 테스트 (USART0)
    uint8_t test_data[] = "Hello, AVR128DA!\r\n";
    if (uart_transmit_buffered(&usart0_instance, test_data, sizeof(test_data) - 1) == 0) {
        printf("USART0: Sent %d bytes successfully\r\n", sizeof(test_data) - 1);
    } else {
        printf("USART0: Transmit buffer full!\r\n");
    }
    _delay_ms(1000);

    // 인터럽트 기반 테스트 (USART1)
    if (uart_transmit_it(&usart1_instance, test_data, sizeof(test_data) - 1) == 0) {
        printf("USART1: Sent %d bytes via interrupt\r\n", sizeof(test_data) - 1);
    } else {
        printf("USART1: Transmit buffer full!\r\n");
    }
    _delay_ms(1000);

    // 수신 및 에러 테스트
    uint8_t rx_buffer[UART_BUFFER_SIZE];
    while (1) {
        // USART0: 폴링 기반 수신
        uint8_t rx_count = uart_receive_buffered(&usart0_instance, rx_buffer, sizeof(rx_buffer));
        if (rx_count > 0) {
            printf("USART0: Received %d bytes: ", rx_count);
            for (uint8_t i = 0; i < rx_count; i++) {
                printf("%c", rx_buffer[i]);
            }
            printf("\r\n");
        }

        // USART1: 인터럽트 기반 수신
        rx_count = uart_receive_it(&usart1_instance, rx_buffer, sizeof(rx_buffer));
        if (rx_count > 0) {
            printf("USART1: Received %d bytes: ", rx_count);
            for (uint8_t i = 0; i < rx_count; i++) {
                printf("%c", rx_buffer[i]);
            }
            printf("\r\n");
        }

        // 에러 확인
        uint8_t errors = uart_check_errors(&usart0_instance);
        if (errors) {
            printf("USART0 Errors: %s%s%s\r\n",
                   (errors & UART_ERROR_FERR) ? "Frame Error " : "",
                   (errors & UART_ERROR_PERR) ? "Parity Error " : "",
                   (errors & UART_ERROR_BUFOVF) ? "Buffer Overflow" : "");
        }

        errors = uart_check_errors(&usart1_instance);
        if (errors) {
            printf("USART1 Errors: %s%s%s\r\n",
                   (errors & UART_ERROR_FERR) ? "Frame Error " : "",
                   (errors & UART_ERROR_PERR) ? "Parity Error " : "",
                   (errors & UART_ERROR_BUFOVF) ? "Buffer Overflow" : "");
        }

        // 가용 바이트 확인
        printf("USART0 Available: %d bytes\r\n", uart_available(&usart0_instance));
        printf("USART1 Available: %d bytes\r\n", uart_available(&usart1_instance));

        // 주기적으로 버퍼 비우기 테스트
        if (uart_available(&usart0_instance) > UART_BUFFER_SIZE / 2) {
            printf("Flushing USART0 buffer\r\n");
            uart_flush(&usart0_instance);
        }
        if (uart_available(&usart1_instance) > UART_BUFFER_SIZE / 2) {
            printf("Flushing USART1 buffer\r\n");
            uart_flush(&usart1_instance);
        }

        _delay_ms(1000); // 1초 대기
    }

    return 0; // 프로그램 종료 (무한 루프이므로 도달하지 않음)
}
/**
 * @file uart_echo_buffered_example.c
 * @brief AVR128DA UART 드라이버 폴링 기반 에코 예제
 * @details USART0을 사용하여 수신 데이터를 링버퍼로 받아 동일 데이터를 송신 (에코).
 *          9600bps, 8-N-1, 비동기, DEFAULT 핀 설정 사용.
 * @author 작성자
 * @date 2025-09-03
 */
#ifndef F_CPU
#define F_CPU 24000000UL // 시스템 클럭 주파수 24MHz 정의
#endif

#include <avr/io.h>      // AVR 레지스터 정의
#include <util/delay.h>  // 지연 함수
#include <stdio.h>       // printf 지원
#include "uart_driver.h" // UART 드라이버 헤더

/**
 * @brief 시스템 클럭을 24MHz OSCHF로 설정하고 Auto-tune 활성화
 * @details 데이터시트(DS40002183A) 기반:
 *          - XOSCHFCTRLA: 32.768kHz 크리스털 활성 (ENABLE=1, SEL=0, RUNSTDBY=0).
 *          - OSCHCTRLA: FRQSEL=0x9(24MHz), AUTOTUNE=1.
 *          - MCLKCTRLB: 프리스케일러 비활성 (PEN=0).
 *          - MCLKCTRLA: OSCHF 선택 (CLKSEL=0x0).
 *          - _PROTECTED_WRITE로 CCP 보호.
 * @note TOSC1/TOSC2(PF6/PF7)에 32.768kHz 크리스털 연결 필수
 */
void clock_init_24mhz(void) {
    _PROTECTED_WRITE(CLKCTRL.XOSCHFCTRLA, CLKCTRL_ENABLE_bm | (0 << CLKCTRL_SEL_bp)); // 32.768kHz 크리스털 활성
    _PROTECTED_WRITE(CLKCTRL.OSCHCTRLA, (0x9 << CLKCTRL_FRQSEL_gp) | CLKCTRL_AUTOTUNE_bm); // 24MHz, Auto-tune
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00); // 프리스케일러 비활성
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, 0x00); // OSCHF 선택
    while ((CLKCTRL.MCLKSTATUS & CLKCTRL_OSCHF_bm) == 0); // 안정화 대기
}

/**
 * @brief 메인 함수
 * @details USART0을 초기화하고, 수신 데이터를 수신 링버퍼에서 읽어 송신 링버퍼로 에코.
 */
int main(void) {
    clock_init_24mhz(); // 클럭 설정

    // USART0 초기화: 9600bps, 8비트, 1 스톱 비트, 패리티 없음, 비동기, DEFAULT
    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); // printf 설정

    printf("UART Echo Test: Send data to USART0 to see it echoed back\r\n");
    _delay_ms(1000);

    uint8_t rx_buffer[UART_BUFFER_SIZE];
    while (1) {
        // 수신 데이터 확인
        uint8_t rx_count = uart_receive_buffered(&usart0_instance, rx_buffer, sizeof(rx_buffer));
        if (rx_count > 0) {
            // 수신 데이터를 송신 링버퍼로 에코
            if (uart_transmit_buffered(&usart0_instance, rx_buffer, rx_count) == 0) {
                printf("Echoed %d bytes\r\n", rx_count);
            } else {
                printf("Transmit buffer full!\r\n");
            }
        }

        // 에러 확인
        uint8_t errors = uart_check_errors(&usart0_instance);
        if (errors) {
            printf("Errors: %s%s%s\r\n",
                   (errors & UART_ERROR_FERR) ? "Frame Error " : "",
                   (errors & UART_ERROR_PERR) ? "Parity Error " : "",
                   (errors & UART_ERROR_BUFOVF) ? "Buffer Overflow" : "");
        }

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

    return 0; // 프로그램 종료 (무한 루프이므로 도달하지 않음)
}
/**
 * @file uart_monitor_example.c
 * @brief AVR128DA UART 드라이버 인터럽트 기반 시리얼 모니터 예제
 * @details USART1을 사용하여 인터럽트 기반으로 데이터를 수신하고, 
 *          수신된 데이터를 16진수로 출력하며, 에러 상태와 가용 바이트를 모니터링.
 * @author 작성자
 * @date 2025-09-03
 */
#ifndef F_CPU
#define F_CPU 24000000UL // 시스템 클럭 주파수 24MHz 정의
#endif

#include <avr/io.h>      // AVR 레지스터 정의
#include <util/delay.h>  // 지연 함수
#include <stdio.h>       // printf 지원
#include <avr/interrupt.h> // 인터럽트 활성화
#include "uart_driver.h" // UART 드라이버 헤더

/**
 * @brief 시스템 클럭을 24MHz OSCHF로 설정하고 Auto-tune 활성화
 * @details 데이터시트(DS40002183A) 기반:
 *          - XOSCHFCTRLA: 32.768kHz 크리스털 활성 (ENABLE=1, SEL=0, RUNSTDBY=0).
 *          - OSCHCTRLA: FRQSEL=0x9(24MHz), AUTOTUNE=1.
 *          - MCLKCTRLB: 프리스케일러 비활성 (PEN=0).
 *          - MCLKCTRLA: OSCHF 선택 (CLKSEL=0x0).
 *          - _PROTECTED_WRITE로 CCP 보호.
 * @note TOSC1/TOSC2(PF6/PF7)에 32.768kHz 크리스털 연결 필수
 */
void clock_init_24mhz(void) {
    _PROTECTED_WRITE(CLKCTRL.XOSCHFCTRLA, CLKCTRL_ENABLE_bm | (0 << CLKCTRL_SEL_bp)); // 32.768kHz 크리스털 활성
    _PROTECTED_WRITE(CLKCTRL.OSCHCTRLA, (0x9 << CLKCTRL_FRQSEL_gp) | CLKCTRL_AUTOTUNE_bm); // 24MHz, Auto-tune
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00); // 프리스케일러 비활성
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, 0x00); // OSCHF 선택
    while ((CLKCTRL.MCLKSTATUS & CLKCTRL_OSCHF_bm) == 0); // 안정화 대기
}

/**
 * @brief 메인 함수
 * @details USART1을 인터럽트 기반으로 초기화하고, 수신 데이터를 16진수로 출력.
 *          주기적으로 에러 상태와 가용 바이트를 모니터링.
 */
int main(void) {
    clock_init_24mhz(); // 클럭 설정

    // 전역 인터럽트 활성화
    sei();

    // USART0 초기화: printf용, 9600bps, 8비트, 1 스톱 비트, 패리티 없음, 비동기, DEFAULT
    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); // printf 설정

    // USART1 초기화: 인터럽트 기반, 9600bps, 8비트, 1 스톱 비트, 패리티 없음, 비동기, DEFAULT
    UART_Instance usart1_instance;
    uart_init(&usart1_instance, &USART1, UART_BAUD_9600, UART_DATA_8BIT, UART_STOP_1BIT, UART_PARITY_NONE, UART_MODE_ASYNC, UART_PORTMUX_DEFAULT);
    uart_enable_tx(&usart1_instance); // 송신 활성화
    uart_enable_rx(&usart1_instance); // 수신 활성화
    uart_transmit_it_init(&usart1_instance); // 송신 인터럽트 활성화
    uart_receive_it_init(&usart1_instance); // 수신 인터럽트 활성화

    printf("UART Serial Monitor Test: Send data to USART1 to see hex output\r\n");
    _delay_ms(1000);

    uint8_t rx_buffer[UART_BUFFER_SIZE];
    while (1) {
        // 인터럽트 기반 수신
        uint8_t rx_count = uart_receive_it(&usart1_instance, rx_buffer, sizeof(rx_buffer));
        if (rx_count > 0) {
            printf("USART1: Received %d bytes (Hex): ", rx_count);
            for (uint8_t i = 0; i < rx_count; i++) {
                printf("%02X ", rx_buffer[i]);
            }
            printf("\r\n");
        }

        // 에러 확인
        uint8_t errors = uart_check_errors(&usart1_instance);
        if (errors) {
            printf("USART1 Errors: %s%s%s\r\n",
                   (errors & UART_ERROR_FERR) ? "Frame Error " : "",
                   (errors & UART_ERROR_PERR) ? "Parity Error " : "",
                   (errors & UART_ERROR_BUFOVF) ? "Buffer Overflow" : "");
        }

        // 가용 바이트 확인
        uint8_t available = uart_available(&usart1_instance);
        if (available > 0) {
            printf("USART1 Available: %d bytes\r\n", available);
        }

        // 버퍼가 절반 이상 차면 비우기
        if (available > UART_BUFFER_SIZE / 2) {
            printf("Flushing USART1 buffer\r\n");
            uart_flush(&usart1_instance);
        }

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

    return 0; 
}

추가팁

  • 보드레이트 최적화: 높은 보드레이트(예: 115200bps)를 사용할 경우 CLK2x 모드를 활성화하여 BAUD 값을 낮추고, 클럭 에러를 최소화하세요.
  • 인터럽트 우선순위: 다중 USART 사용 시, 인터럽트 충돌을 방지하려면 CPUINT의 우선순위를 조정하거나, 중요 USART의 인터럽트를 먼저 처리하도록 설계하세요.
  • 전력 절감: 사용하지 않는 USART는 uart_disable로 비활성화하여 전력을 절감하세요.
  • 핀 충돌 방지: PORTMUX 설정 시 SPI, TWI 등 다른 주변 장치와의 핀 충돌을 확인하세요. 데이터시트의 Pinout 섹션을 참조하세요.
  • 링버퍼 크기 조정: UART_BUFFER_SIZE를 애플리케이션 요구사항에 맞게 조정하여 메모리 사용을 최적화하세요.
  • 디버깅: uart_check_errorsprintf를 활용해 에러 상태를 주기적으로 모니터링하세요.
  • 동기 모드: 동기 모드 사용 시 XCK 핀 설정과 클럭 위상/데이터 순서를 정확히 지정하세요.
  • 테스트 환경: Microchip Studio에서 시뮬레이터를 사용하거나, 실제 하드웨어에서 터미널 프로그램(예: PuTTY)을 통해 출력 확인하세요.

결론

본 UART 드라이버는 AVR128DA64/48/32/28 시리즈의 USART 기능을 완벽히 활용하도록 설계되었습니다. 링버퍼 기반의 효율적인 데이터 관리, 폴링 및 인터럽트 지원, printf 출력, 가용 바이트 확인, 버퍼 비우기, 에러 처리 등 다양한 기능을 제공하며, 모든 AVR128DA 모델에 호환됩니다. 다중 USART(USART0~5)를 지원하는 인터럽트 핸들러와 PORTMUX를 통한 유연한 핀 설정으로 다양한 애플리케이션에 적용 가능합니다. 제공된 예제 코드를 통해 쉽게 테스트할 수 있으며, 추가팁을 참고하여 성능을 최적화할 수 있습니다. 이 드라이버는 임베디드 시스템 개발에서 신뢰할 수 있는 UART 통신 솔루션을 제공합니다.

 

반응형