서론
영구자석 동기 모터(PMSM)를 위한 자기장 방향 제어(FOC)는 전류 벡터를 정밀하게 조작하여 고효율과 뛰어난 동적 응답 특성을 달성하는 고급 모터 제어 기법입니다. 본 가이드에서는 STM32 마이크로컨트롤러를 사용하여 PMSM FOC 시스템을 구축하기 위한 하드웨어 선정부터 핵심 알고리즘 및 펌웨어 구현까지의 전반적인 아키텍처를 다룹니다.
시스템 아키텍처 및 신호 흐름
FOC 제어 시스템은 전력 변환, 신호 피드백, 디지털 연산의 세 가지 주요 하위 시스템으로 구성됩니다.
- 전력 단: PMSM 모터는 3상 인버터(MOSFET/IGBT 브리지)에 의해 구동되며, SVPWM 신호에 따라 동작합니다.
- 피드백 단: 션트 저항 또는 홀 센서를 통해 상전류를 샘플링하고, 엔코더나 홀 센서(혹은 센서리스 관측기)를 통해 회전자 위치와 속도를 측정합니다.
- 제어 단: STM32 MCU는 피드백 데이터를 기반으로 FOC 수학적 모델(Clarke, Park 변환 및 PID 제어)을 연산하고, 새로운 PWM 듀티 사이클을 생성하여 인버터에 전달합니다.
하드웨어 설계 고려사항
마이크로컨트롤러 선정
- STM32F4 시리즈: 부동소수점 연산 장치(FPU)와 고급 타이머를 탑재하여 복잡한 행렬 연산에 유리합니다 (예: STM32F407).
- STM32F3 시리즈: 고속 ADC와 아날로그 비교기가 내장되어 비용 대비 효율적인 모터 제어에 적합합니다.
- STM32G4 시리즈: 디지털 전원 및 모터 제어에 최적화된 수학 가속기(CORDIC)와 고급 아날로그 주변장치를 제공합니다.
전력 구동 및 센싱 회로
- 인버터 브리지: 3상 구동을 위한 전력 스위치 및 게이트 드라이버 (예: IR2110 또는 통합 드라이버 IC).
- 전류 센싱: 인라인 션트 저항을 이용한 ADC 샘플링 또는 격리형 홀 효과 전류 센서.
- 보호 회로: 하드웨어 레벨의 과전류 트립(Desaturation 보호) 및 과전압 클램프 회로.
핵심 제어 알고리즘 구현
FOC의 수학적 모델은 3상 교류를 직류처럼 제어할 수 있도록 좌표계를 변환하는 과정으로 이루어집니다.
클라크(Clark) 및 파크(Park) 좌표 변환
3상 전류를 고정된 2상 직교 좌표계($\alpha-\beta$)로 변환한 후, 회전자와 함께 회전하는 직교 좌표계($d-q$)로 투영합니다.
typedef struct {
float phaseA;
float phaseB;
float phaseC;
} StatorCurrents;
typedef struct {
float alpha;
float beta;
} StationaryFrame;
typedef struct {
float direct;
float quadrature;
} RotatingFrame;
StationaryFrame applyClarkeTransform(StatorCurrents phases) {
StationaryFrame result;
// 전류의 합이 0이라는 가정하에 A상과 B상만 사용
result.alpha = phases.phaseA;
result.beta = (phases.phaseA + 2.0f * phases.phaseB) * 0.577350269f; // 1/sqrt(3)
return result;
}
RotatingFrame applyParkTransform(StationaryFrame stator, float electricalAngle) {
RotatingFrame rotor;
float cosVal = arm_cos_f32(electricalAngle);
float sinVal = arm_sin_f32(electricalAngle);
rotor.direct = (stator.alpha * cosVal) + (stator.beta * sinVal);
rotor.quadrature = (-stator.alpha * sinVal) + (stator.beta * cosVal);
return rotor;
}
역파크(Inverse Park) 변환 및 공간벡터 PWM(SVPWM)
제어기에서 산출된 $d-q$ 축 전압 지령을 다시 $\alpha-\beta$ 좌표계로 환원하고, 이를 기반으로 3상 PWM 듀티를 계산합니다.
StationaryFrame applyInverseParkTransform(RotatingFrame rotor, float electricalAngle) {
StationaryFrame stator;
float cosVal = arm_cos_f32(electricalAngle);
float sinVal = arm_sin_f32(electricalAngle);
stator.alpha = (rotor.direct * cosVal) - (rotor.quadrature * sinVal);
stator.beta = (rotor.direct * sinVal) + (rotor.quadrature * cosVal);
return stator;
}
void computeSVPWMDuties(StationaryFrame vRef, MotorPwmConfig *pwmCfg) {
float vAlpha = vRef.alpha;
float vBeta = vRef.beta;
// 섹터 판별 및 기본 벡터 작용 시간 계산 로직
int sector = determineVoltageSector(vAlpha, vBeta);
float t1 = 0.0f, t2 = 0.0f, t0 = 0.0f;
calculateSwitchingTimes(sector, vAlpha, vBeta, &t1, &t2, &t0);
// 타이머 비교 레지스터 업데이트
__HAL_TIM_SET_COMPARE(pwmCfg->timer, pwmCfg->chU, (uint32_t)(t1 * pwmCfg->maxPeriod));
__HAL_TIM_SET_COMPARE(pwmCfg->timer, pwmCfg->chV, (uint32_t)(t2 * pwmCfg->maxPeriod));
__HAL_TIM_SET_COMPARE(pwmCfg->timer, pwmCfg->chW, (uint32_t)(t0 * pwmCfg->maxPeriod));
}
폐루프 PID 조절기
전류 및 속도 오차를 최소화하기 위한 PI/PID 제어기 구조입니다. 적분기 와인드업(Integral Windup) 방지 로직이 포함되어야 합니다.
typedef struct {
float pGain;
float iGain;
float dGain;
float integrator;
float lastError;
float outMin;
float outMax;
} PidRegulator;
float updatePidController(PidRegulator *pid, float currentError, float deltaTime) {
float pTerm = pid->pGain * currentError;
// 적분항 업데이트 및 안티와인드업
pid->integrator += currentError * deltaTime;
if (pid->integrator > pid->outMax) pid->integrator = pid->outMax;
else if (pid->integrator < pid->outMin) pid->integrator = pid->outMin;
float iTerm = pid->iGain * pid->integrator;
// 미분항 계산
float dTerm = pid->dGain * (currentError - pid->lastError) / deltaTime;
pid->lastError = currentError;
// 최종 출력 합산 및 클램핑
float controlOutput = pTerm + iTerm + dTerm;
if (controlOutput > pid->outMax) controlOutput = pid->outMax;
if (controlOutput < pid->outMin) controlOutput = pid->outMin;
return controlOutput;
}
메인 제어 루프 및 센서리스 관측기
FOC 인터럽트 서비스 루틴(ISR)
ADC 변환 완료 인터럽트 내에서 실행되는 핵심 제어 주기입니다.
void FOC_ExecutionCycle(void) {
StatorCurrents measuredCurrents = readAdcPhaseCurrents();
float rotorAngle = getRotorElectricalAngle();
float mechanicalSpeed = getRotorSpeed();
// 좌표 변환
StationaryFrame iAlphaBeta = applyClarkeTransform(measuredCurrents);
RotatingFrame iDQ = applyParkTransform(iAlphaBeta, rotorAngle);
// 외곽 루프 (속도 제어)
float speedError = targetVelocity - mechanicalSpeed;
float iqReference = updatePidController(&speedLoopPid, speedError, LOOP_PERIOD);
// 내부 루프 (전류 제어)
float idError = 0.0f - iDQ.direct; // MTPA 제어를 위한 Id=0 유지
float iqError = iqReference - iDQ.quadrature;
float vDCommand = updatePidController(&idLoopPid, idError, LOOP_PERIOD);
float vQCommand = updatePidController(&iqLoopPid, iqError, LOOP_PERIOD);
// 역변환 및 PWM 적용
RotatingFrame vDQRef = {vDCommand, vQCommand};
StationaryFrame vAlphaBetaRef = applyInverseParkTransform(vDQRef, rotorAngle);
computeSVPWMDuties(vAlphaBetaRef, &inverterPwm);
}
슬라이딩 모드 관측기(SMO)를 이용한 센서리스 제어
물리적 위치 센서 없이 역기전력(Back-EMF)을 추정하여 회전자 각도를 산출합니다.
typedef struct {
float alphaState;
float betaState;
float zAlpha;
float zBeta;
float errAlpha;
float errBeta;
float slidingGain;
} SlidingModeObserver;
void processSMOEstimation(SlidingModeObserver *smo, StationaryFrame iMeasured, StationaryFrame vApplied, float dt) {
// 전류 추정 모델
float iAlphaEst = smo->alphaState * smo->zAlpha + vApplied.alpha;
float iBetaEst = smo->alphaState * smo->zBeta + vApplied.beta;
// 관측 오차 계산
smo->errAlpha = iMeasured.alpha - iAlphaEst;
smo->errBeta = iMeasured.beta - iBetaEst;
// 불연속 스위칭 함수 (Sign function)
float switchAlpha = (smo->errAlpha > 0) ? smo->slidingGain : -smo->slidingGain;
float switchBeta = (smo->errBeta > 0) ? smo->slidingGain : -smo->slidingGain;
// 상태 변수 적분 업데이트
smo->zAlpha += dt * (smo->betaState * smo->errAlpha + switchAlpha);
smo->zBeta += dt * (smo->betaState * smo->errBeta + switchBeta);
// 저역통과필터(LPF)를 통한 역기전력 추출 및 각도 계산
float emfAlpha = lowPassFilter(switchAlpha);
float emfBeta = lowPassFilter(switchBeta);
estimatedElectricalAngle = atan2f(-emfAlpha, emfBeta);
}
STM32 주변장치 초기화
전류 샘플링을 위한 ADC 설정
고급 타이머의 트리거를 사용하여 PWM 스위칭 노이즈가 없는 시점에 전류를 샘플링합니다.
void configureCurrentSensingAdc(void) {
hadc1.Instance = ADC1;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = ENABLE;
hadc1.Init.ContinuousConvMode = DISABLE; // 타이머 트리거 사용
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T1_TRGO;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 2;
HAL_ADC_Init(&hadc1);
ADC_ChannelConfTypeDef chConfig = {0};
chConfig.Channel = ADC_CHANNEL_1; // U상 션트
chConfig.Rank = 1;
chConfig.SamplingTime = ADC_SAMPLETIME_15CYCLES;
HAL_ADC_ConfigChannel(&hadc1, &chConfig);
chConfig.Channel = ADC_CHANNEL_2; // V상 션트
chConfig.Rank = 2;
HAL_ADC_ConfigChannel(&hadc1, &chConfig);
}
센터 얼라인 PWM 및 엔코더 인터페이스
void configureInverterPwmAndEncoder(void) {
// TIM1을 이용한 센터 얼라인 PWM 생성
htim1.Instance = TIM1;
htim1.Init.Prescaler = 0;
htim1.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1;
htim1.Init.Period = PWM_MAX_DUTY;
htim1.Init.RepetitionCounter = 1; // 매 두 번째 주기마다 ADC 트리거
HAL_TIM_PWM_Init(&htim1);
TIM_OC_InitTypeDef sConfigOC = {0};
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2);
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3);
// TIM2를 이용한 직교 엔코더 모드 설정
htim2.Instance = TIM2;
htim2.Init.Period = 0xFFFFFFFF; // 32비트 카운터 오버플로우 방지
HAL_TIM_Encoder_Init(&htim2, TIM_ENCODERMODE_TI12);
HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
}
시스템 안전 및 보호 메커니즘
하드웨어 및 소프트웨어 보호
void monitorSystemFaults(void) {
// 1. 과전류 보호 (소프트웨어 폴백)
float totalCurrentSq = (iDQ.direct * iDQ.direct) + (iDQ.quadrature * iDQ.quadrature);
if (totalCurrentSq > (MAX_PEAK_CURRENT * MAX_PEAK_CURRENT)) {
HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_ALL);
faultRegister |= FAULT_OVERCURRENT;
}
// 2. 열적 보호 (Over-temperature)
float boardTemp = readNtcTemperature();
if (boardTemp > THROTTLE_TEMP_THRESHOLD) {
currentLimitScaler = 0.5f; // 전류 지령 제한
}
if (boardTemp > SHUTDOWN_TEMP_THRESHOLD) {
HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_ALL);
faultRegister |= FAULT_OVERTEMP;
}
}
제어 루프 튜닝 및 디버깅 전략
대역폭 및 게인 튜닝
- 전류 루프 (내부 루프): 가장 빠른 응답이 요구되며, 일반적으로 1kHz ~ 2kHz의 대역폭을 목표로 합니다. PI 게인은 모터의 인덕턴스와 저항을 기반으로 극점 배치(Pole Placement) 기법으로 초기값을 산출합니다.
- 속도 루프 (외부 루프): 전류 루프 대역폭의 약 1/10 수준인 100Hz ~ 200Hz로 설정하여 시스템의 안정성을 확보합니다.
- 위치 루프: 필요 시 가장 바깥쪽에 위치하며, 10Hz ~ 20Hz 수준의 낮은 대역폭으로 오버슈트를 방지합니다.
디버깅 도구 및 단계별 접근
- 실시간 모니터링: STM32CubeMonitor 또는 J-Scope를 사용하여 전류 파형, 전기각, PID 출력을 실시간으로 그래픽화합니다.
- 단계별 검증:
- 개루프(Open-loop) 상태에서 SVPWM 및 인버터 구동 검증.
- ADC 전류 샘플링 타이밍 및 오프셋 보정.
- 전류 루프 폐루프(Closed-loop) 동작 및 게인 튜닝.
- 속도/위치 루프 활성화 및 부하 테스트.