C 언어로 임베디드 리눅스에서 ADC 입력 처리 방법

임베디드 리눅스 환경에서 센서 데이터를 수집하는 중요한 과정 중 하나는 아날로그 신호를 디지털로 변환하는 ADC(Analog-to-Digital Converter) 입력 처리입니다. 본 기사에서는 C 언어를 활용하여 임베디드 리눅스에서 ADC 입력 데이터를 효과적으로 읽고 처리하는 방법을 단계별로 설명하며, 이를 통해 센서 데이터를 실시간으로 활용하는 기술을 이해할 수 있도록 도와드립니다.

임베디드 리눅스와 ADC의 역할


임베디드 리눅스에서 ADC(Analog-to-Digital Converter)는 아날로그 신호를 디지털 데이터로 변환하여 센서와 프로세서 간의 통신을 가능하게 합니다.

ADC의 기본 원리


ADC는 연속적인 아날로그 입력 신호를 정해진 샘플링 속도로 디지털 값으로 변환합니다. 변환된 값은 비트 해상도(예: 10비트, 12비트)에 따라 디지털 표현의 정밀도가 결정됩니다.

임베디드 리눅스에서 ADC의 필요성


센서 데이터(예: 온도, 압력, 조도)는 대부분 아날로그 신호로 제공됩니다. 이를 프로세서가 이해할 수 있도록 디지털 데이터로 변환해야만 소프트웨어에서 처리할 수 있습니다. 임베디드 리눅스는 이 변환 과정을 제어하고 데이터를 분석하는 데 필요한 플랫폼을 제공합니다.

ADC와 센서 데이터의 연결


센서와 ADC 간의 인터페이스는 일반적으로 다음과 같은 과정을 거칩니다:

  1. 센서 출력 → 아날로그 신호 생성
  2. ADC 입력 → 아날로그 신호 변환
  3. 변환된 데이터 → 프로세서로 전달

이 과정은 센서 데이터의 정확성과 실시간 처리를 보장하는 핵심 단계입니다.

C 언어로 ADC 데이터 읽기 준비

ADC 장치 파일 접근


임베디드 리눅스에서 ADC 입력은 일반적으로 /sys 또는 /dev 디렉토리 아래의 장치 파일로 제공됩니다. 예를 들어, Raspberry Pi와 같은 장치에서는 /sys/bus/iio/devices/iio:device0/in_voltageX_raw 형식으로 ADC 데이터를 읽을 수 있습니다.

ADC 설정을 위한 기본 준비


ADC 데이터를 읽기 전에 다음 단계를 준비해야 합니다:

  1. ADC 드라이버 확인: ADC 하드웨어에 적합한 드라이버가 커널에 로드되어 있는지 확인합니다.
  2. 장치 파일 경로 확인: 사용할 ADC 입력 채널의 장치 파일 경로를 파악합니다.
  3. 사용 권한 설정: ADC 장치 파일에 접근할 수 있도록 적절한 권한을 부여하거나 루트 권한으로 실행합니다.

C 언어에서 파일 접근


C 언어로 ADC 데이터를 읽기 위해 표준 파일 I/O 함수인 fopen(), fscanf(), fclose()를 사용할 수 있습니다.

#include <stdio.h>
#include <stdlib.h>

int main() {
    const char *adc_path = "/sys/bus/iio/devices/iio:device0/in_voltage0_raw";
    FILE *adc_file;
    int adc_value;

    // ADC 파일 열기
    adc_file = fopen(adc_path, "r");
    if (adc_file == NULL) {
        perror("ADC 파일 열기 실패");
        return EXIT_FAILURE;
    }

    // ADC 값 읽기
    fscanf(adc_file, "%d", &adc_value);
    printf("ADC 값: %d\n", adc_value);

    // 파일 닫기
    fclose(adc_file);

    return EXIT_SUCCESS;
}

장치 파일 접근과 오류 처리

  • 파일 경로가 유효하지 않을 경우 프로그램이 실패할 수 있으므로 경로를 확인하고 오류 처리를 추가해야 합니다.
  • 접근 권한 문제가 발생할 경우 chmod 명령어로 파일 권한을 수정하거나 sudo를 사용합니다.

