【STM32-HAL库】一步步搭建出FOC矢量控制(附C代码)

2023-05-16

说明

本文为无刷电机或PMSM电机驱动的简易代码,旨在分享一些个人调试过程的小心得,提供一个demo文件,程序仍有许多不完善的地方,建立起个人的FOC底层驱动,可以帮助快速熟悉FOC算法原理与使用方法,可以帮助验证新的电机控制算法。原理部分不再阐述。
整个部分共有PWM模块、ADC电流采集、定时器编码器配置、SVPWM模块、FOC核心、PID模块、电压限幅模块,其实有了PWM与SVPWM以及一些必要的数学变换,我们就可以开环使电机转起来了,加入电角度与电流采集作为反馈后,我们就能做到电流闭环,再加入速度PID就可以做到速度闭环,其他的模块只是这些目的的辅助手段罢了。

注意:
调试一定要注意安全!!!
使用带有保护的电源,调试时一定要限制电流在安全等级,开关放手边,随时断电!

硬件相关:
(1)MCU为STM32F405RGT6
(2)引脚分配
PWM:TIM1–PA8、PA9、PA10、PB13、PB14、PB15
电流采样:IA–PA6、IB–PA7、IC–PC4
编码器: EA–PA0、EB–PA1
串口: PB6、PB7
(3)编码器为1250线,电机为PMSM、4対极
软件相关:
STM32CubeMX、Keil

如果自制硬件可参考:迷你FOC驱动器

参考资料:
(1)ST电机库
(2)PMSM的FOC 矢量控制算法调试流程,新手上手流程
(3)PMSM矢量控制算法调试流程
(4)FOC和SVPWM的C语言代码实现
(5)上官致远–深入理解无刷直流电机矢量控制技术–科学出版社

0、系统配置

将下列值加入到Cube的User Constants下,然后按照下面的图配置好基本外设。

#define CKTIM 168000000//定时器时钟频率
#define PWM_PRSC 0
#define PWM_FREQ 15000//PWM频率
#define PWM_PERIOD CKTIM/(2*PWM_FREQ*(PWM_PRSC+1))
#define REP_RATE 1 //电流环刷新频率为(REP_RATE+1)/(2*PWM_FREQ)
#define DEADTIME_NS 1000//死区时间ns
#define DEADTIME CKTIM/1000000/2*DEADTIME_NS/1000
#define POLE_PAIR_NUM 4//极对数
#define ENCODER_PPR 1250//编码器线数
#define ALIGNMENT_ANGLE 300
#define COUNTER_RESET (ALIGNMENT_ANGLE*4*ENCODER_PPR/360-1)/POLE_PAIR_NUM
#define ICx_FILTER 8

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1、电机有力了!(PWM模块)

高级定时器主要用于产生6路互补的PWM来驱动MOS管,加入死区防止电源导通,本文未使用刹车引脚。高级定时器1通道1、2、3用于产生PWM,通道4用于触发ADC电流采样,根据扇区的位置,灵活设置PWM占空比,进而选择合理的触发点,避免在噪声点采样。引脚配置与PWM极性请根据自己的硬件合理配置,如IR2101是高电平有效,而IR2103则是低端低有效,高端高有效。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

PWM测试

生成工程后,应首先对PWM模块进行测试,如果有示波器,先测试PWM是否正常(安全起见一路路测试),死区时间是否正确,然后主函数中加入下列代码,导通U相,注意:占空比一定不能设置的过大,防止电流过大,烧毁电机与驱动板。同理可测试其它相。测试完成后进入下一项。
当然,也可以通过这种方法知道你电机的极对数,导通一相后,用手转动电机一圈,感到有几次阻力,就是几对极。或者,不使用驱控板,先用万用表测试电机任意两相间的电阻,然后通合适的电压,如电阻为2欧,则可以通1V电压,然后用手转动电机一圈,感到有几次阻力,就是几对极。

  /* USER CODE BEGIN 2 */
  //此时电机应该是有阻力的
	HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);
	HAL_TIMEx_PWMN_Start(&htim1,TIM_CHANNEL_2);
    __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1,400);//不能设置的过大
	__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2,5600);//5600为最大占空比
  /* USER CODE END 2 */

不加驱动板时50%占空比波形与1000ns死区
在这里插入图片描述
在这里插入图片描述

2、让电机转起来吧!(SVPWM)

在主函数头文件main.h中加入下面定义,这在后面都会用到。

typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef int8_t s8;
typedef int16_t s16;
typedef int32_t s32;
typedef __IO uint32_t  vu32;
typedef __IO uint16_t vu16;
typedef __IO uint8_t  vu8;

#define U8_MAX     ((u8)255)
#define S8_MAX     ((s8)127)
#define S8_MIN     ((s8)-128)
#define U16_MAX    ((u16)65535u)
#define S16_MAX    ((s16)32767)
#define S16_MIN    ((s16)-32768)
#define U32_MAX    ((u32)4294967295uL)
#define S32_MAX    ((s32)2147483647)
#define S32_MIN    ((s32)-2147483648)

加入下面代码,主要是数学变换中的Clark变换、Park变换、反Park变换,以及SVPWM模块。


//结构体定义
typedef struct 
{
  s16 qI_Component1;
  s16 qI_Component2;
} Curr_Components;


typedef struct 
{
  s16 qV_Component1;
  s16 qV_Component2;
} Volt_Components;

typedef struct     //电压值结构体
{
  s16 hCos;
  s16 hSin;
} Trig_Components;  //存放角度sin和cos函数值的结构体

typedef struct
{
    s16 hKp_Gain;			   //比例系数
    u16 hKp_Divisor;		   //比例系数因子
    s16 hKi_Gain;		       //积分系数
    u16 hKi_Divisor;  	       //积分系数因子
    s16 hLower_Limit_Output;   //总输出下限
    s16 hUpper_Limit_Output;   //总输出上限
    s32 wLower_Limit_Integral; //积分项下限
    s32 wUpper_Limit_Integral; //积分项上限
    s32 wIntegral;			   //积分累积和
    s16 hKd_Gain;			   //微分系数
    u16 hKd_Divisor;		   //微分系数因子
    s32 wPreviousError;	       //上次误差
} PID_Struct_t;
 




//数学变换部分
#define S16_MAX    ((s16)32767)
#define S16_MIN    ((s16)-32768)
#define divSQRT_3	(s16)0x49E6      //1/sqrt(3)的Q15格式,1/sqrt(3)*2^15=18918=0x49E6 
#define SIN_MASK  0x0300
#define U0_90     0x0200
#define U90_180   0x0300
#define U180_270  0x0000
#define U270_360  0x0100
#define SQRT_3		1.732051
#define T		    (PWM_PERIOD * 4)
#define T_SQRT3     (u16)(T * SQRT_3)
//SVPWM部分
#define SECTOR_1	(u32)1
#define SECTOR_2	(u32)2
#define SECTOR_3	(u32)3
#define SECTOR_4	(u32)4
#define SECTOR_5	(u32)5
#define SECTOR_6	(u32)6
#define PWM2_MODE 0
#define PWM1_MODE 1
#define TW_AFTER ((u16)(((DEADTIME_NS+MAX_TNTR_NS)*168uL)/1000ul))
#define TW_BEFORE (((u16)(((((u16)(SAMPLING_TIME_NS)))*168uL)/1000ul))+1)
#define TNOISE_NS 1550     //2.55usec
#define TRISE_NS 1550     //2.55usec
#define SAMPLING_TIME_NS   700  //700ns
#define SAMPLING_TIME (u16)(((u16)(SAMPLING_TIME_NS) * 168uL)/1000uL) 
#define TNOISE (u16)((((u16)(TNOISE_NS)) * 168uL)/1000uL)
#define TRISE (u16)((((u16)(TRISE_NS)) * 168uL)/1000uL)
#define TDEAD (u16)((DEADTIME_NS * 168uL)/1000uL)

