穿西装的强子 发表于 2025-5-31 15:31

【AT-START-M412测评】+ 无刷电机控制2-开始移植

本帖最后由 穿西装的强子 于 2025-5-31 15:40 编辑

#申请原创# @21小跑堂
看了很多simple foc的代码,都是用arduino写的,可移植性和操作性就变小了,感觉没那么好用方便。
而且不确定AT32F412是否支持c++,所以还是准备移植基于c语言的foc代码。
按c语言的foc进行移植设计
一、软件框图


二、软件设计
1. PWM配置
工作模式选择‌
[*]必须采用‌中央对齐模式‌(Center-Aligned Mode),以降低开关损耗并提高电流采样精度
[*]高级定时器(如TIM1/TIM8)需使能‌互补输出‌和‌刹车保护‌功能
频率与周期计算

[*]PWM频率推荐‌10–20kHz‌(平衡开关损耗与动态响应)
死区时间设定‌

[*]防止上下桥臂直通,典型值‌≥500ns‌(依据MOS管开关延迟调整)
[*]还有一些使用Nmos和Pmos实现硬件的延迟,防止短路的情况

以下是pwm配置
/* Time Base configuration */
                TMR_TimeBaseStructInit(&TMR_TimeBaseStructure);
                TMR_TimeBaseStructure.TMR_DIV = 0;
                TMR_TimeBaseStructure.TMR_CounterMode = TMR_CounterDIR_CenterAligned1;
                TMR_TimeBaseStructure.TMR_Period = PWM_PERIOD;
                TMR_TimeBaseStructure.TMR_ClockDivision = TMR_CKD_DIV1;
                TMR_TimeBaseStructure.TMR_RepetitionCounter = 0;

                TMR_TimeBaseInit(TMR1, &TMR_TimeBaseStructure);

                /* Channel 1, 2 and 3 Configuration in PWM mode */
                TMR_OCStructInit(&TMR_OCInitStructure);
                TMR_OCInitStructure.TMR_OCMode = TMR_OCMode_PWM2;
                TMR_OCInitStructure.TMR_OutputState = TMR_OutputState_Enable;
                TMR_OCInitStructure.TMR_OutputNState = TMR_OutputNState_Enable;
                TMR_OCInitStructure.TMR_Pulse = PWM_TIM_PULSE>>1;
                TMR_OCInitStructure.TMR_OCPolarity = TMR_OCPolarity_High;
                TMR_OCInitStructure.TMR_OCNPolarity = TMR_OCNPolarity_High;
                TMR_OCInitStructure.TMR_OCIdleState = TMR_OCIdleState_Reset;
                TMR_OCInitStructure.TMR_OCNIdleState = TMR_OCIdleState_Reset;

                TMR_OC1Init(TMR1, &TMR_OCInitStructure);
                TMR_OC2Init(TMR1, &TMR_OCInitStructure);
                TMR_OC3Init(TMR1, &TMR_OCInitStructure);
               
    /* Channel 4 Configuration in PWM mode */
                TMR_OCStructInit(&TMR_OCInitStructure);
                TMR_OCInitStructure.TMR_OCMode = TMR_OCMode_PWM1;
                TMR_OCInitStructure.TMR_OutputState = TMR_OutputState_Enable;
                TMR_OCInitStructure.TMR_OutputNState = TMR_OutputNState_Enable;
                TMR_OCInitStructure.TMR_Pulse = 1;
                TMR_OCInitStructure.TMR_OCPolarity = TMR_OCPolarity_High;
                TMR_OCInitStructure.TMR_OCNPolarity = TMR_OCNPolarity_High;
                TMR_OCInitStructure.TMR_OCIdleState = TMR_OCIdleState_Reset;
                TMR_OCInitStructure.TMR_OCNIdleState = TMR_OCIdleState_Reset;

                TMR_OC4Init(TMR1, &TMR_OCInitStructure);
               
                /* Enables the TIM1 Preload on CC1 Register */
                TMR_OC1PreloadConfig(TMR1, TMR_OCPreload_Enable);
                /* Enables the TIM1 Preload on CC2 Register */
                TMR_OC2PreloadConfig(TMR1, TMR_OCPreload_Enable);
                /* Enables the TIM1 Preload on CC3 Register */
                TMR_OC3PreloadConfig(TMR1, TMR_OCPreload_Enable);
                /* Enables the TIM1 Preload on CC4 Register */
                TMR_OC4PreloadConfig(TMR1, TMR_OCPreload_Enable);


                /* Automatic Output enable, Break, dead time and lock configuration*/
                TMR_BRKDTStructInit(&TMR_BDTRInitStructure);
                TMR_BDTRInitStructure.TMR_OSIMRState = TMR_OSIMRState_Enable;
                TMR_BDTRInitStructure.TMR_OSIMIState = TMR_OSIMIState_Enable;
                TMR_BDTRInitStructure.TMR_LOCKgrade = TMR_LOCKgrade_1;
                TMR_BDTRInitStructure.TMR_DeadTime = DEADTIME;
                TMR_BDTRInitStructure.TMR_Break = TMR_Break_Enable;
                TMR_BDTRInitStructure.TMR_BreakPolarity = TMR_BreakPolarity_Low;
                TMR_BDTRInitStructure.TMR_AutomaticOutput = TMR_AutomaticOutput_Disable;

                TMR_BRKDTConfig(TMR1, &TMR_BDTRInitStructure);

    TMR_ARPreloadConfig(TMR1,ENABLE);


    TMR_SelectOutputTrigger(TMR1, TMR_TRGOSource_OC4Ref);
    TMR_ClearITPendingBit(TMR1, TMR_INT_Break);
    TMR_INTConfig(TMR1, TMR_INT_Break, ENABLE);


                /* TMR1 counter enable */
                TMR_Cmd(TMR1, ENABLE);

                /* Main Output Enable */
                TMR_CtrlPWMOutputs(TMR1, DISABLE);


