GD32L23x 시리즈에서 ADC와 DMA를 활용한 다채널 전압 측정 및 소프트웨어 트리거 구현

GD32L23x 마이크로컨트롤러는 12비트 분해능의 연 속 근사(Successive Approximation) 방식 ADC를 내장하고 있습니다. 이 ADC는 16개의 외부 채널과 4개의 내부 채널에서 아날로그 신호를 샘플링할 수 있으며, 각 채널은 다양한 작동 모드를 지원합니다. 샘플링 및 변환된 결과는 최소 유효 비트(LSB) 또는 최우선 유효 비트(MSB) 정렬 방식으로 데이터 레지스터에 저장됩니다. 12비트 ADC와 3.3V 기준 전압을 사용할 경우, 디지털 값 1 변화에 해당하는 아날로그 전압 변화는 약 0.8mV (3.3V / 2^12) 입니다.

본 문서에서는 ADC와 DMA를 함께 사용하여 3개의 채널에서 전압을 샘플링하는 방법을 다룹니다. 안정적인 데이터 취득을 위해 DMA 전송을 설정하는 과정을 포함합니다. ADC 설정 시 고려해야 할 주요 사항은 다음과 같습니다:

  1. GPIO 설정 (클럭 활성화, 아날로그 입력 모드 설정)
  2. ADC 파라미터 설정 (작동 모드 - 스캔 모드, 연속 모드; 트리거 방식; 채널 설정 - 규칙 그룹 또는 삽입 그룹; ADC 보정)
  3. 인터럽트 및 DMA 설정 (활성화)

참고: ADC의 규칙 그룹(Regular group)은 주기적으로 실행되는 변환을, 삽입 그룹(Injected group)은 인터럽트 기반의 즉각적인 변환을 의미합니다.

ADC 전체 초기화 (PA1, PA6, PB1 채널 사용)

아래 코드는 PA1, PA6, PB1 핀을 ADC 입력으로 사용하기 위한 초기화 함수입니다. 특정 채널에 대한 GPIO 핀 매핑은 해당 칩의 데이터시트를 참조해야 합니다. 예를 들어, PA1은 ADC 채널 1에 해당합니다.

#include "gd32l23x_gpio.h"
#include "gd32l23x_rcu.h"
#include "gd32l23x_adc.h"
#include "gd32l23x_dma.h"

// ADC 샘플링 데이터 저장을 위한 구조체
typedef struct {
    uint16_t sampling_data[3]; // 3개 채널의 데이터 저장
} adc_data_info_struct;

adc_data_info_struct adc_datainfo;

void ADC_All_Init(void)
{
    // GPIO 클럭 활성화
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_GPIOB);

    // GPIO 핀을 아날로그 입력 모드로 설정
    gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_1 | GPIO_PIN_6);
    gpio_mode_set(GPIOB, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_1);

    // ADC 모듈 초기화
    ADC_Module_Init();
    // DMA 모듈 초기화
    DMA_ADC_Configure();
}

ADC 상세 설정

프로그래머블 샘플링 시간 계산

12비트 ADC의 총 샘플링 및 변환 시간은 '샘플링 시간 + 12.5' CKADC 주기입니다. 예를 들어, CKADC가 16MHz이고 샘플링 시간이 2.5주기라면, 총 변환 시간은 (2.5 + 12.5) CKADC 주기가 되며, 이는 0.9375us (15 / 16MHz) 입니다.

본 프로젝트에서는 APB2 버스 클럭이 16MHz이고, 8분주를 사용하여 ADC 클럭(CKADC)을 2MHz로 설정합니다. 이때 샘플링 시간을 55.5주기로 설정하면, 총 변환 시간은 약 34us ((55.5 + 12.5) / 2MHz)가 됩니다.

자동 보정 기능

ADC는 자체 보정 기능을 제공합니다. 보정 과정 동안 ADC는 내부 보정 계수를 계산하며, 이 계수는 ADC 전원이 공급되는 동안 유효합니다. 보정 중에는 ADC를 사용할 수 없으며, 보정이 완료될 때까지 기다려야 합니다. 보정 진행 상태는 CLB 비트로 확인할 수 있으며, 보정이 완료되면 하드웨어에 의해 자동으로 0으로 초기화됩니다. VDDA, VREF+ 및 온도와 같은 ADC 작동 조건이 변경되거나 A/D 변환을 처음 시작할 때 보정 기능을 수행하는 것이 좋습니다.