이와 같은 준비 작업은 ADC 데이터를 올바르게 읽기 위한 기초를 제공합니다.

ADC 데이터 읽기 코드 구현

C 언어로 ADC 데이터를 읽는 기본 코드


ADC 데이터를 읽는 과정은 장치 파일에서 데이터를 열고 읽는 간단한 프로세스로 구성됩니다. 아래는 C 언어를 사용하여 ADC 데이터를 읽는 기본 코드 예제입니다:

#include <stdio.h>
#include <stdlib.h>

// ADC 데이터 읽기 함수
int read_adc(const char *adc_path) {
    FILE *adc_file;
    int adc_value;

    // ADC 장치 파일 열기
    adc_file = fopen(adc_path, "r");
    if (adc_file == NULL) {
        perror("ADC 파일 열기 실패");
        return -1;
    }

    // ADC 값 읽기
    if (fscanf(adc_file, "%d", &adc_value) != 1) {
        perror("ADC 값 읽기 실패");
        fclose(adc_file);
        return -1;
    }

    // 파일 닫기
    fclose(adc_file);

    return adc_value;
}

int main() {
    const char *adc_path = "/sys/bus/iio/devices/iio:device0/in_voltage0_raw";
    int adc_value;

    // ADC 데이터 읽기
    adc_value = read_adc(adc_path);
    if (adc_value < 0) {
        printf("ADC 데이터를 읽는 데 실패했습니다.\n");
        return EXIT_FAILURE;
    }

    // 읽은 데이터 출력
    printf("ADC 값: %d\n", adc_value);

    return EXIT_SUCCESS;
}

코드의 주요 기능 설명

  1. 장치 파일 열기
  • fopen() 함수를 사용하여 ADC 장치 파일을 읽기 모드로 엽니다.
  1. ADC 데이터 읽기
  • fscanf()를 사용하여 ADC 장치 파일에서 데이터를 읽습니다. ADC의 출력은 일반적으로 정수 값으로 제공됩니다.
  1. 오류 처리
  • 파일 열기 실패 및 데이터 읽기 실패 시 적절한 오류 메시지를 출력하고 프로그램을 종료합니다.
  1. 재사용 가능한 함수 설계
  • read_adc() 함수로 구현하여 다른 ADC 입력을 읽을 때도 간편하게 사용할 수 있도록 설계합니다.

출력 예제


ADC 입력 장치가 유효한 값(예: 12비트 ADC의 경우 0~4095)을 출력한다고 가정할 때, 프로그램 실행 결과는 다음과 같습니다:

ADC 값: 2048

확장: 다중 채널 읽기


다수의 ADC 입력 채널을 처리하려면 각 채널의 장치 파일 경로를 배열로 저장하고 루프를 사용하여 데이터를 읽을 수 있습니다.

const char *adc_paths[] = {
    "/sys/bus/iio/devices/iio:device0/in_voltage0_raw",
    "/sys/bus/iio/devices/iio:device0/in_voltage1_raw",
    "/sys/bus/iio/devices/iio:device0/in_voltage2_raw"
};
int num_channels = 3;

for (int i = 0; i < num_channels; i++) {
    int value = read_adc(adc_paths[i]);
    if (value >= 0) {
        printf("Channel %d ADC 값: %d\n", i, value);
    }
}

이 코드는 다중 센서를 사용하는 프로젝트에서 효율적으로 ADC 데이터를 처리하는 방법을 보여줍니다.

데이터 변환 및 처리 방법

ADC에서 읽어온 데이터는 일반적으로 정수 값이며, 이를 실제 물리적 단위(예: 전압, 온도)로 변환하거나 필터링하여 유용한 정보를 추출해야 합니다.

ADC 데이터의 단위 변환


ADC 데이터는 변환 전압 범위와 해상도에 따라 계산됩니다. 예를 들어, 12비트 ADC가 0~3.3V 범위를 지원하는 경우, 변환 공식은 다음과 같습니다:

[
\text{실제 전압} = \frac{\text{ADC 값}}{\text{최대 ADC 값}} \times \text{참조 전압}
]

최대 ADC 값은 (2^{\text{해상도}} – 1)로 계산됩니다.
C 언어로 변환 코드를 작성하면 다음과 같습니다:

