본문 바로가기
MCU/AVR

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

by linuxgo 2025. 9. 4.
반응형

본 문서는 Microchip AVR128DA64/48/32/28 시리즈에 내장된 ADC0 모듈을 분석하고, 이를 활용한 범용 드라이버 설계 및 구현 방법을 설명합니다. 제안된 드라이버는 단일/연속 변환, 10/12비트 해상도, 단일 엔드/차동 입력, 인터럽트 및 폴링 기반 데이터 처리, 온도 센서(TEMPSENSE) 지원 기능을 포함합니다. 또한 모든 AVR128DA 제품군(28/32/48/64핀)과 호환되며, RESRDY(0x30) 및 WCMP(0x32) 인터럽트를 이용해 효율적인 데이터 처리를 제공합니다. 드라이버는 AVR-GCC 및 Microchip Studio 환경에서 동작하도록 구현되었으며, 다양한 입력 선택(AIN0~AIN21)과 전력 최적화를 지원합니다.

AVR128DA ADC block diagram

사양

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

  • 모듈 수: ADC0 (모든 모델 공통).
  • 핀 할당: AIN0~AIN21 (양극 입력), VREFA (외부 기준 전압).
  • 기능:
    • 단일 엔드/차동 변환.
    • 해상도: 10비트(0~1023), 12비트(0~4095).
    • 샘플링: 단일 변환, 누적(1~128 샘플), 프리러닝 모드.
    • 인터럽트: ADC0_RESRDY (변환 완료, 벡터 주소: 0x30), ADC0_WCMP (윈도우 비교, 벡터 주소: 0x32).
    • 전압 기준: 1.024V, 2.048V, 2.500V, 4.096V, VDD, VREFA.
    • 온도 측정: TEMPSENSE 입력 및 SIGROW 보정.
  • 클럭: CLK_ADC (CLK_PER에서 프리스케일링, 최대 24MHz).
  • 전력 최적화: ENABLE=0으로 전력 소모 없음.
  • 리셋 상태: ADC 비활성화, 핀은 GPIO 입력 모드.

인터럽트 벡터

모듈 벡터 번호 프로그램 주소 (워드) 소스 설명
ADC0 24 0x30 ADC0_RESRDY 변환 완료 인터럽트
ADC0 25 0x32 ADC0_WCMP 윈도우 비교 인터럽트

VREF - Voltage Reference

  • Programmable Voltage Reference Sources:
    • ADC0용 기준 전압 1개.
    • DAC0용 기준 전압 1개.
    • 모든 아날로그 비교기(ACs)가 공유하는 기준 전압 1개.
  • Each Reference Source Supports the Following Voltages:
    • 1.024V
    • 2.048V
    • 2.500V
    • 4.096V
    • VDD
    • VREFA
  • ALWAYSON: 기준 전압을 항상 활성화하여 시작 시간을 단축 가능 (전력 소모 증가).

VREF(Voltage Reference) 모듈은 여러 주변 장치(ADC0, DAC0, ACs)에 사용되는 기준 전압 소스를 제어하는 레지스터를 제공합니다. 사용자는 VREF.ADC0REF, VREF.DAC0REF, VREF.ACREF 레지스터를 통해 각 장치의 기준 전압을 선택할 수 있습니다. 기준 전압은 주변 장치가 요청할 때 자동으로 활성화되며, ALWAYSON 비트를 설정하여 자동 비활성화를 비활성화할 수 있습니다. 이는 시작 시간을 줄이지만 전력 소모가 증가합니다.

  • REFSEL[2:0]: 기준 전압 선택 (1.024V, 2.048V, 4.096V, 2.500V, VDD, VREFA).
  • Bandgap Reference Generator: 내부 기준 전압 생성.
  • ALWAYSON: 기준 전압 상시 활성화 제어.
  • Reference Request: 주변 장치의 요청에 따라 Bandgap 활성화.

