본문 바로가기
MCU/AVR

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

by linuxgo 2025. 9. 3.
반응형

개요

본 보고서는 Microchip AVR128DA64/48/32/28 시리즈 마이크로컨트롤러의 GPIO(General Purpose Input/Output) 기능을 분석하고 이를 활용할 수 있는 드라이버를 구현한 내용을 다룹니다. AVR128DA 시리즈는 최대 55개의 프로그래머블 I/O 핀을 제공하며, 각 핀은 입력/출력 방향 제어, 풀업/풀다운 저항, 인터럽트, 드라이브 강도, 슬루 레이트 제어 등의 다양한 기능을 지원합니다. 또한, **가상 포트(VPORT)**를 통한 싱글 사이클 액세스를 제공하여 고속 데이터 처리가 가능하고, 모든 슬립 모드에서 비동기 핀 변경 감지를 지원해 저전력 애플리케이션에도 적합합니다. 본 드라이버는 이러한 기능을 추상화하여, Microchip Studio 및 AVR-GCC 환경에서 AVR128DA64/48/32/28 전 모델에 호환되도록 설계되었습니다.

사양

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

  • 핀 수:
    • AVR128DA64: 최대 55개 (PORTA~PORTG).
    • AVR128DA48: 최대 41개 (PORTA~PORTF, PORTG 일부).
    • AVR128DA32: 최대 27개 (PORTA, PORTC, PORTD, PORTF).
    • AVR128DA28: 최대 23개 (PORTA, PORTC, PORTD, PORTF 일부).
  • 전압 범위: 1.8V ~ 5.5V (VDD).
  • 기능:
    • 입력/출력 방향 설정.
    • 풀업 저항, 입력 인버트, 슬루 레이트 제어.
    • 8비트 단위 출력 (PORTx.OUT, VPORTx.OUT).
    • 인터럽트: 모든 핀에서 지원, Px2/Px6은 비동기 감지로 모든 슬립 모드 웨이크업.
  • 클럭 의존성: Peripheral Clock (CLK_PER)로 동기화, 비동기 감지 지원.
  • 접근 방식:
    • PORTx: 확장 I/O 레지스터 (0x0400~).
    • VPORTx: 싱글 사이클 I/O (0x00~0x1F).
  • 리셋 상태: 모든 핀은 입력 모드, 디지털 입력 버퍼 활성, 출력 트라이스테이트.
  • 전력 최적화: 입력 버퍼 비활성화(ISC=INPUT_DISABLE)로 전력 절감.