2.ADC的配置

采样时机对齐‌

[*]ADC采样点必须设在PWM波形的‌波谷处‌(中央对齐模式中点),避开开关噪声干扰
[*]使用定时器‌TRGO触发信号‌联动ADC启动,而非软件触发
[*]
以下是ADC配置
‌ ADC_InitType ADC_InitStructure;
      
    /* ADCCLK = PCLK2/6 */
    RCC_ADCCLKConfig(RCC_APB2CLK_Div6);
   

//使用双ADC模式,ADC1为主,ADC2为从。当ADC转换配置成由外部事件触发时,用户必须设置成仅触发主ADC,从ADC设置成软件触发,这样可以防止意外的触发从转换。
//但是,主和从ADC的外部触发必须同时被激活,要调用 ADC_ExternalTrigConvCmd(ADC2, ENABLE);

                //ADC1配置
                ADC_Reset(ADC1);
    ADC_StructInit(&ADC_InitStructure);
                ADC_InitStructure.ADC_Mode = ADC_Mode_RegInjecSimult;      //ADC1工作在注入模式
                ADC_InitStructure.ADC_ScanMode = ENABLE;                      //模数转换工作在扫描模式(多通道)还是单次(单通道)模式
                ADC_InitStructure.ADC_ContinuousMode = ENABLE;          //模数转换工作在扫描模式(多通道)还是单次(单通道)模式
                ADC_InitStructure.ADC_ExternalTrig = ADC_ExternalTrig_None;//转换由软件启动
                ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据左对齐
                ADC_InitStructure.ADC_NumOfChannel = 1;               //规定了顺序进行规则转换的ADC通道的数目。这个数目的取值范围是1到16
                ADC_Init(ADC1, &ADC_InitStructure);

    //ADC规则通道设置
                ADC_RegularChannelConfig(ADC1, ADC_Channel_7, 1, ADC_SampleTime_13_5);

    //ADC2配置
                ADC_Reset(ADC2);
    ADC_StructInit(&ADC_InitStructure);
    ADC_InitStructure.ADC_Mode = ADC_Mode_InjecSimult;//ADC2工作在注入模式
    ADC_InitStructure.ADC_ScanMode = ENABLE;         
    ADC_InitStructure.ADC_ContinuousMode = DISABLE;   //连续转换模式,触发后就会一直转换
    ADC_InitStructure.ADC_ExternalTrig = ADC_ExternalTrig_None;//双ADC模式的从ADC必须设置为软件触发
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NumOfChannel = 1;               
    ADC_Init(ADC2, &ADC_InitStructure);
   
               
    ADC_Ctrl(ADC1, ENABLE);                                 //ADC1使能
    ADC_RstCalibration(ADC1);                        //复位校准寄存器
    while(ADC_GetResetCalibrationStatus(ADC1));       //等待校准寄存器复位完成
    ADC_StartCalibration(ADC1);                        //ADC1开始校准
    while(ADC_GetCalibrationStatus(ADC1));            //等待校准完成

    ADC_Ctrl(ADC2, ENABLE);                           //ADC2使能
    ADC_RstCalibration(ADC2);                        //复位校准寄存器
    while(ADC_GetResetCalibrationStatus(ADC2));      //等待校准寄存器复位完成
    ADC_StartCalibration(ADC2);                        //ADC2开始校准
    while(ADC_GetCalibrationStatus(ADC2));                   //等待校准完成

    ADC_get_offset();

    //ADC1 注入通道配置
                ADC_InjectedSequencerLengthConfig(ADC1,1);
                ADC_InjectedChannelConfig(ADC1, ADC_Channel_6, 1, ADC_SampleTime_7_5);   //A相电流
               
    //ADC2 注入通道配置
    ADC_InjectedSequencerLengthConfig(ADC2,1);      //设置ADC2注入组通道数量
    ADC_InjectedChannelConfig(ADC2, ADC_Channel_5, 1,ADC_SampleTime_7_5);       //B相电流

    ADC_ExternalTrigInjectedConvCtrl(ADC2,ENABLE);          //使能外部信号触发注入组转换的功能
                ADC_ExternalTrigInjectedConvConfig(ADC1, ADC_ExternalTrigInjec_TMR1_CC4);    //ADC1注入组转换的触发信号选择,注入组转换由TIM1的CC4触发
    ADC_ClearFlag(ADC1, ADC_FLAG_JEC);
          ADC_INTConfig(ADC1, ADC_INT_JEC, ENABLE);                                        //这里才能打开注入组转换完成中断
               
                ADC_SoftwareStartConvCtrl(ADC1, ENABLE);