초기화

  • 기본 설정: ADC0, DAC0, ACs가 기준 전압을 요청하면 해당 소스가 활성화됨.
  • 기본 기준 전압: 1.024V (REFSEL로 변경 가능).
  • 주의: 한 장치의 기준 전압 변경은 다른 장치에 노이즈를 유발할 수 있으므로, 변경 시 관련 장치를 비활성화하세요.
Offset Name Bit Pos. 7 6 5 4 3 2 1 0
0x00 ADC0REF 7:0 ALWAYSON       REFSEL[2:0]  
0x01 Reserved                  
0x02 DAC0REF 7:0 ALWAYSON       REFSEL[2:0]  
0x03 Reserved                  
0x04 ACREF 7:0 ALWAYSON       REFSEL[2:0]  

ADC0 Reference

  • Name: ADC0REF
  • Offset: 0x00
  • Reset: 0x00
  • Property: -
  • Bit 7 – ALWAYSON: Reference Always On
    • 0: 필요 시 자동 활성화.
    • 1: 항상 활성화.
  • Bits 2:0 – REFSEL[2:0]: Reference Select
    Value Name Description
    0x0 1V024 Internal 1.024V reference
    0x1 2V048 Internal 2.048V reference
    0x2 4V096 Internal 4.096V reference
    0x3 2V500 Internal 2.500V reference
    0x4 - Reserved
    0x5 VDD VDD as reference
    0x6 VREFA External reference from VREFA
    0x7 - Reserved
  • 참고: 내부 기준 전압 값은 대표값이며, 정확한 값은 Electrical Characteristics 참조.

레지스터 설정 상세

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

  • ADCn.CTRLA (0x00):
    • 비트 [7]: ENABLE (ADC 활성화, 0=비활성, 1=활성).
    • 비트 [6]: CONVMODE (0=단일 엔드, 1=차동).
    • 비트 [2]: RESSEL (0=10비트, 1=12비트).
    • 비트 [1]: FREERUN (0=단일 변환, 1=연속 변환).
    • 비트 [0]: LEFTADJ (0=오른쪽 정렬, 1=왼쪽 정렬).
    • 리셋: 0x00, R/W.
  • ADCn.CTRLB (0x01):
    • 비트 [2:0]: SAMPNUM[2:0] (샘플 누적 수: 0x0=1, 0x1=2, ..., 0x7=128).
    • 리셋: 0x00, R/W.
  • ADCn.CTRLC (0x02):
    • 비트 [5:3]: PRESC[2:0] (클럭 프리스케일러: /2, /4, ..., /256).
    • 리셋: 0x00, R/W.
  • ADCn.CTRLE (0x04):
    • 비트 [2:0]: WINCM[2:0] (윈도우 비교 모드: 0=Below, 1=Above, 2=Inside, 3=Outside).
    • 리셋: 0x00, R/W.
  • ADCn.MUXPOS (0x05):
    • 비트 [5:0]: MUXPOS[5:0] (양극 입력: AIN0~AIN21, TEMPSENSE=0x42, GND=0x43, DAC0=0x44, DACREF0/1/2).
    • 리셋: 0x00, R/W.
  • ADCn.MUXNEG (0x06):
    • 비트 [4:0]: MUXNEG[4:0] (음극 입력, 차동 모드: AIN0~AIN15, GND=0x1E, DAC0=0x1F).
    • 리셋: 0x00, R/W.
  • ADCn.COMMAND (0x08):
    • 비트 [0]: STCONV (1=변환 시작, 자동 클리어).
    • 리셋: 0x00, R/W.
  • ADCn.INTCTRL (0x0A):
    • 비트 [1]: WCMP (윈도우 비교 인터럽트 활성화).
    • 비트 [0]: RESRDY (변환 완료 인터럽트 활성화).
    • 리셋: 0x00, R/W.
  • ADCn.INTFLAGS (0x0B):
    • 비트 [1]: WCMP (윈도우 비교 플래그, 1 작성으로 클리어).
    • 비트 [0]: RESRDY (변환 완료 플래그, 1 작성으로 클리어).
    • 리셋: 0x00, R/W.
  • ADCn.RES (0x0C~0x0D):
    • 비트 [15:0]: RES[15:0] (변환 결과, 단일 엔드: 0~4095/1023, 차동: -2048~2047/-512~511).
    • 리셋: 0x0000, R.
  • ADCn.WINLT/HT (0x09/0x0A):
    • 비트 [15:0]: WINLT[15:0], WINHT[15:0] (윈도우 비교 하한/상한 값).
    • 리셋: 0x0000, R/W.