GPIO Bitfield 설정 상세

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

  • PORTx.DIR (0x00):
    • 비트 [7:0]: DIR[7:0] (0: 입력, 1: 출력).
    • 리셋: 0x00, 읽기/쓰기(R/W).
  • PORTx.DIRSET (0x01):
    • 비트 [7:0]: DIRSET[7:0] (1 작성으로 출력 설정).
    • 리셋: 0x00, R/W.
  • PORTx.DIRCLR (0x02):
    • 비트 [7:0]: DIRCLR[7:0] (1 작성으로 입력 설정).
    • 리셋: 0x00, R/W.
  • PORTx.DIRTGL (0x03):
    • 비트 [7:0]: DIRTGL[7:0] (1 작성으로 방향 토글).
    • 리셋: 0x00, R/W.
  • PORTx.OUT (0x04):
    • 비트 [7:0]: OUT[7:0] (0: LOW, 1: HIGH).
    • 리셋: 0x00, R/W.
  • PORTx.OUTSET (0x05):
    • 비트 [7:0]: OUTSET[7:0] (1 작성으로 HIGH 설정).
    • 리셋: 0x00, R/W.
  • PORTx.OUTCLR (0x06):
    • 비트 [7:0]: OUTCLR[7:0] (1 작성으로 LOW 설정).
    • 리셋: 0x00, R/W.
  • PORTx.OUTTGL (0x07):
    • 비트 [7:0]: OUTTGL[7:0] (1 작성으로 출력 토글).
    • 리셋: 0x00, R/W.
  • PORTx.IN (0x08):
    • 비트 [7:0]: IN[7:0] (입력 상태).
    • 리셋: Undefined, 읽기 전용(R).
  • PORTx.INTFLAGS (0x09):
    • 비트 [7:0]: INT[7:0] (인터럽트 플래그, 1 작성으로 클리어).
    • 리셋: 0x00, R/W.
  • PORTx.PORTCTRL (0x0A):
    • 비트 [0]: SRL (0: 슬루 레이트 제한 비활성, 1: 활성).
    • 리셋: 0x00, R/W.
  • PORTx.PINCONFIG (0x0B):
    • 비트 [7]: INVEN (1: 입력 인버트).
    • 비트 [3]: PULLUPEN (1: 풀업 활성).
    • 비트 [2:0]: ISC[2:0] (입력 감지: 0x0=INTDISABLE, 0x1=BOTHEDGES, 0x2=RISING, 0x3=FALLING, 0x4=INPUT_DISABLE, 0x5=LEVEL).
    • 리셋: 0x00, R/W.
  • PORTx.PINCTRLUPD (0x0C):
    • 비트 [7:0]: PINCTRLUPD[7:0] (1 작성으로 PINCONFIG 적용).
    • 리셋: 0x00, R/W.
  • PORTx.PINCTRLSET (0x0D):
    • 비트 [7:0]: PINCTRLSET[7:0] (1 작성으로 PINCONFIG 설정).
    • 리셋: 0x00, R/W.
  • PORTx.PINCTRLCLR (0x0E):
    • 비트 [7:0]: PINCTRLCLR[7:0] (1 작성으로 PINCONFIG 클리어).
    • 리셋: 0x00, R/W.
  • PORTx.PIN0CTRL(0x10~x17):
    • 비트 [7]: INVEN (1: 입력 인버트).
    • 비트 [3]: PULLUPEN (1: 풀업 활성).
    • 비트 [2:0]: ISC[2:0] (입력 감지: 0x0=INTDISABLE, 0x1=BOTHEDGES, 0x2=RISING, 0x3=FALLING, 0x4=INPUT_DISABLE, 0x5=LEVEL).
    • 리셋: 0x00, R/W.
  • VPORTx (0x00~0x1F):
    • DIR, OUT, IN, INTFLAGS 매핑 (싱글 사이클).

GPIO 설정 절차

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

  1. 클럭 설정: CLKCTRL로 CLK_PER 활성화 (예: 24MHz OSCHF, Auto-tune).
  2. 방향 설정: PORTx.DIRSET/CLR로 입력/출력 지정.
  3. 출력 값 설정: PORTx.OUTSET/CLR/TGL 또는 PORTx.OUT에 8비트 값 작성.
  4. 핀 설정: PORTx.PINnCTRL로 풀업, 인버트, 입력 감지(ISC) 설정.
  5. 인터럽트 설정: PINnCTRL.ISC로 감지 모드 설정, INTFLAGS 클리어, 인터럽트 벡터 활성화.
  6. 슬루 레이트 설정: PORTx.PORTCTRL.SRL로 슬루 레이트 제한 활성화/비활성화.
  7. 멀티플렉싱 확인: PORTMUX로 주변 장치와 충돌 방지.
  8. 전력 최적화: 사용하지 않는 핀은 ISC=INPUT_DISABLE.
  9. 테스트: 출력 확인 및 인터럽트 동작 검증.

GPIO 설정 고려사항

  • 전력 최적화: 사용하지 않는 핀은 ISC=INPUT_DISABLE로 입력 버퍼 비활성.
  • 인터럽트: Px2/Px6 핀은 비동기 감지 지원(저전력 웨이크업). ISC 변경 시 동기화 지연 주의. INTFLAGS 클리어 필수.
  • 슬루 레이트: PORTCTRL.SRL=1로 EMI 감소. 고속 신호 시 SRL=0 사용.
  • 멀티플렉싱: PORTMUX 설정으로 주변 장치(USART, SPI 등) 우선순위 확인.
  • 호환성: AVR128DA28/32는 PORTB/E/G 제한. <avr/io.h>로 모델별 정의 제공.
  • CCP 보호: CLKCTRL 등 보호 레지스터는 _PROTECTED_WRITE 사용.
  • 리셋 상태: 리셋 후 핀은 입력, 트라이스테이트. 초기화 전 확인.