3.foc算法
坐标变换核心流程 A --> B
B --> C
C --> D[反Park变换]
D --> E关键算法实现细节电流环控制,q轴电流和d轴电流pid运算float IQ_PID_Proc(float feedback)
{
          float Err = 0.0f;
          float P_term = 0.0f;
          float I_term = 0.0f;
          float output = 0.0f;
      
          Err = Foc_input.Iq_ref - feedback;
      
          P_term = Err*Iq_PID.P_Gain;
          I_term = Err*Iq_PID.I_Gain;
      
          Iq_PID.I_Sum += I_term;
          if(Iq_PID.I_Sum>Iq_PID.I_Sum_max) Iq_PID.I_Sum = Iq_PID.I_Sum_max;
          else if(Iq_PID.I_Sum<Iq_PID.I_Sum_min)Iq_PID.I_Sum = Iq_PID.I_Sum_min;
      
          output = P_term + Iq_PID.I_Sum;
          if(output>Iq_PID.Max_Output) output = Iq_PID.Max_Output;
          else if(output<Iq_PID.Min_Output)output = Iq_PID.Min_Output;
      
          return output;
}速度观测器计算float VoltRs;
    float VoltdPhi;
         
          float g_fluxfluxR = 0.0f;
          float sin_theta = 0.0f;
          float cos_theta = 0.0f;
      
    //calc alpha asix flux
    VoltRs = Foc_observer.Rs * Foc_calc.Ialpha;
          VoltdPhi = Foc_calc.Valpha- VoltRs;
          VoltdPhi += fluxR_in_wb * Foc_observer.Err * Foc_observer.Gain;
          flux_in_wb += VoltdPhi * Foc_observer.Ctrl_ts;
      
          //calc beta asix flux
          VoltRs = Foc_observer.Rs * Foc_calc.Ibeta;
          VoltdPhi = Foc_calc.Vbeta- VoltRs;
          VoltdPhi += fluxR_in_wb * Foc_observer.Err * Foc_observer.Gain;
          flux_in_wb += VoltdPhi * Foc_observer.Ctrl_ts;
      
          //calc flux in stator
    fluxS_in_wb = Foc_observer.Ls * Foc_calc.Ialpha;
    fluxS_in_wb = Foc_observer.Ls * Foc_calc.Ibeta;
               
                //calc flux in rotor
    fluxR_in_wb = flux_in_wb - fluxS_in_wb;
    fluxR_in_wb = flux_in_wb - fluxS_in_wb;
               
                g_fluxfluxR = fluxR_in_wb*fluxR_in_wb + fluxR_in_wb*fluxR_in_wb;

    Foc_observer.Err = Foc_observer.Flux * Foc_observer.Flux - g_fluxfluxR;
               
                sin_theta = arm_sin_f32(Foc_observer.Theta);
                cos_theta = arm_cos_f32(Foc_observer.Theta);      

          Foc_observer.PLL_Err = fluxR_in_wb * cos_theta - fluxR_in_wb * sin_theta;
    Foc_observer.PLL_Interg += Foc_observer.PLL_Err * Foc_observer.PLL_ki;      
    Foc_observer.PLL_Ui = Foc_observer.PLL_Err * Foc_observer.PLL_kp + Foc_observer.PLL_Interg;   
               
                Foc_observer.Theta += Foc_observer.PLL_Ui;
               
               
                if(Foc_observer.Theta<0.0f)
                {
                        Foc_observer.Theta+=MATH_2PI;
                }
                else if(Foc_observer.Theta>MATH_2PI)
                {
                        Foc_observer.Theta-=MATH_2PI;
                }
               
                if(speed_calc_cnt<10)
                {
                        speed_acc += Foc_observer.PLL_Ui;
                        speed_calc_cnt++;
                }
                else
                {
                              speed_now = speed_acc/(0.001f*MATH_2PI);
                              Foc_observer.speed_hz = Foc_observer.speed_hz * 0.99f + speed_now * 0.01f;
                                 
                        speed_acc = 0.0f;
                        speed_calc_cnt = 0;
                }clark变换计算 Foc_calc.Ialpha = (2.0f*Foc_input.Ia - Foc_input.Ib - Foc_input.Ic)/3.0f;
                            Foc_calc.Ibeta = SQRT3*(Foc_input.Ib - Foc_input.Ic)/3.0f; park变换 Foc_calc.sin_Theta = arm_sin_f32(Foc_input.Theta);
                            Foc_calc.cos_Theta = arm_cos_f32(Foc_input.Theta);
                            Foc_calc.Id = Foc_calc.Ialpha*Foc_calc.cos_Theta + Foc_calc.Ibeta*Foc_calc.sin_Theta;
                            Foc_calc.Iq = -Foc_calc.Ialpha*Foc_calc.sin_Theta + Foc_calc.Ibeta*Foc_calc.cos_Theta;逆park变换Foc_calc.Valpha =      Foc_calc.Vd*Foc_calc.cos_Theta - Foc_calc.Vq*Foc_calc.sin_Theta;      
          Foc_calc.Vbeta = Foc_calc.Vd*Foc_calc.sin_Theta + Foc_calc.Vq*Foc_calc.cos_Theta;                SVPWM计算根据 Valpha 和 Vbeta 的值判断当前处于哪个扇区(共6个扇区)。
