STM32 마이크로컨트롤러 기반 PMSM FOC 제어 알고리즘 및 하드웨어 설계 가이드

서론

영구자석 동기 모터(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 출력을 실시간으로 그래픽화합니다.
  • 단계별 검증:
    1. 개루프(Open-loop) 상태에서 SVPWM 및 인버터 구동 검증.
    2. ADC 전류 샘플링 타이밍 및 오프셋 보정.
    3. 전류 루프 폐루프(Closed-loop) 동작 및 게인 튜닝.
    4. 속도/위치 루프 활성화 및 부하 테스트.

태그: STM32 PMSM FOC SVPWM 모터제어

6월 24일 20:35에 게시됨