드라이버 구현 내용

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

  • gpio_init: 다중 핀 초기화 (PORTx.DIRSET/CLR).
  • gpio_set: 개별 핀 출력 설정 (PORTx.OUTSET/CLR).
  • gpio_write_port: 8비트 출력 (PORTx.OUT).
  • vport_write_port: 싱글 사이클 8비트 출력 (VPORTx.OUT).
  • gpio_read: 입력 읽기 (PORTx.IN).
  • gpio_toggle: 출력 토글 (PORTx.OUTTGL).
  • gpio_pullup: 풀업 설정 (PORTx.PINnCTRL.PULLUPEN).
  • gpio_set_interrupt: 인터럽트 감지 모드 설정 (PINnCTRL.ISC).
  • gpio_clear_interrupt: 인터럽트 플래그 클리어 (PORTx.INTFLAGS).
  • gpio_set_slew_rate: 슬루 레이트 제한 설정 (PORTx.PORTCTRL.SRL).
  • 클럭 설정: main.c에서 24MHz OSCHF, Auto-tune 활성화 (CLKCTRL, _PROTECTED_WRITE).
  • 호환성: <avr/io.h>로 모델별 포트 정의 지원. VPORT는 고속 출력 옵션.

구현코드

gpio_driver.h

/**
 * @file gpio_driver.h
 * @brief AVR128DA64/48/32/28 GPIO 드라이버 헤더 파일
 * @details 모든 AVR128DA 시리즈 호환. 다중 핀 초기화, 1바이트 출력, VPORT, 인터럽트, 슬루 레이트 지원.
 */
#ifndef GPIO_DRIVER_H
#define GPIO_DRIVER_H

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

/**
 * @brief GPIO 방향 정의: 입력(0) 또는 출력(1)
 */
#define GPIO_INPUT  0
#define GPIO_OUTPUT 1

/**
 * @brief 풀업 저항 설정 정의: 비활성화(0) 또는 활성화(1)
 */
#define GPIO_PULLUP_DISABLE 0
#define GPIO_PULLUP_ENABLE  1

/**
 * @brief 인터럽트 감지 모드 정의
 * @details PINnCTRL.ISC에 따른 설정 (데이터시트 17.3.6)
 */
#define GPIO_INT_DISABLE    0x0 // 인터럽트 비활성
#define GPIO_INT_BOTHEDGES  0x1 // 양쪽 에지 감지
#define GPIO_INT_RISING     0x2 // 상승 에지 감지
#define GPIO_INT_FALLING    0x3 // 하강 에지 감지
#define GPIO_INT_INPUT_DISABLE 0x4 // 입력 버퍼 비활성
#define GPIO_INT_LEVEL      0x5 // 레벨 감지

/**
 * @brief 슬루 레이트 제한 정의
 * @details PORTCTRL.SRL에 따른 설정 (0: 비활성, 1: 활성)
 */
#define GPIO_SLEW_RATE_DISABLE 0
#define GPIO_SLEW_RATE_ENABLE  1

/**
 * @brief 여러 GPIO 핀을 동시에 초기화
 * @param ports PORT 레지스터 포인터 배열
 * @param pins 핀 번호 배열 (0-7)
 * @param directions 방향 배열 (GPIO_INPUT 또는 GPIO_OUTPUT)
 * @param num_ports 초기화할 포트 수
 * @note AVR128DA28/32에서는 PORTB/PORTE/PORTG 제한
 */
void gpio_init(PORT_t *ports[], uint8_t pins[], uint8_t directions[], uint8_t num_ports);

/**
 * @brief 개별 GPIO 핀 출력 설정
 * @param port PORT 레지스터 포인터
 * @param pin 핀 번호 (0-7)
 * @param value 설정 값 (0: LOW, 1: HIGH)
 */
void gpio_set(PORT_t *port, uint8_t pin, uint8_t value);

/**
 * @brief 포트 전체에 8비트 값 출력
 * @param port PORT 레지스터 포인터
 * @param value 8비트 출력 값 (0x00~0xFF)
 */
void gpio_write_port(PORT_t *port, uint8_t value);

/**
 * @brief 가상 포트로 8비트 값 출력 (싱글 사이클)
 * @param vport VPORT 레지스터 포인터
 * @param value 8비트 출력 값 (0x00~0xFF)
 */
void vport_write_port(VPORT_t *vport, uint8_t value);

/**
 * @brief GPIO 핀 입력 읽기
 * @param port PORT 레지스터 포인터
 * @param pin 핀 번호 (0-7)
 * @return 핀 상태 (0: LOW, 1: HIGH)
 */
uint8_t gpio_read(PORT_t *port, uint8_t pin);

/**
 * @brief GPIO 핀 출력 토글
 * @param port PORT 레지스터 포인터
 * @param pin 핀 번호 (0-7)
 */