利用两个条件判断来确定具体扇区编号(1~6),通过累加的方式赋值给 sector。
根据不同的扇区,分别计算 Tx 和 Ty 的值。
每个扇区的公式不同,但本质是基于 SVPWM 矢量分解原理计算出对应的时间值。
如果 Tx + Ty 超过 PWM 周期 TPWM,则对Tx 和 Ty 进行归一化处理,确保时间不超过周期限制。
不同扇区将 Ta,Tb, Tc 映射到三相输出 Tcmp1, Tcmp2, Tcmp3 上。
通过 switch 语句根据不同扇区选择正确的映射方式。
将计算得到的三相占空比赋值给输出结构体 Foc_output,输出到PWM上。
uint8_t sector = 0;
float Tcmp1,Tcmp2,Tcmp3,Tx,Ty,f_temp,Ta,Tb,Tc;
      float modulate_param = 0.0f;
      
sector = 0;
Tcmp1 = 0.0f;
Tcmp2 = 0.0f;
Tcmp3 = 0.0f;      
      modulate_param = Foc_input.TPWM / Foc_input.Vbus;
      
if(Foc_calc.Vbeta > 0.0f) {
    sector = 1;
}

if ((SQRT3 * Foc_calc.Valpha - Foc_calc.Vbeta) / 2.0f > 0.0f) {
    sector += 2;
}

