# 실시간 제어 시스템 개요

Preempt RT 실시간 제어 시스템의 구현은 전반적인 시스템의 안정성과 정확성을 유지하면서도 고성능을 요구한다. 이 장에서는 제어 시스템의 각 모듈을 구현하고, 이를 통합하는 과정을 다룬다. 시스템의 각 모듈은 실시간으로 작동해야 하며, 다양한 입력에 신속하게 반응해야 한다. 따라서, 각 모듈의 구현은 성능 최적화와 실시간성 보장을 고려하여 설계되어야 한다.

## 1. 센서 입력 모듈 구현

실시간 제어 시스템에서 센서 입력은 시스템의 상태를 결정하는 중요한 요소이다. 센서 입력 모듈은 다양한 센서로부터 데이터를 수집하고 이를 실시간으로 처리하여 제어 시스템에 전달하는 역할을 한다.

### 1.1 센서 데이터 수집

센서 데이터는 일반적으로 아날로그 신호로부터 수집된다. 아날로그 신호는 A/D 변환기를 통해 디지털 신호로 변환된다. 이를 위해 Linux의 `ADC` 드라이버를 활용할 수 있다.

```c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/adc.h>

static int __init sensor_input_init(void) {
    printk(KERN_INFO "Initializing Sensor Input Module\n");
    // Initialize ADC
    int adc_value = adc_read(0);  // Read from ADC channel 0
    printk(KERN_INFO "Sensor Value: %d\n", adc_value);
    return 0;
}

static void __exit sensor_input_exit(void) {
    printk(KERN_INFO "Exiting Sensor Input Module\n");
}

module_init(sensor_input_init);
module_exit(sensor_input_exit);
MODULE_LICENSE("GPL");
```

이 코드는 ADC(Analog-to-Digital Converter) 드라이버를 사용하여 특정 채널에서 아날로그 데이터를 읽어오는 예제이다. 이를 통해 실시간으로 센서 데이터를 수집할 수 있다.

### 1.2 데이터 필터링 및 전처리

센서에서 수집된 데이터는 노이즈가 포함될 수 있으므로, 이를 제거하기 위해 필터링 과정이 필요하다. 대표적인 필터링 방법으로는 이동 평균 필터(Moving Average Filter)가 있다.

이동 평균 필터는 다음과 같이 정의된다.

$$
\mathbf{y}\[n] = \frac{1}{N} \sum\_{k=0}^{N-1} \mathbf{x}\[n-k]
$$

여기서 $\mathbf{y}\[n]$은 필터링된 출력 데이터, $\mathbf{x}\[n]$은 원본 입력 데이터, $N$은 필터의 길이이다.

```c
#define FILTER_LENGTH 5

int moving_average_filter(int *data, int length) {
    int sum = 0;
    for (int i = 0; i < FILTER_LENGTH; i++) {
        sum += data[length - i - 1];
    }
    return sum / FILTER_LENGTH;
}
```

위 코드는 간단한 이동 평균 필터를 구현한 예제이다. 필터의 길이 `FILTER_LENGTH`에 따라 입력 데이터의 평균을 계산하여 노이즈를 제거한다.

## 2. 제어 알고리즘 구현

제어 시스템의 핵심은 제어 알고리즘이다. 센서로부터 입력된 데이터를 바탕으로 제어 알고리즘이 실행되어 적절한 출력 명령을 생성한다. 이 장에서는 가장 기본적인 제어 알고리즘인 PID(Proportional-Integral-Derivative) 제어를 구현한다.

### 2.1 PID 제어 개요

PID 제어는 비례(Proportional), 적분(Integral), 미분(Derivative) 제어로 구성된 피드백 제어 알고리즘이다. PID 제어기의 출력 $u(t)$는 다음과 같이 표현된다.

$$
u(t) = K\_p e(t) + K\_i \int\_{0}^{t} e(\tau) d\tau + K\_d \frac{de(t)}{dt}
$$

여기서 $K\_p$, $K\_i$, $K\_d$는 각각 비례, 적분, 미분 게인이고, $e(t)$는 현재 시간 $t$에서의 오차이다.

### 2.2 PID 제어 알고리즘 구현

PID 제어 알고리즘은 시스템의 오차 값을 바탕으로 제어 신호를 생성한다. 이 과정은 연속적으로 수행되며, 실시간으로 정확한 제어를 위해 최적화되어야 한다.

```c
struct pid_controller {
    double Kp;
    double Ki;
    double Kd;
    double prev_error;
    double integral;
};

double pid_compute(struct pid_controller *pid, double setpoint, double measured_value) {
    double error = setpoint - measured_value;
    pid->integral += error;
    double derivative = error - pid->prev_error;
    double output = (pid->Kp * error) + (pid->Ki * pid->integral) + (pid->Kd * derivative);
    pid->prev_error = error;
    return output;
}
```

이 코드에서 `pid_compute` 함수는 PID 제어 알고리즘을 구현한다. 입력 값인 `setpoint`와 `measured_value` 사이의 오차를 계산하여, PID 제어 신호를 반환한다. 이 함수는 실시간 제어 시스템에서 주기적으로 호출되어야 하며, 각 주기에서 최신 센서 데이터를 입력으로 받아 제어 신호를 계산한다.

### 2.3 PID 제어 튜닝

PID 제어기의 성능은 게인 $K\_p$, $K\_i$, $K\_d$의 값에 의해 크게 좌우된다. 이 게인의 값을 적절히 조정하는 과정을 PID 튜닝이라고 한다. 일반적으로 사용하는 튜닝 방법은 지글러-니콜스(Ziegler-Nichols) 방법이다.

지글러-니콜스 방법에서 $K\_p$는 다음과 같이 설정된다.