ADC 설정 절차

  1. 클럭 설정: CLKCTRL로 CLK_PER을 24MHz OSCHF로 설정.
  2. VREF 설정: VREF.ADC0REF로 기준 전압 선택 (1.024V, 2.048V, 2.500V, 4.096V, VDD, VREFA).
  3. 모드 설정: ADCn.CTRLA의 CONVMODE로 단일 엔드/차동 선택.
  4. 해상도 설정: ADCn.CTRLA의 RESSEL로 10/12비트 선택.
  5. 프리러닝 설정: ADCn.CTRLA의 FREERUN으로 연속 변환 활성화 (옵션).
  6. 샘플 누적 설정: ADCn.CTRLB의 SAMPNUM으로 샘플 수 설정 (1~128).
  7. 클럭 프리스케일링: ADCn.CTRLC의 PRESC로 CLK_ADC 설정.
  8. 입력 선택: ADCn.MUXPOS로 양극 입력, ADCn.MUXNEG로 음극 입력 (차동 모드) 선택.
  9. 윈도우 비교 설정 (옵션): ADCn.CTRLE의 WINCM으로 비교 모드 설정, WINLT/HT로 임계값 설정.
  10. 인터럽트 설정 (옵션): ADCn.INTCTRL로 RESRDY, WCMP 활성화.
  11. ADC 활성화: ADCn.CTRLA의 ENABLE로 ADC 시작.
  12. 변환 시작: ADCn.COMMAND의 STCONV로 변환 시작 또는 프리러닝 모드 사용.
  13. 결과 처리: ADCn.RES 읽기, RESRDY/WCMP 플래그 확인 및 클리어.

ADC 동작

  • 변환 시작: STCONV=1 또는 이벤트 트리거(STARTEI=1).
  • 타이밍:
    • 단일 변환 (12비트): \( \frac{13.5 + 2}{f_{CLK_ADC}} + \frac{4}{f_{CLK_PER}} \).
    • 누적 변환 (n 샘플): \( \frac{2}{f_{CLK_PER}} + n \cdot \frac{13.5 + 2 + SAMPDLY + SAMPLEN}{f_{CLK_ADC}} + \frac{2}{f_{CLK_PER}} \).
  • 결과 처리: ADCn.RES에 저장, RESRDY 플래그 설정, 인터럽트 발생.
  • 윈도우 비교: WINCM 조건 충족 시 WCMP 플래그 설정, 인터럽트 발생.
  • 온도 측정:
    • TEMPSENSE 입력(MUXPOS=0x42), VREF=2.048V, INITDLY≥25μs, SAMPLEN≥28μs.
    • 온도 계산: \( T = \frac{SIGROW.TEMPSENSE1 - ADCn.RES}{4096} \cdot SIGROW.TEMPSENSE0 \).