if ((-SQRT3 * Foc_calc.Valpha - Foc_calc.Vbeta) / 2.0f > 0.0f) {
    sector += 4;
}

switch(sector)
      {
                        case 1: //2扇区
                                                Tx = (-3.0f * Foc_calc.Valpha + SQRT3 * Foc_calc.Vbeta)/2.0f * modulate_param;
                                                Ty = (3.0f * Foc_calc.Valpha + SQRT3 * Foc_calc.Vbeta)/2.0f * modulate_param;
                                                break;
                              
                        case 2: //6扇区
                                                Tx = (3.0f * Foc_calc.Valpha + SQRT3 * Foc_calc.Vbeta)/2.0f * modulate_param;
                                                Ty = -(SQRT3 * Foc_calc.Vbeta * modulate_param);
                                                break;
                              
                        case 3: //1扇区
                                                Tx = -((-3.0f * Foc_calc.Valpha + SQRT3 * Foc_calc.Vbeta)/2.0f * modulate_param);
                                                Ty = SQRT3 * Foc_calc.Vbeta * modulate_param;
                                                break;
                              
                        case 4: //4扇区
                                                Tx = -(SQRT3 * Foc_calc.Vbeta * modulate_param);
                                                Ty = (-3.0f * Foc_calc.Valpha + SQRT3 * Foc_calc.Vbeta)/2.0f * modulate_param;
                                                break;
                              
                        case 5: //3扇区
                                                Tx = SQRT3 * Foc_calc.Vbeta * modulate_param;
                                                Ty = -((3.0f * Foc_calc.Valpha + SQRT3 * Foc_calc.Vbeta)/2.0f *modulate_param);
                                                break;
                              
                        default: //5扇区
                                                Tx = -((3.0f * Foc_calc.Valpha + SQRT3 * Foc_calc.Vbeta)/2.0f * modulate_param);
                                                Ty = -((-3.0f * Foc_calc.Valpha + SQRT3 * Foc_calc.Vbeta)/2.0f * modulate_param);
                                                break;
}

f_temp = Tx + Ty;
if(f_temp > Foc_input.TPWM)
      {
                        Tx = Tx/f_temp*Foc_input.TPWM;
                        Ty = Ty/f_temp*Foc_input.TPWM;
}

Ta = (Foc_input.TPWM - (Tx + Ty)) / 4.0f;
Tb = Tx / 2.0f + Ta;
Tc = Ty / 2.0f + Tb;
switch (sector)
      {
                        case 1:
                                        Tcmp1 = Tb;
                                        Tcmp2 = Ta;
                                        Tcmp3 = Tc;
                                        break;
                              
                        case 2:
                                        Tcmp1 = Ta;
                                        Tcmp2 = Tc;
                                        Tcmp3 = Tb;
                                        break;
                              
                        case 3:
                                        Tcmp1 = Ta;
                                        Tcmp2 = Tb;
                                        Tcmp3 = Tc;
                                        break;
                              
                        case 4:
                                        Tcmp1 = Tc;
                                        Tcmp2 = Tb;
                                        Tcmp3 = Ta;
                                        break;
                              
                        case 5:
                                        Tcmp1 = Tc;
                                        Tcmp2 = Ta;
                                        Tcmp3 = Tb;
                                        break;
                              
                        case 6:
                                        Tcmp1 = Tb;
                                        Tcmp2 = Tc;
                                        Tcmp3 = Ta;
                                        break;
}

Foc_output.Tcmp1 = Tcmp1;
Foc_output.Tcmp2 = Tcmp2;
Foc_output.Tcmp3 = Tcmp3;
      将这些移植到AT32M412中进行验证。


chenjun89 发表于 2025-6-4 22:00

代码可以用代码编辑空间,这样网页显示支持性更好。
页: [1]
查看完整版本: 【AT-START-M412测评】+ 无刷电机控制2-开始移植