#include <stdio.h>

float convert_to_voltage(int adc_value, int resolution, float reference_voltage) {
    int max_adc_value = (1 << resolution) - 1;  // 최대 ADC 값 계산
    return (float)adc_value / max_adc_value * reference_voltage;
}

int main() {
    int adc_value = 2048;  // 예제 ADC 값
    int resolution = 12;  // 12비트 ADC
    float reference_voltage = 3.3;  // 참조 전압 (3.3V)

    float voltage = convert_to_voltage(adc_value, resolution, reference_voltage);
    printf("ADC 값: %d, 변환된 전압: %.2fV\n", adc_value, voltage);

    return 0;
}

출력 결과(예제):

ADC 값: 2048, 변환된 전압: 1.65V

데이터 필터링


센서 데이터는 노이즈가 포함될 수 있으므로 필터링이 필요합니다. 가장 간단한 필터는 평균 필터와 이동 평균 필터입니다.

평균 필터
여러 샘플의 평균을 계산하여 노이즈를 줄입니다.

float calculate_average(int *values, int num_samples) {
    int sum = 0;
    for (int i = 0; i < num_samples; i++) {
        sum += values[i];
    }
    return (float)sum / num_samples;
}

이동 평균 필터
실시간으로 데이터를 처리할 때 최근 (N)개의 데이터를 기반으로 평균을 계산합니다.

#define WINDOW_SIZE 5

float calculate_moving_average(int new_value, int *window, int *index) {
    static int sum = 0;

    // 이전 값 제거
    sum -= window[*index];
    // 새 값 추가
    window[*index] = new_value;
    sum += new_value;

    // 인덱스 순환
    *index = (*index + 1) % WINDOW_SIZE;

    return (float)sum / WINDOW_SIZE;
}

실시간 데이터 변환과 필터링


다음 코드는 ADC 값을 읽고 변환한 후 필터링 과정을 실시간으로 처리하는 예제입니다:

#include <stdio.h>

#define WINDOW_SIZE 5

int main() {
    int adc_values[] = {2048, 2100, 1980, 2050, 2030};  // 예제 데이터
    int window[WINDOW_SIZE] = {0};  // 이동 평균 창
    int index = 0;

    float reference_voltage = 3.3;
    int resolution = 12;

    for (int i = 0; i < 5; i++) {
        // 단위 변환
        float voltage = convert_to_voltage(adc_values[i], resolution, reference_voltage);

        // 필터링
        float filtered_voltage = calculate_moving_average(adc_values[i], window, &index);

        // 결과 출력
        printf("원본 전압: %.2fV, 필터링된 전압: %.2fV\n", voltage, filtered_voltage);
    }

    return 0;
}

결론


ADC 데이터를 변환하고 필터링하는 과정은 노이즈를 줄이고 센서 데이터를 더 유의미하게 만드는 중요한 단계입니다. 평균 필터와 이동 평균 필터는 간단하면서도 효과적인 방법으로, 다양한 임베디드 애플리케이션에서 널리 사용됩니다.

주기적 데이터 읽기를 위한 타이머 구현

센서 데이터를 일정한 간격으로 읽어와야 하는 경우, 주기적인 타이머를 설정하는 것이 중요합니다. C 언어에서는 POSIX 타이머나 usleep()과 같은 함수를 활용하여 주기적인 동작을 구현할 수 있습니다.

POSIX 타이머를 사용한 주기적 데이터 읽기


POSIX 타이머는 정확한 주기로 동작하는 타이머를 설정할 수 있습니다. 아래는 타이머를 사용하여 ADC 데이터를 주기적으로 읽는 예제입니다:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>

#define TIMER_INTERVAL_MS 1000  // 주기: 1000ms (1초)

// 타이머 이벤트 핸들러
void timer_handler(int signum) {
    static int count = 0;
    printf("주기적 작업 실행 #%d\n", ++count);

    // 여기서 ADC 값을 읽는 작업을 수행
    const char *adc_path = "/sys/bus/iio/devices/iio:device0/in_voltage0_raw";
    int adc_value;

    // ADC 데이터 읽기 (이전에 구현한 read_adc 함수 사용)
    adc_value = read_adc(adc_path);
    if (adc_value >= 0) {
        printf("ADC 값: %d\n", adc_value);
    } else {
        printf("ADC 값을 읽는 데 실패했습니다.\n");
    }
}

