Skip to content

Latest commit

 

History

History
338 lines (259 loc) · 11.3 KB

File metadata and controls

338 lines (259 loc) · 11.3 KB

아 FreeRTOS에서 Task 간 통신과 데이터 공유에 사용되는 Queue, Shared Struct + Mutex, EventGroup 방식들을 비교 설명한 표입니다.


FreeRTOS 통신/동기화 방식 비교 테이블

항목 Queue (큐) Shared Struct + Mutex (공유구조체 + 뮤텍스) EventGroup (이벤트 그룹)
주요 용도 Task 간 데이터 전달 Task 간 상태/데이터 공유 Task 간 이벤트 신호 전달
데이터 전달 값 복사하여 전달 (선입선출) ❌ 직접 접근 (포인터 기반, 복사 아님) ❌ 이벤트 비트만 전달 (데이터 없음)
실시간성 신뢰성 높음 (버퍼에 적재) 매우 빠름 (단순 메모리 접근) 매우 빠름 (비트 마스크 연산)
메모리 사용 중간 낮음 매우 낮음
타입 유연성 다양한 구조체 타입 사용 가능 자유롭게 설계 가능 ❌ 단순 정수(비트) 기반
복수 Task 지원 여러 송신/수신 Task 가능 ⚠️ 직접 설계 필요 여러 Task가 하나의 비트를 기다릴 수 있음
선입선출(FIFO) 지원 ❌ 없음 ❌ 없음
사용 예시 센서 → 제어, 명령 → 제어 등 공유 상태: 센서값, 모터 출력 등 "명령 수신됨", "센서 초기화 완료" 등
제한 사항 큐 길이 초과 시 block 또는 discard 동시 접근 시 mutex 필수 최대 24~32 비트 제한
API 예시 xQueueSend(), xQueueReceive() xSemaphoreTake(), xSemaphoreGive() + 구조체 접근 xEventGroupSetBits(), xEventGroupWaitBits()

요약 추천

목적 추천 방식
명령 전송 (ex. BLE → 제어 Task) Queue
실시간 센서 데이터 공유 Shared Struct + Mutex
상태 이벤트 전달 (ex. 연결 끊김) EventGroup
버튼 누름 감지 / 플래그 처리 EventGroup
여러 Task 간 브로드캐스트 전파 EventGroup

예시 상황에 따른 선택

예시 상황 사용 방식 설명
RC 수신기에서 수신된 명령을 제어 Task에 전달 Queue 여러 명령이 연속 들어오는 경우 FIFO 유지
IMU에서 읽은 값을 Stabilizer와 Telemetry가 함께 참조 Shared Struct + Mutex 최신값만 유지되면 됨 (이전 값 불필요)
GPS 고정 완료됨 → 다른 Task들에 알림 EventGroup 여러 Task가 동시에 기다릴 수 있음

FreeRTOS에서 자주 사용되는 세 가지 Task 간 통신/동기화 방식인 Queue, Shared Struct + Mutex, EventGroup 각각의 간단한 샘플 코드 예시입니다.


1. Queue: 명령 전달 (예: CommandTask → StabilizerTask)

QueueHandle_t cmdQueue;