void gpio_toggle(PORT_t *port, uint8_t pin);

/**
 * @brief GPIO 핀 풀업 저항 설정
 * @param port PORT 레지스터 포인터
 * @param pin 핀 번호 (0-7)
 * @param enable 풀업 설정 (GPIO_PULLUP_DISABLE 또는 GPIO_PULLUP_ENABLE)
 */
void gpio_pullup(PORT_t *port, uint8_t pin, uint8_t enable);

/**
 * @brief GPIO 핀 인터럽트 감지 모드 설정
 * @param port PORT 레지스터 포인터
 * @param pin 핀 번호 (0-7)
 * @param mode 인터럽트 감지 모드 (GPIO_INT_xxx)
 * @note 인터럽트 활성화 후 전역 인터럽트(SREG.I) 활성화 필요
 */
void gpio_set_interrupt(PORT_t *port, uint8_t pin, uint8_t mode);

/**
 * @brief GPIO 핀 인터럽트 플래그 클리어
 * @param port PORT 레지스터 포인터
 * @param pin 핀 번호 (0-7)
 */
void gpio_clear_interrupt(PORT_t *port, uint8_t pin);

/**
 * @brief 포트의 슬루 레이트 제한 설정
 * @param port PORT 레지스터 포인터
 * @param enable 슬루 레이트 제한 (GPIO_SLEW_RATE_DISABLE 또는 GPIO_SLEW_RATE_ENABLE)
 */
void gpio_set_slew_rate(PORT_t *port, uint8_t enable);

#endif // GPIO_DRIVER_H

gpio_driver.c

/**
 * @file gpio_driver.c
 * @brief AVR128DA64/48/32/28 GPIO 드라이버 구현
 * @details PORTx 및 VPORTx 레지스터로 GPIO 제어. 인터럽트, 슬루 레이트 추가.
 */
#include "gpio_driver.h"

/**
 * @brief 여러 GPIO 핀을 동시에 초기화
 * @details DIRSET(출력) 또는 DIRCLR(입력) 레지스터로 방향 설정
 */
void gpio_init(PORT_t *ports[], uint8_t pins[], uint8_t directions[], uint8_t num_ports) {
    for (uint8_t i = 0; i < num_ports; i++) {
        if (directions[i] == GPIO_OUTPUT) {
            ports[i]->DIRSET = (1 << pins[i]); // 핀을 출력으로 설정
        } else {
            ports[i]->DIRCLR = (1 << pins[i]); // 핀을 입력으로 설정
        }
    }
}

/**
 * @brief 개별 GPIO 핀 출력 설정
 * @details OUTSET(1) 또는 OUTCLR(0) 레지스터로 핀 상태 설정
 */
void gpio_set(PORT_t *port, uint8_t pin, uint8_t value) {
    if (value) {
        port->OUTSET = (1 << pin); // 핀을 HIGH로 설정
    } else {
        port->OUTCLR = (1 << pin); // 핀을 LOW로 설정
    }
}

/**
 * @brief 포트 전체에 8비트 값 출력
 * @details PORTx.OUT 레지스터에 8비트 값을 직접 작성
 */
void gpio_write_port(PORT_t *port, uint8_t value) {
    port->OUT = value; // 8비트 값으로 포트 출력 설정
}

/**
 * @brief 가상 포트로 8비트 값 출력
 * @details VPORTx.OUT 레지스터에 싱글 사이클로 8비트 값 작성
 */
void vport_write_port(VPORT_t *vport, uint8_t value) {
    vport->OUT = value; // 싱글 사이클로 8비트 값 출력
}

/**
 * @brief GPIO 핀 입력 읽기
 * @details IN 레지스터에서 핀 상태 읽기
 */
uint8_t gpio_read(PORT_t *port, uint8_t pin) {
    return (port->IN & (1 << pin)) ? 1 : 0; // IN 레지스터에서 상태 반환
}

/**
 * @brief GPIO 핀 출력 토글
 * @details OUTTGL 레지스터로 핀 출력 반전
 */
void gpio_toggle(PORT_t *port, uint8_t pin) {
    port->OUTTGL = (1 << pin); // 핀 출력 토글
}

/**
 * @brief GPIO 핀 풀업 저항 설정
 * @details PINnCTRL 레지스터의 PULLUPEN 비트 설정 (오프셋 0x10 + pin)
 */