int main() {
    struct sigaction sa;
    struct itimerval timer;

    // SIGALRM 시그널 핸들러 설정
    sa.sa_handler = &timer_handler;
    sa.sa_flags = SA_RESTART;
    sigaction(SIGALRM, &sa, NULL);

    // 타이머 설정 (1초 주기로 실행)
    timer.it_value.tv_sec = TIMER_INTERVAL_MS / 1000;
    timer.it_value.tv_usec = (TIMER_INTERVAL_MS % 1000) * 1000;
    timer.it_interval.tv_sec = TIMER_INTERVAL_MS / 1000;
    timer.it_interval.tv_usec = (TIMER_INTERVAL_MS % 1000) * 1000;

    setitimer(ITIMER_REAL, &timer, NULL);

    printf("타이머가 시작되었습니다. CTRL+C로 종료할 수 있습니다.\n");

    // 무한 대기 루프
    while (1) {
        pause();  // 시그널 대기
    }

    return 0;
}

코드 설명

  1. POSIX 타이머 설정
  • setitimer()를 사용하여 1초 주기로 타이머를 설정합니다.
  • itimerval 구조체에서 초기 실행 시간(it_value)과 반복 주기(it_interval)를 지정합니다.
  1. 시그널 핸들러 정의
  • SIGALRM 시그널이 발생하면 타이머 핸들러(timer_handler)가 호출됩니다.
  • 핸들러 내에서 ADC 데이터를 읽는 작업을 수행합니다.
  1. 무한 대기 루프
  • pause()를 사용하여 시그널 발생 시까지 대기합니다.

usleep()을 사용한 간단한 구현


usleep() 함수는 간단한 타이머를 구현할 수 있지만, 정확도가 상대적으로 낮고 실시간 응답성을 보장하지 않습니다.

#include <stdio.h>
#include <unistd.h>

#define READ_INTERVAL_MS 1000  // 주기: 1000ms (1초)

int main() {
    const char *adc_path = "/sys/bus/iio/devices/iio:device0/in_voltage0_raw";

    while (1) {
        // ADC 데이터 읽기
        int adc_value = read_adc(adc_path);
        if (adc_value >= 0) {
            printf("ADC 값: %d\n", adc_value);
        } else {
            printf("ADC 값을 읽는 데 실패했습니다.\n");
        }

        // 주기 대기
        usleep(READ_INTERVAL_MS * 1000);  // 밀리초를 마이크로초로 변환
    }

    return 0;
}

POSIX 타이머와 usleep()의 차이점

  • POSIX 타이머
  • 시그널 기반으로 동작하여 더 정확한 타이밍을 제공합니다.
  • CPU 부하가 낮습니다.
  • usleep()
  • 간단하게 사용할 수 있지만, 높은 정확도를 요구하는 경우 부적합합니다.

결론


주기적으로 데이터를 읽는 타이머는 임베디드 시스템에서 필수적인 도구입니다. POSIX 타이머는 정확도가 요구되는 응용 프로그램에 적합하며, 간단한 경우에는 usleep()을 사용할 수 있습니다. 이러한 타이머를 활용하면 실시간 데이터 수집 및 처리가 가능합니다.

센서 데이터의 시각화 및 저장

센서 데이터를 읽고 처리한 후, 데이터를 저장하거나 시각화하면 시스템의 동작 상태를 확인하고 분석할 수 있습니다. 이는 특히 디버깅, 모니터링, 및 장기적인 데이터 분석에 유용합니다.

데이터 저장


읽은 ADC 데이터를 파일로 저장하면 나중에 분석하거나 다른 애플리케이션에서 사용할 수 있습니다.

CSV 파일에 데이터 저장
CSV는 데이터 저장 및 분석에 널리 사용되는 간단한 형식입니다. 아래는 ADC 데이터를 주기적으로 읽어 CSV 파일에 저장하는 예제입니다:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

#define READ_INTERVAL_MS 1000  // 주기: 1000ms (1초)