#if (TNOISE_NS > TRISE_NS)
  #define MAX_TNTR_NS TNOISE_NS
#else
  #define MAX_TNTR_NS TRISE_NS
#endif

//函数声明

//数学变换
Curr_Components Clarke(Curr_Components Curr_Input);
Trig_Components Trig_Functions(s16 hAngle);
Curr_Components Park(Curr_Components Curr_Input, s16 Theta);
Volt_Components Rev_Park(Volt_Components Volt_Input);
//SVPWM
void SVPWM_3ShuntCalcDutyCycles (Volt_Components Stat_Volt_Input);
//FOC核心
void FOC_Model(void);
//系统初始化
void motor_init(void);

//变量定义部分
Trig_Components Vector_Components;
u8 bSector;
u8 PWM4Direction=PWM2_MODE;
s16 cnt = S16_MIN;//开环调试变量

//FOC相关
Trig_Components Vector_Components;
Curr_Components Stat_Curr_a_b;            
Curr_Components Stat_Curr_alfa_beta;       
Curr_Components Stat_Curr_q_d;             
Curr_Components Stat_Curr_q_d_ref_ref;   //电流环的给定值,用于电流环Id,Iq和前馈电流控制的给定值
Volt_Components Stat_Volt_q_d;             
Volt_Components Stat_Volt_alfa_beta; 
PID_Struct_t PID_Torque_InitStructure;
PID_Struct_t PID_Flux_InitStructure;
PID_Struct_t PID_Speed_InitStructure;

void motor_init(void)
{
	//PWM初始化
	HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);
	HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_2);
	HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_3);
	HAL_TIMEx_PWMN_Start(&htim1,TIM_CHANNEL_1);
	HAL_TIMEx_PWMN_Start(&htim1,TIM_CHANNEL_2);
	HAL_TIMEx_PWMN_Start(&htim1,TIM_CHANNEL_3);
  __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1,0);
	__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2,0);
	__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3,0);
  //通道4触发ADC采样
	HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_4);
	__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_4,1000);//初始占空比应该多少?
//	//开启ADC注入转换
//	HAL_ADCEx_InjectedStart_IT(&hadc1);
//	//使能ABZ编码器
//	HAL_TIM_Encoder_Start_IT(&htim2,TIM_CHANNEL_ALL);
//  HAL_TIM_Base_Start_IT(&htim2);
//	//初始化PID控制器
//	PID_Init(&PID_Torque_InitStructure,&PID_Flux_InitStructure,&PID_Speed_InitStructure);
  
//	State = START;
}

void FOC_Model(void)	  //电流环处理函数
{
//	Stat_Curr_a_b = SVPWM_3ShuntGetPhaseCurrentValues();			//读取2相的电流值
//	Stat_Curr_alfa_beta = Clarke(Stat_Curr_a_b);	//Ia,Ib通过Clark变换得到Ialpha和Ibeta  
//	Stat_Curr_q_d = Park( Stat_Curr_alfa_beta,ENC_Get_Electrical_Angle());  //输入电角度、Ialpha和Ibeta,经过Park变换得到Iq、Id							
//	Stat_Volt_q_d.qV_Component1 = PID_Regulator(hTorque_Reference,Stat_Curr_q_d.qI_Component1, &PID_Torque_InitStructure);
//	Stat_Volt_q_d.qV_Component2 = PID_Regulator(hFlux_Reference,Stat_Curr_q_d.qI_Component2, &PID_Flux_InitStructure);  	
//	RevPark_Circle_Limitation();  	//归一化
	
	
	//开环调试
	Stat_Volt_q_d.qV_Component1 = 0;
	Stat_Volt_q_d.qV_Component2 = 3000;
	cnt+=500;
	if(cnt>S16_MAX)
		cnt=S16_MIN;
	Vector_Components = Trig_Functions(cnt);
	Stat_Volt_alfa_beta = Rev_Park(Stat_Volt_q_d);    //反Park变换
	SVPWM_3ShuntCalcDutyCycles(Stat_Volt_alfa_beta);  //svpwm实现函数,实际的电流输出控制	
}