void gpio_pullup(PORT_t *port, uint8_t pin, uint8_t enable) {
    volatile uint8_t *pinctrl = (volatile uint8_t *)((uintptr_t)port + 0x10 + pin);
    if (enable) {
        *pinctrl |= (1 << 3); // PULLUPEN 비트 설정
    } else {
        *pinctrl &= ~(1 << 3); // PULLUPEN 비트 클리어
    }
}

/**
 * @brief GPIO 핀 인터럽트 감지 모드 설정
 * @details PINnCTRL.ISC 비트 설정 (오프셋 0x10 + pin)
 */
void gpio_set_interrupt(PORT_t *port, uint8_t pin, uint8_t mode) {
    volatile uint8_t *pinctrl = (volatile uint8_t *)((uintptr_t)port + 0x10 + pin);
    *pinctrl = (*pinctrl & ~(0x7 << 0)) | (mode << 0); // ISC[2:0] 설정
}

/**
 * @brief GPIO 핀 인터럽트 플래그 클리어
 * @details PORTx.INTFLAGS에 1 작성으로 플래그 클리어
 */
void gpio_clear_interrupt(PORT_t *port, uint8_t pin) {
    port->INTFLAGS = (1 << pin); // INTFLAGS 비트 클리어
}

/**
 * @brief 포트의 슬루 레이트 제한 설정
 * @details PORTx.PORTCTRL.SRL 비트 설정
 */
void gpio_set_slew_rate(PORT_t *port, uint8_t enable) {
    if (enable) {
        port->PORTCTRL |= (1 << 0); // SRL 비트 설정
    } else {
        port->PORTCTRL &= ~(1 << 0); // SRL 비트 클리어
    }
}

main.c

/**
 * @file main.c
 * @brief AVR128DA64/48/32/28 GPIO 드라이버 테스트 프로그램
 * @details 24MHz 클럭 설정 후 GPIO 드라이버를 사용한 기본 동작 테스트
 */
#ifndef F_CPU
#define F_CPU 24000000UL // 시스템 클럭 주파수 24MHz 정의
#endif

#include <avr/io.h>
#include <util/delay.h>
#include "gpio_driver.h"

/**
 * @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 GPIO 드라이버를 사용해 개별 핀, 1바이트 출력, 슬루 레이트 테스트
 */
int main(void) {
    clock_init_24mhz(); // 클럭 설정
    
    // PA0(출력), PB1(입력), PC0(출력) 초기화
    PORT_t *ports[] = {&PORTA, &PORTB, &PORTC};
    uint8_t pins[] = {0, 1, 0};
    uint8_t directions[] = {GPIO_OUTPUT, GPIO_INPUT, GPIO_OUTPUT};
    gpio_init(ports, pins, directions, 3);
    
    PORTA.DIRSET = 0xFF; // PA0~PA7 출력
    PORTC.DIRSET = 0xFF; // PC0~PC7 출력
    gpio_pullup(&PORTB, 1, GPIO_PULLUP_ENABLE); // PB1 풀업
    gpio_set_slew_rate(&PORTA, GPIO_SLEW_RATE_ENABLE); // PORTA 슬루 레이트 제한 활성
    
    uint8_t pattern = 0x55; // 초기 패턴 (01010101)
    
    while (1) {
        uint8_t button_state = gpio_read(&PORTB, 1); // PB1 버튼 읽기
        if (button_state == 0) { // 버튼 눌림 (LOW)
            gpio_write_port(&PORTA, pattern); // PORTA에 패턴 출력
            vport_write_port(&VPORTC, pattern); // VPORTC에 패턴 출력
        } else {
            gpio_write_port(&PORTA, 0x00); // PORTA 모든 핀 LOW
            vport_write_port(&VPORTC, 0x00); // VPORTC 모든 핀 LOW
        }
        
        pattern = (pattern == 0x55) ? 0xAA : 0x55; // 패턴 토글
        _delay_ms(500); // 500ms 딜레이
        
        gpio_toggle(&PORTA, 0); // PA0 토글
        gpio_toggle(&PORTC, 0); // PC0 토글
        _delay_ms(500);
    }
    
    return 0;
}

예제

다음은 드라이버를 활용한 2가지 예제 코드로, 인터럽트와 슬루 레이트 기능을 반영합니다.

예제 1: 버튼 인터럽트로 LED 제어