void TaskCommand(void *pv) {
  int command = 42;  // 예시 명령 코드
  while (true) {
    xQueueSend(cmdQueue, &command, portMAX_DELAY);
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

void TaskStabilizer(void *pv) {
  int received;
  while (true) {
    if (xQueueReceive(cmdQueue, &received, portMAX_DELAY)) {
      Serial.print("명령 수신: "); Serial.println(received);
    }
  }
}

void setup() {
  Serial.begin(115200);
  cmdQueue = xQueueCreate(5, sizeof(int));
  xTaskCreate(TaskCommand, "Cmd", 2048, NULL, 1, NULL);
  xTaskCreate(TaskStabilizer, "Stab", 2048, NULL, 1, NULL);
}

2. Shared Struct + Mutex: 센서 데이터를 여러 Task가 공유

typedef struct {
  float pitch;
  float roll;
  float yaw;
} Attitude;

Attitude imuData;
SemaphoreHandle_t dataMutex;

void TaskSensor(void *pv) {
  while (true) {
    xSemaphoreTake(dataMutex, portMAX_DELAY);
    imuData.pitch += 0.1;
    imuData.roll += 0.2;
    imuData.yaw += 0.3;
    xSemaphoreGive(dataMutex);
    vTaskDelay(10 / portTICK_PERIOD_MS);
  }
}

void TaskTelemetry(void *pv) {
  Attitude localCopy;
  while (true) {
    xSemaphoreTake(dataMutex, portMAX_DELAY);
    localCopy = imuData;
    xSemaphoreGive(dataMutex);
    Serial.printf("Pitch: %.2f, Roll: %.2f, Yaw: %.2f\n",
      localCopy.pitch, localCopy.roll, localCopy.yaw);
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

void setup() {
  Serial.begin(115200);
  dataMutex = xSemaphoreCreateMutex();
  xTaskCreate(TaskSensor, "Sensor", 2048, NULL, 1, NULL);
  xTaskCreate(TaskTelemetry, "Telem", 2048, NULL, 1, NULL);
}

3. EventGroup: 이벤트 신호 전달 (예: GPS 고정 → 다른 Task 알림)

EventGroupHandle_t systemEvents;
#define GPS_FIX_BIT (1 << 0)

void TaskGPS(void *pv) {
  vTaskDelay(3000 / portTICK_PERIOD_MS);  // GPS 고정 시간 시뮬레이션
  xEventGroupSetBits(systemEvents, GPS_FIX_BIT);
  Serial.println("GPS Fix 완료!");
  vTaskDelete(NULL);  // 1회성 이벤트 후 종료
}

void TaskStabilizer(void *pv) {
  EventBits_t bits;
  while (true) {
    bits = xEventGroupWaitBits(systemEvents, GPS_FIX_BIT, false, true, portMAX_DELAY);
    if (bits & GPS_FIX_BIT) {
      Serial.println("GPS 고정됨 → 자세제어 시작");
      // 이후 정상 루프 실행...
      break;
    }
  }
  while (true) {
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

void setup() {
  Serial.begin(115200);
  systemEvents = xEventGroupCreate();
  xTaskCreate(TaskGPS, "GPS", 2048, NULL, 1, NULL);
  xTaskCreate(TaskStabilizer, "Stab", 2048, NULL, 1, NULL);
}

정리

목적 방식 코드 기능
명령 전달 (FIFO) Queue 명령을 Task 간 전달
센서값 공유 Struct+Mutex 센서 데이터를 여러 Task가 공유
상태/이벤트 브로드캐스트 EventGroup 특정 이벤트를 여러 Task에 알림

추가 예제

  • 여러 축(Pitch, Roll, Yaw)의 PID 동시 제어 구조
  • PID와 모터 믹싱 결합
  • 실제 IMU 센서(MPU6050 등)와 결합 예제

1단계: 여러 축(Pitch, Roll, Yaw)의 PID 동시 제어 구조

설명: 각 축마다 독립적인 PID 루프를 만들고, 센서 데이터(자이로/가속도 등)를 기반으로 계산합니다.

구조 예

struct Attitude {
  float pitch;
  float roll;
  float yaw;
};

struct PID {
  float kp, ki, kd;
  float integral;
  float prevError;
};

Attitude currentAttitude;
PID pidPitch = {1.0, 0.05, 0.2, 0, 0};
PID pidRoll  = {1.1, 0.04, 0.3, 0, 0};
PID pidYaw   = {0.8, 0.02, 0.15, 0, 0};

float computePID(PID &pid, float target, float current) {
  float error = target - current;
  pid.integral += error;
  float derivative = error - pid.prevError;
  pid.prevError = error;
  return pid.kp * error + pid.ki * pid.integral + pid.kd * derivative;
}

void TaskPID(void *pv) {
  float targetPitch = 0, targetRoll = 0, targetYaw = 0;
  while (true) {
    float outPitch = computePID(pidPitch, targetPitch, currentAttitude.pitch);
    float outRoll  = computePID(pidRoll, targetRoll, currentAttitude.roll);
    float outYaw   = computePID(pidYaw, targetYaw, currentAttitude.yaw);

    // 여기서 모터 믹싱으로 넘어갈 준비
    Serial.printf("PID Out → Pitch: %.2f, Roll: %.2f, Yaw: %.2f\n",
      outPitch, outRoll, outYaw);

    vTaskDelay(10 / portTICK_PERIOD_MS);  // 100Hz
  }
}

2단계: PID 출력 → 모터 믹싱 결합

설명: 4개의 모터에 대해 PID 출력값과 스로틀(throttle)을 조합하여 각 모터의 PWM 출력값을 만듭니다.

구조 예 (쿼드콥터 X 구성 기준)

float throttle = 1300;  // 기본 스로틀 (1000~2000 us)

void mixMotors(float pitchOut, float rollOut, float yawOut) {
  float motor1 = throttle + pitchOut - rollOut + yawOut;  // Front Left
  float motor2 = throttle + pitchOut + rollOut - yawOut;  // Front Right
  float motor3 = throttle - pitchOut + rollOut + yawOut;  // Rear Right
  float motor4 = throttle - pitchOut - rollOut - yawOut;  // Rear Left

  // 범위 제한
  motor1 = constrain(motor1, 1000, 2000);
  motor2 = constrain(motor2, 1000, 2000);
  motor3 = constrain(motor3, 1000, 2000);
  motor4 = constrain(motor4, 1000, 2000);

  // 출력 (예: ESC 제어용 PWM 신호)
  analogWrite(25, motor1);  // PWM 채널은 예시
  analogWrite(26, motor2);
  analogWrite(27, motor3);
  analogWrite(14, motor4);

  Serial.printf("Motors: %.1f %.1f %.1f %.1f\n", motor1, motor2, motor3, motor4);
}

TaskPID() 내부 마지막 줄에 mixMotors(outPitch, outRoll, outYaw); 호출을 넣으면 됩니다.


3단계: MPU6050 등 IMU 센서와 실제 결합

설명: MPU6050에서 가속도 + 자이로 데이터를 읽어 융합하여 Pitch/Roll/Yaw를 계산합니다.

라이브러리 필요

  • MPU6050 by Jeff Rowberg (i2cdevlib)
  • 또는 Adafruit_MPU6050 등도 가능

간단한 MPU6050 기반 Attitude 계산 예

#include <Wire.h>
#include <MPU6050.h>

MPU6050 mpu;

void TaskSensor(void *pv) {
  mpu.initialize();
  if (!mpu.testConnection()) {
    Serial.println("MPU6050 연결 실패!");
    vTaskDelete(NULL);
  }

  while (true) {
    int16_t ax, ay, az, gx, gy, gz;
    mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);

    // 단순한 각도 계산 (필터 없음)
    currentAttitude.pitch = atan2(ay, az) * 180 / PI;
    currentAttitude.roll  = atan2(ax, az) * 180 / PI;
    currentAttitude.yaw   += gz * 0.001;  // 적분(간단한 시뮬레이션)

    vTaskDelay(10 / portTICK_PERIOD_MS);
  }
}

종합 연동 흐름도

[MPU6050 Task]
   ↓ (currentAttitude 업데이트)

[PID Task]
   → Pitch PID
   → Roll PID
   → Yaw PID
   ↓

[Motor Mixer]
   → Motor1~4 PWM 출력

다음에 할 수 있는 확장

  • Madgwick or Complementary Filter 적용으로 센서 융합 정밀도 향상
  • Queue를 통해 센서 Task ↔ PID Task 분리
  • ESC 캘리브레이션, Arm/Disarm, Failsafe, RC 명령 등 도입
  • MPU6050 DMP 모드를 이용한 자동 융합 활용

더 파악해봐야할 것들... 쭉~~

  • 센서값을 PID Task에 Queue로 전달하는 구조
  • 모터 PWM 출력 구조 자세히 구성
  • 센서 융합 필터 적용 예 (Complementary or Madgwick)