드라이버 구현 내용

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

  • 인터럽트 처리: ADC0_RESRDY(0x30)로 변환 완료, ADC0_WCMP(0x32)로 윈도우 비교 처리. 표준 ISR 사용.
  • 변환 모드: 단일, 누적, 프리러닝 지원.
  • 입력 선택: AIN0~AIN21, TEMPSENSE, GND, DAC0 등.
  • 결과 처리: 10/12비트, 오른쪽/왼쪽 정렬, 단일 엔드(unsigned), 차동(signed).
  • 온도 측정: TEMPSENSE 입력과 SIGROW 보정을 통한 온도 계산.
  • VREF 설정: VREF.ADC0REF를 통해 ADC0 기준 전압 선택 (1.024V, 2.048V, 2.500V, 4.096V, VDD, VREFA).
  • 함수:
    • adc_init: ADC 초기화 (모드, 해상도, 입력, 클럭, VREF 등).
    • adc_start: 변환 시작.
    • adc_read: 폴링 기반 결과 읽기.
    • adc_read_it: 인터럽트 기반 결과 읽기.
    • adc_temp_read: 온도 측정.
    • adc_enable/disable: ADC 활성화/비활성화.
    • adc_window_config: 윈도우 비교 설정.
    • adc_check_flags: RESRDY, WCMP 플래그 확인 및 클리어.

사용방법

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

  1. 헤더 파일 포함: 프로젝트에 adc_driver.h를 포함하고, <avr/io.h>, <stdio.h>, <util/delay.h>를 추가합니다.
  2. 클럭 설정: main.cclock_init_24mhz를 호출하여 24MHz OSCHF 클럭을 설정합니다.
  3. ADC 인스턴스 생성: ADC_Instance 구조체를 선언하고, adc_init으로 초기화합니다. 예:
    ADC_Instance adc0_instance;
    adc_init(&adc0_instance, &ADC0, ADC_MODE_SINGLE, ADC_RES_12BIT, ADC_REF_2V048, ADC_PRESC_DIV4, ADC_INPUT_AIN0);
  4. ADC 활성화: adc_enable로 ADC를 활성화합니다.
  5. 변환 시작: adc_start로 변환 시작 또는 프리러닝 모드 사용.
  6. 데이터 읽기:
    • 폴링 기반: adc_read로 결과 읽기.
    • 인터럽트 기반: adc_read_it_init으로 인터럽트 활성화 후, adc_read_it으로 결과 읽기.
  7. 온도 측정: adc_temp_read로 TEMPSENSE 입력을 통해 온도 읽기.
  8. 윈도우 비교 (옵션): adc_window_config로 비교 모드와 임계값 설정.
  9. 플래그 확인: adc_check_flags로 RESRDY, WCMP 상태 확인 및 클리어.

코드 구현

ADC 드라이버 코드는 다음과 같습니다:

  • ADC 드라이버 코드: adc_driver.h, adc_driver.c, main.c
  • 드라이버를 이용한 예시 코드: adc_single_example.c, adc_temp_example.c
/**
 * @file adc_driver.h
 * @brief AVR128DA64/48/32/28 ADC 드라이버 헤더 파일
 * @details 모든 AVR128DA 시리즈 호환. 단일/연속 변환, 10/12비트, 인터럽트/폴링, 온도 측정 지원.
 * @author 작성자
 * @date 2025-09-03
 */
#ifndef ADC_DRIVER_H
#define ADC_DRIVER_H

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

/**
 * @brief ADC 설정 정의
 */
#define ADC_MODE_SINGLE    0x0 // 단일 엔드 모드
#define ADC_MODE_DIFF      0x1 // 차동 모드
#define ADC_RES_10BIT      0x0 // 10비트 해상도
#define ADC_RES_12BIT      0x1 // 12비트 해상도
#define ADC_REF_1V024      0x0 // 1.024V 기준 전압
#define ADC_REF_2V048      0x1 // 2.048V 기준 전압
#define ADC_REF_4V096      0x2 // 4.096V 기준 전압
#define ADC_REF_2V500      0x3 // 2.500V 기준 전압
#define ADC_REF_VDD        0x5 // VDD 기준 전압
#define ADC_REF_VREFA      0x6 // 외부 VREFA 기준 전압
#define ADC_PRESC_DIV2     0x0 // CLK_ADC /2
#define ADC_PRESC_DIV4     0x1 // CLK_ADC /4
#define ADC_PRESC_DIV256   0x7 // CLK_ADC /256
#define ADC_INPUT_AIN0     0x00 // AIN0 입력
#define ADC_INPUT_TEMPSENSE 0x42 // 온도 센서 입력
#define ADC_WINCM_BELOW    0x0 // 윈도우 비교: Below
#define ADC_WINCM_ABOVE    0x1 // 윈도우 비교: Above
#define ADC_WINCM_INSIDE   0x2 // 윈도우 비교: Inside
#define ADC_WINCM_OUTSIDE  0x3 // 윈도우 비교: Outside