//SVPWM
void SVPWM_3ShuntCalcDutyCycles (Volt_Components Stat_Volt_Input)
{
   s32 wX, wY, wZ, wUAlpha, wUBeta;
   u16  hTimePhA=0, hTimePhB=0, hTimePhC=0, hTimePhD=0;
   u16  hDeltaDuty;
    
   wUAlpha = Stat_Volt_Input.qV_Component1 * T_SQRT3 ;
   wUBeta = -(Stat_Volt_Input.qV_Component2 * T);

   wX = wUBeta;
   wY = (wUBeta + wUAlpha)/2;
   wZ = (wUBeta - wUAlpha)/2;
   
  // Sector calculation from wX, wY, wZ
   if (wY<0)
   {
      if (wZ<0)
      {
        bSector = SECTOR_5;
      }
      else // wZ >= 0
        if (wX<=0)
        {
          bSector = SECTOR_4;
        }
        else // wX > 0
        {
          bSector = SECTOR_3;
        }
   }
   else // wY > 0
   {
     if (wZ>=0)
     {
       bSector = SECTOR_2;
     }
     else // wZ < 0
       if (wX<=0)
       {  
         bSector = SECTOR_6;
       }
       else // wX > 0
       {
         bSector = SECTOR_1;
       }
    }
   
   /* Duty cycles computation */
  PWM4Direction=PWM2_MODE;
    
  switch(bSector)
  {  
    case SECTOR_1:
                hTimePhA = (T/8) + ((((T + wX) - wZ)/2)/131072);
				hTimePhB = hTimePhA + wZ/131072;
				hTimePhC = hTimePhB - wX/131072;
                
                // ADC Syncronization setting value             
                if ((u16)(PWM_PERIOD-hTimePhA) > TW_AFTER)
                {
                  hTimePhD = PWM_PERIOD - 1;
                }
                else
                {
                  hDeltaDuty = (u16)(hTimePhA - hTimePhB);
                  
				  // Definition of crossing point
                  if (hDeltaDuty > (u16)(PWM_PERIOD-hTimePhA)*2) 
                  {
                      hTimePhD = hTimePhA - TW_BEFORE; // Ts before Phase A 
                  }
                  else
                  {
                      hTimePhD = hTimePhA + TW_AFTER; // DT + Tn after Phase A
                     
                    if (hTimePhD >= PWM_PERIOD)
                    {
                      // Trigger of ADC at Falling Edge PWM4
                      // OCR update
                      
                      //Set Polarity of CC4 Low
                      PWM4Direction=PWM1_MODE;
                      
                      hTimePhD = (2 * PWM_PERIOD) - hTimePhD-1;
                    }
                  }
                }
                            
                break;
    case SECTOR_2:
                hTimePhA = (T/8) + ((((T + wY) - wZ)/2)/131072);
				hTimePhB = hTimePhA + wZ/131072;
				hTimePhC = hTimePhA - wY/131072;
                
                // ADC Syncronization setting value
                if ((u16)(PWM_PERIOD-hTimePhB) > TW_AFTER)
                {
                  hTimePhD = PWM_PERIOD - 1;
                }
                else
                {
                  hDeltaDuty = (u16)(hTimePhB - hTimePhA);
                  
                  // Definition of crossing point
                  if (hDeltaDuty > (u16)(PWM_PERIOD-hTimePhB)*2) 
                  {
                    hTimePhD = hTimePhB - TW_BEFORE; // Ts before Phase B 
                  }
                  else
                  {
                    hTimePhD = hTimePhB + TW_AFTER; // DT + Tn after Phase B
                    
                    if (hTimePhD >= PWM_PERIOD)
                    {
                      // Trigger of ADC at Falling Edge PWM4
                      // OCR update
                      
                      //Set Polarity of CC4 Low
                      PWM4Direction=PWM1_MODE;
                      
                      hTimePhD = (2 * PWM_PERIOD) - hTimePhD-1;
                    }
                  }
                }
                
                break;

    case SECTOR_3:
                hTimePhA = (T/8) + ((((T - wX) + wY)/2)/131072);
				        hTimePhC = hTimePhA - wY/131072;
                hTimePhB = hTimePhC + wX/131072;
		
                // ADC Syncronization setting value
                if ((u16)(PWM_PERIOD-hTimePhB) > TW_AFTER)
                {
                  hTimePhD = PWM_PERIOD - 1;
                }
                else
                {
                  hDeltaDuty = (u16)(hTimePhB - hTimePhC);
                  
                  // Definition of crossing point
                  if (hDeltaDuty > (u16)(PWM_PERIOD-hTimePhB)*2) 
                  {
                    hTimePhD = hTimePhB - TW_BEFORE; // Ts before Phase B 
                  }
                  else
                  {
                    hTimePhD = hTimePhB + TW_AFTER; // DT + Tn after Phase B
                    
                    if (hTimePhD >= PWM_PERIOD)
                    {
                      // Trigger of ADC at Falling Edge PWM4
                      // OCR update
                      
                      //Set Polarity of CC4 Low
                      PWM4Direction=PWM1_MODE;
                      
                      hTimePhD = (2 * PWM_PERIOD) - hTimePhD-1;
                    }
                  }
                }
                break;
    
    case SECTOR_4:
                hTimePhA = (T/8) + ((((T + wX) - wZ)/2)/131072);
                hTimePhB = hTimePhA + wZ/131072;
                hTimePhC = hTimePhB - wX/131072;
                
                // ADC Syncronization setting value
                if ((u16)(PWM_PERIOD-hTimePhC) > TW_AFTER)
                {
                  hTimePhD = PWM_PERIOD - 1;
                }
                else
                {
                  hDeltaDuty = (u16)(hTimePhC - hTimePhB);
                  
                  // Definition of crossing point
                  if (hDeltaDuty > (u16)(PWM_PERIOD-hTimePhC)*2)
                  {
                    hTimePhD = hTimePhC - TW_BEFORE; // Ts before Phase C 
                  }
                  else
                  {
                    hTimePhD = hTimePhC + TW_AFTER; // DT + Tn after Phase C
                    
                    if (hTimePhD >= PWM_PERIOD)
                    {
                      // Trigger of ADC at Falling Edge PWM4
                      // OCR update
                      
                      //Set Polarity of CC4 Low
                      PWM4Direction=PWM1_MODE;
                      
                      hTimePhD = (2 * PWM_PERIOD) - hTimePhD-1;
                    }
                  }
                }
                break;  
    
    case SECTOR_5:
                hTimePhA = (T/8) + ((((T + wY) - wZ)/2)/131072);
				hTimePhB = hTimePhA + wZ/131072;
				hTimePhC = hTimePhA - wY/131072;
                
                // ADC Syncronization setting value
                if ((u16)(PWM_PERIOD-hTimePhC) > TW_AFTER)
                {
                  hTimePhD = PWM_PERIOD - 1;
                }
                else
                {
                  hDeltaDuty = (u16)(hTimePhC - hTimePhA);
                  
                  // Definition of crossing point
                  if (hDeltaDuty > (u16)(PWM_PERIOD-hTimePhC)*2) 
                  {
                    hTimePhD = hTimePhC - TW_BEFORE; // Ts before Phase C 
                  }
                  else
                  {
                    hTimePhD = hTimePhC + TW_AFTER; // DT + Tn after Phase C
                    
                    if (hTimePhD >= PWM_PERIOD)
                    {
                      // Trigger of ADC at Falling Edge PWM4
                      // OCR update
                      
                      //Set Polarity of CC4 Low
                      PWM4Direction=PWM1_MODE;
                      
                      hTimePhD = (2 * PWM_PERIOD) - hTimePhD-1;
                    }
                  }
                }

		break;
                
    case SECTOR_6:
                hTimePhA = (T/8) + ((((T - wX) + wY)/2)/131072);
				hTimePhC = hTimePhA - wY/131072;
				hTimePhB = hTimePhC + wX/131072;
                
                // ADC Syncronization setting value
                if ((u16)(PWM_PERIOD-hTimePhA) > TW_AFTER)
                {
                  hTimePhD = PWM_PERIOD - 1;
                }
                else
                {
                  hDeltaDuty = (u16)(hTimePhA - hTimePhC);
                  
                  // Definition of crossing point
                  if (hDeltaDuty > (u16)(PWM_PERIOD-hTimePhA)*2) 
                  {
                    hTimePhD = hTimePhA - TW_BEFORE; // Ts before Phase A 
                  }
                  else
                  {
                    hTimePhD = hTimePhA + TW_AFTER; // DT + Tn after Phase A
                    
                    if (hTimePhD >= PWM_PERIOD)
                    {
                      // Trigger of ADC at Falling Edge PWM4
                      // OCR update
                      
                      //Set Polarity of CC4 Low
                      PWM4Direction=PWM1_MODE;
                      
                      hTimePhD = (2 * PWM_PERIOD) - hTimePhD-1;
                    }
                  }
                }

                break;
    default:
		break;
   }
  
  if (PWM4Direction == PWM2_MODE)
  {
    //Set Polarity of CC4 High
    TIM1->CCER &= 0xDFFF;    
  }
  else
  {
    //Set Polarity of CC4 Low
    TIM1->CCER |= 0x2000;
  }
  
  /* Load compare registers values */ 
	__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1,hTimePhA);
	__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2,hTimePhB);
	__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3,hTimePhC);
	__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_4,hTimePhD);
}