int main() {
    const char *adc_path = "/sys/bus/iio/devices/iio:device0/in_voltage0_raw";
    const char *output_file = "adc_data.csv";
    FILE *file = fopen(output_file, "w");

    if (file == NULL) {
        perror("파일 열기 실패");
        return EXIT_FAILURE;
    }

    // CSV 헤더 작성
    fprintf(file, "Timestamp,ADC Value\n");

    while (1) {
        // ADC 값 읽기
        int adc_value = read_adc(adc_path);
        if (adc_value < 0) {
            printf("ADC 값을 읽는 데 실패했습니다.\n");
            break;
        }

        // 현재 시간 가져오기
        time_t now = time(NULL);
        struct tm *t = localtime(&now);
        char timestamp[20];
        strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", t);

        // 데이터 저장
        fprintf(file, "%s,%d\n", timestamp, adc_value);
        fflush(file);  // 즉시 디스크에 기록

        printf("데이터 저장: %s, ADC 값: %d\n", timestamp, adc_value);

        // 주기 대기
        usleep(READ_INTERVAL_MS * 1000);
    }

    fclose(file);
    return 0;
}

출력 예제 (CSV 내용):

Timestamp,ADC Value
2025-01-18 10:00:00,2048
2025-01-18 10:00:01,2050
2025-01-18 10:00:02,2045

데이터 시각화


데이터를 시각화하면 추세를 확인하고 패턴을 분석할 수 있습니다. C 언어에서는 그래픽 라이브러리를 사용하거나 CSV 데이터를 Python이나 MATLAB과 같은 외부 도구로 시각화할 수 있습니다.

Python으로 데이터 시각화
Python의 matplotlib 라이브러리를 사용하여 CSV 데이터를 시각화하는 방법은 다음과 같습니다:

import matplotlib.pyplot as plt
import pandas as pd

# CSV 데이터 읽기
data = pd.read_csv("adc_data.csv")

# 데이터 시각화
plt.plot(data["Timestamp"], data["ADC Value"], marker='o')
plt.title("ADC Data Over Time")
plt.xlabel("Timestamp")
plt.ylabel("ADC Value")
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

출력 그래프 예제:

  • X축: 시간 (Timestamp)
  • Y축: ADC 값 (ADC Value)

파일 저장과 시각화의 이점

  1. 문제 분석: 저장된 데이터를 통해 오류나 이상 동작을 분석할 수 있습니다.
  2. 성능 모니터링: 실시간 시각화를 통해 시스템 성능을 확인할 수 있습니다.
  3. 장기적 데이터 분석: 저장된 데이터를 사용하여 장기적인 트렌드를 파악하고 최적화 방향을 도출할 수 있습니다.

결론


ADC 데이터를 저장하고 시각화하는 것은 센서 기반 시스템의 상태를 이해하고 개선하는 데 필수적입니다. CSV 형식의 데이터 저장은 간단하고 범용적인 방법이며, Python과 같은 도구를 사용한 시각화는 데이터의 통찰력을 높이는 데 유용합니다.

ADC 관련 트러블슈팅

ADC 데이터를 읽을 때 다양한 문제가 발생할 수 있습니다. 이를 해결하기 위해 주요 오류 원인과 해결 방법을 알아봅니다.

1. ADC 데이터 읽기 실패

원인

  • ADC 장치 파일 경로가 잘못되었거나, ADC 드라이버가 로드되지 않았습니다.
  • 사용자 권한 부족으로 장치 파일에 접근할 수 없습니다.

해결 방법

  1. 장치 파일 확인
  • 장치 파일이 존재하는지 확인:
    bash ls /sys/bus/iio/devices/
  • ADC 관련 파일(예: in_voltage0_raw)이 존재하지 않으면 드라이버를 확인하거나 활성화해야 합니다.
  1. 드라이버 확인 및 로드
  • 적합한 ADC 드라이버가 커널에 로드되어 있는지 확인합니다:
    bash lsmod | grep adc
  • 드라이버가 없으면 modprobe 명령을 사용하여 로드합니다.
  1. 파일 접근 권한 수정
  • 파일 권한 문제 해결:
    bash sudo chmod 666 /sys/bus/iio/devices/iio:device0/in_voltage0_raw