/**
 * @brief ADC 인스턴스 구조체
 */
typedef struct {
    ADC_t *adc;          // ADC 레지스터 포인터
    volatile uint16_t result; // 변환 결과
    volatile uint8_t flags;  // RESRDY, WCMP 플래그
} ADC_Instance;

/**
 * @brief ADC 초기화
 * @param instance ADC 인스턴스 포인터
 * @param adc ADC 레지스터 포인터 (예: &ADC0)
 * @param mode 변환 모드 (ADC_MODE_SINGLE, ADC_MODE_DIFF)
 * @param resolution 해상도 (ADC_RES_10BIT, ADC_RES_12BIT)
 * @param ref 기준 전압 (ADC_REF_1V024, ADC_REF_2V048, ADC_REF_4V096, ADC_REF_2V500, ADC_REF_VDD, ADC_REF_VREFA)
 * @param presc 프리스케일러 (ADC_PRESC_DIV2, ADC_PRESC_DIV4, ..., ADC_PRESC_DIV256)
 * @param input 양극 입력 (ADC_INPUT_AIN0, ADC_INPUT_TEMPSENSE 등)
 */
void adc_init(ADC_Instance *instance, ADC_t *adc, uint8_t mode, uint8_t resolution, uint8_t ref, uint8_t presc, uint8_t input);

/**
 * @brief ADC 활성화
 */
void adc_enable(ADC_Instance *instance);

/**
 * @brief ADC 비활성화
 */
void adc_disable(ADC_Instance *instance);

/**
 * @brief 변환 시작
 */
void adc_start(ADC_Instance *instance);

/**
 * @brief 폴링 기반 결과 읽기
 * @return 변환 결과
 */
uint16_t adc_read(ADC_Instance *instance);

/**
 * @brief 인터럽트 기반 결과 읽기 초기화
 */
void adc_read_it_init(ADC_Instance *instance);

/**
 * @brief 인터럽트 기반 결과 읽기
 * @return 변환 결과
 */
uint16_t adc_read_it(ADC_Instance *instance);

/**
 * @brief 온도 측정
 * @return 온도 (°C)
 */
float adc_temp_read(ADC_Instance *instance);

/**
 * @brief 윈도우 비교 설정
 * @param mode 비교 모드 (ADC_WINCM_BELOW, ADC_WINCM_ABOVE 등)
 * @param low 하한 값
 * @param high 상한 값
 */
void adc_window_config(ADC_Instance *instance, uint8_t mode, uint16_t low, uint16_t high);

/**
 * @brief 플래그 확인 및 클리어
 * @return RESRDY, WCMP 플래그 상태
 */
uint8_t adc_check_flags(ADC_Instance *instance);

#endif // ADC_DRIVER_H
/**
 * @file adc_driver.c
 * @brief AVR128DA64/48/32/28 ADC 드라이버 구현
 * @details 단일/연속 변환, 10/12비트, 인터럽트/폴링, 온도 측정, 윈도우 비교 지원.
 * @author 작성자
 * @date 2025-09-03
 */
#include "adc_driver.h"
#include <avr/interrupt.h>

/**
 * @brief ADC 인스턴스 배열
 */
static ADC_Instance *adc_instances[1] = {NULL};

/**
 * @brief ADC 인스턴스 등록
 */
static void register_instance(ADC_Instance *instance, ADC_t *adc) {
    if (adc == &ADC0) adc_instances[0] = instance;
}