//数学函数
const s16 hSin_Cos_Table[256] = {\
0x0000,0x00C9,0x0192,0x025B,0x0324,0x03ED,0x04B6,0x057F,\
0x0648,0x0711,0x07D9,0x08A2,0x096A,0x0A33,0x0AFB,0x0BC4,\
0x0C8C,0x0D54,0x0E1C,0x0EE3,0x0FAB,0x1072,0x113A,0x1201,\
0x12C8,0x138F,0x1455,0x151C,0x15E2,0x16A8,0x176E,0x1833,\
0x18F9,0x19BE,0x1A82,0x1B47,0x1C0B,0x1CCF,0x1D93,0x1E57,\
0x1F1A,0x1FDD,0x209F,0x2161,0x2223,0x22E5,0x23A6,0x2467,\
0x2528,0x25E8,0x26A8,0x2767,0x2826,0x28E5,0x29A3,0x2A61,\
0x2B1F,0x2BDC,0x2C99,0x2D55,0x2E11,0x2ECC,0x2F87,0x3041,\
0x30FB,0x31B5,0x326E,0x3326,0x33DF,0x3496,0x354D,0x3604,\
0x36BA,0x376F,0x3824,0x38D9,0x398C,0x3A40,0x3AF2,0x3BA5,\
0x3C56,0x3D07,0x3DB8,0x3E68,0x3F17,0x3FC5,0x4073,0x4121,\
0x41CE,0x427A,0x4325,0x43D0,0x447A,0x4524,0x45CD,0x4675,\
0x471C,0x47C3,0x4869,0x490F,0x49B4,0x4A58,0x4AFB,0x4B9D,\
0x4C3F,0x4CE0,0x4D81,0x4E20,0x4EBF,0x4F5D,0x4FFB,0x5097,\
0x5133,0x51CE,0x5268,0x5302,0x539B,0x5432,0x54C9,0x5560,\
0x55F5,0x568A,0x571D,0x57B0,0x5842,0x58D3,0x5964,0x59F3,\
0x5A82,0x5B0F,0x5B9C,0x5C28,0x5CB3,0x5D3E,0x5DC7,0x5E4F,\
0x5ED7,0x5F5D,0x5FE3,0x6068,0x60EB,0x616E,0x61F0,0x6271,\
0x62F1,0x6370,0x63EE,0x646C,0x64E8,0x6563,0x65DD,0x6656,\
0x66CF,0x6746,0x67BC,0x6832,0x68A6,0x6919,0x698B,0x69FD,\
0x6A6D,0x6ADC,0x6B4A,0x6BB7,0x6C23,0x6C8E,0x6CF8,0x6D61,\
0x6DC9,0x6E30,0x6E96,0x6EFB,0x6F5E,0x6FC1,0x7022,0x7083,\
0x70E2,0x7140,0x719D,0x71F9,0x7254,0x72AE,0x7307,0x735E,\
0x73B5,0x740A,0x745F,0x74B2,0x7504,0x7555,0x75A5,0x75F3,\
0x7641,0x768D,0x76D8,0x7722,0x776B,0x77B3,0x77FA,0x783F,\
0x7884,0x78C7,0x7909,0x794A,0x7989,0x79C8,0x7A05,0x7A41,\
0x7A7C,0x7AB6,0x7AEE,0x7B26,0x7B5C,0x7B91,0x7BC5,0x7BF8,\
0x7C29,0x7C59,0x7C88,0x7CB6,0x7CE3,0x7D0E,0x7D39,0x7D62,\
0x7D89,0x7DB0,0x7DD5,0x7DFA,0x7E1D,0x7E3E,0x7E5F,0x7E7E,\
0x7E9C,0x7EB9,0x7ED5,0x7EEF,0x7F09,0x7F21,0x7F37,0x7F4D,\
0x7F61,0x7F74,0x7F86,0x7F97,0x7FA6,0x7FB4,0x7FC1,0x7FCD,\
0x7FD8,0x7FE1,0x7FE9,0x7FF0,0x7FF5,0x7FF9,0x7FFD,0x7FFE};



Curr_Components Clarke(Curr_Components Curr_Input)         
{
  Curr_Components Curr_Output; 
  s32 qIa_divSQRT3_tmp;
  s32 qIb_divSQRT3_tmp;    //定义32位有符号数,用来暂存Q30格式  
  s16 qIa_divSQRT3;
  s16 qIb_divSQRT3 ;
 
  Curr_Output.qI_Component1 = Curr_Input.qI_Component1;     //Ialpha = Ia
 
  qIa_divSQRT3_tmp = divSQRT_3 * Curr_Input.qI_Component1;  //计算Ia/√3
  qIa_divSQRT3_tmp /=32768;                                 //两个Q15数相乘,会变成Q30,因此要右移15位,变回Q15	    
  qIb_divSQRT3_tmp = divSQRT_3 * Curr_Input.qI_Component2;  //计算Ib/√3
  qIb_divSQRT3_tmp /=32768;
  
  qIa_divSQRT3=((s16)(qIa_divSQRT3_tmp));	                //s32赋值给s16	 		
  qIb_divSQRT3=((s16)(qIb_divSQRT3_tmp));				
     
  Curr_Output.qI_Component2=(-(qIa_divSQRT3)-(qIb_divSQRT3)-(qIb_divSQRT3));  //Ibeta = -(2*Ib+Ia)/sqrt(3) 
  return(Curr_Output); 
}
 
	
/*******************************************************************************
* Function Name  : Trig_Functions 
* Description    : 本函数返回输入角度的cos和sin函数值
* Input          : angle in s16 format
* Output         : Cosine and Sine in s16 format
*******************************************************************************/
Trig_Components Trig_Functions(s16 hAngle)  //hAngle=0,转子电角度=0度。hAngle=S16_MAX,转子电角度=180度。hAngle=S16_MIN,转子电角度=-180度
{
  u16 hindex;
  Trig_Components Local_Components;
  
  /* 10 bit index computation  */  
  hindex = (u16)(hAngle + 32768);  
  hindex /= 64;      
  
  switch (hindex & SIN_MASK) 
  {
  case U0_90:
    Local_Components.hSin = hSin_Cos_Table[(u8)(hindex)];
    Local_Components.hCos = hSin_Cos_Table[(u8)(0xFF-(u8)(hindex))];
    break;
  
  case U90_180:  
     Local_Components.hSin = hSin_Cos_Table[(u8)(0xFF-(u8)(hindex))];
     Local_Components.hCos = -hSin_Cos_Table[(u8)(hindex)];
    break;
  
  case U180_270:
     Local_Components.hSin = -hSin_Cos_Table[(u8)(hindex)];
     Local_Components.hCos = -hSin_Cos_Table[(u8)(0xFF-(u8)(hindex))];
    break;
  
  case U270_360:
     Local_Components.hSin =  -hSin_Cos_Table[(u8)(0xFF-(u8)(hindex))];
     Local_Components.hCos =  hSin_Cos_Table[(u8)(hindex)]; 
    break;
  default:
    break;
  }
  return (Local_Components);
}
 

/**********************************************************************************************************
Park变换,输入电角度、Ialpha和Ibeta,经过Park变换得到Iq、Id
**********************************************************************************************************/ 
Curr_Components Park(Curr_Components Curr_Input, s16 Theta)       
{ 
  Curr_Components Curr_Output;
  s32 qId_tmp_1, qId_tmp_2;
  s32 qIq_tmp_1, qIq_tmp_2;     
  s16 qId_1, qId_2;  
  s16 qIq_1, qIq_2;  
  
  Vector_Components = Trig_Functions(Theta);                      //计算电角度的cos和sin
  
  qIq_tmp_1 = Curr_Input.qI_Component1 * Vector_Components.hCos;  //计算Ialpha*cosθ	
  qIq_tmp_1 /= 32768;
  qIq_tmp_2 = Curr_Input.qI_Component2 *Vector_Components.hSin;   //计算Ibeta*sinθ
  qIq_tmp_2 /= 32768;
 
  qIq_1 = ((s16)(qIq_tmp_1));
  qIq_2 = ((s16)(qIq_tmp_2));
  Curr_Output.qI_Component1 = ((qIq_1)-(qIq_2));	//Iq=Ialpha*cosθ- Ibeta*sinθ
  
  qId_tmp_1 = Curr_Input.qI_Component1 * Vector_Components.hSin;  //计算Ialpha*sinθ
  qId_tmp_1 /= 32768;
  qId_tmp_2 = Curr_Input.qI_Component2 * Vector_Components.hCos;  //计算Ibeta*cosθ
  qId_tmp_2 /= 32768;
  
  qId_1 = (s16)(qId_tmp_1);		 
  qId_2 = (s16)(qId_tmp_2);					
  Curr_Output.qI_Component2 = ((qId_1)+(qId_2));   //Id=Ialpha*sinθ+ Ibeta*cosθ
  
  return (Curr_Output);
}
 