2. ADC 값이 고정되어 있거나 불규칙

원인

  • 센서가 제대로 연결되지 않았거나 ADC 입력 핀이 부동 상태입니다.
  • 전원 공급이나 센서 출력 신호에 문제가 있습니다.

해결 방법

  1. 하드웨어 연결 점검
  • 센서와 ADC 핀 연결 상태를 확인합니다.
  • 입력 핀이 부동 상태라면 풀업/풀다운 저항을 추가합니다.
  1. 센서 출력 확인
  • 멀티미터로 센서 출력 전압을 확인하여 예상 범위 내에 있는지 확인합니다.
  • 센서의 전원 공급이 안정적인지 점검합니다.
  1. 신호 안정화
  • 노이즈 제거를 위해 입력 핀에 적합한 필터(예: RC 필터)를 추가합니다.

3. ADC 값의 범위 초과

원인

  • 입력 전압이 ADC의 참조 전압 범위를 초과합니다.
  • ADC 해상도 설정이 잘못되었습니다.

해결 방법

  1. 입력 전압 확인
  • 입력 신호가 ADC의 참조 전압 범위(예: 0~3.3V) 내에 있는지 확인합니다.
  • 초과할 경우 전압 분배 회로를 추가하여 신호를 조정합니다.
  1. ADC 설정 점검
  • 해상도 설정(예: 10비트, 12비트)이 맞는지 확인합니다.
  • ADC 레지스터 값이 적절하게 초기화되었는지 확인합니다.

4. 데이터 읽기 속도가 느림

원인

  • 파일 I/O 방식이 비효율적이거나 주기적인 읽기 로직이 최적화되지 않았습니다.

해결 방법

  1. 비동기 읽기 구현
  • POSIX 타이머나 별도의 스레드를 활용하여 효율적으로 데이터를 읽습니다.
  1. 필요 없는 작업 제거
  • 파일 열기와 닫기를 반복하지 않고, 한 번 열어서 여러 번 읽도록 구현합니다.

5. 예상치 못한 노이즈 문제

원인

  • 신호 노이즈로 인해 ADC 데이터가 불안정합니다.
  • 전원 라인 노이즈 또는 EMI(전자기 간섭)가 원인일 수 있습니다.

해결 방법

  1. 하드웨어 노이즈 감소
  • 신호선에 필터(예: 저역 통과 필터)를 추가합니다.
  • ADC 입력 라인의 배선을 짧게 하고 차폐 케이블을 사용합니다.
  1. 소프트웨어 필터 적용
  • 평균 필터나 이동 평균 필터를 사용하여 노이즈를 제거합니다.

결론


ADC 관련 문제는 하드웨어와 소프트웨어의 결합으로 발생할 수 있습니다. 문제를 체계적으로 분석하고 하드웨어 상태, 드라이버 설정, 소프트웨어 구현을 점검하여 신뢰할 수 있는 데이터 읽기를 보장할 수 있습니다. 이러한 트러블슈팅 방법은 안정적인 임베디드 시스템 구현에 필수적입니다.

예제 프로젝트: 온도 센서 데이터 읽기

ADC와 온도 센서를 활용한 프로젝트는 ADC 입력 처리를 배우기에 적합한 사례입니다. 여기서는 임베디드 리눅스 환경에서 온도 센서를 사용해 데이터를 읽고 처리하는 과정을 설명합니다.

프로젝트 개요


이 프로젝트는 LM35와 같은 온도 센서를 사용하여 온도를 측정하고 ADC를 통해 데이터를 읽은 후, 이를 섭씨 온도로 변환하는 과정을 포함합니다.

구성 요소:

  • 센서: LM35 온도 센서 (출력 전압 10mV/°C)
  • ADC: 임베디드 보드의 ADC 핀
  • 참조 전압: 3.3V
  • ADC 해상도: 10비트

하드웨어 연결

  1. 센서 연결:
  • LM35의 출력 핀을 ADC 입력 핀에 연결합니다.
  • 전원(VCC)과 접지(GND)를 적절히 연결합니다.
  1. 전압 확인:
  • LM35의 출력 전압은 섭씨 온도에 따라 10mV 단위로 변합니다. 예를 들어, 25°C에서는 250mV 출력이 예상됩니다.