/**
 * @brief ADC 초기화
 */
void adc_init(ADC_Instance *instance, ADC_t *adc, uint8_t mode, uint8_t resolution, uint8_t ref, uint8_t presc, uint8_t input) {
    // VREF 설정
    VREF.ADC0REF = (VREF.ADC0REF & ~(0x7 << 0)) | (ref << 0); // ADC0 기준 전압 설정

    // ADC 설정
    adc->CTRLA = (mode << 6) | (resolution << 2); // 모드 및 해상도 설정
    adc->CTRLC = presc << 3; // 프리스케일러 설정
    adc->MUXPOS = input; // 양극 입력 설정
    if (mode == ADC_MODE_DIFF) adc->MUXNEG = input; // 차동 모드 음극 입력 설정
    adc->CTRLB = 0x0; // 샘플 누적 초기화
    adc->INTFLAGS = (1 << 0) | (1 << 1); // RESRDY, WCMP 플래그 클리어

    // 인스턴스 초기화
    instance->adc = adc;
    instance->result = 0;
    instance->flags = 0;
    register_instance(instance, adc);
}

/**
 * @brief ADC 활성화
 */
void adc_enable(ADC_Instance *instance) {
    instance->adc->CTRLA |= (1 << 7); // ENABLE=1
}

/**
 * @brief ADC 비활성화
 */
void adc_disable(ADC_Instance *instance) {
    instance->adc->CTRLA &= ~(1 << 7); // ENABLE=0
}

/**
 * @brief 변환 시작
 */
void adc_start(ADC_Instance *instance) {
    instance->adc->COMMAND = (1 << 0); // STCONV=1
}

/**
 * @brief 폴링 기반 결과 읽기
 */
uint16_t adc_read(ADC_Instance *instance) {
    while (!(instance->adc->INTFLAGS & (1 << 0))); // RESRDY 대기
    instance->adc->INTFLAGS = (1 << 0); // RESRDY 클리어
    return instance->adc->RES; // 결과 반환
}

/**
 * @brief 인터럽트 기반 결과 읽기 초기화
 */
void adc_read_it_init(ADC_Instance *instance) {
    instance->adc->INTCTRL |= (1 << 0); // RESRDY 인터럽트 활성화
}

/**
 * @brief 인터럽트 기반 결과 읽기
 */
uint16_t adc_read_it(ADC_Instance *instance) {
    return instance->result; // 저장된 결과 반환
}

/**
 * @brief 온도 측정
 */
float adc_temp_read(ADC_Instance *instance) {
    instance->adc->CTRLA = (0 << 6) | (1 << 2); // 단일 엔드, 12비트
    instance->adc->MUXPOS = ADC_INPUT_TEMPSENSE; // TEMPSENSE 입력
    VREF.ADC0REF = (VREF.ADC0REF & ~(0x7 << 0)) | (ADC_REF_2V048 << 0); // 2.048V 기준 전압
    instance->adc->CTRLC = (0x7 << 3); // 최대 프리스케일러
    instance->adc->SAMPCTRL = 28; // SAMPLEN=28
    instance->adc->CTRLD = (0x3 << 5); // INITDLY=25μs 이상
    adc_enable(instance);
    adc_start(instance);
    uint16_t result = adc_read(instance);
    float temp = ((float)SIGROW.TEMPSENSE1 - result) / 4096.0 * SIGROW.TEMPSENSE0;
    adc_disable(instance);
    return temp;
}

/**
 * @brief 윈도우 비교 설정
 */
void adc_window_config(ADC_Instance *instance, uint8_t mode, uint16_t low, uint16_t high) {
    instance->adc->CTRLE = mode; // WINCM 설정
    instance->adc->WINLT = low; // 하한 값
    instance->adc->WINHT = high; // 상한 값
    instance->adc->INTCTRL |= (1 << 1); // WCMP 인터럽트 활성화
}

/**
 * @brief 플래그 확인 및 클리어
 */