/**********************************************************************************************************
反park变换,输入Uq、Ud得到Ualpha、Ubeta
**********************************************************************************************************/ 
Volt_Components Rev_Park(Volt_Components Volt_Input)
{ 	
  s32 qValpha_tmp1,qValpha_tmp2,qVbeta_tmp1,qVbeta_tmp2;
  s16 qValpha_1,qValpha_2,qVbeta_1,qVbeta_2;
  Volt_Components Volt_Output;
   
  qValpha_tmp1 = Volt_Input.qV_Component1 * Vector_Components.hCos;  //Uq*cosθ
  qValpha_tmp1 /= 32768; 
  qValpha_tmp2 = Volt_Input.qV_Component2 * Vector_Components.hSin;  //Ud*sinθ
  qValpha_tmp2 /= 32768;
		
  qValpha_1 = (s16)(qValpha_tmp1);		
  qValpha_2 = (s16)(qValpha_tmp2);			
  Volt_Output.qV_Component1 = ((qValpha_1)+(qValpha_2));             //Ualpha=Uq*cosθ+ Ud*sinθ
  
  qVbeta_tmp1 = Volt_Input.qV_Component1 * Vector_Components.hSin;   //Uq*sinθ
  qVbeta_tmp1 /= 32768;  
  qVbeta_tmp2 = Volt_Input.qV_Component2 * Vector_Components.hCos;   //Ud*cosθ
  qVbeta_tmp2 /= 32768;
 
  qVbeta_1 = (s16)(qVbeta_tmp1);				
  qVbeta_2 = (s16)(qVbeta_tmp2);  				
  Volt_Output.qV_Component2 = -(qVbeta_1)+(qVbeta_2);                //Ubeta=Ud*cosθ- Uq*sinθ
 
  return(Volt_Output);
}

加入上面代码后,电机应该就能转动了,如果不转动,适当改变cnt的值,或者加入几毫秒的延迟,因为此刻我们并未将FOC放在ADC中断中,Stat_Volt_q_d.qV_Component2 即Id不要设置的太大,尽量保持在一个安全等级范围内,所以这样来看,使电机转起来只需要PWM模块与反Park变换和SVPWM模块,基本外设我们此时只用到了PWM,其实还是挺简单的哈。但是此刻是开环运行,我们无法得知电机真实的运行状态,所以需要引入电流闭环。

3、测量电角度!(编码器)

本次使用的是ABZ1250线的编码器,通过配置定时器的编码器模式,并设置为4倍频,可以准确的测量出当前电角度,具体配置见下图。注意选择好自己对应的编码器引脚,打开定时器中断,设置优先级为2 。
在这里插入图片描述
然后在初始化中,开启编码器模式,通过串口打印出电角度(可参考:串口使用printf),用手转动电机轴,观察信息是否正确,一圈范围为:-32768—+32768。或者借助步骤2,让电机转起来,然后查看电角度波形,如下图所示。有了电角度后,我们就可以让电机飞了!
在这里插入图片描述

4、测量电流吧!(三电阻采样)

按图示配置好ADC外设(只用了ADC1),并开启ADC中断,等级设置为1.生成代码。
在这里插入图片描述
可以使用步骤1测试ADC的正确性,每次导通一相,读取一次ADC值,增大占空比,看看AD值是否增加。然后,首先需要对初始ADC进行校准,也就是在关闭各个桥臂的情况下,读出3个注入通道的ADC值,作为初始电流偏置值,或者成为零电流值。具体可参考FOC和SVPWM的C语言代码实现。本文处理比较粗糙,直接多次读取后,进行赋值,不建议这种做法。然后通过下面的代码读出3相电流值。然后仍然可以使用开环SVPWM让电机转起来,然后看电流波形是否为正弦波,或者接近正弦波。
要计算出实际电流值:

实际电流值 = (ADC值>>4)/4096*(3.3-1.65)/Amp/R ;
Amp为放大倍数,R为采样电阻值

//3电阻采样电流值
Curr_Components SVPWM_3ShuntGetPhaseCurrentValues(void)
{
  Curr_Components Local_Stator_Currents;
  s32 wAux;

  switch (bSector)
   {
   case 4:
   case 5: //Current on Phase C not accessible     
             
            wAux = (s32)(hPhaseA_OffSet)- (HAL_ADCEx_InjectedGetValue(&hadc1,ADC_INJECTED_RANK_1)<<1);                
            if (wAux < S16_MIN)
            {
              Local_Stator_Currents.qI_Component1= S16_MIN;
            }  
            else  if (wAux > S16_MAX)
                  { 
                    Local_Stator_Currents.qI_Component1= S16_MAX;
                  }
                  else
                  {
                    Local_Stator_Currents.qI_Component1= wAux;
                  }
                     
           // Ib = (hPhaseBOffset)-(ADC Channel 12 value)
            wAux = (s32)(hPhaseB_OffSet)-(HAL_ADCEx_InjectedGetValue(&hadc1,ADC_INJECTED_RANK_2)<<1);
           // Saturation of Ib
            if (wAux < S16_MIN)
            {
              Local_Stator_Currents.qI_Component2= S16_MIN;
            }  
            else  if (wAux > S16_MAX)
                  { 
                    Local_Stator_Currents.qI_Component2= S16_MAX;
                  }
                  else
                  {
                    Local_Stator_Currents.qI_Component2= wAux;
                  }
           break;
           
   case 6:
   case 1:  //Current on Phase A not accessible     
            // Ib = (hPhaseBOffset)-(ADC Channel 12 value)
            wAux = (s32)(hPhaseB_OffSet)-(HAL_ADCEx_InjectedGetValue(&hadc1,ADC_INJECTED_RANK_2)<<1);
            //Saturation of Ib 
            if (wAux < S16_MIN)
            {
              Local_Stator_Currents.qI_Component2= S16_MIN;
            }  
            else  if (wAux > S16_MAX)
                  { 
                    Local_Stator_Currents.qI_Component2= S16_MAX;
                  }
                  else
                  {
                    Local_Stator_Currents.qI_Component2= wAux;
                  }
            // Ia = -Ic -Ib 
            wAux = (HAL_ADCEx_InjectedGetValue(&hadc1,ADC_INJECTED_RANK_3)<<1)-hPhaseC_OffSet-
                                            Local_Stator_Currents.qI_Component2;
            //Saturation of Ia
            if (wAux> S16_MAX)
            {
               Local_Stator_Currents.qI_Component1 = S16_MAX;
            }
            else  if (wAux <S16_MIN)
                  {
                   Local_Stator_Currents.qI_Component1 = S16_MIN;
                  }
                  else
                  {  
                    Local_Stator_Currents.qI_Component1 = wAux;
                  }
           break;
           
   case 2:
   case 3:  // Current on Phase B not accessible
            // Ia = (hPhaseAOffset)-(ADC Channel 11 value)     
            wAux = (s32)(hPhaseA_OffSet)-(HAL_ADCEx_InjectedGetValue(&hadc1,ADC_INJECTED_RANK_1)<<1);
            //Saturation of Ia 
            if (wAux < S16_MIN)
            {
              Local_Stator_Currents.qI_Component1= S16_MIN;
            }  
            else  if (wAux > S16_MAX)
                  { 
                    Local_Stator_Currents.qI_Component1= S16_MAX;
                  }
                  else
                  {
                    Local_Stator_Currents.qI_Component1= wAux;
                  }
     
            // Ib = -Ic-Ia;
            wAux = (HAL_ADCEx_InjectedGetValue(&hadc1,ADC_INJECTED_RANK_3)<<1) - hPhaseC_OffSet - 
                                            Local_Stator_Currents.qI_Component1;
            // Saturation of Ib
            if (wAux> S16_MAX)
            {
              Local_Stator_Currents.qI_Component2=S16_MAX;
            }
            else  if (wAux <S16_MIN)
                  {  
                    Local_Stator_Currents.qI_Component2 = S16_MIN;
                  }
                  else  
                  {
                    Local_Stator_Currents.qI_Component2 = wAux;
                  }                     
           break;

   default:
           break;
   } 
  
  return(Local_Stator_Currents); 
}

5、让电机飞!(电流闭环)