static void ADC_Module_Init(void)
{
    // ADC 클럭 설정: APB2 클럭을 8분주하여 2MHz로 설정 (ADC 최대 클럭: 16MHz)
    rcu_periph_clock_enable(RCU_ADC);
    rcu_adc_clock_config(RCU_ADCCK_APB2_DIV8);

    // ADC 기본 설정 초기화
    adc_deinit();

    // 스캔 모드 및 연속 모드 활성화
    adc_special_function_config(ADC_SCAN_MODE, ENABLE);
    adc_special_function_config(ADC_CONTINUOUS_MODE, ENABLE);

    // ADC 분해능을 12비트로 설정
    adc_resolution_config(ADC_RESOLUTION_12B);
    // 데이터 정렬을 오른쪽으로 설정
    adc_data_alignment_config(ADC_DATAALIGN_RIGHT);

    // 규칙 그룹 채널 수 설정 (3개 채널)
    adc_channel_length_config(ADC_REGULAR_CHANNEL, 3);

    // 외부 트리거 비활성화 (소프트웨어 트리거 사용)
    adc_external_trigger_source_config(ADC_REGULAR_CHANNEL, ADC_EXTTRIG_REGULAR_NONE);
    adc_external_trigger_config(ADC_REGULAR_CHANNEL, ENABLE);

    // 규칙 그룹 채널 구성
    // 채널 0: ADC_CHANNEL_1 (PA1 핀) - 샘플링 시간 55.5 ADC 클럭 주기
    adc_regular_channel_config(0, ADC_CHANNEL_1, ADC_SAMPLETIME_55POINT5);
    // 채널 1: ADC_CHANNEL_9 (PB1 핀) - 샘플링 시간 55.5 ADC 클럭 주기
    adc_regular_channel_config(1, ADC_CHANNEL_9, ADC_SAMPLETIME_55POINT5);
    // 채널 2: ADC_CHANNEL_6 (PA6 핀) - 샘플링 시간 55.5 ADC 클럭 주기
    adc_regular_channel_config(2, ADC_CHANNEL_6, ADC_SAMPLETIME_55POINT5);

    // ADC 활성화
    adc_enable();
    // ADC 보정 활성화
    adc_calibration_enable();
    // DMA 모드 활성화
    adc_dma_mode_enable();

    // 규칙 그룹에 대한 소프트웨어 트리거 활성화 (ADC 변환 시작)
    adc_software_trigger_enable(ADC_REGULAR_CHANNEL);
}

DMA 설정

DMA(Direct Memory Access)를 사용하여 ADC 변환 결과를 메모리로 직접 전송하도록 설정합니다. 이는 CPU 개입 없이 고속 데이터 전송을 가능하게 합니다.

static void DMA_ADC_Configure(void)
{
    dma_parameter_struct dma_init_struct;

    // DMA 컨트롤러 클럭 활성화
    rcu_periph_clock_enable(RCU_DMA);

    // DMA 채널 0 초기화
    dma_deinit(DMA_CH0);

    // DMA 파라미터 설정 구조체 초기화
    dma_init_struct.periph_addr         = (uint32_t)&ADC_RDATA; // ADC 데이터 레지스터 주소
    dma_init_struct.direction           = DMA_PERIPHERAL_TO_MEMORY; // 주변 장치에서 메모리로 전송
    dma_init_struct.memory_addr         = (uint32_t)(&adc_datainfo.sampling_data); // 메모리 버퍼 주소
    dma_init_struct.number              = 3; // 전송할 데이터 수 (3개 채널)
    dma_init_struct.periph_inc          = DMA_PERIPH_INCREASE_DISABLE; // 주변 장치 주소 고정
    dma_init_struct.periph_width        = DMA_PERIPHERAL_WIDTH_16BIT; // 주변 장치 데이터 폭 (16비트)
    dma_init_struct.memory_inc          = DMA_MEMORY_INCREASE_ENABLE; // 메모리 주소 자동 증가
    dma_init_struct.memory_width        = DMA_MEMORY_WIDTH_16BIT; // 메모리 데이터 폭 (16비트)
    dma_init_struct.priority            = DMA_PRIORITY_HIGH; // DMA 채널 우선순위

    // DMA 채널 초기화
    dma_init(DMA_CH0, &dma_init_struct);

    // DMA 채널 0의 요청을 ADC로 설정
    dma_periph_request_config(DMA_CH0, DMA_REQUEST_ADC);

    // DMA 순환 모드 활성화
    dma_circulation_enable(DMA_CH0);
    // DMA 채널 0 활성화
    dma_channel_enable(DMA_CH0);
}