uint8_t adc_check_flags(ADC_Instance *instance) {
    uint8_t flags = instance->adc->INTFLAGS & ((1 << 0) | (1 << 1)); // RESRDY, WCMP 읽기
    instance->adc->INTFLAGS = flags; // 플래그 클리어
    return flags;
}

/**
 * @brief ADC0 변환 완료 인터럽트 핸들러
 */
ISR(ADC0_RESRDY_vect) {
    if (adc_instances[0]) {
        adc_instances[0]->result = ADC0.RES; // 결과 저장
        adc_instances[0]->flags |= (1 << 0); // RESRDY 플래그 설정
        ADC0.INTFLAGS = (1 << 0); // RESRDY 클리어
    }
}

/**
 * @brief ADC0 윈도우 비교 인터럽트 핸들러
 */
ISR(ADC0_WCMP_vect) {
    if (adc_instances[0]) {
        adc_instances[0]->flags |= (1 << 1); // WCMP 플래그 설정
        ADC0.INTFLAGS = (1 << 1); // WCMP 클리어
    }
}
/**
 * @file main.c
 * @brief AVR128DA64/48/32/28 ADC 드라이버 테스트 프로그램
 * @details 24MHz 클럭, ADC0으로 단일 변환, 온도 측정, 윈도우 비교 테스트
 * @author 작성자
 * @date 2025-09-03
 */
#ifndef F_CPU
#define F_CPU 24000000UL // 시스템 클럭 주파수 24MHz 정의
#endif

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

/**
 * @brief 시스템 클럭을 24MHz OSCHF로 설정
 */
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 메인 함수
 */
int main(void) {
    clock_init_24mhz();
    sei();

    // USART0 초기화 (printf용)
    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);

    // ADC0 초기화: 단일 엔드, 12비트, 2.048V 기준, /4 프리스케일러, AIN0 입력
    ADC_Instance adc0_instance;
    adc_init(&adc0_instance, &ADC0, ADC_MODE_SINGLE, ADC_RES_12BIT, ADC_REF_2V048, ADC_PRESC_DIV4, ADC_INPUT_AIN0);
    adc_enable(&adc0_instance);

    printf("ADC Driver Test: Single Conversion and Temperature\r\n");
    _delay_ms(1000);

    // 단일 변환 테스트
    adc_start(&adc0_instance);
    uint16_t result = adc_read(&adc0_instance);
    printf("ADC0 (AIN0): %u (%.3fV)\r\n", result, (float)result * 2.048 / 4096.0);

    // 온도 측정 테스트
    float temp = adc_temp_read(&adc0_instance);
    printf("Temperature: %.1f°C\r\n", temp);
    _delay_ms(1000);

    // 윈도우 비교 테스트
    adc_window_config(&adc0_instance, ADC_WINCM_INSIDE, 1000, 3000);
    adc_read_it_init(&adc0_instance);
    adc_start(&adc0_instance);

    while (1) {
        uint8_t flags = adc_check_flags(&adc0_instance);
        if (flags & (1 << 0)) {
            result = adc_read_it(&adc0_instance);
            printf("ADC0 (Interrupt): %u (%.3fV)\r\n", result, (float)result * 2.048 / 4096.0);
            adc_start(&adc0_instance); // 다음 변환 시작
        }
        if (flags & (1 << 1)) {
            printf("ADC0: Window Comparison Triggered\r\n");
        }
        _delay_ms(500);
    }

    return 0;
}
/**
 * @file adc_single_example.c
 * @brief AVR128DA ADC 드라이버 단일 변환 예제
 * @details ADC0을 사용하여 AIN0에서 단일 변환 결과 읽기
 * @author 작성자
 * @date 2025-09-03
 */
#ifndef F_CPU
#define F_CPU 24000000UL
#endif

#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include "adc_driver.h"
#include "uart_driver.h"

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);
}