下面就是PID相关代码,包括初始化函数和PID函数,初始化函数加入到电机初始化函数中,然后将FOC函数中的开环调试部分注释,其它的打开(电压限幅函数在下面),将电角度设置为0,将q轴参考值,PID参数全部设置为0,d轴参考值设置为1000(具体由板子与电机决定,一定要在安全范围内),然后开始调节PI参数(由小忘大调),可以通过串口曲线绘制,观察PI效果,调好后,将q轴PI参数设置为相同,然后加入启动函数(主要是电角度对齐),将电角度设置成读取电角度函数,设置好Iq参考值,电机会一直开始转,如果不加限幅,会加速到最大。
工程代码见文末

volatile s16 hTorque_Reference;   //q轴设定值
volatile s16 hFlux_Reference;     //d轴设定值
volatile s16 hSpeed_Reference;    //速度环设定值
/****************************** 扭矩的PID参数,即q轴 *******************************************************/
#define PID_TORQUE_REFERENCE   (s16)000  //q轴的设定值,PID的目的就是要让测量的q轴值与设定值误差为0   
#define PID_TORQUE_KP_DEFAULT  (s16)2.35  //Kp默认值
#define PID_TORQUE_KI_DEFAULT  (s16)880   //Ki默认值
#define PID_TORQUE_KD_DEFAULT  (s16)0   //Kd默认值
 
/****************************** 转子磁通的PID参数,即d轴 *******************************************************/
#define PID_FLUX_REFERENCE   (s16)000       //d轴的设定值
#define PID_FLUX_KP_DEFAULT  (s16)2.35  
#define PID_FLUX_KI_DEFAULT  (s16)880  
#define PID_FLUX_KD_DEFAULT  (s16)0
 
/****************************** q轴和d轴PID参数的放大倍数 *******************************************************/
#define TF_KPDIV ((u16)(1024))    //因为Kp、Ki、Kd值很小,而我们需要整数计算,所以需要放大。得出计算结果之后,再缩小。1024
#define TF_KIDIV ((u16)(16384))//16384
#define TF_KDDIV ((u16)(8192))
 
/****************************** 速度环的PID参数 *******************************************************/
#define PID_SPEED_REFERENCE_RPM   (s16)1000         //电机的设定转速
#define PID_SPEED_REFERENCE       (u16)(PID_SPEED_REFERENCE_RPM/6)  //电机转速和速度环的设定值一般都不相等,电机不同,它们的关系也不同
#define PID_SPEED_KP_DEFAULT      (s16)50
#define PID_SPEED_KI_DEFAULT      (s16)10
#define PID_SPEED_KD_DEFAULT      (s16)0
#define NOMINAL_CURRENT           (s16)18000            //motor nominal current (0-pk),3倍的额定电流
#define IQMAX                     NOMINAL_CURRENT	   //速度环输出最大值
 
/****************************** 速度环PID参数的放大倍数 *******************************************************/
#define SP_KPDIV ((u16)(16))
#define SP_KIDIV ((u16)(256))
#define SP_KDDIV ((u16)(16))

void PID_Init (PID_Struct_t *PID_Torque, PID_Struct_t *PID_Flux, PID_Struct_t *PID_Speed)
{
    hTorque_Reference = PID_TORQUE_REFERENCE;          //q轴设定值初始化
/******************************************* 下面是控制扭矩的PID参数,即q轴大小 **************************************************************/
    PID_Torque->hKp_Gain    = PID_TORQUE_KP_DEFAULT;   //Kp参数,放大了hKp_Divisor倍。调节结果除以hKp_Divisor才是真实结果
    PID_Torque->hKp_Divisor = TF_KPDIV;                //Kp参数分数因子
    PID_Torque->hKi_Gain = PID_TORQUE_KI_DEFAULT;      //Ki参数
    PID_Torque->hKi_Divisor = TF_KIDIV;                //Ki参数分数因子
    PID_Torque->hKd_Gain = PID_TORQUE_KD_DEFAULT;      //Kd参数
    PID_Torque->hKd_Divisor = TF_KDDIV;                //Kd参数分数因子
    PID_Torque->wPreviousError = 0;                    //上次计算的误差值,用于D调节
    PID_Torque->hLower_Limit_Output=S16_MIN;           //PID输出下限幅
    PID_Torque->hUpper_Limit_Output= S16_MAX;          //PID输出上限幅
    PID_Torque->wLower_Limit_Integral = S16_MIN * TF_KIDIV;  //I调节的下限福
    PID_Torque->wUpper_Limit_Integral = S16_MAX * TF_KIDIV;  //I调节的上限幅
    PID_Torque->wIntegral = 0;                         //I调节的结果,因为是积分,所以要一直累积
/******************************************* 上面是控制扭矩的PID参数,即q轴大小 **************************************************************/
 
    hFlux_Reference = PID_FLUX_REFERENCE;              //对于SM-PMSM电机,Id = 0
/******************************************* 下面是控制转子磁通的PID参数,即d轴大小 **************************************************************/
    PID_Flux->hKp_Gain    = PID_FLUX_KP_DEFAULT;
    PID_Flux->hKp_Divisor = TF_KPDIV;
    PID_Flux->hKi_Gain = PID_FLUX_KI_DEFAULT;
    PID_Flux->hKi_Divisor = TF_KIDIV;
    PID_Flux->hKd_Gain = PID_FLUX_KD_DEFAULT;
    PID_Flux->hKd_Divisor = TF_KDDIV;
    PID_Flux->wPreviousError = 0;
    PID_Flux->hLower_Limit_Output=S16_MIN;   
    PID_Flux->hUpper_Limit_Output= S16_MAX;   
    PID_Flux->wLower_Limit_Integral = S16_MIN * TF_KIDIV;
    PID_Flux->wUpper_Limit_Integral = S16_MAX * TF_KIDIV;
    PID_Flux->wIntegral = 0;
/******************************************* 上面是控制转子磁通的PID参数,即d轴大小 **************************************************************/
 
    hSpeed_Reference = PID_SPEED_REFERENCE;
/******************************************* 下面是速度环的PID参数 **************************************************************/
    PID_Speed->hKp_Gain    = PID_SPEED_KP_DEFAULT;
    PID_Speed->hKp_Divisor = SP_KPDIV;
    PID_Speed->hKi_Gain = PID_SPEED_KI_DEFAULT;
    PID_Speed->hKi_Divisor = SP_KIDIV;
    PID_Speed->hKd_Gain = PID_SPEED_KD_DEFAULT;
    PID_Speed->hKd_Divisor = SP_KDDIV;
    PID_Speed->wPreviousError = 0;
    PID_Speed->hLower_Limit_Output= -IQMAX;   
    PID_Speed->hUpper_Limit_Output= IQMAX;   
    PID_Speed->wLower_Limit_Integral = -IQMAX * SP_KIDIV;
    PID_Speed->wUpper_Limit_Integral = IQMAX * SP_KIDIV;
    PID_Speed->wIntegral = 0;
/******************************************* 上面是速度环的PID参数 **************************************************************/
}
 
