打印
[活动专区]

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

[复制链接]
487|1
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 穿西装的强子 于 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[Clarke变换] --> B[Park变换]
  B --> C[PI调节器]
  C --> D[反Park变换]
  D --> E[SVPWM生成]
关键算法实现细节
电流环控制,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[2];
    float VoltdPhi[2];
           
          float g_fluxfluxR = 0.0f;
          float sin_theta = 0.0f;
          float cos_theta = 0.0f;
        
    //calc alpha asix flux
    VoltRs[0] = Foc_observer.Rs * Foc_calc.Ialpha;
          VoltdPhi[0] = Foc_calc.Valpha  - VoltRs[0];
          VoltdPhi[0] += fluxR_in_wb[0] * Foc_observer.Err * Foc_observer.Gain;
          flux_in_wb[0] += VoltdPhi[0] * Foc_observer.Ctrl_ts;
        
          //calc beta asix flux
          VoltRs[1] = Foc_observer.Rs * Foc_calc.Ibeta;
          VoltdPhi[1] = Foc_calc.Vbeta  - VoltRs[1];
          VoltdPhi[1] += fluxR_in_wb[1] * Foc_observer.Err * Foc_observer.Gain;
          flux_in_wb[1] += VoltdPhi[1] * Foc_observer.Ctrl_ts;
        
          //calc flux in stator
    fluxS_in_wb[0] = Foc_observer.Ls * Foc_calc.Ialpha;
    fluxS_in_wb[1] = Foc_observer.Ls * Foc_calc.Ibeta;
               
                //calc flux in rotor
    fluxR_in_wb[0] = flux_in_wb[0] - fluxS_in_wb[0];
    fluxR_in_wb[1] = flux_in_wb[1] - fluxS_in_wb[1];
               
                g_fluxfluxR = fluxR_in_wb[0]*fluxR_in_wb[0] + fluxR_in_wb[1]*fluxR_in_wb[1];

    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[1] * cos_theta - fluxR_in_wb[0] * 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,则对TxTy 进行归一化处理,确保时间不超过周期限制。

不同扇区将 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 | 只看该作者
代码可以用代码编辑空间,这样网页显示支持性更好。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

45

主题

193

帖子

3

粉丝