int main(void) {
    clock_init_24mhz();

    // USART0 초기화 (printf용)
    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);

    // ADC0 초기화
    ADC_Instance adc0_instance;
    adc_init(&adc0_instance, &ADC0, ADC_MODE_SINGLE, ADC_RES_12BIT, ADC_REF_2V048, ADC_PRESC_DIV4, ADC_INPUT_AIN0);
    adc_enable(&adc0_instance);

    printf("ADC Single Conversion Test: Reading AIN0\r\n");
    _delay_ms(1000);

    while (1) {
        adc_start(&adc0_instance);
        uint16_t result = adc_read(&adc0_instance);
        printf("AIN0: %u (%.3fV)\r\n", result, (float)result * 2.048 / 4096.0);
        _delay_ms(1000);
    }

    return 0;
}
/**
 * @file adc_temp_example.c
 * @brief AVR128DA ADC 드라이버 온도 측정 예제
 * @details ADC0을 사용하여 TEMPSENSE 입력으로 온도 측정
 * @author 작성자
 * @date 2025-09-03
 */
#ifndef F_CPU
#define F_CPU 24000000UL
#endif

#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include "adc_driver.h"
#include "uart_driver.h"

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);
}

int main(void) {
    clock_init_24mhz();

    // USART0 초기화 (printf용)
    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);

    // ADC0 초기화
    ADC_Instance adc0_instance;
    adc_init(&adc0_instance, &ADC0, ADC_MODE_SINGLE, ADC_RES_12BIT, ADC_REF_2V048, ADC_PRESC_DIV256, ADC_INPUT_TEMPSENSE);

    printf("ADC Temperature Test: Reading TEMPSENSE\r\n");
    _delay_ms(1000);

    while (1) {
        float temp = adc_temp_read(&adc0_instance);
        printf("Temperature: %.1f°C\r\n", temp);
        _delay_ms(1000);
    }

    return 0;
}

추가팁

  • 클럭 최적화: CLK_ADC는 1~2MHz로 설정하여 변환 정확도를 높이세요 (PRESC 조정).
  • VREF 주의사항: 기준 전압 변경 시 DAC0, ACs 등 다른 장치의 노이즈를 방지하기 위해 관련 장치를 비활성화하세요.
  • 전력 절감: 사용하지 않는 ADC는 adc_disable로 비활성화하고, VREF.ADC0REF의 ALWAYSON=0으로 설정하여 전력을 절감하세요.
  • 입력 보호: AIN0~AIN21 핀에 입력 전압이 VDD를 초과하지 않도록 보호 회로를 추가하세요.
  • 온도 보정: TEMPSENSE 측정 시 SIGROW 값의 공장 보정 데이터를 정확히 사용하세요.
  • 디버깅: adc_check_flagsprintf를 활용해 RESRDY, WCMP 상태를 모니터링하세요.
  • 테스트 환경: Microchip Studio 시뮬레이터 또는 실제 하드웨어에서 전압/온도 데이터를 터미널 프로그램(예: PuTTY)으로 확인하세요.

결론

본 문서에서 구현한 ADC 드라이버는 AVR128DA64/48/32/28 시리즈의 ADC0 모듈을 효과적으로 활용할 수 있도록 설계되었습니다. 단일/연속 변환, 다양한 해상도, 온도 측정, 윈도우 비교 기능 등 풍부한 옵션을 제공하며, 모든 모델에서 동일하게 사용할 수 있습니다. 표준 ISR 기반 인터럽트 처리 방식을 적용하여 안정성을 확보하였고, VREF.ADC0REF를 통해 유연한 기준 전압 선택(1.024V, 2.048V, 2.500V, 4.096V, VDD, VREFA)을 지원합니다. 예제 코드를 통해 손쉽게 테스트와 확장이 가능하며, 이를 통해 임베디드 시스템 개발자는 신뢰성 높은 아날로그 신호 처리 환경을 구현할 수 있습니다. 추가 최적화를 통해 성능과 전력 효율을 동시에 확보할 수 있습니다.

 

반응형