//#define DIFFERENTIAL_TERM_ENABLED    //不使用PID的D调节
typedef signed long long s64;
s16 PID_Regulator(s16 hReference, s16 hPresentFeedback, PID_Struct_t *PID_Struct)
{
    s32 wError, wProportional_Term,wIntegral_Term, houtput_32;
    s64 dwAux;
#ifdef DIFFERENTIAL_TERM_ENABLED                         //如果使能了D调节
	s32 wDifferential_Term;
#endif    
	
    wError= (s32)(hReference - hPresentFeedback);		 //设定值-反馈值,取得需要误差量delta_e
    wProportional_Term = PID_Struct->hKp_Gain * wError;	 //PID的P调节,即比例放大调节:wP = Kp * delta_e
 
    if (PID_Struct->hKi_Gain == 0)                       //下面进行PID的I调节,即误差的累积调节
    {
        PID_Struct->wIntegral = 0;                       //如果I参数=0,I调节就=0 
    }
    else
    {
        wIntegral_Term = PID_Struct->hKi_Gain * wError;		    //wI = Ki * delta_e	,本次积分项
        dwAux = PID_Struct->wIntegral + (s64)(wIntegral_Term);	//积分累积的调节量 = 以前的积分累积量 + 本次的积分项
 
        if (dwAux > PID_Struct->wUpper_Limit_Integral)		    //对PID的I调节做限幅
        {
            PID_Struct->wIntegral = PID_Struct->wUpper_Limit_Integral;	//上限
        }
        else if (dwAux < PID_Struct->wLower_Limit_Integral)				//下限
        {
            PID_Struct->wIntegral = PID_Struct->wLower_Limit_Integral;
        }
        else
        {
            PID_Struct->wIntegral = (s32)(dwAux);		          //不超限, 更新积分累积项为dwAux
        }
    }
#ifdef DIFFERENTIAL_TERM_ENABLED						          //如果使能了D调节
	{
	s32 wtemp;
  
	wtemp = wError - PID_Struct->wPreviousError;			      //取得上次和这次的误差之差
	wDifferential_Term = PID_Struct->hKd_Gain * wtemp;	          //D调节结果,wD = Kd * delta_d
	PID_Struct->wPreviousError = wError;    				      //更新上次误差,用于下次运算	
 
	}
	houtput_32 = (wProportional_Term/PID_Struct->hKp_Divisor+     //输出总的调节量 = 比例调节量/分数因子 +
                  PID_Struct->wIntegral/PID_Struct->hKi_Divisor + //				 + 积分调节量/分数因子
                  wDifferential_Term/PID_Struct->hKd_Divisor); 	  //				 + 微分调节量/分数因子
 
#else  	
	//把P调节和I调节结果除以分数因子再相加,得到PI控制的结果
    houtput_32 = (wProportional_Term/PID_Struct->hKp_Divisor + PID_Struct->wIntegral/PID_Struct->hKi_Divisor);       
#endif
    if (houtput_32 >= PID_Struct->hUpper_Limit_Output)	   //PI控制结果限幅
    {
        return(PID_Struct->hUpper_Limit_Output);
    }
    else if (houtput_32 < PID_Struct->hLower_Limit_Output) //下限
    {
        return(PID_Struct->hLower_Limit_Output);
    }
    else
    {
        return((s16)(houtput_32)); 						   //不超限。输出结果 houtput_32
    }
}

速度环跟踪曲线图
阶跃信号
在这里插入图片描述
正弦信号
在这里插入图片描述
工程链接:PMSM电机FOC简易驱动程序

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【STM32-HAL库】一步步搭建出FOC矢量控制(附C代码) 的相关文章

  • 电路设计——教你如何阅读数据手册

    我们为什么要看数据手册 xff0c 数据手册又有什么作用呢 xff1f 我们能够从中得到哪些东西呢 xff1f 哪些是我们所需要的呢 xff1f 下面我们以AD847芯片为例来说一说我们在工作中以及设计中需要注意哪些方面 下面是芯片的数据手
  • ORB-SLAM3笔记(编译、踩坑、论文、看代码)

    目前基于orb slam想做的方向 提升动态建图精度 xff08 东西Map就是上不去 KITTI有几个groundtruth官网下架了找不到而且 红外相机退化环境下的点线融合 数据集https sites google com view
  • 【树莓派】Ubuntu-mate安装及ROS安装

    树莓派使用之Ubuntu mate 烧录镜像至SD卡下载镜像烧录SD卡 将SD插入树莓派实物GIF安装流程 树莓派开机sudo reboot换源下载SSH首先得下载net tools下载openssh 电脑远程操作下载 Xshell设置远程
  • 【SLAM】ORB_SLAM3 初步调试运行详细记录

    前言 相关解析及参考 xff1a 超详细解读ORB SLAM3单目初始化 xff08 下篇 xff09 ORB SLAM3和之前版本有什么不同 xff1f 小白学视觉的技术博客 51CTO博客 orbslam3 官方源码地址 xff1a h
  • 如何实现一个简单的Ubuntu远程虚拟桌面

    文章目录 前言一 什么是noVNC xff1f 二 如何部署1 安装VNC服务端1 1 安装tigervnc standalone server1 2 安装tigervnc standalone server1 3 安装xserver xo
  • 软件开发经验总结 读源代码的艺术

    读取源代码是每一个开发人员成长的必经之路 xff0c 一份优秀的源代码 xff0c 是作者多年开发技术的心血结晶 xff0c 研究一份优秀的源代码 xff0c 总是能够让你的技术得到一定程度的提升 然后 xff0c 读别人的源代码并不是拿着
  • vsCode用户设置vue.js、保存格式化代码

    34 window zoomLevel 34 0 34 workbench iconTheme 34 34 vscode icons 34 34 editor wordWrap 34 34 on 34 vscode默认启用了根据文件类型自动
  • PX4姿态控制算法分析

    PX4姿态控制流程图 图片来源 Px4的姿态控制分为角度环 外环 和角速度环 内环 xff0c 角度环使用P控制 xff0c 角速度环使用PID控制 xff0c 由于偏航通道响应较慢 多旋翼飞行器的俯仰和滚转运动由旋翼的升力力矩产生 xff
  • 滤波器的设计(一)

    滤波器的设计 引言 对实际的控制系统而言 xff0c 采集到的原始信号往往是有噪声的 xff0c 而噪声往往会对系统的稳定性能产生隐患 xff1b 或为了提取有用的控制信号 xff0c 滤除不必要的频域成分 xff0c 数字滤波技术必不可少
  • 算法

    算法 xff08 Algorithm xff09 xff1a 计算机解题的基本思想方法和步骤 算法的描述 xff1a 是对要解决一个问题或要完成一项任务所采取的方法和步骤的描述 xff0c 包括需要什么数据 xff08 输入什么数据 输出什
  • 延迟环节对控制系统的影响

    本文内容来自知乎浅谈控制器的增益大小 xff08 下 xff09 区分惯性环节与延迟环节 惯性环节 1 T s 43
  • 控制系统中"带宽"的理解

    本文来自对知乎文章如何入门自动控制的一些理解 首先来看几个概念 xff1a 带宽频率也称为闭环截止频率 xff0c 是指当闭环幅频特性下降到频率为零时的分贝值以下3dB时 xff0c 对应的频率 xff0c 记作 w b w b w b 开
  • 多旋翼无人机建模-牛顿欧拉法

    多旋翼无人机建模 建模之前我们先分析一下多旋翼无人机有哪些状态量 xff1a 用于表示线运动的三轴位置 速度和加速度 xff1b 用于表示角运动的三轴姿态角和姿态角速度 xff1b 这一共是15个状态量 首先来看线运动方程的建立过程 xff
  • 基于最小二乘法的磁力计椭球拟合方法

    基于最小二乘法的磁力计椭球拟合方法 在写飞控代码时 xff0c 必然要对磁力计的测量数据进行校正 xff0c 本文将介绍一种简单实用的校正方法 基于最小二乘法的椭球拟合方法 本文椭球拟合部分来自博文IMU加速度 磁力计校正 xff0d xf
  • 多旋翼无人机飞控系统设计之详细设计方案

    在进行多旋翼无人机飞控系统设计之前 xff0c 有必要列写一份详细的设计方案书 xff1b 这是飞控系统设计的基石 xff0c 并且在一定程度上指导了后续的研发工作 本篇博文列写了之前笔者在进行无人机飞控开发过程中所撰写的精简版本的设计方案
  • 梅森公式确定系统的传递函数

    梅森增益公式 xff1a 对于一些比较复杂的系统 xff0c 采用结构图等效简化的方法求系统的传递函数是比较麻烦的 而使用梅森公式 xff0c 则可以不用做任何变换 xff0c 只要通过对信号流图进行相应的分析就能直接写出系统的传递函数 下
  • 多旋翼无人机控制之完整闭环控制设计

    本文主要讲解了多旋翼无人机整个闭环系统的设计流程 xff0c 对各个控制器的控制输入与输出 xff0c 控制器的设计要点进行了详细描述 控制逻辑 Q 要让多旋翼无人机按照预设的航线进行飞行 xff0c 需要设计哪些控制器呢 xff1f A
  • 频域分析之超前校正

    本文将主要介绍使用频域响应法进行控制器设计时一种常用且重要的方法 超前校正 超前校正能使系统的瞬态响应得到显著改善 xff0c 而对系统稳态精度的影响则很小 首先来看超前校正的一般形式 K c T
  • 飞控中的一些知识点总结

    本文主要总结了飞控研发过中一些比较重要的知识点 xff0c 部分为本人的实际经验 xff0c 部分为知乎转载 影响飞控性能的一些因素 xff1a 飞控姿态控制算法比较固定 xff0c 基本上都是角度环和角速度环组成的串级PID算法 xff0
  • STM32内存管理以及堆和栈的理解

    今天仔细读了一下内存管理的代码 xff0c 然后还有看了堆栈的相关知识 xff0c 把以前不太明白的一些东西想通了 xff0c 写下来 xff0c 方便以后查看 xff0c 也想大家看了能指出哪里不对 xff0c 然后修改 首先 xff0c