ADC 폴링 방식 데이터 읽기

DMA가 설정되면, ADC는 자동으로 변환된 데이터를 메모리 버퍼로 전송합니다. 다음 함수는 특정 채널의 ADC 값을 읽어오는 예시입니다. 이 함수는 소프트웨어 트리거를 다시 활성화하여 ADC 변환을 명시적으로 시작할 수도 있지만, 연속 모드에서는 이미 변환이 진행 중이므로 주로 데이터 접근 및 플래그 처리에 사용됩니다.

static uint16_t Get_ADC_Channel_Value(uint8_t channel_index)
{
    // DMA 설정으로 인해 adc_datainfo.sampling_data 배열에 값이 자동으로 채워집니다.
    // 특정 채널의 값을 반환합니다.
    // channel_index: 0 (ADC_CHANNEL_1), 1 (ADC_CHANNEL_9), 2 (ADC_CHANNEL_6)

    // ADC_FLAG_STRC 플래그는 변환 완료를 나타내며, DMA 사용 시 명시적으로 클리어할 필요는 없을 수 있습니다.
    // 그러나 일부 상황에서는 이전 변환의 플래그가 남아있을 수 있으므로 클리어하는 것이 안전할 수 있습니다.
    // adc_flag_clear(ADC_FLAG_STRC); // 필요에 따라 주석 해제

    if (channel_index < 3) {
        return adc_datainfo.sampling_data[channel_index];
    } else {
        return 0; // 잘못된 인덱스
    }
}

GD32 ADC + DMA 사용 시 주의사항

  • 스캔 및 연속 모드 필수 설정: ADC를 다채널 및 연속적으로 샘플링하려면 반드시 스캔 모드(ADC_SCAN_MODE)와 연속 모드(ADC_CONTINUOUS_MODE)를 활성화해야 합니다.
    adc_special_function_config(ADC_SCAN_MODE, ENABLE);
    adc_special_function_config(ADC_CONTINUOUS_MODE, ENABLE);
    
  • 지속적인 소프트웨어 트리거: 규칙 채널 설정을 한 후, DMA 없이 데이터를 주기적으로 읽으려면 매번 소프트웨어 트리거를 활성화해야 합니다. 하지만 DMA와 연속 모드를 함께 사용할 때는 초기화 시 한 번의 트리거로 연속 변환이 시작되며, DMA가 데이터를 계속해서 메모리로 옮깁니다. 만약 폴링 방식으로 데이터를 읽으려면, 변환을 강제로 시작하기 위해 소프트웨어 트리거를 다시 활성화해야 할 수 있습니다.
    // DMA 사용 시, 연속 모드에서는 초기화 시 한번 활성화하는 것으로 충분합니다.
    // adc_software_trigger_enable(ADC_REGULAR_CHANNEL);
    
  • GPIO 핀 확인: 각 ADC 채널에 해당하는 GPIO 핀을 정확히 확인하기 위해 데이터시트를 참조하는 것이 중요합니다. 잘못된 핀 설정은 원하는 채널에서 데이터를 읽지 못하게 합니다.
    // PA1 - ADC_CHANNEL_1
    adc_regular_channel_config(0, ADC_CHANNEL_1, ADC_SAMPLETIME_55POINT5);
    // PB1 - ADC_CHANNEL_9
    adc_regular_channel_config(1, ADC_CHANNEL_9, ADC_SAMPLETIME_55POINT5);
    // PA6 - ADC_CHANNEL_6
    adc_regular_channel_config(2, ADC_CHANNEL_6, ADC_SAMPLETIME_55POINT5);
    
  • 순환(Circular) 모드 권장: DMA 설정을 할 때 순환 모드(dma_circulation_enable)를 사용하면, DMA 전송이 완료된 후 자동으로 다음 전송을 시작하므로 연속적인 데이터 처리에 유리합니다.

태그: GD32L23x ADC DMA 다채널 전압 측정

8월 24일 02:33에 게시됨