/**
 * @file button_interrupt_example.c
 * @brief 버튼 인터럽트로 LED 제어 예제
 * @details PB2 버튼의 하강 에지 인터럽트로 PA0 LED 토글, PORTA 슬루 레이트 제한 적용
 */
#ifndef F_CPU
#define F_CPU 24000000UL // 시스템 클럭 24MHz
#endif

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

/**
 * @brief 시스템 클럭 설정 (24MHz, Auto-tune)
 * @details main.c와 동일
 */
void clock_init_24mhz(void) {
    _PROTECTED_WRITE(CLKCTRL.XOSCHFCTRLA, CLKCTRL_ENABLE_bm | (0 << CLKCTRL_SEL_bp));
    _PROTECTED_WRITE(CLKCTRL.OSCHCTRLA, (0x9 << CLKCTRL_FRQSEL_gp) | CLKCTRL_AUTOTUNE_bm);
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00);
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, 0x00);
    while ((CLKCTRL.MCLKSTATUS & CLKCTRL_OSCHF_bm) == 0);
}

/**
 * @brief PORTB 인터럽트 서비스 루틴
 * @details PB2 인터럽트 발생 시 PA0 LED 토글
 */
ISR(PORTB_PORT_vect) {
    if (PORTB.INTFLAGS & (1 << 2)) { // PB2 인터럽트 확인
        gpio_toggle(&PORTA, 0); // PA0 LED 토글
        gpio_clear_interrupt(&PORTB, 2); // 플래그 클리어
    }
}

/**
 * @brief 메인 함수
 * @details PB2 버튼 인터럽트로 PA0 LED 제어, 슬루 레이트 제한 활성
 */
int main(void) {
    clock_init_24mhz(); // 클럭 설정
    
    // PA0(출력), PB2(입력) 초기화
    PORT_t *ports[] = {&PORTA, &PORTB};
    uint8_t pins[] = {0, 2};
    uint8_t directions[] = {GPIO_OUTPUT, GPIO_INPUT};
    gpio_init(ports, pins, directions, 2);
    
    gpio_pullup(&PORTB, 2, GPIO_PULLUP_ENABLE); // PB2 풀업
    gpio_set_slew_rate(&PORTA, GPIO_SLEW_RATE_ENABLE); // PA0 슬루 레이트 제한
    gpio_set_interrupt(&PORTB, 2, GPIO_INT_FALLING); // PB2 하강 에지 인터럽트
    
    sei(); // 전역 인터럽트 활성화
    
    while (1) {
        // 인터럽트 기반 동작, 메인 루프는 대기
        _delay_ms(100); // CPU 부하 감소
    }
    
    return 0;
}

예제 2: 7세그먼트 디스플레이 타이머

/**
 * @file seven_segment_timer_example.c
 * @brief 7세그먼트 디스플레이 타이머 예제
 * @details PORTA에 7세그먼트 디스플레이 연결, PB1 버튼으로 타이머 시작/정지, 슬루 레이트 적용
 */
#ifndef F_CPU
#define F_CPU 24000000UL // 시스템 클럭 24MHz
#endif

#include <avr/io.h>
#include <util/delay.h>
#include "gpio_driver.h"

/**
 * @brief 7세그먼트 디스플레이 패턴 (0~9, 공통 양극)
 * @details 1=꺼짐, 0=켜짐 (공통 양극)
 */
static const uint8_t seg_patterns[] = {
    0xC0, // 0
    0xF9, // 1
    0xA4, // 2
    0xB0, // 3
    0x99, // 4
    0x92, // 5
    0x82, // 6
    0xF8, // 7
    0x80, // 8
    0x90  // 9
};

/**
 * @brief 시스템 클럭 설정 (24MHz, Auto-tune)
 * @details main.c와 동일
 */
void clock_init_24mhz(void) {
    _PROTECTED_WRITE(CLKCTRL.XOSCHFCTRLA, CLKCTRL_ENABLE_bm | (0 << CLKCTRL_SEL_bp));
    _PROTECTED_WRITE(CLKCTRL.OSCHCTRLA, (0x9 << CLKCTRL_FRQSEL_gp) | CLKCTRL_AUTOTUNE_bm);
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00);
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, 0x00);
    while ((CLKCTRL.MCLKSTATUS & CLKCTRL_OSCHF_bm) == 0);
}

/**
 * @brief 메인 함수
 * @details PB1 버튼으로 타이머 시작/정지, PORTA에 카운터 표시
 */