随机推荐

  • 卡尔曼滤波器原理简介

    引用知乎中的一段话 xff1a PID和卡尔曼滤波乃是控制工程师的两大法宝 几十年过去了卡尔曼滤波在理论研究上仍然保持着活跃 xff0c 研究方向包括各种非线性的 xff0c 噪声统计参数未知的自适应和鲁棒滤波 更重要的是现在计算机的发展将
  • 傅里叶变换

    此篇博文主要简述傅里叶变换的相关概念以及如何代码实现离散序列的傅里叶变换 傅里叶变换的一般形式 我们都知道 xff0c 傅里叶变换是频域分析的重要工具 xff1b 其将信号从时域转换到了频域 xff0c 以更直观的角度向我们展示了信号的本质
  • 开源AR库ArUco的原理与位姿估计实战

    近期在搭建SLAM AR的过程中 xff0c 需要用到Marker作为虚拟坐标系的参考 xff0c 使用了开源库ArUco实现了该功能 本文最早发布于keenster cn 参考文献 ArUco marker detection aruco
  • k8s基础认知

    一 什么是k8s xff1a k8s是一个使用go编写的谷歌内部容器管理系统的开源版 xff0c 他由CNCF基金会进行管理 版本叠代周期为三个月 二 k8s的基本概念 1 cluster xff1a 是计算 存储 网络的资源集合 k8s利
  • CodeWarrior 10.7 使用笔记

    1 Code Warrior 10 7 IDE 工程创建 利用工程向导快速创建工程 1 1 点击菜单File BareBoard Project 裸板工程 1 2 设置工程名称 xff08 默认将该工程创建在当前工作空间 xff08 wor
  • LADRC_C代码

    ifndef ADRC H define ADRC H include stm32f10x h typedef struct ADRC float v1 v2 最速输出值 float r 速度因子 float h 步长 float z1 z
  • SBUS 协议

    简介 全称是Serial Bus S BUS是一个串行通信协议 xff0c S BUS是FUTABA提出的舵机控制总线 xff0c S bus使用RS232C串口的硬件协议作为自己的硬件运行基础 使用TTL电平 xff0c 即3 3V 使用
  • 无人机飞控 ardupilot-4.0.7 版本源码总体框架

    无人机飞控 ardupilot 4 0 7 版本源码总体框架 无人机飞控 ardupilot 4 0 7 版本源码总体框架 基本结构 最外层文件 无人机飞控 ardupilot 4 0 7 版本源码总体框架 基本结构 最外层文件 文件名 内
  • C/C++函数注释格式

    目录 C C 43 43 函数注释格式函数注释格式1函数注释格式2函数注释格式3函数注释格式4函数注释格式5 C C 43 43 函数注释格式 函数注释格式1 span class token comment 61 61 61 61 61
  • 磁编码器MT6835_SPI读取位置信息

    文章目录 磁编码器MT6835 SPI读取位置信息简介应用特性和优势参考资料Cubemx 创建 STM32 工程读取编码器位置信息添加 SPI 读取位置信息代码编码器位置信息输出 磁编码器MT6835 SPI读取位置信息 简介 MT6835
  • 四旋翼无人机飞控系统设计(方案篇)

    简介 四旋翼无人机为多旋翼无人机中最经典的机型 xff0c 此设计为四旋翼无人机飞控系统设计 xff08 侧重软件 xff09 xff0c 这里主要涉及基于MCU的无人机飞控程序的编写 xff0c 使用的无人机机架 电机 电调 锂电池 航模
  • 运算放大器-虚短虚断

    运算放大器 虚短虚断 虚短虚断 虚短 虚短指在理想情况下 xff0c 两个输入端的电位相等 xff0c 就好像两个输入端短接在一起 xff0c 但事实上并没有短接 xff0c 称为 虚短 虚短的必要条件是运放引入深度负反馈 V 43 61
  • vscode 配置头文件路径

    1 打开 VSCode 界面 xff0c 在左下角找到齿轮 打开设置 xff0c 点击配置文件 2 在配置文件中添加相应的头文件路径
  • vscode 编码格式配置及保存(统一工程的编码格式,防止中文注释乱码)

    使用 VSCode 统一工程的编码格式 打开不同编码格式的文件 xff0c 点击右下角编码格式 xff0c 通过不同的编码来保存 xff0c 防止更改编码格式时导致中文注释乱码
  • CentOS7安装oracle12G调不出图形化界面

    问题 xff1a centos如果无图形化界面 xff0c 在安装oracle xff0c 调用图形化界面时会遇到问题 xff0c 报错为 xff1a 无法使用命令 usr bin xdpyinfo 自动检查显示器颜色 请检查是否设置了 D
  • STM32外围电路硬件解析

    复位电路 xff1a 复位 xff1a 让MCU回到最开始的状态 并且从头开始 xff0c 重新执行程序 我们什么时候需要复位 xff1f 1 烧录的时候 2 程序跑飞了的时候 3 上电复位 xff08 上电的时候需要复位 xff09 复位
  • stm32f103——中断——UART中断服务函数

    在程序中 xff0c CPU对外界突发事件进行处理的方式又两种 xff1a 1 轮询系统 xff1a xff08 在main中 xff0c 使用while循环 xff0c 进行循环判断外界事物是否发生 xff09 while xff08 1
  • Docker快速入门,看这个就够了

    Docker快速入门 一 Docker介绍1 1 Docker背景1 2 Docker概念1 3 Docker的优势1 4 Docker的三个基本概念 二 Docker的安装和使用2 1 安装依赖包2 2 安装Docker 三 启动Dock
  • Kubernetes(K8S)集群部署搭建图文教程(最全)

    Kubernetes 集群安装 前期准备集群安装系统初始化Harbor采取私有的仓库去镜像使用集群检测集群功能演示 前期准备 第一步 xff1a Router软路由构建 第二步 xff1a centos7安装 5台 xff08 自行安装 x
  • 【STM32-HAL库】一步步搭建出FOC矢量控制(附C代码)

    说明 本文为无刷电机或PMSM电机驱动的简易代码 xff0c 旨在分享一些个人调试过程的小心得 xff0c 提供一个demo文件 xff0c 程序仍有许多不完善的地方 xff0c 建立起个人的FOC底层驱动 xff0c 可以帮助快速熟悉FO