개요
본 문서는 Microchip의 AVR128DA64/48/32/28 시리즈 마이크로컨트롤러에 내장된 UART(USART) 기능을 분석하고, 이를 활용할 수 있는 범용 드라이버를 설계 및 구현한 내용을 다룹니다. AVR128DA 시리즈는 최대 6개의 USART 모듈을 제공하며, 비동기/동기 통신, 다양한 보드레이트 설정, 패리티 및 스톱 비트 지원, 인터럽트 및 폴링 기반 송수신 기능을 포함합니다. 본 드라이버는 링버퍼를 기반으로 효율적인 데이터 송수신을 지원하며, printf를 통한 표준 출력 리디렉션, 가용 바이트 확인(uart_available), 링버퍼 초기화(uart_flush)와 같은 부가 기능을 제공합니다. 또한 AVR128DA64/48/32/28 전 제품군에 호환되도록 설계되어 Microchip Studio 및 AVR-GCC 환경에서 손쉽게 활용 가능합니다.
사양
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 설정은 다음 순서로 진행합니다:
- 클럭 설정: CLKCTRL로 CLK_PER 활성화 (예: 24MHz OSCHF).
- 포트 설정: PORTMUX로 TxD/RxD/XCK 핀 선택, GPIO 방향 설정 (TxD: 출력, RxD: 입력).
- 모드 설정: CTRLC로 비동기/동기, 패리티, 스톱 비트, 데이터 크기 설정.
- 보드레이트 설정: BAUD 레지스터로 보드레이트 계산 및 설정.
- 인터럽트 설정 (옵션): CTRLA로 RXCIE, TXCIE, DREIE 설정, STATUS 플래그 클리어.
- 송수신 활성화: CTRLB로 TXEN, RXEN 설정.
- 링버퍼 초기화: 송수신 링버퍼 초기화.
- printf 설정:
uart_setup_stdio
로printf
출력 리디렉션. - 멀티플렉싱 확인: PORTMUX로 다른 주변 장치와 충돌 방지.
- 테스트: 송신/수신 데이터(폴링/인터럽트), 에러 상태(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_buffered
와uart_receive_buffered
로 다중 바이트 송수신. - 가용 바이트 확인:
uart_available
로 수신 링버퍼의 읽기 가능한 바이트 수 반환. - 링버퍼 비우기:
uart_flush
로 송수신 링버퍼 초기화. - printf 지원:
uart_setup_stdio
로putchar
재정의, 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 드라이버를 사용하는 방법은 다음과 같습니다:
- 헤더 파일 포함: 프로젝트에
uart_driver.h
를 포함하고,<avr/io.h>
,<stdio.h>
,<util/delay.h>
를 추가합니다. - 클럭 설정:
main.c
의clock_init_24mhz
를 호출하여 24MHz OSCHF 클럭을 설정합니다. - 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);
- 송수신 활성화:
uart_enable_tx
와uart_enable_rx
를 호출하여 송수신을 활성화합니다. - printf 설정 (옵션):
uart_setup_stdio
로 UART를 통해printf
출력을 설정합니다. - 데이터 송수신:
- 폴링 기반:
uart_transmit_buffered
로 데이터 송신,uart_receive_buffered
로 데이터 수신. - 인터럽트 기반:
uart_transmit_it_init
와uart_receive_it_init
로 인터럽트 활성화 후,uart_transmit_it
와uart_receive_it
로 송수신.
- 폴링 기반:
- 가용 바이트 확인:
uart_available
로 수신 링버퍼의 읽기 가능한 바이트 수를 확인합니다. - 버퍼 비우기:
uart_flush
로 송수신 링버퍼를 초기화합니다. - 에러 처리:
uart_check_errors
로 프레임 에러(FERR), 패리티 에러(PERR), 버퍼 오버플로우(BUFOVF)를 확인하고 클리어합니다. - 다중 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_errors
와printf
를 활용해 에러 상태를 주기적으로 모니터링하세요. - 동기 모드: 동기 모드 사용 시 XCK 핀 설정과 클럭 위상/데이터 순서를 정확히 지정하세요.
- 테스트 환경: Microchip Studio에서 시뮬레이터를 사용하거나, 실제 하드웨어에서 터미널 프로그램(예: PuTTY)을 통해 출력 확인하세요.
결론
본 UART 드라이버는 AVR128DA64/48/32/28 시리즈의 USART 기능을 완벽히 활용하도록 설계되었습니다. 링버퍼 기반의 효율적인 데이터 관리, 폴링 및 인터럽트 지원, printf
출력, 가용 바이트 확인, 버퍼 비우기, 에러 처리 등 다양한 기능을 제공하며, 모든 AVR128DA 모델에 호환됩니다. 다중 USART(USART0~5)를 지원하는 인터럽트 핸들러와 PORTMUX를 통한 유연한 핀 설정으로 다양한 애플리케이션에 적용 가능합니다. 제공된 예제 코드를 통해 쉽게 테스트할 수 있으며, 추가팁을 참고하여 성능을 최적화할 수 있습니다. 이 드라이버는 임베디드 시스템 개발에서 신뢰할 수 있는 UART 통신 솔루션을 제공합니다.
'MCU > AVR' 카테고리의 다른 글
AVR128DA64/48/32/28 DAC 드라이버 설계 및 구현 (0) | 2025.09.04 |
---|---|
AVR128DA64/48/32/28 ADC 드라이버 설계 및 구현 (0) | 2025.09.04 |
AVR128DA64/48/32/28 SPI 드라이버 설계 및 구현 (0) | 2025.09.03 |
AVR128DA64/48/32/28 I2C (TWI) 드라이버 설계 및 구현 (0) | 2025.09.03 |
AVR128DA64/48/32/28 GPIO 드라이버 설계 및 구현 (0) | 2025.09.03 |
AVR128DB48 클럭 설정 상세 가이드 (0) | 2025.08.20 |
AVR128DB48 ZCD 사용 방법 및 예제 코드 (3) | 2025.08.20 |
AVR128DB48 ADC 차동모드 설정 방법 및 예제 코드 (0) | 2025.08.20 |