데이터 읽기 및 변환

ADC를 통해 읽은 데이터를 섭씨 온도로 변환하는 방법은 아래와 같습니다:

[
\text{온도 (°C)} = \left(\frac{\text{ADC 값}}{\text{최대 ADC 값}}\right) \times \text{참조 전압} \div 0.01
]

코드 구현:

#include <stdio.h>
#include <stdlib.h>

#define ADC_RESOLUTION 1024  // 10비트 ADC
#define REF_VOLTAGE 3.3      // 참조 전압 (3.3V)
#define TEMP_COEFFICIENT 0.01  // LM35 출력 전압 10mV/°C

// 온도 변환 함수
float convert_to_temperature(int adc_value) {
    float voltage = (float)adc_value / (ADC_RESOLUTION - 1) * REF_VOLTAGE;
    return voltage / TEMP_COEFFICIENT;
}

int main() {
    const char *adc_path = "/sys/bus/iio/devices/iio:device0/in_voltage0_raw";
    int adc_value;

    // ADC 데이터 읽기
    adc_value = read_adc(adc_path);
    if (adc_value < 0) {
        printf("ADC 데이터를 읽는 데 실패했습니다.\n");
        return EXIT_FAILURE;
    }

    // 온도 변환
    float temperature = convert_to_temperature(adc_value);

    // 결과 출력
    printf("ADC 값: %d\n", adc_value);
    printf("온도: %.2f°C\n", temperature);

    return EXIT_SUCCESS;
}

출력 예제

ADC 입력값:

  • ADC 값: 512 (예상 값)

변환 결과:

  • 온도: ( \frac{512}{1023} \times 3.3 \div 0.01 = 165.14 ) °C (섭씨)
ADC 값: 512
온도: 165.14°C

응용: 실시간 모니터링


센서 데이터를 주기적으로 읽고 파일에 저장하거나 디스플레이에 출력하면 온도 변화를 실시간으로 추적할 수 있습니다. 아래는 파일 저장 예제입니다:

#include <unistd.h>  // for usleep

int main() {
    const char *adc_path = "/sys/bus/iio/devices/iio:device0/in_voltage0_raw";
    FILE *file = fopen("temperature_log.csv", "w");

    if (file == NULL) {
        perror("파일 열기 실패");
        return EXIT_FAILURE;
    }

    fprintf(file, "Timestamp,Temperature (°C)\n");

    while (1) {
        int adc_value = read_adc(adc_path);
        if (adc_value >= 0) {
            float temperature = convert_to_temperature(adc_value);

            // 시간 가져오기
            time_t now = time(NULL);
            fprintf(file, "%ld,%.2f\n", now, temperature);
            fflush(file);

            printf("온도: %.2f°C\n", temperature);
        }

        usleep(1000000);  // 1초 대기
    }

    fclose(file);
    return 0;
}

확장 아이디어

  1. 경고 시스템: 특정 온도 이상에서 알림을 트리거합니다.
  2. 그래프 생성: 저장된 데이터를 Python으로 시각화하여 온도 변화를 그래프로 표시합니다.
  3. 다중 센서 지원: 여러 ADC 채널을 사용하여 다양한 온도 센서를 읽습니다.

결론


온도 센서를 활용한 ADC 데이터 처리 프로젝트는 센서 데이터 읽기, 변환, 저장, 및 시각화의 전 과정을 학습할 수 있는 훌륭한 예제입니다. 이를 기반으로 다양한 센서와 시스템을 확장하여 응용할 수 있습니다.

요약


본 기사에서는 임베디드 리눅스 환경에서 C 언어를 사용하여 ADC 입력 데이터를 읽고 처리하는 방법을 다뤘습니다. ADC의 기본 원리와 설정 방법부터 데이터 변환, 필터링, 주기적인 데이터 읽기, 시각화 및 저장, 트러블슈팅, 그리고 온도 센서를 활용한 실용적인 예제 프로젝트까지 구체적으로 설명했습니다. 이러한 과정을 통해 센서 데이터를 안정적으로 처리하고 실시간으로 활용하는 기술을 익힐 수 있습니다.