int main(void) {
    clock_init_24mhz(); // 클럭 설정
    
    // PA0~PA7(7세그먼트 출력), PB1(입력) 초기화
    PORT_t *ports[] = {&PORTA, &PORTB};
    uint8_t pins[] = {0, 1};
    uint8_t directions[] = {GPIO_OUTPUT, GPIO_INPUT};
    gpio_init(ports, pins, directions, 2);
    
    PORTA.DIRSET = 0xFF; // PA0~PA7 출력
    gpio_pullup(&PORTB, 1, GPIO_PULLUP_ENABLE); // PB1 풀업
    gpio_set_slew_rate(&PORTA, GPIO_SLEW_RATE_ENABLE); // PORTA 슬루 레이트 제한
    
    uint8_t count = 0; // 타이머 카운터
    uint8_t running = 0; // 타이머 상태 (0: 정지, 1: 동작)
    
    while (1) {
        // PB1 버튼으로 타이머 시작/정지
        if (gpio_read(&PORTB, 1) == 0) {
            running = !running; // 상태 토글
            _delay_ms(200); // 디바운싱
        }
        
        if (running) {
            count = (count + 1) % 10; // 0~9 카운트
            vport_write_port(&VPORTA, seg_patterns[count]); // 7세그먼트 표시
            _delay_ms(1000); // 1초 간격
        } else {
            vport_write_port(&VPORTA, seg_patterns[count]); // 현재 값 유지
            _delay_ms(50); // 대기
        }
    }
    
    return 0;
}

사용방법

  1. 프로젝트 설정:
    •   Microchip Studio에서 AVR 프로젝트 생성.
    •   타겟 장치 선택 (AVR128DA64/48/32/28).
    •   gpio_driver.h, gpio_driver.c, main.c 또는 예제 파일(button_interrupt_example.c, seven_segment_timer_example.c) 추가.
  2. 빌드 및 프로그래밍:
    •   AVR-GCC로 컴파일.
    •   Atmel-ICE, AVR Dragon 등으로 HEX 파일 업로드.
  3. 클럭 설정:
    •   clock_init_24mhz() 호출로 24MHz OSCHF 설정.
    •   TOSC1/TOSC2(PF6/PF7)에 32.768kHz 크리스털 연결.
  4. GPIO 설정:
    •   gpio_init으로 다중 핀 초기화.
    •   gpio_write_port 또는 vport_write_port로 8비트 출력.
    •   gpio_pullup으로 입력 풀업 설정.
    •   gpio_set_interrupt로 인터럽트 설정, sei()로 전역 인터럽트 활성화.
    •   gpio_set_slew_rate로 슬루 레이트 제한 설정.
  5. 모델별 주의:
    •   AVR128DA28/32: PORTB/E/G 제한. 예제에서 PORTB 사용 시 컴파일 에러로 확인.
    •   예: AVR128DA28에서는 PORTA/C/D/F만 사용.

추가팁

  • 고속 출력: vport_write_port로 싱글 사이클 출력 (비트 뱅잉, SPI 등에 유용).
  • 저전력: 사용하지 않는 핀은 ISC=INPUT_DISABLE로 입력 버퍼 비활성.
  • 인터럽트: Px2/Px6 핀(PB2 등)으로 비동기 감지 활용. ISR에서 플래그 클리어 필수.
  • 슬루 레이트: 고속 신호 시 SRL=0, EMI 감소 시 SRL=1.
  • 디버깅: CLKOUT(PA7) 활성화(MCLKCTRLA.CLKOUT=1)로 클럭 검증.
  • 멀티플렉싱: PORTMUX로 주변 장치 충돌 확인.
  • 패키지별 핀 확인: 데이터시트 Table 1-2로 핀 가용성 확인.

결론

AVR128DA64/48/32/28 시리즈용 GPIO 드라이버는 개별 핀 제어, 포트 단위 출력, 인터럽트, 슬루 레이트 제어, VPORT 기반 고속 처리를 지원하며, 저전력 모드 및 멀티플렉싱 환경에서도 안정적으로 동작하도록 설계되었습니다. 예제 코드(버튼 인터럽트, 7세그먼트 디스플레이 제어)를 통해 드라이버 활용 방안을 검증했으며, 전 모델에 호환성을 갖추어 다양한 애플리케이션에서 즉시 활용 가능합니다. 추후 드라이브 강도 설정 등 추가 기능은 데이터시트를 기반으로 확장할 수 있습니다.

반응형