$$
K\_p = 0.6 \times K\_u
$$

여기서 $K\_u$는 시스템이 지속적인 진동을 보일 때의 최댓값이다.

적분 게인 $K\_i$와 미분 게인 $K\_d$는 다음과 같이 설정된다.

$$
K\_i = \frac{2 \times K\_p}{T\_u}, \quad K\_d = \frac{K\_p \times T\_u}{8}
$$

여기서 $T\_u$는 주기의 진동 주기이다.

## 3. 액추에이터 출력 모듈 구현

제어 알고리즘에서 계산된 제어 신호는 액추에이터(Actuator)로 전달되어야 한다. 이 과정에서 제어 신호는 액추에이터가 이해할 수 있는 형태로 변환되고, 실시간으로 전달되어야 한다.

### 3.1 PWM 신호 생성

많은 액추에이터는 PWM(Pulse Width Modulation) 신호를 통해 제어된다. PWM은 신호의 듀티 사이클(Duty Cycle)을 조절하여 출력 전력을 제어하는 방법이다.

```c
#include <linux/pwm.h>

struct pwm_device *pwm;

void pwm_setup(int duty_cycle) {
    pwm = pwm_request(0, "pwm_test");  // Request PWM device
    pwm_config(pwm, duty_cycle, 1000000);  // Configure PWM with given duty cycle
    pwm_enable(pwm);  // Enable PWM
}

void pwm_cleanup(void) {
    pwm_disable(pwm);  // Disable PWM
    pwm_free(pwm);  // Free PWM device
}
```

위 코드는 간단한 PWM 제어 예제이다. `pwm_setup` 함수는 주어진 듀티 사이클을 기반으로 PWM 신호를 설정한다. 이 PWM 신호는 액추에이터에 전달되어 제어 신호로 사용된다.

### 3.2 액추에이터 제어

액추에이터에 따라 제어 신호를 다르게 처리해야 할 수 있다. 예를 들어, 모터 제어의 경우 속도와 방향을 제어해야 할 수 있다.

```c
void motor_control(int speed, int direction) {
    int duty_cycle = calculate_duty_cycle(speed);
    pwm_setup(duty_cycle);
    set_direction_pin(direction);
}
```

이 함수는 모터의 속도와 방향을 제어한다. `calculate_duty_cycle` 함수는 속도에 따른 듀티 사이클을 계산하며, `set_direction_pin` 함수는 모터의 회전 방향을 설정한다.

## 4. 통합 및 테스트

모든 모듈이 구현되면, 이를 통합하여 전체 시스템을 구성해야 한다. 통합 과정에서는 각 모듈이 예상대로 작동하는지 확인하고, 모듈 간의 인터페이스가 올바르게 연결되는지 점검한다.

### 4.1 모듈 통합

통합 단계에서는 센서 입력 모듈, 제어 알고리즘 모듈, 액추에이터 출력 모듈을 하나의 루프에서 실행시켜야 한다. 각 모듈은 실시간으로 데이터를 주고받으며, 시스템의 각 부분이 올바르게 동작하는지 확인한다.

```c
int main(void) {
    struct pid_controller pid = {1.0, 0.1, 0.01, 0, 0};
    while (1) {
        int sensor_value = read_sensor();
        double control_signal = pid_compute(&pid, 100.0, sensor_value);
        motor_control(control_signal, FORWARD);
        sleep(1);  // Control loop period
    }
    return 0;
}
```

이 코드에서는 센서 데이터의 주기적인 읽기, PID 제어 신호 계산, 모터 제어 명령 전송이 하나의 루프에서 통합되어 수행된다.

### 4.2 실시간성 검증

통합된 시스템이 실시간성을 만족하는지 확인하기 위해 각 모듈의 실행 시간을 측정하고, 전체 시스템의 응답 시간을 평가해야 한다. 이를 통해 시스템이 요구되는 시간 내에 모든 작업을 수행할 수 있는지 검증한다.

```c
#include <linux/time.h>

void measure_execution_time(void (*func)()) {
    struct timespec start, end;
    getnstimeofday(&start);
    func();
    getnstimeofday(&end);
    printk(KERN_INFO "Execution time: %ld ns\n", (end.tv_nsec - start.tv_nsec));
}
```

위 코드는 특정 함수의 실행 시간을 측정하는 예제이다. 이 방법을 활용하여 각 모듈의 실행 시간을 측정하고, 실시간 제어 시스템의 성능을 평가할 수 있다.

## 5. 코드 최적화

실시간 제어 시스템의 성능을 극대화하기 위해 코드 최적화가 필요하다. 이는 시스템의 응답 시간을 줄이고, 자원의 효율적인 사용을 보장하기 위해 중요하다.

### 5.1 캐시 최적화

CPU 캐시를 효율적으로 사용하기 위해 데이터 구조와 메모리 접근 패턴을 최적화할 수 있다. 이는 캐시 미스를 줄이고, 데이터 접근 속도를 높인다.

```c
struct sensor_data {
    int data[64];  // Align data to cache line
} __attribute__((aligned(64)));
```

이 예제에서는 `sensor_data` 구조체를 캐시 라인에 맞춰 정렬하여 캐시 효율을 높인다.

### 5.2 루프 최적화

루프 언롤링(Loop Unrolling)과 같은 기법을 사용하여 반복문을 최적화할 수 있다. 이는 반복문 내에서 반복되는 연산을 최소화하여 성능을 향상시킨다.

```c
for (int i = 0; i < 4; i++) {
    sum += data[i];
    sum += data[i + 1];
}
```

위 코드는 반복문을 언롤링하여 반복 횟수를 줄이고, 실행 시간을 단축시킨 예제이다.
