아 FreeRTOS에서 Task 간 통신과 데이터 공유에 사용되는 Queue, Shared Struct + Mutex, EventGroup 방식들을 비교 설명한 표입니다.
| 항목 | 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 각각의 간단한 샘플 코드 예시입니다.
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);
}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);
}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 등)와 결합 예제
설명: 각 축마다 독립적인 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
}
}설명: 4개의 모터에 대해 PID 출력값과 스로틀(throttle)을 조합하여 각 모터의 PWM 출력값을 만듭니다.
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); 호출을 넣으면 됩니다.
설명: MPU6050에서 가속도 + 자이로 데이터를 읽어 융합하여 Pitch/Roll/Yaw를 계산합니다.
MPU6050by Jeff Rowberg (i2cdevlib)- 또는
Adafruit_MPU6050등도 가능
#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 출력
MadgwickorComplementary Filter적용으로 센서 융합 정밀도 향상Queue를 통해 센서 Task ↔ PID Task 분리ESC 캘리브레이션,Arm/Disarm,Failsafe,RC 명령등 도입MPU6050 DMP 모드를 이용한 자동 융합 활용
더 파악해봐야할 것들... 쭉~~
- 센서값을 PID Task에 Queue로 전달하는 구조
- 모터 PWM 출력 구조 자세히 구성
- 센서 융합 필터 적용 예 (Complementary or Madgwick)